- djm@cvs.openbsd.org 2008/05/08 12:21:16
     [monitor.c monitor_wrap.c session.h servconf.c servconf.h session.c]
     [sshd_config sshd_config.5]
     Make the maximum number of sessions run-time controllable via
     a sshd_config MaxSessions knob. This is useful for disabling
     login/shell/subsystem access while leaving port-forwarding working
     (MaxSessions 0), disabling connection multiplexing (MaxSessions 1) or
     simply increasing the number of allows multiplexed sessions.
     Because some bozos are sure to configure MaxSessions in excess of the
     number of available file descriptors in sshd (which, at peak, might be
     as many as 9*MaxSessions), audit sshd to ensure that it doesn't leak fds
     on error paths, and make it fail gracefully on out-of-fd conditions -
     sending channel errors instead of than exiting with fatal().
     bz#1090; MaxSessions config bits and manpage from junyer AT gmail.com
     ok markus@
diff --git a/ChangeLog b/ChangeLog
index 99dbdaf..5ea4afc 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -77,6 +77,21 @@
      shouldn't happen in compliant implementations, but it could be
      abused to leak memory.
      ok markus@ (as part of a larger diff)
+   - djm@cvs.openbsd.org 2008/05/08 12:21:16
+     [monitor.c monitor_wrap.c session.h servconf.c servconf.h session.c]
+     [sshd_config sshd_config.5]
+     Make the maximum number of sessions run-time controllable via
+     a sshd_config MaxSessions knob. This is useful for disabling
+     login/shell/subsystem access while leaving port-forwarding working
+     (MaxSessions 0), disabling connection multiplexing (MaxSessions 1) or
+     simply increasing the number of allows multiplexed sessions.
+     Because some bozos are sure to configure MaxSessions in excess of the
+     number of available file descriptors in sshd (which, at peak, might be
+     as many as 9*MaxSessions), audit sshd to ensure that it doesn't leak fds
+     on error paths, and make it fail gracefully on out-of-fd conditions -
+     sending channel errors instead of than exiting with fatal().
+     bz#1090; MaxSessions config bits and manpage from junyer AT gmail.com
+     ok markus@
 
 20080403
  - (djm) [openbsd-compat/bsd-poll.c] Include stdlib.h to avoid compile-
@@ -3937,4 +3952,4 @@
    OpenServer 6 and add osr5bigcrypt support so when someone migrates
    passwords between UnixWare and OpenServer they will still work. OK dtucker@
 
-$Id: ChangeLog,v 1.4922 2008/05/19 05:28:35 djm Exp $
+$Id: ChangeLog,v 1.4923 2008/05/19 05:34:50 djm Exp $
diff --git a/monitor.c b/monitor.c
index 04f6924..f872edb 100644
--- a/monitor.c
+++ b/monitor.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: monitor.c,v 1.95 2008/05/08 12:02:23 djm Exp $ */
+/* $OpenBSD: monitor.c,v 1.96 2008/05/08 12:21:16 djm Exp $ */
 /*
  * Copyright 2002 Niels Provos <provos@citi.umich.edu>
  * Copyright 2002 Markus Friedl <markus@openbsd.org>
@@ -1273,7 +1273,7 @@
 		debug3("%s: tty %s ptyfd %d", __func__, s->tty, s->ptyfd);
 		session_pty_cleanup2(s);
 	}
-	s->used = 0;
+	session_unused(s->self);
 }
 
 int
diff --git a/monitor_wrap.c b/monitor_wrap.c
index 72fd5c8..e65fb12 100644
--- a/monitor_wrap.c
+++ b/monitor_wrap.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: monitor_wrap.c,v 1.61 2008/05/08 12:02:23 djm Exp $ */
+/* $OpenBSD: monitor_wrap.c,v 1.62 2008/05/08 12:21:16 djm Exp $ */
 /*
  * Copyright 2002 Niels Provos <provos@citi.umich.edu>
  * Copyright 2002 Markus Friedl <markus@openbsd.org>
@@ -666,7 +666,20 @@
 {
 	Buffer m;
 	char *p, *msg;
-	int success = 0;
+	int success = 0, tmp1 = -1, tmp2 = -1;
+
+	/* Kludge: ensure there are fds free to receive the pty/tty */
+	if ((tmp1 = dup(pmonitor->m_recvfd)) == -1 ||
+	    (tmp2 = dup(pmonitor->m_recvfd)) == -1) {
+		error("%s: cannot allocate fds for pty", __func__);
+		if (tmp1 > 0)
+			close(tmp1);
+		if (tmp2 > 0)
+			close(tmp2);
+		return 0;
+	}
+	close(tmp1);
+	close(tmp2);
 
 	buffer_init(&m);
 	mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_PTY, &m);
@@ -711,8 +724,9 @@
 	buffer_free(&m);
 
 	/* closed dup'ed master */
