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.
13262435Sbrueffer * 3. Neither the name of the University nor the names of its contributors
141592Srgrimes *    may be used to endorse or promote products derived from this software
151592Srgrimes *    without specific prior written permission.
161592Srgrimes *
171592Srgrimes * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
181592Srgrimes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
191592Srgrimes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
201592Srgrimes * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
211592Srgrimes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
221592Srgrimes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
231592Srgrimes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
241592Srgrimes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
251592Srgrimes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
261592Srgrimes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
271592Srgrimes * SUCH DAMAGE.
281592Srgrimes */
291592Srgrimes
3017478Smarkm#if 0
311592Srgrimes#ifndef lint
321592Srgrimesstatic char copyright[] =
331592Srgrimes"@(#) Copyright (c) 1985, 1988, 1990, 1992, 1993, 1994\n\
341592Srgrimes	The Regents of the University of California.  All rights reserved.\n";
351592Srgrimes#endif /* not lint */
3617478Smarkm#endif
371592Srgrimes
3831329Scharnier#ifndef lint
3917478Smarkm#if 0
401592Srgrimesstatic char sccsid[] = "@(#)ftpd.c	8.4 (Berkeley) 4/16/94";
4131329Scharnier#endif
421592Srgrimes#endif /* not lint */
431592Srgrimes
44137859Syar#include <sys/cdefs.h>
45137859Syar__FBSDID("$FreeBSD$");
46137859Syar
471592Srgrimes/*
481592Srgrimes * FTP server.
491592Srgrimes */
501592Srgrimes#include <sys/param.h>
511592Srgrimes#include <sys/ioctl.h>
5266907Swollman#include <sys/mman.h>
531592Srgrimes#include <sys/socket.h>
5466907Swollman#include <sys/stat.h>
5566907Swollman#include <sys/time.h>
561592Srgrimes#include <sys/wait.h>
571592Srgrimes
581592Srgrimes#include <netinet/in.h>
591592Srgrimes#include <netinet/in_systm.h>
601592Srgrimes#include <netinet/ip.h>
618240Swollman#include <netinet/tcp.h>
621592Srgrimes
631592Srgrimes#define	FTP_NAMES
641592Srgrimes#include <arpa/ftp.h>
651592Srgrimes#include <arpa/inet.h>
661592Srgrimes#include <arpa/telnet.h>
671592Srgrimes
681592Srgrimes#include <ctype.h>
691592Srgrimes#include <dirent.h>
701592Srgrimes#include <err.h>
711592Srgrimes#include <errno.h>
721592Srgrimes#include <fcntl.h>
731592Srgrimes#include <glob.h>
741592Srgrimes#include <limits.h>
751592Srgrimes#include <netdb.h>
761592Srgrimes#include <pwd.h>
7725187Sdavidn#include <grp.h>
7888763Sache#include <opie.h>
791592Srgrimes#include <signal.h>
80132929Syar#include <stdint.h>
811592Srgrimes#include <stdio.h>
821592Srgrimes#include <stdlib.h>
831592Srgrimes#include <string.h>
841592Srgrimes#include <syslog.h>
851592Srgrimes#include <time.h>
861592Srgrimes#include <unistd.h>
8713139Speter#include <libutil.h>
8825101Sdavidn#ifdef	LOGIN_CAP
8925101Sdavidn#include <login_cap.h>
9025101Sdavidn#endif
911592Srgrimes
9274874Smarkm#ifdef USE_PAM
9351433Smarkm#include <security/pam_appl.h>
9451433Smarkm#endif
9551433Smarkm
961592Srgrimes#include "pathnames.h"
971592Srgrimes#include "extern.h"
981592Srgrimes
991592Srgrimes#include <stdarg.h>
1001592Srgrimes
10125165Sdavidnstatic char version[] = "Version 6.00LS";
10225165Sdavidn#undef main
1031592Srgrimes
10456668Sshinunion sockunion ctrl_addr;
10556668Sshinunion sockunion data_source;
10656668Sshinunion sockunion data_dest;
10756668Sshinunion sockunion his_addr;
10856668Sshinunion sockunion pasv_addr;
1091592Srgrimes
11015196Sdgint	daemon_mode;
1111592Srgrimesint	data;
112109742Syarint	dataport;
113110037Syarint	hostinfo = 1;	/* print host-specific info in messages */
1141592Srgrimesint	logged_in;
1151592Srgrimesstruct	passwd *pw;
116110036Syarchar	*homedir;
11776096Smarkmint	ftpdebug;
1181592Srgrimesint	timeout = 900;    /* timeout after 15 minutes of inactivity */
1191592Srgrimesint	maxtimeout = 7200;/* don't allow idle time to be set beyond 2 hours */
1201592Srgrimesint	logging;
1219933Spstint	restricted_data_ports = 1;
12217435Spstint	paranoid = 1;	  /* be extra careful about security */
12320042Storstenbint	anon_only = 0;    /* Only anonymous ftp allowed */
124168849Syarint	assumeutf8 = 0;   /* Assume that server file names are in UTF-8 */
1251592Srgrimesint	guest;
12617435Spstint	dochroot;
127137862Syarchar	*chrootdir;
128102311Syarint	dowtmp = 1;
1296740Sguidoint	stats;
1306740Sguidoint	statfd = -1;
1311592Srgrimesint	type;
1321592Srgrimesint	form;
1331592Srgrimesint	stru;			/* avoid C keyword */
1341592Srgrimesint	mode;
1351592Srgrimesint	usedefault = 1;		/* for data transfers */
1361592Srgrimesint	pdata = -1;		/* for passive mode */
137137861Syarint	readonly = 0;		/* Server is in readonly mode.	*/
138137861Syarint	noepsv = 0;		/* EPSV command is disabled.	*/
139137861Syarint	noretr = 0;		/* RETR command is disabled.	*/
140137861Syarint	noguestretr = 0;	/* RETR command is disabled for anon users. */
141137861Syarint	noguestmkd = 0;		/* MKD command is disabled for anon users. */
142137861Syarint	noguestmod = 1;		/* anon users may not modify existing files. */
14382460Snik
1441592Srgrimesoff_t	file_size;
1451592Srgrimesoff_t	byte_count;
1461592Srgrimes#if !defined(CMASK) || CMASK == 0
1471592Srgrimes#undef CMASK
1481592Srgrimes#define CMASK 027
1491592Srgrimes#endif
1501592Srgrimesint	defumask = CMASK;		/* default umask value */
1511592Srgrimeschar	tmpline[7];
15227650Sdavidnchar	*hostname;
15378153Sddint	epsvall = 0;
15478153Sdd
15525283Sdavidn#ifdef VIRTUAL_HOSTING
15625283Sdavidnchar	*ftpuser;
15725283Sdavidn
15825283Sdavidnstatic struct ftphost {
15925283Sdavidn	struct ftphost	*next;
16057124Sshin	struct addrinfo *hostinfo;
16125283Sdavidn	char		*hostname;
16225283Sdavidn	char		*anonuser;
16325283Sdavidn	char		*statfile;
16425283Sdavidn	char		*welcome;
16525283Sdavidn	char		*loginmsg;
16625283Sdavidn} *thishost, *firsthost;
16725283Sdavidn
16825283Sdavidn#endif
169137983Syarchar	remotehost[NI_MAXHOST];
1706740Sguidochar	*ident = NULL;
17117435Spst
172202209Sedstatic char	wtmpid[20];
17317435Spst
17474874Smarkm#ifdef USE_PAM
17590148Simpstatic int	auth_pam(struct passwd**, const char*);
176137861Syarpam_handle_t	*pamh = NULL;
17788763Sache#endif
17879469Smarkm
179137861Syarstatic struct opie	opiedata;
180137861Syarstatic char		opieprompt[OPIE_CHALLENGE_MAX+1];
181137861Syarstatic int		pwok;
18217478Smarkm
183154630Syarchar	*pid_file = NULL; /* means default location to pidfile(3) */
18417483Sjulian
1851592Srgrimes/*
18674470Sjlemon * Limit number of pathnames that glob can return.
18774470Sjlemon * A limit of 0 indicates the number of pathnames is unlimited.
18874470Sjlemon */
18974470Sjlemon#define MAXGLOBARGS	16384
19074470Sjlemon#
19174470Sjlemon
19274470Sjlemon/*
1931592Srgrimes * Timeout intervals for retrying connections
1941592Srgrimes * to hosts that don't accept PORT cmds.  This
1951592Srgrimes * is a kludge, but given the problems with TCP...
1961592Srgrimes */
1971592Srgrimes#define	SWAITMAX	90	/* wait at most 90 seconds */
1981592Srgrimes#define	SWAITINT	5	/* interval between retries */
1991592Srgrimes
2001592Srgrimesint	swaitmax = SWAITMAX;
2011592Srgrimesint	swaitint = SWAITINT;
2021592Srgrimes
2031592Srgrimes#ifdef SETPROCTITLE
20413139Speter#ifdef OLD_SETPROCTITLE
2051592Srgrimeschar	**Argv = NULL;		/* pointer to argument vector */
2061592Srgrimeschar	*LastArgv = NULL;	/* end of argv */
20713139Speter#endif /* OLD_SETPROCTITLE */
2081592Srgrimeschar	proctitle[LINE_MAX];	/* initial part of title */
2091592Srgrimes#endif /* SETPROCTITLE */
2101592Srgrimes
211137848Syar#define LOGCMD(cmd, file)		logcmd((cmd), (file), NULL, -1)
212137848Syar#define LOGCMD2(cmd, file1, file2)	logcmd((cmd), (file1), (file2), -1)
213137848Syar#define LOGBYTES(cmd, file, cnt)	logcmd((cmd), (file), NULL, (cnt))
2141592Srgrimes
215140472Syarstatic	volatile sig_atomic_t recvurg;
216140472Syarstatic	int transflag;		/* NB: for debugging only */
217140472Syar
218140472Syar#define STARTXFER	flagxfer(1)
219140472Syar#define ENDXFER		flagxfer(0)
220140472Syar
221140472Syar#define START_UNSAFE	maskurg(1)
222140472Syar#define END_UNSAFE	maskurg(0)
223140472Syar
224140472Syar/* It's OK to put an `else' clause after this macro. */
225140472Syar#define CHECKOOB(action)						\
226140472Syar	if (recvurg) {							\
227140472Syar		recvurg = 0;						\
228140472Syar		if (myoob()) {						\
229140472Syar			ENDXFER;					\
230140472Syar			action;						\
231140472Syar		}							\
232140472Syar	}
233140472Syar
23425283Sdavidn#ifdef VIRTUAL_HOSTING
235156156Sumestatic void	 inithosts(int);
236137861Syarstatic void	 selecthost(union sockunion *);
23725283Sdavidn#endif
23890148Simpstatic void	 ack(char *);
23990148Simpstatic void	 sigurg(int);
240140472Syarstatic void	 maskurg(int);
241140472Syarstatic void	 flagxfer(int);
242140472Syarstatic int	 myoob(void);
243216932Scsjpstatic int	 checkuser(char *, char *, int, char **, int *);
24490148Simpstatic FILE	*dataconn(char *, off_t, char *);
24590148Simpstatic void	 dolog(struct sockaddr *);
24690148Simpstatic void	 end_login(void);
24790148Simpstatic FILE	*getdatasock(char *);
248101537Syarstatic int	 guniquefd(char *, char **);
24990148Simpstatic void	 lostconn(int);
25090148Simpstatic void	 sigquit(int);
25190148Simpstatic int	 receive_data(FILE *, FILE *);
252137660Syarstatic int	 send_data(FILE *, FILE *, size_t, off_t, int);
2531592Srgrimesstatic struct passwd *
25490148Simp		 sgetpwnam(char *);
25590148Simpstatic char	*sgetsave(char *);
25690148Simpstatic void	 reapchild(int);
257137851Syarstatic void	 appendf(char **, char *, ...) __printflike(2, 3);
258137848Syarstatic void	 logcmd(char *, char *, char *, off_t);
25990148Simpstatic void      logxfer(char *, off_t, time_t);
260100486Syarstatic char	*doublequote(char *);
261120059Sumestatic int	*socksetup(int, char *, const char *);
2621592Srgrimes
2631592Srgrimesint
26490148Simpmain(int argc, char *argv[], char **envp)
2651592Srgrimes{
266141918Sstefanf	socklen_t addrlen;
267141918Sstefanf	int ch, on = 1, tos;
2681592Srgrimes	char *cp, line[LINE_MAX];
2691592Srgrimes	FILE *fd;
27056668Sshin	char	*bindname = NULL;
271109742Syar	const char *bindport = "ftp";
27256668Sshin	int	family = AF_UNSPEC;
27389935Syar	struct sigaction sa;
2741592Srgrimes
27536105Sache	tzset();		/* in case no timezone database in ~ftp */
27689935Syar	sigemptyset(&sa.sa_mask);
27789935Syar	sa.sa_flags = SA_RESTART;
27836105Sache
27913139Speter#ifdef OLD_SETPROCTITLE
2801592Srgrimes	/*
2811592Srgrimes	 *  Save start and extent of argv for setproctitle.
2821592Srgrimes	 */
2831592Srgrimes	Argv = argv;
2841592Srgrimes	while (*envp)
2851592Srgrimes		envp++;
2861592Srgrimes	LastArgv = envp[-1] + strlen(envp[-1]);
28713139Speter#endif /* OLD_SETPROCTITLE */
2881592Srgrimes
289138747Syar	/*
290138747Syar	 * Prevent diagnostic messages from appearing on stderr.
291138747Syar	 * We run as a daemon or from inetd; in both cases, there's
292138747Syar	 * more reason in logging to syslog.
293138747Syar	 */
294138747Syar	(void) freopen(_PATH_DEVNULL, "w", stderr);
295138747Syar	opterr = 0;
2966740Sguido
297138747Syar	/*
298138747Syar	 * LOG_NDELAY sets up the logging connection immediately,
299138747Syar	 * necessary for anonymous ftp's that chroot and can't do it later.
300138747Syar	 */
301138747Syar	openlog("ftpd", LOG_PID | LOG_NDELAY, LOG_FTP);
302138747Syar
303110037Syar	while ((ch = getopt(argc, argv,
304168849Syar	                    "468a:AdDEhlmMoOp:P:rRSt:T:u:UvW")) != -1) {
3051592Srgrimes		switch (ch) {
306100717Syar		case '4':
307120059Sume			family = (family == AF_INET6) ? AF_UNSPEC : AF_INET;
30815196Sdg			break;
30915196Sdg
310100717Syar		case '6':
311120059Sume			family = (family == AF_INET) ? AF_UNSPEC : AF_INET6;
312100717Syar			break;
313100717Syar
314168849Syar		case '8':
315168849Syar			assumeutf8 = 1;
316168849Syar			break;
317168849Syar
318100717Syar		case 'a':
319100717Syar			bindname = optarg;
320100717Syar			break;
321100717Syar
322100717Syar		case 'A':
323100717Syar			anon_only = 1;
324100717Syar			break;
325100717Syar
3261592Srgrimes		case 'd':
32776096Smarkm			ftpdebug++;
3281592Srgrimes			break;
3291592Srgrimes
330100717Syar		case 'D':
331100717Syar			daemon_mode++;
332100717Syar			break;
333100717Syar
33470102Sphk		case 'E':
33570102Sphk			noepsv = 1;
33670102Sphk			break;
33770102Sphk
338110037Syar		case 'h':
339110037Syar			hostinfo = 0;
340110037Syar			break;
341110037Syar
3421592Srgrimes		case 'l':
3431592Srgrimes			logging++;	/* > 1 == extra logging */
3441592Srgrimes			break;
3451592Srgrimes
346101537Syar		case 'm':
347101537Syar			noguestmod = 0;
348101537Syar			break;
349101537Syar
350100717Syar		case 'M':
351100717Syar			noguestmkd = 1;
352100717Syar			break;
353100717Syar
354100717Syar		case 'o':
355100717Syar			noretr = 1;
356100717Syar			break;
357100717Syar
358100717Syar		case 'O':
359100717Syar			noguestretr = 1;
360100717Syar			break;
361100717Syar
362100717Syar		case 'p':
363100717Syar			pid_file = optarg;
364100717Syar			break;
365100717Syar
366109742Syar		case 'P':
367109742Syar			bindport = optarg;
368109742Syar			break;
369109742Syar
37070102Sphk		case 'r':
37170102Sphk			readonly = 1;
37270102Sphk			break;
37370102Sphk
37417435Spst		case 'R':
37517435Spst			paranoid = 0;
3769933Spst			break;
3779933Spst
3786740Sguido		case 'S':
37917435Spst			stats++;
3806740Sguido			break;
38117435Spst
38217435Spst		case 't':
38317435Spst			timeout = atoi(optarg);
38417435Spst			if (maxtimeout < timeout)
38517435Spst				maxtimeout = timeout;
38617435Spst			break;
38717435Spst
388100717Syar		case 'T':
389100717Syar			maxtimeout = atoi(optarg);
390100717Syar			if (timeout > maxtimeout)
391100717Syar				timeout = maxtimeout;
39217435Spst			break;
39317435Spst
3941592Srgrimes		case 'u':
3951592Srgrimes		    {
3961592Srgrimes			long val = 0;
3971592Srgrimes
3981592Srgrimes			val = strtol(optarg, &optarg, 8);
3991592Srgrimes			if (*optarg != '\0' || val < 0)
400138747Syar				syslog(LOG_WARNING, "bad value for -u");
4011592Srgrimes			else
4021592Srgrimes				defumask = val;
4031592Srgrimes			break;
4041592Srgrimes		    }
405100717Syar		case 'U':
406100717Syar			restricted_data_ports = 0;
40720042Storstenb			break;
4081592Srgrimes
4091592Srgrimes		case 'v':
410100720Syar			ftpdebug++;
4111592Srgrimes			break;
4121592Srgrimes
413102311Syar		case 'W':
414102311Syar			dowtmp = 0;
415102311Syar			break;
416102311Syar
4171592Srgrimes		default:
418138747Syar			syslog(LOG_WARNING, "unknown flag -%c ignored", optopt);
4191592Srgrimes			break;
4201592Srgrimes		}
4211592Srgrimes	}
42215196Sdg
42315196Sdg	if (daemon_mode) {
424120059Sume		int *ctl_sock, fd, maxfd = -1, nfds, i;
425120059Sume		fd_set defreadfds, readfds;
426120059Sume		pid_t pid;
427154630Syar		struct pidfh *pfh;
42815196Sdg
429154630Syar		if ((pfh = pidfile_open(pid_file, 0600, &pid)) == NULL) {
430154630Syar			if (errno == EEXIST) {
431154630Syar				syslog(LOG_ERR, "%s already running, pid %d",
432154630Syar				       getprogname(), (int)pid);
433154630Syar				exit(1);
434154630Syar			}
435154630Syar			syslog(LOG_WARNING, "pidfile_open: %m");
436154630Syar		}
437154630Syar
43815196Sdg		/*
43915196Sdg		 * Detach from parent.
44015196Sdg		 */
44115196Sdg		if (daemon(1, 1) < 0) {
44215196Sdg			syslog(LOG_ERR, "failed to become a daemon");
44315196Sdg			exit(1);
44415196Sdg		}
445154630Syar
446154630Syar		if (pfh != NULL && pidfile_write(pfh) == -1)
447154630Syar			syslog(LOG_WARNING, "pidfile_write: %m");
448154630Syar
44989935Syar		sa.sa_handler = reapchild;
45089935Syar		(void)sigaction(SIGCHLD, &sa, NULL);
45156668Sshin
452156156Sume#ifdef VIRTUAL_HOSTING
453156156Sume		inithosts(family);
454156156Sume#endif
455156156Sume
45615196Sdg		/*
45715196Sdg		 * Open a socket, bind it to the FTP port, and start
45815196Sdg		 * listening.
45915196Sdg		 */
460120059Sume		ctl_sock = socksetup(family, bindname, bindport);
461120059Sume		if (ctl_sock == NULL)
46215196Sdg			exit(1);
463120059Sume
464120059Sume		FD_ZERO(&defreadfds);
465120059Sume		for (i = 1; i <= *ctl_sock; i++) {
466120059Sume			FD_SET(ctl_sock[i], &defreadfds);
467120059Sume			if (listen(ctl_sock[i], 32) < 0) {
468120059Sume				syslog(LOG_ERR, "control listen: %m");
469120059Sume				exit(1);
470120059Sume			}
471120059Sume			if (maxfd < ctl_sock[i])
472120059Sume				maxfd = ctl_sock[i];
47315196Sdg		}
474120059Sume
47515196Sdg		/*
47615196Sdg		 * Loop forever accepting connection requests and forking off
47715196Sdg		 * children to handle them.
47815196Sdg		 */
47915196Sdg		while (1) {
480120059Sume			FD_COPY(&defreadfds, &readfds);
481120059Sume			nfds = select(maxfd + 1, &readfds, NULL, NULL, 0);
482120059Sume			if (nfds <= 0) {
483120059Sume				if (nfds < 0 && errno != EINTR)
484120059Sume					syslog(LOG_WARNING, "select: %m");
485120059Sume				continue;
486120059Sume			}
487120059Sume
488120059Sume			pid = -1;
489120059Sume                        for (i = 1; i <= *ctl_sock; i++)
490120059Sume				if (FD_ISSET(ctl_sock[i], &readfds)) {
491120059Sume					addrlen = sizeof(his_addr);
492120059Sume					fd = accept(ctl_sock[i],
493120059Sume					    (struct sockaddr *)&his_addr,
494120059Sume					    &addrlen);
495154634Syar					if (fd == -1) {
496154634Syar						syslog(LOG_WARNING,
497154634Syar						       "accept: %m");
498154634Syar						continue;
499135737Smaxim					}
500154634Syar					switch (pid = fork()) {
501154634Syar					case 0:
502154634Syar						/* child */
503154634Syar						(void) dup2(fd, 0);
504154634Syar						(void) dup2(fd, 1);
505154634Syar						(void) close(fd);
506154634Syar						for (i = 1; i <= *ctl_sock; i++)
507154634Syar							close(ctl_sock[i]);
508154634Syar						if (pfh != NULL)
509154634Syar							pidfile_close(pfh);
510154634Syar						goto gotchild;
511154634Syar					case -1:
512154634Syar						syslog(LOG_WARNING, "fork: %m");
513154634Syar						/* FALLTHROUGH */
514154634Syar					default:
515154634Syar						close(fd);
516154634Syar					}
517120059Sume				}
51815196Sdg		}
51915196Sdg	} else {
52015196Sdg		addrlen = sizeof(his_addr);
52115196Sdg		if (getpeername(0, (struct sockaddr *)&his_addr, &addrlen) < 0) {
52215196Sdg			syslog(LOG_ERR, "getpeername (%s): %m",argv[0]);
52315196Sdg			exit(1);
52415196Sdg		}
525156156Sume
526156156Sume#ifdef VIRTUAL_HOSTING
527156156Sume		if (his_addr.su_family == AF_INET6 &&
528156156Sume		    IN6_IS_ADDR_V4MAPPED(&his_addr.su_sin6.sin6_addr))
529156156Sume			family = AF_INET;
530156156Sume		else
531156156Sume			family = his_addr.su_family;
532156156Sume		inithosts(family);
533156156Sume#endif
53415196Sdg	}
53515196Sdg
536154634Syargotchild:
53789935Syar	sa.sa_handler = SIG_DFL;
53889935Syar	(void)sigaction(SIGCHLD, &sa, NULL);
5391592Srgrimes
54089935Syar	sa.sa_handler = sigurg;
54189935Syar	sa.sa_flags = 0;		/* don't restart syscalls for SIGURG */
54289935Syar	(void)sigaction(SIGURG, &sa, NULL);
54389935Syar
54489935Syar	sigfillset(&sa.sa_mask);	/* block all signals in handler */
54589935Syar	sa.sa_flags = SA_RESTART;
54689935Syar	sa.sa_handler = sigquit;
54789935Syar	(void)sigaction(SIGHUP, &sa, NULL);
54889935Syar	(void)sigaction(SIGINT, &sa, NULL);
54989935Syar	(void)sigaction(SIGQUIT, &sa, NULL);
55089935Syar	(void)sigaction(SIGTERM, &sa, NULL);
55189935Syar
55289935Syar	sa.sa_handler = lostconn;
55389935Syar	(void)sigaction(SIGPIPE, &sa, NULL);
55489935Syar
55515196Sdg	addrlen = sizeof(ctrl_addr);
55615196Sdg	if (getsockname(0, (struct sockaddr *)&ctrl_addr, &addrlen) < 0) {
55715196Sdg		syslog(LOG_ERR, "getsockname (%s): %m",argv[0]);
55815196Sdg		exit(1);
55915196Sdg	}
560109742Syar	dataport = ntohs(ctrl_addr.su_port) - 1; /* as per RFC 959 */
56125283Sdavidn#ifdef VIRTUAL_HOSTING
56225283Sdavidn	/* select our identity from virtual host table */
56356668Sshin	selecthost(&ctrl_addr);
56425283Sdavidn#endif
56515196Sdg#ifdef IP_TOS
56656668Sshin	if (ctrl_addr.su_family == AF_INET)
56756668Sshin      {
56815196Sdg	tos = IPTOS_LOWDELAY;
569100612Syar	if (setsockopt(0, IPPROTO_IP, IP_TOS, &tos, sizeof(int)) < 0)
570100609Syar		syslog(LOG_WARNING, "control setsockopt (IP_TOS): %m");
57156668Sshin      }
57215196Sdg#endif
57335482Sdg	/*
57435482Sdg	 * Disable Nagle on the control channel so that we don't have to wait
57535482Sdg	 * for peer's ACK before issuing our next reply.
57635482Sdg	 */
57735482Sdg	if (setsockopt(0, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)) < 0)
578100609Syar		syslog(LOG_WARNING, "control setsockopt (TCP_NODELAY): %m");
57935482Sdg
58056668Sshin	data_source.su_port = htons(ntohs(ctrl_addr.su_port) - 1);
58115196Sdg
582202209Sed	(void)snprintf(wtmpid, sizeof(wtmpid), "%xftpd", getpid());
58317435Spst
5841592Srgrimes	/* Try to handle urgent data inline */
5851592Srgrimes#ifdef SO_OOBINLINE
586100612Syar	if (setsockopt(0, SOL_SOCKET, SO_OOBINLINE, &on, sizeof(on)) < 0)
587100609Syar		syslog(LOG_WARNING, "control setsockopt (SO_OOBINLINE): %m");
5881592Srgrimes#endif
5891592Srgrimes
5901592Srgrimes#ifdef	F_SETOWN
5911592Srgrimes	if (fcntl(fileno(stdin), F_SETOWN, getpid()) == -1)
5921592Srgrimes		syslog(LOG_ERR, "fcntl F_SETOWN: %m");
5931592Srgrimes#endif
59456668Sshin	dolog((struct sockaddr *)&his_addr);
5951592Srgrimes	/*
5961592Srgrimes	 * Set up default state
5971592Srgrimes	 */
5981592Srgrimes	data = -1;
5991592Srgrimes	type = TYPE_A;
6001592Srgrimes	form = FORM_N;
6011592Srgrimes	stru = STRU_F;
6021592Srgrimes	mode = MODE_S;
6031592Srgrimes	tmpline[0] = '\0';
6041592Srgrimes
6051592Srgrimes	/* If logins are disabled, print out the message. */
6061592Srgrimes	if ((fd = fopen(_PATH_NOLOGIN,"r")) != NULL) {
6071592Srgrimes		while (fgets(line, sizeof(line), fd) != NULL) {
6081592Srgrimes			if ((cp = strchr(line, '\n')) != NULL)
6091592Srgrimes				*cp = '\0';
6101592Srgrimes			lreply(530, "%s", line);
6111592Srgrimes		}
6121592Srgrimes		(void) fflush(stdout);
6131592Srgrimes		(void) fclose(fd);
6141592Srgrimes		reply(530, "System not available.");
6151592Srgrimes		exit(0);
6161592Srgrimes	}
61725283Sdavidn#ifdef VIRTUAL_HOSTING
618130428Sobrien	fd = fopen(thishost->welcome, "r");
61925283Sdavidn#else
620130428Sobrien	fd = fopen(_PATH_FTPWELCOME, "r");
62125283Sdavidn#endif
622130428Sobrien	if (fd != NULL) {
6231592Srgrimes		while (fgets(line, sizeof(line), fd) != NULL) {
6241592Srgrimes			if ((cp = strchr(line, '\n')) != NULL)
6251592Srgrimes				*cp = '\0';
6261592Srgrimes			lreply(220, "%s", line);
6271592Srgrimes		}
6281592Srgrimes		(void) fflush(stdout);
6291592Srgrimes		(void) fclose(fd);
6301592Srgrimes		/* reply(220,) must follow */
6311592Srgrimes	}
63225283Sdavidn#ifndef VIRTUAL_HOSTING
63327650Sdavidn	if ((hostname = malloc(MAXHOSTNAMELEN)) == NULL)
63476096Smarkm		fatalerror("Ran out of memory.");
635137983Syar	if (gethostname(hostname, MAXHOSTNAMELEN - 1) < 0)
636137983Syar		hostname[0] = '\0';
63745422Sbrian	hostname[MAXHOSTNAMELEN - 1] = '\0';
63825283Sdavidn#endif
639110037Syar	if (hostinfo)
640110037Syar		reply(220, "%s FTP server (%s) ready.", hostname, version);
641110037Syar	else
642110037Syar		reply(220, "FTP server ready.");
6431592Srgrimes	for (;;)
6441592Srgrimes		(void) yyparse();
6451592Srgrimes	/* NOTREACHED */
6461592Srgrimes}
6471592Srgrimes
6481592Srgrimesstatic void
64990148Simplostconn(int signo)
6501592Srgrimes{
6511592Srgrimes
65276096Smarkm	if (ftpdebug)
6531592Srgrimes		syslog(LOG_DEBUG, "lost connection");
65431329Scharnier	dologout(1);
6551592Srgrimes}
6561592Srgrimes
65789935Syarstatic void
65890148Simpsigquit(int signo)
65989935Syar{
66089935Syar
66189935Syar	syslog(LOG_ERR, "got signal %d", signo);
66289935Syar	dologout(1);
66389935Syar}
66489935Syar
66525283Sdavidn#ifdef VIRTUAL_HOSTING
6661592Srgrimes/*
66725283Sdavidn * read in virtual host tables (if they exist)
66825283Sdavidn */
66925283Sdavidn
67025283Sdavidnstatic void
671156156Sumeinithosts(int family)
67225283Sdavidn{
673100182Syar	int insert;
67499877Syar	size_t len;
67525283Sdavidn	FILE *fp;
67699877Syar	char *cp, *mp, *line;
67799877Syar	char *hostname;
678100182Syar	char *vhost, *anonuser, *statfile, *welcome, *loginmsg;
67925283Sdavidn	struct ftphost *hrp, *lhrp;
68056668Sshin	struct addrinfo hints, *res, *ai;
68125283Sdavidn
68225283Sdavidn	/*
68325283Sdavidn	 * Fill in the default host information
68425283Sdavidn	 */
68599877Syar	if ((hostname = malloc(MAXHOSTNAMELEN)) == NULL)
68676096Smarkm		fatalerror("Ran out of memory.");
687137983Syar	if (gethostname(hostname, MAXHOSTNAMELEN - 1) < 0)
68899877Syar		hostname[0] = '\0';
68999877Syar	hostname[MAXHOSTNAMELEN - 1] = '\0';
69099877Syar	if ((hrp = malloc(sizeof(struct ftphost))) == NULL)
69199877Syar		fatalerror("Ran out of memory.");
69299877Syar	hrp->hostname = hostname;
69357124Sshin	hrp->hostinfo = NULL;
69456668Sshin
69556668Sshin	memset(&hints, 0, sizeof(hints));
696156156Sume	hints.ai_flags = AI_PASSIVE;
697156156Sume	hints.ai_family = family;
698156156Sume	hints.ai_socktype = SOCK_STREAM;
699102183Syar	if (getaddrinfo(hrp->hostname, NULL, &hints, &res) == 0)
70057124Sshin		hrp->hostinfo = res;
70125283Sdavidn	hrp->statfile = _PATH_FTPDSTATFILE;
70225283Sdavidn	hrp->welcome  = _PATH_FTPWELCOME;
70325283Sdavidn	hrp->loginmsg = _PATH_FTPLOGINMESG;
70425283Sdavidn	hrp->anonuser = "ftp";
70525283Sdavidn	hrp->next = NULL;
70625283Sdavidn	thishost = firsthost = lhrp = hrp;
70725283Sdavidn	if ((fp = fopen(_PATH_FTPHOSTS, "r")) != NULL) {
708102474Syar		int addrsize, gothost;
70956668Sshin		void *addr;
71056668Sshin		struct hostent *hp;
71156668Sshin
71299877Syar		while ((line = fgetln(fp, &len)) != NULL) {
71356668Sshin			int	i, hp_error;
71425283Sdavidn
71599877Syar			/* skip comments */
71699877Syar			if (line[0] == '#')
71725283Sdavidn				continue;
71899877Syar			if (line[len - 1] == '\n') {
71999877Syar				line[len - 1] = '\0';
72099877Syar				mp = NULL;
72199877Syar			} else {
72299877Syar				if ((mp = malloc(len + 1)) == NULL)
72399877Syar					fatalerror("Ran out of memory.");
72499877Syar				memcpy(mp, line, len);
72599877Syar				mp[len] = '\0';
72699877Syar				line = mp;
72725283Sdavidn			}
72825283Sdavidn			cp = strtok(line, " \t");
72999877Syar			/* skip empty lines */
73099877Syar			if (cp == NULL)
73199877Syar				goto nextline;
732100182Syar			vhost = cp;
73356668Sshin
734100182Syar			/* set defaults */
735100182Syar			anonuser = "ftp";
736100182Syar			statfile = _PATH_FTPDSTATFILE;
737100182Syar			welcome  = _PATH_FTPWELCOME;
738100182Syar			loginmsg = _PATH_FTPLOGINMESG;
739100182Syar
740100182Syar			/*
741100182Syar			 * Preparse the line so we can use its info
742100182Syar			 * for all the addresses associated with
743100182Syar			 * the virtual host name.
744100182Syar			 * Field 0, the virtual host name, is special:
745100182Syar			 * it's already parsed off and will be strdup'ed
746100182Syar			 * later, after we know its canonical form.
747100182Syar			 */
748100182Syar			for (i = 1; i < 5 && (cp = strtok(NULL, " \t")); i++)
749100182Syar				if (*cp != '-' && (cp = strdup(cp)))
750100182Syar					switch (i) {
751100182Syar					case 1:	/* anon user permissions */
752100182Syar						anonuser = cp;
753100182Syar						break;
754100182Syar					case 2: /* statistics file */
755100182Syar						statfile = cp;
756100182Syar						break;
757100182Syar					case 3: /* welcome message */
758100182Syar						welcome  = cp;
759100182Syar						break;
760100182Syar					case 4: /* login message */
761100182Syar						loginmsg = cp;
762100182Syar						break;
763100182Syar					default: /* programming error */
764100182Syar						abort();
765100182Syar						/* NOTREACHED */
766100182Syar					}
767100182Syar
76856668Sshin			hints.ai_flags = AI_PASSIVE;
769156156Sume			hints.ai_family = family;
770156156Sume			hints.ai_socktype = SOCK_STREAM;
771102183Syar			if (getaddrinfo(vhost, NULL, &hints, &res) != 0)
77299877Syar				goto nextline;
77356668Sshin			for (ai = res; ai != NULL && ai->ai_addr != NULL;
77462100Sdavidn			     ai = ai->ai_next) {
77556668Sshin
77662100Sdavidn			gothost = 0;
77725283Sdavidn			for (hrp = firsthost; hrp != NULL; hrp = hrp->next) {
77857124Sshin				struct addrinfo *hi;
77957124Sshin
78057124Sshin				for (hi = hrp->hostinfo; hi != NULL;
78157124Sshin				     hi = hi->ai_next)
78257124Sshin					if (hi->ai_addrlen == ai->ai_addrlen &&
78357124Sshin					    memcmp(hi->ai_addr,
78457124Sshin						   ai->ai_addr,
78562100Sdavidn						   ai->ai_addr->sa_len) == 0) {
78662100Sdavidn						gothost++;
78757124Sshin						break;
788100183Syar					}
78962100Sdavidn				if (gothost)
79062100Sdavidn					break;
79125283Sdavidn			}
79225283Sdavidn			if (hrp == NULL) {
79325283Sdavidn				if ((hrp = malloc(sizeof(struct ftphost))) == NULL)
79499877Syar					goto nextline;
795102183Syar				hrp->hostname = NULL;
796100182Syar				insert = 1;
797102473Syar			} else {
798106754Syar				if (hrp->hostinfo && hrp->hostinfo != res)
799102473Syar					freeaddrinfo(hrp->hostinfo);
800100182Syar				insert = 0; /* host already in the chain */
801102473Syar			}
80257124Sshin			hrp->hostinfo = res;
80357124Sshin
80425283Sdavidn			/*
80525283Sdavidn			 * determine hostname to use.
80656668Sshin			 * force defined name if there is a valid alias
80725283Sdavidn			 * otherwise fallback to primary hostname
80825283Sdavidn			 */
80956668Sshin			/* XXX: getaddrinfo() can't do alias check */
81057124Sshin			switch(hrp->hostinfo->ai_family) {
81156668Sshin			case AF_INET:
812100259Syar				addr = &((struct sockaddr_in *)hrp->hostinfo->ai_addr)->sin_addr;
813100259Syar				addrsize = sizeof(struct in_addr);
81456668Sshin				break;
81556668Sshin			case AF_INET6:
816100259Syar				addr = &((struct sockaddr_in6 *)hrp->hostinfo->ai_addr)->sin6_addr;
817100259Syar				addrsize = sizeof(struct in6_addr);
81856668Sshin				break;
81956668Sshin			default:
82056668Sshin				/* should not reach here */
821102473Syar				freeaddrinfo(hrp->hostinfo);
822102473Syar				if (insert)
823102473Syar					free(hrp); /*not in chain, can free*/
824102473Syar				else
825102473Syar					hrp->hostinfo = NULL; /*mark as blank*/
82699877Syar				goto nextline;
82756668Sshin				/* NOTREACHED */
82856668Sshin			}
829100612Syar			if ((hp = getipnodebyaddr(addr, addrsize,
83057124Sshin						  hrp->hostinfo->ai_family,
83156668Sshin						  &hp_error)) != NULL) {
832100182Syar				if (strcmp(vhost, hp->h_name) != 0) {
83325283Sdavidn					if (hp->h_aliases == NULL)
834100182Syar						vhost = hp->h_name;
83525283Sdavidn					else {
83625283Sdavidn						i = 0;
83725283Sdavidn						while (hp->h_aliases[i] &&
838100182Syar						       strcmp(vhost, hp->h_aliases[i]) != 0)
83925283Sdavidn							++i;
84025283Sdavidn						if (hp->h_aliases[i] == NULL)
841100182Syar							vhost = hp->h_name;
84225283Sdavidn					}
84325283Sdavidn				}
84425283Sdavidn			}
845102183Syar			if (hrp->hostname &&
846102183Syar			    strcmp(hrp->hostname, vhost) != 0) {
847102183Syar				free(hrp->hostname);
848102183Syar				hrp->hostname = NULL;
849102183Syar			}
850102183Syar			if (hrp->hostname == NULL &&
851102473Syar			    (hrp->hostname = strdup(vhost)) == NULL) {
852102473Syar				freeaddrinfo(hrp->hostinfo);
853102473Syar				hrp->hostinfo = NULL; /* mark as blank */
854102473Syar				if (hp)
855102473Syar					freehostent(hp);
856100182Syar				goto nextline;
857102473Syar			}
858100182Syar			hrp->anonuser = anonuser;
859100182Syar			hrp->statfile = statfile;
860100182Syar			hrp->welcome  = welcome;
861100182Syar			hrp->loginmsg = loginmsg;
862100182Syar			if (insert) {
863100182Syar				hrp->next  = NULL;
864100182Syar				lhrp->next = hrp;
865100182Syar				lhrp = hrp;
866100182Syar			}
867100263Syar			if (hp)
868100263Syar				freehostent(hp);
86956668Sshin		      }
87099877Syarnextline:
87199877Syar			if (mp)
87299877Syar				free(mp);
87325283Sdavidn		}
87425283Sdavidn		(void) fclose(fp);
87525283Sdavidn	}
87625283Sdavidn}
87725283Sdavidn
87825283Sdavidnstatic void
87990148Simpselecthost(union sockunion *su)
88025283Sdavidn{
88125283Sdavidn	struct ftphost	*hrp;
88256668Sshin	u_int16_t port;
88356668Sshin#ifdef INET6
88456668Sshin	struct in6_addr *mapped_in6 = NULL;
88556668Sshin#endif
88657124Sshin	struct addrinfo *hi;
88725283Sdavidn
88856668Sshin#ifdef INET6
88956668Sshin	/*
89056668Sshin	 * XXX IPv4 mapped IPv6 addr consideraton,
89156668Sshin	 * specified in rfc2373.
89256668Sshin	 */
89356668Sshin	if (su->su_family == AF_INET6 &&
89456668Sshin	    IN6_IS_ADDR_V4MAPPED(&su->su_sin6.sin6_addr))
89556668Sshin		mapped_in6 = &su->su_sin6.sin6_addr;
89656668Sshin#endif
89756668Sshin
89825283Sdavidn	hrp = thishost = firsthost;	/* default */
89956668Sshin	port = su->su_port;
90056668Sshin	su->su_port = 0;
90125283Sdavidn	while (hrp != NULL) {
90262100Sdavidn	    for (hi = hrp->hostinfo; hi != NULL; hi = hi->ai_next) {
90357124Sshin		if (memcmp(su, hi->ai_addr, hi->ai_addrlen) == 0) {
90425283Sdavidn			thishost = hrp;
905137987Syar			goto found;
90625283Sdavidn		}
90756668Sshin#ifdef INET6
90856668Sshin		/* XXX IPv4 mapped IPv6 addr consideraton */
90957124Sshin		if (hi->ai_addr->sa_family == AF_INET && mapped_in6 != NULL &&
91056668Sshin		    (memcmp(&mapped_in6->s6_addr[12],
91157124Sshin			    &((struct sockaddr_in *)hi->ai_addr)->sin_addr,
91256668Sshin			    sizeof(struct in_addr)) == 0)) {
91356668Sshin			thishost = hrp;
914137987Syar			goto found;
91556668Sshin		}
91656668Sshin#endif
91762100Sdavidn	    }
91862100Sdavidn	    hrp = hrp->next;
91925283Sdavidn	}
920137987Syarfound:
92156668Sshin	su->su_port = port;
92225283Sdavidn	/* setup static variables as appropriate */
92325283Sdavidn	hostname = thishost->hostname;
92425283Sdavidn	ftpuser = thishost->anonuser;
92525283Sdavidn}
92625283Sdavidn#endif
92725283Sdavidn
92825283Sdavidn/*
9291592Srgrimes * Helper function for sgetpwnam().
9301592Srgrimes */
9311592Srgrimesstatic char *
93290148Simpsgetsave(char *s)
9331592Srgrimes{
934137659Syar	char *new = malloc(strlen(s) + 1);
9351592Srgrimes
9361592Srgrimes	if (new == NULL) {
937137852Syar		reply(421, "Ran out of memory.");
9381592Srgrimes		dologout(1);
9391592Srgrimes		/* NOTREACHED */
9401592Srgrimes	}
9411592Srgrimes	(void) strcpy(new, s);
9421592Srgrimes	return (new);
9431592Srgrimes}
9441592Srgrimes
9451592Srgrimes/*
9461592Srgrimes * Save the result of a getpwnam.  Used for USER command, since
9471592Srgrimes * the data returned must not be clobbered by any other command
9481592Srgrimes * (e.g., globbing).
949137076Syar * NB: The data returned by sgetpwnam() will remain valid until
950137076Syar * the next call to this function.  Its difference from getpwnam()
951137076Syar * is that sgetpwnam() is known to be called from ftpd code only.
9521592Srgrimes */
9531592Srgrimesstatic struct passwd *
95490148Simpsgetpwnam(char *name)
9551592Srgrimes{
9561592Srgrimes	static struct passwd save;
9571592Srgrimes	struct passwd *p;
9581592Srgrimes
9591592Srgrimes	if ((p = getpwnam(name)) == NULL)
9601592Srgrimes		return (p);
9611592Srgrimes	if (save.pw_name) {
9621592Srgrimes		free(save.pw_name);
9631592Srgrimes		free(save.pw_passwd);
964262284Sbrueffer		free(save.pw_class);
9651592Srgrimes		free(save.pw_gecos);
9661592Srgrimes		free(save.pw_dir);
9671592Srgrimes		free(save.pw_shell);
9681592Srgrimes	}
9691592Srgrimes	save = *p;
9701592Srgrimes	save.pw_name = sgetsave(p->pw_name);
9711592Srgrimes	save.pw_passwd = sgetsave(p->pw_passwd);
972262284Sbrueffer	save.pw_class = sgetsave(p->pw_class);
9731592Srgrimes	save.pw_gecos = sgetsave(p->pw_gecos);
9741592Srgrimes	save.pw_dir = sgetsave(p->pw_dir);
9751592Srgrimes	save.pw_shell = sgetsave(p->pw_shell);
9761592Srgrimes	return (&save);
9771592Srgrimes}
9781592Srgrimes
9791592Srgrimesstatic int login_attempts;	/* number of failed login attempts */
9801592Srgrimesstatic int askpasswd;		/* had user command, ask for passwd */
98164778Ssheldonhstatic char curname[MAXLOGNAME];	/* current USER name */
9821592Srgrimes
9831592Srgrimes/*
9841592Srgrimes * USER command.
9851592Srgrimes * Sets global passwd pointer pw if named account exists and is acceptable;
9861592Srgrimes * sets askpasswd if a PASS command is expected.  If logged in previously,
9871592Srgrimes * need to reset state.  If name is "ftp" or "anonymous", the name is not in
9881592Srgrimes * _PATH_FTPUSERS, and ftp account exists, set guest and pw, then just return.
9891592Srgrimes * If account doesn't exist, ask for passwd anyway.  Otherwise, check user
9901592Srgrimes * requesting login privileges.  Disallow anyone who does not have a standard
9911592Srgrimes * shell as returned by getusershell().  Disallow anyone mentioned in the file
9921592Srgrimes * _PATH_FTPUSERS to allow people such as root and uucp to be avoided.
9931592Srgrimes */
9941592Srgrimesvoid
99590148Simpuser(char *name)
9961592Srgrimes{
997216932Scsjp	int ecode;
9981592Srgrimes	char *cp, *shell;
9991592Srgrimes
10001592Srgrimes	if (logged_in) {
10011592Srgrimes		if (guest) {
10021592Srgrimes			reply(530, "Can't change user from guest login.");
10031592Srgrimes			return;
100417435Spst		} else if (dochroot) {
100517435Spst			reply(530, "Can't change user from chroot user.");
100617435Spst			return;
10071592Srgrimes		}
10081592Srgrimes		end_login();
10091592Srgrimes	}
10101592Srgrimes
10111592Srgrimes	guest = 0;
1012130428Sobrien#ifdef VIRTUAL_HOSTING
1013130428Sobrien	pw = sgetpwnam(thishost->anonuser);
1014130428Sobrien#else
1015130428Sobrien	pw = sgetpwnam("ftp");
1016130428Sobrien#endif
10171592Srgrimes	if (strcmp(name, "ftp") == 0 || strcmp(name, "anonymous") == 0) {
1018216932Scsjp		if (checkuser(_PATH_FTPUSERS, "ftp", 0, NULL, &ecode) ||
1019216932Scsjp		    (ecode != 0 && ecode != ENOENT))
10201592Srgrimes			reply(530, "User %s access denied.", name);
1021216932Scsjp		else if (checkuser(_PATH_FTPUSERS, "anonymous", 0, NULL, &ecode) ||
1022216932Scsjp		    (ecode != 0 && ecode != ENOENT))
1023216932Scsjp			reply(530, "User %s access denied.", name);
1024130428Sobrien		else if (pw != NULL) {
10251592Srgrimes			guest = 1;
10261592Srgrimes			askpasswd = 1;
10271592Srgrimes			reply(331,
10283938Spst			"Guest login ok, send your email address as password.");
10291592Srgrimes		} else
10301592Srgrimes			reply(530, "User %s unknown.", name);
10311592Srgrimes		if (!askpasswd && logging)
10321592Srgrimes			syslog(LOG_NOTICE,
10331592Srgrimes			    "ANONYMOUS FTP LOGIN REFUSED FROM %s", remotehost);
10341592Srgrimes		return;
10351592Srgrimes	}
103620042Storstenb	if (anon_only != 0) {
103720042Storstenb		reply(530, "Sorry, only anonymous ftp allowed.");
103820042Storstenb		return;
103920042Storstenb	}
104020042Storstenb
104117478Smarkm	if ((pw = sgetpwnam(name))) {
10421592Srgrimes		if ((shell = pw->pw_shell) == NULL || *shell == 0)
10431592Srgrimes			shell = _PATH_BSHELL;
1044124687Scharnier		setusershell();
10451592Srgrimes		while ((cp = getusershell()) != NULL)
10461592Srgrimes			if (strcmp(cp, shell) == 0)
10471592Srgrimes				break;
10481592Srgrimes		endusershell();
10491592Srgrimes
1050216932Scsjp		if (cp == NULL ||
1051216932Scsjp		    (checkuser(_PATH_FTPUSERS, name, 1, NULL, &ecode) ||
1052216932Scsjp		    (ecode != 0 && ecode != ENOENT))) {
10531592Srgrimes			reply(530, "User %s access denied.", name);
10541592Srgrimes			if (logging)
10551592Srgrimes				syslog(LOG_NOTICE,
10561592Srgrimes				    "FTP LOGIN REFUSED FROM %s, %s",
10571592Srgrimes				    remotehost, name);
1058137811Syar			pw = NULL;
10591592Srgrimes			return;
10601592Srgrimes		}
10611592Srgrimes	}
10621592Srgrimes	if (logging)
10631592Srgrimes		strncpy(curname, name, sizeof(curname)-1);
106488763Sache
106588763Sache	pwok = 0;
106679469Smarkm#ifdef USE_PAM
106779469Smarkm	/* XXX Kluge! The conversation mechanism needs to be fixed. */
106888763Sache#endif
106988763Sache	if (opiechallenge(&opiedata, name, opieprompt) == 0) {
107088763Sache		pwok = (pw != NULL) &&
107188763Sache		       opieaccessfile(remotehost) &&
107288763Sache		       opiealways(pw->pw_dir);
107388763Sache		reply(331, "Response to %s %s for %s.",
107488763Sache		      opieprompt, pwok ? "requested" : "required", name);
107588763Sache	} else {
107688763Sache		pwok = 1;
107784146Sache		reply(331, "Password required for %s.", name);
107888763Sache	}
10791592Srgrimes	askpasswd = 1;
10801592Srgrimes	/*
10811592Srgrimes	 * Delay before reading passwd after first failed
10821592Srgrimes	 * attempt to slow down passwd-guessing programs.
10831592Srgrimes	 */
10841592Srgrimes	if (login_attempts)
1085137659Syar		sleep(login_attempts);
10861592Srgrimes}
10871592Srgrimes
10881592Srgrimes/*
1089109893Syar * Check if a user is in the file "fname",
1090109893Syar * return a pointer to a malloc'd string with the rest
1091109893Syar * of the matching line in "residue" if not NULL.
10921592Srgrimes */
10931592Srgrimesstatic int
1094216932Scsjpcheckuser(char *fname, char *name, int pwset, char **residue, int *ecode)
10951592Srgrimes{
10961592Srgrimes	FILE *fd;
10971592Srgrimes	int found = 0;
109899877Syar	size_t len;
109999877Syar	char *line, *mp, *p;
11001592Srgrimes
1101216932Scsjp	if (ecode != NULL)
1102216932Scsjp		*ecode = 0;
110317435Spst	if ((fd = fopen(fname, "r")) != NULL) {
110499877Syar		while (!found && (line = fgetln(fd, &len)) != NULL) {
110599877Syar			/* skip comments */
110699877Syar			if (line[0] == '#')
110799877Syar				continue;
110899877Syar			if (line[len - 1] == '\n') {
110999877Syar				line[len - 1] = '\0';
111099877Syar				mp = NULL;
111199877Syar			} else {
111299877Syar				if ((mp = malloc(len + 1)) == NULL)
111399877Syar					fatalerror("Ran out of memory.");
111499877Syar				memcpy(mp, line, len);
111599877Syar				mp[len] = '\0';
111699877Syar				line = mp;
111799877Syar			}
111899877Syar			/* avoid possible leading and trailing whitespace */
111999877Syar			p = strtok(line, " \t");
112099877Syar			/* skip empty lines */
112199877Syar			if (p == NULL)
112299877Syar				goto nextline;
112399877Syar			/*
112499877Syar			 * if first chr is '@', check group membership
112599877Syar			 */
112699877Syar			if (p[0] == '@') {
112799877Syar				int i = 0;
112899877Syar				struct group *grp;
112999877Syar
1130109893Syar				if (p[1] == '\0') /* single @ matches anyone */
113199877Syar					found = 1;
1132109893Syar				else {
1133109893Syar					if ((grp = getgrnam(p+1)) == NULL)
1134109893Syar						goto nextline;
1135109893Syar					/*
1136109893Syar					 * Check user's default group
1137109893Syar					 */
1138109893Syar					if (pwset && grp->gr_gid == pw->pw_gid)
1139109893Syar						found = 1;
1140109893Syar					/*
1141109893Syar					 * Check supplementary groups
1142109893Syar					 */
1143109893Syar					while (!found && grp->gr_mem[i])
1144109893Syar						found = strcmp(name,
1145109893Syar							grp->gr_mem[i++])
1146109893Syar							== 0;
1147109893Syar				}
11481592Srgrimes			}
114999877Syar			/*
115099877Syar			 * Otherwise, just check for username match
115199877Syar			 */
115299877Syar			else
115399877Syar				found = strcmp(p, name) == 0;
1154109893Syar			/*
1155109893Syar			 * Save the rest of line to "residue" if matched
1156109893Syar			 */
1157109893Syar			if (found && residue) {
1158109938Syar				if ((p = strtok(NULL, "")) != NULL)
1159109938Syar					p += strspn(p, " \t");
1160109938Syar				if (p && *p) {
1161109893Syar				 	if ((*residue = strdup(p)) == NULL)
1162109893Syar						fatalerror("Ran out of memory.");
1163109893Syar				} else
1164109893Syar					*residue = NULL;
1165109893Syar			}
116699877Syarnextline:
116799877Syar			if (mp)
116899877Syar				free(mp);
116999877Syar		}
11701592Srgrimes		(void) fclose(fd);
1171216932Scsjp	} else if (ecode != NULL)
1172216932Scsjp		*ecode = errno;
11731592Srgrimes	return (found);
11741592Srgrimes}
11751592Srgrimes
11761592Srgrimes/*
11771592Srgrimes * Terminate login as previous user, if any, resetting state;
11781592Srgrimes * used when USER command is given or login fails.
11791592Srgrimes */
11801592Srgrimesstatic void
118190148Simpend_login(void)
11821592Srgrimes{
118374874Smarkm#ifdef USE_PAM
118474874Smarkm	int e;
118574874Smarkm#endif
11861592Srgrimes
1187132893Syar	(void) seteuid(0);
1188202604Sed	if (logged_in && dowtmp)
1189202604Sed		ftpd_logwtmp(wtmpid, NULL, NULL);
11901592Srgrimes	pw = NULL;
119125101Sdavidn#ifdef	LOGIN_CAP
1192223434Strasz	setusercontext(NULL, getpwuid(0), 0, LOGIN_SETALL & ~(LOGIN_SETLOGIN |
1193223434Strasz		       LOGIN_SETUSER | LOGIN_SETGROUP | LOGIN_SETPATH |
1194223434Strasz		       LOGIN_SETENV));
119525101Sdavidn#endif
119674874Smarkm#ifdef USE_PAM
1197137078Syar	if (pamh) {
1198137078Syar		if ((e = pam_setcred(pamh, PAM_DELETE_CRED)) != PAM_SUCCESS)
1199137078Syar			syslog(LOG_ERR, "pam_setcred: %s", pam_strerror(pamh, e));
1200137078Syar		if ((e = pam_close_session(pamh,0)) != PAM_SUCCESS)
1201137078Syar			syslog(LOG_ERR, "pam_close_session: %s", pam_strerror(pamh, e));
1202137078Syar		if ((e = pam_end(pamh, e)) != PAM_SUCCESS)
1203137078Syar			syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e));
1204137078Syar		pamh = NULL;
1205137078Syar	}
120674874Smarkm#endif
12071592Srgrimes	logged_in = 0;
12081592Srgrimes	guest = 0;
120917435Spst	dochroot = 0;
12101592Srgrimes}
12111592Srgrimes
121274874Smarkm#ifdef USE_PAM
121351433Smarkm
121451433Smarkm/*
121551433Smarkm * the following code is stolen from imap-uw PAM authentication module and
121651433Smarkm * login.c
121751433Smarkm */
121851433Smarkm#define COPY_STRING(s) (s ? strdup(s) : NULL)
121951433Smarkm
122051433Smarkmstruct cred_t {
122151433Smarkm	const char *uname;		/* user name */
122251433Smarkm	const char *pass;		/* password */
122351433Smarkm};
122451433Smarkmtypedef struct cred_t cred_t;
122551433Smarkm
122651433Smarkmstatic int
122751433Smarkmauth_conv(int num_msg, const struct pam_message **msg,
122851433Smarkm	  struct pam_response **resp, void *appdata)
122951433Smarkm{
123051433Smarkm	int i;
123151433Smarkm	cred_t *cred = (cred_t *) appdata;
123291244Sdes	struct pam_response *reply;
123351433Smarkm
123491244Sdes	reply = calloc(num_msg, sizeof *reply);
123591244Sdes	if (reply == NULL)
123691244Sdes		return PAM_BUF_ERR;
123791244Sdes
123851433Smarkm	for (i = 0; i < num_msg; i++) {
123951433Smarkm		switch (msg[i]->msg_style) {
124051433Smarkm		case PAM_PROMPT_ECHO_ON:	/* assume want user name */
124151433Smarkm			reply[i].resp_retcode = PAM_SUCCESS;
124251433Smarkm			reply[i].resp = COPY_STRING(cred->uname);
124351433Smarkm			/* PAM frees resp. */
124451433Smarkm			break;
124551433Smarkm		case PAM_PROMPT_ECHO_OFF:	/* assume want password */
124651433Smarkm			reply[i].resp_retcode = PAM_SUCCESS;
124751433Smarkm			reply[i].resp = COPY_STRING(cred->pass);
124851433Smarkm			/* PAM frees resp. */
124951433Smarkm			break;
125051433Smarkm		case PAM_TEXT_INFO:
125151433Smarkm		case PAM_ERROR_MSG:
125251433Smarkm			reply[i].resp_retcode = PAM_SUCCESS;
125351433Smarkm			reply[i].resp = NULL;
125451433Smarkm			break;
125551433Smarkm		default:			/* unknown message style */
125651433Smarkm			free(reply);
125751433Smarkm			return PAM_CONV_ERR;
125851433Smarkm		}
125951433Smarkm	}
126051433Smarkm
126151433Smarkm	*resp = reply;
126251433Smarkm	return PAM_SUCCESS;
126351433Smarkm}
126451433Smarkm
126551433Smarkm/*
126651433Smarkm * Attempt to authenticate the user using PAM.  Returns 0 if the user is
126751433Smarkm * authenticated, or 1 if not authenticated.  If some sort of PAM system
126851433Smarkm * error occurs (e.g., the "/etc/pam.conf" file is missing) then this
126951433Smarkm * function returns -1.  This can be used as an indication that we should
127051433Smarkm * fall back to a different authentication mechanism.
127151433Smarkm */
127251433Smarkmstatic int
127351433Smarkmauth_pam(struct passwd **ppw, const char *pass)
127451433Smarkm{
127551433Smarkm	const char *tmpl_user;
127651433Smarkm	const void *item;
127751433Smarkm	int rval;
127851433Smarkm	int e;
127951433Smarkm	cred_t auth_cred = { (*ppw)->pw_name, pass };
128051433Smarkm	struct pam_conv conv = { &auth_conv, &auth_cred };
128151433Smarkm
128251433Smarkm	e = pam_start("ftpd", (*ppw)->pw_name, &conv, &pamh);
128351433Smarkm	if (e != PAM_SUCCESS) {
1284137108Syar		/*
1285137108Syar		 * In OpenPAM, it's OK to pass NULL to pam_strerror()
1286137108Syar		 * if context creation has failed in the first place.
1287137108Syar		 */
1288137108Syar		syslog(LOG_ERR, "pam_start: %s", pam_strerror(NULL, e));
128951433Smarkm		return -1;
129051433Smarkm	}
129151433Smarkm
129267007Sguido	e = pam_set_item(pamh, PAM_RHOST, remotehost);
129367007Sguido	if (e != PAM_SUCCESS) {
129467007Sguido		syslog(LOG_ERR, "pam_set_item(PAM_RHOST): %s",
129567007Sguido			pam_strerror(pamh, e));
1296137078Syar		if ((e = pam_end(pamh, e)) != PAM_SUCCESS) {
1297137078Syar			syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e));
1298137078Syar		}
1299137078Syar		pamh = NULL;
130067007Sguido		return -1;
130167007Sguido	}
130267007Sguido
130351433Smarkm	e = pam_authenticate(pamh, 0);
130451433Smarkm	switch (e) {
130551433Smarkm	case PAM_SUCCESS:
130651433Smarkm		/*
130751433Smarkm		 * With PAM we support the concept of a "template"
130851433Smarkm		 * user.  The user enters a login name which is
130951433Smarkm		 * authenticated by PAM, usually via a remote service
131051433Smarkm		 * such as RADIUS or TACACS+.  If authentication
131151433Smarkm		 * succeeds, a different but related "template" name
131251433Smarkm		 * is used for setting the credentials, shell, and
131351433Smarkm		 * home directory.  The name the user enters need only
131451433Smarkm		 * exist on the remote authentication server, but the
131551433Smarkm		 * template name must be present in the local password
131651433Smarkm		 * database.
131751433Smarkm		 *
131851433Smarkm		 * This is supported by two various mechanisms in the
131951433Smarkm		 * individual modules.  However, from the application's
132051433Smarkm		 * point of view, the template user is always passed
132151433Smarkm		 * back as a changed value of the PAM_USER item.
132251433Smarkm		 */
132351433Smarkm		if ((e = pam_get_item(pamh, PAM_USER, &item)) ==
132451433Smarkm		    PAM_SUCCESS) {
132551433Smarkm			tmpl_user = (const char *) item;
132651433Smarkm			if (strcmp((*ppw)->pw_name, tmpl_user) != 0)
132751433Smarkm				*ppw = getpwnam(tmpl_user);
132851433Smarkm		} else
132951433Smarkm			syslog(LOG_ERR, "Couldn't get PAM_USER: %s",
133051433Smarkm			    pam_strerror(pamh, e));
133151433Smarkm		rval = 0;
133251433Smarkm		break;
133351433Smarkm
133451433Smarkm	case PAM_AUTH_ERR:
133551433Smarkm	case PAM_USER_UNKNOWN:
133651433Smarkm	case PAM_MAXTRIES:
133751433Smarkm		rval = 1;
133851433Smarkm		break;
133951433Smarkm
134051433Smarkm	default:
134174874Smarkm		syslog(LOG_ERR, "pam_authenticate: %s", pam_strerror(pamh, e));
134251433Smarkm		rval = -1;
134351433Smarkm		break;
134451433Smarkm	}
134551433Smarkm
134674874Smarkm	if (rval == 0) {
134774874Smarkm		e = pam_acct_mgmt(pamh, 0);
1348137986Syar		if (e != PAM_SUCCESS) {
1349137986Syar			syslog(LOG_ERR, "pam_acct_mgmt: %s",
1350137986Syar						pam_strerror(pamh, e));
135174874Smarkm			rval = 1;
135274874Smarkm		}
135351433Smarkm	}
135474874Smarkm
135574874Smarkm	if (rval != 0) {
135674874Smarkm		if ((e = pam_end(pamh, e)) != PAM_SUCCESS) {
135774874Smarkm			syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e));
135874874Smarkm		}
135974874Smarkm		pamh = NULL;
136074874Smarkm	}
136151433Smarkm	return rval;
136251433Smarkm}
136351433Smarkm
136474874Smarkm#endif /* USE_PAM */
136551433Smarkm
13661592Srgrimesvoid
136790148Simppass(char *passwd)
13681592Srgrimes{
1369216932Scsjp	int rval, ecode;
13701592Srgrimes	FILE *fd;
137125101Sdavidn#ifdef	LOGIN_CAP
137225101Sdavidn	login_cap_t *lc = NULL;
137325101Sdavidn#endif
137474874Smarkm#ifdef USE_PAM
137574874Smarkm	int e;
137674874Smarkm#endif
1377109939Syar	char *residue = NULL;
137888763Sache	char *xpasswd;
13791592Srgrimes
13801592Srgrimes	if (logged_in || askpasswd == 0) {
13811592Srgrimes		reply(503, "Login with USER first.");
13821592Srgrimes		return;
13831592Srgrimes	}
13841592Srgrimes	askpasswd = 0;
13851592Srgrimes	if (!guest) {		/* "ftp" is only account allowed no password */
138617435Spst		if (pw == NULL) {
138717435Spst			rval = 1;	/* failure below */
138817435Spst			goto skip;
138917435Spst		}
139074874Smarkm#ifdef USE_PAM
139151433Smarkm		rval = auth_pam(&pw, passwd);
139289622Sache		if (rval >= 0) {
139389622Sache			opieunlock();
139417435Spst			goto skip;
139589622Sache		}
139689622Sache#endif
139788763Sache		if (opieverify(&opiedata, passwd) == 0)
139888763Sache			xpasswd = pw->pw_passwd;
139989622Sache		else if (pwok) {
140088763Sache			xpasswd = crypt(passwd, pw->pw_passwd);
140189622Sache			if (passwd[0] == '\0' && pw->pw_passwd[0] != '\0')
140289622Sache				xpasswd = ":";
140389622Sache		} else {
140488763Sache			rval = 1;
140588763Sache			goto skip;
140688763Sache		}
140788763Sache		rval = strcmp(pw->pw_passwd, xpasswd);
140889622Sache		if (pw->pw_expire && time(NULL) >= pw->pw_expire)
140917435Spst			rval = 1;	/* failure */
141017435Spstskip:
141117435Spst		/*
141217435Spst		 * If rval == 1, the user failed the authentication check
141351433Smarkm		 * above.  If rval == 0, either PAM or local authentication
141417435Spst		 * succeeded.
141517435Spst		 */
141617435Spst		if (rval) {
14171592Srgrimes			reply(530, "Login incorrect.");
1418110691Syar			if (logging) {
14191592Srgrimes				syslog(LOG_NOTICE,
1420110691Syar				    "FTP LOGIN FAILED FROM %s",
1421110691Syar				    remotehost);
1422110691Syar				syslog(LOG_AUTHPRIV | LOG_NOTICE,
14231592Srgrimes				    "FTP LOGIN FAILED FROM %s, %s",
14241592Srgrimes				    remotehost, curname);
1425110691Syar			}
14261592Srgrimes			pw = NULL;
14271592Srgrimes			if (login_attempts++ >= 5) {
14281592Srgrimes				syslog(LOG_NOTICE,
14291592Srgrimes				    "repeated login failures from %s",
14301592Srgrimes				    remotehost);
14311592Srgrimes				exit(0);
14321592Srgrimes			}
14331592Srgrimes			return;
14341592Srgrimes		}
14351592Srgrimes	}
14361592Srgrimes	login_attempts = 0;		/* this time successful */
1437132894Syar	if (setegid(pw->pw_gid) < 0) {
14381592Srgrimes		reply(550, "Can't set gid.");
14391592Srgrimes		return;
14401592Srgrimes	}
144125101Sdavidn	/* May be overridden by login.conf */
144225101Sdavidn	(void) umask(defumask);
144325101Sdavidn#ifdef	LOGIN_CAP
144425674Sdavidn	if ((lc = login_getpwclass(pw)) != NULL) {
1445137983Syar		char	remote_ip[NI_MAXHOST];
144625101Sdavidn
1447137983Syar		if (getnameinfo((struct sockaddr *)&his_addr, his_addr.su_len,
144856668Sshin			remote_ip, sizeof(remote_ip) - 1, NULL, 0,
1449137983Syar			NI_NUMERICHOST))
1450137983Syar				*remote_ip = 0;
145125101Sdavidn		remote_ip[sizeof(remote_ip) - 1] = 0;
145225101Sdavidn		if (!auth_hostok(lc, remotehost, remote_ip)) {
145325101Sdavidn			syslog(LOG_INFO|LOG_AUTH,
145425101Sdavidn			    "FTP LOGIN FAILED (HOST) as %s: permission denied.",
145525101Sdavidn			    pw->pw_name);
1456137849Syar			reply(530, "Permission denied.");
145725101Sdavidn			pw = NULL;
145825101Sdavidn			return;
145925101Sdavidn		}
146025101Sdavidn		if (!auth_timeok(lc, time(NULL))) {
1461137849Syar			reply(530, "Login not available right now.");
146225101Sdavidn			pw = NULL;
146325101Sdavidn			return;
146425101Sdavidn		}
146525101Sdavidn	}
1466223434Strasz	setusercontext(lc, pw, 0, LOGIN_SETALL &
1467223434Strasz		       ~(LOGIN_SETUSER | LOGIN_SETPATH | LOGIN_SETENV));
146825101Sdavidn#else
146940310Sdes	setlogin(pw->pw_name);
14701592Srgrimes	(void) initgroups(pw->pw_name, pw->pw_gid);
147125101Sdavidn#endif
14721592Srgrimes
147374874Smarkm#ifdef USE_PAM
147474874Smarkm	if (pamh) {
147574874Smarkm		if ((e = pam_open_session(pamh, 0)) != PAM_SUCCESS) {
147674874Smarkm			syslog(LOG_ERR, "pam_open_session: %s", pam_strerror(pamh, e));
147774874Smarkm		} else if ((e = pam_setcred(pamh, PAM_ESTABLISH_CRED)) != PAM_SUCCESS) {
147874874Smarkm			syslog(LOG_ERR, "pam_setcred: %s", pam_strerror(pamh, e));
147974874Smarkm		}
148074874Smarkm	}
148174874Smarkm#endif
148274874Smarkm
1483202209Sed	dochroot =
1484216932Scsjp		checkuser(_PATH_FTPCHROOT, pw->pw_name, 1, &residue, &ecode)
1485202209Sed#ifdef	LOGIN_CAP	/* Allow login.conf configuration as well */
1486202209Sed		|| login_getcapbool(lc, "ftp-chroot", 0)
1487202209Sed#endif
1488202209Sed	;
1489216932Scsjp	/*
1490216932Scsjp	 * It is possible that checkuser() failed to open the chroot file.
1491216932Scsjp	 * If this is the case, report that logins are un-available, since we
1492216932Scsjp	 * have no way of checking whether or not the user should be chrooted.
1493216932Scsjp	 * We ignore ENOENT since it is not required that this file be present.
1494216932Scsjp	 */
1495216932Scsjp	if (ecode != 0 && ecode != ENOENT) {
1496216932Scsjp		reply(530, "Login not available right now.");
1497216932Scsjp		return;
1498216932Scsjp	}
1499202209Sed	chrootdir = NULL;
1500202209Sed
1501202604Sed	/* Disable wtmp logging when chrooting. */
1502202604Sed	if (dochroot || guest)
1503202604Sed		dowtmp = 0;
1504202604Sed	if (dowtmp)
1505202209Sed		ftpd_logwtmp(wtmpid, pw->pw_name,
1506102311Syar		    (struct sockaddr *)&his_addr);
15071592Srgrimes	logged_in = 1;
15081592Srgrimes
150917435Spst	if (guest && stats && statfd < 0)
151025283Sdavidn#ifdef VIRTUAL_HOSTING
1511130428Sobrien		statfd = open(thishost->statfile, O_WRONLY|O_APPEND);
151225283Sdavidn#else
1513130428Sobrien		statfd = open(_PATH_FTPDSTATFILE, O_WRONLY|O_APPEND);
151425283Sdavidn#endif
1515130428Sobrien		if (statfd < 0)
15166740Sguido			stats = 0;
15176740Sguido
1518110036Syar	/*
1519110036Syar	 * For a chrooted local user,
1520110036Syar	 * a) see whether ftpchroot(5) specifies a chroot directory,
1521110036Syar	 * b) extract the directory pathname from the line,
1522110036Syar	 * c) expand it to the absolute pathname if necessary.
1523110036Syar	 */
1524110036Syar	if (dochroot && residue &&
1525117349Syar	    (chrootdir = strtok(residue, " \t")) != NULL) {
1526117349Syar		if (chrootdir[0] != '/')
1527117349Syar			asprintf(&chrootdir, "%s/%s", pw->pw_dir, chrootdir);
1528117349Syar		else
1529137862Syar			chrootdir = strdup(chrootdir); /* make it permanent */
1530110036Syar		if (chrootdir == NULL)
1531110036Syar			fatalerror("Ran out of memory.");
1532110036Syar	}
1533110036Syar	if (guest || dochroot) {
15341592Srgrimes		/*
1535110036Syar		 * If no chroot directory set yet, use the login directory.
1536110036Syar		 * Copy it so it can be modified while pw->pw_dir stays intact.
15371592Srgrimes		 */
1538110036Syar		if (chrootdir == NULL &&
1539110036Syar		    (chrootdir = strdup(pw->pw_dir)) == NULL)
1540110036Syar			fatalerror("Ran out of memory.");
1541110036Syar		/*
1542110036Syar		 * Check for the "/chroot/./home" syntax,
1543110036Syar		 * separate the chroot and home directory pathnames.
1544110036Syar		 */
1545110036Syar		if ((homedir = strstr(chrootdir, "/./")) != NULL) {
1546110036Syar			*(homedir++) = '\0';	/* wipe '/' */
1547110036Syar			homedir++;		/* skip '.' */
1548110036Syar		} else {
1549110036Syar			/*
1550110036Syar			 * We MUST do a chdir() after the chroot. Otherwise
1551110036Syar			 * the old current directory will be accessible as "."
1552110036Syar			 * outside the new root!
1553110036Syar			 */
1554110036Syar			homedir = "/";
1555109939Syar		}
1556110036Syar		/*
1557110036Syar		 * Finally, do chroot()
1558110036Syar		 */
1559110036Syar		if (chroot(chrootdir) < 0) {
156017435Spst			reply(550, "Can't change root.");
156117435Spst			goto bad;
156217435Spst		}
1563228843Scperciva		__FreeBSD_libc_enter_restricted_mode();
1564110036Syar	} else	/* real user w/o chroot */
1565110036Syar		homedir = pw->pw_dir;
1566110036Syar	/*
1567110036Syar	 * Set euid *before* doing chdir() so
1568110036Syar	 * a) the user won't be carried to a directory that he couldn't reach
1569110036Syar	 *    on his own due to no permission to upper path components,
1570110036Syar	 * b) NFS mounted homedirs w/restrictive permissions will be accessible
1571110036Syar	 *    (uid 0 has no root power over NFS if not mapped explicitly.)
1572110036Syar	 */
1573132893Syar	if (seteuid(pw->pw_uid) < 0) {
15741592Srgrimes		reply(550, "Can't set uid.");
15751592Srgrimes		goto bad;
15761592Srgrimes	}
1577110036Syar	if (chdir(homedir) < 0) {
1578110036Syar		if (guest || dochroot) {
1579110036Syar			reply(550, "Can't change to base directory.");
1580110036Syar			goto bad;
1581110036Syar		} else {
1582110036Syar			if (chdir("/") < 0) {
1583110036Syar				reply(550, "Root is inaccessible.");
1584110036Syar				goto bad;
1585110036Syar			}
1586137852Syar			lreply(230, "No directory! Logging in with home=/.");
1587110036Syar		}
1588110036Syar	}
15898696Sdg
15901592Srgrimes	/*
15911592Srgrimes	 * Display a login message, if it exists.
15921592Srgrimes	 * N.B. reply(230,) must follow the message.
15931592Srgrimes	 */
159425283Sdavidn#ifdef VIRTUAL_HOSTING
1595130428Sobrien	fd = fopen(thishost->loginmsg, "r");
159625283Sdavidn#else
1597130428Sobrien	fd = fopen(_PATH_FTPLOGINMESG, "r");
159825283Sdavidn#endif
1599130428Sobrien	if (fd != NULL) {
16001592Srgrimes		char *cp, line[LINE_MAX];
16011592Srgrimes
16021592Srgrimes		while (fgets(line, sizeof(line), fd) != NULL) {
16031592Srgrimes			if ((cp = strchr(line, '\n')) != NULL)
16041592Srgrimes				*cp = '\0';
16051592Srgrimes			lreply(230, "%s", line);
16061592Srgrimes		}
16071592Srgrimes		(void) fflush(stdout);
16081592Srgrimes		(void) fclose(fd);
16091592Srgrimes	}
16101592Srgrimes	if (guest) {
16116740Sguido		if (ident != NULL)
16126740Sguido			free(ident);
161317433Spst		ident = strdup(passwd);
161417433Spst		if (ident == NULL)
161576096Smarkm			fatalerror("Ran out of memory.");
161617433Spst
16171592Srgrimes		reply(230, "Guest login ok, access restrictions apply.");
16181592Srgrimes#ifdef SETPROCTITLE
161925283Sdavidn#ifdef VIRTUAL_HOSTING
162025283Sdavidn		if (thishost != firsthost)
162125283Sdavidn			snprintf(proctitle, sizeof(proctitle),
162283308Smikeh				 "%s: anonymous(%s)/%s", remotehost, hostname,
162383308Smikeh				 passwd);
162425283Sdavidn		else
162525283Sdavidn#endif
162625283Sdavidn			snprintf(proctitle, sizeof(proctitle),
162783308Smikeh				 "%s: anonymous/%s", remotehost, passwd);
162813139Speter		setproctitle("%s", proctitle);
16291592Srgrimes#endif /* SETPROCTITLE */
16301592Srgrimes		if (logging)
16311592Srgrimes			syslog(LOG_INFO, "ANONYMOUS FTP LOGIN FROM %s, %s",
16321592Srgrimes			    remotehost, passwd);
16331592Srgrimes	} else {
163484841Syar		if (dochroot)
163584841Syar			reply(230, "User %s logged in, "
163684841Syar				   "access restrictions apply.", pw->pw_name);
163784841Syar		else
163884841Syar			reply(230, "User %s logged in.", pw->pw_name);
163925986Sdanny
16401592Srgrimes#ifdef SETPROCTITLE
16411592Srgrimes		snprintf(proctitle, sizeof(proctitle),
164284842Syar			 "%s: user/%s", remotehost, pw->pw_name);
164313139Speter		setproctitle("%s", proctitle);
16441592Srgrimes#endif /* SETPROCTITLE */
16451592Srgrimes		if (logging)
16461592Srgrimes			syslog(LOG_INFO, "FTP LOGIN FROM %s as %s",
16471592Srgrimes			    remotehost, pw->pw_name);
16481592Srgrimes	}
1649140473Syar	if (logging && (guest || dochroot))
1650137985Syar		syslog(LOG_INFO, "session root changed to %s", chrootdir);
165125101Sdavidn#ifdef	LOGIN_CAP
165225101Sdavidn	login_close(lc);
165325101Sdavidn#endif
1654110036Syar	if (residue)
1655110036Syar		free(residue);
16561592Srgrimes	return;
16571592Srgrimesbad:
16581592Srgrimes	/* Forget all about it... */
165925101Sdavidn#ifdef	LOGIN_CAP
166025101Sdavidn	login_close(lc);
166125101Sdavidn#endif
1662110036Syar	if (residue)
1663110036Syar		free(residue);
16641592Srgrimes	end_login();
16651592Srgrimes}
16661592Srgrimes
16671592Srgrimesvoid
166890148Simpretrieve(char *cmd, char *name)
16691592Srgrimes{
16701592Srgrimes	FILE *fin, *dout;
16711592Srgrimes	struct stat st;
167290148Simp	int (*closefunc)(FILE *);
167336612Sjb	time_t start;
16741592Srgrimes
16751592Srgrimes	if (cmd == 0) {
16761592Srgrimes		fin = fopen(name, "r"), closefunc = fclose;
16771592Srgrimes		st.st_size = 0;
16781592Srgrimes	} else {
16791592Srgrimes		char line[BUFSIZ];
16801592Srgrimes
168131973Simp		(void) snprintf(line, sizeof(line), cmd, name), name = line;
16821592Srgrimes		fin = ftpd_popen(line, "r"), closefunc = ftpd_pclose;
16831592Srgrimes		st.st_size = -1;
16841592Srgrimes		st.st_blksize = BUFSIZ;
16851592Srgrimes	}
16861592Srgrimes	if (fin == NULL) {
16871592Srgrimes		if (errno != 0) {
16881592Srgrimes			perror_reply(550, name);
16891592Srgrimes			if (cmd == 0) {
16901592Srgrimes				LOGCMD("get", name);
16911592Srgrimes			}
16921592Srgrimes		}
16931592Srgrimes		return;
16941592Srgrimes	}
16951592Srgrimes	byte_count = -1;
1696110144Syar	if (cmd == 0) {
1697110144Syar		if (fstat(fileno(fin), &st) < 0) {
1698110144Syar			perror_reply(550, name);
1699110144Syar			goto done;
1700110144Syar		}
1701110144Syar		if (!S_ISREG(st.st_mode)) {
1702125565Syar			/*
1703125565Syar			 * Never sending a raw directory is a workaround
1704125565Syar			 * for buggy clients that will attempt to RETR
1705125565Syar			 * a directory before listing it, e.g., Mozilla.
1706125565Syar			 * Preventing a guest from getting irregular files
1707125565Syar			 * is a simple security measure.
1708125565Syar			 */
1709125565Syar			if (S_ISDIR(st.st_mode) || guest) {
1710110144Syar				reply(550, "%s: not a plain file.", name);
1711110144Syar				goto done;
1712110144Syar			}
1713110144Syar			st.st_size = -1;
1714110144Syar			/* st.st_blksize is set for all descriptor types */
1715110144Syar		}
17161592Srgrimes	}
17171592Srgrimes	if (restart_point) {
17181592Srgrimes		if (type == TYPE_A) {
17191592Srgrimes			off_t i, n;
17201592Srgrimes			int c;
17211592Srgrimes
17221592Srgrimes			n = restart_point;
17231592Srgrimes			i = 0;
17241592Srgrimes			while (i++ < n) {
17251592Srgrimes				if ((c=getc(fin)) == EOF) {
17261592Srgrimes					perror_reply(550, name);
17271592Srgrimes					goto done;
17281592Srgrimes				}
17291592Srgrimes				if (c == '\n')
17301592Srgrimes					i++;
17311592Srgrimes			}
17321592Srgrimes		} else if (lseek(fileno(fin), restart_point, L_SET) < 0) {
17331592Srgrimes			perror_reply(550, name);
17341592Srgrimes			goto done;
17351592Srgrimes		}
17361592Srgrimes	}
17371592Srgrimes	dout = dataconn(name, st.st_size, "w");
17381592Srgrimes	if (dout == NULL)
17391592Srgrimes		goto done;
17406740Sguido	time(&start);
17418240Swollman	send_data(fin, dout, st.st_blksize, st.st_size,
17428240Swollman		  restart_point == 0 && cmd == 0 && S_ISREG(st.st_mode));
1743136929Syar	if (cmd == 0 && guest && stats && byte_count > 0)
1744136929Syar		logxfer(name, byte_count, start);
17451592Srgrimes	(void) fclose(dout);
17461592Srgrimes	data = -1;
17471592Srgrimes	pdata = -1;
17481592Srgrimesdone:
17491592Srgrimes	if (cmd == 0)
17501592Srgrimes		LOGBYTES("get", name, byte_count);
17511592Srgrimes	(*closefunc)(fin);
17521592Srgrimes}
17531592Srgrimes
17541592Srgrimesvoid
175590148Simpstore(char *name, char *mode, int unique)
17561592Srgrimes{
1757101537Syar	int fd;
17581592Srgrimes	FILE *fout, *din;
175990148Simp	int (*closefunc)(FILE *);
17601592Srgrimes
1761101537Syar	if (*mode == 'a') {		/* APPE */
1762101537Syar		if (unique) {
1763101537Syar			/* Programming error */
1764101537Syar			syslog(LOG_ERR, "Internal: unique flag to APPE");
1765101537Syar			unique = 0;
1766101537Syar		}
1767101537Syar		if (guest && noguestmod) {
1768137852Syar			reply(550, "Appending to existing file denied.");
1769101537Syar			goto err;
1770101537Syar		}
1771101537Syar		restart_point = 0;	/* not affected by preceding REST */
17721592Srgrimes	}
1773101537Syar	if (unique)			/* STOU overrides REST */
1774101537Syar		restart_point = 0;
1775101537Syar	if (guest && noguestmod) {
1776101537Syar		if (restart_point) {	/* guest STOR w/REST */
1777137852Syar			reply(550, "Modifying existing file denied.");
1778101537Syar			goto err;
1779101537Syar		} else			/* treat guest STOR as STOU */
1780101537Syar			unique = 1;
1781101537Syar	}
17821592Srgrimes
17831592Srgrimes	if (restart_point)
1784101537Syar		mode = "r+";	/* so ASCII manual seek can work */
1785101537Syar	if (unique) {
1786101537Syar		if ((fd = guniquefd(name, &name)) < 0)
1787101537Syar			goto err;
1788101537Syar		fout = fdopen(fd, mode);
1789101537Syar	} else
1790101537Syar		fout = fopen(name, mode);
17911592Srgrimes	closefunc = fclose;
17921592Srgrimes	if (fout == NULL) {
17931592Srgrimes		perror_reply(553, name);
1794101537Syar		goto err;
17951592Srgrimes	}
17961592Srgrimes	byte_count = -1;
17971592Srgrimes	if (restart_point) {
17981592Srgrimes		if (type == TYPE_A) {
17991592Srgrimes			off_t i, n;
18001592Srgrimes			int c;
18011592Srgrimes
18021592Srgrimes			n = restart_point;
18031592Srgrimes			i = 0;
18041592Srgrimes			while (i++ < n) {
18051592Srgrimes				if ((c=getc(fout)) == EOF) {
18061592Srgrimes					perror_reply(550, name);
18071592Srgrimes					goto done;
18081592Srgrimes				}
18091592Srgrimes				if (c == '\n')
18101592Srgrimes					i++;
18111592Srgrimes			}
18121592Srgrimes			/*
18131592Srgrimes			 * We must do this seek to "current" position
18141592Srgrimes			 * because we are changing from reading to
18151592Srgrimes			 * writing.
18161592Srgrimes			 */
1817132930Syar			if (fseeko(fout, 0, SEEK_CUR) < 0) {
18181592Srgrimes				perror_reply(550, name);
18191592Srgrimes				goto done;
18201592Srgrimes			}
18211592Srgrimes		} else if (lseek(fileno(fout), restart_point, L_SET) < 0) {
18221592Srgrimes			perror_reply(550, name);
18231592Srgrimes			goto done;
18241592Srgrimes		}
18251592Srgrimes	}
1826132930Syar	din = dataconn(name, -1, "r");
18271592Srgrimes	if (din == NULL)
18281592Srgrimes		goto done;
18291592Srgrimes	if (receive_data(din, fout) == 0) {
18301592Srgrimes		if (unique)
18311592Srgrimes			reply(226, "Transfer complete (unique file name:%s).",
18321592Srgrimes			    name);
18331592Srgrimes		else
18341592Srgrimes			reply(226, "Transfer complete.");
18351592Srgrimes	}
18361592Srgrimes	(void) fclose(din);
18371592Srgrimes	data = -1;
18381592Srgrimes	pdata = -1;
18391592Srgrimesdone:
1840102566Syar	LOGBYTES(*mode == 'a' ? "append" : "put", name, byte_count);
18411592Srgrimes	(*closefunc)(fout);
1842101537Syar	return;
1843101537Syarerr:
1844101537Syar	LOGCMD(*mode == 'a' ? "append" : "put" , name);
1845101537Syar	return;
18461592Srgrimes}
18471592Srgrimes
18481592Srgrimesstatic FILE *
184990148Simpgetdatasock(char *mode)
18501592Srgrimes{
18511592Srgrimes	int on = 1, s, t, tries;
18521592Srgrimes
18531592Srgrimes	if (data >= 0)
18541592Srgrimes		return (fdopen(data, mode));
185556668Sshin
185656668Sshin	s = socket(data_dest.su_family, SOCK_STREAM, 0);
18571592Srgrimes	if (s < 0)
18581592Srgrimes		goto bad;
1859100612Syar	if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
1860100609Syar		syslog(LOG_WARNING, "data setsockopt (SO_REUSEADDR): %m");
18611592Srgrimes	/* anchor socket to avoid multi-homing problems */
186256668Sshin	data_source = ctrl_addr;
1863109742Syar	data_source.su_port = htons(dataport);
1864132893Syar	(void) seteuid(0);
18651592Srgrimes	for (tries = 1; ; tries++) {
1866132891Syar		/*
1867132891Syar		 * We should loop here since it's possible that
1868132891Syar		 * another ftpd instance has passed this point and is
1869132891Syar		 * trying to open a data connection in active mode now.
1870132891Syar		 * Until the other connection is opened, we'll be getting
1871132891Syar		 * EADDRINUSE because no SOCK_STREAM sockets in the system
1872132891Syar		 * can share both local and remote addresses, localIP:20
1873132891Syar		 * and *:* in this case.
1874132891Syar		 */
18751592Srgrimes		if (bind(s, (struct sockaddr *)&data_source,
187656668Sshin		    data_source.su_len) >= 0)
18771592Srgrimes			break;
18781592Srgrimes		if (errno != EADDRINUSE || tries > 10)
18791592Srgrimes			goto bad;
18801592Srgrimes		sleep(tries);
18811592Srgrimes	}
1882132893Syar	(void) seteuid(pw->pw_uid);
18831592Srgrimes#ifdef IP_TOS
188456668Sshin	if (data_source.su_family == AF_INET)
188556668Sshin      {
18861592Srgrimes	on = IPTOS_THROUGHPUT;
1887100612Syar	if (setsockopt(s, IPPROTO_IP, IP_TOS, &on, sizeof(int)) < 0)
1888100609Syar		syslog(LOG_WARNING, "data setsockopt (IP_TOS): %m");
188956668Sshin      }
18901592Srgrimes#endif
18918240Swollman#ifdef TCP_NOPUSH
18928240Swollman	/*
18938240Swollman	 * Turn off push flag to keep sender TCP from sending short packets
1894166598Syar	 * at the boundaries of each write().
18958240Swollman	 */
18968240Swollman	on = 1;
1897100612Syar	if (setsockopt(s, IPPROTO_TCP, TCP_NOPUSH, &on, sizeof on) < 0)
1898100609Syar		syslog(LOG_WARNING, "data setsockopt (TCP_NOPUSH): %m");
18998240Swollman#endif
19001592Srgrimes	return (fdopen(s, mode));
19011592Srgrimesbad:
19021592Srgrimes	/* Return the real value of errno (close may change it) */
19031592Srgrimes	t = errno;
1904132893Syar	(void) seteuid(pw->pw_uid);
19051592Srgrimes	(void) close(s);
19061592Srgrimes	errno = t;
19071592Srgrimes	return (NULL);
19081592Srgrimes}
19091592Srgrimes
19101592Srgrimesstatic FILE *
191190148Simpdataconn(char *name, off_t size, char *mode)
19121592Srgrimes{
19131592Srgrimes	char sizebuf[32];
19141592Srgrimes	FILE *file;
1915109611Scjc	int retry = 0, tos, conerrno;
19161592Srgrimes
19171592Srgrimes	file_size = size;
19181592Srgrimes	byte_count = 0;
1919132930Syar	if (size != -1)
1920132929Syar		(void) snprintf(sizebuf, sizeof(sizebuf),
1921132929Syar				" (%jd bytes)", (intmax_t)size);
19221592Srgrimes	else
192331973Simp		*sizebuf = '\0';
19241592Srgrimes	if (pdata >= 0) {
192556668Sshin		union sockunion from;
1926141918Sstefanf		socklen_t fromlen = ctrl_addr.su_len;
1927141918Sstefanf		int flags, s;
192812532Sguido		struct timeval timeout;
192912532Sguido		fd_set set;
19301592Srgrimes
193112532Sguido		FD_ZERO(&set);
193212532Sguido		FD_SET(pdata, &set);
193312532Sguido
193412532Sguido		timeout.tv_usec = 0;
193512532Sguido		timeout.tv_sec = 120;
193612532Sguido
193786628Syar		/*
193886628Syar		 * Granted a socket is in the blocking I/O mode,
193986628Syar		 * accept() will block after a successful select()
194086628Syar		 * if the selected connection dies in between.
194186628Syar		 * Therefore set the non-blocking I/O flag here.
194286628Syar		 */
194386628Syar		if ((flags = fcntl(pdata, F_GETFL, 0)) == -1 ||
194486628Syar		    fcntl(pdata, F_SETFL, flags | O_NONBLOCK) == -1)
194586628Syar			goto pdata_err;
1946132931Syar		if (select(pdata+1, &set, NULL, NULL, &timeout) <= 0 ||
194786628Syar		    (s = accept(pdata, (struct sockaddr *) &from, &fromlen)) < 0)
194886628Syar			goto pdata_err;
19491592Srgrimes		(void) close(pdata);
19501592Srgrimes		pdata = s;
195186628Syar		/*
1952101809Syar		 * Unset the inherited non-blocking I/O flag
1953101809Syar		 * on the child socket so stdio can work on it.
195486628Syar		 */
195586628Syar		if ((flags = fcntl(pdata, F_GETFL, 0)) == -1 ||
195686628Syar		    fcntl(pdata, F_SETFL, flags & ~O_NONBLOCK) == -1)
195786628Syar			goto pdata_err;
19581592Srgrimes#ifdef IP_TOS
195956668Sshin		if (from.su_family == AF_INET)
196056668Sshin	      {
196117435Spst		tos = IPTOS_THROUGHPUT;
1962100612Syar		if (setsockopt(s, IPPROTO_IP, IP_TOS, &tos, sizeof(int)) < 0)
1963100609Syar			syslog(LOG_WARNING, "pdata setsockopt (IP_TOS): %m");
196456668Sshin	      }
19651592Srgrimes#endif
19661592Srgrimes		reply(150, "Opening %s mode data connection for '%s'%s.",
19671592Srgrimes		     type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf);
19681592Srgrimes		return (fdopen(pdata, mode));
196986628Syarpdata_err:
197086628Syar		reply(425, "Can't open data connection.");
197186628Syar		(void) close(pdata);
197286628Syar		pdata = -1;
197386628Syar		return (NULL);
19741592Srgrimes	}
19751592Srgrimes	if (data >= 0) {
19761592Srgrimes		reply(125, "Using existing data connection for '%s'%s.",
19771592Srgrimes		    name, sizebuf);
19781592Srgrimes		usedefault = 1;
19791592Srgrimes		return (fdopen(data, mode));
19801592Srgrimes	}
19811592Srgrimes	if (usedefault)
19821592Srgrimes		data_dest = his_addr;
19831592Srgrimes	usedefault = 1;
1984109611Scjc	do {
1985109611Scjc		file = getdatasock(mode);
1986109611Scjc		if (file == NULL) {
1987137983Syar			char hostbuf[NI_MAXHOST], portbuf[NI_MAXSERV];
1988137983Syar
1989137983Syar			if (getnameinfo((struct sockaddr *)&data_source,
1990137983Syar				data_source.su_len,
1991137983Syar				hostbuf, sizeof(hostbuf) - 1,
1992137983Syar				portbuf, sizeof(portbuf) - 1,
1993137983Syar				NI_NUMERICHOST|NI_NUMERICSERV))
1994137983Syar					*hostbuf = *portbuf = 0;
1995137983Syar			hostbuf[sizeof(hostbuf) - 1] = 0;
1996137983Syar			portbuf[sizeof(portbuf) - 1] = 0;
1997109611Scjc			reply(425, "Can't create data socket (%s,%s): %s.",
1998109611Scjc				hostbuf, portbuf, strerror(errno));
1999109611Scjc			return (NULL);
2000109611Scjc		}
2001109611Scjc		data = fileno(file);
2002109611Scjc		conerrno = 0;
2003109611Scjc		if (connect(data, (struct sockaddr *)&data_dest,
2004109611Scjc		    data_dest.su_len) == 0)
2005109611Scjc			break;
2006109611Scjc		conerrno = errno;
2007109611Scjc		(void) fclose(file);
2008109611Scjc		data = -1;
2009109611Scjc		if (conerrno == EADDRINUSE) {
2010137659Syar			sleep(swaitint);
20111592Srgrimes			retry += swaitint;
2012109611Scjc		} else {
2013109611Scjc			break;
20141592Srgrimes		}
2015109611Scjc	} while (retry <= swaitmax);
2016109611Scjc	if (conerrno != 0) {
2017137850Syar		reply(425, "Can't build data connection: %s.",
2018137850Syar			   strerror(conerrno));
20191592Srgrimes		return (NULL);
20201592Srgrimes	}
20211592Srgrimes	reply(150, "Opening %s mode data connection for '%s'%s.",
20221592Srgrimes	     type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf);
20231592Srgrimes	return (file);
20241592Srgrimes}
20251592Srgrimes
20261592Srgrimes/*
2027140472Syar * A helper macro to avoid code duplication
2028140472Syar * in send_data() and receive_data().
2029140472Syar *
2030140472Syar * XXX We have to block SIGURG during putc() because BSD stdio
2031140472Syar * is unable to restart interrupted write operations and hence
2032140472Syar * the entire buffer contents will be lost as soon as a write()
2033140472Syar * call indicates EINTR to stdio.
2034140472Syar */
2035140472Syar#define FTPD_PUTC(ch, file, label)					\
2036140472Syar	do {								\
2037140472Syar		int ret;						\
2038140472Syar									\
2039140472Syar		do {							\
2040140472Syar			START_UNSAFE;					\
2041140472Syar			ret = putc((ch), (file));			\
2042140472Syar			END_UNSAFE;					\
2043140472Syar			CHECKOOB(return (-1))				\
2044140472Syar			else if (ferror(file))				\
2045140472Syar				goto label;				\
2046140472Syar			clearerr(file);					\
2047140472Syar		} while (ret == EOF);					\
2048140472Syar	} while (0)
2049140472Syar
2050140472Syar/*
20511592Srgrimes * Tranfer the contents of "instr" to "outstr" peer using the appropriate
20528240Swollman * encapsulation of the data subject to Mode, Structure, and Type.
20531592Srgrimes *
20541592Srgrimes * NB: Form isn't handled.
20551592Srgrimes */
205689935Syarstatic int
2057137660Syarsend_data(FILE *instr, FILE *outstr, size_t blksize, off_t filesize, int isreg)
20581592Srgrimes{
2059122751Syar	int c, cp, filefd, netfd;
206070205Sdan	char *buf;
20611592Srgrimes
2062140472Syar	STARTXFER;
2063140472Syar
20641592Srgrimes	switch (type) {
20651592Srgrimes
20661592Srgrimes	case TYPE_A:
2067140472Syar		cp = EOF;
2068140472Syar		for (;;) {
2069140472Syar			c = getc(instr);
2070140472Syar			CHECKOOB(return (-1))
2071140472Syar			else if (c == EOF && ferror(instr))
2072140472Syar				goto file_err;
2073140472Syar			if (c == EOF) {
2074140472Syar				if (ferror(instr)) {	/* resume after OOB */
2075140472Syar					clearerr(instr);
2076140472Syar					continue;
2077140472Syar				}
2078140472Syar				if (feof(instr))	/* EOF */
2079140472Syar					break;
2080140472Syar				syslog(LOG_ERR, "Internal: impossible condition"
2081140472Syar						" on file after getc()");
2082140472Syar				goto file_err;
2083140472Syar			}
2084122751Syar			if (c == '\n' && cp != '\r') {
2085140472Syar				FTPD_PUTC('\r', outstr, data_err);
2086140472Syar				byte_count++;
20871592Srgrimes			}
2088140472Syar			FTPD_PUTC(c, outstr, data_err);
2089140472Syar			byte_count++;
2090122751Syar			cp = c;
20911592Srgrimes		}
2092140472Syar#ifdef notyet	/* BSD stdio isn't ready for that */
2093140472Syar		while (fflush(outstr) == EOF) {
2094140472Syar			CHECKOOB(return (-1))
2095140472Syar			else
2096140472Syar				goto data_err;
2097140472Syar			clearerr(outstr);
2098140472Syar		}
2099140472Syar		ENDXFER;
2100140472Syar#else
2101140472Syar		ENDXFER;
2102140472Syar		if (fflush(outstr) == EOF)
21031592Srgrimes			goto data_err;
2104140472Syar#endif
21051592Srgrimes		reply(226, "Transfer complete.");
210689935Syar		return (0);
21071592Srgrimes
21081592Srgrimes	case TYPE_I:
21091592Srgrimes	case TYPE_L:
21108240Swollman		/*
21118240Swollman		 * isreg is only set if we are not doing restart and we
21128240Swollman		 * are sending a regular file
21138240Swollman		 */
21148240Swollman		netfd = fileno(outstr);
21158870Srgrimes		filefd = fileno(instr);
21168240Swollman
211770205Sdan		if (isreg) {
2118136554Syar			char *msg = "Transfer complete.";
2119140472Syar			off_t cnt, offset;
212070205Sdan			int err;
212170205Sdan
2122136555Syar			cnt = offset = 0;
212370205Sdan
2124136555Syar			while (filesize > 0) {
212590604Smaxim				err = sendfile(filefd, netfd, offset, 0,
2126137811Syar					       NULL, &cnt, 0);
212799212Smaxim				/*
212899212Smaxim				 * Calculate byte_count before OOB processing.
212999212Smaxim				 * It can be used in myoob() later.
213099212Smaxim				 */
213199212Smaxim				byte_count += cnt;
213270205Sdan				offset += cnt;
213390604Smaxim				filesize -= cnt;
2134140472Syar				CHECKOOB(return (-1))
2135140472Syar				else if (err == -1) {
2136140472Syar					if (errno != EINTR &&
2137140472Syar					    cnt == 0 && offset == 0)
213870205Sdan						goto oldway;
213970205Sdan					goto data_err;
214070205Sdan				}
2141140472Syar				if (err == -1)	/* resume after OOB */
2142140472Syar					continue;
2143136554Syar				/*
2144136554Syar				 * We hit the EOF prematurely.
2145136554Syar				 * Perhaps the file was externally truncated.
2146136554Syar				 */
2147136554Syar				if (cnt == 0) {
2148136554Syar					msg = "Transfer finished due to "
2149136554Syar					      "premature end of file.";
2150136554Syar					break;
2151136554Syar				}
215270205Sdan			}
2153140472Syar			ENDXFER;
2154216945Semaste			reply(226, "%s", msg);
215589935Syar			return (0);
21568240Swollman		}
21578240Swollman
21588240Swollmanoldway:
2159137659Syar		if ((buf = malloc(blksize)) == NULL) {
2160140472Syar			ENDXFER;
2161137852Syar			reply(451, "Ran out of memory.");
216289935Syar			return (-1);
21631592Srgrimes		}
21648870Srgrimes
2165140472Syar		for (;;) {
2166140472Syar			int cnt, len;
2167140472Syar			char *bp;
2168140472Syar
2169140472Syar			cnt = read(filefd, buf, blksize);
2170140472Syar			CHECKOOB(free(buf); return (-1))
2171140472Syar			else if (cnt < 0) {
2172140472Syar				free(buf);
21731592Srgrimes				goto file_err;
2174140472Syar			}
2175140472Syar			if (cnt < 0)	/* resume after OOB */
2176140472Syar				continue;
2177140472Syar			if (cnt == 0)	/* EOF */
2178140472Syar				break;
2179140472Syar			for (len = cnt, bp = buf; len > 0;) {
2180140472Syar				cnt = write(netfd, bp, len);
2181140472Syar				CHECKOOB(free(buf); return (-1))
2182140472Syar				else if (cnt < 0) {
2183140472Syar					free(buf);
2184140472Syar					goto data_err;
2185140472Syar				}
2186140472Syar				if (cnt <= 0)
2187140472Syar					continue;
2188140472Syar				len -= cnt;
2189140472Syar				bp += cnt;
2190140472Syar				byte_count += cnt;
2191140472Syar			}
21921592Srgrimes		}
2193140472Syar		ENDXFER;
2194140472Syar		free(buf);
21951592Srgrimes		reply(226, "Transfer complete.");
219689935Syar		return (0);
21971592Srgrimes	default:
2198140472Syar		ENDXFER;
2199137852Syar		reply(550, "Unimplemented TYPE %d in send_data.", type);
220089935Syar		return (-1);
22011592Srgrimes	}
22021592Srgrimes
22031592Srgrimesdata_err:
2204140472Syar	ENDXFER;
22051592Srgrimes	perror_reply(426, "Data connection");
220689935Syar	return (-1);
22071592Srgrimes
22081592Srgrimesfile_err:
2209140472Syar	ENDXFER;
22101592Srgrimes	perror_reply(551, "Error on input file");
221189935Syar	return (-1);
22121592Srgrimes}
22131592Srgrimes
22141592Srgrimes/*
22151592Srgrimes * Transfer data from peer to "outstr" using the appropriate encapulation of
22161592Srgrimes * the data subject to Mode, Structure, and Type.
22171592Srgrimes *
22181592Srgrimes * N.B.: Form isn't handled.
22191592Srgrimes */
22201592Srgrimesstatic int
222190148Simpreceive_data(FILE *instr, FILE *outstr)
22221592Srgrimes{
2223140472Syar	int c, cp;
2224140472Syar	int bare_lfs = 0;
22251592Srgrimes
2226140472Syar	STARTXFER;
222717433Spst
22281592Srgrimes	switch (type) {
22291592Srgrimes
22301592Srgrimes	case TYPE_I:
22311592Srgrimes	case TYPE_L:
2232140472Syar		for (;;) {
2233140472Syar			int cnt, len;
2234140472Syar			char *bp;
2235140472Syar			char buf[BUFSIZ];
2236140472Syar
2237140472Syar			cnt = read(fileno(instr), buf, sizeof(buf));
2238140472Syar			CHECKOOB(return (-1))
2239140472Syar			else if (cnt < 0)
2240140472Syar				goto data_err;
2241140472Syar			if (cnt < 0)	/* resume after OOB */
2242140472Syar				continue;
2243140472Syar			if (cnt == 0)	/* EOF */
2244140472Syar				break;
2245140472Syar			for (len = cnt, bp = buf; len > 0;) {
2246140472Syar				cnt = write(fileno(outstr), bp, len);
2247140472Syar				CHECKOOB(return (-1))
2248140472Syar				else if (cnt < 0)
2249140472Syar					goto file_err;
2250140472Syar				if (cnt <= 0)
2251140472Syar					continue;
2252140472Syar				len -= cnt;
2253140472Syar				bp += cnt;
2254140472Syar				byte_count += cnt;
2255140472Syar			}
22561592Srgrimes		}
2257140472Syar		ENDXFER;
22581592Srgrimes		return (0);
22591592Srgrimes
22601592Srgrimes	case TYPE_E:
2261140472Syar		ENDXFER;
22621592Srgrimes		reply(553, "TYPE E not implemented.");
22631592Srgrimes		return (-1);
22641592Srgrimes
22651592Srgrimes	case TYPE_A:
2266140472Syar		cp = EOF;
2267140472Syar		for (;;) {
2268140472Syar			c = getc(instr);
2269140472Syar			CHECKOOB(return (-1))
2270140472Syar			else if (c == EOF && ferror(instr))
2271140472Syar				goto data_err;
2272140472Syar			if (c == EOF && ferror(instr)) { /* resume after OOB */
2273140472Syar				clearerr(instr);
2274140472Syar				continue;
2275140472Syar			}
2276140472Syar
2277140472Syar			if (cp == '\r') {
2278140472Syar				if (c != '\n')
2279140472Syar					FTPD_PUTC('\r', outstr, file_err);
2280140472Syar			} else
2281140472Syar				if (c == '\n')
2282140472Syar					bare_lfs++;
2283140472Syar			if (c == '\r') {
2284140472Syar				byte_count++;
2285140472Syar				cp = c;
2286140472Syar				continue;
2287140472Syar			}
2288140472Syar
2289140472Syar			/* Check for EOF here in order not to lose last \r. */
2290140472Syar			if (c == EOF) {
2291140472Syar				if (feof(instr))	/* EOF */
2292140472Syar					break;
2293140472Syar				syslog(LOG_ERR, "Internal: impossible condition"
2294140472Syar						" on data stream after getc()");
2295140472Syar				goto data_err;
2296140472Syar			}
2297140472Syar
22981592Srgrimes			byte_count++;
2299140472Syar			FTPD_PUTC(c, outstr, file_err);
2300140472Syar			cp = c;
23011592Srgrimes		}
2302140472Syar#ifdef notyet	/* BSD stdio isn't ready for that */
2303140472Syar		while (fflush(outstr) == EOF) {
2304140472Syar			CHECKOOB(return (-1))
2305140472Syar			else
2306140472Syar				goto file_err;
2307140472Syar			clearerr(outstr);
2308140472Syar		}
2309140472Syar		ENDXFER;
2310140472Syar#else
2311140472Syar		ENDXFER;
2312140472Syar		if (fflush(outstr) == EOF)
23131592Srgrimes			goto file_err;
2314140472Syar#endif
23151592Srgrimes		if (bare_lfs) {
23161592Srgrimes			lreply(226,
2317137852Syar		"WARNING! %d bare linefeeds received in ASCII mode.",
23181592Srgrimes			    bare_lfs);
23191592Srgrimes		(void)printf("   File may not have transferred correctly.\r\n");
23201592Srgrimes		}
23211592Srgrimes		return (0);
23221592Srgrimes	default:
2323140472Syar		ENDXFER;
2324137852Syar		reply(550, "Unimplemented TYPE %d in receive_data.", type);
23251592Srgrimes		return (-1);
23261592Srgrimes	}
23271592Srgrimes
23281592Srgrimesdata_err:
2329140472Syar	ENDXFER;
2330137852Syar	perror_reply(426, "Data connection");
23311592Srgrimes	return (-1);
23321592Srgrimes
23331592Srgrimesfile_err:
2334140472Syar	ENDXFER;
2335137852Syar	perror_reply(452, "Error writing to file");
23361592Srgrimes	return (-1);
23371592Srgrimes}
23381592Srgrimes
23391592Srgrimesvoid
234090148Simpstatfilecmd(char *filename)
23411592Srgrimes{
23421592Srgrimes	FILE *fin;
2343109382Syar	int atstart;
2344137728Syar	int c, code;
23451592Srgrimes	char line[LINE_MAX];
2346137728Syar	struct stat st;
23471592Srgrimes
2348137728Syar	code = lstat(filename, &st) == 0 && S_ISDIR(st.st_mode) ? 212 : 213;
234925165Sdavidn	(void)snprintf(line, sizeof(line), _PATH_LS " -lgA %s", filename);
23501592Srgrimes	fin = ftpd_popen(line, "r");
2351216943Semaste	if (fin == NULL) {
2352216943Semaste		perror_reply(551, filename);
2353216943Semaste		return;
2354216943Semaste	}
2355137729Syar	lreply(code, "Status of %s:", filename);
2356109382Syar	atstart = 1;
23571592Srgrimes	while ((c = getc(fin)) != EOF) {
23581592Srgrimes		if (c == '\n') {
23591592Srgrimes			if (ferror(stdout)){
2360137852Syar				perror_reply(421, "Control connection");
23611592Srgrimes				(void) ftpd_pclose(fin);
23621592Srgrimes				dologout(1);
23631592Srgrimes				/* NOTREACHED */
23641592Srgrimes			}
23651592Srgrimes			if (ferror(fin)) {
23661592Srgrimes				perror_reply(551, filename);
23671592Srgrimes				(void) ftpd_pclose(fin);
23681592Srgrimes				return;
23691592Srgrimes			}
23701592Srgrimes			(void) putc('\r', stdout);
23711592Srgrimes		}
2372109382Syar		/*
2373109382Syar		 * RFC 959 says neutral text should be prepended before
2374109382Syar		 * a leading 3-digit number followed by whitespace, but
2375109382Syar		 * many ftp clients can be confused by any leading digits,
2376109382Syar		 * as a matter of fact.
2377109382Syar		 */
2378109382Syar		if (atstart && isdigit(c))
2379109382Syar			(void) putc(' ', stdout);
23801592Srgrimes		(void) putc(c, stdout);
2381109382Syar		atstart = (c == '\n');
23821592Srgrimes	}
23831592Srgrimes	(void) ftpd_pclose(fin);
2384137852Syar	reply(code, "End of status.");
23851592Srgrimes}
23861592Srgrimes
23871592Srgrimesvoid
238890148Simpstatcmd(void)
23891592Srgrimes{
239056668Sshin	union sockunion *su;
23911592Srgrimes	u_char *a, *p;
239299255Sume	char hname[NI_MAXHOST];
239356668Sshin	int ispassive;
23941592Srgrimes
2395110037Syar	if (hostinfo) {
2396110037Syar		lreply(211, "%s FTP server status:", hostname);
2397110037Syar		printf("     %s\r\n", version);
2398110037Syar	} else
2399110037Syar		lreply(211, "FTP server status:");
24001592Srgrimes	printf("     Connected to %s", remotehost);
240156668Sshin	if (!getnameinfo((struct sockaddr *)&his_addr, his_addr.su_len,
240299255Sume			 hname, sizeof(hname) - 1, NULL, 0, NI_NUMERICHOST)) {
2403137983Syar		hname[sizeof(hname) - 1] = 0;
240456668Sshin		if (strcmp(hname, remotehost) != 0)
240556668Sshin			printf(" (%s)", hname);
240656668Sshin	}
24071592Srgrimes	printf("\r\n");
24081592Srgrimes	if (logged_in) {
24091592Srgrimes		if (guest)
24101592Srgrimes			printf("     Logged in anonymously\r\n");
24111592Srgrimes		else
24121592Srgrimes			printf("     Logged in as %s\r\n", pw->pw_name);
24131592Srgrimes	} else if (askpasswd)
24141592Srgrimes		printf("     Waiting for password\r\n");
24151592Srgrimes	else
24161592Srgrimes		printf("     Waiting for user name\r\n");
24171592Srgrimes	printf("     TYPE: %s", typenames[type]);
24181592Srgrimes	if (type == TYPE_A || type == TYPE_E)
24191592Srgrimes		printf(", FORM: %s", formnames[form]);
24201592Srgrimes	if (type == TYPE_L)
2421103949Smike#if CHAR_BIT == 8
2422103949Smike		printf(" %d", CHAR_BIT);
24231592Srgrimes#else
24241592Srgrimes		printf(" %d", bytesize);	/* need definition! */
24251592Srgrimes#endif
24261592Srgrimes	printf("; STRUcture: %s; transfer MODE: %s\r\n",
24271592Srgrimes	    strunames[stru], modenames[mode]);
24281592Srgrimes	if (data != -1)
24291592Srgrimes		printf("     Data connection open\r\n");
24301592Srgrimes	else if (pdata != -1) {
243156668Sshin		ispassive = 1;
243256668Sshin		su = &pasv_addr;
24331592Srgrimes		goto printaddr;
24341592Srgrimes	} else if (usedefault == 0) {
243556668Sshin		ispassive = 0;
243656668Sshin		su = &data_dest;
24371592Srgrimesprintaddr:
24381592Srgrimes#define UC(b) (((int) b) & 0xff)
243956668Sshin		if (epsvall) {
244056668Sshin			printf("     EPSV only mode (EPSV ALL)\r\n");
244156668Sshin			goto epsvonly;
244256668Sshin		}
244356668Sshin
244456668Sshin		/* PORT/PASV */
244556668Sshin		if (su->su_family == AF_INET) {
244656668Sshin			a = (u_char *) &su->su_sin.sin_addr;
244756668Sshin			p = (u_char *) &su->su_sin.sin_port;
244856668Sshin			printf("     %s (%d,%d,%d,%d,%d,%d)\r\n",
244956668Sshin				ispassive ? "PASV" : "PORT",
245056668Sshin				UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]),
245156668Sshin				UC(p[0]), UC(p[1]));
245256668Sshin		}
245356668Sshin
245456668Sshin		/* LPRT/LPSV */
245556668Sshin	    {
245656668Sshin		int alen, af, i;
245756668Sshin
245856668Sshin		switch (su->su_family) {
245956668Sshin		case AF_INET:
246056668Sshin			a = (u_char *) &su->su_sin.sin_addr;
246156668Sshin			p = (u_char *) &su->su_sin.sin_port;
246256668Sshin			alen = sizeof(su->su_sin.sin_addr);
246356668Sshin			af = 4;
246456668Sshin			break;
246556668Sshin		case AF_INET6:
246656668Sshin			a = (u_char *) &su->su_sin6.sin6_addr;
246756668Sshin			p = (u_char *) &su->su_sin6.sin6_port;
246856668Sshin			alen = sizeof(su->su_sin6.sin6_addr);
246956668Sshin			af = 6;
247056668Sshin			break;
247156668Sshin		default:
247256668Sshin			af = 0;
247356668Sshin			break;
247456668Sshin		}
247556668Sshin		if (af) {
247656668Sshin			printf("     %s (%d,%d,", ispassive ? "LPSV" : "LPRT",
247756668Sshin				af, alen);
247856668Sshin			for (i = 0; i < alen; i++)
247956668Sshin				printf("%d,", UC(a[i]));
248056668Sshin			printf("%d,%d,%d)\r\n", 2, UC(p[0]), UC(p[1]));
248156668Sshin		}
248256668Sshin	    }
248356668Sshin
248456668Sshinepsvonly:;
248556668Sshin		/* EPRT/EPSV */
248656668Sshin	    {
248756668Sshin		int af;
248856668Sshin
248956668Sshin		switch (su->su_family) {
249056668Sshin		case AF_INET:
249156668Sshin			af = 1;
249256668Sshin			break;
249356668Sshin		case AF_INET6:
249456668Sshin			af = 2;
249556668Sshin			break;
249656668Sshin		default:
249756668Sshin			af = 0;
249856668Sshin			break;
249956668Sshin		}
250056668Sshin		if (af) {
250199255Sume			union sockunion tmp;
250299255Sume
250399255Sume			tmp = *su;
250499255Sume			if (tmp.su_family == AF_INET6)
250599255Sume				tmp.su_sin6.sin6_scope_id = 0;
250699255Sume			if (!getnameinfo((struct sockaddr *)&tmp, tmp.su_len,
250756668Sshin					hname, sizeof(hname) - 1, NULL, 0,
250856668Sshin					NI_NUMERICHOST)) {
2509137983Syar				hname[sizeof(hname) - 1] = 0;
251056668Sshin				printf("     %s |%d|%s|%d|\r\n",
251156668Sshin					ispassive ? "EPSV" : "EPRT",
251299255Sume					af, hname, htons(tmp.su_port));
251356668Sshin			}
251456668Sshin		}
251556668Sshin	    }
25161592Srgrimes#undef UC
25171592Srgrimes	} else
25181592Srgrimes		printf("     No data connection\r\n");
2519137852Syar	reply(211, "End of status.");
25201592Srgrimes}
25211592Srgrimes
25221592Srgrimesvoid
252390148Simpfatalerror(char *s)
25241592Srgrimes{
25251592Srgrimes
2526137849Syar	reply(451, "Error in server: %s", s);
25271592Srgrimes	reply(221, "Closing connection due to server error.");
25281592Srgrimes	dologout(0);
25291592Srgrimes	/* NOTREACHED */
25301592Srgrimes}
25311592Srgrimes
25321592Srgrimesvoid
25331592Srgrimesreply(int n, const char *fmt, ...)
25341592Srgrimes{
25351592Srgrimes	va_list ap;
253690148Simp
2537129170Stjr	(void)printf("%d ", n);
25381592Srgrimes	va_start(ap, fmt);
25391592Srgrimes	(void)vprintf(fmt, ap);
2540129170Stjr	va_end(ap);
25411592Srgrimes	(void)printf("\r\n");
25421592Srgrimes	(void)fflush(stdout);
254376096Smarkm	if (ftpdebug) {
25441592Srgrimes		syslog(LOG_DEBUG, "<--- %d ", n);
2545129170Stjr		va_start(ap, fmt);
25461592Srgrimes		vsyslog(LOG_DEBUG, fmt, ap);
2547129170Stjr		va_end(ap);
25481592Srgrimes	}
25491592Srgrimes}
25501592Srgrimes
25511592Srgrimesvoid
25521592Srgrimeslreply(int n, const char *fmt, ...)
25531592Srgrimes{
25541592Srgrimes	va_list ap;
255590148Simp
2556129170Stjr	(void)printf("%d- ", n);
25571592Srgrimes	va_start(ap, fmt);
25581592Srgrimes	(void)vprintf(fmt, ap);
2559129170Stjr	va_end(ap);
25601592Srgrimes	(void)printf("\r\n");
25611592Srgrimes	(void)fflush(stdout);
256276096Smarkm	if (ftpdebug) {
25631592Srgrimes		syslog(LOG_DEBUG, "<--- %d- ", n);
2564129170Stjr		va_start(ap, fmt);
25651592Srgrimes		vsyslog(LOG_DEBUG, fmt, ap);
2566129170Stjr		va_end(ap);
25671592Srgrimes	}
25681592Srgrimes}
25691592Srgrimes
25701592Srgrimesstatic void
257190148Simpack(char *s)
25721592Srgrimes{
25731592Srgrimes
25741592Srgrimes	reply(250, "%s command successful.", s);
25751592Srgrimes}
25761592Srgrimes
25771592Srgrimesvoid
257890148Simpnack(char *s)
25791592Srgrimes{
25801592Srgrimes
25811592Srgrimes	reply(502, "%s command not implemented.", s);
25821592Srgrimes}
25831592Srgrimes
25841592Srgrimes/* ARGSUSED */
25851592Srgrimesvoid
258690148Simpyyerror(char *s)
25871592Srgrimes{
25881592Srgrimes	char *cp;
25891592Srgrimes
259017478Smarkm	if ((cp = strchr(cbuf,'\n')))
25911592Srgrimes		*cp = '\0';
2592137852Syar	reply(500, "%s: command not understood.", cbuf);
25931592Srgrimes}
25941592Srgrimes
25951592Srgrimesvoid
259690148Simpdelete(char *name)
25971592Srgrimes{
25981592Srgrimes	struct stat st;
25991592Srgrimes
26001592Srgrimes	LOGCMD("delete", name);
2601100439Syar	if (lstat(name, &st) < 0) {
26021592Srgrimes		perror_reply(550, name);
26031592Srgrimes		return;
26041592Srgrimes	}
2605137847Syar	if (S_ISDIR(st.st_mode)) {
26061592Srgrimes		if (rmdir(name) < 0) {
26071592Srgrimes			perror_reply(550, name);
26081592Srgrimes			return;
26091592Srgrimes		}
26101592Srgrimes		goto done;
26111592Srgrimes	}
2612125568Syar	if (guest && noguestmod) {
2613137852Syar		reply(550, "Operation not permitted.");
2614125568Syar		return;
2615125568Syar	}
2616125568Syar	if (unlink(name) < 0) {
26171592Srgrimes		perror_reply(550, name);
26181592Srgrimes		return;
26191592Srgrimes	}
26201592Srgrimesdone:
26211592Srgrimes	ack("DELE");
26221592Srgrimes}
26231592Srgrimes
26241592Srgrimesvoid
262590148Simpcwd(char *path)
26261592Srgrimes{
26271592Srgrimes
26281592Srgrimes	if (chdir(path) < 0)
26291592Srgrimes		perror_reply(550, path);
26301592Srgrimes	else
26311592Srgrimes		ack("CWD");
26321592Srgrimes}
26331592Srgrimes
26341592Srgrimesvoid
263590148Simpmakedir(char *name)
26361592Srgrimes{
2637100878Syar	char *s;
26381592Srgrimes
26391592Srgrimes	LOGCMD("mkdir", name);
264099195Smdodd	if (guest && noguestmkd)
2641137853Syar		reply(550, "Operation not permitted.");
264299195Smdodd	else if (mkdir(name, 0777) < 0)
26431592Srgrimes		perror_reply(550, name);
2644100878Syar	else {
2645100878Syar		if ((s = doublequote(name)) == NULL)
2646100878Syar			fatalerror("Ran out of memory.");
2647100878Syar		reply(257, "\"%s\" directory created.", s);
2648100878Syar		free(s);
2649100878Syar	}
26501592Srgrimes}
26511592Srgrimes
26521592Srgrimesvoid
265390148Simpremovedir(char *name)
26541592Srgrimes{
26551592Srgrimes
26561592Srgrimes	LOGCMD("rmdir", name);
26571592Srgrimes	if (rmdir(name) < 0)
26581592Srgrimes		perror_reply(550, name);
26591592Srgrimes	else
26601592Srgrimes		ack("RMD");
26611592Srgrimes}
26621592Srgrimes
26631592Srgrimesvoid
266490148Simppwd(void)
26651592Srgrimes{
2666100486Syar	char *s, path[MAXPATHLEN + 1];
26671592Srgrimes
2668137830Syar	if (getcwd(path, sizeof(path)) == NULL)
2669137839Syar		perror_reply(550, "Get current directory");
2670100486Syar	else {
2671100486Syar		if ((s = doublequote(path)) == NULL)
2672100486Syar			fatalerror("Ran out of memory.");
2673100486Syar		reply(257, "\"%s\" is current directory.", s);
2674100486Syar		free(s);
2675100486Syar	}
26761592Srgrimes}
26771592Srgrimes
26781592Srgrimeschar *
267990148Simprenamefrom(char *name)
26801592Srgrimes{
26811592Srgrimes	struct stat st;
26821592Srgrimes
2683125569Syar	if (guest && noguestmod) {
2684137852Syar		reply(550, "Operation not permitted.");
2685125569Syar		return (NULL);
2686125569Syar	}
2687100439Syar	if (lstat(name, &st) < 0) {
26881592Srgrimes		perror_reply(550, name);
2689125570Syar		return (NULL);
26901592Srgrimes	}
2691137852Syar	reply(350, "File exists, ready for destination name.");
26921592Srgrimes	return (name);
26931592Srgrimes}
26941592Srgrimes
26951592Srgrimesvoid
269690148Simprenamecmd(char *from, char *to)
26971592Srgrimes{
269817433Spst	struct stat st;
26991592Srgrimes
27001592Srgrimes	LOGCMD2("rename", from, to);
270117433Spst
270217433Spst	if (guest && (stat(to, &st) == 0)) {
2703137852Syar		reply(550, "%s: permission denied.", to);
270417433Spst		return;
270517433Spst	}
270617433Spst
27071592Srgrimes	if (rename(from, to) < 0)
27081592Srgrimes		perror_reply(550, "rename");
27091592Srgrimes	else
27101592Srgrimes		ack("RNTO");
27111592Srgrimes}
27121592Srgrimes
27131592Srgrimesstatic void
271490148Simpdolog(struct sockaddr *who)
27151592Srgrimes{
2716137984Syar	char who_name[NI_MAXHOST];
27171592Srgrimes
271856668Sshin	realhostname_sa(remotehost, sizeof(remotehost) - 1, who, who->sa_len);
2719137983Syar	remotehost[sizeof(remotehost) - 1] = 0;
2720137984Syar	if (getnameinfo(who, who->sa_len,
2721137984Syar		who_name, sizeof(who_name) - 1, NULL, 0, NI_NUMERICHOST))
2722137984Syar			*who_name = 0;
2723137984Syar	who_name[sizeof(who_name) - 1] = 0;
272456668Sshin
27251592Srgrimes#ifdef SETPROCTITLE
272625283Sdavidn#ifdef VIRTUAL_HOSTING
272725283Sdavidn	if (thishost != firsthost)
272825283Sdavidn		snprintf(proctitle, sizeof(proctitle), "%s: connected (to %s)",
272925283Sdavidn			 remotehost, hostname);
273025283Sdavidn	else
273125283Sdavidn#endif
273225283Sdavidn		snprintf(proctitle, sizeof(proctitle), "%s: connected",
273325283Sdavidn			 remotehost);
273413139Speter	setproctitle("%s", proctitle);
27351592Srgrimes#endif /* SETPROCTITLE */
27361592Srgrimes
273725283Sdavidn	if (logging) {
273825283Sdavidn#ifdef VIRTUAL_HOSTING
273925283Sdavidn		if (thishost != firsthost)
2740137984Syar			syslog(LOG_INFO, "connection from %s (%s) to %s",
2741137984Syar			       remotehost, who_name, hostname);
274225283Sdavidn		else
274325283Sdavidn#endif
2744137984Syar			syslog(LOG_INFO, "connection from %s (%s)",
2745137984Syar			       remotehost, who_name);
274625283Sdavidn	}
27471592Srgrimes}
27481592Srgrimes
27491592Srgrimes/*
27501592Srgrimes * Record logout in wtmp file
27511592Srgrimes * and exit with supplied status.
27521592Srgrimes */
27531592Srgrimesvoid
275490148Simpdologout(int status)
27551592Srgrimes{
27561592Srgrimes
2757202604Sed	if (logged_in && dowtmp) {
2758132893Syar		(void) seteuid(0);
2759202604Sed		ftpd_logwtmp(wtmpid, NULL, NULL);
27601592Srgrimes	}
27611592Srgrimes	/* beware of flushing buffers after a SIGPIPE */
27621592Srgrimes	_exit(status);
27631592Srgrimes}
27641592Srgrimes
27651592Srgrimesstatic void
276690148Simpsigurg(int signo)
27671592Srgrimes{
276889935Syar
276989935Syar	recvurg = 1;
277089935Syar}
277189935Syar
277289935Syarstatic void
2773140472Syarmaskurg(int flag)
2774140472Syar{
2775140472Syar	int oerrno;
2776140472Syar	sigset_t sset;
2777140472Syar
2778140472Syar	if (!transflag) {
2779140472Syar		syslog(LOG_ERR, "Internal: maskurg() while no transfer");
2780140472Syar		return;
2781140472Syar	}
2782140472Syar	oerrno = errno;
2783140472Syar	sigemptyset(&sset);
2784140472Syar	sigaddset(&sset, SIGURG);
2785140472Syar	sigprocmask(flag ? SIG_BLOCK : SIG_UNBLOCK, &sset, NULL);
2786140472Syar	errno = oerrno;
2787140472Syar}
2788140472Syar
2789140472Syarstatic void
2790140472Syarflagxfer(int flag)
2791140472Syar{
2792140472Syar
2793140472Syar	if (flag) {
2794141967Syar		if (transflag)
2795141967Syar			syslog(LOG_ERR, "Internal: flagxfer(1): "
2796141967Syar					"transfer already under way");
2797141966Syar		transflag = 1;
2798141966Syar		maskurg(0);
2799140472Syar		recvurg = 0;
2800141966Syar	} else {
2801141967Syar		if (!transflag)
2802141967Syar			syslog(LOG_ERR, "Internal: flagxfer(0): "
2803141967Syar					"no active transfer");
2804141966Syar		maskurg(1);
2805140472Syar		transflag = 0;
2806141966Syar	}
2807140472Syar}
2808140472Syar
2809140472Syar/*
2810140472Syar * Returns 0 if OK to resume or -1 if abort requested.
2811140472Syar */
2812140472Syarstatic int
281390148Simpmyoob(void)
281489935Syar{
28151592Srgrimes	char *cp;
2816186405Scperciva	int ret;
28171592Srgrimes
2818140472Syar	if (!transflag) {
2819140472Syar		syslog(LOG_ERR, "Internal: myoob() while no transfer");
2820140472Syar		return (0);
2821140472Syar	}
28221592Srgrimes	cp = tmpline;
2823186405Scperciva	ret = getline(cp, 7, stdin);
2824186405Scperciva	if (ret == -1) {
28251592Srgrimes		reply(221, "You could at least say goodbye.");
28261592Srgrimes		dologout(0);
2827186405Scperciva	} else if (ret == -2) {
2828186405Scperciva		/* Ignore truncated command. */
2829186405Scperciva		return (0);
28301592Srgrimes	}
28311592Srgrimes	upper(cp);
28321592Srgrimes	if (strcmp(cp, "ABOR\r\n") == 0) {
28331592Srgrimes		tmpline[0] = '\0';
28341592Srgrimes		reply(426, "Transfer aborted. Data connection closed.");
2835137852Syar		reply(226, "Abort successful.");
2836140472Syar		return (-1);
28371592Srgrimes	}
28381592Srgrimes	if (strcmp(cp, "STAT\r\n") == 0) {
283951192Smharo		tmpline[0] = '\0';
2840132930Syar		if (file_size != -1)
2841137852Syar			reply(213, "Status: %jd of %jd bytes transferred.",
2842132929Syar				   (intmax_t)byte_count, (intmax_t)file_size);
28431592Srgrimes		else
2844137852Syar			reply(213, "Status: %jd bytes transferred.",
2845132929Syar				   (intmax_t)byte_count);
28461592Srgrimes	}
2847140472Syar	return (0);
28481592Srgrimes}
28491592Srgrimes
28501592Srgrimes/*
28511592Srgrimes * Note: a response of 425 is not mentioned as a possible response to
28521592Srgrimes *	the PASV command in RFC959. However, it has been blessed as
28531592Srgrimes *	a legitimate response by Jon Postel in a telephone conversation
28541592Srgrimes *	with Rick Adams on 25 Jan 89.
28551592Srgrimes */
28561592Srgrimesvoid
285790148Simppassive(void)
28581592Srgrimes{
2859141918Sstefanf	socklen_t len;
2860141918Sstefanf	int on;
28611592Srgrimes	char *p, *a;
28621592Srgrimes
286317433Spst	if (pdata >= 0)		/* close old port if one set */
286417433Spst		close(pdata);
286517433Spst
286656668Sshin	pdata = socket(ctrl_addr.su_family, SOCK_STREAM, 0);
28671592Srgrimes	if (pdata < 0) {
28681592Srgrimes		perror_reply(425, "Can't open passive connection");
28691592Srgrimes		return;
28701592Srgrimes	}
2871100615Syar	on = 1;
2872100615Syar	if (setsockopt(pdata, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
2873100615Syar		syslog(LOG_WARNING, "pdata setsockopt (SO_REUSEADDR): %m");
28749933Spst
2875132893Syar	(void) seteuid(0);
287617433Spst
287719903Spst#ifdef IP_PORTRANGE
287856668Sshin	if (ctrl_addr.su_family == AF_INET) {
2879100615Syar	    on = restricted_data_ports ? IP_PORTRANGE_HIGH
2880100615Syar				       : IP_PORTRANGE_DEFAULT;
288119903Spst
288219903Spst	    if (setsockopt(pdata, IPPROTO_IP, IP_PORTRANGE,
2883100612Syar			    &on, sizeof(on)) < 0)
288419903Spst		    goto pasv_error;
28851592Srgrimes	}
288619903Spst#endif
288760929Snsayer#ifdef IPV6_PORTRANGE
288860929Snsayer	if (ctrl_addr.su_family == AF_INET6) {
2889100615Syar	    on = restricted_data_ports ? IPV6_PORTRANGE_HIGH
2890100615Syar				       : IPV6_PORTRANGE_DEFAULT;
28919933Spst
289260929Snsayer	    if (setsockopt(pdata, IPPROTO_IPV6, IPV6_PORTRANGE,
2893100612Syar			    &on, sizeof(on)) < 0)
289460929Snsayer		    goto pasv_error;
289560929Snsayer	}
289660929Snsayer#endif
289760929Snsayer
289816033Speter	pasv_addr = ctrl_addr;
289956668Sshin	pasv_addr.su_port = 0;
290056668Sshin	if (bind(pdata, (struct sockaddr *)&pasv_addr, pasv_addr.su_len) < 0)
290116033Speter		goto pasv_error;
290217433Spst
2903132893Syar	(void) seteuid(pw->pw_uid);
290416033Speter
29051592Srgrimes	len = sizeof(pasv_addr);
29061592Srgrimes	if (getsockname(pdata, (struct sockaddr *) &pasv_addr, &len) < 0)
29071592Srgrimes		goto pasv_error;
29081592Srgrimes	if (listen(pdata, 1) < 0)
29091592Srgrimes		goto pasv_error;
291056668Sshin	if (pasv_addr.su_family == AF_INET)
291156668Sshin		a = (char *) &pasv_addr.su_sin.sin_addr;
291256668Sshin	else if (pasv_addr.su_family == AF_INET6 &&
291356668Sshin		 IN6_IS_ADDR_V4MAPPED(&pasv_addr.su_sin6.sin6_addr))
291456668Sshin		a = (char *) &pasv_addr.su_sin6.sin6_addr.s6_addr[12];
291556668Sshin	else
291656668Sshin		goto pasv_error;
291756668Sshin
291856668Sshin	p = (char *) &pasv_addr.su_port;
29191592Srgrimes
29201592Srgrimes#define UC(b) (((int) b) & 0xff)
29211592Srgrimes
29221592Srgrimes	reply(227, "Entering Passive Mode (%d,%d,%d,%d,%d,%d)", UC(a[0]),
29231592Srgrimes		UC(a[1]), UC(a[2]), UC(a[3]), UC(p[0]), UC(p[1]));
29241592Srgrimes	return;
29251592Srgrimes
29261592Srgrimespasv_error:
2927132893Syar	(void) seteuid(pw->pw_uid);
29281592Srgrimes	(void) close(pdata);
29291592Srgrimes	pdata = -1;
29301592Srgrimes	perror_reply(425, "Can't open passive connection");
29311592Srgrimes	return;
29321592Srgrimes}
29331592Srgrimes
29341592Srgrimes/*
293556668Sshin * Long Passive defined in RFC 1639.
293656668Sshin *     228 Entering Long Passive Mode
293756668Sshin *         (af, hal, h1, h2, h3,..., pal, p1, p2...)
293856668Sshin */
293956668Sshin
294056668Sshinvoid
294190148Simplong_passive(char *cmd, int pf)
294256668Sshin{
2943141918Sstefanf	socklen_t len;
2944141918Sstefanf	int on;
294556668Sshin	char *p, *a;
294656668Sshin
294756668Sshin	if (pdata >= 0)		/* close old port if one set */
294856668Sshin		close(pdata);
294956668Sshin
295056668Sshin	if (pf != PF_UNSPEC) {
295156668Sshin		if (ctrl_addr.su_family != pf) {
295256668Sshin			switch (ctrl_addr.su_family) {
295356668Sshin			case AF_INET:
295456668Sshin				pf = 1;
295556668Sshin				break;
295656668Sshin			case AF_INET6:
295756668Sshin				pf = 2;
295856668Sshin				break;
295956668Sshin			default:
296056668Sshin				pf = 0;
296156668Sshin				break;
296256668Sshin			}
296356668Sshin			/*
296456668Sshin			 * XXX
296556668Sshin			 * only EPRT/EPSV ready clients will understand this
296656668Sshin			 */
296756668Sshin			if (strcmp(cmd, "EPSV") == 0 && pf) {
296856668Sshin				reply(522, "Network protocol mismatch, "
296956668Sshin					"use (%d)", pf);
297056668Sshin			} else
2971137852Syar				reply(501, "Network protocol mismatch."); /*XXX*/
297256668Sshin
297356668Sshin			return;
297456668Sshin		}
297556668Sshin	}
297656668Sshin
297756668Sshin	pdata = socket(ctrl_addr.su_family, SOCK_STREAM, 0);
297856668Sshin	if (pdata < 0) {
297956668Sshin		perror_reply(425, "Can't open passive connection");
298056668Sshin		return;
298156668Sshin	}
2982100615Syar	on = 1;
2983100615Syar	if (setsockopt(pdata, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
2984100615Syar		syslog(LOG_WARNING, "pdata setsockopt (SO_REUSEADDR): %m");
298556668Sshin
2986132893Syar	(void) seteuid(0);
298756668Sshin
298856668Sshin	pasv_addr = ctrl_addr;
298956668Sshin	pasv_addr.su_port = 0;
299056668Sshin	len = pasv_addr.su_len;
299156668Sshin
299260929Snsayer#ifdef IP_PORTRANGE
299360929Snsayer	if (ctrl_addr.su_family == AF_INET) {
2994100615Syar	    on = restricted_data_ports ? IP_PORTRANGE_HIGH
2995100615Syar				       : IP_PORTRANGE_DEFAULT;
299660929Snsayer
299760929Snsayer	    if (setsockopt(pdata, IPPROTO_IP, IP_PORTRANGE,
2998100612Syar			    &on, sizeof(on)) < 0)
299960929Snsayer		    goto pasv_error;
300060929Snsayer	}
300160929Snsayer#endif
300260929Snsayer#ifdef IPV6_PORTRANGE
300360929Snsayer	if (ctrl_addr.su_family == AF_INET6) {
3004100615Syar	    on = restricted_data_ports ? IPV6_PORTRANGE_HIGH
3005100615Syar				       : IPV6_PORTRANGE_DEFAULT;
300660929Snsayer
300760929Snsayer	    if (setsockopt(pdata, IPPROTO_IPV6, IPV6_PORTRANGE,
3008100612Syar			    &on, sizeof(on)) < 0)
300960929Snsayer		    goto pasv_error;
301060929Snsayer	}
301160929Snsayer#endif
301260929Snsayer
301356668Sshin	if (bind(pdata, (struct sockaddr *)&pasv_addr, len) < 0)
301456668Sshin		goto pasv_error;
301556668Sshin
3016132893Syar	(void) seteuid(pw->pw_uid);
301756668Sshin
301856668Sshin	if (getsockname(pdata, (struct sockaddr *) &pasv_addr, &len) < 0)
301956668Sshin		goto pasv_error;
302056668Sshin	if (listen(pdata, 1) < 0)
302156668Sshin		goto pasv_error;
302256668Sshin
302356668Sshin#define UC(b) (((int) b) & 0xff)
302456668Sshin
302556668Sshin	if (strcmp(cmd, "LPSV") == 0) {
302656668Sshin		p = (char *)&pasv_addr.su_port;
302756668Sshin		switch (pasv_addr.su_family) {
302856668Sshin		case AF_INET:
302956668Sshin			a = (char *) &pasv_addr.su_sin.sin_addr;
303056668Sshin		v4_reply:
303156668Sshin			reply(228,
303256668Sshin"Entering Long Passive Mode (%d,%d,%d,%d,%d,%d,%d,%d,%d)",
303356668Sshin			      4, 4, UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]),
303456668Sshin			      2, UC(p[0]), UC(p[1]));
303556668Sshin			return;
303656668Sshin		case AF_INET6:
303756668Sshin			if (IN6_IS_ADDR_V4MAPPED(&pasv_addr.su_sin6.sin6_addr)) {
303856668Sshin				a = (char *) &pasv_addr.su_sin6.sin6_addr.s6_addr[12];
303956668Sshin				goto v4_reply;
304056668Sshin			}
304156668Sshin			a = (char *) &pasv_addr.su_sin6.sin6_addr;
304256668Sshin			reply(228,
304356668Sshin"Entering Long Passive Mode "
304456668Sshin"(%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d)",
304556668Sshin			      6, 16, UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]),
304656668Sshin			      UC(a[4]), UC(a[5]), UC(a[6]), UC(a[7]),
304756668Sshin			      UC(a[8]), UC(a[9]), UC(a[10]), UC(a[11]),
304856668Sshin			      UC(a[12]), UC(a[13]), UC(a[14]), UC(a[15]),
304956668Sshin			      2, UC(p[0]), UC(p[1]));
305056668Sshin			return;
305156668Sshin		}
305256668Sshin	} else if (strcmp(cmd, "EPSV") == 0) {
305356668Sshin		switch (pasv_addr.su_family) {
305456668Sshin		case AF_INET:
305556668Sshin		case AF_INET6:
305656668Sshin			reply(229, "Entering Extended Passive Mode (|||%d|)",
305756668Sshin				ntohs(pasv_addr.su_port));
305856668Sshin			return;
305956668Sshin		}
306056668Sshin	} else {
306156668Sshin		/* more proper error code? */
306256668Sshin	}
306356668Sshin
306456668Sshinpasv_error:
3065132893Syar	(void) seteuid(pw->pw_uid);
306656668Sshin	(void) close(pdata);
306756668Sshin	pdata = -1;
306856668Sshin	perror_reply(425, "Can't open passive connection");
306956668Sshin	return;
307056668Sshin}
307156668Sshin
307256668Sshin/*
3073101537Syar * Generate unique name for file with basename "local"
3074101537Syar * and open the file in order to avoid possible races.
3075101537Syar * Try "local" first, then "local.1", "local.2" etc, up to "local.99".
3076101537Syar * Return descriptor to the file, set "name" to its name.
3077101537Syar *
30781592Srgrimes * Generates failure reply on error.
30791592Srgrimes */
3080101537Syarstatic int
3081101537Syarguniquefd(char *local, char **name)
30821592Srgrimes{
30831592Srgrimes	static char new[MAXPATHLEN];
30841592Srgrimes	struct stat st;
3085101537Syar	char *cp;
30861592Srgrimes	int count;
3087101537Syar	int fd;
30881592Srgrimes
30891592Srgrimes	cp = strrchr(local, '/');
30901592Srgrimes	if (cp)
30911592Srgrimes		*cp = '\0';
30921592Srgrimes	if (stat(cp ? local : ".", &st) < 0) {
30931592Srgrimes		perror_reply(553, cp ? local : ".");
3094101537Syar		return (-1);
30951592Srgrimes	}
3096101537Syar	if (cp) {
3097101537Syar		/*
3098101537Syar		 * Let not overwrite dirname with counter suffix.
3099101537Syar		 * -4 is for /nn\0
3100101537Syar		 * In this extreme case dot won't be put in front of suffix.
3101101537Syar		 */
3102101537Syar		if (strlen(local) > sizeof(new) - 4) {
3103137852Syar			reply(553, "Pathname too long.");
3104101537Syar			return (-1);
3105101537Syar		}
31061592Srgrimes		*cp = '/';
3107101537Syar	}
310831973Simp	/* -4 is for the .nn<null> we put on the end below */
310931973Simp	(void) snprintf(new, sizeof(new) - 4, "%s", local);
31101592Srgrimes	cp = new + strlen(new);
3111101537Syar	/*
3112101537Syar	 * Don't generate dotfile unless requested explicitly.
3113101537Syar	 * This covers the case when basename gets truncated off
3114101537Syar	 * by buffer size.
3115101537Syar	 */
3116101537Syar	if (cp > new && cp[-1] != '/')
3117101537Syar		*cp++ = '.';
3118101537Syar	for (count = 0; count < 100; count++) {
3119101537Syar		/* At count 0 try unmodified name */
3120101537Syar		if (count)
3121101537Syar			(void)sprintf(cp, "%d", count);
3122101537Syar		if ((fd = open(count ? new : local,
3123101537Syar		    O_RDWR | O_CREAT | O_EXCL, 0666)) >= 0) {
3124101537Syar			*name = count ? new : local;
3125101537Syar			return (fd);
3126101537Syar		}
3127110046Syar		if (errno != EEXIST) {
3128110307Syar			perror_reply(553, count ? new : local);
3129110046Syar			return (-1);
3130110046Syar		}
31311592Srgrimes	}
31321592Srgrimes	reply(452, "Unique file name cannot be created.");
3133101537Syar	return (-1);
31341592Srgrimes}
31351592Srgrimes
31361592Srgrimes/*
31371592Srgrimes * Format and send reply containing system error number.
31381592Srgrimes */
31391592Srgrimesvoid
314090148Simpperror_reply(int code, char *string)
31411592Srgrimes{
31421592Srgrimes
31431592Srgrimes	reply(code, "%s: %s.", string, strerror(errno));
31441592Srgrimes}
31451592Srgrimes
31461592Srgrimesstatic char *onefile[] = {
31471592Srgrimes	"",
31481592Srgrimes	0
31491592Srgrimes};
31501592Srgrimes
31511592Srgrimesvoid
315290148Simpsend_file_list(char *whichf)
31531592Srgrimes{
31541592Srgrimes	struct stat st;
31551592Srgrimes	DIR *dirp = NULL;
31561592Srgrimes	struct dirent *dir;
31571592Srgrimes	FILE *dout = NULL;
31581592Srgrimes	char **dirlist, *dirname;
31591592Srgrimes	int simple = 0;
31601592Srgrimes	int freeglob = 0;
31611592Srgrimes	glob_t gl;
31621592Srgrimes
31631592Srgrimes	if (strpbrk(whichf, "~{[*?") != NULL) {
3164100222Smikeh		int flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE;
31651592Srgrimes
31661592Srgrimes		memset(&gl, 0, sizeof(gl));
316774470Sjlemon		gl.gl_matchc = MAXGLOBARGS;
316880525Smikeh		flags |= GLOB_LIMIT;
31691592Srgrimes		freeglob = 1;
31701592Srgrimes		if (glob(whichf, flags, 0, &gl)) {
3171137852Syar			reply(550, "No matching files found.");
31721592Srgrimes			goto out;
31731592Srgrimes		} else if (gl.gl_pathc == 0) {
31741592Srgrimes			errno = ENOENT;
31751592Srgrimes			perror_reply(550, whichf);
31761592Srgrimes			goto out;
31771592Srgrimes		}
31781592Srgrimes		dirlist = gl.gl_pathv;
31791592Srgrimes	} else {
31801592Srgrimes		onefile[0] = whichf;
31811592Srgrimes		dirlist = onefile;
31821592Srgrimes		simple = 1;
31831592Srgrimes	}
31841592Srgrimes
318517478Smarkm	while ((dirname = *dirlist++)) {
31861592Srgrimes		if (stat(dirname, &st) < 0) {
31871592Srgrimes			/*
31881592Srgrimes			 * If user typed "ls -l", etc, and the client
31891592Srgrimes			 * used NLST, do what the user meant.
31901592Srgrimes			 */
31911592Srgrimes			if (dirname[0] == '-' && *dirlist == NULL &&
3192140472Syar			    dout == NULL)
319325165Sdavidn				retrieve(_PATH_LS " %s", dirname);
3194140472Syar			else
3195140472Syar				perror_reply(550, whichf);
31961592Srgrimes			goto out;
31971592Srgrimes		}
31981592Srgrimes
31991592Srgrimes		if (S_ISREG(st.st_mode)) {
32001592Srgrimes			if (dout == NULL) {
3201132930Syar				dout = dataconn("file list", -1, "w");
32021592Srgrimes				if (dout == NULL)
32031592Srgrimes					goto out;
3204140472Syar				STARTXFER;
32051592Srgrimes			}
3206140472Syar			START_UNSAFE;
32071592Srgrimes			fprintf(dout, "%s%s\n", dirname,
32081592Srgrimes				type == TYPE_A ? "\r" : "");
3209140472Syar			END_UNSAFE;
3210140472Syar			if (ferror(dout))
3211140472Syar				goto data_err;
3212140472Syar			byte_count += strlen(dirname) +
3213140472Syar				      (type == TYPE_A ? 2 : 1);
3214140472Syar			CHECKOOB(goto abrt);
32151592Srgrimes			continue;
32161592Srgrimes		} else if (!S_ISDIR(st.st_mode))
32171592Srgrimes			continue;
32181592Srgrimes
32191592Srgrimes		if ((dirp = opendir(dirname)) == NULL)
32201592Srgrimes			continue;
32211592Srgrimes
32221592Srgrimes		while ((dir = readdir(dirp)) != NULL) {
32231592Srgrimes			char nbuf[MAXPATHLEN];
32241592Srgrimes
3225140472Syar			CHECKOOB(goto abrt);
322689935Syar
32271592Srgrimes			if (dir->d_name[0] == '.' && dir->d_namlen == 1)
32281592Srgrimes				continue;
32291592Srgrimes			if (dir->d_name[0] == '.' && dir->d_name[1] == '.' &&
32301592Srgrimes			    dir->d_namlen == 2)
32311592Srgrimes				continue;
32321592Srgrimes
323399213Smaxim			snprintf(nbuf, sizeof(nbuf),
323431973Simp				"%s/%s", dirname, dir->d_name);
32351592Srgrimes
32361592Srgrimes			/*
32371592Srgrimes			 * We have to do a stat to insure it's
32381592Srgrimes			 * not a directory or special file.
32391592Srgrimes			 */
32401592Srgrimes			if (simple || (stat(nbuf, &st) == 0 &&
32411592Srgrimes			    S_ISREG(st.st_mode))) {
32421592Srgrimes				if (dout == NULL) {
3243132930Syar					dout = dataconn("file list", -1, "w");
32441592Srgrimes					if (dout == NULL)
32451592Srgrimes						goto out;
3246140472Syar					STARTXFER;
32471592Srgrimes				}
3248140472Syar				START_UNSAFE;
32491592Srgrimes				if (nbuf[0] == '.' && nbuf[1] == '/')
32501592Srgrimes					fprintf(dout, "%s%s\n", &nbuf[2],
32511592Srgrimes						type == TYPE_A ? "\r" : "");
32521592Srgrimes				else
32531592Srgrimes					fprintf(dout, "%s%s\n", nbuf,
32541592Srgrimes						type == TYPE_A ? "\r" : "");
3255140472Syar				END_UNSAFE;
3256140472Syar				if (ferror(dout))
3257140472Syar					goto data_err;
3258140472Syar				byte_count += strlen(nbuf) +
3259140472Syar					      (type == TYPE_A ? 2 : 1);
3260140472Syar				CHECKOOB(goto abrt);
32611592Srgrimes			}
32621592Srgrimes		}
32631592Srgrimes		(void) closedir(dirp);
3264140472Syar		dirp = NULL;
32651592Srgrimes	}
32661592Srgrimes
32671592Srgrimes	if (dout == NULL)
32681592Srgrimes		reply(550, "No files found.");
3269140472Syar	else if (ferror(dout))
3270140472Syardata_err:	perror_reply(550, "Data connection");
32711592Srgrimes	else
32721592Srgrimes		reply(226, "Transfer complete.");
3273140472Syarout:
3274140472Syar	if (dout) {
3275140472Syar		ENDXFER;
3276140472Syarabrt:
32771592Srgrimes		(void) fclose(dout);
3278140472Syar		data = -1;
3279140472Syar		pdata = -1;
3280140472Syar	}
3281140472Syar	if (dirp)
3282140472Syar		(void) closedir(dirp);
32831592Srgrimes	if (freeglob) {
32841592Srgrimes		freeglob = 0;
32851592Srgrimes		globfree(&gl);
32861592Srgrimes	}
32871592Srgrimes}
32881592Srgrimes
328915196Sdgvoid
329090148Simpreapchild(int signo)
329115196Sdg{
3292137830Syar	while (waitpid(-1, NULL, WNOHANG) > 0);
329315196Sdg}
329415196Sdg
329513139Speter#ifdef OLD_SETPROCTITLE
32961592Srgrimes/*
32971592Srgrimes * Clobber argv so ps will show what we're doing.  (Stolen from sendmail.)
32981592Srgrimes * Warning, since this is usually started from inetd.conf, it often doesn't
32991592Srgrimes * have much of an environment or arglist to overwrite.
33001592Srgrimes */
33011592Srgrimesvoid
33021592Srgrimessetproctitle(const char *fmt, ...)
33031592Srgrimes{
33041592Srgrimes	int i;
33051592Srgrimes	va_list ap;
33061592Srgrimes	char *p, *bp, ch;
33071592Srgrimes	char buf[LINE_MAX];
33081592Srgrimes
33091592Srgrimes	va_start(ap, fmt);
33101592Srgrimes	(void)vsnprintf(buf, sizeof(buf), fmt, ap);
33111592Srgrimes
33121592Srgrimes	/* make ps print our process name */
33131592Srgrimes	p = Argv[0];
33141592Srgrimes	*p++ = '-';
33151592Srgrimes
33161592Srgrimes	i = strlen(buf);
33171592Srgrimes	if (i > LastArgv - p - 2) {
33181592Srgrimes		i = LastArgv - p - 2;
33191592Srgrimes		buf[i] = '\0';
33201592Srgrimes	}
33211592Srgrimes	bp = buf;
33221592Srgrimes	while (ch = *bp++)
33231592Srgrimes		if (ch != '\n' && ch != '\r')
33241592Srgrimes			*p++ = ch;
33251592Srgrimes	while (p < LastArgv)
33261592Srgrimes		*p++ = ' ';
33271592Srgrimes}
332813139Speter#endif /* OLD_SETPROCTITLE */
33296740Sguido
333017433Spststatic void
3331137848Syarappendf(char **strp, char *fmt, ...)
3332137848Syar{
3333137848Syar	va_list ap;
3334137848Syar	char *ostr, *p;
3335137848Syar
3336137848Syar	va_start(ap, fmt);
3337137848Syar	vasprintf(&p, fmt, ap);
3338137848Syar	va_end(ap);
3339137848Syar	if (p == NULL)
3340137848Syar		fatalerror("Ran out of memory.");
3341137848Syar	if (*strp == NULL)
3342137848Syar		*strp = p;
3343137848Syar	else {
3344137848Syar		ostr = *strp;
3345137848Syar		asprintf(strp, "%s%s", ostr, p);
3346137848Syar		if (*strp == NULL)
3347137848Syar			fatalerror("Ran out of memory.");
3348137848Syar		free(ostr);
3349137848Syar	}
3350137848Syar}
3351137848Syar
3352137848Syarstatic void
3353137848Syarlogcmd(char *cmd, char *file1, char *file2, off_t cnt)
3354137848Syar{
3355137848Syar	char *msg = NULL;
3356137848Syar	char wd[MAXPATHLEN + 1];
3357137848Syar
3358137848Syar	if (logging <= 1)
3359137848Syar		return;
3360137848Syar
3361137985Syar	if (getcwd(wd, sizeof(wd) - 1) == NULL)
3362137985Syar		strcpy(wd, strerror(errno));
3363137985Syar
3364137848Syar	appendf(&msg, "%s", cmd);
3365137848Syar	if (file1)
3366137848Syar		appendf(&msg, " %s", file1);
3367137848Syar	if (file2)
3368137848Syar		appendf(&msg, " %s", file2);
3369137848Syar	if (cnt >= 0)
3370137848Syar		appendf(&msg, " = %jd bytes", (intmax_t)cnt);
3371137985Syar	appendf(&msg, " (wd: %s", wd);
3372137862Syar	if (guest || dochroot)
3373137985Syar		appendf(&msg, "; chrooted");
3374137985Syar	appendf(&msg, ")");
3375137848Syar	syslog(LOG_INFO, "%s", msg);
3376137848Syar	free(msg);
3377137848Syar}
3378137848Syar
3379137848Syarstatic void
338090148Simplogxfer(char *name, off_t size, time_t start)
33816740Sguido{
3382137145Syar	char buf[MAXPATHLEN + 1024];
33836740Sguido	char path[MAXPATHLEN + 1];
338436612Sjb	time_t now;
33856740Sguido
3386137145Syar	if (statfd >= 0) {
33876740Sguido		time(&now);
3388137145Syar		if (realpath(name, path) == NULL) {
3389137145Syar			syslog(LOG_NOTICE, "realpath failed on %s: %m", path);
3390137145Syar			return;
3391137145Syar		}
3392137145Syar		snprintf(buf, sizeof(buf), "%.20s!%s!%s!%s!%jd!%ld\n",
33936740Sguido			ctime(&now)+4, ident, remotehost,
3394137145Syar			path, (intmax_t)size,
339582792Sache			(long)(now - start + (now == start)));
33966740Sguido		write(statfd, buf, strlen(buf));
33976740Sguido	}
33986740Sguido}
3399100486Syar
3400100486Syarstatic char *
3401100486Syardoublequote(char *s)
3402100486Syar{
3403100486Syar	int n;
3404100486Syar	char *p, *s2;
3405100486Syar
3406100486Syar	for (p = s, n = 0; *p; p++)
3407100486Syar		if (*p == '"')
3408100486Syar			n++;
3409100486Syar
3410100486Syar	if ((s2 = malloc(p - s + n + 1)) == NULL)
3411100486Syar		return (NULL);
3412100486Syar
3413100486Syar	for (p = s2; *s; s++, p++) {
3414100486Syar		if ((*p = *s) == '"')
3415100486Syar			*(++p) = '"';
3416100486Syar	}
3417100486Syar	*p = '\0';
3418100486Syar
3419100486Syar	return (s2);
3420100486Syar}
3421120059Sume
3422120059Sume/* setup server socket for specified address family */
3423120059Sume/* if af is PF_UNSPEC more than one socket may be returned */
3424120059Sume/* the returned list is dynamically allocated, so caller needs to free it */
3425120059Sumestatic int *
3426120059Sumesocksetup(int af, char *bindname, const char *bindport)
3427120059Sume{
3428120059Sume	struct addrinfo hints, *res, *r;
3429120059Sume	int error, maxs, *s, *socks;
3430120059Sume	const int on = 1;
3431120059Sume
3432120059Sume	memset(&hints, 0, sizeof(hints));
3433120059Sume	hints.ai_flags = AI_PASSIVE;
3434120059Sume	hints.ai_family = af;
3435120059Sume	hints.ai_socktype = SOCK_STREAM;
3436120059Sume	error = getaddrinfo(bindname, bindport, &hints, &res);
3437120059Sume	if (error) {
3438120059Sume		syslog(LOG_ERR, "%s", gai_strerror(error));
3439120059Sume		if (error == EAI_SYSTEM)
3440120059Sume			syslog(LOG_ERR, "%s", strerror(errno));
3441120059Sume		return NULL;
3442120059Sume	}
3443120059Sume
3444120059Sume	/* Count max number of sockets we may open */
3445120059Sume	for (maxs = 0, r = res; r; r = r->ai_next, maxs++)
3446120059Sume		;
3447120059Sume	socks = malloc((maxs + 1) * sizeof(int));
3448120059Sume	if (!socks) {
3449120059Sume		freeaddrinfo(res);
3450120059Sume		syslog(LOG_ERR, "couldn't allocate memory for sockets");
3451120059Sume		return NULL;
3452120059Sume	}
3453120059Sume
3454120059Sume	*socks = 0;   /* num of sockets counter at start of array */
3455120059Sume	s = socks + 1;
3456120059Sume	for (r = res; r; r = r->ai_next) {
3457120059Sume		*s = socket(r->ai_family, r->ai_socktype, r->ai_protocol);
3458120059Sume		if (*s < 0) {
3459120059Sume			syslog(LOG_DEBUG, "control socket: %m");
3460120059Sume			continue;
3461120059Sume		}
3462120059Sume		if (setsockopt(*s, SOL_SOCKET, SO_REUSEADDR,
3463120059Sume		    &on, sizeof(on)) < 0)
3464120059Sume			syslog(LOG_WARNING,
3465120059Sume			    "control setsockopt (SO_REUSEADDR): %m");
3466120059Sume		if (r->ai_family == AF_INET6) {
3467120059Sume			if (setsockopt(*s, IPPROTO_IPV6, IPV6_V6ONLY,
3468120059Sume			    &on, sizeof(on)) < 0)
3469120059Sume				syslog(LOG_WARNING,
3470120059Sume				    "control setsockopt (IPV6_V6ONLY): %m");
3471120059Sume		}
3472120059Sume		if (bind(*s, r->ai_addr, r->ai_addrlen) < 0) {
3473120059Sume			syslog(LOG_DEBUG, "control bind: %m");
3474120059Sume			close(*s);
3475120059Sume			continue;
3476120059Sume		}
3477120059Sume		(*socks)++;
3478120059Sume		s++;
3479120059Sume	}
3480120059Sume
3481120059Sume	if (res)
3482120059Sume		freeaddrinfo(res);
3483120059Sume
3484120059Sume	if (*socks == 0) {
3485120059Sume		syslog(LOG_ERR, "control socket: Couldn't bind to any socket");
3486120059Sume		free(socks);
3487120059Sume		return NULL;
3488120059Sume	}
3489120059Sume	return(socks);
3490120059Sume}
3491