commit e83547394530b8ebb1428a7c8f98eb7f9ef4fed1
parent 055268d5d2e92ff5d9fee37c086c82613ae83724
Author: Daniel Barlow <dan@telent.net>
Date: Fri, 23 Aug 2024 19:58:05 +0100
patch dropbear to add -U option
Diffstat:
2 files changed, 387 insertions(+), 0 deletions(-)
diff --git a/overlay.nix b/overlay.nix
@@ -134,6 +134,7 @@ extraPkgs // {
'';
in [
passPath
+ ./pkgs/dropbear/add-authkeyfile-option.patch
];
postPatch = ''
(echo '#define DSS_PRIV_FILENAME "/run/dropbear/dropbear_dss_host_key"'
diff --git a/pkgs/dropbear/add-authkeyfile-option.patch b/pkgs/dropbear/add-authkeyfile-option.patch
@@ -0,0 +1,386 @@
+commit bd51aae2e40814ac2ae5801fd9f83f6a4a886fb1
+Author: Daniel Barlow <dan@telent.net>
+Date: Fri Aug 23 11:33:24 2024 +0100
+
+ add -U otion to set path to authorized_keys file
+
+ based on https://github.com/mkj/dropbear/pull/35
+ by Salvador Fandino sfandino@yahoo.com
+
+ - Allow authorized keys inside dirs with the sticky bit set
+
+ - Add option -U for customizing authorized_keys path
+
+ - Updated for dropbear 2024.85 (source files moved to src/)
+
+ - allow %u, %d, %n "format specifiers" in pathname so that the user's
+ username/homedir/uid can be embedded into the path
+
+diff --git a/Makefile.in b/Makefile.in
+index 5ebfca2..686fbfb 100644
+--- a/Makefile.in
++++ b/Makefile.in
+@@ -51,7 +51,7 @@ COMMONOBJS = $(patsubst %,$(OBJ_DIR)/%,$(_COMMONOBJS))
+ _SVROBJS=svr-kex.o svr-auth.o sshpty.o \
+ svr-authpasswd.o svr-authpubkey.o svr-authpubkeyoptions.o svr-session.o svr-service.o \
+ svr-chansession.o svr-runopts.o svr-agentfwd.o svr-main.o svr-x11fwd.o\
+- svr-tcpfwd.o svr-authpam.o
++ svr-tcpfwd.o svr-authpam.o pathexpand.o
+ SVROBJS = $(patsubst %,$(OBJ_DIR)/%,$(_SVROBJS))
+
+ _CLIOBJS=cli-main.o cli-auth.o cli-authpasswd.o cli-kex.o \
+diff --git a/manpages/dropbear.8 b/manpages/dropbear.8
+index bdb2ea0..c8d450d 100644
+--- a/manpages/dropbear.8
++++ b/manpages/dropbear.8
+@@ -29,6 +29,9 @@ or automatically with the '-R' option. See "Host Key Files" below.
+ .B \-R
+ Generate hostkeys automatically. See "Host Key Files" below.
+ .TP
++.B \-U \fIauthorized_keys
++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'.
++.TP
+ .B \-F
+ Don't fork into background.
+ .TP
+diff --git a/src/pathexpand.c b/src/pathexpand.c
+new file mode 100644
+index 0000000..2028733
+--- /dev/null
++++ b/src/pathexpand.c
+@@ -0,0 +1,132 @@
++#include <limits.h>
++#include <string.h>
++#include <stdio.h>
++
++#ifdef TEST_PATHEXPAND
++
++/* to run tests:
++ gcc -Wall -o pathexpand -D TEST_PATHEXPAND=1 src/pathexpand.c && ./pathexpand
++*/
++
++char * pathexpand(char *relfilename);
++
++
++#define m_malloc(c) malloc(c)
++#define m_strdup(c) strdup(c)
++
++struct session {
++ struct AuthState {
++ char * pw_dir;
++ char * pw_name;
++ uid_t pw_uid;
++ } authstate;
++};
++
++struct session ses = {
++ .authstate = {
++ .pw_dir = "/home/dan",
++ .pw_name = "dan",
++ .pw_uid = 12345,
++ }
++};
++
++int exit_status = 0;
++
++int expect_expansion(char * input, char * expected) {
++ char *actual = pathexpand(input);
++ if(strcmp(actual, expected) != 0) {
++ printf("expected %s for %s, got %s\n", expected, input, actual);
++ exit_status++;
++ }
++ free(actual);
++ return exit_status;
++}
++
++int main(int argc, char *argv[]) {
++ for(int i = 1; i < argc; i++) {
++ char *actual = pathexpand(argv[i]);
++ printf("%s => %s\n", argv[i], pathexpand(argv[i]));
++ free(actual);
++ }
++
++ /* a string without % is unaltered */
++ expect_expansion("hello", "hello");
++
++ /* discards single trailing % */
++ expect_expansion("hello%", "hello");
++
++ /* %% is transformed to % */
++ expect_expansion("hello%%", "hello%");
++ expect_expansion("hello%%goodbye", "hello%goodbye");
++
++ /* %u is transformed to uid */
++ expect_expansion("/run/user/%u/authorized_keys", "/run/user/12345/authorized_keys");
++ /* % sequences work when at start of string */
++ expect_expansion("%u/authorized_keys", "12345/authorized_keys");
++
++ /* %d expands to home directory */
++ expect_expansion("%d/.ssh", "/home/dan/.ssh");
++
++ /* %n expands to username */
++ expect_expansion("/tmp/%n/.ssh", "/tmp/dan/.ssh");
++
++ /* unrecognised specifiers are discarded */
++ expect_expansion("/hi/%q/.ssh", "/hi//.ssh");
++
++ exit(exit_status);
++}
++
++#else
++#include "session.h"
++#include "debug.h"
++#endif
++#define NUMLEN(c) strlen(#c)
++
++char * pathexpand(char *relfilename)
++{
++ char * filename;
++ int len;
++
++ len = strlen(relfilename);
++ for(char *p = relfilename; p; p = strchr(p, '%')) {
++ switch(*(p+1)) {
++ case 'd': len += strlen(ses.authstate.pw_dir); break;
++ case 'n': len += strlen(ses.authstate.pw_name); break;
++ case 'u': len += NUMLEN(INT_MAX); break;
++ }
++ if(*(p+1) == '\0') break;
++ p=p+2;
++ }
++ filename = m_malloc(len+1);
++ filename[0] = '\0';
++
++ char *start = relfilename;
++ char *out = filename;
++ char *p = relfilename;
++ do {
++ p = strchrnul(start, '%');
++ strncat(out, start, p - start);
++
++ if(*p == '\0') break;
++
++ switch(*(p+1)) {
++ case '\0':
++ p++; break;
++ case 'd':
++ strcat(out, ses.authstate.pw_dir); break;
++ case 'n':
++ strcat(out, ses.authstate.pw_name); break;
++ case 'u':
++ snprintf(out + strlen(out),
++ NUMLEN(INT_MAX),
++ "%d",
++ ses.authstate.pw_uid);
++ break;
++ case '%':
++ strcat(out, "%"); break;
++ }
++ start = p+2;
++ }
++ while (*p);
++ return filename; /* caller must free */
++}
+diff --git a/src/runopts.h b/src/runopts.h
+index 1c88b5c..707008f 100644
+--- a/src/runopts.h
++++ b/src/runopts.h
+@@ -128,7 +128,8 @@ typedef struct svr_runopts {
+ char * pidfile;
+
+ char * forced_command;
+- char* interface;
++ char * authkeysfile;
++ char * interface;
+
+ #if DROPBEAR_PLUGIN
+ /* malloced */
+diff --git a/src/svr-authpubkey.c b/src/svr-authpubkey.c
+index 5d298cb..54502f4 100644
+--- a/src/svr-authpubkey.c
++++ b/src/svr-authpubkey.c
+@@ -73,7 +73,7 @@
+
+ static int checkpubkey(const char* keyalgo, unsigned int keyalgolen,
+ const unsigned char* keyblob, unsigned int keybloblen);
+-static int checkpubkeyperms(void);
++static int checkpubkeyperms(char *filename, char *base);
+ static void send_msg_userauth_pk_ok(const char* sigalgo, unsigned int sigalgolen,
+ const unsigned char* keyblob, unsigned int keybloblen);
+ static int checkfileperm(char * filename);
+@@ -431,6 +431,7 @@ out:
+ return ret;
+ }
+
++extern char *pathexpand(char *input);
+
+ /* Checks whether a specified publickey (and associated algorithm) is an
+ * acceptable key for authentication */
+@@ -458,19 +459,12 @@ static int checkpubkey(const char* keyalgo, unsigned int keyalgolen,
+ dropbear_exit("Failed to set euid");
+ }
+ #endif
++ filename = pathexpand(svr_opts.authkeysfile);
++
+ /* check file permissions, also whether file exists */
+- if (checkpubkeyperms() == DROPBEAR_FAILURE) {
+- TRACE(("bad authorized_keys permissions, or file doesn't exist"))
++ if (checkpubkeyperms(filename, ses.authstate.pw_dir) == DROPBEAR_FAILURE) {
++ TRACE(("bad authorized keys permissions on %s, or file doesn't exist", filename))
+ } else {
+- /* we don't need to check pw and pw_dir for validity, since
+- * its been done in checkpubkeyperms. */
+- len = strlen(ses.authstate.pw_dir);
+- /* allocate max required pathname storage,
+- * = path + "/.ssh/authorized_keys" + '\0' = pathlen + 22 */
+- filename = m_malloc(len + 22);
+- snprintf(filename, len + 22, "%s/.ssh/authorized_keys",
+- ses.authstate.pw_dir);
+-
+ authfile = fopen(filename, "r");
+ if (!authfile) {
+ TRACE(("checkpubkey: failed opening %s: %s", filename, strerror(errno)))
+@@ -486,7 +480,7 @@ static int checkpubkey(const char* keyalgo, unsigned int keyalgolen,
+ if (authfile == NULL) {
+ goto out;
+ }
+- TRACE(("checkpubkey: opened authorized_keys OK"))
++ TRACE(("checkpubkey: opened %s OK", filename))
+
+ line = buf_new(MAX_AUTHKEYS_LINE);
+ line_num = 0;
+@@ -524,53 +518,47 @@ out:
+
+ /* Returns DROPBEAR_SUCCESS if file permissions for pubkeys are ok,
+ * DROPBEAR_FAILURE otherwise.
+- * Checks that the user's homedir, ~/.ssh, and
+- * ~/.ssh/authorized_keys are all owned by either root or the user, and are
++ * Checks filename and its parent directories recursively until the
++ * base directory (usually ~/) or one of its ancestors (up to /) is
++ * reached.
++ * The files and directories must be all owned by root or the user, and be
+ * g-w, o-w */
+-static int checkpubkeyperms() {
+-
+- char* filename = NULL;
++static int checkpubkeyperms(char *filename, char *base) {
++ char* path = NULL;
+ int ret = DROPBEAR_FAILURE;
+ unsigned int len;
+
+- TRACE(("enter checkpubkeyperms"))
+-
+- if (ses.authstate.pw_dir == NULL) {
+- goto out;
+- }
++ TRACE(("enter checkpubkeyperms(%s, %s)", filename, base))
+
+- if ((len = strlen(ses.authstate.pw_dir)) == 0) {
++ if ((base == NULL) || (base[0] != '/') ||
++ (filename == NULL) || (filename[0] != '/')) {
++ /* both filename and base must be absolute paths */
+ goto out;
+ }
+
+- /* allocate max required pathname storage,
+- * = path + "/.ssh/authorized_keys" + '\0' = pathlen + 22 */
+- len += 22;
+- filename = m_malloc(len);
+- strlcpy(filename, ses.authstate.pw_dir, len);
++ len = strlen(filename);
++ path = m_strdup(filename);
+
+- /* check ~ */
+- if (checkfileperm(filename) != DROPBEAR_SUCCESS) {
+- goto out;
+- }
+-
+- /* check ~/.ssh */
+- strlcat(filename, "/.ssh", len);
+- if (checkfileperm(filename) != DROPBEAR_SUCCESS) {
+- goto out;
+- }
++ while (checkfileperm(len ? path : "/") == DROPBEAR_SUCCESS) {
++ /* check if we are on base trail and if this is the
++ * case, return success */
++ if ((strncmp(base, path, len) == 0) &&
++ (!len || (base[len] == '\0') || (base[len] == '/'))) {
++ ret = DROPBEAR_SUCCESS;
++ break;
++ }
+
+- /* now check ~/.ssh/authorized_keys */
+- strlcat(filename, "/authorized_keys", len);
+- if (checkfileperm(filename) != DROPBEAR_SUCCESS) {
+- goto out;
++ /* look for parent directory */
++ while (--len) {
++ if (path[len] == '/') {
++ path[len] = '\0';
++ break;
++ }
++ }
+ }
+
+- /* file looks ok, return success */
+- ret = DROPBEAR_SUCCESS;
+-
+ out:
+- m_free(filename);
++ m_free(path);
+
+ TRACE(("leave checkpubkeyperms"))
+ return ret;
+@@ -596,7 +584,9 @@ static int checkfileperm(char * filename) {
+ TRACE(("wrong ownership"))
+ }
+ /* check permissions - don't want group or others +w */
+- if (filestat.st_mode & (S_IWGRP | S_IWOTH)) {
++ /* (unless sticky dir, which is allowed) */
++ if ((filestat.st_mode & (S_IWGRP | S_IWOTH)) &&
++ !(S_ISDIR(filestat.st_mode) && (filestat.st_mode & S_ISVTX))) {
+ badperm = 1;
+ TRACE(("wrong perms"))
+ }
+diff --git a/src/svr-runopts.c b/src/svr-runopts.c
+index c4f83c1..faddfa2 100644
+--- a/src/svr-runopts.c
++++ b/src/svr-runopts.c
+@@ -147,6 +147,7 @@ void svr_getopts(int argc, char ** argv) {
+ char* maxauthtries_arg = NULL;
+ char* reexec_fd_arg = NULL;
+ char* keyfile = NULL;
++ char* authkeysfile = NULL;
+ char c;
+ #if DROPBEAR_PLUGIN
+ char* pubkey_plugin = NULL;
+@@ -173,6 +174,8 @@ void svr_getopts(int argc, char ** argv) {
+ svr_opts.hostkey = NULL;
+ svr_opts.delay_hostkey = 0;
+ svr_opts.pidfile = expand_homedir_path(DROPBEAR_PIDFILE);
++ svr_opts.authkeysfile = "%d/.ssh/authorized_keys";
++
+ #if DROPBEAR_SVR_LOCALANYFWD
+ svr_opts.nolocaltcp = 0;
+ #endif
+@@ -322,6 +325,9 @@ void svr_getopts(int argc, char ** argv) {
+ case 'u':
+ /* backwards compatibility with old urandom option */
+ break;
++ case 'U':
++ next = &authkeysfile;
++ break;
+ #if DROPBEAR_PLUGIN
+ case 'A':
+ next = &pubkey_plugin;
+@@ -372,6 +378,10 @@ void svr_getopts(int argc, char ** argv) {
+ addhostkey(keyfile);
+ keyfile = NULL;
+ }
++ if (authkeysfile) {
++ svr_opts.authkeysfile = m_strdup(authkeysfile);
++ authkeysfile = NULL;
++ }
+ }
+ }
+