-	if (close(s->ptymaster) < 0)
-		error("close(s->ptymaster): %s", strerror(errno));
+	if (s->ptymaster != -1 && close(s->ptymaster) < 0)
+		error("close(s->ptymaster/%d): %s",
+		    s->ptymaster, strerror(errno));
 
 	/* unlink pty from session */
 	s->ttyfd = -1;
diff --git a/servconf.c b/servconf.c
index b8a968a..94dff1f 100644
--- a/servconf.c
+++ b/servconf.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: servconf.c,v 1.179 2008/05/08 12:02:23 djm Exp $ */
+/* $OpenBSD: servconf.c,v 1.180 2008/05/08 12:21:16 djm Exp $ */
 /*
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
  *                    All rights reserved
@@ -114,6 +114,7 @@
 	options->max_startups_rate = -1;
 	options->max_startups = -1;
 	options->max_authtries = -1;
+	options->max_sessions = -1;
 	options->banner = NULL;
 	options->use_dns = -1;
 	options->client_alive_interval = -1;
@@ -237,6 +238,8 @@
 		options->max_startups_begin = options->max_startups;
 	if (options->max_authtries == -1)
 		options->max_authtries = DEFAULT_AUTH_FAIL_MAX;
+	if (options->max_sessions == -1)
+		options->max_sessions = DEFAULT_SESSIONS_MAX;
 	if (options->use_dns == -1)
 		options->use_dns = 1;
 	if (options->client_alive_interval == -1)
@@ -291,7 +294,7 @@
 	sAllowUsers, sDenyUsers, sAllowGroups, sDenyGroups,
 	sIgnoreUserKnownHosts, sCiphers, sMacs, sProtocol, sPidFile,
 	sGatewayPorts, sPubkeyAuthentication, sXAuthLocation, sSubsystem,
-	sMaxStartups, sMaxAuthTries,
+	sMaxStartups, sMaxAuthTries, sMaxSessions,
 	sBanner, sUseDNS, sHostbasedAuthentication,
 	sHostbasedUsesNameFromPacketOnly, sClientAliveInterval,
 	sClientAliveCountMax, sAuthorizedKeysFile, sAuthorizedKeysFile2,
@@ -395,6 +398,7 @@
 	{ "subsystem", sSubsystem, SSHCFG_GLOBAL },
 	{ "maxstartups", sMaxStartups, SSHCFG_GLOBAL },
 	{ "maxauthtries", sMaxAuthTries, SSHCFG_GLOBAL },
+	{ "maxsessions", sMaxSessions, SSHCFG_ALL },
 	{ "banner", sBanner, SSHCFG_ALL },
 	{ "usedns", sUseDNS, SSHCFG_GLOBAL },
 	{ "verifyreversemapping", sDeprecated, SSHCFG_GLOBAL },
@@ -695,7 +699,7 @@
 
 	case sServerKeyBits:
 		intptr = &options->server_key_bits;
-parse_int:
+ parse_int:
 		arg = strdelim(&cp);
 		if (!arg || *arg == '\0')
 			fatal("%s line %d: missing integer value.",
@@ -707,7 +711,7 @@
 
 	case sLoginGraceTime:
 		intptr = &options->login_grace_time;
-parse_time:
+ parse_time:
 		arg = strdelim(&cp);
 		if (!arg || *arg == '\0')
 			fatal("%s line %d: missing time value.",
@@ -776,7 +780,7 @@
 			fatal("%s line %d: too many host keys specified (max %d).",
 			    filename, linenum, MAX_HOSTKEYS);
 		charptr = &options->host_key_files[*intptr];
-parse_filename:
+ parse_filename:
 		arg = strdelim(&cp);
 		if (!arg || *arg == '\0')
 			fatal("%s line %d: missing file name.",
@@ -819,7 +823,7 @@
 
 	case sIgnoreRhosts:
 		intptr = &options->ignore_rhosts;
-parse_flag:
+ parse_flag:
 		arg = strdelim(&cp);
 		if (!arg || *arg == '\0')
 			fatal("%s line %d: missing yes/no argument.",
@@ -1155,6 +1159,10 @@
 		intptr = &options->max_authtries;
 		goto parse_int;
 
+	case sMaxSessions:
+		intptr = &options->max_sessions;
+		goto parse_int;
+
 	case sBanner:
 		charptr = &options->banner;
 		goto parse_filename;
@@ -1382,6 +1390,7 @@
 	M_CP_INTOPT(x11_display_offset);
 	M_CP_INTOPT(x11_forwarding);
 	M_CP_INTOPT(x11_use_localhost);
+	M_CP_INTOPT(max_sessions);
 
 	M_CP_STROPT(banner);
 	if (preauth)
diff --git a/servconf.h b/servconf.h
index aaf87cd..819a028 100644
--- a/servconf.h
+++ b/servconf.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: servconf.h,v 1.83 2008/05/07 05:49:37 pyr Exp $ */
+/* $OpenBSD: servconf.h,v 1.84 2008/05/08 12:21:16 djm Exp $ */
 
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -35,6 +35,7 @@
 #define	PERMIT_YES		3
 
 #define DEFAULT_AUTH_FAIL_MAX	6	/* Default for MaxAuthTries */
