ftpd.c revision 122751
11592Srgrimes/*
21592Srgrimes * Copyright (c) 1985, 1988, 1990, 1992, 1993, 1994
31592Srgrimes *	The Regents of the University of California.  All rights reserved.
41592Srgrimes *
51592Srgrimes * Redistribution and use in source and binary forms, with or without
61592Srgrimes * modification, are permitted provided that the following conditions
71592Srgrimes * are met:
81592Srgrimes * 1. Redistributions of source code must retain the above copyright
91592Srgrimes *    notice, this list of conditions and the following disclaimer.
101592Srgrimes * 2. Redistributions in binary form must reproduce the above copyright
111592Srgrimes *    notice, this list of conditions and the following disclaimer in the
121592Srgrimes *    documentation and/or other materials provided with the distribution.
131592Srgrimes * 3. All advertising materials mentioning features or use of this software
141592Srgrimes *    must display the following acknowledgement:
151592Srgrimes *	This product includes software developed by the University of
161592Srgrimes *	California, Berkeley and its contributors.
171592Srgrimes * 4. Neither the name of the University nor the names of its contributors
181592Srgrimes *    may be used to endorse or promote products derived from this software
191592Srgrimes *    without specific prior written permission.
201592Srgrimes *
211592Srgrimes * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
221592Srgrimes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
231592Srgrimes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
241592Srgrimes * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
251592Srgrimes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
261592Srgrimes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
271592Srgrimes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
281592Srgrimes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
291592Srgrimes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
301592Srgrimes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
311592Srgrimes * SUCH DAMAGE.
321592Srgrimes */
331592Srgrimes
3417478Smarkm#if 0
351592Srgrimes#ifndef lint
361592Srgrimesstatic char copyright[] =
371592Srgrimes"@(#) Copyright (c) 1985, 1988, 1990, 1992, 1993, 1994\n\
381592Srgrimes	The Regents of the University of California.  All rights reserved.\n";
391592Srgrimes#endif /* not lint */
4017478Smarkm#endif
411592Srgrimes
4231329Scharnier#ifndef lint
4317478Smarkm#if 0
441592Srgrimesstatic char sccsid[] = "@(#)ftpd.c	8.4 (Berkeley) 4/16/94";
4531329Scharnier#endif
4631329Scharnierstatic const char rcsid[] =
4750476Speter  "$FreeBSD: head/libexec/ftpd/ftpd.c 122751 2003-11-15 11:08:26Z yar $";
481592Srgrimes#endif /* not lint */
491592Srgrimes
501592Srgrimes/*
511592Srgrimes * FTP server.
521592Srgrimes */
531592Srgrimes#include <sys/param.h>
541592Srgrimes#include <sys/ioctl.h>
5566907Swollman#include <sys/mman.h>
561592Srgrimes#include <sys/socket.h>
5766907Swollman#include <sys/stat.h>
5866907Swollman#include <sys/time.h>
591592Srgrimes#include <sys/wait.h>
601592Srgrimes
611592Srgrimes#include <netinet/in.h>
621592Srgrimes#include <netinet/in_systm.h>
631592Srgrimes#include <netinet/ip.h>
648240Swollman#include <netinet/tcp.h>
651592Srgrimes
661592Srgrimes#define	FTP_NAMES
671592Srgrimes#include <arpa/ftp.h>
681592Srgrimes#include <arpa/inet.h>
691592Srgrimes#include <arpa/telnet.h>
701592Srgrimes
711592Srgrimes#include <ctype.h>
721592Srgrimes#include <dirent.h>
731592Srgrimes#include <err.h>
741592Srgrimes#include <errno.h>
751592Srgrimes#include <fcntl.h>
761592Srgrimes#include <glob.h>
771592Srgrimes#include <limits.h>
781592Srgrimes#include <netdb.h>
791592Srgrimes#include <pwd.h>
8025187Sdavidn#include <grp.h>
8188763Sache#include <opie.h>
821592Srgrimes#include <signal.h>
831592Srgrimes#include <stdio.h>
841592Srgrimes#include <stdlib.h>
851592Srgrimes#include <string.h>
861592Srgrimes#include <syslog.h>
871592Srgrimes#include <time.h>
881592Srgrimes#include <unistd.h>
8913139Speter#include <libutil.h>
9025101Sdavidn#ifdef	LOGIN_CAP
9125101Sdavidn#include <login_cap.h>
9225101Sdavidn#endif
931592Srgrimes
9474874Smarkm#ifdef USE_PAM
9551433Smarkm#include <security/pam_appl.h>
9651433Smarkm#endif
9751433Smarkm
981592Srgrimes#include "pathnames.h"
991592Srgrimes#include "extern.h"
1001592Srgrimes
1011592Srgrimes#include <stdarg.h>
1021592Srgrimes
10325165Sdavidnstatic char version[] = "Version 6.00LS";
10425165Sdavidn#undef main
1051592Srgrimes
1061592Srgrimesextern	off_t restart_point;
1071592Srgrimesextern	char cbuf[];
1081592Srgrimes
10956668Sshinunion sockunion ctrl_addr;
11056668Sshinunion sockunion data_source;
11156668Sshinunion sockunion data_dest;
11256668Sshinunion sockunion his_addr;
11356668Sshinunion sockunion pasv_addr;
1141592Srgrimes
11515196Sdgint	daemon_mode;
1161592Srgrimesint	data;
117109742Syarint	dataport;
118110037Syarint	hostinfo = 1;	/* print host-specific info in messages */
1191592Srgrimesint	logged_in;
1201592Srgrimesstruct	passwd *pw;
121110036Syarchar	*homedir;
12276096Smarkmint	ftpdebug;
1231592Srgrimesint	timeout = 900;    /* timeout after 15 minutes of inactivity */
1241592Srgrimesint	maxtimeout = 7200;/* don't allow idle time to be set beyond 2 hours */
1251592Srgrimesint	logging;
1269933Spstint	restricted_data_ports = 1;
12717435Spstint	paranoid = 1;	  /* be extra careful about security */
12820042Storstenbint	anon_only = 0;    /* Only anonymous ftp allowed */
1291592Srgrimesint	guest;
13017435Spstint	dochroot;
131102311Syarint	dowtmp = 1;
1326740Sguidoint	stats;
1336740Sguidoint	statfd = -1;
1341592Srgrimesint	type;
1351592Srgrimesint	form;
1361592Srgrimesint	stru;			/* avoid C keyword */
1371592Srgrimesint	mode;
1381592Srgrimesint	usedefault = 1;		/* for data transfers */
1391592Srgrimesint	pdata = -1;		/* for passive mode */
14070102Sphkint	readonly=0;		/* Server is in readonly mode.	*/
14170102Sphkint	noepsv=0;		/* EPSV command is disabled.	*/
14282460Snikint	noretr=0;		/* RETR command is disabled.	*/
14382796Ssheldonhint	noguestretr=0;		/* RETR command is disabled for anon users. */
14499195Smdoddint	noguestmkd=0;		/* MKD command is disabled for anon users. */
145101537Syarint	noguestmod=1;		/* anon users may not modify existing files. */
14682460Snik
14789935Syarstatic volatile sig_atomic_t recvurg;
1481592Srgrimessig_atomic_t transflag;
1491592Srgrimesoff_t	file_size;
1501592Srgrimesoff_t	byte_count;
1511592Srgrimes#if !defined(CMASK) || CMASK == 0
1521592Srgrimes#undef CMASK
1531592Srgrimes#define CMASK 027
1541592Srgrimes#endif
1551592Srgrimesint	defumask = CMASK;		/* default umask value */
1561592Srgrimeschar	tmpline[7];
15727650Sdavidnchar	*hostname;
15878153Sddint	epsvall = 0;
15978153Sdd
16025283Sdavidn#ifdef VIRTUAL_HOSTING
16125283Sdavidnchar	*ftpuser;
16225283Sdavidn
16325283Sdavidnstatic struct ftphost {
16425283Sdavidn	struct ftphost	*next;
16557124Sshin	struct addrinfo *hostinfo;
16625283Sdavidn	char		*hostname;
16725283Sdavidn	char		*anonuser;
16825283Sdavidn	char		*statfile;
16925283Sdavidn	char		*welcome;
17025283Sdavidn	char		*loginmsg;
17125283Sdavidn} *thishost, *firsthost;
17225283Sdavidn
17325283Sdavidn#endif
17445422Sbrianchar	remotehost[MAXHOSTNAMELEN];
1756740Sguidochar	*ident = NULL;
17617435Spst
17717435Spststatic char ttyline[20];
17817435Spstchar	*tty = ttyline;		/* for klogin */
17917435Spst
18074874Smarkm#ifdef USE_PAM
18190148Simpstatic int	auth_pam(struct passwd**, const char*);
18274874Smarkmpam_handle_t *pamh = NULL;
18388763Sache#endif
18479469Smarkm
18579469Smarkmstatic struct opie opiedata;
18679469Smarkmstatic char opieprompt[OPIE_CHALLENGE_MAX+1];
18788763Sachestatic int pwok;
18817478Smarkm
18917483Sjulianchar	*pid_file = NULL;
19017483Sjulian
1911592Srgrimes/*
19274470Sjlemon * Limit number of pathnames that glob can return.
19374470Sjlemon * A limit of 0 indicates the number of pathnames is unlimited.
19474470Sjlemon */
19574470Sjlemon#define MAXGLOBARGS	16384
19674470Sjlemon#
19774470Sjlemon
19874470Sjlemon/*
1991592Srgrimes * Timeout intervals for retrying connections
2001592Srgrimes * to hosts that don't accept PORT cmds.  This
2011592Srgrimes * is a kludge, but given the problems with TCP...
2021592Srgrimes */
2031592Srgrimes#define	SWAITMAX	90	/* wait at most 90 seconds */
2041592Srgrimes#define	SWAITINT	5	/* interval between retries */
2051592Srgrimes
2061592Srgrimesint	swaitmax = SWAITMAX;
2071592Srgrimesint	swaitint = SWAITINT;
2081592Srgrimes
2091592Srgrimes#ifdef SETPROCTITLE
21013139Speter#ifdef OLD_SETPROCTITLE
2111592Srgrimeschar	**Argv = NULL;		/* pointer to argument vector */
2121592Srgrimeschar	*LastArgv = NULL;	/* end of argv */
21313139Speter#endif /* OLD_SETPROCTITLE */
2141592Srgrimeschar	proctitle[LINE_MAX];	/* initial part of title */
2151592Srgrimes#endif /* SETPROCTITLE */
2161592Srgrimes
2171592Srgrimes#define LOGCMD(cmd, file) \
2181592Srgrimes	if (logging > 1) \
2191592Srgrimes	    syslog(LOG_INFO,"%s %s%s", cmd, \
2201592Srgrimes		*(file) == '/' ? "" : curdir(), file);
2211592Srgrimes#define LOGCMD2(cmd, file1, file2) \
2221592Srgrimes	 if (logging > 1) \
2231592Srgrimes	    syslog(LOG_INFO,"%s %s%s %s%s", cmd, \
2241592Srgrimes		*(file1) == '/' ? "" : curdir(), file1, \
2251592Srgrimes		*(file2) == '/' ? "" : curdir(), file2);
2261592Srgrimes#define LOGBYTES(cmd, file, cnt) \
2271592Srgrimes	if (logging > 1) { \
2281592Srgrimes		if (cnt == (off_t)-1) \
2291592Srgrimes		    syslog(LOG_INFO,"%s %s%s", cmd, \
2301592Srgrimes			*(file) == '/' ? "" : curdir(), file); \
2311592Srgrimes		else \
2321592Srgrimes		    syslog(LOG_INFO, "%s %s%s = %qd bytes", \
2331592Srgrimes			cmd, (*(file) == '/') ? "" : curdir(), file, cnt); \
2341592Srgrimes	}
2351592Srgrimes
23625283Sdavidn#ifdef VIRTUAL_HOSTING
23790148Simpstatic void	 inithosts(void);
23890148Simpstatic void	selecthost(union sockunion *);
23925283Sdavidn#endif
24090148Simpstatic void	 ack(char *);
24190148Simpstatic void	 sigurg(int);
24290148Simpstatic void	 myoob(void);
243109893Syarstatic int	 checkuser(char *, char *, int, char **);
24490148Simpstatic FILE	*dataconn(char *, off_t, char *);
24590148Simpstatic void	 dolog(struct sockaddr *);
24690148Simpstatic char	*curdir(void);
24790148Simpstatic void	 end_login(void);
24890148Simpstatic FILE	*getdatasock(char *);
249101537Syarstatic int	 guniquefd(char *, char **);
25090148Simpstatic void	 lostconn(int);
25190148Simpstatic void	 sigquit(int);
25290148Simpstatic int	 receive_data(FILE *, FILE *);
25390148Simpstatic int	 send_data(FILE *, FILE *, off_t, off_t, int);
2541592Srgrimesstatic struct passwd *
25590148Simp		 sgetpwnam(char *);
25690148Simpstatic char	*sgetsave(char *);
25790148Simpstatic void	 reapchild(int);
25890148Simpstatic void      logxfer(char *, off_t, time_t);
259100486Syarstatic char	*doublequote(char *);
260120059Sumestatic int	*socksetup(int, char *, const char *);
2611592Srgrimes
2621592Srgrimesstatic char *
26390148Simpcurdir(void)
2641592Srgrimes{
2651592Srgrimes	static char path[MAXPATHLEN+1+1];	/* path + '/' + '\0' */
2661592Srgrimes
2671592Srgrimes	if (getcwd(path, sizeof(path)-2) == NULL)
2681592Srgrimes		return ("");
2691592Srgrimes	if (path[1] != '\0')		/* special case for root dir. */
2701592Srgrimes		strcat(path, "/");
2711592Srgrimes	/* For guest account, skip / since it's chrooted */
2721592Srgrimes	return (guest ? path+1 : path);
2731592Srgrimes}
2741592Srgrimes
2751592Srgrimesint
27690148Simpmain(int argc, char *argv[], char **envp)
2771592Srgrimes{
2781592Srgrimes	int addrlen, ch, on = 1, tos;
2791592Srgrimes	char *cp, line[LINE_MAX];
2801592Srgrimes	FILE *fd;
28156668Sshin	int error;
28256668Sshin	char	*bindname = NULL;
283109742Syar	const char *bindport = "ftp";
28456668Sshin	int	family = AF_UNSPEC;
28589935Syar	struct sigaction sa;
2861592Srgrimes
28736105Sache	tzset();		/* in case no timezone database in ~ftp */
28889935Syar	sigemptyset(&sa.sa_mask);
28989935Syar	sa.sa_flags = SA_RESTART;
29036105Sache
29113139Speter#ifdef OLD_SETPROCTITLE
2921592Srgrimes	/*
2931592Srgrimes	 *  Save start and extent of argv for setproctitle.
2941592Srgrimes	 */
2951592Srgrimes	Argv = argv;
2961592Srgrimes	while (*envp)
2971592Srgrimes		envp++;
2981592Srgrimes	LastArgv = envp[-1] + strlen(envp[-1]);
29913139Speter#endif /* OLD_SETPROCTITLE */
3001592Srgrimes
3016740Sguido
302110037Syar	while ((ch = getopt(argc, argv,
303110037Syar	                    "46a:AdDEhlmMoOp:P:rRSt:T:u:UvW")) != -1) {
3041592Srgrimes		switch (ch) {
305100717Syar		case '4':
306120059Sume			family = (family == AF_INET6) ? AF_UNSPEC : AF_INET;
30715196Sdg			break;
30815196Sdg
309100717Syar		case '6':
310120059Sume			family = (family == AF_INET) ? AF_UNSPEC : AF_INET6;
311100717Syar			break;
312100717Syar
313100717Syar		case 'a':
314100717Syar			bindname = optarg;
315100717Syar			break;
316100717Syar
317100717Syar		case 'A':
318100717Syar			anon_only = 1;
319100717Syar			break;
320100717Syar
3211592Srgrimes		case 'd':
32276096Smarkm			ftpdebug++;
3231592Srgrimes			break;
3241592Srgrimes
325100717Syar		case 'D':
326100717Syar			daemon_mode++;
327100717Syar			break;
328100717Syar
32970102Sphk		case 'E':
33070102Sphk			noepsv = 1;
33170102Sphk			break;
33270102Sphk
333110037Syar		case 'h':
334110037Syar			hostinfo = 0;
335110037Syar			break;
336110037Syar
3371592Srgrimes		case 'l':
3381592Srgrimes			logging++;	/* > 1 == extra logging */
3391592Srgrimes			break;
3401592Srgrimes
341101537Syar		case 'm':
342101537Syar			noguestmod = 0;
343101537Syar			break;
344101537Syar
345100717Syar		case 'M':
346100717Syar			noguestmkd = 1;
347100717Syar			break;
348100717Syar
349100717Syar		case 'o':
350100717Syar			noretr = 1;
351100717Syar			break;
352100717Syar
353100717Syar		case 'O':
354100717Syar			noguestretr = 1;
355100717Syar			break;
356100717Syar
357100717Syar		case 'p':
358100717Syar			pid_file = optarg;
359100717Syar			break;
360100717Syar
361109742Syar		case 'P':
362109742Syar			bindport = optarg;
363109742Syar			break;
364109742Syar
36570102Sphk		case 'r':
36670102Sphk			readonly = 1;
36770102Sphk			break;
36870102Sphk
36917435Spst		case 'R':
37017435Spst			paranoid = 0;
3719933Spst			break;
3729933Spst
3736740Sguido		case 'S':
37417435Spst			stats++;
3756740Sguido			break;
37617435Spst
37717435Spst		case 't':
37817435Spst			timeout = atoi(optarg);
37917435Spst			if (maxtimeout < timeout)
38017435Spst				maxtimeout = timeout;
38117435Spst			break;
38217435Spst
383100717Syar		case 'T':
384100717Syar			maxtimeout = atoi(optarg);
385100717Syar			if (timeout > maxtimeout)
386100717Syar				timeout = maxtimeout;
38717435Spst			break;
38817435Spst
3891592Srgrimes		case 'u':
3901592Srgrimes		    {
3911592Srgrimes			long val = 0;
3921592Srgrimes
3931592Srgrimes			val = strtol(optarg, &optarg, 8);
3941592Srgrimes			if (*optarg != '\0' || val < 0)
3951592Srgrimes				warnx("bad value for -u");
3961592Srgrimes			else
3971592Srgrimes				defumask = val;
3981592Srgrimes			break;
3991592Srgrimes		    }
400100717Syar		case 'U':
401100717Syar			restricted_data_ports = 0;
40220042Storstenb			break;
4031592Srgrimes
4041592Srgrimes		case 'v':
405100720Syar			ftpdebug++;
4061592Srgrimes			break;
4071592Srgrimes
408102311Syar		case 'W':
409102311Syar			dowtmp = 0;
410102311Syar			break;
411102311Syar
4121592Srgrimes		default:
4131592Srgrimes			warnx("unknown flag -%c ignored", optopt);
4141592Srgrimes			break;
4151592Srgrimes		}
4161592Srgrimes	}
41715196Sdg
41825283Sdavidn#ifdef VIRTUAL_HOSTING
41925283Sdavidn	inithosts();
42025283Sdavidn#endif
4211592Srgrimes	(void) freopen(_PATH_DEVNULL, "w", stderr);
42215196Sdg
42315196Sdg	/*
42415196Sdg	 * LOG_NDELAY sets up the logging connection immediately,
42515196Sdg	 * necessary for anonymous ftp's that chroot and can't do it later.
42615196Sdg	 */
42715196Sdg	openlog("ftpd", LOG_PID | LOG_NDELAY, LOG_FTP);
42815196Sdg
42915196Sdg	if (daemon_mode) {
430120059Sume		int *ctl_sock, fd, maxfd = -1, nfds, i;
431120059Sume		fd_set defreadfds, readfds;
432120059Sume		pid_t pid;
43315196Sdg
43415196Sdg		/*
43515196Sdg		 * Detach from parent.
43615196Sdg		 */
43715196Sdg		if (daemon(1, 1) < 0) {
43815196Sdg			syslog(LOG_ERR, "failed to become a daemon");
43915196Sdg			exit(1);
44015196Sdg		}
44189935Syar		sa.sa_handler = reapchild;
44289935Syar		(void)sigaction(SIGCHLD, &sa, NULL);
44356668Sshin
44415196Sdg		/*
44515196Sdg		 * Open a socket, bind it to the FTP port, and start
44615196Sdg		 * listening.
44715196Sdg		 */
448120059Sume		ctl_sock = socksetup(family, bindname, bindport);
449120059Sume		if (ctl_sock == NULL)
45015196Sdg			exit(1);
451120059Sume
452120059Sume		FD_ZERO(&defreadfds);
453120059Sume		for (i = 1; i <= *ctl_sock; i++) {
454120059Sume			FD_SET(ctl_sock[i], &defreadfds);
455120059Sume			if (listen(ctl_sock[i], 32) < 0) {
456120059Sume				syslog(LOG_ERR, "control listen: %m");
457120059Sume				exit(1);
458120059Sume			}
459120059Sume			if (maxfd < ctl_sock[i])
460120059Sume				maxfd = ctl_sock[i];
46115196Sdg		}
462120059Sume
46315196Sdg		/*
46417483Sjulian		 * Atomically write process ID
46517483Sjulian		 */
46617483Sjulian		if (pid_file)
46799213Smaxim		{
46817483Sjulian			int fd;
46917483Sjulian			char buf[20];
47017483Sjulian
47117483Sjulian			fd = open(pid_file, O_CREAT | O_WRONLY | O_TRUNC
47217483Sjulian				| O_NONBLOCK | O_EXLOCK, 0644);
47346078Simp			if (fd < 0) {
47417483Sjulian				if (errno == EAGAIN)
47517483Sjulian					errx(1, "%s: file locked", pid_file);
47617483Sjulian				else
47717483Sjulian					err(1, "%s", pid_file);
47846078Simp			}
47917483Sjulian			snprintf(buf, sizeof(buf),
48017483Sjulian				"%lu\n", (unsigned long) getpid());
48117483Sjulian			if (write(fd, buf, strlen(buf)) < 0)
48217483Sjulian				err(1, "%s: write", pid_file);
48317483Sjulian			/* Leave the pid file open and locked */
48417483Sjulian		}
48517483Sjulian		/*
48615196Sdg		 * Loop forever accepting connection requests and forking off
48715196Sdg		 * children to handle them.
48815196Sdg		 */
48915196Sdg		while (1) {
490120059Sume			FD_COPY(&defreadfds, &readfds);
491120059Sume			nfds = select(maxfd + 1, &readfds, NULL, NULL, 0);
492120059Sume			if (nfds <= 0) {
493120059Sume				if (nfds < 0 && errno != EINTR)
494120059Sume					syslog(LOG_WARNING, "select: %m");
495120059Sume				continue;
496120059Sume			}
497120059Sume
498120059Sume			pid = -1;
499120059Sume                        for (i = 1; i <= *ctl_sock; i++)
500120059Sume				if (FD_ISSET(ctl_sock[i], &readfds)) {
501120059Sume					addrlen = sizeof(his_addr);
502120059Sume					fd = accept(ctl_sock[i],
503120059Sume					    (struct sockaddr *)&his_addr,
504120059Sume					    &addrlen);
505120059Sume					if ((pid = fork()) == 0) {
506120059Sume						/* child */
507120059Sume						(void) dup2(fd, 0);
508120059Sume						(void) dup2(fd, 1);
509120059Sume						close(ctl_sock[i]);
510120059Sume					} else
511120059Sume						close(fd);
512120059Sume				}
513120059Sume			if (pid == 0)
51415196Sdg				break;
51515196Sdg		}
51615196Sdg	} else {
51715196Sdg		addrlen = sizeof(his_addr);
51815196Sdg		if (getpeername(0, (struct sockaddr *)&his_addr, &addrlen) < 0) {
51915196Sdg			syslog(LOG_ERR, "getpeername (%s): %m",argv[0]);
52015196Sdg			exit(1);
52115196Sdg		}
52215196Sdg	}
52315196Sdg
52489935Syar	sa.sa_handler = SIG_DFL;
52589935Syar	(void)sigaction(SIGCHLD, &sa, NULL);
5261592Srgrimes
52789935Syar	sa.sa_handler = sigurg;
52889935Syar	sa.sa_flags = 0;		/* don't restart syscalls for SIGURG */
52989935Syar	(void)sigaction(SIGURG, &sa, NULL);
53089935Syar
53189935Syar	sigfillset(&sa.sa_mask);	/* block all signals in handler */
53289935Syar	sa.sa_flags = SA_RESTART;
53389935Syar	sa.sa_handler = sigquit;
53489935Syar	(void)sigaction(SIGHUP, &sa, NULL);
53589935Syar	(void)sigaction(SIGINT, &sa, NULL);
53689935Syar	(void)sigaction(SIGQUIT, &sa, NULL);
53789935Syar	(void)sigaction(SIGTERM, &sa, NULL);
53889935Syar
53989935Syar	sa.sa_handler = lostconn;
54089935Syar	(void)sigaction(SIGPIPE, &sa, NULL);
54189935Syar
54215196Sdg	addrlen = sizeof(ctrl_addr);
54315196Sdg	if (getsockname(0, (struct sockaddr *)&ctrl_addr, &addrlen) < 0) {
54415196Sdg		syslog(LOG_ERR, "getsockname (%s): %m",argv[0]);
54515196Sdg		exit(1);
54615196Sdg	}
547109742Syar	dataport = ntohs(ctrl_addr.su_port) - 1; /* as per RFC 959 */
54825283Sdavidn#ifdef VIRTUAL_HOSTING
54925283Sdavidn	/* select our identity from virtual host table */
55056668Sshin	selecthost(&ctrl_addr);
55125283Sdavidn#endif
55215196Sdg#ifdef IP_TOS
55356668Sshin	if (ctrl_addr.su_family == AF_INET)
55456668Sshin      {
55515196Sdg	tos = IPTOS_LOWDELAY;
556100612Syar	if (setsockopt(0, IPPROTO_IP, IP_TOS, &tos, sizeof(int)) < 0)
557100609Syar		syslog(LOG_WARNING, "control setsockopt (IP_TOS): %m");
55856668Sshin      }
55915196Sdg#endif
56035482Sdg	/*
56135482Sdg	 * Disable Nagle on the control channel so that we don't have to wait
56235482Sdg	 * for peer's ACK before issuing our next reply.
56335482Sdg	 */
56435482Sdg	if (setsockopt(0, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)) < 0)
565100609Syar		syslog(LOG_WARNING, "control setsockopt (TCP_NODELAY): %m");
56635482Sdg
56756668Sshin	data_source.su_port = htons(ntohs(ctrl_addr.su_port) - 1);
56815196Sdg
56917435Spst	/* set this here so klogin can use it... */
57031973Simp	(void)snprintf(ttyline, sizeof(ttyline), "ftp%d", getpid());
57117435Spst
5721592Srgrimes	/* Try to handle urgent data inline */
5731592Srgrimes#ifdef SO_OOBINLINE
574100612Syar	if (setsockopt(0, SOL_SOCKET, SO_OOBINLINE, &on, sizeof(on)) < 0)
575100609Syar		syslog(LOG_WARNING, "control setsockopt (SO_OOBINLINE): %m");
5761592Srgrimes#endif
5771592Srgrimes
5781592Srgrimes#ifdef	F_SETOWN
5791592Srgrimes	if (fcntl(fileno(stdin), F_SETOWN, getpid()) == -1)
5801592Srgrimes		syslog(LOG_ERR, "fcntl F_SETOWN: %m");
5811592Srgrimes#endif
58256668Sshin	dolog((struct sockaddr *)&his_addr);
5831592Srgrimes	/*
5841592Srgrimes	 * Set up default state
5851592Srgrimes	 */
5861592Srgrimes	data = -1;
5871592Srgrimes	type = TYPE_A;
5881592Srgrimes	form = FORM_N;
5891592Srgrimes	stru = STRU_F;
5901592Srgrimes	mode = MODE_S;
5911592Srgrimes	tmpline[0] = '\0';
5921592Srgrimes
5931592Srgrimes	/* If logins are disabled, print out the message. */
5941592Srgrimes	if ((fd = fopen(_PATH_NOLOGIN,"r")) != NULL) {
5951592Srgrimes		while (fgets(line, sizeof(line), fd) != NULL) {
5961592Srgrimes			if ((cp = strchr(line, '\n')) != NULL)
5971592Srgrimes				*cp = '\0';
5981592Srgrimes			lreply(530, "%s", line);
5991592Srgrimes		}
6001592Srgrimes		(void) fflush(stdout);
6011592Srgrimes		(void) fclose(fd);
6021592Srgrimes		reply(530, "System not available.");
6031592Srgrimes		exit(0);
6041592Srgrimes	}
60525283Sdavidn#ifdef VIRTUAL_HOSTING
60625283Sdavidn	if ((fd = fopen(thishost->welcome, "r")) != NULL) {
60725283Sdavidn#else
6081592Srgrimes	if ((fd = fopen(_PATH_FTPWELCOME, "r")) != NULL) {
60925283Sdavidn#endif
6101592Srgrimes		while (fgets(line, sizeof(line), fd) != NULL) {
6111592Srgrimes			if ((cp = strchr(line, '\n')) != NULL)
6121592Srgrimes				*cp = '\0';
6131592Srgrimes			lreply(220, "%s", line);
6141592Srgrimes		}
6151592Srgrimes		(void) fflush(stdout);
6161592Srgrimes		(void) fclose(fd);
6171592Srgrimes		/* reply(220,) must follow */
6181592Srgrimes	}
61925283Sdavidn#ifndef VIRTUAL_HOSTING
62027650Sdavidn	if ((hostname = malloc(MAXHOSTNAMELEN)) == NULL)
62176096Smarkm		fatalerror("Ran out of memory.");
62245422Sbrian	(void) gethostname(hostname, MAXHOSTNAMELEN - 1);
62345422Sbrian	hostname[MAXHOSTNAMELEN - 1] = '\0';
62425283Sdavidn#endif
625110037Syar	if (hostinfo)
626110037Syar		reply(220, "%s FTP server (%s) ready.", hostname, version);
627110037Syar	else
628110037Syar		reply(220, "FTP server ready.");
6291592Srgrimes	for (;;)
6301592Srgrimes		(void) yyparse();
6311592Srgrimes	/* NOTREACHED */
6321592Srgrimes}
6331592Srgrimes
6341592Srgrimesstatic void
63590148Simplostconn(int signo)
6361592Srgrimes{
6371592Srgrimes
63876096Smarkm	if (ftpdebug)
6391592Srgrimes		syslog(LOG_DEBUG, "lost connection");
64031329Scharnier	dologout(1);
6411592Srgrimes}
6421592Srgrimes
64389935Syarstatic void
64490148Simpsigquit(int signo)
64589935Syar{
64689935Syar
64789935Syar	syslog(LOG_ERR, "got signal %d", signo);
64889935Syar	dologout(1);
64989935Syar}
65089935Syar
65125283Sdavidn#ifdef VIRTUAL_HOSTING
6521592Srgrimes/*
65325283Sdavidn * read in virtual host tables (if they exist)
65425283Sdavidn */
65525283Sdavidn
65625283Sdavidnstatic void
65790148Simpinithosts(void)
65825283Sdavidn{
659100182Syar	int insert;
66099877Syar	size_t len;
66125283Sdavidn	FILE *fp;
66299877Syar	char *cp, *mp, *line;
66399877Syar	char *hostname;
664100182Syar	char *vhost, *anonuser, *statfile, *welcome, *loginmsg;
66525283Sdavidn	struct ftphost *hrp, *lhrp;
66656668Sshin	struct addrinfo hints, *res, *ai;
66725283Sdavidn
66825283Sdavidn	/*
66925283Sdavidn	 * Fill in the default host information
67025283Sdavidn	 */
67199877Syar	if ((hostname = malloc(MAXHOSTNAMELEN)) == NULL)
67276096Smarkm		fatalerror("Ran out of memory.");
67399877Syar	if (gethostname(hostname, MAXHOSTNAMELEN) < 0)
67499877Syar		hostname[0] = '\0';
67599877Syar	hostname[MAXHOSTNAMELEN - 1] = '\0';
67699877Syar	if ((hrp = malloc(sizeof(struct ftphost))) == NULL)
67799877Syar		fatalerror("Ran out of memory.");
67899877Syar	hrp->hostname = hostname;
67957124Sshin	hrp->hostinfo = NULL;
68056668Sshin
68156668Sshin	memset(&hints, 0, sizeof(hints));
68256668Sshin	hints.ai_flags = AI_CANONNAME;
68356668Sshin	hints.ai_family = AF_UNSPEC;
684102183Syar	if (getaddrinfo(hrp->hostname, NULL, &hints, &res) == 0)
68557124Sshin		hrp->hostinfo = res;
68625283Sdavidn	hrp->statfile = _PATH_FTPDSTATFILE;
68725283Sdavidn	hrp->welcome  = _PATH_FTPWELCOME;
68825283Sdavidn	hrp->loginmsg = _PATH_FTPLOGINMESG;
68925283Sdavidn	hrp->anonuser = "ftp";
69025283Sdavidn	hrp->next = NULL;
69125283Sdavidn	thishost = firsthost = lhrp = hrp;
69225283Sdavidn	if ((fp = fopen(_PATH_FTPHOSTS, "r")) != NULL) {
693102474Syar		int addrsize, gothost;
69456668Sshin		void *addr;
69556668Sshin		struct hostent *hp;
69656668Sshin
69799877Syar		while ((line = fgetln(fp, &len)) != NULL) {
69856668Sshin			int	i, hp_error;
69925283Sdavidn
70099877Syar			/* skip comments */
70199877Syar			if (line[0] == '#')
70225283Sdavidn				continue;
70399877Syar			if (line[len - 1] == '\n') {
70499877Syar				line[len - 1] = '\0';
70599877Syar				mp = NULL;
70699877Syar			} else {
70799877Syar				if ((mp = malloc(len + 1)) == NULL)
70899877Syar					fatalerror("Ran out of memory.");
70999877Syar				memcpy(mp, line, len);
71099877Syar				mp[len] = '\0';
71199877Syar				line = mp;
71225283Sdavidn			}
71325283Sdavidn			cp = strtok(line, " \t");
71499877Syar			/* skip empty lines */
71599877Syar			if (cp == NULL)
71699877Syar				goto nextline;
717100182Syar			vhost = cp;
71856668Sshin
719100182Syar			/* set defaults */
720100182Syar			anonuser = "ftp";
721100182Syar			statfile = _PATH_FTPDSTATFILE;
722100182Syar			welcome  = _PATH_FTPWELCOME;
723100182Syar			loginmsg = _PATH_FTPLOGINMESG;
724100182Syar
725100182Syar			/*
726100182Syar			 * Preparse the line so we can use its info
727100182Syar			 * for all the addresses associated with
728100182Syar			 * the virtual host name.
729100182Syar			 * Field 0, the virtual host name, is special:
730100182Syar			 * it's already parsed off and will be strdup'ed
731100182Syar			 * later, after we know its canonical form.
732100182Syar			 */
733100182Syar			for (i = 1; i < 5 && (cp = strtok(NULL, " \t")); i++)
734100182Syar				if (*cp != '-' && (cp = strdup(cp)))
735100182Syar					switch (i) {
736100182Syar					case 1:	/* anon user permissions */
737100182Syar						anonuser = cp;
738100182Syar						break;
739100182Syar					case 2: /* statistics file */
740100182Syar						statfile = cp;
741100182Syar						break;
742100182Syar					case 3: /* welcome message */
743100182Syar						welcome  = cp;
744100182Syar						break;
745100182Syar					case 4: /* login message */
746100182Syar						loginmsg = cp;
747100182Syar						break;
748100182Syar					default: /* programming error */
749100182Syar						abort();
750100182Syar						/* NOTREACHED */
751100182Syar					}
752100182Syar
75356668Sshin			hints.ai_flags = 0;
75456668Sshin			hints.ai_family = AF_UNSPEC;
75556668Sshin			hints.ai_flags = AI_PASSIVE;
756102183Syar			if (getaddrinfo(vhost, NULL, &hints, &res) != 0)
75799877Syar				goto nextline;
75856668Sshin			for (ai = res; ai != NULL && ai->ai_addr != NULL;
75962100Sdavidn			     ai = ai->ai_next) {
76056668Sshin
76162100Sdavidn			gothost = 0;
76225283Sdavidn			for (hrp = firsthost; hrp != NULL; hrp = hrp->next) {
76357124Sshin				struct addrinfo *hi;
76457124Sshin
76557124Sshin				for (hi = hrp->hostinfo; hi != NULL;
76657124Sshin				     hi = hi->ai_next)
76757124Sshin					if (hi->ai_addrlen == ai->ai_addrlen &&
76857124Sshin					    memcmp(hi->ai_addr,
76957124Sshin						   ai->ai_addr,
77062100Sdavidn						   ai->ai_addr->sa_len) == 0) {
77162100Sdavidn						gothost++;
77257124Sshin						break;
773100183Syar					}
77462100Sdavidn				if (gothost)
77562100Sdavidn					break;
77625283Sdavidn			}
77725283Sdavidn			if (hrp == NULL) {
77825283Sdavidn				if ((hrp = malloc(sizeof(struct ftphost))) == NULL)
77999877Syar					goto nextline;
780102183Syar				hrp->hostname = NULL;
781100182Syar				insert = 1;
782102473Syar			} else {
783106754Syar				if (hrp->hostinfo && hrp->hostinfo != res)
784102473Syar					freeaddrinfo(hrp->hostinfo);
785100182Syar				insert = 0; /* host already in the chain */
786102473Syar			}
78757124Sshin			hrp->hostinfo = res;
78857124Sshin
78925283Sdavidn			/*
79025283Sdavidn			 * determine hostname to use.
79156668Sshin			 * force defined name if there is a valid alias
79225283Sdavidn			 * otherwise fallback to primary hostname
79325283Sdavidn			 */
79456668Sshin			/* XXX: getaddrinfo() can't do alias check */
79557124Sshin			switch(hrp->hostinfo->ai_family) {
79656668Sshin			case AF_INET:
797100259Syar				addr = &((struct sockaddr_in *)hrp->hostinfo->ai_addr)->sin_addr;
798100259Syar				addrsize = sizeof(struct in_addr);
79956668Sshin				break;
80056668Sshin			case AF_INET6:
801100259Syar				addr = &((struct sockaddr_in6 *)hrp->hostinfo->ai_addr)->sin6_addr;
802100259Syar				addrsize = sizeof(struct in6_addr);
80356668Sshin				break;
80456668Sshin			default:
80556668Sshin				/* should not reach here */
806102473Syar				freeaddrinfo(hrp->hostinfo);
807102473Syar				if (insert)
808102473Syar					free(hrp); /*not in chain, can free*/
809102473Syar				else
810102473Syar					hrp->hostinfo = NULL; /*mark as blank*/
81199877Syar				goto nextline;
81256668Sshin				/* NOTREACHED */
81356668Sshin			}
814100612Syar			if ((hp = getipnodebyaddr(addr, addrsize,
81557124Sshin						  hrp->hostinfo->ai_family,
81656668Sshin						  &hp_error)) != NULL) {
817100182Syar				if (strcmp(vhost, hp->h_name) != 0) {
81825283Sdavidn					if (hp->h_aliases == NULL)
819100182Syar						vhost = hp->h_name;
82025283Sdavidn					else {
82125283Sdavidn						i = 0;
82225283Sdavidn						while (hp->h_aliases[i] &&
823100182Syar						       strcmp(vhost, hp->h_aliases[i]) != 0)
82425283Sdavidn							++i;
82525283Sdavidn						if (hp->h_aliases[i] == NULL)
826100182Syar							vhost = hp->h_name;
82725283Sdavidn					}
82825283Sdavidn				}
82925283Sdavidn			}
830102183Syar			if (hrp->hostname &&
831102183Syar			    strcmp(hrp->hostname, vhost) != 0) {
832102183Syar				free(hrp->hostname);
833102183Syar				hrp->hostname = NULL;
834102183Syar			}
835102183Syar			if (hrp->hostname == NULL &&
836102473Syar			    (hrp->hostname = strdup(vhost)) == NULL) {
837102473Syar				freeaddrinfo(hrp->hostinfo);
838102473Syar				hrp->hostinfo = NULL; /* mark as blank */
839102473Syar				if (hp)
840102473Syar					freehostent(hp);
841100182Syar				goto nextline;
842102473Syar			}
843100182Syar			hrp->anonuser = anonuser;
844100182Syar			hrp->statfile = statfile;
845100182Syar			hrp->welcome  = welcome;
846100182Syar			hrp->loginmsg = loginmsg;
847100182Syar			if (insert) {
848100182Syar				hrp->next  = NULL;
849100182Syar				lhrp->next = hrp;
850100182Syar				lhrp = hrp;
851100182Syar			}
852100263Syar			if (hp)
853100263Syar				freehostent(hp);
85456668Sshin		      }
85599877Syarnextline:
85699877Syar			if (mp)
85799877Syar				free(mp);
85825283Sdavidn		}
85925283Sdavidn		(void) fclose(fp);
86025283Sdavidn	}
86125283Sdavidn}
86225283Sdavidn
86325283Sdavidnstatic void
86490148Simpselecthost(union sockunion *su)
86525283Sdavidn{
86625283Sdavidn	struct ftphost	*hrp;
86756668Sshin	u_int16_t port;
86856668Sshin#ifdef INET6
86956668Sshin	struct in6_addr *mapped_in6 = NULL;
87056668Sshin#endif
87157124Sshin	struct addrinfo *hi;
87225283Sdavidn
87356668Sshin#ifdef INET6
87456668Sshin	/*
87556668Sshin	 * XXX IPv4 mapped IPv6 addr consideraton,
87656668Sshin	 * specified in rfc2373.
87756668Sshin	 */
87856668Sshin	if (su->su_family == AF_INET6 &&
87956668Sshin	    IN6_IS_ADDR_V4MAPPED(&su->su_sin6.sin6_addr))
88056668Sshin		mapped_in6 = &su->su_sin6.sin6_addr;
88156668Sshin#endif
88256668Sshin
88325283Sdavidn	hrp = thishost = firsthost;	/* default */
88456668Sshin	port = su->su_port;
88556668Sshin	su->su_port = 0;
88625283Sdavidn	while (hrp != NULL) {
88762100Sdavidn	    for (hi = hrp->hostinfo; hi != NULL; hi = hi->ai_next) {
88857124Sshin		if (memcmp(su, hi->ai_addr, hi->ai_addrlen) == 0) {
88925283Sdavidn			thishost = hrp;
89025283Sdavidn			break;
89125283Sdavidn		}
89256668Sshin#ifdef INET6
89356668Sshin		/* XXX IPv4 mapped IPv6 addr consideraton */
89457124Sshin		if (hi->ai_addr->sa_family == AF_INET && mapped_in6 != NULL &&
89556668Sshin		    (memcmp(&mapped_in6->s6_addr[12],
89657124Sshin			    &((struct sockaddr_in *)hi->ai_addr)->sin_addr,
89756668Sshin			    sizeof(struct in_addr)) == 0)) {
89856668Sshin			thishost = hrp;
89956668Sshin			break;
90056668Sshin		}
90156668Sshin#endif
90262100Sdavidn	    }
90362100Sdavidn	    hrp = hrp->next;
90425283Sdavidn	}
90556668Sshin	su->su_port = port;
90625283Sdavidn	/* setup static variables as appropriate */
90725283Sdavidn	hostname = thishost->hostname;
90825283Sdavidn	ftpuser = thishost->anonuser;
90925283Sdavidn}
91025283Sdavidn#endif
91125283Sdavidn
91225283Sdavidn/*
9131592Srgrimes * Helper function for sgetpwnam().
9141592Srgrimes */
9151592Srgrimesstatic char *
91690148Simpsgetsave(char *s)
9171592Srgrimes{
9181592Srgrimes	char *new = malloc((unsigned) strlen(s) + 1);
9191592Srgrimes
9201592Srgrimes	if (new == NULL) {
9211592Srgrimes		perror_reply(421, "Local resource failure: malloc");
9221592Srgrimes		dologout(1);
9231592Srgrimes		/* NOTREACHED */
9241592Srgrimes	}
9251592Srgrimes	(void) strcpy(new, s);
9261592Srgrimes	return (new);
9271592Srgrimes}
9281592Srgrimes
9291592Srgrimes/*
9301592Srgrimes * Save the result of a getpwnam.  Used for USER command, since
9311592Srgrimes * the data returned must not be clobbered by any other command
9321592Srgrimes * (e.g., globbing).
9331592Srgrimes */
9341592Srgrimesstatic struct passwd *
93590148Simpsgetpwnam(char *name)
9361592Srgrimes{
9371592Srgrimes	static struct passwd save;
9381592Srgrimes	struct passwd *p;
9391592Srgrimes
9401592Srgrimes	if ((p = getpwnam(name)) == NULL)
9411592Srgrimes		return (p);
9421592Srgrimes	if (save.pw_name) {
9431592Srgrimes		free(save.pw_name);
9441592Srgrimes		free(save.pw_passwd);
9451592Srgrimes		free(save.pw_gecos);
9461592Srgrimes		free(save.pw_dir);
9471592Srgrimes		free(save.pw_shell);
9481592Srgrimes	}
9491592Srgrimes	save = *p;
9501592Srgrimes	save.pw_name = sgetsave(p->pw_name);
9511592Srgrimes	save.pw_passwd = sgetsave(p->pw_passwd);
9521592Srgrimes	save.pw_gecos = sgetsave(p->pw_gecos);
9531592Srgrimes	save.pw_dir = sgetsave(p->pw_dir);
9541592Srgrimes	save.pw_shell = sgetsave(p->pw_shell);
9551592Srgrimes	return (&save);
9561592Srgrimes}
9571592Srgrimes
9581592Srgrimesstatic int login_attempts;	/* number of failed login attempts */
9591592Srgrimesstatic int askpasswd;		/* had user command, ask for passwd */
96064778Ssheldonhstatic char curname[MAXLOGNAME];	/* current USER name */
9611592Srgrimes
9621592Srgrimes/*
9631592Srgrimes * USER command.
9641592Srgrimes * Sets global passwd pointer pw if named account exists and is acceptable;
9651592Srgrimes * sets askpasswd if a PASS command is expected.  If logged in previously,
9661592Srgrimes * need to reset state.  If name is "ftp" or "anonymous", the name is not in
9671592Srgrimes * _PATH_FTPUSERS, and ftp account exists, set guest and pw, then just return.
9681592Srgrimes * If account doesn't exist, ask for passwd anyway.  Otherwise, check user
9691592Srgrimes * requesting login privileges.  Disallow anyone who does not have a standard
9701592Srgrimes * shell as returned by getusershell().  Disallow anyone mentioned in the file
9711592Srgrimes * _PATH_FTPUSERS to allow people such as root and uucp to be avoided.
9721592Srgrimes */
9731592Srgrimesvoid
97490148Simpuser(char *name)
9751592Srgrimes{
9761592Srgrimes	char *cp, *shell;
9771592Srgrimes
9781592Srgrimes	if (logged_in) {
9791592Srgrimes		if (guest) {
9801592Srgrimes			reply(530, "Can't change user from guest login.");
9811592Srgrimes			return;
98217435Spst		} else if (dochroot) {
98317435Spst			reply(530, "Can't change user from chroot user.");
98417435Spst			return;
9851592Srgrimes		}
9861592Srgrimes		end_login();
9871592Srgrimes	}
9881592Srgrimes
9891592Srgrimes	guest = 0;
9901592Srgrimes	if (strcmp(name, "ftp") == 0 || strcmp(name, "anonymous") == 0) {
991109893Syar		if (checkuser(_PATH_FTPUSERS, "ftp", 0, NULL) ||
992109893Syar		    checkuser(_PATH_FTPUSERS, "anonymous", 0, NULL))
9931592Srgrimes			reply(530, "User %s access denied.", name);
99425283Sdavidn#ifdef VIRTUAL_HOSTING
99525283Sdavidn		else if ((pw = sgetpwnam(thishost->anonuser)) != NULL) {
99625283Sdavidn#else
9971592Srgrimes		else if ((pw = sgetpwnam("ftp")) != NULL) {
99825283Sdavidn#endif
9991592Srgrimes			guest = 1;
10001592Srgrimes			askpasswd = 1;
10011592Srgrimes			reply(331,
10023938Spst			"Guest login ok, send your email address as password.");
10031592Srgrimes		} else
10041592Srgrimes			reply(530, "User %s unknown.", name);
10051592Srgrimes		if (!askpasswd && logging)
10061592Srgrimes			syslog(LOG_NOTICE,
10071592Srgrimes			    "ANONYMOUS FTP LOGIN REFUSED FROM %s", remotehost);
10081592Srgrimes		return;
10091592Srgrimes	}
101020042Storstenb	if (anon_only != 0) {
101120042Storstenb		reply(530, "Sorry, only anonymous ftp allowed.");
101220042Storstenb		return;
101320042Storstenb	}
101420042Storstenb
101517478Smarkm	if ((pw = sgetpwnam(name))) {
10161592Srgrimes		if ((shell = pw->pw_shell) == NULL || *shell == 0)
10171592Srgrimes			shell = _PATH_BSHELL;
10181592Srgrimes		while ((cp = getusershell()) != NULL)
10191592Srgrimes			if (strcmp(cp, shell) == 0)
10201592Srgrimes				break;
10211592Srgrimes		endusershell();
10221592Srgrimes
1023109893Syar		if (cp == NULL || checkuser(_PATH_FTPUSERS, name, 1, NULL)) {
10241592Srgrimes			reply(530, "User %s access denied.", name);
10251592Srgrimes			if (logging)
10261592Srgrimes				syslog(LOG_NOTICE,
10271592Srgrimes				    "FTP LOGIN REFUSED FROM %s, %s",
10281592Srgrimes				    remotehost, name);
10291592Srgrimes			pw = (struct passwd *) NULL;
10301592Srgrimes			return;
10311592Srgrimes		}
10321592Srgrimes	}
10331592Srgrimes	if (logging)
10341592Srgrimes		strncpy(curname, name, sizeof(curname)-1);
103588763Sache
103688763Sache	pwok = 0;
103779469Smarkm#ifdef USE_PAM
103879469Smarkm	/* XXX Kluge! The conversation mechanism needs to be fixed. */
103988763Sache#endif
104088763Sache	if (opiechallenge(&opiedata, name, opieprompt) == 0) {
104188763Sache		pwok = (pw != NULL) &&
104288763Sache		       opieaccessfile(remotehost) &&
104388763Sache		       opiealways(pw->pw_dir);
104488763Sache		reply(331, "Response to %s %s for %s.",
104588763Sache		      opieprompt, pwok ? "requested" : "required", name);
104688763Sache	} else {
104788763Sache		pwok = 1;
104884146Sache		reply(331, "Password required for %s.", name);
104988763Sache	}
10501592Srgrimes	askpasswd = 1;
10511592Srgrimes	/*
10521592Srgrimes	 * Delay before reading passwd after first failed
10531592Srgrimes	 * attempt to slow down passwd-guessing programs.
10541592Srgrimes	 */
10551592Srgrimes	if (login_attempts)
10561592Srgrimes		sleep((unsigned) login_attempts);
10571592Srgrimes}
10581592Srgrimes
10591592Srgrimes/*
1060109893Syar * Check if a user is in the file "fname",
1061109893Syar * return a pointer to a malloc'd string with the rest
1062109893Syar * of the matching line in "residue" if not NULL.
10631592Srgrimes */
10641592Srgrimesstatic int
1065109893Syarcheckuser(char *fname, char *name, int pwset, char **residue)
10661592Srgrimes{
10671592Srgrimes	FILE *fd;
10681592Srgrimes	int found = 0;
106999877Syar	size_t len;
107099877Syar	char *line, *mp, *p;
10711592Srgrimes
107217435Spst	if ((fd = fopen(fname, "r")) != NULL) {
107399877Syar		while (!found && (line = fgetln(fd, &len)) != NULL) {
107499877Syar			/* skip comments */
107599877Syar			if (line[0] == '#')
107699877Syar				continue;
107799877Syar			if (line[len - 1] == '\n') {
107899877Syar				line[len - 1] = '\0';
107999877Syar				mp = NULL;
108099877Syar			} else {
108199877Syar				if ((mp = malloc(len + 1)) == NULL)
108299877Syar					fatalerror("Ran out of memory.");
108399877Syar				memcpy(mp, line, len);
108499877Syar				mp[len] = '\0';
108599877Syar				line = mp;
108699877Syar			}
108799877Syar			/* avoid possible leading and trailing whitespace */
108899877Syar			p = strtok(line, " \t");
108999877Syar			/* skip empty lines */
109099877Syar			if (p == NULL)
109199877Syar				goto nextline;
109299877Syar			/*
109399877Syar			 * if first chr is '@', check group membership
109499877Syar			 */
109599877Syar			if (p[0] == '@') {
109699877Syar				int i = 0;
109799877Syar				struct group *grp;
109899877Syar
1099109893Syar				if (p[1] == '\0') /* single @ matches anyone */
110099877Syar					found = 1;
1101109893Syar				else {
1102109893Syar					if ((grp = getgrnam(p+1)) == NULL)
1103109893Syar						goto nextline;
1104109893Syar					/*
1105109893Syar					 * Check user's default group
1106109893Syar					 */
1107109893Syar					if (pwset && grp->gr_gid == pw->pw_gid)
1108109893Syar						found = 1;
1109109893Syar					/*
1110109893Syar					 * Check supplementary groups
1111109893Syar					 */
1112109893Syar					while (!found && grp->gr_mem[i])
1113109893Syar						found = strcmp(name,
1114109893Syar							grp->gr_mem[i++])
1115109893Syar							== 0;
1116109893Syar				}
11171592Srgrimes			}
111899877Syar			/*
111999877Syar			 * Otherwise, just check for username match
112099877Syar			 */
112199877Syar			else
112299877Syar				found = strcmp(p, name) == 0;
1123109893Syar			/*
1124109893Syar			 * Save the rest of line to "residue" if matched
1125109893Syar			 */
1126109893Syar			if (found && residue) {
1127109938Syar				if ((p = strtok(NULL, "")) != NULL)
1128109938Syar					p += strspn(p, " \t");
1129109938Syar				if (p && *p) {
1130109893Syar				 	if ((*residue = strdup(p)) == NULL)
1131109893Syar						fatalerror("Ran out of memory.");
1132109893Syar				} else
1133109893Syar					*residue = NULL;
1134109893Syar			}
113599877Syarnextline:
113699877Syar			if (mp)
113799877Syar				free(mp);
113899877Syar		}
11391592Srgrimes		(void) fclose(fd);
11401592Srgrimes	}
11411592Srgrimes	return (found);
11421592Srgrimes}
11431592Srgrimes
11441592Srgrimes/*
11451592Srgrimes * Terminate login as previous user, if any, resetting state;
11461592Srgrimes * used when USER command is given or login fails.
11471592Srgrimes */
11481592Srgrimesstatic void
114990148Simpend_login(void)
11501592Srgrimes{
115174874Smarkm#ifdef USE_PAM
115274874Smarkm	int e;
115374874Smarkm#endif
11541592Srgrimes
11551592Srgrimes	(void) seteuid((uid_t)0);
1156102311Syar	if (logged_in && dowtmp)
115789920Sume		ftpd_logwtmp(ttyline, "", NULL);
11581592Srgrimes	pw = NULL;
115925101Sdavidn#ifdef	LOGIN_CAP
116025101Sdavidn	setusercontext(NULL, getpwuid(0), (uid_t)0,
1161105877Srwatson		       LOGIN_SETPRIORITY|LOGIN_SETRESOURCES|LOGIN_SETUMASK|
1162105877Srwatson		       LOGIN_SETMAC);
116325101Sdavidn#endif
116474874Smarkm#ifdef USE_PAM
116574874Smarkm	if ((e = pam_setcred(pamh, PAM_DELETE_CRED)) != PAM_SUCCESS)
116674874Smarkm		syslog(LOG_ERR, "pam_setcred: %s", pam_strerror(pamh, e));
116774874Smarkm	if ((e = pam_close_session(pamh,0)) != PAM_SUCCESS)
116874874Smarkm		syslog(LOG_ERR, "pam_close_session: %s", pam_strerror(pamh, e));
116974874Smarkm	if ((e = pam_end(pamh, e)) != PAM_SUCCESS)
117074874Smarkm		syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e));
117174874Smarkm	pamh = NULL;
117274874Smarkm#endif
11731592Srgrimes	logged_in = 0;
11741592Srgrimes	guest = 0;
117517435Spst	dochroot = 0;
11761592Srgrimes}
11771592Srgrimes
117874874Smarkm#ifdef USE_PAM
117951433Smarkm
118051433Smarkm/*
118151433Smarkm * the following code is stolen from imap-uw PAM authentication module and
118251433Smarkm * login.c
118351433Smarkm */
118451433Smarkm#define COPY_STRING(s) (s ? strdup(s) : NULL)
118551433Smarkm
118651433Smarkmstruct cred_t {
118751433Smarkm	const char *uname;		/* user name */
118851433Smarkm	const char *pass;		/* password */
118951433Smarkm};
119051433Smarkmtypedef struct cred_t cred_t;
119151433Smarkm
119251433Smarkmstatic int
119351433Smarkmauth_conv(int num_msg, const struct pam_message **msg,
119451433Smarkm	  struct pam_response **resp, void *appdata)
119551433Smarkm{
119651433Smarkm	int i;
119751433Smarkm	cred_t *cred = (cred_t *) appdata;
119891244Sdes	struct pam_response *reply;
119951433Smarkm
120091244Sdes	reply = calloc(num_msg, sizeof *reply);
120191244Sdes	if (reply == NULL)
120291244Sdes		return PAM_BUF_ERR;
120391244Sdes
120451433Smarkm	for (i = 0; i < num_msg; i++) {
120551433Smarkm		switch (msg[i]->msg_style) {
120651433Smarkm		case PAM_PROMPT_ECHO_ON:	/* assume want user name */
120751433Smarkm			reply[i].resp_retcode = PAM_SUCCESS;
120851433Smarkm			reply[i].resp = COPY_STRING(cred->uname);
120951433Smarkm			/* PAM frees resp. */
121051433Smarkm			break;
121151433Smarkm		case PAM_PROMPT_ECHO_OFF:	/* assume want password */
121251433Smarkm			reply[i].resp_retcode = PAM_SUCCESS;
121351433Smarkm			reply[i].resp = COPY_STRING(cred->pass);
121451433Smarkm			/* PAM frees resp. */
121551433Smarkm			break;
121651433Smarkm		case PAM_TEXT_INFO:
121751433Smarkm		case PAM_ERROR_MSG:
121851433Smarkm			reply[i].resp_retcode = PAM_SUCCESS;
121951433Smarkm			reply[i].resp = NULL;
122051433Smarkm			break;
122151433Smarkm		default:			/* unknown message style */
122251433Smarkm			free(reply);
122351433Smarkm			return PAM_CONV_ERR;
122451433Smarkm		}
122551433Smarkm	}
122651433Smarkm
122751433Smarkm	*resp = reply;
122851433Smarkm	return PAM_SUCCESS;
122951433Smarkm}
123051433Smarkm
123151433Smarkm/*
123251433Smarkm * Attempt to authenticate the user using PAM.  Returns 0 if the user is
123351433Smarkm * authenticated, or 1 if not authenticated.  If some sort of PAM system
123451433Smarkm * error occurs (e.g., the "/etc/pam.conf" file is missing) then this
123551433Smarkm * function returns -1.  This can be used as an indication that we should
123651433Smarkm * fall back to a different authentication mechanism.
123751433Smarkm */
123851433Smarkmstatic int
123951433Smarkmauth_pam(struct passwd **ppw, const char *pass)
124051433Smarkm{
124151433Smarkm	pam_handle_t *pamh = NULL;
124251433Smarkm	const char *tmpl_user;
124351433Smarkm	const void *item;
124451433Smarkm	int rval;
124551433Smarkm	int e;
124651433Smarkm	cred_t auth_cred = { (*ppw)->pw_name, pass };
124751433Smarkm	struct pam_conv conv = { &auth_conv, &auth_cred };
124851433Smarkm
124951433Smarkm	e = pam_start("ftpd", (*ppw)->pw_name, &conv, &pamh);
125051433Smarkm	if (e != PAM_SUCCESS) {
125151433Smarkm		syslog(LOG_ERR, "pam_start: %s", pam_strerror(pamh, e));
125251433Smarkm		return -1;
125351433Smarkm	}
125451433Smarkm
125567007Sguido	e = pam_set_item(pamh, PAM_RHOST, remotehost);
125667007Sguido	if (e != PAM_SUCCESS) {
125767007Sguido		syslog(LOG_ERR, "pam_set_item(PAM_RHOST): %s",
125867007Sguido			pam_strerror(pamh, e));
125967007Sguido		return -1;
126067007Sguido	}
126167007Sguido
126251433Smarkm	e = pam_authenticate(pamh, 0);
126351433Smarkm	switch (e) {
126451433Smarkm	case PAM_SUCCESS:
126551433Smarkm		/*
126651433Smarkm		 * With PAM we support the concept of a "template"
126751433Smarkm		 * user.  The user enters a login name which is
126851433Smarkm		 * authenticated by PAM, usually via a remote service
126951433Smarkm		 * such as RADIUS or TACACS+.  If authentication
127051433Smarkm		 * succeeds, a different but related "template" name
127151433Smarkm		 * is used for setting the credentials, shell, and
127251433Smarkm		 * home directory.  The name the user enters need only
127351433Smarkm		 * exist on the remote authentication server, but the
127451433Smarkm		 * template name must be present in the local password
127551433Smarkm		 * database.
127651433Smarkm		 *
127751433Smarkm		 * This is supported by two various mechanisms in the
127851433Smarkm		 * individual modules.  However, from the application's
127951433Smarkm		 * point of view, the template user is always passed
128051433Smarkm		 * back as a changed value of the PAM_USER item.
128151433Smarkm		 */
128251433Smarkm		if ((e = pam_get_item(pamh, PAM_USER, &item)) ==
128351433Smarkm		    PAM_SUCCESS) {
128451433Smarkm			tmpl_user = (const char *) item;
128551433Smarkm			if (strcmp((*ppw)->pw_name, tmpl_user) != 0)
128651433Smarkm				*ppw = getpwnam(tmpl_user);
128751433Smarkm		} else
128851433Smarkm			syslog(LOG_ERR, "Couldn't get PAM_USER: %s",
128951433Smarkm			    pam_strerror(pamh, e));
129051433Smarkm		rval = 0;
129151433Smarkm		break;
129251433Smarkm
129351433Smarkm	case PAM_AUTH_ERR:
129451433Smarkm	case PAM_USER_UNKNOWN:
129551433Smarkm	case PAM_MAXTRIES:
129651433Smarkm		rval = 1;
129751433Smarkm		break;
129851433Smarkm
129951433Smarkm	default:
130074874Smarkm		syslog(LOG_ERR, "pam_authenticate: %s", pam_strerror(pamh, e));
130151433Smarkm		rval = -1;
130251433Smarkm		break;
130351433Smarkm	}
130451433Smarkm
130574874Smarkm	if (rval == 0) {
130674874Smarkm		e = pam_acct_mgmt(pamh, 0);
130774874Smarkm		if (e == PAM_NEW_AUTHTOK_REQD) {
130874874Smarkm			e = pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
130974874Smarkm			if (e != PAM_SUCCESS) {
131074874Smarkm				syslog(LOG_ERR, "pam_chauthtok: %s", pam_strerror(pamh, e));
131174874Smarkm				rval = 1;
131274874Smarkm			}
131374874Smarkm		} else if (e != PAM_SUCCESS) {
131474874Smarkm			rval = 1;
131574874Smarkm		}
131651433Smarkm	}
131774874Smarkm
131874874Smarkm	if (rval != 0) {
131974874Smarkm		if ((e = pam_end(pamh, e)) != PAM_SUCCESS) {
132074874Smarkm			syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e));
132174874Smarkm		}
132274874Smarkm		pamh = NULL;
132374874Smarkm	}
132451433Smarkm	return rval;
132551433Smarkm}
132651433Smarkm
132774874Smarkm#endif /* USE_PAM */
132851433Smarkm
13291592Srgrimesvoid
133090148Simppass(char *passwd)
13311592Srgrimes{
133217435Spst	int rval;
13331592Srgrimes	FILE *fd;
133425101Sdavidn#ifdef	LOGIN_CAP
133525101Sdavidn	login_cap_t *lc = NULL;
133625101Sdavidn#endif
133774874Smarkm#ifdef USE_PAM
133874874Smarkm	int e;
133974874Smarkm#endif
1340110036Syar	char *chrootdir;
1341109939Syar	char *residue = NULL;
134288763Sache	char *xpasswd;
13431592Srgrimes
13441592Srgrimes	if (logged_in || askpasswd == 0) {
13451592Srgrimes		reply(503, "Login with USER first.");
13461592Srgrimes		return;
13471592Srgrimes	}
13481592Srgrimes	askpasswd = 0;
13491592Srgrimes	if (!guest) {		/* "ftp" is only account allowed no password */
135017435Spst		if (pw == NULL) {
135117435Spst			rval = 1;	/* failure below */
135217435Spst			goto skip;
135317435Spst		}
135474874Smarkm#ifdef USE_PAM
135551433Smarkm		rval = auth_pam(&pw, passwd);
135689622Sache		if (rval >= 0) {
135789622Sache			opieunlock();
135817435Spst			goto skip;
135989622Sache		}
136089622Sache#endif
136188763Sache		if (opieverify(&opiedata, passwd) == 0)
136288763Sache			xpasswd = pw->pw_passwd;
136389622Sache		else if (pwok) {
136488763Sache			xpasswd = crypt(passwd, pw->pw_passwd);
136589622Sache			if (passwd[0] == '\0' && pw->pw_passwd[0] != '\0')
136689622Sache				xpasswd = ":";
136789622Sache		} else {
136888763Sache			rval = 1;
136988763Sache			goto skip;
137088763Sache		}
137188763Sache		rval = strcmp(pw->pw_passwd, xpasswd);
137289622Sache		if (pw->pw_expire && time(NULL) >= pw->pw_expire)
137317435Spst			rval = 1;	/* failure */
137417435Spstskip:
137517435Spst		/*
137617435Spst		 * If rval == 1, the user failed the authentication check
137751433Smarkm		 * above.  If rval == 0, either PAM or local authentication
137817435Spst		 * succeeded.
137917435Spst		 */
138017435Spst		if (rval) {
13811592Srgrimes			reply(530, "Login incorrect.");
1382110691Syar			if (logging) {
13831592Srgrimes				syslog(LOG_NOTICE,
1384110691Syar				    "FTP LOGIN FAILED FROM %s",
1385110691Syar				    remotehost);
1386110691Syar				syslog(LOG_AUTHPRIV | LOG_NOTICE,
13871592Srgrimes				    "FTP LOGIN FAILED FROM %s, %s",
13881592Srgrimes				    remotehost, curname);
1389110691Syar			}
13901592Srgrimes			pw = NULL;
13911592Srgrimes			if (login_attempts++ >= 5) {
13921592Srgrimes				syslog(LOG_NOTICE,
13931592Srgrimes				    "repeated login failures from %s",
13941592Srgrimes				    remotehost);
13951592Srgrimes				exit(0);
13961592Srgrimes			}
13971592Srgrimes			return;
13981592Srgrimes		}
13991592Srgrimes	}
14001592Srgrimes	login_attempts = 0;		/* this time successful */
14011592Srgrimes	if (setegid((gid_t)pw->pw_gid) < 0) {
14021592Srgrimes		reply(550, "Can't set gid.");
14031592Srgrimes		return;
14041592Srgrimes	}
140525101Sdavidn	/* May be overridden by login.conf */
140625101Sdavidn	(void) umask(defumask);
140725101Sdavidn#ifdef	LOGIN_CAP
140825674Sdavidn	if ((lc = login_getpwclass(pw)) != NULL) {
140925101Sdavidn		char	remote_ip[MAXHOSTNAMELEN];
141025101Sdavidn
141156668Sshin		getnameinfo((struct sockaddr *)&his_addr, his_addr.su_len,
141256668Sshin			remote_ip, sizeof(remote_ip) - 1, NULL, 0,
141399255Sume			NI_NUMERICHOST);
141425101Sdavidn		remote_ip[sizeof(remote_ip) - 1] = 0;
141525101Sdavidn		if (!auth_hostok(lc, remotehost, remote_ip)) {
141625101Sdavidn			syslog(LOG_INFO|LOG_AUTH,
141725101Sdavidn			    "FTP LOGIN FAILED (HOST) as %s: permission denied.",
141825101Sdavidn			    pw->pw_name);
141925101Sdavidn			reply(530, "Permission denied.\n");
142025101Sdavidn			pw = NULL;
142125101Sdavidn			return;
142225101Sdavidn		}
142325101Sdavidn		if (!auth_timeok(lc, time(NULL))) {
142425101Sdavidn			reply(530, "Login not available right now.\n");
142525101Sdavidn			pw = NULL;
142625101Sdavidn			return;
142725101Sdavidn		}
142825101Sdavidn	}
142925101Sdavidn	setusercontext(lc, pw, (uid_t)0,
143040310Sdes		LOGIN_SETLOGIN|LOGIN_SETGROUP|LOGIN_SETPRIORITY|
1431105877Srwatson		LOGIN_SETRESOURCES|LOGIN_SETUMASK|LOGIN_SETMAC);
143225101Sdavidn#else
143340310Sdes	setlogin(pw->pw_name);
14341592Srgrimes	(void) initgroups(pw->pw_name, pw->pw_gid);
143525101Sdavidn#endif
14361592Srgrimes
143774874Smarkm#ifdef USE_PAM
143874874Smarkm	if (pamh) {
143974874Smarkm		if ((e = pam_open_session(pamh, 0)) != PAM_SUCCESS) {
144074874Smarkm			syslog(LOG_ERR, "pam_open_session: %s", pam_strerror(pamh, e));
144174874Smarkm		} else if ((e = pam_setcred(pamh, PAM_ESTABLISH_CRED)) != PAM_SUCCESS) {
144274874Smarkm			syslog(LOG_ERR, "pam_setcred: %s", pam_strerror(pamh, e));
144374874Smarkm		}
144474874Smarkm	}
144574874Smarkm#endif
144674874Smarkm
14471592Srgrimes	/* open wtmp before chroot */
1448102311Syar	if (dowtmp)
1449102311Syar		ftpd_logwtmp(ttyline, pw->pw_name,
1450102311Syar		    (struct sockaddr *)&his_addr);
14511592Srgrimes	logged_in = 1;
14521592Srgrimes
145317435Spst	if (guest && stats && statfd < 0)
145425283Sdavidn#ifdef VIRTUAL_HOSTING
145525283Sdavidn		if ((statfd = open(thishost->statfile, O_WRONLY|O_APPEND)) < 0)
145625283Sdavidn#else
14576740Sguido		if ((statfd = open(_PATH_FTPDSTATFILE, O_WRONLY|O_APPEND)) < 0)
145825283Sdavidn#endif
14596740Sguido			stats = 0;
14606740Sguido
146125101Sdavidn	dochroot =
1462109939Syar		checkuser(_PATH_FTPCHROOT, pw->pw_name, 1, &residue)
146325101Sdavidn#ifdef	LOGIN_CAP	/* Allow login.conf configuration as well */
1464109893Syar		|| login_getcapbool(lc, "ftp-chroot", 0)
146525101Sdavidn#endif
1466109893Syar	;
1467110036Syar	chrootdir = NULL;
1468110036Syar	/*
1469110036Syar	 * For a chrooted local user,
1470110036Syar	 * a) see whether ftpchroot(5) specifies a chroot directory,
1471110036Syar	 * b) extract the directory pathname from the line,
1472110036Syar	 * c) expand it to the absolute pathname if necessary.
1473110036Syar	 */
1474110036Syar	if (dochroot && residue &&
1475117349Syar	    (chrootdir = strtok(residue, " \t")) != NULL) {
1476117349Syar		if (chrootdir[0] != '/')
1477117349Syar			asprintf(&chrootdir, "%s/%s", pw->pw_dir, chrootdir);
1478117349Syar		else
1479117349Syar			chrootdir = strdup(chrootdir); /* so it can be freed */
1480110036Syar		if (chrootdir == NULL)
1481110036Syar			fatalerror("Ran out of memory.");
1482110036Syar	}
1483110036Syar	if (guest || dochroot) {
14841592Srgrimes		/*
1485110036Syar		 * If no chroot directory set yet, use the login directory.
1486110036Syar		 * Copy it so it can be modified while pw->pw_dir stays intact.
14871592Srgrimes		 */
1488110036Syar		if (chrootdir == NULL &&
1489110036Syar		    (chrootdir = strdup(pw->pw_dir)) == NULL)
1490110036Syar			fatalerror("Ran out of memory.");
1491110036Syar		/*
1492110036Syar		 * Check for the "/chroot/./home" syntax,
1493110036Syar		 * separate the chroot and home directory pathnames.
1494110036Syar		 */
1495110036Syar		if ((homedir = strstr(chrootdir, "/./")) != NULL) {
1496110036Syar			*(homedir++) = '\0';	/* wipe '/' */
1497110036Syar			homedir++;		/* skip '.' */
1498110036Syar			/* so chrootdir can be freed later */
1499110036Syar			if ((homedir = strdup(homedir)) == NULL)
1500109893Syar				fatalerror("Ran out of memory.");
1501110036Syar		} else {
1502110036Syar			/*
1503110036Syar			 * We MUST do a chdir() after the chroot. Otherwise
1504110036Syar			 * the old current directory will be accessible as "."
1505110036Syar			 * outside the new root!
1506110036Syar			 */
1507110036Syar			homedir = "/";
1508109939Syar		}
1509110036Syar		/*
1510110036Syar		 * Finally, do chroot()
1511110036Syar		 */
1512110036Syar		if (chroot(chrootdir) < 0) {
151317435Spst			reply(550, "Can't change root.");
151417435Spst			goto bad;
151517435Spst		}
1516110036Syar	} else	/* real user w/o chroot */
1517110036Syar		homedir = pw->pw_dir;
1518110036Syar	/*
1519110036Syar	 * Set euid *before* doing chdir() so
1520110036Syar	 * a) the user won't be carried to a directory that he couldn't reach
1521110036Syar	 *    on his own due to no permission to upper path components,
1522110036Syar	 * b) NFS mounted homedirs w/restrictive permissions will be accessible
1523110036Syar	 *    (uid 0 has no root power over NFS if not mapped explicitly.)
1524110036Syar	 */
15251592Srgrimes	if (seteuid((uid_t)pw->pw_uid) < 0) {
15261592Srgrimes		reply(550, "Can't set uid.");
15271592Srgrimes		goto bad;
15281592Srgrimes	}
1529110036Syar	if (chdir(homedir) < 0) {
1530110036Syar		if (guest || dochroot) {
1531110036Syar			reply(550, "Can't change to base directory.");
1532110036Syar			goto bad;
1533110036Syar		} else {
1534110036Syar			if (chdir("/") < 0) {
1535110036Syar				reply(550, "Root is inaccessible.");
1536110036Syar				goto bad;
1537110036Syar			}
1538110036Syar			lreply(230, "No directory! Logging in with home=/");
1539110036Syar		}
1540110036Syar	}
15418696Sdg
15421592Srgrimes	/*
15431592Srgrimes	 * Display a login message, if it exists.
15441592Srgrimes	 * N.B. reply(230,) must follow the message.
15451592Srgrimes	 */
154625283Sdavidn#ifdef VIRTUAL_HOSTING
154725283Sdavidn	if ((fd = fopen(thishost->loginmsg, "r")) != NULL) {
154825283Sdavidn#else
15491592Srgrimes	if ((fd = fopen(_PATH_FTPLOGINMESG, "r")) != NULL) {
155025283Sdavidn#endif
15511592Srgrimes		char *cp, line[LINE_MAX];
15521592Srgrimes
15531592Srgrimes		while (fgets(line, sizeof(line), fd) != NULL) {
15541592Srgrimes			if ((cp = strchr(line, '\n')) != NULL)
15551592Srgrimes				*cp = '\0';
15561592Srgrimes			lreply(230, "%s", line);
15571592Srgrimes		}
15581592Srgrimes		(void) fflush(stdout);
15591592Srgrimes		(void) fclose(fd);
15601592Srgrimes	}
15611592Srgrimes	if (guest) {
15626740Sguido		if (ident != NULL)
15636740Sguido			free(ident);
156417433Spst		ident = strdup(passwd);
156517433Spst		if (ident == NULL)
156676096Smarkm			fatalerror("Ran out of memory.");
156717433Spst
15681592Srgrimes		reply(230, "Guest login ok, access restrictions apply.");
15691592Srgrimes#ifdef SETPROCTITLE
157025283Sdavidn#ifdef VIRTUAL_HOSTING
157125283Sdavidn		if (thishost != firsthost)
157225283Sdavidn			snprintf(proctitle, sizeof(proctitle),
157383308Smikeh				 "%s: anonymous(%s)/%s", remotehost, hostname,
157483308Smikeh				 passwd);
157525283Sdavidn		else
157625283Sdavidn#endif
157725283Sdavidn			snprintf(proctitle, sizeof(proctitle),
157883308Smikeh				 "%s: anonymous/%s", remotehost, passwd);
157913139Speter		setproctitle("%s", proctitle);
15801592Srgrimes#endif /* SETPROCTITLE */
15811592Srgrimes		if (logging)
15821592Srgrimes			syslog(LOG_INFO, "ANONYMOUS FTP LOGIN FROM %s, %s",
15831592Srgrimes			    remotehost, passwd);
15841592Srgrimes	} else {
158584841Syar		if (dochroot)
158684841Syar			reply(230, "User %s logged in, "
158784841Syar				   "access restrictions apply.", pw->pw_name);
158884841Syar		else
158984841Syar			reply(230, "User %s logged in.", pw->pw_name);
159025986Sdanny
15911592Srgrimes#ifdef SETPROCTITLE
15921592Srgrimes		snprintf(proctitle, sizeof(proctitle),
159384842Syar			 "%s: user/%s", remotehost, pw->pw_name);
159413139Speter		setproctitle("%s", proctitle);
15951592Srgrimes#endif /* SETPROCTITLE */
15961592Srgrimes		if (logging)
15971592Srgrimes			syslog(LOG_INFO, "FTP LOGIN FROM %s as %s",
15981592Srgrimes			    remotehost, pw->pw_name);
15991592Srgrimes	}
160025101Sdavidn#ifdef	LOGIN_CAP
160125101Sdavidn	login_close(lc);
160225101Sdavidn#endif
1603110036Syar	if (chrootdir)
1604110036Syar		free(chrootdir);
1605110036Syar	if (residue)
1606110036Syar		free(residue);
16071592Srgrimes	return;
16081592Srgrimesbad:
16091592Srgrimes	/* Forget all about it... */
161025101Sdavidn#ifdef	LOGIN_CAP
161125101Sdavidn	login_close(lc);
161225101Sdavidn#endif
1613110036Syar	if (chrootdir)
1614110036Syar		free(chrootdir);
1615110036Syar	if (residue)
1616110036Syar		free(residue);
16171592Srgrimes	end_login();
16181592Srgrimes}
16191592Srgrimes
16201592Srgrimesvoid
162190148Simpretrieve(char *cmd, char *name)
16221592Srgrimes{
16231592Srgrimes	FILE *fin, *dout;
16241592Srgrimes	struct stat st;
162590148Simp	int (*closefunc)(FILE *);
162636612Sjb	time_t start;
16271592Srgrimes
16281592Srgrimes	if (cmd == 0) {
16291592Srgrimes		fin = fopen(name, "r"), closefunc = fclose;
16301592Srgrimes		st.st_size = 0;
16311592Srgrimes	} else {
16321592Srgrimes		char line[BUFSIZ];
16331592Srgrimes
163431973Simp		(void) snprintf(line, sizeof(line), cmd, name), name = line;
16351592Srgrimes		fin = ftpd_popen(line, "r"), closefunc = ftpd_pclose;
16361592Srgrimes		st.st_size = -1;
16371592Srgrimes		st.st_blksize = BUFSIZ;
16381592Srgrimes	}
16391592Srgrimes	if (fin == NULL) {
16401592Srgrimes		if (errno != 0) {
16411592Srgrimes			perror_reply(550, name);
16421592Srgrimes			if (cmd == 0) {
16431592Srgrimes				LOGCMD("get", name);
16441592Srgrimes			}
16451592Srgrimes		}
16461592Srgrimes		return;
16471592Srgrimes	}
16481592Srgrimes	byte_count = -1;
1649110144Syar	if (cmd == 0) {
1650110144Syar		if (fstat(fileno(fin), &st) < 0) {
1651110144Syar			perror_reply(550, name);
1652110144Syar			goto done;
1653110144Syar		}
1654110144Syar		if (!S_ISREG(st.st_mode)) {
1655110144Syar			if (guest) {
1656110144Syar				reply(550, "%s: not a plain file.", name);
1657110144Syar				goto done;
1658110144Syar			}
1659110144Syar			st.st_size = -1;
1660110144Syar			/* st.st_blksize is set for all descriptor types */
1661110144Syar		}
16621592Srgrimes	}
16631592Srgrimes	if (restart_point) {
16641592Srgrimes		if (type == TYPE_A) {
16651592Srgrimes			off_t i, n;
16661592Srgrimes			int c;
16671592Srgrimes
16681592Srgrimes			n = restart_point;
16691592Srgrimes			i = 0;
16701592Srgrimes			while (i++ < n) {
16711592Srgrimes				if ((c=getc(fin)) == EOF) {
16721592Srgrimes					perror_reply(550, name);
16731592Srgrimes					goto done;
16741592Srgrimes				}
16751592Srgrimes				if (c == '\n')
16761592Srgrimes					i++;
16771592Srgrimes			}
16781592Srgrimes		} else if (lseek(fileno(fin), restart_point, L_SET) < 0) {
16791592Srgrimes			perror_reply(550, name);
16801592Srgrimes			goto done;
16811592Srgrimes		}
16821592Srgrimes	}
16831592Srgrimes	dout = dataconn(name, st.st_size, "w");
16841592Srgrimes	if (dout == NULL)
16851592Srgrimes		goto done;
16866740Sguido	time(&start);
16878240Swollman	send_data(fin, dout, st.st_blksize, st.st_size,
16888240Swollman		  restart_point == 0 && cmd == 0 && S_ISREG(st.st_mode));
16896740Sguido	if (cmd == 0 && guest && stats)
169017433Spst		logxfer(name, st.st_size, start);
16911592Srgrimes	(void) fclose(dout);
16921592Srgrimes	data = -1;
16931592Srgrimes	pdata = -1;
16941592Srgrimesdone:
16951592Srgrimes	if (cmd == 0)
16961592Srgrimes		LOGBYTES("get", name, byte_count);
16971592Srgrimes	(*closefunc)(fin);
16981592Srgrimes}
16991592Srgrimes
17001592Srgrimesvoid
170190148Simpstore(char *name, char *mode, int unique)
17021592Srgrimes{
1703101537Syar	int fd;
17041592Srgrimes	FILE *fout, *din;
170590148Simp	int (*closefunc)(FILE *);
17061592Srgrimes
1707101537Syar	if (*mode == 'a') {		/* APPE */
1708101537Syar		if (unique) {
1709101537Syar			/* Programming error */
1710101537Syar			syslog(LOG_ERR, "Internal: unique flag to APPE");
1711101537Syar			unique = 0;
1712101537Syar		}
1713101537Syar		if (guest && noguestmod) {
1714101537Syar			reply(550, "Appending to existing file denied");
1715101537Syar			goto err;
1716101537Syar		}
1717101537Syar		restart_point = 0;	/* not affected by preceding REST */
17181592Srgrimes	}
1719101537Syar	if (unique)			/* STOU overrides REST */
1720101537Syar		restart_point = 0;
1721101537Syar	if (guest && noguestmod) {
1722101537Syar		if (restart_point) {	/* guest STOR w/REST */
1723101537Syar			reply(550, "Modifying existing file denied");
1724101537Syar			goto err;
1725101537Syar		} else			/* treat guest STOR as STOU */
1726101537Syar			unique = 1;
1727101537Syar	}
17281592Srgrimes
17291592Srgrimes	if (restart_point)
1730101537Syar		mode = "r+";	/* so ASCII manual seek can work */
1731101537Syar	if (unique) {
1732101537Syar		if ((fd = guniquefd(name, &name)) < 0)
1733101537Syar			goto err;
1734101537Syar		fout = fdopen(fd, mode);
1735101537Syar	} else
1736101537Syar		fout = fopen(name, mode);
17371592Srgrimes	closefunc = fclose;
17381592Srgrimes	if (fout == NULL) {
17391592Srgrimes		perror_reply(553, name);
1740101537Syar		goto err;
17411592Srgrimes	}
17421592Srgrimes	byte_count = -1;
17431592Srgrimes	if (restart_point) {
17441592Srgrimes		if (type == TYPE_A) {
17451592Srgrimes			off_t i, n;
17461592Srgrimes			int c;
17471592Srgrimes
17481592Srgrimes			n = restart_point;
17491592Srgrimes			i = 0;
17501592Srgrimes			while (i++ < n) {
17511592Srgrimes				if ((c=getc(fout)) == EOF) {
17521592Srgrimes					perror_reply(550, name);
17531592Srgrimes					goto done;
17541592Srgrimes				}
17551592Srgrimes				if (c == '\n')
17561592Srgrimes					i++;
17571592Srgrimes			}
17581592Srgrimes			/*
17591592Srgrimes			 * We must do this seek to "current" position
17601592Srgrimes			 * because we are changing from reading to
17611592Srgrimes			 * writing.
17621592Srgrimes			 */
176382792Sache			if (fseeko(fout, (off_t)0, SEEK_CUR) < 0) {
17641592Srgrimes				perror_reply(550, name);
17651592Srgrimes				goto done;
17661592Srgrimes			}
17671592Srgrimes		} else if (lseek(fileno(fout), restart_point, L_SET) < 0) {
17681592Srgrimes			perror_reply(550, name);
17691592Srgrimes			goto done;
17701592Srgrimes		}
17711592Srgrimes	}
17721592Srgrimes	din = dataconn(name, (off_t)-1, "r");
17731592Srgrimes	if (din == NULL)
17741592Srgrimes		goto done;
17751592Srgrimes	if (receive_data(din, fout) == 0) {
17761592Srgrimes		if (unique)
17771592Srgrimes			reply(226, "Transfer complete (unique file name:%s).",
17781592Srgrimes			    name);
17791592Srgrimes		else
17801592Srgrimes			reply(226, "Transfer complete.");
17811592Srgrimes	}
17821592Srgrimes	(void) fclose(din);
17831592Srgrimes	data = -1;
17841592Srgrimes	pdata = -1;
17851592Srgrimesdone:
1786102566Syar	LOGBYTES(*mode == 'a' ? "append" : "put", name, byte_count);
17871592Srgrimes	(*closefunc)(fout);
1788101537Syar	return;
1789101537Syarerr:
1790101537Syar	LOGCMD(*mode == 'a' ? "append" : "put" , name);
1791101537Syar	return;
17921592Srgrimes}
17931592Srgrimes
17941592Srgrimesstatic FILE *
179590148Simpgetdatasock(char *mode)
17961592Srgrimes{
17971592Srgrimes	int on = 1, s, t, tries;
17981592Srgrimes
17991592Srgrimes	if (data >= 0)
18001592Srgrimes		return (fdopen(data, mode));
18011592Srgrimes	(void) seteuid((uid_t)0);
180256668Sshin
180356668Sshin	s = socket(data_dest.su_family, SOCK_STREAM, 0);
18041592Srgrimes	if (s < 0)
18051592Srgrimes		goto bad;
1806100612Syar	if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
1807100609Syar		syslog(LOG_WARNING, "data setsockopt (SO_REUSEADDR): %m");
18081592Srgrimes	/* anchor socket to avoid multi-homing problems */
180956668Sshin	data_source = ctrl_addr;
1810109742Syar	data_source.su_port = htons(dataport);
18111592Srgrimes	for (tries = 1; ; tries++) {
18121592Srgrimes		if (bind(s, (struct sockaddr *)&data_source,
181356668Sshin		    data_source.su_len) >= 0)
18141592Srgrimes			break;
18151592Srgrimes		if (errno != EADDRINUSE || tries > 10)
18161592Srgrimes			goto bad;
18171592Srgrimes		sleep(tries);
18181592Srgrimes	}
18191592Srgrimes	(void) seteuid((uid_t)pw->pw_uid);
18201592Srgrimes#ifdef IP_TOS
182156668Sshin	if (data_source.su_family == AF_INET)
182256668Sshin      {
18231592Srgrimes	on = IPTOS_THROUGHPUT;
1824100612Syar	if (setsockopt(s, IPPROTO_IP, IP_TOS, &on, sizeof(int)) < 0)
1825100609Syar		syslog(LOG_WARNING, "data setsockopt (IP_TOS): %m");
182656668Sshin      }
18271592Srgrimes#endif
18288240Swollman#ifdef TCP_NOPUSH
18298240Swollman	/*
18308240Swollman	 * Turn off push flag to keep sender TCP from sending short packets
18318240Swollman	 * at the boundaries of each write().  Should probably do a SO_SNDBUF
18328240Swollman	 * to set the send buffer size as well, but that may not be desirable
18338240Swollman	 * in heavy-load situations.
18348240Swollman	 */
18358240Swollman	on = 1;
1836100612Syar	if (setsockopt(s, IPPROTO_TCP, TCP_NOPUSH, &on, sizeof on) < 0)
1837100609Syar		syslog(LOG_WARNING, "data setsockopt (TCP_NOPUSH): %m");
18388240Swollman#endif
18398240Swollman#ifdef SO_SNDBUF
18408240Swollman	on = 65536;
1841100612Syar	if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, &on, sizeof on) < 0)
1842100609Syar		syslog(LOG_WARNING, "data setsockopt (SO_SNDBUF): %m");
18438240Swollman#endif
18448240Swollman
18451592Srgrimes	return (fdopen(s, mode));
18461592Srgrimesbad:
18471592Srgrimes	/* Return the real value of errno (close may change it) */
18481592Srgrimes	t = errno;
18491592Srgrimes	(void) seteuid((uid_t)pw->pw_uid);
18501592Srgrimes	(void) close(s);
18511592Srgrimes	errno = t;
18521592Srgrimes	return (NULL);
18531592Srgrimes}
18541592Srgrimes
18551592Srgrimesstatic FILE *
185690148Simpdataconn(char *name, off_t size, char *mode)
18571592Srgrimes{
18581592Srgrimes	char sizebuf[32];
18591592Srgrimes	FILE *file;
1860109611Scjc	int retry = 0, tos, conerrno;
18611592Srgrimes
18621592Srgrimes	file_size = size;
18631592Srgrimes	byte_count = 0;
18641592Srgrimes	if (size != (off_t) -1)
186531973Simp		(void) snprintf(sizebuf, sizeof(sizebuf), " (%qd bytes)", size);
18661592Srgrimes	else
186731973Simp		*sizebuf = '\0';
18681592Srgrimes	if (pdata >= 0) {
186956668Sshin		union sockunion from;
187086628Syar		int flags;
187156668Sshin		int s, fromlen = ctrl_addr.su_len;
187212532Sguido		struct timeval timeout;
187312532Sguido		fd_set set;
18741592Srgrimes
187512532Sguido		FD_ZERO(&set);
187612532Sguido		FD_SET(pdata, &set);
187712532Sguido
187812532Sguido		timeout.tv_usec = 0;
187912532Sguido		timeout.tv_sec = 120;
188012532Sguido
188186628Syar		/*
188286628Syar		 * Granted a socket is in the blocking I/O mode,
188386628Syar		 * accept() will block after a successful select()
188486628Syar		 * if the selected connection dies in between.
188586628Syar		 * Therefore set the non-blocking I/O flag here.
188686628Syar		 */
188786628Syar		if ((flags = fcntl(pdata, F_GETFL, 0)) == -1 ||
188886628Syar		    fcntl(pdata, F_SETFL, flags | O_NONBLOCK) == -1)
188986628Syar			goto pdata_err;
189086628Syar		if (select(pdata+1, &set, (fd_set *) 0, (fd_set *) 0, &timeout) <= 0 ||
189186628Syar		    (s = accept(pdata, (struct sockaddr *) &from, &fromlen)) < 0)
189286628Syar			goto pdata_err;
18931592Srgrimes		(void) close(pdata);
18941592Srgrimes		pdata = s;
189586628Syar		/*
1896101809Syar		 * Unset the inherited non-blocking I/O flag
1897101809Syar		 * on the child socket so stdio can work on it.
189886628Syar		 */
189986628Syar		if ((flags = fcntl(pdata, F_GETFL, 0)) == -1 ||
190086628Syar		    fcntl(pdata, F_SETFL, flags & ~O_NONBLOCK) == -1)
190186628Syar			goto pdata_err;
19021592Srgrimes#ifdef IP_TOS
190356668Sshin		if (from.su_family == AF_INET)
190456668Sshin	      {
190517435Spst		tos = IPTOS_THROUGHPUT;
1906100612Syar		if (setsockopt(s, IPPROTO_IP, IP_TOS, &tos, sizeof(int)) < 0)
1907100609Syar			syslog(LOG_WARNING, "pdata setsockopt (IP_TOS): %m");
190856668Sshin	      }
19091592Srgrimes#endif
19101592Srgrimes		reply(150, "Opening %s mode data connection for '%s'%s.",
19111592Srgrimes		     type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf);
19121592Srgrimes		return (fdopen(pdata, mode));
191386628Syarpdata_err:
191486628Syar		reply(425, "Can't open data connection.");
191586628Syar		(void) close(pdata);
191686628Syar		pdata = -1;
191786628Syar		return (NULL);
19181592Srgrimes	}
19191592Srgrimes	if (data >= 0) {
19201592Srgrimes		reply(125, "Using existing data connection for '%s'%s.",
19211592Srgrimes		    name, sizebuf);
19221592Srgrimes		usedefault = 1;
19231592Srgrimes		return (fdopen(data, mode));
19241592Srgrimes	}
19251592Srgrimes	if (usedefault)
19261592Srgrimes		data_dest = his_addr;
19271592Srgrimes	usedefault = 1;
1928109611Scjc	do {
1929109611Scjc		file = getdatasock(mode);
1930109611Scjc		if (file == NULL) {
1931109611Scjc			char hostbuf[BUFSIZ], portbuf[BUFSIZ];
1932109611Scjc			getnameinfo((struct sockaddr *)&data_source,
1933109611Scjc				data_source.su_len, hostbuf, sizeof(hostbuf) - 1,
1934109611Scjc				portbuf, sizeof(portbuf),
1935109611Scjc				NI_NUMERICHOST|NI_NUMERICSERV);
1936109611Scjc			reply(425, "Can't create data socket (%s,%s): %s.",
1937109611Scjc				hostbuf, portbuf, strerror(errno));
1938109611Scjc			return (NULL);
1939109611Scjc		}
1940109611Scjc		data = fileno(file);
1941109611Scjc		conerrno = 0;
1942109611Scjc		if (connect(data, (struct sockaddr *)&data_dest,
1943109611Scjc		    data_dest.su_len) == 0)
1944109611Scjc			break;
1945109611Scjc		conerrno = errno;
1946109611Scjc		(void) fclose(file);
1947109611Scjc		data = -1;
1948109611Scjc		if (conerrno == EADDRINUSE) {
19491592Srgrimes			sleep((unsigned) swaitint);
19501592Srgrimes			retry += swaitint;
1951109611Scjc		} else {
1952109611Scjc			break;
19531592Srgrimes		}
1954109611Scjc	} while (retry <= swaitmax);
1955109611Scjc	if (conerrno != 0) {
19561592Srgrimes		perror_reply(425, "Can't build data connection");
19571592Srgrimes		return (NULL);
19581592Srgrimes	}
19591592Srgrimes	reply(150, "Opening %s mode data connection for '%s'%s.",
19601592Srgrimes	     type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf);
19611592Srgrimes	return (file);
19621592Srgrimes}
19631592Srgrimes
19641592Srgrimes/*
19651592Srgrimes * Tranfer the contents of "instr" to "outstr" peer using the appropriate
19668240Swollman * encapsulation of the data subject to Mode, Structure, and Type.
19671592Srgrimes *
19681592Srgrimes * NB: Form isn't handled.
19691592Srgrimes */
197089935Syarstatic int
197190148Simpsend_data(FILE *instr, FILE *outstr, off_t blksize, off_t filesize, int isreg)
19721592Srgrimes{
1973122751Syar	int c, cp, filefd, netfd;
197470205Sdan	char *buf;
197570205Sdan	off_t cnt;
19761592Srgrimes
19771592Srgrimes	transflag++;
19781592Srgrimes	switch (type) {
19791592Srgrimes
19801592Srgrimes	case TYPE_A:
1981122751Syar		cp = '\0';
19821592Srgrimes		while ((c = getc(instr)) != EOF) {
198389935Syar			if (recvurg)
198489935Syar				goto got_oob;
19851592Srgrimes			byte_count++;
1986122751Syar			if (c == '\n' && cp != '\r') {
19871592Srgrimes				if (ferror(outstr))
19881592Srgrimes					goto data_err;
19891592Srgrimes				(void) putc('\r', outstr);
19901592Srgrimes			}
19911592Srgrimes			(void) putc(c, outstr);
1992122751Syar			cp = c;
19931592Srgrimes		}
199489935Syar		if (recvurg)
199589935Syar			goto got_oob;
19961592Srgrimes		fflush(outstr);
19971592Srgrimes		transflag = 0;
19981592Srgrimes		if (ferror(instr))
19991592Srgrimes			goto file_err;
20001592Srgrimes		if (ferror(outstr))
20011592Srgrimes			goto data_err;
20021592Srgrimes		reply(226, "Transfer complete.");
200389935Syar		return (0);
20041592Srgrimes
20051592Srgrimes	case TYPE_I:
20061592Srgrimes	case TYPE_L:
20078240Swollman		/*
20088240Swollman		 * isreg is only set if we are not doing restart and we
20098240Swollman		 * are sending a regular file
20108240Swollman		 */
20118240Swollman		netfd = fileno(outstr);
20128870Srgrimes		filefd = fileno(instr);
20138240Swollman
201470205Sdan		if (isreg) {
201570205Sdan
201670205Sdan			off_t offset;
201770205Sdan			int err;
201870205Sdan
201970205Sdan			err = cnt = offset = 0;
202070205Sdan
202190604Smaxim			while (err != -1 && filesize > 0) {
202290604Smaxim				err = sendfile(filefd, netfd, offset, 0,
202370205Sdan					(struct sf_hdtr *) NULL, &cnt, 0);
202499212Smaxim				/*
202599212Smaxim				 * Calculate byte_count before OOB processing.
202699212Smaxim				 * It can be used in myoob() later.
202799212Smaxim				 */
202899212Smaxim				byte_count += cnt;
202989935Syar				if (recvurg)
203089935Syar					goto got_oob;
203170205Sdan				offset += cnt;
203290604Smaxim				filesize -= cnt;
20338240Swollman
203470205Sdan				if (err == -1) {
203570205Sdan					if (!cnt)
203670205Sdan						goto oldway;
203770205Sdan
203870205Sdan					goto data_err;
203970205Sdan				}
204070205Sdan			}
204170205Sdan
204299318Sdan			transflag = 0;
20438240Swollman			reply(226, "Transfer complete.");
204489935Syar			return (0);
20458240Swollman		}
20468240Swollman
20478240Swollmanoldway:
20481592Srgrimes		if ((buf = malloc((u_int)blksize)) == NULL) {
20491592Srgrimes			transflag = 0;
20501592Srgrimes			perror_reply(451, "Local resource failure: malloc");
205189935Syar			return (-1);
20521592Srgrimes		}
20538870Srgrimes
20541592Srgrimes		while ((cnt = read(filefd, buf, (u_int)blksize)) > 0 &&
20551592Srgrimes		    write(netfd, buf, cnt) == cnt)
20561592Srgrimes			byte_count += cnt;
20571592Srgrimes		transflag = 0;
20581592Srgrimes		(void)free(buf);
20591592Srgrimes		if (cnt != 0) {
20601592Srgrimes			if (cnt < 0)
20611592Srgrimes				goto file_err;
20621592Srgrimes			goto data_err;
20631592Srgrimes		}
20641592Srgrimes		reply(226, "Transfer complete.");
206589935Syar		return (0);
20661592Srgrimes	default:
20671592Srgrimes		transflag = 0;
20681592Srgrimes		reply(550, "Unimplemented TYPE %d in send_data", type);
206989935Syar		return (-1);
20701592Srgrimes	}
20711592Srgrimes
20721592Srgrimesdata_err:
20731592Srgrimes	transflag = 0;
20741592Srgrimes	perror_reply(426, "Data connection");
207589935Syar	return (-1);
20761592Srgrimes
20771592Srgrimesfile_err:
20781592Srgrimes	transflag = 0;
20791592Srgrimes	perror_reply(551, "Error on input file");
208089935Syar	return (-1);
208189935Syar
208289935Syargot_oob:
208389935Syar	myoob();
208489935Syar	recvurg = 0;
208589935Syar	transflag = 0;
208689935Syar	return (-1);
20871592Srgrimes}
20881592Srgrimes
20891592Srgrimes/*
20901592Srgrimes * Transfer data from peer to "outstr" using the appropriate encapulation of
20911592Srgrimes * the data subject to Mode, Structure, and Type.
20921592Srgrimes *
20931592Srgrimes * N.B.: Form isn't handled.
20941592Srgrimes */
20951592Srgrimesstatic int
209690148Simpreceive_data(FILE *instr, FILE *outstr)
20971592Srgrimes{
20981592Srgrimes	int c;
209917433Spst	int cnt, bare_lfs;
21001592Srgrimes	char buf[BUFSIZ];
21011592Srgrimes
21021592Srgrimes	transflag++;
210317433Spst	bare_lfs = 0;
210417433Spst
21051592Srgrimes	switch (type) {
21061592Srgrimes
21071592Srgrimes	case TYPE_I:
21081592Srgrimes	case TYPE_L:
21091592Srgrimes		while ((cnt = read(fileno(instr), buf, sizeof(buf))) > 0) {
211089935Syar			if (recvurg)
211189935Syar				goto got_oob;
21121592Srgrimes			if (write(fileno(outstr), buf, cnt) != cnt)
21131592Srgrimes				goto file_err;
21141592Srgrimes			byte_count += cnt;
21151592Srgrimes		}
211689935Syar		if (recvurg)
211789935Syar			goto got_oob;
21181592Srgrimes		if (cnt < 0)
21191592Srgrimes			goto data_err;
21201592Srgrimes		transflag = 0;
21211592Srgrimes		return (0);
21221592Srgrimes
21231592Srgrimes	case TYPE_E:
21241592Srgrimes		reply(553, "TYPE E not implemented.");
21251592Srgrimes		transflag = 0;
21261592Srgrimes		return (-1);
21271592Srgrimes
21281592Srgrimes	case TYPE_A:
21291592Srgrimes		while ((c = getc(instr)) != EOF) {
213089935Syar			if (recvurg)
213189935Syar				goto got_oob;
21321592Srgrimes			byte_count++;
21331592Srgrimes			if (c == '\n')
21341592Srgrimes				bare_lfs++;
21351592Srgrimes			while (c == '\r') {
21361592Srgrimes				if (ferror(outstr))
21371592Srgrimes					goto data_err;
21381592Srgrimes				if ((c = getc(instr)) != '\n') {
21391592Srgrimes					(void) putc ('\r', outstr);
21401592Srgrimes					if (c == '\0' || c == EOF)
21411592Srgrimes						goto contin2;
21421592Srgrimes				}
21431592Srgrimes			}
21441592Srgrimes			(void) putc(c, outstr);
21451592Srgrimes	contin2:	;
21461592Srgrimes		}
214789935Syar		if (recvurg)
214889935Syar			goto got_oob;
21491592Srgrimes		fflush(outstr);
21501592Srgrimes		if (ferror(instr))
21511592Srgrimes			goto data_err;
21521592Srgrimes		if (ferror(outstr))
21531592Srgrimes			goto file_err;
21541592Srgrimes		transflag = 0;
21551592Srgrimes		if (bare_lfs) {
21561592Srgrimes			lreply(226,
21571592Srgrimes		"WARNING! %d bare linefeeds received in ASCII mode",
21581592Srgrimes			    bare_lfs);
21591592Srgrimes		(void)printf("   File may not have transferred correctly.\r\n");
21601592Srgrimes		}
21611592Srgrimes		return (0);
21621592Srgrimes	default:
21631592Srgrimes		reply(550, "Unimplemented TYPE %d in receive_data", type);
21641592Srgrimes		transflag = 0;
21651592Srgrimes		return (-1);
21661592Srgrimes	}
21671592Srgrimes
21681592Srgrimesdata_err:
21691592Srgrimes	transflag = 0;
21701592Srgrimes	perror_reply(426, "Data Connection");
21711592Srgrimes	return (-1);
21721592Srgrimes
21731592Srgrimesfile_err:
21741592Srgrimes	transflag = 0;
21751592Srgrimes	perror_reply(452, "Error writing file");
21761592Srgrimes	return (-1);
217789935Syar
217889935Syargot_oob:
217989935Syar	myoob();
218089935Syar	recvurg = 0;
218189935Syar	transflag = 0;
218289935Syar	return (-1);
21831592Srgrimes}
21841592Srgrimes
21851592Srgrimesvoid
218690148Simpstatfilecmd(char *filename)
21871592Srgrimes{
21881592Srgrimes	FILE *fin;
2189109382Syar	int atstart;
21901592Srgrimes	int c;
21911592Srgrimes	char line[LINE_MAX];
21921592Srgrimes
219325165Sdavidn	(void)snprintf(line, sizeof(line), _PATH_LS " -lgA %s", filename);
21941592Srgrimes	fin = ftpd_popen(line, "r");
21951592Srgrimes	lreply(211, "status of %s:", filename);
2196109382Syar	atstart = 1;
21971592Srgrimes	while ((c = getc(fin)) != EOF) {
21981592Srgrimes		if (c == '\n') {
21991592Srgrimes			if (ferror(stdout)){
22001592Srgrimes				perror_reply(421, "control connection");
22011592Srgrimes				(void) ftpd_pclose(fin);
22021592Srgrimes				dologout(1);
22031592Srgrimes				/* NOTREACHED */
22041592Srgrimes			}
22051592Srgrimes			if (ferror(fin)) {
22061592Srgrimes				perror_reply(551, filename);
22071592Srgrimes				(void) ftpd_pclose(fin);
22081592Srgrimes				return;
22091592Srgrimes			}
22101592Srgrimes			(void) putc('\r', stdout);
22111592Srgrimes		}
2212109382Syar		/*
2213109382Syar		 * RFC 959 says neutral text should be prepended before
2214109382Syar		 * a leading 3-digit number followed by whitespace, but
2215109382Syar		 * many ftp clients can be confused by any leading digits,
2216109382Syar		 * as a matter of fact.
2217109382Syar		 */
2218109382Syar		if (atstart && isdigit(c))
2219109382Syar			(void) putc(' ', stdout);
22201592Srgrimes		(void) putc(c, stdout);
2221109382Syar		atstart = (c == '\n');
22221592Srgrimes	}
22231592Srgrimes	(void) ftpd_pclose(fin);
22241592Srgrimes	reply(211, "End of Status");
22251592Srgrimes}
22261592Srgrimes
22271592Srgrimesvoid
222890148Simpstatcmd(void)
22291592Srgrimes{
223056668Sshin	union sockunion *su;
22311592Srgrimes	u_char *a, *p;
223299255Sume	char hname[NI_MAXHOST];
223356668Sshin	int ispassive;
22341592Srgrimes
2235110037Syar	if (hostinfo) {
2236110037Syar		lreply(211, "%s FTP server status:", hostname);
2237110037Syar		printf("     %s\r\n", version);
2238110037Syar	} else
2239110037Syar		lreply(211, "FTP server status:");
22401592Srgrimes	printf("     Connected to %s", remotehost);
224156668Sshin	if (!getnameinfo((struct sockaddr *)&his_addr, his_addr.su_len,
224299255Sume			 hname, sizeof(hname) - 1, NULL, 0, NI_NUMERICHOST)) {
224356668Sshin		if (strcmp(hname, remotehost) != 0)
224456668Sshin			printf(" (%s)", hname);
224556668Sshin	}
22461592Srgrimes	printf("\r\n");
22471592Srgrimes	if (logged_in) {
22481592Srgrimes		if (guest)
22491592Srgrimes			printf("     Logged in anonymously\r\n");
22501592Srgrimes		else
22511592Srgrimes			printf("     Logged in as %s\r\n", pw->pw_name);
22521592Srgrimes	} else if (askpasswd)
22531592Srgrimes		printf("     Waiting for password\r\n");
22541592Srgrimes	else
22551592Srgrimes		printf("     Waiting for user name\r\n");
22561592Srgrimes	printf("     TYPE: %s", typenames[type]);
22571592Srgrimes	if (type == TYPE_A || type == TYPE_E)
22581592Srgrimes		printf(", FORM: %s", formnames[form]);
22591592Srgrimes	if (type == TYPE_L)
2260103949Smike#if CHAR_BIT == 8
2261103949Smike		printf(" %d", CHAR_BIT);
22621592Srgrimes#else
22631592Srgrimes		printf(" %d", bytesize);	/* need definition! */
22641592Srgrimes#endif
22651592Srgrimes	printf("; STRUcture: %s; transfer MODE: %s\r\n",
22661592Srgrimes	    strunames[stru], modenames[mode]);
22671592Srgrimes	if (data != -1)
22681592Srgrimes		printf("     Data connection open\r\n");
22691592Srgrimes	else if (pdata != -1) {
227056668Sshin		ispassive = 1;
227156668Sshin		su = &pasv_addr;
22721592Srgrimes		goto printaddr;
22731592Srgrimes	} else if (usedefault == 0) {
227456668Sshin		ispassive = 0;
227556668Sshin		su = &data_dest;
22761592Srgrimesprintaddr:
22771592Srgrimes#define UC(b) (((int) b) & 0xff)
227856668Sshin		if (epsvall) {
227956668Sshin			printf("     EPSV only mode (EPSV ALL)\r\n");
228056668Sshin			goto epsvonly;
228156668Sshin		}
228256668Sshin
228356668Sshin		/* PORT/PASV */
228456668Sshin		if (su->su_family == AF_INET) {
228556668Sshin			a = (u_char *) &su->su_sin.sin_addr;
228656668Sshin			p = (u_char *) &su->su_sin.sin_port;
228756668Sshin			printf("     %s (%d,%d,%d,%d,%d,%d)\r\n",
228856668Sshin				ispassive ? "PASV" : "PORT",
228956668Sshin				UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]),
229056668Sshin				UC(p[0]), UC(p[1]));
229156668Sshin		}
229256668Sshin
229356668Sshin		/* LPRT/LPSV */
229456668Sshin	    {
229556668Sshin		int alen, af, i;
229656668Sshin
229756668Sshin		switch (su->su_family) {
229856668Sshin		case AF_INET:
229956668Sshin			a = (u_char *) &su->su_sin.sin_addr;
230056668Sshin			p = (u_char *) &su->su_sin.sin_port;
230156668Sshin			alen = sizeof(su->su_sin.sin_addr);
230256668Sshin			af = 4;
230356668Sshin			break;
230456668Sshin		case AF_INET6:
230556668Sshin			a = (u_char *) &su->su_sin6.sin6_addr;
230656668Sshin			p = (u_char *) &su->su_sin6.sin6_port;
230756668Sshin			alen = sizeof(su->su_sin6.sin6_addr);
230856668Sshin			af = 6;
230956668Sshin			break;
231056668Sshin		default:
231156668Sshin			af = 0;
231256668Sshin			break;
231356668Sshin		}
231456668Sshin		if (af) {
231556668Sshin			printf("     %s (%d,%d,", ispassive ? "LPSV" : "LPRT",
231656668Sshin				af, alen);
231756668Sshin			for (i = 0; i < alen; i++)
231856668Sshin				printf("%d,", UC(a[i]));
231956668Sshin			printf("%d,%d,%d)\r\n", 2, UC(p[0]), UC(p[1]));
232056668Sshin		}
232156668Sshin	    }
232256668Sshin
232356668Sshinepsvonly:;
232456668Sshin		/* EPRT/EPSV */
232556668Sshin	    {
232656668Sshin		int af;
232756668Sshin
232856668Sshin		switch (su->su_family) {
232956668Sshin		case AF_INET:
233056668Sshin			af = 1;
233156668Sshin			break;
233256668Sshin		case AF_INET6:
233356668Sshin			af = 2;
233456668Sshin			break;
233556668Sshin		default:
233656668Sshin			af = 0;
233756668Sshin			break;
233856668Sshin		}
233956668Sshin		if (af) {
234099255Sume			union sockunion tmp;
234199255Sume
234299255Sume			tmp = *su;
234399255Sume			if (tmp.su_family == AF_INET6)
234499255Sume				tmp.su_sin6.sin6_scope_id = 0;
234599255Sume			if (!getnameinfo((struct sockaddr *)&tmp, tmp.su_len,
234656668Sshin					hname, sizeof(hname) - 1, NULL, 0,
234756668Sshin					NI_NUMERICHOST)) {
234856668Sshin				printf("     %s |%d|%s|%d|\r\n",
234956668Sshin					ispassive ? "EPSV" : "EPRT",
235099255Sume					af, hname, htons(tmp.su_port));
235156668Sshin			}
235256668Sshin		}
235356668Sshin	    }
23541592Srgrimes#undef UC
23551592Srgrimes	} else
23561592Srgrimes		printf("     No data connection\r\n");
23571592Srgrimes	reply(211, "End of status");
23581592Srgrimes}
23591592Srgrimes
23601592Srgrimesvoid
236190148Simpfatalerror(char *s)
23621592Srgrimes{
23631592Srgrimes
23641592Srgrimes	reply(451, "Error in server: %s\n", s);
23651592Srgrimes	reply(221, "Closing connection due to server error.");
23661592Srgrimes	dologout(0);
23671592Srgrimes	/* NOTREACHED */
23681592Srgrimes}
23691592Srgrimes
23701592Srgrimesvoid
23711592Srgrimesreply(int n, const char *fmt, ...)
23721592Srgrimes{
23731592Srgrimes	va_list ap;
237490148Simp
23751592Srgrimes	va_start(ap, fmt);
23761592Srgrimes	(void)printf("%d ", n);
23771592Srgrimes	(void)vprintf(fmt, ap);
23781592Srgrimes	(void)printf("\r\n");
23791592Srgrimes	(void)fflush(stdout);
238076096Smarkm	if (ftpdebug) {
23811592Srgrimes		syslog(LOG_DEBUG, "<--- %d ", n);
23821592Srgrimes		vsyslog(LOG_DEBUG, fmt, ap);
23831592Srgrimes	}
23841592Srgrimes}
23851592Srgrimes
23861592Srgrimesvoid
23871592Srgrimeslreply(int n, const char *fmt, ...)
23881592Srgrimes{
23891592Srgrimes	va_list ap;
239090148Simp
23911592Srgrimes	va_start(ap, fmt);
23921592Srgrimes	(void)printf("%d- ", n);
23931592Srgrimes	(void)vprintf(fmt, ap);
23941592Srgrimes	(void)printf("\r\n");
23951592Srgrimes	(void)fflush(stdout);
239676096Smarkm	if (ftpdebug) {
23971592Srgrimes		syslog(LOG_DEBUG, "<--- %d- ", n);
23981592Srgrimes		vsyslog(LOG_DEBUG, fmt, ap);
23991592Srgrimes	}
24001592Srgrimes}
24011592Srgrimes
24021592Srgrimesstatic void
240390148Simpack(char *s)
24041592Srgrimes{
24051592Srgrimes
24061592Srgrimes	reply(250, "%s command successful.", s);
24071592Srgrimes}
24081592Srgrimes
24091592Srgrimesvoid
241090148Simpnack(char *s)
24111592Srgrimes{
24121592Srgrimes
24131592Srgrimes	reply(502, "%s command not implemented.", s);
24141592Srgrimes}
24151592Srgrimes
24161592Srgrimes/* ARGSUSED */
24171592Srgrimesvoid
241890148Simpyyerror(char *s)
24191592Srgrimes{
24201592Srgrimes	char *cp;
24211592Srgrimes
242217478Smarkm	if ((cp = strchr(cbuf,'\n')))
24231592Srgrimes		*cp = '\0';
24241592Srgrimes	reply(500, "'%s': command not understood.", cbuf);
24251592Srgrimes}
24261592Srgrimes
24271592Srgrimesvoid
242890148Simpdelete(char *name)
24291592Srgrimes{
24301592Srgrimes	struct stat st;
24311592Srgrimes
24321592Srgrimes	LOGCMD("delete", name);
2433100439Syar	if (lstat(name, &st) < 0) {
24341592Srgrimes		perror_reply(550, name);
24351592Srgrimes		return;
24361592Srgrimes	}
24371592Srgrimes	if ((st.st_mode&S_IFMT) == S_IFDIR) {
24381592Srgrimes		if (rmdir(name) < 0) {
24391592Srgrimes			perror_reply(550, name);
24401592Srgrimes			return;
24411592Srgrimes		}
24421592Srgrimes		goto done;
24431592Srgrimes	}
24441592Srgrimes	if (unlink(name) < 0) {
24451592Srgrimes		perror_reply(550, name);
24461592Srgrimes		return;
24471592Srgrimes	}
24481592Srgrimesdone:
24491592Srgrimes	ack("DELE");
24501592Srgrimes}
24511592Srgrimes
24521592Srgrimesvoid
245390148Simpcwd(char *path)
24541592Srgrimes{
24551592Srgrimes
24561592Srgrimes	if (chdir(path) < 0)
24571592Srgrimes		perror_reply(550, path);
24581592Srgrimes	else
24591592Srgrimes		ack("CWD");
24601592Srgrimes}
24611592Srgrimes
24621592Srgrimesvoid
246390148Simpmakedir(char *name)
24641592Srgrimes{
2465100878Syar	char *s;
24661592Srgrimes
24671592Srgrimes	LOGCMD("mkdir", name);
246899195Smdodd	if (guest && noguestmkd)
246999195Smdodd		reply(550, "%s: permission denied", name);
247099195Smdodd	else if (mkdir(name, 0777) < 0)
24711592Srgrimes		perror_reply(550, name);
2472100878Syar	else {
2473100878Syar		if ((s = doublequote(name)) == NULL)
2474100878Syar			fatalerror("Ran out of memory.");
2475100878Syar		reply(257, "\"%s\" directory created.", s);
2476100878Syar		free(s);
2477100878Syar	}
24781592Srgrimes}
24791592Srgrimes
24801592Srgrimesvoid
248190148Simpremovedir(char *name)
24821592Srgrimes{
24831592Srgrimes
24841592Srgrimes	LOGCMD("rmdir", name);
24851592Srgrimes	if (rmdir(name) < 0)
24861592Srgrimes		perror_reply(550, name);
24871592Srgrimes	else
24881592Srgrimes		ack("RMD");
24891592Srgrimes}
24901592Srgrimes
24911592Srgrimesvoid
249290148Simppwd(void)
24931592Srgrimes{
2494100486Syar	char *s, path[MAXPATHLEN + 1];
24951592Srgrimes
24961592Srgrimes	if (getwd(path) == (char *)NULL)
24971592Srgrimes		reply(550, "%s.", path);
2498100486Syar	else {
2499100486Syar		if ((s = doublequote(path)) == NULL)
2500100486Syar			fatalerror("Ran out of memory.");
2501100486Syar		reply(257, "\"%s\" is current directory.", s);
2502100486Syar		free(s);
2503100486Syar	}
25041592Srgrimes}
25051592Srgrimes
25061592Srgrimeschar *
250790148Simprenamefrom(char *name)
25081592Srgrimes{
25091592Srgrimes	struct stat st;
25101592Srgrimes
2511100439Syar	if (lstat(name, &st) < 0) {
25121592Srgrimes		perror_reply(550, name);
25131592Srgrimes		return ((char *)0);
25141592Srgrimes	}
25151592Srgrimes	reply(350, "File exists, ready for destination name");
25161592Srgrimes	return (name);
25171592Srgrimes}
25181592Srgrimes
25191592Srgrimesvoid
252090148Simprenamecmd(char *from, char *to)
25211592Srgrimes{
252217433Spst	struct stat st;
25231592Srgrimes
25241592Srgrimes	LOGCMD2("rename", from, to);
252517433Spst
252617433Spst	if (guest && (stat(to, &st) == 0)) {
252717433Spst		reply(550, "%s: permission denied", to);
252817433Spst		return;
252917433Spst	}
253017433Spst
25311592Srgrimes	if (rename(from, to) < 0)
25321592Srgrimes		perror_reply(550, "rename");
25331592Srgrimes	else
25341592Srgrimes		ack("RNTO");
25351592Srgrimes}
25361592Srgrimes
25371592Srgrimesstatic void
253890148Simpdolog(struct sockaddr *who)
25391592Srgrimes{
254056668Sshin	int error;
25411592Srgrimes
254256668Sshin	realhostname_sa(remotehost, sizeof(remotehost) - 1, who, who->sa_len);
254356668Sshin
25441592Srgrimes#ifdef SETPROCTITLE
254525283Sdavidn#ifdef VIRTUAL_HOSTING
254625283Sdavidn	if (thishost != firsthost)
254725283Sdavidn		snprintf(proctitle, sizeof(proctitle), "%s: connected (to %s)",
254825283Sdavidn			 remotehost, hostname);
254925283Sdavidn	else
255025283Sdavidn#endif
255125283Sdavidn		snprintf(proctitle, sizeof(proctitle), "%s: connected",
255225283Sdavidn			 remotehost);
255313139Speter	setproctitle("%s", proctitle);
25541592Srgrimes#endif /* SETPROCTITLE */
25551592Srgrimes
255625283Sdavidn	if (logging) {
255725283Sdavidn#ifdef VIRTUAL_HOSTING
255825283Sdavidn		if (thishost != firsthost)
255925283Sdavidn			syslog(LOG_INFO, "connection from %s (to %s)",
256025283Sdavidn			       remotehost, hostname);
256125283Sdavidn		else
256225283Sdavidn#endif
256356668Sshin		{
256456668Sshin			char	who_name[MAXHOSTNAMELEN];
256556668Sshin
256656668Sshin			error = getnameinfo(who, who->sa_len,
256756668Sshin					    who_name, sizeof(who_name) - 1,
256899255Sume					    NULL, 0, NI_NUMERICHOST);
256933782Seivind			syslog(LOG_INFO, "connection from %s (%s)", remotehost,
257056668Sshin			       error == 0 ? who_name : "");
257156668Sshin		}
257225283Sdavidn	}
25731592Srgrimes}
25741592Srgrimes
25751592Srgrimes/*
25761592Srgrimes * Record logout in wtmp file
25771592Srgrimes * and exit with supplied status.
25781592Srgrimes */
25791592Srgrimesvoid
258090148Simpdologout(int status)
25811592Srgrimes{
258222057Sdg	/*
258322057Sdg	 * Prevent reception of SIGURG from resulting in a resumption
258422057Sdg	 * back to the main program loop.
258522058Sdg	 */
258622057Sdg	transflag = 0;
25871592Srgrimes
2588102311Syar	if (logged_in && dowtmp) {
25891592Srgrimes		(void) seteuid((uid_t)0);
259089920Sume		ftpd_logwtmp(ttyline, "", NULL);
25911592Srgrimes	}
25921592Srgrimes	/* beware of flushing buffers after a SIGPIPE */
25931592Srgrimes	_exit(status);
25941592Srgrimes}
25951592Srgrimes
25961592Srgrimesstatic void
259790148Simpsigurg(int signo)
25981592Srgrimes{
259989935Syar
260089935Syar	recvurg = 1;
260189935Syar}
260289935Syar
260389935Syarstatic void
260490148Simpmyoob(void)
260589935Syar{
26061592Srgrimes	char *cp;
26071592Srgrimes
26081592Srgrimes	/* only process if transfer occurring */
26091592Srgrimes	if (!transflag)
26101592Srgrimes		return;
26111592Srgrimes	cp = tmpline;
26121592Srgrimes	if (getline(cp, 7, stdin) == NULL) {
26131592Srgrimes		reply(221, "You could at least say goodbye.");
26141592Srgrimes		dologout(0);
26151592Srgrimes	}
26161592Srgrimes	upper(cp);
26171592Srgrimes	if (strcmp(cp, "ABOR\r\n") == 0) {
26181592Srgrimes		tmpline[0] = '\0';
26191592Srgrimes		reply(426, "Transfer aborted. Data connection closed.");
26201592Srgrimes		reply(226, "Abort successful");
26211592Srgrimes	}
26221592Srgrimes	if (strcmp(cp, "STAT\r\n") == 0) {
262351192Smharo		tmpline[0] = '\0';
26241592Srgrimes		if (file_size != (off_t) -1)
26251592Srgrimes			reply(213, "Status: %qd of %qd bytes transferred",
26261592Srgrimes			    byte_count, file_size);
26271592Srgrimes		else
26281592Srgrimes			reply(213, "Status: %qd bytes transferred", byte_count);
26291592Srgrimes	}
26301592Srgrimes}
26311592Srgrimes
26321592Srgrimes/*
26331592Srgrimes * Note: a response of 425 is not mentioned as a possible response to
26341592Srgrimes *	the PASV command in RFC959. However, it has been blessed as
26351592Srgrimes *	a legitimate response by Jon Postel in a telephone conversation
26361592Srgrimes *	with Rick Adams on 25 Jan 89.
26371592Srgrimes */
26381592Srgrimesvoid
263990148Simppassive(void)
26401592Srgrimes{
2641100615Syar	int len, on;
26421592Srgrimes	char *p, *a;
26431592Srgrimes
264417433Spst	if (pdata >= 0)		/* close old port if one set */
264517433Spst		close(pdata);
264617433Spst
264756668Sshin	pdata = socket(ctrl_addr.su_family, SOCK_STREAM, 0);
26481592Srgrimes	if (pdata < 0) {
26491592Srgrimes		perror_reply(425, "Can't open passive connection");
26501592Srgrimes		return;
26511592Srgrimes	}
2652100615Syar	on = 1;
2653100615Syar	if (setsockopt(pdata, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
2654100615Syar		syslog(LOG_WARNING, "pdata setsockopt (SO_REUSEADDR): %m");
26559933Spst
265617433Spst	(void) seteuid((uid_t)0);
265717433Spst
265819903Spst#ifdef IP_PORTRANGE
265956668Sshin	if (ctrl_addr.su_family == AF_INET) {
2660100615Syar	    on = restricted_data_ports ? IP_PORTRANGE_HIGH
2661100615Syar				       : IP_PORTRANGE_DEFAULT;
266219903Spst
266319903Spst	    if (setsockopt(pdata, IPPROTO_IP, IP_PORTRANGE,
2664100612Syar			    &on, sizeof(on)) < 0)
266519903Spst		    goto pasv_error;
26661592Srgrimes	}
266719903Spst#endif
266860929Snsayer#ifdef IPV6_PORTRANGE
266960929Snsayer	if (ctrl_addr.su_family == AF_INET6) {
2670100615Syar	    on = restricted_data_ports ? IPV6_PORTRANGE_HIGH
2671100615Syar				       : IPV6_PORTRANGE_DEFAULT;
26729933Spst
267360929Snsayer	    if (setsockopt(pdata, IPPROTO_IPV6, IPV6_PORTRANGE,
2674100612Syar			    &on, sizeof(on)) < 0)
267560929Snsayer		    goto pasv_error;
267660929Snsayer	}
267760929Snsayer#endif
267860929Snsayer
267916033Speter	pasv_addr = ctrl_addr;
268056668Sshin	pasv_addr.su_port = 0;
268156668Sshin	if (bind(pdata, (struct sockaddr *)&pasv_addr, pasv_addr.su_len) < 0)
268216033Speter		goto pasv_error;
268317433Spst
268416033Speter	(void) seteuid((uid_t)pw->pw_uid);
268516033Speter
26861592Srgrimes	len = sizeof(pasv_addr);
26871592Srgrimes	if (getsockname(pdata, (struct sockaddr *) &pasv_addr, &len) < 0)
26881592Srgrimes		goto pasv_error;
26891592Srgrimes	if (listen(pdata, 1) < 0)
26901592Srgrimes		goto pasv_error;
269156668Sshin	if (pasv_addr.su_family == AF_INET)
269256668Sshin		a = (char *) &pasv_addr.su_sin.sin_addr;
269356668Sshin	else if (pasv_addr.su_family == AF_INET6 &&
269456668Sshin		 IN6_IS_ADDR_V4MAPPED(&pasv_addr.su_sin6.sin6_addr))
269556668Sshin		a = (char *) &pasv_addr.su_sin6.sin6_addr.s6_addr[12];
269656668Sshin	else
269756668Sshin		goto pasv_error;
269856668Sshin
269956668Sshin	p = (char *) &pasv_addr.su_port;
27001592Srgrimes
27011592Srgrimes#define UC(b) (((int) b) & 0xff)
27021592Srgrimes
27031592Srgrimes	reply(227, "Entering Passive Mode (%d,%d,%d,%d,%d,%d)", UC(a[0]),
27041592Srgrimes		UC(a[1]), UC(a[2]), UC(a[3]), UC(p[0]), UC(p[1]));
27051592Srgrimes	return;
27061592Srgrimes
27071592Srgrimespasv_error:
270817433Spst	(void) seteuid((uid_t)pw->pw_uid);
27091592Srgrimes	(void) close(pdata);
27101592Srgrimes	pdata = -1;
27111592Srgrimes	perror_reply(425, "Can't open passive connection");
27121592Srgrimes	return;
27131592Srgrimes}
27141592Srgrimes
27151592Srgrimes/*
271656668Sshin * Long Passive defined in RFC 1639.
271756668Sshin *     228 Entering Long Passive Mode
271856668Sshin *         (af, hal, h1, h2, h3,..., pal, p1, p2...)
271956668Sshin */
272056668Sshin
272156668Sshinvoid
272290148Simplong_passive(char *cmd, int pf)
272356668Sshin{
2724100615Syar	int len, on;
272556668Sshin	char *p, *a;
272656668Sshin
272756668Sshin	if (pdata >= 0)		/* close old port if one set */
272856668Sshin		close(pdata);
272956668Sshin
273056668Sshin	if (pf != PF_UNSPEC) {
273156668Sshin		if (ctrl_addr.su_family != pf) {
273256668Sshin			switch (ctrl_addr.su_family) {
273356668Sshin			case AF_INET:
273456668Sshin				pf = 1;
273556668Sshin				break;
273656668Sshin			case AF_INET6:
273756668Sshin				pf = 2;
273856668Sshin				break;
273956668Sshin			default:
274056668Sshin				pf = 0;
274156668Sshin				break;
274256668Sshin			}
274356668Sshin			/*
274456668Sshin			 * XXX
274556668Sshin			 * only EPRT/EPSV ready clients will understand this
274656668Sshin			 */
274756668Sshin			if (strcmp(cmd, "EPSV") == 0 && pf) {
274856668Sshin				reply(522, "Network protocol mismatch, "
274956668Sshin					"use (%d)", pf);
275056668Sshin			} else
275156668Sshin				reply(501, "Network protocol mismatch"); /*XXX*/
275256668Sshin
275356668Sshin			return;
275456668Sshin		}
275556668Sshin	}
275656668Sshin
275756668Sshin	pdata = socket(ctrl_addr.su_family, SOCK_STREAM, 0);
275856668Sshin	if (pdata < 0) {
275956668Sshin		perror_reply(425, "Can't open passive connection");
276056668Sshin		return;
276156668Sshin	}
2762100615Syar	on = 1;
2763100615Syar	if (setsockopt(pdata, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
2764100615Syar		syslog(LOG_WARNING, "pdata setsockopt (SO_REUSEADDR): %m");
276556668Sshin
276656668Sshin	(void) seteuid((uid_t)0);
276756668Sshin
276856668Sshin	pasv_addr = ctrl_addr;
276956668Sshin	pasv_addr.su_port = 0;
277056668Sshin	len = pasv_addr.su_len;
277156668Sshin
277260929Snsayer#ifdef IP_PORTRANGE
277360929Snsayer	if (ctrl_addr.su_family == AF_INET) {
2774100615Syar	    on = restricted_data_ports ? IP_PORTRANGE_HIGH
2775100615Syar				       : IP_PORTRANGE_DEFAULT;
277660929Snsayer
277760929Snsayer	    if (setsockopt(pdata, IPPROTO_IP, IP_PORTRANGE,
2778100612Syar			    &on, sizeof(on)) < 0)
277960929Snsayer		    goto pasv_error;
278060929Snsayer	}
278160929Snsayer#endif
278260929Snsayer#ifdef IPV6_PORTRANGE
278360929Snsayer	if (ctrl_addr.su_family == AF_INET6) {
2784100615Syar	    on = restricted_data_ports ? IPV6_PORTRANGE_HIGH
2785100615Syar				       : IPV6_PORTRANGE_DEFAULT;
278660929Snsayer
278760929Snsayer	    if (setsockopt(pdata, IPPROTO_IPV6, IPV6_PORTRANGE,
2788100612Syar			    &on, sizeof(on)) < 0)
278960929Snsayer		    goto pasv_error;
279060929Snsayer	}
279160929Snsayer#endif
279260929Snsayer
279356668Sshin	if (bind(pdata, (struct sockaddr *)&pasv_addr, len) < 0)
279456668Sshin		goto pasv_error;
279556668Sshin
279656668Sshin	(void) seteuid((uid_t)pw->pw_uid);
279756668Sshin
279856668Sshin	if (getsockname(pdata, (struct sockaddr *) &pasv_addr, &len) < 0)
279956668Sshin		goto pasv_error;
280056668Sshin	if (listen(pdata, 1) < 0)
280156668Sshin		goto pasv_error;
280256668Sshin
280356668Sshin#define UC(b) (((int) b) & 0xff)
280456668Sshin
280556668Sshin	if (strcmp(cmd, "LPSV") == 0) {
280656668Sshin		p = (char *)&pasv_addr.su_port;
280756668Sshin		switch (pasv_addr.su_family) {
280856668Sshin		case AF_INET:
280956668Sshin			a = (char *) &pasv_addr.su_sin.sin_addr;
281056668Sshin		v4_reply:
281156668Sshin			reply(228,
281256668Sshin"Entering Long Passive Mode (%d,%d,%d,%d,%d,%d,%d,%d,%d)",
281356668Sshin			      4, 4, UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]),
281456668Sshin			      2, UC(p[0]), UC(p[1]));
281556668Sshin			return;
281656668Sshin		case AF_INET6:
281756668Sshin			if (IN6_IS_ADDR_V4MAPPED(&pasv_addr.su_sin6.sin6_addr)) {
281856668Sshin				a = (char *) &pasv_addr.su_sin6.sin6_addr.s6_addr[12];
281956668Sshin				goto v4_reply;
282056668Sshin			}
282156668Sshin			a = (char *) &pasv_addr.su_sin6.sin6_addr;
282256668Sshin			reply(228,
282356668Sshin"Entering Long Passive Mode "
282456668Sshin"(%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d)",
282556668Sshin			      6, 16, UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]),
282656668Sshin			      UC(a[4]), UC(a[5]), UC(a[6]), UC(a[7]),
282756668Sshin			      UC(a[8]), UC(a[9]), UC(a[10]), UC(a[11]),
282856668Sshin			      UC(a[12]), UC(a[13]), UC(a[14]), UC(a[15]),
282956668Sshin			      2, UC(p[0]), UC(p[1]));
283056668Sshin			return;
283156668Sshin		}
283256668Sshin	} else if (strcmp(cmd, "EPSV") == 0) {
283356668Sshin		switch (pasv_addr.su_family) {
283456668Sshin		case AF_INET:
283556668Sshin		case AF_INET6:
283656668Sshin			reply(229, "Entering Extended Passive Mode (|||%d|)",
283756668Sshin				ntohs(pasv_addr.su_port));
283856668Sshin			return;
283956668Sshin		}
284056668Sshin	} else {
284156668Sshin		/* more proper error code? */
284256668Sshin	}
284356668Sshin
284456668Sshinpasv_error:
284556668Sshin	(void) seteuid((uid_t)pw->pw_uid);
284656668Sshin	(void) close(pdata);
284756668Sshin	pdata = -1;
284856668Sshin	perror_reply(425, "Can't open passive connection");
284956668Sshin	return;
285056668Sshin}
285156668Sshin
285256668Sshin/*
2853101537Syar * Generate unique name for file with basename "local"
2854101537Syar * and open the file in order to avoid possible races.
2855101537Syar * Try "local" first, then "local.1", "local.2" etc, up to "local.99".
2856101537Syar * Return descriptor to the file, set "name" to its name.
2857101537Syar *
28581592Srgrimes * Generates failure reply on error.
28591592Srgrimes */
2860101537Syarstatic int
2861101537Syarguniquefd(char *local, char **name)
28621592Srgrimes{
28631592Srgrimes	static char new[MAXPATHLEN];
28641592Srgrimes	struct stat st;
2865101537Syar	char *cp;
28661592Srgrimes	int count;
2867101537Syar	int fd;
28681592Srgrimes
28691592Srgrimes	cp = strrchr(local, '/');
28701592Srgrimes	if (cp)
28711592Srgrimes		*cp = '\0';
28721592Srgrimes	if (stat(cp ? local : ".", &st) < 0) {
28731592Srgrimes		perror_reply(553, cp ? local : ".");
2874101537Syar		return (-1);
28751592Srgrimes	}
2876101537Syar	if (cp) {
2877101537Syar		/*
2878101537Syar		 * Let not overwrite dirname with counter suffix.
2879101537Syar		 * -4 is for /nn\0
2880101537Syar		 * In this extreme case dot won't be put in front of suffix.
2881101537Syar		 */
2882101537Syar		if (strlen(local) > sizeof(new) - 4) {
2883101537Syar			reply(553, "Pathname too long");
2884101537Syar			return (-1);
2885101537Syar		}
28861592Srgrimes		*cp = '/';
2887101537Syar	}
288831973Simp	/* -4 is for the .nn<null> we put on the end below */
288931973Simp	(void) snprintf(new, sizeof(new) - 4, "%s", local);
28901592Srgrimes	cp = new + strlen(new);
2891101537Syar	/*
2892101537Syar	 * Don't generate dotfile unless requested explicitly.
2893101537Syar	 * This covers the case when basename gets truncated off
2894101537Syar	 * by buffer size.
2895101537Syar	 */
2896101537Syar	if (cp > new && cp[-1] != '/')
2897101537Syar		*cp++ = '.';
2898101537Syar	for (count = 0; count < 100; count++) {
2899101537Syar		/* At count 0 try unmodified name */
2900101537Syar		if (count)
2901101537Syar			(void)sprintf(cp, "%d", count);
2902101537Syar		if ((fd = open(count ? new : local,
2903101537Syar		    O_RDWR | O_CREAT | O_EXCL, 0666)) >= 0) {
2904101537Syar			*name = count ? new : local;
2905101537Syar			return (fd);
2906101537Syar		}
2907110046Syar		if (errno != EEXIST) {
2908110307Syar			perror_reply(553, count ? new : local);
2909110046Syar			return (-1);
2910110046Syar		}
29111592Srgrimes	}
29121592Srgrimes	reply(452, "Unique file name cannot be created.");
2913101537Syar	return (-1);
29141592Srgrimes}
29151592Srgrimes
29161592Srgrimes/*
29171592Srgrimes * Format and send reply containing system error number.
29181592Srgrimes */
29191592Srgrimesvoid
292090148Simpperror_reply(int code, char *string)
29211592Srgrimes{
29221592Srgrimes
29231592Srgrimes	reply(code, "%s: %s.", string, strerror(errno));
29241592Srgrimes}
29251592Srgrimes
29261592Srgrimesstatic char *onefile[] = {
29271592Srgrimes	"",
29281592Srgrimes	0
29291592Srgrimes};
29301592Srgrimes
29311592Srgrimesvoid
293290148Simpsend_file_list(char *whichf)
29331592Srgrimes{
29341592Srgrimes	struct stat st;
29351592Srgrimes	DIR *dirp = NULL;
29361592Srgrimes	struct dirent *dir;
29371592Srgrimes	FILE *dout = NULL;
29381592Srgrimes	char **dirlist, *dirname;
29391592Srgrimes	int simple = 0;
29401592Srgrimes	int freeglob = 0;
29411592Srgrimes	glob_t gl;
29421592Srgrimes
29431592Srgrimes	if (strpbrk(whichf, "~{[*?") != NULL) {
2944100222Smikeh		int flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE;
29451592Srgrimes
29461592Srgrimes		memset(&gl, 0, sizeof(gl));
294774470Sjlemon		gl.gl_matchc = MAXGLOBARGS;
294880525Smikeh		flags |= GLOB_LIMIT;
29491592Srgrimes		freeglob = 1;
29501592Srgrimes		if (glob(whichf, flags, 0, &gl)) {
29511592Srgrimes			reply(550, "not found");
29521592Srgrimes			goto out;
29531592Srgrimes		} else if (gl.gl_pathc == 0) {
29541592Srgrimes			errno = ENOENT;
29551592Srgrimes			perror_reply(550, whichf);
29561592Srgrimes			goto out;
29571592Srgrimes		}
29581592Srgrimes		dirlist = gl.gl_pathv;
29591592Srgrimes	} else {
29601592Srgrimes		onefile[0] = whichf;
29611592Srgrimes		dirlist = onefile;
29621592Srgrimes		simple = 1;
29631592Srgrimes	}
29641592Srgrimes
296517478Smarkm	while ((dirname = *dirlist++)) {
29661592Srgrimes		if (stat(dirname, &st) < 0) {
29671592Srgrimes			/*
29681592Srgrimes			 * If user typed "ls -l", etc, and the client
29691592Srgrimes			 * used NLST, do what the user meant.
29701592Srgrimes			 */
29711592Srgrimes			if (dirname[0] == '-' && *dirlist == NULL &&
29721592Srgrimes			    transflag == 0) {
297325165Sdavidn				retrieve(_PATH_LS " %s", dirname);
29741592Srgrimes				goto out;
29751592Srgrimes			}
29761592Srgrimes			perror_reply(550, whichf);
29771592Srgrimes			if (dout != NULL) {
29781592Srgrimes				(void) fclose(dout);
29791592Srgrimes				transflag = 0;
29801592Srgrimes				data = -1;
29811592Srgrimes				pdata = -1;
29821592Srgrimes			}
29831592Srgrimes			goto out;
29841592Srgrimes		}
29851592Srgrimes
29861592Srgrimes		if (S_ISREG(st.st_mode)) {
29871592Srgrimes			if (dout == NULL) {
29881592Srgrimes				dout = dataconn("file list", (off_t)-1, "w");
29891592Srgrimes				if (dout == NULL)
29901592Srgrimes					goto out;
29911592Srgrimes				transflag++;
29921592Srgrimes			}
29931592Srgrimes			fprintf(dout, "%s%s\n", dirname,
29941592Srgrimes				type == TYPE_A ? "\r" : "");
29951592Srgrimes			byte_count += strlen(dirname) + 1;
29961592Srgrimes			continue;
29971592Srgrimes		} else if (!S_ISDIR(st.st_mode))
29981592Srgrimes			continue;
29991592Srgrimes
30001592Srgrimes		if ((dirp = opendir(dirname)) == NULL)
30011592Srgrimes			continue;
30021592Srgrimes
30031592Srgrimes		while ((dir = readdir(dirp)) != NULL) {
30041592Srgrimes			char nbuf[MAXPATHLEN];
30051592Srgrimes
300689935Syar			if (recvurg) {
300789935Syar				myoob();
300889935Syar				recvurg = 0;
300989935Syar				transflag = 0;
301089935Syar				goto out;
301189935Syar			}
301289935Syar
30131592Srgrimes			if (dir->d_name[0] == '.' && dir->d_namlen == 1)
30141592Srgrimes				continue;
30151592Srgrimes			if (dir->d_name[0] == '.' && dir->d_name[1] == '.' &&
30161592Srgrimes			    dir->d_namlen == 2)
30171592Srgrimes				continue;
30181592Srgrimes
301999213Smaxim			snprintf(nbuf, sizeof(nbuf),
302031973Simp				"%s/%s", dirname, dir->d_name);
30211592Srgrimes
30221592Srgrimes			/*
30231592Srgrimes			 * We have to do a stat to insure it's
30241592Srgrimes			 * not a directory or special file.
30251592Srgrimes			 */
30261592Srgrimes			if (simple || (stat(nbuf, &st) == 0 &&
30271592Srgrimes			    S_ISREG(st.st_mode))) {
30281592Srgrimes				if (dout == NULL) {
30291592Srgrimes					dout = dataconn("file list", (off_t)-1,
30301592Srgrimes						"w");
30311592Srgrimes					if (dout == NULL)
30321592Srgrimes						goto out;
30331592Srgrimes					transflag++;
30341592Srgrimes				}
30351592Srgrimes				if (nbuf[0] == '.' && nbuf[1] == '/')
30361592Srgrimes					fprintf(dout, "%s%s\n", &nbuf[2],
30371592Srgrimes						type == TYPE_A ? "\r" : "");
30381592Srgrimes				else
30391592Srgrimes					fprintf(dout, "%s%s\n", nbuf,
30401592Srgrimes						type == TYPE_A ? "\r" : "");
30411592Srgrimes				byte_count += strlen(nbuf) + 1;
30421592Srgrimes			}
30431592Srgrimes		}
30441592Srgrimes		(void) closedir(dirp);
30451592Srgrimes	}
30461592Srgrimes
30471592Srgrimes	if (dout == NULL)
30481592Srgrimes		reply(550, "No files found.");
30491592Srgrimes	else if (ferror(dout) != 0)
30501592Srgrimes		perror_reply(550, "Data connection");
30511592Srgrimes	else
30521592Srgrimes		reply(226, "Transfer complete.");
30531592Srgrimes
30541592Srgrimes	transflag = 0;
30551592Srgrimes	if (dout != NULL)
30561592Srgrimes		(void) fclose(dout);
30571592Srgrimes	data = -1;
30581592Srgrimes	pdata = -1;
30591592Srgrimesout:
30601592Srgrimes	if (freeglob) {
30611592Srgrimes		freeglob = 0;
30621592Srgrimes		globfree(&gl);
30631592Srgrimes	}
30641592Srgrimes}
30651592Srgrimes
306615196Sdgvoid
306790148Simpreapchild(int signo)
306815196Sdg{
306915196Sdg	while (wait3(NULL, WNOHANG, NULL) > 0);
307015196Sdg}
307115196Sdg
307213139Speter#ifdef OLD_SETPROCTITLE
30731592Srgrimes/*
30741592Srgrimes * Clobber argv so ps will show what we're doing.  (Stolen from sendmail.)
30751592Srgrimes * Warning, since this is usually started from inetd.conf, it often doesn't
30761592Srgrimes * have much of an environment or arglist to overwrite.
30771592Srgrimes */
30781592Srgrimesvoid
30791592Srgrimessetproctitle(const char *fmt, ...)
30801592Srgrimes{
30811592Srgrimes	int i;
30821592Srgrimes	va_list ap;
30831592Srgrimes	char *p, *bp, ch;
30841592Srgrimes	char buf[LINE_MAX];
30851592Srgrimes
30861592Srgrimes	va_start(ap, fmt);
30871592Srgrimes	(void)vsnprintf(buf, sizeof(buf), fmt, ap);
30881592Srgrimes
30891592Srgrimes	/* make ps print our process name */
30901592Srgrimes	p = Argv[0];
30911592Srgrimes	*p++ = '-';
30921592Srgrimes
30931592Srgrimes	i = strlen(buf);
30941592Srgrimes	if (i > LastArgv - p - 2) {
30951592Srgrimes		i = LastArgv - p - 2;
30961592Srgrimes		buf[i] = '\0';
30971592Srgrimes	}
30981592Srgrimes	bp = buf;
30991592Srgrimes	while (ch = *bp++)
31001592Srgrimes		if (ch != '\n' && ch != '\r')
31011592Srgrimes			*p++ = ch;
31021592Srgrimes	while (p < LastArgv)
31031592Srgrimes		*p++ = ' ';
31041592Srgrimes}
310513139Speter#endif /* OLD_SETPROCTITLE */
31066740Sguido
310717433Spststatic void
310890148Simplogxfer(char *name, off_t size, time_t start)
31096740Sguido{
31106740Sguido	char buf[1024];
31116740Sguido	char path[MAXPATHLEN + 1];
311236612Sjb	time_t now;
31136740Sguido
31146740Sguido	if (statfd >= 0 && getwd(path) != NULL) {
31156740Sguido		time(&now);
311682792Sache		snprintf(buf, sizeof(buf), "%.20s!%s!%s!%s/%s!%qd!%ld\n",
31176740Sguido			ctime(&now)+4, ident, remotehost,
311882792Sache			path, name, (long long)size,
311982792Sache			(long)(now - start + (now == start)));
31206740Sguido		write(statfd, buf, strlen(buf));
31216740Sguido	}
31226740Sguido}
3123100486Syar
3124100486Syarstatic char *
3125100486Syardoublequote(char *s)
3126100486Syar{
3127100486Syar	int n;
3128100486Syar	char *p, *s2;
3129100486Syar
3130100486Syar	for (p = s, n = 0; *p; p++)
3131100486Syar		if (*p == '"')
3132100486Syar			n++;
3133100486Syar
3134100486Syar	if ((s2 = malloc(p - s + n + 1)) == NULL)
3135100486Syar		return (NULL);
3136100486Syar
3137100486Syar	for (p = s2; *s; s++, p++) {
3138100486Syar		if ((*p = *s) == '"')
3139100486Syar			*(++p) = '"';
3140100486Syar	}
3141100486Syar	*p = '\0';
3142100486Syar
3143100486Syar	return (s2);
3144100486Syar}
3145120059Sume
3146120059Sume/* setup server socket for specified address family */
3147120059Sume/* if af is PF_UNSPEC more than one socket may be returned */
3148120059Sume/* the returned list is dynamically allocated, so caller needs to free it */
3149120059Sumestatic int *
3150120059Sumesocksetup(int af, char *bindname, const char *bindport)
3151120059Sume{
3152120059Sume	struct addrinfo hints, *res, *r;
3153120059Sume	int error, maxs, *s, *socks;
3154120059Sume	const int on = 1;
3155120059Sume
3156120059Sume	memset(&hints, 0, sizeof(hints));
3157120059Sume	hints.ai_flags = AI_PASSIVE;
3158120059Sume	hints.ai_family = af;
3159120059Sume	hints.ai_socktype = SOCK_STREAM;
3160120059Sume	error = getaddrinfo(bindname, bindport, &hints, &res);
3161120059Sume	if (error) {
3162120059Sume		syslog(LOG_ERR, "%s", gai_strerror(error));
3163120059Sume		if (error == EAI_SYSTEM)
3164120059Sume			syslog(LOG_ERR, "%s", strerror(errno));
3165120059Sume		return NULL;
3166120059Sume	}
3167120059Sume
3168120059Sume	/* Count max number of sockets we may open */
3169120059Sume	for (maxs = 0, r = res; r; r = r->ai_next, maxs++)
3170120059Sume		;
3171120059Sume	socks = malloc((maxs + 1) * sizeof(int));
3172120059Sume	if (!socks) {
3173120059Sume		freeaddrinfo(res);
3174120059Sume		syslog(LOG_ERR, "couldn't allocate memory for sockets");
3175120059Sume		return NULL;
3176120059Sume	}
3177120059Sume
3178120059Sume	*socks = 0;   /* num of sockets counter at start of array */
3179120059Sume	s = socks + 1;
3180120059Sume	for (r = res; r; r = r->ai_next) {
3181120059Sume		*s = socket(r->ai_family, r->ai_socktype, r->ai_protocol);
3182120059Sume		if (*s < 0) {
3183120059Sume			syslog(LOG_DEBUG, "control socket: %m");
3184120059Sume			continue;
3185120059Sume		}
3186120059Sume		if (setsockopt(*s, SOL_SOCKET, SO_REUSEADDR,
3187120059Sume		    &on, sizeof(on)) < 0)
3188120059Sume			syslog(LOG_WARNING,
3189120059Sume			    "control setsockopt (SO_REUSEADDR): %m");
3190120059Sume		if (r->ai_family == AF_INET6) {
3191120059Sume			if (setsockopt(*s, IPPROTO_IPV6, IPV6_V6ONLY,
3192120059Sume			    &on, sizeof(on)) < 0)
3193120059Sume				syslog(LOG_WARNING,
3194120059Sume				    "control setsockopt (IPV6_V6ONLY): %m");
3195120059Sume		}
3196120059Sume		if (bind(*s, r->ai_addr, r->ai_addrlen) < 0) {
3197120059Sume			syslog(LOG_DEBUG, "control bind: %m");
3198120059Sume			close(*s);
3199120059Sume			continue;
3200120059Sume		}
3201120059Sume		(*socks)++;
3202120059Sume		s++;
3203120059Sume	}
3204120059Sume
3205120059Sume	if (res)
3206120059Sume		freeaddrinfo(res);
3207120059Sume
3208120059Sume	if (*socks == 0) {
3209120059Sume		syslog(LOG_ERR, "control socket: Couldn't bind to any socket");
3210120059Sume		free(socks);
3211120059Sume		return NULL;
3212120059Sume	}
3213120059Sume	return(socks);
3214120059Sume}
3215