OSDN Git Service

Don\'t chmod /dev/ptmx when allocating a pty on Android. am: 0199da83f6
[android-x86/external-openssh.git] / auth2-pubkey.c
index 137887e..d943efa 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: auth2-pubkey.c,v 1.29 2011/05/23 03:30:07 djm Exp $ */
+/* $OpenBSD: auth2-pubkey.c,v 1.47 2015/02/17 00:14:05 djm Exp $ */
 /*
  * Copyright (c) 2000 Markus Friedl.  All rights reserved.
  *
 
 #include <sys/types.h>
 #include <sys/stat.h>
+#include <sys/wait.h>
 
+#include <errno.h>
 #include <fcntl.h>
+#ifdef HAVE_PATHS_H
+# include <paths.h>
+#endif
 #include <pwd.h>
+#include <signal.h>
 #include <stdio.h>
 #include <stdarg.h>
 #include <string.h>
 #include <time.h>
 #include <unistd.h>
+#include <limits.h>
 
 #include "xmalloc.h"
 #include "ssh.h"
@@ -42,6 +49,7 @@
 #include "packet.h"
 #include "buffer.h"
 #include "log.h"
+#include "misc.h"
 #include "servconf.h"
 #include "compat.h"
 #include "key.h"
@@ -55,7 +63,6 @@
 #include "ssh-gss.h"
 #endif
 #include "monitor_wrap.h"
-#include "misc.h"
 #include "authfile.h"
 #include "match.h"
 
@@ -69,7 +76,7 @@ userauth_pubkey(Authctxt *authctxt)
 {
        Buffer b;
        Key *key = NULL;
-       char *pkalg;
+       char *pkalg, *userstyle;
        u_char *pkblob, *sig;
        u_int alen, blen, slen;
        int have_sig, pktype;
@@ -110,6 +117,23 @@ userauth_pubkey(Authctxt *authctxt)
                    "(received %d, expected %d)", key->type, pktype);
                goto done;
        }
+       if (key_type_plain(key->type) == KEY_RSA &&
+           (datafellows & SSH_BUG_RSASIGMD5) != 0) {
+               logit("Refusing RSA key because client uses unsafe "
+                   "signature scheme");
+               goto done;
+       }
+       if (auth2_userkey_already_used(authctxt, key)) {
+               logit("refusing previously-used %s key", key_type(key));
+               goto done;
+       }
+       if (match_pattern_list(sshkey_ssh_name(key), options.pubkey_key_types,
+           strlen(options.pubkey_key_types), 0) != 1) {
+               logit("%s: key type %s not in PubkeyAcceptedKeyTypes",
+                   __func__, sshkey_ssh_name(key));
+               goto done;
+       }
+
        if (have_sig) {
                sig = packet_get_string(&slen);
                packet_check_eom();
@@ -121,7 +145,11 @@ userauth_pubkey(Authctxt *authctxt)
                }
                /* reconstruct packet */
                buffer_put_char(&b, SSH2_MSG_USERAUTH_REQUEST);
-               buffer_put_cstring(&b, authctxt->user);
+               xasprintf(&userstyle, "%s%s%s", authctxt->user,
+                   authctxt->style ? ":" : "",
+                   authctxt->style ? authctxt->style : "");
+               buffer_put_cstring(&b, userstyle);
+               free(userstyle);
                buffer_put_cstring(&b,
                    datafellows & SSH_BUG_PKSERVICE ?
                    "ssh-userauth" :
@@ -137,14 +165,20 @@ userauth_pubkey(Authctxt *authctxt)
 #ifdef DEBUG_PK
                buffer_dump(&b);
 #endif
+               pubkey_auth_info(authctxt, key, NULL);
+
                /* test for correct signature */
                authenticated = 0;
                if (PRIVSEP(user_key_allowed(authctxt->pw, key)) &&
                    PRIVSEP(key_verify(key, sig, slen, buffer_ptr(&b),
-                   buffer_len(&b))) == 1)
+                   buffer_len(&b))) == 1) {
                        authenticated = 1;
+                       /* Record the successful key to prevent reuse */
+                       auth2_record_userkey(authctxt, key);
+                       key = NULL; /* Don't free below */
+               }
                buffer_free(&b);
