patch dropbear to add -U option
This commit is contained in:
parent
055268d5d2
commit
e835473945
@ -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"'
|
||||
|
386
pkgs/dropbear/add-authkeyfile-option.patch
Normal file
386
pkgs/dropbear/add-authkeyfile-option.patch
Normal file
@ -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;
|
||||
+ }
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user