Subject: ftpd daemon mode
To: None <tech-userlevel@NetBSD.org>
From: Peter Postma <peter@pointless.nl>
List: tech-userlevel
Date: 06/24/2005 13:19:36
--fUYQa+Pmc3FrFX/N
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

I've added daemon mode (inspired from FreeBSD) to our ftpd, patch is
attached. Comments are welcome.

-- 
Peter Postma

--fUYQa+Pmc3FrFX/N
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="ftpd.diff"

Index: ftpd.8
===================================================================
RCS file: /cvsroot/src/libexec/ftpd/ftpd.8,v
retrieving revision 1.74
diff -u -r1.74 ftpd.8
--- ftpd.8	7 Aug 2003 09:46:39 -0000	1.74
+++ ftpd.8	24 Jun 2005 11:08:50 -0000
@@ -63,7 +63,7 @@
 .\"
 .\"     @(#)ftpd.8	8.2 (Berkeley) 4/19/94
 .\"
-.Dd February 26, 2003
+.Dd June 23, 2005
 .Dt FTPD 8
 .Os
 .Sh NAME
@@ -72,7 +72,7 @@
 Internet File Transfer Protocol server
 .Sh SYNOPSIS
 .Nm
-.Op Fl dHlqQrsuUwWX
+.Op Fl 46DdHlqQrsuUwWX
 .Op Fl a Ar anondir
 .Op Fl c Ar confdir
 .Op Fl C Ar user
@@ -93,6 +93,14 @@
 .Pp
 Available options:
 .Bl -tag -width Ds
+.It Fl 4
+When
+.Fl D
+is specified, bind to IPv4 addresses only.
+.It Fl 6
+When
+.Fl D
+is specified, bind to IPv6 addresses only.
 .It Fl a Ar anondir
 Define
 .Ar anondir
@@ -128,6 +136,16 @@
 .Nm
 exits with an exit code of 0 if access would be granted, or 1 otherwise.
 This can be useful for testing configurations.
+.It Fl D
+Run as daemon,
+.Nm
+will listen on the default FTP port for incoming connections
+and fork a child for each connection.
+This is lower overhead than starting
+.Nm
+from
+.Xr inetd 8
+and thus might be useful on busy servers to reduce load.
 .It Fl d
 Debugging information is written to the syslog using a facility of
 .Dv LOG_FTP .
Index: ftpd.c
===================================================================
RCS file: /cvsroot/src/libexec/ftpd/ftpd.c,v
retrieving revision 1.166
diff -u -r1.166 ftpd.c
--- ftpd.c	23 Jun 2005 04:20:41 -0000	1.166
+++ ftpd.c	24 Jun 2005 11:08:53 -0000
@@ -140,6 +140,7 @@
 #include <limits.h>
 #include <netdb.h>
 #include <pwd.h>
+#include <poll.h>
 #include <signal.h>
 #include <stdarg.h>
 #include <stdio.h>
@@ -181,6 +182,7 @@
 volatile sig_atomic_t	urgflag;
 
 int	data;
+int	Dflag;
 int	sflag;
 int	stru;			/* avoid C keyword */
 int	mode;
@@ -257,6 +259,7 @@
 static void	 toolong(int);
 static void	 sigquit(int);
 static void	 sigurg(int);
+static void	 sigchild(int);
 static int	 handleoobcmd(void);
 static int	 receive_data(FILE *, FILE *);
 static int	 send_data(FILE *, FILE *, const struct stat *, int);
@@ -292,11 +295,13 @@
 	const char	*xferlogname = NULL;
 	long		l;
 	struct sigaction sa;
+	sa_family_t	af = AF_UNSPEC;
 
 	connections = 1;
 	debug = 0;
 	logging = 0;
 	pdata = -1;
+	Dflag = 0;
 	sflag = 0;
 	dataport = 0;
 	dopidfile = 1;		/* default: DO use a pid file to count users */
@@ -320,9 +325,17 @@
 	 */
 	openlog("ftpd", LOG_PID | LOG_NDELAY, LOG_FTP);
 
