ftpd.c revision 129170
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 129170 2004-05-13 05:36:38Z tjr $";
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;
1018124687Scharnier		setusershell();
10191592Srgrimes		while ((cp = getusershell()) != NULL)
10201592Srgrimes			if (strcmp(cp, shell) == 0)
10211592Srgrimes				break;
10221592Srgrimes		endusershell();
10231592Srgrimes
1024109893Syar		if (cp == NULL || checkuser(_PATH_FTPUSERS, name, 1, NULL)) {
10251592Srgrimes			reply(530, "User %s access denied.", name);
10261592Srgrimes			if (logging)
10271592Srgrimes				syslog(LOG_NOTICE,
10281592Srgrimes				    "FTP LOGIN REFUSED FROM %s, %s",
10291592Srgrimes				    remotehost, name);
10301592Srgrimes			pw = (struct passwd *) NULL;
10311592Srgrimes			return;
10321592Srgrimes		}
10331592Srgrimes	}
10341592Srgrimes	if (logging)
10351592Srgrimes		strncpy(curname, name, sizeof(curname)-1);
103688763Sache
103788763Sache	pwok = 0;
103879469Smarkm#ifdef USE_PAM
103979469Smarkm	/* XXX Kluge! The conversation mechanism needs to be fixed. */
104088763Sache#endif
104188763Sache	if (opiechallenge(&opiedata, name, opieprompt) == 0) {
104288763Sache		pwok = (pw != NULL) &&
104388763Sache		       opieaccessfile(remotehost) &&
104488763Sache		       opiealways(pw->pw_dir);
104588763Sache		reply(331, "Response to %s %s for %s.",
104688763Sache		      opieprompt, pwok ? "requested" : "required", name);
104788763Sache	} else {
104888763Sache		pwok = 1;
104984146Sache		reply(331, "Password required for %s.", name);
105088763Sache	}
10511592Srgrimes	askpasswd = 1;
10521592Srgrimes	/*
10531592Srgrimes	 * Delay before reading passwd after first failed
10541592Srgrimes	 * attempt to slow down passwd-guessing programs.
10551592Srgrimes	 */
10561592Srgrimes	if (login_attempts)
10571592Srgrimes		sleep((unsigned) login_attempts);
10581592Srgrimes}
10591592Srgrimes
10601592Srgrimes/*
1061109893Syar * Check if a user is in the file "fname",
1062109893Syar * return a pointer to a malloc'd string with the rest
1063109893Syar * of the matching line in "residue" if not NULL.
10641592Srgrimes */
10651592Srgrimesstatic int
1066109893Syarcheckuser(char *fname, char *name, int pwset, char **residue)
10671592Srgrimes{
10681592Srgrimes	FILE *fd;
10691592Srgrimes	int found = 0;
107099877Syar	size_t len;
107199877Syar	char *line, *mp, *p;
10721592Srgrimes
107317435Spst	if ((fd = fopen(fname, "r")) != NULL) {
107499877Syar		while (!found && (line = fgetln(fd, &len)) != NULL) {
107599877Syar			/* skip comments */
107699877Syar			if (line[0] == '#')
107799877Syar				continue;
107899877Syar			if (line[len - 1] == '\n') {
107999877Syar				line[len - 1] = '\0';
108099877Syar				mp = NULL;
108199877Syar			} else {
108299877Syar				if ((mp = malloc(len + 1)) == NULL)
108399877Syar					fatalerror("Ran out of memory.");
108499877Syar				memcpy(mp, line, len);
108599877Syar				mp[len] = '\0';
108699877Syar				line = mp;
108799877Syar			}
108899877Syar			/* avoid possible leading and trailing whitespace */
108999877Syar			p = strtok(line, " \t");
109099877Syar			/* skip empty lines */
109199877Syar			if (p == NULL)
109299877Syar				goto nextline;
109399877Syar			/*
109499877Syar			 * if first chr is '@', check group membership
109599877Syar			 */
109699877Syar			if (p[0] == '@') {
109799877Syar				int i = 0;
109899877Syar				struct group *grp;
109999877Syar
1100109893Syar				if (p[1] == '\0') /* single @ matches anyone */
110199877Syar					found = 1;
1102109893Syar				else {
1103109893Syar					if ((grp = getgrnam(p+1)) == NULL)
1104109893Syar						goto nextline;
1105109893Syar					/*
1106109893Syar					 * Check user's default group
1107109893Syar					 */
1108109893Syar					if (pwset && grp->gr_gid == pw->pw_gid)
1109109893Syar						found = 1;
1110109893Syar					/*
1111109893Syar					 * Check supplementary groups
1112109893Syar					 */
1113109893Syar					while (!found && grp->gr_mem[i])
1114109893Syar						found = strcmp(name,
1115109893Syar							grp->gr_mem[i++])
1116109893Syar							== 0;
1117109893Syar				}
11181592Srgrimes			}
111999877Syar			/*
112099877Syar			 * Otherwise, just check for username match
112199877Syar			 */
112299877Syar			else
112399877Syar				found = strcmp(p, name) == 0;
1124109893Syar			/*
1125109893Syar			 * Save the rest of line to "residue" if matched
1126109893Syar			 */
1127109893Syar			if (found && residue) {
1128109938Syar				if ((p = strtok(NULL, "")) != NULL)
1129109938Syar					p += strspn(p, " \t");
1130109938Syar				if (p && *p) {
1131109893Syar				 	if ((*residue = strdup(p)) == NULL)
1132109893Syar						fatalerror("Ran out of memory.");
1133109893Syar				} else
1134109893Syar					*residue = NULL;
1135109893Syar			}
113699877Syarnextline:
113799877Syar			if (mp)
113899877Syar				free(mp);
113999877Syar		}
11401592Srgrimes		(void) fclose(fd);
11411592Srgrimes	}
11421592Srgrimes	return (found);
11431592Srgrimes}
11441592Srgrimes
11451592Srgrimes/*
11461592Srgrimes * Terminate login as previous user, if any, resetting state;
11471592Srgrimes * used when USER command is given or login fails.
11481592Srgrimes */
11491592Srgrimesstatic void
115090148Simpend_login(void)
11511592Srgrimes{
115274874Smarkm#ifdef USE_PAM
115374874Smarkm	int e;
115474874Smarkm#endif
11551592Srgrimes
11561592Srgrimes	(void) seteuid((uid_t)0);
1157102311Syar	if (logged_in && dowtmp)
115889920Sume		ftpd_logwtmp(ttyline, "", NULL);
11591592Srgrimes	pw = NULL;
116025101Sdavidn#ifdef	LOGIN_CAP
116125101Sdavidn	setusercontext(NULL, getpwuid(0), (uid_t)0,
1162105877Srwatson		       LOGIN_SETPRIORITY|LOGIN_SETRESOURCES|LOGIN_SETUMASK|
1163105877Srwatson		       LOGIN_SETMAC);
116425101Sdavidn#endif
116574874Smarkm#ifdef USE_PAM
116674874Smarkm	if ((e = pam_setcred(pamh, PAM_DELETE_CRED)) != PAM_SUCCESS)
116774874Smarkm		syslog(LOG_ERR, "pam_setcred: %s", pam_strerror(pamh, e));
116874874Smarkm	if ((e = pam_close_session(pamh,0)) != PAM_SUCCESS)
116974874Smarkm		syslog(LOG_ERR, "pam_close_session: %s", pam_strerror(pamh, e));
117074874Smarkm	if ((e = pam_end(pamh, e)) != PAM_SUCCESS)
117174874Smarkm		syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e));
117274874Smarkm	pamh = NULL;
117374874Smarkm#endif
11741592Srgrimes	logged_in = 0;
11751592Srgrimes	guest = 0;
117617435Spst	dochroot = 0;
11771592Srgrimes}
11781592Srgrimes
117974874Smarkm#ifdef USE_PAM
118051433Smarkm
118151433Smarkm/*
118251433Smarkm * the following code is stolen from imap-uw PAM authentication module and
118351433Smarkm * login.c
118451433Smarkm */
118551433Smarkm#define COPY_STRING(s) (s ? strdup(s) : NULL)
118651433Smarkm
118751433Smarkmstruct cred_t {
118851433Smarkm	const char *uname;		/* user name */
118951433Smarkm	const char *pass;		/* password */
119051433Smarkm};
119151433Smarkmtypedef struct cred_t cred_t;
119251433Smarkm
119351433Smarkmstatic int
119451433Smarkmauth_conv(int num_msg, const struct pam_message **msg,
119551433Smarkm	  struct pam_response **resp, void *appdata)
119651433Smarkm{
119751433Smarkm	int i;
119851433Smarkm	cred_t *cred = (cred_t *) appdata;
119991244Sdes	struct pam_response *reply;
120051433Smarkm
120191244Sdes	reply = calloc(num_msg, sizeof *reply);
120291244Sdes	if (reply == NULL)
120391244Sdes		return PAM_BUF_ERR;
120491244Sdes
120551433Smarkm	for (i = 0; i < num_msg; i++) {
120651433Smarkm		switch (msg[i]->msg_style) {
120751433Smarkm		case PAM_PROMPT_ECHO_ON:	/* assume want user name */
120851433Smarkm			reply[i].resp_retcode = PAM_SUCCESS;
120951433Smarkm			reply[i].resp = COPY_STRING(cred->uname);
121051433Smarkm			/* PAM frees resp. */
121151433Smarkm			break;
121251433Smarkm		case PAM_PROMPT_ECHO_OFF:	/* assume want password */
121351433Smarkm			reply[i].resp_retcode = PAM_SUCCESS;
121451433Smarkm			reply[i].resp = COPY_STRING(cred->pass);
121551433Smarkm			/* PAM frees resp. */
121651433Smarkm			break;
121751433Smarkm		case PAM_TEXT_INFO:
121851433Smarkm		case PAM_ERROR_MSG:
121951433Smarkm			reply[i].resp_retcode = PAM_SUCCESS;
122051433Smarkm			reply[i].resp = NULL;
122151433Smarkm			break;
122251433Smarkm		default:			/* unknown message style */
122351433Smarkm			free(reply);
122451433Smarkm			return PAM_CONV_ERR;
122551433Smarkm		}
122651433Smarkm	}
122751433Smarkm
122851433Smarkm	*resp = reply;
122951433Smarkm	return PAM_SUCCESS;
123051433Smarkm}
123151433Smarkm
123251433Smarkm/*
123351433Smarkm * Attempt to authenticate the user using PAM.  Returns 0 if the user is
123451433Smarkm * authenticated, or 1 if not authenticated.  If some sort of PAM system
123551433Smarkm * error occurs (e.g., the "/etc/pam.conf" file is missing) then this
123651433Smarkm * function returns -1.  This can be used as an indication that we should
123751433Smarkm * fall back to a different authentication mechanism.
123851433Smarkm */
123951433Smarkmstatic int
124051433Smarkmauth_pam(struct passwd **ppw, const char *pass)
124151433Smarkm{
124251433Smarkm	pam_handle_t *pamh = NULL;
124351433Smarkm	const char *tmpl_user;
124451433Smarkm	const void *item;
124551433Smarkm	int rval;
124651433Smarkm	int e;
124751433Smarkm	cred_t auth_cred = { (*ppw)->pw_name, pass };
124851433Smarkm	struct pam_conv conv = { &auth_conv, &auth_cred };
124951433Smarkm
125051433Smarkm	e = pam_start("ftpd", (*ppw)->pw_name, &conv, &pamh);
125151433Smarkm	if (e != PAM_SUCCESS) {
125251433Smarkm		syslog(LOG_ERR, "pam_start: %s", pam_strerror(pamh, e));
125351433Smarkm		return -1;
125451433Smarkm	}
125551433Smarkm
125667007Sguido	e = pam_set_item(pamh, PAM_RHOST, remotehost);
125767007Sguido	if (e != PAM_SUCCESS) {
125867007Sguido		syslog(LOG_ERR, "pam_set_item(PAM_RHOST): %s",
125967007Sguido			pam_strerror(pamh, e));
126067007Sguido		return -1;
126167007Sguido	}
126267007Sguido
126351433Smarkm	e = pam_authenticate(pamh, 0);
126451433Smarkm	switch (e) {
126551433Smarkm	case PAM_SUCCESS:
126651433Smarkm		/*
126751433Smarkm		 * With PAM we support the concept of a "template"
126851433Smarkm		 * user.  The user enters a login name which is
126951433Smarkm		 * authenticated by PAM, usually via a remote service
127051433Smarkm		 * such as RADIUS or TACACS+.  If authentication
127151433Smarkm		 * succeeds, a different but related "template" name
127251433Smarkm		 * is used for setting the credentials, shell, and
127351433Smarkm		 * home directory.  The name the user enters need only
127451433Smarkm		 * exist on the remote authentication server, but the
127551433Smarkm		 * template name must be present in the local password
127651433Smarkm		 * database.
127751433Smarkm		 *
127851433Smarkm		 * This is supported by two various mechanisms in the
127951433Smarkm		 * individual modules.  However, from the application's
128051433Smarkm		 * point of view, the template user is always passed
128151433Smarkm		 * back as a changed value of the PAM_USER item.
128251433Smarkm		 */
128351433Smarkm		if ((e = pam_get_item(pamh, PAM_USER, &item)) ==
128451433Smarkm		    PAM_SUCCESS) {
128551433Smarkm			tmpl_user = (const char *) item;
128651433Smarkm			if (strcmp((*ppw)->pw_name, tmpl_user) != 0)
128751433Smarkm				*ppw = getpwnam(tmpl_user);
128851433Smarkm		} else
128951433Smarkm			syslog(LOG_ERR, "Couldn't get PAM_USER: %s",
129051433Smarkm			    pam_strerror(pamh, e));
129151433Smarkm		rval = 0;
129251433Smarkm		break;
129351433Smarkm
129451433Smarkm	case PAM_AUTH_ERR:
129551433Smarkm	case PAM_USER_UNKNOWN:
129651433Smarkm	case PAM_MAXTRIES:
129751433Smarkm		rval = 1;
129851433Smarkm		break;
129951433Smarkm
130051433Smarkm	default:
130174874Smarkm		syslog(LOG_ERR, "pam_authenticate: %s", pam_strerror(pamh, e));
130251433Smarkm		rval = -1;
130351433Smarkm		break;
130451433Smarkm	}
130551433Smarkm
130674874Smarkm	if (rval == 0) {
130774874Smarkm		e = pam_acct_mgmt(pamh, 0);
130874874Smarkm		if (e == PAM_NEW_AUTHTOK_REQD) {
130974874Smarkm			e = pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
131074874Smarkm			if (e != PAM_SUCCESS) {
131174874Smarkm				syslog(LOG_ERR, "pam_chauthtok: %s", pam_strerror(pamh, e));
131274874Smarkm				rval = 1;
131374874Smarkm			}
131474874Smarkm		} else if (e != PAM_SUCCESS) {
131574874Smarkm			rval = 1;
131674874Smarkm		}
131751433Smarkm	}
131874874Smarkm
131974874Smarkm	if (rval != 0) {
132074874Smarkm		if ((e = pam_end(pamh, e)) != PAM_SUCCESS) {
132174874Smarkm			syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e));
132274874Smarkm		}
132374874Smarkm		pamh = NULL;
132474874Smarkm	}
132551433Smarkm	return rval;
132651433Smarkm}
132751433Smarkm
132874874Smarkm#endif /* USE_PAM */
132951433Smarkm
13301592Srgrimesvoid
133190148Simppass(char *passwd)
13321592Srgrimes{
133317435Spst	int rval;
13341592Srgrimes	FILE *fd;
133525101Sdavidn#ifdef	LOGIN_CAP
133625101Sdavidn	login_cap_t *lc = NULL;
133725101Sdavidn#endif
133874874Smarkm#ifdef USE_PAM
133974874Smarkm	int e;
134074874Smarkm#endif
1341110036Syar	char *chrootdir;
1342109939Syar	char *residue = NULL;
134388763Sache	char *xpasswd;
13441592Srgrimes
13451592Srgrimes	if (logged_in || askpasswd == 0) {
13461592Srgrimes		reply(503, "Login with USER first.");
13471592Srgrimes		return;
13481592Srgrimes	}
13491592Srgrimes	askpasswd = 0;
13501592Srgrimes	if (!guest) {		/* "ftp" is only account allowed no password */
135117435Spst		if (pw == NULL) {
135217435Spst			rval = 1;	/* failure below */
135317435Spst			goto skip;
135417435Spst		}
135574874Smarkm#ifdef USE_PAM
135651433Smarkm		rval = auth_pam(&pw, passwd);
135789622Sache		if (rval >= 0) {
135889622Sache			opieunlock();
135917435Spst			goto skip;
136089622Sache		}
136189622Sache#endif
136288763Sache		if (opieverify(&opiedata, passwd) == 0)
136388763Sache			xpasswd = pw->pw_passwd;
136489622Sache		else if (pwok) {
136588763Sache			xpasswd = crypt(passwd, pw->pw_passwd);
136689622Sache			if (passwd[0] == '\0' && pw->pw_passwd[0] != '\0')
136789622Sache				xpasswd = ":";
136889622Sache		} else {
136988763Sache			rval = 1;
137088763Sache			goto skip;
137188763Sache		}
137288763Sache		rval = strcmp(pw->pw_passwd, xpasswd);
137389622Sache		if (pw->pw_expire && time(NULL) >= pw->pw_expire)
137417435Spst			rval = 1;	/* failure */
137517435Spstskip:
137617435Spst		/*
137717435Spst		 * If rval == 1, the user failed the authentication check
137851433Smarkm		 * above.  If rval == 0, either PAM or local authentication
137917435Spst		 * succeeded.
138017435Spst		 */
138117435Spst		if (rval) {
13821592Srgrimes			reply(530, "Login incorrect.");
1383110691Syar			if (logging) {
13841592Srgrimes				syslog(LOG_NOTICE,
1385110691Syar				    "FTP LOGIN FAILED FROM %s",
1386110691Syar				    remotehost);
1387110691Syar				syslog(LOG_AUTHPRIV | LOG_NOTICE,
13881592Srgrimes				    "FTP LOGIN FAILED FROM %s, %s",
13891592Srgrimes				    remotehost, curname);
1390110691Syar			}
13911592Srgrimes			pw = NULL;
13921592Srgrimes			if (login_attempts++ >= 5) {
13931592Srgrimes				syslog(LOG_NOTICE,
13941592Srgrimes				    "repeated login failures from %s",
13951592Srgrimes				    remotehost);
13961592Srgrimes				exit(0);
13971592Srgrimes			}
13981592Srgrimes			return;
13991592Srgrimes		}
14001592Srgrimes	}
14011592Srgrimes	login_attempts = 0;		/* this time successful */
14021592Srgrimes	if (setegid((gid_t)pw->pw_gid) < 0) {
14031592Srgrimes		reply(550, "Can't set gid.");
14041592Srgrimes		return;
14051592Srgrimes	}
140625101Sdavidn	/* May be overridden by login.conf */
140725101Sdavidn	(void) umask(defumask);
140825101Sdavidn#ifdef	LOGIN_CAP
140925674Sdavidn	if ((lc = login_getpwclass(pw)) != NULL) {
141025101Sdavidn		char	remote_ip[MAXHOSTNAMELEN];
141125101Sdavidn
141256668Sshin		getnameinfo((struct sockaddr *)&his_addr, his_addr.su_len,
141356668Sshin			remote_ip, sizeof(remote_ip) - 1, NULL, 0,
141499255Sume			NI_NUMERICHOST);
141525101Sdavidn		remote_ip[sizeof(remote_ip) - 1] = 0;
141625101Sdavidn		if (!auth_hostok(lc, remotehost, remote_ip)) {
141725101Sdavidn			syslog(LOG_INFO|LOG_AUTH,
141825101Sdavidn			    "FTP LOGIN FAILED (HOST) as %s: permission denied.",
141925101Sdavidn			    pw->pw_name);
142025101Sdavidn			reply(530, "Permission denied.\n");
142125101Sdavidn			pw = NULL;
142225101Sdavidn			return;
142325101Sdavidn		}
142425101Sdavidn		if (!auth_timeok(lc, time(NULL))) {
142525101Sdavidn			reply(530, "Login not available right now.\n");
142625101Sdavidn			pw = NULL;
142725101Sdavidn			return;
142825101Sdavidn		}
142925101Sdavidn	}
143025101Sdavidn	setusercontext(lc, pw, (uid_t)0,
143140310Sdes		LOGIN_SETLOGIN|LOGIN_SETGROUP|LOGIN_SETPRIORITY|
1432105877Srwatson		LOGIN_SETRESOURCES|LOGIN_SETUMASK|LOGIN_SETMAC);
143325101Sdavidn#else
143440310Sdes	setlogin(pw->pw_name);
14351592Srgrimes	(void) initgroups(pw->pw_name, pw->pw_gid);
143625101Sdavidn#endif
14371592Srgrimes
143874874Smarkm#ifdef USE_PAM
143974874Smarkm	if (pamh) {
144074874Smarkm		if ((e = pam_open_session(pamh, 0)) != PAM_SUCCESS) {
144174874Smarkm			syslog(LOG_ERR, "pam_open_session: %s", pam_strerror(pamh, e));
144274874Smarkm		} else if ((e = pam_setcred(pamh, PAM_ESTABLISH_CRED)) != PAM_SUCCESS) {
144374874Smarkm			syslog(LOG_ERR, "pam_setcred: %s", pam_strerror(pamh, e));
144474874Smarkm		}
144574874Smarkm	}
144674874Smarkm#endif
144774874Smarkm
14481592Srgrimes	/* open wtmp before chroot */
1449102311Syar	if (dowtmp)
1450102311Syar		ftpd_logwtmp(ttyline, pw->pw_name,
1451102311Syar		    (struct sockaddr *)&his_addr);
14521592Srgrimes	logged_in = 1;
14531592Srgrimes
145417435Spst	if (guest && stats && statfd < 0)
145525283Sdavidn#ifdef VIRTUAL_HOSTING
145625283Sdavidn		if ((statfd = open(thishost->statfile, O_WRONLY|O_APPEND)) < 0)
145725283Sdavidn#else
14586740Sguido		if ((statfd = open(_PATH_FTPDSTATFILE, O_WRONLY|O_APPEND)) < 0)
145925283Sdavidn#endif
14606740Sguido			stats = 0;
14616740Sguido
146225101Sdavidn	dochroot =
1463109939Syar		checkuser(_PATH_FTPCHROOT, pw->pw_name, 1, &residue)
146425101Sdavidn#ifdef	LOGIN_CAP	/* Allow login.conf configuration as well */
1465109893Syar		|| login_getcapbool(lc, "ftp-chroot", 0)
146625101Sdavidn#endif
1467109893Syar	;
1468110036Syar	chrootdir = NULL;
1469110036Syar	/*
1470110036Syar	 * For a chrooted local user,
1471110036Syar	 * a) see whether ftpchroot(5) specifies a chroot directory,
1472110036Syar	 * b) extract the directory pathname from the line,
1473110036Syar	 * c) expand it to the absolute pathname if necessary.
1474110036Syar	 */
1475110036Syar	if (dochroot && residue &&
1476117349Syar	    (chrootdir = strtok(residue, " \t")) != NULL) {
1477117349Syar		if (chrootdir[0] != '/')
1478117349Syar			asprintf(&chrootdir, "%s/%s", pw->pw_dir, chrootdir);
1479117349Syar		else
1480117349Syar			chrootdir = strdup(chrootdir); /* so it can be freed */
1481110036Syar		if (chrootdir == NULL)
1482110036Syar			fatalerror("Ran out of memory.");
1483110036Syar	}
1484110036Syar	if (guest || dochroot) {
14851592Srgrimes		/*
1486110036Syar		 * If no chroot directory set yet, use the login directory.
1487110036Syar		 * Copy it so it can be modified while pw->pw_dir stays intact.
14881592Srgrimes		 */
1489110036Syar		if (chrootdir == NULL &&
1490110036Syar		    (chrootdir = strdup(pw->pw_dir)) == NULL)
1491110036Syar			fatalerror("Ran out of memory.");
1492110036Syar		/*
1493110036Syar		 * Check for the "/chroot/./home" syntax,
1494110036Syar		 * separate the chroot and home directory pathnames.
1495110036Syar		 */
1496110036Syar		if ((homedir = strstr(chrootdir, "/./")) != NULL) {
1497110036Syar			*(homedir++) = '\0';	/* wipe '/' */
1498110036Syar			homedir++;		/* skip '.' */
1499110036Syar			/* so chrootdir can be freed later */
1500110036Syar			if ((homedir = strdup(homedir)) == NULL)
1501109893Syar				fatalerror("Ran out of memory.");
1502110036Syar		} else {
1503110036Syar			/*
1504110036Syar			 * We MUST do a chdir() after the chroot. Otherwise
1505110036Syar			 * the old current directory will be accessible as "."
1506110036Syar			 * outside the new root!
1507110036Syar			 */
1508110036Syar			homedir = "/";
1509109939Syar		}
1510110036Syar		/*
1511110036Syar		 * Finally, do chroot()
1512110036Syar		 */
1513110036Syar		if (chroot(chrootdir) < 0) {
151417435Spst			reply(550, "Can't change root.");
151517435Spst			goto bad;
151617435Spst		}
1517110036Syar	} else	/* real user w/o chroot */
1518110036Syar		homedir = pw->pw_dir;
1519110036Syar	/*
1520110036Syar	 * Set euid *before* doing chdir() so
1521110036Syar	 * a) the user won't be carried to a directory that he couldn't reach
1522110036Syar	 *    on his own due to no permission to upper path components,
1523110036Syar	 * b) NFS mounted homedirs w/restrictive permissions will be accessible
1524110036Syar	 *    (uid 0 has no root power over NFS if not mapped explicitly.)
1525110036Syar	 */
15261592Srgrimes	if (seteuid((uid_t)pw->pw_uid) < 0) {
15271592Srgrimes		reply(550, "Can't set uid.");
15281592Srgrimes		goto bad;
15291592Srgrimes	}
1530110036Syar	if (chdir(homedir) < 0) {
1531110036Syar		if (guest || dochroot) {
1532110036Syar			reply(550, "Can't change to base directory.");
1533110036Syar			goto bad;
1534110036Syar		} else {
1535110036Syar			if (chdir("/") < 0) {
1536110036Syar				reply(550, "Root is inaccessible.");
1537110036Syar				goto bad;
1538110036Syar			}
1539110036Syar			lreply(230, "No directory! Logging in with home=/");
1540110036Syar		}
1541110036Syar	}
15428696Sdg
15431592Srgrimes	/*
15441592Srgrimes	 * Display a login message, if it exists.
15451592Srgrimes	 * N.B. reply(230,) must follow the message.
15461592Srgrimes	 */
154725283Sdavidn#ifdef VIRTUAL_HOSTING
154825283Sdavidn	if ((fd = fopen(thishost->loginmsg, "r")) != NULL) {
154925283Sdavidn#else
15501592Srgrimes	if ((fd = fopen(_PATH_FTPLOGINMESG, "r")) != NULL) {
155125283Sdavidn#endif
15521592Srgrimes		char *cp, line[LINE_MAX];
15531592Srgrimes
15541592Srgrimes		while (fgets(line, sizeof(line), fd) != NULL) {
15551592Srgrimes			if ((cp = strchr(line, '\n')) != NULL)
15561592Srgrimes				*cp = '\0';
15571592Srgrimes			lreply(230, "%s", line);
15581592Srgrimes		}
15591592Srgrimes		(void) fflush(stdout);
15601592Srgrimes		(void) fclose(fd);
15611592Srgrimes	}
15621592Srgrimes	if (guest) {
15636740Sguido		if (ident != NULL)
15646740Sguido			free(ident);
156517433Spst		ident = strdup(passwd);
156617433Spst		if (ident == NULL)
156776096Smarkm			fatalerror("Ran out of memory.");
156817433Spst
15691592Srgrimes		reply(230, "Guest login ok, access restrictions apply.");
15701592Srgrimes#ifdef SETPROCTITLE
157125283Sdavidn#ifdef VIRTUAL_HOSTING
157225283Sdavidn		if (thishost != firsthost)
157325283Sdavidn			snprintf(proctitle, sizeof(proctitle),
157483308Smikeh				 "%s: anonymous(%s)/%s", remotehost, hostname,
157583308Smikeh				 passwd);
157625283Sdavidn		else
157725283Sdavidn#endif
157825283Sdavidn			snprintf(proctitle, sizeof(proctitle),
157983308Smikeh				 "%s: anonymous/%s", remotehost, passwd);
158013139Speter		setproctitle("%s", proctitle);
15811592Srgrimes#endif /* SETPROCTITLE */
15821592Srgrimes		if (logging)
15831592Srgrimes			syslog(LOG_INFO, "ANONYMOUS FTP LOGIN FROM %s, %s",
15841592Srgrimes			    remotehost, passwd);
15851592Srgrimes	} else {
158684841Syar		if (dochroot)
158784841Syar			reply(230, "User %s logged in, "
158884841Syar				   "access restrictions apply.", pw->pw_name);
158984841Syar		else
159084841Syar			reply(230, "User %s logged in.", pw->pw_name);
159125986Sdanny
15921592Srgrimes#ifdef SETPROCTITLE
15931592Srgrimes		snprintf(proctitle, sizeof(proctitle),
159484842Syar			 "%s: user/%s", remotehost, pw->pw_name);
159513139Speter		setproctitle("%s", proctitle);
15961592Srgrimes#endif /* SETPROCTITLE */
15971592Srgrimes		if (logging)
15981592Srgrimes			syslog(LOG_INFO, "FTP LOGIN FROM %s as %s",
15991592Srgrimes			    remotehost, pw->pw_name);
16001592Srgrimes	}
160125101Sdavidn#ifdef	LOGIN_CAP
160225101Sdavidn	login_close(lc);
160325101Sdavidn#endif
1604110036Syar	if (chrootdir)
1605110036Syar		free(chrootdir);
1606110036Syar	if (residue)
1607110036Syar		free(residue);
16081592Srgrimes	return;
16091592Srgrimesbad:
16101592Srgrimes	/* Forget all about it... */
161125101Sdavidn#ifdef	LOGIN_CAP
161225101Sdavidn	login_close(lc);
161325101Sdavidn#endif
1614110036Syar	if (chrootdir)
1615110036Syar		free(chrootdir);
1616110036Syar	if (residue)
1617110036Syar		free(residue);
16181592Srgrimes	end_login();
16191592Srgrimes}
16201592Srgrimes
16211592Srgrimesvoid
162290148Simpretrieve(char *cmd, char *name)
16231592Srgrimes{
16241592Srgrimes	FILE *fin, *dout;
16251592Srgrimes	struct stat st;
162690148Simp	int (*closefunc)(FILE *);
162736612Sjb	time_t start;
16281592Srgrimes
16291592Srgrimes	if (cmd == 0) {
16301592Srgrimes		fin = fopen(name, "r"), closefunc = fclose;
16311592Srgrimes		st.st_size = 0;
16321592Srgrimes	} else {
16331592Srgrimes		char line[BUFSIZ];
16341592Srgrimes
163531973Simp		(void) snprintf(line, sizeof(line), cmd, name), name = line;
16361592Srgrimes		fin = ftpd_popen(line, "r"), closefunc = ftpd_pclose;
16371592Srgrimes		st.st_size = -1;
16381592Srgrimes		st.st_blksize = BUFSIZ;
16391592Srgrimes	}
16401592Srgrimes	if (fin == NULL) {
16411592Srgrimes		if (errno != 0) {
16421592Srgrimes			perror_reply(550, name);
16431592Srgrimes			if (cmd == 0) {
16441592Srgrimes				LOGCMD("get", name);
16451592Srgrimes			}
16461592Srgrimes		}
16471592Srgrimes		return;
16481592Srgrimes	}
16491592Srgrimes	byte_count = -1;
1650110144Syar	if (cmd == 0) {
1651110144Syar		if (fstat(fileno(fin), &st) < 0) {
1652110144Syar			perror_reply(550, name);
1653110144Syar			goto done;
1654110144Syar		}
1655110144Syar		if (!S_ISREG(st.st_mode)) {
1656125565Syar			/*
1657125565Syar			 * Never sending a raw directory is a workaround
1658125565Syar			 * for buggy clients that will attempt to RETR
1659125565Syar			 * a directory before listing it, e.g., Mozilla.
1660125565Syar			 * Preventing a guest from getting irregular files
1661125565Syar			 * is a simple security measure.
1662125565Syar			 */
1663125565Syar			if (S_ISDIR(st.st_mode) || guest) {
1664110144Syar				reply(550, "%s: not a plain file.", name);
1665110144Syar				goto done;
1666110144Syar			}
1667110144Syar			st.st_size = -1;
1668110144Syar			/* st.st_blksize is set for all descriptor types */
1669110144Syar		}
16701592Srgrimes	}
16711592Srgrimes	if (restart_point) {
16721592Srgrimes		if (type == TYPE_A) {
16731592Srgrimes			off_t i, n;
16741592Srgrimes			int c;
16751592Srgrimes
16761592Srgrimes			n = restart_point;
16771592Srgrimes			i = 0;
16781592Srgrimes			while (i++ < n) {
16791592Srgrimes				if ((c=getc(fin)) == EOF) {
16801592Srgrimes					perror_reply(550, name);
16811592Srgrimes					goto done;
16821592Srgrimes				}
16831592Srgrimes				if (c == '\n')
16841592Srgrimes					i++;
16851592Srgrimes			}
16861592Srgrimes		} else if (lseek(fileno(fin), restart_point, L_SET) < 0) {
16871592Srgrimes			perror_reply(550, name);
16881592Srgrimes			goto done;
16891592Srgrimes		}
16901592Srgrimes	}
16911592Srgrimes	dout = dataconn(name, st.st_size, "w");
16921592Srgrimes	if (dout == NULL)
16931592Srgrimes		goto done;
16946740Sguido	time(&start);
16958240Swollman	send_data(fin, dout, st.st_blksize, st.st_size,
16968240Swollman		  restart_point == 0 && cmd == 0 && S_ISREG(st.st_mode));
16976740Sguido	if (cmd == 0 && guest && stats)
169817433Spst		logxfer(name, st.st_size, start);
16991592Srgrimes	(void) fclose(dout);
17001592Srgrimes	data = -1;
17011592Srgrimes	pdata = -1;
17021592Srgrimesdone:
17031592Srgrimes	if (cmd == 0)
17041592Srgrimes		LOGBYTES("get", name, byte_count);
17051592Srgrimes	(*closefunc)(fin);
17061592Srgrimes}
17071592Srgrimes
17081592Srgrimesvoid
170990148Simpstore(char *name, char *mode, int unique)
17101592Srgrimes{
1711101537Syar	int fd;
17121592Srgrimes	FILE *fout, *din;
171390148Simp	int (*closefunc)(FILE *);
17141592Srgrimes
1715101537Syar	if (*mode == 'a') {		/* APPE */
1716101537Syar		if (unique) {
1717101537Syar			/* Programming error */
1718101537Syar			syslog(LOG_ERR, "Internal: unique flag to APPE");
1719101537Syar			unique = 0;
1720101537Syar		}
1721101537Syar		if (guest && noguestmod) {
1722101537Syar			reply(550, "Appending to existing file denied");
1723101537Syar			goto err;
1724101537Syar		}
1725101537Syar		restart_point = 0;	/* not affected by preceding REST */
17261592Srgrimes	}
1727101537Syar	if (unique)			/* STOU overrides REST */
1728101537Syar		restart_point = 0;
1729101537Syar	if (guest && noguestmod) {
1730101537Syar		if (restart_point) {	/* guest STOR w/REST */
1731101537Syar			reply(550, "Modifying existing file denied");
1732101537Syar			goto err;
1733101537Syar		} else			/* treat guest STOR as STOU */
1734101537Syar			unique = 1;
1735101537Syar	}
17361592Srgrimes
17371592Srgrimes	if (restart_point)
1738101537Syar		mode = "r+";	/* so ASCII manual seek can work */
1739101537Syar	if (unique) {
1740101537Syar		if ((fd = guniquefd(name, &name)) < 0)
1741101537Syar			goto err;
1742101537Syar		fout = fdopen(fd, mode);
1743101537Syar	} else
1744101537Syar		fout = fopen(name, mode);
17451592Srgrimes	closefunc = fclose;
17461592Srgrimes	if (fout == NULL) {
17471592Srgrimes		perror_reply(553, name);
1748101537Syar		goto err;
17491592Srgrimes	}
17501592Srgrimes	byte_count = -1;
17511592Srgrimes	if (restart_point) {
17521592Srgrimes		if (type == TYPE_A) {
17531592Srgrimes			off_t i, n;
17541592Srgrimes			int c;
17551592Srgrimes
17561592Srgrimes			n = restart_point;
17571592Srgrimes			i = 0;
17581592Srgrimes			while (i++ < n) {
17591592Srgrimes				if ((c=getc(fout)) == EOF) {
17601592Srgrimes					perror_reply(550, name);
17611592Srgrimes					goto done;
17621592Srgrimes				}
17631592Srgrimes				if (c == '\n')
17641592Srgrimes					i++;
17651592Srgrimes			}
17661592Srgrimes			/*
17671592Srgrimes			 * We must do this seek to "current" position
17681592Srgrimes			 * because we are changing from reading to
17691592Srgrimes			 * writing.
17701592Srgrimes			 */
177182792Sache			if (fseeko(fout, (off_t)0, SEEK_CUR) < 0) {
17721592Srgrimes				perror_reply(550, name);
17731592Srgrimes				goto done;
17741592Srgrimes			}
17751592Srgrimes		} else if (lseek(fileno(fout), restart_point, L_SET) < 0) {
17761592Srgrimes			perror_reply(550, name);
17771592Srgrimes			goto done;
17781592Srgrimes		}
17791592Srgrimes	}
17801592Srgrimes	din = dataconn(name, (off_t)-1, "r");
17811592Srgrimes	if (din == NULL)
17821592Srgrimes		goto done;
17831592Srgrimes	if (receive_data(din, fout) == 0) {
17841592Srgrimes		if (unique)
17851592Srgrimes			reply(226, "Transfer complete (unique file name:%s).",
17861592Srgrimes			    name);
17871592Srgrimes		else
17881592Srgrimes			reply(226, "Transfer complete.");
17891592Srgrimes	}
17901592Srgrimes	(void) fclose(din);
17911592Srgrimes	data = -1;
17921592Srgrimes	pdata = -1;
17931592Srgrimesdone:
1794102566Syar	LOGBYTES(*mode == 'a' ? "append" : "put", name, byte_count);
17951592Srgrimes	(*closefunc)(fout);
1796101537Syar	return;
1797101537Syarerr:
1798101537Syar	LOGCMD(*mode == 'a' ? "append" : "put" , name);
1799101537Syar	return;
18001592Srgrimes}
18011592Srgrimes
18021592Srgrimesstatic FILE *
180390148Simpgetdatasock(char *mode)
18041592Srgrimes{
18051592Srgrimes	int on = 1, s, t, tries;
18061592Srgrimes
18071592Srgrimes	if (data >= 0)
18081592Srgrimes		return (fdopen(data, mode));
18091592Srgrimes	(void) seteuid((uid_t)0);
181056668Sshin
181156668Sshin	s = socket(data_dest.su_family, SOCK_STREAM, 0);
18121592Srgrimes	if (s < 0)
18131592Srgrimes		goto bad;
1814100612Syar	if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
1815100609Syar		syslog(LOG_WARNING, "data setsockopt (SO_REUSEADDR): %m");
18161592Srgrimes	/* anchor socket to avoid multi-homing problems */
181756668Sshin	data_source = ctrl_addr;
1818109742Syar	data_source.su_port = htons(dataport);
18191592Srgrimes	for (tries = 1; ; tries++) {
18201592Srgrimes		if (bind(s, (struct sockaddr *)&data_source,
182156668Sshin		    data_source.su_len) >= 0)
18221592Srgrimes			break;
18231592Srgrimes		if (errno != EADDRINUSE || tries > 10)
18241592Srgrimes			goto bad;
18251592Srgrimes		sleep(tries);
18261592Srgrimes	}
18271592Srgrimes	(void) seteuid((uid_t)pw->pw_uid);
18281592Srgrimes#ifdef IP_TOS
182956668Sshin	if (data_source.su_family == AF_INET)
183056668Sshin      {
18311592Srgrimes	on = IPTOS_THROUGHPUT;
1832100612Syar	if (setsockopt(s, IPPROTO_IP, IP_TOS, &on, sizeof(int)) < 0)
1833100609Syar		syslog(LOG_WARNING, "data setsockopt (IP_TOS): %m");
183456668Sshin      }
18351592Srgrimes#endif
18368240Swollman#ifdef TCP_NOPUSH
18378240Swollman	/*
18388240Swollman	 * Turn off push flag to keep sender TCP from sending short packets
18398240Swollman	 * at the boundaries of each write().  Should probably do a SO_SNDBUF
18408240Swollman	 * to set the send buffer size as well, but that may not be desirable
18418240Swollman	 * in heavy-load situations.
18428240Swollman	 */
18438240Swollman	on = 1;
1844100612Syar	if (setsockopt(s, IPPROTO_TCP, TCP_NOPUSH, &on, sizeof on) < 0)
1845100609Syar		syslog(LOG_WARNING, "data setsockopt (TCP_NOPUSH): %m");
18468240Swollman#endif
18478240Swollman#ifdef SO_SNDBUF
18488240Swollman	on = 65536;
1849100612Syar	if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, &on, sizeof on) < 0)
1850100609Syar		syslog(LOG_WARNING, "data setsockopt (SO_SNDBUF): %m");
18518240Swollman#endif
18528240Swollman
18531592Srgrimes	return (fdopen(s, mode));
18541592Srgrimesbad:
18551592Srgrimes	/* Return the real value of errno (close may change it) */
18561592Srgrimes	t = errno;
18571592Srgrimes	(void) seteuid((uid_t)pw->pw_uid);
18581592Srgrimes	(void) close(s);
18591592Srgrimes	errno = t;
18601592Srgrimes	return (NULL);
18611592Srgrimes}
18621592Srgrimes
18631592Srgrimesstatic FILE *
186490148Simpdataconn(char *name, off_t size, char *mode)
18651592Srgrimes{
18661592Srgrimes	char sizebuf[32];
18671592Srgrimes	FILE *file;
1868109611Scjc	int retry = 0, tos, conerrno;
18691592Srgrimes
18701592Srgrimes	file_size = size;
18711592Srgrimes	byte_count = 0;
18721592Srgrimes	if (size != (off_t) -1)
187331973Simp		(void) snprintf(sizebuf, sizeof(sizebuf), " (%qd bytes)", size);
18741592Srgrimes	else
187531973Simp		*sizebuf = '\0';
18761592Srgrimes	if (pdata >= 0) {
187756668Sshin		union sockunion from;
187886628Syar		int flags;
187956668Sshin		int s, fromlen = ctrl_addr.su_len;
188012532Sguido		struct timeval timeout;
188112532Sguido		fd_set set;
18821592Srgrimes
188312532Sguido		FD_ZERO(&set);
188412532Sguido		FD_SET(pdata, &set);
188512532Sguido
188612532Sguido		timeout.tv_usec = 0;
188712532Sguido		timeout.tv_sec = 120;
188812532Sguido
188986628Syar		/*
189086628Syar		 * Granted a socket is in the blocking I/O mode,
189186628Syar		 * accept() will block after a successful select()
189286628Syar		 * if the selected connection dies in between.
189386628Syar		 * Therefore set the non-blocking I/O flag here.
189486628Syar		 */
189586628Syar		if ((flags = fcntl(pdata, F_GETFL, 0)) == -1 ||
189686628Syar		    fcntl(pdata, F_SETFL, flags | O_NONBLOCK) == -1)
189786628Syar			goto pdata_err;
189886628Syar		if (select(pdata+1, &set, (fd_set *) 0, (fd_set *) 0, &timeout) <= 0 ||
189986628Syar		    (s = accept(pdata, (struct sockaddr *) &from, &fromlen)) < 0)
190086628Syar			goto pdata_err;
19011592Srgrimes		(void) close(pdata);
19021592Srgrimes		pdata = s;
190386628Syar		/*
1904101809Syar		 * Unset the inherited non-blocking I/O flag
1905101809Syar		 * on the child socket so stdio can work on it.
190686628Syar		 */
190786628Syar		if ((flags = fcntl(pdata, F_GETFL, 0)) == -1 ||
190886628Syar		    fcntl(pdata, F_SETFL, flags & ~O_NONBLOCK) == -1)
190986628Syar			goto pdata_err;
19101592Srgrimes#ifdef IP_TOS
191156668Sshin		if (from.su_family == AF_INET)
191256668Sshin	      {
191317435Spst		tos = IPTOS_THROUGHPUT;
1914100612Syar		if (setsockopt(s, IPPROTO_IP, IP_TOS, &tos, sizeof(int)) < 0)
1915100609Syar			syslog(LOG_WARNING, "pdata setsockopt (IP_TOS): %m");
191656668Sshin	      }
19171592Srgrimes#endif
19181592Srgrimes		reply(150, "Opening %s mode data connection for '%s'%s.",
19191592Srgrimes		     type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf);
19201592Srgrimes		return (fdopen(pdata, mode));
192186628Syarpdata_err:
192286628Syar		reply(425, "Can't open data connection.");
192386628Syar		(void) close(pdata);
192486628Syar		pdata = -1;
192586628Syar		return (NULL);
19261592Srgrimes	}
19271592Srgrimes	if (data >= 0) {
19281592Srgrimes		reply(125, "Using existing data connection for '%s'%s.",
19291592Srgrimes		    name, sizebuf);
19301592Srgrimes		usedefault = 1;
19311592Srgrimes		return (fdopen(data, mode));
19321592Srgrimes	}
19331592Srgrimes	if (usedefault)
19341592Srgrimes		data_dest = his_addr;
19351592Srgrimes	usedefault = 1;
1936109611Scjc	do {
1937109611Scjc		file = getdatasock(mode);
1938109611Scjc		if (file == NULL) {
1939109611Scjc			char hostbuf[BUFSIZ], portbuf[BUFSIZ];
1940109611Scjc			getnameinfo((struct sockaddr *)&data_source,
1941109611Scjc				data_source.su_len, hostbuf, sizeof(hostbuf) - 1,
1942109611Scjc				portbuf, sizeof(portbuf),
1943109611Scjc				NI_NUMERICHOST|NI_NUMERICSERV);
1944109611Scjc			reply(425, "Can't create data socket (%s,%s): %s.",
1945109611Scjc				hostbuf, portbuf, strerror(errno));
1946109611Scjc			return (NULL);
1947109611Scjc		}
1948109611Scjc		data = fileno(file);
1949109611Scjc		conerrno = 0;
1950109611Scjc		if (connect(data, (struct sockaddr *)&data_dest,
1951109611Scjc		    data_dest.su_len) == 0)
1952109611Scjc			break;
1953109611Scjc		conerrno = errno;
1954109611Scjc		(void) fclose(file);
1955109611Scjc		data = -1;
1956109611Scjc		if (conerrno == EADDRINUSE) {
19571592Srgrimes			sleep((unsigned) swaitint);
19581592Srgrimes			retry += swaitint;
1959109611Scjc		} else {
1960109611Scjc			break;
19611592Srgrimes		}
1962109611Scjc	} while (retry <= swaitmax);
1963109611Scjc	if (conerrno != 0) {
19641592Srgrimes		perror_reply(425, "Can't build data connection");
19651592Srgrimes		return (NULL);
19661592Srgrimes	}
19671592Srgrimes	reply(150, "Opening %s mode data connection for '%s'%s.",
19681592Srgrimes	     type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf);
19691592Srgrimes	return (file);
19701592Srgrimes}
19711592Srgrimes
19721592Srgrimes/*
19731592Srgrimes * Tranfer the contents of "instr" to "outstr" peer using the appropriate
19748240Swollman * encapsulation of the data subject to Mode, Structure, and Type.
19751592Srgrimes *
19761592Srgrimes * NB: Form isn't handled.
19771592Srgrimes */
197889935Syarstatic int
197990148Simpsend_data(FILE *instr, FILE *outstr, off_t blksize, off_t filesize, int isreg)
19801592Srgrimes{
1981122751Syar	int c, cp, filefd, netfd;
198270205Sdan	char *buf;
198370205Sdan	off_t cnt;
19841592Srgrimes
19851592Srgrimes	transflag++;
19861592Srgrimes	switch (type) {
19871592Srgrimes
19881592Srgrimes	case TYPE_A:
1989122751Syar		cp = '\0';
19901592Srgrimes		while ((c = getc(instr)) != EOF) {
199189935Syar			if (recvurg)
199289935Syar				goto got_oob;
19931592Srgrimes			byte_count++;
1994122751Syar			if (c == '\n' && cp != '\r') {
19951592Srgrimes				if (ferror(outstr))
19961592Srgrimes					goto data_err;
19971592Srgrimes				(void) putc('\r', outstr);
19981592Srgrimes			}
19991592Srgrimes			(void) putc(c, outstr);
2000122751Syar			cp = c;
20011592Srgrimes		}
200289935Syar		if (recvurg)
200389935Syar			goto got_oob;
20041592Srgrimes		fflush(outstr);
20051592Srgrimes		transflag = 0;
20061592Srgrimes		if (ferror(instr))
20071592Srgrimes			goto file_err;
20081592Srgrimes		if (ferror(outstr))
20091592Srgrimes			goto data_err;
20101592Srgrimes		reply(226, "Transfer complete.");
201189935Syar		return (0);
20121592Srgrimes
20131592Srgrimes	case TYPE_I:
20141592Srgrimes	case TYPE_L:
20158240Swollman		/*
20168240Swollman		 * isreg is only set if we are not doing restart and we
20178240Swollman		 * are sending a regular file
20188240Swollman		 */
20198240Swollman		netfd = fileno(outstr);
20208870Srgrimes		filefd = fileno(instr);
20218240Swollman
202270205Sdan		if (isreg) {
202370205Sdan
202470205Sdan			off_t offset;
202570205Sdan			int err;
202670205Sdan
202770205Sdan			err = cnt = offset = 0;
202870205Sdan
202990604Smaxim			while (err != -1 && filesize > 0) {
203090604Smaxim				err = sendfile(filefd, netfd, offset, 0,
203170205Sdan					(struct sf_hdtr *) NULL, &cnt, 0);
203299212Smaxim				/*
203399212Smaxim				 * Calculate byte_count before OOB processing.
203499212Smaxim				 * It can be used in myoob() later.
203599212Smaxim				 */
203699212Smaxim				byte_count += cnt;
203789935Syar				if (recvurg)
203889935Syar					goto got_oob;
203970205Sdan				offset += cnt;
204090604Smaxim				filesize -= cnt;
20418240Swollman
204270205Sdan				if (err == -1) {
204370205Sdan					if (!cnt)
204470205Sdan						goto oldway;
204570205Sdan
204670205Sdan					goto data_err;
204770205Sdan				}
204870205Sdan			}
204970205Sdan
205099318Sdan			transflag = 0;
20518240Swollman			reply(226, "Transfer complete.");
205289935Syar			return (0);
20538240Swollman		}
20548240Swollman
20558240Swollmanoldway:
20561592Srgrimes		if ((buf = malloc((u_int)blksize)) == NULL) {
20571592Srgrimes			transflag = 0;
20581592Srgrimes			perror_reply(451, "Local resource failure: malloc");
205989935Syar			return (-1);
20601592Srgrimes		}
20618870Srgrimes
20621592Srgrimes		while ((cnt = read(filefd, buf, (u_int)blksize)) > 0 &&
20631592Srgrimes		    write(netfd, buf, cnt) == cnt)
20641592Srgrimes			byte_count += cnt;
20651592Srgrimes		transflag = 0;
20661592Srgrimes		(void)free(buf);
20671592Srgrimes		if (cnt != 0) {
20681592Srgrimes			if (cnt < 0)
20691592Srgrimes				goto file_err;
20701592Srgrimes			goto data_err;
20711592Srgrimes		}
20721592Srgrimes		reply(226, "Transfer complete.");
207389935Syar		return (0);
20741592Srgrimes	default:
20751592Srgrimes		transflag = 0;
20761592Srgrimes		reply(550, "Unimplemented TYPE %d in send_data", type);
207789935Syar		return (-1);
20781592Srgrimes	}
20791592Srgrimes
20801592Srgrimesdata_err:
20811592Srgrimes	transflag = 0;
20821592Srgrimes	perror_reply(426, "Data connection");
208389935Syar	return (-1);
20841592Srgrimes
20851592Srgrimesfile_err:
20861592Srgrimes	transflag = 0;
20871592Srgrimes	perror_reply(551, "Error on input file");
208889935Syar	return (-1);
208989935Syar
209089935Syargot_oob:
209189935Syar	myoob();
209289935Syar	recvurg = 0;
209389935Syar	transflag = 0;
209489935Syar	return (-1);
20951592Srgrimes}
20961592Srgrimes
20971592Srgrimes/*
20981592Srgrimes * Transfer data from peer to "outstr" using the appropriate encapulation of
20991592Srgrimes * the data subject to Mode, Structure, and Type.
21001592Srgrimes *
21011592Srgrimes * N.B.: Form isn't handled.
21021592Srgrimes */
21031592Srgrimesstatic int
210490148Simpreceive_data(FILE *instr, FILE *outstr)
21051592Srgrimes{
21061592Srgrimes	int c;
210717433Spst	int cnt, bare_lfs;
21081592Srgrimes	char buf[BUFSIZ];
21091592Srgrimes
21101592Srgrimes	transflag++;
211117433Spst	bare_lfs = 0;
211217433Spst
21131592Srgrimes	switch (type) {
21141592Srgrimes
21151592Srgrimes	case TYPE_I:
21161592Srgrimes	case TYPE_L:
21171592Srgrimes		while ((cnt = read(fileno(instr), buf, sizeof(buf))) > 0) {
211889935Syar			if (recvurg)
211989935Syar				goto got_oob;
21201592Srgrimes			if (write(fileno(outstr), buf, cnt) != cnt)
21211592Srgrimes				goto file_err;
21221592Srgrimes			byte_count += cnt;
21231592Srgrimes		}
212489935Syar		if (recvurg)
212589935Syar			goto got_oob;
21261592Srgrimes		if (cnt < 0)
21271592Srgrimes			goto data_err;
21281592Srgrimes		transflag = 0;
21291592Srgrimes		return (0);
21301592Srgrimes
21311592Srgrimes	case TYPE_E:
21321592Srgrimes		reply(553, "TYPE E not implemented.");
21331592Srgrimes		transflag = 0;
21341592Srgrimes		return (-1);
21351592Srgrimes
21361592Srgrimes	case TYPE_A:
21371592Srgrimes		while ((c = getc(instr)) != EOF) {
213889935Syar			if (recvurg)
213989935Syar				goto got_oob;
21401592Srgrimes			byte_count++;
21411592Srgrimes			if (c == '\n')
21421592Srgrimes				bare_lfs++;
21431592Srgrimes			while (c == '\r') {
21441592Srgrimes				if (ferror(outstr))
21451592Srgrimes					goto data_err;
21461592Srgrimes				if ((c = getc(instr)) != '\n') {
21471592Srgrimes					(void) putc ('\r', outstr);
21481592Srgrimes					if (c == '\0' || c == EOF)
21491592Srgrimes						goto contin2;
21501592Srgrimes				}
21511592Srgrimes			}
21521592Srgrimes			(void) putc(c, outstr);
21531592Srgrimes	contin2:	;
21541592Srgrimes		}
215589935Syar		if (recvurg)
215689935Syar			goto got_oob;
21571592Srgrimes		fflush(outstr);
21581592Srgrimes		if (ferror(instr))
21591592Srgrimes			goto data_err;
21601592Srgrimes		if (ferror(outstr))
21611592Srgrimes			goto file_err;
21621592Srgrimes		transflag = 0;
21631592Srgrimes		if (bare_lfs) {
21641592Srgrimes			lreply(226,
21651592Srgrimes		"WARNING! %d bare linefeeds received in ASCII mode",
21661592Srgrimes			    bare_lfs);
21671592Srgrimes		(void)printf("   File may not have transferred correctly.\r\n");
21681592Srgrimes		}
21691592Srgrimes		return (0);
21701592Srgrimes	default:
21711592Srgrimes		reply(550, "Unimplemented TYPE %d in receive_data", type);
21721592Srgrimes		transflag = 0;
21731592Srgrimes		return (-1);
21741592Srgrimes	}
21751592Srgrimes
21761592Srgrimesdata_err:
21771592Srgrimes	transflag = 0;
21781592Srgrimes	perror_reply(426, "Data Connection");
21791592Srgrimes	return (-1);
21801592Srgrimes
21811592Srgrimesfile_err:
21821592Srgrimes	transflag = 0;
21831592Srgrimes	perror_reply(452, "Error writing file");
21841592Srgrimes	return (-1);
218589935Syar
218689935Syargot_oob:
218789935Syar	myoob();
218889935Syar	recvurg = 0;
218989935Syar	transflag = 0;
219089935Syar	return (-1);
21911592Srgrimes}
21921592Srgrimes
21931592Srgrimesvoid
219490148Simpstatfilecmd(char *filename)
21951592Srgrimes{
21961592Srgrimes	FILE *fin;
2197109382Syar	int atstart;
21981592Srgrimes	int c;
21991592Srgrimes	char line[LINE_MAX];
22001592Srgrimes
220125165Sdavidn	(void)snprintf(line, sizeof(line), _PATH_LS " -lgA %s", filename);
22021592Srgrimes	fin = ftpd_popen(line, "r");
22031592Srgrimes	lreply(211, "status of %s:", filename);
2204109382Syar	atstart = 1;
22051592Srgrimes	while ((c = getc(fin)) != EOF) {
22061592Srgrimes		if (c == '\n') {
22071592Srgrimes			if (ferror(stdout)){
22081592Srgrimes				perror_reply(421, "control connection");
22091592Srgrimes				(void) ftpd_pclose(fin);
22101592Srgrimes				dologout(1);
22111592Srgrimes				/* NOTREACHED */
22121592Srgrimes			}
22131592Srgrimes			if (ferror(fin)) {
22141592Srgrimes				perror_reply(551, filename);
22151592Srgrimes				(void) ftpd_pclose(fin);
22161592Srgrimes				return;
22171592Srgrimes			}
22181592Srgrimes			(void) putc('\r', stdout);
22191592Srgrimes		}
2220109382Syar		/*
2221109382Syar		 * RFC 959 says neutral text should be prepended before
2222109382Syar		 * a leading 3-digit number followed by whitespace, but
2223109382Syar		 * many ftp clients can be confused by any leading digits,
2224109382Syar		 * as a matter of fact.
2225109382Syar		 */
2226109382Syar		if (atstart && isdigit(c))
2227109382Syar			(void) putc(' ', stdout);
22281592Srgrimes		(void) putc(c, stdout);
2229109382Syar		atstart = (c == '\n');
22301592Srgrimes	}
22311592Srgrimes	(void) ftpd_pclose(fin);
22321592Srgrimes	reply(211, "End of Status");
22331592Srgrimes}
22341592Srgrimes
22351592Srgrimesvoid
223690148Simpstatcmd(void)
22371592Srgrimes{
223856668Sshin	union sockunion *su;
22391592Srgrimes	u_char *a, *p;
224099255Sume	char hname[NI_MAXHOST];
224156668Sshin	int ispassive;
22421592Srgrimes
2243110037Syar	if (hostinfo) {
2244110037Syar		lreply(211, "%s FTP server status:", hostname);
2245110037Syar		printf("     %s\r\n", version);
2246110037Syar	} else
2247110037Syar		lreply(211, "FTP server status:");
22481592Srgrimes	printf("     Connected to %s", remotehost);
224956668Sshin	if (!getnameinfo((struct sockaddr *)&his_addr, his_addr.su_len,
225099255Sume			 hname, sizeof(hname) - 1, NULL, 0, NI_NUMERICHOST)) {
225156668Sshin		if (strcmp(hname, remotehost) != 0)
225256668Sshin			printf(" (%s)", hname);
225356668Sshin	}
22541592Srgrimes	printf("\r\n");
22551592Srgrimes	if (logged_in) {
22561592Srgrimes		if (guest)
22571592Srgrimes			printf("     Logged in anonymously\r\n");
22581592Srgrimes		else
22591592Srgrimes			printf("     Logged in as %s\r\n", pw->pw_name);
22601592Srgrimes	} else if (askpasswd)
22611592Srgrimes		printf("     Waiting for password\r\n");
22621592Srgrimes	else
22631592Srgrimes		printf("     Waiting for user name\r\n");
22641592Srgrimes	printf("     TYPE: %s", typenames[type]);
22651592Srgrimes	if (type == TYPE_A || type == TYPE_E)
22661592Srgrimes		printf(", FORM: %s", formnames[form]);
22671592Srgrimes	if (type == TYPE_L)
2268103949Smike#if CHAR_BIT == 8
2269103949Smike		printf(" %d", CHAR_BIT);
22701592Srgrimes#else
22711592Srgrimes		printf(" %d", bytesize);	/* need definition! */
22721592Srgrimes#endif
22731592Srgrimes	printf("; STRUcture: %s; transfer MODE: %s\r\n",
22741592Srgrimes	    strunames[stru], modenames[mode]);
22751592Srgrimes	if (data != -1)
22761592Srgrimes		printf("     Data connection open\r\n");
22771592Srgrimes	else if (pdata != -1) {
227856668Sshin		ispassive = 1;
227956668Sshin		su = &pasv_addr;
22801592Srgrimes		goto printaddr;
22811592Srgrimes	} else if (usedefault == 0) {
228256668Sshin		ispassive = 0;
228356668Sshin		su = &data_dest;
22841592Srgrimesprintaddr:
22851592Srgrimes#define UC(b) (((int) b) & 0xff)
228656668Sshin		if (epsvall) {
228756668Sshin			printf("     EPSV only mode (EPSV ALL)\r\n");
228856668Sshin			goto epsvonly;
228956668Sshin		}
229056668Sshin
229156668Sshin		/* PORT/PASV */
229256668Sshin		if (su->su_family == AF_INET) {
229356668Sshin			a = (u_char *) &su->su_sin.sin_addr;
229456668Sshin			p = (u_char *) &su->su_sin.sin_port;
229556668Sshin			printf("     %s (%d,%d,%d,%d,%d,%d)\r\n",
229656668Sshin				ispassive ? "PASV" : "PORT",
229756668Sshin				UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]),
229856668Sshin				UC(p[0]), UC(p[1]));
229956668Sshin		}
230056668Sshin
230156668Sshin		/* LPRT/LPSV */
230256668Sshin	    {
230356668Sshin		int alen, af, i;
230456668Sshin
230556668Sshin		switch (su->su_family) {
230656668Sshin		case AF_INET:
230756668Sshin			a = (u_char *) &su->su_sin.sin_addr;
230856668Sshin			p = (u_char *) &su->su_sin.sin_port;
230956668Sshin			alen = sizeof(su->su_sin.sin_addr);
231056668Sshin			af = 4;
231156668Sshin			break;
231256668Sshin		case AF_INET6:
231356668Sshin			a = (u_char *) &su->su_sin6.sin6_addr;
231456668Sshin			p = (u_char *) &su->su_sin6.sin6_port;
231556668Sshin			alen = sizeof(su->su_sin6.sin6_addr);
231656668Sshin			af = 6;
231756668Sshin			break;
231856668Sshin		default:
231956668Sshin			af = 0;
232056668Sshin			break;
232156668Sshin		}
232256668Sshin		if (af) {
232356668Sshin			printf("     %s (%d,%d,", ispassive ? "LPSV" : "LPRT",
232456668Sshin				af, alen);
232556668Sshin			for (i = 0; i < alen; i++)
232656668Sshin				printf("%d,", UC(a[i]));
232756668Sshin			printf("%d,%d,%d)\r\n", 2, UC(p[0]), UC(p[1]));
232856668Sshin		}
232956668Sshin	    }
233056668Sshin
233156668Sshinepsvonly:;
233256668Sshin		/* EPRT/EPSV */
233356668Sshin	    {
233456668Sshin		int af;
233556668Sshin
233656668Sshin		switch (su->su_family) {
233756668Sshin		case AF_INET:
233856668Sshin			af = 1;
233956668Sshin			break;
234056668Sshin		case AF_INET6:
234156668Sshin			af = 2;
234256668Sshin			break;
234356668Sshin		default:
234456668Sshin			af = 0;
234556668Sshin			break;
234656668Sshin		}
234756668Sshin		if (af) {
234899255Sume			union sockunion tmp;
234999255Sume
235099255Sume			tmp = *su;
235199255Sume			if (tmp.su_family == AF_INET6)
235299255Sume				tmp.su_sin6.sin6_scope_id = 0;
235399255Sume			if (!getnameinfo((struct sockaddr *)&tmp, tmp.su_len,
235456668Sshin					hname, sizeof(hname) - 1, NULL, 0,
235556668Sshin					NI_NUMERICHOST)) {
235656668Sshin				printf("     %s |%d|%s|%d|\r\n",
235756668Sshin					ispassive ? "EPSV" : "EPRT",
235899255Sume					af, hname, htons(tmp.su_port));
235956668Sshin			}
236056668Sshin		}
236156668Sshin	    }
23621592Srgrimes#undef UC
23631592Srgrimes	} else
23641592Srgrimes		printf("     No data connection\r\n");
23651592Srgrimes	reply(211, "End of status");
23661592Srgrimes}
23671592Srgrimes
23681592Srgrimesvoid
236990148Simpfatalerror(char *s)
23701592Srgrimes{
23711592Srgrimes
23721592Srgrimes	reply(451, "Error in server: %s\n", s);
23731592Srgrimes	reply(221, "Closing connection due to server error.");
23741592Srgrimes	dologout(0);
23751592Srgrimes	/* NOTREACHED */
23761592Srgrimes}
23771592Srgrimes
23781592Srgrimesvoid
23791592Srgrimesreply(int n, const char *fmt, ...)
23801592Srgrimes{
23811592Srgrimes	va_list ap;
238290148Simp
2383129170Stjr	(void)printf("%d ", n);
23841592Srgrimes	va_start(ap, fmt);
23851592Srgrimes	(void)vprintf(fmt, ap);
2386129170Stjr	va_end(ap);
23871592Srgrimes	(void)printf("\r\n");
23881592Srgrimes	(void)fflush(stdout);
238976096Smarkm	if (ftpdebug) {
23901592Srgrimes		syslog(LOG_DEBUG, "<--- %d ", n);
2391129170Stjr		va_start(ap, fmt);
23921592Srgrimes		vsyslog(LOG_DEBUG, fmt, ap);
2393129170Stjr		va_end(ap);
23941592Srgrimes	}
23951592Srgrimes}
23961592Srgrimes
23971592Srgrimesvoid
23981592Srgrimeslreply(int n, const char *fmt, ...)
23991592Srgrimes{
24001592Srgrimes	va_list ap;
240190148Simp
2402129170Stjr	(void)printf("%d- ", n);
24031592Srgrimes	va_start(ap, fmt);
24041592Srgrimes	(void)vprintf(fmt, ap);
2405129170Stjr	va_end(ap);
24061592Srgrimes	(void)printf("\r\n");
24071592Srgrimes	(void)fflush(stdout);
240876096Smarkm	if (ftpdebug) {
24091592Srgrimes		syslog(LOG_DEBUG, "<--- %d- ", n);
2410129170Stjr		va_start(ap, fmt);
24111592Srgrimes		vsyslog(LOG_DEBUG, fmt, ap);
2412129170Stjr		va_end(ap);
24131592Srgrimes	}
24141592Srgrimes}
24151592Srgrimes
24161592Srgrimesstatic void
241790148Simpack(char *s)
24181592Srgrimes{
24191592Srgrimes
24201592Srgrimes	reply(250, "%s command successful.", s);
24211592Srgrimes}
24221592Srgrimes
24231592Srgrimesvoid
242490148Simpnack(char *s)
24251592Srgrimes{
24261592Srgrimes
24271592Srgrimes	reply(502, "%s command not implemented.", s);
24281592Srgrimes}
24291592Srgrimes
24301592Srgrimes/* ARGSUSED */
24311592Srgrimesvoid
243290148Simpyyerror(char *s)
24331592Srgrimes{
24341592Srgrimes	char *cp;
24351592Srgrimes
243617478Smarkm	if ((cp = strchr(cbuf,'\n')))
24371592Srgrimes		*cp = '\0';
24381592Srgrimes	reply(500, "'%s': command not understood.", cbuf);
24391592Srgrimes}
24401592Srgrimes
24411592Srgrimesvoid
244290148Simpdelete(char *name)
24431592Srgrimes{
24441592Srgrimes	struct stat st;
24451592Srgrimes
24461592Srgrimes	LOGCMD("delete", name);
2447100439Syar	if (lstat(name, &st) < 0) {
24481592Srgrimes		perror_reply(550, name);
24491592Srgrimes		return;
24501592Srgrimes	}
24511592Srgrimes	if ((st.st_mode&S_IFMT) == S_IFDIR) {
24521592Srgrimes		if (rmdir(name) < 0) {
24531592Srgrimes			perror_reply(550, name);
24541592Srgrimes			return;
24551592Srgrimes		}
24561592Srgrimes		goto done;
24571592Srgrimes	}
2458125568Syar	if (guest && noguestmod) {
2459125568Syar		reply(550, "Operation not permitted");
2460125568Syar		return;
2461125568Syar	}
2462125568Syar	if (unlink(name) < 0) {
24631592Srgrimes		perror_reply(550, name);
24641592Srgrimes		return;
24651592Srgrimes	}
24661592Srgrimesdone:
24671592Srgrimes	ack("DELE");
24681592Srgrimes}
24691592Srgrimes
24701592Srgrimesvoid
247190148Simpcwd(char *path)
24721592Srgrimes{
24731592Srgrimes
24741592Srgrimes	if (chdir(path) < 0)
24751592Srgrimes		perror_reply(550, path);
24761592Srgrimes	else
24771592Srgrimes		ack("CWD");
24781592Srgrimes}
24791592Srgrimes
24801592Srgrimesvoid
248190148Simpmakedir(char *name)
24821592Srgrimes{
2483100878Syar	char *s;
24841592Srgrimes
24851592Srgrimes	LOGCMD("mkdir", name);
248699195Smdodd	if (guest && noguestmkd)
248799195Smdodd		reply(550, "%s: permission denied", name);
248899195Smdodd	else if (mkdir(name, 0777) < 0)
24891592Srgrimes		perror_reply(550, name);
2490100878Syar	else {
2491100878Syar		if ((s = doublequote(name)) == NULL)
2492100878Syar			fatalerror("Ran out of memory.");
2493100878Syar		reply(257, "\"%s\" directory created.", s);
2494100878Syar		free(s);
2495100878Syar	}
24961592Srgrimes}
24971592Srgrimes
24981592Srgrimesvoid
249990148Simpremovedir(char *name)
25001592Srgrimes{
25011592Srgrimes
25021592Srgrimes	LOGCMD("rmdir", name);
25031592Srgrimes	if (rmdir(name) < 0)
25041592Srgrimes		perror_reply(550, name);
25051592Srgrimes	else
25061592Srgrimes		ack("RMD");
25071592Srgrimes}
25081592Srgrimes
25091592Srgrimesvoid
251090148Simppwd(void)
25111592Srgrimes{
2512100486Syar	char *s, path[MAXPATHLEN + 1];
25131592Srgrimes
25141592Srgrimes	if (getwd(path) == (char *)NULL)
25151592Srgrimes		reply(550, "%s.", path);
2516100486Syar	else {
2517100486Syar		if ((s = doublequote(path)) == NULL)
2518100486Syar			fatalerror("Ran out of memory.");
2519100486Syar		reply(257, "\"%s\" is current directory.", s);
2520100486Syar		free(s);
2521100486Syar	}
25221592Srgrimes}
25231592Srgrimes
25241592Srgrimeschar *
252590148Simprenamefrom(char *name)
25261592Srgrimes{
25271592Srgrimes	struct stat st;
25281592Srgrimes
2529125569Syar	if (guest && noguestmod) {
2530125569Syar		reply(550, "Operation not permitted");
2531125569Syar		return (NULL);
2532125569Syar	}
2533100439Syar	if (lstat(name, &st) < 0) {
25341592Srgrimes		perror_reply(550, name);
2535125570Syar		return (NULL);
25361592Srgrimes	}
25371592Srgrimes	reply(350, "File exists, ready for destination name");
25381592Srgrimes	return (name);
25391592Srgrimes}
25401592Srgrimes
25411592Srgrimesvoid
254290148Simprenamecmd(char *from, char *to)
25431592Srgrimes{
254417433Spst	struct stat st;
25451592Srgrimes
25461592Srgrimes	LOGCMD2("rename", from, to);
254717433Spst
254817433Spst	if (guest && (stat(to, &st) == 0)) {
254917433Spst		reply(550, "%s: permission denied", to);
255017433Spst		return;
255117433Spst	}
255217433Spst
25531592Srgrimes	if (rename(from, to) < 0)
25541592Srgrimes		perror_reply(550, "rename");
25551592Srgrimes	else
25561592Srgrimes		ack("RNTO");
25571592Srgrimes}
25581592Srgrimes
25591592Srgrimesstatic void
256090148Simpdolog(struct sockaddr *who)
25611592Srgrimes{
256256668Sshin	int error;
25631592Srgrimes
256456668Sshin	realhostname_sa(remotehost, sizeof(remotehost) - 1, who, who->sa_len);
256556668Sshin
25661592Srgrimes#ifdef SETPROCTITLE
256725283Sdavidn#ifdef VIRTUAL_HOSTING
256825283Sdavidn	if (thishost != firsthost)
256925283Sdavidn		snprintf(proctitle, sizeof(proctitle), "%s: connected (to %s)",
257025283Sdavidn			 remotehost, hostname);
257125283Sdavidn	else
257225283Sdavidn#endif
257325283Sdavidn		snprintf(proctitle, sizeof(proctitle), "%s: connected",
257425283Sdavidn			 remotehost);
257513139Speter	setproctitle("%s", proctitle);
25761592Srgrimes#endif /* SETPROCTITLE */
25771592Srgrimes
257825283Sdavidn	if (logging) {
257925283Sdavidn#ifdef VIRTUAL_HOSTING
258025283Sdavidn		if (thishost != firsthost)
258125283Sdavidn			syslog(LOG_INFO, "connection from %s (to %s)",
258225283Sdavidn			       remotehost, hostname);
258325283Sdavidn		else
258425283Sdavidn#endif
258556668Sshin		{
258656668Sshin			char	who_name[MAXHOSTNAMELEN];
258756668Sshin
258856668Sshin			error = getnameinfo(who, who->sa_len,
258956668Sshin					    who_name, sizeof(who_name) - 1,
259099255Sume					    NULL, 0, NI_NUMERICHOST);
259133782Seivind			syslog(LOG_INFO, "connection from %s (%s)", remotehost,
259256668Sshin			       error == 0 ? who_name : "");
259356668Sshin		}
259425283Sdavidn	}
25951592Srgrimes}
25961592Srgrimes
25971592Srgrimes/*
25981592Srgrimes * Record logout in wtmp file
25991592Srgrimes * and exit with supplied status.
26001592Srgrimes */
26011592Srgrimesvoid
260290148Simpdologout(int status)
26031592Srgrimes{
260422057Sdg	/*
260522057Sdg	 * Prevent reception of SIGURG from resulting in a resumption
260622057Sdg	 * back to the main program loop.
260722058Sdg	 */
260822057Sdg	transflag = 0;
26091592Srgrimes
2610102311Syar	if (logged_in && dowtmp) {
26111592Srgrimes		(void) seteuid((uid_t)0);
261289920Sume		ftpd_logwtmp(ttyline, "", NULL);
26131592Srgrimes	}
26141592Srgrimes	/* beware of flushing buffers after a SIGPIPE */
26151592Srgrimes	_exit(status);
26161592Srgrimes}
26171592Srgrimes
26181592Srgrimesstatic void
261990148Simpsigurg(int signo)
26201592Srgrimes{
262189935Syar
262289935Syar	recvurg = 1;
262389935Syar}
262489935Syar
262589935Syarstatic void
262690148Simpmyoob(void)
262789935Syar{
26281592Srgrimes	char *cp;
26291592Srgrimes
26301592Srgrimes	/* only process if transfer occurring */
26311592Srgrimes	if (!transflag)
26321592Srgrimes		return;
26331592Srgrimes	cp = tmpline;
26341592Srgrimes	if (getline(cp, 7, stdin) == NULL) {
26351592Srgrimes		reply(221, "You could at least say goodbye.");
26361592Srgrimes		dologout(0);
26371592Srgrimes	}
26381592Srgrimes	upper(cp);
26391592Srgrimes	if (strcmp(cp, "ABOR\r\n") == 0) {
26401592Srgrimes		tmpline[0] = '\0';
26411592Srgrimes		reply(426, "Transfer aborted. Data connection closed.");
26421592Srgrimes		reply(226, "Abort successful");
26431592Srgrimes	}
26441592Srgrimes	if (strcmp(cp, "STAT\r\n") == 0) {
264551192Smharo		tmpline[0] = '\0';
26461592Srgrimes		if (file_size != (off_t) -1)
26471592Srgrimes			reply(213, "Status: %qd of %qd bytes transferred",
26481592Srgrimes			    byte_count, file_size);
26491592Srgrimes		else
26501592Srgrimes			reply(213, "Status: %qd bytes transferred", byte_count);
26511592Srgrimes	}
26521592Srgrimes}
26531592Srgrimes
26541592Srgrimes/*
26551592Srgrimes * Note: a response of 425 is not mentioned as a possible response to
26561592Srgrimes *	the PASV command in RFC959. However, it has been blessed as
26571592Srgrimes *	a legitimate response by Jon Postel in a telephone conversation
26581592Srgrimes *	with Rick Adams on 25 Jan 89.
26591592Srgrimes */
26601592Srgrimesvoid
266190148Simppassive(void)
26621592Srgrimes{
2663100615Syar	int len, on;
26641592Srgrimes	char *p, *a;
26651592Srgrimes
266617433Spst	if (pdata >= 0)		/* close old port if one set */
266717433Spst		close(pdata);
266817433Spst
266956668Sshin	pdata = socket(ctrl_addr.su_family, SOCK_STREAM, 0);
26701592Srgrimes	if (pdata < 0) {
26711592Srgrimes		perror_reply(425, "Can't open passive connection");
26721592Srgrimes		return;
26731592Srgrimes	}
2674100615Syar	on = 1;
2675100615Syar	if (setsockopt(pdata, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
2676100615Syar		syslog(LOG_WARNING, "pdata setsockopt (SO_REUSEADDR): %m");
26779933Spst
267817433Spst	(void) seteuid((uid_t)0);
267917433Spst
268019903Spst#ifdef IP_PORTRANGE
268156668Sshin	if (ctrl_addr.su_family == AF_INET) {
2682100615Syar	    on = restricted_data_ports ? IP_PORTRANGE_HIGH
2683100615Syar				       : IP_PORTRANGE_DEFAULT;
268419903Spst
268519903Spst	    if (setsockopt(pdata, IPPROTO_IP, IP_PORTRANGE,
2686100612Syar			    &on, sizeof(on)) < 0)
268719903Spst		    goto pasv_error;
26881592Srgrimes	}
268919903Spst#endif
269060929Snsayer#ifdef IPV6_PORTRANGE
269160929Snsayer	if (ctrl_addr.su_family == AF_INET6) {
2692100615Syar	    on = restricted_data_ports ? IPV6_PORTRANGE_HIGH
2693100615Syar				       : IPV6_PORTRANGE_DEFAULT;
26949933Spst
269560929Snsayer	    if (setsockopt(pdata, IPPROTO_IPV6, IPV6_PORTRANGE,
2696100612Syar			    &on, sizeof(on)) < 0)
269760929Snsayer		    goto pasv_error;
269860929Snsayer	}
269960929Snsayer#endif
270060929Snsayer
270116033Speter	pasv_addr = ctrl_addr;
270256668Sshin	pasv_addr.su_port = 0;
270356668Sshin	if (bind(pdata, (struct sockaddr *)&pasv_addr, pasv_addr.su_len) < 0)
270416033Speter		goto pasv_error;
270517433Spst
270616033Speter	(void) seteuid((uid_t)pw->pw_uid);
270716033Speter
27081592Srgrimes	len = sizeof(pasv_addr);
27091592Srgrimes	if (getsockname(pdata, (struct sockaddr *) &pasv_addr, &len) < 0)
27101592Srgrimes		goto pasv_error;
27111592Srgrimes	if (listen(pdata, 1) < 0)
27121592Srgrimes		goto pasv_error;
271356668Sshin	if (pasv_addr.su_family == AF_INET)
271456668Sshin		a = (char *) &pasv_addr.su_sin.sin_addr;
271556668Sshin	else if (pasv_addr.su_family == AF_INET6 &&
271656668Sshin		 IN6_IS_ADDR_V4MAPPED(&pasv_addr.su_sin6.sin6_addr))
271756668Sshin		a = (char *) &pasv_addr.su_sin6.sin6_addr.s6_addr[12];
271856668Sshin	else
271956668Sshin		goto pasv_error;
272056668Sshin
272156668Sshin	p = (char *) &pasv_addr.su_port;
27221592Srgrimes
27231592Srgrimes#define UC(b) (((int) b) & 0xff)
27241592Srgrimes
27251592Srgrimes	reply(227, "Entering Passive Mode (%d,%d,%d,%d,%d,%d)", UC(a[0]),
27261592Srgrimes		UC(a[1]), UC(a[2]), UC(a[3]), UC(p[0]), UC(p[1]));
27271592Srgrimes	return;
27281592Srgrimes
27291592Srgrimespasv_error:
273017433Spst	(void) seteuid((uid_t)pw->pw_uid);
27311592Srgrimes	(void) close(pdata);
27321592Srgrimes	pdata = -1;
27331592Srgrimes	perror_reply(425, "Can't open passive connection");
27341592Srgrimes	return;
27351592Srgrimes}
27361592Srgrimes
27371592Srgrimes/*
273856668Sshin * Long Passive defined in RFC 1639.
273956668Sshin *     228 Entering Long Passive Mode
274056668Sshin *         (af, hal, h1, h2, h3,..., pal, p1, p2...)
274156668Sshin */
274256668Sshin
274356668Sshinvoid
274490148Simplong_passive(char *cmd, int pf)
274556668Sshin{
2746100615Syar	int len, on;
274756668Sshin	char *p, *a;
274856668Sshin
274956668Sshin	if (pdata >= 0)		/* close old port if one set */
275056668Sshin		close(pdata);
275156668Sshin
275256668Sshin	if (pf != PF_UNSPEC) {
275356668Sshin		if (ctrl_addr.su_family != pf) {
275456668Sshin			switch (ctrl_addr.su_family) {
275556668Sshin			case AF_INET:
275656668Sshin				pf = 1;
275756668Sshin				break;
275856668Sshin			case AF_INET6:
275956668Sshin				pf = 2;
276056668Sshin				break;
276156668Sshin			default:
276256668Sshin				pf = 0;
276356668Sshin				break;
276456668Sshin			}
276556668Sshin			/*
276656668Sshin			 * XXX
276756668Sshin			 * only EPRT/EPSV ready clients will understand this
276856668Sshin			 */
276956668Sshin			if (strcmp(cmd, "EPSV") == 0 && pf) {
277056668Sshin				reply(522, "Network protocol mismatch, "
277156668Sshin					"use (%d)", pf);
277256668Sshin			} else
277356668Sshin				reply(501, "Network protocol mismatch"); /*XXX*/
277456668Sshin
277556668Sshin			return;
277656668Sshin		}
277756668Sshin	}
277856668Sshin
277956668Sshin	pdata = socket(ctrl_addr.su_family, SOCK_STREAM, 0);
278056668Sshin	if (pdata < 0) {
278156668Sshin		perror_reply(425, "Can't open passive connection");
278256668Sshin		return;
278356668Sshin	}
2784100615Syar	on = 1;
2785100615Syar	if (setsockopt(pdata, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
2786100615Syar		syslog(LOG_WARNING, "pdata setsockopt (SO_REUSEADDR): %m");
278756668Sshin
278856668Sshin	(void) seteuid((uid_t)0);
278956668Sshin
279056668Sshin	pasv_addr = ctrl_addr;
279156668Sshin	pasv_addr.su_port = 0;
279256668Sshin	len = pasv_addr.su_len;
279356668Sshin
279460929Snsayer#ifdef IP_PORTRANGE
279560929Snsayer	if (ctrl_addr.su_family == AF_INET) {
2796100615Syar	    on = restricted_data_ports ? IP_PORTRANGE_HIGH
2797100615Syar				       : IP_PORTRANGE_DEFAULT;
279860929Snsayer
279960929Snsayer	    if (setsockopt(pdata, IPPROTO_IP, IP_PORTRANGE,
2800100612Syar			    &on, sizeof(on)) < 0)
280160929Snsayer		    goto pasv_error;
280260929Snsayer	}
280360929Snsayer#endif
280460929Snsayer#ifdef IPV6_PORTRANGE
280560929Snsayer	if (ctrl_addr.su_family == AF_INET6) {
2806100615Syar	    on = restricted_data_ports ? IPV6_PORTRANGE_HIGH
2807100615Syar				       : IPV6_PORTRANGE_DEFAULT;
280860929Snsayer
280960929Snsayer	    if (setsockopt(pdata, IPPROTO_IPV6, IPV6_PORTRANGE,
2810100612Syar			    &on, sizeof(on)) < 0)
281160929Snsayer		    goto pasv_error;
281260929Snsayer	}
281360929Snsayer#endif
281460929Snsayer
281556668Sshin	if (bind(pdata, (struct sockaddr *)&pasv_addr, len) < 0)
281656668Sshin		goto pasv_error;
281756668Sshin
281856668Sshin	(void) seteuid((uid_t)pw->pw_uid);
281956668Sshin
282056668Sshin	if (getsockname(pdata, (struct sockaddr *) &pasv_addr, &len) < 0)
282156668Sshin		goto pasv_error;
282256668Sshin	if (listen(pdata, 1) < 0)
282356668Sshin		goto pasv_error;
282456668Sshin
282556668Sshin#define UC(b) (((int) b) & 0xff)
282656668Sshin
282756668Sshin	if (strcmp(cmd, "LPSV") == 0) {
282856668Sshin		p = (char *)&pasv_addr.su_port;
282956668Sshin		switch (pasv_addr.su_family) {
283056668Sshin		case AF_INET:
283156668Sshin			a = (char *) &pasv_addr.su_sin.sin_addr;
283256668Sshin		v4_reply:
283356668Sshin			reply(228,
283456668Sshin"Entering Long Passive Mode (%d,%d,%d,%d,%d,%d,%d,%d,%d)",
283556668Sshin			      4, 4, UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]),
283656668Sshin			      2, UC(p[0]), UC(p[1]));
283756668Sshin			return;
283856668Sshin		case AF_INET6:
283956668Sshin			if (IN6_IS_ADDR_V4MAPPED(&pasv_addr.su_sin6.sin6_addr)) {
284056668Sshin				a = (char *) &pasv_addr.su_sin6.sin6_addr.s6_addr[12];
284156668Sshin				goto v4_reply;
284256668Sshin			}
284356668Sshin			a = (char *) &pasv_addr.su_sin6.sin6_addr;
284456668Sshin			reply(228,
284556668Sshin"Entering Long Passive Mode "
284656668Sshin"(%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d)",
284756668Sshin			      6, 16, UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]),
284856668Sshin			      UC(a[4]), UC(a[5]), UC(a[6]), UC(a[7]),
284956668Sshin			      UC(a[8]), UC(a[9]), UC(a[10]), UC(a[11]),
285056668Sshin			      UC(a[12]), UC(a[13]), UC(a[14]), UC(a[15]),
285156668Sshin			      2, UC(p[0]), UC(p[1]));
285256668Sshin			return;
285356668Sshin		}
285456668Sshin	} else if (strcmp(cmd, "EPSV") == 0) {
285556668Sshin		switch (pasv_addr.su_family) {
285656668Sshin		case AF_INET:
285756668Sshin		case AF_INET6:
285856668Sshin			reply(229, "Entering Extended Passive Mode (|||%d|)",
285956668Sshin				ntohs(pasv_addr.su_port));
286056668Sshin			return;
286156668Sshin		}
286256668Sshin	} else {
286356668Sshin		/* more proper error code? */
286456668Sshin	}
286556668Sshin
286656668Sshinpasv_error:
286756668Sshin	(void) seteuid((uid_t)pw->pw_uid);
286856668Sshin	(void) close(pdata);
286956668Sshin	pdata = -1;
287056668Sshin	perror_reply(425, "Can't open passive connection");
287156668Sshin	return;
287256668Sshin}
287356668Sshin
287456668Sshin/*
2875101537Syar * Generate unique name for file with basename "local"
2876101537Syar * and open the file in order to avoid possible races.
2877101537Syar * Try "local" first, then "local.1", "local.2" etc, up to "local.99".
2878101537Syar * Return descriptor to the file, set "name" to its name.
2879101537Syar *
28801592Srgrimes * Generates failure reply on error.
28811592Srgrimes */
2882101537Syarstatic int
2883101537Syarguniquefd(char *local, char **name)
28841592Srgrimes{
28851592Srgrimes	static char new[MAXPATHLEN];
28861592Srgrimes	struct stat st;
2887101537Syar	char *cp;
28881592Srgrimes	int count;
2889101537Syar	int fd;
28901592Srgrimes
28911592Srgrimes	cp = strrchr(local, '/');
28921592Srgrimes	if (cp)
28931592Srgrimes		*cp = '\0';
28941592Srgrimes	if (stat(cp ? local : ".", &st) < 0) {
28951592Srgrimes		perror_reply(553, cp ? local : ".");
2896101537Syar		return (-1);
28971592Srgrimes	}
2898101537Syar	if (cp) {
2899101537Syar		/*
2900101537Syar		 * Let not overwrite dirname with counter suffix.
2901101537Syar		 * -4 is for /nn\0
2902101537Syar		 * In this extreme case dot won't be put in front of suffix.
2903101537Syar		 */
2904101537Syar		if (strlen(local) > sizeof(new) - 4) {
2905101537Syar			reply(553, "Pathname too long");
2906101537Syar			return (-1);
2907101537Syar		}
29081592Srgrimes		*cp = '/';
2909101537Syar	}
291031973Simp	/* -4 is for the .nn<null> we put on the end below */
291131973Simp	(void) snprintf(new, sizeof(new) - 4, "%s", local);
29121592Srgrimes	cp = new + strlen(new);
2913101537Syar	/*
2914101537Syar	 * Don't generate dotfile unless requested explicitly.
2915101537Syar	 * This covers the case when basename gets truncated off
2916101537Syar	 * by buffer size.
2917101537Syar	 */
2918101537Syar	if (cp > new && cp[-1] != '/')
2919101537Syar		*cp++ = '.';
2920101537Syar	for (count = 0; count < 100; count++) {
2921101537Syar		/* At count 0 try unmodified name */
2922101537Syar		if (count)
2923101537Syar			(void)sprintf(cp, "%d", count);
2924101537Syar		if ((fd = open(count ? new : local,
2925101537Syar		    O_RDWR | O_CREAT | O_EXCL, 0666)) >= 0) {
2926101537Syar			*name = count ? new : local;
2927101537Syar			return (fd);
2928101537Syar		}
2929110046Syar		if (errno != EEXIST) {
2930110307Syar			perror_reply(553, count ? new : local);
2931110046Syar			return (-1);
2932110046Syar		}
29331592Srgrimes	}
29341592Srgrimes	reply(452, "Unique file name cannot be created.");
2935101537Syar	return (-1);
29361592Srgrimes}
29371592Srgrimes
29381592Srgrimes/*
29391592Srgrimes * Format and send reply containing system error number.
29401592Srgrimes */
29411592Srgrimesvoid
294290148Simpperror_reply(int code, char *string)
29431592Srgrimes{
29441592Srgrimes
29451592Srgrimes	reply(code, "%s: %s.", string, strerror(errno));
29461592Srgrimes}
29471592Srgrimes
29481592Srgrimesstatic char *onefile[] = {
29491592Srgrimes	"",
29501592Srgrimes	0
29511592Srgrimes};
29521592Srgrimes
29531592Srgrimesvoid
295490148Simpsend_file_list(char *whichf)
29551592Srgrimes{
29561592Srgrimes	struct stat st;
29571592Srgrimes	DIR *dirp = NULL;
29581592Srgrimes	struct dirent *dir;
29591592Srgrimes	FILE *dout = NULL;
29601592Srgrimes	char **dirlist, *dirname;
29611592Srgrimes	int simple = 0;
29621592Srgrimes	int freeglob = 0;
29631592Srgrimes	glob_t gl;
29641592Srgrimes
29651592Srgrimes	if (strpbrk(whichf, "~{[*?") != NULL) {
2966100222Smikeh		int flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE;
29671592Srgrimes
29681592Srgrimes		memset(&gl, 0, sizeof(gl));
296974470Sjlemon		gl.gl_matchc = MAXGLOBARGS;
297080525Smikeh		flags |= GLOB_LIMIT;
29711592Srgrimes		freeglob = 1;
29721592Srgrimes		if (glob(whichf, flags, 0, &gl)) {
29731592Srgrimes			reply(550, "not found");
29741592Srgrimes			goto out;
29751592Srgrimes		} else if (gl.gl_pathc == 0) {
29761592Srgrimes			errno = ENOENT;
29771592Srgrimes			perror_reply(550, whichf);
29781592Srgrimes			goto out;
29791592Srgrimes		}
29801592Srgrimes		dirlist = gl.gl_pathv;
29811592Srgrimes	} else {
29821592Srgrimes		onefile[0] = whichf;
29831592Srgrimes		dirlist = onefile;
29841592Srgrimes		simple = 1;
29851592Srgrimes	}
29861592Srgrimes
298717478Smarkm	while ((dirname = *dirlist++)) {
29881592Srgrimes		if (stat(dirname, &st) < 0) {
29891592Srgrimes			/*
29901592Srgrimes			 * If user typed "ls -l", etc, and the client
29911592Srgrimes			 * used NLST, do what the user meant.
29921592Srgrimes			 */
29931592Srgrimes			if (dirname[0] == '-' && *dirlist == NULL &&
29941592Srgrimes			    transflag == 0) {
299525165Sdavidn				retrieve(_PATH_LS " %s", dirname);
29961592Srgrimes				goto out;
29971592Srgrimes			}
29981592Srgrimes			perror_reply(550, whichf);
29991592Srgrimes			if (dout != NULL) {
30001592Srgrimes				(void) fclose(dout);
30011592Srgrimes				transflag = 0;
30021592Srgrimes				data = -1;
30031592Srgrimes				pdata = -1;
30041592Srgrimes			}
30051592Srgrimes			goto out;
30061592Srgrimes		}
30071592Srgrimes
30081592Srgrimes		if (S_ISREG(st.st_mode)) {
30091592Srgrimes			if (dout == NULL) {
30101592Srgrimes				dout = dataconn("file list", (off_t)-1, "w");
30111592Srgrimes				if (dout == NULL)
30121592Srgrimes					goto out;
30131592Srgrimes				transflag++;
30141592Srgrimes			}
30151592Srgrimes			fprintf(dout, "%s%s\n", dirname,
30161592Srgrimes				type == TYPE_A ? "\r" : "");
30171592Srgrimes			byte_count += strlen(dirname) + 1;
30181592Srgrimes			continue;
30191592Srgrimes		} else if (!S_ISDIR(st.st_mode))
30201592Srgrimes			continue;
30211592Srgrimes
30221592Srgrimes		if ((dirp = opendir(dirname)) == NULL)
30231592Srgrimes			continue;
30241592Srgrimes
30251592Srgrimes		while ((dir = readdir(dirp)) != NULL) {
30261592Srgrimes			char nbuf[MAXPATHLEN];
30271592Srgrimes
302889935Syar			if (recvurg) {
302989935Syar				myoob();
303089935Syar				recvurg = 0;
303189935Syar				transflag = 0;
303289935Syar				goto out;
303389935Syar			}
303489935Syar
30351592Srgrimes			if (dir->d_name[0] == '.' && dir->d_namlen == 1)
30361592Srgrimes				continue;
30371592Srgrimes			if (dir->d_name[0] == '.' && dir->d_name[1] == '.' &&
30381592Srgrimes			    dir->d_namlen == 2)
30391592Srgrimes				continue;
30401592Srgrimes
304199213Smaxim			snprintf(nbuf, sizeof(nbuf),
304231973Simp				"%s/%s", dirname, dir->d_name);
30431592Srgrimes
30441592Srgrimes			/*
30451592Srgrimes			 * We have to do a stat to insure it's
30461592Srgrimes			 * not a directory or special file.
30471592Srgrimes			 */
30481592Srgrimes			if (simple || (stat(nbuf, &st) == 0 &&
30491592Srgrimes			    S_ISREG(st.st_mode))) {
30501592Srgrimes				if (dout == NULL) {
30511592Srgrimes					dout = dataconn("file list", (off_t)-1,
30521592Srgrimes						"w");
30531592Srgrimes					if (dout == NULL)
30541592Srgrimes						goto out;
30551592Srgrimes					transflag++;
30561592Srgrimes				}
30571592Srgrimes				if (nbuf[0] == '.' && nbuf[1] == '/')
30581592Srgrimes					fprintf(dout, "%s%s\n", &nbuf[2],
30591592Srgrimes						type == TYPE_A ? "\r" : "");
30601592Srgrimes				else
30611592Srgrimes					fprintf(dout, "%s%s\n", nbuf,
30621592Srgrimes						type == TYPE_A ? "\r" : "");
30631592Srgrimes				byte_count += strlen(nbuf) + 1;
30641592Srgrimes			}
30651592Srgrimes		}
30661592Srgrimes		(void) closedir(dirp);
30671592Srgrimes	}
30681592Srgrimes
30691592Srgrimes	if (dout == NULL)
30701592Srgrimes		reply(550, "No files found.");
30711592Srgrimes	else if (ferror(dout) != 0)
30721592Srgrimes		perror_reply(550, "Data connection");
30731592Srgrimes	else
30741592Srgrimes		reply(226, "Transfer complete.");
30751592Srgrimes
30761592Srgrimes	transflag = 0;
30771592Srgrimes	if (dout != NULL)
30781592Srgrimes		(void) fclose(dout);
30791592Srgrimes	data = -1;
30801592Srgrimes	pdata = -1;
30811592Srgrimesout:
30821592Srgrimes	if (freeglob) {
30831592Srgrimes		freeglob = 0;
30841592Srgrimes		globfree(&gl);
30851592Srgrimes	}
30861592Srgrimes}
30871592Srgrimes
308815196Sdgvoid
308990148Simpreapchild(int signo)
309015196Sdg{
309115196Sdg	while (wait3(NULL, WNOHANG, NULL) > 0);
309215196Sdg}
309315196Sdg
309413139Speter#ifdef OLD_SETPROCTITLE
30951592Srgrimes/*
30961592Srgrimes * Clobber argv so ps will show what we're doing.  (Stolen from sendmail.)
30971592Srgrimes * Warning, since this is usually started from inetd.conf, it often doesn't
30981592Srgrimes * have much of an environment or arglist to overwrite.
30991592Srgrimes */
31001592Srgrimesvoid
31011592Srgrimessetproctitle(const char *fmt, ...)
31021592Srgrimes{
31031592Srgrimes	int i;
31041592Srgrimes	va_list ap;
31051592Srgrimes	char *p, *bp, ch;
31061592Srgrimes	char buf[LINE_MAX];
31071592Srgrimes
31081592Srgrimes	va_start(ap, fmt);
31091592Srgrimes	(void)vsnprintf(buf, sizeof(buf), fmt, ap);
31101592Srgrimes
31111592Srgrimes	/* make ps print our process name */
31121592Srgrimes	p = Argv[0];
31131592Srgrimes	*p++ = '-';
31141592Srgrimes
31151592Srgrimes	i = strlen(buf);
31161592Srgrimes	if (i > LastArgv - p - 2) {
31171592Srgrimes		i = LastArgv - p - 2;
31181592Srgrimes		buf[i] = '\0';
31191592Srgrimes	}
31201592Srgrimes	bp = buf;
31211592Srgrimes	while (ch = *bp++)
31221592Srgrimes		if (ch != '\n' && ch != '\r')
31231592Srgrimes			*p++ = ch;
31241592Srgrimes	while (p < LastArgv)
31251592Srgrimes		*p++ = ' ';
31261592Srgrimes}
312713139Speter#endif /* OLD_SETPROCTITLE */
31286740Sguido
312917433Spststatic void
313090148Simplogxfer(char *name, off_t size, time_t start)
31316740Sguido{
31326740Sguido	char buf[1024];
31336740Sguido	char path[MAXPATHLEN + 1];
313436612Sjb	time_t now;
31356740Sguido
31366740Sguido	if (statfd >= 0 && getwd(path) != NULL) {
31376740Sguido		time(&now);
313882792Sache		snprintf(buf, sizeof(buf), "%.20s!%s!%s!%s/%s!%qd!%ld\n",
31396740Sguido			ctime(&now)+4, ident, remotehost,
314082792Sache			path, name, (long long)size,
314182792Sache			(long)(now - start + (now == start)));
31426740Sguido		write(statfd, buf, strlen(buf));
31436740Sguido	}
31446740Sguido}
3145100486Syar
3146100486Syarstatic char *
3147100486Syardoublequote(char *s)
3148100486Syar{
3149100486Syar	int n;
3150100486Syar	char *p, *s2;
3151100486Syar
3152100486Syar	for (p = s, n = 0; *p; p++)
3153100486Syar		if (*p == '"')
3154100486Syar			n++;
3155100486Syar
3156100486Syar	if ((s2 = malloc(p - s + n + 1)) == NULL)
3157100486Syar		return (NULL);
3158100486Syar
3159100486Syar	for (p = s2; *s; s++, p++) {
3160100486Syar		if ((*p = *s) == '"')
3161100486Syar			*(++p) = '"';
3162100486Syar	}
3163100486Syar	*p = '\0';
3164100486Syar
3165100486Syar	return (s2);
3166100486Syar}
3167120059Sume
3168120059Sume/* setup server socket for specified address family */
3169120059Sume/* if af is PF_UNSPEC more than one socket may be returned */
3170120059Sume/* the returned list is dynamically allocated, so caller needs to free it */
3171120059Sumestatic int *
3172120059Sumesocksetup(int af, char *bindname, const char *bindport)
3173120059Sume{
3174120059Sume	struct addrinfo hints, *res, *r;
3175120059Sume	int error, maxs, *s, *socks;
3176120059Sume	const int on = 1;
3177120059Sume
3178120059Sume	memset(&hints, 0, sizeof(hints));
3179120059Sume	hints.ai_flags = AI_PASSIVE;
3180120059Sume	hints.ai_family = af;
3181120059Sume	hints.ai_socktype = SOCK_STREAM;
3182120059Sume	error = getaddrinfo(bindname, bindport, &hints, &res);
3183120059Sume	if (error) {
3184120059Sume		syslog(LOG_ERR, "%s", gai_strerror(error));
3185120059Sume		if (error == EAI_SYSTEM)
3186120059Sume			syslog(LOG_ERR, "%s", strerror(errno));
3187120059Sume		return NULL;
3188120059Sume	}
3189120059Sume
3190120059Sume	/* Count max number of sockets we may open */
3191120059Sume	for (maxs = 0, r = res; r; r = r->ai_next, maxs++)
3192120059Sume		;
3193120059Sume	socks = malloc((maxs + 1) * sizeof(int));
3194120059Sume	if (!socks) {
3195120059Sume		freeaddrinfo(res);
3196120059Sume		syslog(LOG_ERR, "couldn't allocate memory for sockets");
3197120059Sume		return NULL;
3198120059Sume	}
3199120059Sume
3200120059Sume	*socks = 0;   /* num of sockets counter at start of array */
3201120059Sume	s = socks + 1;
3202120059Sume	for (r = res; r; r = r->ai_next) {
3203120059Sume		*s = socket(r->ai_family, r->ai_socktype, r->ai_protocol);
3204120059Sume		if (*s < 0) {
3205120059Sume			syslog(LOG_DEBUG, "control socket: %m");
3206120059Sume			continue;
3207120059Sume		}
3208120059Sume		if (setsockopt(*s, SOL_SOCKET, SO_REUSEADDR,
3209120059Sume		    &on, sizeof(on)) < 0)
3210120059Sume			syslog(LOG_WARNING,
3211120059Sume			    "control setsockopt (SO_REUSEADDR): %m");
3212120059Sume		if (r->ai_family == AF_INET6) {
3213120059Sume			if (setsockopt(*s, IPPROTO_IPV6, IPV6_V6ONLY,
3214120059Sume			    &on, sizeof(on)) < 0)
3215120059Sume				syslog(LOG_WARNING,
3216120059Sume				    "control setsockopt (IPV6_V6ONLY): %m");
3217120059Sume		}
3218120059Sume		if (bind(*s, r->ai_addr, r->ai_addrlen) < 0) {
3219120059Sume			syslog(LOG_DEBUG, "control bind: %m");
3220120059Sume			close(*s);
3221120059Sume			continue;
3222120059Sume		}
3223120059Sume		(*socks)++;
3224120059Sume		s++;
3225120059Sume	}
3226120059Sume
3227120059Sume	if (res)
3228120059Sume		freeaddrinfo(res);
3229120059Sume
3230120059Sume	if (*socks == 0) {
3231120059Sume		syslog(LOG_ERR, "control socket: Couldn't bind to any socket");
3232120059Sume		free(socks);
3233120059Sume		return NULL;
3234120059Sume	}
3235120059Sume	return(socks);
3236120059Sume}
3237