+#define DEFAULT_SESSIONS_MAX	10	/* Default for MaxSessions */
 
 /* Magic name for internal sftp-server */
 #define INTERNAL_SFTP_NAME	"internal-sftp"
@@ -123,6 +124,7 @@
 	int	max_startups_rate;
 	int	max_startups;
 	int	max_authtries;
+	int	max_sessions;
 	char   *banner;			/* SSH-2 banner message */
 	int	use_dns;
 	int	client_alive_interval;	/*
diff --git a/session.c b/session.c
index ca04a45..c8ed252 100644
--- a/session.c
+++ b/session.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: session.c,v 1.236 2008/05/08 12:02:23 djm Exp $ */
+/* $OpenBSD: session.c,v 1.237 2008/05/08 12:21:16 djm Exp $ */
 /*
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
  *                    All rights reserved
@@ -102,9 +102,9 @@
 void	session_pty_cleanup(Session *);
 void	session_proctitle(Session *);
 int	session_setup_x11fwd(Session *);
-void	do_exec_pty(Session *, const char *);
-void	do_exec_no_pty(Session *, const char *);
-void	do_exec(Session *, const char *);
+int	do_exec_pty(Session *, const char *);
+int	do_exec_no_pty(Session *, const char *);
+int	do_exec(Session *, const char *);
 void	do_login(Session *, const char *);
 #ifdef LOGIN_NEEDS_UTMPX
 static void	do_pre_login(Session *s);
@@ -132,8 +132,9 @@
 const char *original_command = NULL;
 
 /* data */
-#define MAX_SESSIONS 20
-Session	sessions[MAX_SESSIONS];
+static int sessions_first_unused = -1;
+static int sessions_nalloc = 0;
+static Session *sessions = NULL;
 
 #define SUBSYSTEM_NONE		0
 #define SUBSYSTEM_EXT		1
@@ -167,7 +168,7 @@
 auth_input_request_forwarding(struct passwd * pw)
 {
 	Channel *nc;
-	int sock;
+	int sock = -1;
 	struct sockaddr_un sunaddr;
 
 	if (auth_sock_name != NULL) {
@@ -179,43 +180,48 @@
 	temporarily_use_uid(pw);
 
 	/* Allocate a buffer for the socket name, and format the name. */
-	auth_sock_name = xmalloc(MAXPATHLEN);
-	auth_sock_dir = xmalloc(MAXPATHLEN);
-	strlcpy(auth_sock_dir, "/tmp/ssh-XXXXXXXXXX", MAXPATHLEN);
+	auth_sock_dir = xstrdup("/tmp/ssh-XXXXXXXXXX");
 
 	/* Create private directory for socket */
 	if (mkdtemp(auth_sock_dir) == NULL) {
 		packet_send_debug("Agent forwarding disabled: "
 		    "mkdtemp() failed: %.100s", strerror(errno));
 		restore_uid();
-		xfree(auth_sock_name);
 		xfree(auth_sock_dir);
-		auth_sock_name = NULL;
 		auth_sock_dir = NULL;
-		return 0;
+		goto authsock_err;
 	}
-	snprintf(auth_sock_name, MAXPATHLEN, "%s/agent.%ld",
-		 auth_sock_dir, (long) getpid());
+
+	xasprintf(&auth_sock_name, "%s/agent.%ld",
+	    auth_sock_dir, (long) getpid());
 
 	/* Create the socket. */
 	sock = socket(AF_UNIX, SOCK_STREAM, 0);
-	if (sock < 0)
-		packet_disconnect("socket: %.100s", strerror(errno));
+	if (sock < 0) {
+		error("socket: %.100s", strerror(errno));
+		restore_uid();
+		goto authsock_err;
+	}
 
 	/* Bind it to the name. */
 	memset(&sunaddr, 0, sizeof(sunaddr));
 	sunaddr.sun_family = AF_UNIX;
 	strlcpy(sunaddr.sun_path, auth_sock_name, sizeof(sunaddr.sun_path));
 
-	if (bind(sock, (struct sockaddr *)&sunaddr, sizeof(sunaddr)) < 0)
-		packet_disconnect("bind: %.100s", strerror(errno));
+	if (bind(sock, (struct sockaddr *)&sunaddr, sizeof(sunaddr)) < 0) {
+		error("bind: %.100s", strerror(errno));
+		restore_uid();
+		goto authsock_err;
+	}
 
 	/* Restore the privileged uid. */
 	restore_uid();
 
 	/* Start listening on the socket. */
-	if (listen(sock, SSH_LISTEN_BACKLOG) < 0)
-		packet_disconnect("listen: %.100s", strerror(errno));
+	if (listen(sock, SSH_LISTEN_BACKLOG) < 0) {
+		error("listen: %.100s", strerror(errno));
+		goto authsock_err;
+	}
 
 	/* Allocate a channel for the authentication agent socket. */
 	nc = channel_new("auth socket",
@@ -224,6 +230,19 @@
 	    0, "auth socket", 1);
 	strlcpy(nc->path, auth_sock_name, sizeof(nc->path));
 	return 1;
+
+ authsock_err:
+	if (auth_sock_name != NULL)
+		xfree(auth_sock_name);
+	if (auth_sock_dir != NULL) {
+		rmdir(auth_sock_dir);
+		xfree(auth_sock_dir);
+	}
+	if (sock != -1)
+		close(sock);
+	auth_sock_name = NULL;
+	auth_sock_dir = NULL;
+	return 0;
 }
 
 static void
@@ -373,10 +392,14 @@
 			if (type == SSH_CMSG_EXEC_CMD) {
 				command = packet_get_string(&dlen);
 				debug("Exec command '%.500s'", command);
-				do_exec(s, command);
+				if (do_exec(s, command) != 0)
+					packet_disconnect(
+					    "command execution failed");
 				xfree(command);
 			} else {
-				do_exec(s, NULL);
+				if (do_exec(s, NULL) != 0)
+					packet_disconnect(
+					    "shell execution failed");
 			}
 			packet_check_eom();
 			session_close(s);
@@ -401,41 +424,84 @@
 	}
 }
 
