version 1.312, 2005/07/25 11:59:40 |
version 1.312.2.3, 2006/11/08 00:44:05 |
|
|
|
/* $OpenBSD$ */ |
/* |
/* |
* Author: Tatu Ylonen <ylo@cs.hut.fi> |
* Author: Tatu Ylonen <ylo@cs.hut.fi> |
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland |
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland |
|
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
*/ |
*/ |
|
|
#include "includes.h" |
#include <sys/types.h> |
RCSID("$OpenBSD$"); |
#include <sys/ioctl.h> |
|
#include <sys/wait.h> |
|
#include <sys/tree.h> |
|
#include <sys/stat.h> |
|
#include <sys/socket.h> |
|
#include <sys/time.h> |
|
|
|
#include <errno.h> |
|
#include <signal.h> |
|
#include <fcntl.h> |
|
#include <netdb.h> |
|
#include <paths.h> |
|
#include <pwd.h> |
|
#include <signal.h> |
|
#include <stdio.h> |
|
#include <stdlib.h> |
|
#include <string.h> |
|
#include <unistd.h> |
|
|
#include <openssl/dh.h> |
#include <openssl/dh.h> |
#include <openssl/bn.h> |
#include <openssl/bn.h> |
#include <openssl/md5.h> |
#include <openssl/md5.h> |
#include <openssl/rand.h> |
#include <openssl/rand.h> |
|
|
|
#include "xmalloc.h" |
#include "ssh.h" |
#include "ssh.h" |
#include "ssh1.h" |
#include "ssh1.h" |
#include "ssh2.h" |
#include "ssh2.h" |
#include "xmalloc.h" |
|
#include "rsa.h" |
#include "rsa.h" |
#include "sshpty.h" |
#include "sshpty.h" |
#include "packet.h" |
#include "packet.h" |
#include "log.h" |
#include "log.h" |
|
#include "buffer.h" |
#include "servconf.h" |
#include "servconf.h" |
#include "uidswap.h" |
#include "uidswap.h" |
#include "compat.h" |
#include "compat.h" |
#include "buffer.h" |
|
#include "bufaux.h" |
|
#include "cipher.h" |
#include "cipher.h" |
#include "kex.h" |
|
#include "key.h" |
#include "key.h" |
|
#include "kex.h" |
#include "dh.h" |
#include "dh.h" |
#include "myproposal.h" |
#include "myproposal.h" |
#include "authfile.h" |
#include "authfile.h" |
#include "pathnames.h" |
#include "pathnames.h" |
#include "atomicio.h" |
#include "atomicio.h" |
#include "canohost.h" |
#include "canohost.h" |
|
#include "hostfile.h" |
#include "auth.h" |
#include "auth.h" |
#include "misc.h" |
#include "misc.h" |
#include "msg.h" |
#include "msg.h" |
|
|
#include "session.h" |
#include "session.h" |
#include "monitor_mm.h" |
#include "monitor_mm.h" |
#include "monitor.h" |
#include "monitor.h" |
|
#ifdef GSSAPI |
|
#include "ssh-gss.h" |
|
#endif |
#include "monitor_wrap.h" |
#include "monitor_wrap.h" |
#include "monitor_fdpass.h" |
#include "monitor_fdpass.h" |
|
#include "version.h" |
|
|
#ifdef LIBWRAP |
#ifdef LIBWRAP |
#include <tcpd.h> |
#include <tcpd.h> |
|
|
int startup_pipe; /* in child */ |
int startup_pipe; /* in child */ |
|
|
/* variables used for privilege separation */ |
/* variables used for privilege separation */ |
int use_privsep; |
int use_privsep = -1; |
struct monitor *pmonitor = NULL; |
struct monitor *pmonitor = NULL; |
|
|
/* global authentication context */ |
/* global authentication context */ |
Authctxt *the_authctxt = NULL; |
Authctxt *the_authctxt = NULL; |
|
|
|
/* sshd_config buffer */ |
|
Buffer cfg; |
|
|
/* message to be displayed after login */ |
/* message to be displayed after login */ |
Buffer loginmsg; |
Buffer loginmsg; |
|
|
|
|
* the effect is to reread the configuration file (and to regenerate |
* the effect is to reread the configuration file (and to regenerate |
* the server key). |
* the server key). |
*/ |
*/ |
|
|
|
/*ARGSUSED*/ |
static void |
static void |
sighup_handler(int sig) |
sighup_handler(int sig) |
{ |
{ |
|
|
/* |
/* |
* Generic signal handler for terminating signals in the master daemon. |
* Generic signal handler for terminating signals in the master daemon. |
*/ |
*/ |
|
/*ARGSUSED*/ |
static void |
static void |
sigterm_handler(int sig) |
sigterm_handler(int sig) |
{ |
{ |
|
|
* SIGCHLD handler. This is called whenever a child dies. This will then |
* SIGCHLD handler. This is called whenever a child dies. This will then |
* reap any zombies left by exited children. |
* reap any zombies left by exited children. |
*/ |
*/ |
|
/*ARGSUSED*/ |
static void |
static void |
main_sigchld_handler(int sig) |
main_sigchld_handler(int sig) |
{ |
{ |
|
|
/* |
/* |
* Signal handler for the alarm after the login grace period has expired. |
* Signal handler for the alarm after the login grace period has expired. |
*/ |
*/ |
|
/*ARGSUSED*/ |
static void |
static void |
grace_alarm_handler(int sig) |
grace_alarm_handler(int sig) |
{ |
{ |
/* XXX no idea how fix this signal handler */ |
|
|
|
if (use_privsep && pmonitor != NULL && pmonitor->m_pid > 0) |
if (use_privsep && pmonitor != NULL && pmonitor->m_pid > 0) |
kill(pmonitor->m_pid, SIGALRM); |
kill(pmonitor->m_pid, SIGALRM); |
|
|
/* Log error and exit. */ |
/* Log error and exit. */ |
fatal("Timeout before authentication for %s", get_remote_ipaddr()); |
sigdie("Timeout before authentication for %s", get_remote_ipaddr()); |
} |
} |
|
|
/* |
/* |
|
|
arc4random_stir(); |
arc4random_stir(); |
} |
} |
|
|
|
/*ARGSUSED*/ |
static void |
static void |
key_regeneration_alarm(int sig) |
key_regeneration_alarm(int sig) |
{ |
{ |
|
|
{ |
{ |
if (authctxt->pw->pw_uid == 0 || options.use_login) { |
if (authctxt->pw->pw_uid == 0 || options.use_login) { |
/* File descriptor passing is broken or root login */ |
/* File descriptor passing is broken or root login */ |
monitor_apply_keystate(pmonitor); |
|
use_privsep = 0; |
use_privsep = 0; |
return; |
goto skip; |
} |
} |
|
|
/* Authentication complete */ |
|
alarm(0); |
|
if (startup_pipe != -1) { |
|
close(startup_pipe); |
|
startup_pipe = -1; |
|
} |
|
|
|
/* New socket pair */ |
/* New socket pair */ |
monitor_reinit(pmonitor); |
monitor_reinit(pmonitor); |
|
|
|
|
/* Drop privileges */ |
/* Drop privileges */ |
do_setusercontext(authctxt->pw); |
do_setusercontext(authctxt->pw); |
|
|
|
skip: |
/* It is safe now to apply the key state */ |
/* It is safe now to apply the key state */ |
monitor_apply_keystate(pmonitor); |
monitor_apply_keystate(pmonitor); |
|
|
|
|
debug3("%s: done", __func__); |
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 (fd > STDOUT_FILENO) |
|
close(fd); |
|
} |
|
debug("inetd sockets after dupping: %d, %d", *sock_in, *sock_out); |
|
} |
|
|
/* |
/* |
|
* Listen for TCP connections |
|
*/ |
|
static void |
|
server_listen(void) |
|
{ |
|
int ret, listen_sock, on = 1; |
|
struct addrinfo *ai; |
|
char ntop[NI_MAXHOST], strport[NI_MAXSERV]; |
|
|
|
for (ai = options.listen_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", |
|
(ret != EAI_SYSTEM) ? gai_strerror(ret) : |
|
strerror(errno)); |
|
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; |
|
} |
|
/* |
|
* Set socket options. |
|
* Allow local port reuse in TIME_WAIT. |
|
*/ |
|
if (setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, |
|
&on, sizeof(on)) == -1) |
|
error("setsockopt SO_REUSEADDR: %s", strerror(errno)); |
|
|
|
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.", ntop, strport); |
|
} |
|
freeaddrinfo(options.listen_addrs); |
|
|
|
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 key_used = 0, 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(); |
|
if (fdset != NULL) |
|
xfree(fdset); |
|
fdset = (fd_set *)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(); |
|
unlink(options.pid_file); |
|
exit(255); |
|
} |
|
if (key_used && key_do_regen) { |
|
generate_ephemeral_server_key(); |
|
key_used = 0; |
|
key_do_regen = 0; |
|
} |
|
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) |
|
error("accept: %.100s", strerror(errno)); |
|
continue; |
|
} |
|
if (unset_nonblock(*newsock) == -1) { |
|
close(*newsock); |
|
continue; |
|
} |
|
if (drop_connection(startups) == 1) { |
|
debug("drop connection #%d", startups); |
|
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]); |
|
} |
|
|
|
/* |
|
* Mark that the key has been used (it |
|
* was "given" to the child). |
|
*/ |
|
if ((options.protocol & SSH_PROTO_1) && |
|
key_used == 0) { |
|
/* Schedule server key regeneration alarm. */ |
|
signal(SIGALRM, key_regeneration_alarm); |
|
alarm(options.key_regeneration_time); |
|
key_used = 1; |
|
} |
|
|
|
close(*newsock); |
|
|
|
/* |
|
* Ensure that our random state differs |
|
* from that of the child |
|
*/ |
|
arc4random_stir(); |
|
} |
|
|
|
/* child process check (or debug mode) */ |
|
if (num_listen_socks < 0) |
|
break; |
|
} |
|
} |
|
|
|
|
|
/* |
* Main program for the daemon. |
* Main program for the daemon. |
*/ |
*/ |
int |
int |
|
|
{ |
{ |
extern char *optarg; |
extern char *optarg; |
extern int optind; |
extern int optind; |
int opt, j, i, fdsetsz, on = 1; |
int opt, i, on = 1; |
int sock_in = -1, sock_out = -1, newsock = -1; |
int sock_in = -1, sock_out = -1, newsock = -1; |
pid_t pid; |
|
socklen_t fromlen; |
|
fd_set *fdset; |
|
struct sockaddr_storage from; |
|
const char *remote_ip; |
const char *remote_ip; |
int remote_port; |
int remote_port; |
FILE *f; |
|
struct addrinfo *ai; |
|
char ntop[NI_MAXHOST], strport[NI_MAXSERV]; |
|
char *line; |
char *line; |
int listen_sock, maxfd; |
int config_s[2] = { -1 , -1 }; |
int startup_p[2] = { -1 , -1 }, config_s[2] = { -1 , -1 }; |
|
int startups = 0; |
|
Key *key; |
Key *key; |
Authctxt *authctxt; |
Authctxt *authctxt; |
int ret, key_used = 0; |
|
Buffer cfg; |
|
|
|
/* Save argv. */ |
/* Save argv. */ |
saved_argv = av; |
saved_argv = av; |
rexec_argc = ac; |
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 configuration options to their default values. */ |
initialize_server_options(&options); |
initialize_server_options(&options); |
|
|
|
|
options.log_level = SYSLOG_LEVEL_QUIET; |
options.log_level = SYSLOG_LEVEL_QUIET; |
break; |
break; |
case 'b': |
case 'b': |
options.server_key_bits = atoi(optarg); |
options.server_key_bits = (int)strtonum(optarg, 256, |
|
32768, NULL); |
break; |
break; |
case 'p': |
case 'p': |
options.ports_from_cmdline = 1; |
options.ports_from_cmdline = 1; |
|
|
test_flag = 1; |
test_flag = 1; |
break; |
break; |
case 'u': |
case 'u': |
utmp_len = atoi(optarg); |
utmp_len = (u_int)strtonum(optarg, 0, MAXHOSTNAMELEN+1, NULL); |
if (utmp_len > MAXHOSTNAMELEN) { |
if (utmp_len > MAXHOSTNAMELEN) { |
fprintf(stderr, "Invalid utmp length.\n"); |
fprintf(stderr, "Invalid utmp length.\n"); |
exit(1); |
exit(1); |
|
|
case 'o': |
case 'o': |
line = xstrdup(optarg); |
line = xstrdup(optarg); |
if (process_server_config_line(&options, line, |
if (process_server_config_line(&options, line, |
"command-line", 0) != 0) |
"command-line", 0, NULL, NULL, NULL, NULL) != 0) |
exit(1); |
exit(1); |
xfree(line); |
xfree(line); |
break; |
break; |
|
|
else |
else |
load_server_config(config_file_name, &cfg); |
load_server_config(config_file_name, &cfg); |
|
|
parse_server_config(&options, |
parse_server_config(&options, rexeced_flag ? "rexec" : config_file_name, |
rexeced_flag ? "rexec" : config_file_name, &cfg); |
&cfg, NULL, NULL, NULL); |
|
|
if (!rexec_flag) |
|
buffer_free(&cfg); |
|
|
|
/* Fill in default values for those options not explicitly set. */ |
/* Fill in default values for those options not explicitly set. */ |
fill_default_server_options(&options); |
fill_default_server_options(&options); |
|
|
|
|
debug("sshd version %.100s", SSH_VERSION); |
debug("sshd version %.100s", SSH_VERSION); |
|
|
/* load private host keys */ |
/* load private host keys */ |
sensitive_data.host_keys = xmalloc(options.num_host_key_files * |
sensitive_data.host_keys = xcalloc(options.num_host_key_files, |
sizeof(Key *)); |
sizeof(Key *)); |
for (i = 0; i < options.num_host_key_files; i++) |
for (i = 0; i < options.num_host_key_files; i++) |
sensitive_data.host_keys[i] = NULL; |
sensitive_data.host_keys[i] = NULL; |
|
|
} |
} |
|
|
if (use_privsep) { |
if (use_privsep) { |
struct passwd *pw; |
|
struct stat st; |
struct stat st; |
|
|
if ((pw = getpwnam(SSH_PRIVSEP_USER)) == NULL) |
if (getpwnam(SSH_PRIVSEP_USER) == NULL) |
fatal("Privilege separation user %s does not exist", |
fatal("Privilege separation user %s does not exist", |
SSH_PRIVSEP_USER); |
SSH_PRIVSEP_USER); |
if ((stat(_PATH_PRIVSEP_CHROOT_DIR, &st) == -1) || |
if ((stat(_PATH_PRIVSEP_CHROOT_DIR, &st) == -1) || |
|
|
exit(0); |
exit(0); |
|
|
if (rexec_flag) { |
if (rexec_flag) { |
rexec_argv = xmalloc(sizeof(char *) * (rexec_argc + 2)); |
rexec_argv = xcalloc(rexec_argc + 2, sizeof(char *)); |
for (i = 0; i < rexec_argc; i++) { |
for (i = 0; i < rexec_argc; i++) { |
debug("rexec_argv[%d]='%s'", i, saved_argv[i]); |
debug("rexec_argv[%d]='%s'", i, saved_argv[i]); |
rexec_argv[i] = saved_argv[i]; |
rexec_argv[i] = saved_argv[i]; |
|
|
* exits. |
* exits. |
*/ |
*/ |
if (!(debug_flag || inetd_flag || no_daemon_flag)) { |
if (!(debug_flag || inetd_flag || no_daemon_flag)) { |
#ifdef TIOCNOTTY |
|
int fd; |
int fd; |
#endif /* TIOCNOTTY */ |
|
if (daemon(0, 0) < 0) |
if (daemon(0, 0) < 0) |
fatal("daemon() failed: %.200s", strerror(errno)); |
fatal("daemon() failed: %.200s", strerror(errno)); |
|
|
/* Disconnect from the controlling tty. */ |
/* Disconnect from the controlling tty. */ |
#ifdef TIOCNOTTY |
|
fd = open(_PATH_TTY, O_RDWR | O_NOCTTY); |
fd = open(_PATH_TTY, O_RDWR | O_NOCTTY); |
if (fd >= 0) { |
if (fd >= 0) { |
(void) ioctl(fd, TIOCNOTTY, NULL); |
(void) ioctl(fd, TIOCNOTTY, NULL); |
close(fd); |
close(fd); |
} |
} |
#endif /* TIOCNOTTY */ |
|
} |
} |
/* Reinitialize the log (because of the fork above). */ |
/* Reinitialize the log (because of the fork above). */ |
log_init(__progname, options.log_level, options.log_facility, log_stderr); |
log_init(__progname, options.log_level, options.log_facility, log_stderr); |
|
|
/* ignore SIGPIPE */ |
/* ignore SIGPIPE */ |
signal(SIGPIPE, SIG_IGN); |
signal(SIGPIPE, SIG_IGN); |
|
|
/* Start listening for a socket, unless started from inetd. */ |
/* Get a connection, either from inetd or a listening TCP socket */ |
if (inetd_flag) { |
if (inetd_flag) { |
int fd; |
server_accept_inetd(&sock_in, &sock_out); |
|
|
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 (fd > STDOUT_FILENO) |
|
close(fd); |
|
} |
|
debug("inetd sockets after dupping: %d, %d", sock_in, sock_out); |
|
if ((options.protocol & SSH_PROTO_1) && |
if ((options.protocol & SSH_PROTO_1) && |
sensitive_data.server_key == NULL) |
sensitive_data.server_key == NULL) |
generate_ephemeral_server_key(); |
generate_ephemeral_server_key(); |
} else { |
} else { |
for (ai = options.listen_addrs; ai; ai = ai->ai_next) { |
server_listen(); |
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", |
|
(ret != EAI_SYSTEM) ? gai_strerror(ret) : |
|
strerror(errno)); |
|
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; |
|
} |
|
/* |
|
* Set socket options. |
|
* Allow local port reuse in TIME_WAIT. |
|
*/ |
|
if (setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, |
|
&on, sizeof(on)) == -1) |
|
error("setsockopt SO_REUSEADDR: %s", strerror(errno)); |
|
|
|
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. */ |
|
logit("Server listening on %s port %s.", ntop, strport); |
|
if (listen(listen_sock, SSH_LISTEN_BACKLOG) < 0) |
|
fatal("listen: %.100s", strerror(errno)); |
|
|
|
} |
|
freeaddrinfo(options.listen_addrs); |
|
|
|
if (!num_listen_socks) |
|
fatal("Cannot bind any address."); |
|
|
|
if (options.protocol & SSH_PROTO_1) |
if (options.protocol & SSH_PROTO_1) |
generate_ephemeral_server_key(); |
generate_ephemeral_server_key(); |
|
|
/* |
|
* Arrange to restart on SIGHUP. The handler needs |
|
* listen_sock. |
|
*/ |
|
signal(SIGHUP, sighup_handler); |
signal(SIGHUP, sighup_handler); |
|
signal(SIGCHLD, main_sigchld_handler); |
signal(SIGTERM, sigterm_handler); |
signal(SIGTERM, sigterm_handler); |
signal(SIGQUIT, sigterm_handler); |
signal(SIGQUIT, sigterm_handler); |
|
|
/* Arrange SIGCHLD to be caught. */ |
/* |
signal(SIGCHLD, main_sigchld_handler); |
* Write out the pid file after the sigterm handler |
|
* is setup and the listen sockets are bound |
/* Write out the pid file after the sigterm handler is setup */ |
*/ |
if (!debug_flag) { |
if (!debug_flag) { |
/* |
FILE *f = fopen(options.pid_file, "w"); |
* Record our pid in /var/run/sshd.pid to make it |
|
* easier to kill the correct sshd. We don't want to |
|
* do this before the bind above because the bind will |
|
* fail if there already is a daemon, and this will |
|
* overwrite any old pid in the file. |
|
*/ |
|
f = fopen(options.pid_file, "w"); |
|
if (f == NULL) { |
if (f == NULL) { |
error("Couldn't create pid file \"%s\": %s", |
error("Couldn't create pid file \"%s\": %s", |
options.pid_file, strerror(errno)); |
options.pid_file, strerror(errno)); |
|
|
} |
} |
} |
} |
|
|
/* setup fd set for listen */ |
/* Accept a connection and return in a forked child */ |
fdset = NULL; |
server_accept_loop(&sock_in, &sock_out, |
maxfd = 0; |
&newsock, config_s); |
for (i = 0; i < num_listen_socks; i++) |
|
if (listen_socks[i] > maxfd) |
|
maxfd = listen_socks[i]; |
|
/* pipes connected to unauthenticated childs */ |
|
startup_pipes = xmalloc(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(); |
|
if (fdset != NULL) |
|
xfree(fdset); |
|
fdsetsz = howmany(maxfd+1, NFDBITS) * sizeof(fd_mask); |
|
fdset = (fd_set *)xmalloc(fdsetsz); |
|
memset(fdset, 0, fdsetsz); |
|
|
|
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(); |
|
unlink(options.pid_file); |
|
exit(255); |
|
} |
|
if (key_used && key_do_regen) { |
|
generate_ephemeral_server_key(); |
|
key_used = 0; |
|
key_do_regen = 0; |
|
} |
|
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) |
|
error("accept: %.100s", strerror(errno)); |
|
continue; |
|
} |
|
if (unset_nonblock(newsock) == -1) { |
|
close(newsock); |
|
continue; |
|
} |
|
if (drop_connection(startups) == 1) { |
|
debug("drop connection #%d", startups); |
|
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; |
|
} else { |
|
/* |
|
* 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]); |
|
} |
|
|
|
/* Mark that the key has been used (it was "given" to the child). */ |
|
if ((options.protocol & SSH_PROTO_1) && |
|
key_used == 0) { |
|
/* Schedule server key regeneration alarm. */ |
|
signal(SIGALRM, key_regeneration_alarm); |
|
alarm(options.key_regeneration_time); |
|
key_used = 1; |
|
} |
|
|
|
arc4random_stir(); |
|
|
|
/* Close the new socket (the child is now taking care of it). */ |
|
close(newsock); |
|
} |
|
/* child process check (or debug mode) */ |
|
if (num_listen_socks < 0) |
|
break; |
|
} |
|
} |
} |
|
|
/* This is the child processing a new connection. */ |
/* This is the child processing a new connection. */ |
|
|
debug("get_remote_port failed"); |
debug("get_remote_port failed"); |
cleanup_exit(255); |
cleanup_exit(255); |
} |
} |
|
|
|
/* |
|
* We use get_canonical_hostname with usedns = 0 instead of |
|
* get_remote_ipaddr here so IP options will be checked. |
|
*/ |
|
(void) get_canonical_hostname(0); |
|
/* |
|
* The rest of the code depends on the fact that |
|
* get_remote_ipaddr() caches the remote ip, even if |
|
* the socket goes away. |
|
*/ |
remote_ip = get_remote_ipaddr(); |
remote_ip = get_remote_ipaddr(); |
|
|
#ifdef LIBWRAP |
#ifdef LIBWRAP |
|
|
verbose("Connection from %.500s port %d", remote_ip, remote_port); |
verbose("Connection from %.500s port %d", remote_ip, remote_port); |
|
|
/* |
/* |
* We don\'t want to listen forever unless the other side |
* We don't want to listen forever unless the other side |
* successfully authenticates itself. So we set up an alarm which is |
* successfully authenticates itself. So we set up an alarm which is |
* cleared after successful authentication. A limit of zero |
* cleared after successful authentication. A limit of zero |
* indicates no limit. Note that we don\'t set the alarm in debugging |
* 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 |
* mode; it is just annoying to have the server exit just when you |
* are about to discover the bug. |
* are about to discover the bug. |
*/ |
*/ |
|
|
packet_set_nonblocking(); |
packet_set_nonblocking(); |
|
|
/* allocate authentication context */ |
/* allocate authentication context */ |
authctxt = xmalloc(sizeof(*authctxt)); |
authctxt = xcalloc(1, sizeof(*authctxt)); |
memset(authctxt, 0, sizeof(*authctxt)); |
|
|
|
/* XXX global for cleanup, access from other modules */ |
/* XXX global for cleanup, access from other modules */ |
the_authctxt = authctxt; |
the_authctxt = authctxt; |
|
|
|
|
authenticated: |
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; |
|
} |
|
|
|
/* |
* In privilege separation, we fork another child and prepare |
* In privilege separation, we fork another child and prepare |
* file descriptor passing. |
* file descriptor passing. |
*/ |
*/ |
|
|
{ |
{ |
int rsafail = 0; |
int rsafail = 0; |
|
|
if (BN_cmp(sensitive_data.server_key->rsa->n, sensitive_data.ssh1_host_key->rsa->n) > 0) { |
if (BN_cmp(sensitive_data.server_key->rsa->n, |
|
sensitive_data.ssh1_host_key->rsa->n) > 0) { |
/* Server key has bigger modulus. */ |
/* Server key has bigger modulus. */ |
if (BN_num_bits(sensitive_data.server_key->rsa->n) < |
if (BN_num_bits(sensitive_data.server_key->rsa->n) < |
BN_num_bits(sensitive_data.ssh1_host_key->rsa->n) + SSH_KEY_BITS_RESERVED) { |
BN_num_bits(sensitive_data.ssh1_host_key->rsa->n) + |
fatal("do_connection: %s: server_key %d < host_key %d + SSH_KEY_BITS_RESERVED %d", |
SSH_KEY_BITS_RESERVED) { |
|
fatal("do_connection: %s: " |
|
"server_key %d < host_key %d + SSH_KEY_BITS_RESERVED %d", |
get_remote_ipaddr(), |
get_remote_ipaddr(), |
BN_num_bits(sensitive_data.server_key->rsa->n), |
BN_num_bits(sensitive_data.server_key->rsa->n), |
BN_num_bits(sensitive_data.ssh1_host_key->rsa->n), |
BN_num_bits(sensitive_data.ssh1_host_key->rsa->n), |
|
|
} else { |
} else { |
/* Host key has bigger modulus (or they are equal). */ |
/* Host key has bigger modulus (or they are equal). */ |
if (BN_num_bits(sensitive_data.ssh1_host_key->rsa->n) < |
if (BN_num_bits(sensitive_data.ssh1_host_key->rsa->n) < |
BN_num_bits(sensitive_data.server_key->rsa->n) + SSH_KEY_BITS_RESERVED) { |
BN_num_bits(sensitive_data.server_key->rsa->n) + |
fatal("do_connection: %s: host_key %d < server_key %d + SSH_KEY_BITS_RESERVED %d", |
SSH_KEY_BITS_RESERVED) { |
|
fatal("do_connection: %s: " |
|
"host_key %d < server_key %d + SSH_KEY_BITS_RESERVED %d", |
get_remote_ipaddr(), |
get_remote_ipaddr(), |
BN_num_bits(sensitive_data.ssh1_host_key->rsa->n), |
BN_num_bits(sensitive_data.ssh1_host_key->rsa->n), |
BN_num_bits(sensitive_data.server_key->rsa->n), |
BN_num_bits(sensitive_data.server_key->rsa->n), |
|
|
* key is in the highest bits. |
* key is in the highest bits. |
*/ |
*/ |
if (!rsafail) { |
if (!rsafail) { |
BN_mask_bits(session_key_int, sizeof(session_key) * 8); |
(void) BN_mask_bits(session_key_int, sizeof(session_key) * 8); |
len = BN_num_bytes(session_key_int); |
len = BN_num_bytes(session_key_int); |
if (len < 0 || (u_int)len > sizeof(session_key)) { |
if (len < 0 || (u_int)len > sizeof(session_key)) { |
error("do_connection: bad session key len from %s: " |
error("do_ssh1_kex: bad session key len from %s: " |
"session_key_int %d > sizeof(session_key) %lu", |
"session_key_int %d > sizeof(session_key) %lu", |
get_remote_ipaddr(), len, (u_long)sizeof(session_key)); |
get_remote_ipaddr(), len, (u_long)sizeof(session_key)); |
rsafail++; |
rsafail++; |
|
|
myproposal[PROPOSAL_COMP_ALGS_CTOS] = |
myproposal[PROPOSAL_COMP_ALGS_CTOS] = |
myproposal[PROPOSAL_COMP_ALGS_STOC] = "none,zlib@openssh.com"; |
myproposal[PROPOSAL_COMP_ALGS_STOC] = "none,zlib@openssh.com"; |
} |
} |
|
|
myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = list_hostkey_types(); |
myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = list_hostkey_types(); |
|
|
/* start key exchange */ |
/* start key exchange */ |
|
|
kex->kex[KEX_DH_GRP1_SHA1] = kexdh_server; |
kex->kex[KEX_DH_GRP1_SHA1] = kexdh_server; |
kex->kex[KEX_DH_GRP14_SHA1] = kexdh_server; |
kex->kex[KEX_DH_GRP14_SHA1] = kexdh_server; |
kex->kex[KEX_DH_GEX_SHA1] = kexgex_server; |
kex->kex[KEX_DH_GEX_SHA1] = kexgex_server; |
|
kex->kex[KEX_DH_GEX_SHA256] = kexgex_server; |
kex->server = 1; |
kex->server = 1; |
kex->client_version_string=client_version_string; |
kex->client_version_string=client_version_string; |
kex->server_version_string=server_version_string; |
kex->server_version_string=server_version_string; |