- djm@cvs.openbsd.org 2004/06/24 19:30:54
     [servconf.c servconf.h sshd.c]
     re-exec sshd on accept(); initial work, final debugging and ok markus@
diff --git a/ChangeLog b/ChangeLog
index 023fde0..4367699 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+20040625
+ - (dtucker) OpenBSD CVS Sync
+   - djm@cvs.openbsd.org 2004/06/24 19:30:54
+     [servconf.c servconf.h sshd.c]
+     re-exec sshd on accept(); initial work, final debugging and ok markus@
+
 20040623
  - (dtucker) [auth1.c] Ensure do_pam_account is called for Protocol 1
    connections with empty passwords.  Patch from davidwu at nbttech.com,
@@ -1399,4 +1405,4 @@
    - (djm) Trim deprecated options from INSTALL. Mention UsePAM
    - (djm) Fix quote handling in sftp; Patch from admorten AT umich.edu
 
-$Id: ChangeLog,v 1.3443 2004/06/23 14:34:53 dtucker Exp $
+$Id: ChangeLog,v 1.3444 2004/06/25 03:33:20 dtucker Exp $
diff --git a/servconf.c b/servconf.c
index ef86516..ea67f62 100644
--- a/servconf.c
+++ b/servconf.c
@@ -10,7 +10,7 @@
  */
 
 #include "includes.h"
-RCSID("$OpenBSD: servconf.c,v 1.133 2004/05/23 23:59:53 dtucker Exp $");
+RCSID("$OpenBSD: servconf.c,v 1.134 2004/06/24 19:30:54 djm Exp $");
 
 #include "ssh.h"
 #include "log.h"
@@ -942,26 +942,50 @@
 /* Reads the server configuration file. */
 
 void
