liminix

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

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