xref: /csrg-svn/libexec/ftpd/ftpd.c (revision 36933)
122499Sdist /*
236304Skarels  * Copyright (c) 1985, 1988 Regents of the University of California.
333738Sbostic  * All rights reserved.
433738Sbostic  *
533738Sbostic  * Redistribution and use in source and binary forms are permitted
634769Sbostic  * provided that the above copyright notice and this paragraph are
734769Sbostic  * duplicated in all such forms and that any documentation,
834769Sbostic  * advertising materials, and other materials related to such
934769Sbostic  * distribution and use acknowledge that the software was developed
1034769Sbostic  * by the University of California, Berkeley.  The name of the
1134769Sbostic  * University may not be used to endorse or promote products derived
1234769Sbostic  * from this software without specific prior written permission.
1334769Sbostic  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
1434769Sbostic  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
15*36933Skarels  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
1622499Sdist  */
1722499Sdist 
1810275Ssam #ifndef lint
1922499Sdist char copyright[] =
2036304Skarels "@(#) Copyright (c) 1985, 1988 Regents of the University of California.\n\
2122499Sdist  All rights reserved.\n";
2233738Sbostic #endif /* not lint */
2310275Ssam 
2422499Sdist #ifndef lint
25*36933Skarels static char sccsid[] = "@(#)ftpd.c	5.27	(Berkeley) 02/28/89";
2633738Sbostic #endif /* not lint */
2722499Sdist 
2810275Ssam /*
2910275Ssam  * FTP server.
3010275Ssam  */
3110303Ssam #include <sys/param.h>
3210275Ssam #include <sys/stat.h>
3310275Ssam #include <sys/ioctl.h>
3410275Ssam #include <sys/socket.h>
3513247Ssam #include <sys/file.h>
3613595Ssam #include <sys/wait.h>
3736620Srick #include <sys/dir.h>
3810275Ssam 
3910275Ssam #include <netinet/in.h>
4010275Ssam 
41*36933Skarels #define	FTP_NAMES
4213034Ssam #include <arpa/ftp.h>
4313211Sroot #include <arpa/inet.h>
4426044Sminshall #include <arpa/telnet.h>
4513034Ssam 
46*36933Skarels #include <ctype.h>
4710275Ssam #include <stdio.h>
4810275Ssam #include <signal.h>
4910275Ssam #include <pwd.h>
5010275Ssam #include <setjmp.h>
5110275Ssam #include <netdb.h>
5210423Ssam #include <errno.h>
5326044Sminshall #include <strings.h>
5426493Sminshall #include <syslog.h>
5536435Sbostic #include <varargs.h>
5610275Ssam 
5710695Ssam /*
5810695Ssam  * File containing login names
5910695Ssam  * NOT to be used on this machine.
6010695Ssam  * Commonly used to disallow uucp.
6110695Ssam  */
6210695Ssam #define	FTPUSERS	"/etc/ftpusers"
6310695Ssam 
6410275Ssam extern	int errno;
6510275Ssam extern	char *sys_errlist[];
6636304Skarels extern	int sys_nerr;
6710275Ssam extern	char *crypt();
6810275Ssam extern	char version[];
6910275Ssam extern	char *home;		/* pointer to home directory for glob */
7036276Sbostic extern	FILE *ftpd_popen(), *fopen(), *freopen();
7136304Skarels extern	int  ftpd_pclose(), fclose();
7226044Sminshall extern	char *getline();
7326044Sminshall extern	char cbuf[];
7436551Sbostic extern	off_t restart_point;
7510275Ssam 
7610275Ssam struct	sockaddr_in ctrl_addr;
7710275Ssam struct	sockaddr_in data_source;
7810275Ssam struct	sockaddr_in data_dest;
7910275Ssam struct	sockaddr_in his_addr;
80*36933Skarels struct	sockaddr_in pasv_addr;
8110275Ssam 
8210275Ssam int	data;
8326044Sminshall jmp_buf	errcatch, urgcatch;
8410275Ssam int	logged_in;
8510275Ssam struct	passwd *pw;
8610275Ssam int	debug;
8726493Sminshall int	timeout = 900;    /* timeout after 15 minutes of inactivity */
88*36933Skarels int	maxtimeout = 7200;/* don't allow idle time to be set beyond 2 hours */
8911757Ssam int	logging;
9010275Ssam int	guest;
9110275Ssam int	type;
9210275Ssam int	form;
9310275Ssam int	stru;			/* avoid C keyword */
9410275Ssam int	mode;
9510321Ssam int	usedefault = 1;		/* for data transfers */
9636304Skarels int	pdata = -1;		/* for passive mode */
9726044Sminshall int	transflag;
98*36933Skarels off_t	file_size;
99*36933Skarels off_t	byte_count;
100*36933Skarels #if !defined(CMASK) || CMASK == 0
101*36933Skarels #undef CMASK
102*36933Skarels #define CMASK 027
103*36933Skarels #endif
104*36933Skarels int	defumask = CMASK;		/* default umask value */
10526044Sminshall char	tmpline[7];
10636276Sbostic char	hostname[MAXHOSTNAMELEN];
10736276Sbostic char	remotehost[MAXHOSTNAMELEN];
10810275Ssam 
10911653Ssam /*
11011653Ssam  * Timeout intervals for retrying connections
11111653Ssam  * to hosts that don't accept PORT cmds.  This
11211653Ssam  * is a kludge, but given the problems with TCP...
11311653Ssam  */
11411653Ssam #define	SWAITMAX	90	/* wait at most 90 seconds */
11511653Ssam #define	SWAITINT	5	/* interval between retries */
11611653Ssam 
11711653Ssam int	swaitmax = SWAITMAX;
11811653Ssam int	swaitint = SWAITINT;
11911653Ssam 
12010275Ssam int	lostconn();
12126044Sminshall int	myoob();
12210275Ssam FILE	*getdatasock(), *dataconn();
12310275Ssam 
12436620Srick #ifdef SETPROCTITLE
12536620Srick char	**Argv = NULL;		/* pointer to argument vector */
12636620Srick char	*LastArgv = NULL;	/* end of argv */
127*36933Skarels char	proctitle[BUFSIZ];	/* initial part of title */
12836620Srick #endif /* SETPROCTITLE */
12936620Srick 
13036620Srick main(argc, argv, envp)
13110275Ssam 	int argc;
13210275Ssam 	char *argv[];
13336620Srick 	char **envp;
13410275Ssam {
13527750Sminshall 	int addrlen, on = 1;
13610275Ssam 	char *cp;
13710275Ssam 
13816339Skarels 	addrlen = sizeof (his_addr);
13936304Skarels 	if (getpeername(0, (struct sockaddr *)&his_addr, &addrlen) < 0) {
14026493Sminshall 		syslog(LOG_ERR, "getpeername (%s): %m",argv[0]);
14110275Ssam 		exit(1);
14210275Ssam 	}
14316339Skarels 	addrlen = sizeof (ctrl_addr);
14436304Skarels 	if (getsockname(0, (struct sockaddr *)&ctrl_addr, &addrlen) < 0) {
14526493Sminshall 		syslog(LOG_ERR, "getsockname (%s): %m",argv[0]);
14616339Skarels 		exit(1);
14716339Skarels 	}
14816339Skarels 	data_source.sin_port = htons(ntohs(ctrl_addr.sin_port) - 1);
14910275Ssam 	debug = 0;
15026493Sminshall 	openlog("ftpd", LOG_PID, LOG_DAEMON);
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);
179*36933Skarels 			if (maxtimeout < timeout)
180*36933Skarels 				maxtimeout = timeout;
18111653Ssam 			goto nextopt;
18211653Ssam 
183*36933Skarels 		case 'T':
184*36933Skarels 			maxtimeout = atoi(++cp);
185*36933Skarels 			if (timeout > maxtimeout)
186*36933Skarels 				timeout = maxtimeout;
187*36933Skarels 			goto nextopt;
188*36933Skarels 
189*36933Skarels 		case 'u':
190*36933Skarels 		    {
191*36933Skarels 			int val = 0;
192*36933Skarels 
193*36933Skarels 			while (*++cp && *cp >= '0' && *cp <= '9')
194*36933Skarels 				val = val*8 + *cp - '0';
195*36933Skarels 			if (*cp)
196*36933Skarels 				fprintf(stderr, "ftpd: Bad value for -u\n");
197*36933Skarels 			else
198*36933Skarels 				defumask = val;
199*36933Skarels 			goto nextopt;
200*36933Skarels 		    }
201*36933Skarels 
20210275Ssam 		default:
20316339Skarels 			fprintf(stderr, "ftpd: Unknown flag -%c ignored.\n",
20416339Skarels 			     *cp);
20510275Ssam 			break;
20610275Ssam 		}
20711653Ssam nextopt:
20810275Ssam 		argc--, argv++;
20910275Ssam 	}
21030944Scsvsj 	(void) freopen("/dev/null", "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 
21627750Sminshall 	/* handle urgent data inline */
21736276Sbostic 	/* Sequent defines this, but it doesn't work */
21827750Sminshall #ifdef SO_OOBINLINE
21936276Sbostic 	if (setsockopt(0, SOL_SOCKET, SO_OOBINLINE, (char *)&on, sizeof(on)) < 0)
22027750Sminshall 		syslog(LOG_ERR, "setsockopt: %m");
22136276Sbostic #endif
222*36933Skarels #ifdef	F_SETOWN
22336304Skarels 	if (fcntl(fileno(stdin), F_SETOWN, getpid()) == -1)
22436304Skarels 		syslog(LOG_ERR, "fcntl F_SETOWN: %m");
225*36933Skarels #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 
24410275Ssam lostconn()
24510275Ssam {
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 *malloc();
26236185Sbostic 	char *new = malloc((unsigned) strlen(s) + 1);
263*36933Skarels 
26436185Sbostic 	if (new == NULL) {
26536620Srick 		perror_reply(421, "Local resource failure: malloc");
26636185Sbostic 		dologout(1);
26736620Srick 		/* NOTREACHED */
26836185Sbostic 	}
26936185Sbostic 	(void) strcpy(new, s);
27036185Sbostic 	return (new);
27136185Sbostic }
27236185Sbostic 
27336185Sbostic /*
27436185Sbostic  * Save the result of a getpwnam.  Used for USER command, since
27536185Sbostic  * the data returned must not be clobbered by any other command
27636185Sbostic  * (e.g., globbing).
27736185Sbostic  */
27836185Sbostic struct passwd *
27936185Sbostic sgetpwnam(name)
28036185Sbostic 	char *name;
28136185Sbostic {
28236185Sbostic 	static struct passwd save;
28336185Sbostic 	register struct passwd *p;
28436185Sbostic 	char *sgetsave();
28536185Sbostic 
28636185Sbostic 	if ((p = getpwnam(name)) == NULL)
28736185Sbostic 		return (p);
28836185Sbostic 	if (save.pw_name) {
28936185Sbostic 		free(save.pw_name);
29036185Sbostic 		free(save.pw_passwd);
29136185Sbostic 		free(save.pw_gecos);
29236185Sbostic 		free(save.pw_dir);
29336185Sbostic 		free(save.pw_shell);
29436185Sbostic 	}
29536185Sbostic 	save = *p;
29636185Sbostic 	save.pw_name = sgetsave(p->pw_name);
29736185Sbostic 	save.pw_passwd = sgetsave(p->pw_passwd);
29836185Sbostic 	save.pw_gecos = sgetsave(p->pw_gecos);
29936185Sbostic 	save.pw_dir = sgetsave(p->pw_dir);
30036185Sbostic 	save.pw_shell = sgetsave(p->pw_shell);
30136185Sbostic 	return (&save);
30236185Sbostic }
30336185Sbostic 
30436304Skarels int login_attempts;		/* number of failed login attempts */
30536304Skarels int askpasswd;			/* had user command, ask for passwd */
30636304Skarels 
30736304Skarels /*
30836304Skarels  * USER command.
30936304Skarels  * Sets global passwd pointer pw if named account exists
31036304Skarels  * and is acceptable; sets askpasswd if a PASS command is
31136304Skarels  * expected. If logged in previously, need to reset state.
31236304Skarels  * If name is "ftp" or "anonymous" and ftp account exists,
31336304Skarels  * set guest and pw, then just return.
31436304Skarels  * If account doesn't exist, ask for passwd anyway.
31536304Skarels  * Otherwise, check user requesting login privileges.
31636304Skarels  * Disallow anyone who does not have a standard
31736304Skarels  * shell returned by getusershell() (/etc/shells).
31836304Skarels  * Disallow anyone mentioned in the file FTPUSERS
31936304Skarels  * to allow people such as root and uucp to be avoided.
32036304Skarels  */
32136304Skarels user(name)
32236304Skarels 	char *name;
32336304Skarels {
32436304Skarels 	register char *cp;
32536304Skarels 	FILE *fd;
32636304Skarels 	char *shell;
32736551Sbostic 	char line[BUFSIZ], *getusershell();
32836304Skarels 
32936304Skarels 	if (logged_in) {
33036304Skarels 		if (guest) {
33136304Skarels 			reply(530, "Can't change user from guest login.");
33236304Skarels 			return;
33336304Skarels 		}
33436304Skarels 		end_login();
33536304Skarels 	}
33636304Skarels 
33736304Skarels 	guest = 0;
33836304Skarels 	if (strcmp(name, "ftp") == 0 || strcmp(name, "anonymous") == 0) {
33936304Skarels 		if ((pw = sgetpwnam("ftp")) != NULL) {
34036304Skarels 			guest = 1;
34136304Skarels 			askpasswd = 1;
34236304Skarels 			reply(331, "Guest login ok, send ident as password.");
343*36933Skarels 		} else
34436304Skarels 			reply(530, "User %s unknown.", name);
34536304Skarels 		return;
34636304Skarels 	}
34736304Skarels 	if (pw = sgetpwnam(name)) {
34836304Skarels 		if ((shell = pw->pw_shell) == NULL || *shell == 0)
34936304Skarels 			shell = "/bin/sh";
35036304Skarels 		while ((cp = getusershell()) != NULL)
35136304Skarels 			if (strcmp(cp, shell) == 0)
35236304Skarels 				break;
35336304Skarels 		endusershell();
35436304Skarels 		if (cp == NULL) {
35536304Skarels 			reply(530, "User %s access denied.", name);
356*36933Skarels 			if (logging)
357*36933Skarels 				syslog(LOG_NOTICE,
358*36933Skarels 				    "FTP LOGIN REFUSED FROM %s, %s",
359*36933Skarels 				    remotehost, name);
36036304Skarels 			pw = (struct passwd *) NULL;
36136304Skarels 			return;
36236304Skarels 		}
36336304Skarels 		if ((fd = fopen(FTPUSERS, "r")) != NULL) {
36436304Skarels 		    while (fgets(line, sizeof (line), fd) != NULL) {
36536304Skarels 			if ((cp = index(line, '\n')) != NULL)
36636304Skarels 				*cp = '\0';
36736304Skarels 			if (strcmp(line, name) == 0) {
36836304Skarels 				reply(530, "User %s access denied.", name);
369*36933Skarels 				if (logging)
370*36933Skarels 					syslog(LOG_NOTICE,
371*36933Skarels 					    "FTP LOGIN REFUSED FROM %s, %s",
372*36933Skarels 					    remotehost, name);
37336304Skarels 				pw = (struct passwd *) NULL;
37436304Skarels 				return;
37536304Skarels 			}
37636304Skarels 		    }
37736304Skarels 		}
37836304Skarels 		(void) fclose(fd);
37936304Skarels 	}
38036304Skarels 	reply(331, "Password required for %s.", name);
38136304Skarels 	askpasswd = 1;
38236304Skarels 	/*
38336304Skarels 	 * Delay before reading passwd after first failed
38436304Skarels 	 * attempt to slow down passwd-guessing programs.
38536304Skarels 	 */
38636304Skarels 	if (login_attempts)
38736304Skarels 		sleep((unsigned) login_attempts);
38836304Skarels }
38936304Skarels 
39036304Skarels /*
39136304Skarels  * Terminate login as previous user, if any, resetting state;
39236304Skarels  * used when USER command is given or login fails.
39336304Skarels  */
39436304Skarels end_login()
39536304Skarels {
39636304Skarels 
39736304Skarels 	(void) seteuid((uid_t)0);
39836304Skarels 	if (logged_in)
39936304Skarels 		logwtmp(ttyline, "", "");
40036304Skarels 	pw = NULL;
40136304Skarels 	logged_in = 0;
40236304Skarels 	guest = 0;
40336304Skarels }
40436304Skarels 
40510275Ssam pass(passwd)
40610275Ssam 	char *passwd;
40710275Ssam {
40836304Skarels 	char *xpasswd, *salt;
40910275Ssam 
41036304Skarels 	if (logged_in || askpasswd == 0) {
41110275Ssam 		reply(503, "Login with USER first.");
41210275Ssam 		return;
41310275Ssam 	}
41436304Skarels 	askpasswd = 0;
41510275Ssam 	if (!guest) {		/* "ftp" is only account allowed no password */
41636304Skarels 		if (pw == NULL)
41736304Skarels 			salt = "xx";
41836304Skarels 		else
41936304Skarels 			salt = pw->pw_passwd;
42036304Skarels 		xpasswd = crypt(passwd, salt);
42116760Slepreau 		/* The strcmp does not catch null passwords! */
42236304Skarels 		if (pw == NULL || *pw->pw_passwd == '\0' ||
42336304Skarels 		    strcmp(xpasswd, pw->pw_passwd)) {
42410275Ssam 			reply(530, "Login incorrect.");
42510275Ssam 			pw = NULL;
42636304Skarels 			if (login_attempts++ >= 5) {
427*36933Skarels 				syslog(LOG_NOTICE,
42836304Skarels 				    "repeated login failures from %s",
42936304Skarels 				    remotehost);
43036304Skarels 				exit(0);
43136304Skarels 			}
43210275Ssam 			return;
43310275Ssam 		}
43410275Ssam 	}
43536304Skarels 	login_attempts = 0;		/* this time successful */
43636304Skarels 	(void) setegid((gid_t)pw->pw_gid);
43736304Skarels 	(void) initgroups(pw->pw_name, pw->pw_gid);
43816033Sralph 
43936192Sbostic 	/* open wtmp before chroot */
44036192Sbostic 	(void)sprintf(ttyline, "ftp%d", getpid());
44136192Sbostic 	logwtmp(ttyline, pw->pw_name, remotehost);
44236192Sbostic 	logged_in = 1;
44336192Sbostic 
44436446Sbostic 	if (guest) {
44536620Srick 		/*
446*36933Skarels 		 * We MUST do a chdir() after the chroot. Otherwise
447*36933Skarels 		 * the old current directory will be accessible as "."
448*36933Skarels 		 * outside the new root!
44936620Srick 		 */
45036620Srick 		if (chroot(pw->pw_dir) < 0 || chdir("/") < 0) {
45136446Sbostic 			reply(550, "Can't set guest privileges.");
45236446Sbostic 			goto bad;
45336446Sbostic 		}
454*36933Skarels 	} else if (chdir(pw->pw_dir) < 0) {
45536620Srick 		if (chdir("/") < 0) {
45636446Sbostic 			reply(530, "User %s: can't change directory to %s.",
45736446Sbostic 			    pw->pw_name, pw->pw_dir);
45836446Sbostic 			goto bad;
459*36933Skarels 		} else
46036446Sbostic 			lreply(230, "No directory! Logging in with home=/");
46136620Srick 	}
46236304Skarels 	if (seteuid((uid_t)pw->pw_uid) < 0) {
46336304Skarels 		reply(550, "Can't set uid.");
46436304Skarels 		goto bad;
46536304Skarels 	}
46636550Sbostic 	if (guest) {
46736192Sbostic 		reply(230, "Guest login ok, access restrictions apply.");
468*36933Skarels #ifdef SETPROCTITLE
469*36933Skarels 		sprintf(proctitle, "%s: anonymous/%.*s", remotehost,
470*36933Skarels 		    sizeof(proctitle) - sizeof(remotehost) -
471*36933Skarels 		    sizeof(": anonymous/"), passwd);
472*36933Skarels 		setproctitle(proctitle);
473*36933Skarels #endif /* SETPROCTITLE */
474*36933Skarels 		if (logging)
475*36933Skarels 			syslog(LOG_INFO, "ANONYMOUS FTP LOGIN FROM %s, %s",
476*36933Skarels 			    remotehost, passwd);
477*36933Skarels 	} else {
47810275Ssam 		reply(230, "User %s logged in.", pw->pw_name);
479*36933Skarels #ifdef SETPROCTITLE
480*36933Skarels 		sprintf(proctitle, "%s: %s", remotehost, pw->pw_name);
481*36933Skarels 		setproctitle(proctitle);
482*36933Skarels #endif /* SETPROCTITLE */
483*36933Skarels 		if (logging)
484*36933Skarels 			syslog(LOG_INFO, "FTP LOGIN FROM %s, %s",
485*36933Skarels 			    remotehost, pw->pw_name);
48636550Sbostic 	}
48710303Ssam 	home = pw->pw_dir;		/* home dir for globbing */
488*36933Skarels 	(void) umask(defumask);
48910303Ssam 	return;
49010303Ssam bad:
49136304Skarels 	/* Forget all about it... */
49236304Skarels 	end_login();
49310275Ssam }
49410275Ssam 
49510275Ssam retrieve(cmd, name)
49610275Ssam 	char *cmd, *name;
49710275Ssam {
49810275Ssam 	FILE *fin, *dout;
49910275Ssam 	struct stat st;
50036620Srick 	int (*closefunc)();
50110275Ssam 
50236557Sbostic 	if (cmd == 0) {
50336446Sbostic 		fin = fopen(name, "r"), closefunc = fclose;
50436557Sbostic 		st.st_size = 0;
50536557Sbostic 	} else {
50610275Ssam 		char line[BUFSIZ];
50710275Ssam 
50826493Sminshall 		(void) sprintf(line, cmd, name), name = line;
50936304Skarels 		fin = ftpd_popen(line, "r"), closefunc = ftpd_pclose;
51036557Sbostic 		st.st_size = -1;
511*36933Skarels 		st.st_blksize = BUFSIZ;
51210275Ssam 	}
51310275Ssam 	if (fin == NULL) {
51413152Ssam 		if (errno != 0)
51536304Skarels 			perror_reply(550, name);
51610275Ssam 		return;
51710275Ssam 	}
51810275Ssam 	if (cmd == 0 &&
519*36933Skarels 	    (fstat(fileno(fin), &st) < 0 || (st.st_mode&S_IFMT) != S_IFREG)) {
52010275Ssam 		reply(550, "%s: not a plain file.", name);
52110275Ssam 		goto done;
52210275Ssam 	}
52336620Srick 	if (restart_point) {
52436551Sbostic 		if (type == TYPE_A) {
525*36933Skarels 			register int i, n, c;
526*36933Skarels 
527*36933Skarels 			n = restart_point;
528*36933Skarels 			i = 0;
529*36933Skarels 			while (i++ < n) {
530*36933Skarels 				if ((c=getc(fin)) == EOF) {
531*36933Skarels 					perror_reply(550, name);
532*36933Skarels 					goto done;
533*36933Skarels 				}
534*36933Skarels 				if (c == '\n')
535*36933Skarels 					i++;
536*36933Skarels 			}
537*36933Skarels 		} else if (lseek(fileno(fin), restart_point, L_SET) < 0) {
53836551Sbostic 			perror_reply(550, name);
53936620Srick 			goto done;
54036620Srick 		}
54136620Srick 	}
54210275Ssam 	dout = dataconn(name, st.st_size, "w");
54310275Ssam 	if (dout == NULL)
54410275Ssam 		goto done;
54536620Srick 	send_data(fin, dout, st.st_blksize);
54626493Sminshall 	(void) fclose(dout);
54726044Sminshall 	data = -1;
54826044Sminshall 	pdata = -1;
54910275Ssam done:
55010275Ssam 	(*closefunc)(fin);
55110275Ssam }
55210275Ssam 
55336304Skarels store(name, mode, unique)
55410275Ssam 	char *name, *mode;
55536304Skarels 	int unique;
55610275Ssam {
55710275Ssam 	FILE *fout, *din;
55836446Sbostic 	struct stat st;
55936620Srick 	int (*closefunc)();
56036304Skarels 	char *gunique();
56110275Ssam 
56236446Sbostic 	if (unique && stat(name, &st) == 0 &&
56336446Sbostic 	    (name = gunique(name)) == NULL)
56436446Sbostic 		return;
56510303Ssam 
56636620Srick 	if (restart_point)
56736620Srick 		mode = "r+w";
56836620Srick 	fout = fopen(name, mode);
56936620Srick 	closefunc = fclose;
57010275Ssam 	if (fout == NULL) {
57136304Skarels 		perror_reply(553, name);
57210275Ssam 		return;
57310275Ssam 	}
57436620Srick 	if (restart_point) {
57536551Sbostic 		if (type == TYPE_A) {
576*36933Skarels 			register int i, n, c;
577*36933Skarels 
578*36933Skarels 			n = restart_point;
579*36933Skarels 			i = 0;
580*36933Skarels 			while (i++ < n) {
581*36933Skarels 				if ((c=getc(fout)) == EOF) {
582*36933Skarels 					perror_reply(550, name);
583*36933Skarels 					goto done;
584*36933Skarels 				}
585*36933Skarels 				if (c == '\n')
586*36933Skarels 					i++;
587*36933Skarels 			}
588*36933Skarels 			/*
589*36933Skarels 			 * We must do this seek to "current" position
590*36933Skarels 			 * because we are changing from reading to
591*36933Skarels 			 * writing.
592*36933Skarels 			 */
593*36933Skarels 			if (fseek(fout, 0L, L_INCR) < 0) {
59436551Sbostic 				perror_reply(550, name);
59536620Srick 				goto done;
59636620Srick 			}
597*36933Skarels 		} else if (lseek(fileno(fout), restart_point, L_SET) < 0) {
59836551Sbostic 			perror_reply(550, name);
59936620Srick 			goto done;
60036620Srick 		}
60136620Srick 	}
60236304Skarels 	din = dataconn(name, (off_t)-1, "r");
60310275Ssam 	if (din == NULL)
60410275Ssam 		goto done;
60536620Srick 	if (receive_data(din, fout) == 0) {
60636620Srick 		if (unique)
60736304Skarels 			reply(226, "Transfer complete (unique file name:%s).",
608*36933Skarels 			    name);
60936304Skarels 		else
61036304Skarels 			reply(226, "Transfer complete.");
61126044Sminshall 	}
61226493Sminshall 	(void) fclose(din);
61326044Sminshall 	data = -1;
61426044Sminshall 	pdata = -1;
61510275Ssam done:
61610275Ssam 	(*closefunc)(fout);
61710275Ssam }
61810275Ssam 
61910275Ssam FILE *
62010275Ssam getdatasock(mode)
62110275Ssam 	char *mode;
62210275Ssam {
62317157Ssam 	int s, on = 1;
62410275Ssam 
62510275Ssam 	if (data >= 0)
62610275Ssam 		return (fdopen(data, mode));
62713247Ssam 	s = socket(AF_INET, SOCK_STREAM, 0);
62810602Ssam 	if (s < 0)
62910275Ssam 		return (NULL);
63036304Skarels 	(void) seteuid((uid_t)0);
63126493Sminshall 	if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof (on)) < 0)
63210602Ssam 		goto bad;
63313152Ssam 	/* anchor socket to avoid multi-homing problems */
63413152Ssam 	data_source.sin_family = AF_INET;
63513152Ssam 	data_source.sin_addr = ctrl_addr.sin_addr;
63636304Skarels 	if (bind(s, (struct sockaddr *)&data_source, sizeof (data_source)) < 0)
63710602Ssam 		goto bad;
63836304Skarels 	(void) seteuid((uid_t)pw->pw_uid);
63910275Ssam 	return (fdopen(s, mode));
64010602Ssam bad:
64136304Skarels 	(void) seteuid((uid_t)pw->pw_uid);
64226493Sminshall 	(void) close(s);
64310602Ssam 	return (NULL);
64410275Ssam }
64510275Ssam 
64610275Ssam FILE *
64710275Ssam dataconn(name, size, mode)
64810275Ssam 	char *name;
64911653Ssam 	off_t size;
65010275Ssam 	char *mode;
65110275Ssam {
65210275Ssam 	char sizebuf[32];
65310275Ssam 	FILE *file;
65411653Ssam 	int retry = 0;
65510275Ssam 
656*36933Skarels 	file_size = size;
657*36933Skarels 	byte_count = 0;
65836304Skarels 	if (size != (off_t) -1)
65926493Sminshall 		(void) sprintf (sizebuf, " (%ld bytes)", size);
66010275Ssam 	else
66110275Ssam 		(void) strcpy(sizebuf, "");
66236304Skarels 	if (pdata >= 0) {
66326044Sminshall 		struct sockaddr_in from;
66426044Sminshall 		int s, fromlen = sizeof(from);
66526044Sminshall 
66636304Skarels 		s = accept(pdata, (struct sockaddr *)&from, &fromlen);
66726044Sminshall 		if (s < 0) {
66826044Sminshall 			reply(425, "Can't open data connection.");
66926044Sminshall 			(void) close(pdata);
67026044Sminshall 			pdata = -1;
67126044Sminshall 			return(NULL);
67226044Sminshall 		}
67326044Sminshall 		(void) close(pdata);
67426044Sminshall 		pdata = s;
67536235Skarels 		reply(150, "Opening %s mode data connection for %s%s.",
67636235Skarels 		     type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf);
67726044Sminshall 		return(fdopen(pdata, mode));
67826044Sminshall 	}
67910275Ssam 	if (data >= 0) {
68010275Ssam 		reply(125, "Using existing data connection for %s%s.",
68110275Ssam 		    name, sizebuf);
68210321Ssam 		usedefault = 1;
68310275Ssam 		return (fdopen(data, mode));
68410275Ssam 	}
68510566Ssam 	if (usedefault)
68610422Ssam 		data_dest = his_addr;
68710422Ssam 	usedefault = 1;
68810275Ssam 	file = getdatasock(mode);
68910275Ssam 	if (file == NULL) {
69010275Ssam 		reply(425, "Can't create data socket (%s,%d): %s.",
69113247Ssam 		    inet_ntoa(data_source.sin_addr),
69210275Ssam 		    ntohs(data_source.sin_port),
69336304Skarels 		    errno < sys_nerr ? sys_errlist[errno] : "unknown error");
69410275Ssam 		return (NULL);
69510275Ssam 	}
69610275Ssam 	data = fileno(file);
69736304Skarels 	while (connect(data, (struct sockaddr *)&data_dest,
69836304Skarels 	    sizeof (data_dest)) < 0) {
69911653Ssam 		if (errno == EADDRINUSE && retry < swaitmax) {
70026493Sminshall 			sleep((unsigned) swaitint);
70111653Ssam 			retry += swaitint;
70211653Ssam 			continue;
70311653Ssam 		}
70436304Skarels 		perror_reply(425, "Can't build data connection");
70510275Ssam 		(void) fclose(file);
70610275Ssam 		data = -1;
70710275Ssam 		return (NULL);
70810275Ssam 	}
70936235Skarels 	reply(150, "Opening %s mode data connection for %s%s.",
71036235Skarels 	     type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf);
71110275Ssam 	return (file);
71210275Ssam }
71310275Ssam 
71410275Ssam /*
71510275Ssam  * Tranfer the contents of "instr" to
71610275Ssam  * "outstr" peer using the appropriate
717*36933Skarels  * encapsulation of the data subject
71810275Ssam  * to Mode, Structure, and Type.
71910275Ssam  *
72010275Ssam  * NB: Form isn't handled.
72110275Ssam  */
72236446Sbostic send_data(instr, outstr, blksize)
72310275Ssam 	FILE *instr, *outstr;
72436446Sbostic 	off_t blksize;
72510275Ssam {
72636446Sbostic 	register int c, cnt;
72736446Sbostic 	register char *buf;
72836446Sbostic 	int netfd, filefd;
72910275Ssam 
73026044Sminshall 	transflag++;
73126044Sminshall 	if (setjmp(urgcatch)) {
73226044Sminshall 		transflag = 0;
73336620Srick 		return;
73426044Sminshall 	}
73510275Ssam 	switch (type) {
73610275Ssam 
73710275Ssam 	case TYPE_A:
73810275Ssam 		while ((c = getc(instr)) != EOF) {
739*36933Skarels 			byte_count++;
74011220Ssam 			if (c == '\n') {
741*36933Skarels 				if (ferror(outstr))
74236620Srick 					goto data_err;
74327750Sminshall 				(void) putc('\r', outstr);
74411220Ssam 			}
74527750Sminshall 			(void) putc(c, outstr);
74610275Ssam 		}
747*36933Skarels 		fflush(outstr);
74826044Sminshall 		transflag = 0;
749*36933Skarels 		if (ferror(instr))
750*36933Skarels 			goto file_err;
751*36933Skarels 		if (ferror(outstr))
75236620Srick 			goto data_err;
75336620Srick 		reply(226, "Transfer complete.");
75436620Srick 		return;
75536446Sbostic 
75610275Ssam 	case TYPE_I:
75710275Ssam 	case TYPE_L:
75836446Sbostic 		if ((buf = malloc((u_int)blksize)) == NULL) {
75936446Sbostic 			transflag = 0;
760*36933Skarels 			perror_reply(451, "Local resource failure: malloc");
761*36933Skarels 			return;
76236446Sbostic 		}
76310275Ssam 		netfd = fileno(outstr);
76410275Ssam 		filefd = fileno(instr);
765*36933Skarels 		while ((cnt = read(filefd, buf, (u_int)blksize)) > 0 &&
76636620Srick 		    write(netfd, buf, cnt) == cnt)
767*36933Skarels 			byte_count += cnt;
76826044Sminshall 		transflag = 0;
76936446Sbostic 		(void)free(buf);
770*36933Skarels 		if (cnt != 0) {
771*36933Skarels 			if (cnt < 0)
772*36933Skarels 				goto file_err;
77336620Srick 			goto data_err;
774*36933Skarels 		}
77536620Srick 		reply(226, "Transfer complete.");
77636620Srick 		return;
77736620Srick 	default:
77836620Srick 		transflag = 0;
77936620Srick 		reply(550, "Unimplemented TYPE %d in send_data", type);
78036620Srick 		return;
78110275Ssam 	}
78236620Srick 
78336620Srick data_err:
78426044Sminshall 	transflag = 0;
785*36933Skarels 	perror_reply(426, "Data connection");
786*36933Skarels 	return;
787*36933Skarels 
788*36933Skarels file_err:
789*36933Skarels 	transflag = 0;
790*36933Skarels 	perror_reply(551, "Error on input file");
79110275Ssam }
79210275Ssam 
79310275Ssam /*
79410275Ssam  * Transfer data from peer to
79510275Ssam  * "outstr" using the appropriate
79610275Ssam  * encapulation of the data subject
79710275Ssam  * to Mode, Structure, and Type.
79810275Ssam  *
79910275Ssam  * N.B.: Form isn't handled.
80010275Ssam  */
80110275Ssam receive_data(instr, outstr)
80210275Ssam 	FILE *instr, *outstr;
80310275Ssam {
80410275Ssam 	register int c;
80511220Ssam 	int cnt;
80610275Ssam 	char buf[BUFSIZ];
80710275Ssam 
80826044Sminshall 	transflag++;
80926044Sminshall 	if (setjmp(urgcatch)) {
81026044Sminshall 		transflag = 0;
811*36933Skarels 		return (-1);
81226044Sminshall 	}
81310275Ssam 	switch (type) {
81410275Ssam 
81510275Ssam 	case TYPE_I:
81610275Ssam 	case TYPE_L:
81726044Sminshall 		while ((cnt = read(fileno(instr), buf, sizeof buf)) > 0) {
81836620Srick 			if (write(fileno(outstr), buf, cnt) != cnt)
819*36933Skarels 				goto file_err;
820*36933Skarels 			byte_count += cnt;
82126044Sminshall 		}
822*36933Skarels 		if (cnt < 0)
82336620Srick 			goto data_err;
82426044Sminshall 		transflag = 0;
825*36933Skarels 		return (0);
82610275Ssam 
82710275Ssam 	case TYPE_E:
82827106Smckusick 		reply(553, "TYPE E not implemented.");
82926044Sminshall 		transflag = 0;
83027106Smckusick 		return (-1);
83110275Ssam 
83210275Ssam 	case TYPE_A:
83310275Ssam 		while ((c = getc(instr)) != EOF) {
834*36933Skarels 			byte_count++;
83527750Sminshall 			while (c == '\r') {
836*36933Skarels 				if (ferror(outstr))
83736620Srick 					goto data_err;
838*36933Skarels 				if ((c = getc(instr)) != '\n') {
83927750Sminshall 					(void) putc ('\r', outstr);
840*36933Skarels 					if (c == '\0' || c == EOF)
841*36933Skarels 						goto contin2;
842*36933Skarels 				}
84310275Ssam 			}
844*36933Skarels 			(void) putc(c, outstr);
845*36933Skarels 	contin2:	;
84610275Ssam 		}
84736620Srick 		fflush(outstr);
848*36933Skarels 		if (ferror(instr))
84936620Srick 			goto data_err;
850*36933Skarels 		if (ferror(outstr))
851*36933Skarels 			goto file_err;
85226044Sminshall 		transflag = 0;
85310275Ssam 		return (0);
85436620Srick 	default:
85536620Srick 		reply(550, "Unimplemented TYPE %d in receive_data", type);
85636620Srick 		transflag = 0;
857*36933Skarels 		return (-1);
85810275Ssam 	}
85936620Srick 
86036620Srick data_err:
86126044Sminshall 	transflag = 0;
862*36933Skarels 	perror_reply(426, "Data Connection");
863*36933Skarels 	return (-1);
864*36933Skarels 
865*36933Skarels file_err:
866*36933Skarels 	transflag = 0;
867*36933Skarels 	perror_reply(452, "Error writing file");
868*36933Skarels 	return (-1);
86910275Ssam }
87010275Ssam 
871*36933Skarels statfilecmd(filename)
872*36933Skarels 	char *filename;
873*36933Skarels {
874*36933Skarels 	char line[BUFSIZ];
875*36933Skarels 	FILE *fin;
876*36933Skarels 	int c;
877*36933Skarels 
878*36933Skarels 	(void) sprintf(line, "/bin/ls -lgA %s", filename);
879*36933Skarels 	fin = ftpd_popen(line, "r");
880*36933Skarels 	lreply(211, "status of %s:", filename);
881*36933Skarels 	while ((c = getc(fin)) != EOF) {
882*36933Skarels 		if (c == '\n') {
883*36933Skarels 			if (ferror(stdout)){
884*36933Skarels 				perror_reply(421, "control connection");
885*36933Skarels 				(void) ftpd_pclose(fin);
886*36933Skarels 				dologout(1);
887*36933Skarels 				/* NOTREACHED */
888*36933Skarels 			}
889*36933Skarels 			if (ferror(fin)) {
890*36933Skarels 				perror_reply(551, filename);
891*36933Skarels 				(void) ftpd_pclose(fin);
892*36933Skarels 				return;
893*36933Skarels 			}
894*36933Skarels 			(void) putc('\r', stdout);
895*36933Skarels 		}
896*36933Skarels 		(void) putc(c, stdout);
897*36933Skarels 	}
898*36933Skarels 	(void) ftpd_pclose(fin);
899*36933Skarels 	reply(211, "End of Status");
900*36933Skarels }
901*36933Skarels 
902*36933Skarels statcmd()
903*36933Skarels {
904*36933Skarels 	struct sockaddr_in *sin;
905*36933Skarels 	u_char *a, *p;
906*36933Skarels 
907*36933Skarels 	lreply(211, "%s FTP server status:", hostname, version);
908*36933Skarels 	printf("     %s\r\n", version);
909*36933Skarels 	printf("     Connected to %s", remotehost);
910*36933Skarels 	if (isdigit(remotehost[0]))
911*36933Skarels 		printf(" (%s)", inet_ntoa(his_addr.sin_addr));
912*36933Skarels 	printf("\r\n");
913*36933Skarels 	if (logged_in) {
914*36933Skarels 		if (guest)
915*36933Skarels 			printf("     Logged in anonymously\r\n");
916*36933Skarels 		else
917*36933Skarels 			printf("     Logged in as %s\r\n", pw->pw_name);
918*36933Skarels 	} else if (askpasswd)
919*36933Skarels 		printf("     Waiting for password\r\n");
920*36933Skarels 	else
921*36933Skarels 		printf("     Waiting for user name\r\n");
922*36933Skarels 	printf("     TYPE: %s", typenames[type]);
923*36933Skarels 	if (type == TYPE_A || type == TYPE_E)
924*36933Skarels 		printf(", FORM: %s", formnames[form]);
925*36933Skarels 	if (type == TYPE_L)
926*36933Skarels #if NBBY == 8
927*36933Skarels 		printf(" %d", NBBY);
928*36933Skarels #else
929*36933Skarels 		printf(" %d", bytesize);	/* need definition! */
930*36933Skarels #endif
931*36933Skarels 	printf("; STRUcture: %s; transfer MODE: %s\r\n",
932*36933Skarels 	    strunames[stru], modenames[mode]);
933*36933Skarels 	if (data != -1)
934*36933Skarels 		printf("     Data connection open\r\n");
935*36933Skarels 	else if (pdata != -1) {
936*36933Skarels 		printf("     in Passive mode");
937*36933Skarels 		sin = &pasv_addr;
938*36933Skarels 		goto printaddr;
939*36933Skarels 	} else if (usedefault == 0) {
940*36933Skarels 		printf("     PORT");
941*36933Skarels 		sin = &data_dest;
942*36933Skarels printaddr:
943*36933Skarels 		a = (u_char *) &sin->sin_addr;
944*36933Skarels 		p = (u_char *) &sin->sin_port;
945*36933Skarels #define UC(b) (((int) b) & 0xff)
946*36933Skarels 		printf(" (%d,%d,%d,%d,%d,%d)\r\n", UC(a[0]),
947*36933Skarels 			UC(a[1]), UC(a[2]), UC(a[3]), UC(p[0]), UC(p[1]));
948*36933Skarels #undef UC
949*36933Skarels 	} else
950*36933Skarels 		printf("     No data connection\r\n");
951*36933Skarels 	reply(211, "End of status");
952*36933Skarels }
953*36933Skarels 
95410275Ssam fatal(s)
95510275Ssam 	char *s;
95610275Ssam {
95710275Ssam 	reply(451, "Error in server: %s\n", s);
95810275Ssam 	reply(221, "Closing connection due to server error.");
95913247Ssam 	dologout(0);
96036620Srick 	/* NOTREACHED */
96110275Ssam }
96210275Ssam 
96336446Sbostic /* VARARGS2 */
96436446Sbostic reply(n, fmt, p0, p1, p2, p3, p4, p5)
96510275Ssam 	int n;
96636446Sbostic 	char *fmt;
96710275Ssam {
96810275Ssam 	printf("%d ", n);
96936446Sbostic 	printf(fmt, p0, p1, p2, p3, p4, p5);
97010275Ssam 	printf("\r\n");
97136435Sbostic 	(void)fflush(stdout);
97210275Ssam 	if (debug) {
97326493Sminshall 		syslog(LOG_DEBUG, "<--- %d ", n);
97436446Sbostic 		syslog(LOG_DEBUG, fmt, p0, p1, p2, p3, p4, p5);
97510275Ssam }
97636620Srick }
97710275Ssam 
97836446Sbostic /* VARARGS2 */
97936446Sbostic lreply(n, fmt, p0, p1, p2, p3, p4, p5)
98010275Ssam 	int n;
98136446Sbostic 	char *fmt;
98210275Ssam {
98336446Sbostic 	printf("%d- ", n);
98436446Sbostic 	printf(fmt, p0, p1, p2, p3, p4, p5);
98536446Sbostic 	printf("\r\n");
98636435Sbostic 	(void)fflush(stdout);
98736446Sbostic 	if (debug) {
98836446Sbostic 		syslog(LOG_DEBUG, "<--- %d- ", n);
98936446Sbostic 		syslog(LOG_DEBUG, fmt, p0, p1, p2, p3, p4, p5);
99036446Sbostic 	}
99110275Ssam }
99210275Ssam 
99310275Ssam ack(s)
99410275Ssam 	char *s;
99510275Ssam {
99627106Smckusick 	reply(250, "%s command successful.", s);
99710275Ssam }
99810275Ssam 
99910275Ssam nack(s)
100010275Ssam 	char *s;
100110275Ssam {
100210275Ssam 	reply(502, "%s command not implemented.", s);
100310275Ssam }
100410275Ssam 
100536304Skarels /* ARGSUSED */
100626493Sminshall yyerror(s)
100726493Sminshall 	char *s;
100810275Ssam {
100926044Sminshall 	char *cp;
101026044Sminshall 
101136551Sbostic 	if (cp = index(cbuf,'\n'))
101236551Sbostic 		*cp = '\0';
1013*36933Skarels 	reply(500, "'%s': command not understood.", cbuf);
101410275Ssam }
101510275Ssam 
101610275Ssam delete(name)
101710275Ssam 	char *name;
101810275Ssam {
101910275Ssam 	struct stat st;
102010275Ssam 
102110275Ssam 	if (stat(name, &st) < 0) {
102236304Skarels 		perror_reply(550, name);
102310275Ssam 		return;
102410275Ssam 	}
102510275Ssam 	if ((st.st_mode&S_IFMT) == S_IFDIR) {
102610275Ssam 		if (rmdir(name) < 0) {
102736304Skarels 			perror_reply(550, name);
102810275Ssam 			return;
102910275Ssam 		}
103010275Ssam 		goto done;
103110275Ssam 	}
103210275Ssam 	if (unlink(name) < 0) {
103336304Skarels 		perror_reply(550, name);
103410275Ssam 		return;
103510275Ssam 	}
103610275Ssam done:
103710275Ssam 	ack("DELE");
103810275Ssam }
103910275Ssam 
104010275Ssam cwd(path)
104110275Ssam 	char *path;
104210275Ssam {
104336620Srick 	if (chdir(path) < 0)
104436304Skarels 		perror_reply(550, path);
104536620Srick 	else
104636620Srick 		ack("CWD");
104710275Ssam }
104810275Ssam 
104910303Ssam makedir(name)
105010275Ssam 	char *name;
105110275Ssam {
105236276Sbostic 	if (mkdir(name, 0777) < 0)
105336304Skarels 		perror_reply(550, name);
105436276Sbostic 	else
105536276Sbostic 		reply(257, "MKD command successful.");
105610275Ssam }
105710275Ssam 
105810303Ssam removedir(name)
105910275Ssam 	char *name;
106010275Ssam {
106136620Srick 	if (rmdir(name) < 0)
106236304Skarels 		perror_reply(550, name);
106336620Srick 	else
106436620Srick 		ack("RMD");
106510275Ssam }
106610275Ssam 
106710303Ssam pwd()
106810275Ssam {
106910303Ssam 	char path[MAXPATHLEN + 1];
107036304Skarels 	extern char *getwd();
107110275Ssam 
107236620Srick 	if (getwd(path) == (char *)NULL)
107327106Smckusick 		reply(550, "%s.", path);
107436620Srick 	else
107536620Srick 		reply(257, "\"%s\" is current directory.", path);
107610275Ssam }
107710275Ssam 
107810275Ssam char *
107910275Ssam renamefrom(name)
108010275Ssam 	char *name;
108110275Ssam {
108210275Ssam 	struct stat st;
108310275Ssam 
108410275Ssam 	if (stat(name, &st) < 0) {
108536304Skarels 		perror_reply(550, name);
108610275Ssam 		return ((char *)0);
108710275Ssam 	}
108810303Ssam 	reply(350, "File exists, ready for destination name");
108910275Ssam 	return (name);
109010275Ssam }
109110275Ssam 
109210275Ssam renamecmd(from, to)
109310275Ssam 	char *from, *to;
109410275Ssam {
109536620Srick 	if (rename(from, to) < 0)
109636304Skarels 		perror_reply(550, "rename");
109736620Srick 	else
109836620Srick 		ack("RNTO");
109910275Ssam }
110010275Ssam 
110110275Ssam dolog(sin)
110210275Ssam 	struct sockaddr_in *sin;
110310275Ssam {
110436304Skarels 	struct hostent *hp = gethostbyaddr((char *)&sin->sin_addr,
110510275Ssam 		sizeof (struct in_addr), AF_INET);
110636304Skarels 	time_t t, time();
110726493Sminshall 	extern char *ctime();
110810275Ssam 
110936304Skarels 	if (hp)
111026493Sminshall 		(void) strncpy(remotehost, hp->h_name, sizeof (remotehost));
111136304Skarels 	else
111226493Sminshall 		(void) strncpy(remotehost, inet_ntoa(sin->sin_addr),
111313247Ssam 		    sizeof (remotehost));
111436620Srick #ifdef SETPROCTITLE
1115*36933Skarels 	sprintf(proctitle, "%s: connected", remotehost);
1116*36933Skarels 	setproctitle(proctitle);
111736620Srick #endif /* SETPROCTITLE */
1118*36933Skarels 
1119*36933Skarels 	if (logging) {
1120*36933Skarels 		t = time((time_t *) 0);
1121*36933Skarels 		syslog(LOG_INFO, "connection from %s at %s",
1122*36933Skarels 		    remotehost, ctime(&t));
1123*36933Skarels 	}
112410275Ssam }
112510695Ssam 
112610695Ssam /*
112713247Ssam  * Record logout in wtmp file
112813247Ssam  * and exit with supplied status.
112913247Ssam  */
113013247Ssam dologout(status)
113113247Ssam 	int status;
113213247Ssam {
113317580Ssam 	if (logged_in) {
113436304Skarels 		(void) seteuid((uid_t)0);
113535672Sbostic 		logwtmp(ttyline, "", "");
113613247Ssam 	}
113714436Ssam 	/* beware of flushing buffers after a SIGPIPE */
113814436Ssam 	_exit(status);
113913247Ssam }
114013247Ssam 
114126044Sminshall myoob()
114226044Sminshall {
114327750Sminshall 	char *cp;
114426044Sminshall 
114527750Sminshall 	/* only process if transfer occurring */
114636304Skarels 	if (!transflag)
114726044Sminshall 		return;
114827750Sminshall 	cp = tmpline;
114927750Sminshall 	if (getline(cp, 7, stdin) == NULL) {
115036304Skarels 		reply(221, "You could at least say goodbye.");
115127750Sminshall 		dologout(0);
115226044Sminshall 	}
115326044Sminshall 	upper(cp);
1154*36933Skarels 	if (strcmp(cp, "ABOR\r\n") == 0) {
1155*36933Skarels 		tmpline[0] = '\0';
1156*36933Skarels 		reply(426, "Transfer aborted. Data connection closed.");
1157*36933Skarels 		reply(226, "Abort successful");
1158*36933Skarels 		longjmp(urgcatch, 1);
1159*36933Skarels 	}
1160*36933Skarels 	if (strcmp(cp, "STAT\r\n") == 0) {
1161*36933Skarels 		if (file_size != (off_t) -1)
1162*36933Skarels 			reply(213, "Status: %lu of %lu bytes transferred",
1163*36933Skarels 			    byte_count, file_size);
1164*36933Skarels 		else
1165*36933Skarels 			reply(213, "Status: %lu bytes transferred", byte_count);
1166*36933Skarels 	}
116726044Sminshall }
116826044Sminshall 
116927106Smckusick /*
117036620Srick  * Note: a response of 425 is not mentioned as a possible response to
117136620Srick  * 	the PASV command in RFC959. However, it has been blessed as
117236620Srick  * 	a legitimate response by Jon Postel in a telephone conversation
117336620Srick  *	with Rick Adams on 25 Jan 89.
117427106Smckusick  */
117526044Sminshall passive()
117626044Sminshall {
117726044Sminshall 	int len;
117826044Sminshall 	register char *p, *a;
117926044Sminshall 
118026044Sminshall 	pdata = socket(AF_INET, SOCK_STREAM, 0);
118126044Sminshall 	if (pdata < 0) {
118236620Srick 		perror_reply(425, "Can't open passive connection");
118326044Sminshall 		return;
118426044Sminshall 	}
1185*36933Skarels 	pasv_addr = ctrl_addr;
1186*36933Skarels 	pasv_addr.sin_port = 0;
118736304Skarels 	(void) seteuid((uid_t)0);
1188*36933Skarels 	if (bind(pdata, (struct sockaddr *)&pasv_addr, sizeof(pasv_addr)) < 0) {
118936304Skarels 		(void) seteuid((uid_t)pw->pw_uid);
119036620Srick 		goto pasv_error;
119126044Sminshall 	}
119236304Skarels 	(void) seteuid((uid_t)pw->pw_uid);
1193*36933Skarels 	len = sizeof(pasv_addr);
1194*36933Skarels 	if (getsockname(pdata, (struct sockaddr *) &pasv_addr, &len) < 0)
119536620Srick 		goto pasv_error;
119636620Srick 	if (listen(pdata, 1) < 0)
119736620Srick 		goto pasv_error;
1198*36933Skarels 	a = (char *) &pasv_addr.sin_addr;
1199*36933Skarels 	p = (char *) &pasv_addr.sin_port;
120026044Sminshall 
120126044Sminshall #define UC(b) (((int) b) & 0xff)
120226044Sminshall 
120326044Sminshall 	reply(227, "Entering Passive Mode (%d,%d,%d,%d,%d,%d)", UC(a[0]),
120426044Sminshall 		UC(a[1]), UC(a[2]), UC(a[3]), UC(p[0]), UC(p[1]));
120536620Srick 	return;
120636620Srick 
120736620Srick pasv_error:
120836620Srick 	(void) close(pdata);
120936620Srick 	pdata = -1;
121036620Srick 	perror_reply(425, "Can't open passive connection");
121136620Srick 	return;
121226044Sminshall }
121326044Sminshall 
121436304Skarels /*
121536304Skarels  * Generate unique name for file with basename "local".
121636304Skarels  * The file named "local" is already known to exist.
121736304Skarels  * Generates failure reply on error.
121836304Skarels  */
121926044Sminshall char *
122026044Sminshall gunique(local)
122126044Sminshall 	char *local;
122226044Sminshall {
122326044Sminshall 	static char new[MAXPATHLEN];
122436304Skarels 	struct stat st;
122526044Sminshall 	char *cp = rindex(local, '/');
1226*36933Skarels 	int count = 0;
122726044Sminshall 
122836304Skarels 	if (cp)
122926044Sminshall 		*cp = '\0';
123036620Srick 	if (stat(cp ? local : ".", &st) < 0) {
1231*36933Skarels 		perror_reply(553, cp ? local : ".");
123226044Sminshall 		return((char *) 0);
123326044Sminshall 	}
123436620Srick 	if (cp)
123536620Srick 		*cp = '/';
123626044Sminshall 	(void) strcpy(new, local);
123726044Sminshall 	cp = new + strlen(new);
123826044Sminshall 	*cp++ = '.';
123936304Skarels 	for (count = 1; count < 100; count++) {
124036304Skarels 		(void) sprintf(cp, "%d", count);
124136304Skarels 		if (stat(new, &st) < 0)
124236304Skarels 			return(new);
124326044Sminshall 	}
124436304Skarels 	reply(452, "Unique file name cannot be created.");
124536304Skarels 	return((char *) 0);
124626044Sminshall }
124736304Skarels 
124836304Skarels /*
124936304Skarels  * Format and send reply containing system error number.
125036304Skarels  */
125136304Skarels perror_reply(code, string)
125236304Skarels 	int code;
125336304Skarels 	char *string;
125436304Skarels {
125536304Skarels 	if (errno < sys_nerr)
125636304Skarels 		reply(code, "%s: %s.", string, sys_errlist[errno]);
125736304Skarels 	else
125836304Skarels 		reply(code, "%s: unknown error %d.", string, errno);
125936304Skarels }
126036620Srick 
126136620Srick static char *onefile[] = {
126236620Srick 	"",
126336620Srick 	0
126436620Srick };
126536620Srick 
126636620Srick send_file_list(whichfiles)
126736620Srick 	char *whichfiles;
126836620Srick {
126936620Srick 	struct stat st;
127036620Srick 	DIR *dirp = NULL;
127136620Srick 	struct direct *dir;
127236620Srick 	FILE *dout = NULL;
127336620Srick 	register char **dirlist, *dirname;
127436620Srick 	char *strpbrk();
127536620Srick 
127636620Srick 	if (strpbrk(whichfiles, "~{[*?") != NULL) {
127736620Srick 		extern char **glob(), *globerr;
1278*36933Skarels 
127936620Srick 		globerr = NULL;
128036620Srick 		dirlist = glob(whichfiles);
128136620Srick 		if (globerr != NULL) {
128236620Srick 			reply(550, globerr);
128336620Srick 			return;
128436620Srick 		} else if (dirlist == NULL) {
128536620Srick 			errno = ENOENT;
128636620Srick 			perror_reply(550, whichfiles);
128736620Srick 			return;
128836620Srick 		}
128936620Srick 	} else {
129036620Srick 		onefile[0] = whichfiles;
129136620Srick 		dirlist = onefile;
129236620Srick 	}
1293*36933Skarels 
1294*36933Skarels 	if (setjmp(urgcatch)) {
1295*36933Skarels 		transflag = 0;
1296*36933Skarels 		return;
1297*36933Skarels 	}
129836620Srick 	while (dirname = *dirlist++) {
129936620Srick 		if (stat(dirname, &st) < 0) {
1300*36933Skarels 			/*
1301*36933Skarels 			 * If user typed "ls -l", etc, and the client
1302*36933Skarels 			 * used NLST, do what the user meant.
1303*36933Skarels 			 */
1304*36933Skarels 			if (dirname[0] == '-' && *dirlist == NULL &&
1305*36933Skarels 			    transflag == 0) {
1306*36933Skarels 				retrieve("/bin/ls %s", dirname);
1307*36933Skarels 				return;
1308*36933Skarels 			}
130936620Srick 			perror_reply(550, whichfiles);
131036620Srick 			if (dout != NULL) {
131136620Srick 				(void) fclose(dout);
1312*36933Skarels 				transflag = 0;
131336620Srick 				data = -1;
131436620Srick 				pdata = -1;
131536620Srick 			}
131636620Srick 			return;
131736620Srick 		}
1318*36933Skarels 
131936620Srick 		if ((st.st_mode&S_IFMT) == S_IFREG) {
132036620Srick 			if (dout == NULL) {
132136620Srick 				dout = dataconn(whichfiles, (off_t)-1, "w");
132236620Srick 				if (dout == NULL)
132336620Srick 					return;
1324*36933Skarels 				transflag++;
132536620Srick 			}
132636620Srick 			fprintf(dout, "%s\n", dirname);
1327*36933Skarels 			byte_count += strlen(dirname) + 1;
132836620Srick 			continue;
1329*36933Skarels 		} else if ((st.st_mode&S_IFMT) != S_IFDIR)
133036620Srick 			continue;
133136620Srick 
133236620Srick 		if ((dirp = opendir(dirname)) == NULL)
133336620Srick 			continue;
133436620Srick 
133536620Srick 		while ((dir = readdir(dirp)) != NULL) {
1336*36933Skarels 			char nbuf[MAXPATHLEN];
133736620Srick 
133836620Srick 			if (dir->d_name[0] == '.' && dir->d_namlen == 1)
133936620Srick 				continue;
1340*36933Skarels 			if (dir->d_name[0] == '.' && dir->d_name[1] == '.' &&
1341*36933Skarels 			    dir->d_namlen == 2)
134236620Srick 				continue;
134336620Srick 
1344*36933Skarels 			sprintf(nbuf, "%s/%s", dirname, dir->d_name);
1345*36933Skarels 
134636620Srick 			/*
1347*36933Skarels 			 * We have to do a stat to insure it's
1348*36933Skarels 			 * not a directory or special file.
134936620Srick 			 */
135036620Srick 			if (stat(nbuf, &st) == 0 &&
135136620Srick 			    (st.st_mode&S_IFMT) == S_IFREG) {
135236620Srick 				if (dout == NULL) {
135336620Srick 					dout = dataconn(whichfiles, (off_t)-1,
135436620Srick 						"w");
135536620Srick 					if (dout == NULL)
135636620Srick 						return;
1357*36933Skarels 					transflag++;
135836620Srick 				}
135936620Srick 				if (nbuf[0] == '.' && nbuf[1] == '/')
136036620Srick 					fprintf(dout, "%s\n", &nbuf[2]);
136136620Srick 				else
136236620Srick 					fprintf(dout, "%s\n", nbuf);
1363*36933Skarels 				byte_count += strlen(nbuf) + 1;
136436620Srick 			}
136536620Srick 		}
136636620Srick 		(void) closedir(dirp);
136736620Srick 	}
136836620Srick 
1369*36933Skarels 	if (dout == NULL)
1370*36933Skarels 		reply(550, "No files found.");
1371*36933Skarels 	else if (ferror(dout) != 0)
1372*36933Skarels 		perror_reply(550, "Data connection");
1373*36933Skarels 	else
137436620Srick 		reply(226, "Transfer complete.");
137536620Srick 
1376*36933Skarels 	transflag = 0;
1377*36933Skarels 	if (dout != NULL)
137836620Srick 		(void) fclose(dout);
137936620Srick 	data = -1;
138036620Srick 	pdata = -1;
138136620Srick }
138236620Srick 
138336620Srick #ifdef SETPROCTITLE
138436620Srick /*
138536620Srick  * clobber argv so ps will show what we're doing.
138636620Srick  * (stolen from sendmail)
138736620Srick  * warning, since this is usually started from inetd.conf, it
138836620Srick  * often doesn't have much of an environment or arglist to overwrite.
138936620Srick  */
139036620Srick 
139136620Srick /*VARARGS1*/
139236620Srick setproctitle(fmt, a, b, c)
139336620Srick char *fmt;
139436620Srick {
139536620Srick 	register char *p, *bp, ch;
139636620Srick 	register int i;
139736620Srick 	char buf[BUFSIZ];
139836620Srick 
139936620Srick 	(void) sprintf(buf, fmt, a, b, c);
140036620Srick 
140136620Srick 	/* make ps print our process name */
140236620Srick 	p = Argv[0];
140336620Srick 	*p++ = '-';
140436620Srick 
140536620Srick 	i = strlen(buf);
140636620Srick 	if (i > LastArgv - p - 2) {
140736620Srick 		i = LastArgv - p - 2;
140836620Srick 		buf[i] = '\0';
140936620Srick 	}
141036620Srick 	bp = buf;
141136620Srick 	while (ch = *bp++)
141236620Srick 		if (ch != '\n' && ch != '\r')
141336620Srick 			*p++ = ch;
141436620Srick 	while (p < LastArgv)
141536620Srick 		*p++ = ' ';
141636620Srick }
141736620Srick #endif /* SETPROCTITLE */
1418