Index: vendor-crypto/openssh/dist/serverloop.c =================================================================== --- vendor-crypto/openssh/dist/serverloop.c (revision 311912) +++ vendor-crypto/openssh/dist/serverloop.c (revision 311913) @@ -1,1409 +1,1409 @@ /* $OpenBSD: serverloop.c,v 1.182 2016/02/08 10:57:07 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved * Server main loop for handling the interactive session. * * As far as I am concerned, the code I have written for this software * can be used freely for any purpose. Any derived versions of this * software must be clearly marked as such, and if the derived work is * incompatible with the protocol description in the RFC file, it must be * called by a name other than "ssh" or "Secure Shell". * * SSH2 support by Markus Friedl. * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "includes.h" #include /* MIN MAX */ #include #include #include #ifdef HAVE_SYS_TIME_H # include #endif #include #include #include #include #include #include #include #include #include #include "openbsd-compat/sys-queue.h" #include "xmalloc.h" #include "packet.h" #include "buffer.h" #include "log.h" #include "misc.h" #include "servconf.h" #include "canohost.h" #include "sshpty.h" #include "channels.h" #include "compat.h" #include "ssh1.h" #include "ssh2.h" #include "key.h" #include "cipher.h" #include "kex.h" #include "hostfile.h" #include "auth.h" #include "session.h" #include "dispatch.h" #include "auth-options.h" #include "serverloop.h" #include "ssherr.h" extern ServerOptions options; /* XXX */ extern Authctxt *the_authctxt; extern int use_privsep; static Buffer stdin_buffer; /* Buffer for stdin data. */ static Buffer stdout_buffer; /* Buffer for stdout data. */ static Buffer stderr_buffer; /* Buffer for stderr data. */ static int fdin; /* Descriptor for stdin (for writing) */ static int fdout; /* Descriptor for stdout (for reading); May be same number as fdin. */ static int fderr; /* Descriptor for stderr. May be -1. */ static long stdin_bytes = 0; /* Number of bytes written to stdin. */ static long stdout_bytes = 0; /* Number of stdout bytes sent to client. */ static long stderr_bytes = 0; /* Number of stderr bytes sent to client. */ static long fdout_bytes = 0; /* Number of stdout bytes read from program. */ static int stdin_eof = 0; /* EOF message received from client. */ static int fdout_eof = 0; /* EOF encountered reading from fdout. */ static int fderr_eof = 0; /* EOF encountered readung from fderr. */ static int fdin_is_tty = 0; /* fdin points to a tty. */ static int connection_in; /* Connection to client (input). */ static int connection_out; /* Connection to client (output). */ static int connection_closed = 0; /* Connection to client closed. */ static u_int buffer_high; /* "Soft" max buffer size. */ static int no_more_sessions = 0; /* Disallow further sessions. */ /* * This SIGCHLD kludge is used to detect when the child exits. The server * will exit after that, as soon as forwarded connections have terminated. */ static volatile sig_atomic_t child_terminated = 0; /* The child has terminated. */ /* Cleanup on signals (!use_privsep case only) */ static volatile sig_atomic_t received_sigterm = 0; /* prototypes */ static void server_init_dispatch(void); /* * we write to this pipe if a SIGCHLD is caught in order to avoid * the race between select() and child_terminated */ static int notify_pipe[2]; static void notify_setup(void) { if (pipe(notify_pipe) < 0) { error("pipe(notify_pipe) failed %s", strerror(errno)); } else if ((fcntl(notify_pipe[0], F_SETFD, FD_CLOEXEC) == -1) || (fcntl(notify_pipe[1], F_SETFD, FD_CLOEXEC) == -1)) { error("fcntl(notify_pipe, F_SETFD) failed %s", strerror(errno)); close(notify_pipe[0]); close(notify_pipe[1]); } else { set_nonblock(notify_pipe[0]); set_nonblock(notify_pipe[1]); return; } notify_pipe[0] = -1; /* read end */ notify_pipe[1] = -1; /* write end */ } static void notify_parent(void) { if (notify_pipe[1] != -1) (void)write(notify_pipe[1], "", 1); } static void notify_prepare(fd_set *readset) { if (notify_pipe[0] != -1) FD_SET(notify_pipe[0], readset); } static void notify_done(fd_set *readset) { char c; if (notify_pipe[0] != -1 && FD_ISSET(notify_pipe[0], readset)) while (read(notify_pipe[0], &c, 1) != -1) debug2("notify_done: reading"); } /*ARGSUSED*/ static void sigchld_handler(int sig) { int save_errno = errno; child_terminated = 1; #ifndef _UNICOS mysignal(SIGCHLD, sigchld_handler); #endif notify_parent(); errno = save_errno; } /*ARGSUSED*/ static void sigterm_handler(int sig) { received_sigterm = sig; } /* * Make packets from buffered stderr data, and buffer it for sending * to the client. */ static void make_packets_from_stderr_data(void) { u_int len; /* Send buffered stderr data to the client. */ while (buffer_len(&stderr_buffer) > 0 && packet_not_very_much_data_to_write()) { len = buffer_len(&stderr_buffer); if (packet_is_interactive()) { if (len > 512) len = 512; } else { /* Keep the packets at reasonable size. */ if (len > packet_get_maxsize()) len = packet_get_maxsize(); } packet_start(SSH_SMSG_STDERR_DATA); packet_put_string(buffer_ptr(&stderr_buffer), len); packet_send(); buffer_consume(&stderr_buffer, len); stderr_bytes += len; } } /* * Make packets from buffered stdout data, and buffer it for sending to the * client. */ static void make_packets_from_stdout_data(void) { u_int len; /* Send buffered stdout data to the client. */ while (buffer_len(&stdout_buffer) > 0 && packet_not_very_much_data_to_write()) { len = buffer_len(&stdout_buffer); if (packet_is_interactive()) { if (len > 512) len = 512; } else { /* Keep the packets at reasonable size. */ if (len > packet_get_maxsize()) len = packet_get_maxsize(); } packet_start(SSH_SMSG_STDOUT_DATA); packet_put_string(buffer_ptr(&stdout_buffer), len); packet_send(); buffer_consume(&stdout_buffer, len); stdout_bytes += len; } } static void client_alive_check(void) { int channel_id; /* timeout, check to see how many we have had */ if (packet_inc_alive_timeouts() > options.client_alive_count_max) { logit("Timeout, client not responding."); cleanup_exit(255); } /* * send a bogus global/channel request with "wantreply", * we should get back a failure */ if ((channel_id = channel_find_open()) == -1) { packet_start(SSH2_MSG_GLOBAL_REQUEST); packet_put_cstring("keepalive@openssh.com"); packet_put_char(1); /* boolean: want reply */ } else { channel_request_start(channel_id, "keepalive@openssh.com", 1); } packet_send(); } /* * Sleep in select() until we can do something. This will initialize the * select masks. Upon return, the masks will indicate which descriptors * have data or can accept data. Optionally, a maximum time can be specified * for the duration of the wait (0 = infinite). */ static void wait_until_can_do_something(fd_set **readsetp, fd_set **writesetp, int *maxfdp, u_int *nallocp, u_int64_t max_time_milliseconds) { struct timeval tv, *tvp; int ret; time_t minwait_secs = 0; int client_alive_scheduled = 0; int program_alive_scheduled = 0; /* Allocate and update select() masks for channel descriptors. */ channel_prepare_select(readsetp, writesetp, maxfdp, nallocp, &minwait_secs, 0); if (minwait_secs != 0) max_time_milliseconds = MIN(max_time_milliseconds, (u_int)minwait_secs * 1000); /* * if using client_alive, set the max timeout accordingly, * and indicate that this particular timeout was for client * alive by setting the client_alive_scheduled flag. * * this could be randomized somewhat to make traffic * analysis more difficult, but we're not doing it yet. */ if (compat20 && max_time_milliseconds == 0 && options.client_alive_interval) { client_alive_scheduled = 1; max_time_milliseconds = (u_int64_t)options.client_alive_interval * 1000; } if (compat20) { #if 0 /* wrong: bad condition XXX */ if (channel_not_very_much_buffered_data()) #endif FD_SET(connection_in, *readsetp); } else { /* * Read packets from the client unless we have too much * buffered stdin or channel data. */ if (buffer_len(&stdin_buffer) < buffer_high && channel_not_very_much_buffered_data()) FD_SET(connection_in, *readsetp); /* * If there is not too much data already buffered going to * the client, try to get some more data from the program. */ if (packet_not_very_much_data_to_write()) { program_alive_scheduled = child_terminated; if (!fdout_eof) FD_SET(fdout, *readsetp); if (!fderr_eof) FD_SET(fderr, *readsetp); } /* * If we have buffered data, try to write some of that data * to the program. */ if (fdin != -1 && buffer_len(&stdin_buffer) > 0) FD_SET(fdin, *writesetp); } notify_prepare(*readsetp); /* * If we have buffered packet data going to the client, mark that * descriptor. */ if (packet_have_data_to_write()) FD_SET(connection_out, *writesetp); /* * If child has terminated and there is enough buffer space to read * from it, then read as much as is available and exit. */ if (child_terminated && packet_not_very_much_data_to_write()) if (max_time_milliseconds == 0 || client_alive_scheduled) max_time_milliseconds = 100; if (max_time_milliseconds == 0) tvp = NULL; else { tv.tv_sec = max_time_milliseconds / 1000; tv.tv_usec = 1000 * (max_time_milliseconds % 1000); tvp = &tv; } /* Wait for something to happen, or the timeout to expire. */ ret = select((*maxfdp)+1, *readsetp, *writesetp, NULL, tvp); if (ret == -1) { memset(*readsetp, 0, *nallocp); memset(*writesetp, 0, *nallocp); if (errno != EINTR) error("select: %.100s", strerror(errno)); } else { if (ret == 0 && client_alive_scheduled) client_alive_check(); if (!compat20 && program_alive_scheduled && fdin_is_tty) { if (!fdout_eof) FD_SET(fdout, *readsetp); if (!fderr_eof) FD_SET(fderr, *readsetp); } } notify_done(*readsetp); } /* * Processes input from the client and the program. Input data is stored * in buffers and processed later. */ static void process_input(fd_set *readset) { int len; char buf[16384]; /* Read and buffer any input data from the client. */ if (FD_ISSET(connection_in, readset)) { len = read(connection_in, buf, sizeof(buf)); if (len == 0) { verbose("Connection closed by %.100s", get_remote_ipaddr()); connection_closed = 1; if (compat20) return; cleanup_exit(255); } else if (len < 0) { if (errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK) { verbose("Read error from remote host " "%.100s: %.100s", get_remote_ipaddr(), strerror(errno)); cleanup_exit(255); } } else { /* Buffer any received data. */ packet_process_incoming(buf, len); } } if (compat20) return; /* Read and buffer any available stdout data from the program. */ if (!fdout_eof && FD_ISSET(fdout, readset)) { errno = 0; len = read(fdout, buf, sizeof(buf)); if (len < 0 && (errno == EINTR || ((errno == EAGAIN || errno == EWOULDBLOCK) && !child_terminated))) { /* do nothing */ #ifndef PTY_ZEROREAD } else if (len <= 0) { #else } else if ((!isatty(fdout) && len <= 0) || (isatty(fdout) && (len < 0 || (len == 0 && errno != 0)))) { #endif fdout_eof = 1; } else { buffer_append(&stdout_buffer, buf, len); fdout_bytes += len; } } /* Read and buffer any available stderr data from the program. */ if (!fderr_eof && FD_ISSET(fderr, readset)) { errno = 0; len = read(fderr, buf, sizeof(buf)); if (len < 0 && (errno == EINTR || ((errno == EAGAIN || errno == EWOULDBLOCK) && !child_terminated))) { /* do nothing */ #ifndef PTY_ZEROREAD } else if (len <= 0) { #else } else if ((!isatty(fderr) && len <= 0) || (isatty(fderr) && (len < 0 || (len == 0 && errno != 0)))) { #endif fderr_eof = 1; } else { buffer_append(&stderr_buffer, buf, len); } } } /* * Sends data from internal buffers to client program stdin. */ static void process_output(fd_set *writeset) { struct termios tio; u_char *data; u_int dlen; int len; /* Write buffered data to program stdin. */ if (!compat20 && fdin != -1 && FD_ISSET(fdin, writeset)) { data = buffer_ptr(&stdin_buffer); dlen = buffer_len(&stdin_buffer); len = write(fdin, data, dlen); if (len < 0 && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)) { /* do nothing */ } else if (len <= 0) { if (fdin != fdout) close(fdin); else shutdown(fdin, SHUT_WR); /* We will no longer send. */ fdin = -1; } else { /* Successful write. */ if (fdin_is_tty && dlen >= 1 && data[0] != '\r' && tcgetattr(fdin, &tio) == 0 && !(tio.c_lflag & ECHO) && (tio.c_lflag & ICANON)) { /* * Simulate echo to reduce the impact of * traffic analysis */ packet_send_ignore(len); packet_send(); } /* Consume the data from the buffer. */ buffer_consume(&stdin_buffer, len); /* Update the count of bytes written to the program. */ stdin_bytes += len; } } /* Send any buffered packet data to the client. */ if (FD_ISSET(connection_out, writeset)) packet_write_poll(); } /* * Wait until all buffered output has been sent to the client. * This is used when the program terminates. */ static void drain_output(void) { /* Send any buffered stdout data to the client. */ if (buffer_len(&stdout_buffer) > 0) { packet_start(SSH_SMSG_STDOUT_DATA); packet_put_string(buffer_ptr(&stdout_buffer), buffer_len(&stdout_buffer)); packet_send(); /* Update the count of sent bytes. */ stdout_bytes += buffer_len(&stdout_buffer); } /* Send any buffered stderr data to the client. */ if (buffer_len(&stderr_buffer) > 0) { packet_start(SSH_SMSG_STDERR_DATA); packet_put_string(buffer_ptr(&stderr_buffer), buffer_len(&stderr_buffer)); packet_send(); /* Update the count of sent bytes. */ stderr_bytes += buffer_len(&stderr_buffer); } /* Wait until all buffered data has been written to the client. */ packet_write_wait(); } static void process_buffered_input_packets(void) { dispatch_run(DISPATCH_NONBLOCK, NULL, active_state); } /* * Performs the interactive session. This handles data transmission between * the client and the program. Note that the notion of stdin, stdout, and * stderr in this function is sort of reversed: this function writes to * stdin (of the child program), and reads from stdout and stderr (of the * child program). */ void server_loop(pid_t pid, int fdin_arg, int fdout_arg, int fderr_arg) { fd_set *readset = NULL, *writeset = NULL; int max_fd = 0; u_int nalloc = 0; int wait_status; /* Status returned by wait(). */ pid_t wait_pid; /* pid returned by wait(). */ int waiting_termination = 0; /* Have displayed waiting close message. */ u_int64_t max_time_milliseconds; u_int previous_stdout_buffer_bytes; u_int stdout_buffer_bytes; int type; debug("Entering interactive session."); /* Initialize the SIGCHLD kludge. */ child_terminated = 0; mysignal(SIGCHLD, sigchld_handler); if (!use_privsep) { signal(SIGTERM, sigterm_handler); signal(SIGINT, sigterm_handler); signal(SIGQUIT, sigterm_handler); } /* Initialize our global variables. */ fdin = fdin_arg; fdout = fdout_arg; fderr = fderr_arg; /* nonblocking IO */ set_nonblock(fdin); set_nonblock(fdout); /* we don't have stderr for interactive terminal sessions, see below */ if (fderr != -1) set_nonblock(fderr); if (!(datafellows & SSH_BUG_IGNOREMSG) && isatty(fdin)) fdin_is_tty = 1; connection_in = packet_get_connection_in(); connection_out = packet_get_connection_out(); notify_setup(); previous_stdout_buffer_bytes = 0; /* Set approximate I/O buffer size. */ if (packet_is_interactive()) buffer_high = 4096; else buffer_high = 64 * 1024; #if 0 /* Initialize max_fd to the maximum of the known file descriptors. */ max_fd = MAX(connection_in, connection_out); max_fd = MAX(max_fd, fdin); max_fd = MAX(max_fd, fdout); if (fderr != -1) max_fd = MAX(max_fd, fderr); #endif /* Initialize Initialize buffers. */ buffer_init(&stdin_buffer); buffer_init(&stdout_buffer); buffer_init(&stderr_buffer); /* * If we have no separate fderr (which is the case when we have a pty * - there we cannot make difference between data sent to stdout and * stderr), indicate that we have seen an EOF from stderr. This way * we don't need to check the descriptor everywhere. */ if (fderr == -1) fderr_eof = 1; server_init_dispatch(); /* Main loop of the server for the interactive session mode. */ for (;;) { /* Process buffered packets from the client. */ process_buffered_input_packets(); /* * If we have received eof, and there is no more pending * input data, cause a real eof by closing fdin. */ if (stdin_eof && fdin != -1 && buffer_len(&stdin_buffer) == 0) { if (fdin != fdout) close(fdin); else shutdown(fdin, SHUT_WR); /* We will no longer send. */ fdin = -1; } /* Make packets from buffered stderr data to send to the client. */ make_packets_from_stderr_data(); /* * Make packets from buffered stdout data to send to the * client. If there is very little to send, this arranges to * not send them now, but to wait a short while to see if we * are getting more data. This is necessary, as some systems * wake up readers from a pty after each separate character. */ max_time_milliseconds = 0; stdout_buffer_bytes = buffer_len(&stdout_buffer); if (stdout_buffer_bytes != 0 && stdout_buffer_bytes < 256 && stdout_buffer_bytes != previous_stdout_buffer_bytes) { /* try again after a while */ max_time_milliseconds = 10; } else { /* Send it now. */ make_packets_from_stdout_data(); } previous_stdout_buffer_bytes = buffer_len(&stdout_buffer); /* Send channel data to the client. */ if (packet_not_very_much_data_to_write()) channel_output_poll(); /* * Bail out of the loop if the program has closed its output * descriptors, and we have no more data to send to the * client, and there is no pending buffered data. */ if (fdout_eof && fderr_eof && !packet_have_data_to_write() && buffer_len(&stdout_buffer) == 0 && buffer_len(&stderr_buffer) == 0) { if (!channel_still_open()) break; if (!waiting_termination) { const char *s = "Waiting for forwarded connections to terminate...\r\n"; char *cp; waiting_termination = 1; buffer_append(&stderr_buffer, s, strlen(s)); /* Display list of open channels. */ cp = channel_open_message(); buffer_append(&stderr_buffer, cp, strlen(cp)); free(cp); } } max_fd = MAX(connection_in, connection_out); max_fd = MAX(max_fd, fdin); max_fd = MAX(max_fd, fdout); max_fd = MAX(max_fd, fderr); max_fd = MAX(max_fd, notify_pipe[0]); /* Sleep in select() until we can do something. */ wait_until_can_do_something(&readset, &writeset, &max_fd, &nalloc, max_time_milliseconds); if (received_sigterm) { logit("Exiting on signal %d", (int)received_sigterm); /* Clean up sessions, utmp, etc. */ cleanup_exit(255); } /* Process any channel events. */ channel_after_select(readset, writeset); /* Process input from the client and from program stdout/stderr. */ process_input(readset); /* Process output to the client and to program stdin. */ process_output(writeset); } free(readset); free(writeset); /* Cleanup and termination code. */ /* Wait until all output has been sent to the client. */ drain_output(); debug("End of interactive session; stdin %ld, stdout (read %ld, sent %ld), stderr %ld bytes.", stdin_bytes, fdout_bytes, stdout_bytes, stderr_bytes); /* Free and clear the buffers. */ buffer_free(&stdin_buffer); buffer_free(&stdout_buffer); buffer_free(&stderr_buffer); /* Close the file descriptors. */ if (fdout != -1) close(fdout); fdout = -1; fdout_eof = 1; if (fderr != -1) close(fderr); fderr = -1; fderr_eof = 1; if (fdin != -1) close(fdin); fdin = -1; channel_free_all(); /* We no longer want our SIGCHLD handler to be called. */ mysignal(SIGCHLD, SIG_DFL); while ((wait_pid = waitpid(-1, &wait_status, 0)) < 0) if (errno != EINTR) packet_disconnect("wait: %.100s", strerror(errno)); if (wait_pid != pid) error("Strange, wait returned pid %ld, expected %ld", (long)wait_pid, (long)pid); /* Check if it exited normally. */ if (WIFEXITED(wait_status)) { /* Yes, normal exit. Get exit status and send it to the client. */ debug("Command exited with status %d.", WEXITSTATUS(wait_status)); packet_start(SSH_SMSG_EXITSTATUS); packet_put_int(WEXITSTATUS(wait_status)); packet_send(); packet_write_wait(); /* * Wait for exit confirmation. Note that there might be * other packets coming before it; however, the program has * already died so we just ignore them. The client is * supposed to respond with the confirmation when it receives * the exit status. */ do { type = packet_read(); } while (type != SSH_CMSG_EXIT_CONFIRMATION); debug("Received exit confirmation."); return; } /* Check if the program terminated due to a signal. */ if (WIFSIGNALED(wait_status)) packet_disconnect("Command terminated on signal %d.", WTERMSIG(wait_status)); /* Some weird exit cause. Just exit. */ packet_disconnect("wait returned status %04x.", wait_status); /* NOTREACHED */ } static void collect_children(void) { pid_t pid; sigset_t oset, nset; int status; /* block SIGCHLD while we check for dead children */ sigemptyset(&nset); sigaddset(&nset, SIGCHLD); sigprocmask(SIG_BLOCK, &nset, &oset); if (child_terminated) { debug("Received SIGCHLD."); while ((pid = waitpid(-1, &status, WNOHANG)) > 0 || (pid < 0 && errno == EINTR)) if (pid > 0) session_close_by_pid(pid, status); child_terminated = 0; } sigprocmask(SIG_SETMASK, &oset, NULL); } void server_loop2(Authctxt *authctxt) { fd_set *readset = NULL, *writeset = NULL; int max_fd; u_int nalloc = 0; u_int64_t rekey_timeout_ms = 0; debug("Entering interactive session for SSH2."); mysignal(SIGCHLD, sigchld_handler); child_terminated = 0; connection_in = packet_get_connection_in(); connection_out = packet_get_connection_out(); if (!use_privsep) { signal(SIGTERM, sigterm_handler); signal(SIGINT, sigterm_handler); signal(SIGQUIT, sigterm_handler); } notify_setup(); max_fd = MAX(connection_in, connection_out); max_fd = MAX(max_fd, notify_pipe[0]); server_init_dispatch(); for (;;) { process_buffered_input_packets(); if (!ssh_packet_is_rekeying(active_state) && packet_not_very_much_data_to_write()) channel_output_poll(); if (options.rekey_interval > 0 && compat20 && !ssh_packet_is_rekeying(active_state)) rekey_timeout_ms = packet_get_rekey_timeout() * 1000; else rekey_timeout_ms = 0; wait_until_can_do_something(&readset, &writeset, &max_fd, &nalloc, rekey_timeout_ms); if (received_sigterm) { logit("Exiting on signal %d", (int)received_sigterm); /* Clean up sessions, utmp, etc. */ cleanup_exit(255); } collect_children(); if (!ssh_packet_is_rekeying(active_state)) channel_after_select(readset, writeset); process_input(readset); if (connection_closed) break; process_output(writeset); } collect_children(); free(readset); free(writeset); /* free all channels, no more reads and writes */ channel_free_all(); /* free remaining sessions, e.g. remove wtmp entries */ session_destroy_all(NULL); } static int server_input_keep_alive(int type, u_int32_t seq, void *ctxt) { debug("Got %d/%u for keepalive", type, seq); /* * reset timeout, since we got a sane answer from the client. * even if this was generated by something other than * the bogus CHANNEL_REQUEST we send for keepalives. */ packet_set_alive_timeouts(0); return 0; } static int server_input_stdin_data(int type, u_int32_t seq, void *ctxt) { char *data; u_int data_len; /* Stdin data from the client. Append it to the buffer. */ /* Ignore any data if the client has closed stdin. */ if (fdin == -1) return 0; data = packet_get_string(&data_len); packet_check_eom(); buffer_append(&stdin_buffer, data, data_len); explicit_bzero(data, data_len); free(data); return 0; } static int server_input_eof(int type, u_int32_t seq, void *ctxt) { /* * Eof from the client. The stdin descriptor to the * program will be closed when all buffered data has * drained. */ debug("EOF received for stdin."); packet_check_eom(); stdin_eof = 1; return 0; } static int server_input_window_size(int type, u_int32_t seq, void *ctxt) { u_int row = packet_get_int(); u_int col = packet_get_int(); u_int xpixel = packet_get_int(); u_int ypixel = packet_get_int(); debug("Window change received."); packet_check_eom(); if (fdin != -1) pty_change_window_size(fdin, row, col, xpixel, ypixel); return 0; } static Channel * server_request_direct_tcpip(void) { Channel *c = NULL; char *target, *originator; u_short target_port, originator_port; target = packet_get_string(NULL); target_port = packet_get_int(); originator = packet_get_string(NULL); originator_port = packet_get_int(); packet_check_eom(); debug("server_request_direct_tcpip: originator %s port %d, target %s " "port %d", originator, originator_port, target, target_port); /* XXX fine grained permissions */ if ((options.allow_tcp_forwarding & FORWARD_LOCAL) != 0 && !no_port_forwarding_flag) { c = channel_connect_to_port(target, target_port, "direct-tcpip", "direct-tcpip"); } else { logit("refused local port forward: " "originator %s port %d, target %s port %d", originator, originator_port, target, target_port); } free(originator); free(target); return c; } static Channel * server_request_direct_streamlocal(void) { Channel *c = NULL; char *target, *originator; u_short originator_port; target = packet_get_string(NULL); originator = packet_get_string(NULL); originator_port = packet_get_int(); packet_check_eom(); debug("server_request_direct_streamlocal: originator %s port %d, target %s", originator, originator_port, target); /* XXX fine grained permissions */ if ((options.allow_streamlocal_forwarding & FORWARD_LOCAL) != 0 && - !no_port_forwarding_flag) { + !no_port_forwarding_flag && use_privsep) { c = channel_connect_to_path(target, "direct-streamlocal@openssh.com", "direct-streamlocal"); } else { logit("refused streamlocal port forward: " "originator %s port %d, target %s", originator, originator_port, target); } free(originator); free(target); return c; } static Channel * server_request_tun(void) { Channel *c = NULL; int mode, tun; int sock; mode = packet_get_int(); switch (mode) { case SSH_TUNMODE_POINTOPOINT: case SSH_TUNMODE_ETHERNET: break; default: packet_send_debug("Unsupported tunnel device mode."); return NULL; } if ((options.permit_tun & mode) == 0) { packet_send_debug("Server has rejected tunnel device " "forwarding"); return NULL; } tun = packet_get_int(); if (forced_tun_device != -1) { if (tun != SSH_TUNID_ANY && forced_tun_device != tun) goto done; tun = forced_tun_device; } sock = tun_open(tun, mode); if (sock < 0) goto done; c = channel_new("tun", SSH_CHANNEL_OPEN, sock, sock, -1, CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, 0, "tun", 1); c->datagram = 1; #if defined(SSH_TUN_FILTER) if (mode == SSH_TUNMODE_POINTOPOINT) channel_register_filter(c->self, sys_tun_infilter, sys_tun_outfilter, NULL, NULL); #endif done: if (c == NULL) packet_send_debug("Failed to open the tunnel device."); return c; } static Channel * server_request_session(void) { Channel *c; debug("input_session_request"); packet_check_eom(); if (no_more_sessions) { packet_disconnect("Possible attack: attempt to open a session " "after additional sessions disabled"); } /* * A server session has no fd to read or write until a * CHANNEL_REQUEST for a shell is made, so we set the type to * SSH_CHANNEL_LARVAL. Additionally, a callback for handling all * CHANNEL_REQUEST messages is registered. */ c = channel_new("session", SSH_CHANNEL_LARVAL, -1, -1, -1, /*window size*/0, CHAN_SES_PACKET_DEFAULT, 0, "server-session", 1); if (session_open(the_authctxt, c->self) != 1) { debug("session open failed, free channel %d", c->self); channel_free(c); return NULL; } channel_register_cleanup(c->self, session_close_by_channel, 0); return c; } static int server_input_channel_open(int type, u_int32_t seq, void *ctxt) { Channel *c = NULL; char *ctype; int rchan; u_int rmaxpack, rwindow, len; ctype = packet_get_string(&len); rchan = packet_get_int(); rwindow = packet_get_int(); rmaxpack = packet_get_int(); debug("server_input_channel_open: ctype %s rchan %d win %d max %d", ctype, rchan, rwindow, rmaxpack); if (strcmp(ctype, "session") == 0) { c = server_request_session(); } else if (strcmp(ctype, "direct-tcpip") == 0) { c = server_request_direct_tcpip(); } else if (strcmp(ctype, "direct-streamlocal@openssh.com") == 0) { c = server_request_direct_streamlocal(); } else if (strcmp(ctype, "tun@openssh.com") == 0) { c = server_request_tun(); } if (c != NULL) { debug("server_input_channel_open: confirm %s", ctype); c->remote_id = rchan; c->remote_window = rwindow; c->remote_maxpacket = rmaxpack; if (c->type != SSH_CHANNEL_CONNECTING) { packet_start(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION); packet_put_int(c->remote_id); packet_put_int(c->self); packet_put_int(c->local_window); packet_put_int(c->local_maxpacket); packet_send(); } } else { debug("server_input_channel_open: failure %s", ctype); packet_start(SSH2_MSG_CHANNEL_OPEN_FAILURE); packet_put_int(rchan); packet_put_int(SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED); if (!(datafellows & SSH_BUG_OPENFAILURE)) { packet_put_cstring("open failed"); packet_put_cstring(""); } packet_send(); } free(ctype); return 0; } static int server_input_hostkeys_prove(struct sshbuf **respp) { struct ssh *ssh = active_state; /* XXX */ struct sshbuf *resp = NULL; struct sshbuf *sigbuf = NULL; struct sshkey *key = NULL, *key_pub = NULL, *key_prv = NULL; int r, ndx, success = 0; const u_char *blob; u_char *sig = 0; size_t blen, slen; if ((resp = sshbuf_new()) == NULL || (sigbuf = sshbuf_new()) == NULL) fatal("%s: sshbuf_new", __func__); while (ssh_packet_remaining(ssh) > 0) { sshkey_free(key); key = NULL; if ((r = sshpkt_get_string_direct(ssh, &blob, &blen)) != 0 || (r = sshkey_from_blob(blob, blen, &key)) != 0) { error("%s: couldn't parse key: %s", __func__, ssh_err(r)); goto out; } /* * Better check that this is actually one of our hostkeys * before attempting to sign anything with it. */ if ((ndx = ssh->kex->host_key_index(key, 1, ssh)) == -1) { error("%s: unknown host %s key", __func__, sshkey_type(key)); goto out; } /* * XXX refactor: make kex->sign just use an index rather * than passing in public and private keys */ if ((key_prv = get_hostkey_by_index(ndx)) == NULL && (key_pub = get_hostkey_public_by_index(ndx, ssh)) == NULL) { error("%s: can't retrieve hostkey %d", __func__, ndx); goto out; } sshbuf_reset(sigbuf); free(sig); sig = NULL; if ((r = sshbuf_put_cstring(sigbuf, "hostkeys-prove-00@openssh.com")) != 0 || (r = sshbuf_put_string(sigbuf, ssh->kex->session_id, ssh->kex->session_id_len)) != 0 || (r = sshkey_puts(key, sigbuf)) != 0 || (r = ssh->kex->sign(key_prv, key_pub, &sig, &slen, sshbuf_ptr(sigbuf), sshbuf_len(sigbuf), NULL, 0)) != 0 || (r = sshbuf_put_string(resp, sig, slen)) != 0) { error("%s: couldn't prepare signature: %s", __func__, ssh_err(r)); goto out; } } /* Success */ *respp = resp; resp = NULL; /* don't free it */ success = 1; out: free(sig); sshbuf_free(resp); sshbuf_free(sigbuf); sshkey_free(key); return success; } static int server_input_global_request(int type, u_int32_t seq, void *ctxt) { char *rtype; int want_reply; int r, success = 0, allocated_listen_port = 0; struct sshbuf *resp = NULL; rtype = packet_get_string(NULL); want_reply = packet_get_char(); debug("server_input_global_request: rtype %s want_reply %d", rtype, want_reply); /* -R style forwarding */ if (strcmp(rtype, "tcpip-forward") == 0) { struct passwd *pw; struct Forward fwd; pw = the_authctxt->pw; if (pw == NULL || !the_authctxt->valid) fatal("server_input_global_request: no/invalid user"); memset(&fwd, 0, sizeof(fwd)); fwd.listen_host = packet_get_string(NULL); fwd.listen_port = (u_short)packet_get_int(); debug("server_input_global_request: tcpip-forward listen %s port %d", fwd.listen_host, fwd.listen_port); /* check permissions */ if ((options.allow_tcp_forwarding & FORWARD_REMOTE) == 0 || no_port_forwarding_flag || (!want_reply && fwd.listen_port == 0) #ifndef NO_IPPORT_RESERVED_CONCEPT || (fwd.listen_port != 0 && fwd.listen_port < IPPORT_RESERVED && pw->pw_uid != 0) #endif ) { success = 0; packet_send_debug("Server has disabled port forwarding."); } else { /* Start listening on the port */ success = channel_setup_remote_fwd_listener(&fwd, &allocated_listen_port, &options.fwd_opts); } free(fwd.listen_host); if ((resp = sshbuf_new()) == NULL) fatal("%s: sshbuf_new", __func__); if (allocated_listen_port != 0 && (r = sshbuf_put_u32(resp, allocated_listen_port)) != 0) fatal("%s: sshbuf_put_u32: %s", __func__, ssh_err(r)); } else if (strcmp(rtype, "cancel-tcpip-forward") == 0) { struct Forward fwd; memset(&fwd, 0, sizeof(fwd)); fwd.listen_host = packet_get_string(NULL); fwd.listen_port = (u_short)packet_get_int(); debug("%s: cancel-tcpip-forward addr %s port %d", __func__, fwd.listen_host, fwd.listen_port); success = channel_cancel_rport_listener(&fwd); free(fwd.listen_host); } else if (strcmp(rtype, "streamlocal-forward@openssh.com") == 0) { struct Forward fwd; memset(&fwd, 0, sizeof(fwd)); fwd.listen_path = packet_get_string(NULL); debug("server_input_global_request: streamlocal-forward listen path %s", fwd.listen_path); /* check permissions */ if ((options.allow_streamlocal_forwarding & FORWARD_REMOTE) == 0 - || no_port_forwarding_flag) { + || no_port_forwarding_flag || !use_privsep) { success = 0; packet_send_debug("Server has disabled port forwarding."); } else { /* Start listening on the socket */ success = channel_setup_remote_fwd_listener( &fwd, NULL, &options.fwd_opts); } free(fwd.listen_path); } else if (strcmp(rtype, "cancel-streamlocal-forward@openssh.com") == 0) { struct Forward fwd; memset(&fwd, 0, sizeof(fwd)); fwd.listen_path = packet_get_string(NULL); debug("%s: cancel-streamlocal-forward path %s", __func__, fwd.listen_path); success = channel_cancel_rport_listener(&fwd); free(fwd.listen_path); } else if (strcmp(rtype, "no-more-sessions@openssh.com") == 0) { no_more_sessions = 1; success = 1; } else if (strcmp(rtype, "hostkeys-prove-00@openssh.com") == 0) { success = server_input_hostkeys_prove(&resp); } if (want_reply) { packet_start(success ? SSH2_MSG_REQUEST_SUCCESS : SSH2_MSG_REQUEST_FAILURE); if (success && resp != NULL) ssh_packet_put_raw(active_state, sshbuf_ptr(resp), sshbuf_len(resp)); packet_send(); packet_write_wait(); } free(rtype); sshbuf_free(resp); return 0; } static int server_input_channel_req(int type, u_int32_t seq, void *ctxt) { Channel *c; int id, reply, success = 0; char *rtype; id = packet_get_int(); rtype = packet_get_string(NULL); reply = packet_get_char(); debug("server_input_channel_req: channel %d request %s reply %d", id, rtype, reply); if ((c = channel_lookup(id)) == NULL) packet_disconnect("server_input_channel_req: " "unknown channel %d", id); if (!strcmp(rtype, "eow@openssh.com")) { packet_check_eom(); chan_rcvd_eow(c); } else if ((c->type == SSH_CHANNEL_LARVAL || c->type == SSH_CHANNEL_OPEN) && strcmp(c->ctype, "session") == 0) success = session_input_channel_req(c, rtype); if (reply && !(c->flags & CHAN_CLOSE_SENT)) { packet_start(success ? SSH2_MSG_CHANNEL_SUCCESS : SSH2_MSG_CHANNEL_FAILURE); packet_put_int(c->remote_id); packet_send(); } free(rtype); return 0; } static void server_init_dispatch_20(void) { debug("server_init_dispatch_20"); dispatch_init(&dispatch_protocol_error); dispatch_set(SSH2_MSG_CHANNEL_CLOSE, &channel_input_oclose); dispatch_set(SSH2_MSG_CHANNEL_DATA, &channel_input_data); dispatch_set(SSH2_MSG_CHANNEL_EOF, &channel_input_ieof); dispatch_set(SSH2_MSG_CHANNEL_EXTENDED_DATA, &channel_input_extended_data); dispatch_set(SSH2_MSG_CHANNEL_OPEN, &server_input_channel_open); dispatch_set(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION, &channel_input_open_confirmation); dispatch_set(SSH2_MSG_CHANNEL_OPEN_FAILURE, &channel_input_open_failure); dispatch_set(SSH2_MSG_CHANNEL_REQUEST, &server_input_channel_req); dispatch_set(SSH2_MSG_CHANNEL_WINDOW_ADJUST, &channel_input_window_adjust); dispatch_set(SSH2_MSG_GLOBAL_REQUEST, &server_input_global_request); /* client_alive */ dispatch_set(SSH2_MSG_CHANNEL_SUCCESS, &server_input_keep_alive); dispatch_set(SSH2_MSG_CHANNEL_FAILURE, &server_input_keep_alive); dispatch_set(SSH2_MSG_REQUEST_SUCCESS, &server_input_keep_alive); dispatch_set(SSH2_MSG_REQUEST_FAILURE, &server_input_keep_alive); /* rekeying */ dispatch_set(SSH2_MSG_KEXINIT, &kex_input_kexinit); } static void server_init_dispatch_13(void) { debug("server_init_dispatch_13"); dispatch_init(NULL); dispatch_set(SSH_CMSG_EOF, &server_input_eof); dispatch_set(SSH_CMSG_STDIN_DATA, &server_input_stdin_data); dispatch_set(SSH_CMSG_WINDOW_SIZE, &server_input_window_size); dispatch_set(SSH_MSG_CHANNEL_CLOSE, &channel_input_close); dispatch_set(SSH_MSG_CHANNEL_CLOSE_CONFIRMATION, &channel_input_close_confirmation); dispatch_set(SSH_MSG_CHANNEL_DATA, &channel_input_data); dispatch_set(SSH_MSG_CHANNEL_OPEN_CONFIRMATION, &channel_input_open_confirmation); dispatch_set(SSH_MSG_CHANNEL_OPEN_FAILURE, &channel_input_open_failure); dispatch_set(SSH_MSG_PORT_OPEN, &channel_input_port_open); } static void server_init_dispatch_15(void) { server_init_dispatch_13(); debug("server_init_dispatch_15"); dispatch_set(SSH_MSG_CHANNEL_CLOSE, &channel_input_ieof); dispatch_set(SSH_MSG_CHANNEL_CLOSE_CONFIRMATION, &channel_input_oclose); } static void server_init_dispatch(void) { if (compat20) server_init_dispatch_20(); else if (compat13) server_init_dispatch_13(); else server_init_dispatch_15(); } Index: vendor-crypto/openssh/dist/ssh-agent.1 =================================================================== --- vendor-crypto/openssh/dist/ssh-agent.1 (revision 311912) +++ vendor-crypto/openssh/dist/ssh-agent.1 (revision 311913) @@ -1,217 +1,230 @@ -.\" $OpenBSD: ssh-agent.1,v 1.62 2015/11/15 23:54:15 jmc Exp $ +.\" $OpenBSD: ssh-agent.1,v 1.63 2016/11/30 03:07:37 djm Exp $ .\" .\" Author: Tatu Ylonen .\" Copyright (c) 1995 Tatu Ylonen , Espoo, Finland .\" All rights reserved .\" .\" As far as I am concerned, the code I have written for this software .\" can be used freely for any purpose. Any derived versions of this .\" software must be clearly marked as such, and if the derived work is .\" incompatible with the protocol description in the RFC file, it must be .\" called by a name other than "ssh" or "Secure Shell". .\" .\" Copyright (c) 1999,2000 Markus Friedl. All rights reserved. .\" Copyright (c) 1999 Aaron Campbell. All rights reserved. .\" Copyright (c) 1999 Theo de Raadt. All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" 1. Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" .\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR .\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES .\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. .\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, .\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT .\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, .\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY .\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" .Dd $Mdocdate: November 15 2015 $ .Dt SSH-AGENT 1 .Os .Sh NAME .Nm ssh-agent .Nd authentication agent .Sh SYNOPSIS .Nm ssh-agent .Op Fl c | s .Op Fl \&Dd .Op Fl a Ar bind_address .Op Fl E Ar fingerprint_hash .Op Fl t Ar life +.Op Fl P Ar pkcs11_whitelist .Op Ar command Op Ar arg ... .Nm ssh-agent .Op Fl c | s .Fl k .Sh DESCRIPTION .Nm is a program to hold private keys used for public key authentication (RSA, DSA, ECDSA, Ed25519). .Nm is usually started in the beginning of an X-session or a login session, and all other windows or programs are started as clients to the ssh-agent program. Through use of environment variables the agent can be located and automatically used for authentication when logging in to other machines using .Xr ssh 1 . .Pp The agent initially does not have any private keys. Keys are added using .Xr ssh 1 (see .Cm AddKeysToAgent in .Xr ssh_config 5 for details) or .Xr ssh-add 1 . Multiple identities may be stored in .Nm concurrently and .Xr ssh 1 will automatically use them if present. .Xr ssh-add 1 is also used to remove keys from .Nm and to query the keys that are held in one. .Pp The options are as follows: .Bl -tag -width Ds .It Fl a Ar bind_address Bind the agent to the .Ux Ns -domain socket .Ar bind_address . The default is .Pa $TMPDIR/ssh-XXXXXXXXXX/agent.\*(Ltppid\*(Gt . .It Fl c Generate C-shell commands on .Dv stdout . This is the default if .Ev SHELL looks like it's a csh style of shell. .It Fl D Foreground mode. When this option is specified .Nm will not fork. .It Fl d Debug mode. When this option is specified .Nm will not fork and will write debug information to standard error. .It Fl E Ar fingerprint_hash Specifies the hash algorithm used when displaying key fingerprints. Valid options are: .Dq md5 and .Dq sha256 . The default is .Dq sha256 . .It Fl k Kill the current agent (given by the .Ev SSH_AGENT_PID environment variable). +.It Fl P +Specify a pattern-list of acceptable paths for PKCS#11 shared libraries +that may be added using the +.Fl s +option to +.Xr ssh-add 1 . +The default is to allow loading PKCS#11 libraries from +.Dq /usr/lib/*,/usr/local/lib/* . +PKCS#11 libraries that do not match the whitelist will be refused. +See PATTERNS in +.Xr ssh_config 5 +for a description of pattern-list syntax. .It Fl s Generate Bourne shell commands on .Dv stdout . This is the default if .Ev SHELL does not look like it's a csh style of shell. .It Fl t Ar life Set a default value for the maximum lifetime of identities added to the agent. The lifetime may be specified in seconds or in a time format specified in .Xr sshd_config 5 . A lifetime specified for an identity with .Xr ssh-add 1 overrides this value. Without this option the default maximum lifetime is forever. .El .Pp If a command line is given, this is executed as a subprocess of the agent. When the command dies, so does the agent. .Pp The idea is that the agent is run in the user's local PC, laptop, or terminal. Authentication data need not be stored on any other machine, and authentication passphrases never go over the network. However, the connection to the agent is forwarded over SSH remote logins, and the user can thus use the privileges given by the identities anywhere in the network in a secure way. .Pp There are two main ways to get an agent set up: The first is that the agent starts a new subcommand into which some environment variables are exported, eg .Cm ssh-agent xterm & . The second is that the agent prints the needed shell commands (either .Xr sh 1 or .Xr csh 1 syntax can be generated) which can be evaluated in the calling shell, eg .Cm eval `ssh-agent -s` for Bourne-type shells such as .Xr sh 1 or .Xr ksh 1 and .Cm eval `ssh-agent -c` for .Xr csh 1 and derivatives. .Pp Later .Xr ssh 1 looks at these variables and uses them to establish a connection to the agent. .Pp The agent will never send a private key over its request channel. Instead, operations that require a private key will be performed by the agent, and the result will be returned to the requester. This way, private keys are not exposed to clients using the agent. .Pp A .Ux Ns -domain socket is created and the name of this socket is stored in the .Ev SSH_AUTH_SOCK environment variable. The socket is made accessible only to the current user. This method is easily abused by root or another instance of the same user. .Pp The .Ev SSH_AGENT_PID environment variable holds the agent's process ID. .Pp The agent exits automatically when the command given on the command line terminates. .Sh FILES .Bl -tag -width Ds .It Pa $TMPDIR/ssh-XXXXXXXXXX/agent.\*(Ltppid\*(Gt .Ux Ns -domain sockets used to contain the connection to the authentication agent. These sockets should only be readable by the owner. The sockets should get automatically removed when the agent exits. .El .Sh SEE ALSO .Xr ssh 1 , .Xr ssh-add 1 , .Xr ssh-keygen 1 , .Xr sshd 8 .Sh AUTHORS OpenSSH is a derivative of the original and free ssh 1.2.12 release by Tatu Ylonen. Aaron Campbell, Bob Beck, Markus Friedl, Niels Provos, Theo de Raadt and Dug Song removed many bugs, re-added newer features and created OpenSSH. Markus Friedl contributed the support for SSH protocol versions 1.5 and 2.0. Index: vendor-crypto/openssh/dist/ssh-agent.c =================================================================== --- vendor-crypto/openssh/dist/ssh-agent.c (revision 311912) +++ vendor-crypto/openssh/dist/ssh-agent.c (revision 311913) @@ -1,1438 +1,1465 @@ /* $OpenBSD: ssh-agent.c,v 1.212 2016/02/15 09:47:49 dtucker Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved * The authentication agent program. * * As far as I am concerned, the code I have written for this software * can be used freely for any purpose. Any derived versions of this * software must be clearly marked as such, and if the derived work is * incompatible with the protocol description in the RFC file, it must be * called by a name other than "ssh" or "Secure Shell". * * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "includes.h" #include /* MIN MAX */ #include #include #include #include #include #ifdef HAVE_SYS_TIME_H # include #endif #ifdef HAVE_SYS_UN_H # include #endif #include "openbsd-compat/sys-queue.h" #ifdef WITH_OPENSSL #include #include "openbsd-compat/openssl-compat.h" #endif #include #include #include #ifdef HAVE_PATHS_H # include #endif #include #include #include #include #include #include #include #ifdef HAVE_UTIL_H # include #endif #include "xmalloc.h" #include "ssh.h" #include "rsa.h" #include "sshbuf.h" #include "sshkey.h" #include "authfd.h" #include "compat.h" #include "log.h" #include "misc.h" #include "digest.h" #include "ssherr.h" +#include "match.h" #ifdef ENABLE_PKCS11 #include "ssh-pkcs11.h" #endif +#ifndef DEFAULT_PKCS11_WHITELIST +# define DEFAULT_PKCS11_WHITELIST "/usr/lib/*,/usr/local/lib/*" +#endif + #if defined(HAVE_SYS_PRCTL_H) #include /* For prctl() and PR_SET_DUMPABLE */ #endif typedef enum { AUTH_UNUSED, AUTH_SOCKET, AUTH_CONNECTION } sock_type; typedef struct { int fd; sock_type type; struct sshbuf *input; struct sshbuf *output; struct sshbuf *request; } SocketEntry; u_int sockets_alloc = 0; SocketEntry *sockets = NULL; typedef struct identity { TAILQ_ENTRY(identity) next; struct sshkey *key; char *comment; char *provider; time_t death; u_int confirm; } Identity; typedef struct { int nentries; TAILQ_HEAD(idqueue, identity) idlist; } Idtab; /* private key table, one per protocol version */ Idtab idtable[3]; int max_fd = 0; /* pid of shell == parent of agent */ pid_t parent_pid = -1; time_t parent_alive_interval = 0; /* pid of process for which cleanup_socket is applicable */ pid_t cleanup_pid = 0; /* pathname and directory for AUTH_SOCKET */ char socket_name[PATH_MAX]; char socket_dir[PATH_MAX]; +/* PKCS#11 path whitelist */ +static char *pkcs11_whitelist; + /* locking */ #define LOCK_SIZE 32 #define LOCK_SALT_SIZE 16 #define LOCK_ROUNDS 1 int locked = 0; char lock_passwd[LOCK_SIZE]; char lock_salt[LOCK_SALT_SIZE]; extern char *__progname; /* Default lifetime in seconds (0 == forever) */ static long lifetime = 0; static int fingerprint_hash = SSH_FP_HASH_DEFAULT; static void close_socket(SocketEntry *e) { close(e->fd); e->fd = -1; e->type = AUTH_UNUSED; sshbuf_free(e->input); sshbuf_free(e->output); sshbuf_free(e->request); } static void idtab_init(void) { int i; for (i = 0; i <=2; i++) { TAILQ_INIT(&idtable[i].idlist); idtable[i].nentries = 0; } } /* return private key table for requested protocol version */ static Idtab * idtab_lookup(int version) { if (version < 1 || version > 2) fatal("internal error, bad protocol version %d", version); return &idtable[version]; } static void free_identity(Identity *id) { sshkey_free(id->key); free(id->provider); free(id->comment); free(id); } /* return matching private key for given public key */ static Identity * lookup_identity(struct sshkey *key, int version) { Identity *id; Idtab *tab = idtab_lookup(version); TAILQ_FOREACH(id, &tab->idlist, next) { if (sshkey_equal(key, id->key)) return (id); } return (NULL); } /* Check confirmation of keysign request */ static int confirm_key(Identity *id) { char *p; int ret = -1; p = sshkey_fingerprint(id->key, fingerprint_hash, SSH_FP_DEFAULT); if (p != NULL && ask_permission("Allow use of key %s?\nKey fingerprint %s.", id->comment, p)) ret = 0; free(p); return (ret); } static void send_status(SocketEntry *e, int success) { int r; if ((r = sshbuf_put_u32(e->output, 1)) != 0 || (r = sshbuf_put_u8(e->output, success ? SSH_AGENT_SUCCESS : SSH_AGENT_FAILURE)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); } /* send list of supported public keys to 'client' */ static void process_request_identities(SocketEntry *e, int version) { Idtab *tab = idtab_lookup(version); Identity *id; struct sshbuf *msg; int r; if ((msg = sshbuf_new()) == NULL) fatal("%s: sshbuf_new failed", __func__); if ((r = sshbuf_put_u8(msg, (version == 1) ? SSH_AGENT_RSA_IDENTITIES_ANSWER : SSH2_AGENT_IDENTITIES_ANSWER)) != 0 || (r = sshbuf_put_u32(msg, tab->nentries)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); TAILQ_FOREACH(id, &tab->idlist, next) { if (id->key->type == KEY_RSA1) { #ifdef WITH_SSH1 if ((r = sshbuf_put_u32(msg, BN_num_bits(id->key->rsa->n))) != 0 || (r = sshbuf_put_bignum1(msg, id->key->rsa->e)) != 0 || (r = sshbuf_put_bignum1(msg, id->key->rsa->n)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); #endif } else { u_char *blob; size_t blen; if ((r = sshkey_to_blob(id->key, &blob, &blen)) != 0) { error("%s: sshkey_to_blob: %s", __func__, ssh_err(r)); continue; } if ((r = sshbuf_put_string(msg, blob, blen)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); free(blob); } if ((r = sshbuf_put_cstring(msg, id->comment)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); } if ((r = sshbuf_put_stringb(e->output, msg)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); sshbuf_free(msg); } #ifdef WITH_SSH1 /* ssh1 only */ static void process_authentication_challenge1(SocketEntry *e) { u_char buf[32], mdbuf[16], session_id[16]; u_int response_type; BIGNUM *challenge; Identity *id; int r, len; struct sshbuf *msg; struct ssh_digest_ctx *md; struct sshkey *key; if ((msg = sshbuf_new()) == NULL) fatal("%s: sshbuf_new failed", __func__); if ((key = sshkey_new(KEY_RSA1)) == NULL) fatal("%s: sshkey_new failed", __func__); if ((challenge = BN_new()) == NULL) fatal("%s: BN_new failed", __func__); if ((r = sshbuf_get_u32(e->request, NULL)) != 0 || /* ignored */ (r = sshbuf_get_bignum1(e->request, key->rsa->e)) != 0 || (r = sshbuf_get_bignum1(e->request, key->rsa->n)) != 0 || (r = sshbuf_get_bignum1(e->request, challenge))) fatal("%s: buffer error: %s", __func__, ssh_err(r)); /* Only protocol 1.1 is supported */ if (sshbuf_len(e->request) == 0) goto failure; if ((r = sshbuf_get(e->request, session_id, sizeof(session_id))) != 0 || (r = sshbuf_get_u32(e->request, &response_type)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); if (response_type != 1) goto failure; id = lookup_identity(key, 1); if (id != NULL && (!id->confirm || confirm_key(id) == 0)) { struct sshkey *private = id->key; /* Decrypt the challenge using the private key. */ if ((r = rsa_private_decrypt(challenge, challenge, private->rsa) != 0)) { fatal("%s: rsa_public_encrypt: %s", __func__, ssh_err(r)); goto failure; /* XXX ? */ } /* The response is MD5 of decrypted challenge plus session id */ len = BN_num_bytes(challenge); if (len <= 0 || len > 32) { logit("%s: bad challenge length %d", __func__, len); goto failure; } memset(buf, 0, 32); BN_bn2bin(challenge, buf + 32 - len); if ((md = ssh_digest_start(SSH_DIGEST_MD5)) == NULL || ssh_digest_update(md, buf, 32) < 0 || ssh_digest_update(md, session_id, 16) < 0 || ssh_digest_final(md, mdbuf, sizeof(mdbuf)) < 0) fatal("%s: md5 failed", __func__); ssh_digest_free(md); /* Send the response. */ if ((r = sshbuf_put_u8(msg, SSH_AGENT_RSA_RESPONSE)) != 0 || (r = sshbuf_put(msg, mdbuf, sizeof(mdbuf))) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); goto send; } failure: /* Unknown identity or protocol error. Send failure. */ if ((r = sshbuf_put_u8(msg, SSH_AGENT_FAILURE)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); send: if ((r = sshbuf_put_stringb(e->output, msg)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); sshkey_free(key); BN_clear_free(challenge); sshbuf_free(msg); } #endif static char * agent_decode_alg(struct sshkey *key, u_int flags) { if (key->type == KEY_RSA) { if (flags & SSH_AGENT_RSA_SHA2_256) return "rsa-sha2-256"; else if (flags & SSH_AGENT_RSA_SHA2_512) return "rsa-sha2-512"; } return NULL; } /* ssh2 only */ static void process_sign_request2(SocketEntry *e) { u_char *blob, *data, *signature = NULL; size_t blen, dlen, slen = 0; u_int compat = 0, flags; int r, ok = -1; struct sshbuf *msg; struct sshkey *key; struct identity *id; if ((msg = sshbuf_new()) == NULL) fatal("%s: sshbuf_new failed", __func__); if ((r = sshbuf_get_string(e->request, &blob, &blen)) != 0 || (r = sshbuf_get_string(e->request, &data, &dlen)) != 0 || (r = sshbuf_get_u32(e->request, &flags)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); if (flags & SSH_AGENT_OLD_SIGNATURE) compat = SSH_BUG_SIGBLOB; if ((r = sshkey_from_blob(blob, blen, &key)) != 0) { error("%s: cannot parse key blob: %s", __func__, ssh_err(r)); goto send; } if ((id = lookup_identity(key, 2)) == NULL) { verbose("%s: %s key not found", __func__, sshkey_type(key)); goto send; } if (id->confirm && confirm_key(id) != 0) { verbose("%s: user refused key", __func__); goto send; } if ((r = sshkey_sign(id->key, &signature, &slen, data, dlen, agent_decode_alg(key, flags), compat)) != 0) { error("%s: sshkey_sign: %s", __func__, ssh_err(r)); goto send; } /* Success */ ok = 0; send: sshkey_free(key); if (ok == 0) { if ((r = sshbuf_put_u8(msg, SSH2_AGENT_SIGN_RESPONSE)) != 0 || (r = sshbuf_put_string(msg, signature, slen)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); } else if ((r = sshbuf_put_u8(msg, SSH_AGENT_FAILURE)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); if ((r = sshbuf_put_stringb(e->output, msg)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); sshbuf_free(msg); free(data); free(blob); free(signature); } /* shared */ static void process_remove_identity(SocketEntry *e, int version) { size_t blen; int r, success = 0; struct sshkey *key = NULL; u_char *blob; #ifdef WITH_SSH1 u_int bits; #endif /* WITH_SSH1 */ switch (version) { #ifdef WITH_SSH1 case 1: if ((key = sshkey_new(KEY_RSA1)) == NULL) { error("%s: sshkey_new failed", __func__); return; } if ((r = sshbuf_get_u32(e->request, &bits)) != 0 || (r = sshbuf_get_bignum1(e->request, key->rsa->e)) != 0 || (r = sshbuf_get_bignum1(e->request, key->rsa->n)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); if (bits != sshkey_size(key)) logit("Warning: identity keysize mismatch: " "actual %u, announced %u", sshkey_size(key), bits); break; #endif /* WITH_SSH1 */ case 2: if ((r = sshbuf_get_string(e->request, &blob, &blen)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); if ((r = sshkey_from_blob(blob, blen, &key)) != 0) error("%s: sshkey_from_blob failed: %s", __func__, ssh_err(r)); free(blob); break; } if (key != NULL) { Identity *id = lookup_identity(key, 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 * the array, we actually free the key there and move * all the entries between the empty slot and the end * of the array. */ Idtab *tab = idtab_lookup(version); if (tab->nentries < 1) fatal("process_remove_identity: " "internal error: tab->nentries %d", tab->nentries); TAILQ_REMOVE(&tab->idlist, id, next); free_identity(id); tab->nentries--; success = 1; } sshkey_free(key); } send_status(e, success); } static void process_remove_all_identities(SocketEntry *e, int version) { Idtab *tab = idtab_lookup(version); Identity *id; /* Loop over all identities and clear the keys. */ for (id = TAILQ_FIRST(&tab->idlist); id; id = TAILQ_FIRST(&tab->idlist)) { TAILQ_REMOVE(&tab->idlist, id, next); free_identity(id); } /* Mark that there are no identities. */ tab->nentries = 0; /* Send success. */ send_status(e, 1); } /* removes expired keys and returns number of seconds until the next expiry */ static time_t reaper(void) { time_t deadline = 0, now = monotime(); Identity *id, *nxt; int version; Idtab *tab; for (version = 1; version < 3; version++) { tab = idtab_lookup(version); for (id = TAILQ_FIRST(&tab->idlist); id; id = nxt) { nxt = TAILQ_NEXT(id, next); 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); } /* * XXX this and the corresponding serialisation function probably belongs * in key.c */ #ifdef WITH_SSH1 static int agent_decode_rsa1(struct sshbuf *m, struct sshkey **kp) { struct sshkey *k = NULL; int r = SSH_ERR_INTERNAL_ERROR; *kp = NULL; if ((k = sshkey_new_private(KEY_RSA1)) == NULL) return SSH_ERR_ALLOC_FAIL; if ((r = sshbuf_get_u32(m, NULL)) != 0 || /* ignored */ (r = sshbuf_get_bignum1(m, k->rsa->n)) != 0 || (r = sshbuf_get_bignum1(m, k->rsa->e)) != 0 || (r = sshbuf_get_bignum1(m, k->rsa->d)) != 0 || (r = sshbuf_get_bignum1(m, k->rsa->iqmp)) != 0 || /* SSH1 and SSL have p and q swapped */ (r = sshbuf_get_bignum1(m, k->rsa->q)) != 0 || /* p */ (r = sshbuf_get_bignum1(m, k->rsa->p)) != 0) /* q */ goto out; /* Generate additional parameters */ if ((r = rsa_generate_additional_parameters(k->rsa)) != 0) goto out; /* enable blinding */ if (RSA_blinding_on(k->rsa, NULL) != 1) { r = SSH_ERR_LIBCRYPTO_ERROR; goto out; } r = 0; /* success */ out: if (r == 0) *kp = k; else sshkey_free(k); return r; } #endif /* WITH_SSH1 */ static void process_add_identity(SocketEntry *e, int version) { Idtab *tab = idtab_lookup(version); Identity *id; int success = 0, confirm = 0; u_int seconds; char *comment = NULL; time_t death = 0; struct sshkey *k = NULL; u_char ctype; int r = SSH_ERR_INTERNAL_ERROR; switch (version) { #ifdef WITH_SSH1 case 1: r = agent_decode_rsa1(e->request, &k); break; #endif /* WITH_SSH1 */ case 2: r = sshkey_private_deserialize(e->request, &k); break; } if (r != 0 || k == NULL || (r = sshbuf_get_cstring(e->request, &comment, NULL)) != 0) { error("%s: decode private key: %s", __func__, ssh_err(r)); goto err; } while (sshbuf_len(e->request)) { if ((r = sshbuf_get_u8(e->request, &ctype)) != 0) { error("%s: buffer error: %s", __func__, ssh_err(r)); goto err; } switch (ctype) { case SSH_AGENT_CONSTRAIN_LIFETIME: if ((r = sshbuf_get_u32(e->request, &seconds)) != 0) { error("%s: bad lifetime constraint: %s", __func__, ssh_err(r)); goto err; } death = monotime() + seconds; break; case SSH_AGENT_CONSTRAIN_CONFIRM: confirm = 1; break; default: error("%s: Unknown constraint %d", __func__, ctype); err: sshbuf_reset(e->request); free(comment); sshkey_free(k); goto send; } } success = 1; if (lifetime && !death) death = monotime() + lifetime; if ((id = lookup_identity(k, version)) == NULL) { id = xcalloc(1, sizeof(Identity)); id->key = k; TAILQ_INSERT_TAIL(&tab->idlist, id, next); /* Increment the number of identities. */ tab->nentries++; } else { sshkey_free(k); free(id->comment); } id->comment = comment; id->death = death; id->confirm = confirm; send: send_status(e, success); } /* XXX todo: encrypt sensitive data with passphrase */ static void process_lock_agent(SocketEntry *e, int lock) { int r, success = 0, delay; char *passwd, passwdhash[LOCK_SIZE]; static u_int fail_count = 0; size_t pwlen; if ((r = sshbuf_get_cstring(e->request, &passwd, &pwlen)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); if (pwlen == 0) { debug("empty password not supported"); } else if (locked && !lock) { if (bcrypt_pbkdf(passwd, pwlen, lock_salt, sizeof(lock_salt), passwdhash, sizeof(passwdhash), LOCK_ROUNDS) < 0) fatal("bcrypt_pbkdf"); if (timingsafe_bcmp(passwdhash, lock_passwd, LOCK_SIZE) == 0) { debug("agent unlocked"); locked = 0; fail_count = 0; explicit_bzero(lock_passwd, sizeof(lock_passwd)); success = 1; } else { /* delay in 0.1s increments up to 10s */ if (fail_count < 100) fail_count++; delay = 100000 * fail_count; debug("unlock failed, delaying %0.1lf seconds", (double)delay/1000000); usleep(delay); } explicit_bzero(passwdhash, sizeof(passwdhash)); } else if (!locked && lock) { debug("agent locked"); locked = 1; arc4random_buf(lock_salt, sizeof(lock_salt)); if (bcrypt_pbkdf(passwd, pwlen, lock_salt, sizeof(lock_salt), lock_passwd, sizeof(lock_passwd), LOCK_ROUNDS) < 0) fatal("bcrypt_pbkdf"); success = 1; } explicit_bzero(passwd, pwlen); free(passwd); send_status(e, success); } static void no_identities(SocketEntry *e, u_int type) { struct sshbuf *msg; int r; if ((msg = sshbuf_new()) == NULL) fatal("%s: sshbuf_new failed", __func__); if ((r = sshbuf_put_u8(msg, (type == SSH_AGENTC_REQUEST_RSA_IDENTITIES) ? SSH_AGENT_RSA_IDENTITIES_ANSWER : SSH2_AGENT_IDENTITIES_ANSWER)) != 0 || (r = sshbuf_put_u32(msg, 0)) != 0 || (r = sshbuf_put_stringb(e->output, msg)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); sshbuf_free(msg); } #ifdef ENABLE_PKCS11 static void process_add_smartcard_key(SocketEntry *e) { - char *provider = NULL, *pin; + char *provider = NULL, *pin, canonical_provider[PATH_MAX]; int r, i, version, count = 0, success = 0, confirm = 0; u_int seconds; time_t death = 0; u_char type; struct sshkey **keys = NULL, *k; Identity *id; Idtab *tab; if ((r = sshbuf_get_cstring(e->request, &provider, NULL)) != 0 || (r = sshbuf_get_cstring(e->request, &pin, NULL)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); while (sshbuf_len(e->request)) { if ((r = sshbuf_get_u8(e->request, &type)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); switch (type) { case SSH_AGENT_CONSTRAIN_LIFETIME: if ((r = sshbuf_get_u32(e->request, &seconds)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); death = monotime() + seconds; break; case SSH_AGENT_CONSTRAIN_CONFIRM: confirm = 1; break; default: error("process_add_smartcard_key: " "Unknown constraint type %d", type); goto send; } } + if (realpath(provider, canonical_provider) == NULL) { + verbose("failed PKCS#11 add of \"%.100s\": realpath: %s", + provider, strerror(errno)); + goto send; + } + if (match_pattern_list(canonical_provider, pkcs11_whitelist, 0) != 1) { + verbose("refusing PKCS#11 add of \"%.100s\": " + "provider not whitelisted", canonical_provider); + goto send; + } + debug("%s: add %.100s", __func__, canonical_provider); if (lifetime && !death) death = monotime() + lifetime; - count = pkcs11_add_provider(provider, pin, &keys); + count = pkcs11_add_provider(canonical_provider, pin, &keys); for (i = 0; i < count; i++) { k = keys[i]; version = k->type == KEY_RSA1 ? 1 : 2; tab = idtab_lookup(version); if (lookup_identity(k, version) == NULL) { id = xcalloc(1, sizeof(Identity)); id->key = k; - id->provider = xstrdup(provider); - id->comment = xstrdup(provider); /* XXX */ + id->provider = xstrdup(canonical_provider); + id->comment = xstrdup(canonical_provider); /* XXX */ id->death = death; id->confirm = confirm; TAILQ_INSERT_TAIL(&tab->idlist, id, next); tab->nentries++; success = 1; } else { sshkey_free(k); } keys[i] = NULL; } send: free(pin); free(provider); free(keys); send_status(e, success); } static void process_remove_smartcard_key(SocketEntry *e) { char *provider = NULL, *pin = NULL; int r, version, success = 0; Identity *id, *nxt; Idtab *tab; if ((r = sshbuf_get_cstring(e->request, &provider, NULL)) != 0 || (r = sshbuf_get_cstring(e->request, &pin, NULL)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); free(pin); for (version = 1; version < 3; version++) { tab = idtab_lookup(version); for (id = TAILQ_FIRST(&tab->idlist); id; id = nxt) { nxt = TAILQ_NEXT(id, next); /* Skip file--based keys */ if (id->provider == NULL) continue; if (!strcmp(provider, id->provider)) { TAILQ_REMOVE(&tab->idlist, id, next); free_identity(id); tab->nentries--; } } } if (pkcs11_del_provider(provider) == 0) success = 1; else error("process_remove_smartcard_key:" " pkcs11_del_provider failed"); free(provider); send_status(e, success); } #endif /* ENABLE_PKCS11 */ /* dispatch incoming messages */ static void process_message(SocketEntry *e) { u_int msg_len; u_char type; const u_char *cp; int r; if (sshbuf_len(e->input) < 5) return; /* Incomplete message. */ cp = sshbuf_ptr(e->input); msg_len = PEEK_U32(cp); if (msg_len > 256 * 1024) { close_socket(e); return; } if (sshbuf_len(e->input) < msg_len + 4) return; /* move the current input to e->request */ sshbuf_reset(e->request); if ((r = sshbuf_get_stringb(e->input, e->request)) != 0 || (r = sshbuf_get_u8(e->request, &type)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); /* check wheter agent is locked */ if (locked && type != SSH_AGENTC_UNLOCK) { sshbuf_reset(e->request); switch (type) { case SSH_AGENTC_REQUEST_RSA_IDENTITIES: case SSH2_AGENTC_REQUEST_IDENTITIES: /* send empty lists */ no_identities(e, type); break; default: /* send a fail message for all other request types */ send_status(e, 0); } return; } debug("type %d", type); switch (type) { case SSH_AGENTC_LOCK: case SSH_AGENTC_UNLOCK: process_lock_agent(e, type == SSH_AGENTC_LOCK); break; #ifdef WITH_SSH1 /* ssh1 */ case SSH_AGENTC_RSA_CHALLENGE: process_authentication_challenge1(e); break; case SSH_AGENTC_REQUEST_RSA_IDENTITIES: process_request_identities(e, 1); break; case SSH_AGENTC_ADD_RSA_IDENTITY: case SSH_AGENTC_ADD_RSA_ID_CONSTRAINED: process_add_identity(e, 1); break; case SSH_AGENTC_REMOVE_RSA_IDENTITY: process_remove_identity(e, 1); break; #endif case SSH_AGENTC_REMOVE_ALL_RSA_IDENTITIES: process_remove_all_identities(e, 1); /* safe for !WITH_SSH1 */ break; /* ssh2 */ case SSH2_AGENTC_SIGN_REQUEST: process_sign_request2(e); break; case SSH2_AGENTC_REQUEST_IDENTITIES: process_request_identities(e, 2); break; case SSH2_AGENTC_ADD_IDENTITY: case SSH2_AGENTC_ADD_ID_CONSTRAINED: process_add_identity(e, 2); break; case SSH2_AGENTC_REMOVE_IDENTITY: process_remove_identity(e, 2); break; case SSH2_AGENTC_REMOVE_ALL_IDENTITIES: process_remove_all_identities(e, 2); break; #ifdef ENABLE_PKCS11 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: process_remove_smartcard_key(e); break; #endif /* ENABLE_PKCS11 */ default: /* Unknown message. Respond with failure. */ error("Unknown message %d", type); sshbuf_reset(e->request); send_status(e, 0); break; } } static void new_socket(sock_type type, int fd) { u_int i, old_alloc, new_alloc; set_nonblock(fd); if (fd > max_fd) max_fd = fd; for (i = 0; i < sockets_alloc; i++) if (sockets[i].type == AUTH_UNUSED) { sockets[i].fd = fd; if ((sockets[i].input = sshbuf_new()) == NULL) fatal("%s: sshbuf_new failed", __func__); if ((sockets[i].output = sshbuf_new()) == NULL) fatal("%s: sshbuf_new failed", __func__); if ((sockets[i].request = sshbuf_new()) == NULL) fatal("%s: sshbuf_new failed", __func__); sockets[i].type = type; return; } old_alloc = sockets_alloc; new_alloc = sockets_alloc + 10; sockets = xreallocarray(sockets, new_alloc, sizeof(sockets[0])); for (i = old_alloc; i < new_alloc; i++) sockets[i].type = AUTH_UNUSED; sockets_alloc = new_alloc; sockets[old_alloc].fd = fd; if ((sockets[old_alloc].input = sshbuf_new()) == NULL) fatal("%s: sshbuf_new failed", __func__); if ((sockets[old_alloc].output = sshbuf_new()) == NULL) fatal("%s: sshbuf_new failed", __func__); if ((sockets[old_alloc].request = sshbuf_new()) == NULL) fatal("%s: sshbuf_new failed", __func__); sockets[old_alloc].type = type; } static int prepare_select(fd_set **fdrp, fd_set **fdwp, int *fdl, u_int *nallocp, struct timeval **tvpp) { u_int i, sz; int n = 0; static struct timeval tv; time_t deadline; for (i = 0; i < sockets_alloc; i++) { switch (sockets[i].type) { case AUTH_SOCKET: case AUTH_CONNECTION: n = MAX(n, sockets[i].fd); break; case AUTH_UNUSED: break; default: fatal("Unknown socket type %d", sockets[i].type); break; } } sz = howmany(n+1, NFDBITS) * sizeof(fd_mask); if (*fdrp == NULL || sz > *nallocp) { free(*fdrp); free(*fdwp); *fdrp = xmalloc(sz); *fdwp = xmalloc(sz); *nallocp = sz; } if (n < *fdl) debug("XXX shrink: %d < %d", n, *fdl); *fdl = n; memset(*fdrp, 0, sz); memset(*fdwp, 0, sz); for (i = 0; i < sockets_alloc; i++) { switch (sockets[i].type) { case AUTH_SOCKET: case AUTH_CONNECTION: FD_SET(sockets[i].fd, *fdrp); if (sshbuf_len(sockets[i].output) > 0) FD_SET(sockets[i].fd, *fdwp); break; default: 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); } static void after_select(fd_set *readset, fd_set *writeset) { struct sockaddr_un sunaddr; socklen_t slen; char buf[1024]; int len, sock, r; u_int i, orig_alloc; uid_t euid; gid_t egid; for (i = 0, orig_alloc = sockets_alloc; i < orig_alloc; i++) switch (sockets[i].type) { case AUTH_UNUSED: break; case AUTH_SOCKET: if (FD_ISSET(sockets[i].fd, readset)) { slen = sizeof(sunaddr); sock = accept(sockets[i].fd, (struct sockaddr *)&sunaddr, &slen); if (sock < 0) { error("accept from AUTH_SOCKET: %s", strerror(errno)); break; } if (getpeereid(sock, &euid, &egid) < 0) { error("getpeereid %d failed: %s", sock, strerror(errno)); close(sock); break; } if ((euid != 0) && (getuid() != euid)) { error("uid mismatch: " "peer euid %u != uid %u", (u_int) euid, (u_int) getuid()); close(sock); break; } new_socket(AUTH_CONNECTION, sock); } break; case AUTH_CONNECTION: if (sshbuf_len(sockets[i].output) > 0 && FD_ISSET(sockets[i].fd, writeset)) { len = write(sockets[i].fd, sshbuf_ptr(sockets[i].output), sshbuf_len(sockets[i].output)); if (len == -1 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)) continue; if (len <= 0) { close_socket(&sockets[i]); break; } if ((r = sshbuf_consume(sockets[i].output, len)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); } if (FD_ISSET(sockets[i].fd, readset)) { 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; } if ((r = sshbuf_put(sockets[i].input, buf, len)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); explicit_bzero(buf, sizeof(buf)); process_message(&sockets[i]); } break; default: fatal("Unknown type %d", sockets[i].type); } } static void cleanup_socket(void) { if (cleanup_pid != 0 && getpid() != cleanup_pid) return; debug("%s: cleanup", __func__); if (socket_name[0]) unlink(socket_name); if (socket_dir[0]) rmdir(socket_dir); } void cleanup_exit(int i) { cleanup_socket(); _exit(i); } /*ARGSUSED*/ static void cleanup_handler(int sig) { cleanup_socket(); #ifdef ENABLE_PKCS11 pkcs11_terminate(); #endif _exit(2); } static void check_parent_exists(void) { /* * If our parent has exited then getppid() will return (pid_t)1, * so testing for that should be safe. */ if (parent_pid != -1 && getppid() != parent_pid) { /* printf("Parent has died - Authentication agent exiting.\n"); */ cleanup_socket(); _exit(2); } } static void usage(void) { fprintf(stderr, "usage: ssh-agent [-c | -s] [-Dd] [-a bind_address] [-E fingerprint_hash]\n" - " [-t life] [command [arg ...]]\n" + " [-P pkcs11_whitelist] [-t life] [command [arg ...]]\n" " ssh-agent [-c | -s] -k\n"); exit(1); } int main(int ac, char **av) { int c_flag = 0, d_flag = 0, D_flag = 0, k_flag = 0, s_flag = 0; int sock, fd, ch, result, saved_errno; u_int nalloc; char *shell, *format, *pidstr, *agentsocket = NULL; fd_set *readsetp = NULL, *writesetp = NULL; #ifdef HAVE_SETRLIMIT struct rlimit rlim; #endif extern int optind; extern char *optarg; pid_t pid; char pidstrbuf[1 + 3 * sizeof pid]; struct timeval *tvp = NULL; size_t len; mode_t prev_mask; ssh_malloc_init(); /* must be called before any mallocs */ /* 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 #ifdef WITH_OPENSSL OpenSSL_add_all_algorithms(); #endif __progname = ssh_get_progname(av[0]); seed_rng(); - while ((ch = getopt(ac, av, "cDdksE:a:t:")) != -1) { + while ((ch = getopt(ac, av, "cDdksE:a:P:t:")) != -1) { switch (ch) { case 'E': fingerprint_hash = ssh_digest_alg_by_name(optarg); if (fingerprint_hash == -1) fatal("Invalid hash algorithm \"%s\"", optarg); break; case 'c': if (s_flag) usage(); c_flag++; break; case 'k': k_flag++; break; + case 'P': + if (pkcs11_whitelist != NULL) + fatal("-P option already specified"); + pkcs11_whitelist = xstrdup(optarg); + break; case 's': if (c_flag) usage(); s_flag++; break; case 'd': if (d_flag || D_flag) usage(); d_flag++; break; case 'D': if (d_flag || D_flag) usage(); D_flag++; break; case 'a': agentsocket = optarg; break; case 't': if ((lifetime = convtime(optarg)) == -1) { fprintf(stderr, "Invalid lifetime\n"); usage(); } break; default: usage(); } } ac -= optind; av += optind; if (ac > 0 && (c_flag || k_flag || s_flag || d_flag || D_flag)) usage(); + if (pkcs11_whitelist == NULL) + pkcs11_whitelist = xstrdup(DEFAULT_PKCS11_WHITELIST); + if (ac == 0 && !c_flag && !s_flag) { shell = getenv("SHELL"); 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 = (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) { perror("kill"); exit(1); } format = c_flag ? "unsetenv %s;\n" : "unset %s;\n"; printf(format, SSH_AUTHSOCKET_ENV_NAME); printf(format, SSH_AGENTPID_ENV_NAME); printf("echo Agent pid %ld killed;\n", (long)pid); exit(0); } parent_pid = getpid(); if (agentsocket == NULL) { /* Create private directory for agent socket */ mktemp_proto(socket_dir, sizeof(socket_dir)); if (mkdtemp(socket_dir) == NULL) { perror("mkdtemp: private socket dir"); exit(1); } snprintf(socket_name, sizeof socket_name, "%s/agent.%ld", socket_dir, (long)parent_pid); } else { /* Try to use specified agent socket */ socket_dir[0] = '\0'; strlcpy(socket_name, agentsocket, sizeof socket_name); } /* * Create socket early so it will exist before command gets run from * the parent. */ prev_mask = umask(0177); sock = unix_listener(socket_name, SSH_LISTEN_BACKLOG, 0); if (sock < 0) { /* XXX - unix_listener() calls error() not perror() */ *socket_name = '\0'; /* Don't unlink any existing file */ cleanup_exit(1); } umask(prev_mask); /* * Fork, and have the parent execute the command, if any, or present * the socket data. The child continues as the authentication agent. */ if (D_flag || d_flag) { log_init(__progname, d_flag ? SYSLOG_LEVEL_DEBUG3 : SYSLOG_LEVEL_INFO, SYSLOG_FACILITY_AUTH, 1); format = c_flag ? "setenv %s %s;\n" : "%s=%s; export %s;\n"; printf(format, SSH_AUTHSOCKET_ENV_NAME, socket_name, SSH_AUTHSOCKET_ENV_NAME); printf("echo Agent pid %ld;\n", (long)parent_pid); fflush(stdout); goto skip; } pid = fork(); if (pid == -1) { perror("fork"); cleanup_exit(1); } if (pid != 0) { /* Parent - execute the given command. */ close(sock); snprintf(pidstrbuf, sizeof pidstrbuf, "%ld", (long)pid); if (ac == 0) { format = c_flag ? "setenv %s %s;\n" : "%s=%s; export %s;\n"; printf(format, SSH_AUTHSOCKET_ENV_NAME, socket_name, SSH_AUTHSOCKET_ENV_NAME); printf(format, SSH_AGENTPID_ENV_NAME, pidstrbuf, SSH_AGENTPID_ENV_NAME); printf("echo Agent pid %ld;\n", (long)pid); exit(0); } if (setenv(SSH_AUTHSOCKET_ENV_NAME, socket_name, 1) == -1 || setenv(SSH_AGENTPID_ENV_NAME, pidstrbuf, 1) == -1) { perror("setenv"); exit(1); } execvp(av[0], av); perror(av[0]); exit(1); } /* child */ log_init(__progname, SYSLOG_LEVEL_INFO, SYSLOG_FACILITY_AUTH, 0); if (setsid() == -1) { error("setsid: %s", strerror(errno)); cleanup_exit(1); } (void)chdir("/"); if ((fd = open(_PATH_DEVNULL, O_RDWR, 0)) != -1) { /* XXX might close listen socket */ (void)dup2(fd, STDIN_FILENO); (void)dup2(fd, STDOUT_FILENO); (void)dup2(fd, STDERR_FILENO); if (fd > 2) close(fd); } #ifdef HAVE_SETRLIMIT /* deny core dumps, since memory contains unencrypted private keys */ rlim.rlim_cur = rlim.rlim_max = 0; if (setrlimit(RLIMIT_CORE, &rlim) < 0) { error("setrlimit RLIMIT_CORE: %s", strerror(errno)); cleanup_exit(1); } #endif skip: cleanup_pid = getpid(); #ifdef ENABLE_PKCS11 pkcs11_init(0); #endif new_socket(AUTH_SOCKET, sock); if (ac > 0) parent_alive_interval = 10; idtab_init(); signal(SIGPIPE, SIG_IGN); signal(SIGINT, (d_flag | D_flag) ? cleanup_handler : SIG_IGN); signal(SIGHUP, cleanup_handler); signal(SIGTERM, cleanup_handler); nalloc = 0; - if (pledge("stdio cpath unix id proc exec", NULL) == -1) + if (pledge("stdio rpath cpath unix id proc exec", NULL) == -1) fatal("%s: pledge: %s", __progname, strerror(errno)); platform_pledge_agent(); while (1) { 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(saved_errno)); } else if (result > 0) after_select(readsetp, writesetp); } /* NOTREACHED */ }