From c8445989f9005acc20e4a123673a93fb3cc57ead Mon Sep 17 00:00:00 2001 From: mouring Date: Tue, 5 Jun 2001 20:25:05 +0000 Subject: [PATCH] - markus@cvs.openbsd.org 2001/05/20 17:20:36 [auth-rsa.c auth.c auth.h auth2.c servconf.c servconf.h sshd.8 sshd_config] configurable authorized_keys{,2} location; originally from peter@; ok djm@ --- ChangeLog | 5 ++ auth-rsa.c | 54 ++++++--------------- auth.c | 135 +++++++++++++++++++++++++++++++++++++++++++++++++++- auth.h | 12 ++++- auth2.c | 58 ++++++---------------- servconf.c | 25 +++++++++- servconf.h | 4 +- sshd.8 | 41 ++++++++++++++-- sshd_config | 5 +- 9 files changed, 246 insertions(+), 93 deletions(-) diff --git a/ChangeLog b/ChangeLog index b7cb2c90..8c03dae8 100644 --- a/ChangeLog +++ b/ChangeLog @@ -42,6 +42,11 @@ - stevesk@cvs.openbsd.org 2001/05/19 19:57:09 [channels.c] typo in error message + - markus@cvs.openbsd.org 2001/05/20 17:20:36 + [auth-rsa.c auth.c auth.h auth2.c servconf.c servconf.h sshd.8 + sshd_config] + configurable authorized_keys{,2} location; originally from peter@; + ok djm@ 20010528 - (tim) [conifgure.in] add setvbuf test needed for sftp-int.c diff --git a/auth-rsa.c b/auth-rsa.c index 59bee18b..491ed81d 100644 --- a/auth-rsa.c +++ b/auth-rsa.c @@ -14,7 +14,7 @@ */ #include "includes.h" -RCSID("$OpenBSD: auth-rsa.c,v 1.40 2001/04/06 21:00:07 markus Exp $"); +RCSID("$OpenBSD: auth-rsa.c,v 1.41 2001/05/20 17:20:35 markus Exp $"); #include #include @@ -122,7 +122,7 @@ auth_rsa_challenge_dialog(RSA *pk) int auth_rsa(struct passwd *pw, BIGNUM *client_n) { - char line[8192], file[MAXPATHLEN]; + char line[8192], *file; int authenticated; u_int bits; FILE *f; @@ -138,13 +138,14 @@ auth_rsa(struct passwd *pw, BIGNUM *client_n) temporarily_use_uid(pw); /* The authorized keys. */ - snprintf(file, sizeof file, "%.500s/%.100s", pw->pw_dir, - _PATH_SSH_USER_PERMITTED_KEYS); + file = authorized_keys_file(pw); + debug("trying public RSA key file %s", file); /* Fail quietly if file does not exist */ if (stat(file, &st) < 0) { /* Restore the privileged uid. */ restore_uid(); + xfree(file); return 0; } /* Open the file containing the authorized keys. */ @@ -154,43 +155,17 @@ auth_rsa(struct passwd *pw, BIGNUM *client_n) restore_uid(); packet_send_debug("Could not open %.900s for reading.", file); packet_send_debug("If your home is on an NFS volume, it may need to be world-readable."); + xfree(file); return 0; } - if (options.strict_modes) { - int fail = 0; - char buf[1024]; - /* Check open file in order to avoid open/stat races */ - if (fstat(fileno(f), &st) < 0 || - (st.st_uid != 0 && st.st_uid != pw->pw_uid) || - (st.st_mode & 022) != 0) { - snprintf(buf, sizeof buf, "RSA authentication refused for %.100s: " - "bad ownership or modes for '%s'.", pw->pw_name, file); - fail = 1; - } else { - /* Check path to _PATH_SSH_USER_PERMITTED_KEYS */ - int i; - static const char *check[] = { - "", _PATH_SSH_USER_DIR, NULL - }; - for (i = 0; check[i]; i++) { - snprintf(line, sizeof line, "%.500s/%.100s", pw->pw_dir, check[i]); - if (stat(line, &st) < 0 || - (st.st_uid != 0 && st.st_uid != pw->pw_uid) || - (st.st_mode & 022) != 0) { - snprintf(buf, sizeof buf, "RSA authentication refused for %.100s: " - "bad ownership or modes for '%s'.", pw->pw_name, line); - fail = 1; - break; - } - } - } - if (fail) { - fclose(f); - log("%s", buf); - packet_send_debug("%s", buf); - restore_uid(); - return 0; - } + if (options.strict_modes && + secure_filename(f, file, pw->pw_uid, line, sizeof(line)) != 0) { + xfree(file); + fclose(f); + log("Authentication refused: %s", line); + packet_send_debug("Authentication refused: %s", line); + restore_uid(); + return 0; } /* Flag indicating whether authentication has succeeded. */ authenticated = 0; @@ -285,6 +260,7 @@ auth_rsa(struct passwd *pw, BIGNUM *client_n) restore_uid(); /* Close the file. */ + xfree(file); fclose(f); RSA_free(pk); diff --git a/auth.c b/auth.c index 1f976eee..07e5b8f0 100644 --- a/auth.c +++ b/auth.c @@ -23,7 +23,7 @@ */ #include "includes.h" -RCSID("$OpenBSD: auth.c,v 1.21 2001/03/19 17:07:23 markus Exp $"); +RCSID("$OpenBSD: auth.c,v 1.22 2001/05/20 17:20:35 markus Exp $"); #ifdef HAVE_LOGIN_H #include @@ -32,6 +32,8 @@ RCSID("$OpenBSD: auth.c,v 1.21 2001/03/19 17:07:23 markus Exp $"); #include #endif /* defined(HAVE_SHADOW_H) && !defined(DISABLE_SHADOW) */ +#include + #include "xmalloc.h" #include "match.h" #include "groupaccess.h" @@ -40,6 +42,8 @@ RCSID("$OpenBSD: auth.c,v 1.21 2001/03/19 17:07:23 markus Exp $"); #include "auth.h" #include "auth-options.h" #include "canohost.h" +#include "buffer.h" +#include "bufaux.h" /* import */ extern ServerOptions options; @@ -222,3 +226,132 @@ auth_root_allowed(char *method) log("ROOT LOGIN REFUSED FROM %.200s", get_remote_ipaddr()); return 0; } + + +/* + * Given a template and a passwd structure, build a filename + * by substituting % tokenised options. Currently, %% becomes '%', + * %h becomes the home directory and %u the username. + * + * This returns a buffer allocated by xmalloc. + */ +char * +expand_filename(const char *filename, struct passwd *pw) +{ + Buffer buffer; + char *file; + const char *cp; + + /* + * Build the filename string in the buffer by making the appropriate + * substitutions to the given file name. + */ + buffer_init(&buffer); + for (cp = filename; *cp; cp++) { + if (cp[0] == '%' && cp[1] == '%') { + buffer_append(&buffer, "%", 1); + cp++; + continue; + } + if (cp[0] == '%' && cp[1] == 'h') { + buffer_append(&buffer, pw->pw_dir, strlen(pw->pw_dir)); + cp++; + continue; + } + if (cp[0] == '%' && cp[1] == 'u') { + buffer_append(&buffer, pw->pw_name, + strlen(pw->pw_name)); + cp++; + continue; + } + buffer_append(&buffer, cp, 1); + } + buffer_append(&buffer, "\0", 1); + + /* + * Ensure that filename starts anchored. If not, be backward + * compatible and prepend the '%h/' + */ + file = xmalloc(MAXPATHLEN); + cp = buffer_ptr(&buffer); + if (*cp != '/') + snprintf(file, MAXPATHLEN, "%s/%s", pw->pw_dir, cp); + else + strlcpy(file, cp, MAXPATHLEN); + + buffer_free(&buffer); + return file; +} + +char * +authorized_keys_file(struct passwd *pw) +{ + return expand_filename(options.authorized_keys_file, pw); +} + +char * +authorized_keys_file2(struct passwd *pw) +{ + return expand_filename(options.authorized_keys_file2, pw); +} + +/* + * Check a given file for security. This is defined as all components + * of the path to the file must either be owned by either the owner of + * of the file or root and no directories must be world writable. + * + * XXX Should any specific check be done for sym links ? + * + * Takes an open file descriptor, the file name, a uid and and + * error buffer plus max size as arguments. + * + * Returns 0 on success and -1 on failure + */ +int +secure_filename(FILE *f, const char *file, uid_t uid, char *err, size_t errlen) +{ + char buf[MAXPATHLEN]; + char *cp; + struct stat st; + + if (realpath(file, buf) == NULL) { + snprintf(err, errlen, "realpath %s failed: %s", file, + strerror(errno)); + return -1; + } + + /* check the open file to avoid races */ + if (fstat(fileno(f), &st) < 0 || + (st.st_uid != 0 && st.st_uid != uid) || + (st.st_mode & 022) != 0) { + snprintf(err, errlen, "bad ownership or modes for file %s", + buf); + return -1; + } + + /* for each component of the canonical path, walking upwards */ + for (;;) { + if ((cp = dirname(buf)) == NULL) { + snprintf(err, errlen, "dirname() failed"); + return -1; + } + strlcpy(buf, cp, sizeof(buf)); + + debug3("secure_filename: checking '%s'", buf); + if (stat(buf, &st) < 0 || + (st.st_uid != 0 && st.st_uid != uid) || + (st.st_mode & 022) != 0) { + snprintf(err, errlen, + "bad ownership or modes for directory %s", buf); + return -1; + } + + /* + * dirname should always complete with a "/" path, + * but we can be paranoid and check for "." too + */ + if ((strcmp("/", buf) == 0) || (strcmp(".", buf) == 0)) + break; + } + return 0; +} diff --git a/auth.h b/auth.h index 20c3ebb7..2d1f1e9b 100644 --- a/auth.h +++ b/auth.h @@ -21,7 +21,7 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * - * $OpenBSD: auth.h,v 1.16 2001/05/18 14:13:28 markus Exp $ + * $OpenBSD: auth.h,v 1.17 2001/05/20 17:20:35 markus Exp $ */ #ifndef AUTH_H #define AUTH_H @@ -159,6 +159,16 @@ int verify_response(Authctxt *authctxt, const char *response); struct passwd * auth_get_user(void); + +/* expand a filename - return buffer is allocated by xmalloc */ +char *expand_filename(const char *template, struct passwd *pw); +char *authorized_keys_file(struct passwd *pw); +char *authorized_keys_file2(struct passwd *pw); + +/* check a file and the path to it */ +int +secure_filename(FILE *f, const char *file, uid_t u, char *err, size_t errlen); + #define AUTH_FAIL_MAX 6 #define AUTH_FAIL_LOG (AUTH_FAIL_MAX/2) #define AUTH_FAIL_MSG "Too many authentication failures for %.100s" diff --git a/auth2.c b/auth2.c index e800c058..9988f7ae 100644 --- a/auth2.c +++ b/auth2.c @@ -23,7 +23,7 @@ */ #include "includes.h" -RCSID("$OpenBSD: auth2.c,v 1.57 2001/05/18 14:13:28 markus Exp $"); +RCSID("$OpenBSD: auth2.c,v 1.58 2001/05/20 17:20:35 markus Exp $"); #include @@ -666,7 +666,7 @@ authmethod_lookup(const char *name) int user_key_allowed(struct passwd *pw, Key *key) { - char line[8192], file[MAXPATHLEN]; + char line[8192], *file; int found_key = 0; FILE *f; u_long linenum = 0; @@ -680,13 +680,14 @@ user_key_allowed(struct passwd *pw, Key *key) temporarily_use_uid(pw); /* The authorized keys. */ - snprintf(file, sizeof file, "%.500s/%.100s", pw->pw_dir, - _PATH_SSH_USER_PERMITTED_KEYS2); + file = authorized_keys_file2(pw); + debug("trying public key file %s", file); /* Fail quietly if file does not exist */ if (stat(file, &st) < 0) { /* Restore the privileged uid. */ restore_uid(); + xfree(file); return 0; } /* Open the file containing the authorized keys. */ @@ -694,48 +695,18 @@ user_key_allowed(struct passwd *pw, Key *key) if (!f) { /* Restore the privileged uid. */ restore_uid(); + xfree(file); return 0; } - if (options.strict_modes) { - int fail = 0; - char buf[1024]; - /* Check open file in order to avoid open/stat races */ - if (fstat(fileno(f), &st) < 0 || - (st.st_uid != 0 && st.st_uid != pw->pw_uid) || - (st.st_mode & 022) != 0) { - snprintf(buf, sizeof buf, - "%s authentication refused for %.100s: " - "bad ownership or modes for '%s'.", - key_type(key), pw->pw_name, file); - fail = 1; - } else { - /* Check path to _PATH_SSH_USER_PERMITTED_KEYS */ - int i; - static const char *check[] = { - "", _PATH_SSH_USER_DIR, NULL - }; - for (i = 0; check[i]; i++) { - snprintf(line, sizeof line, "%.500s/%.100s", - pw->pw_dir, check[i]); - if (stat(line, &st) < 0 || - (st.st_uid != 0 && st.st_uid != pw->pw_uid) || - (st.st_mode & 022) != 0) { - snprintf(buf, sizeof buf, - "%s authentication refused for %.100s: " - "bad ownership or modes for '%s'.", - key_type(key), pw->pw_name, line); - fail = 1; - break; - } - } - } - if (fail) { - fclose(f); - log("%s", buf); - restore_uid(); - return 0; - } + if (options.strict_modes && + secure_filename(f, file, pw->pw_uid, line, sizeof(line)) != 0) { + xfree(file); + fclose(f); + log("Authentication refused: %s", line); + restore_uid(); + return 0; } + found_key = 0; found = key_new(key->type); @@ -778,6 +749,7 @@ user_key_allowed(struct passwd *pw, Key *key) } restore_uid(); fclose(f); + xfree(file); key_free(found); if (!found_key) debug2("key not found"); diff --git a/servconf.c b/servconf.c index 2d10963c..e357d77a 100644 --- a/servconf.c +++ b/servconf.c @@ -10,7 +10,7 @@ */ #include "includes.h" -RCSID("$OpenBSD: servconf.c,v 1.81 2001/05/19 19:43:57 stevesk Exp $"); +RCSID("$OpenBSD: servconf.c,v 1.82 2001/05/20 17:20:35 markus Exp $"); #ifdef KRB4 #include @@ -101,6 +101,8 @@ initialize_server_options(ServerOptions *options) options->reverse_mapping_check = -1; options->client_alive_interval = -1; options->client_alive_count_max = -1; + options->authorized_keys_file = NULL; + options->authorized_keys_file2 = NULL; options->pam_authentication_via_kbd_int = -1; } @@ -208,6 +210,10 @@ fill_default_server_options(ServerOptions *options) options->client_alive_interval = 0; if (options->client_alive_count_max == -1) options->client_alive_count_max = 3; + if (options->authorized_keys_file == NULL) + options->authorized_keys_file = _PATH_SSH_USER_PERMITTED_KEYS; + if (options->authorized_keys_file2 == NULL) + options->authorized_keys_file2 = _PATH_SSH_USER_PERMITTED_KEYS2; if (options->pam_authentication_via_kbd_int == -1) options->pam_authentication_via_kbd_int = 0; } @@ -235,7 +241,8 @@ typedef enum { sGatewayPorts, sPubkeyAuthentication, sXAuthLocation, sSubsystem, sMaxStartups, sBanner, sReverseMappingCheck, sHostbasedAuthentication, sHostbasedUsesNameFromPacketOnly, sClientAliveInterval, - sClientAliveCountMax, sPAMAuthenticationViaKbdInt + sClientAliveCountMax, sAuthorizedKeysFile, sAuthorizedKeysFile2, + sPAMAuthenticationViaKbdInt } ServerOpCodes; /* Textual representation of the tokens. */ @@ -301,6 +308,8 @@ static struct { { "reversemappingcheck", sReverseMappingCheck }, { "clientaliveinterval", sClientAliveInterval }, { "clientalivecountmax", sClientAliveCountMax }, + { "authorizedkeysfile", sAuthorizedKeysFile }, + { "authorizedkeysfile2", sAuthorizedKeysFile2 }, { "PAMAuthenticationViaKbdInt", sPAMAuthenticationViaKbdInt }, { NULL, 0 } }; @@ -802,6 +811,18 @@ parse_flag: case sBanner: charptr = &options->banner; goto parse_filename; + /* + * These options can contain %X options expanded at + * connect time, so that you can specify paths like: + * + * AuthorizedKeysFile /etc/ssh_keys/%u + */ + case sAuthorizedKeysFile: + case sAuthorizedKeysFile2: + charptr = (opcode == sAuthorizedKeysFile ) ? + &options->authorized_keys_file : + &options->authorized_keys_file2; + goto parse_filename; case sClientAliveInterval: intptr = &options->client_alive_interval; diff --git a/servconf.h b/servconf.h index a319a5c6..2bf19fb3 100644 --- a/servconf.h +++ b/servconf.h @@ -11,7 +11,7 @@ * called by a name other than "ssh" or "Secure Shell". */ -/* RCSID("$OpenBSD: servconf.h,v 1.42 2001/05/18 14:13:29 markus Exp $"); */ +/* RCSID("$OpenBSD: servconf.h,v 1.43 2001/05/20 17:20:35 markus Exp $"); */ #ifndef SERVCONF_H #define SERVCONF_H @@ -124,6 +124,8 @@ typedef struct { * for this many intervals, above * diconnect the session */ + char *authorized_keys_file; /* File containing public RSA keys */ + char *authorized_keys_file2; /* File containing public SSH2 keys */ int pam_authentication_via_kbd_int; } ServerOptions; /* diff --git a/sshd.8 b/sshd.8 index 02960b70..a66dac54 100644 --- a/sshd.8 +++ b/sshd.8 @@ -34,7 +34,7 @@ .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" -.\" $OpenBSD: sshd.8,v 1.124 2001/05/19 19:43:57 stevesk Exp $ +.\" $OpenBSD: sshd.8,v 1.125 2001/05/20 17:20:35 markus Exp $ .Dd September 25, 1999 .Dt SSHD 8 .Os @@ -331,6 +331,34 @@ wildcards in the patterns. Only user names are valid; a numerical user ID isn't recognized. By default login is allowed regardless of the user name. .Pp +.It Cm AuthorizedKeysFile +Specifies the file that contains the public RSA keys that can be used +for RSA authentication in protocol version 1. +.Cm AuthorizedKeysFile +may contain tokens of the form %T which are substituted during connection +set-up. The following tokens are defined; %% is replaces by a literal '%', +%h is replaced by the home directory of the user being authenticated and +%u is replaced by the username of that user. +After expansion, +.Cm AuthorizedKeysFile +is taken to be an absolute path or one realtive to the user's home +directory. +The default is +.Dq .ssh/authorized_keys +.It Cm AuthorizedKeysFile2 +Specifies the file that contains the public keys that can be used +for public key authentication in protocol version 2. +.Cm AuthorizedKeysFile2 +may contain tokens of the form %T which are substituted during connection +set-up. The following tokens are defined; %% is replaces by a literal '%', +%h is replaced by the home directory of the user being authenticated and +%u is replaced by the username of that user. +After expansion, +.Cm AuthorizedKeysFile2 +is taken to be an absolute path or one realtive to the user's home +directory. +The default is +.Dq .ssh/authorized_keys2 .It Cm Banner In some jurisdictions, sending a warning message before authentication may be relevant for getting legal protection. @@ -883,15 +911,18 @@ authentication protocol and cookie in standard input. Runs user's shell or command. .El .Sh AUTHORIZED_KEYS FILE FORMAT -The .Pa $HOME/.ssh/authorized_keys -file lists the RSA keys that are +is the default file that lists the RSA keys that are permitted for RSA authentication in protocol version 1. -Similarly, the +.Cm AuthorizedKeysFile +may be used to specify an alternative file. +Similarly, .Pa $HOME/.ssh/authorized_keys2 -file lists the DSA and RSA keys that are +is the default file that lists the DSA and RSA keys that are permitted for public key authentication (PubkeyAuthentication) in protocol version 2. +.Cm AuthorizedKeysFile2 +may be used to specify an alternative file. .Pp Each line of the file contains one key (empty lines and lines starting with a diff --git a/sshd_config b/sshd_config index 8c411e47..90df340a 100644 --- a/sshd_config +++ b/sshd_config @@ -1,4 +1,4 @@ -# $OpenBSD: sshd_config,v 1.38 2001/04/15 21:41:29 deraadt Exp $ +# $OpenBSD: sshd_config,v 1.39 2001/05/20 17:20:36 markus Exp $ # This sshd was compiled with PATH=/usr/bin:/bin:/usr/sbin:/sbin @@ -41,6 +41,9 @@ RhostsRSAAuthentication no HostbasedAuthentication no # RSAAuthentication yes +PubkeyAuthentication yes +#AuthorizedKeysFile %h/.ssh/authorized_keys +#AuthorizedKeysFile2 %h/.ssh/authorized_keys2 # To disable tunneled clear text passwords, change to no here! PasswordAuthentication yes -- 2.45.1