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--