Dropbear on MMUless machines

Erich Schubert erich at debian.org
Thu Sep 23 20:59:34 WST 2004


Hi,
i'm running dropbear on an MMUless embedded ARM system.
Since mmuless machines do not support fork(), it would probably be
necessary to split dropbear into two apps, one accepting the connections
and the other then handling the individual client sessions.

Since that looked like a lot of work, i decided to do a shorter
approach: i modified dropbear to run as a "single-shot" ssh server, to
be run in inetd mode. (In my case, i used tcpsvd from the ipsvd package)

This works fine, i can do multiple ssh sessions, and while i'm not
connected dropbear isn't even loaded into memory.

Attached is the patch for dropbear 0.41 doing both the inetd-mode
modifications and replacing calls to fork() by calls to vfork().

Greetings,
Erich Schubert
-- 
      erich@(mucl.de|debian.org)      --      GPG Key ID: 4B3A135C     (o_
    A polar bear is a rectangular bear after a coordinate transform.   //\
 Mancher findet sein Herz nicht eher, als bis er seinen Kopf verliert. V_/_
-------------- next part --------------
This patch does
* add a new inetmain.c file, for single-shot inetd operation
  (#define INET_MODE, tested with ipsvd only)
* rework code to allow separate in and out fds (needed for inetd mode)
* replace all fork() calls by vfork() for mmuless machines
  (#define HAVE_NO_FORK)
diff -Nru dropbear-0.41/auth.c ../firm/uClinux/apps-gpl/dropbear/auth.c
--- dropbear-0.41/auth.c	2004-01-16 06:38:45.000000000 +0100
+++ ../firm/uClinux/apps-gpl/dropbear/auth.c	2004-08-19 15:37:41.000000000 +0200
@@ -340,7 +340,9 @@
 	encrypt_packet();
 
 	ses.authstate.authdone = 1;
+#ifndef INET_MODE
 	close(ses.childpipe); /* remove from the list of pre-auth sockets */
+#endif
 	TRACE(("leave send_msg_userauth_success"));
 
 }
diff -Nru dropbear-0.41/chansession.c ../firm/uClinux/apps-gpl/dropbear/chansession.c
--- dropbear-0.41/chansession.c	2004-01-17 07:44:54.000000000 +0100
+++ ../firm/uClinux/apps-gpl/dropbear/chansession.c	2004-09-23 14:34:04.000000000 +0200
@@ -592,12 +592,17 @@
 	if (pipe(errfds) != 0)
 		return DROPBEAR_FAILURE;
 
+#ifndef HAVE_NO_FORK
 	pid = fork();
+#else
+	pid = vfork();
+#endif
 	if (pid < 0)
 		return DROPBEAR_FAILURE;
 
 	if (!pid) {
 		/* child */
+		/* FIXME: Verify that the vforked child doesn't harm parent */
 
 		/* redirect stdin/stdout */
 #define FDIN 0
@@ -672,11 +677,16 @@
 		return DROPBEAR_FAILURE;
 	}
 	
+#ifndef HAVE_NO_FORK
 	pid = fork();
+#else	
+	pid = vfork();
+#endif
 	if (pid < 0)
 		return DROPBEAR_FAILURE;
 
 	if (pid == 0) {
+		/* FIXME: verify the vforked child doesn't harm the parent */
 		/* child */
 		
 		/* redirect stdin/stdout/stderr */
@@ -788,12 +798,14 @@
 	char * baseshell;
 	unsigned int i;
 
+#ifndef HAVE_NO_FORK
 	/* wipe the hostkey */
 	sign_key_free(ses.opts->hostkey);
 	ses.opts->hostkey = NULL;
 
 	/* overwrite the prng state */
 	seedrandom();
+#endif
 
 	/* close file descriptors except stdin/stdout/stderr
 	 * Need to be sure FDs are closed here to avoid reading files as root */
diff -Nru dropbear-0.41/compat.c ../firm/uClinux/apps-gpl/dropbear/compat.c
--- dropbear-0.41/compat.c	2003-12-16 07:41:24.000000000 +0100
+++ ../firm/uClinux/apps-gpl/dropbear/compat.c	2004-06-23 14:12:45.000000000 +0200
@@ -155,6 +155,7 @@
 }
 #endif /* HAVE_STRLCAT */
 
+#ifndef HAVE_NO_FORK
 #ifndef HAVE_DAEMON
 /* From NetBSD - daemonise a process */
 
@@ -187,6 +188,7 @@
 	return 0;
 }
 #endif /* HAVE_DAEMON */
+#endif
 
 #ifndef HAVE_BASENAME
 
diff -Nru dropbear-0.41/inetmain.c ../firm/uClinux/apps-gpl/dropbear/inetmain.c
--- dropbear-0.41/inetmain.c	1970-01-01 01:00:00.000000000 +0100
+++ ../firm/uClinux/apps-gpl/dropbear/inetmain.c	2004-08-19 16:15:46.000000000 +0200
@@ -0,0 +1,115 @@
+/*
+ * Dropbear - a SSH2 server
+ * 
+ * Copyright (c) 2002,2003 Matt Johnston
+ * All rights reserved.
+ * 
+ * Inetd mode (actually for tcpsvd/tcpserver) by Erich Schubert, 2004
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE. */
+
+#include "includes.h"
+#include "dbutil.h"
+#include "session.h"
+#include "buffer.h"
+#include "signkey.h"
+#include "runopts.h"
+
+static void sigchld_handler(int dummy);
+static void sigsegv_handler(int);
+static void sigintterm_handler(int fish);
+
+int main(int argc, char ** argv)
+{
+	runopts * opts;
+	FILE * pidfile;
+
+	struct sigaction sa_chld;
+
+	/* get commandline options */
+	opts = getrunopts(argc, argv);
+
+#ifndef DISABLE_SYSLOG
+	if (usingsyslog) {
+		startsyslog();
+	}
+#endif
+
+	/* set up cleanup handler */
+	if (signal(SIGINT, sigintterm_handler) == SIG_ERR || 
+#ifndef DEBUG_VALGRIND
+		signal(SIGTERM, sigintterm_handler) == SIG_ERR ||
+#endif
+		signal(SIGPIPE, SIG_IGN) == SIG_ERR) {
+		dropbear_exit("signal() error");
+	}
+
+	/* catch and reap zombie children */
+	sa_chld.sa_handler = sigchld_handler;
+	sa_chld.sa_flags = SA_NOCLDSTOP;
+	if (sigaction(SIGCHLD, &sa_chld, NULL) < 0) {
+		dropbear_exit("signal() error");
+	}
+	if (signal(SIGSEGV, sigsegv_handler) == SIG_ERR) {
+		dropbear_exit("signal() error");
+	}
+
+	{
+		/* child */
+		char * addrstring = NULL;
+
+		addrstring = getenv("TCPREMOTEIP");
+		if (!addrstring) addrstring = "unknown";
+		dropbear_log(LOG_INFO, "Child connection from %s", addrstring);
+	}
+
+	/* start the session */
+	child_session(0, 1, opts);
+	/* don't return */
+	assert(0);
+
+	/* don't reach here */
+	return -1;
+}
+
+/* catch + reap zombie children */
+static void sigchld_handler(int fish) {
+	struct sigaction sa_chld;
+
+	while(waitpid(-1, NULL, WNOHANG) > 0); 
+
+	sa_chld.sa_handler = sigchld_handler;
+	sa_chld.sa_flags = SA_NOCLDSTOP;
+	if (sigaction(SIGCHLD, &sa_chld, NULL) < 0) {
+		dropbear_exit("signal() error");
+	}
+}
+
+/* catch any segvs */
+static void sigsegv_handler(int fish) {
+	fprintf(stderr, "Aiee, segfault! You should probably report "
+			"this as a bug to the developer\n");
+	exit(EXIT_FAILURE);
+}
+
+/* catch ctrl-c or sigterm */
+static void sigintterm_handler(int fish) {
+
+	exitflag = 1;
+}
diff -Nru dropbear-0.41/packet.c ../firm/uClinux/apps-gpl/dropbear/packet.c
--- dropbear-0.41/packet.c	2004-01-13 13:42:40.000000000 +0100
+++ ../firm/uClinux/apps-gpl/dropbear/packet.c	2004-08-19 16:15:08.000000000 +0200
@@ -63,7 +63,11 @@
 	len = writebuf->len - writebuf->pos;
 	assert(len > 0);
 	/* Try to write as much as possible */
+#ifndef INET_MODE
 	written = write(ses.sock, buf_getptr(writebuf, len), len);
+#else
+	written = write(ses.sockout, buf_getptr(writebuf, len), len);
+#endif
 
 	if (written < 0) {
 		if (errno == EINTR) {
@@ -121,7 +125,11 @@
 	 * mightn't be any available (EAGAIN) */
 	assert(ses.readbuf != NULL);
 	maxlen = ses.readbuf->len - ses.readbuf->pos;
+#ifndef INET_MODE
 	len = read(ses.sock, buf_getptr(ses.readbuf, maxlen), maxlen);
+#else
+	len = read(ses.sockin, buf_getptr(ses.readbuf, maxlen), maxlen);
+#endif
 
 	if (len == 0) {
 		session_remoteclosed();
@@ -170,8 +178,13 @@
 	maxlen = blocksize - ses.readbuf->pos;
 			
 	/* read the rest of the packet if possible */
+#ifndef INET_MODE
 	len = read(ses.sock, buf_getwriteptr(ses.readbuf, maxlen),
 			maxlen);
+#else
+	len = read(ses.sockin, buf_getwriteptr(ses.readbuf, maxlen),
+			maxlen);
+#endif
 	if (len == 0) {
 		session_remoteclosed();
 	}
diff -Nru dropbear-0.41/scp.c ../firm/uClinux/apps-gpl/dropbear/scp.c
--- dropbear-0.41/scp.c	2004-01-19 15:01:26.000000000 +0100
+++ ../firm/uClinux/apps-gpl/dropbear/scp.c	2004-06-23 14:12:04.000000000 +0200
@@ -166,8 +166,16 @@
 	close(reserved[0]);
 	close(reserved[1]);
 
+	/* prepare command */
+	/* doing this outside the vfork is needed for clean MMUless operation */
+	args.list[0] = ssh_program;
+	if (remuser != NULL)
+		addargs(&args, "-l%s", remuser);
+	addargs(&args, "%s", host);
+	addargs(&args, "%s", cmd);
+
 	/* Fork a child to execute the command on the remote host using ssh. */
-	do_cmd_pid = fork();
+	do_cmd_pid = vfork();
 	if (do_cmd_pid == 0) {
 		/* Child. */
 		close(pin[1]);
@@ -177,12 +185,6 @@
 		close(pin[0]);
 		close(pout[1]);
 
-		args.list[0] = ssh_program;
-		if (remuser != NULL)
-			addargs(&args, "-l%s", remuser);
-		addargs(&args, "%s", host);
-		addargs(&args, "%s", cmd);
-
 		execvp(ssh_program, args.list);
 		perror(ssh_program);
 		exit(1);
@@ -190,6 +192,19 @@
 		fprintf(stderr, "Fatal error: fork: %s\n", strerror(errno));
 		exit(1);
 	}
+	/* clean up command */
+	/* pop cmd */
+	free(args->list[--args->num]);
+	args->list[args->num]=NULL;
+	/* pop host */
+	free(args->list[--args->num-1]);
+	args->list[args->num]=NULL;
+	/* pop user */
+	if (remuser != NULL) {
+		free(args->list[--args->num-1]);
+		args->list[args->num]=NULL;
+	}
+
 	/* Parent.  Close the other side, and return the local side. */
 	close(pin[0]);
 	*fdout = pin[1];
diff -Nru dropbear-0.41/session.c ../firm/uClinux/apps-gpl/dropbear/session.c
--- dropbear-0.41/session.c	2003-12-15 08:16:39.000000000 +0100
+++ ../firm/uClinux/apps-gpl/dropbear/session.c	2004-08-19 15:50:00.000000000 +0200
@@ -42,23 +42,34 @@
 /* this is set when we get SIGINT or SIGTERM, the handler is in main.c */
 int exitflag = 0;
 
+#ifndef INET_MODE
 static void session_init(int sock, runopts *opts, int childpipe,
 		struct sockaddr *remoteaddr);
+#else
+static void session_init(int sockin, int sockout, runopts *opts);
+#endif
 static void checktimeouts();
 static void session_identification();
 static int ident_readln(int fd, char* buf, int count);
 
 struct sshsession ses;
 
+#ifndef INET_MODE
 void child_session(int sock, runopts *opts, int childpipe,
 		struct sockaddr *remoteaddr) {
-
+#else
+void child_session(int sockin, int sockout, runopts *opts) {
+#endif
 	fd_set readfd, writefd;
 	struct timeval timeout;
 	int val;
 	
 	crypto_init();
+#ifndef INET_MODE
 	session_init(sock, opts, childpipe, remoteaddr);
+#else
+	session_init(sockin, sockout, opts);
+#endif
 
 	/* exchange identification, version etc */
 	session_identification();
@@ -79,10 +90,12 @@
 		FD_ZERO(&writefd);
 		FD_ZERO(&readfd);
 		assert(ses.payload == NULL);
-		if (ses.sock != -1) {
-			FD_SET(ses.sock, &readfd);
+		if (ses.sockin != -1) {
+			FD_SET(ses.sockin, &readfd);
+		}
+		if (ses.sockout != -1) {
 			if (!isempty(&ses.writequeue)) {
-				FD_SET(ses.sock, &writefd);
+				FD_SET(ses.sockout, &writefd);
 			}
 		}
 
@@ -114,12 +127,13 @@
 		}
 
 		/* process session socket's incoming/outgoing data */
-		if (ses.sock != -1) {
-			if (FD_ISSET(ses.sock, &writefd) && !isempty(&ses.writequeue)) {
+		if (ses.sockout != -1) {
+			if (FD_ISSET(ses.sockout, &writefd) && !isempty(&ses.writequeue)) {
 				write_packet();
 			}
-
-			if (FD_ISSET(ses.sock, &readfd)) {
+		}
+		if (ses.sockin != -1) {
+			if (FD_ISSET(ses.sockin, &readfd)) {
 				read_packet();
 			}
 			
@@ -174,8 +188,10 @@
 /* called when the remote side closes the connection */
 void session_remoteclosed() {
 
-	close(ses.sock);
-	ses.sock = -1;
+	close(ses.sockin);
+	ses.sockin = -1;
+	close(ses.sockout);
+	ses.sockout = -1;
 	dropbear_close("Exited normally");
 
 }
@@ -202,20 +218,37 @@
 }
 
 /* called only at the start of a session, set up initial state */
+#ifndef INET_MODE
 static void session_init(int sock, runopts *opts, int childpipe,
 		struct sockaddr *remoteaddr) {
+#else
+static void session_init(int sockin, int sockout, runopts *opts) {
+#endif
 
 	struct timeval tv;
 	TRACE(("enter session_init"));
 
+#ifndef INET_MODE
 	ses.remoteaddr = remoteaddr;
 
 	ses.hostname = getaddrhostname(remoteaddr);
+#else
+	/* no ses.remoteaddr in inet mode */
+	ses.hostname = getenv("TCPREMOTEHOST");
+	if (!ses.hostname) ses.hostname = getenv("TCPREMOTEIP");
+	if (!ses.hostname) ses.hostname = "unknown";
+	/* dropbear expects this to have been allocated */
+	ses.hostname = strdup(ses.hostname);
+#endif
 
-	ses.sock = sock;
-	ses.maxfd = sock;
+	ses.sockin  = sockin;
+	ses.sockout = sockout;
+	ses.maxfd = sockin;
+	if (sockout > sockin) ses.maxfd = sockout;
 
+#ifndef INET_MODE
 	ses.childpipe = childpipe;
+#endif
 
 	ses.opts = opts;
 
@@ -280,12 +313,12 @@
 	char done = 0;
 
 	/* write our version string, this blocks */
-	if (atomicio(write, ses.sock, LOCAL_IDENT "\r\n",
+	if (atomicio(write, ses.sockout, LOCAL_IDENT "\r\n",
 				strlen(LOCAL_IDENT "\r\n")) == DROPBEAR_FAILURE) {
 		dropbear_exit("Error writing ident string");
 	}
 
-	len = ident_readln(ses.sock, linebuf, 256);
+	len = ident_readln(ses.sockin, linebuf, 256);
 	if (len >= 4 && memcmp(linebuf, "SSH-", 4) == 0) {
 		/* start of line matches */
 		done = 1;
diff -Nru dropbear-0.41/session.h ../firm/uClinux/apps-gpl/dropbear/session.h
--- dropbear-0.41/session.h	2004-01-13 13:42:41.000000000 +0100
+++ ../firm/uClinux/apps-gpl/dropbear/session.h	2004-08-19 16:13:11.000000000 +0200
@@ -39,8 +39,12 @@
 extern int exitflag;
 
 void session_cleanup();
+#ifndef INET_MODE
 void child_session(int sock, runopts *opts, int childpipe,
 		struct sockaddr *remoteaddr);
+#else
+void child_session(int sockin, int sockout, runopts *opts);
+#endif
 void session_remoteclosed();
 
 struct key_context {
@@ -70,11 +74,20 @@
 struct sshsession {
 
 	runopts * opts; /* runtime options, incl hostkey, banner etc */
+#ifndef INET_MODE
 	int sock;
+#else
+	int sockin;
+	int sockout;
+#endif
+#ifndef INET_MODE
 	int childpipe; /* kept open until we successfully authenticate */
+#endif
 	long connecttime; /* time of initial connection */
 
+#ifndef INET_MODE
 	struct sockaddr *remoteaddr; /* the host and port of the client */
+#endif
 	unsigned char *hostname; /* the remote hostname */
 
 	int maxfd; /* the maximum file descriptor to check with select() */


More information about the Dropbear mailing list