add-authkeyfile-option.patch (12100B)
1 From 9c0ac9e41a393e0f16a57e36d9369d61d39e9aa5 Mon Sep 17 00:00:00 2001 2 From: Daniel Barlow <dan@telent.net> 3 Date: Fri, 23 Aug 2024 11:33:24 +0100 4 Subject: [PATCH] add -U otion to set path to authorized_keys file 5 6 based on https://github.com/mkj/dropbear/pull/35 7 by Salvador Fandino sfandino@yahoo.com 8 9 - Allow authorized keys inside dirs with the sticky bit set 10 11 - Add option -U for customizing authorized_keys path 12 13 - Updated for dropbear 2024.85 (source files moved to src/) 14 15 - allow %u, %d, %n "format specifiers" in pathname so that the user's 16 username/homedir/uid can be embedded into the path 17 --- 18 Makefile.in | 2 +- 19 manpages/dropbear.8 | 3 + 20 src/pathexpand.c | 149 +++++++++++++++++++++++++++++++++++++++++++ 21 src/runopts.h | 3 +- 22 src/svr-authpubkey.c | 86 +++++++++++-------------- 23 src/svr-runopts.c | 10 +++ 24 6 files changed, 203 insertions(+), 50 deletions(-) 25 create mode 100644 src/pathexpand.c 26 27 diff --git a/Makefile.in b/Makefile.in 28 index 5ebfca2..686fbfb 100644 29 --- a/Makefile.in 30 +++ b/Makefile.in 31 @@ -51,7 +51,7 @@ COMMONOBJS = $(patsubst %,$(OBJ_DIR)/%,$(_COMMONOBJS)) 32 _SVROBJS=svr-kex.o svr-auth.o sshpty.o \ 33 svr-authpasswd.o svr-authpubkey.o svr-authpubkeyoptions.o svr-session.o svr-service.o \ 34 svr-chansession.o svr-runopts.o svr-agentfwd.o svr-main.o svr-x11fwd.o\ 35 - svr-tcpfwd.o svr-authpam.o 36 + svr-tcpfwd.o svr-authpam.o pathexpand.o 37 SVROBJS = $(patsubst %,$(OBJ_DIR)/%,$(_SVROBJS)) 38 39 _CLIOBJS=cli-main.o cli-auth.o cli-authpasswd.o cli-kex.o \ 40 diff --git a/manpages/dropbear.8 b/manpages/dropbear.8 41 index bdb2ea0..c8d450d 100644 42 --- a/manpages/dropbear.8 43 +++ b/manpages/dropbear.8 44 @@ -29,6 +29,9 @@ or automatically with the '-R' option. See "Host Key Files" below. 45 .B \-R 46 Generate hostkeys automatically. See "Host Key Files" below. 47 .TP 48 +.B \-U \fIauthorized_keys 49 +Absolute pathname to file containing authorized user keys. May contain the sequences %d, %n, %u which are expanded to the user's home directory, username and numeric uid respectively. Default '%d/.ssh/authorized_keys'. 50 +.TP 51 .B \-F 52 Don't fork into background. 53 .TP 54 diff --git a/src/pathexpand.c b/src/pathexpand.c 55 new file mode 100644 56 index 0000000..07e6955 57 --- /dev/null 58 +++ b/src/pathexpand.c 59 @@ -0,0 +1,149 @@ 60 +#include <limits.h> 61 +#include <string.h> 62 +#include <stdio.h> 63 + 64 +#ifdef TEST_PATHEXPAND 65 +#include <stdlib.h> 66 + 67 +/* to run tests: 68 + gcc -Wall -o pathexpand -D TEST_PATHEXPAND=1 src/pathexpand.c && ./pathexpand 69 +*/ 70 + 71 +char * pathexpand(char *relfilename); 72 + 73 + 74 +#define m_malloc(c) malloc(c) 75 +#define m_strdup(c) strdup(c) 76 +#define TRACE(c) dropbear_trace1 c 77 +#include <stdarg.h> 78 + 79 +inline static void dropbear_trace1(const char* format, ...) { 80 + va_list param; 81 + 82 + va_start(param, format); 83 + /* vfprintf(stderr, format, param); */ 84 + /* fprintf(stderr, "\n"); */ 85 +} 86 + 87 + 88 +struct session { 89 + struct AuthState { 90 + char * pw_dir; 91 + char * pw_name; 92 + uid_t pw_uid; 93 + } authstate; 94 +}; 95 + 96 +struct session ses = { 97 + .authstate = { 98 + .pw_dir = "/home/dan", 99 + .pw_name = "dan", 100 + .pw_uid = 12345, 101 + } 102 +}; 103 + 104 +int exit_status = 0; 105 + 106 +int expect_expansion(char * input, char * expected) { 107 + char *actual = pathexpand(input); 108 + if(strcmp(actual, expected) != 0) { 109 + printf("expected %s for %s, got %s\n", expected, input, actual); 110 + exit_status++; 111 + } 112 + free(actual); 113 + return exit_status; 114 +} 115 + 116 +int main(int argc, char *argv[]) { 117 + for(int i = 1; i < argc; i++) { 118 + char *actual = pathexpand(argv[i]); 119 + printf("%s => %s\n", argv[i], pathexpand(argv[i])); 120 + free(actual); 121 + } 122 + 123 + /* a string without % is unaltered */ 124 + expect_expansion("hello", "hello"); 125 + 126 + /* discards single trailing % */ 127 + expect_expansion("hello%", "hello"); 128 + 129 + /* %% is transformed to % */ 130 + expect_expansion("hello%%", "hello%"); 131 + expect_expansion("hello%%goodbye", "hello%goodbye"); 132 + 133 + /* %u is transformed to uid */ 134 + expect_expansion("/run/user/%u/authorized_keys", "/run/user/12345/authorized_keys"); 135 + /* % sequences work when at start of string */ 136 + expect_expansion("%u/authorized_keys", "12345/authorized_keys"); 137 + 138 + /* %d expands to home directory */ 139 + expect_expansion("%d/.ssh", "/home/dan/.ssh"); 140 + 141 + /* %n expands to username */ 142 + expect_expansion("/tmp/%n/.ssh", "/tmp/dan/.ssh"); 143 + 144 + /* unrecognised specifiers are discarded */ 145 + expect_expansion("/hi/%q/.ssh", "/hi//.ssh"); 146 + 147 + 148 + expect_expansion("%d/.ssh/authorized_keys", "/home/dan/.ssh/authorized_keys"); 149 + exit(exit_status); 150 +} 151 + 152 +#else 153 +#include "session.h" 154 +#include "debug.h" 155 +#endif 156 +#define NUMLEN(c) strlen(#c) 157 + 158 +char * pathexpand(char *relfilename) 159 +{ 160 + char * filename; 161 + int len; 162 + 163 + len = strlen(relfilename); 164 + for(char *p = relfilename; p; p = strchr(p, '%')) { 165 + switch(*(p+1)) { 166 + case 'd': len += strlen(ses.authstate.pw_dir); break; 167 + case 'n': len += strlen(ses.authstate.pw_name); break; 168 + case 'u': len += NUMLEN(INT_MAX); break; 169 + } 170 + if(*(p+1) == '\0') break; 171 + p=p+2; 172 + } 173 + filename = m_malloc(len+1); 174 + filename[0] = '\0'; 175 + 176 + char *start = relfilename; 177 + char *out = filename; 178 + char *p = relfilename; 179 + do { 180 + p = strchr(start, '%'); 181 + 182 + if(!p) { 183 + strcat(out, start); 184 + break; 185 + } 186 + strncat(out, start, p - start); 187 + 188 + switch(*(p+1)) { 189 + case '\0': 190 + p++; break; 191 + case 'd': 192 + strcat(out, ses.authstate.pw_dir); break; 193 + case 'n': 194 + strcat(out, ses.authstate.pw_name); break; 195 + case 'u': 196 + snprintf(out + strlen(out), 197 + NUMLEN(INT_MAX), 198 + "%d", 199 + ses.authstate.pw_uid); 200 + break; 201 + case '%': 202 + strcat(out, "%"); break; 203 + } 204 + start = p+2; 205 + } 206 + while (*p); 207 + return filename; /* caller must free */ 208 +} 209 diff --git a/src/runopts.h b/src/runopts.h 210 index 1c88b5c..707008f 100644 211 --- a/src/runopts.h 212 +++ b/src/runopts.h 213 @@ -128,7 +128,8 @@ typedef struct svr_runopts { 214 char * pidfile; 215 216 char * forced_command; 217 - char* interface; 218 + char * authkeysfile; 219 + char * interface; 220 221 #if DROPBEAR_PLUGIN 222 /* malloced */ 223 diff --git a/src/svr-authpubkey.c b/src/svr-authpubkey.c 224 index 5d298cb..54502f4 100644 225 --- a/src/svr-authpubkey.c 226 +++ b/src/svr-authpubkey.c 227 @@ -73,7 +73,7 @@ 228 229 static int checkpubkey(const char* keyalgo, unsigned int keyalgolen, 230 const unsigned char* keyblob, unsigned int keybloblen); 231 -static int checkpubkeyperms(void); 232 +static int checkpubkeyperms(char *filename, char *base); 233 static void send_msg_userauth_pk_ok(const char* sigalgo, unsigned int sigalgolen, 234 const unsigned char* keyblob, unsigned int keybloblen); 235 static int checkfileperm(char * filename); 236 @@ -431,6 +431,7 @@ out: 237 return ret; 238 } 239 240 +extern char *pathexpand(char *input); 241 242 /* Checks whether a specified publickey (and associated algorithm) is an 243 * acceptable key for authentication */ 244 @@ -458,19 +459,12 @@ static int checkpubkey(const char* keyalgo, unsigned int keyalgolen, 245 dropbear_exit("Failed to set euid"); 246 } 247 #endif 248 + filename = pathexpand(svr_opts.authkeysfile); 249 + 250 /* check file permissions, also whether file exists */ 251 - if (checkpubkeyperms() == DROPBEAR_FAILURE) { 252 - TRACE(("bad authorized_keys permissions, or file doesn't exist")) 253 + if (checkpubkeyperms(filename, ses.authstate.pw_dir) == DROPBEAR_FAILURE) { 254 + TRACE(("bad authorized keys permissions on %s, or file doesn't exist", filename)) 255 } else { 256 - /* we don't need to check pw and pw_dir for validity, since 257 - * its been done in checkpubkeyperms. */ 258 - len = strlen(ses.authstate.pw_dir); 259 - /* allocate max required pathname storage, 260 - * = path + "/.ssh/authorized_keys" + '\0' = pathlen + 22 */ 261 - filename = m_malloc(len + 22); 262 - snprintf(filename, len + 22, "%s/.ssh/authorized_keys", 263 - ses.authstate.pw_dir); 264 - 265 authfile = fopen(filename, "r"); 266 if (!authfile) { 267 TRACE(("checkpubkey: failed opening %s: %s", filename, strerror(errno))) 268 @@ -486,7 +480,7 @@ static int checkpubkey(const char* keyalgo, unsigned int keyalgolen, 269 if (authfile == NULL) { 270 goto out; 271 } 272 - TRACE(("checkpubkey: opened authorized_keys OK")) 273 + TRACE(("checkpubkey: opened %s OK", filename)) 274 275 line = buf_new(MAX_AUTHKEYS_LINE); 276 line_num = 0; 277 @@ -524,53 +518,47 @@ out: 278 279 /* Returns DROPBEAR_SUCCESS if file permissions for pubkeys are ok, 280 * DROPBEAR_FAILURE otherwise. 281 - * Checks that the user's homedir, ~/.ssh, and 282 - * ~/.ssh/authorized_keys are all owned by either root or the user, and are 283 + * Checks filename and its parent directories recursively until the 284 + * base directory (usually ~/) or one of its ancestors (up to /) is 285 + * reached. 286 + * The files and directories must be all owned by root or the user, and be 287 * g-w, o-w */ 288 -static int checkpubkeyperms() { 289 - 290 - char* filename = NULL; 291 +static int checkpubkeyperms(char *filename, char *base) { 292 + char* path = NULL; 293 int ret = DROPBEAR_FAILURE; 294 unsigned int len; 295 296 - TRACE(("enter checkpubkeyperms")) 297 - 298 - if (ses.authstate.pw_dir == NULL) { 299 - goto out; 300 - } 301 + TRACE(("enter checkpubkeyperms(%s, %s)", filename, base)) 302 303 - if ((len = strlen(ses.authstate.pw_dir)) == 0) { 304 + if ((base == NULL) || (base[0] != '/') || 305 + (filename == NULL) || (filename[0] != '/')) { 306 + /* both filename and base must be absolute paths */ 307 goto out; 308 } 309 310 - /* allocate max required pathname storage, 311 - * = path + "/.ssh/authorized_keys" + '\0' = pathlen + 22 */ 312 - len += 22; 313 - filename = m_malloc(len); 314 - strlcpy(filename, ses.authstate.pw_dir, len); 315 + len = strlen(filename); 316 + path = m_strdup(filename); 317 318 - /* check ~ */ 319 - if (checkfileperm(filename) != DROPBEAR_SUCCESS) { 320 - goto out; 321 - } 322 - 323 - /* check ~/.ssh */ 324 - strlcat(filename, "/.ssh", len); 325 - if (checkfileperm(filename) != DROPBEAR_SUCCESS) { 326 - goto out; 327 - } 328 + while (checkfileperm(len ? path : "/") == DROPBEAR_SUCCESS) { 329 + /* check if we are on base trail and if this is the 330 + * case, return success */ 331 + if ((strncmp(base, path, len) == 0) && 332 + (!len || (base[len] == '\0') || (base[len] == '/'))) { 333 + ret = DROPBEAR_SUCCESS; 334 + break; 335 + } 336 337 - /* now check ~/.ssh/authorized_keys */ 338 - strlcat(filename, "/authorized_keys", len); 339 - if (checkfileperm(filename) != DROPBEAR_SUCCESS) { 340 - goto out; 341 + /* look for parent directory */ 342 + while (--len) { 343 + if (path[len] == '/') { 344 + path[len] = '\0'; 345 + break; 346 + } 347 + } 348 } 349 350 - /* file looks ok, return success */ 351 - ret = DROPBEAR_SUCCESS; 352 - 353 out: 354 - m_free(filename); 355 + m_free(path); 356 357 TRACE(("leave checkpubkeyperms")) 358 return ret; 359 @@ -596,7 +584,9 @@ static int checkfileperm(char * filename) { 360 TRACE(("wrong ownership")) 361 } 362 /* check permissions - don't want group or others +w */ 363 - if (filestat.st_mode & (S_IWGRP | S_IWOTH)) { 364 + /* (unless sticky dir, which is allowed) */ 365 + if ((filestat.st_mode & (S_IWGRP | S_IWOTH)) && 366 + !(S_ISDIR(filestat.st_mode) && (filestat.st_mode & S_ISVTX))) { 367 badperm = 1; 368 TRACE(("wrong perms")) 369 } 370 diff --git a/src/svr-runopts.c b/src/svr-runopts.c 371 index c4f83c1..faddfa2 100644 372 --- a/src/svr-runopts.c 373 +++ b/src/svr-runopts.c 374 @@ -147,6 +147,7 @@ void svr_getopts(int argc, char ** argv) { 375 char* maxauthtries_arg = NULL; 376 char* reexec_fd_arg = NULL; 377 char* keyfile = NULL; 378 + char* authkeysfile = NULL; 379 char c; 380 #if DROPBEAR_PLUGIN 381 char* pubkey_plugin = NULL; 382 @@ -173,6 +174,8 @@ void svr_getopts(int argc, char ** argv) { 383 svr_opts.hostkey = NULL; 384 svr_opts.delay_hostkey = 0; 385 svr_opts.pidfile = expand_homedir_path(DROPBEAR_PIDFILE); 386 + svr_opts.authkeysfile = "%d/.ssh/authorized_keys"; 387 + 388 #if DROPBEAR_SVR_LOCALANYFWD 389 svr_opts.nolocaltcp = 0; 390 #endif 391 @@ -322,6 +325,9 @@ void svr_getopts(int argc, char ** argv) { 392 case 'u': 393 /* backwards compatibility with old urandom option */ 394 break; 395 + case 'U': 396 + next = &authkeysfile; 397 + break; 398 #if DROPBEAR_PLUGIN 399 case 'A': 400 next = &pubkey_plugin; 401 @@ -372,6 +378,10 @@ void svr_getopts(int argc, char ** argv) { 402 addhostkey(keyfile); 403 keyfile = NULL; 404 } 405 + if (authkeysfile) { 406 + svr_opts.authkeysfile = m_strdup(authkeysfile); 407 + authkeysfile = NULL; 408 + } 409 } 410 } 411 412 -- 413 2.47.0 414