ftpd.c revision 102183
11592Srgrimes/*
21592Srgrimes * Copyright (c) 1985, 1988, 1990, 1992, 1993, 1994
31592Srgrimes *	The Regents of the University of California.  All rights reserved.
41592Srgrimes *
51592Srgrimes * Redistribution and use in source and binary forms, with or without
61592Srgrimes * modification, are permitted provided that the following conditions
71592Srgrimes * are met:
81592Srgrimes * 1. Redistributions of source code must retain the above copyright
91592Srgrimes *    notice, this list of conditions and the following disclaimer.
101592Srgrimes * 2. Redistributions in binary form must reproduce the above copyright
111592Srgrimes *    notice, this list of conditions and the following disclaimer in the
121592Srgrimes *    documentation and/or other materials provided with the distribution.
131592Srgrimes * 3. All advertising materials mentioning features or use of this software
141592Srgrimes *    must display the following acknowledgement:
151592Srgrimes *	This product includes software developed by the University of
161592Srgrimes *	California, Berkeley and its contributors.
171592Srgrimes * 4. Neither the name of the University nor the names of its contributors
181592Srgrimes *    may be used to endorse or promote products derived from this software
191592Srgrimes *    without specific prior written permission.
201592Srgrimes *
211592Srgrimes * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
221592Srgrimes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
231592Srgrimes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
241592Srgrimes * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
251592Srgrimes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
261592Srgrimes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
271592Srgrimes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
281592Srgrimes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
291592Srgrimes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
301592Srgrimes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
311592Srgrimes * SUCH DAMAGE.
321592Srgrimes */
331592Srgrimes
3417478Smarkm#if 0
351592Srgrimes#ifndef lint
361592Srgrimesstatic char copyright[] =
371592Srgrimes"@(#) Copyright (c) 1985, 1988, 1990, 1992, 1993, 1994\n\
381592Srgrimes	The Regents of the University of California.  All rights reserved.\n";
391592Srgrimes#endif /* not lint */
4017478Smarkm#endif
411592Srgrimes
4231329Scharnier#ifndef lint
4317478Smarkm#if 0
441592Srgrimesstatic char sccsid[] = "@(#)ftpd.c	8.4 (Berkeley) 4/16/94";
4531329Scharnier#endif
4631329Scharnierstatic const char rcsid[] =
4750476Speter  "$FreeBSD: head/libexec/ftpd/ftpd.c 102183 2002-08-20 14:56:06Z yar $";
481592Srgrimes#endif /* not lint */
491592Srgrimes
501592Srgrimes/*
511592Srgrimes * FTP server.
521592Srgrimes */
531592Srgrimes#include <sys/param.h>
541592Srgrimes#include <sys/ioctl.h>
5566907Swollman#include <sys/mman.h>
561592Srgrimes#include <sys/socket.h>
5766907Swollman#include <sys/stat.h>
5866907Swollman#include <sys/time.h>
591592Srgrimes#include <sys/wait.h>
601592Srgrimes
611592Srgrimes#include <netinet/in.h>
621592Srgrimes#include <netinet/in_systm.h>
631592Srgrimes#include <netinet/ip.h>
648240Swollman#include <netinet/tcp.h>
651592Srgrimes
661592Srgrimes#define	FTP_NAMES
671592Srgrimes#include <arpa/ftp.h>
681592Srgrimes#include <arpa/inet.h>
691592Srgrimes#include <arpa/telnet.h>
701592Srgrimes
711592Srgrimes#include <ctype.h>
721592Srgrimes#include <dirent.h>
731592Srgrimes#include <err.h>
741592Srgrimes#include <errno.h>
751592Srgrimes#include <fcntl.h>
761592Srgrimes#include <glob.h>
771592Srgrimes#include <limits.h>
781592Srgrimes#include <netdb.h>
791592Srgrimes#include <pwd.h>
8025187Sdavidn#include <grp.h>
8188763Sache#include <opie.h>
821592Srgrimes#include <signal.h>
831592Srgrimes#include <stdio.h>
841592Srgrimes#include <stdlib.h>
851592Srgrimes#include <string.h>
861592Srgrimes#include <syslog.h>
871592Srgrimes#include <time.h>
881592Srgrimes#include <unistd.h>
8913139Speter#include <libutil.h>
9025101Sdavidn#ifdef	LOGIN_CAP
9125101Sdavidn#include <login_cap.h>
9225101Sdavidn#endif
931592Srgrimes
9474874Smarkm#ifdef USE_PAM
9551433Smarkm#include <security/pam_appl.h>
9651433Smarkm#endif
9751433Smarkm
981592Srgrimes#include "pathnames.h"
991592Srgrimes#include "extern.h"
1001592Srgrimes
1011592Srgrimes#include <stdarg.h>
1021592Srgrimes
10325165Sdavidnstatic char version[] = "Version 6.00LS";
10425165Sdavidn#undef main
1051592Srgrimes
1061592Srgrimesextern	off_t restart_point;
1071592Srgrimesextern	char cbuf[];
1081592Srgrimes
10956668Sshinunion sockunion server_addr;
11056668Sshinunion sockunion ctrl_addr;
11156668Sshinunion sockunion data_source;
11256668Sshinunion sockunion data_dest;
11356668Sshinunion sockunion his_addr;
11456668Sshinunion sockunion pasv_addr;
1151592Srgrimes
11615196Sdgint	daemon_mode;
1171592Srgrimesint	data;
1181592Srgrimesint	logged_in;
1191592Srgrimesstruct	passwd *pw;
12076096Smarkmint	ftpdebug;
1211592Srgrimesint	timeout = 900;    /* timeout after 15 minutes of inactivity */
1221592Srgrimesint	maxtimeout = 7200;/* don't allow idle time to be set beyond 2 hours */
1231592Srgrimesint	logging;
1249933Spstint	restricted_data_ports = 1;
12517435Spstint	paranoid = 1;	  /* be extra careful about security */
12620042Storstenbint	anon_only = 0;    /* Only anonymous ftp allowed */
1271592Srgrimesint	guest;
12817435Spstint	dochroot;
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 */
13770102Sphkint	readonly=0;		/* Server is in readonly mode.	*/
13870102Sphkint	noepsv=0;		/* EPSV command is disabled.	*/
13982460Snikint	noretr=0;		/* RETR command is disabled.	*/
14082796Ssheldonhint	noguestretr=0;		/* RETR command is disabled for anon users. */
14199195Smdoddint	noguestmkd=0;		/* MKD command is disabled for anon users. */
142101537Syarint	noguestmod=1;		/* anon users may not modify existing files. */
14382460Snik
14489935Syarstatic volatile sig_atomic_t recvurg;
1451592Srgrimessig_atomic_t transflag;
1461592Srgrimesoff_t	file_size;
1471592Srgrimesoff_t	byte_count;
1481592Srgrimes#if !defined(CMASK) || CMASK == 0
1491592Srgrimes#undef CMASK
1501592Srgrimes#define CMASK 027
1511592Srgrimes#endif
1521592Srgrimesint	defumask = CMASK;		/* default umask value */
1531592Srgrimeschar	tmpline[7];
15427650Sdavidnchar	*hostname;
15578153Sddint	epsvall = 0;
15678153Sdd
15725283Sdavidn#ifdef VIRTUAL_HOSTING
15825283Sdavidnchar	*ftpuser;
15925283Sdavidn
16025283Sdavidnstatic struct ftphost {
16125283Sdavidn	struct ftphost	*next;
16257124Sshin	struct addrinfo *hostinfo;
16325283Sdavidn	char		*hostname;
16425283Sdavidn	char		*anonuser;
16525283Sdavidn	char		*statfile;
16625283Sdavidn	char		*welcome;
16725283Sdavidn	char		*loginmsg;
16825283Sdavidn} *thishost, *firsthost;
16925283Sdavidn
17025283Sdavidn#endif
17145422Sbrianchar	remotehost[MAXHOSTNAMELEN];
1726740Sguidochar	*ident = NULL;
17317435Spst
17417435Spststatic char ttyline[20];
17517435Spstchar	*tty = ttyline;		/* for klogin */
17617435Spst
17774874Smarkm#ifdef USE_PAM
17890148Simpstatic int	auth_pam(struct passwd**, const char*);
17974874Smarkmpam_handle_t *pamh = NULL;
18088763Sache#endif
18179469Smarkm
18279469Smarkmstatic struct opie opiedata;
18379469Smarkmstatic char opieprompt[OPIE_CHALLENGE_MAX+1];
18488763Sachestatic int pwok;
18517478Smarkm
18617483Sjulianchar	*pid_file = NULL;
18717483Sjulian
1881592Srgrimes/*
18974470Sjlemon * Limit number of pathnames that glob can return.
19074470Sjlemon * A limit of 0 indicates the number of pathnames is unlimited.
19174470Sjlemon */
19274470Sjlemon#define MAXGLOBARGS	16384
19374470Sjlemon#
19474470Sjlemon
19574470Sjlemon/*
1961592Srgrimes * Timeout intervals for retrying connections
1971592Srgrimes * to hosts that don't accept PORT cmds.  This
1981592Srgrimes * is a kludge, but given the problems with TCP...
1991592Srgrimes */
2001592Srgrimes#define	SWAITMAX	90	/* wait at most 90 seconds */
2011592Srgrimes#define	SWAITINT	5	/* interval between retries */
2021592Srgrimes
2031592Srgrimesint	swaitmax = SWAITMAX;
2041592Srgrimesint	swaitint = SWAITINT;
2051592Srgrimes
2061592Srgrimes#ifdef SETPROCTITLE
20713139Speter#ifdef OLD_SETPROCTITLE
2081592Srgrimeschar	**Argv = NULL;		/* pointer to argument vector */
2091592Srgrimeschar	*LastArgv = NULL;	/* end of argv */
21013139Speter#endif /* OLD_SETPROCTITLE */
2111592Srgrimeschar	proctitle[LINE_MAX];	/* initial part of title */
2121592Srgrimes#endif /* SETPROCTITLE */
2131592Srgrimes
2141592Srgrimes#define LOGCMD(cmd, file) \
2151592Srgrimes	if (logging > 1) \
2161592Srgrimes	    syslog(LOG_INFO,"%s %s%s", cmd, \
2171592Srgrimes		*(file) == '/' ? "" : curdir(), file);
2181592Srgrimes#define LOGCMD2(cmd, file1, file2) \
2191592Srgrimes	 if (logging > 1) \
2201592Srgrimes	    syslog(LOG_INFO,"%s %s%s %s%s", cmd, \
2211592Srgrimes		*(file1) == '/' ? "" : curdir(), file1, \
2221592Srgrimes		*(file2) == '/' ? "" : curdir(), file2);
2231592Srgrimes#define LOGBYTES(cmd, file, cnt) \
2241592Srgrimes	if (logging > 1) { \
2251592Srgrimes		if (cnt == (off_t)-1) \
2261592Srgrimes		    syslog(LOG_INFO,"%s %s%s", cmd, \
2271592Srgrimes			*(file) == '/' ? "" : curdir(), file); \
2281592Srgrimes		else \
2291592Srgrimes		    syslog(LOG_INFO, "%s %s%s = %qd bytes", \
2301592Srgrimes			cmd, (*(file) == '/') ? "" : curdir(), file, cnt); \
2311592Srgrimes	}
2321592Srgrimes
23325283Sdavidn#ifdef VIRTUAL_HOSTING
23490148Simpstatic void	 inithosts(void);
23590148Simpstatic void	selecthost(union sockunion *);
23625283Sdavidn#endif
23790148Simpstatic void	 ack(char *);
23890148Simpstatic void	 sigurg(int);
23990148Simpstatic void	 myoob(void);
24090148Simpstatic int	 checkuser(char *, char *, int);
24190148Simpstatic FILE	*dataconn(char *, off_t, char *);
24290148Simpstatic void	 dolog(struct sockaddr *);
24390148Simpstatic char	*curdir(void);
24490148Simpstatic void	 end_login(void);
24590148Simpstatic FILE	*getdatasock(char *);
246101537Syarstatic int	 guniquefd(char *, char **);
24790148Simpstatic void	 lostconn(int);
24890148Simpstatic void	 sigquit(int);
24990148Simpstatic int	 receive_data(FILE *, FILE *);
25090148Simpstatic int	 send_data(FILE *, FILE *, off_t, off_t, int);
2511592Srgrimesstatic struct passwd *
25290148Simp		 sgetpwnam(char *);
25390148Simpstatic char	*sgetsave(char *);
25490148Simpstatic void	 reapchild(int);
25590148Simpstatic void      logxfer(char *, off_t, time_t);
256100486Syarstatic char	*doublequote(char *);
2571592Srgrimes
2581592Srgrimesstatic char *
25990148Simpcurdir(void)
2601592Srgrimes{
2611592Srgrimes	static char path[MAXPATHLEN+1+1];	/* path + '/' + '\0' */
2621592Srgrimes
2631592Srgrimes	if (getcwd(path, sizeof(path)-2) == NULL)
2641592Srgrimes		return ("");
2651592Srgrimes	if (path[1] != '\0')		/* special case for root dir. */
2661592Srgrimes		strcat(path, "/");
2671592Srgrimes	/* For guest account, skip / since it's chrooted */
2681592Srgrimes	return (guest ? path+1 : path);
2691592Srgrimes}
2701592Srgrimes
2711592Srgrimesint
27290148Simpmain(int argc, char *argv[], char **envp)
2731592Srgrimes{
2741592Srgrimes	int addrlen, ch, on = 1, tos;
2751592Srgrimes	char *cp, line[LINE_MAX];
2761592Srgrimes	FILE *fd;
27756668Sshin	int error;
27856668Sshin	char	*bindname = NULL;
27956668Sshin	int	family = AF_UNSPEC;
28056668Sshin	int	enable_v4 = 0;
28189935Syar	struct sigaction sa;
2821592Srgrimes
28336105Sache	tzset();		/* in case no timezone database in ~ftp */
28489935Syar	sigemptyset(&sa.sa_mask);
28589935Syar	sa.sa_flags = SA_RESTART;
28636105Sache
28713139Speter#ifdef OLD_SETPROCTITLE
2881592Srgrimes	/*
2891592Srgrimes	 *  Save start and extent of argv for setproctitle.
2901592Srgrimes	 */
2911592Srgrimes	Argv = argv;
2921592Srgrimes	while (*envp)
2931592Srgrimes		envp++;
2941592Srgrimes	LastArgv = envp[-1] + strlen(envp[-1]);
29513139Speter#endif /* OLD_SETPROCTITLE */
2961592Srgrimes
2976740Sguido
298101537Syar	while ((ch = getopt(argc, argv, "46a:AdDElmMoOp:rRSt:T:u:Uv")) != -1) {
2991592Srgrimes		switch (ch) {
300100717Syar		case '4':
301100717Syar			enable_v4 = 1;
302100717Syar			if (family == AF_UNSPEC)
303100717Syar				family = AF_INET;
30415196Sdg			break;
30515196Sdg
306100717Syar		case '6':
307100717Syar			family = AF_INET6;
308100717Syar			break;
309100717Syar
310100717Syar		case 'a':
311100717Syar			bindname = optarg;
312100717Syar			break;
313100717Syar
314100717Syar		case 'A':
315100717Syar			anon_only = 1;
316100717Syar			break;
317100717Syar
3181592Srgrimes		case 'd':
31976096Smarkm			ftpdebug++;
3201592Srgrimes			break;
3211592Srgrimes
322100717Syar		case 'D':
323100717Syar			daemon_mode++;
324100717Syar			break;
325100717Syar
32670102Sphk		case 'E':
32770102Sphk			noepsv = 1;
32870102Sphk			break;
32970102Sphk
3301592Srgrimes		case 'l':
3311592Srgrimes			logging++;	/* > 1 == extra logging */
3321592Srgrimes			break;
3331592Srgrimes
334101537Syar		case 'm':
335101537Syar			noguestmod = 0;
336101537Syar			break;
337101537Syar
338100717Syar		case 'M':
339100717Syar			noguestmkd = 1;
340100717Syar			break;
341100717Syar
342100717Syar		case 'o':
343100717Syar			noretr = 1;
344100717Syar			break;
345100717Syar
346100717Syar		case 'O':
347100717Syar			noguestretr = 1;
348100717Syar			break;
349100717Syar
350100717Syar		case 'p':
351100717Syar			pid_file = optarg;
352100717Syar			break;
353100717Syar
35470102Sphk		case 'r':
35570102Sphk			readonly = 1;
35670102Sphk			break;
35770102Sphk
35817435Spst		case 'R':
35917435Spst			paranoid = 0;
3609933Spst			break;
3619933Spst
3626740Sguido		case 'S':
36317435Spst			stats++;
3646740Sguido			break;
36517435Spst
36617435Spst		case 't':
36717435Spst			timeout = atoi(optarg);
36817435Spst			if (maxtimeout < timeout)
36917435Spst				maxtimeout = timeout;
37017435Spst			break;
37117435Spst
372100717Syar		case 'T':
373100717Syar			maxtimeout = atoi(optarg);
374100717Syar			if (timeout > maxtimeout)
375100717Syar				timeout = maxtimeout;
37617435Spst			break;
37717435Spst
3781592Srgrimes		case 'u':
3791592Srgrimes		    {
3801592Srgrimes			long val = 0;
3811592Srgrimes
3821592Srgrimes			val = strtol(optarg, &optarg, 8);
3831592Srgrimes			if (*optarg != '\0' || val < 0)
3841592Srgrimes				warnx("bad value for -u");
3851592Srgrimes			else
3861592Srgrimes				defumask = val;
3871592Srgrimes			break;
3881592Srgrimes		    }
389100717Syar		case 'U':
390100717Syar			restricted_data_ports = 0;
39120042Storstenb			break;
3921592Srgrimes
3931592Srgrimes		case 'v':
394100720Syar			ftpdebug++;
3951592Srgrimes			break;
3961592Srgrimes
3971592Srgrimes		default:
3981592Srgrimes			warnx("unknown flag -%c ignored", optopt);
3991592Srgrimes			break;
4001592Srgrimes		}
4011592Srgrimes	}
40215196Sdg
40325283Sdavidn#ifdef VIRTUAL_HOSTING
40425283Sdavidn	inithosts();
40525283Sdavidn#endif
4061592Srgrimes	(void) freopen(_PATH_DEVNULL, "w", stderr);
40715196Sdg
40815196Sdg	/*
40915196Sdg	 * LOG_NDELAY sets up the logging connection immediately,
41015196Sdg	 * necessary for anonymous ftp's that chroot and can't do it later.
41115196Sdg	 */
41215196Sdg	openlog("ftpd", LOG_PID | LOG_NDELAY, LOG_FTP);
41315196Sdg
41415196Sdg	if (daemon_mode) {
41515196Sdg		int ctl_sock, fd;
41656668Sshin		struct addrinfo hints, *res;
41715196Sdg
41815196Sdg		/*
41915196Sdg		 * Detach from parent.
42015196Sdg		 */
42115196Sdg		if (daemon(1, 1) < 0) {
42215196Sdg			syslog(LOG_ERR, "failed to become a daemon");
42315196Sdg			exit(1);
42415196Sdg		}
42589935Syar		sa.sa_handler = reapchild;
42689935Syar		(void)sigaction(SIGCHLD, &sa, NULL);
42756668Sshin		/* init bind_sa */
42856668Sshin		memset(&hints, 0, sizeof(hints));
42956668Sshin
43056668Sshin		hints.ai_family = family == AF_UNSPEC ? AF_INET : family;
43156668Sshin		hints.ai_socktype = SOCK_STREAM;
43256668Sshin		hints.ai_protocol = 0;
43356668Sshin		hints.ai_flags = AI_PASSIVE;
43456668Sshin		error = getaddrinfo(bindname, "ftp", &hints, &res);
43556668Sshin		if (error) {
43656668Sshin			if (family == AF_UNSPEC) {
43756668Sshin				hints.ai_family = AF_UNSPEC;
43856668Sshin				error = getaddrinfo(bindname, "ftp", &hints,
43956668Sshin						    &res);
44056668Sshin			}
44156668Sshin		}
44256668Sshin		if (error) {
44368901Skris			syslog(LOG_ERR, "%s", gai_strerror(error));
44456668Sshin			if (error == EAI_SYSTEM)
44568901Skris				syslog(LOG_ERR, "%s", strerror(errno));
44615196Sdg			exit(1);
44715196Sdg		}
44856668Sshin		if (res->ai_addr == NULL) {
44956668Sshin			syslog(LOG_ERR, "-a %s: getaddrinfo failed", hostname);
45056668Sshin			exit(1);
45164699Sru		} else
45264699Sru			family = res->ai_addr->sa_family;
45315196Sdg		/*
45415196Sdg		 * Open a socket, bind it to the FTP port, and start
45515196Sdg		 * listening.
45615196Sdg		 */
45756668Sshin		ctl_sock = socket(family, SOCK_STREAM, 0);
45815196Sdg		if (ctl_sock < 0) {
45915196Sdg			syslog(LOG_ERR, "control socket: %m");
46015196Sdg			exit(1);
46115196Sdg		}
46215196Sdg		if (setsockopt(ctl_sock, SOL_SOCKET, SO_REUSEADDR,
463100612Syar		    &on, sizeof(on)) < 0)
464100609Syar			syslog(LOG_WARNING,
465100609Syar			       "control setsockopt (SO_REUSEADDR): %m");
46656668Sshin		if (family == AF_INET6 && enable_v4 == 0) {
467100505Sume			if (setsockopt(ctl_sock, IPPROTO_IPV6, IPV6_V6ONLY,
468100612Syar				       &on, sizeof (on)) < 0)
469100609Syar				syslog(LOG_WARNING,
470100609Syar				       "control setsockopt (IPV6_V6ONLY): %m");
47156668Sshin		}
47256668Sshin		memcpy(&server_addr, res->ai_addr, res->ai_addr->sa_len);
47356668Sshin		if (bind(ctl_sock, (struct sockaddr *)&server_addr,
47456668Sshin			 server_addr.su_len) < 0) {
47515196Sdg			syslog(LOG_ERR, "control bind: %m");
47615196Sdg			exit(1);
47715196Sdg		}
47815196Sdg		if (listen(ctl_sock, 32) < 0) {
47915196Sdg			syslog(LOG_ERR, "control listen: %m");
48015196Sdg			exit(1);
48115196Sdg		}
48215196Sdg		/*
48317483Sjulian		 * Atomically write process ID
48417483Sjulian		 */
48517483Sjulian		if (pid_file)
48699213Smaxim		{
48717483Sjulian			int fd;
48817483Sjulian			char buf[20];
48917483Sjulian
49017483Sjulian			fd = open(pid_file, O_CREAT | O_WRONLY | O_TRUNC
49117483Sjulian				| O_NONBLOCK | O_EXLOCK, 0644);
49246078Simp			if (fd < 0) {
49317483Sjulian				if (errno == EAGAIN)
49417483Sjulian					errx(1, "%s: file locked", pid_file);
49517483Sjulian				else
49617483Sjulian					err(1, "%s", pid_file);
49746078Simp			}
49817483Sjulian			snprintf(buf, sizeof(buf),
49917483Sjulian				"%lu\n", (unsigned long) getpid());
50017483Sjulian			if (write(fd, buf, strlen(buf)) < 0)
50117483Sjulian				err(1, "%s: write", pid_file);
50217483Sjulian			/* Leave the pid file open and locked */
50317483Sjulian		}
50417483Sjulian		/*
50515196Sdg		 * Loop forever accepting connection requests and forking off
50615196Sdg		 * children to handle them.
50715196Sdg		 */
50815196Sdg		while (1) {
50956668Sshin			addrlen = server_addr.su_len;
51015196Sdg			fd = accept(ctl_sock, (struct sockaddr *)&his_addr, &addrlen);
51115196Sdg			if (fork() == 0) {
51215196Sdg				/* child */
51315196Sdg				(void) dup2(fd, 0);
51415196Sdg				(void) dup2(fd, 1);
51515196Sdg				close(ctl_sock);
51615196Sdg				break;
51715196Sdg			}
51815196Sdg			close(fd);
51915196Sdg		}
52015196Sdg	} else {
52115196Sdg		addrlen = sizeof(his_addr);
52215196Sdg		if (getpeername(0, (struct sockaddr *)&his_addr, &addrlen) < 0) {
52315196Sdg			syslog(LOG_ERR, "getpeername (%s): %m",argv[0]);
52415196Sdg			exit(1);
52515196Sdg		}
52615196Sdg	}
52715196Sdg
52889935Syar	sa.sa_handler = SIG_DFL;
52989935Syar	(void)sigaction(SIGCHLD, &sa, NULL);
5301592Srgrimes
53189935Syar	sa.sa_handler = sigurg;
53289935Syar	sa.sa_flags = 0;		/* don't restart syscalls for SIGURG */
53389935Syar	(void)sigaction(SIGURG, &sa, NULL);
53489935Syar
53589935Syar	sigfillset(&sa.sa_mask);	/* block all signals in handler */
53689935Syar	sa.sa_flags = SA_RESTART;
53789935Syar	sa.sa_handler = sigquit;
53889935Syar	(void)sigaction(SIGHUP, &sa, NULL);
53989935Syar	(void)sigaction(SIGINT, &sa, NULL);
54089935Syar	(void)sigaction(SIGQUIT, &sa, NULL);
54189935Syar	(void)sigaction(SIGTERM, &sa, NULL);
54289935Syar
54389935Syar	sa.sa_handler = lostconn;
54489935Syar	(void)sigaction(SIGPIPE, &sa, NULL);
54589935Syar
54615196Sdg	addrlen = sizeof(ctrl_addr);
54715196Sdg	if (getsockname(0, (struct sockaddr *)&ctrl_addr, &addrlen) < 0) {
54815196Sdg		syslog(LOG_ERR, "getsockname (%s): %m",argv[0]);
54915196Sdg		exit(1);
55015196Sdg	}
55125283Sdavidn#ifdef VIRTUAL_HOSTING
55225283Sdavidn	/* select our identity from virtual host table */
55356668Sshin	selecthost(&ctrl_addr);
55425283Sdavidn#endif
55515196Sdg#ifdef IP_TOS
55656668Sshin	if (ctrl_addr.su_family == AF_INET)
55756668Sshin      {
55815196Sdg	tos = IPTOS_LOWDELAY;
559100612Syar	if (setsockopt(0, IPPROTO_IP, IP_TOS, &tos, sizeof(int)) < 0)
560100609Syar		syslog(LOG_WARNING, "control setsockopt (IP_TOS): %m");
56156668Sshin      }
56215196Sdg#endif
56335482Sdg	/*
56435482Sdg	 * Disable Nagle on the control channel so that we don't have to wait
56535482Sdg	 * for peer's ACK before issuing our next reply.
56635482Sdg	 */
56735482Sdg	if (setsockopt(0, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)) < 0)
568100609Syar		syslog(LOG_WARNING, "control setsockopt (TCP_NODELAY): %m");
56935482Sdg
57056668Sshin	data_source.su_port = htons(ntohs(ctrl_addr.su_port) - 1);
57115196Sdg
57217435Spst	/* set this here so klogin can use it... */
57331973Simp	(void)snprintf(ttyline, sizeof(ttyline), "ftp%d", getpid());
57417435Spst
5751592Srgrimes	/* Try to handle urgent data inline */
5761592Srgrimes#ifdef SO_OOBINLINE
577100612Syar	if (setsockopt(0, SOL_SOCKET, SO_OOBINLINE, &on, sizeof(on)) < 0)
578100609Syar		syslog(LOG_WARNING, "control setsockopt (SO_OOBINLINE): %m");
5791592Srgrimes#endif
5801592Srgrimes
5811592Srgrimes#ifdef	F_SETOWN
5821592Srgrimes	if (fcntl(fileno(stdin), F_SETOWN, getpid()) == -1)
5831592Srgrimes		syslog(LOG_ERR, "fcntl F_SETOWN: %m");
5841592Srgrimes#endif
58556668Sshin	dolog((struct sockaddr *)&his_addr);
5861592Srgrimes	/*
5871592Srgrimes	 * Set up default state
5881592Srgrimes	 */
5891592Srgrimes	data = -1;
5901592Srgrimes	type = TYPE_A;
5911592Srgrimes	form = FORM_N;
5921592Srgrimes	stru = STRU_F;
5931592Srgrimes	mode = MODE_S;
5941592Srgrimes	tmpline[0] = '\0';
5951592Srgrimes
5961592Srgrimes	/* If logins are disabled, print out the message. */
5971592Srgrimes	if ((fd = fopen(_PATH_NOLOGIN,"r")) != NULL) {
5981592Srgrimes		while (fgets(line, sizeof(line), fd) != NULL) {
5991592Srgrimes			if ((cp = strchr(line, '\n')) != NULL)
6001592Srgrimes				*cp = '\0';
6011592Srgrimes			lreply(530, "%s", line);
6021592Srgrimes		}
6031592Srgrimes		(void) fflush(stdout);
6041592Srgrimes		(void) fclose(fd);
6051592Srgrimes		reply(530, "System not available.");
6061592Srgrimes		exit(0);
6071592Srgrimes	}
60825283Sdavidn#ifdef VIRTUAL_HOSTING
60925283Sdavidn	if ((fd = fopen(thishost->welcome, "r")) != NULL) {
61025283Sdavidn#else
6111592Srgrimes	if ((fd = fopen(_PATH_FTPWELCOME, "r")) != NULL) {
61225283Sdavidn#endif
6131592Srgrimes		while (fgets(line, sizeof(line), fd) != NULL) {
6141592Srgrimes			if ((cp = strchr(line, '\n')) != NULL)
6151592Srgrimes				*cp = '\0';
6161592Srgrimes			lreply(220, "%s", line);
6171592Srgrimes		}
6181592Srgrimes		(void) fflush(stdout);
6191592Srgrimes		(void) fclose(fd);
6201592Srgrimes		/* reply(220,) must follow */
6211592Srgrimes	}
62225283Sdavidn#ifndef VIRTUAL_HOSTING
62327650Sdavidn	if ((hostname = malloc(MAXHOSTNAMELEN)) == NULL)
62476096Smarkm		fatalerror("Ran out of memory.");
62545422Sbrian	(void) gethostname(hostname, MAXHOSTNAMELEN - 1);
62645422Sbrian	hostname[MAXHOSTNAMELEN - 1] = '\0';
62725283Sdavidn#endif
6281592Srgrimes	reply(220, "%s FTP server (%s) ready.", hostname, version);
6291592Srgrimes	for (;;)
6301592Srgrimes		(void) yyparse();
6311592Srgrimes	/* NOTREACHED */
6321592Srgrimes}
6331592Srgrimes
6341592Srgrimesstatic void
63590148Simplostconn(int signo)
6361592Srgrimes{
6371592Srgrimes
63876096Smarkm	if (ftpdebug)
6391592Srgrimes		syslog(LOG_DEBUG, "lost connection");
64031329Scharnier	dologout(1);
6411592Srgrimes}
6421592Srgrimes
64389935Syarstatic void
64490148Simpsigquit(int signo)
64589935Syar{
64689935Syar
64789935Syar	syslog(LOG_ERR, "got signal %d", signo);
64889935Syar	dologout(1);
64989935Syar}
65089935Syar
65125283Sdavidn#ifdef VIRTUAL_HOSTING
6521592Srgrimes/*
65325283Sdavidn * read in virtual host tables (if they exist)
65425283Sdavidn */
65525283Sdavidn
65625283Sdavidnstatic void
65790148Simpinithosts(void)
65825283Sdavidn{
659100182Syar	int insert;
66099877Syar	size_t len;
66125283Sdavidn	FILE *fp;
66299877Syar	char *cp, *mp, *line;
66399877Syar	char *hostname;
664100182Syar	char *vhost, *anonuser, *statfile, *welcome, *loginmsg;
66525283Sdavidn	struct ftphost *hrp, *lhrp;
66656668Sshin	struct addrinfo hints, *res, *ai;
66725283Sdavidn
66825283Sdavidn	/*
66925283Sdavidn	 * Fill in the default host information
67025283Sdavidn	 */
67199877Syar	if ((hostname = malloc(MAXHOSTNAMELEN)) == NULL)
67276096Smarkm		fatalerror("Ran out of memory.");
67399877Syar	if (gethostname(hostname, MAXHOSTNAMELEN) < 0)
67499877Syar		hostname[0] = '\0';
67599877Syar	hostname[MAXHOSTNAMELEN - 1] = '\0';
67699877Syar	if ((hrp = malloc(sizeof(struct ftphost))) == NULL)
67799877Syar		fatalerror("Ran out of memory.");
67899877Syar	hrp->hostname = hostname;
67957124Sshin	hrp->hostinfo = NULL;
68056668Sshin
68156668Sshin	memset(&hints, 0, sizeof(hints));
68256668Sshin	hints.ai_flags = AI_CANONNAME;
68356668Sshin	hints.ai_family = AF_UNSPEC;
684102183Syar	if (getaddrinfo(hrp->hostname, NULL, &hints, &res) == 0)
68557124Sshin		hrp->hostinfo = res;
68625283Sdavidn	hrp->statfile = _PATH_FTPDSTATFILE;
68725283Sdavidn	hrp->welcome  = _PATH_FTPWELCOME;
68825283Sdavidn	hrp->loginmsg = _PATH_FTPLOGINMESG;
68925283Sdavidn	hrp->anonuser = "ftp";
69025283Sdavidn	hrp->next = NULL;
69125283Sdavidn	thishost = firsthost = lhrp = hrp;
69225283Sdavidn	if ((fp = fopen(_PATH_FTPHOSTS, "r")) != NULL) {
69362100Sdavidn		int addrsize, error, gothost;
69456668Sshin		void *addr;
69556668Sshin		struct hostent *hp;
69656668Sshin
69799877Syar		while ((line = fgetln(fp, &len)) != NULL) {
69856668Sshin			int	i, hp_error;
69925283Sdavidn
70099877Syar			/* skip comments */
70199877Syar			if (line[0] == '#')
70225283Sdavidn				continue;
70399877Syar			if (line[len - 1] == '\n') {
70499877Syar				line[len - 1] = '\0';
70599877Syar				mp = NULL;
70699877Syar			} else {
70799877Syar				if ((mp = malloc(len + 1)) == NULL)
70899877Syar					fatalerror("Ran out of memory.");
70999877Syar				memcpy(mp, line, len);
71099877Syar				mp[len] = '\0';
71199877Syar				line = mp;
71225283Sdavidn			}
71325283Sdavidn			cp = strtok(line, " \t");
71499877Syar			/* skip empty lines */
71599877Syar			if (cp == NULL)
71699877Syar				goto nextline;
717100182Syar			vhost = cp;
71856668Sshin
719100182Syar			/* set defaults */
720100182Syar			anonuser = "ftp";
721100182Syar			statfile = _PATH_FTPDSTATFILE;
722100182Syar			welcome  = _PATH_FTPWELCOME;
723100182Syar			loginmsg = _PATH_FTPLOGINMESG;
724100182Syar
725100182Syar			/*
726100182Syar			 * Preparse the line so we can use its info
727100182Syar			 * for all the addresses associated with
728100182Syar			 * the virtual host name.
729100182Syar			 * Field 0, the virtual host name, is special:
730100182Syar			 * it's already parsed off and will be strdup'ed
731100182Syar			 * later, after we know its canonical form.
732100182Syar			 */
733100182Syar			for (i = 1; i < 5 && (cp = strtok(NULL, " \t")); i++)
734100182Syar				if (*cp != '-' && (cp = strdup(cp)))
735100182Syar					switch (i) {
736100182Syar					case 1:	/* anon user permissions */
737100182Syar						anonuser = cp;
738100182Syar						break;
739100182Syar					case 2: /* statistics file */
740100182Syar						statfile = cp;
741100182Syar						break;
742100182Syar					case 3: /* welcome message */
743100182Syar						welcome  = cp;
744100182Syar						break;
745100182Syar					case 4: /* login message */
746100182Syar						loginmsg = cp;
747100182Syar						break;
748100182Syar					default: /* programming error */
749100182Syar						abort();
750100182Syar						/* NOTREACHED */
751100182Syar					}
752100182Syar
75356668Sshin			hints.ai_flags = 0;
75456668Sshin			hints.ai_family = AF_UNSPEC;
75556668Sshin			hints.ai_flags = AI_PASSIVE;
756102183Syar			if (getaddrinfo(vhost, NULL, &hints, &res) != 0)
75799877Syar				goto nextline;
75856668Sshin			for (ai = res; ai != NULL && ai->ai_addr != NULL;
75962100Sdavidn			     ai = ai->ai_next) {
76056668Sshin
76162100Sdavidn			gothost = 0;
76225283Sdavidn			for (hrp = firsthost; hrp != NULL; hrp = hrp->next) {
76357124Sshin				struct addrinfo *hi;
76457124Sshin
76557124Sshin				for (hi = hrp->hostinfo; hi != NULL;
76657124Sshin				     hi = hi->ai_next)
76757124Sshin					if (hi->ai_addrlen == ai->ai_addrlen &&
76857124Sshin					    memcmp(hi->ai_addr,
76957124Sshin						   ai->ai_addr,
77062100Sdavidn						   ai->ai_addr->sa_len) == 0) {
77162100Sdavidn						gothost++;
77257124Sshin						break;
773100183Syar					}
77462100Sdavidn				if (gothost)
77562100Sdavidn					break;
77625283Sdavidn			}
77725283Sdavidn			if (hrp == NULL) {
77825283Sdavidn				if ((hrp = malloc(sizeof(struct ftphost))) == NULL)
77999877Syar					goto nextline;
780102183Syar				hrp->hostname = NULL;
781102183Syar				hrp->hostinfo = NULL;
782100182Syar				insert = 1;
783100182Syar			} else
784100182Syar				insert = 0; /* host already in the chain */
785102183Syar			if (hrp->hostinfo)
786102183Syar				freeaddrinfo(hrp->hostinfo);
78757124Sshin			hrp->hostinfo = res;
78857124Sshin
78925283Sdavidn			/*
79025283Sdavidn			 * determine hostname to use.
79156668Sshin			 * force defined name if there is a valid alias
79225283Sdavidn			 * otherwise fallback to primary hostname
79325283Sdavidn			 */
79456668Sshin			/* XXX: getaddrinfo() can't do alias check */
79557124Sshin			switch(hrp->hostinfo->ai_family) {
79656668Sshin			case AF_INET:
797100259Syar				addr = &((struct sockaddr_in *)hrp->hostinfo->ai_addr)->sin_addr;
798100259Syar				addrsize = sizeof(struct in_addr);
79956668Sshin				break;
80056668Sshin			case AF_INET6:
801100259Syar				addr = &((struct sockaddr_in6 *)hrp->hostinfo->ai_addr)->sin6_addr;
802100259Syar				addrsize = sizeof(struct in6_addr);
80356668Sshin				break;
80456668Sshin			default:
80556668Sshin				/* should not reach here */
80657124Sshin				if (hrp->hostinfo != NULL)
80757124Sshin					freeaddrinfo(hrp->hostinfo);
80856668Sshin				free(hrp);
80999877Syar				goto nextline;
81056668Sshin				/* NOTREACHED */
81156668Sshin			}
812100612Syar			if ((hp = getipnodebyaddr(addr, addrsize,
81357124Sshin						  hrp->hostinfo->ai_family,
81456668Sshin						  &hp_error)) != NULL) {
815100182Syar				if (strcmp(vhost, hp->h_name) != 0) {
81625283Sdavidn					if (hp->h_aliases == NULL)
817100182Syar						vhost = hp->h_name;
81825283Sdavidn					else {
81925283Sdavidn						i = 0;
82025283Sdavidn						while (hp->h_aliases[i] &&
821100182Syar						       strcmp(vhost, hp->h_aliases[i]) != 0)
82225283Sdavidn							++i;
82325283Sdavidn						if (hp->h_aliases[i] == NULL)
824100182Syar							vhost = hp->h_name;
82525283Sdavidn					}
82625283Sdavidn				}
82725283Sdavidn			}
828102183Syar			if (hrp->hostname &&
829102183Syar			    strcmp(hrp->hostname, vhost) != 0) {
830102183Syar				free(hrp->hostname);
831102183Syar				hrp->hostname = NULL;
832102183Syar			}
833102183Syar			if (hrp->hostname == NULL &&
834102183Syar			    (hrp->hostname = strdup(vhost)) == NULL)
835100182Syar				goto nextline;
836100182Syar			hrp->anonuser = anonuser;
837100182Syar			hrp->statfile = statfile;
838100182Syar			hrp->welcome  = welcome;
839100182Syar			hrp->loginmsg = loginmsg;
840100182Syar			if (insert) {
841100182Syar				hrp->next  = NULL;
842100182Syar				lhrp->next = hrp;
843100182Syar				lhrp = hrp;
844100182Syar			}
845100263Syar			if (hp)
846100263Syar				freehostent(hp);
84756668Sshin		      }
84899877Syarnextline:
84999877Syar			if (mp)
85099877Syar				free(mp);
85125283Sdavidn		}
85225283Sdavidn		(void) fclose(fp);
85325283Sdavidn	}
85425283Sdavidn}
85525283Sdavidn
85625283Sdavidnstatic void
85790148Simpselecthost(union sockunion *su)
85825283Sdavidn{
85925283Sdavidn	struct ftphost	*hrp;
86056668Sshin	u_int16_t port;
86156668Sshin#ifdef INET6
86256668Sshin	struct in6_addr *mapped_in6 = NULL;
86356668Sshin#endif
86457124Sshin	struct addrinfo *hi;
86525283Sdavidn
86656668Sshin#ifdef INET6
86756668Sshin	/*
86856668Sshin	 * XXX IPv4 mapped IPv6 addr consideraton,
86956668Sshin	 * specified in rfc2373.
87056668Sshin	 */
87156668Sshin	if (su->su_family == AF_INET6 &&
87256668Sshin	    IN6_IS_ADDR_V4MAPPED(&su->su_sin6.sin6_addr))
87356668Sshin		mapped_in6 = &su->su_sin6.sin6_addr;
87456668Sshin#endif
87556668Sshin
87625283Sdavidn	hrp = thishost = firsthost;	/* default */
87756668Sshin	port = su->su_port;
87856668Sshin	su->su_port = 0;
87925283Sdavidn	while (hrp != NULL) {
88062100Sdavidn	    for (hi = hrp->hostinfo; hi != NULL; hi = hi->ai_next) {
88157124Sshin		if (memcmp(su, hi->ai_addr, hi->ai_addrlen) == 0) {
88225283Sdavidn			thishost = hrp;
88325283Sdavidn			break;
88425283Sdavidn		}
88556668Sshin#ifdef INET6
88656668Sshin		/* XXX IPv4 mapped IPv6 addr consideraton */
88757124Sshin		if (hi->ai_addr->sa_family == AF_INET && mapped_in6 != NULL &&
88856668Sshin		    (memcmp(&mapped_in6->s6_addr[12],
88957124Sshin			    &((struct sockaddr_in *)hi->ai_addr)->sin_addr,
89056668Sshin			    sizeof(struct in_addr)) == 0)) {
89156668Sshin			thishost = hrp;
89256668Sshin			break;
89356668Sshin		}
89456668Sshin#endif
89562100Sdavidn	    }
89662100Sdavidn	    hrp = hrp->next;
89725283Sdavidn	}
89856668Sshin	su->su_port = port;
89925283Sdavidn	/* setup static variables as appropriate */
90025283Sdavidn	hostname = thishost->hostname;
90125283Sdavidn	ftpuser = thishost->anonuser;
90225283Sdavidn}
90325283Sdavidn#endif
90425283Sdavidn
90525283Sdavidn/*
9061592Srgrimes * Helper function for sgetpwnam().
9071592Srgrimes */
9081592Srgrimesstatic char *
90990148Simpsgetsave(char *s)
9101592Srgrimes{
9111592Srgrimes	char *new = malloc((unsigned) strlen(s) + 1);
9121592Srgrimes
9131592Srgrimes	if (new == NULL) {
9141592Srgrimes		perror_reply(421, "Local resource failure: malloc");
9151592Srgrimes		dologout(1);
9161592Srgrimes		/* NOTREACHED */
9171592Srgrimes	}
9181592Srgrimes	(void) strcpy(new, s);
9191592Srgrimes	return (new);
9201592Srgrimes}
9211592Srgrimes
9221592Srgrimes/*
9231592Srgrimes * Save the result of a getpwnam.  Used for USER command, since
9241592Srgrimes * the data returned must not be clobbered by any other command
9251592Srgrimes * (e.g., globbing).
9261592Srgrimes */
9271592Srgrimesstatic struct passwd *
92890148Simpsgetpwnam(char *name)
9291592Srgrimes{
9301592Srgrimes	static struct passwd save;
9311592Srgrimes	struct passwd *p;
9321592Srgrimes
9331592Srgrimes	if ((p = getpwnam(name)) == NULL)
9341592Srgrimes		return (p);
9351592Srgrimes	if (save.pw_name) {
9361592Srgrimes		free(save.pw_name);
9371592Srgrimes		free(save.pw_passwd);
9381592Srgrimes		free(save.pw_gecos);
9391592Srgrimes		free(save.pw_dir);
9401592Srgrimes		free(save.pw_shell);
9411592Srgrimes	}
9421592Srgrimes	save = *p;
9431592Srgrimes	save.pw_name = sgetsave(p->pw_name);
9441592Srgrimes	save.pw_passwd = sgetsave(p->pw_passwd);
9451592Srgrimes	save.pw_gecos = sgetsave(p->pw_gecos);
9461592Srgrimes	save.pw_dir = sgetsave(p->pw_dir);
9471592Srgrimes	save.pw_shell = sgetsave(p->pw_shell);
9481592Srgrimes	return (&save);
9491592Srgrimes}
9501592Srgrimes
9511592Srgrimesstatic int login_attempts;	/* number of failed login attempts */
9521592Srgrimesstatic int askpasswd;		/* had user command, ask for passwd */
95364778Ssheldonhstatic char curname[MAXLOGNAME];	/* current USER name */
9541592Srgrimes
9551592Srgrimes/*
9561592Srgrimes * USER command.
9571592Srgrimes * Sets global passwd pointer pw if named account exists and is acceptable;
9581592Srgrimes * sets askpasswd if a PASS command is expected.  If logged in previously,
9591592Srgrimes * need to reset state.  If name is "ftp" or "anonymous", the name is not in
9601592Srgrimes * _PATH_FTPUSERS, and ftp account exists, set guest and pw, then just return.
9611592Srgrimes * If account doesn't exist, ask for passwd anyway.  Otherwise, check user
9621592Srgrimes * requesting login privileges.  Disallow anyone who does not have a standard
9631592Srgrimes * shell as returned by getusershell().  Disallow anyone mentioned in the file
9641592Srgrimes * _PATH_FTPUSERS to allow people such as root and uucp to be avoided.
9651592Srgrimes */
9661592Srgrimesvoid
96790148Simpuser(char *name)
9681592Srgrimes{
9691592Srgrimes	char *cp, *shell;
9701592Srgrimes
9711592Srgrimes	if (logged_in) {
9721592Srgrimes		if (guest) {
9731592Srgrimes			reply(530, "Can't change user from guest login.");
9741592Srgrimes			return;
97517435Spst		} else if (dochroot) {
97617435Spst			reply(530, "Can't change user from chroot user.");
97717435Spst			return;
9781592Srgrimes		}
9791592Srgrimes		end_login();
9801592Srgrimes	}
9811592Srgrimes
9821592Srgrimes	guest = 0;
9831592Srgrimes	if (strcmp(name, "ftp") == 0 || strcmp(name, "anonymous") == 0) {
98436349Ssteve		if (checkuser(_PATH_FTPUSERS, "ftp", 0) ||
98536349Ssteve		    checkuser(_PATH_FTPUSERS, "anonymous", 0))
9861592Srgrimes			reply(530, "User %s access denied.", name);
98725283Sdavidn#ifdef VIRTUAL_HOSTING
98825283Sdavidn		else if ((pw = sgetpwnam(thishost->anonuser)) != NULL) {
98925283Sdavidn#else
9901592Srgrimes		else if ((pw = sgetpwnam("ftp")) != NULL) {
99125283Sdavidn#endif
9921592Srgrimes			guest = 1;
9931592Srgrimes			askpasswd = 1;
9941592Srgrimes			reply(331,
9953938Spst			"Guest login ok, send your email address as password.");
9961592Srgrimes		} else
9971592Srgrimes			reply(530, "User %s unknown.", name);
9981592Srgrimes		if (!askpasswd && logging)
9991592Srgrimes			syslog(LOG_NOTICE,
10001592Srgrimes			    "ANONYMOUS FTP LOGIN REFUSED FROM %s", remotehost);
10011592Srgrimes		return;
10021592Srgrimes	}
100320042Storstenb	if (anon_only != 0) {
100420042Storstenb		reply(530, "Sorry, only anonymous ftp allowed.");
100520042Storstenb		return;
100620042Storstenb	}
100720042Storstenb
100817478Smarkm	if ((pw = sgetpwnam(name))) {
10091592Srgrimes		if ((shell = pw->pw_shell) == NULL || *shell == 0)
10101592Srgrimes			shell = _PATH_BSHELL;
10111592Srgrimes		while ((cp = getusershell()) != NULL)
10121592Srgrimes			if (strcmp(cp, shell) == 0)
10131592Srgrimes				break;
10141592Srgrimes		endusershell();
10151592Srgrimes
101636349Ssteve		if (cp == NULL || checkuser(_PATH_FTPUSERS, name, 1)) {
10171592Srgrimes			reply(530, "User %s access denied.", name);
10181592Srgrimes			if (logging)
10191592Srgrimes				syslog(LOG_NOTICE,
10201592Srgrimes				    "FTP LOGIN REFUSED FROM %s, %s",
10211592Srgrimes				    remotehost, name);
10221592Srgrimes			pw = (struct passwd *) NULL;
10231592Srgrimes			return;
10241592Srgrimes		}
10251592Srgrimes	}
10261592Srgrimes	if (logging)
10271592Srgrimes		strncpy(curname, name, sizeof(curname)-1);
102888763Sache
102988763Sache	pwok = 0;
103079469Smarkm#ifdef USE_PAM
103179469Smarkm	/* XXX Kluge! The conversation mechanism needs to be fixed. */
103288763Sache#endif
103388763Sache	if (opiechallenge(&opiedata, name, opieprompt) == 0) {
103488763Sache		pwok = (pw != NULL) &&
103588763Sache		       opieaccessfile(remotehost) &&
103688763Sache		       opiealways(pw->pw_dir);
103788763Sache		reply(331, "Response to %s %s for %s.",
103888763Sache		      opieprompt, pwok ? "requested" : "required", name);
103988763Sache	} else {
104088763Sache		pwok = 1;
104184146Sache		reply(331, "Password required for %s.", name);
104288763Sache	}
10431592Srgrimes	askpasswd = 1;
10441592Srgrimes	/*
10451592Srgrimes	 * Delay before reading passwd after first failed
10461592Srgrimes	 * attempt to slow down passwd-guessing programs.
10471592Srgrimes	 */
10481592Srgrimes	if (login_attempts)
10491592Srgrimes		sleep((unsigned) login_attempts);
10501592Srgrimes}
10511592Srgrimes
10521592Srgrimes/*
105317435Spst * Check if a user is in the file "fname"
10541592Srgrimes */
10551592Srgrimesstatic int
105690148Simpcheckuser(char *fname, char *name, int pwset)
10571592Srgrimes{
10581592Srgrimes	FILE *fd;
10591592Srgrimes	int found = 0;
106099877Syar	size_t len;
106199877Syar	char *line, *mp, *p;
10621592Srgrimes
106317435Spst	if ((fd = fopen(fname, "r")) != NULL) {
106499877Syar		while (!found && (line = fgetln(fd, &len)) != NULL) {
106599877Syar			/* skip comments */
106699877Syar			if (line[0] == '#')
106799877Syar				continue;
106899877Syar			if (line[len - 1] == '\n') {
106999877Syar				line[len - 1] = '\0';
107099877Syar				mp = NULL;
107199877Syar			} else {
107299877Syar				if ((mp = malloc(len + 1)) == NULL)
107399877Syar					fatalerror("Ran out of memory.");
107499877Syar				memcpy(mp, line, len);
107599877Syar				mp[len] = '\0';
107699877Syar				line = mp;
107799877Syar			}
107899877Syar			/* avoid possible leading and trailing whitespace */
107999877Syar			p = strtok(line, " \t");
108099877Syar			/* skip empty lines */
108199877Syar			if (p == NULL)
108299877Syar				goto nextline;
108399877Syar			/*
108499877Syar			 * if first chr is '@', check group membership
108599877Syar			 */
108699877Syar			if (p[0] == '@') {
108799877Syar				int i = 0;
108899877Syar				struct group *grp;
108999877Syar
109099877Syar				if ((grp = getgrnam(p+1)) == NULL)
109199877Syar					goto nextline;
109225187Sdavidn				/*
109399877Syar				 * Check user's default group
109425187Sdavidn				 */
109599877Syar				if (pwset && grp->gr_gid == pw->pw_gid)
109699877Syar					found = 1;
109725187Sdavidn				/*
109899877Syar				 * Check supplementary groups
109925187Sdavidn				 */
110099877Syar				while (!found && grp->gr_mem[i])
110199877Syar					found = strcmp(name,
110299877Syar						grp->gr_mem[i++])
110399877Syar						== 0;
11041592Srgrimes			}
110599877Syar			/*
110699877Syar			 * Otherwise, just check for username match
110799877Syar			 */
110899877Syar			else
110999877Syar				found = strcmp(p, name) == 0;
111099877Syarnextline:
111199877Syar			if (mp)
111299877Syar				free(mp);
111399877Syar		}
11141592Srgrimes		(void) fclose(fd);
11151592Srgrimes	}
11161592Srgrimes	return (found);
11171592Srgrimes}
11181592Srgrimes
11191592Srgrimes/*
11201592Srgrimes * Terminate login as previous user, if any, resetting state;
11211592Srgrimes * used when USER command is given or login fails.
11221592Srgrimes */
11231592Srgrimesstatic void
112490148Simpend_login(void)
11251592Srgrimes{
112674874Smarkm#ifdef USE_PAM
112774874Smarkm	int e;
112874874Smarkm#endif
11291592Srgrimes
11301592Srgrimes	(void) seteuid((uid_t)0);
11311592Srgrimes	if (logged_in)
113289920Sume		ftpd_logwtmp(ttyline, "", NULL);
11331592Srgrimes	pw = NULL;
113425101Sdavidn#ifdef	LOGIN_CAP
113525101Sdavidn	setusercontext(NULL, getpwuid(0), (uid_t)0,
113625101Sdavidn		       LOGIN_SETPRIORITY|LOGIN_SETRESOURCES|LOGIN_SETUMASK);
113725101Sdavidn#endif
113874874Smarkm#ifdef USE_PAM
113974874Smarkm	if ((e = pam_setcred(pamh, PAM_DELETE_CRED)) != PAM_SUCCESS)
114074874Smarkm		syslog(LOG_ERR, "pam_setcred: %s", pam_strerror(pamh, e));
114174874Smarkm	if ((e = pam_close_session(pamh,0)) != PAM_SUCCESS)
114274874Smarkm		syslog(LOG_ERR, "pam_close_session: %s", pam_strerror(pamh, e));
114374874Smarkm	if ((e = pam_end(pamh, e)) != PAM_SUCCESS)
114474874Smarkm		syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e));
114574874Smarkm	pamh = NULL;
114674874Smarkm#endif
11471592Srgrimes	logged_in = 0;
11481592Srgrimes	guest = 0;
114917435Spst	dochroot = 0;
11501592Srgrimes}
11511592Srgrimes
115274874Smarkm#ifdef USE_PAM
115351433Smarkm
115451433Smarkm/*
115551433Smarkm * the following code is stolen from imap-uw PAM authentication module and
115651433Smarkm * login.c
115751433Smarkm */
115851433Smarkm#define COPY_STRING(s) (s ? strdup(s) : NULL)
115951433Smarkm
116051433Smarkmstruct cred_t {
116151433Smarkm	const char *uname;		/* user name */
116251433Smarkm	const char *pass;		/* password */
116351433Smarkm};
116451433Smarkmtypedef struct cred_t cred_t;
116551433Smarkm
116651433Smarkmstatic int
116751433Smarkmauth_conv(int num_msg, const struct pam_message **msg,
116851433Smarkm	  struct pam_response **resp, void *appdata)
116951433Smarkm{
117051433Smarkm	int i;
117151433Smarkm	cred_t *cred = (cred_t *) appdata;
117291244Sdes	struct pam_response *reply;
117351433Smarkm
117491244Sdes	reply = calloc(num_msg, sizeof *reply);
117591244Sdes	if (reply == NULL)
117691244Sdes		return PAM_BUF_ERR;
117791244Sdes
117851433Smarkm	for (i = 0; i < num_msg; i++) {
117951433Smarkm		switch (msg[i]->msg_style) {
118051433Smarkm		case PAM_PROMPT_ECHO_ON:	/* assume want user name */
118151433Smarkm			reply[i].resp_retcode = PAM_SUCCESS;
118251433Smarkm			reply[i].resp = COPY_STRING(cred->uname);
118351433Smarkm			/* PAM frees resp. */
118451433Smarkm			break;
118551433Smarkm		case PAM_PROMPT_ECHO_OFF:	/* assume want password */
118651433Smarkm			reply[i].resp_retcode = PAM_SUCCESS;
118751433Smarkm			reply[i].resp = COPY_STRING(cred->pass);
118851433Smarkm			/* PAM frees resp. */
118951433Smarkm			break;
119051433Smarkm		case PAM_TEXT_INFO:
119151433Smarkm		case PAM_ERROR_MSG:
119251433Smarkm			reply[i].resp_retcode = PAM_SUCCESS;
119351433Smarkm			reply[i].resp = NULL;
119451433Smarkm			break;
119551433Smarkm		default:			/* unknown message style */
119651433Smarkm			free(reply);
119751433Smarkm			return PAM_CONV_ERR;
119851433Smarkm		}
119951433Smarkm	}
120051433Smarkm
120151433Smarkm	*resp = reply;
120251433Smarkm	return PAM_SUCCESS;
120351433Smarkm}
120451433Smarkm
120551433Smarkm/*
120651433Smarkm * Attempt to authenticate the user using PAM.  Returns 0 if the user is
120751433Smarkm * authenticated, or 1 if not authenticated.  If some sort of PAM system
120851433Smarkm * error occurs (e.g., the "/etc/pam.conf" file is missing) then this
120951433Smarkm * function returns -1.  This can be used as an indication that we should
121051433Smarkm * fall back to a different authentication mechanism.
121151433Smarkm */
121251433Smarkmstatic int
121351433Smarkmauth_pam(struct passwd **ppw, const char *pass)
121451433Smarkm{
121551433Smarkm	pam_handle_t *pamh = NULL;
121651433Smarkm	const char *tmpl_user;
121751433Smarkm	const void *item;
121851433Smarkm	int rval;
121951433Smarkm	int e;
122051433Smarkm	cred_t auth_cred = { (*ppw)->pw_name, pass };
122151433Smarkm	struct pam_conv conv = { &auth_conv, &auth_cred };
122251433Smarkm
122351433Smarkm	e = pam_start("ftpd", (*ppw)->pw_name, &conv, &pamh);
122451433Smarkm	if (e != PAM_SUCCESS) {
122551433Smarkm		syslog(LOG_ERR, "pam_start: %s", pam_strerror(pamh, e));
122651433Smarkm		return -1;
122751433Smarkm	}
122851433Smarkm
122967007Sguido	e = pam_set_item(pamh, PAM_RHOST, remotehost);
123067007Sguido	if (e != PAM_SUCCESS) {
123167007Sguido		syslog(LOG_ERR, "pam_set_item(PAM_RHOST): %s",
123267007Sguido			pam_strerror(pamh, e));
123367007Sguido		return -1;
123467007Sguido	}
123567007Sguido
123651433Smarkm	e = pam_authenticate(pamh, 0);
123751433Smarkm	switch (e) {
123851433Smarkm	case PAM_SUCCESS:
123951433Smarkm		/*
124051433Smarkm		 * With PAM we support the concept of a "template"
124151433Smarkm		 * user.  The user enters a login name which is
124251433Smarkm		 * authenticated by PAM, usually via a remote service
124351433Smarkm		 * such as RADIUS or TACACS+.  If authentication
124451433Smarkm		 * succeeds, a different but related "template" name
124551433Smarkm		 * is used for setting the credentials, shell, and
124651433Smarkm		 * home directory.  The name the user enters need only
124751433Smarkm		 * exist on the remote authentication server, but the
124851433Smarkm		 * template name must be present in the local password
124951433Smarkm		 * database.
125051433Smarkm		 *
125151433Smarkm		 * This is supported by two various mechanisms in the
125251433Smarkm		 * individual modules.  However, from the application's
125351433Smarkm		 * point of view, the template user is always passed
125451433Smarkm		 * back as a changed value of the PAM_USER item.
125551433Smarkm		 */
125651433Smarkm		if ((e = pam_get_item(pamh, PAM_USER, &item)) ==
125751433Smarkm		    PAM_SUCCESS) {
125851433Smarkm			tmpl_user = (const char *) item;
125951433Smarkm			if (strcmp((*ppw)->pw_name, tmpl_user) != 0)
126051433Smarkm				*ppw = getpwnam(tmpl_user);
126151433Smarkm		} else
126251433Smarkm			syslog(LOG_ERR, "Couldn't get PAM_USER: %s",
126351433Smarkm			    pam_strerror(pamh, e));
126451433Smarkm		rval = 0;
126551433Smarkm		break;
126651433Smarkm
126751433Smarkm	case PAM_AUTH_ERR:
126851433Smarkm	case PAM_USER_UNKNOWN:
126951433Smarkm	case PAM_MAXTRIES:
127051433Smarkm		rval = 1;
127151433Smarkm		break;
127251433Smarkm
127351433Smarkm	default:
127474874Smarkm		syslog(LOG_ERR, "pam_authenticate: %s", pam_strerror(pamh, e));
127551433Smarkm		rval = -1;
127651433Smarkm		break;
127751433Smarkm	}
127851433Smarkm
127974874Smarkm	if (rval == 0) {
128074874Smarkm		e = pam_acct_mgmt(pamh, 0);
128174874Smarkm		if (e == PAM_NEW_AUTHTOK_REQD) {
128274874Smarkm			e = pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
128374874Smarkm			if (e != PAM_SUCCESS) {
128474874Smarkm				syslog(LOG_ERR, "pam_chauthtok: %s", pam_strerror(pamh, e));
128574874Smarkm				rval = 1;
128674874Smarkm			}
128774874Smarkm		} else if (e != PAM_SUCCESS) {
128874874Smarkm			rval = 1;
128974874Smarkm		}
129051433Smarkm	}
129174874Smarkm
129274874Smarkm	if (rval != 0) {
129374874Smarkm		if ((e = pam_end(pamh, e)) != PAM_SUCCESS) {
129474874Smarkm			syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e));
129574874Smarkm		}
129674874Smarkm		pamh = NULL;
129774874Smarkm	}
129851433Smarkm	return rval;
129951433Smarkm}
130051433Smarkm
130174874Smarkm#endif /* USE_PAM */
130251433Smarkm
13031592Srgrimesvoid
130490148Simppass(char *passwd)
13051592Srgrimes{
130617435Spst	int rval;
13071592Srgrimes	FILE *fd;
130825101Sdavidn#ifdef	LOGIN_CAP
130925101Sdavidn	login_cap_t *lc = NULL;
131025101Sdavidn#endif
131174874Smarkm#ifdef USE_PAM
131274874Smarkm	int e;
131374874Smarkm#endif
131488763Sache	char *xpasswd;
13151592Srgrimes
13161592Srgrimes	if (logged_in || askpasswd == 0) {
13171592Srgrimes		reply(503, "Login with USER first.");
13181592Srgrimes		return;
13191592Srgrimes	}
13201592Srgrimes	askpasswd = 0;
13211592Srgrimes	if (!guest) {		/* "ftp" is only account allowed no password */
132217435Spst		if (pw == NULL) {
132317435Spst			rval = 1;	/* failure below */
132417435Spst			goto skip;
132517435Spst		}
132674874Smarkm#ifdef USE_PAM
132751433Smarkm		rval = auth_pam(&pw, passwd);
132889622Sache		if (rval >= 0) {
132989622Sache			opieunlock();
133017435Spst			goto skip;
133189622Sache		}
133289622Sache#endif
133388763Sache		if (opieverify(&opiedata, passwd) == 0)
133488763Sache			xpasswd = pw->pw_passwd;
133589622Sache		else if (pwok) {
133688763Sache			xpasswd = crypt(passwd, pw->pw_passwd);
133789622Sache			if (passwd[0] == '\0' && pw->pw_passwd[0] != '\0')
133889622Sache				xpasswd = ":";
133989622Sache		} else {
134088763Sache			rval = 1;
134188763Sache			goto skip;
134288763Sache		}
134388763Sache		rval = strcmp(pw->pw_passwd, xpasswd);
134489622Sache		if (pw->pw_expire && time(NULL) >= pw->pw_expire)
134517435Spst			rval = 1;	/* failure */
134617435Spstskip:
134717435Spst		/*
134817435Spst		 * If rval == 1, the user failed the authentication check
134951433Smarkm		 * above.  If rval == 0, either PAM or local authentication
135017435Spst		 * succeeded.
135117435Spst		 */
135217435Spst		if (rval) {
13531592Srgrimes			reply(530, "Login incorrect.");
13541592Srgrimes			if (logging)
13551592Srgrimes				syslog(LOG_NOTICE,
13561592Srgrimes				    "FTP LOGIN FAILED FROM %s, %s",
13571592Srgrimes				    remotehost, curname);
13581592Srgrimes			pw = NULL;
13591592Srgrimes			if (login_attempts++ >= 5) {
13601592Srgrimes				syslog(LOG_NOTICE,
13611592Srgrimes				    "repeated login failures from %s",
13621592Srgrimes				    remotehost);
13631592Srgrimes				exit(0);
13641592Srgrimes			}
13651592Srgrimes			return;
13661592Srgrimes		}
13671592Srgrimes	}
13681592Srgrimes	login_attempts = 0;		/* this time successful */
13691592Srgrimes	if (setegid((gid_t)pw->pw_gid) < 0) {
13701592Srgrimes		reply(550, "Can't set gid.");
13711592Srgrimes		return;
13721592Srgrimes	}
137325101Sdavidn	/* May be overridden by login.conf */
137425101Sdavidn	(void) umask(defumask);
137525101Sdavidn#ifdef	LOGIN_CAP
137625674Sdavidn	if ((lc = login_getpwclass(pw)) != NULL) {
137725101Sdavidn		char	remote_ip[MAXHOSTNAMELEN];
137825101Sdavidn
137956668Sshin		getnameinfo((struct sockaddr *)&his_addr, his_addr.su_len,
138056668Sshin			remote_ip, sizeof(remote_ip) - 1, NULL, 0,
138199255Sume			NI_NUMERICHOST);
138225101Sdavidn		remote_ip[sizeof(remote_ip) - 1] = 0;
138325101Sdavidn		if (!auth_hostok(lc, remotehost, remote_ip)) {
138425101Sdavidn			syslog(LOG_INFO|LOG_AUTH,
138525101Sdavidn			    "FTP LOGIN FAILED (HOST) as %s: permission denied.",
138625101Sdavidn			    pw->pw_name);
138725101Sdavidn			reply(530, "Permission denied.\n");
138825101Sdavidn			pw = NULL;
138925101Sdavidn			return;
139025101Sdavidn		}
139125101Sdavidn		if (!auth_timeok(lc, time(NULL))) {
139225101Sdavidn			reply(530, "Login not available right now.\n");
139325101Sdavidn			pw = NULL;
139425101Sdavidn			return;
139525101Sdavidn		}
139625101Sdavidn	}
139725101Sdavidn	setusercontext(lc, pw, (uid_t)0,
139840310Sdes		LOGIN_SETLOGIN|LOGIN_SETGROUP|LOGIN_SETPRIORITY|
139940310Sdes		LOGIN_SETRESOURCES|LOGIN_SETUMASK);
140025101Sdavidn#else
140140310Sdes	setlogin(pw->pw_name);
14021592Srgrimes	(void) initgroups(pw->pw_name, pw->pw_gid);
140325101Sdavidn#endif
14041592Srgrimes
140574874Smarkm#ifdef USE_PAM
140674874Smarkm	if (pamh) {
140774874Smarkm		if ((e = pam_open_session(pamh, 0)) != PAM_SUCCESS) {
140874874Smarkm			syslog(LOG_ERR, "pam_open_session: %s", pam_strerror(pamh, e));
140974874Smarkm		} else if ((e = pam_setcred(pamh, PAM_ESTABLISH_CRED)) != PAM_SUCCESS) {
141074874Smarkm			syslog(LOG_ERR, "pam_setcred: %s", pam_strerror(pamh, e));
141174874Smarkm		}
141274874Smarkm	}
141374874Smarkm#endif
141474874Smarkm
14151592Srgrimes	/* open wtmp before chroot */
141689920Sume	ftpd_logwtmp(ttyline, pw->pw_name, (struct sockaddr *)&his_addr);
14171592Srgrimes	logged_in = 1;
14181592Srgrimes
141917435Spst	if (guest && stats && statfd < 0)
142025283Sdavidn#ifdef VIRTUAL_HOSTING
142125283Sdavidn		if ((statfd = open(thishost->statfile, O_WRONLY|O_APPEND)) < 0)
142225283Sdavidn#else
14236740Sguido		if ((statfd = open(_PATH_FTPDSTATFILE, O_WRONLY|O_APPEND)) < 0)
142425283Sdavidn#endif
14256740Sguido			stats = 0;
14266740Sguido
142725101Sdavidn	dochroot =
142825101Sdavidn#ifdef	LOGIN_CAP	/* Allow login.conf configuration as well */
142925101Sdavidn		login_getcapbool(lc, "ftp-chroot", 0) ||
143025101Sdavidn#endif
143136349Ssteve		checkuser(_PATH_FTPCHROOT, pw->pw_name, 1);
14321592Srgrimes	if (guest) {
14331592Srgrimes		/*
14341592Srgrimes		 * We MUST do a chdir() after the chroot. Otherwise
14351592Srgrimes		 * the old current directory will be accessible as "."
14361592Srgrimes		 * outside the new root!
14371592Srgrimes		 */
14381592Srgrimes		if (chroot(pw->pw_dir) < 0 || chdir("/") < 0) {
14391592Srgrimes			reply(550, "Can't set guest privileges.");
14401592Srgrimes			goto bad;
14411592Srgrimes		}
144217435Spst	} else if (dochroot) {
144317435Spst		if (chroot(pw->pw_dir) < 0 || chdir("/") < 0) {
144417435Spst			reply(550, "Can't change root.");
144517435Spst			goto bad;
144617435Spst		}
14471592Srgrimes	} else if (chdir(pw->pw_dir) < 0) {
14481592Srgrimes		if (chdir("/") < 0) {
14491592Srgrimes			reply(530, "User %s: can't change directory to %s.",
14501592Srgrimes			    pw->pw_name, pw->pw_dir);
14511592Srgrimes			goto bad;
14521592Srgrimes		} else
14531592Srgrimes			lreply(230, "No directory! Logging in with home=/");
14541592Srgrimes	}
14551592Srgrimes	if (seteuid((uid_t)pw->pw_uid) < 0) {
14561592Srgrimes		reply(550, "Can't set uid.");
14571592Srgrimes		goto bad;
14581592Srgrimes	}
14598696Sdg
14601592Srgrimes	/*
14611592Srgrimes	 * Display a login message, if it exists.
14621592Srgrimes	 * N.B. reply(230,) must follow the message.
14631592Srgrimes	 */
146425283Sdavidn#ifdef VIRTUAL_HOSTING
146525283Sdavidn	if ((fd = fopen(thishost->loginmsg, "r")) != NULL) {
146625283Sdavidn#else
14671592Srgrimes	if ((fd = fopen(_PATH_FTPLOGINMESG, "r")) != NULL) {
146825283Sdavidn#endif
14691592Srgrimes		char *cp, line[LINE_MAX];
14701592Srgrimes
14711592Srgrimes		while (fgets(line, sizeof(line), fd) != NULL) {
14721592Srgrimes			if ((cp = strchr(line, '\n')) != NULL)
14731592Srgrimes				*cp = '\0';
14741592Srgrimes			lreply(230, "%s", line);
14751592Srgrimes		}
14761592Srgrimes		(void) fflush(stdout);
14771592Srgrimes		(void) fclose(fd);
14781592Srgrimes	}
14791592Srgrimes	if (guest) {
14806740Sguido		if (ident != NULL)
14816740Sguido			free(ident);
148217433Spst		ident = strdup(passwd);
148317433Spst		if (ident == NULL)
148476096Smarkm			fatalerror("Ran out of memory.");
148517433Spst
14861592Srgrimes		reply(230, "Guest login ok, access restrictions apply.");
14871592Srgrimes#ifdef SETPROCTITLE
148825283Sdavidn#ifdef VIRTUAL_HOSTING
148925283Sdavidn		if (thishost != firsthost)
149025283Sdavidn			snprintf(proctitle, sizeof(proctitle),
149183308Smikeh				 "%s: anonymous(%s)/%s", remotehost, hostname,
149283308Smikeh				 passwd);
149325283Sdavidn		else
149425283Sdavidn#endif
149525283Sdavidn			snprintf(proctitle, sizeof(proctitle),
149683308Smikeh				 "%s: anonymous/%s", remotehost, passwd);
149713139Speter		setproctitle("%s", proctitle);
14981592Srgrimes#endif /* SETPROCTITLE */
14991592Srgrimes		if (logging)
15001592Srgrimes			syslog(LOG_INFO, "ANONYMOUS FTP LOGIN FROM %s, %s",
15011592Srgrimes			    remotehost, passwd);
15021592Srgrimes	} else {
150384841Syar		if (dochroot)
150484841Syar			reply(230, "User %s logged in, "
150584841Syar				   "access restrictions apply.", pw->pw_name);
150684841Syar		else
150784841Syar			reply(230, "User %s logged in.", pw->pw_name);
150825986Sdanny
15091592Srgrimes#ifdef SETPROCTITLE
15101592Srgrimes		snprintf(proctitle, sizeof(proctitle),
151184842Syar			 "%s: user/%s", remotehost, pw->pw_name);
151213139Speter		setproctitle("%s", proctitle);
15131592Srgrimes#endif /* SETPROCTITLE */
15141592Srgrimes		if (logging)
15151592Srgrimes			syslog(LOG_INFO, "FTP LOGIN FROM %s as %s",
15161592Srgrimes			    remotehost, pw->pw_name);
15171592Srgrimes	}
151825101Sdavidn#ifdef	LOGIN_CAP
151925101Sdavidn	login_close(lc);
152025101Sdavidn#endif
15211592Srgrimes	return;
15221592Srgrimesbad:
15231592Srgrimes	/* Forget all about it... */
152425101Sdavidn#ifdef	LOGIN_CAP
152525101Sdavidn	login_close(lc);
152625101Sdavidn#endif
15271592Srgrimes	end_login();
15281592Srgrimes}
15291592Srgrimes
15301592Srgrimesvoid
153190148Simpretrieve(char *cmd, char *name)
15321592Srgrimes{
15331592Srgrimes	FILE *fin, *dout;
15341592Srgrimes	struct stat st;
153590148Simp	int (*closefunc)(FILE *);
153636612Sjb	time_t start;
15371592Srgrimes
15381592Srgrimes	if (cmd == 0) {
15391592Srgrimes		fin = fopen(name, "r"), closefunc = fclose;
15401592Srgrimes		st.st_size = 0;
15411592Srgrimes	} else {
15421592Srgrimes		char line[BUFSIZ];
15431592Srgrimes
154431973Simp		(void) snprintf(line, sizeof(line), cmd, name), name = line;
15451592Srgrimes		fin = ftpd_popen(line, "r"), closefunc = ftpd_pclose;
15461592Srgrimes		st.st_size = -1;
15471592Srgrimes		st.st_blksize = BUFSIZ;
15481592Srgrimes	}
15491592Srgrimes	if (fin == NULL) {
15501592Srgrimes		if (errno != 0) {
15511592Srgrimes			perror_reply(550, name);
15521592Srgrimes			if (cmd == 0) {
15531592Srgrimes				LOGCMD("get", name);
15541592Srgrimes			}
15551592Srgrimes		}
15561592Srgrimes		return;
15571592Srgrimes	}
15581592Srgrimes	byte_count = -1;
15591592Srgrimes	if (cmd == 0 && (fstat(fileno(fin), &st) < 0 || !S_ISREG(st.st_mode))) {
15601592Srgrimes		reply(550, "%s: not a plain file.", name);
15611592Srgrimes		goto done;
15621592Srgrimes	}
15631592Srgrimes	if (restart_point) {
15641592Srgrimes		if (type == TYPE_A) {
15651592Srgrimes			off_t i, n;
15661592Srgrimes			int c;
15671592Srgrimes
15681592Srgrimes			n = restart_point;
15691592Srgrimes			i = 0;
15701592Srgrimes			while (i++ < n) {
15711592Srgrimes				if ((c=getc(fin)) == EOF) {
15721592Srgrimes					perror_reply(550, name);
15731592Srgrimes					goto done;
15741592Srgrimes				}
15751592Srgrimes				if (c == '\n')
15761592Srgrimes					i++;
15771592Srgrimes			}
15781592Srgrimes		} else if (lseek(fileno(fin), restart_point, L_SET) < 0) {
15791592Srgrimes			perror_reply(550, name);
15801592Srgrimes			goto done;
15811592Srgrimes		}
15821592Srgrimes	}
15831592Srgrimes	dout = dataconn(name, st.st_size, "w");
15841592Srgrimes	if (dout == NULL)
15851592Srgrimes		goto done;
15866740Sguido	time(&start);
15878240Swollman	send_data(fin, dout, st.st_blksize, st.st_size,
15888240Swollman		  restart_point == 0 && cmd == 0 && S_ISREG(st.st_mode));
15896740Sguido	if (cmd == 0 && guest && stats)
159017433Spst		logxfer(name, st.st_size, start);
15911592Srgrimes	(void) fclose(dout);
15921592Srgrimes	data = -1;
15931592Srgrimes	pdata = -1;
15941592Srgrimesdone:
15951592Srgrimes	if (cmd == 0)
15961592Srgrimes		LOGBYTES("get", name, byte_count);
15971592Srgrimes	(*closefunc)(fin);
15981592Srgrimes}
15991592Srgrimes
16001592Srgrimesvoid
160190148Simpstore(char *name, char *mode, int unique)
16021592Srgrimes{
1603101537Syar	int fd;
16041592Srgrimes	FILE *fout, *din;
16051592Srgrimes	struct stat st;
160690148Simp	int (*closefunc)(FILE *);
16071592Srgrimes
1608101537Syar	if (*mode == 'a') {		/* APPE */
1609101537Syar		if (unique) {
1610101537Syar			/* Programming error */
1611101537Syar			syslog(LOG_ERR, "Internal: unique flag to APPE");
1612101537Syar			unique = 0;
1613101537Syar		}
1614101537Syar		if (guest && noguestmod) {
1615101537Syar			reply(550, "Appending to existing file denied");
1616101537Syar			goto err;
1617101537Syar		}
1618101537Syar		restart_point = 0;	/* not affected by preceding REST */
16191592Srgrimes	}
1620101537Syar	if (unique)			/* STOU overrides REST */
1621101537Syar		restart_point = 0;
1622101537Syar	if (guest && noguestmod) {
1623101537Syar		if (restart_point) {	/* guest STOR w/REST */
1624101537Syar			reply(550, "Modifying existing file denied");
1625101537Syar			goto err;
1626101537Syar		} else			/* treat guest STOR as STOU */
1627101537Syar			unique = 1;
1628101537Syar	}
16291592Srgrimes
16301592Srgrimes	if (restart_point)
1631101537Syar		mode = "r+";	/* so ASCII manual seek can work */
1632101537Syar	if (unique) {
1633101537Syar		if ((fd = guniquefd(name, &name)) < 0)
1634101537Syar			goto err;
1635101537Syar		fout = fdopen(fd, mode);
1636101537Syar	} else
1637101537Syar		fout = fopen(name, mode);
16381592Srgrimes	closefunc = fclose;
16391592Srgrimes	if (fout == NULL) {
16401592Srgrimes		perror_reply(553, name);
1641101537Syar		goto err;
16421592Srgrimes	}
16431592Srgrimes	byte_count = -1;
16441592Srgrimes	if (restart_point) {
16451592Srgrimes		if (type == TYPE_A) {
16461592Srgrimes			off_t i, n;
16471592Srgrimes			int c;
16481592Srgrimes
16491592Srgrimes			n = restart_point;
16501592Srgrimes			i = 0;
16511592Srgrimes			while (i++ < n) {
16521592Srgrimes				if ((c=getc(fout)) == EOF) {
16531592Srgrimes					perror_reply(550, name);
16541592Srgrimes					goto done;
16551592Srgrimes				}
16561592Srgrimes				if (c == '\n')
16571592Srgrimes					i++;
16581592Srgrimes			}
16591592Srgrimes			/*
16601592Srgrimes			 * We must do this seek to "current" position
16611592Srgrimes			 * because we are changing from reading to
16621592Srgrimes			 * writing.
16631592Srgrimes			 */
166482792Sache			if (fseeko(fout, (off_t)0, SEEK_CUR) < 0) {
16651592Srgrimes				perror_reply(550, name);
16661592Srgrimes				goto done;
16671592Srgrimes			}
16681592Srgrimes		} else if (lseek(fileno(fout), restart_point, L_SET) < 0) {
16691592Srgrimes			perror_reply(550, name);
16701592Srgrimes			goto done;
16711592Srgrimes		}
16721592Srgrimes	}
16731592Srgrimes	din = dataconn(name, (off_t)-1, "r");
16741592Srgrimes	if (din == NULL)
16751592Srgrimes		goto done;
16761592Srgrimes	if (receive_data(din, fout) == 0) {
16771592Srgrimes		if (unique)
16781592Srgrimes			reply(226, "Transfer complete (unique file name:%s).",
16791592Srgrimes			    name);
16801592Srgrimes		else
16811592Srgrimes			reply(226, "Transfer complete.");
16821592Srgrimes	}
16831592Srgrimes	(void) fclose(din);
16841592Srgrimes	data = -1;
16851592Srgrimes	pdata = -1;
16861592Srgrimesdone:
16871592Srgrimes	LOGBYTES(*mode == 'w' ? "put" : "append", name, byte_count);
16881592Srgrimes	(*closefunc)(fout);
1689101537Syar	return;
1690101537Syarerr:
1691101537Syar	LOGCMD(*mode == 'a' ? "append" : "put" , name);
1692101537Syar	return;
16931592Srgrimes}
16941592Srgrimes
16951592Srgrimesstatic FILE *
169690148Simpgetdatasock(char *mode)
16971592Srgrimes{
16981592Srgrimes	int on = 1, s, t, tries;
16991592Srgrimes
17001592Srgrimes	if (data >= 0)
17011592Srgrimes		return (fdopen(data, mode));
17021592Srgrimes	(void) seteuid((uid_t)0);
170356668Sshin
170456668Sshin	s = socket(data_dest.su_family, SOCK_STREAM, 0);
17051592Srgrimes	if (s < 0)
17061592Srgrimes		goto bad;
1707100612Syar	if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
1708100609Syar		syslog(LOG_WARNING, "data setsockopt (SO_REUSEADDR): %m");
17091592Srgrimes	/* anchor socket to avoid multi-homing problems */
171056668Sshin	data_source = ctrl_addr;
171156668Sshin	data_source.su_port = htons(20); /* ftp-data port */
17121592Srgrimes	for (tries = 1; ; tries++) {
17131592Srgrimes		if (bind(s, (struct sockaddr *)&data_source,
171456668Sshin		    data_source.su_len) >= 0)
17151592Srgrimes			break;
17161592Srgrimes		if (errno != EADDRINUSE || tries > 10)
17171592Srgrimes			goto bad;
17181592Srgrimes		sleep(tries);
17191592Srgrimes	}
17201592Srgrimes	(void) seteuid((uid_t)pw->pw_uid);
17211592Srgrimes#ifdef IP_TOS
172256668Sshin	if (data_source.su_family == AF_INET)
172356668Sshin      {
17241592Srgrimes	on = IPTOS_THROUGHPUT;
1725100612Syar	if (setsockopt(s, IPPROTO_IP, IP_TOS, &on, sizeof(int)) < 0)
1726100609Syar		syslog(LOG_WARNING, "data setsockopt (IP_TOS): %m");
172756668Sshin      }
17281592Srgrimes#endif
17298240Swollman#ifdef TCP_NOPUSH
17308240Swollman	/*
17318240Swollman	 * Turn off push flag to keep sender TCP from sending short packets
17328240Swollman	 * at the boundaries of each write().  Should probably do a SO_SNDBUF
17338240Swollman	 * to set the send buffer size as well, but that may not be desirable
17348240Swollman	 * in heavy-load situations.
17358240Swollman	 */
17368240Swollman	on = 1;
1737100612Syar	if (setsockopt(s, IPPROTO_TCP, TCP_NOPUSH, &on, sizeof on) < 0)
1738100609Syar		syslog(LOG_WARNING, "data setsockopt (TCP_NOPUSH): %m");
17398240Swollman#endif
17408240Swollman#ifdef SO_SNDBUF
17418240Swollman	on = 65536;
1742100612Syar	if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, &on, sizeof on) < 0)
1743100609Syar		syslog(LOG_WARNING, "data setsockopt (SO_SNDBUF): %m");
17448240Swollman#endif
17458240Swollman
17461592Srgrimes	return (fdopen(s, mode));
17471592Srgrimesbad:
17481592Srgrimes	/* Return the real value of errno (close may change it) */
17491592Srgrimes	t = errno;
17501592Srgrimes	(void) seteuid((uid_t)pw->pw_uid);
17511592Srgrimes	(void) close(s);
17521592Srgrimes	errno = t;
17531592Srgrimes	return (NULL);
17541592Srgrimes}
17551592Srgrimes
17561592Srgrimesstatic FILE *
175790148Simpdataconn(char *name, off_t size, char *mode)
17581592Srgrimes{
17591592Srgrimes	char sizebuf[32];
17601592Srgrimes	FILE *file;
17611592Srgrimes	int retry = 0, tos;
17621592Srgrimes
17631592Srgrimes	file_size = size;
17641592Srgrimes	byte_count = 0;
17651592Srgrimes	if (size != (off_t) -1)
176631973Simp		(void) snprintf(sizebuf, sizeof(sizebuf), " (%qd bytes)", size);
17671592Srgrimes	else
176831973Simp		*sizebuf = '\0';
17691592Srgrimes	if (pdata >= 0) {
177056668Sshin		union sockunion from;
177186628Syar		int flags;
177256668Sshin		int s, fromlen = ctrl_addr.su_len;
177312532Sguido		struct timeval timeout;
177412532Sguido		fd_set set;
17751592Srgrimes
177612532Sguido		FD_ZERO(&set);
177712532Sguido		FD_SET(pdata, &set);
177812532Sguido
177912532Sguido		timeout.tv_usec = 0;
178012532Sguido		timeout.tv_sec = 120;
178112532Sguido
178286628Syar		/*
178386628Syar		 * Granted a socket is in the blocking I/O mode,
178486628Syar		 * accept() will block after a successful select()
178586628Syar		 * if the selected connection dies in between.
178686628Syar		 * Therefore set the non-blocking I/O flag here.
178786628Syar		 */
178886628Syar		if ((flags = fcntl(pdata, F_GETFL, 0)) == -1 ||
178986628Syar		    fcntl(pdata, F_SETFL, flags | O_NONBLOCK) == -1)
179086628Syar			goto pdata_err;
179186628Syar		if (select(pdata+1, &set, (fd_set *) 0, (fd_set *) 0, &timeout) <= 0 ||
179286628Syar		    (s = accept(pdata, (struct sockaddr *) &from, &fromlen)) < 0)
179386628Syar			goto pdata_err;
17941592Srgrimes		(void) close(pdata);
17951592Srgrimes		pdata = s;
179686628Syar		/*
1797101809Syar		 * Unset the inherited non-blocking I/O flag
1798101809Syar		 * on the child socket so stdio can work on it.
179986628Syar		 */
180086628Syar		if ((flags = fcntl(pdata, F_GETFL, 0)) == -1 ||
180186628Syar		    fcntl(pdata, F_SETFL, flags & ~O_NONBLOCK) == -1)
180286628Syar			goto pdata_err;
18031592Srgrimes#ifdef IP_TOS
180456668Sshin		if (from.su_family == AF_INET)
180556668Sshin	      {
180617435Spst		tos = IPTOS_THROUGHPUT;
1807100612Syar		if (setsockopt(s, IPPROTO_IP, IP_TOS, &tos, sizeof(int)) < 0)
1808100609Syar			syslog(LOG_WARNING, "pdata setsockopt (IP_TOS): %m");
180956668Sshin	      }
18101592Srgrimes#endif
18111592Srgrimes		reply(150, "Opening %s mode data connection for '%s'%s.",
18121592Srgrimes		     type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf);
18131592Srgrimes		return (fdopen(pdata, mode));
181486628Syarpdata_err:
181586628Syar		reply(425, "Can't open data connection.");
181686628Syar		(void) close(pdata);
181786628Syar		pdata = -1;
181886628Syar		return (NULL);
18191592Srgrimes	}
18201592Srgrimes	if (data >= 0) {
18211592Srgrimes		reply(125, "Using existing data connection for '%s'%s.",
18221592Srgrimes		    name, sizebuf);
18231592Srgrimes		usedefault = 1;
18241592Srgrimes		return (fdopen(data, mode));
18251592Srgrimes	}
18261592Srgrimes	if (usedefault)
18271592Srgrimes		data_dest = his_addr;
18281592Srgrimes	usedefault = 1;
18291592Srgrimes	file = getdatasock(mode);
18301592Srgrimes	if (file == NULL) {
183156668Sshin		char hostbuf[BUFSIZ], portbuf[BUFSIZ];
183256668Sshin		getnameinfo((struct sockaddr *)&data_source,
183356668Sshin			data_source.su_len, hostbuf, sizeof(hostbuf) - 1,
183456668Sshin			portbuf, sizeof(portbuf),
183599255Sume			NI_NUMERICHOST|NI_NUMERICSERV);
183656668Sshin		reply(425, "Can't create data socket (%s,%s): %s.",
183756668Sshin			hostbuf, portbuf, strerror(errno));
18381592Srgrimes		return (NULL);
18391592Srgrimes	}
18401592Srgrimes	data = fileno(file);
18411592Srgrimes	while (connect(data, (struct sockaddr *)&data_dest,
184256668Sshin	    data_dest.su_len) < 0) {
18431592Srgrimes		if (errno == EADDRINUSE && retry < swaitmax) {
18441592Srgrimes			sleep((unsigned) swaitint);
18451592Srgrimes			retry += swaitint;
18461592Srgrimes			continue;
18471592Srgrimes		}
18481592Srgrimes		perror_reply(425, "Can't build data connection");
18491592Srgrimes		(void) fclose(file);
18501592Srgrimes		data = -1;
18511592Srgrimes		return (NULL);
18521592Srgrimes	}
18531592Srgrimes	reply(150, "Opening %s mode data connection for '%s'%s.",
18541592Srgrimes	     type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf);
18551592Srgrimes	return (file);
18561592Srgrimes}
18571592Srgrimes
18581592Srgrimes/*
18591592Srgrimes * Tranfer the contents of "instr" to "outstr" peer using the appropriate
18608240Swollman * encapsulation of the data subject to Mode, Structure, and Type.
18611592Srgrimes *
18621592Srgrimes * NB: Form isn't handled.
18631592Srgrimes */
186489935Syarstatic int
186590148Simpsend_data(FILE *instr, FILE *outstr, off_t blksize, off_t filesize, int isreg)
18661592Srgrimes{
186770205Sdan	int c, filefd, netfd;
186870205Sdan	char *buf;
186970205Sdan	off_t cnt;
18701592Srgrimes
18711592Srgrimes	transflag++;
18721592Srgrimes	switch (type) {
18731592Srgrimes
18741592Srgrimes	case TYPE_A:
18751592Srgrimes		while ((c = getc(instr)) != EOF) {
187689935Syar			if (recvurg)
187789935Syar				goto got_oob;
18781592Srgrimes			byte_count++;
18791592Srgrimes			if (c == '\n') {
18801592Srgrimes				if (ferror(outstr))
18811592Srgrimes					goto data_err;
18821592Srgrimes				(void) putc('\r', outstr);
18831592Srgrimes			}
18841592Srgrimes			(void) putc(c, outstr);
18851592Srgrimes		}
188689935Syar		if (recvurg)
188789935Syar			goto got_oob;
18881592Srgrimes		fflush(outstr);
18891592Srgrimes		transflag = 0;
18901592Srgrimes		if (ferror(instr))
18911592Srgrimes			goto file_err;
18921592Srgrimes		if (ferror(outstr))
18931592Srgrimes			goto data_err;
18941592Srgrimes		reply(226, "Transfer complete.");
189589935Syar		return (0);
18961592Srgrimes
18971592Srgrimes	case TYPE_I:
18981592Srgrimes	case TYPE_L:
18998240Swollman		/*
19008240Swollman		 * isreg is only set if we are not doing restart and we
19018240Swollman		 * are sending a regular file
19028240Swollman		 */
19038240Swollman		netfd = fileno(outstr);
19048870Srgrimes		filefd = fileno(instr);
19058240Swollman
190670205Sdan		if (isreg) {
190770205Sdan
190870205Sdan			off_t offset;
190970205Sdan			int err;
191070205Sdan
191170205Sdan			err = cnt = offset = 0;
191270205Sdan
191390604Smaxim			while (err != -1 && filesize > 0) {
191490604Smaxim				err = sendfile(filefd, netfd, offset, 0,
191570205Sdan					(struct sf_hdtr *) NULL, &cnt, 0);
191699212Smaxim				/*
191799212Smaxim				 * Calculate byte_count before OOB processing.
191899212Smaxim				 * It can be used in myoob() later.
191999212Smaxim				 */
192099212Smaxim				byte_count += cnt;
192189935Syar				if (recvurg)
192289935Syar					goto got_oob;
192370205Sdan				offset += cnt;
192490604Smaxim				filesize -= cnt;
19258240Swollman
192670205Sdan				if (err == -1) {
192770205Sdan					if (!cnt)
192870205Sdan						goto oldway;
192970205Sdan
193070205Sdan					goto data_err;
193170205Sdan				}
193270205Sdan			}
193370205Sdan
193499318Sdan			transflag = 0;
19358240Swollman			reply(226, "Transfer complete.");
193689935Syar			return (0);
19378240Swollman		}
19388240Swollman
19398240Swollmanoldway:
19401592Srgrimes		if ((buf = malloc((u_int)blksize)) == NULL) {
19411592Srgrimes			transflag = 0;
19421592Srgrimes			perror_reply(451, "Local resource failure: malloc");
194389935Syar			return (-1);
19441592Srgrimes		}
19458870Srgrimes
19461592Srgrimes		while ((cnt = read(filefd, buf, (u_int)blksize)) > 0 &&
19471592Srgrimes		    write(netfd, buf, cnt) == cnt)
19481592Srgrimes			byte_count += cnt;
19491592Srgrimes		transflag = 0;
19501592Srgrimes		(void)free(buf);
19511592Srgrimes		if (cnt != 0) {
19521592Srgrimes			if (cnt < 0)
19531592Srgrimes				goto file_err;
19541592Srgrimes			goto data_err;
19551592Srgrimes		}
19561592Srgrimes		reply(226, "Transfer complete.");
195789935Syar		return (0);
19581592Srgrimes	default:
19591592Srgrimes		transflag = 0;
19601592Srgrimes		reply(550, "Unimplemented TYPE %d in send_data", type);
196189935Syar		return (-1);
19621592Srgrimes	}
19631592Srgrimes
19641592Srgrimesdata_err:
19651592Srgrimes	transflag = 0;
19661592Srgrimes	perror_reply(426, "Data connection");
196789935Syar	return (-1);
19681592Srgrimes
19691592Srgrimesfile_err:
19701592Srgrimes	transflag = 0;
19711592Srgrimes	perror_reply(551, "Error on input file");
197289935Syar	return (-1);
197389935Syar
197489935Syargot_oob:
197589935Syar	myoob();
197689935Syar	recvurg = 0;
197789935Syar	transflag = 0;
197889935Syar	return (-1);
19791592Srgrimes}
19801592Srgrimes
19811592Srgrimes/*
19821592Srgrimes * Transfer data from peer to "outstr" using the appropriate encapulation of
19831592Srgrimes * the data subject to Mode, Structure, and Type.
19841592Srgrimes *
19851592Srgrimes * N.B.: Form isn't handled.
19861592Srgrimes */
19871592Srgrimesstatic int
198890148Simpreceive_data(FILE *instr, FILE *outstr)
19891592Srgrimes{
19901592Srgrimes	int c;
199117433Spst	int cnt, bare_lfs;
19921592Srgrimes	char buf[BUFSIZ];
19931592Srgrimes
19941592Srgrimes	transflag++;
199517433Spst	bare_lfs = 0;
199617433Spst
19971592Srgrimes	switch (type) {
19981592Srgrimes
19991592Srgrimes	case TYPE_I:
20001592Srgrimes	case TYPE_L:
20011592Srgrimes		while ((cnt = read(fileno(instr), buf, sizeof(buf))) > 0) {
200289935Syar			if (recvurg)
200389935Syar				goto got_oob;
20041592Srgrimes			if (write(fileno(outstr), buf, cnt) != cnt)
20051592Srgrimes				goto file_err;
20061592Srgrimes			byte_count += cnt;
20071592Srgrimes		}
200889935Syar		if (recvurg)
200989935Syar			goto got_oob;
20101592Srgrimes		if (cnt < 0)
20111592Srgrimes			goto data_err;
20121592Srgrimes		transflag = 0;
20131592Srgrimes		return (0);
20141592Srgrimes
20151592Srgrimes	case TYPE_E:
20161592Srgrimes		reply(553, "TYPE E not implemented.");
20171592Srgrimes		transflag = 0;
20181592Srgrimes		return (-1);
20191592Srgrimes
20201592Srgrimes	case TYPE_A:
20211592Srgrimes		while ((c = getc(instr)) != EOF) {
202289935Syar			if (recvurg)
202389935Syar				goto got_oob;
20241592Srgrimes			byte_count++;
20251592Srgrimes			if (c == '\n')
20261592Srgrimes				bare_lfs++;
20271592Srgrimes			while (c == '\r') {
20281592Srgrimes				if (ferror(outstr))
20291592Srgrimes					goto data_err;
20301592Srgrimes				if ((c = getc(instr)) != '\n') {
20311592Srgrimes					(void) putc ('\r', outstr);
20321592Srgrimes					if (c == '\0' || c == EOF)
20331592Srgrimes						goto contin2;
20341592Srgrimes				}
20351592Srgrimes			}
20361592Srgrimes			(void) putc(c, outstr);
20371592Srgrimes	contin2:	;
20381592Srgrimes		}
203989935Syar		if (recvurg)
204089935Syar			goto got_oob;
20411592Srgrimes		fflush(outstr);
20421592Srgrimes		if (ferror(instr))
20431592Srgrimes			goto data_err;
20441592Srgrimes		if (ferror(outstr))
20451592Srgrimes			goto file_err;
20461592Srgrimes		transflag = 0;
20471592Srgrimes		if (bare_lfs) {
20481592Srgrimes			lreply(226,
20491592Srgrimes		"WARNING! %d bare linefeeds received in ASCII mode",
20501592Srgrimes			    bare_lfs);
20511592Srgrimes		(void)printf("   File may not have transferred correctly.\r\n");
20521592Srgrimes		}
20531592Srgrimes		return (0);
20541592Srgrimes	default:
20551592Srgrimes		reply(550, "Unimplemented TYPE %d in receive_data", type);
20561592Srgrimes		transflag = 0;
20571592Srgrimes		return (-1);
20581592Srgrimes	}
20591592Srgrimes
20601592Srgrimesdata_err:
20611592Srgrimes	transflag = 0;
20621592Srgrimes	perror_reply(426, "Data Connection");
20631592Srgrimes	return (-1);
20641592Srgrimes
20651592Srgrimesfile_err:
20661592Srgrimes	transflag = 0;
20671592Srgrimes	perror_reply(452, "Error writing file");
20681592Srgrimes	return (-1);
206989935Syar
207089935Syargot_oob:
207189935Syar	myoob();
207289935Syar	recvurg = 0;
207389935Syar	transflag = 0;
207489935Syar	return (-1);
20751592Srgrimes}
20761592Srgrimes
20771592Srgrimesvoid
207890148Simpstatfilecmd(char *filename)
20791592Srgrimes{
20801592Srgrimes	FILE *fin;
20811592Srgrimes	int c;
20821592Srgrimes	char line[LINE_MAX];
20831592Srgrimes
208425165Sdavidn	(void)snprintf(line, sizeof(line), _PATH_LS " -lgA %s", filename);
20851592Srgrimes	fin = ftpd_popen(line, "r");
20861592Srgrimes	lreply(211, "status of %s:", filename);
20871592Srgrimes	while ((c = getc(fin)) != EOF) {
20881592Srgrimes		if (c == '\n') {
20891592Srgrimes			if (ferror(stdout)){
20901592Srgrimes				perror_reply(421, "control connection");
20911592Srgrimes				(void) ftpd_pclose(fin);
20921592Srgrimes				dologout(1);
20931592Srgrimes				/* NOTREACHED */
20941592Srgrimes			}
20951592Srgrimes			if (ferror(fin)) {
20961592Srgrimes				perror_reply(551, filename);
20971592Srgrimes				(void) ftpd_pclose(fin);
20981592Srgrimes				return;
20991592Srgrimes			}
21001592Srgrimes			(void) putc('\r', stdout);
21011592Srgrimes		}
21021592Srgrimes		(void) putc(c, stdout);
21031592Srgrimes	}
21041592Srgrimes	(void) ftpd_pclose(fin);
21051592Srgrimes	reply(211, "End of Status");
21061592Srgrimes}
21071592Srgrimes
21081592Srgrimesvoid
210990148Simpstatcmd(void)
21101592Srgrimes{
211156668Sshin	union sockunion *su;
21121592Srgrimes	u_char *a, *p;
211399255Sume	char hname[NI_MAXHOST];
211456668Sshin	int ispassive;
21151592Srgrimes
21161592Srgrimes	lreply(211, "%s FTP server status:", hostname, version);
21171592Srgrimes	printf("     %s\r\n", version);
21181592Srgrimes	printf("     Connected to %s", remotehost);
211956668Sshin	if (!getnameinfo((struct sockaddr *)&his_addr, his_addr.su_len,
212099255Sume			 hname, sizeof(hname) - 1, NULL, 0, NI_NUMERICHOST)) {
212156668Sshin		if (strcmp(hname, remotehost) != 0)
212256668Sshin			printf(" (%s)", hname);
212356668Sshin	}
21241592Srgrimes	printf("\r\n");
21251592Srgrimes	if (logged_in) {
21261592Srgrimes		if (guest)
21271592Srgrimes			printf("     Logged in anonymously\r\n");
21281592Srgrimes		else
21291592Srgrimes			printf("     Logged in as %s\r\n", pw->pw_name);
21301592Srgrimes	} else if (askpasswd)
21311592Srgrimes		printf("     Waiting for password\r\n");
21321592Srgrimes	else
21331592Srgrimes		printf("     Waiting for user name\r\n");
21341592Srgrimes	printf("     TYPE: %s", typenames[type]);
21351592Srgrimes	if (type == TYPE_A || type == TYPE_E)
21361592Srgrimes		printf(", FORM: %s", formnames[form]);
21371592Srgrimes	if (type == TYPE_L)
21381592Srgrimes#if NBBY == 8
21391592Srgrimes		printf(" %d", NBBY);
21401592Srgrimes#else
21411592Srgrimes		printf(" %d", bytesize);	/* need definition! */
21421592Srgrimes#endif
21431592Srgrimes	printf("; STRUcture: %s; transfer MODE: %s\r\n",
21441592Srgrimes	    strunames[stru], modenames[mode]);
21451592Srgrimes	if (data != -1)
21461592Srgrimes		printf("     Data connection open\r\n");
21471592Srgrimes	else if (pdata != -1) {
214856668Sshin		ispassive = 1;
214956668Sshin		su = &pasv_addr;
21501592Srgrimes		goto printaddr;
21511592Srgrimes	} else if (usedefault == 0) {
215256668Sshin		ispassive = 0;
215356668Sshin		su = &data_dest;
21541592Srgrimesprintaddr:
21551592Srgrimes#define UC(b) (((int) b) & 0xff)
215656668Sshin		if (epsvall) {
215756668Sshin			printf("     EPSV only mode (EPSV ALL)\r\n");
215856668Sshin			goto epsvonly;
215956668Sshin		}
216056668Sshin
216156668Sshin		/* PORT/PASV */
216256668Sshin		if (su->su_family == AF_INET) {
216356668Sshin			a = (u_char *) &su->su_sin.sin_addr;
216456668Sshin			p = (u_char *) &su->su_sin.sin_port;
216556668Sshin			printf("     %s (%d,%d,%d,%d,%d,%d)\r\n",
216656668Sshin				ispassive ? "PASV" : "PORT",
216756668Sshin				UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]),
216856668Sshin				UC(p[0]), UC(p[1]));
216956668Sshin		}
217056668Sshin
217156668Sshin		/* LPRT/LPSV */
217256668Sshin	    {
217356668Sshin		int alen, af, i;
217456668Sshin
217556668Sshin		switch (su->su_family) {
217656668Sshin		case AF_INET:
217756668Sshin			a = (u_char *) &su->su_sin.sin_addr;
217856668Sshin			p = (u_char *) &su->su_sin.sin_port;
217956668Sshin			alen = sizeof(su->su_sin.sin_addr);
218056668Sshin			af = 4;
218156668Sshin			break;
218256668Sshin		case AF_INET6:
218356668Sshin			a = (u_char *) &su->su_sin6.sin6_addr;
218456668Sshin			p = (u_char *) &su->su_sin6.sin6_port;
218556668Sshin			alen = sizeof(su->su_sin6.sin6_addr);
218656668Sshin			af = 6;
218756668Sshin			break;
218856668Sshin		default:
218956668Sshin			af = 0;
219056668Sshin			break;
219156668Sshin		}
219256668Sshin		if (af) {
219356668Sshin			printf("     %s (%d,%d,", ispassive ? "LPSV" : "LPRT",
219456668Sshin				af, alen);
219556668Sshin			for (i = 0; i < alen; i++)
219656668Sshin				printf("%d,", UC(a[i]));
219756668Sshin			printf("%d,%d,%d)\r\n", 2, UC(p[0]), UC(p[1]));
219856668Sshin		}
219956668Sshin	    }
220056668Sshin
220156668Sshinepsvonly:;
220256668Sshin		/* EPRT/EPSV */
220356668Sshin	    {
220456668Sshin		int af;
220556668Sshin
220656668Sshin		switch (su->su_family) {
220756668Sshin		case AF_INET:
220856668Sshin			af = 1;
220956668Sshin			break;
221056668Sshin		case AF_INET6:
221156668Sshin			af = 2;
221256668Sshin			break;
221356668Sshin		default:
221456668Sshin			af = 0;
221556668Sshin			break;
221656668Sshin		}
221756668Sshin		if (af) {
221899255Sume			union sockunion tmp;
221999255Sume
222099255Sume			tmp = *su;
222199255Sume			if (tmp.su_family == AF_INET6)
222299255Sume				tmp.su_sin6.sin6_scope_id = 0;
222399255Sume			if (!getnameinfo((struct sockaddr *)&tmp, tmp.su_len,
222456668Sshin					hname, sizeof(hname) - 1, NULL, 0,
222556668Sshin					NI_NUMERICHOST)) {
222656668Sshin				printf("     %s |%d|%s|%d|\r\n",
222756668Sshin					ispassive ? "EPSV" : "EPRT",
222899255Sume					af, hname, htons(tmp.su_port));
222956668Sshin			}
223056668Sshin		}
223156668Sshin	    }
22321592Srgrimes#undef UC
22331592Srgrimes	} else
22341592Srgrimes		printf("     No data connection\r\n");
22351592Srgrimes	reply(211, "End of status");
22361592Srgrimes}
22371592Srgrimes
22381592Srgrimesvoid
223990148Simpfatalerror(char *s)
22401592Srgrimes{
22411592Srgrimes
22421592Srgrimes	reply(451, "Error in server: %s\n", s);
22431592Srgrimes	reply(221, "Closing connection due to server error.");
22441592Srgrimes	dologout(0);
22451592Srgrimes	/* NOTREACHED */
22461592Srgrimes}
22471592Srgrimes
22481592Srgrimesvoid
22491592Srgrimesreply(int n, const char *fmt, ...)
22501592Srgrimes{
22511592Srgrimes	va_list ap;
225290148Simp
22531592Srgrimes	va_start(ap, fmt);
22541592Srgrimes	(void)printf("%d ", n);
22551592Srgrimes	(void)vprintf(fmt, ap);
22561592Srgrimes	(void)printf("\r\n");
22571592Srgrimes	(void)fflush(stdout);
225876096Smarkm	if (ftpdebug) {
22591592Srgrimes		syslog(LOG_DEBUG, "<--- %d ", n);
22601592Srgrimes		vsyslog(LOG_DEBUG, fmt, ap);
22611592Srgrimes	}
22621592Srgrimes}
22631592Srgrimes
22641592Srgrimesvoid
22651592Srgrimeslreply(int n, const char *fmt, ...)
22661592Srgrimes{
22671592Srgrimes	va_list ap;
226890148Simp
22691592Srgrimes	va_start(ap, fmt);
22701592Srgrimes	(void)printf("%d- ", n);
22711592Srgrimes	(void)vprintf(fmt, ap);
22721592Srgrimes	(void)printf("\r\n");
22731592Srgrimes	(void)fflush(stdout);
227476096Smarkm	if (ftpdebug) {
22751592Srgrimes		syslog(LOG_DEBUG, "<--- %d- ", n);
22761592Srgrimes		vsyslog(LOG_DEBUG, fmt, ap);
22771592Srgrimes	}
22781592Srgrimes}
22791592Srgrimes
22801592Srgrimesstatic void
228190148Simpack(char *s)
22821592Srgrimes{
22831592Srgrimes
22841592Srgrimes	reply(250, "%s command successful.", s);
22851592Srgrimes}
22861592Srgrimes
22871592Srgrimesvoid
228890148Simpnack(char *s)
22891592Srgrimes{
22901592Srgrimes
22911592Srgrimes	reply(502, "%s command not implemented.", s);
22921592Srgrimes}
22931592Srgrimes
22941592Srgrimes/* ARGSUSED */
22951592Srgrimesvoid
229690148Simpyyerror(char *s)
22971592Srgrimes{
22981592Srgrimes	char *cp;
22991592Srgrimes
230017478Smarkm	if ((cp = strchr(cbuf,'\n')))
23011592Srgrimes		*cp = '\0';
23021592Srgrimes	reply(500, "'%s': command not understood.", cbuf);
23031592Srgrimes}
23041592Srgrimes
23051592Srgrimesvoid
230690148Simpdelete(char *name)
23071592Srgrimes{
23081592Srgrimes	struct stat st;
23091592Srgrimes
23101592Srgrimes	LOGCMD("delete", name);
2311100439Syar	if (lstat(name, &st) < 0) {
23121592Srgrimes		perror_reply(550, name);
23131592Srgrimes		return;
23141592Srgrimes	}
23151592Srgrimes	if ((st.st_mode&S_IFMT) == S_IFDIR) {
23161592Srgrimes		if (rmdir(name) < 0) {
23171592Srgrimes			perror_reply(550, name);
23181592Srgrimes			return;
23191592Srgrimes		}
23201592Srgrimes		goto done;
23211592Srgrimes	}
23221592Srgrimes	if (unlink(name) < 0) {
23231592Srgrimes		perror_reply(550, name);
23241592Srgrimes		return;
23251592Srgrimes	}
23261592Srgrimesdone:
23271592Srgrimes	ack("DELE");
23281592Srgrimes}
23291592Srgrimes
23301592Srgrimesvoid
233190148Simpcwd(char *path)
23321592Srgrimes{
23331592Srgrimes
23341592Srgrimes	if (chdir(path) < 0)
23351592Srgrimes		perror_reply(550, path);
23361592Srgrimes	else
23371592Srgrimes		ack("CWD");
23381592Srgrimes}
23391592Srgrimes
23401592Srgrimesvoid
234190148Simpmakedir(char *name)
23421592Srgrimes{
2343100878Syar	char *s;
23441592Srgrimes
23451592Srgrimes	LOGCMD("mkdir", name);
234699195Smdodd	if (guest && noguestmkd)
234799195Smdodd		reply(550, "%s: permission denied", name);
234899195Smdodd	else if (mkdir(name, 0777) < 0)
23491592Srgrimes		perror_reply(550, name);
2350100878Syar	else {
2351100878Syar		if ((s = doublequote(name)) == NULL)
2352100878Syar			fatalerror("Ran out of memory.");
2353100878Syar		reply(257, "\"%s\" directory created.", s);
2354100878Syar		free(s);
2355100878Syar	}
23561592Srgrimes}
23571592Srgrimes
23581592Srgrimesvoid
235990148Simpremovedir(char *name)
23601592Srgrimes{
23611592Srgrimes
23621592Srgrimes	LOGCMD("rmdir", name);
23631592Srgrimes	if (rmdir(name) < 0)
23641592Srgrimes		perror_reply(550, name);
23651592Srgrimes	else
23661592Srgrimes		ack("RMD");
23671592Srgrimes}
23681592Srgrimes
23691592Srgrimesvoid
237090148Simppwd(void)
23711592Srgrimes{
2372100486Syar	char *s, path[MAXPATHLEN + 1];
23731592Srgrimes
23741592Srgrimes	if (getwd(path) == (char *)NULL)
23751592Srgrimes		reply(550, "%s.", path);
2376100486Syar	else {
2377100486Syar		if ((s = doublequote(path)) == NULL)
2378100486Syar			fatalerror("Ran out of memory.");
2379100486Syar		reply(257, "\"%s\" is current directory.", s);
2380100486Syar		free(s);
2381100486Syar	}
23821592Srgrimes}
23831592Srgrimes
23841592Srgrimeschar *
238590148Simprenamefrom(char *name)
23861592Srgrimes{
23871592Srgrimes	struct stat st;
23881592Srgrimes
2389100439Syar	if (lstat(name, &st) < 0) {
23901592Srgrimes		perror_reply(550, name);
23911592Srgrimes		return ((char *)0);
23921592Srgrimes	}
23931592Srgrimes	reply(350, "File exists, ready for destination name");
23941592Srgrimes	return (name);
23951592Srgrimes}
23961592Srgrimes
23971592Srgrimesvoid
239890148Simprenamecmd(char *from, char *to)
23991592Srgrimes{
240017433Spst	struct stat st;
24011592Srgrimes
24021592Srgrimes	LOGCMD2("rename", from, to);
240317433Spst
240417433Spst	if (guest && (stat(to, &st) == 0)) {
240517433Spst		reply(550, "%s: permission denied", to);
240617433Spst		return;
240717433Spst	}
240817433Spst
24091592Srgrimes	if (rename(from, to) < 0)
24101592Srgrimes		perror_reply(550, "rename");
24111592Srgrimes	else
24121592Srgrimes		ack("RNTO");
24131592Srgrimes}
24141592Srgrimes
24151592Srgrimesstatic void
241690148Simpdolog(struct sockaddr *who)
24171592Srgrimes{
241856668Sshin	int error;
24191592Srgrimes
242056668Sshin	realhostname_sa(remotehost, sizeof(remotehost) - 1, who, who->sa_len);
242156668Sshin
24221592Srgrimes#ifdef SETPROCTITLE
242325283Sdavidn#ifdef VIRTUAL_HOSTING
242425283Sdavidn	if (thishost != firsthost)
242525283Sdavidn		snprintf(proctitle, sizeof(proctitle), "%s: connected (to %s)",
242625283Sdavidn			 remotehost, hostname);
242725283Sdavidn	else
242825283Sdavidn#endif
242925283Sdavidn		snprintf(proctitle, sizeof(proctitle), "%s: connected",
243025283Sdavidn			 remotehost);
243113139Speter	setproctitle("%s", proctitle);
24321592Srgrimes#endif /* SETPROCTITLE */
24331592Srgrimes
243425283Sdavidn	if (logging) {
243525283Sdavidn#ifdef VIRTUAL_HOSTING
243625283Sdavidn		if (thishost != firsthost)
243725283Sdavidn			syslog(LOG_INFO, "connection from %s (to %s)",
243825283Sdavidn			       remotehost, hostname);
243925283Sdavidn		else
244025283Sdavidn#endif
244156668Sshin		{
244256668Sshin			char	who_name[MAXHOSTNAMELEN];
244356668Sshin
244456668Sshin			error = getnameinfo(who, who->sa_len,
244556668Sshin					    who_name, sizeof(who_name) - 1,
244699255Sume					    NULL, 0, NI_NUMERICHOST);
244733782Seivind			syslog(LOG_INFO, "connection from %s (%s)", remotehost,
244856668Sshin			       error == 0 ? who_name : "");
244956668Sshin		}
245025283Sdavidn	}
24511592Srgrimes}
24521592Srgrimes
24531592Srgrimes/*
24541592Srgrimes * Record logout in wtmp file
24551592Srgrimes * and exit with supplied status.
24561592Srgrimes */
24571592Srgrimesvoid
245890148Simpdologout(int status)
24591592Srgrimes{
246022057Sdg	/*
246122057Sdg	 * Prevent reception of SIGURG from resulting in a resumption
246222057Sdg	 * back to the main program loop.
246322058Sdg	 */
246422057Sdg	transflag = 0;
24651592Srgrimes
24661592Srgrimes	if (logged_in) {
24671592Srgrimes		(void) seteuid((uid_t)0);
246889920Sume		ftpd_logwtmp(ttyline, "", NULL);
24691592Srgrimes	}
24701592Srgrimes	/* beware of flushing buffers after a SIGPIPE */
24711592Srgrimes	_exit(status);
24721592Srgrimes}
24731592Srgrimes
24741592Srgrimesstatic void
247590148Simpsigurg(int signo)
24761592Srgrimes{
247789935Syar
247889935Syar	recvurg = 1;
247989935Syar}
248089935Syar
248189935Syarstatic void
248290148Simpmyoob(void)
248389935Syar{
24841592Srgrimes	char *cp;
24851592Srgrimes
24861592Srgrimes	/* only process if transfer occurring */
24871592Srgrimes	if (!transflag)
24881592Srgrimes		return;
24891592Srgrimes	cp = tmpline;
24901592Srgrimes	if (getline(cp, 7, stdin) == NULL) {
24911592Srgrimes		reply(221, "You could at least say goodbye.");
24921592Srgrimes		dologout(0);
24931592Srgrimes	}
24941592Srgrimes	upper(cp);
24951592Srgrimes	if (strcmp(cp, "ABOR\r\n") == 0) {
24961592Srgrimes		tmpline[0] = '\0';
24971592Srgrimes		reply(426, "Transfer aborted. Data connection closed.");
24981592Srgrimes		reply(226, "Abort successful");
24991592Srgrimes	}
25001592Srgrimes	if (strcmp(cp, "STAT\r\n") == 0) {
250151192Smharo		tmpline[0] = '\0';
25021592Srgrimes		if (file_size != (off_t) -1)
25031592Srgrimes			reply(213, "Status: %qd of %qd bytes transferred",
25041592Srgrimes			    byte_count, file_size);
25051592Srgrimes		else
25061592Srgrimes			reply(213, "Status: %qd bytes transferred", byte_count);
25071592Srgrimes	}
25081592Srgrimes}
25091592Srgrimes
25101592Srgrimes/*
25111592Srgrimes * Note: a response of 425 is not mentioned as a possible response to
25121592Srgrimes *	the PASV command in RFC959. However, it has been blessed as
25131592Srgrimes *	a legitimate response by Jon Postel in a telephone conversation
25141592Srgrimes *	with Rick Adams on 25 Jan 89.
25151592Srgrimes */
25161592Srgrimesvoid
251790148Simppassive(void)
25181592Srgrimes{
2519100615Syar	int len, on;
25201592Srgrimes	char *p, *a;
25211592Srgrimes
252217433Spst	if (pdata >= 0)		/* close old port if one set */
252317433Spst		close(pdata);
252417433Spst
252556668Sshin	pdata = socket(ctrl_addr.su_family, SOCK_STREAM, 0);
25261592Srgrimes	if (pdata < 0) {
25271592Srgrimes		perror_reply(425, "Can't open passive connection");
25281592Srgrimes		return;
25291592Srgrimes	}
2530100615Syar	on = 1;
2531100615Syar	if (setsockopt(pdata, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
2532100615Syar		syslog(LOG_WARNING, "pdata setsockopt (SO_REUSEADDR): %m");
25339933Spst
253417433Spst	(void) seteuid((uid_t)0);
253517433Spst
253619903Spst#ifdef IP_PORTRANGE
253756668Sshin	if (ctrl_addr.su_family == AF_INET) {
2538100615Syar	    on = restricted_data_ports ? IP_PORTRANGE_HIGH
2539100615Syar				       : IP_PORTRANGE_DEFAULT;
254019903Spst
254119903Spst	    if (setsockopt(pdata, IPPROTO_IP, IP_PORTRANGE,
2542100612Syar			    &on, sizeof(on)) < 0)
254319903Spst		    goto pasv_error;
25441592Srgrimes	}
254519903Spst#endif
254660929Snsayer#ifdef IPV6_PORTRANGE
254760929Snsayer	if (ctrl_addr.su_family == AF_INET6) {
2548100615Syar	    on = restricted_data_ports ? IPV6_PORTRANGE_HIGH
2549100615Syar				       : IPV6_PORTRANGE_DEFAULT;
25509933Spst
255160929Snsayer	    if (setsockopt(pdata, IPPROTO_IPV6, IPV6_PORTRANGE,
2552100612Syar			    &on, sizeof(on)) < 0)
255360929Snsayer		    goto pasv_error;
255460929Snsayer	}
255560929Snsayer#endif
255660929Snsayer
255716033Speter	pasv_addr = ctrl_addr;
255856668Sshin	pasv_addr.su_port = 0;
255956668Sshin	if (bind(pdata, (struct sockaddr *)&pasv_addr, pasv_addr.su_len) < 0)
256016033Speter		goto pasv_error;
256117433Spst
256216033Speter	(void) seteuid((uid_t)pw->pw_uid);
256316033Speter
25641592Srgrimes	len = sizeof(pasv_addr);
25651592Srgrimes	if (getsockname(pdata, (struct sockaddr *) &pasv_addr, &len) < 0)
25661592Srgrimes		goto pasv_error;
25671592Srgrimes	if (listen(pdata, 1) < 0)
25681592Srgrimes		goto pasv_error;
256956668Sshin	if (pasv_addr.su_family == AF_INET)
257056668Sshin		a = (char *) &pasv_addr.su_sin.sin_addr;
257156668Sshin	else if (pasv_addr.su_family == AF_INET6 &&
257256668Sshin		 IN6_IS_ADDR_V4MAPPED(&pasv_addr.su_sin6.sin6_addr))
257356668Sshin		a = (char *) &pasv_addr.su_sin6.sin6_addr.s6_addr[12];
257456668Sshin	else
257556668Sshin		goto pasv_error;
257656668Sshin
257756668Sshin	p = (char *) &pasv_addr.su_port;
25781592Srgrimes
25791592Srgrimes#define UC(b) (((int) b) & 0xff)
25801592Srgrimes
25811592Srgrimes	reply(227, "Entering Passive Mode (%d,%d,%d,%d,%d,%d)", UC(a[0]),
25821592Srgrimes		UC(a[1]), UC(a[2]), UC(a[3]), UC(p[0]), UC(p[1]));
25831592Srgrimes	return;
25841592Srgrimes
25851592Srgrimespasv_error:
258617433Spst	(void) seteuid((uid_t)pw->pw_uid);
25871592Srgrimes	(void) close(pdata);
25881592Srgrimes	pdata = -1;
25891592Srgrimes	perror_reply(425, "Can't open passive connection");
25901592Srgrimes	return;
25911592Srgrimes}
25921592Srgrimes
25931592Srgrimes/*
259456668Sshin * Long Passive defined in RFC 1639.
259556668Sshin *     228 Entering Long Passive Mode
259656668Sshin *         (af, hal, h1, h2, h3,..., pal, p1, p2...)
259756668Sshin */
259856668Sshin
259956668Sshinvoid
260090148Simplong_passive(char *cmd, int pf)
260156668Sshin{
2602100615Syar	int len, on;
260356668Sshin	char *p, *a;
260456668Sshin
260556668Sshin	if (pdata >= 0)		/* close old port if one set */
260656668Sshin		close(pdata);
260756668Sshin
260856668Sshin	if (pf != PF_UNSPEC) {
260956668Sshin		if (ctrl_addr.su_family != pf) {
261056668Sshin			switch (ctrl_addr.su_family) {
261156668Sshin			case AF_INET:
261256668Sshin				pf = 1;
261356668Sshin				break;
261456668Sshin			case AF_INET6:
261556668Sshin				pf = 2;
261656668Sshin				break;
261756668Sshin			default:
261856668Sshin				pf = 0;
261956668Sshin				break;
262056668Sshin			}
262156668Sshin			/*
262256668Sshin			 * XXX
262356668Sshin			 * only EPRT/EPSV ready clients will understand this
262456668Sshin			 */
262556668Sshin			if (strcmp(cmd, "EPSV") == 0 && pf) {
262656668Sshin				reply(522, "Network protocol mismatch, "
262756668Sshin					"use (%d)", pf);
262856668Sshin			} else
262956668Sshin				reply(501, "Network protocol mismatch"); /*XXX*/
263056668Sshin
263156668Sshin			return;
263256668Sshin		}
263356668Sshin	}
263456668Sshin
263556668Sshin	pdata = socket(ctrl_addr.su_family, SOCK_STREAM, 0);
263656668Sshin	if (pdata < 0) {
263756668Sshin		perror_reply(425, "Can't open passive connection");
263856668Sshin		return;
263956668Sshin	}
2640100615Syar	on = 1;
2641100615Syar	if (setsockopt(pdata, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
2642100615Syar		syslog(LOG_WARNING, "pdata setsockopt (SO_REUSEADDR): %m");
264356668Sshin
264456668Sshin	(void) seteuid((uid_t)0);
264556668Sshin
264656668Sshin	pasv_addr = ctrl_addr;
264756668Sshin	pasv_addr.su_port = 0;
264856668Sshin	len = pasv_addr.su_len;
264956668Sshin
265060929Snsayer#ifdef IP_PORTRANGE
265160929Snsayer	if (ctrl_addr.su_family == AF_INET) {
2652100615Syar	    on = restricted_data_ports ? IP_PORTRANGE_HIGH
2653100615Syar				       : IP_PORTRANGE_DEFAULT;
265460929Snsayer
265560929Snsayer	    if (setsockopt(pdata, IPPROTO_IP, IP_PORTRANGE,
2656100612Syar			    &on, sizeof(on)) < 0)
265760929Snsayer		    goto pasv_error;
265860929Snsayer	}
265960929Snsayer#endif
266060929Snsayer#ifdef IPV6_PORTRANGE
266160929Snsayer	if (ctrl_addr.su_family == AF_INET6) {
2662100615Syar	    on = restricted_data_ports ? IPV6_PORTRANGE_HIGH
2663100615Syar				       : IPV6_PORTRANGE_DEFAULT;
266460929Snsayer
266560929Snsayer	    if (setsockopt(pdata, IPPROTO_IPV6, IPV6_PORTRANGE,
2666100612Syar			    &on, sizeof(on)) < 0)
266760929Snsayer		    goto pasv_error;
266860929Snsayer	}
266960929Snsayer#endif
267060929Snsayer
267156668Sshin	if (bind(pdata, (struct sockaddr *)&pasv_addr, len) < 0)
267256668Sshin		goto pasv_error;
267356668Sshin
267456668Sshin	(void) seteuid((uid_t)pw->pw_uid);
267556668Sshin
267656668Sshin	if (getsockname(pdata, (struct sockaddr *) &pasv_addr, &len) < 0)
267756668Sshin		goto pasv_error;
267856668Sshin	if (listen(pdata, 1) < 0)
267956668Sshin		goto pasv_error;
268056668Sshin
268156668Sshin#define UC(b) (((int) b) & 0xff)
268256668Sshin
268356668Sshin	if (strcmp(cmd, "LPSV") == 0) {
268456668Sshin		p = (char *)&pasv_addr.su_port;
268556668Sshin		switch (pasv_addr.su_family) {
268656668Sshin		case AF_INET:
268756668Sshin			a = (char *) &pasv_addr.su_sin.sin_addr;
268856668Sshin		v4_reply:
268956668Sshin			reply(228,
269056668Sshin"Entering Long Passive Mode (%d,%d,%d,%d,%d,%d,%d,%d,%d)",
269156668Sshin			      4, 4, UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]),
269256668Sshin			      2, UC(p[0]), UC(p[1]));
269356668Sshin			return;
269456668Sshin		case AF_INET6:
269556668Sshin			if (IN6_IS_ADDR_V4MAPPED(&pasv_addr.su_sin6.sin6_addr)) {
269656668Sshin				a = (char *) &pasv_addr.su_sin6.sin6_addr.s6_addr[12];
269756668Sshin				goto v4_reply;
269856668Sshin			}
269956668Sshin			a = (char *) &pasv_addr.su_sin6.sin6_addr;
270056668Sshin			reply(228,
270156668Sshin"Entering Long Passive Mode "
270256668Sshin"(%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d)",
270356668Sshin			      6, 16, UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]),
270456668Sshin			      UC(a[4]), UC(a[5]), UC(a[6]), UC(a[7]),
270556668Sshin			      UC(a[8]), UC(a[9]), UC(a[10]), UC(a[11]),
270656668Sshin			      UC(a[12]), UC(a[13]), UC(a[14]), UC(a[15]),
270756668Sshin			      2, UC(p[0]), UC(p[1]));
270856668Sshin			return;
270956668Sshin		}
271056668Sshin	} else if (strcmp(cmd, "EPSV") == 0) {
271156668Sshin		switch (pasv_addr.su_family) {
271256668Sshin		case AF_INET:
271356668Sshin		case AF_INET6:
271456668Sshin			reply(229, "Entering Extended Passive Mode (|||%d|)",
271556668Sshin				ntohs(pasv_addr.su_port));
271656668Sshin			return;
271756668Sshin		}
271856668Sshin	} else {
271956668Sshin		/* more proper error code? */
272056668Sshin	}
272156668Sshin
272256668Sshinpasv_error:
272356668Sshin	(void) seteuid((uid_t)pw->pw_uid);
272456668Sshin	(void) close(pdata);
272556668Sshin	pdata = -1;
272656668Sshin	perror_reply(425, "Can't open passive connection");
272756668Sshin	return;
272856668Sshin}
272956668Sshin
273056668Sshin/*
2731101537Syar * Generate unique name for file with basename "local"
2732101537Syar * and open the file in order to avoid possible races.
2733101537Syar * Try "local" first, then "local.1", "local.2" etc, up to "local.99".
2734101537Syar * Return descriptor to the file, set "name" to its name.
2735101537Syar *
27361592Srgrimes * Generates failure reply on error.
27371592Srgrimes */
2738101537Syarstatic int
2739101537Syarguniquefd(char *local, char **name)
27401592Srgrimes{
27411592Srgrimes	static char new[MAXPATHLEN];
27421592Srgrimes	struct stat st;
2743101537Syar	char *cp;
27441592Srgrimes	int count;
2745101537Syar	int fd;
27461592Srgrimes
27471592Srgrimes	cp = strrchr(local, '/');
27481592Srgrimes	if (cp)
27491592Srgrimes		*cp = '\0';
27501592Srgrimes	if (stat(cp ? local : ".", &st) < 0) {
27511592Srgrimes		perror_reply(553, cp ? local : ".");
2752101537Syar		return (-1);
27531592Srgrimes	}
2754101537Syar	if (cp) {
2755101537Syar		/*
2756101537Syar		 * Let not overwrite dirname with counter suffix.
2757101537Syar		 * -4 is for /nn\0
2758101537Syar		 * In this extreme case dot won't be put in front of suffix.
2759101537Syar		 */
2760101537Syar		if (strlen(local) > sizeof(new) - 4) {
2761101537Syar			reply(553, "Pathname too long");
2762101537Syar			return (-1);
2763101537Syar		}
27641592Srgrimes		*cp = '/';
2765101537Syar	}
276631973Simp	/* -4 is for the .nn<null> we put on the end below */
276731973Simp	(void) snprintf(new, sizeof(new) - 4, "%s", local);
27681592Srgrimes	cp = new + strlen(new);
2769101537Syar	/*
2770101537Syar	 * Don't generate dotfile unless requested explicitly.
2771101537Syar	 * This covers the case when basename gets truncated off
2772101537Syar	 * by buffer size.
2773101537Syar	 */
2774101537Syar	if (cp > new && cp[-1] != '/')
2775101537Syar		*cp++ = '.';
2776101537Syar	for (count = 0; count < 100; count++) {
2777101537Syar		/* At count 0 try unmodified name */
2778101537Syar		if (count)
2779101537Syar			(void)sprintf(cp, "%d", count);
2780101537Syar		if ((fd = open(count ? new : local,
2781101537Syar		    O_RDWR | O_CREAT | O_EXCL, 0666)) >= 0) {
2782101537Syar			*name = count ? new : local;
2783101537Syar			return (fd);
2784101537Syar		}
27851592Srgrimes	}
27861592Srgrimes	reply(452, "Unique file name cannot be created.");
2787101537Syar	return (-1);
27881592Srgrimes}
27891592Srgrimes
27901592Srgrimes/*
27911592Srgrimes * Format and send reply containing system error number.
27921592Srgrimes */
27931592Srgrimesvoid
279490148Simpperror_reply(int code, char *string)
27951592Srgrimes{
27961592Srgrimes
27971592Srgrimes	reply(code, "%s: %s.", string, strerror(errno));
27981592Srgrimes}
27991592Srgrimes
28001592Srgrimesstatic char *onefile[] = {
28011592Srgrimes	"",
28021592Srgrimes	0
28031592Srgrimes};
28041592Srgrimes
28051592Srgrimesvoid
280690148Simpsend_file_list(char *whichf)
28071592Srgrimes{
28081592Srgrimes	struct stat st;
28091592Srgrimes	DIR *dirp = NULL;
28101592Srgrimes	struct dirent *dir;
28111592Srgrimes	FILE *dout = NULL;
28121592Srgrimes	char **dirlist, *dirname;
28131592Srgrimes	int simple = 0;
28141592Srgrimes	int freeglob = 0;
28151592Srgrimes	glob_t gl;
28161592Srgrimes
28171592Srgrimes	if (strpbrk(whichf, "~{[*?") != NULL) {
2818100222Smikeh		int flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE;
28191592Srgrimes
28201592Srgrimes		memset(&gl, 0, sizeof(gl));
282174470Sjlemon		gl.gl_matchc = MAXGLOBARGS;
282280525Smikeh		flags |= GLOB_LIMIT;
28231592Srgrimes		freeglob = 1;
28241592Srgrimes		if (glob(whichf, flags, 0, &gl)) {
28251592Srgrimes			reply(550, "not found");
28261592Srgrimes			goto out;
28271592Srgrimes		} else if (gl.gl_pathc == 0) {
28281592Srgrimes			errno = ENOENT;
28291592Srgrimes			perror_reply(550, whichf);
28301592Srgrimes			goto out;
28311592Srgrimes		}
28321592Srgrimes		dirlist = gl.gl_pathv;
28331592Srgrimes	} else {
28341592Srgrimes		onefile[0] = whichf;
28351592Srgrimes		dirlist = onefile;
28361592Srgrimes		simple = 1;
28371592Srgrimes	}
28381592Srgrimes
283917478Smarkm	while ((dirname = *dirlist++)) {
28401592Srgrimes		if (stat(dirname, &st) < 0) {
28411592Srgrimes			/*
28421592Srgrimes			 * If user typed "ls -l", etc, and the client
28431592Srgrimes			 * used NLST, do what the user meant.
28441592Srgrimes			 */
28451592Srgrimes			if (dirname[0] == '-' && *dirlist == NULL &&
28461592Srgrimes			    transflag == 0) {
284725165Sdavidn				retrieve(_PATH_LS " %s", dirname);
28481592Srgrimes				goto out;
28491592Srgrimes			}
28501592Srgrimes			perror_reply(550, whichf);
28511592Srgrimes			if (dout != NULL) {
28521592Srgrimes				(void) fclose(dout);
28531592Srgrimes				transflag = 0;
28541592Srgrimes				data = -1;
28551592Srgrimes				pdata = -1;
28561592Srgrimes			}
28571592Srgrimes			goto out;
28581592Srgrimes		}
28591592Srgrimes
28601592Srgrimes		if (S_ISREG(st.st_mode)) {
28611592Srgrimes			if (dout == NULL) {
28621592Srgrimes				dout = dataconn("file list", (off_t)-1, "w");
28631592Srgrimes				if (dout == NULL)
28641592Srgrimes					goto out;
28651592Srgrimes				transflag++;
28661592Srgrimes			}
28671592Srgrimes			fprintf(dout, "%s%s\n", dirname,
28681592Srgrimes				type == TYPE_A ? "\r" : "");
28691592Srgrimes			byte_count += strlen(dirname) + 1;
28701592Srgrimes			continue;
28711592Srgrimes		} else if (!S_ISDIR(st.st_mode))
28721592Srgrimes			continue;
28731592Srgrimes
28741592Srgrimes		if ((dirp = opendir(dirname)) == NULL)
28751592Srgrimes			continue;
28761592Srgrimes
28771592Srgrimes		while ((dir = readdir(dirp)) != NULL) {
28781592Srgrimes			char nbuf[MAXPATHLEN];
28791592Srgrimes
288089935Syar			if (recvurg) {
288189935Syar				myoob();
288289935Syar				recvurg = 0;
288389935Syar				transflag = 0;
288489935Syar				goto out;
288589935Syar			}
288689935Syar
28871592Srgrimes			if (dir->d_name[0] == '.' && dir->d_namlen == 1)
28881592Srgrimes				continue;
28891592Srgrimes			if (dir->d_name[0] == '.' && dir->d_name[1] == '.' &&
28901592Srgrimes			    dir->d_namlen == 2)
28911592Srgrimes				continue;
28921592Srgrimes
289399213Smaxim			snprintf(nbuf, sizeof(nbuf),
289431973Simp				"%s/%s", dirname, dir->d_name);
28951592Srgrimes
28961592Srgrimes			/*
28971592Srgrimes			 * We have to do a stat to insure it's
28981592Srgrimes			 * not a directory or special file.
28991592Srgrimes			 */
29001592Srgrimes			if (simple || (stat(nbuf, &st) == 0 &&
29011592Srgrimes			    S_ISREG(st.st_mode))) {
29021592Srgrimes				if (dout == NULL) {
29031592Srgrimes					dout = dataconn("file list", (off_t)-1,
29041592Srgrimes						"w");
29051592Srgrimes					if (dout == NULL)
29061592Srgrimes						goto out;
29071592Srgrimes					transflag++;
29081592Srgrimes				}
29091592Srgrimes				if (nbuf[0] == '.' && nbuf[1] == '/')
29101592Srgrimes					fprintf(dout, "%s%s\n", &nbuf[2],
29111592Srgrimes						type == TYPE_A ? "\r" : "");
29121592Srgrimes				else
29131592Srgrimes					fprintf(dout, "%s%s\n", nbuf,
29141592Srgrimes						type == TYPE_A ? "\r" : "");
29151592Srgrimes				byte_count += strlen(nbuf) + 1;
29161592Srgrimes			}
29171592Srgrimes		}
29181592Srgrimes		(void) closedir(dirp);
29191592Srgrimes	}
29201592Srgrimes
29211592Srgrimes	if (dout == NULL)
29221592Srgrimes		reply(550, "No files found.");
29231592Srgrimes	else if (ferror(dout) != 0)
29241592Srgrimes		perror_reply(550, "Data connection");
29251592Srgrimes	else
29261592Srgrimes		reply(226, "Transfer complete.");
29271592Srgrimes
29281592Srgrimes	transflag = 0;
29291592Srgrimes	if (dout != NULL)
29301592Srgrimes		(void) fclose(dout);
29311592Srgrimes	data = -1;
29321592Srgrimes	pdata = -1;
29331592Srgrimesout:
29341592Srgrimes	if (freeglob) {
29351592Srgrimes		freeglob = 0;
29361592Srgrimes		globfree(&gl);
29371592Srgrimes	}
29381592Srgrimes}
29391592Srgrimes
294015196Sdgvoid
294190148Simpreapchild(int signo)
294215196Sdg{
294315196Sdg	while (wait3(NULL, WNOHANG, NULL) > 0);
294415196Sdg}
294515196Sdg
294613139Speter#ifdef OLD_SETPROCTITLE
29471592Srgrimes/*
29481592Srgrimes * Clobber argv so ps will show what we're doing.  (Stolen from sendmail.)
29491592Srgrimes * Warning, since this is usually started from inetd.conf, it often doesn't
29501592Srgrimes * have much of an environment or arglist to overwrite.
29511592Srgrimes */
29521592Srgrimesvoid
29531592Srgrimessetproctitle(const char *fmt, ...)
29541592Srgrimes{
29551592Srgrimes	int i;
29561592Srgrimes	va_list ap;
29571592Srgrimes	char *p, *bp, ch;
29581592Srgrimes	char buf[LINE_MAX];
29591592Srgrimes
29601592Srgrimes	va_start(ap, fmt);
29611592Srgrimes	(void)vsnprintf(buf, sizeof(buf), fmt, ap);
29621592Srgrimes
29631592Srgrimes	/* make ps print our process name */
29641592Srgrimes	p = Argv[0];
29651592Srgrimes	*p++ = '-';
29661592Srgrimes
29671592Srgrimes	i = strlen(buf);
29681592Srgrimes	if (i > LastArgv - p - 2) {
29691592Srgrimes		i = LastArgv - p - 2;
29701592Srgrimes		buf[i] = '\0';
29711592Srgrimes	}
29721592Srgrimes	bp = buf;
29731592Srgrimes	while (ch = *bp++)
29741592Srgrimes		if (ch != '\n' && ch != '\r')
29751592Srgrimes			*p++ = ch;
29761592Srgrimes	while (p < LastArgv)
29771592Srgrimes		*p++ = ' ';
29781592Srgrimes}
297913139Speter#endif /* OLD_SETPROCTITLE */
29806740Sguido
298117433Spststatic void
298290148Simplogxfer(char *name, off_t size, time_t start)
29836740Sguido{
29846740Sguido	char buf[1024];
29856740Sguido	char path[MAXPATHLEN + 1];
298636612Sjb	time_t now;
29876740Sguido
29886740Sguido	if (statfd >= 0 && getwd(path) != NULL) {
29896740Sguido		time(&now);
299082792Sache		snprintf(buf, sizeof(buf), "%.20s!%s!%s!%s/%s!%qd!%ld\n",
29916740Sguido			ctime(&now)+4, ident, remotehost,
299282792Sache			path, name, (long long)size,
299382792Sache			(long)(now - start + (now == start)));
29946740Sguido		write(statfd, buf, strlen(buf));
29956740Sguido	}
29966740Sguido}
2997100486Syar
2998100486Syarstatic char *
2999100486Syardoublequote(char *s)
3000100486Syar{
3001100486Syar	int n;
3002100486Syar	char *p, *s2;
3003100486Syar
3004100486Syar	for (p = s, n = 0; *p; p++)
3005100486Syar		if (*p == '"')
3006100486Syar			n++;
3007100486Syar
3008100486Syar	if ((s2 = malloc(p - s + n + 1)) == NULL)
3009100486Syar		return (NULL);
3010100486Syar
3011100486Syar	for (p = s2; *s; s++, p++) {
3012100486Syar		if ((*p = *s) == '"')
3013100486Syar			*(++p) = '"';
3014100486Syar	}
3015100486Syar	*p = '\0';
3016100486Syar
3017100486Syar	return (s2);
3018100486Syar}
3019