xref: /csrg-svn/libexec/ftpd/ftpd.c (revision 36304)
122499Sdist /*
2*36304Skarels  * 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[] =
20*36304Skarels "@(#) 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*36304Skarels static char sccsid[] = "@(#)ftpd.c	5.20 (Berkeley) 12/07/88";
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>
5210275Ssam 
5310695Ssam /*
5410695Ssam  * File containing login names
5510695Ssam  * NOT to be used on this machine.
5610695Ssam  * Commonly used to disallow uucp.
5710695Ssam  */
5810695Ssam #define	FTPUSERS	"/etc/ftpusers"
5910695Ssam 
6010275Ssam extern	int errno;
6110275Ssam extern	char *sys_errlist[];
62*36304Skarels extern	int sys_nerr;
6310275Ssam extern	char *crypt();
6410275Ssam extern	char version[];
6510275Ssam extern	char *home;		/* pointer to home directory for glob */
6636276Sbostic extern	FILE *ftpd_popen(), *fopen(), *freopen();
67*36304Skarels extern	int  ftpd_pclose(), fclose();
6826044Sminshall extern	char *getline();
6926044Sminshall extern	char cbuf[];
7010275Ssam 
7110275Ssam struct	sockaddr_in ctrl_addr;
7210275Ssam struct	sockaddr_in data_source;
7310275Ssam struct	sockaddr_in data_dest;
7410275Ssam struct	sockaddr_in his_addr;
7510275Ssam 
7610275Ssam int	data;
7726044Sminshall jmp_buf	errcatch, urgcatch;
7810275Ssam int	logged_in;
7910275Ssam struct	passwd *pw;
8010275Ssam int	debug;
8126493Sminshall int	timeout = 900;    /* timeout after 15 minutes of inactivity */
8211757Ssam int	logging;
8310275Ssam int	guest;
8410275Ssam int	type;
8510275Ssam int	form;
8610275Ssam int	stru;			/* avoid C keyword */
8710275Ssam int	mode;
8810321Ssam int	usedefault = 1;		/* for data transfers */
89*36304Skarels int	pdata = -1;		/* for passive mode */
9026044Sminshall int	transflag;
9126044Sminshall char	tmpline[7];
9236276Sbostic char	hostname[MAXHOSTNAMELEN];
9336276Sbostic char	remotehost[MAXHOSTNAMELEN];
9410275Ssam 
9511653Ssam /*
9611653Ssam  * Timeout intervals for retrying connections
9711653Ssam  * to hosts that don't accept PORT cmds.  This
9811653Ssam  * is a kludge, but given the problems with TCP...
9911653Ssam  */
10011653Ssam #define	SWAITMAX	90	/* wait at most 90 seconds */
10111653Ssam #define	SWAITINT	5	/* interval between retries */
10211653Ssam 
10311653Ssam int	swaitmax = SWAITMAX;
10411653Ssam int	swaitint = SWAITINT;
10511653Ssam 
10610275Ssam int	lostconn();
10726044Sminshall int	myoob();
10810275Ssam FILE	*getdatasock(), *dataconn();
10910275Ssam 
11010275Ssam main(argc, argv)
11110275Ssam 	int argc;
11210275Ssam 	char *argv[];
11310275Ssam {
11427750Sminshall 	int addrlen, on = 1;
11510275Ssam 	char *cp;
11610275Ssam 
11716339Skarels 	addrlen = sizeof (his_addr);
118*36304Skarels 	if (getpeername(0, (struct sockaddr *)&his_addr, &addrlen) < 0) {
11926493Sminshall 		syslog(LOG_ERR, "getpeername (%s): %m",argv[0]);
12010275Ssam 		exit(1);
12110275Ssam 	}
12216339Skarels 	addrlen = sizeof (ctrl_addr);
123*36304Skarels 	if (getsockname(0, (struct sockaddr *)&ctrl_addr, &addrlen) < 0) {
12426493Sminshall 		syslog(LOG_ERR, "getsockname (%s): %m",argv[0]);
12516339Skarels 		exit(1);
12616339Skarels 	}
12716339Skarels 	data_source.sin_port = htons(ntohs(ctrl_addr.sin_port) - 1);
12810275Ssam 	debug = 0;
12926493Sminshall 	openlog("ftpd", LOG_PID, LOG_DAEMON);
13010275Ssam 	argc--, argv++;
13110275Ssam 	while (argc > 0 && *argv[0] == '-') {
13210275Ssam 		for (cp = &argv[0][1]; *cp; cp++) switch (*cp) {
13310275Ssam 
13411653Ssam 		case 'v':
13511653Ssam 			debug = 1;
13611653Ssam 			break;
13711653Ssam 
13810275Ssam 		case 'd':
13910275Ssam 			debug = 1;
14010275Ssam 			break;
14110275Ssam 
14211757Ssam 		case 'l':
14311757Ssam 			logging = 1;
14411757Ssam 			break;
14511757Ssam 
14611653Ssam 		case 't':
14711653Ssam 			timeout = atoi(++cp);
14811653Ssam 			goto nextopt;
14911653Ssam 			break;
15011653Ssam 
15110275Ssam 		default:
15216339Skarels 			fprintf(stderr, "ftpd: Unknown flag -%c ignored.\n",
15316339Skarels 			     *cp);
15410275Ssam 			break;
15510275Ssam 		}
15611653Ssam nextopt:
15710275Ssam 		argc--, argv++;
15810275Ssam 	}
15930944Scsvsj 	(void) freopen("/dev/null", "w", stderr);
16026493Sminshall 	(void) signal(SIGPIPE, lostconn);
16126493Sminshall 	(void) signal(SIGCHLD, SIG_IGN);
16235691Sbostic 	if ((int)signal(SIGURG, myoob) < 0)
16326493Sminshall 		syslog(LOG_ERR, "signal: %m");
16435691Sbostic 
16527750Sminshall 	/* handle urgent data inline */
16636276Sbostic 	/* Sequent defines this, but it doesn't work */
16727750Sminshall #ifdef SO_OOBINLINE
16836276Sbostic 	if (setsockopt(0, SOL_SOCKET, SO_OOBINLINE, (char *)&on, sizeof(on)) < 0)
16927750Sminshall 		syslog(LOG_ERR, "setsockopt: %m");
17036276Sbostic #endif
171*36304Skarels 	if (fcntl(fileno(stdin), F_SETOWN, getpid()) == -1)
172*36304Skarels 		syslog(LOG_ERR, "fcntl F_SETOWN: %m");
17316760Slepreau 	dolog(&his_addr);
17416339Skarels 	/* do telnet option negotiation here */
17516339Skarels 	/*
17616339Skarels 	 * Set up default state
17716339Skarels 	 */
17816339Skarels 	data = -1;
17916339Skarels 	type = TYPE_A;
18016339Skarels 	form = FORM_N;
18116339Skarels 	stru = STRU_F;
18216339Skarels 	mode = MODE_S;
18326044Sminshall 	tmpline[0] = '\0';
18426493Sminshall 	(void) gethostname(hostname, sizeof (hostname));
18536276Sbostic 	reply(220, "%s FTP server (%s) ready.", hostname, version);
186*36304Skarels 	(void) setjmp(errcatch);
187*36304Skarels 	for (;;)
18826493Sminshall 		(void) yyparse();
18910275Ssam }
19010419Ssam 
19110275Ssam lostconn()
19210275Ssam {
19310275Ssam 
19414089Ssam 	if (debug)
19526493Sminshall 		syslog(LOG_DEBUG, "lost connection");
19614089Ssam 	dologout(-1);
19710275Ssam }
19810275Ssam 
19935672Sbostic static char ttyline[20];
20035672Sbostic 
20136185Sbostic /*
20236185Sbostic  * Helper function for sgetpwnam().
20336185Sbostic  */
20436185Sbostic char *
20536185Sbostic sgetsave(s)
20636185Sbostic 	char *s;
20736185Sbostic {
20836185Sbostic 	char *malloc();
20936185Sbostic 	char *new = malloc((unsigned) strlen(s) + 1);
21036185Sbostic 
21136185Sbostic 	if (new == NULL) {
21236276Sbostic 		reply(553, "Local resource failure: malloc");
21336185Sbostic 		dologout(1);
21436185Sbostic 	}
21536185Sbostic 	(void) strcpy(new, s);
21636185Sbostic 	return (new);
21736185Sbostic }
21836185Sbostic 
21936185Sbostic /*
22036185Sbostic  * Save the result of a getpwnam.  Used for USER command, since
22136185Sbostic  * the data returned must not be clobbered by any other command
22236185Sbostic  * (e.g., globbing).
22336185Sbostic  */
22436185Sbostic struct passwd *
22536185Sbostic sgetpwnam(name)
22636185Sbostic 	char *name;
22736185Sbostic {
22836185Sbostic 	static struct passwd save;
22936185Sbostic 	register struct passwd *p;
23036185Sbostic 	char *sgetsave();
23136185Sbostic 
23236185Sbostic 	if ((p = getpwnam(name)) == NULL)
23336185Sbostic 		return (p);
23436185Sbostic 	if (save.pw_name) {
23536185Sbostic 		free(save.pw_name);
23636185Sbostic 		free(save.pw_passwd);
23736185Sbostic 		free(save.pw_comment);
23836185Sbostic 		free(save.pw_gecos);
23936185Sbostic 		free(save.pw_dir);
24036185Sbostic 		free(save.pw_shell);
24136185Sbostic 	}
24236185Sbostic 	save = *p;
24336185Sbostic 	save.pw_name = sgetsave(p->pw_name);
24436185Sbostic 	save.pw_passwd = sgetsave(p->pw_passwd);
24536185Sbostic 	save.pw_comment = sgetsave(p->pw_comment);
24636185Sbostic 	save.pw_gecos = sgetsave(p->pw_gecos);
24736185Sbostic 	save.pw_dir = sgetsave(p->pw_dir);
24836185Sbostic 	save.pw_shell = sgetsave(p->pw_shell);
24936185Sbostic 	return (&save);
25036185Sbostic }
25136185Sbostic 
252*36304Skarels int login_attempts;		/* number of failed login attempts */
253*36304Skarels int askpasswd;			/* had user command, ask for passwd */
254*36304Skarels 
255*36304Skarels /*
256*36304Skarels  * USER command.
257*36304Skarels  * Sets global passwd pointer pw if named account exists
258*36304Skarels  * and is acceptable; sets askpasswd if a PASS command is
259*36304Skarels  * expected. If logged in previously, need to reset state.
260*36304Skarels  * If name is "ftp" or "anonymous" and ftp account exists,
261*36304Skarels  * set guest and pw, then just return.
262*36304Skarels  * If account doesn't exist, ask for passwd anyway.
263*36304Skarels  * Otherwise, check user requesting login privileges.
264*36304Skarels  * Disallow anyone who does not have a standard
265*36304Skarels  * shell returned by getusershell() (/etc/shells).
266*36304Skarels  * Disallow anyone mentioned in the file FTPUSERS
267*36304Skarels  * to allow people such as root and uucp to be avoided.
268*36304Skarels  */
269*36304Skarels user(name)
270*36304Skarels 	char *name;
271*36304Skarels {
272*36304Skarels 	register char *cp;
273*36304Skarels 	FILE *fd;
274*36304Skarels 	char *shell;
275*36304Skarels 	char line[BUFSIZ], *index(), *getusershell();
276*36304Skarels 
277*36304Skarels 	if (logged_in) {
278*36304Skarels 		if (guest) {
279*36304Skarels 			reply(530, "Can't change user from guest login.");
280*36304Skarels 			return;
281*36304Skarels 		}
282*36304Skarels 		end_login();
283*36304Skarels 	}
284*36304Skarels 
285*36304Skarels 	guest = 0;
286*36304Skarels 	if (strcmp(name, "ftp") == 0 || strcmp(name, "anonymous") == 0) {
287*36304Skarels 		if ((pw = sgetpwnam("ftp")) != NULL) {
288*36304Skarels 			guest = 1;
289*36304Skarels 			askpasswd = 1;
290*36304Skarels 			reply(331, "Guest login ok, send ident as password.");
291*36304Skarels 		} else
292*36304Skarels 			reply(530, "User %s unknown.", name);
293*36304Skarels 		return;
294*36304Skarels 	}
295*36304Skarels 	if (pw = sgetpwnam(name)) {
296*36304Skarels 		if ((shell = pw->pw_shell) == NULL || *shell == 0)
297*36304Skarels 			shell = "/bin/sh";
298*36304Skarels 		while ((cp = getusershell()) != NULL)
299*36304Skarels 			if (strcmp(cp, shell) == 0)
300*36304Skarels 				break;
301*36304Skarels 		endusershell();
302*36304Skarels 		if (cp == NULL) {
303*36304Skarels 			reply(530, "User %s access denied.", name);
304*36304Skarels 			pw = (struct passwd *) NULL;
305*36304Skarels 			return;
306*36304Skarels 		}
307*36304Skarels 		if ((fd = fopen(FTPUSERS, "r")) != NULL) {
308*36304Skarels 		    while (fgets(line, sizeof (line), fd) != NULL) {
309*36304Skarels 			if ((cp = index(line, '\n')) != NULL)
310*36304Skarels 				*cp = '\0';
311*36304Skarels 			if (strcmp(line, name) == 0) {
312*36304Skarels 				reply(530, "User %s access denied.", name);
313*36304Skarels 				pw = (struct passwd *) NULL;
314*36304Skarels 				return;
315*36304Skarels 			}
316*36304Skarels 		    }
317*36304Skarels 		}
318*36304Skarels 		(void) fclose(fd);
319*36304Skarels 	}
320*36304Skarels 	reply(331, "Password required for %s.", name);
321*36304Skarels 	askpasswd = 1;
322*36304Skarels 	/*
323*36304Skarels 	 * Delay before reading passwd after first failed
324*36304Skarels 	 * attempt to slow down passwd-guessing programs.
325*36304Skarels 	 */
326*36304Skarels 	if (login_attempts)
327*36304Skarels 		sleep((unsigned) login_attempts);
328*36304Skarels }
329*36304Skarels 
330*36304Skarels /*
331*36304Skarels  * Terminate login as previous user, if any, resetting state;
332*36304Skarels  * used when USER command is given or login fails.
333*36304Skarels  */
334*36304Skarels end_login()
335*36304Skarels {
336*36304Skarels 
337*36304Skarels 	(void) seteuid((uid_t)0);
338*36304Skarels 	if (logged_in)
339*36304Skarels 		logwtmp(ttyline, "", "");
340*36304Skarels 	pw = NULL;
341*36304Skarels 	logged_in = 0;
342*36304Skarels 	guest = 0;
343*36304Skarels }
344*36304Skarels 
34510275Ssam pass(passwd)
34610275Ssam 	char *passwd;
34710275Ssam {
348*36304Skarels 	char *xpasswd, *salt;
34910275Ssam 
350*36304Skarels 	if (logged_in || askpasswd == 0) {
35110275Ssam 		reply(503, "Login with USER first.");
35210275Ssam 		return;
35310275Ssam 	}
354*36304Skarels 	askpasswd = 0;
35510275Ssam 	if (!guest) {		/* "ftp" is only account allowed no password */
356*36304Skarels 		if (pw == NULL)
357*36304Skarels 			salt = "xx";
358*36304Skarels 		else
359*36304Skarels 			salt = pw->pw_passwd;
360*36304Skarels 		xpasswd = crypt(passwd, salt);
36116760Slepreau 		/* The strcmp does not catch null passwords! */
362*36304Skarels 		if (pw == NULL || *pw->pw_passwd == '\0' ||
363*36304Skarels 		    strcmp(xpasswd, pw->pw_passwd)) {
36410275Ssam 			reply(530, "Login incorrect.");
36510275Ssam 			pw = NULL;
366*36304Skarels 			if (login_attempts++ >= 5) {
367*36304Skarels 				syslog(LOG_ERR,
368*36304Skarels 				    "repeated login failures from %s",
369*36304Skarels 				    remotehost);
370*36304Skarels 				exit(0);
371*36304Skarels 			}
37210275Ssam 			return;
37310275Ssam 		}
37410275Ssam 	}
375*36304Skarels 	login_attempts = 0;		/* this time successful */
376*36304Skarels 	(void) setegid((gid_t)pw->pw_gid);
377*36304Skarels 	(void) initgroups(pw->pw_name, pw->pw_gid);
37810275Ssam 	if (chdir(pw->pw_dir)) {
37927106Smckusick 		reply(530, "User %s: can't change directory to %s.",
38010275Ssam 			pw->pw_name, pw->pw_dir);
38110303Ssam 		goto bad;
38210275Ssam 	}
38316033Sralph 
38436192Sbostic 	/* open wtmp before chroot */
38536192Sbostic 	(void)sprintf(ttyline, "ftp%d", getpid());
38636192Sbostic 	logwtmp(ttyline, pw->pw_name, remotehost);
38736192Sbostic 	logged_in = 1;
38836192Sbostic 
389*36304Skarels 	if (guest && chroot(pw->pw_dir) < 0) {
390*36304Skarels 		reply(550, "Can't set guest privileges.");
391*36304Skarels 		goto bad;
392*36304Skarels 	}
393*36304Skarels 	if (seteuid((uid_t)pw->pw_uid) < 0) {
394*36304Skarels 		reply(550, "Can't set uid.");
395*36304Skarels 		goto bad;
396*36304Skarels 	}
397*36304Skarels 	if (guest)
39836192Sbostic 		reply(230, "Guest login ok, access restrictions apply.");
399*36304Skarels 	else
40010275Ssam 		reply(230, "User %s logged in.", pw->pw_name);
40110303Ssam 	home = pw->pw_dir;		/* home dir for globbing */
40210303Ssam 	return;
40310303Ssam bad:
404*36304Skarels 	/* Forget all about it... */
405*36304Skarels 	end_login();
40610275Ssam }
40710275Ssam 
40810275Ssam retrieve(cmd, name)
40910275Ssam 	char *cmd, *name;
41010275Ssam {
41110275Ssam 	FILE *fin, *dout;
41210275Ssam 	struct stat st;
41326044Sminshall 	int (*closefunc)(), tmp;
41410275Ssam 
41510275Ssam 	if (cmd == 0) {
41610317Ssam #ifdef notdef
41710317Ssam 		/* no remote command execution -- it's a security hole */
41811653Ssam 		if (*name == '|')
419*36304Skarels 			fin = ftpd_popen(name + 1, "r"),
420*36304Skarels 			    closefunc = ftpd_pclose;
42110275Ssam 		else
42210317Ssam #endif
42310275Ssam 			fin = fopen(name, "r"), closefunc = fclose;
42410275Ssam 	} else {
42510275Ssam 		char line[BUFSIZ];
42610275Ssam 
42726493Sminshall 		(void) sprintf(line, cmd, name), name = line;
428*36304Skarels 		fin = ftpd_popen(line, "r"), closefunc = ftpd_pclose;
42910275Ssam 	}
43010275Ssam 	if (fin == NULL) {
43113152Ssam 		if (errno != 0)
432*36304Skarels 			perror_reply(550, name);
43310275Ssam 		return;
43410275Ssam 	}
43510275Ssam 	st.st_size = 0;
43610275Ssam 	if (cmd == 0 &&
43710275Ssam 	    (stat(name, &st) < 0 || (st.st_mode&S_IFMT) != S_IFREG)) {
43810275Ssam 		reply(550, "%s: not a plain file.", name);
43910275Ssam 		goto done;
44010275Ssam 	}
44110275Ssam 	dout = dataconn(name, st.st_size, "w");
44210275Ssam 	if (dout == NULL)
44310275Ssam 		goto done;
44426044Sminshall 	if ((tmp = send_data(fin, dout)) > 0 || ferror(dout) > 0) {
445*36304Skarels 		perror_reply(550, name);
44626044Sminshall 	}
44726044Sminshall 	else if (tmp == 0) {
44810275Ssam 		reply(226, "Transfer complete.");
44926044Sminshall 	}
45026493Sminshall 	(void) fclose(dout);
45126044Sminshall 	data = -1;
45226044Sminshall 	pdata = -1;
45310275Ssam done:
45410275Ssam 	(*closefunc)(fin);
45510275Ssam }
45610275Ssam 
457*36304Skarels store(name, mode, unique)
45810275Ssam 	char *name, *mode;
459*36304Skarels 	int unique;
46010275Ssam {
46110275Ssam 	FILE *fout, *din;
462*36304Skarels 	int (*closefunc)(), tmp;
463*36304Skarels 	char *gunique();
46410275Ssam 
46510317Ssam #ifdef notdef
46610317Ssam 	/* no remote command execution -- it's a security hole */
46711653Ssam 	if (name[0] == '|')
468*36304Skarels 		fout = ftpd_popen(&name[1], "w"), closefunc = ftpd_pclose;
46910317Ssam 	else
47010317Ssam #endif
47110317Ssam 	{
47210303Ssam 		struct stat st;
47310303Ssam 
474*36304Skarels 		if (unique && stat(name, &st) == 0 &&
475*36304Skarels 		    (name = gunique(name)) == NULL)
476*36304Skarels 			return;
477*36304Skarels 		fout = fopen(name, mode), closefunc = fclose;
47810303Ssam 	}
47910275Ssam 	if (fout == NULL) {
480*36304Skarels 		perror_reply(553, name);
48110275Ssam 		return;
48210275Ssam 	}
483*36304Skarels 	din = dataconn(name, (off_t)-1, "r");
48410275Ssam 	if (din == NULL)
48510275Ssam 		goto done;
486*36304Skarels 	if ((tmp = receive_data(din, fout)) > 0)
487*36304Skarels 		perror_reply(552, name);
488*36304Skarels 	else if (tmp == 0) {
489*36304Skarels 		if (ferror(fout) > 0)
490*36304Skarels 			perror_reply(552, name);
491*36304Skarels 		else if (unique)
492*36304Skarels 			reply(226, "Transfer complete (unique file name:%s).",
493*36304Skarels 			    name);
494*36304Skarels 		else
495*36304Skarels 			reply(226, "Transfer complete.");
49626044Sminshall 	}
49726493Sminshall 	(void) fclose(din);
49826044Sminshall 	data = -1;
49926044Sminshall 	pdata = -1;
50010275Ssam done:
50110275Ssam 	(*closefunc)(fout);
50210275Ssam }
50310275Ssam 
50410275Ssam FILE *
50510275Ssam getdatasock(mode)
50610275Ssam 	char *mode;
50710275Ssam {
50817157Ssam 	int s, on = 1;
50910275Ssam 
51010275Ssam 	if (data >= 0)
51110275Ssam 		return (fdopen(data, mode));
51213247Ssam 	s = socket(AF_INET, SOCK_STREAM, 0);
51310602Ssam 	if (s < 0)
51410275Ssam 		return (NULL);
515*36304Skarels 	(void) seteuid((uid_t)0);
51626493Sminshall 	if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof (on)) < 0)
51710602Ssam 		goto bad;
51813152Ssam 	/* anchor socket to avoid multi-homing problems */
51913152Ssam 	data_source.sin_family = AF_INET;
52013152Ssam 	data_source.sin_addr = ctrl_addr.sin_addr;
521*36304Skarels 	if (bind(s, (struct sockaddr *)&data_source, sizeof (data_source)) < 0)
52210602Ssam 		goto bad;
523*36304Skarels 	(void) seteuid((uid_t)pw->pw_uid);
52410275Ssam 	return (fdopen(s, mode));
52510602Ssam bad:
526*36304Skarels 	(void) seteuid((uid_t)pw->pw_uid);
52726493Sminshall 	(void) close(s);
52810602Ssam 	return (NULL);
52910275Ssam }
53010275Ssam 
53110275Ssam FILE *
53210275Ssam dataconn(name, size, mode)
53310275Ssam 	char *name;
53411653Ssam 	off_t size;
53510275Ssam 	char *mode;
53610275Ssam {
53710275Ssam 	char sizebuf[32];
53810275Ssam 	FILE *file;
53911653Ssam 	int retry = 0;
54010275Ssam 
541*36304Skarels 	if (size != (off_t) -1)
54226493Sminshall 		(void) sprintf (sizebuf, " (%ld bytes)", size);
54310275Ssam 	else
54410275Ssam 		(void) strcpy(sizebuf, "");
545*36304Skarels 	if (pdata >= 0) {
54626044Sminshall 		struct sockaddr_in from;
54726044Sminshall 		int s, fromlen = sizeof(from);
54826044Sminshall 
549*36304Skarels 		s = accept(pdata, (struct sockaddr *)&from, &fromlen);
55026044Sminshall 		if (s < 0) {
55126044Sminshall 			reply(425, "Can't open data connection.");
55226044Sminshall 			(void) close(pdata);
55326044Sminshall 			pdata = -1;
55426044Sminshall 			return(NULL);
55526044Sminshall 		}
55626044Sminshall 		(void) close(pdata);
55726044Sminshall 		pdata = s;
55836235Skarels 		reply(150, "Opening %s mode data connection for %s%s.",
55936235Skarels 		     type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf);
56026044Sminshall 		return(fdopen(pdata, mode));
56126044Sminshall 	}
56210275Ssam 	if (data >= 0) {
56310275Ssam 		reply(125, "Using existing data connection for %s%s.",
56410275Ssam 		    name, sizebuf);
56510321Ssam 		usedefault = 1;
56610275Ssam 		return (fdopen(data, mode));
56710275Ssam 	}
56810566Ssam 	if (usedefault)
56910422Ssam 		data_dest = his_addr;
57010422Ssam 	usedefault = 1;
57110275Ssam 	file = getdatasock(mode);
57210275Ssam 	if (file == NULL) {
57310275Ssam 		reply(425, "Can't create data socket (%s,%d): %s.",
57413247Ssam 		    inet_ntoa(data_source.sin_addr),
57510275Ssam 		    ntohs(data_source.sin_port),
576*36304Skarels 		    errno < sys_nerr ? sys_errlist[errno] : "unknown error");
57710275Ssam 		return (NULL);
57810275Ssam 	}
57910275Ssam 	data = fileno(file);
580*36304Skarels 	while (connect(data, (struct sockaddr *)&data_dest,
581*36304Skarels 	    sizeof (data_dest)) < 0) {
58211653Ssam 		if (errno == EADDRINUSE && retry < swaitmax) {
58326493Sminshall 			sleep((unsigned) swaitint);
58411653Ssam 			retry += swaitint;
58511653Ssam 			continue;
58611653Ssam 		}
587*36304Skarels 		perror_reply(425, "Can't build data connection");
58810275Ssam 		(void) fclose(file);
58910275Ssam 		data = -1;
59010275Ssam 		return (NULL);
59110275Ssam 	}
59236235Skarels 	reply(150, "Opening %s mode data connection for %s%s.",
59336235Skarels 	     type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf);
59410275Ssam 	return (file);
59510275Ssam }
59610275Ssam 
59710275Ssam /*
59810275Ssam  * Tranfer the contents of "instr" to
59910275Ssam  * "outstr" peer using the appropriate
60010275Ssam  * encapulation of the date subject
60110275Ssam  * to Mode, Structure, and Type.
60210275Ssam  *
60310275Ssam  * NB: Form isn't handled.
60410275Ssam  */
60510275Ssam send_data(instr, outstr)
60610275Ssam 	FILE *instr, *outstr;
60710275Ssam {
60810275Ssam 	register int c;
60910275Ssam 	int netfd, filefd, cnt;
61010275Ssam 	char buf[BUFSIZ];
61110275Ssam 
61226044Sminshall 	transflag++;
61326044Sminshall 	if (setjmp(urgcatch)) {
61426044Sminshall 		transflag = 0;
61526044Sminshall 		return(-1);
61626044Sminshall 	}
61710275Ssam 	switch (type) {
61810275Ssam 
61910275Ssam 	case TYPE_A:
62010275Ssam 		while ((c = getc(instr)) != EOF) {
62111220Ssam 			if (c == '\n') {
62226044Sminshall 				if (ferror (outstr)) {
62326044Sminshall 					transflag = 0;
62411220Ssam 					return (1);
62526044Sminshall 				}
62627750Sminshall 				(void) putc('\r', outstr);
62711220Ssam 			}
62827750Sminshall 			(void) putc(c, outstr);
62926044Sminshall 		/*	if (c == '\r')			*/
63026044Sminshall 		/*		putc ('\0', outstr);	*/
63110275Ssam 		}
63226044Sminshall 		transflag = 0;
63326044Sminshall 		if (ferror (instr) || ferror (outstr)) {
63411220Ssam 			return (1);
63526044Sminshall 		}
63610275Ssam 		return (0);
63710275Ssam 
63810275Ssam 	case TYPE_I:
63910275Ssam 	case TYPE_L:
64010275Ssam 		netfd = fileno(outstr);
64110275Ssam 		filefd = fileno(instr);
64210275Ssam 
64326044Sminshall 		while ((cnt = read(filefd, buf, sizeof (buf))) > 0) {
64426044Sminshall 			if (write(netfd, buf, cnt) < 0) {
64526044Sminshall 				transflag = 0;
64610275Ssam 				return (1);
64726044Sminshall 			}
64826044Sminshall 		}
64926044Sminshall 		transflag = 0;
65010275Ssam 		return (cnt < 0);
65110275Ssam 	}
65227106Smckusick 	reply(550, "Unimplemented TYPE %d in send_data", type);
65326044Sminshall 	transflag = 0;
65427106Smckusick 	return (-1);
65510275Ssam }
65610275Ssam 
65710275Ssam /*
65810275Ssam  * Transfer data from peer to
65910275Ssam  * "outstr" using the appropriate
66010275Ssam  * encapulation of the data subject
66110275Ssam  * to Mode, Structure, and Type.
66210275Ssam  *
66310275Ssam  * N.B.: Form isn't handled.
66410275Ssam  */
66510275Ssam receive_data(instr, outstr)
66610275Ssam 	FILE *instr, *outstr;
66710275Ssam {
66810275Ssam 	register int c;
66911220Ssam 	int cnt;
67010275Ssam 	char buf[BUFSIZ];
67110275Ssam 
67210275Ssam 
67326044Sminshall 	transflag++;
67426044Sminshall 	if (setjmp(urgcatch)) {
67526044Sminshall 		transflag = 0;
67626044Sminshall 		return(-1);
67726044Sminshall 	}
67810275Ssam 	switch (type) {
67910275Ssam 
68010275Ssam 	case TYPE_I:
68110275Ssam 	case TYPE_L:
68226044Sminshall 		while ((cnt = read(fileno(instr), buf, sizeof buf)) > 0) {
68326044Sminshall 			if (write(fileno(outstr), buf, cnt) < 0) {
68426044Sminshall 				transflag = 0;
68510275Ssam 				return (1);
68626044Sminshall 			}
68726044Sminshall 		}
68826044Sminshall 		transflag = 0;
68910275Ssam 		return (cnt < 0);
69010275Ssam 
69110275Ssam 	case TYPE_E:
69227106Smckusick 		reply(553, "TYPE E not implemented.");
69326044Sminshall 		transflag = 0;
69427106Smckusick 		return (-1);
69510275Ssam 
69610275Ssam 	case TYPE_A:
69710275Ssam 		while ((c = getc(instr)) != EOF) {
69827750Sminshall 			while (c == '\r') {
69926044Sminshall 				if (ferror (outstr)) {
70026044Sminshall 					transflag = 0;
70111220Ssam 					return (1);
70226044Sminshall 				}
70311220Ssam 				if ((c = getc(instr)) != '\n')
70427750Sminshall 					(void) putc ('\r', outstr);
70526044Sminshall 			/*	if (c == '\0')			*/
70626044Sminshall 			/*		continue;		*/
70710275Ssam 			}
70827750Sminshall 			(void) putc (c, outstr);
70910275Ssam 		}
71026044Sminshall 		transflag = 0;
71111220Ssam 		if (ferror (instr) || ferror (outstr))
71211220Ssam 			return (1);
71310275Ssam 		return (0);
71410275Ssam 	}
71526044Sminshall 	transflag = 0;
71610275Ssam 	fatal("Unknown type in receive_data.");
71710275Ssam 	/*NOTREACHED*/
71810275Ssam }
71910275Ssam 
72010275Ssam fatal(s)
72110275Ssam 	char *s;
72210275Ssam {
72310275Ssam 	reply(451, "Error in server: %s\n", s);
72410275Ssam 	reply(221, "Closing connection due to server error.");
72513247Ssam 	dologout(0);
72610275Ssam }
72710275Ssam 
728*36304Skarels /* VARARGS2 */
72932110Smckusick reply(n, s, p0, p1, p2, p3, p4)
73010275Ssam 	int n;
73110275Ssam 	char *s;
73210275Ssam {
73310275Ssam 
73410275Ssam 	printf("%d ", n);
73532110Smckusick 	printf(s, p0, p1, p2, p3, p4);
73610275Ssam 	printf("\r\n");
73726493Sminshall 	(void) fflush(stdout);
73810275Ssam 	if (debug) {
73926493Sminshall 		syslog(LOG_DEBUG, "<--- %d ", n);
74032110Smckusick 		syslog(LOG_DEBUG, s, p0, p1, p2, p3, p4);
74110275Ssam 	}
74210275Ssam }
74310275Ssam 
744*36304Skarels /* VARARGS2 */
74532110Smckusick lreply(n, s, p0, p1, p2, p3, p4)
74610275Ssam 	int n;
74710275Ssam 	char *s;
74810275Ssam {
74910275Ssam 	printf("%d-", n);
75032110Smckusick 	printf(s, p0, p1, p2, p3, p4);
75110275Ssam 	printf("\r\n");
75226493Sminshall 	(void) fflush(stdout);
75310275Ssam 	if (debug) {
75426493Sminshall 		syslog(LOG_DEBUG, "<--- %d- ", n);
75532110Smckusick 		syslog(LOG_DEBUG, s, p0, p1, p2, p3, p4);
75610275Ssam 	}
75710275Ssam }
75810275Ssam 
75910275Ssam ack(s)
76010275Ssam 	char *s;
76110275Ssam {
76227106Smckusick 	reply(250, "%s command successful.", s);
76310275Ssam }
76410275Ssam 
76510275Ssam nack(s)
76610275Ssam 	char *s;
76710275Ssam {
76810275Ssam 	reply(502, "%s command not implemented.", s);
76910275Ssam }
77010275Ssam 
771*36304Skarels /* ARGSUSED */
77226493Sminshall yyerror(s)
77326493Sminshall 	char *s;
77410275Ssam {
77526044Sminshall 	char *cp;
77626044Sminshall 
77726044Sminshall 	cp = index(cbuf,'\n');
77826044Sminshall 	*cp = '\0';
77926044Sminshall 	reply(500, "'%s': command not understood.",cbuf);
78010275Ssam }
78110275Ssam 
78210275Ssam delete(name)
78310275Ssam 	char *name;
78410275Ssam {
78510275Ssam 	struct stat st;
78610275Ssam 
78710275Ssam 	if (stat(name, &st) < 0) {
788*36304Skarels 		perror_reply(550, name);
78910275Ssam 		return;
79010275Ssam 	}
79110275Ssam 	if ((st.st_mode&S_IFMT) == S_IFDIR) {
79210275Ssam 		if (rmdir(name) < 0) {
793*36304Skarels 			perror_reply(550, name);
79410275Ssam 			return;
79510275Ssam 		}
79610275Ssam 		goto done;
79710275Ssam 	}
79810275Ssam 	if (unlink(name) < 0) {
799*36304Skarels 		perror_reply(550, name);
80010275Ssam 		return;
80110275Ssam 	}
80210275Ssam done:
80310275Ssam 	ack("DELE");
80410275Ssam }
80510275Ssam 
80610275Ssam cwd(path)
80710275Ssam 	char *path;
80810275Ssam {
80910275Ssam 
81010275Ssam 	if (chdir(path) < 0) {
811*36304Skarels 		perror_reply(550, path);
81210275Ssam 		return;
81310275Ssam 	}
81410275Ssam 	ack("CWD");
81510275Ssam }
81610275Ssam 
81710303Ssam makedir(name)
81810275Ssam 	char *name;
81910275Ssam {
82036276Sbostic 	if (mkdir(name, 0777) < 0)
821*36304Skarels 		perror_reply(550, name);
82236276Sbostic 	else
82336276Sbostic 		reply(257, "MKD command successful.");
82410275Ssam }
82510275Ssam 
82610303Ssam removedir(name)
82710275Ssam 	char *name;
82810275Ssam {
82910275Ssam 
83010275Ssam 	if (rmdir(name) < 0) {
831*36304Skarels 		perror_reply(550, name);
83210275Ssam 		return;
83310275Ssam 	}
83427106Smckusick 	ack("RMD");
83510275Ssam }
83610275Ssam 
83710303Ssam pwd()
83810275Ssam {
83910303Ssam 	char path[MAXPATHLEN + 1];
840*36304Skarels 	extern char *getwd();
84110275Ssam 
842*36304Skarels 	if (getwd(path) == (char *)NULL) {
84327106Smckusick 		reply(550, "%s.", path);
84410275Ssam 		return;
84510275Ssam 	}
84627106Smckusick 	reply(257, "\"%s\" is current directory.", path);
84710275Ssam }
84810275Ssam 
84910275Ssam char *
85010275Ssam renamefrom(name)
85110275Ssam 	char *name;
85210275Ssam {
85310275Ssam 	struct stat st;
85410275Ssam 
85510275Ssam 	if (stat(name, &st) < 0) {
856*36304Skarels 		perror_reply(550, name);
85710275Ssam 		return ((char *)0);
85810275Ssam 	}
85910303Ssam 	reply(350, "File exists, ready for destination name");
86010275Ssam 	return (name);
86110275Ssam }
86210275Ssam 
86310275Ssam renamecmd(from, to)
86410275Ssam 	char *from, *to;
86510275Ssam {
86610275Ssam 
86710275Ssam 	if (rename(from, to) < 0) {
868*36304Skarels 		perror_reply(550, "rename");
86910275Ssam 		return;
87010275Ssam 	}
87110275Ssam 	ack("RNTO");
87210275Ssam }
87310275Ssam 
87410275Ssam dolog(sin)
87510275Ssam 	struct sockaddr_in *sin;
87610275Ssam {
877*36304Skarels 	struct hostent *hp = gethostbyaddr((char *)&sin->sin_addr,
87810275Ssam 		sizeof (struct in_addr), AF_INET);
879*36304Skarels 	time_t t, time();
88026493Sminshall 	extern char *ctime();
88110275Ssam 
882*36304Skarels 	if (hp)
88326493Sminshall 		(void) strncpy(remotehost, hp->h_name, sizeof (remotehost));
884*36304Skarels 	else
88526493Sminshall 		(void) strncpy(remotehost, inet_ntoa(sin->sin_addr),
88613247Ssam 		    sizeof (remotehost));
88713247Ssam 	if (!logging)
88813247Ssam 		return;
88926493Sminshall 	t = time((time_t *) 0);
890*36304Skarels 	syslog(LOG_INFO, "connection from %s at %s",
891*36304Skarels 	    remotehost, ctime(&t));
89210275Ssam }
89310695Ssam 
89410695Ssam /*
89513247Ssam  * Record logout in wtmp file
89613247Ssam  * and exit with supplied status.
89713247Ssam  */
89813247Ssam dologout(status)
89913247Ssam 	int status;
90013247Ssam {
90117580Ssam 	if (logged_in) {
902*36304Skarels 		(void) seteuid((uid_t)0);
90335672Sbostic 		logwtmp(ttyline, "", "");
90413247Ssam 	}
90514436Ssam 	/* beware of flushing buffers after a SIGPIPE */
90614436Ssam 	_exit(status);
90713247Ssam }
90813247Ssam 
90926044Sminshall myoob()
91026044Sminshall {
91127750Sminshall 	char *cp;
91226044Sminshall 
91327750Sminshall 	/* only process if transfer occurring */
914*36304Skarels 	if (!transflag)
91526044Sminshall 		return;
91627750Sminshall 	cp = tmpline;
91727750Sminshall 	if (getline(cp, 7, stdin) == NULL) {
918*36304Skarels 		reply(221, "You could at least say goodbye.");
91927750Sminshall 		dologout(0);
92026044Sminshall 	}
92126044Sminshall 	upper(cp);
92226227Ssam 	if (strcmp(cp, "ABOR\r\n"))
92326044Sminshall 		return;
92426044Sminshall 	tmpline[0] = '\0';
92526044Sminshall 	reply(426,"Transfer aborted. Data connection closed.");
92626044Sminshall 	reply(226,"Abort successful");
92726044Sminshall 	longjmp(urgcatch, 1);
92826044Sminshall }
92926044Sminshall 
93027106Smckusick /*
93127106Smckusick  * Note: The 530 reply codes could be 4xx codes, except nothing is
93227106Smckusick  * given in the state tables except 421 which implies an exit.  (RFC959)
93327106Smckusick  */
93426044Sminshall passive()
93526044Sminshall {
93626044Sminshall 	int len;
93726044Sminshall 	struct sockaddr_in tmp;
93826044Sminshall 	register char *p, *a;
93926044Sminshall 
94026044Sminshall 	pdata = socket(AF_INET, SOCK_STREAM, 0);
94126044Sminshall 	if (pdata < 0) {
94227106Smckusick 		reply(530, "Can't open passive connection");
94326044Sminshall 		return;
94426044Sminshall 	}
94526044Sminshall 	tmp = ctrl_addr;
94626044Sminshall 	tmp.sin_port = 0;
947*36304Skarels 	(void) seteuid((uid_t)0);
94826493Sminshall 	if (bind(pdata, (struct sockaddr *) &tmp, sizeof(tmp)) < 0) {
949*36304Skarels 		(void) seteuid((uid_t)pw->pw_uid);
95026044Sminshall 		(void) close(pdata);
95126044Sminshall 		pdata = -1;
95227106Smckusick 		reply(530, "Can't open passive connection");
95326044Sminshall 		return;
95426044Sminshall 	}
955*36304Skarels 	(void) seteuid((uid_t)pw->pw_uid);
95626044Sminshall 	len = sizeof(tmp);
957*36304Skarels 	if (getsockname(pdata, (struct sockaddr *) &tmp, &len) < 0) {
95826044Sminshall 		(void) close(pdata);
95926044Sminshall 		pdata = -1;
96027106Smckusick 		reply(530, "Can't open passive connection");
96126044Sminshall 		return;
96226044Sminshall 	}
96326044Sminshall 	if (listen(pdata, 1) < 0) {
96426044Sminshall 		(void) close(pdata);
96526044Sminshall 		pdata = -1;
96627106Smckusick 		reply(530, "Can't open passive connection");
96726044Sminshall 		return;
96826044Sminshall 	}
96926044Sminshall 	a = (char *) &tmp.sin_addr;
97026044Sminshall 	p = (char *) &tmp.sin_port;
97126044Sminshall 
97226044Sminshall #define UC(b) (((int) b) & 0xff)
97326044Sminshall 
97426044Sminshall 	reply(227, "Entering Passive Mode (%d,%d,%d,%d,%d,%d)", UC(a[0]),
97526044Sminshall 		UC(a[1]), UC(a[2]), UC(a[3]), UC(p[0]), UC(p[1]));
97626044Sminshall }
97726044Sminshall 
978*36304Skarels /*
979*36304Skarels  * Generate unique name for file with basename "local".
980*36304Skarels  * The file named "local" is already known to exist.
981*36304Skarels  * Generates failure reply on error.
982*36304Skarels  */
98326044Sminshall char *
98426044Sminshall gunique(local)
98526044Sminshall 	char *local;
98626044Sminshall {
98726044Sminshall 	static char new[MAXPATHLEN];
988*36304Skarels 	struct stat st;
98926044Sminshall 	char *cp = rindex(local, '/');
99026044Sminshall 	int d, count=0;
99126044Sminshall 
992*36304Skarels 	if (cp)
99326044Sminshall 		*cp = '\0';
994*36304Skarels 	d = stat(cp ? local : ".", &st);
995*36304Skarels 	if (cp)
99626044Sminshall 		*cp = '/';
99726044Sminshall 	if (d < 0) {
998*36304Skarels 		perror_reply(553, local);
99926044Sminshall 		return((char *) 0);
100026044Sminshall 	}
100126044Sminshall 	(void) strcpy(new, local);
100226044Sminshall 	cp = new + strlen(new);
100326044Sminshall 	*cp++ = '.';
1004*36304Skarels 	for (count = 1; count < 100; count++) {
1005*36304Skarels 		(void) sprintf(cp, "%d", count);
1006*36304Skarels 		if (stat(new, &st) < 0)
1007*36304Skarels 			return(new);
100826044Sminshall 	}
1009*36304Skarels 	reply(452, "Unique file name cannot be created.");
1010*36304Skarels 	return((char *) 0);
101126044Sminshall }
1012*36304Skarels 
1013*36304Skarels /*
1014*36304Skarels  * Format and send reply containing system error number.
1015*36304Skarels  */
1016*36304Skarels perror_reply(code, string)
1017*36304Skarels 	int code;
1018*36304Skarels 	char *string;
1019*36304Skarels {
1020*36304Skarels 
1021*36304Skarels 	if (errno < sys_nerr)
1022*36304Skarels 		reply(code, "%s: %s.", string, sys_errlist[errno]);
1023*36304Skarels 	else
1024*36304Skarels 		reply(code, "%s: unknown error %d.", string, errno);
1025*36304Skarels }
1026