+#define USE_PIPES
 /*
  * This is called to fork and execute a command when we have no tty.  This
  * will call do_child from the child, and server_loop from the parent after
  * setting up file descriptors and such.
  */
-void
+int
 do_exec_no_pty(Session *s, const char *command)
 {
 	pid_t pid;
 
 #ifdef USE_PIPES
 	int pin[2], pout[2], perr[2];
+
 	/* Allocate pipes for communicating with the program. */
-	if (pipe(pin) < 0 || pipe(pout) < 0 || pipe(perr) < 0)
-		packet_disconnect("Could not create pipes: %.100s",
-				  strerror(errno));
-#else /* USE_PIPES */
+	if (pipe(pin) < 0) {
+		error("%s: pipe in: %.100s", __func__, strerror(errno));
+		return -1;
+	}
+	if (pipe(pout) < 0) {
+		error("%s: pipe out: %.100s", __func__, strerror(errno));
+		close(pin[0]);
+		close(pin[1]);
+		return -1;
+	}
+	if (pipe(perr) < 0) {
+		error("%s: pipe err: %.100s", __func__, strerror(errno));
+		close(pin[0]);
+		close(pin[1]);
+		close(pout[0]);
+		close(pout[1]);
+		return -1;
+	}
+#else
 	int inout[2], err[2];
+
 	/* Uses socket pairs to communicate with the program. */
-	if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) < 0 ||
-	    socketpair(AF_UNIX, SOCK_STREAM, 0, err) < 0)
-		packet_disconnect("Could not create socket pairs: %.100s",
-				  strerror(errno));
-#endif /* USE_PIPES */
+	if (socketpair(AF_UNIX, SOCK_STREAM, 0, inout) < 0) {
+		error("%s: socketpair #1: %.100s", __func__, strerror(errno));
+		return -1;
+	}
+	if (socketpair(AF_UNIX, SOCK_STREAM, 0, err) < 0) {
+		error("%s: socketpair #2: %.100s", __func__, strerror(errno));
+		close(inout[0]);
+		close(inout[1]);
+		return -1;
+	}
+#endif
+
 	if (s == NULL)
 		fatal("do_exec_no_pty: no session");
 
 	session_proctitle(s);
 
 	/* Fork the child. */