-read_server_config(ServerOptions *options, const char *filename)
+load_server_config(const char *filename, Buffer *conf)
 {
-	int linenum, bad_options = 0;
-	char line[1024];
+	char line[1024], *cp;
 	FILE *f;
 
-	debug2("read_server_config: filename %s", filename);
-	f = fopen(filename, "r");
-	if (!f) {
+	debug2("%s: filename %s", __func__, filename);
+	if ((f = fopen(filename, "r")) == NULL) {
 		perror(filename);
 		exit(1);
 	}
-	linenum = 0;
+	buffer_clear(conf);
 	while (fgets(line, sizeof(line), f)) {
-		/* Update line number counter. */
-		linenum++;
-		if (process_server_config_line(options, line, filename, linenum) != 0)
+		/*
+		 * Trim out comments and strip whitespace
+		 * NB - preserve newlines, they are needed to reproduce 
+		 * line numbers later for error messages
+		 */
+		if ((cp = strchr(line, '#')) != NULL)
+			memcpy(cp, "\n", 2);
+		cp = line + strspn(line, " \t\r");
+
+		buffer_append(conf, cp, strlen(cp));
+	}
+	buffer_append(conf, "\0", 1);
+	fclose(f);
+	debug2("%s: done config len = %d", __func__, buffer_len(conf));
+}
+
+void
+parse_server_config(ServerOptions *options, const char *filename, Buffer *conf)
+{
+	int linenum, bad_options = 0;
+	char *cp, *cbuf;
+
+	debug2("%s: config %s len %d", __func__, filename, buffer_len(conf));
+
+	cbuf = xstrdup(buffer_ptr(conf));
+	linenum = 0;
+	while((cp = strsep(&cbuf, "\n")) != NULL) {
+		if (process_server_config_line(options, cp, filename,
+		    linenum++) != 0)
 			bad_options++;
 	}
-	fclose(f);
+	free(cbuf);
 	if (bad_options > 0)
 		fatal("%s: terminating, %d bad configuration options",
 		    filename, bad_options);
diff --git a/servconf.h b/servconf.h
index 36d2e5c..ebd0568 100644
--- a/servconf.h
+++ b/servconf.h
@@ -1,4 +1,4 @@
-/*	$OpenBSD: servconf.h,v 1.69 2004/05/23 23:59:53 dtucker Exp $	*/
+/*	$OpenBSD: servconf.h,v 1.70 2004/06/24 19:30:54 djm Exp $	*/
 
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -16,6 +16,8 @@
 #ifndef SERVCONF_H
 #define SERVCONF_H
 
+#include "buffer.h"
+
 #define MAX_PORTS		256	/* Max # ports. */
 
 #define MAX_ALLOW_USERS		256	/* Max # users on allow list. */
@@ -134,9 +136,9 @@
 }       ServerOptions;
 
 void	 initialize_server_options(ServerOptions *);
-void	 read_server_config(ServerOptions *, const char *);
 void	 fill_default_server_options(ServerOptions *);
 int	 process_server_config_line(ServerOptions *, char *, const char *, int);
-
+void	 load_server_config(const char *, Buffer *);
+void	 parse_server_config(ServerOptions *, const char *, Buffer *);
 
 #endif				/* SERVCONF_H */
diff --git a/sshd.c b/sshd.c
index 3437917..98e90b5 100644
--- a/sshd.c
+++ b/sshd.c
@@ -42,7 +42,7 @@
  */
 
 #include "includes.h"
-RCSID("$OpenBSD: sshd.c,v 1.293 2004/06/14 01:44:39 djm Exp $");
+RCSID("$OpenBSD: sshd.c,v 1.294 2004/06/24 19:30:54 djm Exp $");
 
 #include <openssl/dh.h>
 #include <openssl/bn.h>
@@ -65,6 +65,7 @@
 #include "uidswap.h"
 #include "compat.h"
 #include "buffer.h"
+#include "bufaux.h"
 #include "cipher.h"
 #include "kex.h"
 #include "key.h"
@@ -76,6 +77,7 @@
 #include "canohost.h"
 #include "auth.h"
 #include "misc.h"
+#include "msg.h"
 #include "dispatch.h"
 #include "channels.h"
 #include "session.h"
@@ -137,6 +139,12 @@
 char **saved_argv;
 int saved_argc;
 
+/* 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.
@@ -771,6 +779,87 @@
 	exit(1);
 }
 
+static void
+send_rexec_state(int fd, Buffer *conf)
+{
+	Buffer m;
+
+	debug3("%s: entering fd = %d config len %d", __func__, fd,
+	    buffer_len(conf));
+
+	/*
+	 * Protocol from reexec master to child:
+	 *	string	configuration
+	 *	u_int	ephemeral_key_follows
+	 *	bignum	e		(only if ephemeral_key_follows == 1)
+	 *	bignum	n			"
+	 *	bignum	d			"
+	 *	bignum	iqmp			"
+	 *	bignum	p			"
+	 *	bignum	q			"
+	 */
+	buffer_init(&m);
+	buffer_put_cstring(&m, buffer_ptr(conf));
+
+	if (sensitive_data.server_key != NULL && 
+	    sensitive_data.server_key->type == KEY_RSA1) {
+		buffer_put_int(&m, 1);
+		buffer_put_bignum(&m, sensitive_data.server_key->rsa->e);
+		buffer_put_bignum(&m, sensitive_data.server_key->rsa->n);
+		buffer_put_bignum(&m, sensitive_data.server_key->rsa->d);
+		buffer_put_bignum(&m, sensitive_data.server_key->rsa->iqmp);
+		buffer_put_bignum(&m, sensitive_data.server_key->rsa->p);
+		buffer_put_bignum(&m, sensitive_data.server_key->rsa->q);
+	} else
+		buffer_put_int(&m, 0);
+
+	if (ssh_msg_send(fd, 0, &m) == -1)
+		fatal("%s: ssh_msg_send failed", __func__);
+
+	buffer_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 + 1);
+	xfree(cp);
+
+	if (buffer_get_int(&m)) {
+		if (sensitive_data.server_key != NULL)
+			key_free(sensitive_data.server_key);
+		sensitive_data.server_key = key_new_private(KEY_RSA1);
+		buffer_get_bignum(&m, sensitive_data.server_key->rsa->e);
+		buffer_get_bignum(&m, sensitive_data.server_key->rsa->n);
+		buffer_get_bignum(&m, sensitive_data.server_key->rsa->d);
+		buffer_get_bignum(&m, sensitive_data.server_key->rsa->iqmp);
+		buffer_get_bignum(&m, sensitive_data.server_key->rsa->p);
+		buffer_get_bignum(&m, sensitive_data.server_key->rsa->q);
+		rsa_generate_additional_parameters(
+		    sensitive_data.server_key->rsa);
+	}
+	buffer_free(&m);
+
+	debug3("%s: done", __func__);
+}
+
 /*
  * Main program for the daemon.
  */
@@ -791,11 +880,12 @@
 	char ntop[NI_MAXHOST], strport[NI_MAXSERV];
 	char *line;
 	int listen_sock, maxfd;
-	int startup_p[2];
+	int startup_p[2], config_s[2];
 	int startups = 0;
 	Key *key;
 	Authctxt *authctxt;
 	int ret, key_used = 0;
+	Buffer cfg;
 
 #ifdef HAVE_SECUREWARE
 	(void)set_auth_parameters(ac, av);
@@ -823,7 +913,7 @@
 	initialize_server_options(&options);
 
 	/* Parse command-line arguments. */
