]> andersk Git - openssh.git/blobdiff - ssh-agent.c
- djm@cvs.openbsd.org 2010/01/30 02:54:53
[openssh.git] / ssh-agent.c
index fbd41834e126801db5d806b079fbc44bd6754f87..df3a87d9a53021252a4856f0834ab05438805e04 100644 (file)
@@ -1,3 +1,4 @@
+/* $OpenBSD: ssh-agent.c,v 1.162 2009/09/01 14:43:17 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
  */
 
 #include "includes.h"
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#ifdef HAVE_SYS_TIME_H
+# include <sys/time.h>
+#endif
+#ifdef HAVE_SYS_UN_H
+# include <sys/un.h>
+#endif
 #include "openbsd-compat/sys-queue.h"
-RCSID("$OpenBSD: ssh-agent.c,v 1.109 2003/04/08 20:21:29 itojun Exp $");
 
 #include <openssl/evp.h>
 #include <openssl/md5.h>
+#include "openbsd-compat/openssl-compat.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#ifdef HAVE_PATHS_H
+# include <paths.h>
+#endif
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <string.h>
+#include <unistd.h>
 
+#include "xmalloc.h"
 #include "ssh.h"
 #include "rsa.h"
 #include "buffer.h"
-#include "bufaux.h"
-#include "xmalloc.h"
-#include "getput.h"
 #include "key.h"
 #include "authfd.h"
 #include "compat.h"
 #include "log.h"
-#include "readpass.h"
 #include "misc.h"
 
 #ifdef SMARTCARD
 #include "scard.h"
 #endif
 
+#if defined(HAVE_SYS_PRCTL_H)
+#include <sys/prctl.h> /* For prctl() and PR_SET_DUMPABLE */
+#endif
+
 typedef enum {
        AUTH_UNUSED,
        AUTH_SOCKET,
@@ -94,20 +121,17 @@ int max_fd = 0;
 
 /* pid of shell == parent of agent */
 pid_t parent_pid = -1;
+u_int parent_alive_interval = 0;
 
 /* pathname and directory for AUTH_SOCKET */
-char socket_name[1024];
-char socket_dir[1024];
+char socket_name[MAXPATHLEN];
+char socket_dir[MAXPATHLEN];
 
 /* locking */
 int locked = 0;
 char *lock_passwd = NULL;
 
-#ifdef HAVE___PROGNAME
 extern char *__progname;
-#else
-char *__progname;
-#endif
 
 /* Default lifetime (0 == forever) */
 static int lifetime = 0;
@@ -169,23 +193,15 @@ lookup_identity(Key *key, int version)
 static int
 confirm_key(Identity *id)
 {
-       char *p, prompt[1024];
+       char *p;
        int ret = -1;
 
        p = key_fingerprint(id->key, SSH_FP_MD5, SSH_FP_HEX);
-       snprintf(prompt, sizeof(prompt), "Allow use of key %s?\n"
-           "Key fingerprint %s.", id->comment, p);
+       if (ask_permission("Allow use of key %s?\nKey fingerprint %s.",
+           id->comment, p))
+               ret = 0;
        xfree(p);
-       p = read_passphrase(prompt, RP_ALLOW_EOF);
-       if (p != NULL) {
-               /*
-                * Accept empty responses and responses consisting 
-                * of the word "yes" as affirmative.
-                */
-               if (*p == '\0' || *p == '\n' || strcasecmp(p, "yes") == 0)
-                       ret = 0;
-               xfree(p);
-       }
+
        return (ret);
 }
 
@@ -296,6 +312,7 @@ process_sign_request2(SocketEntry *e)
        u_char *blob, *data, *signature = NULL;
        u_int blen, dlen, slen = 0;
        extern int datafellows;
+       int odatafellows;
        int ok = -1, flags;
        Buffer msg;
        Key *key;
@@ -306,6 +323,7 @@ process_sign_request2(SocketEntry *e)
        data = buffer_get_string(&e->request, &dlen);
 
        flags = buffer_get_int(&e->request);
+       odatafellows = datafellows;
        if (flags & SSH_AGENT_OLD_SIGNATURE)
                datafellows = SSH_BUG_SIGBLOB;
 
@@ -314,8 +332,8 @@ process_sign_request2(SocketEntry *e)
                Identity *id = lookup_identity(key, 2);
                if (id != NULL && (!id->confirm || confirm_key(id) == 0))
                        ok = key_sign(id->key, &signature, &slen, data, dlen);
+               key_free(key);
        }
