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: stable/10/libexec/ftpd/ftpd.c 327019 2017-12-20 07:18:07Z delphij $");
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
423325472Seugen	/* handle filesize limit gracefully */
424325472Seugen	sa.sa_handler = SIG_IGN;
425325472Seugen	(void)sigaction(SIGXFSZ, &sa, NULL);
426325472Seugen
42715196Sdg	if (daemon_mode) {
428120059Sume		int *ctl_sock, fd, maxfd = -1, nfds, i;
429120059Sume		fd_set defreadfds, readfds;
430120059Sume		pid_t pid;
431154630Syar		struct pidfh *pfh;
43215196Sdg
433154630Syar		if ((pfh = pidfile_open(pid_file, 0600, &pid)) == NULL) {
434154630Syar			if (errno == EEXIST) {
435154630Syar				syslog(LOG_ERR, "%s already running, pid %d",
436154630Syar				       getprogname(), (int)pid);
437154630Syar				exit(1);
438154630Syar			}
439154630Syar			syslog(LOG_WARNING, "pidfile_open: %m");
440154630Syar		}
441154630Syar
44215196Sdg		/*
44315196Sdg		 * Detach from parent.
44415196Sdg		 */
44515196Sdg		if (daemon(1, 1) < 0) {
44615196Sdg			syslog(LOG_ERR, "failed to become a daemon");
44715196Sdg			exit(1);
44815196Sdg		}
449154630Syar
450154630Syar		if (pfh != NULL && pidfile_write(pfh) == -1)
451154630Syar			syslog(LOG_WARNING, "pidfile_write: %m");
452154630Syar
45389935Syar		sa.sa_handler = reapchild;
45489935Syar		(void)sigaction(SIGCHLD, &sa, NULL);
45556668Sshin
456156156Sume#ifdef VIRTUAL_HOSTING
457156156Sume		inithosts(family);
458156156Sume#endif
459156156Sume
46015196Sdg		/*
46115196Sdg		 * Open a socket, bind it to the FTP port, and start
46215196Sdg		 * listening.
46315196Sdg		 */
464120059Sume		ctl_sock = socksetup(family, bindname, bindport);
465120059Sume		if (ctl_sock == NULL)
46615196Sdg			exit(1);
467120059Sume
468120059Sume		FD_ZERO(&defreadfds);
469120059Sume		for (i = 1; i <= *ctl_sock; i++) {
470120059Sume			FD_SET(ctl_sock[i], &defreadfds);
471120059Sume			if (listen(ctl_sock[i], 32) < 0) {
472120059Sume				syslog(LOG_ERR, "control listen: %m");
473120059Sume				exit(1);
474120059Sume			}
475120059Sume			if (maxfd < ctl_sock[i])
476120059Sume				maxfd = ctl_sock[i];
47715196Sdg		}
478120059Sume
47915196Sdg		/*
48015196Sdg		 * Loop forever accepting connection requests and forking off
48115196Sdg		 * children to handle them.
48215196Sdg		 */
48315196Sdg		while (1) {
484120059Sume			FD_COPY(&defreadfds, &readfds);
485120059Sume			nfds = select(maxfd + 1, &readfds, NULL, NULL, 0);
486120059Sume			if (nfds <= 0) {
487120059Sume				if (nfds < 0 && errno != EINTR)
488120059Sume					syslog(LOG_WARNING, "select: %m");
489120059Sume				continue;
490120059Sume			}
491120059Sume
492120059Sume			pid = -1;
493120059Sume                        for (i = 1; i <= *ctl_sock; i++)
494120059Sume				if (FD_ISSET(ctl_sock[i], &readfds)) {
495120059Sume					addrlen = sizeof(his_addr);
496120059Sume					fd = accept(ctl_sock[i],
497120059Sume					    (struct sockaddr *)&his_addr,
498120059Sume					    &addrlen);
499154634Syar					if (fd == -1) {
500154634Syar						syslog(LOG_WARNING,
501154634Syar						       "accept: %m");
502154634Syar						continue;
503135737Smaxim					}
504154634Syar					switch (pid = fork()) {
505154634Syar					case 0:
506154634Syar						/* child */
507154634Syar						(void) dup2(fd, 0);
508154634Syar						(void) dup2(fd, 1);
509154634Syar						(void) close(fd);
510154634Syar						for (i = 1; i <= *ctl_sock; i++)
511154634Syar							close(ctl_sock[i]);
512154634Syar						if (pfh != NULL)
513154634Syar							pidfile_close(pfh);
514154634Syar						goto gotchild;
515154634Syar					case -1:
516154634Syar						syslog(LOG_WARNING, "fork: %m");
517154634Syar						/* FALLTHROUGH */
518154634Syar					default:
519154634Syar						close(fd);
520154634Syar					}
521120059Sume				}
52215196Sdg		}
52315196Sdg	} else {
52415196Sdg		addrlen = sizeof(his_addr);
52515196Sdg		if (getpeername(0, (struct sockaddr *)&his_addr, &addrlen) < 0) {
52615196Sdg			syslog(LOG_ERR, "getpeername (%s): %m",argv[0]);
52715196Sdg			exit(1);
52815196Sdg		}
529156156Sume
530156156Sume#ifdef VIRTUAL_HOSTING
531156156Sume		if (his_addr.su_family == AF_INET6 &&
532156156Sume		    IN6_IS_ADDR_V4MAPPED(&his_addr.su_sin6.sin6_addr))
533156156Sume			family = AF_INET;
534156156Sume		else
535156156Sume			family = his_addr.su_family;
536156156Sume		inithosts(family);
537156156Sume#endif
53815196Sdg	}
53915196Sdg
540154634Syargotchild:
54189935Syar	sa.sa_handler = SIG_DFL;
54289935Syar	(void)sigaction(SIGCHLD, &sa, NULL);
5431592Srgrimes
54489935Syar	sa.sa_handler = sigurg;
54589935Syar	sa.sa_flags = 0;		/* don't restart syscalls for SIGURG */
54689935Syar	(void)sigaction(SIGURG, &sa, NULL);
54789935Syar
54889935Syar	sigfillset(&sa.sa_mask);	/* block all signals in handler */
54989935Syar	sa.sa_flags = SA_RESTART;
55089935Syar	sa.sa_handler = sigquit;
55189935Syar	(void)sigaction(SIGHUP, &sa, NULL);
55289935Syar	(void)sigaction(SIGINT, &sa, NULL);
55389935Syar	(void)sigaction(SIGQUIT, &sa, NULL);
55489935Syar	(void)sigaction(SIGTERM, &sa, NULL);
55589935Syar
55689935Syar	sa.sa_handler = lostconn;
55789935Syar	(void)sigaction(SIGPIPE, &sa, NULL);
55889935Syar
55915196Sdg	addrlen = sizeof(ctrl_addr);
56015196Sdg	if (getsockname(0, (struct sockaddr *)&ctrl_addr, &addrlen) < 0) {
56115196Sdg		syslog(LOG_ERR, "getsockname (%s): %m",argv[0]);
56215196Sdg		exit(1);
56315196Sdg	}
564109742Syar	dataport = ntohs(ctrl_addr.su_port) - 1; /* as per RFC 959 */
56525283Sdavidn#ifdef VIRTUAL_HOSTING
56625283Sdavidn	/* select our identity from virtual host table */
56756668Sshin	selecthost(&ctrl_addr);
56825283Sdavidn#endif
56915196Sdg#ifdef IP_TOS
57056668Sshin	if (ctrl_addr.su_family == AF_INET)
57156668Sshin      {
57215196Sdg	tos = IPTOS_LOWDELAY;
573100612Syar	if (setsockopt(0, IPPROTO_IP, IP_TOS, &tos, sizeof(int)) < 0)
574100609Syar		syslog(LOG_WARNING, "control setsockopt (IP_TOS): %m");
57556668Sshin      }
57615196Sdg#endif
57735482Sdg	/*
57835482Sdg	 * Disable Nagle on the control channel so that we don't have to wait
57935482Sdg	 * for peer's ACK before issuing our next reply.
58035482Sdg	 */
58135482Sdg	if (setsockopt(0, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)) < 0)
582100609Syar		syslog(LOG_WARNING, "control setsockopt (TCP_NODELAY): %m");
58335482Sdg
58456668Sshin	data_source.su_port = htons(ntohs(ctrl_addr.su_port) - 1);
58515196Sdg
586202209Sed	(void)snprintf(wtmpid, sizeof(wtmpid), "%xftpd", getpid());
58717435Spst
5881592Srgrimes	/* Try to handle urgent data inline */
5891592Srgrimes#ifdef SO_OOBINLINE
590100612Syar	if (setsockopt(0, SOL_SOCKET, SO_OOBINLINE, &on, sizeof(on)) < 0)
591100609Syar		syslog(LOG_WARNING, "control setsockopt (SO_OOBINLINE): %m");
5921592Srgrimes#endif
5931592Srgrimes
5941592Srgrimes#ifdef	F_SETOWN
5951592Srgrimes	if (fcntl(fileno(stdin), F_SETOWN, getpid()) == -1)
5961592Srgrimes		syslog(LOG_ERR, "fcntl F_SETOWN: %m");
5971592Srgrimes#endif
59856668Sshin	dolog((struct sockaddr *)&his_addr);
5991592Srgrimes	/*
6001592Srgrimes	 * Set up default state
6011592Srgrimes	 */
6021592Srgrimes	data = -1;
6031592Srgrimes	type = TYPE_A;
6041592Srgrimes	form = FORM_N;
6051592Srgrimes	stru = STRU_F;
6061592Srgrimes	mode = MODE_S;
6071592Srgrimes	tmpline[0] = '\0';
6081592Srgrimes
6091592Srgrimes	/* If logins are disabled, print out the message. */
6101592Srgrimes	if ((fd = fopen(_PATH_NOLOGIN,"r")) != NULL) {
6111592Srgrimes		while (fgets(line, sizeof(line), fd) != NULL) {
6121592Srgrimes			if ((cp = strchr(line, '\n')) != NULL)
6131592Srgrimes				*cp = '\0';
6141592Srgrimes			lreply(530, "%s", line);
6151592Srgrimes		}
6161592Srgrimes		(void) fflush(stdout);
6171592Srgrimes		(void) fclose(fd);
6181592Srgrimes		reply(530, "System not available.");
6191592Srgrimes		exit(0);
6201592Srgrimes	}
62125283Sdavidn#ifdef VIRTUAL_HOSTING
622130428Sobrien	fd = fopen(thishost->welcome, "r");
62325283Sdavidn#else
624130428Sobrien	fd = fopen(_PATH_FTPWELCOME, "r");
62525283Sdavidn#endif
626130428Sobrien	if (fd != NULL) {
6271592Srgrimes		while (fgets(line, sizeof(line), fd) != NULL) {
6281592Srgrimes			if ((cp = strchr(line, '\n')) != NULL)
6291592Srgrimes				*cp = '\0';
6301592Srgrimes			lreply(220, "%s", line);
6311592Srgrimes		}
6321592Srgrimes		(void) fflush(stdout);
6331592Srgrimes		(void) fclose(fd);
6341592Srgrimes		/* reply(220,) must follow */
6351592Srgrimes	}
63625283Sdavidn#ifndef VIRTUAL_HOSTING
63727650Sdavidn	if ((hostname = malloc(MAXHOSTNAMELEN)) == NULL)
63876096Smarkm		fatalerror("Ran out of memory.");
639137983Syar	if (gethostname(hostname, MAXHOSTNAMELEN - 1) < 0)
640137983Syar		hostname[0] = '\0';
64145422Sbrian	hostname[MAXHOSTNAMELEN - 1] = '\0';
64225283Sdavidn#endif
643110037Syar	if (hostinfo)
644110037Syar		reply(220, "%s FTP server (%s) ready.", hostname, version);
645110037Syar	else
646110037Syar		reply(220, "FTP server ready.");
6471592Srgrimes	for (;;)
6481592Srgrimes		(void) yyparse();
6491592Srgrimes	/* NOTREACHED */
6501592Srgrimes}
6511592Srgrimes
6521592Srgrimesstatic void
65390148Simplostconn(int signo)
6541592Srgrimes{
6551592Srgrimes
65676096Smarkm	if (ftpdebug)
6571592Srgrimes		syslog(LOG_DEBUG, "lost connection");
65831329Scharnier	dologout(1);
6591592Srgrimes}
6601592Srgrimes
66189935Syarstatic void
66290148Simpsigquit(int signo)
66389935Syar{
66489935Syar
66589935Syar	syslog(LOG_ERR, "got signal %d", signo);
66689935Syar	dologout(1);
66789935Syar}
66889935Syar
66925283Sdavidn#ifdef VIRTUAL_HOSTING
6701592Srgrimes/*
67125283Sdavidn * read in virtual host tables (if they exist)
67225283Sdavidn */
67325283Sdavidn
67425283Sdavidnstatic void
675156156Sumeinithosts(int family)
67625283Sdavidn{
677100182Syar	int insert;
67899877Syar	size_t len;
67925283Sdavidn	FILE *fp;
68099877Syar	char *cp, *mp, *line;
68199877Syar	char *hostname;
682100182Syar	char *vhost, *anonuser, *statfile, *welcome, *loginmsg;
68325283Sdavidn	struct ftphost *hrp, *lhrp;
68456668Sshin	struct addrinfo hints, *res, *ai;
68525283Sdavidn
68625283Sdavidn	/*
68725283Sdavidn	 * Fill in the default host information
68825283Sdavidn	 */
68999877Syar	if ((hostname = malloc(MAXHOSTNAMELEN)) == NULL)
69076096Smarkm		fatalerror("Ran out of memory.");
691137983Syar	if (gethostname(hostname, MAXHOSTNAMELEN - 1) < 0)
69299877Syar		hostname[0] = '\0';
69399877Syar	hostname[MAXHOSTNAMELEN - 1] = '\0';
69499877Syar	if ((hrp = malloc(sizeof(struct ftphost))) == NULL)
69599877Syar		fatalerror("Ran out of memory.");
69699877Syar	hrp->hostname = hostname;
69757124Sshin	hrp->hostinfo = NULL;
69856668Sshin
69956668Sshin	memset(&hints, 0, sizeof(hints));
700156156Sume	hints.ai_flags = AI_PASSIVE;
701156156Sume	hints.ai_family = family;
702156156Sume	hints.ai_socktype = SOCK_STREAM;
703102183Syar	if (getaddrinfo(hrp->hostname, NULL, &hints, &res) == 0)
70457124Sshin		hrp->hostinfo = res;
70525283Sdavidn	hrp->statfile = _PATH_FTPDSTATFILE;
70625283Sdavidn	hrp->welcome  = _PATH_FTPWELCOME;
70725283Sdavidn	hrp->loginmsg = _PATH_FTPLOGINMESG;
70825283Sdavidn	hrp->anonuser = "ftp";
70925283Sdavidn	hrp->next = NULL;
71025283Sdavidn	thishost = firsthost = lhrp = hrp;
71125283Sdavidn	if ((fp = fopen(_PATH_FTPHOSTS, "r")) != NULL) {
712102474Syar		int addrsize, gothost;
71356668Sshin		void *addr;
71456668Sshin		struct hostent *hp;
71556668Sshin
71699877Syar		while ((line = fgetln(fp, &len)) != NULL) {
71756668Sshin			int	i, hp_error;
71825283Sdavidn
71999877Syar			/* skip comments */
72099877Syar			if (line[0] == '#')
72125283Sdavidn				continue;
72299877Syar			if (line[len - 1] == '\n') {
72399877Syar				line[len - 1] = '\0';
72499877Syar				mp = NULL;
72599877Syar			} else {
72699877Syar				if ((mp = malloc(len + 1)) == NULL)
72799877Syar					fatalerror("Ran out of memory.");
72899877Syar				memcpy(mp, line, len);
72999877Syar				mp[len] = '\0';
73099877Syar				line = mp;
73125283Sdavidn			}
73225283Sdavidn			cp = strtok(line, " \t");
73399877Syar			/* skip empty lines */
73499877Syar			if (cp == NULL)
73599877Syar				goto nextline;
736100182Syar			vhost = cp;
73756668Sshin
738100182Syar			/* set defaults */
739100182Syar			anonuser = "ftp";
740100182Syar			statfile = _PATH_FTPDSTATFILE;
741100182Syar			welcome  = _PATH_FTPWELCOME;
742100182Syar			loginmsg = _PATH_FTPLOGINMESG;
743100182Syar
744100182Syar			/*
745100182Syar			 * Preparse the line so we can use its info
746100182Syar			 * for all the addresses associated with
747100182Syar			 * the virtual host name.
748100182Syar			 * Field 0, the virtual host name, is special:
749100182Syar			 * it's already parsed off and will be strdup'ed
750100182Syar			 * later, after we know its canonical form.
751100182Syar			 */
752100182Syar			for (i = 1; i < 5 && (cp = strtok(NULL, " \t")); i++)
753100182Syar				if (*cp != '-' && (cp = strdup(cp)))
754100182Syar					switch (i) {
755100182Syar					case 1:	/* anon user permissions */
756100182Syar						anonuser = cp;
757100182Syar						break;
758100182Syar					case 2: /* statistics file */
759100182Syar						statfile = cp;
760100182Syar						break;
761100182Syar					case 3: /* welcome message */
762100182Syar						welcome  = cp;
763100182Syar						break;
764100182Syar					case 4: /* login message */
765100182Syar						loginmsg = cp;
766100182Syar						break;
767100182Syar					default: /* programming error */
768100182Syar						abort();
769100182Syar						/* NOTREACHED */
770100182Syar					}
771100182Syar
77256668Sshin			hints.ai_flags = AI_PASSIVE;
773156156Sume			hints.ai_family = family;
774156156Sume			hints.ai_socktype = SOCK_STREAM;
775102183Syar			if (getaddrinfo(vhost, NULL, &hints, &res) != 0)
77699877Syar				goto nextline;
77756668Sshin			for (ai = res; ai != NULL && ai->ai_addr != NULL;
77862100Sdavidn			     ai = ai->ai_next) {
77956668Sshin
78062100Sdavidn			gothost = 0;
78125283Sdavidn			for (hrp = firsthost; hrp != NULL; hrp = hrp->next) {
78257124Sshin				struct addrinfo *hi;
78357124Sshin
78457124Sshin				for (hi = hrp->hostinfo; hi != NULL;
78557124Sshin				     hi = hi->ai_next)
78657124Sshin					if (hi->ai_addrlen == ai->ai_addrlen &&
78757124Sshin					    memcmp(hi->ai_addr,
78857124Sshin						   ai->ai_addr,
78962100Sdavidn						   ai->ai_addr->sa_len) == 0) {
79062100Sdavidn						gothost++;
79157124Sshin						break;
792100183Syar					}
79362100Sdavidn				if (gothost)
79462100Sdavidn					break;
79525283Sdavidn			}
79625283Sdavidn			if (hrp == NULL) {
79725283Sdavidn				if ((hrp = malloc(sizeof(struct ftphost))) == NULL)
79899877Syar					goto nextline;
799102183Syar				hrp->hostname = NULL;
800100182Syar				insert = 1;
801102473Syar			} else {
802106754Syar				if (hrp->hostinfo && hrp->hostinfo != res)
803102473Syar					freeaddrinfo(hrp->hostinfo);
804100182Syar				insert = 0; /* host already in the chain */
805102473Syar			}
80657124Sshin			hrp->hostinfo = res;
80757124Sshin
80825283Sdavidn			/*
80925283Sdavidn			 * determine hostname to use.
81056668Sshin			 * force defined name if there is a valid alias
81125283Sdavidn			 * otherwise fallback to primary hostname
81225283Sdavidn			 */
81356668Sshin			/* XXX: getaddrinfo() can't do alias check */
81457124Sshin			switch(hrp->hostinfo->ai_family) {
81556668Sshin			case AF_INET:
816100259Syar				addr = &((struct sockaddr_in *)hrp->hostinfo->ai_addr)->sin_addr;
817100259Syar				addrsize = sizeof(struct in_addr);
81856668Sshin				break;
81956668Sshin			case AF_INET6:
820100259Syar				addr = &((struct sockaddr_in6 *)hrp->hostinfo->ai_addr)->sin6_addr;
821100259Syar				addrsize = sizeof(struct in6_addr);
82256668Sshin				break;
82356668Sshin			default:
82456668Sshin				/* should not reach here */
825102473Syar				freeaddrinfo(hrp->hostinfo);
826102473Syar				if (insert)
827102473Syar					free(hrp); /*not in chain, can free*/
828102473Syar				else
829102473Syar					hrp->hostinfo = NULL; /*mark as blank*/
83099877Syar				goto nextline;
83156668Sshin				/* NOTREACHED */
83256668Sshin			}
833100612Syar			if ((hp = getipnodebyaddr(addr, addrsize,
83457124Sshin						  hrp->hostinfo->ai_family,
83556668Sshin						  &hp_error)) != NULL) {
836100182Syar				if (strcmp(vhost, hp->h_name) != 0) {
83725283Sdavidn					if (hp->h_aliases == NULL)
838100182Syar						vhost = hp->h_name;
83925283Sdavidn					else {
84025283Sdavidn						i = 0;
84125283Sdavidn						while (hp->h_aliases[i] &&
842100182Syar						       strcmp(vhost, hp->h_aliases[i]) != 0)
84325283Sdavidn							++i;
84425283Sdavidn						if (hp->h_aliases[i] == NULL)
845100182Syar							vhost = hp->h_name;
84625283Sdavidn					}
84725283Sdavidn				}
84825283Sdavidn			}
849102183Syar			if (hrp->hostname &&
850102183Syar			    strcmp(hrp->hostname, vhost) != 0) {
851102183Syar				free(hrp->hostname);
852102183Syar				hrp->hostname = NULL;
853102183Syar			}
854102183Syar			if (hrp->hostname == NULL &&
855102473Syar			    (hrp->hostname = strdup(vhost)) == NULL) {
856102473Syar				freeaddrinfo(hrp->hostinfo);
857102473Syar				hrp->hostinfo = NULL; /* mark as blank */
858102473Syar				if (hp)
859102473Syar					freehostent(hp);
860100182Syar				goto nextline;
861102473Syar			}
862100182Syar			hrp->anonuser = anonuser;
863100182Syar			hrp->statfile = statfile;
864100182Syar			hrp->welcome  = welcome;
865100182Syar			hrp->loginmsg = loginmsg;
866100182Syar			if (insert) {
867100182Syar				hrp->next  = NULL;
868100182Syar				lhrp->next = hrp;
869100182Syar				lhrp = hrp;
870100182Syar			}
871100263Syar			if (hp)
872100263Syar				freehostent(hp);
87356668Sshin		      }
87499877Syarnextline:
87599877Syar			if (mp)
87699877Syar				free(mp);
87725283Sdavidn		}
87825283Sdavidn		(void) fclose(fp);
87925283Sdavidn	}
88025283Sdavidn}
88125283Sdavidn
88225283Sdavidnstatic void
88390148Simpselecthost(union sockunion *su)
88425283Sdavidn{
88525283Sdavidn	struct ftphost	*hrp;
88656668Sshin	u_int16_t port;
88756668Sshin#ifdef INET6
88856668Sshin	struct in6_addr *mapped_in6 = NULL;
88956668Sshin#endif
89057124Sshin	struct addrinfo *hi;
89125283Sdavidn
89256668Sshin#ifdef INET6
89356668Sshin	/*
89456668Sshin	 * XXX IPv4 mapped IPv6 addr consideraton,
89556668Sshin	 * specified in rfc2373.
89656668Sshin	 */
89756668Sshin	if (su->su_family == AF_INET6 &&
89856668Sshin	    IN6_IS_ADDR_V4MAPPED(&su->su_sin6.sin6_addr))
89956668Sshin		mapped_in6 = &su->su_sin6.sin6_addr;
90056668Sshin#endif
90156668Sshin
90225283Sdavidn	hrp = thishost = firsthost;	/* default */
90356668Sshin	port = su->su_port;
90456668Sshin	su->su_port = 0;
90525283Sdavidn	while (hrp != NULL) {
90662100Sdavidn	    for (hi = hrp->hostinfo; hi != NULL; hi = hi->ai_next) {
90757124Sshin		if (memcmp(su, hi->ai_addr, hi->ai_addrlen) == 0) {
90825283Sdavidn			thishost = hrp;
909137987Syar			goto found;
91025283Sdavidn		}
91156668Sshin#ifdef INET6
91256668Sshin		/* XXX IPv4 mapped IPv6 addr consideraton */
91357124Sshin		if (hi->ai_addr->sa_family == AF_INET && mapped_in6 != NULL &&
91456668Sshin		    (memcmp(&mapped_in6->s6_addr[12],
91557124Sshin			    &((struct sockaddr_in *)hi->ai_addr)->sin_addr,
91656668Sshin			    sizeof(struct in_addr)) == 0)) {
91756668Sshin			thishost = hrp;
918137987Syar			goto found;
91956668Sshin		}
92056668Sshin#endif
92162100Sdavidn	    }
92262100Sdavidn	    hrp = hrp->next;
92325283Sdavidn	}
924137987Syarfound:
92556668Sshin	su->su_port = port;
92625283Sdavidn	/* setup static variables as appropriate */
92725283Sdavidn	hostname = thishost->hostname;
92825283Sdavidn	ftpuser = thishost->anonuser;
92925283Sdavidn}
93025283Sdavidn#endif
93125283Sdavidn
93225283Sdavidn/*
9331592Srgrimes * Helper function for sgetpwnam().
9341592Srgrimes */
9351592Srgrimesstatic char *
93690148Simpsgetsave(char *s)
9371592Srgrimes{
938137659Syar	char *new = malloc(strlen(s) + 1);
9391592Srgrimes
9401592Srgrimes	if (new == NULL) {
941137852Syar		reply(421, "Ran out of memory.");
9421592Srgrimes		dologout(1);
9431592Srgrimes		/* NOTREACHED */
9441592Srgrimes	}
9451592Srgrimes	(void) strcpy(new, s);
9461592Srgrimes	return (new);
9471592Srgrimes}
9481592Srgrimes
9491592Srgrimes/*
9501592Srgrimes * Save the result of a getpwnam.  Used for USER command, since
9511592Srgrimes * the data returned must not be clobbered by any other command
9521592Srgrimes * (e.g., globbing).
953137076Syar * NB: The data returned by sgetpwnam() will remain valid until
954137076Syar * the next call to this function.  Its difference from getpwnam()
955137076Syar * is that sgetpwnam() is known to be called from ftpd code only.
9561592Srgrimes */
9571592Srgrimesstatic struct passwd *
95890148Simpsgetpwnam(char *name)
9591592Srgrimes{
9601592Srgrimes	static struct passwd save;
9611592Srgrimes	struct passwd *p;
9621592Srgrimes
9631592Srgrimes	if ((p = getpwnam(name)) == NULL)
9641592Srgrimes		return (p);
9651592Srgrimes	if (save.pw_name) {
9661592Srgrimes		free(save.pw_name);
9671592Srgrimes		free(save.pw_passwd);
968262284Sbrueffer		free(save.pw_class);
9691592Srgrimes		free(save.pw_gecos);
9701592Srgrimes		free(save.pw_dir);
9711592Srgrimes		free(save.pw_shell);
9721592Srgrimes	}
9731592Srgrimes	save = *p;
9741592Srgrimes	save.pw_name = sgetsave(p->pw_name);
9751592Srgrimes	save.pw_passwd = sgetsave(p->pw_passwd);
976262284Sbrueffer	save.pw_class = sgetsave(p->pw_class);
9771592Srgrimes	save.pw_gecos = sgetsave(p->pw_gecos);
9781592Srgrimes	save.pw_dir = sgetsave(p->pw_dir);
9791592Srgrimes	save.pw_shell = sgetsave(p->pw_shell);
9801592Srgrimes	return (&save);
9811592Srgrimes}
9821592Srgrimes
9831592Srgrimesstatic int login_attempts;	/* number of failed login attempts */
9841592Srgrimesstatic int askpasswd;		/* had user command, ask for passwd */
98564778Ssheldonhstatic char curname[MAXLOGNAME];	/* current USER name */
9861592Srgrimes
9871592Srgrimes/*
9881592Srgrimes * USER command.
9891592Srgrimes * Sets global passwd pointer pw if named account exists and is acceptable;
9901592Srgrimes * sets askpasswd if a PASS command is expected.  If logged in previously,
9911592Srgrimes * need to reset state.  If name is "ftp" or "anonymous", the name is not in
9921592Srgrimes * _PATH_FTPUSERS, and ftp account exists, set guest and pw, then just return.
9931592Srgrimes * If account doesn't exist, ask for passwd anyway.  Otherwise, check user
9941592Srgrimes * requesting login privileges.  Disallow anyone who does not have a standard
9951592Srgrimes * shell as returned by getusershell().  Disallow anyone mentioned in the file
9961592Srgrimes * _PATH_FTPUSERS to allow people such as root and uucp to be avoided.
9971592Srgrimes */
9981592Srgrimesvoid
99990148Simpuser(char *name)
10001592Srgrimes{
1001216932Scsjp	int ecode;
10021592Srgrimes	char *cp, *shell;
10031592Srgrimes
10041592Srgrimes	if (logged_in) {
10051592Srgrimes		if (guest) {
10061592Srgrimes			reply(530, "Can't change user from guest login.");
10071592Srgrimes			return;
100817435Spst		} else if (dochroot) {
100917435Spst			reply(530, "Can't change user from chroot user.");
101017435Spst			return;
10111592Srgrimes		}
10121592Srgrimes		end_login();
10131592Srgrimes	}
10141592Srgrimes
10151592Srgrimes	guest = 0;
1016130428Sobrien#ifdef VIRTUAL_HOSTING
1017130428Sobrien	pw = sgetpwnam(thishost->anonuser);
1018130428Sobrien#else
1019130428Sobrien	pw = sgetpwnam("ftp");
1020130428Sobrien#endif
10211592Srgrimes	if (strcmp(name, "ftp") == 0 || strcmp(name, "anonymous") == 0) {
1022216932Scsjp		if (checkuser(_PATH_FTPUSERS, "ftp", 0, NULL, &ecode) ||
1023216932Scsjp		    (ecode != 0 && ecode != ENOENT))
10241592Srgrimes			reply(530, "User %s access denied.", name);
1025216932Scsjp		else if (checkuser(_PATH_FTPUSERS, "anonymous", 0, NULL, &ecode) ||
1026216932Scsjp		    (ecode != 0 && ecode != ENOENT))
1027216932Scsjp			reply(530, "User %s access denied.", name);
1028130428Sobrien		else if (pw != NULL) {
10291592Srgrimes			guest = 1;
10301592Srgrimes			askpasswd = 1;
10311592Srgrimes			reply(331,
10323938Spst			"Guest login ok, send your email address as password.");
10331592Srgrimes		} else
10341592Srgrimes			reply(530, "User %s unknown.", name);
10351592Srgrimes		if (!askpasswd && logging)
10361592Srgrimes			syslog(LOG_NOTICE,
10371592Srgrimes			    "ANONYMOUS FTP LOGIN REFUSED FROM %s", remotehost);
10381592Srgrimes		return;
10391592Srgrimes	}
104020042Storstenb	if (anon_only != 0) {
104120042Storstenb		reply(530, "Sorry, only anonymous ftp allowed.");
104220042Storstenb		return;
104320042Storstenb	}
104420042Storstenb
104517478Smarkm	if ((pw = sgetpwnam(name))) {
10461592Srgrimes		if ((shell = pw->pw_shell) == NULL || *shell == 0)
10471592Srgrimes			shell = _PATH_BSHELL;
1048124687Scharnier		setusershell();
10491592Srgrimes		while ((cp = getusershell()) != NULL)
10501592Srgrimes			if (strcmp(cp, shell) == 0)
10511592Srgrimes				break;
10521592Srgrimes		endusershell();
10531592Srgrimes
1054216932Scsjp		if (cp == NULL ||
1055216932Scsjp		    (checkuser(_PATH_FTPUSERS, name, 1, NULL, &ecode) ||
1056216932Scsjp		    (ecode != 0 && ecode != ENOENT))) {
10571592Srgrimes			reply(530, "User %s access denied.", name);
10581592Srgrimes			if (logging)
10591592Srgrimes				syslog(LOG_NOTICE,
10601592Srgrimes				    "FTP LOGIN REFUSED FROM %s, %s",
10611592Srgrimes				    remotehost, name);
1062137811Syar			pw = NULL;
10631592Srgrimes			return;
10641592Srgrimes		}
10651592Srgrimes	}
10661592Srgrimes	if (logging)
1067327019Sdelphij		strlcpy(curname, name, sizeof(curname));
106888763Sache
106988763Sache	pwok = 0;
107079469Smarkm#ifdef USE_PAM
107179469Smarkm	/* XXX Kluge! The conversation mechanism needs to be fixed. */
107288763Sache#endif
107388763Sache	if (opiechallenge(&opiedata, name, opieprompt) == 0) {
107488763Sache		pwok = (pw != NULL) &&
107588763Sache		       opieaccessfile(remotehost) &&
107688763Sache		       opiealways(pw->pw_dir);
107788763Sache		reply(331, "Response to %s %s for %s.",
107888763Sache		      opieprompt, pwok ? "requested" : "required", name);
107988763Sache	} else {
108088763Sache		pwok = 1;
108184146Sache		reply(331, "Password required for %s.", name);
108288763Sache	}
10831592Srgrimes	askpasswd = 1;
10841592Srgrimes	/*
10851592Srgrimes	 * Delay before reading passwd after first failed
10861592Srgrimes	 * attempt to slow down passwd-guessing programs.
10871592Srgrimes	 */
10881592Srgrimes	if (login_attempts)
1089137659Syar		sleep(login_attempts);
10901592Srgrimes}
10911592Srgrimes
10921592Srgrimes/*
1093109893Syar * Check if a user is in the file "fname",
1094109893Syar * return a pointer to a malloc'd string with the rest
1095109893Syar * of the matching line in "residue" if not NULL.
10961592Srgrimes */
10971592Srgrimesstatic int
1098216932Scsjpcheckuser(char *fname, char *name, int pwset, char **residue, int *ecode)
10991592Srgrimes{
11001592Srgrimes	FILE *fd;
11011592Srgrimes	int found = 0;
110299877Syar	size_t len;
110399877Syar	char *line, *mp, *p;
11041592Srgrimes
1105216932Scsjp	if (ecode != NULL)
1106216932Scsjp		*ecode = 0;
110717435Spst	if ((fd = fopen(fname, "r")) != NULL) {
110899877Syar		while (!found && (line = fgetln(fd, &len)) != NULL) {
110999877Syar			/* skip comments */
111099877Syar			if (line[0] == '#')
111199877Syar				continue;
111299877Syar			if (line[len - 1] == '\n') {
111399877Syar				line[len - 1] = '\0';
111499877Syar				mp = NULL;
111599877Syar			} else {
111699877Syar				if ((mp = malloc(len + 1)) == NULL)
111799877Syar					fatalerror("Ran out of memory.");
111899877Syar				memcpy(mp, line, len);
111999877Syar				mp[len] = '\0';
112099877Syar				line = mp;
112199877Syar			}
112299877Syar			/* avoid possible leading and trailing whitespace */
112399877Syar			p = strtok(line, " \t");
112499877Syar			/* skip empty lines */
112599877Syar			if (p == NULL)
112699877Syar				goto nextline;
112799877Syar			/*
112899877Syar			 * if first chr is '@', check group membership
112999877Syar			 */
113099877Syar			if (p[0] == '@') {
113199877Syar				int i = 0;
113299877Syar				struct group *grp;
113399877Syar
1134109893Syar				if (p[1] == '\0') /* single @ matches anyone */
113599877Syar					found = 1;
1136109893Syar				else {
1137109893Syar					if ((grp = getgrnam(p+1)) == NULL)
1138109893Syar						goto nextline;
1139109893Syar					/*
1140109893Syar					 * Check user's default group
1141109893Syar					 */
1142109893Syar					if (pwset && grp->gr_gid == pw->pw_gid)
1143109893Syar						found = 1;
1144109893Syar					/*
1145109893Syar					 * Check supplementary groups
1146109893Syar					 */
1147109893Syar					while (!found && grp->gr_mem[i])
1148109893Syar						found = strcmp(name,
1149109893Syar							grp->gr_mem[i++])
1150109893Syar							== 0;
1151109893Syar				}
11521592Srgrimes			}
115399877Syar			/*
115499877Syar			 * Otherwise, just check for username match
115599877Syar			 */
115699877Syar			else
115799877Syar				found = strcmp(p, name) == 0;
1158109893Syar			/*
1159109893Syar			 * Save the rest of line to "residue" if matched
1160109893Syar			 */
1161109893Syar			if (found && residue) {
1162109938Syar				if ((p = strtok(NULL, "")) != NULL)
1163109938Syar					p += strspn(p, " \t");
1164109938Syar				if (p && *p) {
1165109893Syar				 	if ((*residue = strdup(p)) == NULL)
1166109893Syar						fatalerror("Ran out of memory.");
1167109893Syar				} else
1168109893Syar					*residue = NULL;
1169109893Syar			}
117099877Syarnextline:
117199877Syar			if (mp)
117299877Syar				free(mp);
117399877Syar		}
11741592Srgrimes		(void) fclose(fd);
1175216932Scsjp	} else if (ecode != NULL)
1176216932Scsjp		*ecode = errno;
11771592Srgrimes	return (found);
11781592Srgrimes}
11791592Srgrimes
11801592Srgrimes/*
11811592Srgrimes * Terminate login as previous user, if any, resetting state;
11821592Srgrimes * used when USER command is given or login fails.
11831592Srgrimes */
11841592Srgrimesstatic void
118590148Simpend_login(void)
11861592Srgrimes{
118774874Smarkm#ifdef USE_PAM
118874874Smarkm	int e;
118974874Smarkm#endif
11901592Srgrimes
1191132893Syar	(void) seteuid(0);
119225101Sdavidn#ifdef	LOGIN_CAP
1193223434Strasz	setusercontext(NULL, getpwuid(0), 0, LOGIN_SETALL & ~(LOGIN_SETLOGIN |
1194223434Strasz		       LOGIN_SETUSER | LOGIN_SETGROUP | LOGIN_SETPATH |
1195223434Strasz		       LOGIN_SETENV));
119625101Sdavidn#endif
1197325472Seugen	if (logged_in && dowtmp)
1198325472Seugen		ftpd_logwtmp(wtmpid, NULL, NULL);
1199325472Seugen	pw = NULL;
120074874Smarkm#ifdef USE_PAM
1201137078Syar	if (pamh) {
1202137078Syar		if ((e = pam_setcred(pamh, PAM_DELETE_CRED)) != PAM_SUCCESS)
1203137078Syar			syslog(LOG_ERR, "pam_setcred: %s", pam_strerror(pamh, e));
1204137078Syar		if ((e = pam_close_session(pamh,0)) != PAM_SUCCESS)
1205137078Syar			syslog(LOG_ERR, "pam_close_session: %s", pam_strerror(pamh, e));
1206137078Syar		if ((e = pam_end(pamh, e)) != PAM_SUCCESS)
1207137078Syar			syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e));
1208137078Syar		pamh = NULL;
1209137078Syar	}
121074874Smarkm#endif
12111592Srgrimes	logged_in = 0;
12121592Srgrimes	guest = 0;
121317435Spst	dochroot = 0;
12141592Srgrimes}
12151592Srgrimes
121674874Smarkm#ifdef USE_PAM
121751433Smarkm
121851433Smarkm/*
121951433Smarkm * the following code is stolen from imap-uw PAM authentication module and
122051433Smarkm * login.c
122151433Smarkm */
122251433Smarkm#define COPY_STRING(s) (s ? strdup(s) : NULL)
122351433Smarkm
122451433Smarkmstruct cred_t {
122551433Smarkm	const char *uname;		/* user name */
122651433Smarkm	const char *pass;		/* password */
122751433Smarkm};
122851433Smarkmtypedef struct cred_t cred_t;
122951433Smarkm
123051433Smarkmstatic int
123151433Smarkmauth_conv(int num_msg, const struct pam_message **msg,
123251433Smarkm	  struct pam_response **resp, void *appdata)
123351433Smarkm{
123451433Smarkm	int i;
123551433Smarkm	cred_t *cred = (cred_t *) appdata;
123691244Sdes	struct pam_response *reply;
123751433Smarkm
123891244Sdes	reply = calloc(num_msg, sizeof *reply);
123991244Sdes	if (reply == NULL)
124091244Sdes		return PAM_BUF_ERR;
124191244Sdes
124251433Smarkm	for (i = 0; i < num_msg; i++) {
124351433Smarkm		switch (msg[i]->msg_style) {
124451433Smarkm		case PAM_PROMPT_ECHO_ON:	/* assume want user name */
124551433Smarkm			reply[i].resp_retcode = PAM_SUCCESS;
124651433Smarkm			reply[i].resp = COPY_STRING(cred->uname);
124751433Smarkm			/* PAM frees resp. */
124851433Smarkm			break;
124951433Smarkm		case PAM_PROMPT_ECHO_OFF:	/* assume want password */
125051433Smarkm			reply[i].resp_retcode = PAM_SUCCESS;
125151433Smarkm			reply[i].resp = COPY_STRING(cred->pass);
125251433Smarkm			/* PAM frees resp. */
125351433Smarkm			break;
125451433Smarkm		case PAM_TEXT_INFO:
125551433Smarkm		case PAM_ERROR_MSG:
125651433Smarkm			reply[i].resp_retcode = PAM_SUCCESS;
125751433Smarkm			reply[i].resp = NULL;
125851433Smarkm			break;
125951433Smarkm		default:			/* unknown message style */
126051433Smarkm			free(reply);
126151433Smarkm			return PAM_CONV_ERR;
126251433Smarkm		}
126351433Smarkm	}
126451433Smarkm
126551433Smarkm	*resp = reply;
126651433Smarkm	return PAM_SUCCESS;
126751433Smarkm}
126851433Smarkm
126951433Smarkm/*
127051433Smarkm * Attempt to authenticate the user using PAM.  Returns 0 if the user is
127151433Smarkm * authenticated, or 1 if not authenticated.  If some sort of PAM system
127251433Smarkm * error occurs (e.g., the "/etc/pam.conf" file is missing) then this
127351433Smarkm * function returns -1.  This can be used as an indication that we should
127451433Smarkm * fall back to a different authentication mechanism.
127551433Smarkm */
127651433Smarkmstatic int
127751433Smarkmauth_pam(struct passwd **ppw, const char *pass)
127851433Smarkm{
127951433Smarkm	const char *tmpl_user;
128051433Smarkm	const void *item;
128151433Smarkm	int rval;
128251433Smarkm	int e;
128351433Smarkm	cred_t auth_cred = { (*ppw)->pw_name, pass };
128451433Smarkm	struct pam_conv conv = { &auth_conv, &auth_cred };
128551433Smarkm
128651433Smarkm	e = pam_start("ftpd", (*ppw)->pw_name, &conv, &pamh);
128751433Smarkm	if (e != PAM_SUCCESS) {
1288137108Syar		/*
1289137108Syar		 * In OpenPAM, it's OK to pass NULL to pam_strerror()
1290137108Syar		 * if context creation has failed in the first place.
1291137108Syar		 */
1292137108Syar		syslog(LOG_ERR, "pam_start: %s", pam_strerror(NULL, e));
129351433Smarkm		return -1;
129451433Smarkm	}
129551433Smarkm
129667007Sguido	e = pam_set_item(pamh, PAM_RHOST, remotehost);
129767007Sguido	if (e != PAM_SUCCESS) {
129867007Sguido		syslog(LOG_ERR, "pam_set_item(PAM_RHOST): %s",
129967007Sguido			pam_strerror(pamh, e));
1300137078Syar		if ((e = pam_end(pamh, e)) != PAM_SUCCESS) {
1301137078Syar			syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e));
1302137078Syar		}
1303137078Syar		pamh = NULL;
130467007Sguido		return -1;
130567007Sguido	}
130667007Sguido
130751433Smarkm	e = pam_authenticate(pamh, 0);
130851433Smarkm	switch (e) {
130951433Smarkm	case PAM_SUCCESS:
131051433Smarkm		/*
131151433Smarkm		 * With PAM we support the concept of a "template"
131251433Smarkm		 * user.  The user enters a login name which is
131351433Smarkm		 * authenticated by PAM, usually via a remote service
131451433Smarkm		 * such as RADIUS or TACACS+.  If authentication
131551433Smarkm		 * succeeds, a different but related "template" name
131651433Smarkm		 * is used for setting the credentials, shell, and
131751433Smarkm		 * home directory.  The name the user enters need only
131851433Smarkm		 * exist on the remote authentication server, but the
131951433Smarkm		 * template name must be present in the local password
132051433Smarkm		 * database.
132151433Smarkm		 *
132251433Smarkm		 * This is supported by two various mechanisms in the
132351433Smarkm		 * individual modules.  However, from the application's
132451433Smarkm		 * point of view, the template user is always passed
132551433Smarkm		 * back as a changed value of the PAM_USER item.
132651433Smarkm		 */
132751433Smarkm		if ((e = pam_get_item(pamh, PAM_USER, &item)) ==
132851433Smarkm		    PAM_SUCCESS) {
132951433Smarkm			tmpl_user = (const char *) item;
133051433Smarkm			if (strcmp((*ppw)->pw_name, tmpl_user) != 0)
133151433Smarkm				*ppw = getpwnam(tmpl_user);
133251433Smarkm		} else
133351433Smarkm			syslog(LOG_ERR, "Couldn't get PAM_USER: %s",
133451433Smarkm			    pam_strerror(pamh, e));
133551433Smarkm		rval = 0;
133651433Smarkm		break;
133751433Smarkm
133851433Smarkm	case PAM_AUTH_ERR:
133951433Smarkm	case PAM_USER_UNKNOWN:
134051433Smarkm	case PAM_MAXTRIES:
134151433Smarkm		rval = 1;
134251433Smarkm		break;
134351433Smarkm
134451433Smarkm	default:
134574874Smarkm		syslog(LOG_ERR, "pam_authenticate: %s", pam_strerror(pamh, e));
134651433Smarkm		rval = -1;
134751433Smarkm		break;
134851433Smarkm	}
134951433Smarkm
135074874Smarkm	if (rval == 0) {
135174874Smarkm		e = pam_acct_mgmt(pamh, 0);
1352137986Syar		if (e != PAM_SUCCESS) {
1353137986Syar			syslog(LOG_ERR, "pam_acct_mgmt: %s",
1354137986Syar						pam_strerror(pamh, e));
135574874Smarkm			rval = 1;
135674874Smarkm		}
135751433Smarkm	}
135874874Smarkm
135974874Smarkm	if (rval != 0) {
136074874Smarkm		if ((e = pam_end(pamh, e)) != PAM_SUCCESS) {
136174874Smarkm			syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e));
136274874Smarkm		}
136374874Smarkm		pamh = NULL;
136474874Smarkm	}
136551433Smarkm	return rval;
136651433Smarkm}
136751433Smarkm
136874874Smarkm#endif /* USE_PAM */
136951433Smarkm
13701592Srgrimesvoid
137190148Simppass(char *passwd)
13721592Srgrimes{
1373216932Scsjp	int rval, ecode;
13741592Srgrimes	FILE *fd;
137525101Sdavidn#ifdef	LOGIN_CAP
137625101Sdavidn	login_cap_t *lc = NULL;
137725101Sdavidn#endif
137874874Smarkm#ifdef USE_PAM
137974874Smarkm	int e;
138074874Smarkm#endif
1381109939Syar	char *residue = NULL;
138288763Sache	char *xpasswd;
13831592Srgrimes
13841592Srgrimes	if (logged_in || askpasswd == 0) {
13851592Srgrimes		reply(503, "Login with USER first.");
13861592Srgrimes		return;
13871592Srgrimes	}
13881592Srgrimes	askpasswd = 0;
13891592Srgrimes	if (!guest) {		/* "ftp" is only account allowed no password */
139017435Spst		if (pw == NULL) {
139117435Spst			rval = 1;	/* failure below */
139217435Spst			goto skip;
139317435Spst		}
139474874Smarkm#ifdef USE_PAM
139551433Smarkm		rval = auth_pam(&pw, passwd);
139689622Sache		if (rval >= 0) {
139789622Sache			opieunlock();
139817435Spst			goto skip;
139989622Sache		}
140089622Sache#endif
140188763Sache		if (opieverify(&opiedata, passwd) == 0)
140288763Sache			xpasswd = pw->pw_passwd;
140389622Sache		else if (pwok) {
140488763Sache			xpasswd = crypt(passwd, pw->pw_passwd);
140589622Sache			if (passwd[0] == '\0' && pw->pw_passwd[0] != '\0')
140689622Sache				xpasswd = ":";
140789622Sache		} else {
140888763Sache			rval = 1;
140988763Sache			goto skip;
141088763Sache		}
141188763Sache		rval = strcmp(pw->pw_passwd, xpasswd);
141289622Sache		if (pw->pw_expire && time(NULL) >= pw->pw_expire)
141317435Spst			rval = 1;	/* failure */
141417435Spstskip:
141517435Spst		/*
141617435Spst		 * If rval == 1, the user failed the authentication check
141751433Smarkm		 * above.  If rval == 0, either PAM or local authentication
141817435Spst		 * succeeded.
141917435Spst		 */
142017435Spst		if (rval) {
14211592Srgrimes			reply(530, "Login incorrect.");
1422110691Syar			if (logging) {
14231592Srgrimes				syslog(LOG_NOTICE,
1424110691Syar				    "FTP LOGIN FAILED FROM %s",
1425110691Syar				    remotehost);
1426110691Syar				syslog(LOG_AUTHPRIV | LOG_NOTICE,
14271592Srgrimes				    "FTP LOGIN FAILED FROM %s, %s",
14281592Srgrimes				    remotehost, curname);
1429110691Syar			}
14301592Srgrimes			pw = NULL;
14311592Srgrimes			if (login_attempts++ >= 5) {
14321592Srgrimes				syslog(LOG_NOTICE,
14331592Srgrimes				    "repeated login failures from %s",
14341592Srgrimes				    remotehost);
14351592Srgrimes				exit(0);
14361592Srgrimes			}
14371592Srgrimes			return;
14381592Srgrimes		}
14391592Srgrimes	}
14401592Srgrimes	login_attempts = 0;		/* this time successful */
1441132894Syar	if (setegid(pw->pw_gid) < 0) {
14421592Srgrimes		reply(550, "Can't set gid.");
14431592Srgrimes		return;
14441592Srgrimes	}
144525101Sdavidn	/* May be overridden by login.conf */
144625101Sdavidn	(void) umask(defumask);
144725101Sdavidn#ifdef	LOGIN_CAP
144825674Sdavidn	if ((lc = login_getpwclass(pw)) != NULL) {
1449137983Syar		char	remote_ip[NI_MAXHOST];
145025101Sdavidn
1451137983Syar		if (getnameinfo((struct sockaddr *)&his_addr, his_addr.su_len,
145256668Sshin			remote_ip, sizeof(remote_ip) - 1, NULL, 0,
1453137983Syar			NI_NUMERICHOST))
1454137983Syar				*remote_ip = 0;
145525101Sdavidn		remote_ip[sizeof(remote_ip) - 1] = 0;
145625101Sdavidn		if (!auth_hostok(lc, remotehost, remote_ip)) {
145725101Sdavidn			syslog(LOG_INFO|LOG_AUTH,
145825101Sdavidn			    "FTP LOGIN FAILED (HOST) as %s: permission denied.",
145925101Sdavidn			    pw->pw_name);
1460137849Syar			reply(530, "Permission denied.");
146125101Sdavidn			pw = NULL;
146225101Sdavidn			return;
146325101Sdavidn		}
146425101Sdavidn		if (!auth_timeok(lc, time(NULL))) {
1465137849Syar			reply(530, "Login not available right now.");
146625101Sdavidn			pw = NULL;
146725101Sdavidn			return;
146825101Sdavidn		}
146925101Sdavidn	}
1470223434Strasz	setusercontext(lc, pw, 0, LOGIN_SETALL &
1471325472Seugen		       ~(LOGIN_SETRESOURCES | LOGIN_SETUSER | LOGIN_SETPATH | LOGIN_SETENV));
147225101Sdavidn#else
147340310Sdes	setlogin(pw->pw_name);
14741592Srgrimes	(void) initgroups(pw->pw_name, pw->pw_gid);
147525101Sdavidn#endif
14761592Srgrimes
147774874Smarkm#ifdef USE_PAM
147874874Smarkm	if (pamh) {
147974874Smarkm		if ((e = pam_open_session(pamh, 0)) != PAM_SUCCESS) {
148074874Smarkm			syslog(LOG_ERR, "pam_open_session: %s", pam_strerror(pamh, e));
148174874Smarkm		} else if ((e = pam_setcred(pamh, PAM_ESTABLISH_CRED)) != PAM_SUCCESS) {
148274874Smarkm			syslog(LOG_ERR, "pam_setcred: %s", pam_strerror(pamh, e));
148374874Smarkm		}
148474874Smarkm	}
148574874Smarkm#endif
148674874Smarkm
1487202209Sed	dochroot =
1488216932Scsjp		checkuser(_PATH_FTPCHROOT, pw->pw_name, 1, &residue, &ecode)
1489202209Sed#ifdef	LOGIN_CAP	/* Allow login.conf configuration as well */
1490202209Sed		|| login_getcapbool(lc, "ftp-chroot", 0)
1491202209Sed#endif
1492202209Sed	;
1493216932Scsjp	/*
1494216932Scsjp	 * It is possible that checkuser() failed to open the chroot file.
1495216932Scsjp	 * If this is the case, report that logins are un-available, since we
1496216932Scsjp	 * have no way of checking whether or not the user should be chrooted.
1497216932Scsjp	 * We ignore ENOENT since it is not required that this file be present.
1498216932Scsjp	 */
1499216932Scsjp	if (ecode != 0 && ecode != ENOENT) {
1500216932Scsjp		reply(530, "Login not available right now.");
1501216932Scsjp		return;
1502216932Scsjp	}
1503202209Sed	chrootdir = NULL;
1504202209Sed
1505202604Sed	/* Disable wtmp logging when chrooting. */
1506202604Sed	if (dochroot || guest)
1507202604Sed		dowtmp = 0;
1508202604Sed	if (dowtmp)
1509202209Sed		ftpd_logwtmp(wtmpid, pw->pw_name,
1510102311Syar		    (struct sockaddr *)&his_addr);
15111592Srgrimes	logged_in = 1;
15121592Srgrimes
1513325472Seugen#ifdef	LOGIN_CAP
1514325472Seugen	setusercontext(lc, pw, 0, LOGIN_SETRESOURCES);
1515325472Seugen#endif
1516325472Seugen
151717435Spst	if (guest && stats && statfd < 0)
151825283Sdavidn#ifdef VIRTUAL_HOSTING
1519130428Sobrien		statfd = open(thishost->statfile, O_WRONLY|O_APPEND);
152025283Sdavidn#else
1521130428Sobrien		statfd = open(_PATH_FTPDSTATFILE, O_WRONLY|O_APPEND);
152225283Sdavidn#endif
1523130428Sobrien		if (statfd < 0)
15246740Sguido			stats = 0;
15256740Sguido
1526110036Syar	/*
1527110036Syar	 * For a chrooted local user,
1528110036Syar	 * a) see whether ftpchroot(5) specifies a chroot directory,
1529110036Syar	 * b) extract the directory pathname from the line,
1530110036Syar	 * c) expand it to the absolute pathname if necessary.
1531110036Syar	 */
1532110036Syar	if (dochroot && residue &&
1533117349Syar	    (chrootdir = strtok(residue, " \t")) != NULL) {
1534117349Syar		if (chrootdir[0] != '/')
1535117349Syar			asprintf(&chrootdir, "%s/%s", pw->pw_dir, chrootdir);
1536117349Syar		else
1537137862Syar			chrootdir = strdup(chrootdir); /* make it permanent */
1538110036Syar		if (chrootdir == NULL)
1539110036Syar			fatalerror("Ran out of memory.");
1540110036Syar	}
1541110036Syar	if (guest || dochroot) {
15421592Srgrimes		/*
1543110036Syar		 * If no chroot directory set yet, use the login directory.
1544110036Syar		 * Copy it so it can be modified while pw->pw_dir stays intact.
15451592Srgrimes		 */
1546110036Syar		if (chrootdir == NULL &&
1547110036Syar		    (chrootdir = strdup(pw->pw_dir)) == NULL)
1548110036Syar			fatalerror("Ran out of memory.");
1549110036Syar		/*
1550110036Syar		 * Check for the "/chroot/./home" syntax,
1551110036Syar		 * separate the chroot and home directory pathnames.
1552110036Syar		 */
1553110036Syar		if ((homedir = strstr(chrootdir, "/./")) != NULL) {
1554110036Syar			*(homedir++) = '\0';	/* wipe '/' */
1555110036Syar			homedir++;		/* skip '.' */
1556110036Syar		} else {
1557110036Syar			/*
1558110036Syar			 * We MUST do a chdir() after the chroot. Otherwise
1559110036Syar			 * the old current directory will be accessible as "."
1560110036Syar			 * outside the new root!
1561110036Syar			 */
1562110036Syar			homedir = "/";
1563109939Syar		}
1564110036Syar		/*
1565110036Syar		 * Finally, do chroot()
1566110036Syar		 */
1567110036Syar		if (chroot(chrootdir) < 0) {
156817435Spst			reply(550, "Can't change root.");
156917435Spst			goto bad;
157017435Spst		}
1571228843Scperciva		__FreeBSD_libc_enter_restricted_mode();
1572110036Syar	} else	/* real user w/o chroot */
1573110036Syar		homedir = pw->pw_dir;
1574110036Syar	/*
1575110036Syar	 * Set euid *before* doing chdir() so
1576110036Syar	 * a) the user won't be carried to a directory that he couldn't reach
1577110036Syar	 *    on his own due to no permission to upper path components,
1578110036Syar	 * b) NFS mounted homedirs w/restrictive permissions will be accessible
1579110036Syar	 *    (uid 0 has no root power over NFS if not mapped explicitly.)
1580110036Syar	 */
1581132893Syar	if (seteuid(pw->pw_uid) < 0) {
15821592Srgrimes		reply(550, "Can't set uid.");
15831592Srgrimes		goto bad;
15841592Srgrimes	}
1585110036Syar	if (chdir(homedir) < 0) {
1586110036Syar		if (guest || dochroot) {
1587110036Syar			reply(550, "Can't change to base directory.");
1588110036Syar			goto bad;
1589110036Syar		} else {
1590110036Syar			if (chdir("/") < 0) {
1591110036Syar				reply(550, "Root is inaccessible.");
1592110036Syar				goto bad;
1593110036Syar			}
1594137852Syar			lreply(230, "No directory! Logging in with home=/.");
1595110036Syar		}
1596110036Syar	}
15978696Sdg
15981592Srgrimes	/*
15991592Srgrimes	 * Display a login message, if it exists.
16001592Srgrimes	 * N.B. reply(230,) must follow the message.
16011592Srgrimes	 */
160225283Sdavidn#ifdef VIRTUAL_HOSTING
1603130428Sobrien	fd = fopen(thishost->loginmsg, "r");
160425283Sdavidn#else
1605130428Sobrien	fd = fopen(_PATH_FTPLOGINMESG, "r");
160625283Sdavidn#endif
1607130428Sobrien	if (fd != NULL) {
16081592Srgrimes		char *cp, line[LINE_MAX];
16091592Srgrimes
16101592Srgrimes		while (fgets(line, sizeof(line), fd) != NULL) {
16111592Srgrimes			if ((cp = strchr(line, '\n')) != NULL)
16121592Srgrimes				*cp = '\0';
16131592Srgrimes			lreply(230, "%s", line);
16141592Srgrimes		}
16151592Srgrimes		(void) fflush(stdout);
16161592Srgrimes		(void) fclose(fd);
16171592Srgrimes	}
16181592Srgrimes	if (guest) {
16196740Sguido		if (ident != NULL)
16206740Sguido			free(ident);
162117433Spst		ident = strdup(passwd);
162217433Spst		if (ident == NULL)
162376096Smarkm			fatalerror("Ran out of memory.");
162417433Spst
16251592Srgrimes		reply(230, "Guest login ok, access restrictions apply.");
16261592Srgrimes#ifdef SETPROCTITLE
162725283Sdavidn#ifdef VIRTUAL_HOSTING
162825283Sdavidn		if (thishost != firsthost)
162925283Sdavidn			snprintf(proctitle, sizeof(proctitle),
163083308Smikeh				 "%s: anonymous(%s)/%s", remotehost, hostname,
163183308Smikeh				 passwd);
163225283Sdavidn		else
163325283Sdavidn#endif
163425283Sdavidn			snprintf(proctitle, sizeof(proctitle),
163583308Smikeh				 "%s: anonymous/%s", remotehost, passwd);
163613139Speter		setproctitle("%s", proctitle);
16371592Srgrimes#endif /* SETPROCTITLE */
16381592Srgrimes		if (logging)
16391592Srgrimes			syslog(LOG_INFO, "ANONYMOUS FTP LOGIN FROM %s, %s",
16401592Srgrimes			    remotehost, passwd);
16411592Srgrimes	} else {
164284841Syar		if (dochroot)
164384841Syar			reply(230, "User %s logged in, "
164484841Syar				   "access restrictions apply.", pw->pw_name);
164584841Syar		else
164684841Syar			reply(230, "User %s logged in.", pw->pw_name);
164725986Sdanny
16481592Srgrimes#ifdef SETPROCTITLE
16491592Srgrimes		snprintf(proctitle, sizeof(proctitle),
165084842Syar			 "%s: user/%s", remotehost, pw->pw_name);
165113139Speter		setproctitle("%s", proctitle);
16521592Srgrimes#endif /* SETPROCTITLE */
16531592Srgrimes		if (logging)
16541592Srgrimes			syslog(LOG_INFO, "FTP LOGIN FROM %s as %s",
16551592Srgrimes			    remotehost, pw->pw_name);
16561592Srgrimes	}
1657140473Syar	if (logging && (guest || dochroot))
1658137985Syar		syslog(LOG_INFO, "session root changed to %s", chrootdir);
165925101Sdavidn#ifdef	LOGIN_CAP
166025101Sdavidn	login_close(lc);
166125101Sdavidn#endif
1662110036Syar	if (residue)
1663110036Syar		free(residue);
16641592Srgrimes	return;
16651592Srgrimesbad:
16661592Srgrimes	/* Forget all about it... */
166725101Sdavidn#ifdef	LOGIN_CAP
166825101Sdavidn	login_close(lc);
166925101Sdavidn#endif
1670110036Syar	if (residue)
1671110036Syar		free(residue);
16721592Srgrimes	end_login();
16731592Srgrimes}
16741592Srgrimes
16751592Srgrimesvoid
167690148Simpretrieve(char *cmd, char *name)
16771592Srgrimes{
16781592Srgrimes	FILE *fin, *dout;
16791592Srgrimes	struct stat st;
168090148Simp	int (*closefunc)(FILE *);
168136612Sjb	time_t start;
1682300273Struckman	char line[BUFSIZ];
16831592Srgrimes
16841592Srgrimes	if (cmd == 0) {
16851592Srgrimes		fin = fopen(name, "r"), closefunc = fclose;
16861592Srgrimes		st.st_size = 0;
16871592Srgrimes	} else {
1688300273Struckman		(void) snprintf(line, sizeof(line), cmd, name);
1689300273Struckman		name = line;
16901592Srgrimes		fin = ftpd_popen(line, "r"), closefunc = ftpd_pclose;
16911592Srgrimes		st.st_size = -1;
16921592Srgrimes		st.st_blksize = BUFSIZ;
16931592Srgrimes	}
16941592Srgrimes	if (fin == NULL) {
16951592Srgrimes		if (errno != 0) {
16961592Srgrimes			perror_reply(550, name);
16971592Srgrimes			if (cmd == 0) {
16981592Srgrimes				LOGCMD("get", name);
16991592Srgrimes			}
17001592Srgrimes		}
17011592Srgrimes		return;
17021592Srgrimes	}
17031592Srgrimes	byte_count = -1;
1704110144Syar	if (cmd == 0) {
1705110144Syar		if (fstat(fileno(fin), &st) < 0) {
1706110144Syar			perror_reply(550, name);
1707110144Syar			goto done;
1708110144Syar		}
1709110144Syar		if (!S_ISREG(st.st_mode)) {
1710125565Syar			/*
1711125565Syar			 * Never sending a raw directory is a workaround
1712125565Syar			 * for buggy clients that will attempt to RETR
1713125565Syar			 * a directory before listing it, e.g., Mozilla.
1714125565Syar			 * Preventing a guest from getting irregular files
1715125565Syar			 * is a simple security measure.
1716125565Syar			 */
1717125565Syar			if (S_ISDIR(st.st_mode) || guest) {
1718110144Syar				reply(550, "%s: not a plain file.", name);
1719110144Syar				goto done;
1720110144Syar			}
1721110144Syar			st.st_size = -1;
1722110144Syar			/* st.st_blksize is set for all descriptor types */
1723110144Syar		}
17241592Srgrimes	}
17251592Srgrimes	if (restart_point) {
17261592Srgrimes		if (type == TYPE_A) {
17271592Srgrimes			off_t i, n;
17281592Srgrimes			int c;
17291592Srgrimes
17301592Srgrimes			n = restart_point;
17311592Srgrimes			i = 0;
17321592Srgrimes			while (i++ < n) {
17331592Srgrimes				if ((c=getc(fin)) == EOF) {
17341592Srgrimes					perror_reply(550, name);
17351592Srgrimes					goto done;
17361592Srgrimes				}
17371592Srgrimes				if (c == '\n')
17381592Srgrimes					i++;
17391592Srgrimes			}
17401592Srgrimes		} else if (lseek(fileno(fin), restart_point, L_SET) < 0) {
17411592Srgrimes			perror_reply(550, name);
17421592Srgrimes			goto done;
17431592Srgrimes		}
17441592Srgrimes	}
17451592Srgrimes	dout = dataconn(name, st.st_size, "w");
17461592Srgrimes	if (dout == NULL)
17471592Srgrimes		goto done;
17486740Sguido	time(&start);
17498240Swollman	send_data(fin, dout, st.st_blksize, st.st_size,
17508240Swollman		  restart_point == 0 && cmd == 0 && S_ISREG(st.st_mode));
1751136929Syar	if (cmd == 0 && guest && stats && byte_count > 0)
1752136929Syar		logxfer(name, byte_count, start);
17531592Srgrimes	(void) fclose(dout);
17541592Srgrimes	data = -1;
17551592Srgrimes	pdata = -1;
17561592Srgrimesdone:
17571592Srgrimes	if (cmd == 0)
17581592Srgrimes		LOGBYTES("get", name, byte_count);
17591592Srgrimes	(*closefunc)(fin);
17601592Srgrimes}
17611592Srgrimes
17621592Srgrimesvoid
176390148Simpstore(char *name, char *mode, int unique)
17641592Srgrimes{
1765101537Syar	int fd;
17661592Srgrimes	FILE *fout, *din;
176790148Simp	int (*closefunc)(FILE *);
17681592Srgrimes
1769101537Syar	if (*mode == 'a') {		/* APPE */
1770101537Syar		if (unique) {
1771101537Syar			/* Programming error */
1772101537Syar			syslog(LOG_ERR, "Internal: unique flag to APPE");
1773101537Syar			unique = 0;
1774101537Syar		}
1775101537Syar		if (guest && noguestmod) {
1776137852Syar			reply(550, "Appending to existing file denied.");
1777101537Syar			goto err;
1778101537Syar		}
1779101537Syar		restart_point = 0;	/* not affected by preceding REST */
17801592Srgrimes	}
1781101537Syar	if (unique)			/* STOU overrides REST */
1782101537Syar		restart_point = 0;
1783101537Syar	if (guest && noguestmod) {
1784101537Syar		if (restart_point) {	/* guest STOR w/REST */
1785137852Syar			reply(550, "Modifying existing file denied.");
1786101537Syar			goto err;
1787101537Syar		} else			/* treat guest STOR as STOU */
1788101537Syar			unique = 1;
1789101537Syar	}
17901592Srgrimes
17911592Srgrimes	if (restart_point)
1792101537Syar		mode = "r+";	/* so ASCII manual seek can work */
1793101537Syar	if (unique) {
1794101537Syar		if ((fd = guniquefd(name, &name)) < 0)
1795101537Syar			goto err;
1796101537Syar		fout = fdopen(fd, mode);
1797101537Syar	} else
1798101537Syar		fout = fopen(name, mode);
17991592Srgrimes	closefunc = fclose;
18001592Srgrimes	if (fout == NULL) {
18011592Srgrimes		perror_reply(553, name);
1802101537Syar		goto err;
18031592Srgrimes	}
18041592Srgrimes	byte_count = -1;
18051592Srgrimes	if (restart_point) {
18061592Srgrimes		if (type == TYPE_A) {
18071592Srgrimes			off_t i, n;
18081592Srgrimes			int c;
18091592Srgrimes
18101592Srgrimes			n = restart_point;
18111592Srgrimes			i = 0;
18121592Srgrimes			while (i++ < n) {
18131592Srgrimes				if ((c=getc(fout)) == EOF) {
18141592Srgrimes					perror_reply(550, name);
18151592Srgrimes					goto done;
18161592Srgrimes				}
18171592Srgrimes				if (c == '\n')
18181592Srgrimes					i++;
18191592Srgrimes			}
18201592Srgrimes			/*
18211592Srgrimes			 * We must do this seek to "current" position
18221592Srgrimes			 * because we are changing from reading to
18231592Srgrimes			 * writing.
18241592Srgrimes			 */
1825132930Syar			if (fseeko(fout, 0, SEEK_CUR) < 0) {
18261592Srgrimes				perror_reply(550, name);
18271592Srgrimes				goto done;
18281592Srgrimes			}
18291592Srgrimes		} else if (lseek(fileno(fout), restart_point, L_SET) < 0) {
18301592Srgrimes			perror_reply(550, name);
18311592Srgrimes			goto done;
18321592Srgrimes		}
18331592Srgrimes	}
1834132930Syar	din = dataconn(name, -1, "r");
18351592Srgrimes	if (din == NULL)
18361592Srgrimes		goto done;
18371592Srgrimes	if (receive_data(din, fout) == 0) {
18381592Srgrimes		if (unique)
18391592Srgrimes			reply(226, "Transfer complete (unique file name:%s).",
18401592Srgrimes			    name);
18411592Srgrimes		else
18421592Srgrimes			reply(226, "Transfer complete.");
18431592Srgrimes	}
18441592Srgrimes	(void) fclose(din);
18451592Srgrimes	data = -1;
18461592Srgrimes	pdata = -1;
18471592Srgrimesdone:
1848102566Syar	LOGBYTES(*mode == 'a' ? "append" : "put", name, byte_count);
18491592Srgrimes	(*closefunc)(fout);
1850101537Syar	return;
1851101537Syarerr:
1852101537Syar	LOGCMD(*mode == 'a' ? "append" : "put" , name);
1853101537Syar	return;
18541592Srgrimes}
18551592Srgrimes
18561592Srgrimesstatic FILE *
185790148Simpgetdatasock(char *mode)
18581592Srgrimes{
18591592Srgrimes	int on = 1, s, t, tries;
18601592Srgrimes
18611592Srgrimes	if (data >= 0)
18621592Srgrimes		return (fdopen(data, mode));
186356668Sshin
186456668Sshin	s = socket(data_dest.su_family, SOCK_STREAM, 0);
18651592Srgrimes	if (s < 0)
18661592Srgrimes		goto bad;
1867100612Syar	if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
1868100609Syar		syslog(LOG_WARNING, "data setsockopt (SO_REUSEADDR): %m");
18691592Srgrimes	/* anchor socket to avoid multi-homing problems */
187056668Sshin	data_source = ctrl_addr;
1871109742Syar	data_source.su_port = htons(dataport);
1872132893Syar	(void) seteuid(0);
18731592Srgrimes	for (tries = 1; ; tries++) {
1874132891Syar		/*
1875132891Syar		 * We should loop here since it's possible that
1876132891Syar		 * another ftpd instance has passed this point and is
1877132891Syar		 * trying to open a data connection in active mode now.
1878132891Syar		 * Until the other connection is opened, we'll be getting
1879132891Syar		 * EADDRINUSE because no SOCK_STREAM sockets in the system
1880132891Syar		 * can share both local and remote addresses, localIP:20
1881132891Syar		 * and *:* in this case.
1882132891Syar		 */
18831592Srgrimes		if (bind(s, (struct sockaddr *)&data_source,
188456668Sshin		    data_source.su_len) >= 0)
18851592Srgrimes			break;
18861592Srgrimes		if (errno != EADDRINUSE || tries > 10)
18871592Srgrimes			goto bad;
18881592Srgrimes		sleep(tries);
18891592Srgrimes	}
1890132893Syar	(void) seteuid(pw->pw_uid);
18911592Srgrimes#ifdef IP_TOS
189256668Sshin	if (data_source.su_family == AF_INET)
189356668Sshin      {
18941592Srgrimes	on = IPTOS_THROUGHPUT;
1895100612Syar	if (setsockopt(s, IPPROTO_IP, IP_TOS, &on, sizeof(int)) < 0)
1896100609Syar		syslog(LOG_WARNING, "data setsockopt (IP_TOS): %m");
189756668Sshin      }
18981592Srgrimes#endif
18998240Swollman#ifdef TCP_NOPUSH
19008240Swollman	/*
19018240Swollman	 * Turn off push flag to keep sender TCP from sending short packets
1902166598Syar	 * at the boundaries of each write().
19038240Swollman	 */
19048240Swollman	on = 1;
1905100612Syar	if (setsockopt(s, IPPROTO_TCP, TCP_NOPUSH, &on, sizeof on) < 0)
1906100609Syar		syslog(LOG_WARNING, "data setsockopt (TCP_NOPUSH): %m");
19078240Swollman#endif
19081592Srgrimes	return (fdopen(s, mode));
19091592Srgrimesbad:
19101592Srgrimes	/* Return the real value of errno (close may change it) */
19111592Srgrimes	t = errno;
1912132893Syar	(void) seteuid(pw->pw_uid);
19131592Srgrimes	(void) close(s);
19141592Srgrimes	errno = t;
19151592Srgrimes	return (NULL);
19161592Srgrimes}
19171592Srgrimes
19181592Srgrimesstatic FILE *
191990148Simpdataconn(char *name, off_t size, char *mode)
19201592Srgrimes{
19211592Srgrimes	char sizebuf[32];
19221592Srgrimes	FILE *file;
1923109611Scjc	int retry = 0, tos, conerrno;
19241592Srgrimes
19251592Srgrimes	file_size = size;
19261592Srgrimes	byte_count = 0;
1927132930Syar	if (size != -1)
1928132929Syar		(void) snprintf(sizebuf, sizeof(sizebuf),
1929132929Syar				" (%jd bytes)", (intmax_t)size);
19301592Srgrimes	else
193131973Simp		*sizebuf = '\0';
19321592Srgrimes	if (pdata >= 0) {
193356668Sshin		union sockunion from;
1934141918Sstefanf		socklen_t fromlen = ctrl_addr.su_len;
1935141918Sstefanf		int flags, s;
193612532Sguido		struct timeval timeout;
193712532Sguido		fd_set set;
19381592Srgrimes
193912532Sguido		FD_ZERO(&set);
194012532Sguido		FD_SET(pdata, &set);
194112532Sguido
194212532Sguido		timeout.tv_usec = 0;
194312532Sguido		timeout.tv_sec = 120;
194412532Sguido
194586628Syar		/*
194686628Syar		 * Granted a socket is in the blocking I/O mode,
194786628Syar		 * accept() will block after a successful select()
194886628Syar		 * if the selected connection dies in between.
194986628Syar		 * Therefore set the non-blocking I/O flag here.
195086628Syar		 */
195186628Syar		if ((flags = fcntl(pdata, F_GETFL, 0)) == -1 ||
195286628Syar		    fcntl(pdata, F_SETFL, flags | O_NONBLOCK) == -1)
195386628Syar			goto pdata_err;
1954132931Syar		if (select(pdata+1, &set, NULL, NULL, &timeout) <= 0 ||
195586628Syar		    (s = accept(pdata, (struct sockaddr *) &from, &fromlen)) < 0)
195686628Syar			goto pdata_err;
19571592Srgrimes		(void) close(pdata);
19581592Srgrimes		pdata = s;
195986628Syar		/*
1960101809Syar		 * Unset the inherited non-blocking I/O flag
1961101809Syar		 * on the child socket so stdio can work on it.
196286628Syar		 */
196386628Syar		if ((flags = fcntl(pdata, F_GETFL, 0)) == -1 ||
196486628Syar		    fcntl(pdata, F_SETFL, flags & ~O_NONBLOCK) == -1)
196586628Syar			goto pdata_err;
19661592Srgrimes#ifdef IP_TOS
196756668Sshin		if (from.su_family == AF_INET)
196856668Sshin	      {
196917435Spst		tos = IPTOS_THROUGHPUT;
1970100612Syar		if (setsockopt(s, IPPROTO_IP, IP_TOS, &tos, sizeof(int)) < 0)
1971100609Syar			syslog(LOG_WARNING, "pdata setsockopt (IP_TOS): %m");
197256668Sshin	      }
19731592Srgrimes#endif
19741592Srgrimes		reply(150, "Opening %s mode data connection for '%s'%s.",
19751592Srgrimes		     type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf);
19761592Srgrimes		return (fdopen(pdata, mode));
197786628Syarpdata_err:
197886628Syar		reply(425, "Can't open data connection.");
197986628Syar		(void) close(pdata);
198086628Syar		pdata = -1;
198186628Syar		return (NULL);
19821592Srgrimes	}
19831592Srgrimes	if (data >= 0) {
19841592Srgrimes		reply(125, "Using existing data connection for '%s'%s.",
19851592Srgrimes		    name, sizebuf);
19861592Srgrimes		usedefault = 1;
19871592Srgrimes		return (fdopen(data, mode));
19881592Srgrimes	}
19891592Srgrimes	if (usedefault)
19901592Srgrimes		data_dest = his_addr;
19911592Srgrimes	usedefault = 1;
1992109611Scjc	do {
1993109611Scjc		file = getdatasock(mode);
1994109611Scjc		if (file == NULL) {
1995137983Syar			char hostbuf[NI_MAXHOST], portbuf[NI_MAXSERV];
1996137983Syar
1997137983Syar			if (getnameinfo((struct sockaddr *)&data_source,
1998137983Syar				data_source.su_len,
1999137983Syar				hostbuf, sizeof(hostbuf) - 1,
2000137983Syar				portbuf, sizeof(portbuf) - 1,
2001137983Syar				NI_NUMERICHOST|NI_NUMERICSERV))
2002137983Syar					*hostbuf = *portbuf = 0;
2003137983Syar			hostbuf[sizeof(hostbuf) - 1] = 0;
2004137983Syar			portbuf[sizeof(portbuf) - 1] = 0;
2005109611Scjc			reply(425, "Can't create data socket (%s,%s): %s.",
2006109611Scjc				hostbuf, portbuf, strerror(errno));
2007109611Scjc			return (NULL);
2008109611Scjc		}
2009109611Scjc		data = fileno(file);
2010109611Scjc		conerrno = 0;
2011109611Scjc		if (connect(data, (struct sockaddr *)&data_dest,
2012109611Scjc		    data_dest.su_len) == 0)
2013109611Scjc			break;
2014109611Scjc		conerrno = errno;
2015109611Scjc		(void) fclose(file);
2016109611Scjc		data = -1;
2017109611Scjc		if (conerrno == EADDRINUSE) {
2018137659Syar			sleep(swaitint);
20191592Srgrimes			retry += swaitint;
2020109611Scjc		} else {
2021109611Scjc			break;
20221592Srgrimes		}
2023109611Scjc	} while (retry <= swaitmax);
2024109611Scjc	if (conerrno != 0) {
2025137850Syar		reply(425, "Can't build data connection: %s.",
2026137850Syar			   strerror(conerrno));
20271592Srgrimes		return (NULL);
20281592Srgrimes	}
20291592Srgrimes	reply(150, "Opening %s mode data connection for '%s'%s.",
20301592Srgrimes	     type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf);
20311592Srgrimes	return (file);
20321592Srgrimes}
20331592Srgrimes
20341592Srgrimes/*
2035140472Syar * A helper macro to avoid code duplication
2036140472Syar * in send_data() and receive_data().
2037140472Syar *
2038140472Syar * XXX We have to block SIGURG during putc() because BSD stdio
2039140472Syar * is unable to restart interrupted write operations and hence
2040140472Syar * the entire buffer contents will be lost as soon as a write()
2041140472Syar * call indicates EINTR to stdio.
2042140472Syar */
2043140472Syar#define FTPD_PUTC(ch, file, label)					\
2044140472Syar	do {								\
2045140472Syar		int ret;						\
2046140472Syar									\
2047140472Syar		do {							\
2048140472Syar			START_UNSAFE;					\
2049140472Syar			ret = putc((ch), (file));			\
2050140472Syar			END_UNSAFE;					\
2051140472Syar			CHECKOOB(return (-1))				\
2052140472Syar			else if (ferror(file))				\
2053140472Syar				goto label;				\
2054140472Syar			clearerr(file);					\
2055140472Syar		} while (ret == EOF);					\
2056140472Syar	} while (0)
2057140472Syar
2058140472Syar/*
20591592Srgrimes * Tranfer the contents of "instr" to "outstr" peer using the appropriate
20608240Swollman * encapsulation of the data subject to Mode, Structure, and Type.
20611592Srgrimes *
20621592Srgrimes * NB: Form isn't handled.
20631592Srgrimes */
206489935Syarstatic int
2065137660Syarsend_data(FILE *instr, FILE *outstr, size_t blksize, off_t filesize, int isreg)
20661592Srgrimes{
2067122751Syar	int c, cp, filefd, netfd;
206870205Sdan	char *buf;
20691592Srgrimes
2070140472Syar	STARTXFER;
2071140472Syar
20721592Srgrimes	switch (type) {
20731592Srgrimes
20741592Srgrimes	case TYPE_A:
2075140472Syar		cp = EOF;
2076140472Syar		for (;;) {
2077140472Syar			c = getc(instr);
2078140472Syar			CHECKOOB(return (-1))
2079140472Syar			else if (c == EOF && ferror(instr))
2080140472Syar				goto file_err;
2081140472Syar			if (c == EOF) {
2082140472Syar				if (ferror(instr)) {	/* resume after OOB */
2083140472Syar					clearerr(instr);
2084140472Syar					continue;
2085140472Syar				}
2086140472Syar				if (feof(instr))	/* EOF */
2087140472Syar					break;
2088140472Syar				syslog(LOG_ERR, "Internal: impossible condition"
2089140472Syar						" on file after getc()");
2090140472Syar				goto file_err;
2091140472Syar			}
2092122751Syar			if (c == '\n' && cp != '\r') {
2093140472Syar				FTPD_PUTC('\r', outstr, data_err);
2094140472Syar				byte_count++;
20951592Srgrimes			}
2096140472Syar			FTPD_PUTC(c, outstr, data_err);
2097140472Syar			byte_count++;
2098122751Syar			cp = c;
20991592Srgrimes		}
2100140472Syar#ifdef notyet	/* BSD stdio isn't ready for that */
2101140472Syar		while (fflush(outstr) == EOF) {
2102140472Syar			CHECKOOB(return (-1))
2103140472Syar			else
2104140472Syar				goto data_err;
2105140472Syar			clearerr(outstr);
2106140472Syar		}
2107140472Syar		ENDXFER;
2108140472Syar#else
2109140472Syar		ENDXFER;
2110140472Syar		if (fflush(outstr) == EOF)
21111592Srgrimes			goto data_err;
2112140472Syar#endif
21131592Srgrimes		reply(226, "Transfer complete.");
211489935Syar		return (0);
21151592Srgrimes
21161592Srgrimes	case TYPE_I:
21171592Srgrimes	case TYPE_L:
21188240Swollman		/*
21198240Swollman		 * isreg is only set if we are not doing restart and we
21208240Swollman		 * are sending a regular file
21218240Swollman		 */
21228240Swollman		netfd = fileno(outstr);
21238870Srgrimes		filefd = fileno(instr);
21248240Swollman
212570205Sdan		if (isreg) {
2126136554Syar			char *msg = "Transfer complete.";
2127140472Syar			off_t cnt, offset;
212870205Sdan			int err;
212970205Sdan
2130136555Syar			cnt = offset = 0;
213170205Sdan
2132136555Syar			while (filesize > 0) {
213390604Smaxim				err = sendfile(filefd, netfd, offset, 0,
2134137811Syar					       NULL, &cnt, 0);
213599212Smaxim				/*
213699212Smaxim				 * Calculate byte_count before OOB processing.
213799212Smaxim				 * It can be used in myoob() later.
213899212Smaxim				 */
213999212Smaxim				byte_count += cnt;
214070205Sdan				offset += cnt;
214190604Smaxim				filesize -= cnt;
2142140472Syar				CHECKOOB(return (-1))
2143140472Syar				else if (err == -1) {
2144140472Syar					if (errno != EINTR &&
2145140472Syar					    cnt == 0 && offset == 0)
214670205Sdan						goto oldway;
214770205Sdan					goto data_err;
214870205Sdan				}
2149140472Syar				if (err == -1)	/* resume after OOB */
2150140472Syar					continue;
2151136554Syar				/*
2152136554Syar				 * We hit the EOF prematurely.
2153136554Syar				 * Perhaps the file was externally truncated.
2154136554Syar				 */
2155136554Syar				if (cnt == 0) {
2156136554Syar					msg = "Transfer finished due to "
2157136554Syar					      "premature end of file.";
2158136554Syar					break;
2159136554Syar				}
216070205Sdan			}
2161140472Syar			ENDXFER;
2162216945Semaste			reply(226, "%s", msg);
216389935Syar			return (0);
21648240Swollman		}
21658240Swollman
21668240Swollmanoldway:
2167137659Syar		if ((buf = malloc(blksize)) == NULL) {
2168140472Syar			ENDXFER;
2169137852Syar			reply(451, "Ran out of memory.");
217089935Syar			return (-1);
21711592Srgrimes		}
21728870Srgrimes
2173140472Syar		for (;;) {
2174140472Syar			int cnt, len;
2175140472Syar			char *bp;
2176140472Syar
2177140472Syar			cnt = read(filefd, buf, blksize);
2178140472Syar			CHECKOOB(free(buf); return (-1))
2179140472Syar			else if (cnt < 0) {
2180140472Syar				free(buf);
21811592Srgrimes				goto file_err;
2182140472Syar			}
2183140472Syar			if (cnt < 0)	/* resume after OOB */
2184140472Syar				continue;
2185140472Syar			if (cnt == 0)	/* EOF */
2186140472Syar				break;
2187140472Syar			for (len = cnt, bp = buf; len > 0;) {
2188140472Syar				cnt = write(netfd, bp, len);
2189140472Syar				CHECKOOB(free(buf); return (-1))
2190140472Syar				else if (cnt < 0) {
2191140472Syar					free(buf);
2192140472Syar					goto data_err;
2193140472Syar				}
2194140472Syar				if (cnt <= 0)
2195140472Syar					continue;
2196140472Syar				len -= cnt;
2197140472Syar				bp += cnt;
2198140472Syar				byte_count += cnt;
2199140472Syar			}
22001592Srgrimes		}
2201140472Syar		ENDXFER;
2202140472Syar		free(buf);
22031592Srgrimes		reply(226, "Transfer complete.");
220489935Syar		return (0);
22051592Srgrimes	default:
2206140472Syar		ENDXFER;
2207137852Syar		reply(550, "Unimplemented TYPE %d in send_data.", type);
220889935Syar		return (-1);
22091592Srgrimes	}
22101592Srgrimes
22111592Srgrimesdata_err:
2212140472Syar	ENDXFER;
22131592Srgrimes	perror_reply(426, "Data connection");
221489935Syar	return (-1);
22151592Srgrimes
22161592Srgrimesfile_err:
2217140472Syar	ENDXFER;
22181592Srgrimes	perror_reply(551, "Error on input file");
221989935Syar	return (-1);
22201592Srgrimes}
22211592Srgrimes
22221592Srgrimes/*
22231592Srgrimes * Transfer data from peer to "outstr" using the appropriate encapulation of
22241592Srgrimes * the data subject to Mode, Structure, and Type.
22251592Srgrimes *
22261592Srgrimes * N.B.: Form isn't handled.
22271592Srgrimes */
22281592Srgrimesstatic int
222990148Simpreceive_data(FILE *instr, FILE *outstr)
22301592Srgrimes{
2231140472Syar	int c, cp;
2232140472Syar	int bare_lfs = 0;
22331592Srgrimes
2234140472Syar	STARTXFER;
223517433Spst
22361592Srgrimes	switch (type) {
22371592Srgrimes
22381592Srgrimes	case TYPE_I:
22391592Srgrimes	case TYPE_L:
2240140472Syar		for (;;) {
2241140472Syar			int cnt, len;
2242140472Syar			char *bp;
2243140472Syar			char buf[BUFSIZ];
2244140472Syar
2245140472Syar			cnt = read(fileno(instr), buf, sizeof(buf));
2246140472Syar			CHECKOOB(return (-1))
2247140472Syar			else if (cnt < 0)
2248140472Syar				goto data_err;
2249140472Syar			if (cnt < 0)	/* resume after OOB */
2250140472Syar				continue;
2251140472Syar			if (cnt == 0)	/* EOF */
2252140472Syar				break;
2253140472Syar			for (len = cnt, bp = buf; len > 0;) {
2254140472Syar				cnt = write(fileno(outstr), bp, len);
2255140472Syar				CHECKOOB(return (-1))
2256140472Syar				else if (cnt < 0)
2257140472Syar					goto file_err;
2258140472Syar				if (cnt <= 0)
2259140472Syar					continue;
2260140472Syar				len -= cnt;
2261140472Syar				bp += cnt;
2262140472Syar				byte_count += cnt;
2263140472Syar			}
22641592Srgrimes		}
2265140472Syar		ENDXFER;
22661592Srgrimes		return (0);
22671592Srgrimes
22681592Srgrimes	case TYPE_E:
2269140472Syar		ENDXFER;
22701592Srgrimes		reply(553, "TYPE E not implemented.");
22711592Srgrimes		return (-1);
22721592Srgrimes
22731592Srgrimes	case TYPE_A:
2274140472Syar		cp = EOF;
2275140472Syar		for (;;) {
2276140472Syar			c = getc(instr);
2277140472Syar			CHECKOOB(return (-1))
2278140472Syar			else if (c == EOF && ferror(instr))
2279140472Syar				goto data_err;
2280140472Syar			if (c == EOF && ferror(instr)) { /* resume after OOB */
2281140472Syar				clearerr(instr);
2282140472Syar				continue;
2283140472Syar			}
2284140472Syar
2285140472Syar			if (cp == '\r') {
2286140472Syar				if (c != '\n')
2287140472Syar					FTPD_PUTC('\r', outstr, file_err);
2288140472Syar			} else
2289140472Syar				if (c == '\n')
2290140472Syar					bare_lfs++;
2291140472Syar			if (c == '\r') {
2292140472Syar				byte_count++;
2293140472Syar				cp = c;
2294140472Syar				continue;
2295140472Syar			}
2296140472Syar
2297140472Syar			/* Check for EOF here in order not to lose last \r. */
2298140472Syar			if (c == EOF) {
2299140472Syar				if (feof(instr))	/* EOF */
2300140472Syar					break;
2301140472Syar				syslog(LOG_ERR, "Internal: impossible condition"
2302140472Syar						" on data stream after getc()");
2303140472Syar				goto data_err;
2304140472Syar			}
2305140472Syar
23061592Srgrimes			byte_count++;
2307140472Syar			FTPD_PUTC(c, outstr, file_err);
2308140472Syar			cp = c;
23091592Srgrimes		}
2310140472Syar#ifdef notyet	/* BSD stdio isn't ready for that */
2311140472Syar		while (fflush(outstr) == EOF) {
2312140472Syar			CHECKOOB(return (-1))
2313140472Syar			else
2314140472Syar				goto file_err;
2315140472Syar			clearerr(outstr);
2316140472Syar		}
2317140472Syar		ENDXFER;
2318140472Syar#else
2319140472Syar		ENDXFER;
2320140472Syar		if (fflush(outstr) == EOF)
23211592Srgrimes			goto file_err;
2322140472Syar#endif
23231592Srgrimes		if (bare_lfs) {
23241592Srgrimes			lreply(226,
2325137852Syar		"WARNING! %d bare linefeeds received in ASCII mode.",
23261592Srgrimes			    bare_lfs);
23271592Srgrimes		(void)printf("   File may not have transferred correctly.\r\n");
23281592Srgrimes		}
23291592Srgrimes		return (0);
23301592Srgrimes	default:
2331140472Syar		ENDXFER;
2332137852Syar		reply(550, "Unimplemented TYPE %d in receive_data.", type);
23331592Srgrimes		return (-1);
23341592Srgrimes	}
23351592Srgrimes
23361592Srgrimesdata_err:
2337140472Syar	ENDXFER;
2338137852Syar	perror_reply(426, "Data connection");
23391592Srgrimes	return (-1);
23401592Srgrimes
23411592Srgrimesfile_err:
2342140472Syar	ENDXFER;
2343137852Syar	perror_reply(452, "Error writing to file");
23441592Srgrimes	return (-1);
23451592Srgrimes}
23461592Srgrimes
23471592Srgrimesvoid
234890148Simpstatfilecmd(char *filename)
23491592Srgrimes{
23501592Srgrimes	FILE *fin;
2351109382Syar	int atstart;
2352137728Syar	int c, code;
23531592Srgrimes	char line[LINE_MAX];
2354137728Syar	struct stat st;
23551592Srgrimes
2356137728Syar	code = lstat(filename, &st) == 0 && S_ISDIR(st.st_mode) ? 212 : 213;
235725165Sdavidn	(void)snprintf(line, sizeof(line), _PATH_LS " -lgA %s", filename);
23581592Srgrimes	fin = ftpd_popen(line, "r");
2359216943Semaste	if (fin == NULL) {
2360216943Semaste		perror_reply(551, filename);
2361216943Semaste		return;
2362216943Semaste	}
2363137729Syar	lreply(code, "Status of %s:", filename);
2364109382Syar	atstart = 1;
23651592Srgrimes	while ((c = getc(fin)) != EOF) {
23661592Srgrimes		if (c == '\n') {
23671592Srgrimes			if (ferror(stdout)){
2368137852Syar				perror_reply(421, "Control connection");
23691592Srgrimes				(void) ftpd_pclose(fin);
23701592Srgrimes				dologout(1);
23711592Srgrimes				/* NOTREACHED */
23721592Srgrimes			}
23731592Srgrimes			if (ferror(fin)) {
23741592Srgrimes				perror_reply(551, filename);
23751592Srgrimes				(void) ftpd_pclose(fin);
23761592Srgrimes				return;
23771592Srgrimes			}
23781592Srgrimes			(void) putc('\r', stdout);
23791592Srgrimes		}
2380109382Syar		/*
2381109382Syar		 * RFC 959 says neutral text should be prepended before
2382109382Syar		 * a leading 3-digit number followed by whitespace, but
2383109382Syar		 * many ftp clients can be confused by any leading digits,
2384109382Syar		 * as a matter of fact.
2385109382Syar		 */
2386109382Syar		if (atstart && isdigit(c))
2387109382Syar			(void) putc(' ', stdout);
23881592Srgrimes		(void) putc(c, stdout);
2389109382Syar		atstart = (c == '\n');
23901592Srgrimes	}
23911592Srgrimes	(void) ftpd_pclose(fin);
2392137852Syar	reply(code, "End of status.");
23931592Srgrimes}
23941592Srgrimes
23951592Srgrimesvoid
239690148Simpstatcmd(void)
23971592Srgrimes{
239856668Sshin	union sockunion *su;
23991592Srgrimes	u_char *a, *p;
240099255Sume	char hname[NI_MAXHOST];
240156668Sshin	int ispassive;
24021592Srgrimes
2403110037Syar	if (hostinfo) {
2404110037Syar		lreply(211, "%s FTP server status:", hostname);
2405110037Syar		printf("     %s\r\n", version);
2406110037Syar	} else
2407110037Syar		lreply(211, "FTP server status:");
24081592Srgrimes	printf("     Connected to %s", remotehost);
240956668Sshin	if (!getnameinfo((struct sockaddr *)&his_addr, his_addr.su_len,
241099255Sume			 hname, sizeof(hname) - 1, NULL, 0, NI_NUMERICHOST)) {
2411137983Syar		hname[sizeof(hname) - 1] = 0;
241256668Sshin		if (strcmp(hname, remotehost) != 0)
241356668Sshin			printf(" (%s)", hname);
241456668Sshin	}
24151592Srgrimes	printf("\r\n");
24161592Srgrimes	if (logged_in) {
24171592Srgrimes		if (guest)
24181592Srgrimes			printf("     Logged in anonymously\r\n");
24191592Srgrimes		else
24201592Srgrimes			printf("     Logged in as %s\r\n", pw->pw_name);
24211592Srgrimes	} else if (askpasswd)
24221592Srgrimes		printf("     Waiting for password\r\n");
24231592Srgrimes	else
24241592Srgrimes		printf("     Waiting for user name\r\n");
24251592Srgrimes	printf("     TYPE: %s", typenames[type]);
24261592Srgrimes	if (type == TYPE_A || type == TYPE_E)
24271592Srgrimes		printf(", FORM: %s", formnames[form]);
24281592Srgrimes	if (type == TYPE_L)
2429103949Smike#if CHAR_BIT == 8
2430103949Smike		printf(" %d", CHAR_BIT);
24311592Srgrimes#else
24321592Srgrimes		printf(" %d", bytesize);	/* need definition! */
24331592Srgrimes#endif
24341592Srgrimes	printf("; STRUcture: %s; transfer MODE: %s\r\n",
24351592Srgrimes	    strunames[stru], modenames[mode]);
24361592Srgrimes	if (data != -1)
24371592Srgrimes		printf("     Data connection open\r\n");
24381592Srgrimes	else if (pdata != -1) {
243956668Sshin		ispassive = 1;
244056668Sshin		su = &pasv_addr;
24411592Srgrimes		goto printaddr;
24421592Srgrimes	} else if (usedefault == 0) {
244356668Sshin		ispassive = 0;
244456668Sshin		su = &data_dest;
24451592Srgrimesprintaddr:
24461592Srgrimes#define UC(b) (((int) b) & 0xff)
244756668Sshin		if (epsvall) {
244856668Sshin			printf("     EPSV only mode (EPSV ALL)\r\n");
244956668Sshin			goto epsvonly;
245056668Sshin		}
245156668Sshin
245256668Sshin		/* PORT/PASV */
245356668Sshin		if (su->su_family == AF_INET) {
245456668Sshin			a = (u_char *) &su->su_sin.sin_addr;
245556668Sshin			p = (u_char *) &su->su_sin.sin_port;
245656668Sshin			printf("     %s (%d,%d,%d,%d,%d,%d)\r\n",
245756668Sshin				ispassive ? "PASV" : "PORT",
245856668Sshin				UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]),
245956668Sshin				UC(p[0]), UC(p[1]));
246056668Sshin		}
246156668Sshin
246256668Sshin		/* LPRT/LPSV */
246356668Sshin	    {
246456668Sshin		int alen, af, i;
246556668Sshin
246656668Sshin		switch (su->su_family) {
246756668Sshin		case AF_INET:
246856668Sshin			a = (u_char *) &su->su_sin.sin_addr;
246956668Sshin			p = (u_char *) &su->su_sin.sin_port;
247056668Sshin			alen = sizeof(su->su_sin.sin_addr);
247156668Sshin			af = 4;
247256668Sshin			break;
247356668Sshin		case AF_INET6:
247456668Sshin			a = (u_char *) &su->su_sin6.sin6_addr;
247556668Sshin			p = (u_char *) &su->su_sin6.sin6_port;
247656668Sshin			alen = sizeof(su->su_sin6.sin6_addr);
247756668Sshin			af = 6;
247856668Sshin			break;
247956668Sshin		default:
248056668Sshin			af = 0;
248156668Sshin			break;
248256668Sshin		}
248356668Sshin		if (af) {
248456668Sshin			printf("     %s (%d,%d,", ispassive ? "LPSV" : "LPRT",
248556668Sshin				af, alen);
248656668Sshin			for (i = 0; i < alen; i++)
248756668Sshin				printf("%d,", UC(a[i]));
248856668Sshin			printf("%d,%d,%d)\r\n", 2, UC(p[0]), UC(p[1]));
248956668Sshin		}
249056668Sshin	    }
249156668Sshin
249256668Sshinepsvonly:;
249356668Sshin		/* EPRT/EPSV */
249456668Sshin	    {
249556668Sshin		int af;
249656668Sshin
249756668Sshin		switch (su->su_family) {
249856668Sshin		case AF_INET:
249956668Sshin			af = 1;
250056668Sshin			break;
250156668Sshin		case AF_INET6:
250256668Sshin			af = 2;
250356668Sshin			break;
250456668Sshin		default:
250556668Sshin			af = 0;
250656668Sshin			break;
250756668Sshin		}
250856668Sshin		if (af) {
250999255Sume			union sockunion tmp;
251099255Sume
251199255Sume			tmp = *su;
251299255Sume			if (tmp.su_family == AF_INET6)
251399255Sume				tmp.su_sin6.sin6_scope_id = 0;
251499255Sume			if (!getnameinfo((struct sockaddr *)&tmp, tmp.su_len,
251556668Sshin					hname, sizeof(hname) - 1, NULL, 0,
251656668Sshin					NI_NUMERICHOST)) {
2517137983Syar				hname[sizeof(hname) - 1] = 0;
251856668Sshin				printf("     %s |%d|%s|%d|\r\n",
251956668Sshin					ispassive ? "EPSV" : "EPRT",
252099255Sume					af, hname, htons(tmp.su_port));
252156668Sshin			}
252256668Sshin		}
252356668Sshin	    }
25241592Srgrimes#undef UC
25251592Srgrimes	} else
25261592Srgrimes		printf("     No data connection\r\n");
2527137852Syar	reply(211, "End of status.");
25281592Srgrimes}
25291592Srgrimes
25301592Srgrimesvoid
253190148Simpfatalerror(char *s)
25321592Srgrimes{
25331592Srgrimes
2534137849Syar	reply(451, "Error in server: %s", s);
25351592Srgrimes	reply(221, "Closing connection due to server error.");
25361592Srgrimes	dologout(0);
25371592Srgrimes	/* NOTREACHED */
25381592Srgrimes}
25391592Srgrimes
25401592Srgrimesvoid
25411592Srgrimesreply(int n, const char *fmt, ...)
25421592Srgrimes{
25431592Srgrimes	va_list ap;
254490148Simp
2545129170Stjr	(void)printf("%d ", n);
25461592Srgrimes	va_start(ap, fmt);
25471592Srgrimes	(void)vprintf(fmt, ap);
2548129170Stjr	va_end(ap);
25491592Srgrimes	(void)printf("\r\n");
25501592Srgrimes	(void)fflush(stdout);
255176096Smarkm	if (ftpdebug) {
25521592Srgrimes		syslog(LOG_DEBUG, "<--- %d ", n);
2553129170Stjr		va_start(ap, fmt);
25541592Srgrimes		vsyslog(LOG_DEBUG, fmt, ap);
2555129170Stjr		va_end(ap);
25561592Srgrimes	}
25571592Srgrimes}
25581592Srgrimes
25591592Srgrimesvoid
25601592Srgrimeslreply(int n, const char *fmt, ...)
25611592Srgrimes{
25621592Srgrimes	va_list ap;
256390148Simp
2564129170Stjr	(void)printf("%d- ", n);
25651592Srgrimes	va_start(ap, fmt);
25661592Srgrimes	(void)vprintf(fmt, ap);
2567129170Stjr	va_end(ap);
25681592Srgrimes	(void)printf("\r\n");
25691592Srgrimes	(void)fflush(stdout);
257076096Smarkm	if (ftpdebug) {
25711592Srgrimes		syslog(LOG_DEBUG, "<--- %d- ", n);
2572129170Stjr		va_start(ap, fmt);
25731592Srgrimes		vsyslog(LOG_DEBUG, fmt, ap);
2574129170Stjr		va_end(ap);
25751592Srgrimes	}
25761592Srgrimes}
25771592Srgrimes
25781592Srgrimesstatic void
257990148Simpack(char *s)
25801592Srgrimes{
25811592Srgrimes
25821592Srgrimes	reply(250, "%s command successful.", s);
25831592Srgrimes}
25841592Srgrimes
25851592Srgrimesvoid
258690148Simpnack(char *s)
25871592Srgrimes{
25881592Srgrimes
25891592Srgrimes	reply(502, "%s command not implemented.", s);
25901592Srgrimes}
25911592Srgrimes
25921592Srgrimes/* ARGSUSED */
25931592Srgrimesvoid
259490148Simpyyerror(char *s)
25951592Srgrimes{
25961592Srgrimes	char *cp;
25971592Srgrimes
259817478Smarkm	if ((cp = strchr(cbuf,'\n')))
25991592Srgrimes		*cp = '\0';
2600137852Syar	reply(500, "%s: command not understood.", cbuf);
26011592Srgrimes}
26021592Srgrimes
26031592Srgrimesvoid
260490148Simpdelete(char *name)
26051592Srgrimes{
26061592Srgrimes	struct stat st;
26071592Srgrimes
26081592Srgrimes	LOGCMD("delete", name);
2609100439Syar	if (lstat(name, &st) < 0) {
26101592Srgrimes		perror_reply(550, name);
26111592Srgrimes		return;
26121592Srgrimes	}
2613137847Syar	if (S_ISDIR(st.st_mode)) {
26141592Srgrimes		if (rmdir(name) < 0) {
26151592Srgrimes			perror_reply(550, name);
26161592Srgrimes			return;
26171592Srgrimes		}
26181592Srgrimes		goto done;
26191592Srgrimes	}
2620125568Syar	if (guest && noguestmod) {
2621137852Syar		reply(550, "Operation not permitted.");
2622125568Syar		return;
2623125568Syar	}
2624125568Syar	if (unlink(name) < 0) {
26251592Srgrimes		perror_reply(550, name);
26261592Srgrimes		return;
26271592Srgrimes	}
26281592Srgrimesdone:
26291592Srgrimes	ack("DELE");
26301592Srgrimes}
26311592Srgrimes
26321592Srgrimesvoid
263390148Simpcwd(char *path)
26341592Srgrimes{
26351592Srgrimes
26361592Srgrimes	if (chdir(path) < 0)
26371592Srgrimes		perror_reply(550, path);
26381592Srgrimes	else
26391592Srgrimes		ack("CWD");
26401592Srgrimes}
26411592Srgrimes
26421592Srgrimesvoid
264390148Simpmakedir(char *name)
26441592Srgrimes{
2645100878Syar	char *s;
26461592Srgrimes
26471592Srgrimes	LOGCMD("mkdir", name);
264899195Smdodd	if (guest && noguestmkd)
2649137853Syar		reply(550, "Operation not permitted.");
265099195Smdodd	else if (mkdir(name, 0777) < 0)
26511592Srgrimes		perror_reply(550, name);
2652100878Syar	else {
2653100878Syar		if ((s = doublequote(name)) == NULL)
2654100878Syar			fatalerror("Ran out of memory.");
2655100878Syar		reply(257, "\"%s\" directory created.", s);
2656100878Syar		free(s);
2657100878Syar	}
26581592Srgrimes}
26591592Srgrimes
26601592Srgrimesvoid
266190148Simpremovedir(char *name)
26621592Srgrimes{
26631592Srgrimes
26641592Srgrimes	LOGCMD("rmdir", name);
26651592Srgrimes	if (rmdir(name) < 0)
26661592Srgrimes		perror_reply(550, name);
26671592Srgrimes	else
26681592Srgrimes		ack("RMD");
26691592Srgrimes}
26701592Srgrimes
26711592Srgrimesvoid
267290148Simppwd(void)
26731592Srgrimes{
2674100486Syar	char *s, path[MAXPATHLEN + 1];
26751592Srgrimes
2676137830Syar	if (getcwd(path, sizeof(path)) == NULL)
2677137839Syar		perror_reply(550, "Get current directory");
2678100486Syar	else {
2679100486Syar		if ((s = doublequote(path)) == NULL)
2680100486Syar			fatalerror("Ran out of memory.");
2681100486Syar		reply(257, "\"%s\" is current directory.", s);
2682100486Syar		free(s);
2683100486Syar	}
26841592Srgrimes}
26851592Srgrimes
26861592Srgrimeschar *
268790148Simprenamefrom(char *name)
26881592Srgrimes{
26891592Srgrimes	struct stat st;
26901592Srgrimes
2691125569Syar	if (guest && noguestmod) {
2692137852Syar		reply(550, "Operation not permitted.");
2693125569Syar		return (NULL);
2694125569Syar	}
2695100439Syar	if (lstat(name, &st) < 0) {
26961592Srgrimes		perror_reply(550, name);
2697125570Syar		return (NULL);
26981592Srgrimes	}
2699137852Syar	reply(350, "File exists, ready for destination name.");
27001592Srgrimes	return (name);
27011592Srgrimes}
27021592Srgrimes
27031592Srgrimesvoid
270490148Simprenamecmd(char *from, char *to)
27051592Srgrimes{
270617433Spst	struct stat st;
27071592Srgrimes
27081592Srgrimes	LOGCMD2("rename", from, to);
270917433Spst
271017433Spst	if (guest && (stat(to, &st) == 0)) {
2711137852Syar		reply(550, "%s: permission denied.", to);
271217433Spst		return;
271317433Spst	}
271417433Spst
27151592Srgrimes	if (rename(from, to) < 0)
27161592Srgrimes		perror_reply(550, "rename");
27171592Srgrimes	else
27181592Srgrimes		ack("RNTO");
27191592Srgrimes}
27201592Srgrimes
27211592Srgrimesstatic void
272290148Simpdolog(struct sockaddr *who)
27231592Srgrimes{
2724137984Syar	char who_name[NI_MAXHOST];
27251592Srgrimes
272656668Sshin	realhostname_sa(remotehost, sizeof(remotehost) - 1, who, who->sa_len);
2727137983Syar	remotehost[sizeof(remotehost) - 1] = 0;
2728137984Syar	if (getnameinfo(who, who->sa_len,
2729137984Syar		who_name, sizeof(who_name) - 1, NULL, 0, NI_NUMERICHOST))
2730137984Syar			*who_name = 0;
2731137984Syar	who_name[sizeof(who_name) - 1] = 0;
273256668Sshin
27331592Srgrimes#ifdef SETPROCTITLE
273425283Sdavidn#ifdef VIRTUAL_HOSTING
273525283Sdavidn	if (thishost != firsthost)
273625283Sdavidn		snprintf(proctitle, sizeof(proctitle), "%s: connected (to %s)",
273725283Sdavidn			 remotehost, hostname);
273825283Sdavidn	else
273925283Sdavidn#endif
274025283Sdavidn		snprintf(proctitle, sizeof(proctitle), "%s: connected",
274125283Sdavidn			 remotehost);
274213139Speter	setproctitle("%s", proctitle);
27431592Srgrimes#endif /* SETPROCTITLE */
27441592Srgrimes
274525283Sdavidn	if (logging) {
274625283Sdavidn#ifdef VIRTUAL_HOSTING
274725283Sdavidn		if (thishost != firsthost)
2748137984Syar			syslog(LOG_INFO, "connection from %s (%s) to %s",
2749137984Syar			       remotehost, who_name, hostname);
275025283Sdavidn		else
275125283Sdavidn#endif
2752137984Syar			syslog(LOG_INFO, "connection from %s (%s)",
2753137984Syar			       remotehost, who_name);
275425283Sdavidn	}
27551592Srgrimes}
27561592Srgrimes
27571592Srgrimes/*
27581592Srgrimes * Record logout in wtmp file
27591592Srgrimes * and exit with supplied status.
27601592Srgrimes */
27611592Srgrimesvoid
276290148Simpdologout(int status)
27631592Srgrimes{
27641592Srgrimes
2765202604Sed	if (logged_in && dowtmp) {
2766132893Syar		(void) seteuid(0);
2767325472Seugen#ifdef		LOGIN_CAP
2768325472Seugen 	        setusercontext(NULL, getpwuid(0), 0, LOGIN_SETALL & ~(LOGIN_SETLOGIN |
2769325472Seugen		       LOGIN_SETUSER | LOGIN_SETGROUP | LOGIN_SETPATH |
2770325472Seugen		       LOGIN_SETENV));
2771325472Seugen#endif
2772202604Sed		ftpd_logwtmp(wtmpid, NULL, NULL);
27731592Srgrimes	}
27741592Srgrimes	/* beware of flushing buffers after a SIGPIPE */
27751592Srgrimes	_exit(status);
27761592Srgrimes}
27771592Srgrimes
27781592Srgrimesstatic void
277990148Simpsigurg(int signo)
27801592Srgrimes{
278189935Syar
278289935Syar	recvurg = 1;
278389935Syar}
278489935Syar
278589935Syarstatic void
2786140472Syarmaskurg(int flag)
2787140472Syar{
2788140472Syar	int oerrno;
2789140472Syar	sigset_t sset;
2790140472Syar
2791140472Syar	if (!transflag) {
2792140472Syar		syslog(LOG_ERR, "Internal: maskurg() while no transfer");
2793140472Syar		return;
2794140472Syar	}
2795140472Syar	oerrno = errno;
2796140472Syar	sigemptyset(&sset);
2797140472Syar	sigaddset(&sset, SIGURG);
2798140472Syar	sigprocmask(flag ? SIG_BLOCK : SIG_UNBLOCK, &sset, NULL);
2799140472Syar	errno = oerrno;
2800140472Syar}
2801140472Syar
2802140472Syarstatic void
2803140472Syarflagxfer(int flag)
2804140472Syar{
2805140472Syar
2806140472Syar	if (flag) {
2807141967Syar		if (transflag)
2808141967Syar			syslog(LOG_ERR, "Internal: flagxfer(1): "
2809141967Syar					"transfer already under way");
2810141966Syar		transflag = 1;
2811141966Syar		maskurg(0);
2812140472Syar		recvurg = 0;
2813141966Syar	} else {
2814141967Syar		if (!transflag)
2815141967Syar			syslog(LOG_ERR, "Internal: flagxfer(0): "
2816141967Syar					"no active transfer");
2817141966Syar		maskurg(1);
2818140472Syar		transflag = 0;
2819141966Syar	}
2820140472Syar}
2821140472Syar
2822140472Syar/*
2823140472Syar * Returns 0 if OK to resume or -1 if abort requested.
2824140472Syar */
2825140472Syarstatic int
282690148Simpmyoob(void)
282789935Syar{
28281592Srgrimes	char *cp;
2829186405Scperciva	int ret;
28301592Srgrimes
2831140472Syar	if (!transflag) {
2832140472Syar		syslog(LOG_ERR, "Internal: myoob() while no transfer");
2833140472Syar		return (0);
2834140472Syar	}
28351592Srgrimes	cp = tmpline;
2836186405Scperciva	ret = getline(cp, 7, stdin);
2837186405Scperciva	if (ret == -1) {
28381592Srgrimes		reply(221, "You could at least say goodbye.");
28391592Srgrimes		dologout(0);
2840186405Scperciva	} else if (ret == -2) {
2841186405Scperciva		/* Ignore truncated command. */
2842186405Scperciva		return (0);
28431592Srgrimes	}
28441592Srgrimes	upper(cp);
28451592Srgrimes	if (strcmp(cp, "ABOR\r\n") == 0) {
28461592Srgrimes		tmpline[0] = '\0';
28471592Srgrimes		reply(426, "Transfer aborted. Data connection closed.");
2848137852Syar		reply(226, "Abort successful.");
2849140472Syar		return (-1);
28501592Srgrimes	}
28511592Srgrimes	if (strcmp(cp, "STAT\r\n") == 0) {
285251192Smharo		tmpline[0] = '\0';
2853132930Syar		if (file_size != -1)
2854137852Syar			reply(213, "Status: %jd of %jd bytes transferred.",
2855132929Syar				   (intmax_t)byte_count, (intmax_t)file_size);
28561592Srgrimes		else
2857137852Syar			reply(213, "Status: %jd bytes transferred.",
2858132929Syar				   (intmax_t)byte_count);
28591592Srgrimes	}
2860140472Syar	return (0);
28611592Srgrimes}
28621592Srgrimes
28631592Srgrimes/*
28641592Srgrimes * Note: a response of 425 is not mentioned as a possible response to
28651592Srgrimes *	the PASV command in RFC959. However, it has been blessed as
28661592Srgrimes *	a legitimate response by Jon Postel in a telephone conversation
28671592Srgrimes *	with Rick Adams on 25 Jan 89.
28681592Srgrimes */
28691592Srgrimesvoid
287090148Simppassive(void)
28711592Srgrimes{
2872141918Sstefanf	socklen_t len;
2873141918Sstefanf	int on;
28741592Srgrimes	char *p, *a;
28751592Srgrimes
287617433Spst	if (pdata >= 0)		/* close old port if one set */
287717433Spst		close(pdata);
287817433Spst
287956668Sshin	pdata = socket(ctrl_addr.su_family, SOCK_STREAM, 0);
28801592Srgrimes	if (pdata < 0) {
28811592Srgrimes		perror_reply(425, "Can't open passive connection");
28821592Srgrimes		return;
28831592Srgrimes	}
2884100615Syar	on = 1;
2885100615Syar	if (setsockopt(pdata, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
2886100615Syar		syslog(LOG_WARNING, "pdata setsockopt (SO_REUSEADDR): %m");
28879933Spst
2888132893Syar	(void) seteuid(0);
288917433Spst
289019903Spst#ifdef IP_PORTRANGE
289156668Sshin	if (ctrl_addr.su_family == AF_INET) {
2892100615Syar	    on = restricted_data_ports ? IP_PORTRANGE_HIGH
2893100615Syar				       : IP_PORTRANGE_DEFAULT;
289419903Spst
289519903Spst	    if (setsockopt(pdata, IPPROTO_IP, IP_PORTRANGE,
2896100612Syar			    &on, sizeof(on)) < 0)
289719903Spst		    goto pasv_error;
28981592Srgrimes	}
289919903Spst#endif
290060929Snsayer#ifdef IPV6_PORTRANGE
290160929Snsayer	if (ctrl_addr.su_family == AF_INET6) {
2902100615Syar	    on = restricted_data_ports ? IPV6_PORTRANGE_HIGH
2903100615Syar				       : IPV6_PORTRANGE_DEFAULT;
29049933Spst
290560929Snsayer	    if (setsockopt(pdata, IPPROTO_IPV6, IPV6_PORTRANGE,
2906100612Syar			    &on, sizeof(on)) < 0)
290760929Snsayer		    goto pasv_error;
290860929Snsayer	}
290960929Snsayer#endif
291060929Snsayer
291116033Speter	pasv_addr = ctrl_addr;
291256668Sshin	pasv_addr.su_port = 0;
291356668Sshin	if (bind(pdata, (struct sockaddr *)&pasv_addr, pasv_addr.su_len) < 0)
291416033Speter		goto pasv_error;
291517433Spst
2916132893Syar	(void) seteuid(pw->pw_uid);
291716033Speter
29181592Srgrimes	len = sizeof(pasv_addr);
29191592Srgrimes	if (getsockname(pdata, (struct sockaddr *) &pasv_addr, &len) < 0)
29201592Srgrimes		goto pasv_error;
29211592Srgrimes	if (listen(pdata, 1) < 0)
29221592Srgrimes		goto pasv_error;
292356668Sshin	if (pasv_addr.su_family == AF_INET)
292456668Sshin		a = (char *) &pasv_addr.su_sin.sin_addr;
292556668Sshin	else if (pasv_addr.su_family == AF_INET6 &&
292656668Sshin		 IN6_IS_ADDR_V4MAPPED(&pasv_addr.su_sin6.sin6_addr))
292756668Sshin		a = (char *) &pasv_addr.su_sin6.sin6_addr.s6_addr[12];
292856668Sshin	else
292956668Sshin		goto pasv_error;
293056668Sshin
293156668Sshin	p = (char *) &pasv_addr.su_port;
29321592Srgrimes
29331592Srgrimes#define UC(b) (((int) b) & 0xff)
29341592Srgrimes
29351592Srgrimes	reply(227, "Entering Passive Mode (%d,%d,%d,%d,%d,%d)", UC(a[0]),
29361592Srgrimes		UC(a[1]), UC(a[2]), UC(a[3]), UC(p[0]), UC(p[1]));
29371592Srgrimes	return;
29381592Srgrimes
29391592Srgrimespasv_error:
2940132893Syar	(void) seteuid(pw->pw_uid);
29411592Srgrimes	(void) close(pdata);
29421592Srgrimes	pdata = -1;
29431592Srgrimes	perror_reply(425, "Can't open passive connection");
29441592Srgrimes	return;
29451592Srgrimes}
29461592Srgrimes
29471592Srgrimes/*
294856668Sshin * Long Passive defined in RFC 1639.
294956668Sshin *     228 Entering Long Passive Mode
295056668Sshin *         (af, hal, h1, h2, h3,..., pal, p1, p2...)
295156668Sshin */
295256668Sshin
295356668Sshinvoid
295490148Simplong_passive(char *cmd, int pf)
295556668Sshin{
2956141918Sstefanf	socklen_t len;
2957141918Sstefanf	int on;
295856668Sshin	char *p, *a;
295956668Sshin
296056668Sshin	if (pdata >= 0)		/* close old port if one set */
296156668Sshin		close(pdata);
296256668Sshin
296356668Sshin	if (pf != PF_UNSPEC) {
296456668Sshin		if (ctrl_addr.su_family != pf) {
296556668Sshin			switch (ctrl_addr.su_family) {
296656668Sshin			case AF_INET:
296756668Sshin				pf = 1;
296856668Sshin				break;
296956668Sshin			case AF_INET6:
297056668Sshin				pf = 2;
297156668Sshin				break;
297256668Sshin			default:
297356668Sshin				pf = 0;
297456668Sshin				break;
297556668Sshin			}
297656668Sshin			/*
297756668Sshin			 * XXX
297856668Sshin			 * only EPRT/EPSV ready clients will understand this
297956668Sshin			 */
298056668Sshin			if (strcmp(cmd, "EPSV") == 0 && pf) {
298156668Sshin				reply(522, "Network protocol mismatch, "
298256668Sshin					"use (%d)", pf);
298356668Sshin			} else
2984137852Syar				reply(501, "Network protocol mismatch."); /*XXX*/
298556668Sshin
298656668Sshin			return;
298756668Sshin		}
298856668Sshin	}
298956668Sshin
299056668Sshin	pdata = socket(ctrl_addr.su_family, SOCK_STREAM, 0);
299156668Sshin	if (pdata < 0) {
299256668Sshin		perror_reply(425, "Can't open passive connection");
299356668Sshin		return;
299456668Sshin	}
2995100615Syar	on = 1;
2996100615Syar	if (setsockopt(pdata, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
2997100615Syar		syslog(LOG_WARNING, "pdata setsockopt (SO_REUSEADDR): %m");
299856668Sshin
2999132893Syar	(void) seteuid(0);
300056668Sshin
300156668Sshin	pasv_addr = ctrl_addr;
300256668Sshin	pasv_addr.su_port = 0;
300356668Sshin	len = pasv_addr.su_len;
300456668Sshin
300560929Snsayer#ifdef IP_PORTRANGE
300660929Snsayer	if (ctrl_addr.su_family == AF_INET) {
3007100615Syar	    on = restricted_data_ports ? IP_PORTRANGE_HIGH
3008100615Syar				       : IP_PORTRANGE_DEFAULT;
300960929Snsayer
301060929Snsayer	    if (setsockopt(pdata, IPPROTO_IP, IP_PORTRANGE,
3011100612Syar			    &on, sizeof(on)) < 0)
301260929Snsayer		    goto pasv_error;
301360929Snsayer	}
301460929Snsayer#endif
301560929Snsayer#ifdef IPV6_PORTRANGE
301660929Snsayer	if (ctrl_addr.su_family == AF_INET6) {
3017100615Syar	    on = restricted_data_ports ? IPV6_PORTRANGE_HIGH
3018100615Syar				       : IPV6_PORTRANGE_DEFAULT;
301960929Snsayer
302060929Snsayer	    if (setsockopt(pdata, IPPROTO_IPV6, IPV6_PORTRANGE,
3021100612Syar			    &on, sizeof(on)) < 0)
302260929Snsayer		    goto pasv_error;
302360929Snsayer	}
302460929Snsayer#endif
302560929Snsayer
302656668Sshin	if (bind(pdata, (struct sockaddr *)&pasv_addr, len) < 0)
302756668Sshin		goto pasv_error;
302856668Sshin
3029132893Syar	(void) seteuid(pw->pw_uid);
303056668Sshin
303156668Sshin	if (getsockname(pdata, (struct sockaddr *) &pasv_addr, &len) < 0)
303256668Sshin		goto pasv_error;
303356668Sshin	if (listen(pdata, 1) < 0)
303456668Sshin		goto pasv_error;
303556668Sshin
303656668Sshin#define UC(b) (((int) b) & 0xff)
303756668Sshin
303856668Sshin	if (strcmp(cmd, "LPSV") == 0) {
303956668Sshin		p = (char *)&pasv_addr.su_port;
304056668Sshin		switch (pasv_addr.su_family) {
304156668Sshin		case AF_INET:
304256668Sshin			a = (char *) &pasv_addr.su_sin.sin_addr;
304356668Sshin		v4_reply:
304456668Sshin			reply(228,
304556668Sshin"Entering Long Passive Mode (%d,%d,%d,%d,%d,%d,%d,%d,%d)",
304656668Sshin			      4, 4, UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]),
304756668Sshin			      2, UC(p[0]), UC(p[1]));
304856668Sshin			return;
304956668Sshin		case AF_INET6:
305056668Sshin			if (IN6_IS_ADDR_V4MAPPED(&pasv_addr.su_sin6.sin6_addr)) {
305156668Sshin				a = (char *) &pasv_addr.su_sin6.sin6_addr.s6_addr[12];
305256668Sshin				goto v4_reply;
305356668Sshin			}
305456668Sshin			a = (char *) &pasv_addr.su_sin6.sin6_addr;
305556668Sshin			reply(228,
305656668Sshin"Entering Long Passive Mode "
305756668Sshin"(%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d)",
305856668Sshin			      6, 16, UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]),
305956668Sshin			      UC(a[4]), UC(a[5]), UC(a[6]), UC(a[7]),
306056668Sshin			      UC(a[8]), UC(a[9]), UC(a[10]), UC(a[11]),
306156668Sshin			      UC(a[12]), UC(a[13]), UC(a[14]), UC(a[15]),
306256668Sshin			      2, UC(p[0]), UC(p[1]));
306356668Sshin			return;
306456668Sshin		}
306556668Sshin	} else if (strcmp(cmd, "EPSV") == 0) {
306656668Sshin		switch (pasv_addr.su_family) {
306756668Sshin		case AF_INET:
306856668Sshin		case AF_INET6:
306956668Sshin			reply(229, "Entering Extended Passive Mode (|||%d|)",
307056668Sshin				ntohs(pasv_addr.su_port));
307156668Sshin			return;
307256668Sshin		}
307356668Sshin	} else {
307456668Sshin		/* more proper error code? */
307556668Sshin	}
307656668Sshin
307756668Sshinpasv_error:
3078132893Syar	(void) seteuid(pw->pw_uid);
307956668Sshin	(void) close(pdata);
308056668Sshin	pdata = -1;
308156668Sshin	perror_reply(425, "Can't open passive connection");
308256668Sshin	return;
308356668Sshin}
308456668Sshin
308556668Sshin/*
3086101537Syar * Generate unique name for file with basename "local"
3087101537Syar * and open the file in order to avoid possible races.
3088101537Syar * Try "local" first, then "local.1", "local.2" etc, up to "local.99".
3089101537Syar * Return descriptor to the file, set "name" to its name.
3090101537Syar *
30911592Srgrimes * Generates failure reply on error.
30921592Srgrimes */
3093101537Syarstatic int
3094101537Syarguniquefd(char *local, char **name)
30951592Srgrimes{
30961592Srgrimes	static char new[MAXPATHLEN];
30971592Srgrimes	struct stat st;
3098101537Syar	char *cp;
30991592Srgrimes	int count;
3100101537Syar	int fd;
31011592Srgrimes
31021592Srgrimes	cp = strrchr(local, '/');
31031592Srgrimes	if (cp)
31041592Srgrimes		*cp = '\0';
31051592Srgrimes	if (stat(cp ? local : ".", &st) < 0) {
31061592Srgrimes		perror_reply(553, cp ? local : ".");
3107101537Syar		return (-1);
31081592Srgrimes	}
3109101537Syar	if (cp) {
3110101537Syar		/*
3111101537Syar		 * Let not overwrite dirname with counter suffix.
3112101537Syar		 * -4 is for /nn\0
3113101537Syar		 * In this extreme case dot won't be put in front of suffix.
3114101537Syar		 */
3115101537Syar		if (strlen(local) > sizeof(new) - 4) {
3116137852Syar			reply(553, "Pathname too long.");
3117101537Syar			return (-1);
3118101537Syar		}
31191592Srgrimes		*cp = '/';
3120101537Syar	}
312131973Simp	/* -4 is for the .nn<null> we put on the end below */
312231973Simp	(void) snprintf(new, sizeof(new) - 4, "%s", local);
31231592Srgrimes	cp = new + strlen(new);
3124101537Syar	/*
3125101537Syar	 * Don't generate dotfile unless requested explicitly.
3126101537Syar	 * This covers the case when basename gets truncated off
3127101537Syar	 * by buffer size.
3128101537Syar	 */
3129101537Syar	if (cp > new && cp[-1] != '/')
3130101537Syar		*cp++ = '.';
3131101537Syar	for (count = 0; count < 100; count++) {
3132101537Syar		/* At count 0 try unmodified name */
3133101537Syar		if (count)
3134101537Syar			(void)sprintf(cp, "%d", count);
3135101537Syar		if ((fd = open(count ? new : local,
3136101537Syar		    O_RDWR | O_CREAT | O_EXCL, 0666)) >= 0) {
3137101537Syar			*name = count ? new : local;
3138101537Syar			return (fd);
3139101537Syar		}
3140110046Syar		if (errno != EEXIST) {
3141110307Syar			perror_reply(553, count ? new : local);
3142110046Syar			return (-1);
3143110046Syar		}
31441592Srgrimes	}
31451592Srgrimes	reply(452, "Unique file name cannot be created.");
3146101537Syar	return (-1);
31471592Srgrimes}
31481592Srgrimes
31491592Srgrimes/*
31501592Srgrimes * Format and send reply containing system error number.
31511592Srgrimes */
31521592Srgrimesvoid
315390148Simpperror_reply(int code, char *string)
31541592Srgrimes{
31551592Srgrimes
31561592Srgrimes	reply(code, "%s: %s.", string, strerror(errno));
31571592Srgrimes}
31581592Srgrimes
31591592Srgrimesstatic char *onefile[] = {
31601592Srgrimes	"",
31611592Srgrimes	0
31621592Srgrimes};
31631592Srgrimes
31641592Srgrimesvoid
316590148Simpsend_file_list(char *whichf)
31661592Srgrimes{
31671592Srgrimes	struct stat st;
31681592Srgrimes	DIR *dirp = NULL;
31691592Srgrimes	struct dirent *dir;
31701592Srgrimes	FILE *dout = NULL;
31711592Srgrimes	char **dirlist, *dirname;
31721592Srgrimes	int simple = 0;
31731592Srgrimes	int freeglob = 0;
31741592Srgrimes	glob_t gl;
31751592Srgrimes
31761592Srgrimes	if (strpbrk(whichf, "~{[*?") != NULL) {
3177100222Smikeh		int flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE;
31781592Srgrimes
31791592Srgrimes		memset(&gl, 0, sizeof(gl));
318074470Sjlemon		gl.gl_matchc = MAXGLOBARGS;
318180525Smikeh		flags |= GLOB_LIMIT;
31821592Srgrimes		freeglob = 1;
31831592Srgrimes		if (glob(whichf, flags, 0, &gl)) {
3184137852Syar			reply(550, "No matching files found.");
31851592Srgrimes			goto out;
31861592Srgrimes		} else if (gl.gl_pathc == 0) {
31871592Srgrimes			errno = ENOENT;
31881592Srgrimes			perror_reply(550, whichf);
31891592Srgrimes			goto out;
31901592Srgrimes		}
31911592Srgrimes		dirlist = gl.gl_pathv;
31921592Srgrimes	} else {
31931592Srgrimes		onefile[0] = whichf;
31941592Srgrimes		dirlist = onefile;
31951592Srgrimes		simple = 1;
31961592Srgrimes	}
31971592Srgrimes
319817478Smarkm	while ((dirname = *dirlist++)) {
31991592Srgrimes		if (stat(dirname, &st) < 0) {
32001592Srgrimes			/*
32011592Srgrimes			 * If user typed "ls -l", etc, and the client
32021592Srgrimes			 * used NLST, do what the user meant.
32031592Srgrimes			 */
32041592Srgrimes			if (dirname[0] == '-' && *dirlist == NULL &&
3205140472Syar			    dout == NULL)
320625165Sdavidn				retrieve(_PATH_LS " %s", dirname);
3207140472Syar			else
3208140472Syar				perror_reply(550, whichf);
32091592Srgrimes			goto out;
32101592Srgrimes		}
32111592Srgrimes
32121592Srgrimes		if (S_ISREG(st.st_mode)) {
32131592Srgrimes			if (dout == NULL) {
3214132930Syar				dout = dataconn("file list", -1, "w");
32151592Srgrimes				if (dout == NULL)
32161592Srgrimes					goto out;
3217140472Syar				STARTXFER;
32181592Srgrimes			}
3219140472Syar			START_UNSAFE;
32201592Srgrimes			fprintf(dout, "%s%s\n", dirname,
32211592Srgrimes				type == TYPE_A ? "\r" : "");
3222140472Syar			END_UNSAFE;
3223140472Syar			if (ferror(dout))
3224140472Syar				goto data_err;
3225140472Syar			byte_count += strlen(dirname) +
3226140472Syar				      (type == TYPE_A ? 2 : 1);
3227140472Syar			CHECKOOB(goto abrt);
32281592Srgrimes			continue;
32291592Srgrimes		} else if (!S_ISDIR(st.st_mode))
32301592Srgrimes			continue;
32311592Srgrimes
32321592Srgrimes		if ((dirp = opendir(dirname)) == NULL)
32331592Srgrimes			continue;
32341592Srgrimes
32351592Srgrimes		while ((dir = readdir(dirp)) != NULL) {
32361592Srgrimes			char nbuf[MAXPATHLEN];
32371592Srgrimes
3238140472Syar			CHECKOOB(goto abrt);
323989935Syar
32401592Srgrimes			if (dir->d_name[0] == '.' && dir->d_namlen == 1)
32411592Srgrimes				continue;
32421592Srgrimes			if (dir->d_name[0] == '.' && dir->d_name[1] == '.' &&
32431592Srgrimes			    dir->d_namlen == 2)
32441592Srgrimes				continue;
32451592Srgrimes
324699213Smaxim			snprintf(nbuf, sizeof(nbuf),
324731973Simp				"%s/%s", dirname, dir->d_name);
32481592Srgrimes
32491592Srgrimes			/*
32501592Srgrimes			 * We have to do a stat to insure it's
32511592Srgrimes			 * not a directory or special file.
32521592Srgrimes			 */
32531592Srgrimes			if (simple || (stat(nbuf, &st) == 0 &&
32541592Srgrimes			    S_ISREG(st.st_mode))) {
32551592Srgrimes				if (dout == NULL) {
3256132930Syar					dout = dataconn("file list", -1, "w");
32571592Srgrimes					if (dout == NULL)
32581592Srgrimes						goto out;
3259140472Syar					STARTXFER;
32601592Srgrimes				}
3261140472Syar				START_UNSAFE;
32621592Srgrimes				if (nbuf[0] == '.' && nbuf[1] == '/')
32631592Srgrimes					fprintf(dout, "%s%s\n", &nbuf[2],
32641592Srgrimes						type == TYPE_A ? "\r" : "");
32651592Srgrimes				else
32661592Srgrimes					fprintf(dout, "%s%s\n", nbuf,
32671592Srgrimes						type == TYPE_A ? "\r" : "");
3268140472Syar				END_UNSAFE;
3269140472Syar				if (ferror(dout))
3270140472Syar					goto data_err;
3271140472Syar				byte_count += strlen(nbuf) +
3272140472Syar					      (type == TYPE_A ? 2 : 1);
3273140472Syar				CHECKOOB(goto abrt);
32741592Srgrimes			}
32751592Srgrimes		}
32761592Srgrimes		(void) closedir(dirp);
3277140472Syar		dirp = NULL;
32781592Srgrimes	}
32791592Srgrimes
32801592Srgrimes	if (dout == NULL)
32811592Srgrimes		reply(550, "No files found.");
3282140472Syar	else if (ferror(dout))
3283140472Syardata_err:	perror_reply(550, "Data connection");
32841592Srgrimes	else
32851592Srgrimes		reply(226, "Transfer complete.");
3286140472Syarout:
3287140472Syar	if (dout) {
3288140472Syar		ENDXFER;
3289140472Syarabrt:
32901592Srgrimes		(void) fclose(dout);
3291140472Syar		data = -1;
3292140472Syar		pdata = -1;
3293140472Syar	}
3294140472Syar	if (dirp)
3295140472Syar		(void) closedir(dirp);
32961592Srgrimes	if (freeglob) {
32971592Srgrimes		freeglob = 0;
32981592Srgrimes		globfree(&gl);
32991592Srgrimes	}
33001592Srgrimes}
33011592Srgrimes
330215196Sdgvoid
330390148Simpreapchild(int signo)
330415196Sdg{
3305137830Syar	while (waitpid(-1, NULL, WNOHANG) > 0);
330615196Sdg}
330715196Sdg
330813139Speter#ifdef OLD_SETPROCTITLE
33091592Srgrimes/*
33101592Srgrimes * Clobber argv so ps will show what we're doing.  (Stolen from sendmail.)
33111592Srgrimes * Warning, since this is usually started from inetd.conf, it often doesn't
33121592Srgrimes * have much of an environment or arglist to overwrite.
33131592Srgrimes */
33141592Srgrimesvoid
33151592Srgrimessetproctitle(const char *fmt, ...)
33161592Srgrimes{
33171592Srgrimes	int i;
33181592Srgrimes	va_list ap;
33191592Srgrimes	char *p, *bp, ch;
33201592Srgrimes	char buf[LINE_MAX];
33211592Srgrimes
33221592Srgrimes	va_start(ap, fmt);
33231592Srgrimes	(void)vsnprintf(buf, sizeof(buf), fmt, ap);
33241592Srgrimes
33251592Srgrimes	/* make ps print our process name */
33261592Srgrimes	p = Argv[0];
33271592Srgrimes	*p++ = '-';
33281592Srgrimes
33291592Srgrimes	i = strlen(buf);
33301592Srgrimes	if (i > LastArgv - p - 2) {
33311592Srgrimes		i = LastArgv - p - 2;
33321592Srgrimes		buf[i] = '\0';
33331592Srgrimes	}
33341592Srgrimes	bp = buf;
33351592Srgrimes	while (ch = *bp++)
33361592Srgrimes		if (ch != '\n' && ch != '\r')
33371592Srgrimes			*p++ = ch;
33381592Srgrimes	while (p < LastArgv)
33391592Srgrimes		*p++ = ' ';
33401592Srgrimes}
334113139Speter#endif /* OLD_SETPROCTITLE */
33426740Sguido
334317433Spststatic void
3344137848Syarappendf(char **strp, char *fmt, ...)
3345137848Syar{
3346137848Syar	va_list ap;
3347137848Syar	char *ostr, *p;
3348137848Syar
3349137848Syar	va_start(ap, fmt);
3350137848Syar	vasprintf(&p, fmt, ap);
3351137848Syar	va_end(ap);
3352137848Syar	if (p == NULL)
3353137848Syar		fatalerror("Ran out of memory.");
3354137848Syar	if (*strp == NULL)
3355137848Syar		*strp = p;
3356137848Syar	else {
3357137848Syar		ostr = *strp;
3358137848Syar		asprintf(strp, "%s%s", ostr, p);
3359137848Syar		if (*strp == NULL)
3360137848Syar			fatalerror("Ran out of memory.");
3361137848Syar		free(ostr);
3362137848Syar	}
3363137848Syar}
3364137848Syar
3365137848Syarstatic void
3366137848Syarlogcmd(char *cmd, char *file1, char *file2, off_t cnt)
3367137848Syar{
3368137848Syar	char *msg = NULL;
3369137848Syar	char wd[MAXPATHLEN + 1];
3370137848Syar
3371137848Syar	if (logging <= 1)
3372137848Syar		return;
3373137848Syar
3374137985Syar	if (getcwd(wd, sizeof(wd) - 1) == NULL)
3375137985Syar		strcpy(wd, strerror(errno));
3376137985Syar
3377137848Syar	appendf(&msg, "%s", cmd);
3378137848Syar	if (file1)
3379137848Syar		appendf(&msg, " %s", file1);
3380137848Syar	if (file2)
3381137848Syar		appendf(&msg, " %s", file2);
3382137848Syar	if (cnt >= 0)
3383137848Syar		appendf(&msg, " = %jd bytes", (intmax_t)cnt);
3384137985Syar	appendf(&msg, " (wd: %s", wd);
3385137862Syar	if (guest || dochroot)
3386137985Syar		appendf(&msg, "; chrooted");
3387137985Syar	appendf(&msg, ")");
3388137848Syar	syslog(LOG_INFO, "%s", msg);
3389137848Syar	free(msg);
3390137848Syar}
3391137848Syar
3392137848Syarstatic void
339390148Simplogxfer(char *name, off_t size, time_t start)
33946740Sguido{
3395137145Syar	char buf[MAXPATHLEN + 1024];
33966740Sguido	char path[MAXPATHLEN + 1];
339736612Sjb	time_t now;
33986740Sguido
3399137145Syar	if (statfd >= 0) {
34006740Sguido		time(&now);
3401137145Syar		if (realpath(name, path) == NULL) {
3402137145Syar			syslog(LOG_NOTICE, "realpath failed on %s: %m", path);
3403137145Syar			return;
3404137145Syar		}
3405137145Syar		snprintf(buf, sizeof(buf), "%.20s!%s!%s!%s!%jd!%ld\n",
34066740Sguido			ctime(&now)+4, ident, remotehost,
3407137145Syar			path, (intmax_t)size,
340882792Sache			(long)(now - start + (now == start)));
34096740Sguido		write(statfd, buf, strlen(buf));
34106740Sguido	}
34116740Sguido}
3412100486Syar
3413100486Syarstatic char *
3414100486Syardoublequote(char *s)
3415100486Syar{
3416100486Syar	int n;
3417100486Syar	char *p, *s2;
3418100486Syar
3419100486Syar	for (p = s, n = 0; *p; p++)
3420100486Syar		if (*p == '"')
3421100486Syar			n++;
3422100486Syar
3423100486Syar	if ((s2 = malloc(p - s + n + 1)) == NULL)
3424100486Syar		return (NULL);
3425100486Syar
3426100486Syar	for (p = s2; *s; s++, p++) {
3427100486Syar		if ((*p = *s) == '"')
3428100486Syar			*(++p) = '"';
3429100486Syar	}
3430100486Syar	*p = '\0';
3431100486Syar
3432100486Syar	return (s2);
3433100486Syar}
3434120059Sume
3435120059Sume/* setup server socket for specified address family */
3436120059Sume/* if af is PF_UNSPEC more than one socket may be returned */
3437120059Sume/* the returned list is dynamically allocated, so caller needs to free it */
3438120059Sumestatic int *
3439120059Sumesocksetup(int af, char *bindname, const char *bindport)
3440120059Sume{
3441120059Sume	struct addrinfo hints, *res, *r;
3442120059Sume	int error, maxs, *s, *socks;
3443120059Sume	const int on = 1;
3444120059Sume
3445120059Sume	memset(&hints, 0, sizeof(hints));
3446120059Sume	hints.ai_flags = AI_PASSIVE;
3447120059Sume	hints.ai_family = af;
3448120059Sume	hints.ai_socktype = SOCK_STREAM;
3449120059Sume	error = getaddrinfo(bindname, bindport, &hints, &res);
3450120059Sume	if (error) {
3451120059Sume		syslog(LOG_ERR, "%s", gai_strerror(error));
3452120059Sume		if (error == EAI_SYSTEM)
3453120059Sume			syslog(LOG_ERR, "%s", strerror(errno));
3454120059Sume		return NULL;
3455120059Sume	}
3456120059Sume
3457120059Sume	/* Count max number of sockets we may open */
3458120059Sume	for (maxs = 0, r = res; r; r = r->ai_next, maxs++)
3459120059Sume		;
3460120059Sume	socks = malloc((maxs + 1) * sizeof(int));
3461120059Sume	if (!socks) {
3462120059Sume		freeaddrinfo(res);
3463120059Sume		syslog(LOG_ERR, "couldn't allocate memory for sockets");
3464120059Sume		return NULL;
3465120059Sume	}
3466120059Sume
3467120059Sume	*socks = 0;   /* num of sockets counter at start of array */
3468120059Sume	s = socks + 1;
3469120059Sume	for (r = res; r; r = r->ai_next) {
3470120059Sume		*s = socket(r->ai_family, r->ai_socktype, r->ai_protocol);
3471120059Sume		if (*s < 0) {
3472120059Sume			syslog(LOG_DEBUG, "control socket: %m");
3473120059Sume			continue;
3474120059Sume		}
3475120059Sume		if (setsockopt(*s, SOL_SOCKET, SO_REUSEADDR,
3476120059Sume		    &on, sizeof(on)) < 0)
3477120059Sume			syslog(LOG_WARNING,
3478120059Sume			    "control setsockopt (SO_REUSEADDR): %m");
3479120059Sume		if (r->ai_family == AF_INET6) {
3480120059Sume			if (setsockopt(*s, IPPROTO_IPV6, IPV6_V6ONLY,
3481120059Sume			    &on, sizeof(on)) < 0)
3482120059Sume				syslog(LOG_WARNING,
3483120059Sume				    "control setsockopt (IPV6_V6ONLY): %m");
3484120059Sume		}
3485120059Sume		if (bind(*s, r->ai_addr, r->ai_addrlen) < 0) {
3486120059Sume			syslog(LOG_DEBUG, "control bind: %m");
3487120059Sume			close(*s);
3488120059Sume			continue;
3489120059Sume		}
3490120059Sume		(*socks)++;
3491120059Sume		s++;
3492120059Sume	}
3493120059Sume
3494120059Sume	if (res)
3495120059Sume		freeaddrinfo(res);
3496120059Sume
3497120059Sume	if (*socks == 0) {
3498120059Sume		syslog(LOG_ERR, "control socket: Couldn't bind to any socket");
3499120059Sume		free(socks);
3500120059Sume		return NULL;
3501120059Sume	}
3502120059Sume	return(socks);
3503120059Sume}
3504