diff options
Diffstat (limited to 'sshd.c')
-rw-r--r-- | sshd.c | 1726 |
1 files changed, 534 insertions, 1192 deletions
@@ -1,23 +1,5 @@ -/* $OpenBSD: sshd.c,v 1.602 2024/01/08 00:34:34 djm Exp $ */ +/* $OpenBSD: sshd.c,v 1.607 2024/06/06 19:50:01 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,331 @@ 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) -{ - /* - * 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); - } - - /* Log error and exit. */ - sigdie("Timeout before authentication for %s port %d", - ssh_remote_ipaddr(the_active_state), - ssh_remote_port(the_active_state)); -} - -/* Destroy the host and server keys. They will no longer be needed. */ -void -destroy_sensitive_data(void) -{ - u_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; - } - } -} - -/* Demote private to public keys for network child */ -void -demote_sensitive_data(void) +child_close(struct early_child *child, int force_final, int quiet) { - struct sshkey *tmp; - u_int i; - int r; - - 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; - } - /* Certs do not need demotion */ + if (!quiet) + debug_f("enter%s", force_final ? " (forcing)" : ""); + if (child->pipefd != -1) { + close(child->pipefd); + child->pipefd = -1; } + if (child->pid == -1 || force_final) + child_finish(child); } +/* Record a child exit. Safe to call from signal handlers */ 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 - - explicit_bzero(rnd, sizeof(rnd)); -} - -static void -privsep_preauth_child(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) +child_exit(pid_t pid, int status) { - int status, r; - 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 i; - /* Wait for the child's exit status */ - while (waitpid(pid, &status, 0) == -1) { - if (errno == EINTR) - continue; - pmonitor->m_pid = -1; - fatal_f("waitpid: %s", strerror(errno)); + 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; } - 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; } } +/* + * 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 -privsep_postauth(struct ssh *ssh, Authctxt *authctxt) +child_reap(struct early_child *child) { -#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; - } - - /* 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); + LogLevel level = SYSLOG_LEVEL_DEBUG1; + int was_crash, penalty_type = SRCLIMIT_PENALTY_NONE; - /* NEVERREACHED */ - exit(0); + /* 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; + } } - - /* 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. + * XXX would be nice to have more subtlety here. + * - Different penalties + * a) authentication failures without success (e.g. brute force) + * b) login grace exceeded (penalise DoS) + * c) monitor crash (penalise exploit attempt) + * d) unpriv preauth crash (penalise exploit attempt) + * - Unpriv auth exit status/WIFSIGNALLED is not available because + * the "mm_request_receive: monitor fd closed" fatal kills the + * monitor before waitpid() can occur. It would be good to use the + * unpriv exit status to detect crashes. + * + * For now, just penalise (a), (b) and (c), since that is what we have + * readily available. The authentication failures detection cannot + * discern between failed authentication and other connection problems + * until we have the unpriv exist status plumbed through (and the unpriv + * child modified to use a different exit status when auth has been + * attempted), but it's a start. */ - ssh_packet_set_authenticated(ssh); + if (child->have_addr) + srclimit_penalise(&child->addr, penalty_type); + + 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 -append_hostkey_type(struct sshbuf *b, const char *s) +child_reap_all_exited(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].have_status) + continue; + child_reap(&(children[i])); } - if ((r = sshbuf_putf(b, "%s%s", sshbuf_len(b) > 0 ? "," : "", s)) != 0) - fatal_fr(r, "sshbuf_putf"); } -static char * -list_hostkey_types(void) +static void +close_startup_pipes(void) { - struct sshbuf *b; - struct sshkey *key; - char *ret; - u_int i; + 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 (children == NULL) + return; + for (i = 0; i < options.max_startups; i++) { + if (children[i].pipefd != -1) + child_close(&(children[i]), 1, 1); } - 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 */ +/* + * SIGCHLD handler. This is called whenever a child dies. This will then + * reap any zombies left by exited children. + */ 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; + int save_errno = errno; + pid_t pid; + int status; - 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__); + for (;;) { + if ((pid = waitpid(-1, &status, WNOHANG)) == 0) + break; + else if (pid == -1) { + if (errno == EINTR) + continue; + break; } - /* 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++; + child_exit(pid, status); + received_sigchld = 1; } - 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); + errno = save_errno; } /* @@ -833,7 +569,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 +579,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 +619,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 +648,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 +716,17 @@ send_rexec_state(int fd, struct sshbuf *conf) fatal_fr(r, "compose includes"); } + host |