-	if ((pid = fork()) == 0) {
+	switch ((pid = fork())) {
+	case -1:
+		error("%s: fork: %.100s", __func__, strerror(errno));
+#ifdef USE_PIPES
+		close(pin[0]);
+		close(pin[1]);
+		close(pout[0]);
+		close(pout[1]);
+		close(perr[0]);
+		close(perr[1]);
+#else
+		close(inout[0]);
+		close(inout[1]);
+		close(err[0]);
+		close(err[1]);
+#endif
+		return -1;
+	case 0:
 		is_child = 1;
 
 		/* Child.  Reinitialize the log since the pid has changed. */
-		log_init(__progname, options.log_level, options.log_facility, log_stderr);
+		log_init(__progname, options.log_level,
+		    options.log_facility, log_stderr);
 
 		/*
 		 * Create a new session and process group since the 4.4BSD
@@ -465,7 +531,7 @@
 		if (dup2(perr[1], 2) < 0)
 			perror("dup2 stderr");
 		close(perr[1]);
-#else /* USE_PIPES */
+#else
 		/*
 		 * Redirect stdin, stdout, and stderr.  Stdin and stdout will
 		 * use the same socket, as some programs (particularly rdist)
@@ -475,11 +541,14 @@
 		close(err[1]);
 		if (dup2(inout[0], 0) < 0)	/* stdin */
 			perror("dup2 stdin");
-		if (dup2(inout[0], 1) < 0)	/* stdout.  Note: same socket as stdin. */
+		if (dup2(inout[0], 1) < 0)	/* stdout (same as stdin) */
 			perror("dup2 stdout");
+		close(inout[0]);
 		if (dup2(err[0], 2) < 0)	/* stderr */
 			perror("dup2 stderr");
-#endif /* USE_PIPES */
+		close(err[0]);
+#endif
+
 
 #ifdef _UNICOS
 		cray_init_job(s->pw); /* set up cray jid and tmpdir */
@@ -488,7 +557,10 @@
 		/* Do processing for the child (exec command etc). */
 		do_child(s, command);
 		/* NOTREACHED */
+	default:
+		break;
 	}
+
 #ifdef _UNICOS
 	signal(WJSIGNAL, cray_job_termination_handler);
 #endif /* _UNICOS */
@@ -496,11 +568,18 @@
 	if (is_winnt)
 		cygwin_set_impersonation_token(INVALID_HANDLE_VALUE);
 #endif
-	if (pid < 0)
-		packet_disconnect("fork failed: %.100s", strerror(errno));
+
 	s->pid = pid;
 	/* Set interactive/non-interactive mode. */
 	packet_set_interactive(s->display != NULL);
+
+	/*
+	 * Clear loginmsg, since it's the child's responsibility to display
+	 * it to the user, otherwise multiple sessions may accumulate
+	 * multiple copies of the login messages.
+	 */
+	buffer_clear(&loginmsg);
+
 #ifdef USE_PIPES
 	/* We are the parent.  Close the child sides of the pipes. */
 	close(pin[0]);
@@ -518,29 +597,26 @@
 		server_loop(pid, pin[1], pout[0], perr[0]);
 		/* server_loop has closed pin[1], pout[0], and perr[0]. */
 	}
-#else /* USE_PIPES */
+#else
 	/* We are the parent.  Close the child sides of the socket pairs. */
 	close(inout[0]);
 	close(err[0]);
 
 	/*
-	 * Clear loginmsg, since it's the child's responsibility to display
-	 * it to the user, otherwise multiple sessions may accumulate
-	 * multiple copies of the login messages.
-	 */
-	buffer_clear(&loginmsg);
-
-	/*
 	 * Enter the interactive session.  Note: server_loop must be able to
 	 * handle the case that fdin and fdout are the same.
 	 */
 	if (compat20) {
-		session_set_fds(s, inout[1], inout[1], s->is_subsystem ? -1 : err[1]);
+		session_set_fds(s, inout[1], inout[1],
+		    s->is_subsystem ? -1 : err[1]);
+		if (s->is_subsystem)
+			close(err[1]);
 	} else {
 		server_loop(pid, inout[1], inout[1], err[1]);
 		/* server_loop has closed inout[1] and err[1]. */
 	}
-#endif /* USE_PIPES */
+#endif
+	return 0;
 }
 
 /*
@@ -549,7 +625,7 @@
  * setting up file descriptors, controlling tty, updating wtmp, utmp,
  * lastlog, and other such operations.
  */
