Subject: Re: ftpd daemon mode
To: Luke Mewburn <lukem@NetBSD.org>
From: Peter Postma <peter@pointless.nl>
List: tech-userlevel
Date: 07/22/2005 15:03:27
--EeQfGwPcQSOJBaQU
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

On Sat, Jul 16, 2005 at 03:52:13PM +1000, Luke Mewburn wrote:
> On Fri, Jun 24, 2005 at 01:19:36PM +0200, Peter Postma wrote:
>   | I've added daemon mode (inspired from FreeBSD) to our ftpd, patch is
>   | attached. Comments are welcome.
> 
> Various consistency tweaks.
> Please examine ftpd and ensure that the rest of your changes
> seem consistent with the coding practice used within.
> 

Thanks, I've fixed them all.

> 
> Please use sigaction() for consistency with the rest of ftpd(8).
> ("signal(3) Must Die"!)
> 
> Something like:
> 		memset(&sa, 0, sizeof(sa));
> 		sa.sa_handler = SIG_IGN;
> 		sa.sa_flags = SA_NOCLDWAIT;
> 		sigemptyset(&sa.sa_mask);
> 		(void) sigaction(SIGCHLD, &sa, NULL);
> 
> I'm not sure how this will interact with ftpd_popen() and ftpd_pclose().
> I think the subsequent change of SIGCHLD to SIG_DFL later in main()
> (i.e, in the child ftpd) will allow ftpd_pclose()'s waitpid() to DTRT
> without having the parent-daemon-ftpd reap/ignore the status.
> 
> I think your patch's behaviour of setting SIGCHLD to the sigchild()
> handler that waitpid()s everything may have had adverse effects
> with ftpd_pclose().
> 
> Someone with more knowledge/experience of SIGCHLD semantics in
> this situation may be able to provide enlightenment.
> 

I see. But SA_NOCLDWAIT, is that portable enough? (thinking of tnftpd)

I've attached the updated patch, is it ok to commit?

Thanks,
-- 
Peter Postma

--EeQfGwPcQSOJBaQU
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	22 Jul 2005 12:40:35 -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	22 Jul 2005 12:40:37 -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;
@@ -292,11 +294,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 +324,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 +348,10 @@
 			exit(checkaccess(optarg) ? 0 : 1);
 			/* NOTREACHED */
 
+		case 'D':
+			Dflag = 1;
+			break;
+
 		case 'd':
 		case 'v':		/* deprecated */
 			debug = 1;
@@ -462,6 +478,108 @@
 	}
 	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)memset(&sa, 0, sizeof(sa));
+		sa.sa_handler = SIG_IGN;
+		sa.sa_flags = SA_NOCLDWAIT;
+		sigemptyset(&sa.sa_mask);
+		(void)sigaction(SIGCHLD, &sa, NULL);
+
+		(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] == -1)
+				continue;
+			(void)setsockopt(socks[n], SOL_SOCKET, SO_REUSEADDR,
+			    &on, sizeof(on));
+			if (bind(socks[n], res->ai_addr, res->ai_addrlen)
+			    == -1) {
+				(void)close(socks[n]);
+				continue;
+			}
+			if (listen(socks[n], 12) == -1) {
+				(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) == -1) {
+				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 == -1) {
+						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) {

--EeQfGwPcQSOJBaQU--