/* $OpenBSD: sshd.c,v 1.499 2017/11/14 00:45:29 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , 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. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef WITH_OPENSSL #include #endif #include "xmalloc.h" #include "ssh.h" #include "ssh2.h" #include "sshpty.h" #include "packet.h" #include "log.h" #include "buffer.h" #include "misc.h" #include "match.h" #include "servconf.h" #include "uidswap.h" #include "compat.h" #include "cipher.h" #include "digest.h" #include "key.h" #include "kex.h" #include "myproposal.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 "version.h" #include "ssherr.h" /* Re-exec fds */ #define REEXEC_DEVCRYPTO_RESERVED_FD (STDERR_FILENO + 1) #define REEXEC_STARTUP_PIPE_FD (STDERR_FILENO + 2) #define REEXEC_CONFIG_PASS_FD (STDERR_FILENO + 3) #define REEXEC_MIN_FREE_FD (STDERR_FILENO + 4) 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 * log, the daemon will not go to background, and will exit after processing * the first connection. */ 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. */ int test_flag = 0; /* Flag indicating that the daemon is being started from inetd. */ int inetd_flag = 0; /* Flag indicating that sshd should not detach and become a daemon. */ int no_daemon_flag = 0; /* debug goes to stderr unless inetd_flag is set */ int log_stderr = 0; /* Saved arguments to main(). */ char **saved_argv; /* re-exec */ int rexeced_flag = 0; int rexec_flag = 1; int rexec_argc = 0; char **rexec_argv; /* * The sockets that the server is listening; this is used in the SIGHUP * signal handler. */ #define MAX_LISTEN_SOCKS 16 int listen_socks[MAX_LISTEN_SOCKS]; int num_listen_socks = 0; /* * the client's version string, passed by sshd2 in compat mode. if != NULL, * sshd will skip the version-number exchange */ char *client_version_string = NULL; char *server_version_string = NULL; /* Daemon's agent connection */ int auth_sock = -1; 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 * that the pages do not get written into swap. However, there are some * problems. The private key contains BIGNUMs, and we do not (in principle) * have access to the internals of them, and locking just the structure is * not very useful. Currently, memory locking is not implemented. */ struct { struct sshkey **host_keys; /* all private host keys */ struct sshkey **host_pubkeys; /* all public host keys */ struct sshkey **host_certificates; /* all public host certificates */ int have_ssh2_key; } sensitive_data; /* This is set to true when a signal is received. */ static volatile sig_atomic_t received_sighup = 0; static volatile sig_atomic_t received_sigterm = 0; /* session identifier, used by RSA-auth */ u_char session_id[16]; /* same for ssh2 */ u_char *session_id2 = NULL; u_int session_id2_len = 0; /* record remote hostname or ip */ u_int utmp_len = HOST_NAME_MAX+1; /* options.max_startup sized array of fd ints */ int *startup_pipes = NULL; int startup_pipe; /* in child */ /* variables used for privilege separation */ int use_privsep = -1; struct monitor *pmonitor = NULL; int privsep_is_preauth = 1; /* global authentication context */ Authctxt *the_authctxt = NULL; /* sshd_config buffer */ Buffer cfg; /* message to be displayed after login */ Buffer loginmsg; /* Prototypes for various functions defined later in this file. */ void destroy_sensitive_data(void); void demote_sensitive_data(void); static void do_ssh2_kex(void); /* * Close all listening sockets */ static void close_listen_socks(void) { int i; for (i = 0; i < num_listen_socks; i++) close(listen_socks[i]); num_listen_socks = -1; } static void close_startup_pipes(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). */ /*ARGSUSED*/ static void sighup_handler(int sig) { int save_errno = errno; received_sighup = 1; signal(SIGHUP, sighup_handler); errno = save_errno; } /* * Called from the main program after receiving SIGHUP. * Restarts the server. */ static void sighup_restart(void) { logit("Received SIGHUP; restarting."); if (options.pid_file != NULL) unlink(options.pid_file); close_listen_socks(); close_startup_pipes(); alarm(0); /* alarm timer persists across exec */ 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); } /* * Generic signal handler for terminating signals in the master daemon. */ /*ARGSUSED*/ static void sigterm_handler(int sig) { received_sigterm = sig; } /* * SIGCHLD handler. This is called whenever a child dies. This will then * reap any zombies left by exited children. */ /*ARGSUSED*/ static void main_sigchld_handler(int sig) { int save_errno = errno; pid_t pid; int status; while ((pid = waitpid(-1, &status, WNOHANG)) > 0 || (pid < 0 && errno == EINTR)) ; signal(SIGCHLD, main_sigchld_handler); errno = save_errno; } /* * Signal handler for the alarm after the login grace period has expired. */ /*ARGSUSED*/ static void grace_alarm_handler(int sig) { if (use_privsep && pmonitor != NULL && pmonitor->m_pid > 0) kill(pmonitor->m_pid, SIGALRM); /* * Try to kill any processes that we have spawned, E.g. authorized * keys command helpers. */ if (getpgid(0) == getpid()) { signal(SIGTERM, SIG_IGN); kill(0, SIGTERM); } /* Log error and exit. */ sigdie("Timeout before authentication for %s port %d", ssh_remote_ipaddr(active_state), ssh_remote_port(active_state)); } static void sshd_exchange_identification(struct ssh *ssh, int sock_in, int sock_out) { u_int i; int remote_major, remote_minor; char *s; char buf[256]; /* Must not be larger than remote_version. */ char remote_version[256]; /* Must be at least as big as buf. */ xasprintf(&server_version_string, "SSH-%d.%d-%.100s%s%s\r\n", PROTOCOL_MAJOR_2, PROTOCOL_MINOR_2, SSH_VERSION, *options.version_addendum == '\0' ? "" : " ", options.version_addendum); /* Send our protocol version identification. */ if (atomicio(vwrite, sock_out, server_version_string, strlen(server_version_string)) != strlen(server_version_string)) { logit("Could not write ident string to %s port %d", ssh_remote_ipaddr(ssh), ssh_remote_port(ssh)); cleanup_exit(255); } /* Read other sides version identification. */ memset(buf, 0, sizeof(buf)); for (i = 0; i < sizeof(buf) - 1; i++) { if (atomicio(read, sock_in, &buf[i], 1) != 1) { logit("Did not receive identification string " "from %s port %d", ssh_remote_ipaddr(ssh), ssh_remote_port(ssh)); cleanup_exit(255); } if (buf[i] == '\r') { buf[i] = 0; /* Kludge for F-Secure Macintosh < 1.0.2 */ if (i == 12 && strncmp(buf, "SSH-1.5-W1.0", 12) == 0) break; continue; } if (buf[i] == '\n') { buf[i] = 0; break; } } buf[sizeof(buf) - 1] = 0; client_version_string = xstrdup(buf); /* * Check that the versions match. In future this might accept * several versions and set appropriate flags to handle them. */ if (sscanf(client_version_string, "SSH-%d.%d-%[^\n]\n", &remote_major, &remote_minor, remote_version) != 3) { s = "Protocol mismatch.\n"; (void) atomicio(vwrite, sock_out, s, strlen(s)); logit("Bad protocol version identification '%.100s' " "from %s port %d", client_version_string, ssh_remote_ipaddr(ssh), ssh_remote_port(ssh)); close(sock_in); close(sock_out); cleanup_exit(255); } debug("Client protocol version %d.%d; client software version %.100s", remote_major, remote_minor, remote_version); ssh->compat = compat_datafellows(remote_version); if ((ssh->compat & SSH_BUG_PROBE) != 0) { logit("probed from %s port %d with %s. Don't panic.", ssh_remote_ipaddr(ssh), ssh_remote_port(ssh), client_version_string); cleanup_exit(255); } if ((ssh->compat & SSH_BUG_SCANNER) != 0) { logit("scanned from %s port %d with %s. Don't panic.", ssh_remote_ipaddr(ssh), ssh_remote_port(ssh), client_version_string); cleanup_exit(255); } if ((ssh->compat & SSH_BUG_RSASIGMD5) != 0) { logit("Client version \"%.100s\" uses unsafe RSA signature " "scheme; disabling use of RSA keys", remote_version); } if ((ssh->compat & SSH_BUG_DERIVEKEY) != 0) { fatal("Client version \"%.100s\" uses unsafe key agreement; " "refusing connection", remote_version); } chop(server_version_string); debug("Local version string %.200s", server_version_string); if (remote_major != 2 || (remote_major == 1 && remote_minor != 99)) { s = "Protocol major versions differ.\n"; (void) atomicio(vwrite, sock_out, s, strlen(s)); close(sock_in); close(sock_out); logit("Protocol major versions differ for %s port %d: " "%.200s vs. %.200s", ssh_remote_ipaddr(ssh), ssh_remote_port(ssh), server_version_string, client_version_string); cleanup_exit(255); } } /* 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]) { key_free(sensitive_data.host_keys[i]); sensitive_data.host_keys[i] = NULL; } if (sensitive_data.host_certificates[i]) { key_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) { struct sshkey *tmp; u_int i; for (i = 0; i < options.num_host_key_files; i++) { if (sensitive_data.host_keys[i]) { tmp = key_demote(sensitive_data.host_keys[i]); key_free(sensitive_data.host_keys[i]); sensitive_data.host_keys[i] = tmp; } /* Certs do not need demotion */ } } static void privsep_preauth_child(void) { gid_t gidset[1]; struct passwd *pw; /* Enable challenge-response authentication for privilege separation */ privsep_challenge_enable(); #ifdef GSSAPI /* Cache supported mechanism OIDs for later use */ if (options.gss_authentication) ssh_gssapi_prepare_supported_oids(); #endif /* Demote the private keys to public keys. */ demote_sensitive_data(); /* Demote the child */ if (getuid() == 0 || geteuid() == 0) { if ((pw = getpwnam(SSH_PRIVSEP_USER)) == NULL) fatal("Privilege separation user %s does not exist", SSH_PRIVSEP_USER); explicit_bzero(pw->pw_passwd, strlen(pw->pw_passwd)); endpwent(); /* 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 * NB. Can't use setusercontext() after chroot. */ debug3("privsep user:group %u:%u", (u_int)pw->pw_uid, (u_int)pw->pw_gid); gidset[0] = pw->pw_gid; if (setgroups(1, gidset) < 0) fatal("setgroups: %.100s", strerror(errno)); permanently_set_uid(pw); } } static int privsep_preauth(Authctxt *authctxt) { 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 = &active_state->kex; if (use_privsep == PRIVSEP_ON) box = ssh_sandbox_init(); 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("Could not get agent socket: %s", ssh_err(r)); have_agent = 0; } } if (box != NULL) ssh_sandbox_parent_preauth(box, pid); monitor_child_preauth(authctxt, pmonitor); /* Wait for the child's exit status */ while (waitpid(pid, &status, 0) < 0) { if (errno == EINTR) continue; pmonitor->m_pid = -1; fatal("%s: waitpid: %s", __func__, strerror(errno)); } privsep_is_preauth = 0; pmonitor->m_pid = -1; if (WIFEXITED(status)) { if (WEXITSTATUS(status) != 0) fatal("%s: preauth child exited with status %d", __func__, WEXITSTATUS(status)); } else if (WIFSIGNALED(status)) fatal("%s: preauth child terminated by signal %d", __func__, 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(Authctxt *authctxt) { if (authctxt->pw->pw_uid == 0) { /* 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); buffer_clear(&loginmsg); monitor_clear_keystate(pmonitor); monitor_child_postauth(pmonitor); /* NEVERREACHED */ exit(0); } /* child */ close(pmonitor->m_sendfd); pmonitor->m_sendfd = -1; /* Demote the private keys to public keys. */ demote_sensitive_data(); /* Drop privileges */ do_setusercontext(authctxt->pw); skip: /* It is safe now to apply the key state */ monitor_apply_keystate(pmonitor); /* * Tell the packet layer that authentication was successful, since * this information is not part of the key state. */ packet_set_authenticated(); } static char * list_hostkey_types(void) { Buffer b; const char *p; char *ret; u_int i; struct sshkey *key; buffer_init(&b); 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; /* Check that the key is accepted in HostkeyAlgorithms */ if (match_pattern_list(sshkey_ssh_name(key), options.hostkeyalgorithms, 0) != 1) { debug3("%s: %s key not permitted by HostkeyAlgorithms", __func__, sshkey_ssh_name(key)); continue; } switch (key->type) { case KEY_RSA: case KEY_DSA: case KEY_ECDSA: case KEY_ED25519: if (buffer_len(&b) > 0) buffer_append(&b, ",", 1); p = key_ssh_name(key); buffer_append(&b, p, strlen(p)); /* for RSA we also support SHA2 signatures */ if (key->type == KEY_RSA) { p = ",rsa-sha2-512,rsa-sha2-256"; buffer_append(&b, p, strlen(p)); } 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: case KEY_DSA_CERT: case KEY_ECDSA_CERT: case KEY_ED25519_CERT: if (buffer_len(&b) > 0) buffer_append(&b, ",", 1); p = key_ssh_name(key); buffer_append(&b, p, strlen(p)); break; } } if ((ret = sshbuf_dup_string(&b)) == NULL) fatal("%s: sshbuf_dup_string failed", __func__); buffer_free(&b); debug("list_hostkey_types: %s", ret); return ret; } static struct sshkey * get_hostkey_by_type(int type, int nid, int need_private, struct ssh *ssh) { u_int i; struct sshkey *key; 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: 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 && (key->type != KEY_ECDSA || key->ecdsa_nid == nid)) return need_private ? sensitive_data.host_keys[i] : key; } return NULL; } struct sshkey * get_hostkey_public_by_type(int type, int nid, struct ssh *ssh) { return get_hostkey_by_type(type, nid, 0, ssh); } struct sshkey * get_hostkey_private_by_type(int type, int nid, struct ssh *ssh) { return get_hostkey_by_type(type, nid, 1, ssh); } struct sshkey * get_hostkey_by_index(int ind) { if (ind < 0 || (u_int)ind >= options.num_host_key_files) return (NULL); return (sensitive_data.host_keys[ind]); } struct sshkey * get_hostkey_public_by_index(int ind, struct ssh *ssh) { if (ind < 0 || (u_int)ind >= options.num_host_key_files) return (NULL); return (sensitive_data.host_pubkeys[ind]); } int get_hostkey_index(struct sshkey *key, int compare, struct ssh *ssh) { u_int i; for (i = 0; i < options.num_host_key_files; i++) { if (key_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); } /* Inform the client of all hostkeys */ static void notify_hostkeys(struct ssh *ssh) { 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 (datafellows & SSH_BUG_HOSTKEYS) return; if ((buf = sshbuf_new()) == NULL) fatal("%s: sshbuf_new", __func__); 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("%s: key %d: %s %s", __func__, i, sshkey_ssh_name(key), fp); free(fp); if (nkeys == 0) { packet_start(SSH2_MSG_GLOBAL_REQUEST); packet_put_cstring("hostkeys-00@openssh.com"); packet_put_char(0); /* want-reply */ } sshbuf_reset(buf); if ((r = sshkey_putb(key, buf)) != 0) fatal("%s: couldn't put hostkey %d: %s", __func__, i, ssh_err(r)); packet_put_string(sshbuf_ptr(buf), sshbuf_len(buf)); nkeys++; } debug3("%s: sent %u hostkeys", __func__, nkeys); if (nkeys == 0) fatal("%s: no hostkeys", __func__); packet_send(); sshbuf_free(buf); } /* * returns 1 if connection should be dropped, 0 otherwise. * dropping starts at connection #max_startups_begin with a probability * of (max_startups_rate/100). the probability increases linearly until * all connections are dropped for startups > max_startups */ static int drop_connection(int startups) { int p, r; if (startups < options.max_startups_begin) return 0; if (startups >= options.max_startups) return 1; if (options.max_startups_rate == 100) return 1; p = 100 - options.max_startups_rate; p *= startups - options.max_startups_begin; p /= options.max_startups - options.max_startups_begin; p += options.max_startups_rate; r = arc4random_uniform(100); debug("drop_connection: p %d, r %d", p, r); return (r < p) ? 1 : 0; } static void usage(void) { fprintf(stderr, "%s, %s\n", SSH_VERSION, #ifdef WITH_OPENSSL SSLeay_version(SSLEAY_VERSION) #else "without OpenSSL" #endif ); fprintf(stderr, "usage: sshd [-46DdeiqTt] [-C connection_spec] [-c host_cert_file]\n" " [-E log_file] [-f config_file] [-g login_grace_time]\n" " [-h host_key_file] [-o option] [-p port] [-u len]\n" ); exit(1); } static void send_rexec_state(int fd, struct sshbuf *conf) { struct sshbuf *m; int r; debug3("%s: entering fd = %d config len %zu", __func__, fd, sshbuf_len(conf)); /* * Protocol from reexec master to child: * string configuration */ if ((m = sshbuf_new()) == NULL) fatal("%s: sshbuf_new failed", __func__); if ((r = sshbuf_put_stringb(m, conf)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); if (ssh_msg_send(fd, 0, m) == -1) fatal("%s: ssh_msg_send failed", __func__); sshbuf_free(m); debug3("%s: done", __func__); } static void recv_rexec_state(int fd, Buffer *conf) { Buffer m; char *cp; u_int len; debug3("%s: entering fd = %d", __func__, fd); buffer_init(&m); if (ssh_msg_recv(fd, &m) == -1) fatal("%s: ssh_msg_recv failed", __func__); if (buffer_get_char(&m) != 0) fatal("%s: rexec version mismatch", __func__); cp = buffer_get_string(&m, &len); if (conf != NULL) buffer_append(conf, cp, len); free(cp); buffer_free(&m); debug3("%s: done", __func__); } /* Accept a connection from inetd */ static void server_accept_inetd(int *sock_in, int *sock_out) { int fd; startup_pipe = -1; if (rexeced_flag) { close(REEXEC_CONFIG_PASS_FD); *sock_in = *sock_out = dup(STDIN_FILENO); if (!debug_flag) { startup_pipe = dup(REEXEC_STARTUP_PIPE_FD); close(REEXEC_STARTUP_PIPE_FD); } } 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 ((fd = open(_PATH_DEVNULL, O_RDWR, 0)) != -1) { dup2(fd, STDIN_FILENO); dup2(fd, STDOUT_FILENO); if (!log_stderr) dup2(fd, STDERR_FILENO); if (fd > (log_stderr ? STDERR_FILENO : STDOUT_FILENO)) close(fd); } debug("inetd sockets after dupping: %d, %d", *sock_in, *sock_out); } /* * Listen for TCP connections */ static void listen_on_addrs(struct listenaddr *la) { int ret, listen_sock; struct addrinfo *ai; char ntop[NI_MAXHOST], strport[NI_MAXSERV]; for (ai = la->addrs; ai; ai = ai->ai_next) { if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) continue; if (num_listen_socks >= MAX_LISTEN_SOCKS) fatal("Too many listen sockets. " "Enlarge MAX_LISTEN_SOCKS"); if ((ret = getnameinfo(ai->ai_addr, ai->ai_addrlen, ntop, sizeof(ntop), strport, sizeof(strport), NI_NUMERICHOST|NI_NUMERICSERV)) != 0) { error("getnameinfo failed: %.100s", ssh_gai_strerror(ret)); continue; } /* Create socket for listening. */ listen_sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); if (listen_sock < 0) { /* kernel may not support ipv6 */ verbose("socket: %.100s", strerror(errno)); continue; } if (set_nonblock(listen_sock) == -1) { close(listen_sock); continue; } if (fcntl(listen_sock, F_SETFD, FD_CLOEXEC) == -1) { verbose("socket: CLOEXEC: %s", strerror(errno)); close(listen_sock); continue; } /* Socket options */ set_reuseaddr(listen_sock); if (la->rdomain != NULL && set_rdomain(listen_sock, la->rdomain) == -1) { close(listen_sock); continue; } debug("Bind to port %s on %s.", strport, ntop); /* Bind the socket to the desired port. */ if (bind(listen_sock, ai->ai_addr, ai->ai_addrlen) < 0) { error("Bind to port %s on %s failed: %.200s.", strport, ntop, strerror(errno)); close(listen_sock); continue; } listen_socks[num_listen_socks] = listen_sock; num_listen_socks++; /* Start listening on the port. */ if (listen(listen_sock, SSH_LISTEN_BACKLOG) < 0) fatal("listen on [%s]:%s: %.100s", ntop, strport, strerror(errno)); logit("Server listening on %s port %s%s%s.", ntop, strport, la->rdomain == NULL ? "" : " rdomain ", la->rdomain == NULL ? "" : la->rdomain); } } static void server_listen(void) { u_int i; for (i = 0; i < options.num_listen_addrs; i++) { listen_on_addrs(&options.listen_addrs[i]); freeaddrinfo(options.listen_addrs[i].addrs); free(options.listen_addrs[i].rdomain); memset(&options.listen_addrs[i], 0, sizeof(options.listen_addrs[i])); } free(options.listen_addrs); options.listen_addrs = NULL; options.num_listen_addrs = 0; if (!num_listen_socks) fatal("Cannot bind any address."); } /* * The main TCP accept loop. Note that, for the non-debug case, returns * from this function are in a forked subprocess. */ static void server_accept_loop(int *sock_in, int *sock_out, int *newsock, int *config_s) { fd_set *fdset; int i, j, ret, maxfd; int startups = 0; int startup_p[2] = { -1 , -1 }; struct sockaddr_storage from; socklen_t fromlen; pid_t pid; /* setup fd set for accept */ fdset = NULL; maxfd = 0; for (i = 0; i < num_listen_socks; i++) if (listen_socks[i] > maxfd) maxfd = listen_socks[i]; /* pipes connected to unauthenticated childs */ startup_pipes = xcalloc(options.max_startups, sizeof(int)); for (i = 0; i < options.max_startups; i++) startup_pipes[i] = -1; /* * Stay listening for connections until the system crashes or * the daemon is killed with a signal. */ for (;;) { if (received_sighup) sighup_restart(); free(fdset); fdset = xcalloc(howmany(maxfd + 1, NFDBITS), sizeof(fd_mask)); for (i = 0; i < num_listen_socks; i++) FD_SET(listen_socks[i], fdset); for (i = 0; i < options.max_startups; i++) if (startup_pipes[i] != -1) FD_SET(startup_pipes[i], fdset); /* Wait in select until there is a connection. */ ret = select(maxfd+1, fdset, NULL, NULL, NULL); if (ret < 0 && errno != EINTR) error("select: %.100s", strerror(errno)); if (received_sigterm) { logit("Received signal %d; terminating.", (int) received_sigterm); close_listen_socks(); if (options.pid_file != NULL) unlink(options.pid_file); exit(received_sigterm == SIGTERM ? 0 : 255); } if (ret < 0) continue; for (i = 0; i < options.max_startups; i++) if (startup_pipes[i] != -1 && FD_ISSET(startup_pipes[i], fdset)) { /* * the read end of the pipe is ready * if the child has closed the pipe * after successful authentication * or if the child has died */ close(startup_pipes[i]); startup_pipes[i] = -1; startups--; } for (i = 0; i < num_listen_socks; i++) { if (!FD_ISSET(listen_socks[i], fdset)) continue; fromlen = sizeof(from); *newsock = accept(listen_socks[i], (struct sockaddr *)&from, &fromlen); if (*newsock < 0) { if (errno != EINTR && errno != EWOULDBLOCK && errno != ECONNABORTED) error("accept: %.100s", strerror(errno)); if (errno == EMFILE || errno == ENFILE) usleep(100 * 1000); continue; } if (unset_nonblock(*newsock) == -1) { close(*newsock); continue; } if (drop_connection(startups) == 1) { char *laddr = get_local_ipaddr(*newsock); char *raddr = get_peer_ipaddr(*newsock); verbose("drop connection #%d from [%s]:%d " "on [%s]:%d past MaxStartups", startups, raddr, get_peer_port(*newsock), laddr, get_local_port(*newsock)); free(laddr); free(raddr); close(*newsock); continue; } if (pipe(startup_p) == -1) { close(*newsock); continue; } if (rexec_flag && socketpair(AF_UNIX, SOCK_STREAM, 0, config_s) == -1) { error("reexec socketpair: %s", strerror(errno)); close(*newsock); close(startup_p[0]); close(startup_p[1]); continue; } for (j = 0; j < options.max_startups; j++) if (startup_pipes[j] == -1) { startup_pipes[j] = startup_p[0]; if (maxfd < startup_p[0]) maxfd = startup_p[0]; startups++; break; } /* * Got connection. Fork a child to handle it, unless * we are in debugging mode. */ if (debug_flag) { /* * In debugging mode. Close the listening * socket, and start processing the * connection without forking. */ debug("Server will not fork when running in debugging mode."); close_listen_socks(); *sock_in = *newsock; *sock_out = *newsock; 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]); } break; } /* * Normal production daemon. Fork, and have * the child process the connection. The * parent continues listening. */ if ((pid = fork()) == 0) { /* * Child. Close the listening and * max_startup sockets. Start using * the accepted socket. Reinitialize * logging (since our pid has changed). * We break out of the loop to handle * the connection. */ startup_pipe = startup_p[1]; close_startup_pipes(); close_listen_socks(); *sock_in = *newsock; *sock_out = *newsock; log_init(__progname, options.log_level, options.log_facility, log_stderr); if (rexec_flag) close(config_s[0]); break; } /* Parent. Stay in the loop. */ if (pid < 0) error("fork: %.100s", strerror(errno)); else debug("Forked child %ld.", (long)pid); close(startup_p[1]); if (rexec_flag) { send_rexec_state(config_s[0], &cfg); close(config_s[0]); close(config_s[1]); } close(*newsock); } /* child process check (or debug mode) */ if (num_listen_socks < 0) break; } } /* * 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 autentication 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) { 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) < 0) 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; } /* Set the routing domain for this process */ static void set_process_rdomain(struct ssh *ssh, const char *name) { 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("%s: set routing domain %d (was %d)", __func__, rtable, ortable); } /* * Main program for the daemon. */ int main(int ac, char **av) { struct ssh *ssh = NULL; extern char *optarg; extern int optind; int r, opt, on = 1, 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 }; 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; ssh_malloc_init(); /* must be called before any mallocs */ /* Save argv. */ saved_argv = av; rexec_argc = ac; /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */ sanitise_stdfd(); /* Initialize configuration options to their default values. */ initialize_server_options(&options); /* Parse command-line arguments. */ while ((opt = getopt(ac, av, "C:E:b:c:f:g:h:k:o:p:u:46DQRTdeiqrt")) != -1) { switch (opt) { case '4': options.address_family = AF_INET; break; case '6': options.address_family = AF_INET6; break; case 'f': config_file_name = optarg; break; case 'c': servconf_add_hostcert("[command-line]", 0, &options, optarg); break; case 'd': if (debug_flag == 0) { debug_flag = 1; options.log_level = SYSLOG_LEVEL_DEBUG1; } else if (options.log_level < SYSLOG_LEVEL_DEBUG3) options.log_level++; break; case 'D': no_daemon_flag = 1; break; case 'E': logfile = optarg; /* FALLTHROUGH */ case 'e': log_stderr = 1; break; case 'i': inetd_flag = 1; break; case 'r': rexec_flag = 0; break; case 'R': rexeced_flag = 1; inetd_flag = 1; break; case 'Q': /* ignored */ break; case 'q': options.log_level = SYSLOG_LEVEL_QUIET; break; case 'b': /* protocol 1, ignored */ break; case 'p': options.ports_from_cmdline = 1; if (options.num_ports >= MAX_PORTS) { fprintf(stderr, "too many ports.\n"); exit(1); } options.ports[options.num_ports++] = a2port(optarg); if (options.ports[options.num_ports-1] <= 0) { fprintf(stderr, "Bad port number.\n"); exit(1); } break; case 'g': if ((options.login_grace_time = convtime(optarg)) == -1) { fprintf(stderr, "Invalid login grace time.\n"); exit(1); } break; case 'k': /* protocol 1, ignored */ break; case 'h': servconf_add_hostkey("[command-line]", 0, &options, optarg); break; case 't': test_flag = 1; break; case 'T': test_flag = 2; break; case 'C': connection_info = get_connection_info(0, 0); if (parse_server_match_testspec(connection_info, optarg) == -1) exit(1); break; case 'u': utmp_len = (u_int)strtonum(optarg, 0, HOST_NAME_MAX+1+1, NULL); if (utmp_len > HOST_NAME_MAX+1) { fprintf(stderr, "Invalid utmp length.\n"); exit(1); } break; case 'o': line = xstrdup(optarg); if (process_server_config_line(&options, line, "command-line", 0, NULL, NULL) != 0) exit(1); free(line); break; case '?': default: usage(); break; } } if (rexeced_flag || inetd_flag) rexec_flag = 0; if (!test_flag && (rexec_flag && (av[0] == NULL || *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); #ifdef WITH_OPENSSL OpenSSL_add_all_algorithms(); #endif /* If requested, redirect the logs to the specified logfile. */ if (logfile != NULL) log_redirect_stderr_to(logfile); /* * Force logging to stderr until we have loaded the private host * key (unless started from inetd) */ log_init(__progname, options.log_level == SYSLOG_LEVEL_NOT_SET ? SYSLOG_LEVEL_INFO : options.log_level, options.log_facility == SYSLOG_FACILITY_NOT_SET ? SYSLOG_FACILITY_AUTH : options.log_facility, log_stderr || !inetd_flag); sensitive_data.have_ssh2_key = 0; /* * If we're not doing an extended test do not silently ignore connection * test params. */ if (test_flag < 2 && connection_info != NULL) fatal("Config test connection parameter (-C) provided without " "test mode (-T)"); /* Fetch our configuration */ buffer_init(&cfg); if (rexeced_flag) recv_rexec_state(REEXEC_CONFIG_PASS_FD, &cfg); else 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, NULL); /* Fill in default values for those options not explicitly set. */ fill_default_server_options(&options); /* challenge-response is implemented via keyboard interactive */ if (options.challenge_response_authentication) options.kbd_interactive_authentication = 1; /* Check that options are sensible */ if (options.authorized_keys_command_user == NULL && (options.authorized_keys_command != NULL && strcasecmp(options.authorized_keys_command, "none") != 0)) fatal("AuthorizedKeysCommand set without " "AuthorizedKeysCommandUser"); if (options.authorized_principals_command_user == NULL && (options.authorized_principals_command != NULL && strcasecmp(options.authorized_principals_command, "none") != 0)) fatal("AuthorizedPrincipalsCommand set without " "AuthorizedPrincipalsCommandUser"); /* * 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 * and warns for trivial misconfigurations that could break login. */ if (options.num_auth_methods != 0) { for (i = 0; i < options.num_auth_methods; i++) { if (auth2_methods_valid(options.auth_methods[i], 1) == 0) break; } if (i >= options.num_auth_methods) fatal("AuthenticationMethods cannot be satisfied by " "enabled authentication methods"); } /* Check that there are no remaining arguments. */ if (optind < ac) { fprintf(stderr, "Extra argument %s.\n", av[optind]); exit(1); } debug("sshd version %s, %s", SSH_VERSION, #ifdef WITH_OPENSSL SSLeay_version(SSLEAY_VERSION) #else "without OpenSSL" #endif ); /* load host keys */ sensitive_data.host_keys = xcalloc(options.num_host_key_files, sizeof(struct sshkey *)); sensitive_data.host_pubkeys = xcalloc(options.num_host_key_files, sizeof(struct sshkey *)); if (options.host_key_agent) { if (strcmp(options.host_key_agent, SSH_AUTHSOCKET_ENV_NAME)) setenv(SSH_AUTHSOCKET_ENV_NAME, options.host_key_agent, 1); if ((r = ssh_get_authentication_socket(NULL)) == 0) have_agent = 1; else error("Could not connect to agent \"%s\": %s", options.host_key_agent, ssh_err(r)); } for (i = 0; i < options.num_host_key_files; i++) { if (options.host_key_files[i] == NULL) continue; key = key_load_private(options.host_key_files[i], "", NULL); pubkey = key_load_public(options.host_key_files[i], NULL); if (pubkey == NULL && key != NULL) pubkey = key_demote(key); sensitive_data.host_keys[i] = key; sensitive_data.host_pubkeys[i] = pubkey; if (key == NULL && pubkey != NULL && have_agent) { debug("will rely on agent for hostkey %s", options.host_key_files[i]); keytype = pubkey->type; } else if (key != NULL) { keytype = key->type; } else { error("Could not load host key: %s", options.host_key_files[i]); sensitive_data.host_keys[i] = NULL; sensitive_data.host_pubkeys[i] = NULL; continue; } switch (keytype) { case KEY_RSA: case KEY_DSA: case KEY_ECDSA: case KEY_ED25519: if (have_agent || key != NULL) sensitive_data.have_ssh2_key = 1; break; } if ((fp = sshkey_fingerprint(pubkey, options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL) fatal("sshkey_fingerprint failed"); debug("%s host key #%d: %s %s", key ? "private" : "agent", i, sshkey_ssh_name(pubkey), fp); free(fp); } if (!sensitive_data.have_ssh2_key) { logit("sshd: no hostkeys available -- exiting."); exit(1); } /* * Load certificates. They are stored in an array at identical * indices to the public keys that they relate to. */ sensitive_data.host_certificates = xcalloc(options.num_host_key_files, sizeof(struct sshkey *)); for (i = 0; i < options.num_host_key_files; i++) sensitive_data.host_certificates[i] = NULL; for (i = 0; i < options.num_host_cert_files; i++) { if (options.host_cert_files[i] == NULL) continue; key = key_load_public(options.host_cert_files[i], NULL); if (key == NULL) { error("Could not load host certificate: %s", options.host_cert_files[i]); continue; } if (!key_is_cert(key)) { error("Certificate file is not a certificate: %s", options.host_cert_files[i]); key_free(key); continue; } /* Find matching private key */ for (j = 0; j < options.num_host_key_files; j++) { if (key_equal_public(key, sensitive_data.host_keys[j])) { sensitive_data.host_certificates[j] = key; break; } } if (j >= options.num_host_key_files) { error("No matching private key for certificate: %s", options.host_cert_files[i]); key_free(key); continue; } sensitive_data.host_certificates[j] = key; debug("host certificate: #%u type %d %s", j, key->type, key_type(key)); } if (use_privsep) { struct stat st; if (getpwnam(SSH_PRIVSEP_USER) == NULL) fatal("Privilege separation user %s does not exist", SSH_PRIVSEP_USER); if ((stat(_PATH_PRIVSEP_CHROOT_DIR, &st) == -1) || (S_ISDIR(st.st_mode) == 0)) fatal("Missing privilege separation directory: %s", _PATH_PRIVSEP_CHROOT_DIR); if (st.st_uid != 0 || (st.st_mode & (S_IWGRP|S_IWOTH)) != 0) fatal("%s must be owned by root and not group or " "world-writable.", _PATH_PRIVSEP_CHROOT_DIR); } if (test_flag > 1) { /* * 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(0, 0); parse_server_match_config(&options, connection_info); dump_config(&options); } /* Configuration looks good, so exit if in test mode. */ if (test_flag) exit(0); 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; } /* Ensure that umask disallows at least group and world write */ new_umask = umask(0077) | 0022; (void) umask(new_umask); /* Initialize the log (it is reinitialized below in case we forked). */ if (debug_flag && (!inetd_flag || rexeced_flag)) log_stderr = 1; log_init(__progname, options.log_level, options.log_facility, log_stderr); /* * If not in debugging mode, not started from inetd and not already * daemonized (eg re-exec via SIGHUP), disconnect from the controlling * terminal, and fork. The original process exits. */ already_daemon = daemonized(); if (!(debug_flag || inetd_flag || no_daemon_flag || already_daemon)) { if (daemon(0, 0) < 0) fatal("daemon() failed: %.200s", strerror(errno)); disconnect_controlling_tty(); } /* Reinitialize the log (because of the fork above). */ log_init(__progname, options.log_level, options.log_facility, log_stderr); /* Chdir to the root directory so that the current disk can be unmounted if desired. */ if (chdir("/") == -1) error("chdir(\"/\"): %s", strerror(errno)); /* ignore SIGPIPE */ signal(SIGPIPE, SIG_IGN); /* Get a connection, either from inetd or a listening TCP socket */ if (inetd_flag) { server_accept_inetd(&sock_in, &sock_out); } else { server_listen(); signal(SIGHUP, sighup_handler); signal(SIGCHLD, main_sigchld_handler); signal(SIGTERM, sigterm_handler); signal(SIGQUIT, sigterm_handler); /* * Write out the pid file after the sigterm handler * is setup and the listen sockets are bound */ if (options.pid_file != NULL && !debug_flag) { FILE *f = fopen(options.pid_file, "w"); if (f == NULL) { error("Couldn't create pid file \"%s\": %s", options.pid_file, strerror(errno)); } else { fprintf(f, "%ld\n", (long) getpid()); fclose(f); } } /* Accept a connection and return in a forked child */ server_accept_loop(&sock_in, &sock_out, &newsock, config_s); } /* This is the child processing a new connection. */ setproctitle("%s", "[accepted]"); /* * Create a new session and process group since the 4.4BSD * setlogin() affects the entire process group. We don't * want the child to be able to affect the parent. */ if (!debug_flag && !inetd_flag && setsid() < 0) error("setsid: %.100s", strerror(errno)); if (rexec_flag) { int fd; debug("rexec start in %d out %d newsock %d pipe %d sock %d", sock_in, sock_out, newsock, startup_pipe, config_s[0]); dup2(newsock, STDIN_FILENO); dup2(STDIN_FILENO, STDOUT_FILENO); if (startup_pipe == -1) close(REEXEC_STARTUP_PIPE_FD); else if (startup_pipe != REEXEC_STARTUP_PIPE_FD) { dup2(startup_pipe, REEXEC_STARTUP_PIPE_FD); close(startup_pipe); startup_pipe = REEXEC_STARTUP_PIPE_FD; } dup2(config_s[1], REEXEC_CONFIG_PASS_FD); close(config_s[1]); 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 ((fd = open(_PATH_DEVNULL, O_RDWR, 0)) != -1) { dup2(fd, STDIN_FILENO); dup2(fd, STDOUT_FILENO); if (fd > STDERR_FILENO) close(fd); } 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); /* * Disable the key regeneration alarm. We will not regenerate the * key since we are no longer in a position to give it to anyone. We * will not restart on SIGHUP since it no longer makes sense. */ alarm(0); signal(SIGALRM, SIG_DFL); signal(SIGHUP, SIG_DFL); signal(SIGTERM, SIG_DFL); signal(SIGQUIT, SIG_DFL); signal(SIGCHLD, SIG_DFL); /* * Register our connection. This turns encryption off because we do * not have a key. */ packet_set_connection(sock_in, sock_out); packet_set_server(); ssh = active_state; /* XXX */ check_ip_options(ssh); /* Prepare the channels layer */ channel_init_channels(ssh); channel_set_af(ssh, options.address_family); process_permitopen(ssh, &options); /* Set SO_KEEPALIVE if requested. */ if (options.tcp_keep_alive && packet_connection_is_on_socket() && setsockopt(sock_in, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)) < 0) error("setsockopt SO_KEEPALIVE: %.100s", strerror(errno)); if ((remote_port = ssh_remote_port(ssh)) < 0) { debug("ssh_remote_port failed"); cleanup_exit(255); } /* * 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); 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. */ signal(SIGALRM, grace_alarm_handler); if (!debug_flag) alarm(options.login_grace_time); sshd_exchange_identification(ssh, sock_in, sock_out); packet_set_nonblocking(); /* allocate authentication context */ authctxt = xcalloc(1, sizeof(*authctxt)); /* XXX global for cleanup, access from other modules */ the_authctxt = authctxt; /* prepare buffer to collect messages to display to user after login */ buffer_init(&loginmsg); auth_debug_reset(); if (use_privsep) { if (privsep_preauth(authctxt) == 1) goto authenticated; } else if (have_agent) { if ((r = ssh_get_authentication_socket(&auth_sock)) != 0) { error("Unable to get agent socket: %s", ssh_err(r)); have_agent = 0; } } /* perform the key exchange */ /* authenticate user and start session */ do_ssh2_kex(); do_authentication2(authctxt); /* * If we use privilege separation, the unprivileged child transfers * the current keystate and exits */ if (use_privsep) { mm_send_keystate(pmonitor); packet_clear_keys(); exit(0); } authenticated: /* * Cancel the alarm we set to limit the time taken for * authentication. */ alarm(0); signal(SIGALRM, SIG_DFL); authctxt->authenticated = 1; if (startup_pipe != -1) { close(startup_pipe); startup_pipe = -1; } if (options.routing_domain != NULL) set_process_rdomain(ssh, options.routing_domain); /* * In privilege separation, we fork another child and prepare * file descriptor passing. */ if (use_privsep) { privsep_postauth(authctxt); /* the monitor process [priv] will not return */ } packet_set_timeout(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. */ packet_get_bytes(&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); packet_close(); if (use_privsep) mm_terminate(); exit(0); } int sshd_hostkey_sign(struct sshkey *privkey, struct sshkey *pubkey, u_char **signature, size_t *slen, const u_char *data, size_t dlen, const char *alg, u_int flag) { int r; u_int xxx_slen, xxx_dlen = dlen; if (privkey) { if (PRIVSEP(key_sign(privkey, signature, &xxx_slen, data, xxx_dlen, alg) < 0)) fatal("%s: key_sign failed", __func__); if (slen) *slen = xxx_slen; } else if (use_privsep) { if (mm_key_sign(pubkey, signature, &xxx_slen, data, xxx_dlen, alg) < 0) fatal("%s: pubkey_sign failed", __func__); if (slen) *slen = xxx_slen; } else { if ((r = ssh_agent_sign(auth_sock, pubkey, signature, slen, data, dlen, alg, datafellows)) != 0) fatal("%s: ssh_agent_sign failed: %s", __func__, ssh_err(r)); } return 0; } /* SSH2 key exchange */ static void do_ssh2_kex(void) { char *myproposal[PROPOSAL_MAX] = { KEX_SERVER }; struct kex *kex; int r; myproposal[PROPOSAL_KEX_ALGS] = compat_kex_proposal( options.kex_algorithms); myproposal[PROPOSAL_ENC_ALGS_CTOS] = compat_cipher_proposal( options.ciphers); myproposal[PROPOSAL_ENC_ALGS_STOC] = compat_cipher_proposal( options.ciphers); myproposal[PROPOSAL_MAC_ALGS_CTOS] = myproposal[PROPOSAL_MAC_ALGS_STOC] = options.macs; if (options.compression == COMP_NONE) { myproposal[PROPOSAL_COMP_ALGS_CTOS] = myproposal[PROPOSAL_COMP_ALGS_STOC] = "none"; } if (options.rekey_limit || options.rekey_interval) packet_set_rekey_limits(options.rekey_limit, options.rekey_interval); myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = compat_pkalg_proposal( list_hostkey_types()); /* start key exchange */ if ((r = kex_setup(active_state, myproposal)) != 0) fatal("kex_setup: %s", ssh_err(r)); kex = active_state->kex; #ifdef WITH_OPENSSL kex->kex[KEX_DH_GRP1_SHA1] = kexdh_server; kex->kex[KEX_DH_GRP14_SHA1] = kexdh_server; kex->kex[KEX_DH_GRP14_SHA256] = kexdh_server; kex->kex[KEX_DH_GRP16_SHA512] = kexdh_server; kex->kex[KEX_DH_GRP18_SHA512] = kexdh_server; kex->kex[KEX_DH_GEX_SHA1] = kexgex_server; kex->kex[KEX_DH_GEX_SHA256] = kexgex_server; kex->kex[KEX_ECDH_SHA2] = kexecdh_server; #endif kex->kex[KEX_C25519_SHA256] = kexc25519_server; kex->server = 1; kex->client_version_string=client_version_string; kex->server_version_string=server_version_string; 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(active_state, DISPATCH_BLOCK, &kex->done); session_id2 = kex->session_id; session_id2_len = kex->session_id_len; #ifdef DEBUG_KEXDH /* send 1st encrypted/maced/compressed message */ packet_start(SSH2_MSG_IGNORE); packet_put_cstring("markus"); packet_send(); packet_write_wait(); #endif debug("KEX done"); } /* server specific fatal cleanup */ void cleanup_exit(int i) { struct ssh *ssh = active_state; /* XXX */ if (the_authctxt) { do_cleanup(ssh, 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("%s: kill(%d): %s", __func__, pmonitor->m_pid, strerror(errno)); } } _exit(i); }