-void
+int
 do_exec_pty(Session *s, const char *command)
 {
 	int fdout, ptyfd, ttyfd, ptymaster;
@@ -560,12 +636,46 @@
 	ptyfd = s->ptyfd;
 	ttyfd = s->ttyfd;
 
+	/*
+	 * Create another descriptor of the pty master side for use as the
+	 * standard input.  We could use the original descriptor, but this
+	 * simplifies code in server_loop.  The descriptor is bidirectional.
+	 * Do this before forking (and cleanup in the child) so as to
+	 * detect and gracefully fail out-of-fd conditions.
+	 */
+	if ((fdout = dup(ptyfd)) < 0) {
+		error("%s: dup #1: %s", __func__, strerror(errno));
+		close(ttyfd);
+		close(ptyfd);
+		return -1;
+	}
+	/* we keep a reference to the pty master */
+	if ((ptymaster = dup(ptyfd)) < 0) {
+		error("%s: dup #2: %s", __func__, strerror(errno));
+		close(ttyfd);
+		close(ptyfd);
+		close(fdout);
+		return -1;
+	}
+
 	/* Fork the child. */
-	if ((pid = fork()) == 0) {
+	switch ((pid = fork())) {
+	case -1:
+		error("%s: fork: %.100s", __func__, strerror(errno));
+		close(fdout);
+		close(ptymaster);
+		close(ttyfd);
+		close(ptyfd);
+		return -1;
+	case 0:
 		is_child = 1;
 
+		close(fdout);
+		close(ptymaster);
+
 		/* Child.  Reinitialize the log because the pid has changed. */
-		log_init(__progname, options.log_level, options.log_facility, log_stderr);
+		log_init(__progname, options.log_level,
+		    options.log_facility, log_stderr);
 		/* Close the master side of the pseudo tty. */
 		close(ptyfd);
 
@@ -596,11 +706,16 @@
 			do_pre_login(s);
 # endif
 #endif
-
-		/* Do common processing for the child, such as execing the command. */
-		do_child(s, command);
-		/* NOTREACHED */
+		/*
+		 * Do common processing for the child, such as execing
+		 * the command.
+		 */
+ 		do_child(s, command);
+ 		/* NOTREACHED */
+	default:
+		break;
 	}
+
 #ifdef _UNICOS
 	signal(WJSIGNAL, cray_job_termination_handler);
 #endif /* _UNICOS */
@@ -608,29 +723,14 @@
 	if (is_winnt)
 		cygwin_set_impersonation_token(INVALID_HANDLE_VALUE);
 #endif
-	if (pid < 0)
-		packet_disconnect("fork failed: %.100s", strerror(errno));
+
 	s->pid = pid;
 
 	/* Parent.  Close the slave side of the pseudo tty. */
 	close(ttyfd);
 
-	/*
-	 * Create another descriptor of the pty master side for use as the
-	 * standard input.  We could use the original descriptor, but this
-	 * simplifies code in server_loop.  The descriptor is bidirectional.
-	 */
-	fdout = dup(ptyfd);
-	if (fdout < 0)
-		packet_disconnect("dup #1 failed: %.100s", strerror(errno));
-
-	/* we keep a reference to the pty master */
-	ptymaster = dup(ptyfd);
-	if (ptymaster < 0)
-		packet_disconnect("dup #2 failed: %.100s", strerror(errno));
-	s->ptymaster = ptymaster;
-
 	/* Enter interactive session. */
+	s->ptymaster = ptymaster;
 	packet_set_interactive(1);
 	if (compat20) {
 		session_set_fds(s, ptyfd, fdout, -1);
@@ -638,6 +738,7 @@
 		server_loop(pid, ptyfd, fdout, -1);
 		/* server_loop _has_ closed ptyfd and fdout. */
 	}
+	return 0;
 }
 
 #ifdef LOGIN_NEEDS_UTMPX
@@ -672,9 +773,11 @@
  * This is called to fork and execute a command.  If another command is
  * to be forced, execute that instead.
  */
-void
+int
 do_exec(Session *s, const char *command)
 {
+	int ret;
+
 	if (options.adm_forced_command) {
 		original_command = command;
 		command = options.adm_forced_command;
@@ -705,9 +808,9 @@
 	}
 #endif
 	if (s->ttyfd != -1)
-		do_exec_pty(s, command);
+		ret = do_exec_pty(s, command);
 	else
-		do_exec_no_pty(s, command);
+		ret = do_exec_no_pty(s, command);
 
 	original_command = NULL;
 
@@ -717,6 +820,8 @@
 	 * multiple copies of the login messages.
 	 */
 	buffer_clear(&loginmsg);
+
+	return ret;
 }
 
 /* administrative, login(1)-like work */
@@ -1740,43 +1845,79 @@
 	exit(1);
 }
 