-	while ((opt = getopt(ac, av, "f:p:b:k:h:g:u:o:dDeiqtQ46")) != -1) {
+	while ((opt = getopt(ac, av, "f:p:b:k:h:g:u:o:dDeiqrtQR46")) != -1) {
 		switch (opt) {
 		case '4':
 			IPv4or6 = AF_INET;
@@ -850,6 +940,13 @@
 		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;
@@ -913,6 +1010,13 @@
 			break;
 		}
 	}
+	if (rexeced_flag || inetd_flag)
+		rexec_flag = 0;
+	if (rexec_flag && (av[0] == NULL || *av[0] != '/'))
+		fatal("sshd re-exec requires execution with an absolute path");
+	if (rexeced_flag)
+		closefrom(STDERR_FILENO + 3);
+
 	SSLeay_add_all_algorithms();
 	channel_set_af(IPv4or6);
 
@@ -943,8 +1047,23 @@
 
 	seed_rng();
 
-	/* Read server configuration options from the configuration file. */
-	read_server_config(&options, config_file_name);
+	sensitive_data.server_key = NULL;
+	sensitive_data.ssh1_host_key = NULL;
+	sensitive_data.have_ssh1_key = 0;
+	sensitive_data.have_ssh2_key = 0;
+
+	/* Fetch our configuration */
+	buffer_init(&cfg);
+	if (rexeced_flag)
+		recv_rexec_state(STDERR_FILENO + 2, &cfg);
+	else
+		load_server_config(config_file_name, &cfg);
+
+	parse_server_config(&options,
+	    rexeced_flag ? "rexec" : config_file_name, &cfg);
+
+	if (!rexec_flag)
+		buffer_free(&cfg);
 
 	/* Fill in default values for those options not explicitly set. */
 	fill_default_server_options(&options);
@@ -962,10 +1081,6 @@
 	    sizeof(Key *));
 	for (i = 0; i < options.num_host_key_files; i++)
 		sensitive_data.host_keys[i] = NULL;
-	sensitive_data.server_key = NULL;
-	sensitive_data.ssh1_host_key = NULL;
-	sensitive_data.have_ssh1_key = 0;
-	sensitive_data.have_ssh2_key = 0;
 
 	for (i = 0; i < options.num_host_key_files; i++) {
 		key = key_load_private(options.host_key_files[i], "", NULL);
@@ -1064,6 +1179,16 @@
 	if (setgroups(0, NULL) < 0)
 		debug("setgroups() failed: %.200s", strerror(errno));
 
+	if (rexec_flag) {
+		rexec_argv = xmalloc(sizeof(char *) * (rexec_argc + 2));
+		for (i = 0; i < 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;
+	}
+
 	/* Initialize the log (it is reinitialized below in case we forked). */
 	if (debug_flag && !inetd_flag)
 		log_stderr = 1;
@@ -1105,19 +1230,34 @@
 
 	/* Start listening for a socket, unless started from inetd. */
 	if (inetd_flag) {
-		int s1;
-		s1 = dup(0);	/* Make sure descriptors 0, 1, and 2 are in use. */
-		dup(s1);
-		sock_in = dup(0);
-		sock_out = dup(1);
+		int fd;
+
 		startup_pipe = -1;
+		if (rexeced_flag) {
+			close(STDERR_FILENO + 2);
+			sock_in = sock_out = dup(STDIN_FILENO);
+			if (!debug_flag) {
+				startup_pipe = dup(STDERR_FILENO + 1);
+				close(STDERR_FILENO + 1);
+			}
+		} 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
+		 * 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)
 			generate_ephemeral_server_key();
 	} else {
 		for (ai = options.listen_addrs; ai; ai = ai->ai_next) {
@@ -1297,6 +1437,16 @@
 					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];
@@ -1320,8 +1470,15 @@
 					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 {
 					/*
@@ -1355,6 +1512,12 @@
 
 				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) {
@@ -1378,6 +1541,40 @@
 	/* This is the child processing a new connection. */
 	setproctitle("%s", "[accepted]");
 
+	if (rexec_flag) {
+		int fd;
+
+		debug("rexec newsock %d pipe %d sock %d", newsock, 
+		    startup_pipe, config_s[0]);
+		dup2(newsock, STDIN_FILENO);
+		dup2(STDIN_FILENO, STDOUT_FILENO);
+		if (startup_pipe == -1)
+			close(STDERR_FILENO + 1);
+		else
+			dup2(startup_pipe, STDERR_FILENO + 1);
+
+		dup2(config_s[1], STDERR_FILENO + 2);
+		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(STDERR_FILENO + 2, NULL);
+		log_init(__progname, options.log_level,
+		    options.log_facility, log_stderr);
+
+		/* Clean up fds */
+		close(config_s[1]);
+		close(STDERR_FILENO + 1);
+		close(STDERR_FILENO + 2);
+		if ((fd = open(_PATH_DEVNULL, O_RDWR, 0)) != -1) {
+			dup2(fd, STDIN_FILENO);
+			dup2(fd, STDOUT_FILENO);
+			if (fd > STDERR_FILENO)
+				close(fd);
+		}
+	}
+
 	/*
 	 * Create a new session and process group since the 4.4BSD
 	 * setlogin() affects the entire process group.  We don't