-               xfree(sig);
+               free(sig);
        } else {
                debug("test whether pkalg/pkblob are acceptable");
                packet_check_eom();
@@ -172,13 +206,50 @@ done:
        debug2("userauth_pubkey: authenticated %d pkalg %s", authenticated, pkalg);
        if (key != NULL)
                key_free(key);
-       xfree(pkalg);
-       xfree(pkblob);
+       free(pkalg);
+       free(pkblob);
        return authenticated;
 }
 
+void
+pubkey_auth_info(Authctxt *authctxt, const Key *key, const char *fmt, ...)
+{
+       char *fp, *extra;
+       va_list ap;
+       int i;
+
+       extra = NULL;
+       if (fmt != NULL) {
+               va_start(ap, fmt);
+               i = vasprintf(&extra, fmt, ap);
+               va_end(ap);
+               if (i < 0 || extra == NULL)
+                       fatal("%s: vasprintf failed", __func__);        
+       }
+
+       if (key_is_cert(key)) {
+               fp = sshkey_fingerprint(key->cert->signature_key,
+                   options.fingerprint_hash, SSH_FP_DEFAULT);
+               auth_info(authctxt, "%s ID %s (serial %llu) CA %s %s%s%s", 
+                   key_type(key), key->cert->key_id,
+                   (unsigned long long)key->cert->serial,
+                   key_type(key->cert->signature_key),
+                   fp == NULL ? "(null)" : fp,
+                   extra == NULL ? "" : ", ", extra == NULL ? "" : extra);
+               free(fp);
+       } else {
+               fp = sshkey_fingerprint(key, options.fingerprint_hash,
+                   SSH_FP_DEFAULT);
+               auth_info(authctxt, "%s %s%s%s", key_type(key),
+                   fp == NULL ? "(null)" : fp,
+                   extra == NULL ? "" : ", ", extra == NULL ? "" : extra);
+               free(fp);
+       }
+       free(extra);
+}
+
 static int
-match_principals_option(const char *principal_list, struct KeyCert *cert)
+match_principals_option(const char *principal_list, struct sshkey_cert *cert)
 {
        char *result;
        u_int i;
@@ -190,7 +261,7 @@ match_principals_option(const char *principal_list, struct KeyCert *cert)
                    principal_list, NULL)) != NULL) {
                        debug3("matched principal from key options \"%.100s\"",
                            result);
-                       xfree(result);
+                       free(result);
                        return 1;
                }
        }
@@ -198,7 +269,7 @@ match_principals_option(const char *principal_list, struct KeyCert *cert)
 }
 
 static int
-match_principals_file(char *file, struct passwd *pw, struct KeyCert *cert)
+match_principals_file(char *file, struct passwd *pw, struct sshkey_cert *cert)
 {
        FILE *f;
        char line[SSH_MAX_PUBKEY_BYTES], *cp, *ep, *line_opts;
@@ -238,8 +309,9 @@ match_principals_file(char *file, struct passwd *pw, struct KeyCert *cert)
                }
                for (i = 0; i < cert->nprincipals; i++) {
                        if (strcmp(cp, cert->principals[i]) == 0) {
-                               debug3("matched principal from file \"%.100s\"",
-                                   cert->principals[i]);
+                               debug3("matched principal \"%.100s\" "
+                                   "from file \"%s\" on line %lu",
+                                   cert->principals[i], file, linenum);
                                if (auth_parse_options(pw, line_opts,
                                    file, linenum) != 1)
                                        continue;
@@ -252,37 +324,30 @@ match_principals_file(char *file, struct passwd *pw, struct KeyCert *cert)
        fclose(f);
        restore_uid();
        return 0;
-}      
+}
 
-/* return 1 if user allows given key */
+/*
+ * Checks whether key is allowed in authorized_keys-format file,
+ * returns 1 if the key is allowed or 0 otherwise.
+ */
 static int