+void
+session_unused(int id)
+{
+	debug3("%s: session id %d unused", __func__, id);
+	if (id >= options.max_sessions ||
+	    id >= sessions_nalloc) {
+		fatal("%s: insane session id %d (max %d nalloc %d)",
+		    __func__, id, options.max_sessions, sessions_nalloc);
+	}
+	bzero(&sessions[id], sizeof(*sessions));
+	sessions[id].self = id;
+	sessions[id].used = 0;
+	sessions[id].chanid = -1;
+	sessions[id].ptyfd = -1;
+	sessions[id].ttyfd = -1;
+	sessions[id].ptymaster = -1;
+	sessions[id].x11_chanids = NULL;
+	sessions[id].next_unused = sessions_first_unused;
+	sessions_first_unused = id;
+}
+
 Session *
 session_new(void)
 {
-	int i;
-	static int did_init = 0;
-	if (!did_init) {
-		debug("session_new: init");
-		for (i = 0; i < MAX_SESSIONS; i++) {
-			sessions[i].used = 0;
+	Session *s, *tmp;
+
+	if (sessions_first_unused == -1) {
+		if (sessions_nalloc >= options.max_sessions)
+			return NULL;
+		debug2("%s: allocate (allocated %d max %d)",
+		    __func__, sessions_nalloc, options.max_sessions);
+		tmp = xrealloc(sessions, sessions_nalloc + 1,
+		    sizeof(*sessions));
+		if (tmp == NULL) {
+			error("%s: cannot allocate %d sessions",
+			    __func__, sessions_nalloc + 1);
+			return NULL;
 		}
-		did_init = 1;
+		sessions = tmp;
+		session_unused(sessions_nalloc++);
 	}
-	for (i = 0; i < MAX_SESSIONS; i++) {
-		Session *s = &sessions[i];
-		if (! s->used) {
-			memset(s, 0, sizeof(*s));
-			s->chanid = -1;
-			s->ptyfd = -1;
-			s->ttyfd = -1;
-			s->used = 1;
-			s->self = i;
-			s->x11_chanids = NULL;
-			debug("session_new: session %d", i);
-			return s;
-		}
+
+	if (sessions_first_unused >= sessions_nalloc ||
+	    sessions_first_unused < 0) {
+		fatal("%s: insane first_unused %d max %d nalloc %d",
+		    __func__, sessions_first_unused, options.max_sessions,
+		    sessions_nalloc);
 	}
-	return NULL;
+
+	s = &sessions[sessions_first_unused];
+	if (s->used) {
+		fatal("%s: session %d already used",
+		    __func__, sessions_first_unused);
+	}
+	sessions_first_unused = s->next_unused;
+	s->used = 1;
+	s->next_unused = -1;
+	debug("session_new: session %d", s->self);
+
+	return s;
 }
 
 static void
 session_dump(void)
 {
 	int i;
-	for (i = 0; i < MAX_SESSIONS; i++) {
+	for (i = 0; i < sessions_nalloc; i++) {
 		Session *s = &sessions[i];
-		debug("dump: used %d session %d %p channel %d pid %ld",
+
+		debug("dump: used %d next_unused %d session %d %p "
+		    "channel %d pid %ld",
 		    s->used,
+		    s->next_unused,
 		    s->self,
 		    s,
 		    s->chanid,
@@ -1806,7 +1947,7 @@
 session_by_tty(char *tty)
 {
 	int i;
-	for (i = 0; i < MAX_SESSIONS; i++) {
+	for (i = 0; i < sessions_nalloc; i++) {
 		Session *s = &sessions[i];
 		if (s->used && s->ttyfd != -1 && strcmp(s->tty, tty) == 0) {
 			debug("session_by_tty: session %d tty %s", i, tty);
@@ -1822,10 +1963,11 @@
 session_by_channel(int id)
 {
 	int i;
-	for (i = 0; i < MAX_SESSIONS; i++) {
+	for (i = 0; i < sessions_nalloc; i++) {
 		Session *s = &sessions[i];
 		if (s->used && s->chanid == id) {
-			debug("session_by_channel: session %d channel %d", i, id);
+			debug("session_by_channel: session %d channel %d",
+			    i, id);
 			return s;
 		}
 	}
@@ -1839,7 +1981,7 @@
 {
 	int i, j;
 
-	for (i = 0; i < MAX_SESSIONS; i++) {
+	for (i = 0; i < sessions_nalloc; i++) {
 		Session *s = &sessions[i];
 
 		if (s->x11_chanids == NULL || !s->used)
@@ -1862,7 +2004,7 @@
 {
 	int i;
 	debug("session_by_pid: pid %ld", (long)pid);
-	for (i = 0; i < MAX_SESSIONS; i++) {
+	for (i = 0; i < sessions_nalloc; i++) {
 		Session *s = &sessions[i];
 		if (s->used && s->pid == pid)
 			return s;
@@ -1918,7 +2060,8 @@
 
 	/* Allocate a pty and open it. */
 	debug("Allocating pty.");
-	if (!PRIVSEP(pty_allocate(&s->ptyfd, &s->ttyfd, s->tty, sizeof(s->tty)))) {
+	if (!PRIVSEP(pty_allocate(&s->ptyfd, &s->ttyfd, s->tty,
+	    sizeof(s->tty)))) {
 		if (s->term)
 			xfree(s->term);
 		s->term = NULL;
@@ -1971,8 +2114,7 @@
 				s->is_subsystem = SUBSYSTEM_EXT;
 			}
 			debug("subsystem: exec() %s", cmd);
-			do_exec(s, cmd);
-			success = 1;
+			success = do_exec(s, cmd) == 0;
 			break;
 		}
 	}
@@ -2015,19 +2157,19 @@
 session_shell_req(Session *s)
 {
 	packet_check_eom();
-	do_exec(s, NULL);
-	return 1;
+	return do_exec(s, NULL) == 0;
 }
 
 static int
 session_exec_req(Session *s)
 {
-	u_int len;
+	u_int len, success;
+
 	char *command = packet_get_string(&len);
 	packet_check_eom();
-	do_exec(s, command);
+	success = do_exec(s, command) == 0;
 	xfree(command);
-	return 1;
+	return success;
 }
 
 static int
@@ -2037,8 +2179,7 @@
 	packet_get_int();	/* ignored */
 	packet_check_eom();
 
-	if (s->ttyfd == -1 ||
-	    tcsendbreak(s->ttyfd, 0) < 0)
+	if (s->ttyfd == -1 || tcsendbreak(s->ttyfd, 0) < 0)
 		return 0;
 	return 1;
 }
@@ -2185,8 +2326,9 @@
 	 * the pty cleanup, so that another process doesn't get this pty
 	 * while we're still cleaning up.
 	 */
-	if (close(s->ptymaster) < 0)
-		error("close(s->ptymaster/%d): %s", s->ptymaster, strerror(errno));
+	if (s->ptymaster != -1 && close(s->ptymaster) < 0)
+		error("close(s->ptymaster/%d): %s",
+		    s->ptymaster, strerror(errno));
 
 	/* unlink pty from session */
 	s->ttyfd = -1;
@@ -2346,7 +2488,6 @@
 		xfree(s->auth_data);
 	if (s->auth_proto)
 		xfree(s->auth_proto);
-	s->used = 0;
 	if (s->env != NULL) {
 		for (i = 0; i < s->num_env; i++) {
 			xfree(s->env[i].name);
@@ -2355,6 +2496,7 @@
 		xfree(s->env);
 	}
 	session_proctitle(s);
+	session_unused(s->self);
 }
 
 void
@@ -2418,7 +2560,7 @@
 session_destroy_all(void (*closefunc)(Session *))
 {
 	int i;
-	for (i = 0; i < MAX_SESSIONS; i++) {
+	for (i = 0; i < sessions_nalloc; i++) {
 		Session *s = &sessions[i];
 		if (s->used) {
 			if (closefunc != NULL)
@@ -2437,7 +2579,7 @@
 	char *cp;
 
 	buf[0] = '\0';
-	for (i = 0; i < MAX_SESSIONS; i++) {
+	for (i = 0; i < sessions_nalloc; i++) {
 		Session *s = &sessions[i];
 		if (s->used && s->ttyfd != -1) {
 
diff --git a/session.h b/session.h
index ee9338e..cbb8e3a 100644
--- a/session.h
+++ b/session.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: session.h,v 1.29 2006/08/03 03:34:42 deraadt Exp $ */
+/* $OpenBSD: session.h,v 1.30 2008/05/08 12:21:16 djm Exp $ */
 
 /*
  * Copyright (c) 2000, 2001 Markus Friedl.  All rights reserved.
@@ -31,6 +31,7 @@
 struct Session {
 	int	used;
 	int	self;
+	int	next_unused;
 	struct passwd *pw;
 	Authctxt *authctxt;
 	pid_t	pid;
@@ -65,6 +66,7 @@
 void	 do_cleanup(Authctxt *);
 
 int	 session_open(Authctxt *, int);
+void	 session_unused(int);
 int	 session_input_channel_req(Channel *, const char *);
 void	 session_close_by_pid(pid_t, int);
 void	 session_close_by_channel(int, void *);
diff --git a/sshd_config b/sshd_config
index 1f97a9d..c5ee7c8 100644
--- a/sshd_config
+++ b/sshd_config
@@ -1,4 +1,4 @@
-#	$OpenBSD: sshd_config,v 1.78 2008/05/07 06:43:35 pyr Exp $
+#	$OpenBSD: sshd_config,v 1.79 2008/05/08 12:21:16 djm Exp $
 
 # This is the sshd server system-wide configuration file.  See
 # sshd_config(5) for more information.
@@ -41,6 +41,7 @@
 #PermitRootLogin yes
 #StrictModes yes
 #MaxAuthTries 6
+#MaxSessions 10
 
 #RSAAuthentication yes
 #PubkeyAuthentication yes
diff --git a/sshd_config.5 b/sshd_config.5
index 99b5621..0d8c140 100644
--- a/sshd_config.5
+++ b/sshd_config.5
@@ -34,8 +34,8 @@
 .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 .\"
-.\" $OpenBSD: sshd_config.5,v 1.89 2008/05/07 08:00:14 jmc Exp $
-.Dd $Mdocdate: May 19 2008 $
+.\" $OpenBSD: sshd_config.5,v 1.90 2008/05/08 12:21:16 djm Exp $
+.Dd $Mdocdate: May 8 2008 $
 .Dt SSHD_CONFIG 5
 .Os
 .Sh NAME
@@ -594,6 +594,9 @@
 Once the number of failures reaches half this value,
 additional failures are logged.
 The default is 6.
+.It Cm MaxSessions
+Specifies the maximum number of open sessions permitted per network connection.
+The default is 10.
 .It Cm MaxStartups
 Specifies the maximum number of concurrent unauthenticated connections to the
 SSH daemon.