ftpd.c revision 101809
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 101809 2002-08-13 14:08:38Z 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;
68456668Sshin	getaddrinfo(hrp->hostname, NULL, &hints, &res);
68556668Sshin	if (res)
68657124Sshin		hrp->hostinfo = res;
68725283Sdavidn	hrp->statfile = _PATH_FTPDSTATFILE;
68825283Sdavidn	hrp->welcome  = _PATH_FTPWELCOME;
68925283Sdavidn	hrp->loginmsg = _PATH_FTPLOGINMESG;
69025283Sdavidn	hrp->anonuser = "ftp";
69125283Sdavidn	hrp->next = NULL;
69225283Sdavidn	thishost = firsthost = lhrp = hrp;
69325283Sdavidn	if ((fp = fopen(_PATH_FTPHOSTS, "r")) != NULL) {
69462100Sdavidn		int addrsize, error, gothost;
69556668Sshin		void *addr;
69656668Sshin		struct hostent *hp;
69756668Sshin
69899877Syar		while ((line = fgetln(fp, &len)) != NULL) {
69956668Sshin			int	i, hp_error;
70025283Sdavidn
70199877Syar			/* skip comments */
70299877Syar			if (line[0] == '#')
70325283Sdavidn				continue;
70499877Syar			if (line[len - 1] == '\n') {
70599877Syar				line[len - 1] = '\0';
70699877Syar				mp = NULL;
70799877Syar			} else {
70899877Syar				if ((mp = malloc(len + 1)) == NULL)
70999877Syar					fatalerror("Ran out of memory.");
71099877Syar				memcpy(mp, line, len);
71199877Syar				mp[len] = '\0';
71299877Syar				line = mp;
71325283Sdavidn			}
71425283Sdavidn			cp = strtok(line, " \t");
71599877Syar			/* skip empty lines */
71699877Syar			if (cp == NULL)
71799877Syar				goto nextline;
718100182Syar			vhost = cp;
71956668Sshin
720100182Syar			/* set defaults */
721100182Syar			anonuser = "ftp";
722100182Syar			statfile = _PATH_FTPDSTATFILE;
723100182Syar			welcome  = _PATH_FTPWELCOME;
724100182Syar			loginmsg = _PATH_FTPLOGINMESG;
725100182Syar
726100182Syar			/*
727100182Syar			 * Preparse the line so we can use its info
728100182Syar			 * for all the addresses associated with
729100182Syar			 * the virtual host name.
730100182Syar			 * Field 0, the virtual host name, is special:
731100182Syar			 * it's already parsed off and will be strdup'ed
732100182Syar			 * later, after we know its canonical form.
733100182Syar			 */
734100182Syar			for (i = 1; i < 5 && (cp = strtok(NULL, " \t")); i++)
735100182Syar				if (*cp != '-' && (cp = strdup(cp)))
736100182Syar					switch (i) {
737100182Syar					case 1:	/* anon user permissions */
738100182Syar						anonuser = cp;
739100182Syar						break;
740100182Syar					case 2: /* statistics file */
741100182Syar						statfile = cp;
742100182Syar						break;
743100182Syar					case 3: /* welcome message */
744100182Syar						welcome  = cp;
745100182Syar						break;
746100182Syar					case 4: /* login message */
747100182Syar						loginmsg = cp;
748100182Syar						break;
749100182Syar					default: /* programming error */
750100182Syar						abort();
751100182Syar						/* NOTREACHED */
752100182Syar					}
753100182Syar
75456668Sshin			hints.ai_flags = 0;
75556668Sshin			hints.ai_family = AF_UNSPEC;
75656668Sshin			hints.ai_flags = AI_PASSIVE;
757100182Syar			error = getaddrinfo(vhost, NULL, &hints, &res);
75856668Sshin			if (error != NULL)
75999877Syar				goto nextline;
76056668Sshin			for (ai = res; ai != NULL && ai->ai_addr != NULL;
76162100Sdavidn			     ai = ai->ai_next) {
76256668Sshin
76362100Sdavidn			gothost = 0;
76425283Sdavidn			for (hrp = firsthost; hrp != NULL; hrp = hrp->next) {
76557124Sshin				struct addrinfo *hi;
76657124Sshin
76757124Sshin				for (hi = hrp->hostinfo; hi != NULL;
76857124Sshin				     hi = hi->ai_next)
76957124Sshin					if (hi->ai_addrlen == ai->ai_addrlen &&
77057124Sshin					    memcmp(hi->ai_addr,
77157124Sshin						   ai->ai_addr,
77262100Sdavidn						   ai->ai_addr->sa_len) == 0) {
77362100Sdavidn						gothost++;
77457124Sshin						break;
775100183Syar					}
77662100Sdavidn				if (gothost)
77762100Sdavidn					break;
77825283Sdavidn			}
77925283Sdavidn			if (hrp == NULL) {
78025283Sdavidn				if ((hrp = malloc(sizeof(struct ftphost))) == NULL)
78199877Syar					goto nextline;
782100182Syar				insert = 1;
783100182Syar			} else
784100182Syar				insert = 0; /* host already in the chain */
78557124Sshin			hrp->hostinfo = res;
78657124Sshin
78725283Sdavidn			/*
78825283Sdavidn			 * determine hostname to use.
78956668Sshin			 * force defined name if there is a valid alias
79025283Sdavidn			 * otherwise fallback to primary hostname
79125283Sdavidn			 */
79256668Sshin			/* XXX: getaddrinfo() can't do alias check */
79357124Sshin			switch(hrp->hostinfo->ai_family) {
79456668Sshin			case AF_INET:
795100259Syar				addr = &((struct sockaddr_in *)hrp->hostinfo->ai_addr)->sin_addr;
796100259Syar				addrsize = sizeof(struct in_addr);
79756668Sshin				break;
79856668Sshin			case AF_INET6:
799100259Syar				addr = &((struct sockaddr_in6 *)hrp->hostinfo->ai_addr)->sin6_addr;
800100259Syar				addrsize = sizeof(struct in6_addr);
80156668Sshin				break;
80256668Sshin			default:
80356668Sshin				/* should not reach here */
80457124Sshin				if (hrp->hostinfo != NULL)
80557124Sshin					freeaddrinfo(hrp->hostinfo);
80656668Sshin				free(hrp);
80799877Syar				goto nextline;
80856668Sshin				/* NOTREACHED */
80956668Sshin			}
810100612Syar			if ((hp = getipnodebyaddr(addr, addrsize,
81157124Sshin						  hrp->hostinfo->ai_family,
81256668Sshin						  &hp_error)) != NULL) {
813100182Syar				if (strcmp(vhost, hp->h_name) != 0) {
81425283Sdavidn					if (hp->h_aliases == NULL)
815100182Syar						vhost = hp->h_name;
81625283Sdavidn					else {
81725283Sdavidn						i = 0;
81825283Sdavidn						while (hp->h_aliases[i] &&
819100182Syar						       strcmp(vhost, hp->h_aliases[i]) != 0)
82025283Sdavidn							++i;
82125283Sdavidn						if (hp->h_aliases[i] == NULL)
822100182Syar							vhost = hp->h_name;
82325283Sdavidn					}
82425283Sdavidn				}
82525283Sdavidn			}
826100182Syar			if ((hrp->hostname = strdup(vhost)) == NULL)
827100182Syar				goto nextline;
828100182Syar			hrp->anonuser = anonuser;
829100182Syar			hrp->statfile = statfile;
830100182Syar			hrp->welcome  = welcome;
831100182Syar			hrp->loginmsg = loginmsg;
832100182Syar			if (insert) {
833100182Syar				hrp->next  = NULL;
834100182Syar				lhrp->next = hrp;
835100182Syar				lhrp = hrp;
836100182Syar			}
837100263Syar			if (hp)
838100263Syar				freehostent(hp);
83956668Sshin		      }
84099877Syarnextline:
84199877Syar			if (mp)
84299877Syar				free(mp);
84325283Sdavidn		}
84425283Sdavidn		(void) fclose(fp);
84525283Sdavidn	}
84625283Sdavidn}
84725283Sdavidn
84825283Sdavidnstatic void
84990148Simpselecthost(union sockunion *su)
85025283Sdavidn{
85125283Sdavidn	struct ftphost	*hrp;
85256668Sshin	u_int16_t port;
85356668Sshin#ifdef INET6
85456668Sshin	struct in6_addr *mapped_in6 = NULL;
85556668Sshin#endif
85657124Sshin	struct addrinfo *hi;
85725283Sdavidn
85856668Sshin#ifdef INET6
85956668Sshin	/*
86056668Sshin	 * XXX IPv4 mapped IPv6 addr consideraton,
86156668Sshin	 * specified in rfc2373.
86256668Sshin	 */
86356668Sshin	if (su->su_family == AF_INET6 &&
86456668Sshin	    IN6_IS_ADDR_V4MAPPED(&su->su_sin6.sin6_addr))
86556668Sshin		mapped_in6 = &su->su_sin6.sin6_addr;
86656668Sshin#endif
86756668Sshin
86825283Sdavidn	hrp = thishost = firsthost;	/* default */
86956668Sshin	port = su->su_port;
87056668Sshin	su->su_port = 0;
87125283Sdavidn	while (hrp != NULL) {
87262100Sdavidn	    for (hi = hrp->hostinfo; hi != NULL; hi = hi->ai_next) {
87357124Sshin		if (memcmp(su, hi->ai_addr, hi->ai_addrlen) == 0) {
87425283Sdavidn			thishost = hrp;
87525283Sdavidn			break;
87625283Sdavidn		}
87756668Sshin#ifdef INET6
87856668Sshin		/* XXX IPv4 mapped IPv6 addr consideraton */
87957124Sshin		if (hi->ai_addr->sa_family == AF_INET && mapped_in6 != NULL &&
88056668Sshin		    (memcmp(&mapped_in6->s6_addr[12],
88157124Sshin			    &((struct sockaddr_in *)hi->ai_addr)->sin_addr,
88256668Sshin			    sizeof(struct in_addr)) == 0)) {
88356668Sshin			thishost = hrp;
88456668Sshin			break;
88556668Sshin		}
88656668Sshin#endif
88762100Sdavidn	    }
88862100Sdavidn	    hrp = hrp->next;
88925283Sdavidn	}
89056668Sshin	su->su_port = port;
89125283Sdavidn	/* setup static variables as appropriate */
89225283Sdavidn	hostname = thishost->hostname;
89325283Sdavidn	ftpuser = thishost->anonuser;
89425283Sdavidn}
89525283Sdavidn#endif
89625283Sdavidn
89725283Sdavidn/*
8981592Srgrimes * Helper function for sgetpwnam().
8991592Srgrimes */
9001592Srgrimesstatic char *
90190148Simpsgetsave(char *s)
9021592Srgrimes{
9031592Srgrimes	char *new = malloc((unsigned) strlen(s) + 1);
9041592Srgrimes
9051592Srgrimes	if (new == NULL) {
9061592Srgrimes		perror_reply(421, "Local resource failure: malloc");
9071592Srgrimes		dologout(1);
9081592Srgrimes		/* NOTREACHED */
9091592Srgrimes	}
9101592Srgrimes	(void) strcpy(new, s);
9111592Srgrimes	return (new);
9121592Srgrimes}
9131592Srgrimes
9141592Srgrimes/*
9151592Srgrimes * Save the result of a getpwnam.  Used for USER command, since
9161592Srgrimes * the data returned must not be clobbered by any other command
9171592Srgrimes * (e.g., globbing).
9181592Srgrimes */
9191592Srgrimesstatic struct passwd *
92090148Simpsgetpwnam(char *name)
9211592Srgrimes{
9221592Srgrimes	static struct passwd save;
9231592Srgrimes	struct passwd *p;
9241592Srgrimes
9251592Srgrimes	if ((p = getpwnam(name)) == NULL)
9261592Srgrimes		return (p);
9271592Srgrimes	if (save.pw_name) {
9281592Srgrimes		free(save.pw_name);
9291592Srgrimes		free(save.pw_passwd);
9301592Srgrimes		free(save.pw_gecos);
9311592Srgrimes		free(save.pw_dir);
9321592Srgrimes		free(save.pw_shell);
9331592Srgrimes	}
9341592Srgrimes	save = *p;
9351592Srgrimes	save.pw_name = sgetsave(p->pw_name);
9361592Srgrimes	save.pw_passwd = sgetsave(p->pw_passwd);
9371592Srgrimes	save.pw_gecos = sgetsave(p->pw_gecos);
9381592Srgrimes	save.pw_dir = sgetsave(p->pw_dir);
9391592Srgrimes	save.pw_shell = sgetsave(p->pw_shell);
9401592Srgrimes	return (&save);
9411592Srgrimes}
9421592Srgrimes
9431592Srgrimesstatic int login_attempts;	/* number of failed login attempts */
9441592Srgrimesstatic int askpasswd;		/* had user command, ask for passwd */
94564778Ssheldonhstatic char curname[MAXLOGNAME];	/* current USER name */
9461592Srgrimes
9471592Srgrimes/*
9481592Srgrimes * USER command.
9491592Srgrimes * Sets global passwd pointer pw if named account exists and is acceptable;
9501592Srgrimes * sets askpasswd if a PASS command is expected.  If logged in previously,
9511592Srgrimes * need to reset state.  If name is "ftp" or "anonymous", the name is not in
9521592Srgrimes * _PATH_FTPUSERS, and ftp account exists, set guest and pw, then just return.
9531592Srgrimes * If account doesn't exist, ask for passwd anyway.  Otherwise, check user
9541592Srgrimes * requesting login privileges.  Disallow anyone who does not have a standard
9551592Srgrimes * shell as returned by getusershell().  Disallow anyone mentioned in the file
9561592Srgrimes * _PATH_FTPUSERS to allow people such as root and uucp to be avoided.
9571592Srgrimes */
9581592Srgrimesvoid
95990148Simpuser(char *name)
9601592Srgrimes{
9611592Srgrimes	char *cp, *shell;
9621592Srgrimes
9631592Srgrimes	if (logged_in) {
9641592Srgrimes		if (guest) {
9651592Srgrimes			reply(530, "Can't change user from guest login.");
9661592Srgrimes			return;
96717435Spst		} else if (dochroot) {
96817435Spst			reply(530, "Can't change user from chroot user.");
96917435Spst			return;
9701592Srgrimes		}
9711592Srgrimes		end_login();
9721592Srgrimes	}
9731592Srgrimes
9741592Srgrimes	guest = 0;
9751592Srgrimes	if (strcmp(name, "ftp") == 0 || strcmp(name, "anonymous") == 0) {
97636349Ssteve		if (checkuser(_PATH_FTPUSERS, "ftp", 0) ||
97736349Ssteve		    checkuser(_PATH_FTPUSERS, "anonymous", 0))
9781592Srgrimes			reply(530, "User %s access denied.", name);
97925283Sdavidn#ifdef VIRTUAL_HOSTING
98025283Sdavidn		else if ((pw = sgetpwnam(thishost->anonuser)) != NULL) {
98125283Sdavidn#else
9821592Srgrimes		else if ((pw = sgetpwnam("ftp")) != NULL) {
98325283Sdavidn#endif
9841592Srgrimes			guest = 1;
9851592Srgrimes			askpasswd = 1;
9861592Srgrimes			reply(331,
9873938Spst			"Guest login ok, send your email address as password.");
9881592Srgrimes		} else
9891592Srgrimes			reply(530, "User %s unknown.", name);
9901592Srgrimes		if (!askpasswd && logging)
9911592Srgrimes			syslog(LOG_NOTICE,
9921592Srgrimes			    "ANONYMOUS FTP LOGIN REFUSED FROM %s", remotehost);
9931592Srgrimes		return;
9941592Srgrimes	}
99520042Storstenb	if (anon_only != 0) {
99620042Storstenb		reply(530, "Sorry, only anonymous ftp allowed.");
99720042Storstenb		return;
99820042Storstenb	}
99920042Storstenb
100017478Smarkm	if ((pw = sgetpwnam(name))) {
10011592Srgrimes		if ((shell = pw->pw_shell) == NULL || *shell == 0)
10021592Srgrimes			shell = _PATH_BSHELL;
10031592Srgrimes		while ((cp = getusershell()) != NULL)
10041592Srgrimes			if (strcmp(cp, shell) == 0)
10051592Srgrimes				break;
10061592Srgrimes		endusershell();
10071592Srgrimes
100836349Ssteve		if (cp == NULL || checkuser(_PATH_FTPUSERS, name, 1)) {
10091592Srgrimes			reply(530, "User %s access denied.", name);
10101592Srgrimes			if (logging)
10111592Srgrimes				syslog(LOG_NOTICE,
10121592Srgrimes				    "FTP LOGIN REFUSED FROM %s, %s",
10131592Srgrimes				    remotehost, name);
10141592Srgrimes			pw = (struct passwd *) NULL;
10151592Srgrimes			return;
10161592Srgrimes		}
10171592Srgrimes	}
10181592Srgrimes	if (logging)
10191592Srgrimes		strncpy(curname, name, sizeof(curname)-1);
102088763Sache
102188763Sache	pwok = 0;
102279469Smarkm#ifdef USE_PAM
102379469Smarkm	/* XXX Kluge! The conversation mechanism needs to be fixed. */
102488763Sache#endif
102588763Sache	if (opiechallenge(&opiedata, name, opieprompt) == 0) {
102688763Sache		pwok = (pw != NULL) &&
102788763Sache		       opieaccessfile(remotehost) &&
102888763Sache		       opiealways(pw->pw_dir);
102988763Sache		reply(331, "Response to %s %s for %s.",
103088763Sache		      opieprompt, pwok ? "requested" : "required", name);
103188763Sache	} else {
103288763Sache		pwok = 1;
103384146Sache		reply(331, "Password required for %s.", name);
103488763Sache	}
10351592Srgrimes	askpasswd = 1;
10361592Srgrimes	/*
10371592Srgrimes	 * Delay before reading passwd after first failed
10381592Srgrimes	 * attempt to slow down passwd-guessing programs.
10391592Srgrimes	 */
10401592Srgrimes	if (login_attempts)
10411592Srgrimes		sleep((unsigned) login_attempts);
10421592Srgrimes}
10431592Srgrimes
10441592Srgrimes/*
104517435Spst * Check if a user is in the file "fname"
10461592Srgrimes */
10471592Srgrimesstatic int
104890148Simpcheckuser(char *fname, char *name, int pwset)
10491592Srgrimes{
10501592Srgrimes	FILE *fd;
10511592Srgrimes	int found = 0;
105299877Syar	size_t len;
105399877Syar	char *line, *mp, *p;
10541592Srgrimes
105517435Spst	if ((fd = fopen(fname, "r")) != NULL) {
105699877Syar		while (!found && (line = fgetln(fd, &len)) != NULL) {
105799877Syar			/* skip comments */
105899877Syar			if (line[0] == '#')
105999877Syar				continue;
106099877Syar			if (line[len - 1] == '\n') {
106199877Syar				line[len - 1] = '\0';
106299877Syar				mp = NULL;
106399877Syar			} else {
106499877Syar				if ((mp = malloc(len + 1)) == NULL)
106599877Syar					fatalerror("Ran out of memory.");
106699877Syar				memcpy(mp, line, len);
106799877Syar				mp[len] = '\0';
106899877Syar				line = mp;
106999877Syar			}
107099877Syar			/* avoid possible leading and trailing whitespace */
107199877Syar			p = strtok(line, " \t");
107299877Syar			/* skip empty lines */
107399877Syar			if (p == NULL)
107499877Syar				goto nextline;
107599877Syar			/*
107699877Syar			 * if first chr is '@', check group membership
107799877Syar			 */
107899877Syar			if (p[0] == '@') {
107999877Syar				int i = 0;
108099877Syar				struct group *grp;
108199877Syar
108299877Syar				if ((grp = getgrnam(p+1)) == NULL)
108399877Syar					goto nextline;
108425187Sdavidn				/*
108599877Syar				 * Check user's default group
108625187Sdavidn				 */
108799877Syar				if (pwset && grp->gr_gid == pw->pw_gid)
108899877Syar					found = 1;
108925187Sdavidn				/*
109099877Syar				 * Check supplementary groups
109125187Sdavidn				 */
109299877Syar				while (!found && grp->gr_mem[i])
109399877Syar					found = strcmp(name,
109499877Syar						grp->gr_mem[i++])
109599877Syar						== 0;
10961592Srgrimes			}
109799877Syar			/*
109899877Syar			 * Otherwise, just check for username match
109999877Syar			 */
110099877Syar			else
110199877Syar				found = strcmp(p, name) == 0;
110299877Syarnextline:
110399877Syar			if (mp)
110499877Syar				free(mp);
110599877Syar		}
11061592Srgrimes		(void) fclose(fd);
11071592Srgrimes	}
11081592Srgrimes	return (found);
11091592Srgrimes}
11101592Srgrimes
11111592Srgrimes/*
11121592Srgrimes * Terminate login as previous user, if any, resetting state;
11131592Srgrimes * used when USER command is given or login fails.
11141592Srgrimes */
11151592Srgrimesstatic void
111690148Simpend_login(void)
11171592Srgrimes{
111874874Smarkm#ifdef USE_PAM
111974874Smarkm	int e;
112074874Smarkm#endif
11211592Srgrimes
11221592Srgrimes	(void) seteuid((uid_t)0);
11231592Srgrimes	if (logged_in)
112489920Sume		ftpd_logwtmp(ttyline, "", NULL);
11251592Srgrimes	pw = NULL;
112625101Sdavidn#ifdef	LOGIN_CAP
112725101Sdavidn	setusercontext(NULL, getpwuid(0), (uid_t)0,
112825101Sdavidn		       LOGIN_SETPRIORITY|LOGIN_SETRESOURCES|LOGIN_SETUMASK);
112925101Sdavidn#endif
113074874Smarkm#ifdef USE_PAM
113174874Smarkm	if ((e = pam_setcred(pamh, PAM_DELETE_CRED)) != PAM_SUCCESS)
113274874Smarkm		syslog(LOG_ERR, "pam_setcred: %s", pam_strerror(pamh, e));
113374874Smarkm	if ((e = pam_close_session(pamh,0)) != PAM_SUCCESS)
113474874Smarkm		syslog(LOG_ERR, "pam_close_session: %s", pam_strerror(pamh, e));
113574874Smarkm	if ((e = pam_end(pamh, e)) != PAM_SUCCESS)
113674874Smarkm		syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e));
113774874Smarkm	pamh = NULL;
113874874Smarkm#endif
11391592Srgrimes	logged_in = 0;
11401592Srgrimes	guest = 0;
114117435Spst	dochroot = 0;
11421592Srgrimes}
11431592Srgrimes
114474874Smarkm#ifdef USE_PAM
114551433Smarkm
114651433Smarkm/*
114751433Smarkm * the following code is stolen from imap-uw PAM authentication module and
114851433Smarkm * login.c
114951433Smarkm */
115051433Smarkm#define COPY_STRING(s) (s ? strdup(s) : NULL)
115151433Smarkm
115251433Smarkmstruct cred_t {
115351433Smarkm	const char *uname;		/* user name */
115451433Smarkm	const char *pass;		/* password */
115551433Smarkm};
115651433Smarkmtypedef struct cred_t cred_t;
115751433Smarkm
115851433Smarkmstatic int
115951433Smarkmauth_conv(int num_msg, const struct pam_message **msg,
116051433Smarkm	  struct pam_response **resp, void *appdata)
116151433Smarkm{
116251433Smarkm	int i;
116351433Smarkm	cred_t *cred = (cred_t *) appdata;
116491244Sdes	struct pam_response *reply;
116551433Smarkm
116691244Sdes	reply = calloc(num_msg, sizeof *reply);
116791244Sdes	if (reply == NULL)
116891244Sdes		return PAM_BUF_ERR;
116991244Sdes
117051433Smarkm	for (i = 0; i < num_msg; i++) {
117151433Smarkm		switch (msg[i]->msg_style) {
117251433Smarkm		case PAM_PROMPT_ECHO_ON:	/* assume want user name */
117351433Smarkm			reply[i].resp_retcode = PAM_SUCCESS;
117451433Smarkm			reply[i].resp = COPY_STRING(cred->uname);
117551433Smarkm			/* PAM frees resp. */
117651433Smarkm			break;
117751433Smarkm		case PAM_PROMPT_ECHO_OFF:	/* assume want password */
117851433Smarkm			reply[i].resp_retcode = PAM_SUCCESS;
117951433Smarkm			reply[i].resp = COPY_STRING(cred->pass);
118051433Smarkm			/* PAM frees resp. */
118151433Smarkm			break;
118251433Smarkm		case PAM_TEXT_INFO:
118351433Smarkm		case PAM_ERROR_MSG:
118451433Smarkm			reply[i].resp_retcode = PAM_SUCCESS;
118551433Smarkm			reply[i].resp = NULL;
118651433Smarkm			break;
118751433Smarkm		default:			/* unknown message style */
118851433Smarkm			free(reply);
118951433Smarkm			return PAM_CONV_ERR;
119051433Smarkm		}
119151433Smarkm	}
119251433Smarkm
119351433Smarkm	*resp = reply;
119451433Smarkm	return PAM_SUCCESS;
119551433Smarkm}
119651433Smarkm
119751433Smarkm/*
119851433Smarkm * Attempt to authenticate the user using PAM.  Returns 0 if the user is
119951433Smarkm * authenticated, or 1 if not authenticated.  If some sort of PAM system
120051433Smarkm * error occurs (e.g., the "/etc/pam.conf" file is missing) then this
120151433Smarkm * function returns -1.  This can be used as an indication that we should
120251433Smarkm * fall back to a different authentication mechanism.
120351433Smarkm */
120451433Smarkmstatic int
120551433Smarkmauth_pam(struct passwd **ppw, const char *pass)
120651433Smarkm{
120751433Smarkm	pam_handle_t *pamh = NULL;
120851433Smarkm	const char *tmpl_user;
120951433Smarkm	const void *item;
121051433Smarkm	int rval;
121151433Smarkm	int e;
121251433Smarkm	cred_t auth_cred = { (*ppw)->pw_name, pass };
121351433Smarkm	struct pam_conv conv = { &auth_conv, &auth_cred };
121451433Smarkm
121551433Smarkm	e = pam_start("ftpd", (*ppw)->pw_name, &conv, &pamh);
121651433Smarkm	if (e != PAM_SUCCESS) {
121751433Smarkm		syslog(LOG_ERR, "pam_start: %s", pam_strerror(pamh, e));
121851433Smarkm		return -1;
121951433Smarkm	}
122051433Smarkm
122167007Sguido	e = pam_set_item(pamh, PAM_RHOST, remotehost);
122267007Sguido	if (e != PAM_SUCCESS) {
122367007Sguido		syslog(LOG_ERR, "pam_set_item(PAM_RHOST): %s",
122467007Sguido			pam_strerror(pamh, e));
122567007Sguido		return -1;
122667007Sguido	}
122767007Sguido
122851433Smarkm	e = pam_authenticate(pamh, 0);
122951433Smarkm	switch (e) {
123051433Smarkm	case PAM_SUCCESS:
123151433Smarkm		/*
123251433Smarkm		 * With PAM we support the concept of a "template"
123351433Smarkm		 * user.  The user enters a login name which is
123451433Smarkm		 * authenticated by PAM, usually via a remote service
123551433Smarkm		 * such as RADIUS or TACACS+.  If authentication
123651433Smarkm		 * succeeds, a different but related "template" name
123751433Smarkm		 * is used for setting the credentials, shell, and
123851433Smarkm		 * home directory.  The name the user enters need only
123951433Smarkm		 * exist on the remote authentication server, but the
124051433Smarkm		 * template name must be present in the local password
124151433Smarkm		 * database.
124251433Smarkm		 *
124351433Smarkm		 * This is supported by two various mechanisms in the
124451433Smarkm		 * individual modules.  However, from the application's
124551433Smarkm		 * point of view, the template user is always passed
124651433Smarkm		 * back as a changed value of the PAM_USER item.
124751433Smarkm		 */
124851433Smarkm		if ((e = pam_get_item(pamh, PAM_USER, &item)) ==
124951433Smarkm		    PAM_SUCCESS) {
125051433Smarkm			tmpl_user = (const char *) item;
125151433Smarkm			if (strcmp((*ppw)->pw_name, tmpl_user) != 0)
125251433Smarkm				*ppw = getpwnam(tmpl_user);
125351433Smarkm		} else
125451433Smarkm			syslog(LOG_ERR, "Couldn't get PAM_USER: %s",
125551433Smarkm			    pam_strerror(pamh, e));
125651433Smarkm		rval = 0;
125751433Smarkm		break;
125851433Smarkm
125951433Smarkm	case PAM_AUTH_ERR:
126051433Smarkm	case PAM_USER_UNKNOWN:
126151433Smarkm	case PAM_MAXTRIES:
126251433Smarkm		rval = 1;
126351433Smarkm		break;
126451433Smarkm
126551433Smarkm	default:
126674874Smarkm		syslog(LOG_ERR, "pam_authenticate: %s", pam_strerror(pamh, e));
126751433Smarkm		rval = -1;
126851433Smarkm		break;
126951433Smarkm	}
127051433Smarkm
127174874Smarkm	if (rval == 0) {
127274874Smarkm		e = pam_acct_mgmt(pamh, 0);
127374874Smarkm		if (e == PAM_NEW_AUTHTOK_REQD) {
127474874Smarkm			e = pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
127574874Smarkm			if (e != PAM_SUCCESS) {
127674874Smarkm				syslog(LOG_ERR, "pam_chauthtok: %s", pam_strerror(pamh, e));
127774874Smarkm				rval = 1;
127874874Smarkm			}
127974874Smarkm		} else if (e != PAM_SUCCESS) {
128074874Smarkm			rval = 1;
128174874Smarkm		}
128251433Smarkm	}
128374874Smarkm
128474874Smarkm	if (rval != 0) {
128574874Smarkm		if ((e = pam_end(pamh, e)) != PAM_SUCCESS) {
128674874Smarkm			syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e));
128774874Smarkm		}
128874874Smarkm		pamh = NULL;
128974874Smarkm	}
129051433Smarkm	return rval;
129151433Smarkm}
129251433Smarkm
129374874Smarkm#endif /* USE_PAM */
129451433Smarkm
12951592Srgrimesvoid
129690148Simppass(char *passwd)
12971592Srgrimes{
129817435Spst	int rval;
12991592Srgrimes	FILE *fd;
130025101Sdavidn#ifdef	LOGIN_CAP
130125101Sdavidn	login_cap_t *lc = NULL;
130225101Sdavidn#endif
130374874Smarkm#ifdef USE_PAM
130474874Smarkm	int e;
130574874Smarkm#endif
130688763Sache	char *xpasswd;
13071592Srgrimes
13081592Srgrimes	if (logged_in || askpasswd == 0) {
13091592Srgrimes		reply(503, "Login with USER first.");
13101592Srgrimes		return;
13111592Srgrimes	}
13121592Srgrimes	askpasswd = 0;
13131592Srgrimes	if (!guest) {		/* "ftp" is only account allowed no password */
131417435Spst		if (pw == NULL) {
131517435Spst			rval = 1;	/* failure below */
131617435Spst			goto skip;
131717435Spst		}
131874874Smarkm#ifdef USE_PAM
131951433Smarkm		rval = auth_pam(&pw, passwd);
132089622Sache		if (rval >= 0) {
132189622Sache			opieunlock();
132217435Spst			goto skip;
132389622Sache		}
132489622Sache#endif
132588763Sache		if (opieverify(&opiedata, passwd) == 0)
132688763Sache			xpasswd = pw->pw_passwd;
132789622Sache		else if (pwok) {
132888763Sache			xpasswd = crypt(passwd, pw->pw_passwd);
132989622Sache			if (passwd[0] == '\0' && pw->pw_passwd[0] != '\0')
133089622Sache				xpasswd = ":";
133189622Sache		} else {
133288763Sache			rval = 1;
133388763Sache			goto skip;
133488763Sache		}
133588763Sache		rval = strcmp(pw->pw_passwd, xpasswd);
133689622Sache		if (pw->pw_expire && time(NULL) >= pw->pw_expire)
133717435Spst			rval = 1;	/* failure */
133817435Spstskip:
133917435Spst		/*
134017435Spst		 * If rval == 1, the user failed the authentication check
134151433Smarkm		 * above.  If rval == 0, either PAM or local authentication
134217435Spst		 * succeeded.
134317435Spst		 */
134417435Spst		if (rval) {
13451592Srgrimes			reply(530, "Login incorrect.");
13461592Srgrimes			if (logging)
13471592Srgrimes				syslog(LOG_NOTICE,
13481592Srgrimes				    "FTP LOGIN FAILED FROM %s, %s",
13491592Srgrimes				    remotehost, curname);
13501592Srgrimes			pw = NULL;
13511592Srgrimes			if (login_attempts++ >= 5) {
13521592Srgrimes				syslog(LOG_NOTICE,
13531592Srgrimes				    "repeated login failures from %s",
13541592Srgrimes				    remotehost);
13551592Srgrimes				exit(0);
13561592Srgrimes			}
13571592Srgrimes			return;
13581592Srgrimes		}
13591592Srgrimes	}
13601592Srgrimes	login_attempts = 0;		/* this time successful */
13611592Srgrimes	if (setegid((gid_t)pw->pw_gid) < 0) {
13621592Srgrimes		reply(550, "Can't set gid.");
13631592Srgrimes		return;
13641592Srgrimes	}
136525101Sdavidn	/* May be overridden by login.conf */
136625101Sdavidn	(void) umask(defumask);
136725101Sdavidn#ifdef	LOGIN_CAP
136825674Sdavidn	if ((lc = login_getpwclass(pw)) != NULL) {
136925101Sdavidn		char	remote_ip[MAXHOSTNAMELEN];
137025101Sdavidn
137156668Sshin		getnameinfo((struct sockaddr *)&his_addr, his_addr.su_len,
137256668Sshin			remote_ip, sizeof(remote_ip) - 1, NULL, 0,
137399255Sume			NI_NUMERICHOST);
137425101Sdavidn		remote_ip[sizeof(remote_ip) - 1] = 0;
137525101Sdavidn		if (!auth_hostok(lc, remotehost, remote_ip)) {
137625101Sdavidn			syslog(LOG_INFO|LOG_AUTH,
137725101Sdavidn			    "FTP LOGIN FAILED (HOST) as %s: permission denied.",
137825101Sdavidn			    pw->pw_name);
137925101Sdavidn			reply(530, "Permission denied.\n");
138025101Sdavidn			pw = NULL;
138125101Sdavidn			return;
138225101Sdavidn		}
138325101Sdavidn		if (!auth_timeok(lc, time(NULL))) {
138425101Sdavidn			reply(530, "Login not available right now.\n");
138525101Sdavidn			pw = NULL;
138625101Sdavidn			return;
138725101Sdavidn		}
138825101Sdavidn	}
138925101Sdavidn	setusercontext(lc, pw, (uid_t)0,
139040310Sdes		LOGIN_SETLOGIN|LOGIN_SETGROUP|LOGIN_SETPRIORITY|
139140310Sdes		LOGIN_SETRESOURCES|LOGIN_SETUMASK);
139225101Sdavidn#else
139340310Sdes	setlogin(pw->pw_name);
13941592Srgrimes	(void) initgroups(pw->pw_name, pw->pw_gid);
139525101Sdavidn#endif
13961592Srgrimes
139774874Smarkm#ifdef USE_PAM
139874874Smarkm	if (pamh) {
139974874Smarkm		if ((e = pam_open_session(pamh, 0)) != PAM_SUCCESS) {
140074874Smarkm			syslog(LOG_ERR, "pam_open_session: %s", pam_strerror(pamh, e));
140174874Smarkm		} else if ((e = pam_setcred(pamh, PAM_ESTABLISH_CRED)) != PAM_SUCCESS) {
140274874Smarkm			syslog(LOG_ERR, "pam_setcred: %s", pam_strerror(pamh, e));
140374874Smarkm		}
140474874Smarkm	}
140574874Smarkm#endif
140674874Smarkm
14071592Srgrimes	/* open wtmp before chroot */
140889920Sume	ftpd_logwtmp(ttyline, pw->pw_name, (struct sockaddr *)&his_addr);
14091592Srgrimes	logged_in = 1;
14101592Srgrimes
141117435Spst	if (guest && stats && statfd < 0)
141225283Sdavidn#ifdef VIRTUAL_HOSTING
141325283Sdavidn		if ((statfd = open(thishost->statfile, O_WRONLY|O_APPEND)) < 0)
141425283Sdavidn#else
14156740Sguido		if ((statfd = open(_PATH_FTPDSTATFILE, O_WRONLY|O_APPEND)) < 0)
141625283Sdavidn#endif
14176740Sguido			stats = 0;
14186740Sguido
141925101Sdavidn	dochroot =
142025101Sdavidn#ifdef	LOGIN_CAP	/* Allow login.conf configuration as well */
142125101Sdavidn		login_getcapbool(lc, "ftp-chroot", 0) ||
142225101Sdavidn#endif
142336349Ssteve		checkuser(_PATH_FTPCHROOT, pw->pw_name, 1);
14241592Srgrimes	if (guest) {
14251592Srgrimes		/*
14261592Srgrimes		 * We MUST do a chdir() after the chroot. Otherwise
14271592Srgrimes		 * the old current directory will be accessible as "."
14281592Srgrimes		 * outside the new root!
14291592Srgrimes		 */
14301592Srgrimes		if (chroot(pw->pw_dir) < 0 || chdir("/") < 0) {
14311592Srgrimes			reply(550, "Can't set guest privileges.");
14321592Srgrimes			goto bad;
14331592Srgrimes		}
143417435Spst	} else if (dochroot) {
143517435Spst		if (chroot(pw->pw_dir) < 0 || chdir("/") < 0) {
143617435Spst			reply(550, "Can't change root.");
143717435Spst			goto bad;
143817435Spst		}
14391592Srgrimes	} else if (chdir(pw->pw_dir) < 0) {
14401592Srgrimes		if (chdir("/") < 0) {
14411592Srgrimes			reply(530, "User %s: can't change directory to %s.",
14421592Srgrimes			    pw->pw_name, pw->pw_dir);
14431592Srgrimes			goto bad;
14441592Srgrimes		} else
14451592Srgrimes			lreply(230, "No directory! Logging in with home=/");
14461592Srgrimes	}
14471592Srgrimes	if (seteuid((uid_t)pw->pw_uid) < 0) {
14481592Srgrimes		reply(550, "Can't set uid.");
14491592Srgrimes		goto bad;
14501592Srgrimes	}
14518696Sdg
14521592Srgrimes	/*
14531592Srgrimes	 * Display a login message, if it exists.
14541592Srgrimes	 * N.B. reply(230,) must follow the message.
14551592Srgrimes	 */
145625283Sdavidn#ifdef VIRTUAL_HOSTING
145725283Sdavidn	if ((fd = fopen(thishost->loginmsg, "r")) != NULL) {
145825283Sdavidn#else
14591592Srgrimes	if ((fd = fopen(_PATH_FTPLOGINMESG, "r")) != NULL) {
146025283Sdavidn#endif
14611592Srgrimes		char *cp, line[LINE_MAX];
14621592Srgrimes
14631592Srgrimes		while (fgets(line, sizeof(line), fd) != NULL) {
14641592Srgrimes			if ((cp = strchr(line, '\n')) != NULL)
14651592Srgrimes				*cp = '\0';
14661592Srgrimes			lreply(230, "%s", line);
14671592Srgrimes		}
14681592Srgrimes		(void) fflush(stdout);
14691592Srgrimes		(void) fclose(fd);
14701592Srgrimes	}
14711592Srgrimes	if (guest) {
14726740Sguido		if (ident != NULL)
14736740Sguido			free(ident);
147417433Spst		ident = strdup(passwd);
147517433Spst		if (ident == NULL)
147676096Smarkm			fatalerror("Ran out of memory.");
147717433Spst
14781592Srgrimes		reply(230, "Guest login ok, access restrictions apply.");
14791592Srgrimes#ifdef SETPROCTITLE
148025283Sdavidn#ifdef VIRTUAL_HOSTING
148125283Sdavidn		if (thishost != firsthost)
148225283Sdavidn			snprintf(proctitle, sizeof(proctitle),
148383308Smikeh				 "%s: anonymous(%s)/%s", remotehost, hostname,
148483308Smikeh				 passwd);
148525283Sdavidn		else
148625283Sdavidn#endif
148725283Sdavidn			snprintf(proctitle, sizeof(proctitle),
148883308Smikeh				 "%s: anonymous/%s", remotehost, passwd);
148913139Speter		setproctitle("%s", proctitle);
14901592Srgrimes#endif /* SETPROCTITLE */
14911592Srgrimes		if (logging)
14921592Srgrimes			syslog(LOG_INFO, "ANONYMOUS FTP LOGIN FROM %s, %s",
14931592Srgrimes			    remotehost, passwd);
14941592Srgrimes	} else {
149584841Syar		if (dochroot)
149684841Syar			reply(230, "User %s logged in, "
149784841Syar				   "access restrictions apply.", pw->pw_name);
149884841Syar		else
149984841Syar			reply(230, "User %s logged in.", pw->pw_name);
150025986Sdanny
15011592Srgrimes#ifdef SETPROCTITLE
15021592Srgrimes		snprintf(proctitle, sizeof(proctitle),
150384842Syar			 "%s: user/%s", remotehost, pw->pw_name);
150413139Speter		setproctitle("%s", proctitle);
15051592Srgrimes#endif /* SETPROCTITLE */
15061592Srgrimes		if (logging)
15071592Srgrimes			syslog(LOG_INFO, "FTP LOGIN FROM %s as %s",
15081592Srgrimes			    remotehost, pw->pw_name);
15091592Srgrimes	}
151025101Sdavidn#ifdef	LOGIN_CAP
151125101Sdavidn	login_close(lc);
151225101Sdavidn#endif
15131592Srgrimes	return;
15141592Srgrimesbad:
15151592Srgrimes	/* Forget all about it... */
151625101Sdavidn#ifdef	LOGIN_CAP
151725101Sdavidn	login_close(lc);
151825101Sdavidn#endif
15191592Srgrimes	end_login();
15201592Srgrimes}
15211592Srgrimes
15221592Srgrimesvoid
152390148Simpretrieve(char *cmd, char *name)
15241592Srgrimes{
15251592Srgrimes	FILE *fin, *dout;
15261592Srgrimes	struct stat st;
152790148Simp	int (*closefunc)(FILE *);
152836612Sjb	time_t start;
15291592Srgrimes
15301592Srgrimes	if (cmd == 0) {
15311592Srgrimes		fin = fopen(name, "r"), closefunc = fclose;
15321592Srgrimes		st.st_size = 0;
15331592Srgrimes	} else {
15341592Srgrimes		char line[BUFSIZ];
15351592Srgrimes
153631973Simp		(void) snprintf(line, sizeof(line), cmd, name), name = line;
15371592Srgrimes		fin = ftpd_popen(line, "r"), closefunc = ftpd_pclose;
15381592Srgrimes		st.st_size = -1;
15391592Srgrimes		st.st_blksize = BUFSIZ;
15401592Srgrimes	}
15411592Srgrimes	if (fin == NULL) {
15421592Srgrimes		if (errno != 0) {
15431592Srgrimes			perror_reply(550, name);
15441592Srgrimes			if (cmd == 0) {
15451592Srgrimes				LOGCMD("get", name);
15461592Srgrimes			}
15471592Srgrimes		}
15481592Srgrimes		return;
15491592Srgrimes	}
15501592Srgrimes	byte_count = -1;
15511592Srgrimes	if (cmd == 0 && (fstat(fileno(fin), &st) < 0 || !S_ISREG(st.st_mode))) {
15521592Srgrimes		reply(550, "%s: not a plain file.", name);
15531592Srgrimes		goto done;
15541592Srgrimes	}
15551592Srgrimes	if (restart_point) {
15561592Srgrimes		if (type == TYPE_A) {
15571592Srgrimes			off_t i, n;
15581592Srgrimes			int c;
15591592Srgrimes
15601592Srgrimes			n = restart_point;
15611592Srgrimes			i = 0;
15621592Srgrimes			while (i++ < n) {
15631592Srgrimes				if ((c=getc(fin)) == EOF) {
15641592Srgrimes					perror_reply(550, name);
15651592Srgrimes					goto done;
15661592Srgrimes				}
15671592Srgrimes				if (c == '\n')
15681592Srgrimes					i++;
15691592Srgrimes			}
15701592Srgrimes		} else if (lseek(fileno(fin), restart_point, L_SET) < 0) {
15711592Srgrimes			perror_reply(550, name);
15721592Srgrimes			goto done;
15731592Srgrimes		}
15741592Srgrimes	}
15751592Srgrimes	dout = dataconn(name, st.st_size, "w");
15761592Srgrimes	if (dout == NULL)
15771592Srgrimes		goto done;
15786740Sguido	time(&start);
15798240Swollman	send_data(fin, dout, st.st_blksize, st.st_size,
15808240Swollman		  restart_point == 0 && cmd == 0 && S_ISREG(st.st_mode));
15816740Sguido	if (cmd == 0 && guest && stats)
158217433Spst		logxfer(name, st.st_size, start);
15831592Srgrimes	(void) fclose(dout);
15841592Srgrimes	data = -1;
15851592Srgrimes	pdata = -1;
15861592Srgrimesdone:
15871592Srgrimes	if (cmd == 0)
15881592Srgrimes		LOGBYTES("get", name, byte_count);
15891592Srgrimes	(*closefunc)(fin);
15901592Srgrimes}
15911592Srgrimes
15921592Srgrimesvoid
159390148Simpstore(char *name, char *mode, int unique)
15941592Srgrimes{
1595101537Syar	int fd;
15961592Srgrimes	FILE *fout, *din;
15971592Srgrimes	struct stat st;
159890148Simp	int (*closefunc)(FILE *);
15991592Srgrimes
1600101537Syar	if (*mode == 'a') {		/* APPE */
1601101537Syar		if (unique) {
1602101537Syar			/* Programming error */
1603101537Syar			syslog(LOG_ERR, "Internal: unique flag to APPE");
1604101537Syar			unique = 0;
1605101537Syar		}
1606101537Syar		if (guest && noguestmod) {
1607101537Syar			reply(550, "Appending to existing file denied");
1608101537Syar			goto err;
1609101537Syar		}
1610101537Syar		restart_point = 0;	/* not affected by preceding REST */
16111592Srgrimes	}
1612101537Syar	if (unique)			/* STOU overrides REST */
1613101537Syar		restart_point = 0;
1614101537Syar	if (guest && noguestmod) {
1615101537Syar		if (restart_point) {	/* guest STOR w/REST */
1616101537Syar			reply(550, "Modifying existing file denied");
1617101537Syar			goto err;
1618101537Syar		} else			/* treat guest STOR as STOU */
1619101537Syar			unique = 1;
1620101537Syar	}
16211592Srgrimes
16221592Srgrimes	if (restart_point)
1623101537Syar		mode = "r+";	/* so ASCII manual seek can work */
1624101537Syar	if (unique) {
1625101537Syar		if ((fd = guniquefd(name, &name)) < 0)
1626101537Syar			goto err;
1627101537Syar		fout = fdopen(fd, mode);
1628101537Syar	} else
1629101537Syar		fout = fopen(name, mode);
16301592Srgrimes	closefunc = fclose;
16311592Srgrimes	if (fout == NULL) {
16321592Srgrimes		perror_reply(553, name);
1633101537Syar		goto err;
16341592Srgrimes	}
16351592Srgrimes	byte_count = -1;
16361592Srgrimes	if (restart_point) {
16371592Srgrimes		if (type == TYPE_A) {
16381592Srgrimes			off_t i, n;
16391592Srgrimes			int c;
16401592Srgrimes
16411592Srgrimes			n = restart_point;
16421592Srgrimes			i = 0;
16431592Srgrimes			while (i++ < n) {
16441592Srgrimes				if ((c=getc(fout)) == EOF) {
16451592Srgrimes					perror_reply(550, name);
16461592Srgrimes					goto done;
16471592Srgrimes				}
16481592Srgrimes				if (c == '\n')
16491592Srgrimes					i++;
16501592Srgrimes			}
16511592Srgrimes			/*
16521592Srgrimes			 * We must do this seek to "current" position
16531592Srgrimes			 * because we are changing from reading to
16541592Srgrimes			 * writing.
16551592Srgrimes			 */
165682792Sache			if (fseeko(fout, (off_t)0, SEEK_CUR) < 0) {
16571592Srgrimes				perror_reply(550, name);
16581592Srgrimes				goto done;
16591592Srgrimes			}
16601592Srgrimes		} else if (lseek(fileno(fout), restart_point, L_SET) < 0) {
16611592Srgrimes			perror_reply(550, name);
16621592Srgrimes			goto done;
16631592Srgrimes		}
16641592Srgrimes	}
16651592Srgrimes	din = dataconn(name, (off_t)-1, "r");
16661592Srgrimes	if (din == NULL)
16671592Srgrimes		goto done;
16681592Srgrimes	if (receive_data(din, fout) == 0) {
16691592Srgrimes		if (unique)
16701592Srgrimes			reply(226, "Transfer complete (unique file name:%s).",
16711592Srgrimes			    name);
16721592Srgrimes		else
16731592Srgrimes			reply(226, "Transfer complete.");
16741592Srgrimes	}
16751592Srgrimes	(void) fclose(din);
16761592Srgrimes	data = -1;
16771592Srgrimes	pdata = -1;
16781592Srgrimesdone:
16791592Srgrimes	LOGBYTES(*mode == 'w' ? "put" : "append", name, byte_count);
16801592Srgrimes	(*closefunc)(fout);
1681101537Syar	return;
1682101537Syarerr:
1683101537Syar	LOGCMD(*mode == 'a' ? "append" : "put" , name);
1684101537Syar	return;
16851592Srgrimes}
16861592Srgrimes
16871592Srgrimesstatic FILE *
168890148Simpgetdatasock(char *mode)
16891592Srgrimes{
16901592Srgrimes	int on = 1, s, t, tries;
16911592Srgrimes
16921592Srgrimes	if (data >= 0)
16931592Srgrimes		return (fdopen(data, mode));
16941592Srgrimes	(void) seteuid((uid_t)0);
169556668Sshin
169656668Sshin	s = socket(data_dest.su_family, SOCK_STREAM, 0);
16971592Srgrimes	if (s < 0)
16981592Srgrimes		goto bad;
1699100612Syar	if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
1700100609Syar		syslog(LOG_WARNING, "data setsockopt (SO_REUSEADDR): %m");
17011592Srgrimes	/* anchor socket to avoid multi-homing problems */
170256668Sshin	data_source = ctrl_addr;
170356668Sshin	data_source.su_port = htons(20); /* ftp-data port */
17041592Srgrimes	for (tries = 1; ; tries++) {
17051592Srgrimes		if (bind(s, (struct sockaddr *)&data_source,
170656668Sshin		    data_source.su_len) >= 0)
17071592Srgrimes			break;
17081592Srgrimes		if (errno != EADDRINUSE || tries > 10)
17091592Srgrimes			goto bad;
17101592Srgrimes		sleep(tries);
17111592Srgrimes	}
17121592Srgrimes	(void) seteuid((uid_t)pw->pw_uid);
17131592Srgrimes#ifdef IP_TOS
171456668Sshin	if (data_source.su_family == AF_INET)
171556668Sshin      {
17161592Srgrimes	on = IPTOS_THROUGHPUT;
1717100612Syar	if (setsockopt(s, IPPROTO_IP, IP_TOS, &on, sizeof(int)) < 0)
1718100609Syar		syslog(LOG_WARNING, "data setsockopt (IP_TOS): %m");
171956668Sshin      }
17201592Srgrimes#endif
17218240Swollman#ifdef TCP_NOPUSH
17228240Swollman	/*
17238240Swollman	 * Turn off push flag to keep sender TCP from sending short packets
17248240Swollman	 * at the boundaries of each write().  Should probably do a SO_SNDBUF
17258240Swollman	 * to set the send buffer size as well, but that may not be desirable
17268240Swollman	 * in heavy-load situations.
17278240Swollman	 */
17288240Swollman	on = 1;
1729100612Syar	if (setsockopt(s, IPPROTO_TCP, TCP_NOPUSH, &on, sizeof on) < 0)
1730100609Syar		syslog(LOG_WARNING, "data setsockopt (TCP_NOPUSH): %m");
17318240Swollman#endif
17328240Swollman#ifdef SO_SNDBUF
17338240Swollman	on = 65536;
1734100612Syar	if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, &on, sizeof on) < 0)
1735100609Syar		syslog(LOG_WARNING, "data setsockopt (SO_SNDBUF): %m");
17368240Swollman#endif
17378240Swollman
17381592Srgrimes	return (fdopen(s, mode));
17391592Srgrimesbad:
17401592Srgrimes	/* Return the real value of errno (close may change it) */
17411592Srgrimes	t = errno;
17421592Srgrimes	(void) seteuid((uid_t)pw->pw_uid);
17431592Srgrimes	(void) close(s);
17441592Srgrimes	errno = t;
17451592Srgrimes	return (NULL);
17461592Srgrimes}
17471592Srgrimes
17481592Srgrimesstatic FILE *
174990148Simpdataconn(char *name, off_t size, char *mode)
17501592Srgrimes{
17511592Srgrimes	char sizebuf[32];
17521592Srgrimes	FILE *file;
17531592Srgrimes	int retry = 0, tos;
17541592Srgrimes
17551592Srgrimes	file_size = size;
17561592Srgrimes	byte_count = 0;
17571592Srgrimes	if (size != (off_t) -1)
175831973Simp		(void) snprintf(sizebuf, sizeof(sizebuf), " (%qd bytes)", size);
17591592Srgrimes	else
176031973Simp		*sizebuf = '\0';
17611592Srgrimes	if (pdata >= 0) {
176256668Sshin		union sockunion from;
176386628Syar		int flags;
176456668Sshin		int s, fromlen = ctrl_addr.su_len;
176512532Sguido		struct timeval timeout;
176612532Sguido		fd_set set;
17671592Srgrimes
176812532Sguido		FD_ZERO(&set);
176912532Sguido		FD_SET(pdata, &set);
177012532Sguido
177112532Sguido		timeout.tv_usec = 0;
177212532Sguido		timeout.tv_sec = 120;
177312532Sguido
177486628Syar		/*
177586628Syar		 * Granted a socket is in the blocking I/O mode,
177686628Syar		 * accept() will block after a successful select()
177786628Syar		 * if the selected connection dies in between.
177886628Syar		 * Therefore set the non-blocking I/O flag here.
177986628Syar		 */
178086628Syar		if ((flags = fcntl(pdata, F_GETFL, 0)) == -1 ||
178186628Syar		    fcntl(pdata, F_SETFL, flags | O_NONBLOCK) == -1)
178286628Syar			goto pdata_err;
178386628Syar		if (select(pdata+1, &set, (fd_set *) 0, (fd_set *) 0, &timeout) <= 0 ||
178486628Syar		    (s = accept(pdata, (struct sockaddr *) &from, &fromlen)) < 0)
178586628Syar			goto pdata_err;
17861592Srgrimes		(void) close(pdata);
17871592Srgrimes		pdata = s;
178886628Syar		/*
1789101809Syar		 * Unset the inherited non-blocking I/O flag
1790101809Syar		 * on the child socket so stdio can work on it.
179186628Syar		 */
179286628Syar		if ((flags = fcntl(pdata, F_GETFL, 0)) == -1 ||
179386628Syar		    fcntl(pdata, F_SETFL, flags & ~O_NONBLOCK) == -1)
179486628Syar			goto pdata_err;
17951592Srgrimes#ifdef IP_TOS
179656668Sshin		if (from.su_family == AF_INET)
179756668Sshin	      {
179817435Spst		tos = IPTOS_THROUGHPUT;
1799100612Syar		if (setsockopt(s, IPPROTO_IP, IP_TOS, &tos, sizeof(int)) < 0)
1800100609Syar			syslog(LOG_WARNING, "pdata setsockopt (IP_TOS): %m");
180156668Sshin	      }
18021592Srgrimes#endif
18031592Srgrimes		reply(150, "Opening %s mode data connection for '%s'%s.",
18041592Srgrimes		     type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf);
18051592Srgrimes		return (fdopen(pdata, mode));
180686628Syarpdata_err:
180786628Syar		reply(425, "Can't open data connection.");
180886628Syar		(void) close(pdata);
180986628Syar		pdata = -1;
181086628Syar		return (NULL);
18111592Srgrimes	}
18121592Srgrimes	if (data >= 0) {
18131592Srgrimes		reply(125, "Using existing data connection for '%s'%s.",
18141592Srgrimes		    name, sizebuf);
18151592Srgrimes		usedefault = 1;
18161592Srgrimes		return (fdopen(data, mode));
18171592Srgrimes	}
18181592Srgrimes	if (usedefault)
18191592Srgrimes		data_dest = his_addr;
18201592Srgrimes	usedefault = 1;
18211592Srgrimes	file = getdatasock(mode);
18221592Srgrimes	if (file == NULL) {
182356668Sshin		char hostbuf[BUFSIZ], portbuf[BUFSIZ];
182456668Sshin		getnameinfo((struct sockaddr *)&data_source,
182556668Sshin			data_source.su_len, hostbuf, sizeof(hostbuf) - 1,
182656668Sshin			portbuf, sizeof(portbuf),
182799255Sume			NI_NUMERICHOST|NI_NUMERICSERV);
182856668Sshin		reply(425, "Can't create data socket (%s,%s): %s.",
182956668Sshin			hostbuf, portbuf, strerror(errno));
18301592Srgrimes		return (NULL);
18311592Srgrimes	}
18321592Srgrimes	data = fileno(file);
18331592Srgrimes	while (connect(data, (struct sockaddr *)&data_dest,
183456668Sshin	    data_dest.su_len) < 0) {
18351592Srgrimes		if (errno == EADDRINUSE && retry < swaitmax) {
18361592Srgrimes			sleep((unsigned) swaitint);
18371592Srgrimes			retry += swaitint;
18381592Srgrimes			continue;
18391592Srgrimes		}
18401592Srgrimes		perror_reply(425, "Can't build data connection");
18411592Srgrimes		(void) fclose(file);
18421592Srgrimes		data = -1;
18431592Srgrimes		return (NULL);
18441592Srgrimes	}
18451592Srgrimes	reply(150, "Opening %s mode data connection for '%s'%s.",
18461592Srgrimes	     type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf);
18471592Srgrimes	return (file);
18481592Srgrimes}
18491592Srgrimes
18501592Srgrimes/*
18511592Srgrimes * Tranfer the contents of "instr" to "outstr" peer using the appropriate
18528240Swollman * encapsulation of the data subject to Mode, Structure, and Type.
18531592Srgrimes *
18541592Srgrimes * NB: Form isn't handled.
18551592Srgrimes */
185689935Syarstatic int
185790148Simpsend_data(FILE *instr, FILE *outstr, off_t blksize, off_t filesize, int isreg)
18581592Srgrimes{
185970205Sdan	int c, filefd, netfd;
186070205Sdan	char *buf;
186170205Sdan	off_t cnt;
18621592Srgrimes
18631592Srgrimes	transflag++;
18641592Srgrimes	switch (type) {
18651592Srgrimes
18661592Srgrimes	case TYPE_A:
18671592Srgrimes		while ((c = getc(instr)) != EOF) {
186889935Syar			if (recvurg)
186989935Syar				goto got_oob;
18701592Srgrimes			byte_count++;
18711592Srgrimes			if (c == '\n') {
18721592Srgrimes				if (ferror(outstr))
18731592Srgrimes					goto data_err;
18741592Srgrimes				(void) putc('\r', outstr);
18751592Srgrimes			}
18761592Srgrimes			(void) putc(c, outstr);
18771592Srgrimes		}
187889935Syar		if (recvurg)
187989935Syar			goto got_oob;
18801592Srgrimes		fflush(outstr);
18811592Srgrimes		transflag = 0;
18821592Srgrimes		if (ferror(instr))
18831592Srgrimes			goto file_err;
18841592Srgrimes		if (ferror(outstr))
18851592Srgrimes			goto data_err;
18861592Srgrimes		reply(226, "Transfer complete.");
188789935Syar		return (0);
18881592Srgrimes
18891592Srgrimes	case TYPE_I:
18901592Srgrimes	case TYPE_L:
18918240Swollman		/*
18928240Swollman		 * isreg is only set if we are not doing restart and we
18938240Swollman		 * are sending a regular file
18948240Swollman		 */
18958240Swollman		netfd = fileno(outstr);
18968870Srgrimes		filefd = fileno(instr);
18978240Swollman
189870205Sdan		if (isreg) {
189970205Sdan
190070205Sdan			off_t offset;
190170205Sdan			int err;
190270205Sdan
190370205Sdan			err = cnt = offset = 0;
190470205Sdan
190590604Smaxim			while (err != -1 && filesize > 0) {
190690604Smaxim				err = sendfile(filefd, netfd, offset, 0,
190770205Sdan					(struct sf_hdtr *) NULL, &cnt, 0);
190899212Smaxim				/*
190999212Smaxim				 * Calculate byte_count before OOB processing.
191099212Smaxim				 * It can be used in myoob() later.
191199212Smaxim				 */
191299212Smaxim				byte_count += cnt;
191389935Syar				if (recvurg)
191489935Syar					goto got_oob;
191570205Sdan				offset += cnt;
191690604Smaxim				filesize -= cnt;
19178240Swollman
191870205Sdan				if (err == -1) {
191970205Sdan					if (!cnt)
192070205Sdan						goto oldway;
192170205Sdan
192270205Sdan					goto data_err;
192370205Sdan				}
192470205Sdan			}
192570205Sdan
192699318Sdan			transflag = 0;
19278240Swollman			reply(226, "Transfer complete.");
192889935Syar			return (0);
19298240Swollman		}
19308240Swollman
19318240Swollmanoldway:
19321592Srgrimes		if ((buf = malloc((u_int)blksize)) == NULL) {
19331592Srgrimes			transflag = 0;
19341592Srgrimes			perror_reply(451, "Local resource failure: malloc");
193589935Syar			return (-1);
19361592Srgrimes		}
19378870Srgrimes
19381592Srgrimes		while ((cnt = read(filefd, buf, (u_int)blksize)) > 0 &&
19391592Srgrimes		    write(netfd, buf, cnt) == cnt)
19401592Srgrimes			byte_count += cnt;
19411592Srgrimes		transflag = 0;
19421592Srgrimes		(void)free(buf);
19431592Srgrimes		if (cnt != 0) {
19441592Srgrimes			if (cnt < 0)
19451592Srgrimes				goto file_err;
19461592Srgrimes			goto data_err;
19471592Srgrimes		}
19481592Srgrimes		reply(226, "Transfer complete.");
194989935Syar		return (0);
19501592Srgrimes	default:
19511592Srgrimes		transflag = 0;
19521592Srgrimes		reply(550, "Unimplemented TYPE %d in send_data", type);
195389935Syar		return (-1);
19541592Srgrimes	}
19551592Srgrimes
19561592Srgrimesdata_err:
19571592Srgrimes	transflag = 0;
19581592Srgrimes	perror_reply(426, "Data connection");
195989935Syar	return (-1);
19601592Srgrimes
19611592Srgrimesfile_err:
19621592Srgrimes	transflag = 0;
19631592Srgrimes	perror_reply(551, "Error on input file");
196489935Syar	return (-1);
196589935Syar
196689935Syargot_oob:
196789935Syar	myoob();
196889935Syar	recvurg = 0;
196989935Syar	transflag = 0;
197089935Syar	return (-1);
19711592Srgrimes}
19721592Srgrimes
19731592Srgrimes/*
19741592Srgrimes * Transfer data from peer to "outstr" using the appropriate encapulation of
19751592Srgrimes * the data subject to Mode, Structure, and Type.
19761592Srgrimes *
19771592Srgrimes * N.B.: Form isn't handled.
19781592Srgrimes */
19791592Srgrimesstatic int
198090148Simpreceive_data(FILE *instr, FILE *outstr)
19811592Srgrimes{
19821592Srgrimes	int c;
198317433Spst	int cnt, bare_lfs;
19841592Srgrimes	char buf[BUFSIZ];
19851592Srgrimes
19861592Srgrimes	transflag++;
198717433Spst	bare_lfs = 0;
198817433Spst
19891592Srgrimes	switch (type) {
19901592Srgrimes
19911592Srgrimes	case TYPE_I:
19921592Srgrimes	case TYPE_L:
19931592Srgrimes		while ((cnt = read(fileno(instr), buf, sizeof(buf))) > 0) {
199489935Syar			if (recvurg)
199589935Syar				goto got_oob;
19961592Srgrimes			if (write(fileno(outstr), buf, cnt) != cnt)
19971592Srgrimes				goto file_err;
19981592Srgrimes			byte_count += cnt;
19991592Srgrimes		}
200089935Syar		if (recvurg)
200189935Syar			goto got_oob;
20021592Srgrimes		if (cnt < 0)
20031592Srgrimes			goto data_err;
20041592Srgrimes		transflag = 0;
20051592Srgrimes		return (0);
20061592Srgrimes
20071592Srgrimes	case TYPE_E:
20081592Srgrimes		reply(553, "TYPE E not implemented.");
20091592Srgrimes		transflag = 0;
20101592Srgrimes		return (-1);
20111592Srgrimes
20121592Srgrimes	case TYPE_A:
20131592Srgrimes		while ((c = getc(instr)) != EOF) {
201489935Syar			if (recvurg)
201589935Syar				goto got_oob;
20161592Srgrimes			byte_count++;
20171592Srgrimes			if (c == '\n')
20181592Srgrimes				bare_lfs++;
20191592Srgrimes			while (c == '\r') {
20201592Srgrimes				if (ferror(outstr))
20211592Srgrimes					goto data_err;
20221592Srgrimes				if ((c = getc(instr)) != '\n') {
20231592Srgrimes					(void) putc ('\r', outstr);
20241592Srgrimes					if (c == '\0' || c == EOF)
20251592Srgrimes						goto contin2;
20261592Srgrimes				}
20271592Srgrimes			}
20281592Srgrimes			(void) putc(c, outstr);
20291592Srgrimes	contin2:	;
20301592Srgrimes		}
203189935Syar		if (recvurg)
203289935Syar			goto got_oob;
20331592Srgrimes		fflush(outstr);
20341592Srgrimes		if (ferror(instr))
20351592Srgrimes			goto data_err;
20361592Srgrimes		if (ferror(outstr))
20371592Srgrimes			goto file_err;
20381592Srgrimes		transflag = 0;
20391592Srgrimes		if (bare_lfs) {
20401592Srgrimes			lreply(226,
20411592Srgrimes		"WARNING! %d bare linefeeds received in ASCII mode",
20421592Srgrimes			    bare_lfs);
20431592Srgrimes		(void)printf("   File may not have transferred correctly.\r\n");
20441592Srgrimes		}
20451592Srgrimes		return (0);
20461592Srgrimes	default:
20471592Srgrimes		reply(550, "Unimplemented TYPE %d in receive_data", type);
20481592Srgrimes		transflag = 0;
20491592Srgrimes		return (-1);
20501592Srgrimes	}
20511592Srgrimes
20521592Srgrimesdata_err:
20531592Srgrimes	transflag = 0;
20541592Srgrimes	perror_reply(426, "Data Connection");
20551592Srgrimes	return (-1);
20561592Srgrimes
20571592Srgrimesfile_err:
20581592Srgrimes	transflag = 0;
20591592Srgrimes	perror_reply(452, "Error writing file");
20601592Srgrimes	return (-1);
206189935Syar
206289935Syargot_oob:
206389935Syar	myoob();
206489935Syar	recvurg = 0;
206589935Syar	transflag = 0;
206689935Syar	return (-1);
20671592Srgrimes}
20681592Srgrimes
20691592Srgrimesvoid
207090148Simpstatfilecmd(char *filename)
20711592Srgrimes{
20721592Srgrimes	FILE *fin;
20731592Srgrimes	int c;
20741592Srgrimes	char line[LINE_MAX];
20751592Srgrimes
207625165Sdavidn	(void)snprintf(line, sizeof(line), _PATH_LS " -lgA %s", filename);
20771592Srgrimes	fin = ftpd_popen(line, "r");
20781592Srgrimes	lreply(211, "status of %s:", filename);
20791592Srgrimes	while ((c = getc(fin)) != EOF) {
20801592Srgrimes		if (c == '\n') {
20811592Srgrimes			if (ferror(stdout)){
20821592Srgrimes				perror_reply(421, "control connection");
20831592Srgrimes				(void) ftpd_pclose(fin);
20841592Srgrimes				dologout(1);
20851592Srgrimes				/* NOTREACHED */
20861592Srgrimes			}
20871592Srgrimes			if (ferror(fin)) {
20881592Srgrimes				perror_reply(551, filename);
20891592Srgrimes				(void) ftpd_pclose(fin);
20901592Srgrimes				return;
20911592Srgrimes			}
20921592Srgrimes			(void) putc('\r', stdout);
20931592Srgrimes		}
20941592Srgrimes		(void) putc(c, stdout);
20951592Srgrimes	}
20961592Srgrimes	(void) ftpd_pclose(fin);
20971592Srgrimes	reply(211, "End of Status");
20981592Srgrimes}
20991592Srgrimes
21001592Srgrimesvoid
210190148Simpstatcmd(void)
21021592Srgrimes{
210356668Sshin	union sockunion *su;
21041592Srgrimes	u_char *a, *p;
210599255Sume	char hname[NI_MAXHOST];
210656668Sshin	int ispassive;
21071592Srgrimes
21081592Srgrimes	lreply(211, "%s FTP server status:", hostname, version);
21091592Srgrimes	printf("     %s\r\n", version);
21101592Srgrimes	printf("     Connected to %s", remotehost);
211156668Sshin	if (!getnameinfo((struct sockaddr *)&his_addr, his_addr.su_len,
211299255Sume			 hname, sizeof(hname) - 1, NULL, 0, NI_NUMERICHOST)) {
211356668Sshin		if (strcmp(hname, remotehost) != 0)
211456668Sshin			printf(" (%s)", hname);
211556668Sshin	}
21161592Srgrimes	printf("\r\n");
21171592Srgrimes	if (logged_in) {
21181592Srgrimes		if (guest)
21191592Srgrimes			printf("     Logged in anonymously\r\n");
21201592Srgrimes		else
21211592Srgrimes			printf("     Logged in as %s\r\n", pw->pw_name);
21221592Srgrimes	} else if (askpasswd)
21231592Srgrimes		printf("     Waiting for password\r\n");
21241592Srgrimes	else
21251592Srgrimes		printf("     Waiting for user name\r\n");
21261592Srgrimes	printf("     TYPE: %s", typenames[type]);
21271592Srgrimes	if (type == TYPE_A || type == TYPE_E)
21281592Srgrimes		printf(", FORM: %s", formnames[form]);
21291592Srgrimes	if (type == TYPE_L)
21301592Srgrimes#if NBBY == 8
21311592Srgrimes		printf(" %d", NBBY);
21321592Srgrimes#else
21331592Srgrimes		printf(" %d", bytesize);	/* need definition! */
21341592Srgrimes#endif
21351592Srgrimes	printf("; STRUcture: %s; transfer MODE: %s\r\n",
21361592Srgrimes	    strunames[stru], modenames[mode]);
21371592Srgrimes	if (data != -1)
21381592Srgrimes		printf("     Data connection open\r\n");
21391592Srgrimes	else if (pdata != -1) {
214056668Sshin		ispassive = 1;
214156668Sshin		su = &pasv_addr;
21421592Srgrimes		goto printaddr;
21431592Srgrimes	} else if (usedefault == 0) {
214456668Sshin		ispassive = 0;
214556668Sshin		su = &data_dest;
21461592Srgrimesprintaddr:
21471592Srgrimes#define UC(b) (((int) b) & 0xff)
214856668Sshin		if (epsvall) {
214956668Sshin			printf("     EPSV only mode (EPSV ALL)\r\n");
215056668Sshin			goto epsvonly;
215156668Sshin		}
215256668Sshin
215356668Sshin		/* PORT/PASV */
215456668Sshin		if (su->su_family == AF_INET) {
215556668Sshin			a = (u_char *) &su->su_sin.sin_addr;
215656668Sshin			p = (u_char *) &su->su_sin.sin_port;
215756668Sshin			printf("     %s (%d,%d,%d,%d,%d,%d)\r\n",
215856668Sshin				ispassive ? "PASV" : "PORT",
215956668Sshin				UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]),
216056668Sshin				UC(p[0]), UC(p[1]));
216156668Sshin		}
216256668Sshin
216356668Sshin		/* LPRT/LPSV */
216456668Sshin	    {
216556668Sshin		int alen, af, i;
216656668Sshin
216756668Sshin		switch (su->su_family) {
216856668Sshin		case AF_INET:
216956668Sshin			a = (u_char *) &su->su_sin.sin_addr;
217056668Sshin			p = (u_char *) &su->su_sin.sin_port;
217156668Sshin			alen = sizeof(su->su_sin.sin_addr);
217256668Sshin			af = 4;
217356668Sshin			break;
217456668Sshin		case AF_INET6:
217556668Sshin			a = (u_char *) &su->su_sin6.sin6_addr;
217656668Sshin			p = (u_char *) &su->su_sin6.sin6_port;
217756668Sshin			alen = sizeof(su->su_sin6.sin6_addr);
217856668Sshin			af = 6;
217956668Sshin			break;
218056668Sshin		default:
218156668Sshin			af = 0;
218256668Sshin			break;
218356668Sshin		}
218456668Sshin		if (af) {
218556668Sshin			printf("     %s (%d,%d,", ispassive ? "LPSV" : "LPRT",
218656668Sshin				af, alen);
218756668Sshin			for (i = 0; i < alen; i++)
218856668Sshin				printf("%d,", UC(a[i]));
218956668Sshin			printf("%d,%d,%d)\r\n", 2, UC(p[0]), UC(p[1]));
219056668Sshin		}
219156668Sshin	    }
219256668Sshin
219356668Sshinepsvonly:;
219456668Sshin		/* EPRT/EPSV */
219556668Sshin	    {
219656668Sshin		int af;
219756668Sshin
219856668Sshin		switch (su->su_family) {
219956668Sshin		case AF_INET:
220056668Sshin			af = 1;
220156668Sshin			break;
220256668Sshin		case AF_INET6:
220356668Sshin			af = 2;
220456668Sshin			break;
220556668Sshin		default:
220656668Sshin			af = 0;
220756668Sshin			break;
220856668Sshin		}
220956668Sshin		if (af) {
221099255Sume			union sockunion tmp;
221199255Sume
221299255Sume			tmp = *su;
221399255Sume			if (tmp.su_family == AF_INET6)
221499255Sume				tmp.su_sin6.sin6_scope_id = 0;
221599255Sume			if (!getnameinfo((struct sockaddr *)&tmp, tmp.su_len,
221656668Sshin					hname, sizeof(hname) - 1, NULL, 0,
221756668Sshin					NI_NUMERICHOST)) {
221856668Sshin				printf("     %s |%d|%s|%d|\r\n",
221956668Sshin					ispassive ? "EPSV" : "EPRT",
222099255Sume					af, hname, htons(tmp.su_port));
222156668Sshin			}
222256668Sshin		}
222356668Sshin	    }
22241592Srgrimes#undef UC
22251592Srgrimes	} else
22261592Srgrimes		printf("     No data connection\r\n");
22271592Srgrimes	reply(211, "End of status");
22281592Srgrimes}
22291592Srgrimes
22301592Srgrimesvoid
223190148Simpfatalerror(char *s)
22321592Srgrimes{
22331592Srgrimes
22341592Srgrimes	reply(451, "Error in server: %s\n", s);
22351592Srgrimes	reply(221, "Closing connection due to server error.");
22361592Srgrimes	dologout(0);
22371592Srgrimes	/* NOTREACHED */
22381592Srgrimes}
22391592Srgrimes
22401592Srgrimesvoid
22411592Srgrimesreply(int n, const char *fmt, ...)
22421592Srgrimes{
22431592Srgrimes	va_list ap;
224490148Simp
22451592Srgrimes	va_start(ap, fmt);
22461592Srgrimes	(void)printf("%d ", n);
22471592Srgrimes	(void)vprintf(fmt, ap);
22481592Srgrimes	(void)printf("\r\n");
22491592Srgrimes	(void)fflush(stdout);
225076096Smarkm	if (ftpdebug) {
22511592Srgrimes		syslog(LOG_DEBUG, "<--- %d ", n);
22521592Srgrimes		vsyslog(LOG_DEBUG, fmt, ap);
22531592Srgrimes	}
22541592Srgrimes}
22551592Srgrimes
22561592Srgrimesvoid
22571592Srgrimeslreply(int n, const char *fmt, ...)
22581592Srgrimes{
22591592Srgrimes	va_list ap;
226090148Simp
22611592Srgrimes	va_start(ap, fmt);
22621592Srgrimes	(void)printf("%d- ", n);
22631592Srgrimes	(void)vprintf(fmt, ap);
22641592Srgrimes	(void)printf("\r\n");
22651592Srgrimes	(void)fflush(stdout);
226676096Smarkm	if (ftpdebug) {
22671592Srgrimes		syslog(LOG_DEBUG, "<--- %d- ", n);
22681592Srgrimes		vsyslog(LOG_DEBUG, fmt, ap);
22691592Srgrimes	}
22701592Srgrimes}
22711592Srgrimes
22721592Srgrimesstatic void
227390148Simpack(char *s)
22741592Srgrimes{
22751592Srgrimes
22761592Srgrimes	reply(250, "%s command successful.", s);
22771592Srgrimes}
22781592Srgrimes
22791592Srgrimesvoid
228090148Simpnack(char *s)
22811592Srgrimes{
22821592Srgrimes
22831592Srgrimes	reply(502, "%s command not implemented.", s);
22841592Srgrimes}
22851592Srgrimes
22861592Srgrimes/* ARGSUSED */
22871592Srgrimesvoid
228890148Simpyyerror(char *s)
22891592Srgrimes{
22901592Srgrimes	char *cp;
22911592Srgrimes
229217478Smarkm	if ((cp = strchr(cbuf,'\n')))
22931592Srgrimes		*cp = '\0';
22941592Srgrimes	reply(500, "'%s': command not understood.", cbuf);
22951592Srgrimes}
22961592Srgrimes
22971592Srgrimesvoid
229890148Simpdelete(char *name)
22991592Srgrimes{
23001592Srgrimes	struct stat st;
23011592Srgrimes
23021592Srgrimes	LOGCMD("delete", name);
2303100439Syar	if (lstat(name, &st) < 0) {
23041592Srgrimes		perror_reply(550, name);
23051592Srgrimes		return;
23061592Srgrimes	}
23071592Srgrimes	if ((st.st_mode&S_IFMT) == S_IFDIR) {
23081592Srgrimes		if (rmdir(name) < 0) {
23091592Srgrimes			perror_reply(550, name);
23101592Srgrimes			return;
23111592Srgrimes		}
23121592Srgrimes		goto done;
23131592Srgrimes	}
23141592Srgrimes	if (unlink(name) < 0) {
23151592Srgrimes		perror_reply(550, name);
23161592Srgrimes		return;
23171592Srgrimes	}
23181592Srgrimesdone:
23191592Srgrimes	ack("DELE");
23201592Srgrimes}
23211592Srgrimes
23221592Srgrimesvoid
232390148Simpcwd(char *path)
23241592Srgrimes{
23251592Srgrimes
23261592Srgrimes	if (chdir(path) < 0)
23271592Srgrimes		perror_reply(550, path);
23281592Srgrimes	else
23291592Srgrimes		ack("CWD");
23301592Srgrimes}
23311592Srgrimes
23321592Srgrimesvoid
233390148Simpmakedir(char *name)
23341592Srgrimes{
2335100878Syar	char *s;
23361592Srgrimes
23371592Srgrimes	LOGCMD("mkdir", name);
233899195Smdodd	if (guest && noguestmkd)
233999195Smdodd		reply(550, "%s: permission denied", name);
234099195Smdodd	else if (mkdir(name, 0777) < 0)
23411592Srgrimes		perror_reply(550, name);
2342100878Syar	else {
2343100878Syar		if ((s = doublequote(name)) == NULL)
2344100878Syar			fatalerror("Ran out of memory.");
2345100878Syar		reply(257, "\"%s\" directory created.", s);
2346100878Syar		free(s);
2347100878Syar	}
23481592Srgrimes}
23491592Srgrimes
23501592Srgrimesvoid
235190148Simpremovedir(char *name)
23521592Srgrimes{
23531592Srgrimes
23541592Srgrimes	LOGCMD("rmdir", name);
23551592Srgrimes	if (rmdir(name) < 0)
23561592Srgrimes		perror_reply(550, name);
23571592Srgrimes	else
23581592Srgrimes		ack("RMD");
23591592Srgrimes}
23601592Srgrimes
23611592Srgrimesvoid
236290148Simppwd(void)
23631592Srgrimes{
2364100486Syar	char *s, path[MAXPATHLEN + 1];
23651592Srgrimes
23661592Srgrimes	if (getwd(path) == (char *)NULL)
23671592Srgrimes		reply(550, "%s.", path);
2368100486Syar	else {
2369100486Syar		if ((s = doublequote(path)) == NULL)
2370100486Syar			fatalerror("Ran out of memory.");
2371100486Syar		reply(257, "\"%s\" is current directory.", s);
2372100486Syar		free(s);
2373100486Syar	}
23741592Srgrimes}
23751592Srgrimes
23761592Srgrimeschar *
237790148Simprenamefrom(char *name)
23781592Srgrimes{
23791592Srgrimes	struct stat st;
23801592Srgrimes
2381100439Syar	if (lstat(name, &st) < 0) {
23821592Srgrimes		perror_reply(550, name);
23831592Srgrimes		return ((char *)0);
23841592Srgrimes	}
23851592Srgrimes	reply(350, "File exists, ready for destination name");
23861592Srgrimes	return (name);
23871592Srgrimes}
23881592Srgrimes
23891592Srgrimesvoid
239090148Simprenamecmd(char *from, char *to)
23911592Srgrimes{
239217433Spst	struct stat st;
23931592Srgrimes
23941592Srgrimes	LOGCMD2("rename", from, to);
239517433Spst
239617433Spst	if (guest && (stat(to, &st) == 0)) {
239717433Spst		reply(550, "%s: permission denied", to);
239817433Spst		return;
239917433Spst	}
240017433Spst
24011592Srgrimes	if (rename(from, to) < 0)
24021592Srgrimes		perror_reply(550, "rename");
24031592Srgrimes	else
24041592Srgrimes		ack("RNTO");
24051592Srgrimes}
24061592Srgrimes
24071592Srgrimesstatic void
240890148Simpdolog(struct sockaddr *who)
24091592Srgrimes{
241056668Sshin	int error;
24111592Srgrimes
241256668Sshin	realhostname_sa(remotehost, sizeof(remotehost) - 1, who, who->sa_len);
241356668Sshin
24141592Srgrimes#ifdef SETPROCTITLE
241525283Sdavidn#ifdef VIRTUAL_HOSTING
241625283Sdavidn	if (thishost != firsthost)
241725283Sdavidn		snprintf(proctitle, sizeof(proctitle), "%s: connected (to %s)",
241825283Sdavidn			 remotehost, hostname);
241925283Sdavidn	else
242025283Sdavidn#endif
242125283Sdavidn		snprintf(proctitle, sizeof(proctitle), "%s: connected",
242225283Sdavidn			 remotehost);
242313139Speter	setproctitle("%s", proctitle);
24241592Srgrimes#endif /* SETPROCTITLE */
24251592Srgrimes
242625283Sdavidn	if (logging) {
242725283Sdavidn#ifdef VIRTUAL_HOSTING
242825283Sdavidn		if (thishost != firsthost)
242925283Sdavidn			syslog(LOG_INFO, "connection from %s (to %s)",
243025283Sdavidn			       remotehost, hostname);
243125283Sdavidn		else
243225283Sdavidn#endif
243356668Sshin		{
243456668Sshin			char	who_name[MAXHOSTNAMELEN];
243556668Sshin
243656668Sshin			error = getnameinfo(who, who->sa_len,
243756668Sshin					    who_name, sizeof(who_name) - 1,
243899255Sume					    NULL, 0, NI_NUMERICHOST);
243933782Seivind			syslog(LOG_INFO, "connection from %s (%s)", remotehost,
244056668Sshin			       error == 0 ? who_name : "");
244156668Sshin		}
244225283Sdavidn	}
24431592Srgrimes}
24441592Srgrimes
24451592Srgrimes/*
24461592Srgrimes * Record logout in wtmp file
24471592Srgrimes * and exit with supplied status.
24481592Srgrimes */
24491592Srgrimesvoid
245090148Simpdologout(int status)
24511592Srgrimes{
245222057Sdg	/*
245322057Sdg	 * Prevent reception of SIGURG from resulting in a resumption
245422057Sdg	 * back to the main program loop.
245522058Sdg	 */
245622057Sdg	transflag = 0;
24571592Srgrimes
24581592Srgrimes	if (logged_in) {
24591592Srgrimes		(void) seteuid((uid_t)0);
246089920Sume		ftpd_logwtmp(ttyline, "", NULL);
24611592Srgrimes	}
24621592Srgrimes	/* beware of flushing buffers after a SIGPIPE */
24631592Srgrimes	_exit(status);
24641592Srgrimes}
24651592Srgrimes
24661592Srgrimesstatic void
246790148Simpsigurg(int signo)
24681592Srgrimes{
246989935Syar
247089935Syar	recvurg = 1;
247189935Syar}
247289935Syar
247389935Syarstatic void
247490148Simpmyoob(void)
247589935Syar{
24761592Srgrimes	char *cp;
24771592Srgrimes
24781592Srgrimes	/* only process if transfer occurring */
24791592Srgrimes	if (!transflag)
24801592Srgrimes		return;
24811592Srgrimes	cp = tmpline;
24821592Srgrimes	if (getline(cp, 7, stdin) == NULL) {
24831592Srgrimes		reply(221, "You could at least say goodbye.");
24841592Srgrimes		dologout(0);
24851592Srgrimes	}
24861592Srgrimes	upper(cp);
24871592Srgrimes	if (strcmp(cp, "ABOR\r\n") == 0) {
24881592Srgrimes		tmpline[0] = '\0';
24891592Srgrimes		reply(426, "Transfer aborted. Data connection closed.");
24901592Srgrimes		reply(226, "Abort successful");
24911592Srgrimes	}
24921592Srgrimes	if (strcmp(cp, "STAT\r\n") == 0) {
249351192Smharo		tmpline[0] = '\0';
24941592Srgrimes		if (file_size != (off_t) -1)
24951592Srgrimes			reply(213, "Status: %qd of %qd bytes transferred",
24961592Srgrimes			    byte_count, file_size);
24971592Srgrimes		else
24981592Srgrimes			reply(213, "Status: %qd bytes transferred", byte_count);
24991592Srgrimes	}
25001592Srgrimes}
25011592Srgrimes
25021592Srgrimes/*
25031592Srgrimes * Note: a response of 425 is not mentioned as a possible response to
25041592Srgrimes *	the PASV command in RFC959. However, it has been blessed as
25051592Srgrimes *	a legitimate response by Jon Postel in a telephone conversation
25061592Srgrimes *	with Rick Adams on 25 Jan 89.
25071592Srgrimes */
25081592Srgrimesvoid
250990148Simppassive(void)
25101592Srgrimes{
2511100615Syar	int len, on;
25121592Srgrimes	char *p, *a;
25131592Srgrimes
251417433Spst	if (pdata >= 0)		/* close old port if one set */
251517433Spst		close(pdata);
251617433Spst
251756668Sshin	pdata = socket(ctrl_addr.su_family, SOCK_STREAM, 0);
25181592Srgrimes	if (pdata < 0) {
25191592Srgrimes		perror_reply(425, "Can't open passive connection");
25201592Srgrimes		return;
25211592Srgrimes	}
2522100615Syar	on = 1;
2523100615Syar	if (setsockopt(pdata, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
2524100615Syar		syslog(LOG_WARNING, "pdata setsockopt (SO_REUSEADDR): %m");
25259933Spst
252617433Spst	(void) seteuid((uid_t)0);
252717433Spst
252819903Spst#ifdef IP_PORTRANGE
252956668Sshin	if (ctrl_addr.su_family == AF_INET) {
2530100615Syar	    on = restricted_data_ports ? IP_PORTRANGE_HIGH
2531100615Syar				       : IP_PORTRANGE_DEFAULT;
253219903Spst
253319903Spst	    if (setsockopt(pdata, IPPROTO_IP, IP_PORTRANGE,
2534100612Syar			    &on, sizeof(on)) < 0)
253519903Spst		    goto pasv_error;
25361592Srgrimes	}
253719903Spst#endif
253860929Snsayer#ifdef IPV6_PORTRANGE
253960929Snsayer	if (ctrl_addr.su_family == AF_INET6) {
2540100615Syar	    on = restricted_data_ports ? IPV6_PORTRANGE_HIGH
2541100615Syar				       : IPV6_PORTRANGE_DEFAULT;
25429933Spst
254360929Snsayer	    if (setsockopt(pdata, IPPROTO_IPV6, IPV6_PORTRANGE,
2544100612Syar			    &on, sizeof(on)) < 0)
254560929Snsayer		    goto pasv_error;
254660929Snsayer	}
254760929Snsayer#endif
254860929Snsayer
254916033Speter	pasv_addr = ctrl_addr;
255056668Sshin	pasv_addr.su_port = 0;
255156668Sshin	if (bind(pdata, (struct sockaddr *)&pasv_addr, pasv_addr.su_len) < 0)
255216033Speter		goto pasv_error;
255317433Spst
255416033Speter	(void) seteuid((uid_t)pw->pw_uid);
255516033Speter
25561592Srgrimes	len = sizeof(pasv_addr);
25571592Srgrimes	if (getsockname(pdata, (struct sockaddr *) &pasv_addr, &len) < 0)
25581592Srgrimes		goto pasv_error;
25591592Srgrimes	if (listen(pdata, 1) < 0)
25601592Srgrimes		goto pasv_error;
256156668Sshin	if (pasv_addr.su_family == AF_INET)
256256668Sshin		a = (char *) &pasv_addr.su_sin.sin_addr;
256356668Sshin	else if (pasv_addr.su_family == AF_INET6 &&
256456668Sshin		 IN6_IS_ADDR_V4MAPPED(&pasv_addr.su_sin6.sin6_addr))
256556668Sshin		a = (char *) &pasv_addr.su_sin6.sin6_addr.s6_addr[12];
256656668Sshin	else
256756668Sshin		goto pasv_error;
256856668Sshin
256956668Sshin	p = (char *) &pasv_addr.su_port;
25701592Srgrimes
25711592Srgrimes#define UC(b) (((int) b) & 0xff)
25721592Srgrimes
25731592Srgrimes	reply(227, "Entering Passive Mode (%d,%d,%d,%d,%d,%d)", UC(a[0]),
25741592Srgrimes		UC(a[1]), UC(a[2]), UC(a[3]), UC(p[0]), UC(p[1]));
25751592Srgrimes	return;
25761592Srgrimes
25771592Srgrimespasv_error:
257817433Spst	(void) seteuid((uid_t)pw->pw_uid);
25791592Srgrimes	(void) close(pdata);
25801592Srgrimes	pdata = -1;
25811592Srgrimes	perror_reply(425, "Can't open passive connection");
25821592Srgrimes	return;
25831592Srgrimes}
25841592Srgrimes
25851592Srgrimes/*
258656668Sshin * Long Passive defined in RFC 1639.
258756668Sshin *     228 Entering Long Passive Mode
258856668Sshin *         (af, hal, h1, h2, h3,..., pal, p1, p2...)
258956668Sshin */
259056668Sshin
259156668Sshinvoid
259290148Simplong_passive(char *cmd, int pf)
259356668Sshin{
2594100615Syar	int len, on;
259556668Sshin	char *p, *a;
259656668Sshin
259756668Sshin	if (pdata >= 0)		/* close old port if one set */
259856668Sshin		close(pdata);
259956668Sshin
260056668Sshin	if (pf != PF_UNSPEC) {
260156668Sshin		if (ctrl_addr.su_family != pf) {
260256668Sshin			switch (ctrl_addr.su_family) {
260356668Sshin			case AF_INET:
260456668Sshin				pf = 1;
260556668Sshin				break;
260656668Sshin			case AF_INET6:
260756668Sshin				pf = 2;
260856668Sshin				break;
260956668Sshin			default:
261056668Sshin				pf = 0;
261156668Sshin				break;
261256668Sshin			}
261356668Sshin			/*
261456668Sshin			 * XXX
261556668Sshin			 * only EPRT/EPSV ready clients will understand this
261656668Sshin			 */
261756668Sshin			if (strcmp(cmd, "EPSV") == 0 && pf) {
261856668Sshin				reply(522, "Network protocol mismatch, "
261956668Sshin					"use (%d)", pf);
262056668Sshin			} else
262156668Sshin				reply(501, "Network protocol mismatch"); /*XXX*/
262256668Sshin
262356668Sshin			return;
262456668Sshin		}
262556668Sshin	}
262656668Sshin
262756668Sshin	pdata = socket(ctrl_addr.su_family, SOCK_STREAM, 0);
262856668Sshin	if (pdata < 0) {
262956668Sshin		perror_reply(425, "Can't open passive connection");
263056668Sshin		return;
263156668Sshin	}
2632100615Syar	on = 1;
2633100615Syar	if (setsockopt(pdata, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
2634100615Syar		syslog(LOG_WARNING, "pdata setsockopt (SO_REUSEADDR): %m");
263556668Sshin
263656668Sshin	(void) seteuid((uid_t)0);
263756668Sshin
263856668Sshin	pasv_addr = ctrl_addr;
263956668Sshin	pasv_addr.su_port = 0;
264056668Sshin	len = pasv_addr.su_len;
264156668Sshin
264260929Snsayer#ifdef IP_PORTRANGE
264360929Snsayer	if (ctrl_addr.su_family == AF_INET) {
2644100615Syar	    on = restricted_data_ports ? IP_PORTRANGE_HIGH
2645100615Syar				       : IP_PORTRANGE_DEFAULT;
264660929Snsayer
264760929Snsayer	    if (setsockopt(pdata, IPPROTO_IP, IP_PORTRANGE,
2648100612Syar			    &on, sizeof(on)) < 0)
264960929Snsayer		    goto pasv_error;
265060929Snsayer	}
265160929Snsayer#endif
265260929Snsayer#ifdef IPV6_PORTRANGE
265360929Snsayer	if (ctrl_addr.su_family == AF_INET6) {
2654100615Syar	    on = restricted_data_ports ? IPV6_PORTRANGE_HIGH
2655100615Syar				       : IPV6_PORTRANGE_DEFAULT;
265660929Snsayer
265760929Snsayer	    if (setsockopt(pdata, IPPROTO_IPV6, IPV6_PORTRANGE,
2658100612Syar			    &on, sizeof(on)) < 0)
265960929Snsayer		    goto pasv_error;
266060929Snsayer	}
266160929Snsayer#endif
266260929Snsayer
266356668Sshin	if (bind(pdata, (struct sockaddr *)&pasv_addr, len) < 0)
266456668Sshin		goto pasv_error;
266556668Sshin
266656668Sshin	(void) seteuid((uid_t)pw->pw_uid);
266756668Sshin
266856668Sshin	if (getsockname(pdata, (struct sockaddr *) &pasv_addr, &len) < 0)
266956668Sshin		goto pasv_error;
267056668Sshin	if (listen(pdata, 1) < 0)
267156668Sshin		goto pasv_error;
267256668Sshin
267356668Sshin#define UC(b) (((int) b) & 0xff)
267456668Sshin
267556668Sshin	if (strcmp(cmd, "LPSV") == 0) {
267656668Sshin		p = (char *)&pasv_addr.su_port;
267756668Sshin		switch (pasv_addr.su_family) {
267856668Sshin		case AF_INET:
267956668Sshin			a = (char *) &pasv_addr.su_sin.sin_addr;
268056668Sshin		v4_reply:
268156668Sshin			reply(228,
268256668Sshin"Entering Long Passive Mode (%d,%d,%d,%d,%d,%d,%d,%d,%d)",
268356668Sshin			      4, 4, UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]),
268456668Sshin			      2, UC(p[0]), UC(p[1]));
268556668Sshin			return;
268656668Sshin		case AF_INET6:
268756668Sshin			if (IN6_IS_ADDR_V4MAPPED(&pasv_addr.su_sin6.sin6_addr)) {
268856668Sshin				a = (char *) &pasv_addr.su_sin6.sin6_addr.s6_addr[12];
268956668Sshin				goto v4_reply;
269056668Sshin			}
269156668Sshin			a = (char *) &pasv_addr.su_sin6.sin6_addr;
269256668Sshin			reply(228,
269356668Sshin"Entering Long Passive Mode "
269456668Sshin"(%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d)",
269556668Sshin			      6, 16, UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]),
269656668Sshin			      UC(a[4]), UC(a[5]), UC(a[6]), UC(a[7]),
269756668Sshin			      UC(a[8]), UC(a[9]), UC(a[10]), UC(a[11]),
269856668Sshin			      UC(a[12]), UC(a[13]), UC(a[14]), UC(a[15]),
269956668Sshin			      2, UC(p[0]), UC(p[1]));
270056668Sshin			return;
270156668Sshin		}
270256668Sshin	} else if (strcmp(cmd, "EPSV") == 0) {
270356668Sshin		switch (pasv_addr.su_family) {
270456668Sshin		case AF_INET:
270556668Sshin		case AF_INET6:
270656668Sshin			reply(229, "Entering Extended Passive Mode (|||%d|)",
270756668Sshin				ntohs(pasv_addr.su_port));
270856668Sshin			return;
270956668Sshin		}
271056668Sshin	} else {
271156668Sshin		/* more proper error code? */
271256668Sshin	}
271356668Sshin
271456668Sshinpasv_error:
271556668Sshin	(void) seteuid((uid_t)pw->pw_uid);
271656668Sshin	(void) close(pdata);
271756668Sshin	pdata = -1;
271856668Sshin	perror_reply(425, "Can't open passive connection");
271956668Sshin	return;
272056668Sshin}
272156668Sshin
272256668Sshin/*
2723101537Syar * Generate unique name for file with basename "local"
2724101537Syar * and open the file in order to avoid possible races.
2725101537Syar * Try "local" first, then "local.1", "local.2" etc, up to "local.99".
2726101537Syar * Return descriptor to the file, set "name" to its name.
2727101537Syar *
27281592Srgrimes * Generates failure reply on error.
27291592Srgrimes */
2730101537Syarstatic int
2731101537Syarguniquefd(char *local, char **name)
27321592Srgrimes{
27331592Srgrimes	static char new[MAXPATHLEN];
27341592Srgrimes	struct stat st;
2735101537Syar	char *cp;
27361592Srgrimes	int count;
2737101537Syar	int fd;
27381592Srgrimes
27391592Srgrimes	cp = strrchr(local, '/');
27401592Srgrimes	if (cp)
27411592Srgrimes		*cp = '\0';
27421592Srgrimes	if (stat(cp ? local : ".", &st) < 0) {
27431592Srgrimes		perror_reply(553, cp ? local : ".");
2744101537Syar		return (-1);
27451592Srgrimes	}
2746101537Syar	if (cp) {
2747101537Syar		/*
2748101537Syar		 * Let not overwrite dirname with counter suffix.
2749101537Syar		 * -4 is for /nn\0
2750101537Syar		 * In this extreme case dot won't be put in front of suffix.
2751101537Syar		 */
2752101537Syar		if (strlen(local) > sizeof(new) - 4) {
2753101537Syar			reply(553, "Pathname too long");
2754101537Syar			return (-1);
2755101537Syar		}
27561592Srgrimes		*cp = '/';
2757101537Syar	}
275831973Simp	/* -4 is for the .nn<null> we put on the end below */
275931973Simp	(void) snprintf(new, sizeof(new) - 4, "%s", local);
27601592Srgrimes	cp = new + strlen(new);
2761101537Syar	/*
2762101537Syar	 * Don't generate dotfile unless requested explicitly.
2763101537Syar	 * This covers the case when basename gets truncated off
2764101537Syar	 * by buffer size.
2765101537Syar	 */
2766101537Syar	if (cp > new && cp[-1] != '/')
2767101537Syar		*cp++ = '.';
2768101537Syar	for (count = 0; count < 100; count++) {
2769101537Syar		/* At count 0 try unmodified name */
2770101537Syar		if (count)
2771101537Syar			(void)sprintf(cp, "%d", count);
2772101537Syar		if ((fd = open(count ? new : local,
2773101537Syar		    O_RDWR | O_CREAT | O_EXCL, 0666)) >= 0) {
2774101537Syar			*name = count ? new : local;
2775101537Syar			return (fd);
2776101537Syar		}
27771592Srgrimes	}
27781592Srgrimes	reply(452, "Unique file name cannot be created.");
2779101537Syar	return (-1);
27801592Srgrimes}
27811592Srgrimes
27821592Srgrimes/*
27831592Srgrimes * Format and send reply containing system error number.
27841592Srgrimes */
27851592Srgrimesvoid
278690148Simpperror_reply(int code, char *string)
27871592Srgrimes{
27881592Srgrimes
27891592Srgrimes	reply(code, "%s: %s.", string, strerror(errno));
27901592Srgrimes}
27911592Srgrimes
27921592Srgrimesstatic char *onefile[] = {
27931592Srgrimes	"",
27941592Srgrimes	0
27951592Srgrimes};
27961592Srgrimes
27971592Srgrimesvoid
279890148Simpsend_file_list(char *whichf)
27991592Srgrimes{
28001592Srgrimes	struct stat st;
28011592Srgrimes	DIR *dirp = NULL;
28021592Srgrimes	struct dirent *dir;
28031592Srgrimes	FILE *dout = NULL;
28041592Srgrimes	char **dirlist, *dirname;
28051592Srgrimes	int simple = 0;
28061592Srgrimes	int freeglob = 0;
28071592Srgrimes	glob_t gl;
28081592Srgrimes
28091592Srgrimes	if (strpbrk(whichf, "~{[*?") != NULL) {
2810100222Smikeh		int flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE;
28111592Srgrimes
28121592Srgrimes		memset(&gl, 0, sizeof(gl));
281374470Sjlemon		gl.gl_matchc = MAXGLOBARGS;
281480525Smikeh		flags |= GLOB_LIMIT;
28151592Srgrimes		freeglob = 1;
28161592Srgrimes		if (glob(whichf, flags, 0, &gl)) {
28171592Srgrimes			reply(550, "not found");
28181592Srgrimes			goto out;
28191592Srgrimes		} else if (gl.gl_pathc == 0) {
28201592Srgrimes			errno = ENOENT;
28211592Srgrimes			perror_reply(550, whichf);
28221592Srgrimes			goto out;
28231592Srgrimes		}
28241592Srgrimes		dirlist = gl.gl_pathv;
28251592Srgrimes	} else {
28261592Srgrimes		onefile[0] = whichf;
28271592Srgrimes		dirlist = onefile;
28281592Srgrimes		simple = 1;
28291592Srgrimes	}
28301592Srgrimes
283117478Smarkm	while ((dirname = *dirlist++)) {
28321592Srgrimes		if (stat(dirname, &st) < 0) {
28331592Srgrimes			/*
28341592Srgrimes			 * If user typed "ls -l", etc, and the client
28351592Srgrimes			 * used NLST, do what the user meant.
28361592Srgrimes			 */
28371592Srgrimes			if (dirname[0] == '-' && *dirlist == NULL &&
28381592Srgrimes			    transflag == 0) {
283925165Sdavidn				retrieve(_PATH_LS " %s", dirname);
28401592Srgrimes				goto out;
28411592Srgrimes			}
28421592Srgrimes			perror_reply(550, whichf);
28431592Srgrimes			if (dout != NULL) {
28441592Srgrimes				(void) fclose(dout);
28451592Srgrimes				transflag = 0;
28461592Srgrimes				data = -1;
28471592Srgrimes				pdata = -1;
28481592Srgrimes			}
28491592Srgrimes			goto out;
28501592Srgrimes		}
28511592Srgrimes
28521592Srgrimes		if (S_ISREG(st.st_mode)) {
28531592Srgrimes			if (dout == NULL) {
28541592Srgrimes				dout = dataconn("file list", (off_t)-1, "w");
28551592Srgrimes				if (dout == NULL)
28561592Srgrimes					goto out;
28571592Srgrimes				transflag++;
28581592Srgrimes			}
28591592Srgrimes			fprintf(dout, "%s%s\n", dirname,
28601592Srgrimes				type == TYPE_A ? "\r" : "");
28611592Srgrimes			byte_count += strlen(dirname) + 1;
28621592Srgrimes			continue;
28631592Srgrimes		} else if (!S_ISDIR(st.st_mode))
28641592Srgrimes			continue;
28651592Srgrimes
28661592Srgrimes		if ((dirp = opendir(dirname)) == NULL)
28671592Srgrimes			continue;
28681592Srgrimes
28691592Srgrimes		while ((dir = readdir(dirp)) != NULL) {
28701592Srgrimes			char nbuf[MAXPATHLEN];
28711592Srgrimes
287289935Syar			if (recvurg) {
287389935Syar				myoob();
287489935Syar				recvurg = 0;
287589935Syar				transflag = 0;
287689935Syar				goto out;
287789935Syar			}
287889935Syar
28791592Srgrimes			if (dir->d_name[0] == '.' && dir->d_namlen == 1)
28801592Srgrimes				continue;
28811592Srgrimes			if (dir->d_name[0] == '.' && dir->d_name[1] == '.' &&
28821592Srgrimes			    dir->d_namlen == 2)
28831592Srgrimes				continue;
28841592Srgrimes
288599213Smaxim			snprintf(nbuf, sizeof(nbuf),
288631973Simp				"%s/%s", dirname, dir->d_name);
28871592Srgrimes
28881592Srgrimes			/*
28891592Srgrimes			 * We have to do a stat to insure it's
28901592Srgrimes			 * not a directory or special file.
28911592Srgrimes			 */
28921592Srgrimes			if (simple || (stat(nbuf, &st) == 0 &&
28931592Srgrimes			    S_ISREG(st.st_mode))) {
28941592Srgrimes				if (dout == NULL) {
28951592Srgrimes					dout = dataconn("file list", (off_t)-1,
28961592Srgrimes						"w");
28971592Srgrimes					if (dout == NULL)
28981592Srgrimes						goto out;
28991592Srgrimes					transflag++;
29001592Srgrimes				}
29011592Srgrimes				if (nbuf[0] == '.' && nbuf[1] == '/')
29021592Srgrimes					fprintf(dout, "%s%s\n", &nbuf[2],
29031592Srgrimes						type == TYPE_A ? "\r" : "");
29041592Srgrimes				else
29051592Srgrimes					fprintf(dout, "%s%s\n", nbuf,
29061592Srgrimes						type == TYPE_A ? "\r" : "");
29071592Srgrimes				byte_count += strlen(nbuf) + 1;
29081592Srgrimes			}
29091592Srgrimes		}
29101592Srgrimes		(void) closedir(dirp);
29111592Srgrimes	}
29121592Srgrimes
29131592Srgrimes	if (dout == NULL)
29141592Srgrimes		reply(550, "No files found.");
29151592Srgrimes	else if (ferror(dout) != 0)
29161592Srgrimes		perror_reply(550, "Data connection");
29171592Srgrimes	else
29181592Srgrimes		reply(226, "Transfer complete.");
29191592Srgrimes
29201592Srgrimes	transflag = 0;
29211592Srgrimes	if (dout != NULL)
29221592Srgrimes		(void) fclose(dout);
29231592Srgrimes	data = -1;
29241592Srgrimes	pdata = -1;
29251592Srgrimesout:
29261592Srgrimes	if (freeglob) {
29271592Srgrimes		freeglob = 0;
29281592Srgrimes		globfree(&gl);
29291592Srgrimes	}
29301592Srgrimes}
29311592Srgrimes
293215196Sdgvoid
293390148Simpreapchild(int signo)
293415196Sdg{
293515196Sdg	while (wait3(NULL, WNOHANG, NULL) > 0);
293615196Sdg}
293715196Sdg
293813139Speter#ifdef OLD_SETPROCTITLE
29391592Srgrimes/*
29401592Srgrimes * Clobber argv so ps will show what we're doing.  (Stolen from sendmail.)
29411592Srgrimes * Warning, since this is usually started from inetd.conf, it often doesn't
29421592Srgrimes * have much of an environment or arglist to overwrite.
29431592Srgrimes */
29441592Srgrimesvoid
29451592Srgrimessetproctitle(const char *fmt, ...)
29461592Srgrimes{
29471592Srgrimes	int i;
29481592Srgrimes	va_list ap;
29491592Srgrimes	char *p, *bp, ch;
29501592Srgrimes	char buf[LINE_MAX];
29511592Srgrimes
29521592Srgrimes	va_start(ap, fmt);
29531592Srgrimes	(void)vsnprintf(buf, sizeof(buf), fmt, ap);
29541592Srgrimes
29551592Srgrimes	/* make ps print our process name */
29561592Srgrimes	p = Argv[0];
29571592Srgrimes	*p++ = '-';
29581592Srgrimes
29591592Srgrimes	i = strlen(buf);
29601592Srgrimes	if (i > LastArgv - p - 2) {
29611592Srgrimes		i = LastArgv - p - 2;
29621592Srgrimes		buf[i] = '\0';
29631592Srgrimes	}
29641592Srgrimes	bp = buf;
29651592Srgrimes	while (ch = *bp++)
29661592Srgrimes		if (ch != '\n' && ch != '\r')
29671592Srgrimes			*p++ = ch;
29681592Srgrimes	while (p < LastArgv)
29691592Srgrimes		*p++ = ' ';
29701592Srgrimes}
297113139Speter#endif /* OLD_SETPROCTITLE */
29726740Sguido
297317433Spststatic void
297490148Simplogxfer(char *name, off_t size, time_t start)
29756740Sguido{
29766740Sguido	char buf[1024];
29776740Sguido	char path[MAXPATHLEN + 1];
297836612Sjb	time_t now;
29796740Sguido
29806740Sguido	if (statfd >= 0 && getwd(path) != NULL) {
29816740Sguido		time(&now);
298282792Sache		snprintf(buf, sizeof(buf), "%.20s!%s!%s!%s/%s!%qd!%ld\n",
29836740Sguido			ctime(&now)+4, ident, remotehost,
298482792Sache			path, name, (long long)size,
298582792Sache			(long)(now - start + (now == start)));
29866740Sguido		write(statfd, buf, strlen(buf));
29876740Sguido	}
29886740Sguido}
2989100486Syar
2990100486Syarstatic char *
2991100486Syardoublequote(char *s)
2992100486Syar{
2993100486Syar	int n;
2994100486Syar	char *p, *s2;
2995100486Syar
2996100486Syar	for (p = s, n = 0; *p; p++)
2997100486Syar		if (*p == '"')
2998100486Syar			n++;
2999100486Syar
3000100486Syar	if ((s2 = malloc(p - s + n + 1)) == NULL)
3001100486Syar		return (NULL);
3002100486Syar
3003100486Syar	for (p = s2; *s; s++, p++) {
3004100486Syar		if ((*p = *s) == '"')
3005100486Syar			*(++p) = '"';
3006100486Syar	}
3007100486Syar	*p = '\0';
3008100486Syar
3009100486Syar	return (s2);
3010100486Syar}
3011