-user_key_allowed2(struct passwd *pw, Key *key, char *file)
+check_authkeys_file(FILE *f, char *file, Key* key, struct passwd *pw)
 {
        char line[SSH_MAX_PUBKEY_BYTES];
        const char *reason;
        int found_key = 0;
-       FILE *f;
        u_long linenum = 0;
        Key *found;
        char *fp;
 
-       /* Temporarily use the user's uid. */
-       temporarily_use_uid(pw);
-
-       debug("trying public key file %s", file);
-       f = auth_openkeyfile(file, pw, options.strict_modes);
-
-       if (!f) {
-               restore_uid();
-               return 0;
-       }
-
        found_key = 0;
-       found = key_new(key_is_cert(key) ? KEY_UNSPEC : key->type);
 
+       found = NULL;
        while (read_keyfile_line(f, file, line, sizeof(line), &linenum) != -1) {
                char *cp, *key_options = NULL;
-
+               if (found != NULL)
+                       key_free(found);
+               found = key_new(key_is_cert(key) ? KEY_UNSPEC : key->type);
                auth_clear_options();
 
                /* Skip leading whitespace, empty and comment lines. */
@@ -319,8 +384,9 @@ user_key_allowed2(struct passwd *pw, Key *key, char *file)
                                continue;
                        if (!key_is_cert_authority)
                                continue;
-                       fp = key_fingerprint(found, SSH_FP_MD5,
-                           SSH_FP_HEX);
+                       if ((fp = sshkey_fingerprint(found,
+                           options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL)
+                               continue;
                        debug("matching CA found: file %s, line %lu, %s %s",
                            file, linenum, key_type(found), fp);
                        /*
@@ -334,7 +400,7 @@ user_key_allowed2(struct passwd *pw, Key *key, char *file)
                                reason = "Certificate does not contain an "
                                    "authorized principal";
  fail_reason:
-                               xfree(fp);
+                               free(fp);
                                error("%s", reason);
                                auth_debug_add("%s", reason);
                                continue;
@@ -344,13 +410,13 @@ user_key_allowed2(struct passwd *pw, Key *key, char *file)
                            &reason) != 0)
                                goto fail_reason;
                        if (auth_cert_options(key, pw) != 0) {
-                               xfree(fp);
+                               free(fp);
                                continue;
                        }
                        verbose("Accepted certificate ID \"%s\" "
                            "signed by %s CA %s via %s", key->cert->key_id,
                            key_type(found), fp, file);
-                       xfree(fp);
+                       free(fp);
                        found_key = 1;
                        break;
                } else if (key_equal(found, key)) {
@@ -359,19 +425,18 @@ user_key_allowed2(struct passwd *pw, Key *key, char *file)
                                continue;
                        if (key_is_cert_authority)
                                continue;
+                       if ((fp = sshkey_fingerprint(found,
+                           options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL)
+                               continue;
+                       debug("matching key found: file %s, line %lu %s %s",
+                           file, linenum, key_type(found), fp);
+                       free(fp);
                        found_key = 1;
-                       debug("matching key found: file %s, line %lu",
-                           file, linenum);
-                       fp = key_fingerprint(found, SSH_FP_MD5, SSH_FP_HEX);
-                       verbose("Found matching %s key: %s",
-                           key_type(found), fp);
-                       xfree(fp);
                        break;
                }
        }
-       restore_uid();
-       fclose(f);
-       key_free(found);
+       if (found != NULL)
+               key_free(found);
        if (!found_key)
                debug2("key not found");
        return found_key;
@@ -388,11 +453,12 @@ user_cert_trusted_ca(struct passwd *pw, Key *key)
        if (!key_is_cert(key) || options.trusted_user_ca_keys == NULL)
                return 0;
 
-       ca_fp = key_fingerprint(key->cert->signature_key,
-           SSH_FP_MD5, SSH_FP_HEX);
+       if ((ca_fp = sshkey_fingerprint(key->cert->signature_key,
+           options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL)
+               return 0;
 
-       if (key_in_file(key->cert->signature_key,
-           options.trusted_user_ca_keys, 1) != 1) {
+       if (sshkey_in_file(key->cert->signature_key,
+           options.trusted_user_ca_keys, 1, 0) != 0) {
                debug2("%s: CA %s %s is not listed in %s", __func__,
                    key_type(key->cert->signature_key), ca_fp,
                    options.trusted_user_ca_keys);
@@ -425,14 +491,185 @@ user_cert_trusted_ca(struct passwd *pw, Key *key)
        ret = 1;
 
  out:
-       if (principals_file != NULL)
-               xfree(principals_file);
-       if (ca_fp != NULL)
-               xfree(ca_fp);
+       free(principals_file);
+       free(ca_fp);
        return ret;
 }
 
-/* check whether given key is in .ssh/authorized_keys* */
+/*
+ * Checks whether key is allowed in file.
+ * returns 1 if the key is allowed or 0 otherwise.
+ */
+static int
+user_key_allowed2(struct passwd *pw, Key *key, char *file)
+{
+       FILE *f;
+       int found_key = 0;
+
+       /* Temporarily use the user's uid. */
+       temporarily_use_uid(pw);
+
+       debug("trying public key file %s", file);
+       if ((f = auth_openkeyfile(file, pw, options.strict_modes)) != NULL) {
+               found_key = check_authkeys_file(f, file, key, pw);
+               fclose(f);
+       }
+
+       restore_uid();
+       return found_key;
+}
+
+/*
+ * Checks whether key is allowed in output of command.
+ * returns 1 if the key is allowed or 0 otherwise.
+ */
+static int
+user_key_command_allowed2(struct passwd *user_pw, Key *key)
+{
+       FILE *f;
+       int ok, found_key = 0;
+       struct passwd *pw;
+       struct stat st;
+       int status, devnull, p[2], i;
+       pid_t pid;
+       char *username, errmsg[512];
+
+       if (options.authorized_keys_command == NULL ||
+           options.authorized_keys_command[0] != '/')
+               return 0;
+
+       if (options.authorized_keys_command_user == NULL) {
+               error("No user for AuthorizedKeysCommand specified, skipping");
+               return 0;
+       }
+
+       username = percent_expand(options.authorized_keys_command_user,
+           "u", user_pw->pw_name, (char *)NULL);
+       pw = getpwnam(username);
+       if (pw == NULL) {
+               error("AuthorizedKeysCommandUser \"%s\" not found: %s",
+                   username, strerror(errno));
+               free(username);
+               return 0;
+       }
+       free(username);
+
+       temporarily_use_uid(pw);
+
+       if (stat(options.authorized_keys_command, &st) < 0) {
+               error("Could not stat AuthorizedKeysCommand \"%s\": %s",
+                   options.authorized_keys_command, strerror(errno));
+               goto out;
+       }
+       if (auth_secure_path(options.authorized_keys_command, &st, NULL, 0,
+           errmsg, sizeof(errmsg)) != 0) {
+               error("Unsafe AuthorizedKeysCommand: %s", errmsg);
+               goto out;
+       }
+
+       if (pipe(p) != 0) {
+               error("%s: pipe: %s", __func__, strerror(errno));
+               goto out;
+       }
+
+       debug3("Running AuthorizedKeysCommand: \"%s %s\" as \"%s\"",
+           options.authorized_keys_command, user_pw->pw_name, pw->pw_name);
+
+       /*
+        * Don't want to call this in the child, where it can fatal() and
+        * run cleanup_exit() code.
+        */
+       restore_uid();
+
+       switch ((pid = fork())) {
+       case -1: /* error */
+               error("%s: fork: %s", __func__, strerror(errno));
+               close(p[0]);
+               close(p[1]);
+               return 0;
+       case 0: /* child */
+               for (i = 0; i < NSIG; i++)
+                       signal(i, SIG_DFL);
+
+               if ((devnull = open(_PATH_DEVNULL, O_RDWR)) == -1) {
+                       error("%s: open %s: %s", __func__, _PATH_DEVNULL,
+                           strerror(errno));
+                       _exit(1);
+               }
+               /* Keep stderr around a while longer to catch errors */
+               if (dup2(devnull, STDIN_FILENO) == -1 ||
+                   dup2(p[1], STDOUT_FILENO) == -1) {
+                       error("%s: dup2: %s", __func__, strerror(errno));
+                       _exit(1);
+               }
+               closefrom(STDERR_FILENO + 1);
+
+               /* Don't use permanently_set_uid() here to avoid fatal() */
+               if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) != 0) {
+                       error("setresgid %u: %s", (u_int)pw->pw_gid,
+                           strerror(errno));
+                       _exit(1);
+               }
+               if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) != 0) {
+                       error("setresuid %u: %s", (u_int)pw->pw_uid,
+                           strerror(errno));
+                       _exit(1);
+               }
+               /* stdin is pointed to /dev/null at this point */
+               if (dup2(STDIN_FILENO, STDERR_FILENO) == -1) {
+                       error("%s: dup2: %s", __func__, strerror(errno));
+                       _exit(1);
+               }
+
+               execl(options.authorized_keys_command,
+                   options.authorized_keys_command, user_pw->pw_name, NULL);
+
+               error("AuthorizedKeysCommand %s exec failed: %s",
+                   options.authorized_keys_command, strerror(errno));
+               _exit(127);
+       default: /* parent */
+               break;
+       }
+
+       temporarily_use_uid(pw);
+
+       close(p[1]);
+       if ((f = fdopen(p[0], "r")) == NULL) {
+               error("%s: fdopen: %s", __func__, strerror(errno));
+               close(p[0]);
+               /* Don't leave zombie child */
+               kill(pid, SIGTERM);
+               while (waitpid(pid, NULL, 0) == -1 && errno == EINTR)
+                       ;
+               goto out;
+       }
+       ok = check_authkeys_file(f, options.authorized_keys_command, key, pw);
+       fclose(f);
+
+       while (waitpid(pid, &status, 0) == -1) {
+               if (errno != EINTR) {
+                       error("%s: waitpid: %s", __func__, strerror(errno));
+                       goto out;
+               }
+       }
+       if (WIFSIGNALED(status)) {
+               error("AuthorizedKeysCommand %s exited on signal %d",
+                   options.authorized_keys_command, WTERMSIG(status));
+               goto out;
+       } else if (WEXITSTATUS(status) != 0) {
+               error("AuthorizedKeysCommand %s returned status %d",
+                   options.authorized_keys_command, WEXITSTATUS(status));
+               goto out;
+       }
+       found_key = ok;
+ out:
+       restore_uid();
+       return found_key;
+}
+
+/*
+ * Check whether key authenticates and authorises the user.
+ */
 int
 user_key_allowed(struct passwd *pw, Key *key)
 {
@@ -448,16 +685,53 @@ user_key_allowed(struct passwd *pw, Key *key)
        if (success)
                return success;
 
+       success = user_key_command_allowed2(pw, key);
+       if (success > 0)
+               return success;
+
        for (i = 0; !success && i < options.num_authkeys_files; i++) {
+
+               if (strcasecmp(options.authorized_keys_files[i], "none") == 0)
+                       continue;
                file = expand_authorized_keys(
                    options.authorized_keys_files[i], pw);
+
                success = user_key_allowed2(pw, key, file);
-               xfree(file);
+               free(file);
        }
 
        return success;
 }
 
+/* Records a public key in the list of previously-successful keys */
+void
+auth2_record_userkey(Authctxt *authctxt, struct sshkey *key)
+{
+       struct sshkey **tmp;
+
+       if (authctxt->nprev_userkeys >= INT_MAX ||
+           (tmp = reallocarray(authctxt->prev_userkeys,
+           authctxt->nprev_userkeys + 1, sizeof(*tmp))) == NULL)
+               fatal("%s: reallocarray failed", __func__);
+       authctxt->prev_userkeys = tmp;
+       authctxt->prev_userkeys[authctxt->nprev_userkeys] = key;
+       authctxt->nprev_userkeys++;
+}
+
+/* Checks whether a key has already been used successfully for authentication */
+int
+auth2_userkey_already_used(Authctxt *authctxt, struct sshkey *key)
+{
+       u_int i;
+
+       for (i = 0; i < authctxt->nprev_userkeys; i++) {
+               if (sshkey_equal_public(key, authctxt->prev_userkeys[i])) {
+                       return 1;
+               }
+       }
+       return 0;
+}
+
 Authmethod method_pubkey = {
        "publickey",
        userauth_pubkey,