xref: /csrg-svn/libexec/ftpd/ftpd.c (revision 36557)
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
1534769Sbostic  * WARRANTIES OF MERCHANTIBILITY 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*36557Sbostic static char sccsid[] = "@(#)ftpd.c	5.25 (Berkeley) 01/16/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>
3710275Ssam 
3810275Ssam #include <netinet/in.h>
3910275Ssam 
4013034Ssam #include <arpa/ftp.h>
4113211Sroot #include <arpa/inet.h>
4226044Sminshall #include <arpa/telnet.h>
4313034Ssam 
4410275Ssam #include <stdio.h>
4510275Ssam #include <signal.h>
4610275Ssam #include <pwd.h>
4710275Ssam #include <setjmp.h>
4810275Ssam #include <netdb.h>
4910423Ssam #include <errno.h>
5026044Sminshall #include <strings.h>
5126493Sminshall #include <syslog.h>
5236435Sbostic #include <varargs.h>
5310275Ssam 
5410695Ssam /*
5510695Ssam  * File containing login names
5610695Ssam  * NOT to be used on this machine.
5710695Ssam  * Commonly used to disallow uucp.
5810695Ssam  */
5910695Ssam #define	FTPUSERS	"/etc/ftpusers"
6010695Ssam 
6110275Ssam extern	int errno;
6210275Ssam extern	char *sys_errlist[];
6336304Skarels extern	int sys_nerr;
6410275Ssam extern	char *crypt();
6510275Ssam extern	char version[];
6610275Ssam extern	char *home;		/* pointer to home directory for glob */
6736276Sbostic extern	FILE *ftpd_popen(), *fopen(), *freopen();
6836304Skarels extern	int  ftpd_pclose(), fclose();
6926044Sminshall extern	char *getline();
7026044Sminshall extern	char cbuf[];
7136551Sbostic extern	off_t restart_point;
7210275Ssam 
7310275Ssam struct	sockaddr_in ctrl_addr;
7410275Ssam struct	sockaddr_in data_source;
7510275Ssam struct	sockaddr_in data_dest;
7610275Ssam struct	sockaddr_in his_addr;
7710275Ssam 
7810275Ssam int	data;
7926044Sminshall jmp_buf	errcatch, urgcatch;
8010275Ssam int	logged_in;
8110275Ssam struct	passwd *pw;
8210275Ssam int	debug;
8326493Sminshall int	timeout = 900;    /* timeout after 15 minutes of inactivity */
8411757Ssam int	logging;
8510275Ssam int	guest;
8610275Ssam int	type;
8710275Ssam int	form;
8810275Ssam int	stru;			/* avoid C keyword */
8910275Ssam int	mode;
9010321Ssam int	usedefault = 1;		/* for data transfers */
9136304Skarels int	pdata = -1;		/* for passive mode */
9226044Sminshall int	transflag;
9326044Sminshall char	tmpline[7];
9436276Sbostic char	hostname[MAXHOSTNAMELEN];
9536276Sbostic char	remotehost[MAXHOSTNAMELEN];
9610275Ssam 
9711653Ssam /*
9811653Ssam  * Timeout intervals for retrying connections
9911653Ssam  * to hosts that don't accept PORT cmds.  This
10011653Ssam  * is a kludge, but given the problems with TCP...
10111653Ssam  */
10211653Ssam #define	SWAITMAX	90	/* wait at most 90 seconds */
10311653Ssam #define	SWAITINT	5	/* interval between retries */
10411653Ssam 
10511653Ssam int	swaitmax = SWAITMAX;
10611653Ssam int	swaitint = SWAITINT;
10711653Ssam 
10810275Ssam int	lostconn();
10926044Sminshall int	myoob();
11010275Ssam FILE	*getdatasock(), *dataconn();
11110275Ssam 
11210275Ssam main(argc, argv)
11310275Ssam 	int argc;
11410275Ssam 	char *argv[];
11510275Ssam {
11627750Sminshall 	int addrlen, on = 1;
11710275Ssam 	char *cp;
11810275Ssam 
11916339Skarels 	addrlen = sizeof (his_addr);
12036304Skarels 	if (getpeername(0, (struct sockaddr *)&his_addr, &addrlen) < 0) {
12126493Sminshall 		syslog(LOG_ERR, "getpeername (%s): %m",argv[0]);
12210275Ssam 		exit(1);
12310275Ssam 	}
12416339Skarels 	addrlen = sizeof (ctrl_addr);
12536304Skarels 	if (getsockname(0, (struct sockaddr *)&ctrl_addr, &addrlen) < 0) {
12626493Sminshall 		syslog(LOG_ERR, "getsockname (%s): %m",argv[0]);
12716339Skarels 		exit(1);
12816339Skarels 	}
12916339Skarels 	data_source.sin_port = htons(ntohs(ctrl_addr.sin_port) - 1);
13010275Ssam 	debug = 0;
13126493Sminshall 	openlog("ftpd", LOG_PID, LOG_DAEMON);
13210275Ssam 	argc--, argv++;
13310275Ssam 	while (argc > 0 && *argv[0] == '-') {
13410275Ssam 		for (cp = &argv[0][1]; *cp; cp++) switch (*cp) {
13510275Ssam 
13611653Ssam 		case 'v':
13711653Ssam 			debug = 1;
13811653Ssam 			break;
13911653Ssam 
14010275Ssam 		case 'd':
14110275Ssam 			debug = 1;
14210275Ssam 			break;
14310275Ssam 
14411757Ssam 		case 'l':
14511757Ssam 			logging = 1;
14611757Ssam 			break;
14711757Ssam 
14811653Ssam 		case 't':
14911653Ssam 			timeout = atoi(++cp);
15011653Ssam 			goto nextopt;
15111653Ssam 
15210275Ssam 		default:
15316339Skarels 			fprintf(stderr, "ftpd: Unknown flag -%c ignored.\n",
15416339Skarels 			     *cp);
15510275Ssam 			break;
15610275Ssam 		}
15711653Ssam nextopt:
15810275Ssam 		argc--, argv++;
15910275Ssam 	}
16030944Scsvsj 	(void) freopen("/dev/null", "w", stderr);
16126493Sminshall 	(void) signal(SIGPIPE, lostconn);
16226493Sminshall 	(void) signal(SIGCHLD, SIG_IGN);
16335691Sbostic 	if ((int)signal(SIGURG, myoob) < 0)
16426493Sminshall 		syslog(LOG_ERR, "signal: %m");
16535691Sbostic 
16627750Sminshall 	/* handle urgent data inline */
16736276Sbostic 	/* Sequent defines this, but it doesn't work */
16827750Sminshall #ifdef SO_OOBINLINE
16936276Sbostic 	if (setsockopt(0, SOL_SOCKET, SO_OOBINLINE, (char *)&on, sizeof(on)) < 0)
17027750Sminshall 		syslog(LOG_ERR, "setsockopt: %m");
17136276Sbostic #endif
17236304Skarels 	if (fcntl(fileno(stdin), F_SETOWN, getpid()) == -1)
17336304Skarels 		syslog(LOG_ERR, "fcntl F_SETOWN: %m");
17416760Slepreau 	dolog(&his_addr);
17516339Skarels 	/* do telnet option negotiation here */
17616339Skarels 	/*
17716339Skarels 	 * Set up default state
17816339Skarels 	 */
17916339Skarels 	data = -1;
18016339Skarels 	type = TYPE_A;
18116339Skarels 	form = FORM_N;
18216339Skarels 	stru = STRU_F;
18316339Skarels 	mode = MODE_S;
18426044Sminshall 	tmpline[0] = '\0';
18526493Sminshall 	(void) gethostname(hostname, sizeof (hostname));
18636276Sbostic 	reply(220, "%s FTP server (%s) ready.", hostname, version);
18736304Skarels 	(void) setjmp(errcatch);
18836304Skarels 	for (;;)
18926493Sminshall 		(void) yyparse();
19010275Ssam }
19110419Ssam 
19210275Ssam lostconn()
19310275Ssam {
19410275Ssam 
19514089Ssam 	if (debug)
19626493Sminshall 		syslog(LOG_DEBUG, "lost connection");
19714089Ssam 	dologout(-1);
19810275Ssam }
19910275Ssam 
20035672Sbostic static char ttyline[20];
20135672Sbostic 
20236185Sbostic /*
20336185Sbostic  * Helper function for sgetpwnam().
20436185Sbostic  */
20536185Sbostic char *
20636185Sbostic sgetsave(s)
20736185Sbostic 	char *s;
20836185Sbostic {
20936185Sbostic 	char *malloc();
21036185Sbostic 	char *new = malloc((unsigned) strlen(s) + 1);
21136185Sbostic 
21236185Sbostic 	if (new == NULL) {
21336276Sbostic 		reply(553, "Local resource failure: malloc");
21436185Sbostic 		dologout(1);
21536185Sbostic 	}
21636185Sbostic 	(void) strcpy(new, s);
21736185Sbostic 	return (new);
21836185Sbostic }
21936185Sbostic 
22036185Sbostic /*
22136185Sbostic  * Save the result of a getpwnam.  Used for USER command, since
22236185Sbostic  * the data returned must not be clobbered by any other command
22336185Sbostic  * (e.g., globbing).
22436185Sbostic  */
22536185Sbostic struct passwd *
22636185Sbostic sgetpwnam(name)
22736185Sbostic 	char *name;
22836185Sbostic {
22936185Sbostic 	static struct passwd save;
23036185Sbostic 	register struct passwd *p;
23136185Sbostic 	char *sgetsave();
23236185Sbostic 
23336185Sbostic 	if ((p = getpwnam(name)) == NULL)
23436185Sbostic 		return (p);
23536185Sbostic 	if (save.pw_name) {
23636185Sbostic 		free(save.pw_name);
23736185Sbostic 		free(save.pw_passwd);
23836185Sbostic 		free(save.pw_comment);
23936185Sbostic 		free(save.pw_gecos);
24036185Sbostic 		free(save.pw_dir);
24136185Sbostic 		free(save.pw_shell);
24236185Sbostic 	}
24336185Sbostic 	save = *p;
24436185Sbostic 	save.pw_name = sgetsave(p->pw_name);
24536185Sbostic 	save.pw_passwd = sgetsave(p->pw_passwd);
24636185Sbostic 	save.pw_comment = sgetsave(p->pw_comment);
24736185Sbostic 	save.pw_gecos = sgetsave(p->pw_gecos);
24836185Sbostic 	save.pw_dir = sgetsave(p->pw_dir);
24936185Sbostic 	save.pw_shell = sgetsave(p->pw_shell);
25036185Sbostic 	return (&save);
25136185Sbostic }
25236185Sbostic 
25336304Skarels int login_attempts;		/* number of failed login attempts */
25436304Skarels int askpasswd;			/* had user command, ask for passwd */
25536304Skarels 
25636304Skarels /*
25736304Skarels  * USER command.
25836304Skarels  * Sets global passwd pointer pw if named account exists
25936304Skarels  * and is acceptable; sets askpasswd if a PASS command is
26036304Skarels  * expected. If logged in previously, need to reset state.
26136304Skarels  * If name is "ftp" or "anonymous" and ftp account exists,
26236304Skarels  * set guest and pw, then just return.
26336304Skarels  * If account doesn't exist, ask for passwd anyway.
26436304Skarels  * Otherwise, check user requesting login privileges.
26536304Skarels  * Disallow anyone who does not have a standard
26636304Skarels  * shell returned by getusershell() (/etc/shells).
26736304Skarels  * Disallow anyone mentioned in the file FTPUSERS
26836304Skarels  * to allow people such as root and uucp to be avoided.
26936304Skarels  */
27036304Skarels user(name)
27136304Skarels 	char *name;
27236304Skarels {
27336304Skarels 	register char *cp;
27436304Skarels 	FILE *fd;
27536304Skarels 	char *shell;
27636551Sbostic 	char line[BUFSIZ], *getusershell();
27736304Skarels 
27836304Skarels 	if (logged_in) {
27936304Skarels 		if (guest) {
28036304Skarels 			reply(530, "Can't change user from guest login.");
28136304Skarels 			return;
28236304Skarels 		}
28336304Skarels 		end_login();
28436304Skarels 	}
28536304Skarels 
28636304Skarels 	guest = 0;
28736304Skarels 	if (strcmp(name, "ftp") == 0 || strcmp(name, "anonymous") == 0) {
28836304Skarels 		if ((pw = sgetpwnam("ftp")) != NULL) {
28936304Skarels 			guest = 1;
29036304Skarels 			askpasswd = 1;
29136304Skarels 			reply(331, "Guest login ok, send ident as password.");
29236551Sbostic 		}
29336551Sbostic 		else
29436304Skarels 			reply(530, "User %s unknown.", name);
29536304Skarels 		return;
29636304Skarels 	}
29736304Skarels 	if (pw = sgetpwnam(name)) {
29836304Skarels 		if ((shell = pw->pw_shell) == NULL || *shell == 0)
29936304Skarels 			shell = "/bin/sh";
30036304Skarels 		while ((cp = getusershell()) != NULL)
30136304Skarels 			if (strcmp(cp, shell) == 0)
30236304Skarels 				break;
30336304Skarels 		endusershell();
30436304Skarels 		if (cp == NULL) {
30536304Skarels 			reply(530, "User %s access denied.", name);
30636550Sbostic 			syslog(LOG_ERR, "FTP LOGIN REFUSED FROM %s, %s",
30736550Sbostic 			    remotehost, name);
30836304Skarels 			pw = (struct passwd *) NULL;
30936304Skarels 			return;
31036304Skarels 		}
31136304Skarels 		if ((fd = fopen(FTPUSERS, "r")) != NULL) {
31236304Skarels 		    while (fgets(line, sizeof (line), fd) != NULL) {
31336304Skarels 			if ((cp = index(line, '\n')) != NULL)
31436304Skarels 				*cp = '\0';
31536304Skarels 			if (strcmp(line, name) == 0) {
31636304Skarels 				reply(530, "User %s access denied.", name);
31736550Sbostic 				syslog(LOG_ERR, "FTP LOGIN REFUSED FROM %s, %s",
31836550Sbostic 				    remotehost, name);
31936304Skarels 				pw = (struct passwd *) NULL;
32036304Skarels 				return;
32136304Skarels 			}
32236304Skarels 		    }
32336304Skarels 		}
32436304Skarels 		(void) fclose(fd);
32536304Skarels 	}
32636304Skarels 	reply(331, "Password required for %s.", name);
32736304Skarels 	askpasswd = 1;
32836304Skarels 	/*
32936304Skarels 	 * Delay before reading passwd after first failed
33036304Skarels 	 * attempt to slow down passwd-guessing programs.
33136304Skarels 	 */
33236304Skarels 	if (login_attempts)
33336304Skarels 		sleep((unsigned) login_attempts);
33436304Skarels }
33536304Skarels 
33636304Skarels /*
33736304Skarels  * Terminate login as previous user, if any, resetting state;
33836304Skarels  * used when USER command is given or login fails.
33936304Skarels  */
34036304Skarels end_login()
34136304Skarels {
34236304Skarels 
34336304Skarels 	(void) seteuid((uid_t)0);
34436304Skarels 	if (logged_in)
34536304Skarels 		logwtmp(ttyline, "", "");
34636304Skarels 	pw = NULL;
34736304Skarels 	logged_in = 0;
34836304Skarels 	guest = 0;
34936304Skarels }
35036304Skarels 
35110275Ssam pass(passwd)
35210275Ssam 	char *passwd;
35310275Ssam {
35436304Skarels 	char *xpasswd, *salt;
35510275Ssam 
35636304Skarels 	if (logged_in || askpasswd == 0) {
35710275Ssam 		reply(503, "Login with USER first.");
35810275Ssam 		return;
35910275Ssam 	}
36036304Skarels 	askpasswd = 0;
36110275Ssam 	if (!guest) {		/* "ftp" is only account allowed no password */
36236304Skarels 		if (pw == NULL)
36336304Skarels 			salt = "xx";
36436304Skarels 		else
36536304Skarels 			salt = pw->pw_passwd;
36636304Skarels 		xpasswd = crypt(passwd, salt);
36716760Slepreau 		/* The strcmp does not catch null passwords! */
36836304Skarels 		if (pw == NULL || *pw->pw_passwd == '\0' ||
36936304Skarels 		    strcmp(xpasswd, pw->pw_passwd)) {
37010275Ssam 			reply(530, "Login incorrect.");
37110275Ssam 			pw = NULL;
37236304Skarels 			if (login_attempts++ >= 5) {
37336304Skarels 				syslog(LOG_ERR,
37436304Skarels 				    "repeated login failures from %s",
37536304Skarels 				    remotehost);
37636304Skarels 				exit(0);
37736304Skarels 			}
37810275Ssam 			return;
37910275Ssam 		}
38010275Ssam 	}
38136304Skarels 	login_attempts = 0;		/* this time successful */
38236304Skarels 	(void) setegid((gid_t)pw->pw_gid);
38336304Skarels 	(void) initgroups(pw->pw_name, pw->pw_gid);
38416033Sralph 
38536192Sbostic 	/* open wtmp before chroot */
38636192Sbostic 	(void)sprintf(ttyline, "ftp%d", getpid());
38736192Sbostic 	logwtmp(ttyline, pw->pw_name, remotehost);
38836192Sbostic 	logged_in = 1;
38936192Sbostic 
39036446Sbostic 	if (guest) {
39136446Sbostic 		if (chroot(pw->pw_dir) < 0) {
39236446Sbostic 			reply(550, "Can't set guest privileges.");
39336446Sbostic 			goto bad;
39436446Sbostic 		}
39536304Skarels 	}
39636446Sbostic 	else if (chdir(pw->pw_dir))
39736446Sbostic 		if (chdir("/")) {
39836446Sbostic 			reply(530, "User %s: can't change directory to %s.",
39936446Sbostic 			    pw->pw_name, pw->pw_dir);
40036446Sbostic 			goto bad;
40136446Sbostic 		}
40236446Sbostic 		else
40336446Sbostic 			lreply(230, "No directory! Logging in with home=/");
40436304Skarels 	if (seteuid((uid_t)pw->pw_uid) < 0) {
40536304Skarels 		reply(550, "Can't set uid.");
40636304Skarels 		goto bad;
40736304Skarels 	}
40836550Sbostic 	if (guest) {
40936192Sbostic 		reply(230, "Guest login ok, access restrictions apply.");
41036550Sbostic 		syslog(LOG_INFO, "ANONYMOUS FTP LOGIN FROM %s, %s",
41136550Sbostic 		    remotehost, passwd);
41236551Sbostic 	}
41336551Sbostic 	else {
41410275Ssam 		reply(230, "User %s logged in.", pw->pw_name);
41536550Sbostic 		syslog(LOG_INFO, "FTP LOGIN FROM %s, %s",
41636550Sbostic 		    remotehost, pw->pw_name);
41736550Sbostic 	}
41810303Ssam 	home = pw->pw_dir;		/* home dir for globbing */
41910303Ssam 	return;
42010303Ssam bad:
42136304Skarels 	/* Forget all about it... */
42236304Skarels 	end_login();
42310275Ssam }
42410275Ssam 
42510275Ssam retrieve(cmd, name)
42610275Ssam 	char *cmd, *name;
42710275Ssam {
42810275Ssam 	FILE *fin, *dout;
42910275Ssam 	struct stat st;
43026044Sminshall 	int (*closefunc)(), tmp;
43110275Ssam 
432*36557Sbostic 	if (cmd == 0) {
43336446Sbostic 		fin = fopen(name, "r"), closefunc = fclose;
434*36557Sbostic 		st.st_size = 0;
435*36557Sbostic 	} else {
43610275Ssam 		char line[BUFSIZ];
43710275Ssam 
43826493Sminshall 		(void) sprintf(line, cmd, name), name = line;
43936304Skarels 		fin = ftpd_popen(line, "r"), closefunc = ftpd_pclose;
440*36557Sbostic 		st.st_size = -1;
44110275Ssam 	}
44210275Ssam 	if (fin == NULL) {
44313152Ssam 		if (errno != 0)
44436304Skarels 			perror_reply(550, name);
44510275Ssam 		return;
44610275Ssam 	}
44710275Ssam 	if (cmd == 0 &&
44810275Ssam 	    (stat(name, &st) < 0 || (st.st_mode&S_IFMT) != S_IFREG)) {
44910275Ssam 		reply(550, "%s: not a plain file.", name);
45010275Ssam 		goto done;
45110275Ssam 	}
45236551Sbostic 	if (restart_point)
45336551Sbostic 		if (type == TYPE_A) {
45436551Sbostic 			if (fseek(fin, restart_point, L_SET) < 0)
45536551Sbostic 				perror_reply(550, name);
45636551Sbostic 		}
45736551Sbostic 		else if (lseek(fileno(fin), restart_point, L_SET) < 0)
45836551Sbostic 			perror_reply(550, name);
45910275Ssam 	dout = dataconn(name, st.st_size, "w");
46010275Ssam 	if (dout == NULL)
46110275Ssam 		goto done;
46236446Sbostic 	if ((tmp = send_data(fin, dout, st.st_blksize)) > 0 ||
46336551Sbostic 	    ferror(dout) > 0)
46436304Skarels 		perror_reply(550, name);
46536551Sbostic 	else if (tmp == 0)
46610275Ssam 		reply(226, "Transfer complete.");
46726493Sminshall 	(void) fclose(dout);
46826044Sminshall 	data = -1;
46926044Sminshall 	pdata = -1;
47010275Ssam done:
47110275Ssam 	(*closefunc)(fin);
47210275Ssam }
47310275Ssam 
47436304Skarels store(name, mode, unique)
47510275Ssam 	char *name, *mode;
47636304Skarels 	int unique;
47710275Ssam {
47810275Ssam 	FILE *fout, *din;
47936446Sbostic 	struct stat st;
48036304Skarels 	int (*closefunc)(), tmp;
48136304Skarels 	char *gunique();
48210275Ssam 
48336446Sbostic 	if (unique && stat(name, &st) == 0 &&
48436446Sbostic 	    (name = gunique(name)) == NULL)
48536446Sbostic 		return;
48610303Ssam 
48736446Sbostic 	fout = fopen(name, mode), closefunc = fclose;
48810275Ssam 	if (fout == NULL) {
48936304Skarels 		perror_reply(553, name);
49010275Ssam 		return;
49110275Ssam 	}
49236551Sbostic 	if (restart_point)
49336551Sbostic 		if (type == TYPE_A) {
49436551Sbostic 			if (fseek(fout, restart_point, L_SET) < 0)
49536551Sbostic 				perror_reply(550, name);
49636551Sbostic 		}
49736551Sbostic 		else if (lseek(fileno(fout), restart_point, L_SET) < 0)
49836551Sbostic 			perror_reply(550, name);
49936304Skarels 	din = dataconn(name, (off_t)-1, "r");
50010275Ssam 	if (din == NULL)
50110275Ssam 		goto done;
50236304Skarels 	if ((tmp = receive_data(din, fout)) > 0)
50336304Skarels 		perror_reply(552, name);
50436304Skarels 	else if (tmp == 0) {
50536304Skarels 		if (ferror(fout) > 0)
50636304Skarels 			perror_reply(552, name);
50736304Skarels 		else if (unique)
50836304Skarels 			reply(226, "Transfer complete (unique file name:%s).",
50936304Skarels 			    name);
51036304Skarels 		else
51136304Skarels 			reply(226, "Transfer complete.");
51226044Sminshall 	}
51326493Sminshall 	(void) fclose(din);
51426044Sminshall 	data = -1;
51526044Sminshall 	pdata = -1;
51610275Ssam done:
51710275Ssam 	(*closefunc)(fout);
51810275Ssam }
51910275Ssam 
52010275Ssam FILE *
52110275Ssam getdatasock(mode)
52210275Ssam 	char *mode;
52310275Ssam {
52417157Ssam 	int s, on = 1;
52510275Ssam 
52610275Ssam 	if (data >= 0)
52710275Ssam 		return (fdopen(data, mode));
52813247Ssam 	s = socket(AF_INET, SOCK_STREAM, 0);
52910602Ssam 	if (s < 0)
53010275Ssam 		return (NULL);
53136304Skarels 	(void) seteuid((uid_t)0);
53226493Sminshall 	if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof (on)) < 0)
53310602Ssam 		goto bad;
53413152Ssam 	/* anchor socket to avoid multi-homing problems */
53513152Ssam 	data_source.sin_family = AF_INET;
53613152Ssam 	data_source.sin_addr = ctrl_addr.sin_addr;
53736304Skarels 	if (bind(s, (struct sockaddr *)&data_source, sizeof (data_source)) < 0)
53810602Ssam 		goto bad;
53936304Skarels 	(void) seteuid((uid_t)pw->pw_uid);
54010275Ssam 	return (fdopen(s, mode));
54110602Ssam bad:
54236304Skarels 	(void) seteuid((uid_t)pw->pw_uid);
54326493Sminshall 	(void) close(s);
54410602Ssam 	return (NULL);
54510275Ssam }
54610275Ssam 
54710275Ssam FILE *
54810275Ssam dataconn(name, size, mode)
54910275Ssam 	char *name;
55011653Ssam 	off_t size;
55110275Ssam 	char *mode;
55210275Ssam {
55310275Ssam 	char sizebuf[32];
55410275Ssam 	FILE *file;
55511653Ssam 	int retry = 0;
55610275Ssam 
55736304Skarels 	if (size != (off_t) -1)
55826493Sminshall 		(void) sprintf (sizebuf, " (%ld bytes)", size);
55910275Ssam 	else
56010275Ssam 		(void) strcpy(sizebuf, "");
56136304Skarels 	if (pdata >= 0) {
56226044Sminshall 		struct sockaddr_in from;
56326044Sminshall 		int s, fromlen = sizeof(from);
56426044Sminshall 
56536304Skarels 		s = accept(pdata, (struct sockaddr *)&from, &fromlen);
56626044Sminshall 		if (s < 0) {
56726044Sminshall 			reply(425, "Can't open data connection.");
56826044Sminshall 			(void) close(pdata);
56926044Sminshall 			pdata = -1;
57026044Sminshall 			return(NULL);
57126044Sminshall 		}
57226044Sminshall 		(void) close(pdata);
57326044Sminshall 		pdata = s;
57436235Skarels 		reply(150, "Opening %s mode data connection for %s%s.",
57536235Skarels 		     type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf);
57626044Sminshall 		return(fdopen(pdata, mode));
57726044Sminshall 	}
57810275Ssam 	if (data >= 0) {
57910275Ssam 		reply(125, "Using existing data connection for %s%s.",
58010275Ssam 		    name, sizebuf);
58110321Ssam 		usedefault = 1;
58210275Ssam 		return (fdopen(data, mode));
58310275Ssam 	}
58410566Ssam 	if (usedefault)
58510422Ssam 		data_dest = his_addr;
58610422Ssam 	usedefault = 1;
58710275Ssam 	file = getdatasock(mode);
58810275Ssam 	if (file == NULL) {
58910275Ssam 		reply(425, "Can't create data socket (%s,%d): %s.",
59013247Ssam 		    inet_ntoa(data_source.sin_addr),
59110275Ssam 		    ntohs(data_source.sin_port),
59236304Skarels 		    errno < sys_nerr ? sys_errlist[errno] : "unknown error");
59310275Ssam 		return (NULL);
59410275Ssam 	}
59510275Ssam 	data = fileno(file);
59636304Skarels 	while (connect(data, (struct sockaddr *)&data_dest,
59736304Skarels 	    sizeof (data_dest)) < 0) {
59811653Ssam 		if (errno == EADDRINUSE && retry < swaitmax) {
59926493Sminshall 			sleep((unsigned) swaitint);
60011653Ssam 			retry += swaitint;
60111653Ssam 			continue;
60211653Ssam 		}
60336304Skarels 		perror_reply(425, "Can't build data connection");
60410275Ssam 		(void) fclose(file);
60510275Ssam 		data = -1;
60610275Ssam 		return (NULL);
60710275Ssam 	}
60836235Skarels 	reply(150, "Opening %s mode data connection for %s%s.",
60936235Skarels 	     type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf);
61010275Ssam 	return (file);
61110275Ssam }
61210275Ssam 
61310275Ssam /*
61410275Ssam  * Tranfer the contents of "instr" to
61510275Ssam  * "outstr" peer using the appropriate
61610275Ssam  * encapulation of the date subject
61710275Ssam  * to Mode, Structure, and Type.
61810275Ssam  *
61910275Ssam  * NB: Form isn't handled.
62010275Ssam  */
62136446Sbostic send_data(instr, outstr, blksize)
62210275Ssam 	FILE *instr, *outstr;
62336446Sbostic 	off_t blksize;
62410275Ssam {
62536446Sbostic 	register int c, cnt;
62636446Sbostic 	register char *buf;
62736446Sbostic 	int netfd, filefd;
62810275Ssam 
62926044Sminshall 	transflag++;
63026044Sminshall 	if (setjmp(urgcatch)) {
63126044Sminshall 		transflag = 0;
63226044Sminshall 		return(-1);
63326044Sminshall 	}
63410275Ssam 	switch (type) {
63510275Ssam 
63610275Ssam 	case TYPE_A:
63710275Ssam 		while ((c = getc(instr)) != EOF) {
63811220Ssam 			if (c == '\n') {
63926044Sminshall 				if (ferror (outstr)) {
64026044Sminshall 					transflag = 0;
64111220Ssam 					return (1);
64226044Sminshall 				}
64327750Sminshall 				(void) putc('\r', outstr);
64411220Ssam 			}
64527750Sminshall 			(void) putc(c, outstr);
64610275Ssam 		}
64726044Sminshall 		transflag = 0;
64826044Sminshall 		if (ferror (instr) || ferror (outstr)) {
64911220Ssam 			return (1);
65026044Sminshall 		}
65110275Ssam 		return (0);
65236446Sbostic 
65310275Ssam 	case TYPE_I:
65410275Ssam 	case TYPE_L:
65536446Sbostic 		if ((buf = malloc((u_int)blksize)) == NULL) {
65636446Sbostic 			transflag = 0;
65736446Sbostic 			return (1);
65836446Sbostic 		}
65910275Ssam 		netfd = fileno(outstr);
66010275Ssam 		filefd = fileno(instr);
66136446Sbostic 		while ((cnt = read(filefd, buf, sizeof(buf))) > 0 &&
66236446Sbostic 		    write(netfd, buf, cnt) == cnt);
66326044Sminshall 		transflag = 0;
66436446Sbostic 		(void)free(buf);
66536446Sbostic 		return (cnt != 0);
66610275Ssam 	}
66727106Smckusick 	reply(550, "Unimplemented TYPE %d in send_data", type);
66826044Sminshall 	transflag = 0;
66927106Smckusick 	return (-1);
67010275Ssam }
67110275Ssam 
67210275Ssam /*
67310275Ssam  * Transfer data from peer to
67410275Ssam  * "outstr" using the appropriate
67510275Ssam  * encapulation of the data subject
67610275Ssam  * to Mode, Structure, and Type.
67710275Ssam  *
67810275Ssam  * N.B.: Form isn't handled.
67910275Ssam  */
68010275Ssam receive_data(instr, outstr)
68110275Ssam 	FILE *instr, *outstr;
68210275Ssam {
68310275Ssam 	register int c;
68411220Ssam 	int cnt;
68510275Ssam 	char buf[BUFSIZ];
68610275Ssam 
68710275Ssam 
68826044Sminshall 	transflag++;
68926044Sminshall 	if (setjmp(urgcatch)) {
69026044Sminshall 		transflag = 0;
69126044Sminshall 		return(-1);
69226044Sminshall 	}
69310275Ssam 	switch (type) {
69410275Ssam 
69510275Ssam 	case TYPE_I:
69610275Ssam 	case TYPE_L:
69726044Sminshall 		while ((cnt = read(fileno(instr), buf, sizeof buf)) > 0) {
69826044Sminshall 			if (write(fileno(outstr), buf, cnt) < 0) {
69926044Sminshall 				transflag = 0;
70010275Ssam 				return (1);
70126044Sminshall 			}
70226044Sminshall 		}
70326044Sminshall 		transflag = 0;
70410275Ssam 		return (cnt < 0);
70510275Ssam 
70610275Ssam 	case TYPE_E:
70727106Smckusick 		reply(553, "TYPE E not implemented.");
70826044Sminshall 		transflag = 0;
70927106Smckusick 		return (-1);
71010275Ssam 
71110275Ssam 	case TYPE_A:
71210275Ssam 		while ((c = getc(instr)) != EOF) {
71327750Sminshall 			while (c == '\r') {
71426044Sminshall 				if (ferror (outstr)) {
71526044Sminshall 					transflag = 0;
71611220Ssam 					return (1);
71726044Sminshall 				}
71811220Ssam 				if ((c = getc(instr)) != '\n')
71927750Sminshall 					(void) putc ('\r', outstr);
72026044Sminshall 			/*	if (c == '\0')			*/
72126044Sminshall 			/*		continue;		*/
72210275Ssam 			}
72327750Sminshall 			(void) putc (c, outstr);
72410275Ssam 		}
72526044Sminshall 		transflag = 0;
72611220Ssam 		if (ferror (instr) || ferror (outstr))
72711220Ssam 			return (1);
72810275Ssam 		return (0);
72910275Ssam 	}
73026044Sminshall 	transflag = 0;
73110275Ssam 	fatal("Unknown type in receive_data.");
73210275Ssam 	/*NOTREACHED*/
73310275Ssam }
73410275Ssam 
73510275Ssam fatal(s)
73610275Ssam 	char *s;
73710275Ssam {
73810275Ssam 	reply(451, "Error in server: %s\n", s);
73910275Ssam 	reply(221, "Closing connection due to server error.");
74013247Ssam 	dologout(0);
74110275Ssam }
74210275Ssam 
74336446Sbostic /* VARARGS2 */
74436446Sbostic reply(n, fmt, p0, p1, p2, p3, p4, p5)
74510275Ssam 	int n;
74636446Sbostic 	char *fmt;
74710275Ssam {
74810275Ssam 	printf("%d ", n);
74936446Sbostic 	printf(fmt, p0, p1, p2, p3, p4, p5);
75010275Ssam 	printf("\r\n");
75136435Sbostic 	(void)fflush(stdout);
75210275Ssam 	if (debug) {
75326493Sminshall 		syslog(LOG_DEBUG, "<--- %d ", n);
75436446Sbostic 		syslog(LOG_DEBUG, fmt, p0, p1, p2, p3, p4, p5);
75510275Ssam 	}
75610275Ssam }
75710275Ssam 
75836446Sbostic /* VARARGS2 */
75936446Sbostic lreply(n, fmt, p0, p1, p2, p3, p4, p5)
76010275Ssam 	int n;
76136446Sbostic 	char *fmt;
76210275Ssam {
76336446Sbostic 	printf("%d- ", n);
76436446Sbostic 	printf(fmt, p0, p1, p2, p3, p4, p5);
76536446Sbostic 	printf("\r\n");
76636435Sbostic 	(void)fflush(stdout);
76736446Sbostic 	if (debug) {
76836446Sbostic 		syslog(LOG_DEBUG, "<--- %d- ", n);
76936446Sbostic 		syslog(LOG_DEBUG, fmt, p0, p1, p2, p3, p4, p5);
77036446Sbostic 	}
77110275Ssam }
77210275Ssam 
77310275Ssam ack(s)
77410275Ssam 	char *s;
77510275Ssam {
77627106Smckusick 	reply(250, "%s command successful.", s);
77710275Ssam }
77810275Ssam 
77910275Ssam nack(s)
78010275Ssam 	char *s;
78110275Ssam {
78210275Ssam 	reply(502, "%s command not implemented.", s);
78310275Ssam }
78410275Ssam 
78536304Skarels /* ARGSUSED */
78626493Sminshall yyerror(s)
78726493Sminshall 	char *s;
78810275Ssam {
78926044Sminshall 	char *cp;
79026044Sminshall 
79136551Sbostic 	if (cp = index(cbuf,'\n'))
79236551Sbostic 		*cp = '\0';
79326044Sminshall 	reply(500, "'%s': command not understood.",cbuf);
79410275Ssam }
79510275Ssam 
79610275Ssam delete(name)
79710275Ssam 	char *name;
79810275Ssam {
79910275Ssam 	struct stat st;
80010275Ssam 
80110275Ssam 	if (stat(name, &st) < 0) {
80236304Skarels 		perror_reply(550, name);
80310275Ssam 		return;
80410275Ssam 	}
80510275Ssam 	if ((st.st_mode&S_IFMT) == S_IFDIR) {
80610275Ssam 		if (rmdir(name) < 0) {
80736304Skarels 			perror_reply(550, name);
80810275Ssam 			return;
80910275Ssam 		}
81010275Ssam 		goto done;
81110275Ssam 	}
81210275Ssam 	if (unlink(name) < 0) {
81336304Skarels 		perror_reply(550, name);
81410275Ssam 		return;
81510275Ssam 	}
81610275Ssam done:
81710275Ssam 	ack("DELE");
81810275Ssam }
81910275Ssam 
82010275Ssam cwd(path)
82110275Ssam 	char *path;
82210275Ssam {
82310275Ssam 
82410275Ssam 	if (chdir(path) < 0) {
82536304Skarels 		perror_reply(550, path);
82610275Ssam 		return;
82710275Ssam 	}
82810275Ssam 	ack("CWD");
82910275Ssam }
83010275Ssam 
83110303Ssam makedir(name)
83210275Ssam 	char *name;
83310275Ssam {
83436276Sbostic 	if (mkdir(name, 0777) < 0)
83536304Skarels 		perror_reply(550, name);
83636276Sbostic 	else
83736276Sbostic 		reply(257, "MKD command successful.");
83810275Ssam }
83910275Ssam 
84010303Ssam removedir(name)
84110275Ssam 	char *name;
84210275Ssam {
84310275Ssam 
84410275Ssam 	if (rmdir(name) < 0) {
84536304Skarels 		perror_reply(550, name);
84610275Ssam 		return;
84710275Ssam 	}
84827106Smckusick 	ack("RMD");
84910275Ssam }
85010275Ssam 
85110303Ssam pwd()
85210275Ssam {
85310303Ssam 	char path[MAXPATHLEN + 1];
85436304Skarels 	extern char *getwd();
85510275Ssam 
85636304Skarels 	if (getwd(path) == (char *)NULL) {
85727106Smckusick 		reply(550, "%s.", path);
85810275Ssam 		return;
85910275Ssam 	}
86027106Smckusick 	reply(257, "\"%s\" is current directory.", path);
86110275Ssam }
86210275Ssam 
86310275Ssam char *
86410275Ssam renamefrom(name)
86510275Ssam 	char *name;
86610275Ssam {
86710275Ssam 	struct stat st;
86810275Ssam 
86910275Ssam 	if (stat(name, &st) < 0) {
87036304Skarels 		perror_reply(550, name);
87110275Ssam 		return ((char *)0);
87210275Ssam 	}
87310303Ssam 	reply(350, "File exists, ready for destination name");
87410275Ssam 	return (name);
87510275Ssam }
87610275Ssam 
87710275Ssam renamecmd(from, to)
87810275Ssam 	char *from, *to;
87910275Ssam {
88010275Ssam 
88110275Ssam 	if (rename(from, to) < 0) {
88236304Skarels 		perror_reply(550, "rename");
88310275Ssam 		return;
88410275Ssam 	}
88510275Ssam 	ack("RNTO");
88610275Ssam }
88710275Ssam 
88810275Ssam dolog(sin)
88910275Ssam 	struct sockaddr_in *sin;
89010275Ssam {
89136304Skarels 	struct hostent *hp = gethostbyaddr((char *)&sin->sin_addr,
89210275Ssam 		sizeof (struct in_addr), AF_INET);
89336304Skarels 	time_t t, time();
89426493Sminshall 	extern char *ctime();
89510275Ssam 
89636304Skarels 	if (hp)
89726493Sminshall 		(void) strncpy(remotehost, hp->h_name, sizeof (remotehost));
89836304Skarels 	else
89926493Sminshall 		(void) strncpy(remotehost, inet_ntoa(sin->sin_addr),
90013247Ssam 		    sizeof (remotehost));
90113247Ssam 	if (!logging)
90213247Ssam 		return;
90326493Sminshall 	t = time((time_t *) 0);
90436304Skarels 	syslog(LOG_INFO, "connection from %s at %s",
90536304Skarels 	    remotehost, ctime(&t));
90610275Ssam }
90710695Ssam 
90810695Ssam /*
90913247Ssam  * Record logout in wtmp file
91013247Ssam  * and exit with supplied status.
91113247Ssam  */
91213247Ssam dologout(status)
91313247Ssam 	int status;
91413247Ssam {
91517580Ssam 	if (logged_in) {
91636304Skarels 		(void) seteuid((uid_t)0);
91735672Sbostic 		logwtmp(ttyline, "", "");
91813247Ssam 	}
91914436Ssam 	/* beware of flushing buffers after a SIGPIPE */
92014436Ssam 	_exit(status);
92113247Ssam }
92213247Ssam 
92326044Sminshall myoob()
92426044Sminshall {
92527750Sminshall 	char *cp;
92626044Sminshall 
92727750Sminshall 	/* only process if transfer occurring */
92836304Skarels 	if (!transflag)
92926044Sminshall 		return;
93027750Sminshall 	cp = tmpline;
93127750Sminshall 	if (getline(cp, 7, stdin) == NULL) {
93236304Skarels 		reply(221, "You could at least say goodbye.");
93327750Sminshall 		dologout(0);
93426044Sminshall 	}
93526044Sminshall 	upper(cp);
93626227Ssam 	if (strcmp(cp, "ABOR\r\n"))
93726044Sminshall 		return;
93826044Sminshall 	tmpline[0] = '\0';
93926044Sminshall 	reply(426,"Transfer aborted. Data connection closed.");
94026044Sminshall 	reply(226,"Abort successful");
94126044Sminshall 	longjmp(urgcatch, 1);
94226044Sminshall }
94326044Sminshall 
94427106Smckusick /*
94527106Smckusick  * Note: The 530 reply codes could be 4xx codes, except nothing is
94627106Smckusick  * given in the state tables except 421 which implies an exit.  (RFC959)
94727106Smckusick  */
94826044Sminshall passive()
94926044Sminshall {
95026044Sminshall 	int len;
95126044Sminshall 	struct sockaddr_in tmp;
95226044Sminshall 	register char *p, *a;
95326044Sminshall 
95426044Sminshall 	pdata = socket(AF_INET, SOCK_STREAM, 0);
95526044Sminshall 	if (pdata < 0) {
95627106Smckusick 		reply(530, "Can't open passive connection");
95726044Sminshall 		return;
95826044Sminshall 	}
95926044Sminshall 	tmp = ctrl_addr;
96026044Sminshall 	tmp.sin_port = 0;
96136304Skarels 	(void) seteuid((uid_t)0);
96226493Sminshall 	if (bind(pdata, (struct sockaddr *) &tmp, sizeof(tmp)) < 0) {
96336304Skarels 		(void) seteuid((uid_t)pw->pw_uid);
96426044Sminshall 		(void) close(pdata);
96526044Sminshall 		pdata = -1;
96627106Smckusick 		reply(530, "Can't open passive connection");
96726044Sminshall 		return;
96826044Sminshall 	}
96936304Skarels 	(void) seteuid((uid_t)pw->pw_uid);
97026044Sminshall 	len = sizeof(tmp);
97136304Skarels 	if (getsockname(pdata, (struct sockaddr *) &tmp, &len) < 0) {
97226044Sminshall 		(void) close(pdata);
97326044Sminshall 		pdata = -1;
97427106Smckusick 		reply(530, "Can't open passive connection");
97526044Sminshall 		return;
97626044Sminshall 	}
97726044Sminshall 	if (listen(pdata, 1) < 0) {
97826044Sminshall 		(void) close(pdata);
97926044Sminshall 		pdata = -1;
98027106Smckusick 		reply(530, "Can't open passive connection");
98126044Sminshall 		return;
98226044Sminshall 	}
98326044Sminshall 	a = (char *) &tmp.sin_addr;
98426044Sminshall 	p = (char *) &tmp.sin_port;
98526044Sminshall 
98626044Sminshall #define UC(b) (((int) b) & 0xff)
98726044Sminshall 
98826044Sminshall 	reply(227, "Entering Passive Mode (%d,%d,%d,%d,%d,%d)", UC(a[0]),
98926044Sminshall 		UC(a[1]), UC(a[2]), UC(a[3]), UC(p[0]), UC(p[1]));
99026044Sminshall }
99126044Sminshall 
99236304Skarels /*
99336304Skarels  * Generate unique name for file with basename "local".
99436304Skarels  * The file named "local" is already known to exist.
99536304Skarels  * Generates failure reply on error.
99636304Skarels  */
99726044Sminshall char *
99826044Sminshall gunique(local)
99926044Sminshall 	char *local;
100026044Sminshall {
100126044Sminshall 	static char new[MAXPATHLEN];
100236304Skarels 	struct stat st;
100326044Sminshall 	char *cp = rindex(local, '/');
100426044Sminshall 	int d, count=0;
100526044Sminshall 
100636304Skarels 	if (cp)
100726044Sminshall 		*cp = '\0';
100836304Skarels 	d = stat(cp ? local : ".", &st);
100936304Skarels 	if (cp)
101026044Sminshall 		*cp = '/';
101126044Sminshall 	if (d < 0) {
101236304Skarels 		perror_reply(553, local);
101326044Sminshall 		return((char *) 0);
101426044Sminshall 	}
101526044Sminshall 	(void) strcpy(new, local);
101626044Sminshall 	cp = new + strlen(new);
101726044Sminshall 	*cp++ = '.';
101836304Skarels 	for (count = 1; count < 100; count++) {
101936304Skarels 		(void) sprintf(cp, "%d", count);
102036304Skarels 		if (stat(new, &st) < 0)
102136304Skarels 			return(new);
102226044Sminshall 	}
102336304Skarels 	reply(452, "Unique file name cannot be created.");
102436304Skarels 	return((char *) 0);
102526044Sminshall }
102636304Skarels 
102736304Skarels /*
102836304Skarels  * Format and send reply containing system error number.
102936304Skarels  */
103036304Skarels perror_reply(code, string)
103136304Skarels 	int code;
103236304Skarels 	char *string;
103336304Skarels {
103436304Skarels 
103536304Skarels 	if (errno < sys_nerr)
103636304Skarels 		reply(code, "%s: %s.", string, sys_errlist[errno]);
103736304Skarels 	else
103836304Skarels 		reply(code, "%s: unknown error %d.", string, errno);
103936304Skarels }
1040