diff options
Diffstat (limited to 'sshd.c')
-rw-r--r-- | sshd.c | 1703 |
1 files changed, 513 insertions, 1190 deletions
@@ -1,23 +1,5 @@ -/* $OpenBSD: sshd.c,v 1.602 2024/01/08 00:34:34 djm Exp $ */ +/* $OpenBSD: sshd.c,v 1.609 2024/06/27 23:01:15 djm Exp $ */ /* - * Author: Tatu Ylonen <ylo@cs.hut.fi> - * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland - * All rights reserved - * This program is the ssh daemon. It listens for connections from clients, - * and performs authentication, executes use commands or shell, and forwards - * information to/from the application to the user client over an encrypted - * connection. This can also handle forwarding of X11, TCP/IP, and - * authentication agent connections. - * - * 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 implementation: - * Privilege Separation: - * * Copyright (c) 2000, 2001, 2002 Markus Friedl. All rights reserved. * Copyright (c) 2002 Niels Provos. All rights reserved. * @@ -77,8 +59,7 @@ #include <limits.h> #ifdef WITH_OPENSSL -#include <openssl/dh.h> -#include <openssl/bn.h> +#include <openssl/evp.h> #include <openssl/rand.h> #include "openbsd-compat/openssl-compat.h" #endif @@ -90,43 +71,26 @@ #include "xmalloc.h" #include "ssh.h" -#include "ssh2.h" #include "sshpty.h" -#include "packet.h" #include "log.h" #include "sshbuf.h" #include "misc.h" -#include "match.h" #include "servconf.h" -#include "uidswap.h" #include "compat.h" -#include "cipher.h" #include "digest.h" #include "sshkey.h" -#include "kex.h" #include "authfile.h" #include "pathnames.h" -#include "atomicio.h" #include "canohost.h" #include "hostfile.h" #include "auth.h" #include "authfd.h" #include "msg.h" -#include "dispatch.h" -#include "channels.h" -#include "session.h" -#include "monitor.h" -#ifdef GSSAPI -#include "ssh-gss.h" -#endif -#include "monitor_wrap.h" -#include "ssh-sandbox.h" -#include "auth-options.h" #include "version.h" #include "ssherr.h" #include "sk-api.h" +#include "addr.h" #include "srclimit.h" -#include "dh.h" /* Re-exec fds */ #define REEXEC_DEVCRYPTO_RESERVED_FD (STDERR_FILENO + 1) @@ -139,9 +103,6 @@ extern char *__progname; /* Server configuration options. */ ServerOptions options; -/* Name of the server configuration file. */ -char *config_file_name = _PATH_SERVER_CONFIG_FILE; - /* * Debug mode flag. This can be set on the command line. If debug * mode is enabled, extra debugging output will be sent to the system @@ -150,33 +111,10 @@ char *config_file_name = _PATH_SERVER_CONFIG_FILE; */ int debug_flag = 0; -/* - * Indicating that the daemon should only test the configuration and keys. - * If test_flag > 1 ("-T" flag), then sshd will also dump the effective - * configuration, optionally using connection information provided by the - * "-C" flag. - */ -static int test_flag = 0; - -/* Flag indicating that the daemon is being started from inetd. */ -static int inetd_flag = 0; - -/* Flag indicating that sshd should not detach and become a daemon. */ -static int no_daemon_flag = 0; - -/* debug goes to stderr unless inetd_flag is set */ -static int log_stderr = 0; - /* Saved arguments to main(). */ static char **saved_argv; static int saved_argc; -/* re-exec */ -static int rexeced_flag = 0; -static int rexec_flag = 1; -static int rexec_argc = 0; -static char **rexec_argv; - /* * The sockets that the server is listening; this is used in the SIGHUP * signal handler. @@ -185,10 +123,6 @@ static char **rexec_argv; static int listen_socks[MAX_LISTEN_SOCKS]; static int num_listen_socks = 0; -/* Daemon's agent connection */ -int auth_sock = -1; -static int have_agent = 0; - /* * Any really sensitive data in the application is contained in this * structure. The idea is that this structure could be locked into memory so @@ -205,6 +139,8 @@ struct { } sensitive_data; /* This is set to true when a signal is received. */ +static volatile sig_atomic_t received_siginfo = 0; +static volatile sig_atomic_t received_sigchld = 0; static volatile sig_atomic_t received_sighup = 0; static volatile sig_atomic_t received_sigterm = 0; @@ -212,8 +148,9 @@ static volatile sig_atomic_t received_sigterm = 0; u_int utmp_len = HOST_NAME_MAX+1; /* - * startup_pipes/flags are used for tracking children of the listening sshd - * process early in their lifespans. This tracking is needed for three things: + * The early_child/children array below is used for tracking children of the + * listening sshd process early in their lifespans, before they have + * completed authentication. This tracking is needed for four things: * * 1) Implementing the MaxStartups limit of concurrent unauthenticated * connections. @@ -222,29 +159,33 @@ u_int utmp_len = HOST_NAME_MAX+1; * after it restarts. * 3) Ensuring that rexec'd sshd processes have received their initial state * from the parent listen process before handling SIGHUP. + * 4) Tracking and logging unsuccessful exits from the preauth sshd monitor, + * including and especially those for LoginGraceTime timeouts. * * Child processes signal that they have completed closure of the listen_socks * and (if applicable) received their rexec state by sending a char over their - * sock. Child processes signal that authentication has completed by closing - * the sock (or by exiting). + * sock. + * + * Child processes signal that authentication has completed by sending a + * second char over the socket before closing it, otherwise the listener will + * continue tracking the child (and using up a MaxStartups slot) until the + * preauth subprocess exits, whereupon the listener will log its exit status. + * preauth processes will exit with a status of EXIT_LOGIN_GRACE to indicate + * they did not authenticate before the LoginGraceTime alarm fired. */ -static int *startup_pipes = NULL; -static int *startup_flags = NULL; /* Indicates child closed listener */ +struct early_child { + int pipefd; + int early; /* Indicates child closed listener */ + char *id; /* human readable connection identifier */ + pid_t pid; + struct xaddr addr; + int have_addr; + int status, have_status; +}; +static struct early_child *children; +static int children_active; static int startup_pipe = -1; /* in child */ -/* variables used for privilege separation */ -int use_privsep = -1; -struct monitor *pmonitor = NULL; -int privsep_is_preauth = 1; -static int privsep_chroot = 1; - -/* global connection state and authentication contexts */ -Authctxt *the_authctxt = NULL; -struct ssh *the_active_state; - -/* global key/cert auth options. XXX move to permanent ssh->authctxt? */ -struct sshauthopt *auth_opts = NULL; - /* sshd_config buffer */ struct sshbuf *cfg; @@ -257,11 +198,6 @@ struct sshbuf *loginmsg; /* Unprivileged user */ struct passwd *privsep_pw = NULL; -/* Prototypes for various functions defined later in this file. */ -void destroy_sensitive_data(void); -void demote_sensitive_data(void); -static void do_ssh2_kex(struct ssh *); - static char *listener_proctitle; /* @@ -277,531 +213,310 @@ close_listen_socks(void) num_listen_socks = 0; } +/* Allocate and initialise the children array */ static void -close_startup_pipes(void) +child_alloc(void) { int i; - if (startup_pipes) - for (i = 0; i < options.max_startups; i++) - if (startup_pipes[i] != -1) - close(startup_pipes[i]); -} - -/* - * Signal handler for SIGHUP. Sshd execs itself when it receives SIGHUP; - * the effect is to reread the configuration file (and to regenerate - * the server key). - */ - -static void -sighup_handler(int sig) -{ - received_sighup = 1; + children = xcalloc(options.max_startups, sizeof(*children)); + for (i = 0; i < options.max_startups; i++) { + children[i].pipefd = -1; + children[i].pid = -1; + } } -/* - * Called from the main program after receiving SIGHUP. - * Restarts the server. - */ -static void -sighup_restart(void) +/* Register a new connection in the children array; child pid comes later */ +static struct early_child * +child_register(int pipefd, int sockfd) { - logit("Received SIGHUP; restarting."); - if (options.pid_file != NULL) - unlink(options.pid_file); - platform_pre_restart(); - close_listen_socks(); - close_startup_pipes(); - ssh_signal(SIGHUP, SIG_IGN); /* will be restored after exec */ - execv(saved_argv[0], saved_argv); - logit("RESTART FAILED: av[0]='%.100s', error: %.100s.", saved_argv[0], - strerror(errno)); - exit(1); -} + int i, lport, rport; + char *laddr = NULL, *raddr = NULL; + struct early_child *child = NULL; + struct sockaddr_storage addr; + socklen_t addrlen = sizeof(addr); + struct sockaddr *sa = (struct sockaddr *)&addr; + + for (i = 0; i < options.max_startups; i++) { + if (children[i].pipefd != -1 || children[i].pid > 0) + continue; + child = &(children[i]); + break; + } + if (child == NULL) { + fatal_f("error: accepted connection when all %d child " + " slots full", options.max_startups); + } + child->pipefd = pipefd; + child->early = 1; + /* record peer address, if available */ + if (getpeername(sockfd, sa, &addrlen) == 0 && + addr_sa_to_xaddr(sa, addrlen, &child->addr) == 0) + child->have_addr = 1; + /* format peer address string for logs */ + if ((lport = get_local_port(sockfd)) == 0 || + (rport = get_peer_port(sockfd)) == 0) { + /* Not a TCP socket */ + raddr = get_peer_ipaddr(sockfd); + xasprintf(&child->id, "connection from %s", raddr); + } else { + laddr = get_local_ipaddr(sockfd); + raddr = get_peer_ipaddr(sockfd); + xasprintf(&child->id, "connection from %s to %s", laddr, raddr); + } + free(laddr); + free(raddr); + if (++children_active > options.max_startups) + fatal_f("internal error: more children than max_startups"); -/* - * Generic signal handler for terminating signals in the master daemon. - */ -static void -sigterm_handler(int sig) -{ - received_sigterm = sig; + return child; } /* - * SIGCHLD handler. This is called whenever a child dies. This will then - * reap any zombies left by exited children. + * Finally free a child entry. Don't call this directly. */ static void -main_sigchld_handler(int sig) +child_finish(struct early_child *child) { - int save_errno = errno; - pid_t pid; - int status; - - while ((pid = waitpid(-1, &status, WNOHANG)) > 0 || - (pid == -1 && errno == EINTR)) - ; - errno = save_errno; + if (children_active == 0) + fatal_f("internal error: children_active underflow"); + if (child->pipefd != -1) + close(child->pipefd); + free(child->id); + memset(child, '\0', sizeof(*child)); + child->pipefd = -1; + child->pid = -1; + children_active--; } /* - * Signal handler for the alarm after the login grace period has expired. + * Close a child's pipe. This will not stop tracking the child immediately + * (it will still be tracked for waitpid()) unless force_final is set, or + * child has already exited. */ static void -grace_alarm_handler(int sig) +child_close(struct early_child *child, int force_final, int quiet) { - /* - * Try to kill any processes that we have spawned, E.g. authorized - * keys command helpers or privsep children. - */ - if (getpgid(0) == getpid()) { - ssh_signal(SIGTERM, SIG_IGN); - kill(0, SIGTERM); + if (!quiet) + debug_f("enter%s", force_final ? " (forcing)" : ""); + if (child->pipefd != -1) { + close(child->pipefd); + child->pipefd = -1; } - - /* Log error and exit. */ - sigdie("Timeout before authentication for %s port %d", - ssh_remote_ipaddr(the_active_state), - ssh_remote_port(the_active_state)); + if (child->pid == -1 || force_final) + child_finish(child); } -/* Destroy the host and server keys. They will no longer be needed. */ -void -destroy_sensitive_data(void) +/* Record a child exit. Safe to call from signal handlers */ +static void +child_exit(pid_t pid, int status) { - u_int i; + int i; - for (i = 0; i < options.num_host_key_files; i++) { - if (sensitive_data.host_keys[i]) { - sshkey_free(sensitive_data.host_keys[i]); - sensitive_data.host_keys[i] = NULL; - } - if (sensitive_data.host_certificates[i]) { - sshkey_free(sensitive_data.host_certificates[i]); - sensitive_data.host_certificates[i] = NULL; + if (children == NULL || pid <= 0) + return; + for (i = 0; i < options.max_startups; i++) { + if (children[i].pid == pid) { + children[i].have_status = 1; + children[i].status = status; + break; } } } -/* Demote private to public keys for network child */ -void -demote_sensitive_data(void) +/* + * Reap a child entry that has exited, as previously flagged + * using child_exit(). + * Handles logging of exit condition and will finalise the child if its pipe + * had already been closed. + */ +static void +child_reap(struct early_child *child) { - struct sshkey *tmp; - u_int i; - int r; + LogLevel level = SYSLOG_LEVEL_DEBUG1; + int was_crash, penalty_type = SRCLIMIT_PENALTY_NONE; - for (i = 0; i < options.num_host_key_files; i++) { - if (sensitive_data.host_keys[i]) { - if ((r = sshkey_from_private( - sensitive_data.host_keys[i], &tmp)) != 0) - fatal_r(r, "could not demote host %s key", - sshkey_type(sensitive_data.host_keys[i])); - sshkey_free(sensitive_data.host_keys[i]); - sensitive_data.host_keys[i] = tmp; + /* Log exit information */ + if (WIFSIGNALED(child->status)) { + /* + * Increase logging for signals potentially associated + * with serious conditions. + */ + if ((was_crash = signal_is_crash(WTERMSIG(child->status)))) + level = SYSLOG_LEVEL_ERROR; + do_log2(level, "session process %ld for %s killed by " + "signal %d%s", (long)child->pid, child->id, + WTERMSIG(child->status), child->early ? " (early)" : ""); + if (was_crash) + penalty_type = SRCLIMIT_PENALTY_CRASH; + } else if (!WIFEXITED(child->status)) { + penalty_type = SRCLIMIT_PENALTY_CRASH; + error("session process %ld for %s terminated abnormally, " + "status=0x%x%s", (long)child->pid, child->id, child->status, + child->early ? " (early)" : ""); + } else { + /* Normal exit. We care about the status */ + switch (WEXITSTATUS(child->status)) { + case 0: + debug3_f("preauth child %ld for %s completed " + "normally %s", (long)child->pid, child->id, + child->early ? " (early)" : ""); + break; + case EXIT_LOGIN_GRACE: + penalty_type = SRCLIMIT_PENALTY_GRACE_EXCEEDED; + logit("Timeout before authentication for %s, " + "pid = %ld%s", child->id, (long)child->pid, + child->early ? " (early)" : ""); + break; + case EXIT_CHILD_CRASH: + penalty_type = SRCLIMIT_PENALTY_CRASH; + logit("Session process %ld unpriv child crash for %s%s", + (long)child->pid, child->id, + child->early ? " (early)" : ""); + break; + case EXIT_AUTH_ATTEMPTED: + penalty_type = SRCLIMIT_PENALTY_AUTHFAIL; + debug_f("preauth child %ld for %s exited " + "after unsuccessful auth attempt %s", + (long)child->pid, child->id, + child->early ? " (early)" : ""); + break; + default: + penalty_type = SRCLIMIT_PENALTY_NOAUTH; + debug_f("preauth child %ld for %s exited " + "with status %d%s", (long)child->pid, child->id, + WEXITSTATUS(child->status), + child->early ? " (early)" : ""); + break; } - /* Certs do not need demotion */ } -} - -static void -reseed_prngs(void) -{ - u_int32_t rnd[256]; -#ifdef WITH_OPENSSL - RAND_poll(); -#endif - arc4random_stir(); /* noop on recent arc4random() implementations */ - arc4random_buf(rnd, sizeof(rnd)); /* let arc4random notice PID change */ - -#ifdef WITH_OPENSSL - RAND_seed(rnd, sizeof(rnd)); - /* give libcrypto a chance to notice the PID change */ - if ((RAND_bytes((u_char *)rnd, 1)) != 1) - fatal("%s: RAND_bytes failed", __func__); -#endif + if (child->have_addr) + srclimit_penalise(&child->addr, penalty_type); - explicit_bzero(rnd, sizeof(rnd)); + child->pid = -1; + child->have_status = 0; + if (child->pipefd == -1) + child_finish(child); } +/* Reap all children that have exited; called after SIGCHLD */ static void -privsep_preauth_child(void) +child_reap_all_exited(void) { - gid_t gidset[1]; - - /* Enable challenge-response authentication for privilege separation */ - privsep_challenge_enable(); - -#ifdef GSSAPI - /* Cache supported mechanism OIDs for later use */ - ssh_gssapi_prepare_supported_oids(); -#endif - - reseed_prngs(); - - /* Demote the private keys to public keys. */ - demote_sensitive_data(); - - /* Demote the child */ - if (privsep_chroot) { - /* Change our root directory */ - if (chroot(_PATH_PRIVSEP_CHROOT_DIR) == -1) - fatal("chroot(\"%s\"): %s", _PATH_PRIVSEP_CHROOT_DIR, - strerror(errno)); - if (chdir("/") == -1) - fatal("chdir(\"/\"): %s", strerror(errno)); - - /* Drop our privileges */ - debug3("privsep user:group %u:%u", (u_int)privsep_pw->pw_uid, - (u_int)privsep_pw->pw_gid); - gidset[0] = privsep_pw->pw_gid; - if (setgroups(1, gidset) == -1) - fatal("setgroups: %.100s", strerror(errno)); - permanently_set_uid(privsep_pw); - } -} - -static int -privsep_preauth(struct ssh *ssh) -{ - int status, r; + int i; pid_t pid; - struct ssh_sandbox *box = NULL; - - /* Set up unprivileged child process to deal with network data */ - pmonitor = monitor_init(); - /* Store a pointer to the kex for later rekeying */ - pmonitor->m_pkex = &ssh->kex; - - if (use_privsep == PRIVSEP_ON) - box = ssh_sandbox_init(pmonitor); - pid = fork(); - if (pid == -1) { - fatal("fork of unprivileged child failed"); - } else if (pid != 0) { - debug2("Network child is on pid %ld", (long)pid); - - pmonitor->m_pid = pid; - if (have_agent) { - r = ssh_get_authentication_socket(&auth_sock); - if (r != 0) { - error_r(r, "Could not get agent socket"); - have_agent = 0; - } - } - if (box != NULL) - ssh_sandbox_parent_preauth(box, pid); - monitor_child_preauth(ssh, pmonitor); + int status; + + if (children == NULL) + return; - /* Wait for the child's exit status */ - while (waitpid(pid, &status, 0) == -1) { - if (errno == EINTR) + for (;;) { + if ((pid = waitpid(-1, &status, WNOHANG)) == 0) + break; + else if (pid == -1) { + if (errno == EINTR || errno == EAGAIN) continue; - pmonitor->m_pid = -1; - fatal_f("waitpid: %s", strerror(errno)); + if (errno != ECHILD) + error_f("waitpid: %s", strerror(errno)); + break; } - privsep_is_preauth = 0; - pmonitor->m_pid = -1; - if (WIFEXITED(status)) { - if (WEXITSTATUS(status) != 0) - fatal_f("preauth child exited with status %d", - WEXITSTATUS(status)); - } else if (WIFSIGNALED(status)) - fatal_f("preauth child terminated by signal %d", - WTERMSIG(status)); - if (box != NULL) - ssh_sandbox_parent_finish(box); - return 1; - } else { - /* child */ - close(pmonitor->m_sendfd); - close(pmonitor->m_log_recvfd); - - /* Arrange for logging to be sent to the monitor */ - set_log_handler(mm_log_handler, pmonitor); - - privsep_preauth_child(); - setproctitle("%s", "[net]"); - if (box != NULL) - ssh_sandbox_child(box); - - return 0; - } -} - -static void -privsep_postauth(struct ssh *ssh, Authctxt *authctxt) -{ -#ifdef DISABLE_FD_PASSING - if (1) { -#else - if (authctxt->pw->pw_uid == 0) { -#endif - /* File descriptor passing is broken or root login */ - use_privsep = 0; - goto skip; + child_exit(pid, status); } - /* New socket pair */ - monitor_reinit(pmonitor); - - pmonitor->m_pid = fork(); - if (pmonitor->m_pid == -1) - fatal("fork of unprivileged child failed"); - else if (pmonitor->m_pid != 0) { - verbose("User child is on pid %ld", (long)pmonitor->m_pid); - sshbuf_reset(loginmsg); - monitor_clear_keystate(ssh, pmonitor); - monitor_child_postauth(ssh, pmonitor); - - /* NEVERREACHED */ - exit(0); + for (i = 0; i < options.max_startups; i++) { + if (!children[i].have_status) + continue; + child_reap(&(children[i])); } - - /* child */ - - close(pmonitor->m_sendfd); - pmonitor->m_sendfd = -1; - - /* Demote the private keys to public keys. */ - demote_sensitive_data(); - - reseed_prngs(); - - /* Drop privileges */ - do_setusercontext(authctxt->pw); - - skip: - /* It is safe now to apply the key state */ - monitor_apply_keystate(ssh, pmonitor); - - /* - * Tell the packet layer that authentication was successful, since - * this information is not part of the key state. - */ - ssh_packet_set_authenticated(ssh); } static void -append_hostkey_type(struct sshbuf *b, const char *s) +close_startup_pipes(void) { - int r; + int i; - if (match_pattern_list(s, options.hostkeyalgorithms, 0) != 1) { - debug3_f("%s key not permitted by HostkeyAlgorithms", s); + if (children == NULL) return; + for (i = 0; i < options.max_startups; i++) { + if (children[i].pipefd != -1) + child_close(&(children[i]), 1, 1); } - if ((r = sshbuf_putf(b, "%s%s", sshbuf_len(b) > 0 ? "," : "", s)) != 0) - fatal_fr(r, "sshbuf_putf"); -} - -static char * -list_hostkey_types(void) -{ - struct sshbuf *b; - struct sshkey *key; - char *ret; - u_int i; - - if ((b = sshbuf_new()) == NULL) - fatal_f("sshbuf_new failed"); - for (i = 0; i < options.num_host_key_files; i++) { - key = sensitive_data.host_keys[i]; - if (key == NULL) - key = sensitive_data.host_pubkeys[i]; - if (key == NULL) - continue; - switch (key->type) { - case KEY_RSA: - /* for RSA we also support SHA2 signatures */ - append_hostkey_type(b, "rsa-sha2-512"); - append_hostkey_type(b, "rsa-sha2-256"); - /* FALLTHROUGH */ - case KEY_DSA: - case KEY_ECDSA: - case KEY_ED25519: - case KEY_ECDSA_SK: - case KEY_ED25519_SK: - case KEY_XMSS: - append_hostkey_type(b, sshkey_ssh_name(key)); - break; - } - /* If the private key has a cert peer, then list that too */ - key = sensitive_data.host_certificates[i]; - if (key == NULL) - continue; - switch (key->type) { - case KEY_RSA_CERT: - /* for RSA we also support SHA2 signatures */ - append_hostkey_type(b, - "rsa-sha2-512-cert-v01@openssh.com"); - append_hostkey_type(b, - "rsa-sha2-256-cert-v01@openssh.com"); - /* FALLTHROUGH */ - case KEY_DSA_CERT: - case KEY_ECDSA_CERT: - case KEY_ED25519_CERT: - case KEY_ECDSA_SK_CERT: - case KEY_ED25519_SK_CERT: - case KEY_XMSS_CERT: - append_hostkey_type(b, sshkey_ssh_name(key)); - break; - } - } - if ((ret = sshbuf_dup_string(b)) == NULL) - fatal_f("sshbuf_dup_string failed"); - sshbuf_free(b); - debug_f("%s", ret); - return ret; } -static struct sshkey * -get_hostkey_by_type(int type, int nid, int need_private, struct ssh *ssh) +/* Called after SIGINFO */ +static void +show_info(void) { - u_int i; - struct sshkey *key; + int i; - for (i = 0; i < options.num_host_key_files; i++) { - switch (type) { - case KEY_RSA_CERT: - case KEY_DSA_CERT: - case KEY_ECDSA_CERT: - case KEY_ED25519_CERT: - case KEY_ECDSA_SK_CERT: - case KEY_ED25519_SK_CERT: - case KEY_XMSS_CERT: - key = sensitive_data.host_certificates[i]; - break; - default: - key = sensitive_data.host_keys[i]; - if (key == NULL && !need_private) - key = sensitive_data.host_pubkeys[i]; - break; - } - if (key == NULL || key->type != type) + /* XXX print listening sockets here too */ + if (children == NULL) + return; + logit("%d active startups", children_active); + for (i = 0; i < options.max_startups; i++) { + if (children[i].pipefd == -1 && children[i].pid <= 0) continue; - switch (type) { - case KEY_ECDSA: - case KEY_ECDSA_SK: - case KEY_ECDSA_CERT: - case KEY_ECDSA_SK_CERT: - if (key->ecdsa_nid != nid) - continue; - /* FALLTHROUGH */ - default: - return need_private ? - sensitive_data.host_keys[i] : key; - } + logit("child %d: fd=%d pid=%ld %s%s", i, children[i].pipefd, + (long)children[i].pid, children[i].id, + children[i].early ? " (early)" : ""); } - return NULL; + srclimit_penalty_info(); } -struct sshkey * -get_hostkey_public_by_type(int type, int nid, struct ssh *ssh) -{ - return get_hostkey_by_type(type, nid, 0, ssh); -} +/* + * Signal handler for SIGHUP. Sshd execs itself when it receives SIGHUP; + * the effect is to reread the configuration file (and to regenerate + * the server key). + */ -struct sshkey * -get_hostkey_private_by_type(int type, int nid, struct ssh *ssh) +static void +sighup_handler(int sig) { - return get_hostkey_by_type(type, nid, 1, ssh); + received_sighup = 1; } -struct sshkey * -get_hostkey_by_index(int ind) +/* + * Called from the main program after receiving SIGHUP. + * Restarts the server. + */ +static void +sighup_restart(void) { - if (ind < 0 || (u_int)ind >= options.num_host_key_files) - return (NULL); - return (sensitive_data.host_keys[ind]); + logit("Received SIGHUP; restarting."); + if (options.pid_file != NULL) + unlink(options.pid_file); + platform_pre_restart(); + close_listen_socks(); + close_startup_pipes(); + ssh_signal(SIGHUP, SIG_IGN); /* will be restored after exec */ + execv(saved_argv[0], saved_argv); + logit("RESTART FAILED: av[0]='%.100s', error: %.100s.", saved_argv[0], + strerror(errno)); + exit(1); } -struct sshkey * -get_hostkey_public_by_index(int ind, struct ssh *ssh) +/* + * Generic signal handler for terminating signals in the master daemon. + */ +static void +sigterm_handler(int sig) { - if (ind < 0 || (u_int)ind >= options.num_host_key_files) - return (NULL); - return (sensitive_data.host_pubkeys[ind]); + received_sigterm = sig; } -int -get_hostkey_index(struct sshkey *key, int compare, struct ssh *ssh) +#ifdef SIGINFO +static void +siginfo_handler(int sig) { - u_int i; - - for (i = 0; i < options.num_host_key_files; i++) { - if (sshkey_is_cert(key)) { - if (key == sensitive_data.host_certificates[i] || - (compare && sensitive_data.host_certificates[i] && - sshkey_equal(key, - sensitive_data.host_certificates[i]))) - return (i); - } else { - if (key == sensitive_data.host_keys[i] || - (compare && sensitive_data.host_keys[i] && - sshkey_equal(key, sensitive_data.host_keys[i]))) - return (i); - if (key == sensitive_data.host_pubkeys[i] || - (compare && sensitive_data.host_pubkeys[i] && - sshkey_equal(key, sensitive_data.host_pubkeys[i]))) - return (i); - } - } - return (-1); + received_siginfo = 1; } +#endif -/* Inform the client of all hostkeys */ static void -notify_hostkeys(struct ssh *ssh) +main_sigchld_handler(int sig) { - struct sshbuf *buf; - struct sshkey *key; - u_int i, nkeys; - int r; - char *fp; - - /* Some clients cannot cope with the hostkeys message, skip those. */ - if (ssh->compat & SSH_BUG_HOSTKEYS) - return; - - if ((buf = sshbuf_new()) == NULL) - fatal_f("sshbuf_new"); - for (i = nkeys = 0; i < options.num_host_key_files; i++) { - key = get_hostkey_public_by_index(i, ssh); - if (key == NULL || key->type == KEY_UNSPEC || - sshkey_is_cert(key)) - continue; - fp = sshkey_fingerprint(key, options.fingerprint_hash, - SSH_FP_DEFAULT); - debug3_f("key %d: %s %s", i, sshkey_ssh_name(key), fp); - free(fp); - if (nkeys == 0) { - /* - * Start building the request when we find the - * first usable key. - */ - if ((r = sshpkt_start(ssh, SSH2_MSG_GLOBAL_REQUEST)) != 0 || - (r = sshpkt_put_cstring(ssh, "hostkeys-00@openssh.com")) != 0 || - (r = sshpkt_put_u8(ssh, 0)) != 0) /* want reply */ - sshpkt_fatal(ssh, r, "%s: start request", __func__); - } - /* Append the key to the request */ - sshbuf_reset(buf); - if ((r = sshkey_putb(key, buf)) != 0) - fatal_fr(r, "couldn't put hostkey %d", i); - if ((r = sshpkt_put_stringb(ssh, buf)) != 0) - sshpkt_fatal(ssh, r, "%s: append key", __func__); - nkeys++; - } - debug3_f("sent %u hostkeys", nkeys); - if (nkeys == 0) - fatal_f("no hostkeys"); - if ((r = sshpkt_send(ssh)) != 0) - sshpkt_fatal(ssh, r, "%s: send", __func__); - sshbuf_free(buf); + received_sigchld = 1; } /* @@ -833,7 +548,7 @@ should_drop_connection(int startups) } /* - * Check whether connection should be accepted by MaxStartups. + * Check whether connection should be accepted by MaxStartups or for penalty. * Returns 0 if the connection is accepted. If the connection is refused, * returns 1 and attempts to send notification to client. * Logs when the MaxStartups condition is entered or exited, and periodically @@ -843,12 +558,17 @@ static int drop_connection(int sock, int startups, int notify_pipe) { char *laddr, *raddr; - const char msg[] = "Exceeded MaxStartups\r\n"; + const char *reason = NULL, msg[] = "Not allowed at this time\r\n"; static time_t last_drop, first_drop; static u_int ndropped; LogLevel drop_level = SYSLOG_LEVEL_VERBOSE; time_t now; + if (!srclimit_penalty_check_allow(sock, &reason)) { + drop_level = SYSLOG_LEVEL_INFO; + goto handle; + } + now = monotime(); if (!should_drop_connection(startups) && srclimit_check_allow(sock, notify_pipe) == 1) { @@ -878,12 +598,16 @@ drop_connection(int sock, int startups, int notify_pipe) } last_drop = now; ndropped++; + reason = "past Maxstartups"; + handle: laddr = get_local_ipaddr(sock); raddr = get_peer_ipaddr(sock); - do_log2(drop_level, "drop connection #%d from [%s]:%d on [%s]:%d " - "past MaxStartups", startups, raddr, get_peer_port(sock), - laddr, get_local_port(sock)); + do_log2(drop_level, "drop connection #%d from [%s]:%d on [%s]:%d %s", + startups, + raddr, get_peer_port(sock), + laddr, get_local_port(sock), + reason); free(laddr); free(raddr); /* best-effort notification to client */ @@ -903,17 +627,64 @@ usage(void) exit(1); } +static struct sshbuf * +pack_hostkeys(void) +{ + struct sshbuf *keybuf = NULL, *hostkeys = NULL; + int r; + u_int i; + + if ((keybuf = sshbuf_new()) == NULL || + (hostkeys = sshbuf_new()) == NULL) + fatal_f("sshbuf_new failed"); + + /* pack hostkeys into a string. Empty key slots get empty strings */ + for (i = 0; i < options.num_host_key_files; i++) { + /* private key */ + sshbuf_reset(keybuf); + if (sensitive_data.host_keys[i] != NULL && + (r = sshkey_private_serialize(sensitive_data.host_keys[i], + keybuf)) != 0) + fatal_fr(r, "serialize hostkey private"); + if ((r = sshbuf_put_stringb(hostkeys, keybuf)) != 0) + fatal_fr(r, "compose hostkey private"); + /* public key */ + if (sensitive_data.host_pubkeys[i] != NULL) { + if ((r = sshkey_puts(sensitive_data.host_pubkeys[i], + hostkeys)) != 0) + fatal_fr(r, "compose hostkey public"); + } else { + if ((r = sshbuf_put_string(hostkeys, NULL, 0)) != 0) + fatal_fr(r, "compose hostkey empty public"); + } + /* cert */ + if (sensitive_data.host_certificates[i] != NULL) { + if ((r = sshkey_puts( + sensitive_data.host_certificates[i], + hostkeys)) != 0) + fatal_fr(r, "compose host cert"); + } else { + if ((r = sshbuf_put_string(hostkeys, NULL, 0)) != 0) + fatal_fr(r, "compose host cert empty"); + } + } + + sshbuf_free(keybuf); + return hostkeys; +} + static void send_rexec_state(int fd, struct sshbuf *conf) { - struct sshbuf *m = NULL, *inc = NULL; + struct sshbuf *m = NULL, *inc = NULL, *hostkeys = NULL; struct include_item *item = NULL; - int r; + int r, sz; debug3_f("entering fd = %d config len %zu", fd, sshbuf_len(conf)); - if ((m = sshbuf_new()) == NULL || (inc = sshbuf_new()) == NULL) + if ((m = sshbuf_new()) == NULL || + (inc = sshbuf_new()) == NULL) fatal_f("sshbuf_new failed"); /* pack includes into a string */ @@ -924,9 +695,17 @@ send_rexec_state(int fd, struct sshbuf *conf) fatal_fr(r, "compose includes"); } + hostkeys = pack_hostkeys(); + /* * Protocol from reexec master to child: * string configuration + * uint64 timing_secret + * string host_keys[] { + * string private_key + * string public_key + * string certificate + * } * string included_files[] { * string selector * string filename @@ -934,81 +713,26 @@ send_rexec_state(int fd, struct sshbuf *conf) * } */ if ((r = sshbuf_put_stringb(m, conf)) != 0 || + (r = sshbuf_put_u64(m, options.timing_secret)) != 0 || + (r = sshbuf_put_stringb(m, hostkeys)) != 0 || (r = sshbuf_put_stringb(m, inc)) != 0) fatal_fr(r, "compose config"); + + /* We need to fit the entire message inside the socket send buffer */ + sz = ROUNDUP(sshbuf_len(m) + 5, 16*1024); + if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &sz, sizeof sz) == -1) + fatal_f("setsockopt SO_SNDBUF: %s", strerror(errno)); + if (ssh_msg_send(fd, 0, m) == -1) error_f("ssh_msg_send failed"); sshbuf_free(m); sshbuf_free(inc); + sshbuf_free(hostkeys); debug3_f("done"); } -static void -recv_rexec_state(int fd, struct sshbuf *conf) -{ - struct sshbuf *m, *inc; - u_char *cp, ver; - size_t len; - int r; - struct include_item *item; - - debug3_f("entering fd = %d", fd); - - if ((m = sshbuf_new()) == NULL || (inc = sshbuf_new()) == NULL) - fatal_f("sshbuf_new failed"); - if (ssh_msg_recv(fd, m) == -1) - fatal_f("ssh_msg_recv failed"); - if ((r = sshbuf_get_u8(m, &ver)) != 0) - fatal_fr(r, "parse version"); - if (ver != 0) - fatal_f("rexec version mismatch"); - if ((r = sshbuf_get_string(m, &cp, &len)) != 0 || - (r = sshbuf_get_stringb(m, inc)) != 0) - fatal_fr(r, "parse config"); - - if (conf != NULL && (r = sshbuf_put(conf, cp, len))) - fatal_fr(r, "sshbuf_put"); - - while (sshbuf_len(inc) != 0) { - item = xcalloc(1, sizeof(*item)); - if ((item->contents = sshbuf_new()) == NULL) - fatal_f("sshbuf_new failed"); - if ((r = sshbuf_get_cstring(inc, &item->selector, NULL)) != 0 || - (r = sshbuf_get_cstring(inc, &item->filename, NULL)) != 0 || - (r = sshbuf_get_stringb(inc, item->contents)) != 0) - fatal_fr(r, "parse includes"); - TAILQ_INSERT_TAIL(&includes, item, entry); - } - - free(cp); - sshbuf_free(m); - - debug3_f("done"); -} - -/* Accept a connection from inetd */ -static void -server_accept_inetd(int *sock_in, int *sock_out) -{ - if (rexeced_flag) { - close(REEXEC_CONFIG_PASS_FD); - *sock_in = *sock_out = dup(STDIN_FILENO); - } else { - *sock_in = dup(STDIN_FILENO); - *sock_out = dup(STDOUT_FILENO); - } - /* - * We intentionally do not close the descriptors 0, 1, and 2 - * as our code for setting the descriptors won't work if - * ttyfd happens to be one of those. - */ - if (stdfd_devnull(1, 1, !log_stderr) == -1) - error_f("stdfd_devnull failed"); - debug("inetd sockets after dupping: %d, %d", *sock_in, *sock_out); -} - /* * Listen for TCP connections */ @@ -1090,8 +814,12 @@ server_listen(void) u_int i; /* Initialise per-source limit tracking. */ - srclimit_init(options.max_startups, options.per_source_max_startups, - options.per_source_masklen_ipv4, options.per_source_masklen_ipv6); + srclimit_init(options.max_startups, + options.per_source_max_startups, + options.per_source_masklen_ipv4, + options.per_source_masklen_ipv6, + &options.per_source_penalty, + options.per_source_penalty_exempt); for (i = 0; i < options.num_listen_addrs; i++) { listen_on_addrs(&options.listen_addrs[i]); @@ -1113,35 +841,36 @@ server_listen(void) * from this function are in a forked subprocess. */ static void -server_accept_loop(int *sock_in, int *sock_out, int *newsock, int *config_s) +server_accept_loop(int *sock_in, int *sock_out, int *newsock, int *config_s, + int log_stderr) { struct pollfd *pfd = NULL; - int i, j, ret, npfd; - int ostartups = -1, startups = 0, listening = 0, lameduck = 0; + int i, ret, npfd; + int oactive = -1, listening = 0, lameduck = 0; int startup_p[2] = { -1 , -1 }, *startup_pollfd; char c = 0; struct sockaddr_storage from; + struct early_child *child; socklen_t fromlen; - pid_t pid; u_char rnd[256]; sigset_t nsigset, osigset; /* pipes connected to unauthenticated child sshd processes */ - startup_pipes = xcalloc(options.max_startups, sizeof(int)); - startup_flags = xcalloc(options.max_startups, sizeof(int)); + child_alloc(); startup_pollfd = xcalloc(options.max_startups, sizeof(int)); - for (i = 0; i < options.max_startups; i++) - startup_pipes[i] = -1; /* * Prepare signal mask that we use to block signals that might set - * received_sigterm or received_sighup, so that we are guaranteed + * received_sigterm/hup/chld/info, so that we are guaranteed * to immediately wake up the ppoll if a signal is received after * the flag is checked. */ sigemptyset(&nsigset); sigaddset(&nsigset, SIGHUP); sigaddset(&nsigset, SIGCHLD); +#ifdef SIGINFO + sigaddset(&nsigset, SIGINFO); +#endif sigaddset(&nsigset, SIGTERM); sigaddset(&nsigset, SIGQUIT); @@ -1163,11 +892,19 @@ server_accept_loop(int *sock_in, int *sock_out, int *newsock, int *config_s) unlink(options.pid_file); exit(received_sigterm == SIGTERM ? 0 : 255); } - if (ostartups != startups) { + if (received_sigchld) { + child_reap_all_exited(); + received_sigchld = 0; + } + if (received_siginfo) { + show_info(); + received_siginfo = 0; + } + if (oactive != children_active) { setproctitle("%s [listener] %d of %d-%d startups", - listener_proctitle, startups, + listener_proctitle, children_active, options.max_startups_begin, options.max_startups); - ostartups = startups; + oactive = children_active; } if (received_sighup) { if (!lameduck) { @@ -1188,8 +925,8 @@ server_accept_loop(int *sock_in, int *sock_out, int *newsock, int *config_s) npfd = num_listen_socks; for (i = 0; i < options.max_startups; i++) { startup_pollfd[i] = -1; - if (startup_pipes[i] != -1) { - pfd[npfd].fd = startup_pipes[i]; + if (children[i].pipefd != -1) { + pfd[npfd].fd = children[i].pipefd; pfd[npfd].events = POLLIN; startup_pollfd[i] = npfd++; } @@ -1207,34 +944,46 @@ server_accept_loop(int *sock_in, int *sock_out, int *newsock, int *config_s) continue; for (i = 0; i < options.max_startups; i++) { - if (startup_pipes[i] == -1 || + if (children[i].pipefd == -1 || startup_pollfd[i] == -1 || !(pfd[startup_pollfd[i]].revents & (POLLIN|POLLHUP))) continue; - switch (read(startup_pipes[i], &c, sizeof(c))) { + switch (read(children[i].pipefd, &c, sizeof(c))) { case -1: if (errno == EINTR || errno == EAGAIN) continue; if (errno != EPIPE) { error_f("startup pipe %d (fd=%d): " - "read %s", i, startup_pipes[i], + "read %s", i, children[i].pipefd, strerror(errno)); } /* FALLTHROUGH */ case 0: - /* child exited or completed auth */ - close(startup_pipes[i]); - srclimit_done(startup_pipes[i]); - startup_pipes[i] = -1; - startups--; - if (startup_flags[i]) + /* child exited preauth */ + if (children[i].early) listening--; + srclimit_done(children[i].pipefd); + child_close(&(children[i]), 0, 0); break; case 1: - /* child has finished preliminaries */ - if (startup_flags[i]) { + if (children[i].early && c == '\0') { + /* child has finished preliminaries */ listening--; - startup_flags[i] = 0; + children[i].early = 0; + debug2_f("child %lu for %s received " + "config", (long)children[i].pid, + children[i].id); + } else if (!children[i].early && c == '\001') { + /* child has completed auth */ + debug2_f("child %lu for %s auth done", + (long)children[i].pid, + children[i].id); + child_close(&(children[i]), 1, 0); + } else { + error_f("unexpected message 0x%02x " + "child %ld for %s in state %d", + (int)c, (long)children[i].pid, + children[i].id, children[i].early); } break; } @@ -1263,14 +1012,15 @@ server_accept_loop(int *sock_in, int *sock_out, int *newsock, int *config_s) close(*newsock); continue; } - if (drop_connection(*newsock, startups, startup_p[0])) { + if (drop_connection(*newsock, + children_active, startup_p[0])) { close(*newsock); close(startup_p[0]); close(startup_p[1]); continue; } - if (rexec_flag && socketpair(AF_UNIX, + if (socketpair(AF_UNIX, SOCK_STREAM, 0, config_s) == -1) { error("reexec socketpair: %s", strerror(errno)); @@ -1280,14 +1030,6 @@ server_accept_loop(int *sock_in, int *sock_out, int *newsock, int *config_s) continue; } - for (j = 0; j < options.max_startups; j++) - if (startup_pipes[j] == -1) { - startup_pipes[j] = startup_p[0]; - startups++; - startup_flags[j] = 1; - break; - } - /* * Got connection. Fork a child to handle it, unless * we are in debugging mode. @@ -1305,11 +1047,8 @@ server_accept_loop(int *sock_in, int *sock_out, int *newsock, int *config_s) close(startup_p[0]); close(startup_p[1]); startup_pipe = -1; - pid = getpid(); - if (rexec_flag) { - send_rexec_state(config_s[0], cfg); - close(config_s[0]); - } + send_rexec_state(config_s[0], cfg); + close(config_s[0]); free(pfd); return; } @@ -1321,7 +1060,8 @@ server_accept_loop(int *sock_in, int *sock_out, int *newsock, int *config_s) */ platform_pre_fork(); listening++; - if ((pid = fork()) == 0) { + child = child_register(startup_p[0], *newsock); + if ((child->pid = fork()) == 0) { /* * Child. Close the listening and * max_startup sockets. Start using @@ -1340,37 +1080,23 @@ server_accept_loop(int *sock_in, int *sock_out, int *newsock, int *config_s) options.log_level, options.log_facility, log_stderr); - if (rexec_flag) - close(config_s[0]); - else { - /* - * Signal parent that the preliminaries - * for this child are complete. For the - * re-exec case, this happens after the - * child has received the rexec state - * from the server. - */ - (void)atomicio(vwrite, startup_pipe, - "\0", 1); - } + close(config_s[0]); free(pfd); return; } /* Parent. Stay in the loop. */ - platform_post_fork_parent(pid); - if (pid == -1) + platform_post_fork_parent(child->pid); + if (child->pid == -1) error("fork: %.100s", strerror(errno)); else - debug("Forked child %ld.", (long)pid); + debug("Forked child %ld.", (long)child->pid); close(startup_p[1]); - if (rexec_flag) { - close(config_s[1]); - send_rexec_state(config_s[0], cfg); - close(config_s[0]); - } + close(config_s[1]); + send_rexec_state(config_s[0], cfg); + close(config_s[0]); close(*newsock); /* @@ -1389,88 +1115,6 @@ server_accept_loop(int *sock_in, int *sock_out, int *newsock, int *config_s) } } -/* - * If IP options are supported, make sure there are none (log and - * return an error if any are found). Basically we are worried about - * source routing; it can be used to pretend you are somebody - * (ip-address) you are not. That itself may be "almost acceptable" - * under certain circumstances, but rhosts authentication is useless - * if source routing is accepted. Notice also that if we just dropped - * source routing here, the other side could use IP spoofing to do - * rest of the interaction and could still bypass security. So we - * exit here if we detect any IP options. - */ -static void -check_ip_options(struct ssh *ssh) -{ -#ifdef IP_OPTIONS - int sock_in = ssh_packet_get_connection_in(ssh); - struct sockaddr_storage from; - u_char opts[200]; - socklen_t i, option_size = sizeof(opts), fromlen = sizeof(from); - char text[sizeof(opts) * 3 + 1]; - - memset(&from, 0, sizeof(from)); - if (getpeername(sock_in, (struct sockaddr *)&from, - &fromlen) == -1) - return; - if (from.ss_family != AF_INET) - return; - /* XXX IPv6 options? */ - - if (getsockopt(sock_in, IPPROTO_IP, IP_OPTIONS, opts, - &option_size) >= 0 && option_size != 0) { - text[0] = '\0'; - for (i = 0; i < option_size; i++) - snprintf(text + i*3, sizeof(text) - i*3, - " %2.2x", opts[i]); - fatal("Connection from %.100s port %d with IP opts: %.800s", - ssh_remote_ipaddr(ssh), ssh_remote_port(ssh), text); - } - return; -#endif /* IP_OPTIONS */ -} - -/* Set the routing domain for this process */ -static void -set_process_rdomain(struct ssh *ssh, const char *name) -{ -#if defined(HAVE_SYS_SET_PROCESS_RDOMAIN) - if (name == NULL) - return; /* default */ - - if (strcmp(name, "%D") == 0) { - /* "expands" to routing domain of connection */ - if ((name = ssh_packet_rdomain_in(ssh)) == NULL) - return; - } - /* NB. We don't pass 'ssh' to sys_set_process_rdomain() */ - return sys_set_process_rdomain(name); -#elif defined(__OpenBSD__) - int rtable, ortable = getrtable(); - const char *errstr; - - if (name == NULL) - return; /* default */ - - if (strcmp(name, "%D") == 0) { - /* "expands" to routing domain of connection */ - if ((name = ssh_packet_rdomain_in(ssh)) == NULL) - return; - } - - rtable = (int)strtonum(name, 0, 255, &errstr); - if (errstr != NULL) /* Shouldn't happen */ - fatal("Invalid routing domain \"%s\": %s", name, errstr); - if (rtable != ortable && setrtable(rtable) != 0) - fatal("Unable to set routing domain %d: %s", - rtable, strerror(errno)); - debug_f("set routing domain %d (was %d)", rtable, ortable); -#else /* defined(__OpenBSD__) */ - fatal("Unable to set routing domain: not supported in this platform"); -#endif -} - static void accumulate_host_timing_secret(struct sshbuf *server_cfg, struct sshkey *key) @@ -1520,14 +1164,8 @@ prepare_proctitle(int ac, char **av) } static void -print_config(struct ssh *ssh, struct connection_info *connection_info) +print_config(struct connection_info *connection_info) { - /* - * If no connection info was provided by -C then use - * use a blank one that will cause no predicate to match. - */ - if (connection_info == NULL) - connection_info = get_connection_info(ssh, 0, 0); connection_info->test = 1; parse_server_match_config(&options, &includes, connection_info); dump_config(&options); @@ -1540,24 +1178,24 @@ print_config(struct ssh *ssh, struct connection_info *connection_info) int main(int ac, char **av) { - struct ssh *ssh = NULL; extern char *optarg; extern int optind; - int r, opt, on = 1, do_dump_cfg = 0, already_daemon, remote_port; - int sock_in = -1, sock_out = -1, newsock = -1; - const char *remote_ip, *rdomain; - char *fp, *line, *laddr, *logfile = NULL; - int config_s[2] = { -1 , -1 }; + int log_stderr = 0, inetd_flag = 0, test_flag = 0, no_daemon_flag = 0; + char *config_file_name = _PATH_SERVER_CONFIG_FILE; + int r, opt, do_dump_cfg = 0, keytype, already_daemon, have_agent = 0; + int sock_in = -1, sock_out = -1, newsock = -1, rexec_argc = 0; + int devnull, config_s[2] = { -1 , -1 }, have_connection_info = 0; + int need_chroot = 1; + char *fp, *line, *logfile = NULL, **rexec_argv = NULL; + struct stat sb; u_int i, j; - u_int64_t ibytes, obytes; mode_t new_umask; struct sshkey *key; struct sshkey *pubkey; - int keytype; - Authctxt *authctxt; - struct connection_info *connection_info = NULL; + struct connection_info connection_info; sigset_t sigmask; + memset(&connection_info, 0, sizeof(connection_info)); #ifdef HAVE_SECUREWARE (void)set_auth_parameters(ac, av); #endif @@ -1629,11 +1267,10 @@ main(int ac, char **av) inetd_flag = 1; break; case 'r': - rexec_flag = 0; + logit("-r option is deprecated"); break; case 'R': - rexeced_flag = 1; - inetd_flag = 1; + fatal("-R not supported here"); break; case 'Q': /* ignored */ @@ -1676,10 +1313,10 @@ main(int ac, char **av) test_flag = 2; break; case 'C': - connection_info = get_connection_info(ssh, 0, 0); - if (parse_server_match_testspec(connection_info, + if (parse_server_match_testspec(&connection_info, optarg) == -1) exit(1); + have_connection_info = 1; break; case 'u': utmp_len = (u_int)strtonum(optarg, 0, HOST_NAME_MAX+1+1, NULL); @@ -1704,20 +1341,34 @@ main(int ac, char **av) break; } } - if (rexeced_flag || inetd_flag) - rexec_flag = 0; - if (!test_flag && !do_dump_cfg && rexec_flag && !path_absolute(av[0])) - fatal("sshd re-exec requires execution with an absolute path"); - if (rexeced_flag) - closefrom(REEXEC_MIN_FREE_FD); - else - closefrom(REEXEC_DEVCRYPTO_RESERVED_FD); + if (!test_flag && !do_dump_cfg && !path_absolute(av[0])) + fatal("sshd requires execution with an absolute path"); + + closefrom(STDERR_FILENO + 1); + + /* Reserve fds we'll need later for reexec things */ + if ((devnull = open(_PATH_DEVNULL, O_RDWR)) == -1) + fatal("open %s: %s", _PATH_DEVNULL, strerror(errno)); + while (devnull < REEXEC_MIN_FREE_FD) { + if ((devnull = dup(devnull)) == -1) + fatal("dup %s: %s", _PATH_DEVNULL, strerror(errno)); + } seed_rng(); /* If requested, redirect the logs to the specified logfile. */ - if (logfile != NULL) - log_redirect_stderr_to(logfile); + if (logfile != NULL) { + char *cp, pid_s[32]; + + snprintf(pid_s, sizeof(pid_s), "%ld", (unsigned long)getpid()); + cp = percent_expand(logfile, + "p", pid_s, + "P", "sshd", + (char *)NULL); + log_redirect_stderr_to(cp); + free(cp); + } + /* * Force logging to stderr until we have loaded the private host * key (unless started from inetd) @@ -1742,35 +1393,18 @@ main(int ac, char **av) * If we're not doing an extended test do not silently ignore connection * test params. */ - if (test_flag < 2 && connection_info != NULL) + if (test_flag < 2 && have_connection_info) fatal("Config test connection parameter (-C) provided without " "test mode (-T)"); /* Fetch our configuration */ if ((cfg = sshbuf_new()) == NULL) - fatal_f("sshbuf_new failed"); - if (rexeced_flag) { - setproctitle("%s", "[rexeced]"); - recv_rexec_state(REEXEC_CONFIG_PASS_FD, cfg); - if (!debug_flag) { - startup_pipe = dup(REEXEC_STARTUP_PIPE_FD); - close(REEXEC_STARTUP_PIPE_FD); - /* - * Signal parent that this child is at a point where - * they can go away if they have a SIGHUP pending. - */ - (void)atomicio(vwrite, startup_pipe, "\0", 1); - } - } else if (strcasecmp(config_file_name, "none") != 0) + fatal("sshbuf_new config failed"); + if (strcasecmp(config_file_name, "none") != 0) load_server_config(config_file_name, cfg); - parse_server_config(&options, rexeced_flag ? "rexec" : config_file_name, - cfg, &includes, NULL, rexeced_flag); - -#ifdef WITH_OPENSSL - if (options.moduli_file != NULL) - dh_set_moduli_file(options.moduli_file); -#endif + parse_server_config(&options, config_file_name, cfg, + &includes, NULL, 0); /* Fill in default values for those options not explicitly set. */ fill_default_server_options(&options); @@ -1790,7 +1424,7 @@ main(int ac, char **av) /* * Check whether there is any path through configured auth methods. * Unfortunately it is not possible to verify this generally before - * daemonisation in the presence of Match block, but this catches + * daemonisation in the presence of Match blocks, but this catches * and warns for trivial misconfigurations that could break login. */ if (options.num_auth_methods != 0) { @@ -1813,20 +1447,7 @@ main(int ac, char **av) debug("sshd version %s, %s", SSH_VERSION, SSH_OPENSSL_VERSION); if (do_dump_cfg) - print_config(ssh, connection_info); - - /* Store privilege separation user for later use if required. */ - privsep_chroot = use_privsep && (getuid() == 0 || geteuid() == 0); - if ((privsep_pw = getpwnam(SSH_PRIVSEP_USER)) == NULL) { - if (privsep_chroot || options.kerberos_authentication) - fatal("Privilege separation user %s does not exist", - SSH_PRIVSEP_USER); - } else { - privsep_pw = pwcopy(privsep_pw); - freezero(privsep_pw->pw_passwd, strlen(privsep_pw->pw_passwd)); - privsep_pw->pw_passwd = xstrdup("*"); - } - endpwent(); + print_config(&connection_info); /* load host keys */ sensitive_data.host_keys = xcalloc(options.num_host_key_files, @@ -1978,27 +1599,33 @@ main(int ac, char **av) sshkey_type(key)); } - if (privsep_chroot) { - struct stat st; + /* Ensure privsep directory is correctly configured. */ + need_chroot = ((getuid() == 0 || geteuid() == 0) || + options.kerberos_authentication); + if ((getpwnam(SSH_PRIVSEP_USER)) == NULL && need_chroot) { + fatal("Privilege separation user %s does not exist", + SSH_PRIVSEP_USER); + } + endpwent(); - if ((stat(_PATH_PRIVSEP_CHROOT_DIR, &st) == -1) || - (S_ISDIR(st.st_mode) == 0)) + if (need_chroot) { + if ((stat(_PATH_PRIVSEP_CHROOT_DIR, &sb) == -1) || + (S_ISDIR(sb.st_mode) == 0)) fatal("Missing privilege separation directory: %s", _PATH_PRIVSEP_CHROOT_DIR); - #ifdef HAVE_CYGWIN if (check_ntsec(_PATH_PRIVSEP_CHROOT_DIR) && - (st.st_uid != getuid () || - (st.st_mode & (S_IWGRP|S_IWOTH)) != 0)) + (sb.st_uid != getuid () || + (sb.st_mode & (S_IWGRP|S_IWOTH)) != 0)) #else - if (st.st_uid != 0 || (st.st_mode & (S_IWGRP|S_IWOTH)) != 0) + if (sb.st_uid != 0 || (sb.st_mode & (S_IWGRP|S_IWOTH)) != 0) #endif fatal("%s must be owned by root and not group or " "world-writable.", _PATH_PRIVSEP_CHROOT_DIR); } if (test_flag > 1) - print_config(ssh, connection_info); + print_config(&connection_info); /* Configuration looks good, so exit if in test mode. */ if (test_flag) @@ -2014,17 +1641,22 @@ main(int ac, char **av) if (setgroups(0, NULL) < 0) debug("setgroups() failed: %.200s", strerror(errno)); - if (rexec_flag) { - if (rexec_argc < 0) - fatal("rexec_argc %d < 0", rexec_argc); - rexec_argv = xcalloc(rexec_argc + 2, sizeof(char *)); - for (i = 0; i < (u_int)rexec_argc; i++) { - debug("rexec_argv[%d]='%s'", i, saved_argv[i]); - rexec_argv[i] = saved_argv[i]; - } - rexec_argv[rexec_argc] = "-R"; - rexec_argv[rexec_argc + 1] = NULL; + /* Prepare arguments for sshd-session */ + if (rexec_argc < 0) + fatal("rexec_argc %d < 0", rexec_argc); + rexec_argv = xcalloc(rexec_argc + 3, sizeof(char *)); + /* Point to the sshd-session binary instead of sshd */ + rexec_argv[0] = options.sshd_session_path; + for (i = 1; i < (u_int)rexec_argc; i++) { + debug("rexec_argv[%d]='%s'", i, saved_argv[i]); + rexec_argv[i] = saved_argv[i]; } + rexec_argv[rexec_argc++] = "-R"; + rexec_argv[rexec_argc] = NULL; + if (stat(rexec_argv[0], &sb) != 0 || !(sb.st_mode & (S_IXOTH|S_IXUSR))) + fatal("%s does not exist or is not executable", rexec_argv[0]); + debug3("using %s for re-exec", rexec_argv[0]); + listener_proctitle = prepare_proctitle(ac, av); /* Ensure that umask disallows at least group and world write */ @@ -2032,7 +1664,7 @@ main(int ac, char **av) (void) umask(new_umask); /* Initialize the log (it is reinitialized below in case we forked). */ - if (debug_flag && (!inetd_flag || rexeced_flag)) + if (debug_flag && !inetd_flag) log_stderr = 1; log_init(__progname, options.log_level, options.log_facility, log_stderr); @@ -2067,7 +1699,11 @@ main(int ac, char **av) /* Get a connection, either from inetd or a listening TCP socket */ if (inetd_flag) { - server_accept_inetd(&sock_in, &sock_out); + /* Send configuration to ancestor sshd-session process */ + if (socketpair(AF_UNIX, SOCK_STREAM, 0, config_s) == -1) + fatal("socketpair: %s", strerror(errno)); + send_rexec_state(config_s[0], cfg); + close(config_s[0]); } else { platform_pre_listen(); server_listen(); @@ -2076,6 +1712,11 @@ main(int ac, char **av) ssh_signal(SIGCHLD, main_sigchld_handler); ssh_signal(SIGTERM, sigterm_handler); ssh_signal(SIGQUIT, sigterm_handler); +#ifdef SIGINFO + ssh_signal(SIGINFO, siginfo_handler); +#endif + + platform_post_listen(); /* * Write out the pid file after the sigterm handler @@ -2095,7 +1736,7 @@ main(int ac, char **av) /* Accept a connection and return in a forked child */ server_accept_loop(&sock_in, &sock_out, - &newsock, config_s); + &newsock, config_s, log_stderr); } /* This is the child processing a new connection. */ @@ -2109,358 +1750,40 @@ main(int ac, char **av) if (!debug_flag && !inetd_flag && setsid() == -1) error("setsid: %.100s", strerror(errno)); - if (rexec_flag) { - debug("rexec start in %d out %d newsock %d pipe %d sock %d", - sock_in, sock_out, newsock, startup_pipe, config_s[0]); + debug("rexec start in %d out %d newsock %d pipe %d sock %d/%d", + sock_in, sock_out, newsock, startup_pipe, config_s[0], config_s[1]); + if (!inetd_flag) { if (dup2(newsock, STDIN_FILENO) == -1) - debug3_f("dup2 stdin: %s", strerror(errno)); + fatal("dup2 stdin: %s", strerror(errno)); if (dup2(STDIN_FILENO, STDOUT_FILENO) == -1) - debug3_f("dup2 stdout: %s", strerror(errno)); - if (startup_pipe == -1) - close(REEXEC_STARTUP_PIPE_FD); - else if (startup_pipe != REEXEC_STARTUP_PIPE_FD) { - if (dup2(startup_pipe, REEXEC_STARTUP_PIPE_FD) == -1) - debug3_f("dup2 startup_p: %s", strerror(errno)); - close(startup_pipe); - startup_pipe = REEXEC_STARTUP_PIPE_FD; - } - + fatal("dup2 stdout: %s", strerror(errno)); + if (newsock > STDOUT_FILENO) + close(newsock); + } + if (config_s[1] != REEXEC_CONFIG_PASS_FD) { if (dup2(config_s[1], REEXEC_CONFIG_PASS_FD) == -1) - debug3_f("dup2 config_s: %s", strerror(errno)); + fatal("dup2 config_s: %s", strerror(errno)); close(config_s[1]); - - ssh_signal(SIGHUP, SIG_IGN); /* avoid reset to SIG_DFL */ - execv(rexec_argv[0], rexec_argv); - - /* Reexec has failed, fall back and continue */ - error("rexec of %s failed: %s", rexec_argv[0], strerror(errno)); - recv_rexec_state(REEXEC_CONFIG_PASS_FD, NULL); - log_init(__progname, options.log_level, - options.log_facility, log_stderr); - - /* Clean up fds */ - close(REEXEC_CONFIG_PASS_FD); - newsock = sock_out = sock_in = dup(STDIN_FILENO); - if (stdfd_devnull(1, 1, 0) == -1) - error_f("stdfd_devnull failed"); - debug("rexec cleanup in %d out %d newsock %d pipe %d sock %d", - sock_in, sock_out, newsock, startup_pipe, config_s[0]); - } - - /* Executed child processes don't need these. */ - fcntl(sock_out, F_SETFD, FD_CLOEXEC); - fcntl(sock_in, F_SETFD, FD_CLOEXEC); - - /* We will not restart on SIGHUP since it no longer makes sense. */ - ssh_signal(SIGALRM, SIG_DFL); - ssh_signal(SIGHUP, SIG_DFL); - ssh_signal(SIGTERM, SIG_DFL); - ssh_signal(SIGQUIT, SIG_DFL); - ssh_signal(SIGCHLD, SIG_DFL); - ssh_signal(SIGINT, SIG_DFL); - - /* - * Register our connection. This turns encryption off because we do - * not have a key. - */ - if ((ssh = ssh_packet_set_connection(NULL, sock_in, sock_out)) == NULL) - fatal("Unable to create connection"); - the_active_state = ssh; - ssh_packet_set_server(ssh); - - check_ip_options(ssh); - - /* Prepare the channels layer */ - channel_init_channels(ssh); - channel_set_af(ssh, options.address_family); - process_channel_timeouts(ssh, &options); - process_permitopen(ssh, &options); - - /* Set SO_KEEPALIVE if requested. */ - if (options.tcp_keep_alive && ssh_packet_connection_is_on_socket(ssh) && - setsockopt(sock_in, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)) == -1) - error("setsockopt SO_KEEPALIVE: %.100s", strerror(errno)); - - if ((remote_port = ssh_remote_port(ssh)) < 0) { - debug("ssh_remote_port failed"); - cleanup_exit(255); - } - - if (options.routing_domain != NULL) - set_process_rdomain(ssh, options.routing_domain); - - /* - * The rest of the code depends on the fact that - * ssh_remote_ipaddr() caches the remote ip, even if - * the socket goes away. - */ - remote_ip = ssh_remote_ipaddr(ssh); - -#ifdef SSH_AUDIT_EVENTS - audit_connection_from(remote_ip, remote_port); -#endif - - rdomain = ssh_packet_rdomain_in(ssh); - - /* Log the connection. */ - laddr = get_local_ipaddr(sock_in); - verbose("Connection from %s port %d on %s port %d%s%s%s", - remote_ip, remote_port, laddr, ssh_local_port(ssh), - rdomain == NULL ? "" : " rdomain \"", - rdomain == NULL ? "" : rdomain, - rdomain == NULL ? "" : "\""); - free(laddr); - - /* - * We don't want to listen forever unless the other side - * successfully authenticates itself. So we set up an alarm which is - * cleared after successful authentication. A limit of zero - * indicates no limit. Note that we don't set the alarm in debugging - * mode; it is just annoying to have the server exit just when you - * are about to discover the bug. - */ - ssh_signal(SIGALRM, grace_alarm_handler); - if (!debug_flag) - alarm(options.login_grace_time); - - if ((r = kex_exchange_identification(ssh, -1, - options.version_addendum)) != 0) - sshpkt_fatal(ssh, r, "banner exchange"); - - ssh_packet_set_nonblocking(ssh); - - /* allocate authentication context */ - authctxt = xcalloc(1, sizeof(*authctxt)); - ssh->authctxt = authctxt; - - authctxt->loginmsg = loginmsg; - - /* XXX global for cleanup, access from other modules */ - the_authctxt = authctxt; - - /* Set default key authentication options */ - if ((auth_opts = sshauthopt_new_with_keys_defaults()) == NULL) - fatal("allocation failed"); - - /* prepare buffer to collect messages to display to user after login */ - if ((loginmsg = sshbuf_new()) == NULL) - fatal_f("sshbuf_new failed"); - auth_debug_reset(); - - if (use_privsep) { - if (privsep_preauth(ssh) == 1) - goto authenticated; - } else if (have_agent) { - if ((r = ssh_get_authentication_socket(&auth_sock)) != 0) { - error_r(r, "Unable to get agent socket"); - have_agent = 0; - } - } - - /* perform the key exchange */ - /* authenticate user and start session */ - do_ssh2_kex(ssh); - do_authentication2(ssh); - - /* - * If we use privilege separation, the unprivileged child transfers - * the current keystate and exits - */ - if (use_privsep) { - mm_send_keystate(ssh, pmonitor); - ssh_packet_clear_keys(ssh); - exit(0); } - - authenticated: - /* - * Cancel the alarm we set to limit the time taken for - * authentication. - */ - alarm(0); - ssh_signal(SIGALRM, SIG_DFL); - authctxt->authenticated = 1; - if (startup_pipe != -1) { + if (startup_pipe == -1) + close(REEXEC_STARTUP_PIPE_FD); + else if (startup_pipe != REEXEC_STARTUP_PIPE_FD) { + if (dup2(startup_pipe, REEXEC_STARTUP_PIPE_FD) == -1) + fatal("dup2 startup_p: %s", strerror(errno)); close(startup_pipe); - startup_pipe = -1; - } - -#ifdef SSH_AUDIT_EVENTS - audit_event(ssh, SSH_AUTH_SUCCESS); -#endif - -#ifdef GSSAPI - if (options.gss_authentication) { - temporarily_use_uid(authctxt->pw); - ssh_gssapi_storecreds(); - restore_uid(); - } -#endif -#ifdef USE_PAM - if (options.use_pam) { - do_pam_setcred(1); - do_pam_session(ssh); - } -#endif - - /* - * In privilege separation, we fork another child and prepare - * file descriptor passing. - */ - if (use_privsep) { - privsep_postauth(ssh, authctxt); - /* the monitor process [priv] will not return */ - } - - ssh_packet_set_timeout(ssh, options.client_alive_interval, - options.client_alive_count_max); - - /* Try to send all our hostkeys to the client */ - notify_hostkeys(ssh); - - /* Start session. */ - do_authenticated(ssh, authctxt); - - /* The connection has been terminated. */ - ssh_packet_get_bytes(ssh, &ibytes, &obytes); - verbose("Transferred: sent %llu, received %llu bytes", - (unsigned long long)obytes, (unsigned long long)ibytes); - - verbose("Closing connection to %.500s port %d", remote_ip, remote_port); - -#ifdef USE_PAM - if (options.use_pam) - finish_pam(); -#endif /* USE_PAM */ - -#ifdef SSH_AUDIT_EVENTS - PRIVSEP(audit_event(ssh, SSH_CONNECTION_CLOSE)); -#endif - - ssh_packet_close(ssh); - - if (use_privsep) - mm_terminate(); - - exit(0); -} - -int -sshd_hostkey_sign(struct ssh *ssh, struct sshkey *privkey, - struct sshkey *pubkey, u_char **signature, size_t *slenp, - const u_char *data, size_t dlen, const char *alg) -{ - int r; - - if (use_privsep) { - if (privkey) { - if (mm_sshkey_sign(ssh, privkey, signature, slenp, - data, dlen, alg, options.sk_provider, NULL, - ssh->compat) < 0) - fatal_f("privkey sign failed"); - } else { - if (mm_sshkey_sign(ssh, pubkey, signature, slenp, - data, dlen, alg, options.sk_provider, NULL, - ssh->compat) < 0) - fatal_f("pubkey sign failed"); - } - } else { - if (privkey) { - if (sshkey_sign(privkey, signature, slenp, data, dlen, - alg, options.sk_provider, NULL, ssh->compat) < 0) - fatal_f("privkey sign failed"); - } else { - if ((r = ssh_agent_sign(auth_sock, pubkey, - signature, slenp, data, dlen, alg, - ssh->compat)) != 0) { - fatal_fr(r, "agent sign failed"); - } - } } - return 0; -} - -/* SSH2 key exchange */ -static void -do_ssh2_kex(struct ssh *ssh) -{ - char *hkalgs = NULL, *myproposal[PROPOSAL_MAX]; - const char *compression = NULL; - struct kex *kex; - int r; - - if (options.rekey_limit || options.rekey_interval) - ssh_packet_set_rekey_limits(ssh, options.rekey_limit, - options.rekey_interval); - - if (options.compression == COMP_NONE) - compression = "none"; - hkalgs = list_hostkey_types(); + log_redirect_stderr_to(NULL); + closefrom(REEXEC_MIN_FREE_FD); - kex_proposal_populate_entries(ssh, myproposal, options.kex_algorithms, - options.ciphers, options.macs, compression, hkalgs); + ssh_signal(SIGHUP, SIG_IGN); /* avoid reset to SIG_DFL */ + execv(rexec_argv[0], rexec_argv); - free(hkalgs); - - /* start key exchange */ - if ((r = kex_setup(ssh, myproposal)) != 0) - fatal_r(r, "kex_setup"); - kex_set_server_sig_algs(ssh, options.pubkey_accepted_algos); - kex = ssh->kex; - -#ifdef WITH_OPENSSL - kex->kex[KEX_DH_GRP1_SHA1] = kex_gen_server; - kex->kex[KEX_DH_GRP14_SHA1] = kex_gen_server; - kex->kex[KEX_DH_GRP14_SHA256] = kex_gen_server; - kex->kex[KEX_DH_GRP16_SHA512] = kex_gen_server; - kex->kex[KEX_DH_GRP18_SHA512] = kex_gen_server; - kex->kex[KEX_DH_GEX_SHA1] = kexgex_server; - kex->kex[KEX_DH_GEX_SHA256] = kexgex_server; -# ifdef OPENSSL_HAS_ECC - kex->kex[KEX_ECDH_SHA2] = kex_gen_server; -# endif -#endif - kex->kex[KEX_C25519_SHA256] = kex_gen_server; - kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_server; - kex->load_host_public_key=&get_hostkey_public_by_type; - kex->load_host_private_key=&get_hostkey_private_by_type; - kex->host_key_index=&get_hostkey_index; - kex->sign = sshd_hostkey_sign; - - ssh_dispatch_run_fatal(ssh, DISPATCH_BLOCK, &kex->done); - kex_proposal_free_entries(myproposal); - -#ifdef DEBUG_KEXDH - /* send 1st encrypted/maced/compressed message */ - if ((r = sshpkt_start(ssh, SSH2_MSG_IGNORE)) != 0 || - (r = sshpkt_put_cstring(ssh, "markus")) != 0 || - (r = sshpkt_send(ssh)) != 0 || - (r = ssh_packet_write_wait(ssh)) != 0) - fatal_fr(r, "send test"); -#endif - debug("KEX done"); + fatal("rexec of %s failed: %s", rexec_argv[0], strerror(errno)); } /* server specific fatal cleanup */ void cleanup_exit(int i) { - if (the_active_state != NULL && the_authctxt != NULL) { - do_cleanup(the_active_state, the_authctxt); - if (use_privsep && privsep_is_preauth && - pmonitor != NULL && pmonitor->m_pid > 1) { - debug("Killing privsep child %d", pmonitor->m_pid); - if (kill(pmonitor->m_pid, SIGKILL) != 0 && - errno != ESRCH) { - error_f("kill(%d): %s", pmonitor->m_pid, - strerror(errno)); - } - } - } -#ifdef SSH_AUDIT_EVENTS - /* done after do_cleanup so it can cancel the PAM auth 'thread' */ - if (the_active_state != NULL && (!use_privsep || mm_is_monitor())) - audit_event(the_active_state, SSH_CONNECTION_ABANDON); -#endif _exit(i); } |