ftpd.c revision 141918
118334Speter/*
290075Sobrien * Copyright (c) 1985, 1988, 1990, 1992, 1993, 1994
3110611Skan *	The Regents of the University of California.  All rights reserved.
418334Speter *
590075Sobrien * Redistribution and use in source and binary forms, with or without
618334Speter * modification, are permitted provided that the following conditions
790075Sobrien * are met:
890075Sobrien * 1. Redistributions of source code must retain the above copyright
990075Sobrien *    notice, this list of conditions and the following disclaimer.
1090075Sobrien * 2. Redistributions in binary form must reproduce the above copyright
1118334Speter *    notice, this list of conditions and the following disclaimer in the
1290075Sobrien *    documentation and/or other materials provided with the distribution.
1390075Sobrien * 3. All advertising materials mentioning features or use of this software
1490075Sobrien *    must display the following acknowledgement:
1590075Sobrien *	This product includes software developed by the University of
1618334Speter *	California, Berkeley and its contributors.
1718334Speter * 4. Neither the name of the University nor the names of its contributors
1890075Sobrien *    may be used to endorse or promote products derived from this software
1990075Sobrien *    without specific prior written permission.
2090075Sobrien *
2118334Speter * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
2290075Sobrien * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2390075Sobrien * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2490075Sobrien * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
2518334Speter * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2618334Speter * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2718334Speter * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2818334Speter * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2918334Speter * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
3018334Speter * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
3118334Speter * SUCH DAMAGE.
3218334Speter */
3318334Speter
3418334Speter#if 0
3518334Speter#ifndef lint
3618334Speterstatic char copyright[] =
3718334Speter"@(#) Copyright (c) 1985, 1988, 1990, 1992, 1993, 1994\n\
3850397Sobrien	The Regents of the University of California.  All rights reserved.\n";
3918334Speter#endif /* not lint */
4090075Sobrien#endif
4118334Speter
4218334Speter#ifndef lint
4318334Speter#if 0
4418334Speterstatic char sccsid[] = "@(#)ftpd.c	8.4 (Berkeley) 4/16/94";
4550397Sobrien#endif
4650397Sobrien#endif /* not lint */
4790075Sobrien
4818334Speter#include <sys/cdefs.h>
4918334Speter__FBSDID("$FreeBSD: head/libexec/ftpd/ftpd.c 141918 2005-02-14 17:42:58Z stefanf $");
5050397Sobrien
5150397Sobrien/*
5290075Sobrien * FTP server.
5390075Sobrien */
5418334Speter#include <sys/param.h>
5518334Speter#include <sys/ioctl.h>
5618334Speter#include <sys/mman.h>
5718334Speter#include <sys/socket.h>
5818334Speter#include <sys/stat.h>
5918334Speter#include <sys/time.h>
6018334Speter#include <sys/wait.h>
6118334Speter
62110611Skan#include <netinet/in.h>
6390075Sobrien#include <netinet/in_systm.h>
6490075Sobrien#include <netinet/ip.h>
6590075Sobrien#include <netinet/tcp.h>
6690075Sobrien
6790075Sobrien#define	FTP_NAMES
6890075Sobrien#include <arpa/ftp.h>
6990075Sobrien#include <arpa/inet.h>
7090075Sobrien#include <arpa/telnet.h>
7190075Sobrien
7290075Sobrien#include <ctype.h>
7390075Sobrien#include <dirent.h>
7452284Sobrien#include <err.h>
7552284Sobrien#include <errno.h>
7652284Sobrien#include <fcntl.h>
7752284Sobrien#include <glob.h>
7852284Sobrien#include <limits.h>
7952284Sobrien#include <netdb.h>
8052284Sobrien#include <pwd.h>
8190075Sobrien#include <grp.h>
8218334Speter#include <opie.h>
8318334Speter#include <signal.h>
8452284Sobrien#include <stdint.h>
8518334Speter#include <stdio.h>
8690075Sobrien#include <stdlib.h>
8750397Sobrien#include <string.h>
8890075Sobrien#include <syslog.h>
8990075Sobrien#include <time.h>
9090075Sobrien#include <unistd.h>
9118334Speter#include <libutil.h>
9218334Speter#ifdef	LOGIN_CAP
9390075Sobrien#include <login_cap.h>
9490075Sobrien#endif
9590075Sobrien
9690075Sobrien#ifdef USE_PAM
9790075Sobrien#include <security/pam_appl.h>
9890075Sobrien#endif
9990075Sobrien
10090075Sobrien#include "pathnames.h"
10150397Sobrien#include "extern.h"
10290075Sobrien
10390075Sobrien#include <stdarg.h>
10490075Sobrien
10590075Sobrienstatic char version[] = "Version 6.00LS";
10690075Sobrien#undef main
10790075Sobrien
10890075Sobrienextern	off_t restart_point;
10990075Sobrienextern	char cbuf[];
11018334Speter
11190075Sobrienunion sockunion ctrl_addr;
11290075Sobrienunion sockunion data_source;
11318334Speterunion sockunion data_dest;
11490075Sobrienunion sockunion his_addr;
11590075Sobrienunion sockunion pasv_addr;
11690075Sobrien
11790075Sobrienint	daemon_mode;
11890075Sobrienint	data;
11918334Speterint	dataport;
12018334Speterint	hostinfo = 1;	/* print host-specific info in messages */
12190075Sobrienint	logged_in;
12290075Sobrienstruct	passwd *pw;
123110611Skanchar	*homedir;
124110611Skanint	ftpdebug;
125110611Skanint	timeout = 900;    /* timeout after 15 minutes of inactivity */
126110611Skanint	maxtimeout = 7200;/* don't allow idle time to be set beyond 2 hours */
127110611Skanint	logging;
128110611Skanint	restricted_data_ports = 1;
129110611Skanint	paranoid = 1;	  /* be extra careful about security */
130110611Skanint	anon_only = 0;    /* Only anonymous ftp allowed */
131110611Skanint	guest;
132110611Skanint	dochroot;
133110611Skanchar	*chrootdir;
134110611Skanint	dowtmp = 1;
135110611Skanint	stats;
136110611Skanint	statfd = -1;
137110611Skanint	type;
138110611Skanint	form;
139110611Skanint	stru;			/* avoid C keyword */
140110611Skanint	mode;
141110611Skanint	usedefault = 1;		/* for data transfers */
142110611Skanint	pdata = -1;		/* for passive mode */
143110611Skanint	readonly = 0;		/* Server is in readonly mode.	*/
14490075Sobrienint	noepsv = 0;		/* EPSV command is disabled.	*/
14590075Sobrienint	noretr = 0;		/* RETR command is disabled.	*/
14690075Sobrienint	noguestretr = 0;	/* RETR command is disabled for anon users. */
14790075Sobrienint	noguestmkd = 0;		/* MKD command is disabled for anon users. */
14890075Sobrienint	noguestmod = 1;		/* anon users may not modify existing files. */
14918334Speter
15090075Sobrienoff_t	file_size;
15118334Speteroff_t	byte_count;
15290075Sobrien#if !defined(CMASK) || CMASK == 0
15318334Speter#undef CMASK
15490075Sobrien#define CMASK 027
15590075Sobrien#endif
15690075Sobrienint	defumask = CMASK;		/* default umask value */
15790075Sobrienchar	tmpline[7];
15890075Sobrienchar	*hostname;
15990075Sobrienint	epsvall = 0;
16090075Sobrien
16190075Sobrien#ifdef VIRTUAL_HOSTING
162110611Skanchar	*ftpuser;
16390075Sobrien
16418334Speterstatic struct ftphost {
16590075Sobrien	struct ftphost	*next;
16690075Sobrien	struct addrinfo *hostinfo;
16718334Speter	char		*hostname;
16890075Sobrien	char		*anonuser;
16918334Speter	char		*statfile;
17018334Speter	char		*welcome;
17118334Speter	char		*loginmsg;
17290075Sobrien} *thishost, *firsthost;
17318334Speter
17490075Sobrien#endif
17590075Sobrienchar	remotehost[NI_MAXHOST];
17690075Sobrienchar	*ident = NULL;
17790075Sobrien
17890075Sobrienstatic char	ttyline[20];
17990075Sobrienchar		*tty = ttyline;		/* for klogin */
18018334Speter
18118334Speter#ifdef USE_PAM
18218334Speterstatic int	auth_pam(struct passwd**, const char*);
18318334Speterpam_handle_t	*pamh = NULL;
18418334Speter#endif
18590075Sobrien
18690075Sobrienstatic struct opie	opiedata;
18790075Sobrienstatic char		opieprompt[OPIE_CHALLENGE_MAX+1];
18890075Sobrienstatic int		pwok;
18990075Sobrien
19090075Sobrienchar	*pid_file = NULL;
19190075Sobrien
19290075Sobrien/*
19390075Sobrien * Limit number of pathnames that glob can return.
19490075Sobrien * A limit of 0 indicates the number of pathnames is unlimited.
19590075Sobrien */
19690075Sobrien#define MAXGLOBARGS	16384
19790075Sobrien#
19890075Sobrien
19990075Sobrien/*
20090075Sobrien * Timeout intervals for retrying connections
20190075Sobrien * to hosts that don't accept PORT cmds.  This
20218334Speter * is a kludge, but given the problems with TCP...
20390075Sobrien */
20490075Sobrien#define	SWAITMAX	90	/* wait at most 90 seconds */
20590075Sobrien#define	SWAITINT	5	/* interval between retries */
20652284Sobrien
20752284Sobrienint	swaitmax = SWAITMAX;
20852284Sobrienint	swaitint = SWAITINT;
20952284Sobrien
21052284Sobrien#ifdef SETPROCTITLE
21152284Sobrien#ifdef OLD_SETPROCTITLE
21252284Sobrienchar	**Argv = NULL;		/* pointer to argument vector */
21352284Sobrienchar	*LastArgv = NULL;	/* end of argv */
21452284Sobrien#endif /* OLD_SETPROCTITLE */
21552284Sobrienchar	proctitle[LINE_MAX];	/* initial part of title */
21652284Sobrien#endif /* SETPROCTITLE */
21752284Sobrien
21852284Sobrien#define LOGCMD(cmd, file)		logcmd((cmd), (file), NULL, -1)
21952284Sobrien#define LOGCMD2(cmd, file1, file2)	logcmd((cmd), (file1), (file2), -1)
22052284Sobrien#define LOGBYTES(cmd, file, cnt)	logcmd((cmd), (file), NULL, (cnt))
22152284Sobrien
22252284Sobrienstatic	volatile sig_atomic_t recvurg;
22352284Sobrienstatic	int transflag;		/* NB: for debugging only */
22452284Sobrien
22552284Sobrien#define STARTXFER	flagxfer(1)
22652284Sobrien#define ENDXFER		flagxfer(0)
22752284Sobrien
22852284Sobrien#define START_UNSAFE	maskurg(1)
22952284Sobrien#define END_UNSAFE	maskurg(0)
23052284Sobrien
23152284Sobrien/* It's OK to put an `else' clause after this macro. */
23252284Sobrien#define CHECKOOB(action)						\
23352284Sobrien	if (recvurg) {							\
23452284Sobrien		recvurg = 0;						\
23552284Sobrien		if (myoob()) {						\
23652284Sobrien			ENDXFER;					\
23752284Sobrien			action;						\
23852284Sobrien		}							\
23952284Sobrien	}
24052284Sobrien
24152284Sobrien#ifdef VIRTUAL_HOSTING
24252284Sobrienstatic void	 inithosts(void);
24352284Sobrienstatic void	 selecthost(union sockunion *);
24490075Sobrien#endif
24552284Sobrienstatic void	 ack(char *);
24652284Sobrienstatic void	 sigurg(int);
24790075Sobrienstatic void	 maskurg(int);
24852284Sobrienstatic void	 flagxfer(int);
24952284Sobrienstatic int	 myoob(void);
25052284Sobrienstatic int	 checkuser(char *, char *, int, char **);
25152284Sobrienstatic FILE	*dataconn(char *, off_t, char *);
25252284Sobrienstatic void	 dolog(struct sockaddr *);
25390075Sobrienstatic void	 end_login(void);
25452284Sobrienstatic FILE	*getdatasock(char *);
25590075Sobrienstatic int	 guniquefd(char *, char **);
25690075Sobrienstatic void	 lostconn(int);
25752284Sobrienstatic void	 sigquit(int);
25890075Sobrienstatic int	 receive_data(FILE *, FILE *);
25990075Sobrienstatic int	 send_data(FILE *, FILE *, size_t, off_t, int);
26090075Sobrienstatic struct passwd *
26190075Sobrien		 sgetpwnam(char *);
26290075Sobrienstatic char	*sgetsave(char *);
26390075Sobrienstatic void	 reapchild(int);
26490075Sobrienstatic void	 appendf(char **, char *, ...) __printflike(2, 3);
26552284Sobrienstatic void	 logcmd(char *, char *, char *, off_t);
26690075Sobrienstatic void      logxfer(char *, off_t, time_t);
26790075Sobrienstatic char	*doublequote(char *);
26890075Sobrienstatic int	*socksetup(int, char *, const char *);
26990075Sobrien
27052284Sobrienint
27190075Sobrienmain(int argc, char *argv[], char **envp)
27290075Sobrien{
27390075Sobrien	socklen_t addrlen;
27490075Sobrien	int ch, on = 1, tos;
27590075Sobrien	char *cp, line[LINE_MAX];
27690075Sobrien	FILE *fd;
27790075Sobrien	char	*bindname = NULL;
27890075Sobrien	const char *bindport = "ftp";
27990075Sobrien	int	family = AF_UNSPEC;
28090075Sobrien	struct sigaction sa;
28190075Sobrien
28252284Sobrien	tzset();		/* in case no timezone database in ~ftp */
28390075Sobrien	sigemptyset(&sa.sa_mask);
28490075Sobrien	sa.sa_flags = SA_RESTART;
28590075Sobrien
28690075Sobrien#ifdef OLD_SETPROCTITLE
28790075Sobrien	/*
28890075Sobrien	 *  Save start and extent of argv for setproctitle.
28990075Sobrien	 */
29090075Sobrien	Argv = argv;
29190075Sobrien	while (*envp)
29290075Sobrien		envp++;
29390075Sobrien	LastArgv = envp[-1] + strlen(envp[-1]);
29490075Sobrien#endif /* OLD_SETPROCTITLE */
29552284Sobrien
29652284Sobrien	/*
29752284Sobrien	 * Prevent diagnostic messages from appearing on stderr.
29852284Sobrien	 * We run as a daemon or from inetd; in both cases, there's
29952284Sobrien	 * more reason in logging to syslog.
30018334Speter	 */
30118334Speter	(void) freopen(_PATH_DEVNULL, "w", stderr);
30218334Speter	opterr = 0;
30318334Speter
30418334Speter	/*
30518334Speter	 * LOG_NDELAY sets up the logging connection immediately,
30618334Speter	 * necessary for anonymous ftp's that chroot and can't do it later.
30718334Speter	 */
30818334Speter	openlog("ftpd", LOG_PID | LOG_NDELAY, LOG_FTP);
30918334Speter
31018334Speter	while ((ch = getopt(argc, argv,
31118334Speter	                    "46a:AdDEhlmMoOp:P:rRSt:T:u:UvW")) != -1) {
31218334Speter		switch (ch) {
31318334Speter		case '4':
31418334Speter			family = (family == AF_INET6) ? AF_UNSPEC : AF_INET;
31518334Speter			break;
31652750Sobrien
31718334Speter		case '6':
318110611Skan			family = (family == AF_INET) ? AF_UNSPEC : AF_INET6;
319110611Skan			break;
32018334Speter
32118334Speter		case 'a':
32218334Speter			bindname = optarg;
32390075Sobrien			break;
32418334Speter
32518334Speter		case 'A':
32618334Speter			anon_only = 1;
32718334Speter			break;
32818334Speter
32918334Speter		case 'd':
33018334Speter			ftpdebug++;
33118334Speter			break;
33252284Sobrien
33318334Speter		case 'D':
33450397Sobrien			daemon_mode++;
33550397Sobrien			break;
33650397Sobrien
33750397Sobrien		case 'E':
33818334Speter			noepsv = 1;
33918334Speter			break;
34018334Speter
34118334Speter		case 'h':
34218334Speter			hostinfo = 0;
34318334Speter			break;
34418334Speter
34518334Speter		case 'l':
34618334Speter			logging++;	/* > 1 == extra logging */
34718334Speter			break;
34818334Speter
34918334Speter		case 'm':
35018334Speter			noguestmod = 0;
35118334Speter			break;
35218334Speter
35318334Speter		case 'M':
35418334Speter			noguestmkd = 1;
35518334Speter			break;
35618334Speter
35718334Speter		case 'o':
35818334Speter			noretr = 1;
35918334Speter			break;
36018334Speter
36118334Speter		case 'O':
36218334Speter			noguestretr = 1;
36318334Speter			break;
36450397Sobrien
36550397Sobrien		case 'p':
36650397Sobrien			pid_file = optarg;
36750397Sobrien			break;
36850397Sobrien
36950397Sobrien		case 'P':
37050397Sobrien			bindport = optarg;
37150397Sobrien			break;
37250397Sobrien
37350397Sobrien		case 'r':
37418334Speter			readonly = 1;
37518334Speter			break;
37618334Speter
37752284Sobrien		case 'R':
37852284Sobrien			paranoid = 0;
37952284Sobrien			break;
38052284Sobrien
38118334Speter		case 'S':
38218334Speter			stats++;
38390075Sobrien			break;
38418334Speter
38518334Speter		case 't':
38650397Sobrien			timeout = atoi(optarg);
38750397Sobrien			if (maxtimeout < timeout)
38818334Speter				maxtimeout = timeout;
38918334Speter			break;
39018334Speter
39118334Speter		case 'T':
39218334Speter			maxtimeout = atoi(optarg);
39318334Speter			if (timeout > maxtimeout)
39418334Speter				timeout = maxtimeout;
39518334Speter			break;
39618334Speter
39718334Speter		case 'u':
39818334Speter		    {
39918334Speter			long val = 0;
40018334Speter
40118334Speter			val = strtol(optarg, &optarg, 8);
40218334Speter			if (*optarg != '\0' || val < 0)
40318334Speter				syslog(LOG_WARNING, "bad value for -u");
40418334Speter			else
40518334Speter				defumask = val;
40650397Sobrien			break;
40718334Speter		    }
40818334Speter		case 'U':
40950397Sobrien			restricted_data_ports = 0;
41018334Speter			break;
41118334Speter
41218334Speter		case 'v':
41318334Speter			ftpdebug++;
41418334Speter			break;
41518334Speter
41618334Speter		case 'W':
41790075Sobrien			dowtmp = 0;
41818334Speter			break;
41918334Speter
42018334Speter		default:
42118334Speter			syslog(LOG_WARNING, "unknown flag -%c ignored", optopt);
42218334Speter			break;
42318334Speter		}
42490075Sobrien	}
42518334Speter
42618334Speter#ifdef VIRTUAL_HOSTING
42718334Speter	inithosts();
42852750Sobrien#endif
42952750Sobrien
43090075Sobrien	if (daemon_mode) {
43152750Sobrien		int *ctl_sock, fd, maxfd = -1, nfds, i;
43252750Sobrien		fd_set defreadfds, readfds;
43352750Sobrien		pid_t pid;
43452750Sobrien
43552750Sobrien		/*
43652750Sobrien		 * Detach from parent.
43752750Sobrien		 */
43852750Sobrien		if (daemon(1, 1) < 0) {
43952750Sobrien			syslog(LOG_ERR, "failed to become a daemon");
44052750Sobrien			exit(1);
44152750Sobrien		}
44218334Speter		sa.sa_handler = reapchild;
44390075Sobrien		(void)sigaction(SIGCHLD, &sa, NULL);
44490075Sobrien
44590075Sobrien		/*
44690075Sobrien		 * Open a socket, bind it to the FTP port, and start
44718334Speter		 * listening.
44890075Sobrien		 */
44918334Speter		ctl_sock = socksetup(family, bindname, bindport);
45090075Sobrien		if (ctl_sock == NULL)
45190075Sobrien			exit(1);
45290075Sobrien
45390075Sobrien		FD_ZERO(&defreadfds);
45490075Sobrien		for (i = 1; i <= *ctl_sock; i++) {
45590075Sobrien			FD_SET(ctl_sock[i], &defreadfds);
45690075Sobrien			if (listen(ctl_sock[i], 32) < 0) {
45790075Sobrien				syslog(LOG_ERR, "control listen: %m");
45890075Sobrien				exit(1);
45990075Sobrien			}
46090075Sobrien			if (maxfd < ctl_sock[i])
46190075Sobrien				maxfd = ctl_sock[i];
46290075Sobrien		}
46390075Sobrien
46490075Sobrien		/*
46590075Sobrien		 * Atomically write process ID
46618334Speter		 */
46790075Sobrien		if (pid_file)
46890075Sobrien		{
46990075Sobrien			int fd;
47090075Sobrien			char buf[20];
47152750Sobrien
47290075Sobrien			fd = open(pid_file, O_CREAT | O_WRONLY | O_TRUNC
47390075Sobrien				| O_NONBLOCK | O_EXLOCK, 0644);
47452750Sobrien			if (fd < 0) {
47552750Sobrien				if (errno == EAGAIN)
47652750Sobrien					syslog(LOG_ERR,
47752750Sobrien					    "%s: already locked", pid_file);
47852750Sobrien				else
47990075Sobrien					syslog(LOG_ERR, "%s: %m", pid_file);
48052750Sobrien				exit(1);
48152750Sobrien			}
48252750Sobrien			snprintf(buf, sizeof(buf),
48318334Speter				"%lu\n", (unsigned long) getpid());
48490075Sobrien			if (write(fd, buf, strlen(buf)) < 0) {
48590075Sobrien				syslog(LOG_ERR, "%s: write: %m", pid_file);
48690075Sobrien				exit(1);
48790075Sobrien			}
48852750Sobrien			/* Leave the pid file open and locked */
48990075Sobrien		}
49090075Sobrien		/*
49190075Sobrien		 * Loop forever accepting connection requests and forking off
49290075Sobrien		 * children to handle them.
49390075Sobrien		 */
49490075Sobrien		while (1) {
49590075Sobrien			FD_COPY(&defreadfds, &readfds);
49690075Sobrien			nfds = select(maxfd + 1, &readfds, NULL, NULL, 0);
49790075Sobrien			if (nfds <= 0) {
49890075Sobrien				if (nfds < 0 && errno != EINTR)
49990075Sobrien					syslog(LOG_WARNING, "select: %m");
50090075Sobrien				continue;
50190075Sobrien			}
50290075Sobrien
50390075Sobrien			pid = -1;
50490075Sobrien                        for (i = 1; i <= *ctl_sock; i++)
50590075Sobrien				if (FD_ISSET(ctl_sock[i], &readfds)) {
50652750Sobrien					addrlen = sizeof(his_addr);
50752750Sobrien					fd = accept(ctl_sock[i],
50818334Speter					    (struct sockaddr *)&his_addr,
50952750Sobrien					    &addrlen);
51052750Sobrien					if (fd >= 0) {
51152750Sobrien						if ((pid = fork()) == 0) {
51218334Speter							/* child */
51352750Sobrien							(void) dup2(fd, 0);
51452750Sobrien							(void) dup2(fd, 1);
51552750Sobrien							close(ctl_sock[i]);
51652750Sobrien						} else
51752750Sobrien							close(fd);
51852750Sobrien					}
51918334Speter				}
52018334Speter			if (pid == 0)
52118334Speter				break;
52218334Speter		}
52318334Speter	} else {
52418334Speter		addrlen = sizeof(his_addr);
52552750Sobrien		if (getpeername(0, (struct sockaddr *)&his_addr, &addrlen) < 0) {
52652750Sobrien			syslog(LOG_ERR, "getpeername (%s): %m",argv[0]);
52752750Sobrien			exit(1);
52852750Sobrien		}
52952750Sobrien	}
53052750Sobrien
53152750Sobrien	sa.sa_handler = SIG_DFL;
53252750Sobrien	(void)sigaction(SIGCHLD, &sa, NULL);
53318334Speter
53418334Speter	sa.sa_handler = sigurg;
53518334Speter	sa.sa_flags = 0;		/* don't restart syscalls for SIGURG */
53618334Speter	(void)sigaction(SIGURG, &sa, NULL);
53790075Sobrien
53890075Sobrien	sigfillset(&sa.sa_mask);	/* block all signals in handler */
53952750Sobrien	sa.sa_flags = SA_RESTART;
54052750Sobrien	sa.sa_handler = sigquit;
54152750Sobrien	(void)sigaction(SIGHUP, &sa, NULL);
54252750Sobrien	(void)sigaction(SIGINT, &sa, NULL);
54352750Sobrien	(void)sigaction(SIGQUIT, &sa, NULL);
54418334Speter	(void)sigaction(SIGTERM, &sa, NULL);
54518334Speter
54618334Speter	sa.sa_handler = lostconn;
54790075Sobrien	(void)sigaction(SIGPIPE, &sa, NULL);
54818334Speter
54990075Sobrien	addrlen = sizeof(ctrl_addr);
55090075Sobrien	if (getsockname(0, (struct sockaddr *)&ctrl_addr, &addrlen) < 0) {
55190075Sobrien		syslog(LOG_ERR, "getsockname (%s): %m",argv[0]);
55290075Sobrien		exit(1);
55318334Speter	}
55418334Speter	dataport = ntohs(ctrl_addr.su_port) - 1; /* as per RFC 959 */
55518334Speter#ifdef VIRTUAL_HOSTING
55690075Sobrien	/* select our identity from virtual host table */
55790075Sobrien	selecthost(&ctrl_addr);
55890075Sobrien#endif
55990075Sobrien#ifdef IP_TOS
56090075Sobrien	if (ctrl_addr.su_family == AF_INET)
56118334Speter      {
56290075Sobrien	tos = IPTOS_LOWDELAY;
56390075Sobrien	if (setsockopt(0, IPPROTO_IP, IP_TOS, &tos, sizeof(int)) < 0)
56490075Sobrien		syslog(LOG_WARNING, "control setsockopt (IP_TOS): %m");
56590075Sobrien      }
56618334Speter#endif
56790075Sobrien	/*
56890075Sobrien	 * Disable Nagle on the control channel so that we don't have to wait
56990075Sobrien	 * for peer's ACK before issuing our next reply.
57018334Speter	 */
57118334Speter	if (setsockopt(0, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)) < 0)
57290075Sobrien		syslog(LOG_WARNING, "control setsockopt (TCP_NODELAY): %m");
57390075Sobrien
57418334Speter	data_source.su_port = htons(ntohs(ctrl_addr.su_port) - 1);
57590075Sobrien
57618334Speter	/* set this here so klogin can use it... */
57718334Speter	(void)snprintf(ttyline, sizeof(ttyline), "ftp%d", getpid());
57818334Speter
57918334Speter	/* Try to handle urgent data inline */
58018334Speter#ifdef SO_OOBINLINE
58118334Speter	if (setsockopt(0, SOL_SOCKET, SO_OOBINLINE, &on, sizeof(on)) < 0)
58218334Speter		syslog(LOG_WARNING, "control setsockopt (SO_OOBINLINE): %m");
58318334Speter#endif
58418334Speter
58518334Speter#ifdef	F_SETOWN
58618334Speter	if (fcntl(fileno(stdin), F_SETOWN, getpid()) == -1)
58718334Speter		syslog(LOG_ERR, "fcntl F_SETOWN: %m");
58818334Speter#endif
58918334Speter	dolog((struct sockaddr *)&his_addr);
59018334Speter	/*
59118334Speter	 * Set up default state
59218334Speter	 */
59318334Speter	data = -1;
59418334Speter	type = TYPE_A;
59518334Speter	form = FORM_N;
59618334Speter	stru = STRU_F;
59718334Speter	mode = MODE_S;
59818334Speter	tmpline[0] = '\0';
59990075Sobrien
60090075Sobrien	/* If logins are disabled, print out the message. */
60118334Speter	if ((fd = fopen(_PATH_NOLOGIN,"r")) != NULL) {
60218334Speter		while (fgets(line, sizeof(line), fd) != NULL) {
60390075Sobrien			if ((cp = strchr(line, '\n')) != NULL)
60490075Sobrien				*cp = '\0';
60590075Sobrien			lreply(530, "%s", line);
60618334Speter		}
60790075Sobrien		(void) fflush(stdout);
60818334Speter		(void) fclose(fd);
60990075Sobrien		reply(530, "System not available.");
61090075Sobrien		exit(0);
61190075Sobrien	}
61218334Speter#ifdef VIRTUAL_HOSTING
61318334Speter	fd = fopen(thishost->welcome, "r");
61418334Speter#else
61518334Speter	fd = fopen(_PATH_FTPWELCOME, "r");
61618334Speter#endif
61718334Speter	if (fd != NULL) {
61818334Speter		while (fgets(line, sizeof(line), fd) != NULL) {
61918334Speter			if ((cp = strchr(line, '\n')) != NULL)
62018334Speter				*cp = '\0';
62118334Speter			lreply(220, "%s", line);
62218334Speter		}
62318334Speter		(void) fflush(stdout);
62418334Speter		(void) fclose(fd);
62518334Speter		/* reply(220,) must follow */
62618334Speter	}
62718334Speter#ifndef VIRTUAL_HOSTING
62818334Speter	if ((hostname = malloc(MAXHOSTNAMELEN)) == NULL)
62918334Speter		fatalerror("Ran out of memory.");
63018334Speter	if (gethostname(hostname, MAXHOSTNAMELEN - 1) < 0)
63118334Speter		hostname[0] = '\0';
63218334Speter	hostname[MAXHOSTNAMELEN - 1] = '\0';
63318334Speter#endif
63418334Speter	if (hostinfo)
63518334Speter		reply(220, "%s FTP server (%s) ready.", hostname, version);
63618334Speter	else
63718334Speter		reply(220, "FTP server ready.");
63818334Speter	for (;;)
63918334Speter		(void) yyparse();
64018334Speter	/* NOTREACHED */
64118334Speter}
64218334Speter
64318334Speterstatic void
64418334Speterlostconn(int signo)
64518334Speter{
64618334Speter
64718334Speter	if (ftpdebug)
64818334Speter		syslog(LOG_DEBUG, "lost connection");
64918334Speter	dologout(1);
65018334Speter}
65118334Speter
65218334Speterstatic void
65318334Spetersigquit(int signo)
65418334Speter{
65518334Speter
65618334Speter	syslog(LOG_ERR, "got signal %d", signo);
65718334Speter	dologout(1);
65890075Sobrien}
65990075Sobrien
66090075Sobrien#ifdef VIRTUAL_HOSTING
66190075Sobrien/*
66290075Sobrien * read in virtual host tables (if they exist)
66390075Sobrien */
66490075Sobrien
66590075Sobrienstatic void
66690075Sobrieninithosts(void)
66790075Sobrien{
66818334Speter	int insert;
66990075Sobrien	size_t len;
67018334Speter	FILE *fp;
67190075Sobrien	char *cp, *mp, *line;
67290075Sobrien	char *hostname;
67390075Sobrien	char *vhost, *anonuser, *statfile, *welcome, *loginmsg;
67418334Speter	struct ftphost *hrp, *lhrp;
67590075Sobrien	struct addrinfo hints, *res, *ai;
67690075Sobrien
67790075Sobrien	/*
67818334Speter	 * Fill in the default host information
67990075Sobrien	 */
68090075Sobrien	if ((hostname = malloc(MAXHOSTNAMELEN)) == NULL)
68190075Sobrien		fatalerror("Ran out of memory.");
68290075Sobrien	if (gethostname(hostname, MAXHOSTNAMELEN - 1) < 0)
68390075Sobrien		hostname[0] = '\0';
68490075Sobrien	hostname[MAXHOSTNAMELEN - 1] = '\0';
68518334Speter	if ((hrp = malloc(sizeof(struct ftphost))) == NULL)
68690075Sobrien		fatalerror("Ran out of memory.");
68790075Sobrien	hrp->hostname = hostname;
68890075Sobrien	hrp->hostinfo = NULL;
68990075Sobrien
69018334Speter	memset(&hints, 0, sizeof(hints));
69190075Sobrien	hints.ai_flags = AI_CANONNAME;
69218334Speter	hints.ai_family = AF_UNSPEC;
69390075Sobrien	if (getaddrinfo(hrp->hostname, NULL, &hints, &res) == 0)
69490075Sobrien		hrp->hostinfo = res;
69518334Speter	hrp->statfile = _PATH_FTPDSTATFILE;
69690075Sobrien	hrp->welcome  = _PATH_FTPWELCOME;
69790075Sobrien	hrp->loginmsg = _PATH_FTPLOGINMESG;
69890075Sobrien	hrp->anonuser = "ftp";
69990075Sobrien	hrp->next = NULL;
70090075Sobrien	thishost = firsthost = lhrp = hrp;
70190075Sobrien	if ((fp = fopen(_PATH_FTPHOSTS, "r")) != NULL) {
70290075Sobrien		int addrsize, gothost;
70390075Sobrien		void *addr;
70490075Sobrien		struct hostent *hp;
70590075Sobrien
70690075Sobrien		while ((line = fgetln(fp, &len)) != NULL) {
70790075Sobrien			int	i, hp_error;
70890075Sobrien
70990075Sobrien			/* skip comments */
71090075Sobrien			if (line[0] == '#')
71190075Sobrien				continue;
71290075Sobrien			if (line[len - 1] == '\n') {
71390075Sobrien				line[len - 1] = '\0';
71490075Sobrien				mp = NULL;
71590075Sobrien			} else {
71690075Sobrien				if ((mp = malloc(len + 1)) == NULL)
71790075Sobrien					fatalerror("Ran out of memory.");
71890075Sobrien				memcpy(mp, line, len);
71990075Sobrien				mp[len] = '\0';
72090075Sobrien				line = mp;
72118334Speter			}
72218334Speter			cp = strtok(line, " \t");
72390075Sobrien			/* skip empty lines */
72418334Speter			if (cp == NULL)
72590075Sobrien				goto nextline;
72690075Sobrien			vhost = cp;
72718334Speter
72890075Sobrien			/* set defaults */
72918334Speter			anonuser = "ftp";
73018334Speter			statfile = _PATH_FTPDSTATFILE;
73118334Speter			welcome  = _PATH_FTPWELCOME;
73218334Speter			loginmsg = _PATH_FTPLOGINMESG;
73318334Speter
73490075Sobrien			/*
73590075Sobrien			 * Preparse the line so we can use its info
73690075Sobrien			 * for all the addresses associated with
73790075Sobrien			 * the virtual host name.
73890075Sobrien			 * Field 0, the virtual host name, is special:
73990075Sobrien			 * it's already parsed off and will be strdup'ed
74090075Sobrien			 * later, after we know its canonical form.
74118334Speter			 */
74290075Sobrien			for (i = 1; i < 5 && (cp = strtok(NULL, " \t")); i++)
74390075Sobrien				if (*cp != '-' && (cp = strdup(cp)))
74490075Sobrien					switch (i) {
74590075Sobrien					case 1:	/* anon user permissions */
74690075Sobrien						anonuser = cp;
74790075Sobrien						break;
74890075Sobrien					case 2: /* statistics file */
74990075Sobrien						statfile = cp;
75090075Sobrien						break;
75152284Sobrien					case 3: /* welcome message */
75290075Sobrien						welcome  = cp;
75390075Sobrien						break;
75490075Sobrien					case 4: /* login message */
75590075Sobrien						loginmsg = cp;
75690075Sobrien						break;
75790075Sobrien					default: /* programming error */
75890075Sobrien						abort();
75990075Sobrien						/* NOTREACHED */
76090075Sobrien					}
76190075Sobrien
76290075Sobrien			hints.ai_flags = 0;
76390075Sobrien			hints.ai_family = AF_UNSPEC;
76490075Sobrien			hints.ai_flags = AI_PASSIVE;
76590075Sobrien			if (getaddrinfo(vhost, NULL, &hints, &res) != 0)
76690075Sobrien				goto nextline;
76790075Sobrien			for (ai = res; ai != NULL && ai->ai_addr != NULL;
76890075Sobrien			     ai = ai->ai_next) {
76990075Sobrien
77090075Sobrien			gothost = 0;
77190075Sobrien			for (hrp = firsthost; hrp != NULL; hrp = hrp->next) {
77290075Sobrien				struct addrinfo *hi;
77390075Sobrien
77490075Sobrien				for (hi = hrp->hostinfo; hi != NULL;
77552284Sobrien				     hi = hi->ai_next)
77690075Sobrien					if (hi->ai_addrlen == ai->ai_addrlen &&
77790075Sobrien					    memcmp(hi->ai_addr,
77890075Sobrien						   ai->ai_addr,
77990075Sobrien						   ai->ai_addr->sa_len) == 0) {
78090075Sobrien						gothost++;
78190075Sobrien						break;
78218334Speter					}
78390075Sobrien				if (gothost)
78490075Sobrien					break;
78518334Speter			}
78690075Sobrien			if (hrp == NULL) {
78790075Sobrien				if ((hrp = malloc(sizeof(struct ftphost))) == NULL)
78890075Sobrien					goto nextline;
78990075Sobrien				hrp->hostname = NULL;
79090075Sobrien				insert = 1;
79190075Sobrien			} else {
79290075Sobrien				if (hrp->hostinfo && hrp->hostinfo != res)
79390075Sobrien					freeaddrinfo(hrp->hostinfo);
79490075Sobrien				insert = 0; /* host already in the chain */
79590075Sobrien			}
79690075Sobrien			hrp->hostinfo = res;
79718334Speter
79890075Sobrien			/*
79990075Sobrien			 * determine hostname to use.
80090075Sobrien			 * force defined name if there is a valid alias
80118334Speter			 * otherwise fallback to primary hostname
80290075Sobrien			 */
80390075Sobrien			/* XXX: getaddrinfo() can't do alias check */
80490075Sobrien			switch(hrp->hostinfo->ai_family) {
80590075Sobrien			case AF_INET:
80618334Speter				addr = &((struct sockaddr_in *)hrp->hostinfo->ai_addr)->sin_addr;
80718334Speter				addrsize = sizeof(struct in_addr);
80818334Speter				break;
80918334Speter			case AF_INET6:
81018334Speter				addr = &((struct sockaddr_in6 *)hrp->hostinfo->ai_addr)->sin6_addr;
81118334Speter				addrsize = sizeof(struct in6_addr);
81218334Speter				break;
81318334Speter			default:
81418334Speter				/* should not reach here */
81518334Speter				freeaddrinfo(hrp->hostinfo);
81618334Speter				if (insert)
81718334Speter					free(hrp); /*not in chain, can free*/
81818334Speter				else
81918334Speter					hrp->hostinfo = NULL; /*mark as blank*/
82018334Speter				goto nextline;
82118334Speter				/* NOTREACHED */
82218334Speter			}
82318334Speter			if ((hp = getipnodebyaddr(addr, addrsize,
82418334Speter						  hrp->hostinfo->ai_family,
82518334Speter						  &hp_error)) != NULL) {
82618334Speter				if (strcmp(vhost, hp->h_name) != 0) {
82718334Speter					if (hp->h_aliases == NULL)
82818334Speter						vhost = hp->h_name;
82918334Speter					else {
83018334Speter						i = 0;
83118334Speter						while (hp->h_aliases[i] &&
83218334Speter						       strcmp(vhost, hp->h_aliases[i]) != 0)
83390075Sobrien							++i;
83490075Sobrien						if (hp->h_aliases[i] == NULL)
83590075Sobrien							vhost = hp->h_name;
83690075Sobrien					}
83718334Speter				}
83890075Sobrien			}
83990075Sobrien			if (hrp->hostname &&
84090075Sobrien			    strcmp(hrp->hostname, vhost) != 0) {
84190075Sobrien				free(hrp->hostname);
84290075Sobrien				hrp->hostname = NULL;
84390075Sobrien			}
84490075Sobrien			if (hrp->hostname == NULL &&
84590075Sobrien			    (hrp->hostname = strdup(vhost)) == NULL) {
84618334Speter				freeaddrinfo(hrp->hostinfo);
84718334Speter				hrp->hostinfo = NULL; /* mark as blank */
84818334Speter				if (hp)
84918334Speter					freehostent(hp);
85018334Speter				goto nextline;
85190075Sobrien			}
85290075Sobrien			hrp->anonuser = anonuser;
85390075Sobrien			hrp->statfile = statfile;
85490075Sobrien			hrp->welcome  = welcome;
85590075Sobrien			hrp->loginmsg = loginmsg;
85690075Sobrien			if (insert) {
85790075Sobrien				hrp->next  = NULL;
85890075Sobrien				lhrp->next = hrp;
85990075Sobrien				lhrp = hrp;
86090075Sobrien			}
86190075Sobrien			if (hp)
86290075Sobrien				freehostent(hp);
86390075Sobrien		      }
86490075Sobriennextline:
86590075Sobrien			if (mp)
86690075Sobrien				free(mp);
86790075Sobrien		}
86890075Sobrien		(void) fclose(fp);
86990075Sobrien	}
87090075Sobrien}
87190075Sobrien
87290075Sobrienstatic void
87390075Sobrienselecthost(union sockunion *su)
87490075Sobrien{
87590075Sobrien	struct ftphost	*hrp;
87690075Sobrien	u_int16_t port;
87790075Sobrien#ifdef INET6
87890075Sobrien	struct in6_addr *mapped_in6 = NULL;
87990075Sobrien#endif
88090075Sobrien	struct addrinfo *hi;
88190075Sobrien
88290075Sobrien#ifdef INET6
88390075Sobrien	/*
88490075Sobrien	 * XXX IPv4 mapped IPv6 addr consideraton,
88590075Sobrien	 * specified in rfc2373.
88690075Sobrien	 */
88790075Sobrien	if (su->su_family == AF_INET6 &&
88890075Sobrien	    IN6_IS_ADDR_V4MAPPED(&su->su_sin6.sin6_addr))
88990075Sobrien		mapped_in6 = &su->su_sin6.sin6_addr;
89090075Sobrien#endif
89190075Sobrien
89290075Sobrien	hrp = thishost = firsthost;	/* default */
89390075Sobrien	port = su->su_port;
89490075Sobrien	su->su_port = 0;
89590075Sobrien	while (hrp != NULL) {
89690075Sobrien	    for (hi = hrp->hostinfo; hi != NULL; hi = hi->ai_next) {
89790075Sobrien		if (memcmp(su, hi->ai_addr, hi->ai_addrlen) == 0) {
89890075Sobrien			thishost = hrp;
89918334Speter			goto found;
90018334Speter		}
90118334Speter#ifdef INET6
90218334Speter		/* XXX IPv4 mapped IPv6 addr consideraton */
90318334Speter		if (hi->ai_addr->sa_family == AF_INET && mapped_in6 != NULL &&
90418334Speter		    (memcmp(&mapped_in6->s6_addr[12],
90518334Speter			    &((struct sockaddr_in *)hi->ai_addr)->sin_addr,
90618334Speter			    sizeof(struct in_addr)) == 0)) {
90718334Speter			thishost = hrp;
90818334Speter			goto found;
90918334Speter		}
91090075Sobrien#endif
91190075Sobrien	    }
91290075Sobrien	    hrp = hrp->next;
91390075Sobrien	}
91418334Speterfound:
91518334Speter	su->su_port = port;
91618334Speter	/* setup static variables as appropriate */
91718334Speter	hostname = thishost->hostname;
91818334Speter	ftpuser = thishost->anonuser;
91918334Speter}
92018334Speter#endif
92118334Speter
92218334Speter/*
92318334Speter * Helper function for sgetpwnam().
92418334Speter */
92518334Speterstatic char *
92618334Spetersgetsave(char *s)
92718334Speter{
92818334Speter	char *new = malloc(strlen(s) + 1);
92918334Speter
93018334Speter	if (new == NULL) {
93118334Speter		reply(421, "Ran out of memory.");
93290075Sobrien		dologout(1);
93390075Sobrien		/* NOTREACHED */
93490075Sobrien	}
93590075Sobrien	(void) strcpy(new, s);
93690075Sobrien	return (new);
93790075Sobrien}
93890075Sobrien
93990075Sobrien/*
94018334Speter * Save the result of a getpwnam.  Used for USER command, since
94118334Speter * the data returned must not be clobbered by any other command
94218334Speter * (e.g., globbing).
94318334Speter * NB: The data returned by sgetpwnam() will remain valid until
94418334Speter * the next call to this function.  Its difference from getpwnam()
94518334Speter * is that sgetpwnam() is known to be called from ftpd code only.
94618334Speter */
94718334Speterstatic struct passwd *
94818334Spetersgetpwnam(char *name)
94918334Speter{
95018334Speter	static struct passwd save;
95118334Speter	struct passwd *p;
95218334Speter
95318334Speter	if ((p = getpwnam(name)) == NULL)
95418334Speter		return (p);
95518334Speter	if (save.pw_name) {
95618334Speter		free(save.pw_name);
95718334Speter		free(save.pw_passwd);
95818334Speter		free(save.pw_gecos);
95918334Speter		free(save.pw_dir);
96018334Speter		free(save.pw_shell);
96118334Speter	}
96218334Speter	save = *p;
96318334Speter	save.pw_name = sgetsave(p->pw_name);
96418334Speter	save.pw_passwd = sgetsave(p->pw_passwd);
96518334Speter	save.pw_gecos = sgetsave(p->pw_gecos);
96618334Speter	save.pw_dir = sgetsave(p->pw_dir);
96718334Speter	save.pw_shell = sgetsave(p->pw_shell);
96818334Speter	return (&save);
96918334Speter}
97018334Speter
97118334Speterstatic int login_attempts;	/* number of failed login attempts */
97218334Speterstatic int askpasswd;		/* had user command, ask for passwd */
97318334Speterstatic char curname[MAXLOGNAME];	/* current USER name */
97418334Speter
97518334Speter/*
97618334Speter * USER command.
97718334Speter * Sets global passwd pointer pw if named account exists and is acceptable;
97818334Speter * sets askpasswd if a PASS command is expected.  If logged in previously,
97918334Speter * need to reset state.  If name is "ftp" or "anonymous", the name is not in
98018334Speter * _PATH_FTPUSERS, and ftp account exists, set guest and pw, then just return.
98118334Speter * If account doesn't exist, ask for passwd anyway.  Otherwise, check user
98218334Speter * requesting login privileges.  Disallow anyone who does not have a standard
98318334Speter * shell as returned by getusershell().  Disallow anyone mentioned in the file
98418334Speter * _PATH_FTPUSERS to allow people such as root and uucp to be avoided.
98518334Speter */
98618334Spetervoid
98718334Speteruser(char *name)
98818334Speter{
98918334Speter	char *cp, *shell;
99018334Speter
99118334Speter	if (logged_in) {
99218334Speter		if (guest) {
99318334Speter			reply(530, "Can't change user from guest login.");
99418334Speter			return;
99518334Speter		} else if (dochroot) {
99618334Speter			reply(530, "Can't change user from chroot user.");
99718334Speter			return;
99818334Speter		}
99918334Speter		end_login();
100018334Speter	}
100118334Speter
100218334Speter	guest = 0;
100318334Speter#ifdef VIRTUAL_HOSTING
100418334Speter	pw = sgetpwnam(thishost->anonuser);
100518334Speter#else
100618334Speter	pw = sgetpwnam("ftp");
100718334Speter#endif
100818334Speter	if (strcmp(name, "ftp") == 0 || strcmp(name, "anonymous") == 0) {
100918334Speter		if (checkuser(_PATH_FTPUSERS, "ftp", 0, NULL) ||
101018334Speter		    checkuser(_PATH_FTPUSERS, "anonymous", 0, NULL))
101118334Speter			reply(530, "User %s access denied.", name);
101218334Speter		else if (pw != NULL) {
101318334Speter			guest = 1;
101418334Speter			askpasswd = 1;
101590075Sobrien			reply(331,
101690075Sobrien			"Guest login ok, send your email address as password.");
101790075Sobrien		} else
101890075Sobrien			reply(530, "User %s unknown.", name);
101990075Sobrien		if (!askpasswd && logging)
102090075Sobrien			syslog(LOG_NOTICE,
102118334Speter			    "ANONYMOUS FTP LOGIN REFUSED FROM %s", remotehost);
102218334Speter		return;
102318334Speter	}
102418334Speter	if (anon_only != 0) {
102518334Speter		reply(530, "Sorry, only anonymous ftp allowed.");
102690075Sobrien		return;
102790075Sobrien	}
102890075Sobrien
102990075Sobrien	if ((pw = sgetpwnam(name))) {
103090075Sobrien		if ((shell = pw->pw_shell) == NULL || *shell == 0)
103118334Speter			shell = _PATH_BSHELL;
103290075Sobrien		setusershell();
103390075Sobrien		while ((cp = getusershell()) != NULL)
103418334Speter			if (strcmp(cp, shell) == 0)
103518334Speter				break;
103618334Speter		endusershell();
103790075Sobrien
103890075Sobrien		if (cp == NULL || checkuser(_PATH_FTPUSERS, name, 1, NULL)) {
103990075Sobrien			reply(530, "User %s access denied.", name);
104090075Sobrien			if (logging)
104190075Sobrien				syslog(LOG_NOTICE,
104218334Speter				    "FTP LOGIN REFUSED FROM %s, %s",
104390075Sobrien				    remotehost, name);
104418334Speter			pw = NULL;
104518334Speter			return;
104618334Speter		}
104790075Sobrien	}
104890075Sobrien	if (logging)
104990075Sobrien		strncpy(curname, name, sizeof(curname)-1);
105090075Sobrien
105190075Sobrien	pwok = 0;
105218334Speter#ifdef USE_PAM
105390075Sobrien	/* XXX Kluge! The conversation mechanism needs to be fixed. */
105418334Speter#endif
105518334Speter	if (opiechallenge(&opiedata, name, opieprompt) == 0) {
105618334Speter		pwok = (pw != NULL) &&
105790075Sobrien		       opieaccessfile(remotehost) &&
105890075Sobrien		       opiealways(pw->pw_dir);
105990075Sobrien		reply(331, "Response to %s %s for %s.",
106090075Sobrien		      opieprompt, pwok ? "requested" : "required", name);
106190075Sobrien	} else {
106290075Sobrien		pwok = 1;
106390075Sobrien		reply(331, "Password required for %s.", name);
106490075Sobrien	}
106590075Sobrien	askpasswd = 1;
106690075Sobrien	/*
106790075Sobrien	 * Delay before reading passwd after first failed
106818334Speter	 * attempt to slow down passwd-guessing programs.
106918334Speter	 */
107018334Speter	if (login_attempts)
107118334Speter		sleep(login_attempts);
107218334Speter}
107318334Speter
107418334Speter/*
107518334Speter * Check if a user is in the file "fname",
107618334Speter * return a pointer to a malloc'd string with the rest
107790075Sobrien * of the matching line in "residue" if not NULL.
107890075Sobrien */
107990075Sobrienstatic int
108090075Sobriencheckuser(char *fname, char *name, int pwset, char **residue)
108190075Sobrien{
108290075Sobrien	FILE *fd;
108390075Sobrien	int found = 0;
108450397Sobrien	size_t len;
108550397Sobrien	char *line, *mp, *p;
108618334Speter
108718334Speter	if ((fd = fopen(fname, "r")) != NULL) {
108818334Speter		while (!found && (line = fgetln(fd, &len)) != NULL) {
108918334Speter			/* skip comments */
109018334Speter			if (line[0] == '#')
109118334Speter				continue;
109218334Speter			if (line[len - 1] == '\n') {
109318334Speter				line[len - 1] = '\0';
109418334Speter				mp = NULL;
109518334Speter			} else {
109618334Speter				if ((mp = malloc(len + 1)) == NULL)
109718334Speter					fatalerror("Ran out of memory.");
109818334Speter				memcpy(mp, line, len);
109918334Speter				mp[len] = '\0';
110018334Speter				line = mp;
110118334Speter			}
1102107590Sobrien			/* avoid possible leading and trailing whitespace */
110318334Speter			p = strtok(line, " \t");
1104107590Sobrien			/* skip empty lines */
1105107590Sobrien			if (p == NULL)
1106107590Sobrien				goto nextline;
1107107590Sobrien			/*
1108107590Sobrien			 * if first chr is '@', check group membership
1109107590Sobrien			 */
1110107590Sobrien			if (p[0] == '@') {
1111107590Sobrien				int i = 0;
1112107590Sobrien				struct group *grp;
1113107590Sobrien
1114107590Sobrien				if (p[1] == '\0') /* single @ matches anyone */
1115107590Sobrien					found = 1;
1116107590Sobrien				else {
1117107590Sobrien					if ((grp = getgrnam(p+1)) == NULL)
111818334Speter						goto nextline;
111990075Sobrien					/*
112018334Speter					 * Check user's default group
112190075Sobrien					 */
112290075Sobrien					if (pwset && grp->gr_gid == pw->pw_gid)
112390075Sobrien						found = 1;
112418334Speter					/*
112518334Speter					 * Check supplementary groups
112618334Speter					 */
112718334Speter					while (!found && grp->gr_mem[i])
112890075Sobrien						found = strcmp(name,
112990075Sobrien							grp->gr_mem[i++])
113090075Sobrien							== 0;
113190075Sobrien				}
113218334Speter			}
113390075Sobrien			/*
113490075Sobrien			 * Otherwise, just check for username match
113590075Sobrien			 */
113618334Speter			else
113790075Sobrien				found = strcmp(p, name) == 0;
113890075Sobrien			/*
113990075Sobrien			 * Save the rest of line to "residue" if matched
114090075Sobrien			 */
114190075Sobrien			if (found && residue) {
114290075Sobrien				if ((p = strtok(NULL, "")) != NULL)
114390075Sobrien					p += strspn(p, " \t");
114490075Sobrien				if (p && *p) {
114590075Sobrien				 	if ((*residue = strdup(p)) == NULL)
114618334Speter						fatalerror("Ran out of memory.");
114718334Speter				} else
114818334Speter					*residue = NULL;
114990075Sobrien			}
115090075Sobriennextline:
115118334Speter			if (mp)
115290075Sobrien				free(mp);
115390075Sobrien		}
115490075Sobrien		(void) fclose(fd);
115518334Speter	}
115618334Speter	return (found);
115718334Speter}
115818334Speter
115990075Sobrien/*
116018334Speter * Terminate login as previous user, if any, resetting state;
116118334Speter * used when USER command is given or login fails.
116218334Speter */
116318334Speterstatic void
116418334Speterend_login(void)
116518334Speter{
116618334Speter#ifdef USE_PAM
116718334Speter	int e;
116818334Speter#endif
116918334Speter
117018334Speter	(void) seteuid(0);
117118334Speter	if (logged_in && dowtmp)
117218334Speter		ftpd_logwtmp(ttyline, "", NULL);
117318334Speter	pw = NULL;
117418334Speter#ifdef	LOGIN_CAP
117518334Speter	setusercontext(NULL, getpwuid(0), 0,
117618334Speter		       LOGIN_SETPRIORITY|LOGIN_SETRESOURCES|LOGIN_SETUMASK|
117718334Speter		       LOGIN_SETMAC);
117818334Speter#endif
117918334Speter#ifdef USE_PAM
118018334Speter	if (pamh) {
118118334Speter		if ((e = pam_setcred(pamh, PAM_DELETE_CRED)) != PAM_SUCCESS)
118218334Speter			syslog(LOG_ERR, "pam_setcred: %s", pam_strerror(pamh, e));
118318334Speter		if ((e = pam_close_session(pamh,0)) != PAM_SUCCESS)
118418334Speter			syslog(LOG_ERR, "pam_close_session: %s", pam_strerror(pamh, e));
118590075Sobrien		if ((e = pam_end(pamh, e)) != PAM_SUCCESS)
118690075Sobrien			syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e));
118790075Sobrien		pamh = NULL;
118890075Sobrien	}
118990075Sobrien#endif
119090075Sobrien	logged_in = 0;
119190075Sobrien	guest = 0;
119290075Sobrien	dochroot = 0;
119390075Sobrien}
119490075Sobrien
119590075Sobrien#ifdef USE_PAM
119690075Sobrien
119790075Sobrien/*
119890075Sobrien * the following code is stolen from imap-uw PAM authentication module and
119990075Sobrien * login.c
120090075Sobrien */
120190075Sobrien#define COPY_STRING(s) (s ? strdup(s) : NULL)
120290075Sobrien
120390075Sobrienstruct cred_t {
120490075Sobrien	const char *uname;		/* user name */
120590075Sobrien	const char *pass;		/* password */
120690075Sobrien};
120790075Sobrientypedef struct cred_t cred_t;
120890075Sobrien
120990075Sobrienstatic int
121090075Sobrienauth_conv(int num_msg, const struct pam_message **msg,
121190075Sobrien	  struct pam_response **resp, void *appdata)
121290075Sobrien{
121390075Sobrien	int i;
121490075Sobrien	cred_t *cred = (cred_t *) appdata;
121590075Sobrien	struct pam_response *reply;
121690075Sobrien
121790075Sobrien	reply = calloc(num_msg, sizeof *reply);
121890075Sobrien	if (reply == NULL)
121990075Sobrien		return PAM_BUF_ERR;
122090075Sobrien
122190075Sobrien	for (i = 0; i < num_msg; i++) {
122290075Sobrien		switch (msg[i]->msg_style) {
122390075Sobrien		case PAM_PROMPT_ECHO_ON:	/* assume want user name */
122490075Sobrien			reply[i].resp_retcode = PAM_SUCCESS;
122590075Sobrien			reply[i].resp = COPY_STRING(cred->uname);
122690075Sobrien			/* PAM frees resp. */
122790075Sobrien			break;
122890075Sobrien		case PAM_PROMPT_ECHO_OFF:	/* assume want password */
122990075Sobrien			reply[i].resp_retcode = PAM_SUCCESS;
123090075Sobrien			reply[i].resp = COPY_STRING(cred->pass);
123190075Sobrien			/* PAM frees resp. */
123290075Sobrien			break;
123390075Sobrien		case PAM_TEXT_INFO:
123490075Sobrien		case PAM_ERROR_MSG:
123590075Sobrien			reply[i].resp_retcode = PAM_SUCCESS;
123690075Sobrien			reply[i].resp = NULL;
123790075Sobrien			break;
123890075Sobrien		default:			/* unknown message style */
123990075Sobrien			free(reply);
124090075Sobrien			return PAM_CONV_ERR;
124190075Sobrien		}
124290075Sobrien	}
124390075Sobrien
124490075Sobrien	*resp = reply;
124590075Sobrien	return PAM_SUCCESS;
124690075Sobrien}
124752284Sobrien
124852284Sobrien/*
124952284Sobrien * Attempt to authenticate the user using PAM.  Returns 0 if the user is
125052284Sobrien * authenticated, or 1 if not authenticated.  If some sort of PAM system
125152284Sobrien * error occurs (e.g., the "/etc/pam.conf" file is missing) then this
125252284Sobrien * function returns -1.  This can be used as an indication that we should
125390075Sobrien * fall back to a different authentication mechanism.
125452284Sobrien */
125590075Sobrienstatic int
125652284Sobrienauth_pam(struct passwd **ppw, const char *pass)
125752284Sobrien{
125852284Sobrien	const char *tmpl_user;
125952284Sobrien	const void *item;
126052284Sobrien	int rval;
126152284Sobrien	int e;
126252284Sobrien	cred_t auth_cred = { (*ppw)->pw_name, pass };
126352284Sobrien	struct pam_conv conv = { &auth_conv, &auth_cred };
126452284Sobrien
126552284Sobrien	e = pam_start("ftpd", (*ppw)->pw_name, &conv, &pamh);
126652284Sobrien	if (e != PAM_SUCCESS) {
126752284Sobrien		/*
126852284Sobrien		 * In OpenPAM, it's OK to pass NULL to pam_strerror()
126952284Sobrien		 * if context creation has failed in the first place.
127052284Sobrien		 */
127152284Sobrien		syslog(LOG_ERR, "pam_start: %s", pam_strerror(NULL, e));
127252284Sobrien		return -1;
127352284Sobrien	}
127452284Sobrien
127552284Sobrien	e = pam_set_item(pamh, PAM_RHOST, remotehost);
127652284Sobrien	if (e != PAM_SUCCESS) {
127790075Sobrien		syslog(LOG_ERR, "pam_set_item(PAM_RHOST): %s",
127890075Sobrien			pam_strerror(pamh, e));
127990075Sobrien		if ((e = pam_end(pamh, e)) != PAM_SUCCESS) {
128052284Sobrien			syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e));
128152284Sobrien		}
128252284Sobrien		pamh = NULL;
128352284Sobrien		return -1;
128452284Sobrien	}
128552284Sobrien
128690075Sobrien	e = pam_authenticate(pamh, 0);
128790075Sobrien	switch (e) {
128852284Sobrien	case PAM_SUCCESS:
128952284Sobrien		/*
129052284Sobrien		 * With PAM we support the concept of a "template"
129190075Sobrien		 * user.  The user enters a login name which is
129290075Sobrien		 * authenticated by PAM, usually via a remote service
129390075Sobrien		 * such as RADIUS or TACACS+.  If authentication
129490075Sobrien		 * succeeds, a different but related "template" name
129590075Sobrien		 * is used for setting the credentials, shell, and
129690075Sobrien		 * home directory.  The name the user enters need only
129790075Sobrien		 * exist on the remote authentication server, but the
129890075Sobrien		 * template name must be present in the local password
129990075Sobrien		 * database.
130090075Sobrien		 *
130190075Sobrien		 * This is supported by two various mechanisms in the
130290075Sobrien		 * individual modules.  However, from the application's
130390075Sobrien		 * point of view, the template user is always passed
130490075Sobrien		 * back as a changed value of the PAM_USER item.
130590075Sobrien		 */
130690075Sobrien		if ((e = pam_get_item(pamh, PAM_USER, &item)) ==
130790075Sobrien		    PAM_SUCCESS) {
130890075Sobrien			tmpl_user = (const char *) item;
130990075Sobrien			if (strcmp((*ppw)->pw_name, tmpl_user) != 0)
131090075Sobrien				*ppw = getpwnam(tmpl_user);
131190075Sobrien		} else
131290075Sobrien			syslog(LOG_ERR, "Couldn't get PAM_USER: %s",
131390075Sobrien			    pam_strerror(pamh, e));
131452284Sobrien		rval = 0;
131552284Sobrien		break;
131690075Sobrien
131790075Sobrien	case PAM_AUTH_ERR:
131890075Sobrien	case PAM_USER_UNKNOWN:
131990075Sobrien	case PAM_MAXTRIES:
132090075Sobrien		rval = 1;
132190075Sobrien		break;
132290075Sobrien
132390075Sobrien	default:
132490075Sobrien		syslog(LOG_ERR, "pam_authenticate: %s", pam_strerror(pamh, e));
132590075Sobrien		rval = -1;
132690075Sobrien		break;
132790075Sobrien	}
132890075Sobrien
132990075Sobrien	if (rval == 0) {
133090075Sobrien		e = pam_acct_mgmt(pamh, 0);
133190075Sobrien		if (e != PAM_SUCCESS) {
133290075Sobrien			syslog(LOG_ERR, "pam_acct_mgmt: %s",
133318334Speter						pam_strerror(pamh, e));
133418334Speter			rval = 1;
133518334Speter		}
133618334Speter	}
133718334Speter
133818334Speter	if (rval != 0) {
133918334Speter		if ((e = pam_end(pamh, e)) != PAM_SUCCESS) {
134090075Sobrien			syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e));
134118334Speter		}
134290075Sobrien		pamh = NULL;
134390075Sobrien	}
134490075Sobrien	return rval;
134590075Sobrien}
134690075Sobrien
134790075Sobrien#endif /* USE_PAM */
134890075Sobrien
134918334Spetervoid
135018334Speterpass(char *passwd)
135118334Speter{
135218334Speter	int rval;
135318334Speter	FILE *fd;
135418334Speter#ifdef	LOGIN_CAP
135518334Speter	login_cap_t *lc = NULL;
135618334Speter#endif
135718334Speter#ifdef USE_PAM
135818334Speter	int e;
135918334Speter#endif
136018334Speter	char *residue = NULL;
136118334Speter	char *xpasswd;
136218334Speter
136318334Speter	if (logged_in || askpasswd == 0) {
136418334Speter		reply(503, "Login with USER first.");
136518334Speter		return;
136618334Speter	}
136752284Sobrien	askpasswd = 0;
136818334Speter	if (!guest) {		/* "ftp" is only account allowed no password */
136918334Speter		if (pw == NULL) {
137018334Speter			rval = 1;	/* failure below */
137118334Speter			goto skip;
137218334Speter		}
137318334Speter#ifdef USE_PAM
137418334Speter		rval = auth_pam(&pw, passwd);
137518334Speter		if (rval >= 0) {
137618334Speter			opieunlock();
137718334Speter			goto skip;
137818334Speter		}
137918334Speter#endif
138018334Speter		if (opieverify(&opiedata, passwd) == 0)
138118334Speter			xpasswd = pw->pw_passwd;
138218334Speter		else if (pwok) {
138390075Sobrien			xpasswd = crypt(passwd, pw->pw_passwd);
138490075Sobrien			if (passwd[0] == '\0' && pw->pw_passwd[0] != '\0')
138590075Sobrien				xpasswd = ":";
138690075Sobrien		} else {
138718334Speter			rval = 1;
138818334Speter			goto skip;
138918334Speter		}
139018334Speter		rval = strcmp(pw->pw_passwd, xpasswd);
139118334Speter		if (pw->pw_expire && time(NULL) >= pw->pw_expire)
139290075Sobrien			rval = 1;	/* failure */
139390075Sobrienskip:
139450397Sobrien		/*
139518334Speter		 * If rval == 1, the user failed the authentication check
139618334Speter		 * above.  If rval == 0, either PAM or local authentication
139718334Speter		 * succeeded.
139818334Speter		 */
139918334Speter		if (rval) {
140018334Speter			reply(530, "Login incorrect.");
140118334Speter			if (logging) {
140218334Speter				syslog(LOG_NOTICE,
140318334Speter				    "FTP LOGIN FAILED FROM %s",
140418334Speter				    remotehost);
140518334Speter				syslog(LOG_AUTHPRIV | LOG_NOTICE,
140618334Speter				    "FTP LOGIN FAILED FROM %s, %s",
140750397Sobrien				    remotehost, curname);
140850397Sobrien			}
140950397Sobrien			pw = NULL;
141050397Sobrien			if (login_attempts++ >= 5) {
141118334Speter				syslog(LOG_NOTICE,
141218334Speter				    "repeated login failures from %s",
141318334Speter				    remotehost);
141418334Speter				exit(0);
141518334Speter			}
141618334Speter			return;
141718334Speter		}
141818334Speter	}
141918334Speter	login_attempts = 0;		/* this time successful */
142018334Speter	if (setegid(pw->pw_gid) < 0) {
142118334Speter		reply(550, "Can't set gid.");
142218334Speter		return;
142318334Speter	}
142418334Speter	/* May be overridden by login.conf */
142518334Speter	(void) umask(defumask);
142618334Speter#ifdef	LOGIN_CAP
142718334Speter	if ((lc = login_getpwclass(pw)) != NULL) {
142818334Speter		char	remote_ip[NI_MAXHOST];
142918334Speter
143018334Speter		if (getnameinfo((struct sockaddr *)&his_addr, his_addr.su_len,
143118334Speter			remote_ip, sizeof(remote_ip) - 1, NULL, 0,
143218334Speter			NI_NUMERICHOST))
143318334Speter				*remote_ip = 0;
143418334Speter		remote_ip[sizeof(remote_ip) - 1] = 0;
143518334Speter		if (!auth_hostok(lc, remotehost, remote_ip)) {
143618334Speter			syslog(LOG_INFO|LOG_AUTH,
143718334Speter			    "FTP LOGIN FAILED (HOST) as %s: permission denied.",
143818334Speter			    pw->pw_name);
143918334Speter			reply(530, "Permission denied.");
144018334Speter			pw = NULL;
144190075Sobrien			return;
144218334Speter		}
144390075Sobrien		if (!auth_timeok(lc, time(NULL))) {
144490075Sobrien			reply(530, "Login not available right now.");
144590075Sobrien			pw = NULL;
144618334Speter			return;
144790075Sobrien		}
144818334Speter	}
144990075Sobrien	setusercontext(lc, pw, 0,
145090075Sobrien		LOGIN_SETLOGIN|LOGIN_SETGROUP|LOGIN_SETPRIORITY|
145190075Sobrien		LOGIN_SETRESOURCES|LOGIN_SETUMASK|LOGIN_SETMAC);
145218334Speter#else
145318334Speter	setlogin(pw->pw_name);
145418334Speter	(void) initgroups(pw->pw_name, pw->pw_gid);
145518334Speter#endif
145618334Speter
145718334Speter#ifdef USE_PAM
145818334Speter	if (pamh) {
145918334Speter		if ((e = pam_open_session(pamh, 0)) != PAM_SUCCESS) {
146018334Speter			syslog(LOG_ERR, "pam_open_session: %s", pam_strerror(pamh, e));
146118334Speter		} else if ((e = pam_setcred(pamh, PAM_ESTABLISH_CRED)) != PAM_SUCCESS) {
146218334Speter			syslog(LOG_ERR, "pam_setcred: %s", pam_strerror(pamh, e));
146318334Speter		}
146418334Speter	}
146518334Speter#endif
146690075Sobrien
146790075Sobrien	/* open wtmp before chroot */
146890075Sobrien	if (dowtmp)
146990075Sobrien		ftpd_logwtmp(ttyline, pw->pw_name,
147090075Sobrien		    (struct sockaddr *)&his_addr);
147190075Sobrien	logged_in = 1;
147290075Sobrien
147318334Speter	if (guest && stats && statfd < 0)
147490075Sobrien#ifdef VIRTUAL_HOSTING
147590075Sobrien		statfd = open(thishost->statfile, O_WRONLY|O_APPEND);
147618334Speter#else
147718334Speter		statfd = open(_PATH_FTPDSTATFILE, O_WRONLY|O_APPEND);
147818334Speter#endif
147918334Speter		if (statfd < 0)
148018334Speter			stats = 0;
148118334Speter
148290075Sobrien	dochroot =
148390075Sobrien		checkuser(_PATH_FTPCHROOT, pw->pw_name, 1, &residue)
148490075Sobrien#ifdef	LOGIN_CAP	/* Allow login.conf configuration as well */
148590075Sobrien		|| login_getcapbool(lc, "ftp-chroot", 0)
148690075Sobrien#endif
148790075Sobrien	;
148818334Speter	chrootdir = NULL;
148918334Speter	/*
149018334Speter	 * For a chrooted local user,
149118334Speter	 * a) see whether ftpchroot(5) specifies a chroot directory,
149218334Speter	 * b) extract the directory pathname from the line,
149318334Speter	 * c) expand it to the absolute pathname if necessary.
149418334Speter	 */
149518334Speter	if (dochroot && residue &&
149650397Sobrien	    (chrootdir = strtok(residue, " \t")) != NULL) {
149750397Sobrien		if (chrootdir[0] != '/')
149818334Speter			asprintf(&chrootdir, "%s/%s", pw->pw_dir, chrootdir);
149918334Speter		else
150018334Speter			chrootdir = strdup(chrootdir); /* make it permanent */
150118334Speter		if (chrootdir == NULL)
150218334Speter			fatalerror("Ran out of memory.");
150390075Sobrien	}
150418334Speter	if (guest || dochroot) {
150590075Sobrien		/*
150690075Sobrien		 * If no chroot directory set yet, use the login directory.
150790075Sobrien		 * Copy it so it can be modified while pw->pw_dir stays intact.
150890075Sobrien		 */
150990075Sobrien		if (chrootdir == NULL &&
151090075Sobrien		    (chrootdir = strdup(pw->pw_dir)) == NULL)
151190075Sobrien			fatalerror("Ran out of memory.");
151218334Speter		/*
151318334Speter		 * Check for the "/chroot/./home" syntax,
151418334Speter		 * separate the chroot and home directory pathnames.
151518334Speter		 */
151618334Speter		if ((homedir = strstr(chrootdir, "/./")) != NULL) {
151718334Speter			*(homedir++) = '\0';	/* wipe '/' */
151818334Speter			homedir++;		/* skip '.' */
151918334Speter		} else {
152018334Speter			/*
152150397Sobrien			 * We MUST do a chdir() after the chroot. Otherwise
152250397Sobrien			 * the old current directory will be accessible as "."
152350397Sobrien			 * outside the new root!
152418334Speter			 */
152550397Sobrien			homedir = "/";
152690075Sobrien		}
152750397Sobrien		/*
152850397Sobrien		 * Finally, do chroot()
152990075Sobrien		 */
153050397Sobrien		if (chroot(chrootdir) < 0) {
153150397Sobrien			reply(550, "Can't change root.");
153218334Speter			goto bad;
153318334Speter		}
153418334Speter	} else	/* real user w/o chroot */
153518334Speter		homedir = pw->pw_dir;
153618334Speter	/*
153718334Speter	 * Set euid *before* doing chdir() so
153890075Sobrien	 * a) the user won't be carried to a directory that he couldn't reach
153918334Speter	 *    on his own due to no permission to upper path components,
154018334Speter	 * b) NFS mounted homedirs w/restrictive permissions will be accessible
154190075Sobrien	 *    (uid 0 has no root power over NFS if not mapped explicitly.)
154218334Speter	 */
154390075Sobrien	if (seteuid(pw->pw_uid) < 0) {
154418334Speter		reply(550, "Can't set uid.");
154518334Speter		goto bad;
154618334Speter	}
154718334Speter	if (chdir(homedir) < 0) {
154818334Speter		if (guest || dochroot) {
154918334Speter			reply(550, "Can't change to base directory.");
155018334Speter			goto bad;
155118334Speter		} else {
155218334Speter			if (chdir("/") < 0) {
155318334Speter				reply(550, "Root is inaccessible.");
155418334Speter				goto bad;
155518334Speter			}
155690075Sobrien			lreply(230, "No directory! Logging in with home=/.");
155718334Speter		}
155818334Speter	}
155918334Speter
156018334Speter	/*
156118334Speter	 * Display a login message, if it exists.
156290075Sobrien	 * N.B. reply(230,) must follow the message.
156390075Sobrien	 */
156490075Sobrien#ifdef VIRTUAL_HOSTING
156590075Sobrien	fd = fopen(thishost->loginmsg, "r");
156690075Sobrien#else
156790075Sobrien	fd = fopen(_PATH_FTPLOGINMESG, "r");
156890075Sobrien#endif
156990075Sobrien	if (fd != NULL) {
157090075Sobrien		char *cp, line[LINE_MAX];
157190075Sobrien
157290075Sobrien		while (fgets(line, sizeof(line), fd) != NULL) {
157390075Sobrien			if ((cp = strchr(line, '\n')) != NULL)
157490075Sobrien				*cp = '\0';
157590075Sobrien			lreply(230, "%s", line);
157690075Sobrien		}
157790075Sobrien		(void) fflush(stdout);
157890075Sobrien		(void) fclose(fd);
157990075Sobrien	}
158090075Sobrien	if (guest) {
158190075Sobrien		if (ident != NULL)
158290075Sobrien			free(ident);
158390075Sobrien		ident = strdup(passwd);
158490075Sobrien		if (ident == NULL)
158590075Sobrien			fatalerror("Ran out of memory.");
158690075Sobrien
158790075Sobrien		reply(230, "Guest login ok, access restrictions apply.");
158890075Sobrien#ifdef SETPROCTITLE
158990075Sobrien#ifdef VIRTUAL_HOSTING
159090075Sobrien		if (thishost != firsthost)
159190075Sobrien			snprintf(proctitle, sizeof(proctitle),
159290075Sobrien				 "%s: anonymous(%s)/%s", remotehost, hostname,
159390075Sobrien				 passwd);
159490075Sobrien		else
159590075Sobrien#endif
159690075Sobrien			snprintf(proctitle, sizeof(proctitle),
159790075Sobrien				 "%s: anonymous/%s", remotehost, passwd);
159890075Sobrien		setproctitle("%s", proctitle);
159990075Sobrien#endif /* SETPROCTITLE */
160090075Sobrien		if (logging)
160190075Sobrien			syslog(LOG_INFO, "ANONYMOUS FTP LOGIN FROM %s, %s",
160290075Sobrien			    remotehost, passwd);
160390075Sobrien	} else {
160490075Sobrien		if (dochroot)
160590075Sobrien			reply(230, "User %s logged in, "
160690075Sobrien				   "access restrictions apply.", pw->pw_name);
160790075Sobrien		else
160890075Sobrien			reply(230, "User %s logged in.", pw->pw_name);
160990075Sobrien
161090075Sobrien#ifdef SETPROCTITLE
161190075Sobrien		snprintf(proctitle, sizeof(proctitle),
161290075Sobrien			 "%s: user/%s", remotehost, pw->pw_name);
161390075Sobrien		setproctitle("%s", proctitle);
161490075Sobrien#endif /* SETPROCTITLE */
161590075Sobrien		if (logging)
161690075Sobrien			syslog(LOG_INFO, "FTP LOGIN FROM %s as %s",
161790075Sobrien			    remotehost, pw->pw_name);
161890075Sobrien	}
161990075Sobrien	if (logging && (guest || dochroot))
162090075Sobrien		syslog(LOG_INFO, "session root changed to %s", chrootdir);
162190075Sobrien#ifdef	LOGIN_CAP
162290075Sobrien	login_close(lc);
162390075Sobrien#endif
162490075Sobrien	if (residue)
162590075Sobrien		free(residue);
162690075Sobrien	return;
162790075Sobrienbad:
162890075Sobrien	/* Forget all about it... */
162990075Sobrien#ifdef	LOGIN_CAP
163090075Sobrien	login_close(lc);
163190075Sobrien#endif
163290075Sobrien	if (residue)
163390075Sobrien		free(residue);
163490075Sobrien	end_login();
163590075Sobrien}
163690075Sobrien
163790075Sobrienvoid
163890075Sobrienretrieve(char *cmd, char *name)
163990075Sobrien{
164090075Sobrien	FILE *fin, *dout;
164190075Sobrien	struct stat st;
164290075Sobrien	int (*closefunc)(FILE *);
164390075Sobrien	time_t start;
164490075Sobrien
164590075Sobrien	if (cmd == 0) {
164690075Sobrien		fin = fopen(name, "r"), closefunc = fclose;
164790075Sobrien		st.st_size = 0;
164890075Sobrien	} else {
164990075Sobrien		char line[BUFSIZ];
165090075Sobrien
165190075Sobrien		(void) snprintf(line, sizeof(line), cmd, name), name = line;
165290075Sobrien		fin = ftpd_popen(line, "r"), closefunc = ftpd_pclose;
165390075Sobrien		st.st_size = -1;
165490075Sobrien		st.st_blksize = BUFSIZ;
165590075Sobrien	}
165690075Sobrien	if (fin == NULL) {
165790075Sobrien		if (errno != 0) {
165890075Sobrien			perror_reply(550, name);
165990075Sobrien			if (cmd == 0) {
166090075Sobrien				LOGCMD("get", name);
166190075Sobrien			}
166290075Sobrien		}
166390075Sobrien		return;
166490075Sobrien	}
166590075Sobrien	byte_count = -1;
166690075Sobrien	if (cmd == 0) {
166790075Sobrien		if (fstat(fileno(fin), &st) < 0) {
166890075Sobrien			perror_reply(550, name);
166990075Sobrien			goto done;
167090075Sobrien		}
167190075Sobrien		if (!S_ISREG(st.st_mode)) {
167290075Sobrien			/*
167390075Sobrien			 * Never sending a raw directory is a workaround
167490075Sobrien			 * for buggy clients that will attempt to RETR
167590075Sobrien			 * a directory before listing it, e.g., Mozilla.
167690075Sobrien			 * Preventing a guest from getting irregular files
167790075Sobrien			 * is a simple security measure.
167890075Sobrien			 */
167990075Sobrien			if (S_ISDIR(st.st_mode) || guest) {
168090075Sobrien				reply(550, "%s: not a plain file.", name);
168190075Sobrien				goto done;
168290075Sobrien			}
168390075Sobrien			st.st_size = -1;
168490075Sobrien			/* st.st_blksize is set for all descriptor types */
168590075Sobrien		}
168690075Sobrien	}
168790075Sobrien	if (restart_point) {
168890075Sobrien		if (type == TYPE_A) {
168990075Sobrien			off_t i, n;
169090075Sobrien			int c;
169118334Speter
169218334Speter			n = restart_point;
169318334Speter			i = 0;
169418334Speter			while (i++ < n) {
169518334Speter				if ((c=getc(fin)) == EOF) {
169618334Speter					perror_reply(550, name);
169718334Speter					goto done;
169818334Speter				}
169918334Speter				if (c == '\n')
170018334Speter					i++;
170118334Speter			}
170218334Speter		} else if (lseek(fileno(fin), restart_point, L_SET) < 0) {
170318334Speter			perror_reply(550, name);
170418334Speter			goto done;
170518334Speter		}
170618334Speter	}
170718334Speter	dout = dataconn(name, st.st_size, "w");
170818334Speter	if (dout == NULL)
170918334Speter		goto done;
171018334Speter	time(&start);
171118334Speter	send_data(fin, dout, st.st_blksize, st.st_size,
171218334Speter		  restart_point == 0 && cmd == 0 && S_ISREG(st.st_mode));
171318334Speter	if (cmd == 0 && guest && stats && byte_count > 0)
171418334Speter		logxfer(name, byte_count, start);
171518334Speter	(void) fclose(dout);
171618334Speter	data = -1;
171718334Speter	pdata = -1;
171818334Speterdone:
171918334Speter	if (cmd == 0)
172018334Speter		LOGBYTES("get", name, byte_count);
172118334Speter	(*closefunc)(fin);
172218334Speter}
172318334Speter
172490075Sobrienvoid
172518334Speterstore(char *name, char *mode, int unique)
172618334Speter{
172718334Speter	int fd;
172850397Sobrien	FILE *fout, *din;
172950397Sobrien	int (*closefunc)(FILE *);
173018334Speter
173118334Speter	if (*mode == 'a') {		/* APPE */
173218334Speter		if (unique) {
173318334Speter			/* Programming error */
173418334Speter			syslog(LOG_ERR, "Internal: unique flag to APPE");
173518334Speter			unique = 0;
173618334Speter		}
173718334Speter		if (guest && noguestmod) {
173818334Speter			reply(550, "Appending to existing file denied.");
173918334Speter			goto err;
174018334Speter		}
174118334Speter		restart_point = 0;	/* not affected by preceding REST */
174218334Speter	}
174390075Sobrien	if (unique)			/* STOU overrides REST */
174418334Speter		restart_point = 0;
174518334Speter	if (guest && noguestmod) {
174690075Sobrien		if (restart_point) {	/* guest STOR w/REST */
174718334Speter			reply(550, "Modifying existing file denied.");
174818334Speter			goto err;
174990075Sobrien		} else			/* treat guest STOR as STOU */
175090075Sobrien			unique = 1;
175118334Speter	}
175290075Sobrien
175318334Speter	if (restart_point)
175490075Sobrien		mode = "r+";	/* so ASCII manual seek can work */
175590075Sobrien	if (unique) {
175690075Sobrien		if ((fd = guniquefd(name, &name)) < 0)
175718334Speter			goto err;
175890075Sobrien		fout = fdopen(fd, mode);
175990075Sobrien	} else
176018334Speter		fout = fopen(name, mode);
176190075Sobrien	closefunc = fclose;
176290075Sobrien	if (fout == NULL) {
176390075Sobrien		perror_reply(553, name);
176418334Speter		goto err;
176518334Speter	}
176618334Speter	byte_count = -1;
176718334Speter	if (restart_point) {
176818334Speter		if (type == TYPE_A) {
176918334Speter			off_t i, n;
177018334Speter			int c;
177118334Speter
177290075Sobrien			n = restart_point;
177352284Sobrien			i = 0;
177418334Speter			while (i++ < n) {
177518334Speter				if ((c=getc(fout)) == EOF) {
177618334Speter					perror_reply(550, name);
177718334Speter					goto done;
177890075Sobrien				}
177918334Speter				if (c == '\n')
178018334Speter					i++;
178118334Speter			}
178218334Speter			/*
178318334Speter			 * We must do this seek to "current" position
178452284Sobrien			 * because we are changing from reading to
178552284Sobrien			 * writing.
178618334Speter			 */
178790075Sobrien			if (fseeko(fout, 0, SEEK_CUR) < 0) {
178852284Sobrien				perror_reply(550, name);
178952284Sobrien				goto done;
179052284Sobrien			}
179152284Sobrien		} else if (lseek(fileno(fout), restart_point, L_SET) < 0) {
179290075Sobrien			perror_reply(550, name);
179352284Sobrien			goto done;
179452284Sobrien		}
179552284Sobrien	}
179652284Sobrien	din = dataconn(name, -1, "r");
179752284Sobrien	if (din == NULL)
179852284Sobrien		goto done;
179952284Sobrien	if (receive_data(din, fout) == 0) {
180052284Sobrien		if (unique)
180152284Sobrien			reply(226, "Transfer complete (unique file name:%s).",
180252284Sobrien			    name);
180352284Sobrien		else
180452284Sobrien			reply(226, "Transfer complete.");
180552284Sobrien	}
180652284Sobrien	(void) fclose(din);
180752284Sobrien	data = -1;
180890075Sobrien	pdata = -1;
180952284Sobriendone:
181052284Sobrien	LOGBYTES(*mode == 'a' ? "append" : "put", name, byte_count);
181190075Sobrien	(*closefunc)(fout);
181252284Sobrien	return;
181352284Sobrienerr:
181452284Sobrien	LOGCMD(*mode == 'a' ? "append" : "put" , name);
181518334Speter	return;
181618334Speter}
181718334Speter
181818334Speterstatic FILE *
181918334Spetergetdatasock(char *mode)
182018334Speter{
182118334Speter	int on = 1, s, t, tries;
182218334Speter
182318334Speter	if (data >= 0)
182418334Speter		return (fdopen(data, mode));
182518334Speter
182690075Sobrien	s = socket(data_dest.su_family, SOCK_STREAM, 0);
182790075Sobrien	if (s < 0)
182818334Speter		goto bad;
182918334Speter	if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
183018334Speter		syslog(LOG_WARNING, "data setsockopt (SO_REUSEADDR): %m");
183118334Speter	/* anchor socket to avoid multi-homing problems */
183218334Speter	data_source = ctrl_addr;
183390075Sobrien	data_source.su_port = htons(dataport);
183490075Sobrien	(void) seteuid(0);
183590075Sobrien	for (tries = 1; ; tries++) {
183690075Sobrien		/*
183790075Sobrien		 * We should loop here since it's possible that
183890075Sobrien		 * another ftpd instance has passed this point and is
183990075Sobrien		 * trying to open a data connection in active mode now.
184090075Sobrien		 * Until the other connection is opened, we'll be getting
184190075Sobrien		 * EADDRINUSE because no SOCK_STREAM sockets in the system
184218334Speter		 * can share both local and remote addresses, localIP:20
184318334Speter		 * and *:* in this case.
184418334Speter		 */
184518334Speter		if (bind(s, (struct sockaddr *)&data_source,
184618334Speter		    data_source.su_len) >= 0)
184718334Speter			break;
184818334Speter		if (errno != EADDRINUSE || tries > 10)
184918334Speter			goto bad;
185018334Speter		sleep(tries);
185118334Speter	}
185218334Speter	(void) seteuid(pw->pw_uid);
185318334Speter#ifdef IP_TOS
185490075Sobrien	if (data_source.su_family == AF_INET)
185518334Speter      {
185618334Speter	on = IPTOS_THROUGHPUT;
185718334Speter	if (setsockopt(s, IPPROTO_IP, IP_TOS, &on, sizeof(int)) < 0)
185818334Speter		syslog(LOG_WARNING, "data setsockopt (IP_TOS): %m");
185918334Speter      }
186090075Sobrien#endif
186118334Speter#ifdef TCP_NOPUSH
186218334Speter	/*
186318334Speter	 * Turn off push flag to keep sender TCP from sending short packets
186418334Speter	 * at the boundaries of each write().  Should probably do a SO_SNDBUF
186518334Speter	 * to set the send buffer size as well, but that may not be desirable
186618334Speter	 * in heavy-load situations.
186718334Speter	 */
186818334Speter	on = 1;
186918334Speter	if (setsockopt(s, IPPROTO_TCP, TCP_NOPUSH, &on, sizeof on) < 0)
187018334Speter		syslog(LOG_WARNING, "data setsockopt (TCP_NOPUSH): %m");
187118334Speter#endif
187218334Speter#ifdef SO_SNDBUF
187318334Speter	on = 65536;
187418334Speter	if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, &on, sizeof on) < 0)
187518334Speter		syslog(LOG_WARNING, "data setsockopt (SO_SNDBUF): %m");
187618334Speter#endif
187790075Sobrien
187818334Speter	return (fdopen(s, mode));
187918334Speterbad:
188018334Speter	/* Return the real value of errno (close may change it) */
188118334Speter	t = errno;
188218334Speter	(void) seteuid(pw->pw_uid);
188318334Speter	(void) close(s);
188418334Speter	errno = t;
188518334Speter	return (NULL);
188618334Speter}
188718334Speter
188818334Speterstatic FILE *
188918334Speterdataconn(char *name, off_t size, char *mode)
189018334Speter{
189118334Speter	char sizebuf[32];
189218334Speter	FILE *file;
189318334Speter	int retry = 0, tos, conerrno;
189418334Speter
189518334Speter	file_size = size;
189618334Speter	byte_count = 0;
189718334Speter	if (size != -1)
189818334Speter		(void) snprintf(sizebuf, sizeof(sizebuf),
189918334Speter				" (%jd bytes)", (intmax_t)size);
190018334Speter	else
190118334Speter		*sizebuf = '\0';
190218334Speter	if (pdata >= 0) {
190390075Sobrien		union sockunion from;
190418334Speter		socklen_t fromlen = ctrl_addr.su_len;
190590075Sobrien		int flags, s;
190618334Speter		struct timeval timeout;
190718334Speter		fd_set set;
190818334Speter
190990075Sobrien		FD_ZERO(&set);
191090075Sobrien		FD_SET(pdata, &set);
191118334Speter
191218334Speter		timeout.tv_usec = 0;
191318334Speter		timeout.tv_sec = 120;
191418334Speter
191518334Speter		/*
191618334Speter		 * Granted a socket is in the blocking I/O mode,
191718334Speter		 * accept() will block after a successful select()
191818334Speter		 * if the selected connection dies in between.
191918334Speter		 * Therefore set the non-blocking I/O flag here.
192018334Speter		 */
192118334Speter		if ((flags = fcntl(pdata, F_GETFL, 0)) == -1 ||
192218334Speter		    fcntl(pdata, F_SETFL, flags | O_NONBLOCK) == -1)
192318334Speter			goto pdata_err;
192418334Speter		if (select(pdata+1, &set, NULL, NULL, &timeout) <= 0 ||
192518334Speter		    (s = accept(pdata, (struct sockaddr *) &from, &fromlen)) < 0)
192618334Speter			goto pdata_err;
192718334Speter		(void) close(pdata);
192818334Speter		pdata = s;
192918334Speter		/*
193018334Speter		 * Unset the inherited non-blocking I/O flag
193118334Speter		 * on the child socket so stdio can work on it.
193218334Speter		 */
193318334Speter		if ((flags = fcntl(pdata, F_GETFL, 0)) == -1 ||
193418334Speter		    fcntl(pdata, F_SETFL, flags & ~O_NONBLOCK) == -1)
193518334Speter			goto pdata_err;
193618334Speter#ifdef IP_TOS
193790075Sobrien		if (from.su_family == AF_INET)
193890075Sobrien	      {
193918334Speter		tos = IPTOS_THROUGHPUT;
194090075Sobrien		if (setsockopt(s, IPPROTO_IP, IP_TOS, &tos, sizeof(int)) < 0)
194190075Sobrien			syslog(LOG_WARNING, "pdata setsockopt (IP_TOS): %m");
194290075Sobrien	      }
194390075Sobrien#endif
194490075Sobrien		reply(150, "Opening %s mode data connection for '%s'%s.",
194590075Sobrien		     type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf);
194690075Sobrien		return (fdopen(pdata, mode));
194796263Sobrienpdata_err:
194896263Sobrien		reply(425, "Can't open data connection.");
194918334Speter		(void) close(pdata);
195090075Sobrien		pdata = -1;
195190075Sobrien		return (NULL);
195296263Sobrien	}
195318334Speter	if (data >= 0) {
195490075Sobrien		reply(125, "Using existing data connection for '%s'%s.",
195590075Sobrien		    name, sizebuf);
195618334Speter		usedefault = 1;
195790075Sobrien		return (fdopen(data, mode));
195890075Sobrien	}
195990075Sobrien	if (usedefault)
196090075Sobrien		data_dest = his_addr;
196150397Sobrien	usedefault = 1;
196296263Sobrien	do {
196390075Sobrien		file = getdatasock(mode);
196496263Sobrien		if (file == NULL) {
196596263Sobrien			char hostbuf[NI_MAXHOST], portbuf[NI_MAXSERV];
196696263Sobrien
196750397Sobrien			if (getnameinfo((struct sockaddr *)&data_source,
196890075Sobrien				data_source.su_len,
196990075Sobrien				hostbuf, sizeof(hostbuf) - 1,
197090075Sobrien				portbuf, sizeof(portbuf) - 1,
197190075Sobrien				NI_NUMERICHOST|NI_NUMERICSERV))
197290075Sobrien					*hostbuf = *portbuf = 0;
197350397Sobrien			hostbuf[sizeof(hostbuf) - 1] = 0;
197490075Sobrien			portbuf[sizeof(portbuf) - 1] = 0;
197596263Sobrien			reply(425, "Can't create data socket (%s,%s): %s.",
197696263Sobrien				hostbuf, portbuf, strerror(errno));
197796263Sobrien			return (NULL);
197896263Sobrien		}
197996263Sobrien		data = fileno(file);
198096263Sobrien		conerrno = 0;
198196263Sobrien		if (connect(data, (struct sockaddr *)&data_dest,
198296263Sobrien		    data_dest.su_len) == 0)
198350397Sobrien			break;
198490075Sobrien		conerrno = errno;
198590075Sobrien		(void) fclose(file);
198690075Sobrien		data = -1;
198790075Sobrien		if (conerrno == EADDRINUSE) {
198818334Speter			sleep(swaitint);
198990075Sobrien			retry += swaitint;
199090075Sobrien		} else {
199190075Sobrien			break;
199218334Speter		}
199390075Sobrien	} while (retry <= swaitmax);
199490075Sobrien	if (conerrno != 0) {
199590075Sobrien		reply(425, "Can't build data connection: %s.",
199690075Sobrien			   strerror(conerrno));
199718334Speter		return (NULL);
199818334Speter	}
199990075Sobrien	reply(150, "Opening %s mode data connection for '%s'%s.",
200090075Sobrien	     type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf);
200190075Sobrien	return (file);
200290075Sobrien}
200318334Speter
200490075Sobrien/*
200590075Sobrien * A helper macro to avoid code duplication
200690075Sobrien * in send_data() and receive_data().
200790075Sobrien *
200890075Sobrien * XXX We have to block SIGURG during putc() because BSD stdio
200990075Sobrien * is unable to restart interrupted write operations and hence
201090075Sobrien * the entire buffer contents will be lost as soon as a write()
201190075Sobrien * call indicates EINTR to stdio.
201290075Sobrien */
201318334Speter#define FTPD_PUTC(ch, file, label)					\
201490075Sobrien	do {								\
201590075Sobrien		int ret;						\
201690075Sobrien									\
201790075Sobrien		do {							\
201890075Sobrien			START_UNSAFE;					\
201918334Speter			ret = putc((ch), (file));			\
202090075Sobrien			END_UNSAFE;					\
202190075Sobrien			CHECKOOB(return (-1))				\
202290075Sobrien			else if (ferror(file))				\
202390075Sobrien				goto label;				\
202490075Sobrien			clearerr(file);					\
202590075Sobrien		} while (ret == EOF);					\
202618334Speter	} while (0)
202790075Sobrien
202890075Sobrien/*
202990075Sobrien * Tranfer the contents of "instr" to "outstr" peer using the appropriate
203090075Sobrien * encapsulation of the data subject to Mode, Structure, and Type.
203190075Sobrien *
203290075Sobrien * NB: Form isn't handled.
203318334Speter */
203418334Speterstatic int
203518334Spetersend_data(FILE *instr, FILE *outstr, size_t blksize, off_t filesize, int isreg)
203618334Speter{
203718334Speter	int c, cp, filefd, netfd;
203818334Speter	char *buf;
203990075Sobrien
204090075Sobrien	STARTXFER;
204118334Speter
204290075Sobrien	switch (type) {
204318334Speter
204490075Sobrien	case TYPE_A:
204518334Speter		cp = EOF;
204618334Speter		for (;;) {
204790075Sobrien			c = getc(instr);
204818334Speter			CHECKOOB(return (-1))
204990075Sobrien			else if (c == EOF && ferror(instr))
205090075Sobrien				goto file_err;
205190075Sobrien			if (c == EOF) {
205290075Sobrien				if (ferror(instr)) {	/* resume after OOB */
205390075Sobrien					clearerr(instr);
205490075Sobrien					continue;
205590075Sobrien				}
205690075Sobrien				if (feof(instr))	/* EOF */
205790075Sobrien					break;
205890075Sobrien				syslog(LOG_ERR, "Internal: impossible condition"
205990075Sobrien						" on file after getc()");
206090075Sobrien				goto file_err;
206190075Sobrien			}
206290075Sobrien			if (c == '\n' && cp != '\r') {
206390075Sobrien				FTPD_PUTC('\r', outstr, data_err);
206490075Sobrien				byte_count++;
206590075Sobrien			}
206690075Sobrien			FTPD_PUTC(c, outstr, data_err);
206790075Sobrien			byte_count++;
206818334Speter			cp = c;
206918334Speter		}
207090075Sobrien#ifdef notyet	/* BSD stdio isn't ready for that */
207190075Sobrien		while (fflush(outstr) == EOF) {
207290075Sobrien			CHECKOOB(return (-1))
207390075Sobrien			else
207490075Sobrien				goto data_err;
207590075Sobrien			clearerr(outstr);
207690075Sobrien		}
207790075Sobrien		ENDXFER;
207890075Sobrien#else
207990075Sobrien		ENDXFER;
208090075Sobrien		if (fflush(outstr) == EOF)
208190075Sobrien			goto data_err;
208290075Sobrien#endif
208390075Sobrien		reply(226, "Transfer complete.");
208490075Sobrien		return (0);
208590075Sobrien
208690075Sobrien	case TYPE_I:
208790075Sobrien	case TYPE_L:
208890075Sobrien		/*
208990075Sobrien		 * isreg is only set if we are not doing restart and we
209090075Sobrien		 * are sending a regular file
209190075Sobrien		 */
209290075Sobrien		netfd = fileno(outstr);
209390075Sobrien		filefd = fileno(instr);
209418334Speter
209518334Speter		if (isreg) {
209618334Speter			char *msg = "Transfer complete.";
209790075Sobrien			off_t cnt, offset;
209890075Sobrien			int err;
209918334Speter
210018334Speter			cnt = offset = 0;
210190075Sobrien
210218334Speter			while (filesize > 0) {
210390075Sobrien				err = sendfile(filefd, netfd, offset, 0,
210418334Speter					       NULL, &cnt, 0);
210590075Sobrien				/*
210618334Speter				 * Calculate byte_count before OOB processing.
210718334Speter				 * It can be used in myoob() later.
210818334Speter				 */
210918334Speter				byte_count += cnt;
211090075Sobrien				offset += cnt;
211118334Speter				filesize -= cnt;
211218334Speter				CHECKOOB(return (-1))
211318334Speter				else if (err == -1) {
211418334Speter					if (errno != EINTR &&
211518334Speter					    cnt == 0 && offset == 0)
211618334Speter						goto oldway;
211790075Sobrien					goto data_err;
211890075Sobrien				}
211990075Sobrien				if (err == -1)	/* resume after OOB */
212090075Sobrien					continue;
212190075Sobrien				/*
212290075Sobrien				 * We hit the EOF prematurely.
212390075Sobrien				 * Perhaps the file was externally truncated.
212418334Speter				 */
212596263Sobrien				if (cnt == 0) {
212696263Sobrien					msg = "Transfer finished due to "
212796263Sobrien					      "premature end of file.";
212890075Sobrien					break;
212990075Sobrien				}
213018334Speter			}
213118334Speter			ENDXFER;
213218334Speter			reply(226, msg);
213390075Sobrien			return (0);
213490075Sobrien		}
213518334Speter
213618334Speteroldway:
213790075Sobrien		if ((buf = malloc(blksize)) == NULL) {
213890075Sobrien			ENDXFER;
213918334Speter			reply(451, "Ran out of memory.");
214090075Sobrien			return (-1);
214190075Sobrien		}
214218334Speter
214390075Sobrien		for (;;) {
214490075Sobrien			int cnt, len;
214590075Sobrien			char *bp;
214618334Speter
214790075Sobrien			cnt = read(filefd, buf, blksize);
214890075Sobrien			CHECKOOB(free(buf); return (-1))
214990075Sobrien			else if (cnt < 0) {
215018334Speter				free(buf);
215190075Sobrien				goto file_err;
215290075Sobrien			}
215390075Sobrien			if (cnt < 0)	/* resume after OOB */
215418334Speter				continue;
215590075Sobrien			if (cnt == 0)	/* EOF */
215690075Sobrien				break;
215790075Sobrien			for (len = cnt, bp = buf; len > 0;) {
215890075Sobrien				cnt = write(netfd, bp, len);
215990075Sobrien				CHECKOOB(free(buf); return (-1))
216090075Sobrien				else if (cnt < 0) {
216190075Sobrien					free(buf);
216290075Sobrien					goto data_err;
216390075Sobrien				}
216490075Sobrien				if (cnt <= 0)
216590075Sobrien					continue;
216690075Sobrien				len -= cnt;
216790075Sobrien				bp += cnt;
216890075Sobrien				byte_count += cnt;
216990075Sobrien			}
217090075Sobrien		}
217190075Sobrien		ENDXFER;
217290075Sobrien		free(buf);
217390075Sobrien		reply(226, "Transfer complete.");
217490075Sobrien		return (0);
217518334Speter	default:
217690075Sobrien		ENDXFER;
217790075Sobrien		reply(550, "Unimplemented TYPE %d in send_data.", type);
217818334Speter		return (-1);
217918334Speter	}
218090075Sobrien
218118334Speterdata_err:
218290075Sobrien	ENDXFER;
218390075Sobrien	perror_reply(426, "Data connection");
218418334Speter	return (-1);
218590075Sobrien
218690075Sobrienfile_err:
218718334Speter	ENDXFER;
218818334Speter	perror_reply(551, "Error on input file");
218990075Sobrien	return (-1);
219090075Sobrien}
219190075Sobrien
219218334Speter/*
219390075Sobrien * Transfer data from peer to "outstr" using the appropriate encapulation of
219418334Speter * the data subject to Mode, Structure, and Type.
219518334Speter *
219690075Sobrien * N.B.: Form isn't handled.
219790075Sobrien */
219890075Sobrienstatic int
219990075Sobrienreceive_data(FILE *instr, FILE *outstr)
220018334Speter{
220190075Sobrien	int c, cp;
220290075Sobrien	int bare_lfs = 0;
220318334Speter
220418334Speter	STARTXFER;
220590075Sobrien
220618334Speter	switch (type) {
220790075Sobrien
220890075Sobrien	case TYPE_I:
220990075Sobrien	case TYPE_L:
221090075Sobrien		for (;;) {
221118334Speter			int cnt, len;
221290075Sobrien			char *bp;
221318334Speter			char buf[BUFSIZ];
221418334Speter
221590075Sobrien			cnt = read(fileno(instr), buf, sizeof(buf));
221690075Sobrien			CHECKOOB(return (-1))
221718334Speter			else if (cnt < 0)
221890075Sobrien				goto data_err;
221990075Sobrien			if (cnt < 0)	/* resume after OOB */
222090075Sobrien				continue;
222190075Sobrien			if (cnt == 0)	/* EOF */
222218334Speter				break;
222390075Sobrien			for (len = cnt, bp = buf; len > 0;) {
222490075Sobrien				cnt = write(fileno(outstr), bp, len);
222590075Sobrien				CHECKOOB(return (-1))
222690075Sobrien				else if (cnt < 0)
222718334Speter					goto file_err;
222890075Sobrien				if (cnt <= 0)
222990075Sobrien					continue;
223090075Sobrien				len -= cnt;
223190075Sobrien				bp += cnt;
223218334Speter				byte_count += cnt;
223390075Sobrien			}
223490075Sobrien		}
223590075Sobrien		ENDXFER;
223618334Speter		return (0);
223718334Speter
223890075Sobrien	case TYPE_E:
223990075Sobrien		ENDXFER;
224090075Sobrien		reply(553, "TYPE E not implemented.");
224118334Speter		return (-1);
224290075Sobrien
224318334Speter	case TYPE_A:
224490075Sobrien		cp = EOF;
224518334Speter		for (;;) {
224618334Speter			c = getc(instr);
224718334Speter			CHECKOOB(return (-1))
224818334Speter			else if (c == EOF && ferror(instr))
224952284Sobrien				goto data_err;
225018334Speter			if (c == EOF && ferror(instr)) { /* resume after OOB */
225152284Sobrien				clearerr(instr);
225252284Sobrien				continue;
225352284Sobrien			}
225452284Sobrien
225552284Sobrien			if (cp == '\r') {
225652284Sobrien				if (c != '\n')
225752284Sobrien					FTPD_PUTC('\r', outstr, file_err);
225852284Sobrien			} else
225918334Speter				if (c == '\n')
226018334Speter					bare_lfs++;
226118334Speter			if (c == '\r') {
226218334Speter				byte_count++;
226390075Sobrien				cp = c;
226490075Sobrien				continue;
226590075Sobrien			}
226690075Sobrien
226718334Speter			/* Check for EOF here in order not to lose last \r. */
226818334Speter			if (c == EOF) {
226918334Speter				if (feof(instr))	/* EOF */
227018334Speter					break;
227118334Speter				syslog(LOG_ERR, "Internal: impossible condition"
227218334Speter						" on data stream after getc()");
227318334Speter				goto data_err;
227418334Speter			}
227590075Sobrien
227618334Speter			byte_count++;
227718334Speter			FTPD_PUTC(c, outstr, file_err);
227818334Speter			cp = c;
227918334Speter		}
228018334Speter#ifdef notyet	/* BSD stdio isn't ready for that */
228118334Speter		while (fflush(outstr) == EOF) {
228218334Speter			CHECKOOB(return (-1))
228318334Speter			else
228418334Speter				goto file_err;
228518334Speter			clearerr(outstr);
228618334Speter		}
228718334Speter		ENDXFER;
228890075Sobrien#else
228918334Speter		ENDXFER;
229018334Speter		if (fflush(outstr) == EOF)
229118334Speter			goto file_err;
229290075Sobrien#endif
229390075Sobrien		if (bare_lfs) {
229490075Sobrien			lreply(226,
229590075Sobrien		"WARNING! %d bare linefeeds received in ASCII mode.",
229690075Sobrien			    bare_lfs);
229718334Speter		(void)printf("   File may not have transferred correctly.\r\n");
229818334Speter		}
229918334Speter		return (0);
230018334Speter	default:
230118334Speter		ENDXFER;
230218334Speter		reply(550, "Unimplemented TYPE %d in receive_data.", type);
230318334Speter		return (-1);
230418334Speter	}
230518334Speter
230618334Speterdata_err:
230718334Speter	ENDXFER;
230818334Speter	perror_reply(426, "Data connection");
230990075Sobrien	return (-1);
231018334Speter
231118334Speterfile_err:
231218334Speter	ENDXFER;
231390075Sobrien	perror_reply(452, "Error writing to file");
231490075Sobrien	return (-1);
231590075Sobrien}
231690075Sobrien
231790075Sobrienvoid
231818334Speterstatfilecmd(char *filename)
231918334Speter{
232018334Speter	FILE *fin;
232118334Speter	int atstart;
232218334Speter	int c, code;
232318334Speter	char line[LINE_MAX];
232418334Speter	struct stat st;
232518334Speter
232618334Speter	code = lstat(filename, &st) == 0 && S_ISDIR(st.st_mode) ? 212 : 213;
232790075Sobrien	(void)snprintf(line, sizeof(line), _PATH_LS " -lgA %s", filename);
232818334Speter	fin = ftpd_popen(line, "r");
232918334Speter	lreply(code, "Status of %s:", filename);
233090075Sobrien	atstart = 1;
233118334Speter	while ((c = getc(fin)) != EOF) {
233218334Speter		if (c == '\n') {
233318334Speter			if (ferror(stdout)){
233418334Speter				perror_reply(421, "Control connection");
233518334Speter				(void) ftpd_pclose(fin);
233618334Speter				dologout(1);
233718334Speter				/* NOTREACHED */
233818334Speter			}
233918334Speter			if (ferror(fin)) {
234018334Speter				perror_reply(551, filename);
234118334Speter				(void) ftpd_pclose(fin);
234218334Speter				return;
234318334Speter			}
234418334Speter			(void) putc('\r', stdout);
234518334Speter		}
234618334Speter		/*
234718334Speter		 * RFC 959 says neutral text should be prepended before
234818334Speter		 * a leading 3-digit number followed by whitespace, but
234918334Speter		 * many ftp clients can be confused by any leading digits,
235018334Speter		 * as a matter of fact.
235118334Speter		 */
235218334Speter		if (atstart && isdigit(c))
235318334Speter			(void) putc(' ', stdout);
235418334Speter		(void) putc(c, stdout);
235518334Speter		atstart = (c == '\n');
235618334Speter	}
235718334Speter	(void) ftpd_pclose(fin);
235850397Sobrien	reply(code, "End of status.");
235950397Sobrien}
236050397Sobrien
236150397Sobrienvoid
236250397Sobrienstatcmd(void)
236350397Sobrien{
236450397Sobrien	union sockunion *su;
236518334Speter	u_char *a, *p;
236618334Speter	char hname[NI_MAXHOST];
236718334Speter	int ispassive;
236818334Speter
236918334Speter	if (hostinfo) {
237018334Speter		lreply(211, "%s FTP server status:", hostname);
237118334Speter		printf("     %s\r\n", version);
237218334Speter	} else
237352284Sobrien		lreply(211, "FTP server status:");
237452284Sobrien	printf("     Connected to %s", remotehost);
237552284Sobrien	if (!getnameinfo((struct sockaddr *)&his_addr, his_addr.su_len,
237652284Sobrien			 hname, sizeof(hname) - 1, NULL, 0, NI_NUMERICHOST)) {
237752284Sobrien		hname[sizeof(hname) - 1] = 0;
237818334Speter		if (strcmp(hname, remotehost) != 0)
237918334Speter			printf(" (%s)", hname);
238018334Speter	}
238118334Speter	printf("\r\n");
238218334Speter	if (logged_in) {
238318334Speter		if (guest)
238418334Speter			printf("     Logged in anonymously\r\n");
238518334Speter		else
238618334Speter			printf("     Logged in as %s\r\n", pw->pw_name);
238718334Speter	} else if (askpasswd)
238818334Speter		printf("     Waiting for password\r\n");
238918334Speter	else
239018334Speter		printf("     Waiting for user name\r\n");
239118334Speter	printf("     TYPE: %s", typenames[type]);
239218334Speter	if (type == TYPE_A || type == TYPE_E)
239318334Speter		printf(", FORM: %s", formnames[form]);
239490075Sobrien	if (type == TYPE_L)
239518334Speter#if CHAR_BIT == 8
239618334Speter		printf(" %d", CHAR_BIT);
239718334Speter#else
239818334Speter		printf(" %d", bytesize);	/* need definition! */
239918334Speter#endif
240018334Speter	printf("; STRUcture: %s; transfer MODE: %s\r\n",
240118334Speter	    strunames[stru], modenames[mode]);
240218334Speter	if (data != -1)
240318334Speter		printf("     Data connection open\r\n");
240418334Speter	else if (pdata != -1) {
240518334Speter		ispassive = 1;
240618334Speter		su = &pasv_addr;
240790075Sobrien		goto printaddr;
240890075Sobrien	} else if (usedefault == 0) {
240990075Sobrien		ispassive = 0;
241090075Sobrien		su = &data_dest;
241190075Sobrienprintaddr:
241218334Speter#define UC(b) (((int) b) & 0xff)
241318334Speter		if (epsvall) {
241418334Speter			printf("     EPSV only mode (EPSV ALL)\r\n");
241518334Speter			goto epsvonly;
241618334Speter		}
241718334Speter
241818334Speter		/* PORT/PASV */
241918334Speter		if (su->su_family == AF_INET) {
242018334Speter			a = (u_char *) &su->su_sin.sin_addr;
242118334Speter			p = (u_char *) &su->su_sin.sin_port;
242218334Speter			printf("     %s (%d,%d,%d,%d,%d,%d)\r\n",
242318334Speter				ispassive ? "PASV" : "PORT",
242418334Speter				UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]),
242518334Speter				UC(p[0]), UC(p[1]));
242618334Speter		}
242718334Speter
242818334Speter		/* LPRT/LPSV */
242918334Speter	    {
243018334Speter		int alen, af, i;
243118334Speter
243218334Speter		switch (su->su_family) {
243318334Speter		case AF_INET:
243418334Speter			a = (u_char *) &su->su_sin.sin_addr;
243518334Speter			p = (u_char *) &su->su_sin.sin_port;
243618334Speter			alen = sizeof(su->su_sin.sin_addr);
243718334Speter			af = 4;
243818334Speter			break;
243918334Speter		case AF_INET6:
244018334Speter			a = (u_char *) &su->su_sin6.sin6_addr;
244118334Speter			p = (u_char *) &su->su_sin6.sin6_port;
244218334Speter			alen = sizeof(su->su_sin6.sin6_addr);
244318334Speter			af = 6;
244418334Speter			break;
244518334Speter		default:
244618334Speter			af = 0;
244718334Speter			break;
244818334Speter		}
244918334Speter		if (af) {
245018334Speter			printf("     %s (%d,%d,", ispassive ? "LPSV" : "LPRT",
245118334Speter				af, alen);
245218334Speter			for (i = 0; i < alen; i++)
245318334Speter				printf("%d,", UC(a[i]));
245418334Speter			printf("%d,%d,%d)\r\n", 2, UC(p[0]), UC(p[1]));
245518334Speter		}
245618334Speter	    }
245718334Speter
245818334Speterepsvonly:;
245918334Speter		/* EPRT/EPSV */
246018334Speter	    {
246118334Speter		int af;
246218334Speter
246390075Sobrien		switch (su->su_family) {
246490075Sobrien		case AF_INET:
246590075Sobrien			af = 1;
246618334Speter			break;
246718334Speter		case AF_INET6:
246818334Speter			af = 2;
2469104752Skan			break;
2470104752Skan		default:
2471104752Skan			af = 0;
2472104752Skan			break;
2473104752Skan		}
2474104752Skan		if (af) {
2475104752Skan			union sockunion tmp;
2476104752Skan
2477104752Skan			tmp = *su;
2478104752Skan			if (tmp.su_family == AF_INET6)
2479104752Skan				tmp.su_sin6.sin6_scope_id = 0;
2480104752Skan			if (!getnameinfo((struct sockaddr *)&tmp, tmp.su_len,
2481					hname, sizeof(hname) - 1, NULL, 0,
2482					NI_NUMERICHOST)) {
2483				hname[sizeof(hname) - 1] = 0;
2484				printf("     %s |%d|%s|%d|\r\n",
2485					ispassive ? "EPSV" : "EPRT",
2486					af, hname, htons(tmp.su_port));
2487			}
2488		}
2489	    }
2490#undef UC
2491	} else
2492		printf("     No data connection\r\n");
2493	reply(211, "End of status.");
2494}
2495
2496void
2497fatalerror(char *s)
2498{
2499
2500	reply(451, "Error in server: %s", s);
2501	reply(221, "Closing connection due to server error.");
2502	dologout(0);
2503	/* NOTREACHED */
2504}
2505
2506void
2507reply(int n, const char *fmt, ...)
2508{
2509	va_list ap;
2510
2511	(void)printf("%d ", n);
2512	va_start(ap, fmt);
2513	(void)vprintf(fmt, ap);
2514	va_end(ap);
2515	(void)printf("\r\n");
2516	(void)fflush(stdout);
2517	if (ftpdebug) {
2518		syslog(LOG_DEBUG, "<--- %d ", n);
2519		va_start(ap, fmt);
2520		vsyslog(LOG_DEBUG, fmt, ap);
2521		va_end(ap);
2522	}
2523}
2524
2525void
2526lreply(int n, const char *fmt, ...)
2527{
2528	va_list ap;
2529
2530	(void)printf("%d- ", n);
2531	va_start(ap, fmt);
2532	(void)vprintf(fmt, ap);
2533	va_end(ap);
2534	(void)printf("\r\n");
2535	(void)fflush(stdout);
2536	if (ftpdebug) {
2537		syslog(LOG_DEBUG, "<--- %d- ", n);
2538		va_start(ap, fmt);
2539		vsyslog(LOG_DEBUG, fmt, ap);
2540		va_end(ap);
2541	}
2542}
2543
2544static void
2545ack(char *s)
2546{
2547
2548	reply(250, "%s command successful.", s);
2549}
2550
2551void
2552nack(char *s)
2553{
2554
2555	reply(502, "%s command not implemented.", s);
2556}
2557
2558/* ARGSUSED */
2559void
2560yyerror(char *s)
2561{
2562	char *cp;
2563
2564	if ((cp = strchr(cbuf,'\n')))
2565		*cp = '\0';
2566	reply(500, "%s: command not understood.", cbuf);
2567}
2568
2569void
2570delete(char *name)
2571{
2572	struct stat st;
2573
2574	LOGCMD("delete", name);
2575	if (lstat(name, &st) < 0) {
2576		perror_reply(550, name);
2577		return;
2578	}
2579	if (S_ISDIR(st.st_mode)) {
2580		if (rmdir(name) < 0) {
2581			perror_reply(550, name);
2582			return;
2583		}
2584		goto done;
2585	}
2586	if (guest && noguestmod) {
2587		reply(550, "Operation not permitted.");
2588		return;
2589	}
2590	if (unlink(name) < 0) {
2591		perror_reply(550, name);
2592		return;
2593	}
2594done:
2595	ack("DELE");
2596}
2597
2598void
2599cwd(char *path)
2600{
2601
2602	if (chdir(path) < 0)
2603		perror_reply(550, path);
2604	else
2605		ack("CWD");
2606}
2607
2608void
2609makedir(char *name)
2610{
2611	char *s;
2612
2613	LOGCMD("mkdir", name);
2614	if (guest && noguestmkd)
2615		reply(550, "Operation not permitted.");
2616	else if (mkdir(name, 0777) < 0)
2617		perror_reply(550, name);
2618	else {
2619		if ((s = doublequote(name)) == NULL)
2620			fatalerror("Ran out of memory.");
2621		reply(257, "\"%s\" directory created.", s);
2622		free(s);
2623	}
2624}
2625
2626void
2627removedir(char *name)
2628{
2629
2630	LOGCMD("rmdir", name);
2631	if (rmdir(name) < 0)
2632		perror_reply(550, name);
2633	else
2634		ack("RMD");
2635}
2636
2637void
2638pwd(void)
2639{
2640	char *s, path[MAXPATHLEN + 1];
2641
2642	if (getcwd(path, sizeof(path)) == NULL)
2643		perror_reply(550, "Get current directory");
2644	else {
2645		if ((s = doublequote(path)) == NULL)
2646			fatalerror("Ran out of memory.");
2647		reply(257, "\"%s\" is current directory.", s);
2648		free(s);
2649	}
2650}
2651
2652char *
2653renamefrom(char *name)
2654{
2655	struct stat st;
2656
2657	if (guest && noguestmod) {
2658		reply(550, "Operation not permitted.");
2659		return (NULL);
2660	}
2661	if (lstat(name, &st) < 0) {
2662		perror_reply(550, name);
2663		return (NULL);
2664	}
2665	reply(350, "File exists, ready for destination name.");
2666	return (name);
2667}
2668
2669void
2670renamecmd(char *from, char *to)
2671{
2672	struct stat st;
2673
2674	LOGCMD2("rename", from, to);
2675
2676	if (guest && (stat(to, &st) == 0)) {
2677		reply(550, "%s: permission denied.", to);
2678		return;
2679	}
2680
2681	if (rename(from, to) < 0)
2682		perror_reply(550, "rename");
2683	else
2684		ack("RNTO");
2685}
2686
2687static void
2688dolog(struct sockaddr *who)
2689{
2690	char who_name[NI_MAXHOST];
2691
2692	realhostname_sa(remotehost, sizeof(remotehost) - 1, who, who->sa_len);
2693	remotehost[sizeof(remotehost) - 1] = 0;
2694	if (getnameinfo(who, who->sa_len,
2695		who_name, sizeof(who_name) - 1, NULL, 0, NI_NUMERICHOST))
2696			*who_name = 0;
2697	who_name[sizeof(who_name) - 1] = 0;
2698
2699#ifdef SETPROCTITLE
2700#ifdef VIRTUAL_HOSTING
2701	if (thishost != firsthost)
2702		snprintf(proctitle, sizeof(proctitle), "%s: connected (to %s)",
2703			 remotehost, hostname);
2704	else
2705#endif
2706		snprintf(proctitle, sizeof(proctitle), "%s: connected",
2707			 remotehost);
2708	setproctitle("%s", proctitle);
2709#endif /* SETPROCTITLE */
2710
2711	if (logging) {
2712#ifdef VIRTUAL_HOSTING
2713		if (thishost != firsthost)
2714			syslog(LOG_INFO, "connection from %s (%s) to %s",
2715			       remotehost, who_name, hostname);
2716		else
2717#endif
2718			syslog(LOG_INFO, "connection from %s (%s)",
2719			       remotehost, who_name);
2720	}
2721}
2722
2723/*
2724 * Record logout in wtmp file
2725 * and exit with supplied status.
2726 */
2727void
2728dologout(int status)
2729{
2730
2731	if (logged_in && dowtmp) {
2732		(void) seteuid(0);
2733		ftpd_logwtmp(ttyline, "", NULL);
2734	}
2735	/* beware of flushing buffers after a SIGPIPE */
2736	_exit(status);
2737}
2738
2739static void
2740sigurg(int signo)
2741{
2742
2743	recvurg = 1;
2744}
2745
2746static void
2747maskurg(int flag)
2748{
2749	int oerrno;
2750	sigset_t sset;
2751
2752	if (!transflag) {
2753		syslog(LOG_ERR, "Internal: maskurg() while no transfer");
2754		return;
2755	}
2756	oerrno = errno;
2757	sigemptyset(&sset);
2758	sigaddset(&sset, SIGURG);
2759	sigprocmask(flag ? SIG_BLOCK : SIG_UNBLOCK, &sset, NULL);
2760	errno = oerrno;
2761}
2762
2763static void
2764flagxfer(int flag)
2765{
2766
2767	maskurg(!flag);
2768	if (flag) {
2769		recvurg = 0;
2770		transflag = 1;
2771	} else
2772		transflag = 0;
2773}
2774
2775/*
2776 * Returns 0 if OK to resume or -1 if abort requested.
2777 */
2778static int
2779myoob(void)
2780{
2781	char *cp;
2782
2783	if (!transflag) {
2784		syslog(LOG_ERR, "Internal: myoob() while no transfer");
2785		return (0);
2786	}
2787	cp = tmpline;
2788	if (getline(cp, 7, stdin) == NULL) {
2789		reply(221, "You could at least say goodbye.");
2790		dologout(0);
2791	}
2792	upper(cp);
2793	if (strcmp(cp, "ABOR\r\n") == 0) {
2794		tmpline[0] = '\0';
2795		reply(426, "Transfer aborted. Data connection closed.");
2796		reply(226, "Abort successful.");
2797		return (-1);
2798	}
2799	if (strcmp(cp, "STAT\r\n") == 0) {
2800		tmpline[0] = '\0';
2801		if (file_size != -1)
2802			reply(213, "Status: %jd of %jd bytes transferred.",
2803				   (intmax_t)byte_count, (intmax_t)file_size);
2804		else
2805			reply(213, "Status: %jd bytes transferred.",
2806				   (intmax_t)byte_count);
2807	}
2808	return (0);
2809}
2810
2811/*
2812 * Note: a response of 425 is not mentioned as a possible response to
2813 *	the PASV command in RFC959. However, it has been blessed as
2814 *	a legitimate response by Jon Postel in a telephone conversation
2815 *	with Rick Adams on 25 Jan 89.
2816 */
2817void
2818passive(void)
2819{
2820	socklen_t len;
2821	int on;
2822	char *p, *a;
2823
2824	if (pdata >= 0)		/* close old port if one set */
2825		close(pdata);
2826
2827	pdata = socket(ctrl_addr.su_family, SOCK_STREAM, 0);
2828	if (pdata < 0) {
2829		perror_reply(425, "Can't open passive connection");
2830		return;
2831	}
2832	on = 1;
2833	if (setsockopt(pdata, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
2834		syslog(LOG_WARNING, "pdata setsockopt (SO_REUSEADDR): %m");
2835
2836	(void) seteuid(0);
2837
2838#ifdef IP_PORTRANGE
2839	if (ctrl_addr.su_family == AF_INET) {
2840	    on = restricted_data_ports ? IP_PORTRANGE_HIGH
2841				       : IP_PORTRANGE_DEFAULT;
2842
2843	    if (setsockopt(pdata, IPPROTO_IP, IP_PORTRANGE,
2844			    &on, sizeof(on)) < 0)
2845		    goto pasv_error;
2846	}
2847#endif
2848#ifdef IPV6_PORTRANGE
2849	if (ctrl_addr.su_family == AF_INET6) {
2850	    on = restricted_data_ports ? IPV6_PORTRANGE_HIGH
2851				       : IPV6_PORTRANGE_DEFAULT;
2852
2853	    if (setsockopt(pdata, IPPROTO_IPV6, IPV6_PORTRANGE,
2854			    &on, sizeof(on)) < 0)
2855		    goto pasv_error;
2856	}
2857#endif
2858
2859	pasv_addr = ctrl_addr;
2860	pasv_addr.su_port = 0;
2861	if (bind(pdata, (struct sockaddr *)&pasv_addr, pasv_addr.su_len) < 0)
2862		goto pasv_error;
2863
2864	(void) seteuid(pw->pw_uid);
2865
2866	len = sizeof(pasv_addr);
2867	if (getsockname(pdata, (struct sockaddr *) &pasv_addr, &len) < 0)
2868		goto pasv_error;
2869	if (listen(pdata, 1) < 0)
2870		goto pasv_error;
2871	if (pasv_addr.su_family == AF_INET)
2872		a = (char *) &pasv_addr.su_sin.sin_addr;
2873	else if (pasv_addr.su_family == AF_INET6 &&
2874		 IN6_IS_ADDR_V4MAPPED(&pasv_addr.su_sin6.sin6_addr))
2875		a = (char *) &pasv_addr.su_sin6.sin6_addr.s6_addr[12];
2876	else
2877		goto pasv_error;
2878
2879	p = (char *) &pasv_addr.su_port;
2880
2881#define UC(b) (((int) b) & 0xff)
2882
2883	reply(227, "Entering Passive Mode (%d,%d,%d,%d,%d,%d)", UC(a[0]),
2884		UC(a[1]), UC(a[2]), UC(a[3]), UC(p[0]), UC(p[1]));
2885	return;
2886
2887pasv_error:
2888	(void) seteuid(pw->pw_uid);
2889	(void) close(pdata);
2890	pdata = -1;
2891	perror_reply(425, "Can't open passive connection");
2892	return;
2893}
2894
2895/*
2896 * Long Passive defined in RFC 1639.
2897 *     228 Entering Long Passive Mode
2898 *         (af, hal, h1, h2, h3,..., pal, p1, p2...)
2899 */
2900
2901void
2902long_passive(char *cmd, int pf)
2903{
2904	socklen_t len;
2905	int on;
2906	char *p, *a;
2907
2908	if (pdata >= 0)		/* close old port if one set */
2909		close(pdata);
2910
2911	if (pf != PF_UNSPEC) {
2912		if (ctrl_addr.su_family != pf) {
2913			switch (ctrl_addr.su_family) {
2914			case AF_INET:
2915				pf = 1;
2916				break;
2917			case AF_INET6:
2918				pf = 2;
2919				break;
2920			default:
2921				pf = 0;
2922				break;
2923			}
2924			/*
2925			 * XXX
2926			 * only EPRT/EPSV ready clients will understand this
2927			 */
2928			if (strcmp(cmd, "EPSV") == 0 && pf) {
2929				reply(522, "Network protocol mismatch, "
2930					"use (%d)", pf);
2931			} else
2932				reply(501, "Network protocol mismatch."); /*XXX*/
2933
2934			return;
2935		}
2936	}
2937
2938	pdata = socket(ctrl_addr.su_family, SOCK_STREAM, 0);
2939	if (pdata < 0) {
2940		perror_reply(425, "Can't open passive connection");
2941		return;
2942	}
2943	on = 1;
2944	if (setsockopt(pdata, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
2945		syslog(LOG_WARNING, "pdata setsockopt (SO_REUSEADDR): %m");
2946
2947	(void) seteuid(0);
2948
2949	pasv_addr = ctrl_addr;
2950	pasv_addr.su_port = 0;
2951	len = pasv_addr.su_len;
2952
2953#ifdef IP_PORTRANGE
2954	if (ctrl_addr.su_family == AF_INET) {
2955	    on = restricted_data_ports ? IP_PORTRANGE_HIGH
2956				       : IP_PORTRANGE_DEFAULT;
2957
2958	    if (setsockopt(pdata, IPPROTO_IP, IP_PORTRANGE,
2959			    &on, sizeof(on)) < 0)
2960		    goto pasv_error;
2961	}
2962#endif
2963#ifdef IPV6_PORTRANGE
2964	if (ctrl_addr.su_family == AF_INET6) {
2965	    on = restricted_data_ports ? IPV6_PORTRANGE_HIGH
2966				       : IPV6_PORTRANGE_DEFAULT;
2967
2968	    if (setsockopt(pdata, IPPROTO_IPV6, IPV6_PORTRANGE,
2969			    &on, sizeof(on)) < 0)
2970		    goto pasv_error;
2971	}
2972#endif
2973
2974	if (bind(pdata, (struct sockaddr *)&pasv_addr, len) < 0)
2975		goto pasv_error;
2976
2977	(void) seteuid(pw->pw_uid);
2978
2979	if (getsockname(pdata, (struct sockaddr *) &pasv_addr, &len) < 0)
2980		goto pasv_error;
2981	if (listen(pdata, 1) < 0)
2982		goto pasv_error;
2983
2984#define UC(b) (((int) b) & 0xff)
2985
2986	if (strcmp(cmd, "LPSV") == 0) {
2987		p = (char *)&pasv_addr.su_port;
2988		switch (pasv_addr.su_family) {
2989		case AF_INET:
2990			a = (char *) &pasv_addr.su_sin.sin_addr;
2991		v4_reply:
2992			reply(228,
2993"Entering Long Passive Mode (%d,%d,%d,%d,%d,%d,%d,%d,%d)",
2994			      4, 4, UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]),
2995			      2, UC(p[0]), UC(p[1]));
2996			return;
2997		case AF_INET6:
2998			if (IN6_IS_ADDR_V4MAPPED(&pasv_addr.su_sin6.sin6_addr)) {
2999				a = (char *) &pasv_addr.su_sin6.sin6_addr.s6_addr[12];
3000				goto v4_reply;
3001			}
3002			a = (char *) &pasv_addr.su_sin6.sin6_addr;
3003			reply(228,
3004"Entering Long Passive Mode "
3005"(%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d)",
3006			      6, 16, UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]),
3007			      UC(a[4]), UC(a[5]), UC(a[6]), UC(a[7]),
3008			      UC(a[8]), UC(a[9]), UC(a[10]), UC(a[11]),
3009			      UC(a[12]), UC(a[13]), UC(a[14]), UC(a[15]),
3010			      2, UC(p[0]), UC(p[1]));
3011			return;
3012		}
3013	} else if (strcmp(cmd, "EPSV") == 0) {
3014		switch (pasv_addr.su_family) {
3015		case AF_INET:
3016		case AF_INET6:
3017			reply(229, "Entering Extended Passive Mode (|||%d|)",
3018				ntohs(pasv_addr.su_port));
3019			return;
3020		}
3021	} else {
3022		/* more proper error code? */
3023	}
3024
3025pasv_error:
3026	(void) seteuid(pw->pw_uid);
3027	(void) close(pdata);
3028	pdata = -1;
3029	perror_reply(425, "Can't open passive connection");
3030	return;
3031}
3032
3033/*
3034 * Generate unique name for file with basename "local"
3035 * and open the file in order to avoid possible races.
3036 * Try "local" first, then "local.1", "local.2" etc, up to "local.99".
3037 * Return descriptor to the file, set "name" to its name.
3038 *
3039 * Generates failure reply on error.
3040 */
3041static int
3042guniquefd(char *local, char **name)
3043{
3044	static char new[MAXPATHLEN];
3045	struct stat st;
3046	char *cp;
3047	int count;
3048	int fd;
3049
3050	cp = strrchr(local, '/');
3051	if (cp)
3052		*cp = '\0';
3053	if (stat(cp ? local : ".", &st) < 0) {
3054		perror_reply(553, cp ? local : ".");
3055		return (-1);
3056	}
3057	if (cp) {
3058		/*
3059		 * Let not overwrite dirname with counter suffix.
3060		 * -4 is for /nn\0
3061		 * In this extreme case dot won't be put in front of suffix.
3062		 */
3063		if (strlen(local) > sizeof(new) - 4) {
3064			reply(553, "Pathname too long.");
3065			return (-1);
3066		}
3067		*cp = '/';
3068	}
3069	/* -4 is for the .nn<null> we put on the end below */
3070	(void) snprintf(new, sizeof(new) - 4, "%s", local);
3071	cp = new + strlen(new);
3072	/*
3073	 * Don't generate dotfile unless requested explicitly.
3074	 * This covers the case when basename gets truncated off
3075	 * by buffer size.
3076	 */
3077	if (cp > new && cp[-1] != '/')
3078		*cp++ = '.';
3079	for (count = 0; count < 100; count++) {
3080		/* At count 0 try unmodified name */
3081		if (count)
3082			(void)sprintf(cp, "%d", count);
3083		if ((fd = open(count ? new : local,
3084		    O_RDWR | O_CREAT | O_EXCL, 0666)) >= 0) {
3085			*name = count ? new : local;
3086			return (fd);
3087		}
3088		if (errno != EEXIST) {
3089			perror_reply(553, count ? new : local);
3090			return (-1);
3091		}
3092	}
3093	reply(452, "Unique file name cannot be created.");
3094	return (-1);
3095}
3096
3097/*
3098 * Format and send reply containing system error number.
3099 */
3100void
3101perror_reply(int code, char *string)
3102{
3103
3104	reply(code, "%s: %s.", string, strerror(errno));
3105}
3106
3107static char *onefile[] = {
3108	"",
3109	0
3110};
3111
3112void
3113send_file_list(char *whichf)
3114{
3115	struct stat st;
3116	DIR *dirp = NULL;
3117	struct dirent *dir;
3118	FILE *dout = NULL;
3119	char **dirlist, *dirname;
3120	int simple = 0;
3121	int freeglob = 0;
3122	glob_t gl;
3123
3124	if (strpbrk(whichf, "~{[*?") != NULL) {
3125		int flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE;
3126
3127		memset(&gl, 0, sizeof(gl));
3128		gl.gl_matchc = MAXGLOBARGS;
3129		flags |= GLOB_LIMIT;
3130		freeglob = 1;
3131		if (glob(whichf, flags, 0, &gl)) {
3132			reply(550, "No matching files found.");
3133			goto out;
3134		} else if (gl.gl_pathc == 0) {
3135			errno = ENOENT;
3136			perror_reply(550, whichf);
3137			goto out;
3138		}
3139		dirlist = gl.gl_pathv;
3140	} else {
3141		onefile[0] = whichf;
3142		dirlist = onefile;
3143		simple = 1;
3144	}
3145
3146	while ((dirname = *dirlist++)) {
3147		if (stat(dirname, &st) < 0) {
3148			/*
3149			 * If user typed "ls -l", etc, and the client
3150			 * used NLST, do what the user meant.
3151			 */
3152			if (dirname[0] == '-' && *dirlist == NULL &&
3153			    dout == NULL)
3154				retrieve(_PATH_LS " %s", dirname);
3155			else
3156				perror_reply(550, whichf);
3157			goto out;
3158		}
3159
3160		if (S_ISREG(st.st_mode)) {
3161			if (dout == NULL) {
3162				dout = dataconn("file list", -1, "w");
3163				if (dout == NULL)
3164					goto out;
3165				STARTXFER;
3166			}
3167			START_UNSAFE;
3168			fprintf(dout, "%s%s\n", dirname,
3169				type == TYPE_A ? "\r" : "");
3170			END_UNSAFE;
3171			if (ferror(dout))
3172				goto data_err;
3173			byte_count += strlen(dirname) +
3174				      (type == TYPE_A ? 2 : 1);
3175			CHECKOOB(goto abrt);
3176			continue;
3177		} else if (!S_ISDIR(st.st_mode))
3178			continue;
3179
3180		if ((dirp = opendir(dirname)) == NULL)
3181			continue;
3182
3183		while ((dir = readdir(dirp)) != NULL) {
3184			char nbuf[MAXPATHLEN];
3185
3186			CHECKOOB(goto abrt);
3187
3188			if (dir->d_name[0] == '.' && dir->d_namlen == 1)
3189				continue;
3190			if (dir->d_name[0] == '.' && dir->d_name[1] == '.' &&
3191			    dir->d_namlen == 2)
3192				continue;
3193
3194			snprintf(nbuf, sizeof(nbuf),
3195				"%s/%s", dirname, dir->d_name);
3196
3197			/*
3198			 * We have to do a stat to insure it's
3199			 * not a directory or special file.
3200			 */
3201			if (simple || (stat(nbuf, &st) == 0 &&
3202			    S_ISREG(st.st_mode))) {
3203				if (dout == NULL) {
3204					dout = dataconn("file list", -1, "w");
3205					if (dout == NULL)
3206						goto out;
3207					STARTXFER;
3208				}
3209				START_UNSAFE;
3210				if (nbuf[0] == '.' && nbuf[1] == '/')
3211					fprintf(dout, "%s%s\n", &nbuf[2],
3212						type == TYPE_A ? "\r" : "");
3213				else
3214					fprintf(dout, "%s%s\n", nbuf,
3215						type == TYPE_A ? "\r" : "");
3216				END_UNSAFE;
3217				if (ferror(dout))
3218					goto data_err;
3219				byte_count += strlen(nbuf) +
3220					      (type == TYPE_A ? 2 : 1);
3221				CHECKOOB(goto abrt);
3222			}
3223		}
3224		(void) closedir(dirp);
3225		dirp = NULL;
3226	}
3227
3228	if (dout == NULL)
3229		reply(550, "No files found.");
3230	else if (ferror(dout))
3231data_err:	perror_reply(550, "Data connection");
3232	else
3233		reply(226, "Transfer complete.");
3234out:
3235	if (dout) {
3236		ENDXFER;
3237abrt:
3238		(void) fclose(dout);
3239		data = -1;
3240		pdata = -1;
3241	}
3242	if (dirp)
3243		(void) closedir(dirp);
3244	if (freeglob) {
3245		freeglob = 0;
3246		globfree(&gl);
3247	}
3248}
3249
3250void
3251reapchild(int signo)
3252{
3253	while (waitpid(-1, NULL, WNOHANG) > 0);
3254}
3255
3256#ifdef OLD_SETPROCTITLE
3257/*
3258 * Clobber argv so ps will show what we're doing.  (Stolen from sendmail.)
3259 * Warning, since this is usually started from inetd.conf, it often doesn't
3260 * have much of an environment or arglist to overwrite.
3261 */
3262void
3263setproctitle(const char *fmt, ...)
3264{
3265	int i;
3266	va_list ap;
3267	char *p, *bp, ch;
3268	char buf[LINE_MAX];
3269
3270	va_start(ap, fmt);
3271	(void)vsnprintf(buf, sizeof(buf), fmt, ap);
3272
3273	/* make ps print our process name */
3274	p = Argv[0];
3275	*p++ = '-';
3276
3277	i = strlen(buf);
3278	if (i > LastArgv - p - 2) {
3279		i = LastArgv - p - 2;
3280		buf[i] = '\0';
3281	}
3282	bp = buf;
3283	while (ch = *bp++)
3284		if (ch != '\n' && ch != '\r')
3285			*p++ = ch;
3286	while (p < LastArgv)
3287		*p++ = ' ';
3288}
3289#endif /* OLD_SETPROCTITLE */
3290
3291static void
3292appendf(char **strp, char *fmt, ...)
3293{
3294	va_list ap;
3295	char *ostr, *p;
3296
3297	va_start(ap, fmt);
3298	vasprintf(&p, fmt, ap);
3299	va_end(ap);
3300	if (p == NULL)
3301		fatalerror("Ran out of memory.");
3302	if (*strp == NULL)
3303		*strp = p;
3304	else {
3305		ostr = *strp;
3306		asprintf(strp, "%s%s", ostr, p);
3307		if (*strp == NULL)
3308			fatalerror("Ran out of memory.");
3309		free(ostr);
3310	}
3311}
3312
3313static void
3314logcmd(char *cmd, char *file1, char *file2, off_t cnt)
3315{
3316	char *msg = NULL;
3317	char wd[MAXPATHLEN + 1];
3318
3319	if (logging <= 1)
3320		return;
3321
3322	if (getcwd(wd, sizeof(wd) - 1) == NULL)
3323		strcpy(wd, strerror(errno));
3324
3325	appendf(&msg, "%s", cmd);
3326	if (file1)
3327		appendf(&msg, " %s", file1);
3328	if (file2)
3329		appendf(&msg, " %s", file2);
3330	if (cnt >= 0)
3331		appendf(&msg, " = %jd bytes", (intmax_t)cnt);
3332	appendf(&msg, " (wd: %s", wd);
3333	if (guest || dochroot)
3334		appendf(&msg, "; chrooted");
3335	appendf(&msg, ")");
3336	syslog(LOG_INFO, "%s", msg);
3337	free(msg);
3338}
3339
3340static void
3341logxfer(char *name, off_t size, time_t start)
3342{
3343	char buf[MAXPATHLEN + 1024];
3344	char path[MAXPATHLEN + 1];
3345	time_t now;
3346
3347	if (statfd >= 0) {
3348		time(&now);
3349		if (realpath(name, path) == NULL) {
3350			syslog(LOG_NOTICE, "realpath failed on %s: %m", path);
3351			return;
3352		}
3353		snprintf(buf, sizeof(buf), "%.20s!%s!%s!%s!%jd!%ld\n",
3354			ctime(&now)+4, ident, remotehost,
3355			path, (intmax_t)size,
3356			(long)(now - start + (now == start)));
3357		write(statfd, buf, strlen(buf));
3358	}
3359}
3360
3361static char *
3362doublequote(char *s)
3363{
3364	int n;
3365	char *p, *s2;
3366
3367	for (p = s, n = 0; *p; p++)
3368		if (*p == '"')
3369			n++;
3370
3371	if ((s2 = malloc(p - s + n + 1)) == NULL)
3372		return (NULL);
3373
3374	for (p = s2; *s; s++, p++) {
3375		if ((*p = *s) == '"')
3376			*(++p) = '"';
3377	}
3378	*p = '\0';
3379
3380	return (s2);
3381}
3382
3383/* setup server socket for specified address family */
3384/* if af is PF_UNSPEC more than one socket may be returned */
3385/* the returned list is dynamically allocated, so caller needs to free it */
3386static int *
3387socksetup(int af, char *bindname, const char *bindport)
3388{
3389	struct addrinfo hints, *res, *r;
3390	int error, maxs, *s, *socks;
3391	const int on = 1;
3392
3393	memset(&hints, 0, sizeof(hints));
3394	hints.ai_flags = AI_PASSIVE;
3395	hints.ai_family = af;
3396	hints.ai_socktype = SOCK_STREAM;
3397	error = getaddrinfo(bindname, bindport, &hints, &res);
3398	if (error) {
3399		syslog(LOG_ERR, "%s", gai_strerror(error));
3400		if (error == EAI_SYSTEM)
3401			syslog(LOG_ERR, "%s", strerror(errno));
3402		return NULL;
3403	}
3404
3405	/* Count max number of sockets we may open */
3406	for (maxs = 0, r = res; r; r = r->ai_next, maxs++)
3407		;
3408	socks = malloc((maxs + 1) * sizeof(int));
3409	if (!socks) {
3410		freeaddrinfo(res);
3411		syslog(LOG_ERR, "couldn't allocate memory for sockets");
3412		return NULL;
3413	}
3414
3415	*socks = 0;   /* num of sockets counter at start of array */
3416	s = socks + 1;
3417	for (r = res; r; r = r->ai_next) {
3418		*s = socket(r->ai_family, r->ai_socktype, r->ai_protocol);
3419		if (*s < 0) {
3420			syslog(LOG_DEBUG, "control socket: %m");
3421			continue;
3422		}
3423		if (setsockopt(*s, SOL_SOCKET, SO_REUSEADDR,
3424		    &on, sizeof(on)) < 0)
3425			syslog(LOG_WARNING,
3426			    "control setsockopt (SO_REUSEADDR): %m");
3427		if (r->ai_family == AF_INET6) {
3428			if (setsockopt(*s, IPPROTO_IPV6, IPV6_V6ONLY,
3429			    &on, sizeof(on)) < 0)
3430				syslog(LOG_WARNING,
3431				    "control setsockopt (IPV6_V6ONLY): %m");
3432		}
3433		if (bind(*s, r->ai_addr, r->ai_addrlen) < 0) {
3434			syslog(LOG_DEBUG, "control bind: %m");
3435			close(*s);
3436			continue;
3437		}
3438		(*socks)++;
3439		s++;
3440	}
3441
3442	if (res)
3443		freeaddrinfo(res);
3444
3445	if (*socks == 0) {
3446		syslog(LOG_ERR, "control socket: Couldn't bind to any socket");
3447		free(socks);
3448		return NULL;
3449	}
3450	return(socks);
3451}
3452