-       key_free(key);
        buffer_init(&msg);
        if (ok == 0) {
                buffer_put_char(&msg, SSH2_AGENT_SIGN_RESPONSE);
@@ -331,6 +349,7 @@ process_sign_request2(SocketEntry *e)
        xfree(blob);
        if (signature != NULL)
                xfree(signature);
+       datafellows = odatafellows;
 }
 
 /* shared */
@@ -364,7 +383,7 @@ process_remove_identity(SocketEntry *e, int version)
                if (id != NULL) {
                        /*
                         * We have this key.  Free the old key.  Since we
-                        * don\'t want to leave empty slots in the middle of
+                        * don't want to leave empty slots in the middle of
                         * the array, we actually free the key there and move
                         * all the entries between the empty slot and the end
                         * of the array.
@@ -407,10 +426,11 @@ process_remove_all_identities(SocketEntry *e, int version)
        buffer_put_char(&e->output, SSH_AGENT_SUCCESS);
 }
 
-static void
+/* removes expired keys and returns number of seconds until the next expiry */
+static u_int
 reaper(void)
 {
-       u_int now = time(NULL);
+       u_int deadline = 0, now = time(NULL);
        Identity *id, *nxt;
        int version;
        Idtab *tab;
@@ -419,19 +439,29 @@ reaper(void)
                tab = idtab_lookup(version);
                for (id = TAILQ_FIRST(&tab->idlist); id; id = nxt) {
                        nxt = TAILQ_NEXT(id, next);
-                       if (id->death != 0 && now >= id->death) {
+                       if (id->death == 0)
+                               continue;
+                       if (now >= id->death) {
+                               debug("expiring key '%s'", id->comment);
                                TAILQ_REMOVE(&tab->idlist, id, next);
                                free_identity(id);
                                tab->nentries--;
-                       }
+                       } else
+                               deadline = (deadline == 0) ? id->death :
+                                   MIN(deadline, id->death);
                }
        }
+       if (deadline == 0 || deadline <= now)
+               return 0;
+       else
+               return (deadline - now);
 }
 
 static void
 process_add_identity(SocketEntry *e, int version)
 {
        Idtab *tab = idtab_lookup(version);
+       Identity *id;
        int type, success = 0, death = 0, confirm = 0;
        char *type_name, *comment;
        Key *k = NULL;
@@ -499,9 +529,8 @@ process_add_identity(SocketEntry *e, int version)
                xfree(comment);
                goto send;
        }