-	while ((ch = getopt(argc, argv, "a:c:C:de:h:HlL:P:qQrst:T:uUvV:wWX"))
-	    != -1) {
+	while ((ch = getopt(argc, argv,
+	    "46a:c:C:Dde:h:HlL:P:qQrst:T:uUvV:wWX")) != -1) {
 		switch (ch) {
+		case '4':
+			af = AF_INET;
+			break;
+
+		case '6':
+			af = AF_INET6;
+			break;
+
 		case 'a':
 			anondir = optarg;
 			break;
@@ -336,6 +349,10 @@
 			exit(checkaccess(optarg) ? 0 : 1);
 			/* NOTREACHED */
 
+		case 'D':
+			Dflag = 1;
+			break;
+
 		case 'd':
 		case 'v':		/* deprecated */
 			debug = 1;
@@ -462,6 +479,103 @@
 	}
 	curname[0] = '\0';
 
+	if (Dflag) {
+		int error, fd, i, n, *socks;
+		struct pollfd *fds;
+		struct addrinfo hints, *res, *res0;
+
+		if (daemon(1, 0) == -1) {
+			syslog(LOG_ERR, "failed to daemonize: %m");
+			exit(1);
+		}
+		(void)signal(SIGCHLD, sigchild);
+
+		(void)memset(&hints, 0, sizeof(hints));
+		hints.ai_flags = AI_PASSIVE;
+		hints.ai_family = af;
+		hints.ai_socktype = SOCK_STREAM;
+		error = getaddrinfo(NULL, "ftp", &hints, &res0);
+		if (error) {
+			syslog(LOG_ERR, "getaddrinfo: %s", gai_strerror(error));
+			exit(1);
+		}
+
+		for (n = 0, res = res0; res != NULL; res = res->ai_next)
+			n++;
+		if (n == 0) {
+			syslog(LOG_ERR, "no addresses available");
+			exit(1);
+		}
+		socks = malloc(n * sizeof(int));
+		fds = malloc(n * sizeof(struct pollfd));
+		if (socks == NULL || fds == NULL) {
+			syslog(LOG_ERR, "malloc: %m");
+			exit(1);
+		}
+
+		for (n = 0, res = res0; res != NULL; res = res->ai_next) {
+			socks[n] = socket(res->ai_family, res->ai_socktype,
+			    res->ai_protocol);
+			if (socks[n] < 0)
+				continue;
+			(void)setsockopt(socks[n], SOL_SOCKET, SO_REUSEADDR,
+			    &on, sizeof(on));
+			if (bind(socks[n], res->ai_addr, res->ai_addrlen) < 0) {
+				(void)close(socks[n]);
+				continue;
+			}
+			if (listen(socks[n], 12) < 0) {
+				(void)close(socks[n]);
+				continue;
+			}
+
+			fds[n].fd = socks[n];
+			fds[n].events = POLLIN;
+			n++;
+		}
+		if (n == 0) {
+			syslog(LOG_ERR, "%m");
+			exit(1);
+		}
+		freeaddrinfo(res0);
+
+		if (pidfile(NULL) == -1)
+			syslog(LOG_ERR, "failed to write a pid file: %m");
+
+		for (;;) {
+			if (poll(fds, n, INFTIM) < 0) {
+				if (errno == EINTR)
+					continue;
+				syslog(LOG_ERR, "poll: %m");
+				exit(1);
+			}
+			for (i = 0; i < n; i++) {
+				if (fds[i].revents & POLLIN) {
+					fd = accept(fds[i].fd, NULL, NULL);
+					if (fd < 0) {
+						syslog(LOG_ERR, "accept: %m");
+						continue;
+					}
+					switch (fork()) {
+					case -1:
+						syslog(LOG_ERR, "fork: %m");
+						break;
+					case 0:
+						goto child;
+						/* NOTREACHED */
+					}
+					(void)close(fd);
+				}
+			}
+		}
+child:
+		(void)dup2(fd, STDIN_FILENO);
+		(void)dup2(fd, STDOUT_FILENO);
+		(void)dup2(fd, STDERR_FILENO);
+		for (i = 0; i < n; i++)
+			(void)close(socks[i]);
+	}
+
 	memset((char *)&his_addr, 0, sizeof(his_addr));
 	addrlen = sizeof(his_addr.si_su);
 	if (getpeername(0, (struct sockaddr *)&his_addr.si_su, &addrlen) < 0) {
@@ -670,6 +784,15 @@
 	urgflag = 1;
 }
 
+static void
+sigchild(int signo)
+{
+	int saved_errno = errno;
+
+	while (waitpid(-1, NULL, WNOHANG) > 0)
+		continue;
+	errno = saved_errno;
+}
 
 /*
  * Save the result of a getpwnam.  Used for USER command, since

--fUYQa+Pmc3FrFX/N--