xref: /csrg-svn/libexec/ftpd/ftpd.c (revision 50391)
122499Sdist /*
244339Skarels  * Copyright (c) 1985, 1988, 1990 Regents of the University of California.
333738Sbostic  * All rights reserved.
433738Sbostic  *
542666Sbostic  * %sccs.include.redist.c%
622499Sdist  */
722499Sdist 
810275Ssam #ifndef lint
922499Sdist char copyright[] =
1044339Skarels "@(#) Copyright (c) 1985, 1988, 1990 Regents of the University of California.\n\
1122499Sdist  All rights reserved.\n";
1233738Sbostic #endif /* not lint */
1310275Ssam 
1422499Sdist #ifndef lint
15*50391Skarels static char sccsid[] = "@(#)ftpd.c	5.40 (Berkeley) 07/02/91";
1633738Sbostic #endif /* not lint */
1722499Sdist 
1810275Ssam /*
1910275Ssam  * FTP server.
2010275Ssam  */
2110303Ssam #include <sys/param.h>
2210275Ssam #include <sys/stat.h>
2310275Ssam #include <sys/ioctl.h>
2410275Ssam #include <sys/socket.h>
2513595Ssam #include <sys/wait.h>
2610275Ssam 
2710275Ssam #include <netinet/in.h>
2844339Skarels #include <netinet/in_systm.h>
2944339Skarels #include <netinet/ip.h>
3010275Ssam 
3136933Skarels #define	FTP_NAMES
3213034Ssam #include <arpa/ftp.h>
3313211Sroot #include <arpa/inet.h>
3426044Sminshall #include <arpa/telnet.h>
3513034Ssam 
3610275Ssam #include <signal.h>
3746669Sbostic #include <dirent.h>
3846669Sbostic #include <fcntl.h>
3946669Sbostic #include <time.h>
4010275Ssam #include <pwd.h>
4110275Ssam #include <setjmp.h>
4210275Ssam #include <netdb.h>
4310423Ssam #include <errno.h>
4426493Sminshall #include <syslog.h>
4536435Sbostic #include <varargs.h>
4646669Sbostic #include <unistd.h>
4746669Sbostic #include <stdio.h>
4846669Sbostic #include <ctype.h>
4946669Sbostic #include <stdlib.h>
5046669Sbostic #include <string.h>
5137459Skarels #include "pathnames.h"
5210275Ssam 
5310695Ssam /*
5410695Ssam  * File containing login names
5510695Ssam  * NOT to be used on this machine.
5610695Ssam  * Commonly used to disallow uucp.
5710695Ssam  */
5810275Ssam extern	int errno;
5910275Ssam extern	char *crypt();
6010275Ssam extern	char version[];
6110275Ssam extern	char *home;		/* pointer to home directory for glob */
6236276Sbostic extern	FILE *ftpd_popen(), *fopen(), *freopen();
6336304Skarels extern	int  ftpd_pclose(), fclose();
6426044Sminshall extern	char *getline();
6526044Sminshall extern	char cbuf[];
6637459Skarels extern	off_t restart_point;
6710275Ssam 
6810275Ssam struct	sockaddr_in ctrl_addr;
6910275Ssam struct	sockaddr_in data_source;
7010275Ssam struct	sockaddr_in data_dest;
7110275Ssam struct	sockaddr_in his_addr;
7236933Skarels struct	sockaddr_in pasv_addr;
7310275Ssam 
7410275Ssam int	data;
7526044Sminshall jmp_buf	errcatch, urgcatch;
7610275Ssam int	logged_in;
7710275Ssam struct	passwd *pw;
7810275Ssam int	debug;
7926493Sminshall int	timeout = 900;    /* timeout after 15 minutes of inactivity */
8036933Skarels int	maxtimeout = 7200;/* don't allow idle time to be set beyond 2 hours */
8111757Ssam int	logging;
8210275Ssam int	guest;
8310275Ssam int	type;
8410275Ssam int	form;
8510275Ssam int	stru;			/* avoid C keyword */
8610275Ssam int	mode;
8710321Ssam int	usedefault = 1;		/* for data transfers */
8836304Skarels int	pdata = -1;		/* for passive mode */
8926044Sminshall int	transflag;
9036933Skarels off_t	file_size;
9136933Skarels off_t	byte_count;
9236933Skarels #if !defined(CMASK) || CMASK == 0
9336933Skarels #undef CMASK
9436933Skarels #define CMASK 027
9536933Skarels #endif
9636933Skarels int	defumask = CMASK;		/* default umask value */
9726044Sminshall char	tmpline[7];
9836276Sbostic char	hostname[MAXHOSTNAMELEN];
9936276Sbostic char	remotehost[MAXHOSTNAMELEN];
10010275Ssam 
10111653Ssam /*
10211653Ssam  * Timeout intervals for retrying connections
10311653Ssam  * to hosts that don't accept PORT cmds.  This
10411653Ssam  * is a kludge, but given the problems with TCP...
10511653Ssam  */
10611653Ssam #define	SWAITMAX	90	/* wait at most 90 seconds */
10711653Ssam #define	SWAITINT	5	/* interval between retries */
10811653Ssam 
10911653Ssam int	swaitmax = SWAITMAX;
11011653Ssam int	swaitint = SWAITINT;
11111653Ssam 
11246669Sbostic void	lostconn(), myoob();
11310275Ssam FILE	*getdatasock(), *dataconn();
11410275Ssam 
11536620Srick #ifdef SETPROCTITLE
11636620Srick char	**Argv = NULL;		/* pointer to argument vector */
11736620Srick char	*LastArgv = NULL;	/* end of argv */
11836933Skarels char	proctitle[BUFSIZ];	/* initial part of title */
11936620Srick #endif /* SETPROCTITLE */
12036620Srick 
12136620Srick main(argc, argv, envp)
12210275Ssam 	int argc;
12310275Ssam 	char *argv[];
12436620Srick 	char **envp;
12510275Ssam {
12644339Skarels 	int addrlen, on = 1, tos;
12710275Ssam 	char *cp;
12810275Ssam 
12945028Skarels 	/*
13045028Skarels 	 * LOG_NDELAY sets up the logging connection immediately,
13145028Skarels 	 * necessary for anonymous ftp's that chroot and can't do it later.
13245028Skarels 	 */
13345028Skarels 	openlog("ftpd", LOG_PID | LOG_NDELAY, LOG_DAEMON);
13416339Skarels 	addrlen = sizeof (his_addr);
13536304Skarels 	if (getpeername(0, (struct sockaddr *)&his_addr, &addrlen) < 0) {
13626493Sminshall 		syslog(LOG_ERR, "getpeername (%s): %m",argv[0]);
13710275Ssam 		exit(1);
13810275Ssam 	}
13916339Skarels 	addrlen = sizeof (ctrl_addr);
14036304Skarels 	if (getsockname(0, (struct sockaddr *)&ctrl_addr, &addrlen) < 0) {
14126493Sminshall 		syslog(LOG_ERR, "getsockname (%s): %m",argv[0]);
14216339Skarels 		exit(1);
14316339Skarels 	}
14444339Skarels #ifdef IP_TOS
14544339Skarels 	tos = IPTOS_LOWDELAY;
14644339Skarels 	if (setsockopt(0, IPPROTO_IP, IP_TOS, (char *)&tos, sizeof(int)) < 0)
14744339Skarels 		syslog(LOG_WARNING, "setsockopt (IP_TOS): %m");
14844339Skarels #endif
14916339Skarels 	data_source.sin_port = htons(ntohs(ctrl_addr.sin_port) - 1);
15010275Ssam 	debug = 0;
15136620Srick #ifdef SETPROCTITLE
15236620Srick 	/*
15336620Srick 	 *  Save start and extent of argv for setproctitle.
15436620Srick 	 */
15536620Srick 	Argv = argv;
15636620Srick 	while (*envp)
15736620Srick 		envp++;
15836620Srick 	LastArgv = envp[-1] + strlen(envp[-1]);
15936620Srick #endif /* SETPROCTITLE */
16036620Srick 
16110275Ssam 	argc--, argv++;
16210275Ssam 	while (argc > 0 && *argv[0] == '-') {
16310275Ssam 		for (cp = &argv[0][1]; *cp; cp++) switch (*cp) {
16410275Ssam 
16511653Ssam 		case 'v':
16611653Ssam 			debug = 1;
16711653Ssam 			break;
16811653Ssam 
16910275Ssam 		case 'd':
17010275Ssam 			debug = 1;
17110275Ssam 			break;
17210275Ssam 
17311757Ssam 		case 'l':
17411757Ssam 			logging = 1;
17511757Ssam 			break;
17611757Ssam 
17711653Ssam 		case 't':
17811653Ssam 			timeout = atoi(++cp);
17936933Skarels 			if (maxtimeout < timeout)
18036933Skarels 				maxtimeout = timeout;
18111653Ssam 			goto nextopt;
18211653Ssam 
18336933Skarels 		case 'T':
18436933Skarels 			maxtimeout = atoi(++cp);
18536933Skarels 			if (timeout > maxtimeout)
18636933Skarels 				timeout = maxtimeout;
18736933Skarels 			goto nextopt;
18836933Skarels 
18936933Skarels 		case 'u':
19036933Skarels 		    {
19136933Skarels 			int val = 0;
19236933Skarels 
19336933Skarels 			while (*++cp && *cp >= '0' && *cp <= '9')
19436933Skarels 				val = val*8 + *cp - '0';
19536933Skarels 			if (*cp)
19636933Skarels 				fprintf(stderr, "ftpd: Bad value for -u\n");
19736933Skarels 			else
19836933Skarels 				defumask = val;
19936933Skarels 			goto nextopt;
20036933Skarels 		    }
20136933Skarels 
20210275Ssam 		default:
20316339Skarels 			fprintf(stderr, "ftpd: Unknown flag -%c ignored.\n",
20416339Skarels 			     *cp);
20510275Ssam 			break;
20610275Ssam 		}
20711653Ssam nextopt:
20810275Ssam 		argc--, argv++;
20910275Ssam 	}
21037459Skarels 	(void) freopen(_PATH_DEVNULL, "w", stderr);
21126493Sminshall 	(void) signal(SIGPIPE, lostconn);
21226493Sminshall 	(void) signal(SIGCHLD, SIG_IGN);
21335691Sbostic 	if ((int)signal(SIGURG, myoob) < 0)
21426493Sminshall 		syslog(LOG_ERR, "signal: %m");
21535691Sbostic 
21638134Srick 	/* Try to handle urgent data inline */
21727750Sminshall #ifdef SO_OOBINLINE
21836276Sbostic 	if (setsockopt(0, SOL_SOCKET, SO_OOBINLINE, (char *)&on, sizeof(on)) < 0)
21927750Sminshall 		syslog(LOG_ERR, "setsockopt: %m");
22036276Sbostic #endif
22138134Srick 
22236933Skarels #ifdef	F_SETOWN
22336304Skarels 	if (fcntl(fileno(stdin), F_SETOWN, getpid()) == -1)
22436304Skarels 		syslog(LOG_ERR, "fcntl F_SETOWN: %m");
22536933Skarels #endif
22616760Slepreau 	dolog(&his_addr);
22716339Skarels 	/*
22816339Skarels 	 * Set up default state
22916339Skarels 	 */
23016339Skarels 	data = -1;
23116339Skarels 	type = TYPE_A;
23216339Skarels 	form = FORM_N;
23316339Skarels 	stru = STRU_F;
23416339Skarels 	mode = MODE_S;
23526044Sminshall 	tmpline[0] = '\0';
23626493Sminshall 	(void) gethostname(hostname, sizeof (hostname));
23736276Sbostic 	reply(220, "%s FTP server (%s) ready.", hostname, version);
23836304Skarels 	(void) setjmp(errcatch);
23936304Skarels 	for (;;)
24026493Sminshall 		(void) yyparse();
24136620Srick 	/* NOTREACHED */
24210275Ssam }
24310419Ssam 
24446669Sbostic void
24510275Ssam lostconn()
24610275Ssam {
24714089Ssam 	if (debug)
24826493Sminshall 		syslog(LOG_DEBUG, "lost connection");
24914089Ssam 	dologout(-1);
25010275Ssam }
25110275Ssam 
25235672Sbostic static char ttyline[20];
25335672Sbostic 
25436185Sbostic /*
25536185Sbostic  * Helper function for sgetpwnam().
25636185Sbostic  */
25736185Sbostic char *
25836185Sbostic sgetsave(s)
25936185Sbostic 	char *s;
26036185Sbostic {
26136185Sbostic 	char *new = malloc((unsigned) strlen(s) + 1);
26236933Skarels 
26336185Sbostic 	if (new == NULL) {
26436620Srick 		perror_reply(421, "Local resource failure: malloc");
26536185Sbostic 		dologout(1);
26636620Srick 		/* NOTREACHED */
26736185Sbostic 	}
26836185Sbostic 	(void) strcpy(new, s);
26936185Sbostic 	return (new);
27036185Sbostic }
27136185Sbostic 
27236185Sbostic /*
27336185Sbostic  * Save the result of a getpwnam.  Used for USER command, since
27436185Sbostic  * the data returned must not be clobbered by any other command
27536185Sbostic  * (e.g., globbing).
27636185Sbostic  */
27736185Sbostic struct passwd *
27836185Sbostic sgetpwnam(name)
27936185Sbostic 	char *name;
28036185Sbostic {
28136185Sbostic 	static struct passwd save;
28236185Sbostic 	register struct passwd *p;
28336185Sbostic 	char *sgetsave();
28436185Sbostic 
28536185Sbostic 	if ((p = getpwnam(name)) == NULL)
28636185Sbostic 		return (p);
28736185Sbostic 	if (save.pw_name) {
28836185Sbostic 		free(save.pw_name);
28936185Sbostic 		free(save.pw_passwd);
29036185Sbostic 		free(save.pw_gecos);
29136185Sbostic 		free(save.pw_dir);
29236185Sbostic 		free(save.pw_shell);
29336185Sbostic 	}
29436185Sbostic 	save = *p;
29536185Sbostic 	save.pw_name = sgetsave(p->pw_name);
29636185Sbostic 	save.pw_passwd = sgetsave(p->pw_passwd);
29736185Sbostic 	save.pw_gecos = sgetsave(p->pw_gecos);
29836185Sbostic 	save.pw_dir = sgetsave(p->pw_dir);
29936185Sbostic 	save.pw_shell = sgetsave(p->pw_shell);
30036185Sbostic 	return (&save);
30136185Sbostic }
30236185Sbostic 
30336304Skarels int login_attempts;		/* number of failed login attempts */
30436304Skarels int askpasswd;			/* had user command, ask for passwd */
30536304Skarels 
30636304Skarels /*
30736304Skarels  * USER command.
30842327Sbostic  * Sets global passwd pointer pw if named account exists and is acceptable;
30942327Sbostic  * sets askpasswd if a PASS command is expected.  If logged in previously,
31042327Sbostic  * need to reset state.  If name is "ftp" or "anonymous", the name is not in
31142327Sbostic  * _PATH_FTPUSERS, and ftp account exists, set guest and pw, then just return.
31242327Sbostic  * If account doesn't exist, ask for passwd anyway.  Otherwise, check user
31342327Sbostic  * requesting login privileges.  Disallow anyone who does not have a standard
31442327Sbostic  * shell as returned by getusershell().  Disallow anyone mentioned in the file
31542327Sbostic  * _PATH_FTPUSERS to allow people such as root and uucp to be avoided.
31636304Skarels  */
31736304Skarels user(name)
31836304Skarels 	char *name;
31936304Skarels {
32036304Skarels 	register char *cp;
32136304Skarels 	char *shell;
32240183Smckusick 	char *getusershell();
32336304Skarels 
32436304Skarels 	if (logged_in) {
32536304Skarels 		if (guest) {
32636304Skarels 			reply(530, "Can't change user from guest login.");
32736304Skarels 			return;
32836304Skarels 		}
32936304Skarels 		end_login();
33036304Skarels 	}
33136304Skarels 
33236304Skarels 	guest = 0;
33336304Skarels 	if (strcmp(name, "ftp") == 0 || strcmp(name, "anonymous") == 0) {
33440183Smckusick 		if (checkuser("ftp") || checkuser("anonymous"))
33540155Smckusick 			reply(530, "User %s access denied.", name);
33640155Smckusick 		else if ((pw = sgetpwnam("ftp")) != NULL) {
33736304Skarels 			guest = 1;
33836304Skarels 			askpasswd = 1;
33936304Skarels 			reply(331, "Guest login ok, send ident as password.");
34036933Skarels 		} else
34136304Skarels 			reply(530, "User %s unknown.", name);
34236304Skarels 		return;
34336304Skarels 	}
34436304Skarels 	if (pw = sgetpwnam(name)) {
34536304Skarels 		if ((shell = pw->pw_shell) == NULL || *shell == 0)
34637459Skarels 			shell = _PATH_BSHELL;
34736304Skarels 		while ((cp = getusershell()) != NULL)
34836304Skarels 			if (strcmp(cp, shell) == 0)
34936304Skarels 				break;
35036304Skarels 		endusershell();
35140183Smckusick 		if (cp == NULL || checkuser(name)) {
35236304Skarels 			reply(530, "User %s access denied.", name);
35336933Skarels 			if (logging)
35436933Skarels 				syslog(LOG_NOTICE,
35536933Skarels 				    "FTP LOGIN REFUSED FROM %s, %s",
35636933Skarels 				    remotehost, name);
35736304Skarels 			pw = (struct passwd *) NULL;
35836304Skarels 			return;
35936304Skarels 		}
36036304Skarels 	}
36136304Skarels 	reply(331, "Password required for %s.", name);
36236304Skarels 	askpasswd = 1;
36336304Skarels 	/*
36436304Skarels 	 * Delay before reading passwd after first failed
36536304Skarels 	 * attempt to slow down passwd-guessing programs.
36636304Skarels 	 */
36736304Skarels 	if (login_attempts)
36836304Skarels 		sleep((unsigned) login_attempts);
36936304Skarels }
37036304Skarels 
37136304Skarels /*
37240183Smckusick  * Check if a user is in the file _PATH_FTPUSERS
37340183Smckusick  */
37440183Smckusick checkuser(name)
37540183Smckusick 	char *name;
37640183Smckusick {
37742327Sbostic 	register FILE *fd;
37842327Sbostic 	register char *p;
37942327Sbostic 	char line[BUFSIZ];
38040183Smckusick 
38140183Smckusick 	if ((fd = fopen(_PATH_FTPUSERS, "r")) != NULL) {
38242327Sbostic 		while (fgets(line, sizeof(line), fd) != NULL)
38342327Sbostic 			if ((p = index(line, '\n')) != NULL) {
38442327Sbostic 				*p = '\0';
38542327Sbostic 				if (line[0] == '#')
38642327Sbostic 					continue;
38742327Sbostic 				if (strcmp(line, name) == 0)
38842327Sbostic 					return (1);
38942327Sbostic 			}
39040183Smckusick 		(void) fclose(fd);
39140183Smckusick 	}
39240183Smckusick 	return (0);
39340183Smckusick }
39440183Smckusick 
39540183Smckusick /*
39636304Skarels  * Terminate login as previous user, if any, resetting state;
39736304Skarels  * used when USER command is given or login fails.
39836304Skarels  */
39936304Skarels end_login()
40036304Skarels {
40136304Skarels 
40236304Skarels 	(void) seteuid((uid_t)0);
40336304Skarels 	if (logged_in)
40436304Skarels 		logwtmp(ttyline, "", "");
40536304Skarels 	pw = NULL;
40636304Skarels 	logged_in = 0;
40736304Skarels 	guest = 0;
40836304Skarels }
40936304Skarels 
41010275Ssam pass(passwd)
41110275Ssam 	char *passwd;
41210275Ssam {
41336304Skarels 	char *xpasswd, *salt;
41410275Ssam 
41536304Skarels 	if (logged_in || askpasswd == 0) {
41610275Ssam 		reply(503, "Login with USER first.");
41710275Ssam 		return;
41810275Ssam 	}
41936304Skarels 	askpasswd = 0;
42010275Ssam 	if (!guest) {		/* "ftp" is only account allowed no password */
42136304Skarels 		if (pw == NULL)
42236304Skarels 			salt = "xx";
42336304Skarels 		else
42436304Skarels 			salt = pw->pw_passwd;
42536304Skarels 		xpasswd = crypt(passwd, salt);
42616760Slepreau 		/* The strcmp does not catch null passwords! */
42736304Skarels 		if (pw == NULL || *pw->pw_passwd == '\0' ||
42836304Skarels 		    strcmp(xpasswd, pw->pw_passwd)) {
42910275Ssam 			reply(530, "Login incorrect.");
43010275Ssam 			pw = NULL;
43136304Skarels 			if (login_attempts++ >= 5) {
43236933Skarels 				syslog(LOG_NOTICE,
43336304Skarels 				    "repeated login failures from %s",
43436304Skarels 				    remotehost);
43536304Skarels 				exit(0);
43636304Skarels 			}
43710275Ssam 			return;
43810275Ssam 		}
43910275Ssam 	}
44036304Skarels 	login_attempts = 0;		/* this time successful */
44136304Skarels 	(void) setegid((gid_t)pw->pw_gid);
44236304Skarels 	(void) initgroups(pw->pw_name, pw->pw_gid);
44316033Sralph 
44436192Sbostic 	/* open wtmp before chroot */
44536192Sbostic 	(void)sprintf(ttyline, "ftp%d", getpid());
44636192Sbostic 	logwtmp(ttyline, pw->pw_name, remotehost);
44736192Sbostic 	logged_in = 1;
44836192Sbostic 
44936446Sbostic 	if (guest) {
45036620Srick 		/*
45136933Skarels 		 * We MUST do a chdir() after the chroot. Otherwise
45236933Skarels 		 * the old current directory will be accessible as "."
45336933Skarels 		 * outside the new root!
45436620Srick 		 */
45536620Srick 		if (chroot(pw->pw_dir) < 0 || chdir("/") < 0) {
45636446Sbostic 			reply(550, "Can't set guest privileges.");
45736446Sbostic 			goto bad;
45836446Sbostic 		}
45936933Skarels 	} else if (chdir(pw->pw_dir) < 0) {
46036620Srick 		if (chdir("/") < 0) {
46136446Sbostic 			reply(530, "User %s: can't change directory to %s.",
46236446Sbostic 			    pw->pw_name, pw->pw_dir);
46336446Sbostic 			goto bad;
46436933Skarels 		} else
46536446Sbostic 			lreply(230, "No directory! Logging in with home=/");
46636620Srick 	}
46736304Skarels 	if (seteuid((uid_t)pw->pw_uid) < 0) {
46836304Skarels 		reply(550, "Can't set uid.");
46936304Skarels 		goto bad;
47036304Skarels 	}
47136550Sbostic 	if (guest) {
47236192Sbostic 		reply(230, "Guest login ok, access restrictions apply.");
47336933Skarels #ifdef SETPROCTITLE
47436933Skarels 		sprintf(proctitle, "%s: anonymous/%.*s", remotehost,
47536933Skarels 		    sizeof(proctitle) - sizeof(remotehost) -
47636933Skarels 		    sizeof(": anonymous/"), passwd);
47736933Skarels 		setproctitle(proctitle);
47836933Skarels #endif /* SETPROCTITLE */
47936933Skarels 		if (logging)
48036933Skarels 			syslog(LOG_INFO, "ANONYMOUS FTP LOGIN FROM %s, %s",
48136933Skarels 			    remotehost, passwd);
48236933Skarels 	} else {
48310275Ssam 		reply(230, "User %s logged in.", pw->pw_name);
48436933Skarels #ifdef SETPROCTITLE
48536933Skarels 		sprintf(proctitle, "%s: %s", remotehost, pw->pw_name);
48636933Skarels 		setproctitle(proctitle);
48736933Skarels #endif /* SETPROCTITLE */
48836933Skarels 		if (logging)
48936933Skarels 			syslog(LOG_INFO, "FTP LOGIN FROM %s, %s",
49036933Skarels 			    remotehost, pw->pw_name);
49136550Sbostic 	}
49210303Ssam 	home = pw->pw_dir;		/* home dir for globbing */
49336933Skarels 	(void) umask(defumask);
49410303Ssam 	return;
49510303Ssam bad:
49636304Skarels 	/* Forget all about it... */
49736304Skarels 	end_login();
49810275Ssam }
49910275Ssam 
50010275Ssam retrieve(cmd, name)
50110275Ssam 	char *cmd, *name;
50210275Ssam {
50310275Ssam 	FILE *fin, *dout;
50410275Ssam 	struct stat st;
50536620Srick 	int (*closefunc)();
50610275Ssam 
50736557Sbostic 	if (cmd == 0) {
50836446Sbostic 		fin = fopen(name, "r"), closefunc = fclose;
50936557Sbostic 		st.st_size = 0;
51036557Sbostic 	} else {
51110275Ssam 		char line[BUFSIZ];
51210275Ssam 
51326493Sminshall 		(void) sprintf(line, cmd, name), name = line;
51436304Skarels 		fin = ftpd_popen(line, "r"), closefunc = ftpd_pclose;
51536557Sbostic 		st.st_size = -1;
51636933Skarels 		st.st_blksize = BUFSIZ;
51710275Ssam 	}
51810275Ssam 	if (fin == NULL) {
51913152Ssam 		if (errno != 0)
52036304Skarels 			perror_reply(550, name);
52110275Ssam 		return;
52210275Ssam 	}
52310275Ssam 	if (cmd == 0 &&
52436933Skarels 	    (fstat(fileno(fin), &st) < 0 || (st.st_mode&S_IFMT) != S_IFREG)) {
52510275Ssam 		reply(550, "%s: not a plain file.", name);
52610275Ssam 		goto done;
52710275Ssam 	}
52837459Skarels 	if (restart_point) {
52937459Skarels 		if (type == TYPE_A) {
53037459Skarels 			register int i, n, c;
53137459Skarels 
53237459Skarels 			n = restart_point;
53337459Skarels 			i = 0;
53437459Skarels 			while (i++ < n) {
53537459Skarels 				if ((c=getc(fin)) == EOF) {
53637459Skarels 					perror_reply(550, name);
53737459Skarels 					goto done;
53837459Skarels 				}
53937459Skarels 				if (c == '\n')
54037459Skarels 					i++;
54137459Skarels 			}
54237459Skarels 		} else if (lseek(fileno(fin), restart_point, L_SET) < 0) {
54337459Skarels 			perror_reply(550, name);
54437459Skarels 			goto done;
54537459Skarels 		}
54637459Skarels 	}
54710275Ssam 	dout = dataconn(name, st.st_size, "w");
54810275Ssam 	if (dout == NULL)
54910275Ssam 		goto done;
55036620Srick 	send_data(fin, dout, st.st_blksize);
55126493Sminshall 	(void) fclose(dout);
55226044Sminshall 	data = -1;
55326044Sminshall 	pdata = -1;
55410275Ssam done:
55510275Ssam 	(*closefunc)(fin);
55610275Ssam }
55710275Ssam 
55836304Skarels store(name, mode, unique)
55910275Ssam 	char *name, *mode;
56036304Skarels 	int unique;
56110275Ssam {
56210275Ssam 	FILE *fout, *din;
56336446Sbostic 	struct stat st;
56436620Srick 	int (*closefunc)();
56536304Skarels 	char *gunique();
56610275Ssam 
56736446Sbostic 	if (unique && stat(name, &st) == 0 &&
56836446Sbostic 	    (name = gunique(name)) == NULL)
56936446Sbostic 		return;
57010303Ssam 
57137459Skarels 	if (restart_point)
57237459Skarels 		mode = "r+w";
57336620Srick 	fout = fopen(name, mode);
57436620Srick 	closefunc = fclose;
57510275Ssam 	if (fout == NULL) {
57636304Skarels 		perror_reply(553, name);
57710275Ssam 		return;
57810275Ssam 	}
57937459Skarels 	if (restart_point) {
58037459Skarels 		if (type == TYPE_A) {
58137459Skarels 			register int i, n, c;
58237459Skarels 
58337459Skarels 			n = restart_point;
58437459Skarels 			i = 0;
58537459Skarels 			while (i++ < n) {
58637459Skarels 				if ((c=getc(fout)) == EOF) {
58737459Skarels 					perror_reply(550, name);
58837459Skarels 					goto done;
58937459Skarels 				}
59037459Skarels 				if (c == '\n')
59137459Skarels 					i++;
59237459Skarels 			}
59337459Skarels 			/*
59437459Skarels 			 * We must do this seek to "current" position
59537459Skarels 			 * because we are changing from reading to
59637459Skarels 			 * writing.
59737459Skarels 			 */
59837459Skarels 			if (fseek(fout, 0L, L_INCR) < 0) {
59937459Skarels 				perror_reply(550, name);
60037459Skarels 				goto done;
60137459Skarels 			}
60237459Skarels 		} else if (lseek(fileno(fout), restart_point, L_SET) < 0) {
60337459Skarels 			perror_reply(550, name);
60437459Skarels 			goto done;
60537459Skarels 		}
60637459Skarels 	}
60736304Skarels 	din = dataconn(name, (off_t)-1, "r");
60810275Ssam 	if (din == NULL)
60910275Ssam 		goto done;
61036620Srick 	if (receive_data(din, fout) == 0) {
61136620Srick 		if (unique)
61236304Skarels 			reply(226, "Transfer complete (unique file name:%s).",
61336933Skarels 			    name);
61436304Skarels 		else
61536304Skarels 			reply(226, "Transfer complete.");
61626044Sminshall 	}
61726493Sminshall 	(void) fclose(din);
61826044Sminshall 	data = -1;
61926044Sminshall 	pdata = -1;
62010275Ssam done:
62110275Ssam 	(*closefunc)(fout);
62210275Ssam }
62310275Ssam 
62410275Ssam FILE *
62510275Ssam getdatasock(mode)
62610275Ssam 	char *mode;
62710275Ssam {
62837459Skarels 	int s, on = 1, tries;
62910275Ssam 
63010275Ssam 	if (data >= 0)
63110275Ssam 		return (fdopen(data, mode));
632*50391Skarels 	(void) seteuid((uid_t)0);
63313247Ssam 	s = socket(AF_INET, SOCK_STREAM, 0);
63410602Ssam 	if (s < 0)
635*50391Skarels 		goto bad;
63637459Skarels 	if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
63737459Skarels 	    (char *) &on, sizeof (on)) < 0)
63810602Ssam 		goto bad;
63913152Ssam 	/* anchor socket to avoid multi-homing problems */
64013152Ssam 	data_source.sin_family = AF_INET;
64113152Ssam 	data_source.sin_addr = ctrl_addr.sin_addr;
64237459Skarels 	for (tries = 1; ; tries++) {
64337459Skarels 		if (bind(s, (struct sockaddr *)&data_source,
64437459Skarels 		    sizeof (data_source)) >= 0)
64537459Skarels 			break;
64637459Skarels 		if (errno != EADDRINUSE || tries > 10)
64737459Skarels 			goto bad;
64837459Skarels 		sleep(tries);
64937459Skarels 	}
65036304Skarels 	(void) seteuid((uid_t)pw->pw_uid);
65144339Skarels #ifdef IP_TOS
65244339Skarels 	on = IPTOS_THROUGHPUT;
65344339Skarels 	if (setsockopt(s, IPPROTO_IP, IP_TOS, (char *)&on, sizeof(int)) < 0)
65444339Skarels 		syslog(LOG_WARNING, "setsockopt (IP_TOS): %m");
65544339Skarels #endif
65610275Ssam 	return (fdopen(s, mode));
65710602Ssam bad:
65836304Skarels 	(void) seteuid((uid_t)pw->pw_uid);
65926493Sminshall 	(void) close(s);
66010602Ssam 	return (NULL);
66110275Ssam }
66210275Ssam 
66310275Ssam FILE *
66410275Ssam dataconn(name, size, mode)
66510275Ssam 	char *name;
66611653Ssam 	off_t size;
66710275Ssam 	char *mode;
66810275Ssam {
66910275Ssam 	char sizebuf[32];
67010275Ssam 	FILE *file;
67144339Skarels 	int retry = 0, tos;
67210275Ssam 
67336933Skarels 	file_size = size;
67436933Skarels 	byte_count = 0;
67536304Skarels 	if (size != (off_t) -1)
67626493Sminshall 		(void) sprintf (sizebuf, " (%ld bytes)", size);
67710275Ssam 	else
67810275Ssam 		(void) strcpy(sizebuf, "");
67936304Skarels 	if (pdata >= 0) {
68026044Sminshall 		struct sockaddr_in from;
68126044Sminshall 		int s, fromlen = sizeof(from);
68226044Sminshall 
68336304Skarels 		s = accept(pdata, (struct sockaddr *)&from, &fromlen);
68426044Sminshall 		if (s < 0) {
68526044Sminshall 			reply(425, "Can't open data connection.");
68626044Sminshall 			(void) close(pdata);
68726044Sminshall 			pdata = -1;
68826044Sminshall 			return(NULL);
68926044Sminshall 		}
69026044Sminshall 		(void) close(pdata);
69126044Sminshall 		pdata = s;
69244339Skarels #ifdef IP_TOS
69344339Skarels 		tos = IPTOS_LOWDELAY;
69444339Skarels 		(void) setsockopt(s, IPPROTO_IP, IP_TOS, (char *)&tos,
69544339Skarels 		    sizeof(int));
69644339Skarels #endif
69736235Skarels 		reply(150, "Opening %s mode data connection for %s%s.",
69836235Skarels 		     type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf);
69926044Sminshall 		return(fdopen(pdata, mode));
70026044Sminshall 	}
70110275Ssam 	if (data >= 0) {
70210275Ssam 		reply(125, "Using existing data connection for %s%s.",
70310275Ssam 		    name, sizebuf);
70410321Ssam 		usedefault = 1;
70510275Ssam 		return (fdopen(data, mode));
70610275Ssam 	}
70710566Ssam 	if (usedefault)
70810422Ssam 		data_dest = his_addr;
70910422Ssam 	usedefault = 1;
71010275Ssam 	file = getdatasock(mode);
71110275Ssam 	if (file == NULL) {
71210275Ssam 		reply(425, "Can't create data socket (%s,%d): %s.",
71313247Ssam 		    inet_ntoa(data_source.sin_addr),
71442412Sbostic 		    ntohs(data_source.sin_port), strerror(errno));
71510275Ssam 		return (NULL);
71610275Ssam 	}
71710275Ssam 	data = fileno(file);
71836304Skarels 	while (connect(data, (struct sockaddr *)&data_dest,
71936304Skarels 	    sizeof (data_dest)) < 0) {
72011653Ssam 		if (errno == EADDRINUSE && retry < swaitmax) {
72126493Sminshall 			sleep((unsigned) swaitint);
72211653Ssam 			retry += swaitint;
72311653Ssam 			continue;
72411653Ssam 		}
72536304Skarels 		perror_reply(425, "Can't build data connection");
72610275Ssam 		(void) fclose(file);
72710275Ssam 		data = -1;
72810275Ssam 		return (NULL);
72910275Ssam 	}
73036235Skarels 	reply(150, "Opening %s mode data connection for %s%s.",
73136235Skarels 	     type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf);
73210275Ssam 	return (file);
73310275Ssam }
73410275Ssam 
73510275Ssam /*
73610275Ssam  * Tranfer the contents of "instr" to
73710275Ssam  * "outstr" peer using the appropriate
73836933Skarels  * encapsulation of the data subject
73910275Ssam  * to Mode, Structure, and Type.
74010275Ssam  *
74110275Ssam  * NB: Form isn't handled.
74210275Ssam  */
74336446Sbostic send_data(instr, outstr, blksize)
74410275Ssam 	FILE *instr, *outstr;
74536446Sbostic 	off_t blksize;
74610275Ssam {
74736446Sbostic 	register int c, cnt;
74836446Sbostic 	register char *buf;
74936446Sbostic 	int netfd, filefd;
75010275Ssam 
75126044Sminshall 	transflag++;
75226044Sminshall 	if (setjmp(urgcatch)) {
75326044Sminshall 		transflag = 0;
75436620Srick 		return;
75526044Sminshall 	}
75610275Ssam 	switch (type) {
75710275Ssam 
75810275Ssam 	case TYPE_A:
75910275Ssam 		while ((c = getc(instr)) != EOF) {
76036933Skarels 			byte_count++;
76111220Ssam 			if (c == '\n') {
76236933Skarels 				if (ferror(outstr))
76336620Srick 					goto data_err;
76427750Sminshall 				(void) putc('\r', outstr);
76511220Ssam 			}
76627750Sminshall 			(void) putc(c, outstr);
76710275Ssam 		}
76836933Skarels 		fflush(outstr);
76926044Sminshall 		transflag = 0;
77036933Skarels 		if (ferror(instr))
77136933Skarels 			goto file_err;
77236933Skarels 		if (ferror(outstr))
77336620Srick 			goto data_err;
77436620Srick 		reply(226, "Transfer complete.");
77536620Srick 		return;
77636446Sbostic 
77710275Ssam 	case TYPE_I:
77810275Ssam 	case TYPE_L:
77936446Sbostic 		if ((buf = malloc((u_int)blksize)) == NULL) {
78036446Sbostic 			transflag = 0;
78136933Skarels 			perror_reply(451, "Local resource failure: malloc");
78236933Skarels 			return;
78336446Sbostic 		}
78410275Ssam 		netfd = fileno(outstr);
78510275Ssam 		filefd = fileno(instr);
78636933Skarels 		while ((cnt = read(filefd, buf, (u_int)blksize)) > 0 &&
78736620Srick 		    write(netfd, buf, cnt) == cnt)
78836933Skarels 			byte_count += cnt;
78926044Sminshall 		transflag = 0;
79036446Sbostic 		(void)free(buf);
79136933Skarels 		if (cnt != 0) {
79236933Skarels 			if (cnt < 0)
79336933Skarels 				goto file_err;
79436620Srick 			goto data_err;
79536933Skarels 		}
79636620Srick 		reply(226, "Transfer complete.");
79736620Srick 		return;
79836620Srick 	default:
79936620Srick 		transflag = 0;
80036620Srick 		reply(550, "Unimplemented TYPE %d in send_data", type);
80136620Srick 		return;
80210275Ssam 	}
80336620Srick 
80436620Srick data_err:
80526044Sminshall 	transflag = 0;
80636933Skarels 	perror_reply(426, "Data connection");
80736933Skarels 	return;
80836933Skarels 
80936933Skarels file_err:
81036933Skarels 	transflag = 0;
81136933Skarels 	perror_reply(551, "Error on input file");
81210275Ssam }
81310275Ssam 
81410275Ssam /*
81510275Ssam  * Transfer data from peer to
81610275Ssam  * "outstr" using the appropriate
81710275Ssam  * encapulation of the data subject
81810275Ssam  * to Mode, Structure, and Type.
81910275Ssam  *
82010275Ssam  * N.B.: Form isn't handled.
82110275Ssam  */
82210275Ssam receive_data(instr, outstr)
82310275Ssam 	FILE *instr, *outstr;
82410275Ssam {
82510275Ssam 	register int c;
82638134Srick 	int cnt, bare_lfs = 0;
82710275Ssam 	char buf[BUFSIZ];
82810275Ssam 
82926044Sminshall 	transflag++;
83026044Sminshall 	if (setjmp(urgcatch)) {
83126044Sminshall 		transflag = 0;
83236933Skarels 		return (-1);
83326044Sminshall 	}
83410275Ssam 	switch (type) {
83510275Ssam 
83610275Ssam 	case TYPE_I:
83710275Ssam 	case TYPE_L:
83826044Sminshall 		while ((cnt = read(fileno(instr), buf, sizeof buf)) > 0) {
83936620Srick 			if (write(fileno(outstr), buf, cnt) != cnt)
84036933Skarels 				goto file_err;
84136933Skarels 			byte_count += cnt;
84226044Sminshall 		}
84336933Skarels 		if (cnt < 0)
84436620Srick 			goto data_err;
84526044Sminshall 		transflag = 0;
84636933Skarels 		return (0);
84710275Ssam 
84810275Ssam 	case TYPE_E:
84927106Smckusick 		reply(553, "TYPE E not implemented.");
85026044Sminshall 		transflag = 0;
85127106Smckusick 		return (-1);
85210275Ssam 
85310275Ssam 	case TYPE_A:
85410275Ssam 		while ((c = getc(instr)) != EOF) {
85536933Skarels 			byte_count++;
85638134Srick 			if (c == '\n')
85738134Srick 				bare_lfs++;
85827750Sminshall 			while (c == '\r') {
85936933Skarels 				if (ferror(outstr))
86036620Srick 					goto data_err;
86136933Skarels 				if ((c = getc(instr)) != '\n') {
86227750Sminshall 					(void) putc ('\r', outstr);
86336933Skarels 					if (c == '\0' || c == EOF)
86436933Skarels 						goto contin2;
86536933Skarels 				}
86610275Ssam 			}
86736933Skarels 			(void) putc(c, outstr);
86836933Skarels 	contin2:	;
86910275Ssam 		}
87036620Srick 		fflush(outstr);
87136933Skarels 		if (ferror(instr))
87236620Srick 			goto data_err;
87336933Skarels 		if (ferror(outstr))
87436933Skarels 			goto file_err;
87526044Sminshall 		transflag = 0;
87638134Srick 		if (bare_lfs) {
87738134Srick 			lreply(230, "WARNING! %d bare linefeeds received in ASCII mode", bare_lfs);
87838134Srick 			printf("   File may not have transferred correctly.\r\n");
87938134Srick 		}
88010275Ssam 		return (0);
88136620Srick 	default:
88236620Srick 		reply(550, "Unimplemented TYPE %d in receive_data", type);
88336620Srick 		transflag = 0;
88436933Skarels 		return (-1);
88510275Ssam 	}
88636620Srick 
88736620Srick data_err:
88826044Sminshall 	transflag = 0;
88936933Skarels 	perror_reply(426, "Data Connection");
89036933Skarels 	return (-1);
89136933Skarels 
89236933Skarels file_err:
89336933Skarels 	transflag = 0;
89436933Skarels 	perror_reply(452, "Error writing file");
89536933Skarels 	return (-1);
89610275Ssam }
89710275Ssam 
89836933Skarels statfilecmd(filename)
89936933Skarels 	char *filename;
90036933Skarels {
90136933Skarels 	char line[BUFSIZ];
90236933Skarels 	FILE *fin;
90336933Skarels 	int c;
90436933Skarels 
90536933Skarels 	(void) sprintf(line, "/bin/ls -lgA %s", filename);
90636933Skarels 	fin = ftpd_popen(line, "r");
90736933Skarels 	lreply(211, "status of %s:", filename);
90836933Skarels 	while ((c = getc(fin)) != EOF) {
90936933Skarels 		if (c == '\n') {
91036933Skarels 			if (ferror(stdout)){
91136933Skarels 				perror_reply(421, "control connection");
91236933Skarels 				(void) ftpd_pclose(fin);
91336933Skarels 				dologout(1);
91436933Skarels 				/* NOTREACHED */
91536933Skarels 			}
91636933Skarels 			if (ferror(fin)) {
91736933Skarels 				perror_reply(551, filename);
91836933Skarels 				(void) ftpd_pclose(fin);
91936933Skarels 				return;
92036933Skarels 			}
92136933Skarels 			(void) putc('\r', stdout);
92236933Skarels 		}
92336933Skarels 		(void) putc(c, stdout);
92436933Skarels 	}
92536933Skarels 	(void) ftpd_pclose(fin);
92636933Skarels 	reply(211, "End of Status");
92736933Skarels }
92836933Skarels 
92936933Skarels statcmd()
93036933Skarels {
93136933Skarels 	struct sockaddr_in *sin;
93236933Skarels 	u_char *a, *p;
93336933Skarels 
93436933Skarels 	lreply(211, "%s FTP server status:", hostname, version);
93536933Skarels 	printf("     %s\r\n", version);
93636933Skarels 	printf("     Connected to %s", remotehost);
93738134Srick 	if (!isdigit(remotehost[0]))
93836933Skarels 		printf(" (%s)", inet_ntoa(his_addr.sin_addr));
93936933Skarels 	printf("\r\n");
94036933Skarels 	if (logged_in) {
94136933Skarels 		if (guest)
94236933Skarels 			printf("     Logged in anonymously\r\n");
94336933Skarels 		else
94436933Skarels 			printf("     Logged in as %s\r\n", pw->pw_name);
94536933Skarels 	} else if (askpasswd)
94636933Skarels 		printf("     Waiting for password\r\n");
94736933Skarels 	else
94836933Skarels 		printf("     Waiting for user name\r\n");
94936933Skarels 	printf("     TYPE: %s", typenames[type]);
95036933Skarels 	if (type == TYPE_A || type == TYPE_E)
95136933Skarels 		printf(", FORM: %s", formnames[form]);
95236933Skarels 	if (type == TYPE_L)
95336933Skarels #if NBBY == 8
95436933Skarels 		printf(" %d", NBBY);
95536933Skarels #else
95636933Skarels 		printf(" %d", bytesize);	/* need definition! */
95736933Skarels #endif
95836933Skarels 	printf("; STRUcture: %s; transfer MODE: %s\r\n",
95936933Skarels 	    strunames[stru], modenames[mode]);
96036933Skarels 	if (data != -1)
96136933Skarels 		printf("     Data connection open\r\n");
96236933Skarels 	else if (pdata != -1) {
96336933Skarels 		printf("     in Passive mode");
96436933Skarels 		sin = &pasv_addr;
96536933Skarels 		goto printaddr;
96636933Skarels 	} else if (usedefault == 0) {
96736933Skarels 		printf("     PORT");
96836933Skarels 		sin = &data_dest;
96936933Skarels printaddr:
97036933Skarels 		a = (u_char *) &sin->sin_addr;
97136933Skarels 		p = (u_char *) &sin->sin_port;
97236933Skarels #define UC(b) (((int) b) & 0xff)
97336933Skarels 		printf(" (%d,%d,%d,%d,%d,%d)\r\n", UC(a[0]),
97436933Skarels 			UC(a[1]), UC(a[2]), UC(a[3]), UC(p[0]), UC(p[1]));
97536933Skarels #undef UC
97636933Skarels 	} else
97736933Skarels 		printf("     No data connection\r\n");
97836933Skarels 	reply(211, "End of status");
97936933Skarels }
98036933Skarels 
98110275Ssam fatal(s)
98210275Ssam 	char *s;
98310275Ssam {
98410275Ssam 	reply(451, "Error in server: %s\n", s);
98510275Ssam 	reply(221, "Closing connection due to server error.");
98613247Ssam 	dologout(0);
98736620Srick 	/* NOTREACHED */
98810275Ssam }
98910275Ssam 
99036446Sbostic /* VARARGS2 */
99136446Sbostic reply(n, fmt, p0, p1, p2, p3, p4, p5)
99210275Ssam 	int n;
99336446Sbostic 	char *fmt;
99410275Ssam {
99510275Ssam 	printf("%d ", n);
99636446Sbostic 	printf(fmt, p0, p1, p2, p3, p4, p5);
99710275Ssam 	printf("\r\n");
99836435Sbostic 	(void)fflush(stdout);
99910275Ssam 	if (debug) {
100026493Sminshall 		syslog(LOG_DEBUG, "<--- %d ", n);
100136446Sbostic 		syslog(LOG_DEBUG, fmt, p0, p1, p2, p3, p4, p5);
100210275Ssam }
100336620Srick }
100410275Ssam 
100536446Sbostic /* VARARGS2 */
100636446Sbostic lreply(n, fmt, p0, p1, p2, p3, p4, p5)
100710275Ssam 	int n;
100836446Sbostic 	char *fmt;
100910275Ssam {
101036446Sbostic 	printf("%d- ", n);
101136446Sbostic 	printf(fmt, p0, p1, p2, p3, p4, p5);
101236446Sbostic 	printf("\r\n");
101336435Sbostic 	(void)fflush(stdout);
101436446Sbostic 	if (debug) {
101536446Sbostic 		syslog(LOG_DEBUG, "<--- %d- ", n);
101636446Sbostic 		syslog(LOG_DEBUG, fmt, p0, p1, p2, p3, p4, p5);
101736446Sbostic 	}
101810275Ssam }
101910275Ssam 
102010275Ssam ack(s)
102110275Ssam 	char *s;
102210275Ssam {
102327106Smckusick 	reply(250, "%s command successful.", s);
102410275Ssam }
102510275Ssam 
102610275Ssam nack(s)
102710275Ssam 	char *s;
102810275Ssam {
102910275Ssam 	reply(502, "%s command not implemented.", s);
103010275Ssam }
103110275Ssam 
103236304Skarels /* ARGSUSED */
103326493Sminshall yyerror(s)
103426493Sminshall 	char *s;
103510275Ssam {
103626044Sminshall 	char *cp;
103726044Sminshall 
103836551Sbostic 	if (cp = index(cbuf,'\n'))
103936551Sbostic 		*cp = '\0';
104036933Skarels 	reply(500, "'%s': command not understood.", cbuf);
104110275Ssam }
104210275Ssam 
104310275Ssam delete(name)
104410275Ssam 	char *name;
104510275Ssam {
104610275Ssam 	struct stat st;
104710275Ssam 
104810275Ssam 	if (stat(name, &st) < 0) {
104936304Skarels 		perror_reply(550, name);
105010275Ssam 		return;
105110275Ssam 	}
105210275Ssam 	if ((st.st_mode&S_IFMT) == S_IFDIR) {
105310275Ssam 		if (rmdir(name) < 0) {
105436304Skarels 			perror_reply(550, name);
105510275Ssam 			return;
105610275Ssam 		}
105710275Ssam 		goto done;
105810275Ssam 	}
105910275Ssam 	if (unlink(name) < 0) {
106036304Skarels 		perror_reply(550, name);
106110275Ssam 		return;
106210275Ssam 	}
106310275Ssam done:
106410275Ssam 	ack("DELE");
106510275Ssam }
106610275Ssam 
106710275Ssam cwd(path)
106810275Ssam 	char *path;
106910275Ssam {
107036620Srick 	if (chdir(path) < 0)
107136304Skarels 		perror_reply(550, path);
107236620Srick 	else
107336620Srick 		ack("CWD");
107410275Ssam }
107510275Ssam 
107610303Ssam makedir(name)
107710275Ssam 	char *name;
107810275Ssam {
107936276Sbostic 	if (mkdir(name, 0777) < 0)
108036304Skarels 		perror_reply(550, name);
108136276Sbostic 	else
108236276Sbostic 		reply(257, "MKD command successful.");
108310275Ssam }
108410275Ssam 
108510303Ssam removedir(name)
108610275Ssam 	char *name;
108710275Ssam {
108836620Srick 	if (rmdir(name) < 0)
108936304Skarels 		perror_reply(550, name);
109036620Srick 	else
109136620Srick 		ack("RMD");
109210275Ssam }
109310275Ssam 
109410303Ssam pwd()
109510275Ssam {
109610303Ssam 	char path[MAXPATHLEN + 1];
109736304Skarels 	extern char *getwd();
109810275Ssam 
109936620Srick 	if (getwd(path) == (char *)NULL)
110027106Smckusick 		reply(550, "%s.", path);
110136620Srick 	else
110236620Srick 		reply(257, "\"%s\" is current directory.", path);
110310275Ssam }
110410275Ssam 
110510275Ssam char *
110610275Ssam renamefrom(name)
110710275Ssam 	char *name;
110810275Ssam {
110910275Ssam 	struct stat st;
111010275Ssam 
111110275Ssam 	if (stat(name, &st) < 0) {
111236304Skarels 		perror_reply(550, name);
111310275Ssam 		return ((char *)0);
111410275Ssam 	}
111510303Ssam 	reply(350, "File exists, ready for destination name");
111610275Ssam 	return (name);
111710275Ssam }
111810275Ssam 
111910275Ssam renamecmd(from, to)
112010275Ssam 	char *from, *to;
112110275Ssam {
112236620Srick 	if (rename(from, to) < 0)
112336304Skarels 		perror_reply(550, "rename");
112436620Srick 	else
112536620Srick 		ack("RNTO");
112610275Ssam }
112710275Ssam 
112810275Ssam dolog(sin)
112910275Ssam 	struct sockaddr_in *sin;
113010275Ssam {
113136304Skarels 	struct hostent *hp = gethostbyaddr((char *)&sin->sin_addr,
113210275Ssam 		sizeof (struct in_addr), AF_INET);
113336304Skarels 	time_t t, time();
113426493Sminshall 	extern char *ctime();
113510275Ssam 
113636304Skarels 	if (hp)
113726493Sminshall 		(void) strncpy(remotehost, hp->h_name, sizeof (remotehost));
113836304Skarels 	else
113926493Sminshall 		(void) strncpy(remotehost, inet_ntoa(sin->sin_addr),
114013247Ssam 		    sizeof (remotehost));
114136620Srick #ifdef SETPROCTITLE
114236933Skarels 	sprintf(proctitle, "%s: connected", remotehost);
114336933Skarels 	setproctitle(proctitle);
114436620Srick #endif /* SETPROCTITLE */
114536933Skarels 
114636933Skarels 	if (logging) {
114736933Skarels 		t = time((time_t *) 0);
114836933Skarels 		syslog(LOG_INFO, "connection from %s at %s",
114936933Skarels 		    remotehost, ctime(&t));
115036933Skarels 	}
115110275Ssam }
115210695Ssam 
115310695Ssam /*
115413247Ssam  * Record logout in wtmp file
115513247Ssam  * and exit with supplied status.
115613247Ssam  */
115713247Ssam dologout(status)
115813247Ssam 	int status;
115913247Ssam {
116017580Ssam 	if (logged_in) {
116136304Skarels 		(void) seteuid((uid_t)0);
116235672Sbostic 		logwtmp(ttyline, "", "");
116313247Ssam 	}
116414436Ssam 	/* beware of flushing buffers after a SIGPIPE */
116514436Ssam 	_exit(status);
116613247Ssam }
116713247Ssam 
116846669Sbostic void
116926044Sminshall myoob()
117026044Sminshall {
117127750Sminshall 	char *cp;
117226044Sminshall 
117327750Sminshall 	/* only process if transfer occurring */
117436304Skarels 	if (!transflag)
117526044Sminshall 		return;
117627750Sminshall 	cp = tmpline;
117727750Sminshall 	if (getline(cp, 7, stdin) == NULL) {
117836304Skarels 		reply(221, "You could at least say goodbye.");
117927750Sminshall 		dologout(0);
118026044Sminshall 	}
118126044Sminshall 	upper(cp);
118236933Skarels 	if (strcmp(cp, "ABOR\r\n") == 0) {
118336933Skarels 		tmpline[0] = '\0';
118436933Skarels 		reply(426, "Transfer aborted. Data connection closed.");
118536933Skarels 		reply(226, "Abort successful");
118636933Skarels 		longjmp(urgcatch, 1);
118736933Skarels 	}
118836933Skarels 	if (strcmp(cp, "STAT\r\n") == 0) {
118936933Skarels 		if (file_size != (off_t) -1)
119036933Skarels 			reply(213, "Status: %lu of %lu bytes transferred",
119136933Skarels 			    byte_count, file_size);
119236933Skarels 		else
119336933Skarels 			reply(213, "Status: %lu bytes transferred", byte_count);
119436933Skarels 	}
119526044Sminshall }
119626044Sminshall 
119727106Smckusick /*
119836620Srick  * Note: a response of 425 is not mentioned as a possible response to
119936620Srick  * 	the PASV command in RFC959. However, it has been blessed as
120036620Srick  * 	a legitimate response by Jon Postel in a telephone conversation
120136620Srick  *	with Rick Adams on 25 Jan 89.
120227106Smckusick  */
120326044Sminshall passive()
120426044Sminshall {
120526044Sminshall 	int len;
120626044Sminshall 	register char *p, *a;
120726044Sminshall 
120826044Sminshall 	pdata = socket(AF_INET, SOCK_STREAM, 0);
120926044Sminshall 	if (pdata < 0) {
121036620Srick 		perror_reply(425, "Can't open passive connection");
121126044Sminshall 		return;
121226044Sminshall 	}
121336933Skarels 	pasv_addr = ctrl_addr;
121436933Skarels 	pasv_addr.sin_port = 0;
121536304Skarels 	(void) seteuid((uid_t)0);
121636933Skarels 	if (bind(pdata, (struct sockaddr *)&pasv_addr, sizeof(pasv_addr)) < 0) {
121736304Skarels 		(void) seteuid((uid_t)pw->pw_uid);
121836620Srick 		goto pasv_error;
121926044Sminshall 	}
122036304Skarels 	(void) seteuid((uid_t)pw->pw_uid);
122136933Skarels 	len = sizeof(pasv_addr);
122236933Skarels 	if (getsockname(pdata, (struct sockaddr *) &pasv_addr, &len) < 0)
122336620Srick 		goto pasv_error;
122436620Srick 	if (listen(pdata, 1) < 0)
122536620Srick 		goto pasv_error;
122636933Skarels 	a = (char *) &pasv_addr.sin_addr;
122736933Skarels 	p = (char *) &pasv_addr.sin_port;
122826044Sminshall 
122926044Sminshall #define UC(b) (((int) b) & 0xff)
123026044Sminshall 
123126044Sminshall 	reply(227, "Entering Passive Mode (%d,%d,%d,%d,%d,%d)", UC(a[0]),
123226044Sminshall 		UC(a[1]), UC(a[2]), UC(a[3]), UC(p[0]), UC(p[1]));
123336620Srick 	return;
123436620Srick 
123536620Srick pasv_error:
123636620Srick 	(void) close(pdata);
123736620Srick 	pdata = -1;
123836620Srick 	perror_reply(425, "Can't open passive connection");
123936620Srick 	return;
124026044Sminshall }
124126044Sminshall 
124236304Skarels /*
124336304Skarels  * Generate unique name for file with basename "local".
124436304Skarels  * The file named "local" is already known to exist.
124536304Skarels  * Generates failure reply on error.
124636304Skarels  */
124726044Sminshall char *
124826044Sminshall gunique(local)
124926044Sminshall 	char *local;
125026044Sminshall {
125126044Sminshall 	static char new[MAXPATHLEN];
125236304Skarels 	struct stat st;
125326044Sminshall 	char *cp = rindex(local, '/');
125436933Skarels 	int count = 0;
125526044Sminshall 
125636304Skarels 	if (cp)
125726044Sminshall 		*cp = '\0';
125836620Srick 	if (stat(cp ? local : ".", &st) < 0) {
125936933Skarels 		perror_reply(553, cp ? local : ".");
126026044Sminshall 		return((char *) 0);
126126044Sminshall 	}
126236620Srick 	if (cp)
126336620Srick 		*cp = '/';
126426044Sminshall 	(void) strcpy(new, local);
126526044Sminshall 	cp = new + strlen(new);
126626044Sminshall 	*cp++ = '.';
126736304Skarels 	for (count = 1; count < 100; count++) {
126836304Skarels 		(void) sprintf(cp, "%d", count);
126936304Skarels 		if (stat(new, &st) < 0)
127036304Skarels 			return(new);
127126044Sminshall 	}
127236304Skarels 	reply(452, "Unique file name cannot be created.");
127336304Skarels 	return((char *) 0);
127426044Sminshall }
127536304Skarels 
127636304Skarels /*
127736304Skarels  * Format and send reply containing system error number.
127836304Skarels  */
127936304Skarels perror_reply(code, string)
128036304Skarels 	int code;
128136304Skarels 	char *string;
128236304Skarels {
128342412Sbostic 	reply(code, "%s: %s.", string, strerror(errno));
128436304Skarels }
128536620Srick 
128636620Srick static char *onefile[] = {
128736620Srick 	"",
128836620Srick 	0
128936620Srick };
129036620Srick 
129136620Srick send_file_list(whichfiles)
129236620Srick 	char *whichfiles;
129336620Srick {
129436620Srick 	struct stat st;
129536620Srick 	DIR *dirp = NULL;
129646669Sbostic 	struct dirent *dir;
129736620Srick 	FILE *dout = NULL;
129836620Srick 	register char **dirlist, *dirname;
129937459Skarels 	int simple = 0;
130036620Srick 	char *strpbrk();
130136620Srick 
130236620Srick 	if (strpbrk(whichfiles, "~{[*?") != NULL) {
130346669Sbostic 		extern char **ftpglob(), *globerr;
130436933Skarels 
130536620Srick 		globerr = NULL;
130646669Sbostic 		dirlist = ftpglob(whichfiles);
130736620Srick 		if (globerr != NULL) {
130836620Srick 			reply(550, globerr);
130936620Srick 			return;
131036620Srick 		} else if (dirlist == NULL) {
131136620Srick 			errno = ENOENT;
131236620Srick 			perror_reply(550, whichfiles);
131336620Srick 			return;
131436620Srick 		}
131536620Srick 	} else {
131636620Srick 		onefile[0] = whichfiles;
131736620Srick 		dirlist = onefile;
131837459Skarels 		simple = 1;
131936620Srick 	}
132036933Skarels 
132136933Skarels 	if (setjmp(urgcatch)) {
132236933Skarels 		transflag = 0;
132336933Skarels 		return;
132436933Skarels 	}
132536620Srick 	while (dirname = *dirlist++) {
132636620Srick 		if (stat(dirname, &st) < 0) {
132736933Skarels 			/*
132836933Skarels 			 * If user typed "ls -l", etc, and the client
132936933Skarels 			 * used NLST, do what the user meant.
133036933Skarels 			 */
133136933Skarels 			if (dirname[0] == '-' && *dirlist == NULL &&
133236933Skarels 			    transflag == 0) {
133336933Skarels 				retrieve("/bin/ls %s", dirname);
133436933Skarels 				return;
133536933Skarels 			}
133636620Srick 			perror_reply(550, whichfiles);
133736620Srick 			if (dout != NULL) {
133836620Srick 				(void) fclose(dout);
133936933Skarels 				transflag = 0;
134036620Srick 				data = -1;
134136620Srick 				pdata = -1;
134236620Srick 			}
134336620Srick 			return;
134436620Srick 		}
134536933Skarels 
134636620Srick 		if ((st.st_mode&S_IFMT) == S_IFREG) {
134736620Srick 			if (dout == NULL) {
134837459Skarels 				dout = dataconn("file list", (off_t)-1, "w");
134936620Srick 				if (dout == NULL)
135036620Srick 					return;
135136933Skarels 				transflag++;
135236620Srick 			}
135338158Srick 			fprintf(dout, "%s%s\n", dirname,
135438158Srick 				type == TYPE_A ? "\r" : "");
135536933Skarels 			byte_count += strlen(dirname) + 1;
135636620Srick 			continue;
135736933Skarels 		} else if ((st.st_mode&S_IFMT) != S_IFDIR)
135836620Srick 			continue;
135936620Srick 
136036620Srick 		if ((dirp = opendir(dirname)) == NULL)
136136620Srick 			continue;
136236620Srick 
136336620Srick 		while ((dir = readdir(dirp)) != NULL) {
136436933Skarels 			char nbuf[MAXPATHLEN];
136536620Srick 
136636620Srick 			if (dir->d_name[0] == '.' && dir->d_namlen == 1)
136736620Srick 				continue;
136836933Skarels 			if (dir->d_name[0] == '.' && dir->d_name[1] == '.' &&
136936933Skarels 			    dir->d_namlen == 2)
137036620Srick 				continue;
137136620Srick 
137236933Skarels 			sprintf(nbuf, "%s/%s", dirname, dir->d_name);
137336933Skarels 
137436620Srick 			/*
137536933Skarels 			 * We have to do a stat to insure it's
137636933Skarels 			 * not a directory or special file.
137736620Srick 			 */
137837459Skarels 			if (simple || (stat(nbuf, &st) == 0 &&
137937459Skarels 			    (st.st_mode&S_IFMT) == S_IFREG)) {
138036620Srick 				if (dout == NULL) {
138137459Skarels 					dout = dataconn("file list", (off_t)-1,
138236620Srick 						"w");
138336620Srick 					if (dout == NULL)
138436620Srick 						return;
138536933Skarels 					transflag++;
138636620Srick 				}
138736620Srick 				if (nbuf[0] == '.' && nbuf[1] == '/')
138838158Srick 					fprintf(dout, "%s%s\n", &nbuf[2],
138938158Srick 						type == TYPE_A ? "\r" : "");
139036620Srick 				else
139138158Srick 					fprintf(dout, "%s%s\n", nbuf,
139238158Srick 						type == TYPE_A ? "\r" : "");
139336933Skarels 				byte_count += strlen(nbuf) + 1;
139436620Srick 			}
139536620Srick 		}
139636620Srick 		(void) closedir(dirp);
139736620Srick 	}
139836620Srick 
139936933Skarels 	if (dout == NULL)
140036933Skarels 		reply(550, "No files found.");
140136933Skarels 	else if (ferror(dout) != 0)
140236933Skarels 		perror_reply(550, "Data connection");
140336933Skarels 	else
140436620Srick 		reply(226, "Transfer complete.");
140536620Srick 
140636933Skarels 	transflag = 0;
140736933Skarels 	if (dout != NULL)
140836620Srick 		(void) fclose(dout);
140936620Srick 	data = -1;
141036620Srick 	pdata = -1;
141136620Srick }
141236620Srick 
141336620Srick #ifdef SETPROCTITLE
141436620Srick /*
141536620Srick  * clobber argv so ps will show what we're doing.
141636620Srick  * (stolen from sendmail)
141736620Srick  * warning, since this is usually started from inetd.conf, it
141836620Srick  * often doesn't have much of an environment or arglist to overwrite.
141936620Srick  */
142036620Srick 
142136620Srick /*VARARGS1*/
142236620Srick setproctitle(fmt, a, b, c)
142336620Srick char *fmt;
142436620Srick {
142536620Srick 	register char *p, *bp, ch;
142636620Srick 	register int i;
142736620Srick 	char buf[BUFSIZ];
142836620Srick 
142936620Srick 	(void) sprintf(buf, fmt, a, b, c);
143036620Srick 
143136620Srick 	/* make ps print our process name */
143236620Srick 	p = Argv[0];
143336620Srick 	*p++ = '-';
143436620Srick 
143536620Srick 	i = strlen(buf);
143636620Srick 	if (i > LastArgv - p - 2) {
143736620Srick 		i = LastArgv - p - 2;
143836620Srick 		buf[i] = '\0';
143936620Srick 	}
144036620Srick 	bp = buf;
144136620Srick 	while (ch = *bp++)
144236620Srick 		if (ch != '\n' && ch != '\r')
144336620Srick 			*p++ = ch;
144436620Srick 	while (p < LastArgv)
144536620Srick 		*p++ = ' ';
144636620Srick }
144736620Srick #endif /* SETPROCTITLE */
1448