-       success = 1;
        while (buffer_len(&e->request)) {
-               switch (buffer_get_char(&e->request)) {
+               switch ((type = buffer_get_char(&e->request))) {
                case SSH_AGENT_CONSTRAIN_LIFETIME:
                        death = time(NULL) + buffer_get_int(&e->request);
                        break;
@@ -509,24 +538,29 @@ process_add_identity(SocketEntry *e, int version)
                        confirm = 1;
                        break;
                default:
-                       break;
+                       error("process_add_identity: "
+                           "Unknown constraint type %d", type);
+                       xfree(comment);
+                       key_free(k);
+                       goto send;
                }
        }
+       success = 1;
        if (lifetime && !death)
                death = time(NULL) + lifetime;
-       if (lookup_identity(k, version) == NULL) {
-               Identity *id = xmalloc(sizeof(Identity));
+       if ((id = lookup_identity(k, version)) == NULL) {
+               id = xmalloc(sizeof(Identity));
                id->key = k;
-               id->comment = comment;
-               id->death = death;
-               id->confirm = confirm;
                TAILQ_INSERT_TAIL(&tab->idlist, id, next);
                /* Increment the number of identities. */
                tab->nentries++;
        } else {
                key_free(k);
-               xfree(comment);
+               xfree(id->comment);
        }
+       id->comment = comment;
+       id->death = death;
+       id->confirm = confirm;
 send:
        buffer_put_int(&e->output, 1);
        buffer_put_char(&e->output,
@@ -577,16 +611,36 @@ no_identities(SocketEntry *e, u_int type)
 
 #ifdef SMARTCARD
 static void
-process_add_smartcard_key (SocketEntry *e)
+process_add_smartcard_key(SocketEntry *e)
 {
        char *sc_reader_id = NULL, *pin;
-       int i, version, success = 0;
+       int i, type, version, success = 0, death = 0, confirm = 0;
        Key **keys, *k;
        Identity *id;
        Idtab *tab;
 
        sc_reader_id = buffer_get_string(&e->request, NULL);
        pin = buffer_get_string(&e->request, NULL);
+
+       while (buffer_len(&e->request)) {
+               switch ((type = buffer_get_char(&e->request))) {
+               case SSH_AGENT_CONSTRAIN_LIFETIME:
+                       death = time(NULL) + buffer_get_int(&e->request);
+                       break;
+               case SSH_AGENT_CONSTRAIN_CONFIRM:
+                       confirm = 1;
+                       break;
+               default:
+                       error("process_add_smartcard_key: "
+                           "Unknown constraint type %d", type);
+                       xfree(sc_reader_id);
+                       xfree(pin);
+                       goto send;
+               }
+       }
+       if (lifetime && !death)
+               death = time(NULL) + lifetime;
+
        keys = sc_get_keys(sc_reader_id, pin);
        xfree(sc_reader_id);
        xfree(pin);
@@ -602,9 +656,9 @@ process_add_smartcard_key (SocketEntry *e)
                if (lookup_identity(k, version) == NULL) {
                        id = xmalloc(sizeof(Identity));
                        id->key = k;
-                       id->comment = xstrdup("smartcard key");
-                       id->death = 0;
-                       id->confirm = 0;
+                       id->comment = sc_get_key_label(k);
+                       id->death = death;
+                       id->confirm = confirm;
                        TAILQ_INSERT_TAIL(&tab->idlist, id, next);
                        tab->nentries++;
                        success = 1;
@@ -668,13 +722,10 @@ process_message(SocketEntry *e)
        u_int msg_len, type;
        u_char *cp;
 
-       /* kill dead keys */
-       reaper();
-
        if (buffer_len(&e->input) < 5)
                return;         /* Incomplete message. */
        cp = buffer_ptr(&e->input);
-       msg_len = GET_32BIT(cp);
+       msg_len = get_u32(cp);
        if (msg_len > 256 * 1024) {
                close_socket(e);
                return;
@@ -748,6 +799,7 @@ process_message(SocketEntry *e)
                break;
 #ifdef SMARTCARD
        case SSH_AGENTC_ADD_SMARTCARD_KEY:
+       case SSH_AGENTC_ADD_SMARTCARD_KEY_CONSTRAINED:
                process_add_smartcard_key(e);
                break;
        case SSH_AGENTC_REMOVE_SMARTCARD_KEY:
@@ -767,10 +819,9 @@ process_message(SocketEntry *e)
 static void
 new_socket(sock_type type, int fd)
 {
-       u_int i, old_alloc;
+       u_int i, old_alloc, new_alloc;
 
-       if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0)
-               error("fcntl O_NONBLOCK: %s", strerror(errno));
+       set_nonblock(fd);
 
        if (fd > max_fd)
                max_fd = fd;
@@ -778,32 +829,32 @@ new_socket(sock_type type, int fd)
        for (i = 0; i < sockets_alloc; i++)
                if (sockets[i].type == AUTH_UNUSED) {
                        sockets[i].fd = fd;
-                       sockets[i].type = type;
                        buffer_init(&sockets[i].input);
                        buffer_init(&sockets[i].output);
                        buffer_init(&sockets[i].request);
+                       sockets[i].type = type;
                        return;
                }
        old_alloc = sockets_alloc;
-       sockets_alloc += 10;
-       if (sockets)
-               sockets = xrealloc(sockets, sockets_alloc * sizeof(sockets[0]));
-       else
-               sockets = xmalloc(sockets_alloc * sizeof(sockets[0]));
-       for (i = old_alloc; i < sockets_alloc; i++)
+       new_alloc = sockets_alloc + 10;
+       sockets = xrealloc(sockets, new_alloc, sizeof(sockets[0]));
+       for (i = old_alloc; i < new_alloc; i++)
                sockets[i].type = AUTH_UNUSED;
-       sockets[old_alloc].type = type;
+       sockets_alloc = new_alloc;
        sockets[old_alloc].fd = fd;
        buffer_init(&sockets[old_alloc].input);
        buffer_init(&sockets[old_alloc].output);
        buffer_init(&sockets[old_alloc].request);
+       sockets[old_alloc].type = type;
 }
 
 static int
-prepare_select(fd_set **fdrp, fd_set **fdwp, int *fdl, int *nallocp)
+prepare_select(fd_set **fdrp, fd_set **fdwp, int *fdl, u_int *nallocp,
+    struct timeval **tvpp)
 {
-       u_int i, sz;
+       u_int i, sz, deadline;
        int n = 0;
+       static struct timeval tv;
 
        for (i = 0; i < sockets_alloc; i++) {
                switch (sockets[i].type) {
@@ -847,6 +898,17 @@ prepare_select(fd_set **fdrp, fd_set **fdwp, int *fdl, int *nallocp)
                        break;
                }
        }
+       deadline = reaper();
+       if (parent_alive_interval != 0)
+               deadline = (deadline == 0) ? parent_alive_interval :
+                   MIN(deadline, parent_alive_interval);
+       if (deadline == 0) {
+               *tvpp = NULL;
+       } else {
+               tv.tv_sec = deadline;
+               tv.tv_usec = 0;
+               *tvpp = &tv;
+       }
        return (1);
 }
 
@@ -857,11 +919,11 @@ after_select(fd_set *readset, fd_set *writeset)
        socklen_t slen;
        char buf[1024];
        int len, sock;
-       u_int i;
+       u_int i, orig_alloc;
        uid_t euid;
        gid_t egid;
 
-       for (i = 0; i < sockets_alloc; i++)
+       for (i = 0, orig_alloc = sockets_alloc; i < orig_alloc; i++)
                switch (sockets[i].type) {
                case AUTH_UNUSED:
                        break;
@@ -869,7 +931,7 @@ after_select(fd_set *readset, fd_set *writeset)
                        if (FD_ISSET(sockets[i].fd, readset)) {
                                slen = sizeof(sunaddr);
                                sock = accept(sockets[i].fd,
-                                   (struct sockaddr *) &sunaddr, &slen);
+                                   (struct sockaddr *)&sunaddr, &slen);
                                if (sock < 0) {
                                        error("accept from AUTH_SOCKET: %s",
                                            strerror(errno));
@@ -894,15 +956,13 @@ after_select(fd_set *readset, fd_set *writeset)
                case AUTH_CONNECTION:
                        if (buffer_len(&sockets[i].output) > 0 &&
                            FD_ISSET(sockets[i].fd, writeset)) {
-                               do {
-                                       len = write(sockets[i].fd,
-                                           buffer_ptr(&sockets[i].output),
-                                           buffer_len(&sockets[i].output));
-                                       if (len == -1 && (errno == EAGAIN ||
-                                           errno == EINTR))
-                                               continue;
-                                       break;
-                               } while (1);
+                               len = write(sockets[i].fd,
+                                   buffer_ptr(&sockets[i].output),
+                                   buffer_len(&sockets[i].output));
+                               if (len == -1 && (errno == EAGAIN ||
+                                   errno == EWOULDBLOCK ||
+                                   errno == EINTR))
+                                       continue;
                                if (len <= 0) {
                                        close_socket(&sockets[i]);
                                        break;
@@ -910,13 +970,11 @@ after_select(fd_set *readset, fd_set *writeset)
                                buffer_consume(&sockets[i].output, len);
                        }
                        if (FD_ISSET(sockets[i].fd, readset)) {
-                               do {
-                                       len = read(sockets[i].fd, buf, sizeof(buf));
-                                       if (len == -1 && (errno == EAGAIN ||
-                                           errno == EINTR))
-                                               continue;
-                                       break;
-                               } while (1);
+                               len = read(sockets[i].fd, buf, sizeof(buf));
+                               if (len == -1 && (errno == EAGAIN ||
+                                   errno == EWOULDBLOCK ||
+                                   errno == EINTR))
+                                       continue;
                                if (len <= 0) {
                                        close_socket(&sockets[i]);
                                        break;
@@ -931,7 +989,7 @@ after_select(fd_set *readset, fd_set *writeset)
 }
 
 static void
-cleanup_socket(void *p)
+cleanup_socket(void)
 {
        if (socket_name[0])
                unlink(socket_name);
@@ -939,38 +997,35 @@ cleanup_socket(void *p)
                rmdir(socket_dir);
 }
 
-static void
+void
 cleanup_exit(int i)
 {
-       cleanup_socket(NULL);
-       exit(i);
+       cleanup_socket();
+       _exit(i);
 }
 
+/*ARGSUSED*/
 static void
 cleanup_handler(int sig)
 {
-       cleanup_socket(NULL);
+       cleanup_socket();
        _exit(2);
 }
 
 static void
-check_parent_exists(int sig)
+check_parent_exists(void)
 {
-       int save_errno = errno;
-
        if (parent_pid != -1 && kill(parent_pid, 0) < 0) {
                /* printf("Parent has died - Authentication agent exiting.\n"); */
-               cleanup_handler(sig); /* safe */
+               cleanup_socket();
+               _exit(2);
        }
-       mysignal(SIGALRM, check_parent_exists);
-       alarm(10);
-       errno = save_errno;
 }
 
 static void
 usage(void)
 {
-       fprintf(stderr, "Usage: %s [options] [command [args ...]]\n",
+       fprintf(stderr, "usage: %s [options] [command [arg ...]]\n",
            __progname);
        fprintf(stderr, "Options:\n");
        fprintf(stderr, "  -c          Generate C-shell commands on stdout.\n");
@@ -986,28 +1041,37 @@ int
 main(int ac, char **av)
 {
        int c_flag = 0, d_flag = 0, k_flag = 0, s_flag = 0;
-       int sock, fd,  ch, nalloc;
+       int sock, fd, ch, result, saved_errno;
+       u_int nalloc;
        char *shell, *format, *pidstr, *agentsocket = NULL;
        fd_set *readsetp = NULL, *writesetp = NULL;
        struct sockaddr_un sunaddr;
 #ifdef HAVE_SETRLIMIT
        struct rlimit rlim;
 #endif
-#ifdef HAVE_CYGWIN
        int prev_mask;
-#endif
        extern int optind;
        extern char *optarg;
        pid_t pid;
        char pidstrbuf[1 + 3 * sizeof pid];
+       struct timeval *tvp = NULL;
+       size_t len;
+
+       /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
+       sanitise_stdfd();
 
        /* drop */
        setegid(getgid());
        setgid(getgid());
 
+#if defined(HAVE_PRCTL) && defined(PR_SET_DUMPABLE)
+       /* Disable ptrace on Linux without sgid bit */
+       prctl(PR_SET_DUMPABLE, 0);
+#endif
+
        SSLeay_add_all_algorithms();
 
-       __progname = get_progname(av[0]);
+       __progname = ssh_get_progname(av[0]);
        init_rng();
        seed_rng();
 
@@ -1052,20 +1116,24 @@ main(int ac, char **av)
 
        if (ac == 0 && !c_flag && !s_flag) {
                shell = getenv("SHELL");
-               if (shell != NULL && strncmp(shell + strlen(shell) - 3, "csh", 3) == 0)
+               if (shell != NULL && (len = strlen(shell)) > 2 &&
+                   strncmp(shell + len - 3, "csh", 3) == 0)
                        c_flag = 1;
        }
        if (k_flag) {
+               const char *errstr = NULL;
+
                pidstr = getenv(SSH_AGENTPID_ENV_NAME);
                if (pidstr == NULL) {
                        fprintf(stderr, "%s not set, cannot kill agent\n",
                            SSH_AGENTPID_ENV_NAME);
                        exit(1);
                }
-               pid = atoi(pidstr);
-               if (pid < 1) {
-                       fprintf(stderr, "%s=\"%s\", which is not a good PID\n",
-                           SSH_AGENTPID_ENV_NAME, pidstr);
+               pid = (int)strtonum(pidstr, 2, INT_MAX, &errstr);
+               if (errstr) {
+                       fprintf(stderr,
+                           "%s=\"%s\", which is not a good PID: %s\n",
+                           SSH_AGENTPID_ENV_NAME, pidstr, errstr);
                        exit(1);
                }
                if (kill(pid, SIGTERM) == -1) {
@@ -1082,7 +1150,7 @@ main(int ac, char **av)
 
        if (agentsocket == NULL) {
                /* Create private directory for agent socket */
-               strlcpy(socket_dir, "/tmp/ssh-XXXXXXXX", sizeof socket_dir);
+               strlcpy(socket_dir, "/tmp/ssh-XXXXXXXXXX", sizeof socket_dir);
                if (mkdtemp(socket_dir) == NULL) {
                        perror("mkdtemp: private socket dir");
                        exit(1);
@@ -1102,25 +1170,21 @@ main(int ac, char **av)
        sock = socket(AF_UNIX, SOCK_STREAM, 0);
        if (sock < 0) {
                perror("socket");
+               *socket_name = '\0'; /* Don't unlink any existing file */
                cleanup_exit(1);
        }
        memset(&sunaddr, 0, sizeof(sunaddr));
        sunaddr.sun_family = AF_UNIX;
        strlcpy(sunaddr.sun_path, socket_name, sizeof(sunaddr.sun_path));
-#ifdef HAVE_CYGWIN
        prev_mask = umask(0177);
-#endif
-       if (bind(sock, (struct sockaddr *) & sunaddr, sizeof(sunaddr)) < 0) {
+       if (bind(sock, (struct sockaddr *) &sunaddr, sizeof(sunaddr)) < 0) {
                perror("bind");
-#ifdef HAVE_CYGWIN
+               *socket_name = '\0'; /* Don't unlink any existing file */
                umask(prev_mask);
-#endif
                cleanup_exit(1);
        }
-#ifdef HAVE_CYGWIN
        umask(prev_mask);
-#endif
-       if (listen(sock, 128) < 0) {
+       if (listen(sock, SSH_LISTEN_BACKLOG) < 0) {
                perror("listen");
                cleanup_exit(1);
        }
@@ -1191,12 +1255,9 @@ main(int ac, char **av)
 #endif
 
 skip:
-       fatal_add_cleanup(cleanup_socket, NULL);
        new_socket(AUTH_SOCKET, sock);
-       if (ac > 0) {
-               mysignal(SIGALRM, check_parent_exists);
-               alarm(10);
-       }
+       if (ac > 0)
+               parent_alive_interval = 10;
        idtab_init();
        if (!d_flag)
                signal(SIGINT, SIG_IGN);
@@ -1206,13 +1267,18 @@ skip:
        nalloc = 0;
 
        while (1) {
-               prepare_select(&readsetp, &writesetp, &max_fd, &nalloc);
-               if (select(max_fd + 1, readsetp, writesetp, NULL, NULL) < 0) {
-                       if (errno == EINTR)
+               prepare_select(&readsetp, &writesetp, &max_fd, &nalloc, &tvp);
+               result = select(max_fd + 1, readsetp, writesetp, NULL, tvp);
+               saved_errno = errno;
+               if (parent_alive_interval != 0)
+                       check_parent_exists();
+               (void) reaper();        /* remove expired keys */
+               if (result < 0) {
+                       if (saved_errno == EINTR)
                                continue;
-                       fatal("select: %s", strerror(errno));
-               }
-               after_select(readsetp, writesetp);
+                       fatal("select: %s", strerror(saved_errno));
+               } else if (result > 0)
+                       after_select(readsetp, writesetp);
        }
        /* NOTREACHED */
 }
This page took 0.061249 seconds and 4 git commands to generate.