xref: /csrg-svn/libexec/ftpd/ftpd.c (revision 36276)
122499Sdist /*
226044Sminshall  * Copyright (c) 1985 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[] =
2026044Sminshall "@(#) Copyright (c) 1985 Regents of the University of California.\n\
2122499Sdist  All rights reserved.\n";
2233738Sbostic #endif /* not lint */
2310275Ssam 
2422499Sdist #ifndef lint
25*36276Sbostic static char sccsid[] = "@(#)ftpd.c	5.19 (Berkeley) 11/30/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[];
6210275Ssam extern	char *crypt();
6310275Ssam extern	char version[];
6410275Ssam extern	char *home;		/* pointer to home directory for glob */
65*36276Sbostic extern	FILE *ftpd_popen(), *fopen(), *freopen();
6626493Sminshall extern	int  pclose(), fclose();
6726044Sminshall extern	char *getline();
6826044Sminshall extern	char cbuf[];
6910275Ssam 
7010275Ssam struct	sockaddr_in ctrl_addr;
7110275Ssam struct	sockaddr_in data_source;
7210275Ssam struct	sockaddr_in data_dest;
7310275Ssam struct	sockaddr_in his_addr;
7410275Ssam 
7510275Ssam int	data;
7626044Sminshall jmp_buf	errcatch, urgcatch;
7710275Ssam int	logged_in;
7810275Ssam struct	passwd *pw;
7910275Ssam int	debug;
8026493Sminshall int	timeout = 900;    /* timeout after 15 minutes of inactivity */
8111757Ssam int	logging;
8210275Ssam int	guest;
8310275Ssam int	type;
8410275Ssam int	form;
8510275Ssam int	stru;			/* avoid C keyword */
8610275Ssam int	mode;
8710321Ssam int	usedefault = 1;		/* for data transfers */
8826044Sminshall int	pdata;			/* for passive mode */
8926044Sminshall int	unique;
9026044Sminshall int	transflag;
9126044Sminshall char	tmpline[7];
92*36276Sbostic char	hostname[MAXHOSTNAMELEN];
93*36276Sbostic 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;
11526044Sminshall 	long pgid;
11610275Ssam 	char *cp;
11710275Ssam 
11816339Skarels 	addrlen = sizeof (his_addr);
11916339Skarels 	if (getpeername(0, &his_addr, &addrlen) < 0) {
12026493Sminshall 		syslog(LOG_ERR, "getpeername (%s): %m",argv[0]);
12110275Ssam 		exit(1);
12210275Ssam 	}
12316339Skarels 	addrlen = sizeof (ctrl_addr);
12426493Sminshall 	if (getsockname(0, (char *) &ctrl_addr, &addrlen) < 0) {
12526493Sminshall 		syslog(LOG_ERR, "getsockname (%s): %m",argv[0]);
12616339Skarels 		exit(1);
12716339Skarels 	}
12816339Skarels 	data_source.sin_port = htons(ntohs(ctrl_addr.sin_port) - 1);
12910275Ssam 	debug = 0;
13026493Sminshall 	openlog("ftpd", LOG_PID, LOG_DAEMON);
13110275Ssam 	argc--, argv++;
13210275Ssam 	while (argc > 0 && *argv[0] == '-') {
13310275Ssam 		for (cp = &argv[0][1]; *cp; cp++) switch (*cp) {
13410275Ssam 
13511653Ssam 		case 'v':
13611653Ssam 			debug = 1;
13711653Ssam 			break;
13811653Ssam 
13910275Ssam 		case 'd':
14010275Ssam 			debug = 1;
14110275Ssam 			break;
14210275Ssam 
14311757Ssam 		case 'l':
14411757Ssam 			logging = 1;
14511757Ssam 			break;
14611757Ssam 
14711653Ssam 		case 't':
14811653Ssam 			timeout = atoi(++cp);
14911653Ssam 			goto nextopt;
15011653Ssam 			break;
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 */
167*36276Sbostic 	/* Sequent defines this, but it doesn't work */
16827750Sminshall #ifdef SO_OOBINLINE
169*36276Sbostic 	if (setsockopt(0, SOL_SOCKET, SO_OOBINLINE, (char *)&on, sizeof(on)) < 0)
17027750Sminshall 		syslog(LOG_ERR, "setsockopt: %m");
171*36276Sbostic #endif
17226044Sminshall 	pgid = getpid();
17327750Sminshall 	if (ioctl(fileno(stdin), SIOCSPGRP, (char *) &pgid) < 0) {
17426493Sminshall 		syslog(LOG_ERR, "ioctl: %m");
17526044Sminshall 	}
17616760Slepreau 	dolog(&his_addr);
17716339Skarels 	/* do telnet option negotiation here */
17816339Skarels 	/*
17916339Skarels 	 * Set up default state
18016339Skarels 	 */
18116339Skarels 	data = -1;
18216339Skarels 	type = TYPE_A;
18316339Skarels 	form = FORM_N;
18416339Skarels 	stru = STRU_F;
18516339Skarels 	mode = MODE_S;
18626044Sminshall 	tmpline[0] = '\0';
18726493Sminshall 	(void) gethostname(hostname, sizeof (hostname));
188*36276Sbostic 	reply(220, "%s FTP server (%s) ready.", hostname, version);
18910275Ssam 	for (;;) {
19026493Sminshall 		(void) setjmp(errcatch);
19126493Sminshall 		(void) yyparse();
19210275Ssam 	}
19310275Ssam }
19410419Ssam 
19510275Ssam lostconn()
19610275Ssam {
19710275Ssam 
19814089Ssam 	if (debug)
19926493Sminshall 		syslog(LOG_DEBUG, "lost connection");
20014089Ssam 	dologout(-1);
20110275Ssam }
20210275Ssam 
20335672Sbostic static char ttyline[20];
20435672Sbostic 
20536185Sbostic /*
20636185Sbostic  * Helper function for sgetpwnam().
20736185Sbostic  */
20836185Sbostic char *
20936185Sbostic sgetsave(s)
21036185Sbostic 	char *s;
21136185Sbostic {
21236185Sbostic #ifdef notdef
21336185Sbostic 	char *new = strdup(s);
21436185Sbostic #else
21536185Sbostic 	char *malloc();
21636185Sbostic 	char *new = malloc((unsigned) strlen(s) + 1);
21736185Sbostic #endif
21836185Sbostic 
21936185Sbostic 	if (new == NULL) {
220*36276Sbostic 		reply(553, "Local resource failure: malloc");
22136185Sbostic 		dologout(1);
22236185Sbostic 	}
22336185Sbostic #ifndef notdef
22436185Sbostic 	(void) strcpy(new, s);
22536185Sbostic #endif
22636185Sbostic 	return (new);
22736185Sbostic }
22836185Sbostic 
22936185Sbostic /*
23036185Sbostic  * Save the result of a getpwnam.  Used for USER command, since
23136185Sbostic  * the data returned must not be clobbered by any other command
23236185Sbostic  * (e.g., globbing).
23336185Sbostic  */
23436185Sbostic struct passwd *
23536185Sbostic sgetpwnam(name)
23636185Sbostic 	char *name;
23736185Sbostic {
23836185Sbostic 	static struct passwd save;
23936185Sbostic 	register struct passwd *p;
24036185Sbostic 	char *sgetsave();
24136185Sbostic 
24236185Sbostic 	if ((p = getpwnam(name)) == NULL)
24336185Sbostic 		return (p);
24436185Sbostic 	if (save.pw_name) {
24536185Sbostic 		free(save.pw_name);
24636185Sbostic 		free(save.pw_passwd);
24736185Sbostic 		free(save.pw_comment);
24836185Sbostic 		free(save.pw_gecos);
24936185Sbostic 		free(save.pw_dir);
25036185Sbostic 		free(save.pw_shell);
25136185Sbostic 	}
25236185Sbostic 	save = *p;
25336185Sbostic 	save.pw_name = sgetsave(p->pw_name);
25436185Sbostic 	save.pw_passwd = sgetsave(p->pw_passwd);
25536185Sbostic 	save.pw_comment = sgetsave(p->pw_comment);
25636185Sbostic 	save.pw_gecos = sgetsave(p->pw_gecos);
25736185Sbostic 	save.pw_dir = sgetsave(p->pw_dir);
25836185Sbostic 	save.pw_shell = sgetsave(p->pw_shell);
25936185Sbostic 	return (&save);
26036185Sbostic }
26136185Sbostic 
26210275Ssam pass(passwd)
26310275Ssam 	char *passwd;
26410275Ssam {
26536185Sbostic 	char *xpasswd;
26610275Ssam 
26710275Ssam 	if (logged_in || pw == NULL) {
26810275Ssam 		reply(503, "Login with USER first.");
26910275Ssam 		return;
27010275Ssam 	}
27110275Ssam 	if (!guest) {		/* "ftp" is only account allowed no password */
27210275Ssam 		xpasswd = crypt(passwd, pw->pw_passwd);
27316760Slepreau 		/* The strcmp does not catch null passwords! */
27416760Slepreau 		if (*pw->pw_passwd == '\0' || strcmp(xpasswd, pw->pw_passwd)) {
27510275Ssam 			reply(530, "Login incorrect.");
27610275Ssam 			pw = NULL;
27710275Ssam 			return;
27810275Ssam 		}
27910275Ssam 	}
28010303Ssam 	setegid(pw->pw_gid);
28110275Ssam 	initgroups(pw->pw_name, pw->pw_gid);
28210275Ssam 	if (chdir(pw->pw_dir)) {
28327106Smckusick 		reply(530, "User %s: can't change directory to %s.",
28410275Ssam 			pw->pw_name, pw->pw_dir);
28510303Ssam 		goto bad;
28610275Ssam 	}
28716033Sralph 
28836192Sbostic 	/* open wtmp before chroot */
28936192Sbostic 	(void)sprintf(ttyline, "ftp%d", getpid());
29036192Sbostic 	logwtmp(ttyline, pw->pw_name, remotehost);
29136192Sbostic 	logged_in = 1;
29236192Sbostic 
29336192Sbostic 	if (guest) {
29436192Sbostic 		if (chroot(pw->pw_dir) < 0) {
29536192Sbostic 			reply(550, "Can't set guest privileges.");
29636192Sbostic 			goto bad;
29716760Slepreau 		}
29836192Sbostic 		reply(230, "Guest login ok, access restrictions apply.");
29936192Sbostic 	} else
30010275Ssam 		reply(230, "User %s logged in.", pw->pw_name);
30110303Ssam 	seteuid(pw->pw_uid);
30210303Ssam 	home = pw->pw_dir;		/* home dir for globbing */
30310303Ssam 	return;
30410303Ssam bad:
30510303Ssam 	seteuid(0);
30610303Ssam 	pw = NULL;
30710275Ssam }
30810275Ssam 
30910275Ssam retrieve(cmd, name)
31010275Ssam 	char *cmd, *name;
31110275Ssam {
31210275Ssam 	FILE *fin, *dout;
31310275Ssam 	struct stat st;
31426044Sminshall 	int (*closefunc)(), tmp;
31510275Ssam 
31610275Ssam 	if (cmd == 0) {
31710317Ssam #ifdef notdef
31810317Ssam 		/* no remote command execution -- it's a security hole */
31911653Ssam 		if (*name == '|')
320*36276Sbostic 			fin = ftpd_popen(name + 1, "r"), closefunc = pclose;
32110275Ssam 		else
32210317Ssam #endif
32310275Ssam 			fin = fopen(name, "r"), closefunc = fclose;
32410275Ssam 	} else {
32510275Ssam 		char line[BUFSIZ];
32610275Ssam 
32726493Sminshall 		(void) sprintf(line, cmd, name), name = line;
328*36276Sbostic 		fin = ftpd_popen(line, "r"), closefunc = pclose;
32910275Ssam 	}
33010275Ssam 	if (fin == NULL) {
33113152Ssam 		if (errno != 0)
33213152Ssam 			reply(550, "%s: %s.", name, sys_errlist[errno]);
33310275Ssam 		return;
33410275Ssam 	}
33510275Ssam 	st.st_size = 0;
33610275Ssam 	if (cmd == 0 &&
33710275Ssam 	    (stat(name, &st) < 0 || (st.st_mode&S_IFMT) != S_IFREG)) {
33810275Ssam 		reply(550, "%s: not a plain file.", name);
33910275Ssam 		goto done;
34010275Ssam 	}
34110275Ssam 	dout = dataconn(name, st.st_size, "w");
34210275Ssam 	if (dout == NULL)
34310275Ssam 		goto done;
34426044Sminshall 	if ((tmp = send_data(fin, dout)) > 0 || ferror(dout) > 0) {
34510275Ssam 		reply(550, "%s: %s.", name, sys_errlist[errno]);
34626044Sminshall 	}
34726044Sminshall 	else if (tmp == 0) {
34810275Ssam 		reply(226, "Transfer complete.");
34926044Sminshall 	}
35026493Sminshall 	(void) fclose(dout);
35126044Sminshall 	data = -1;
35226044Sminshall 	pdata = -1;
35310275Ssam done:
35410275Ssam 	(*closefunc)(fin);
35510275Ssam }
35610275Ssam 
35710275Ssam store(name, mode)
35810275Ssam 	char *name, *mode;
35910275Ssam {
36010275Ssam 	FILE *fout, *din;
36126044Sminshall 	int (*closefunc)(), dochown = 0, tmp;
36226044Sminshall 	char *gunique(), *local;
36310275Ssam 
36410317Ssam #ifdef notdef
36510317Ssam 	/* no remote command execution -- it's a security hole */
36611653Ssam 	if (name[0] == '|')
367*36276Sbostic 		fout = ftpd_popen(&name[1], "w"), closefunc = pclose;
36810317Ssam 	else
36910317Ssam #endif
37010317Ssam 	{
37110303Ssam 		struct stat st;
37210303Ssam 
37326044Sminshall 		local = name;
37426044Sminshall 		if (stat(name, &st) < 0) {
37510303Ssam 			dochown++;
37626044Sminshall 		}
37726044Sminshall 		else if (unique) {
37826044Sminshall 			if ((local = gunique(name)) == NULL) {
37926044Sminshall 				return;
38026044Sminshall 			}
38126044Sminshall 			dochown++;
38226044Sminshall 		}
38326044Sminshall 		fout = fopen(local, mode), closefunc = fclose;
38410303Ssam 	}
38510275Ssam 	if (fout == NULL) {
38627106Smckusick 		reply(553, "%s: %s.", local, sys_errlist[errno]);
38710275Ssam 		return;
38810275Ssam 	}
38926044Sminshall 	din = dataconn(local, (off_t)-1, "r");
39010275Ssam 	if (din == NULL)
39110275Ssam 		goto done;
39226044Sminshall 	if ((tmp = receive_data(din, fout)) > 0 || ferror(fout) > 0) {
39327106Smckusick 		reply(552, "%s: %s.", local, sys_errlist[errno]);
39426044Sminshall 	}
39526044Sminshall 	else if (tmp == 0 && !unique) {
39610275Ssam 		reply(226, "Transfer complete.");
39726044Sminshall 	}
39826044Sminshall 	else if (tmp == 0 && unique) {
39926044Sminshall 		reply(226, "Transfer complete (unique file name:%s).", local);
40026044Sminshall 	}
40126493Sminshall 	(void) fclose(din);
40226044Sminshall 	data = -1;
40326044Sminshall 	pdata = -1;
40410275Ssam done:
40510303Ssam 	if (dochown)
406*36276Sbostic 		(void) fchown(fileno(fout), pw->pw_uid, -1);
40710275Ssam 	(*closefunc)(fout);
40810275Ssam }
40910275Ssam 
41010275Ssam FILE *
41110275Ssam getdatasock(mode)
41210275Ssam 	char *mode;
41310275Ssam {
41417157Ssam 	int s, on = 1;
41510275Ssam 
41610275Ssam 	if (data >= 0)
41710275Ssam 		return (fdopen(data, mode));
41813247Ssam 	s = socket(AF_INET, SOCK_STREAM, 0);
41910602Ssam 	if (s < 0)
42010275Ssam 		return (NULL);
42110275Ssam 	seteuid(0);
42226493Sminshall 	if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof (on)) < 0)
42310602Ssam 		goto bad;
42413152Ssam 	/* anchor socket to avoid multi-homing problems */
42513152Ssam 	data_source.sin_family = AF_INET;
42613152Ssam 	data_source.sin_addr = ctrl_addr.sin_addr;
42726493Sminshall 	if (bind(s, &data_source, sizeof (data_source)) < 0)
42810602Ssam 		goto bad;
42910311Ssam 	seteuid(pw->pw_uid);
43010275Ssam 	return (fdopen(s, mode));
43110602Ssam bad:
43210602Ssam 	seteuid(pw->pw_uid);
43326493Sminshall 	(void) close(s);
43410602Ssam 	return (NULL);
43510275Ssam }
43610275Ssam 
43710275Ssam FILE *
43810275Ssam dataconn(name, size, mode)
43910275Ssam 	char *name;
44011653Ssam 	off_t size;
44110275Ssam 	char *mode;
44210275Ssam {
44310275Ssam 	char sizebuf[32];
44410275Ssam 	FILE *file;
44511653Ssam 	int retry = 0;
44610275Ssam 
44710275Ssam 	if (size >= 0)
44826493Sminshall 		(void) sprintf (sizebuf, " (%ld bytes)", size);
44910275Ssam 	else
45010275Ssam 		(void) strcpy(sizebuf, "");
45126044Sminshall 	if (pdata > 0) {
45226044Sminshall 		struct sockaddr_in from;
45326044Sminshall 		int s, fromlen = sizeof(from);
45426044Sminshall 
45526493Sminshall 		s = accept(pdata, &from, &fromlen);
45626044Sminshall 		if (s < 0) {
45726044Sminshall 			reply(425, "Can't open data connection.");
45826044Sminshall 			(void) close(pdata);
45926044Sminshall 			pdata = -1;
46026044Sminshall 			return(NULL);
46126044Sminshall 		}
46226044Sminshall 		(void) close(pdata);
46326044Sminshall 		pdata = s;
46436235Skarels 		reply(150, "Opening %s mode data connection for %s%s.",
46536235Skarels 		     type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf);
46626044Sminshall 		return(fdopen(pdata, mode));
46726044Sminshall 	}
46810275Ssam 	if (data >= 0) {
46910275Ssam 		reply(125, "Using existing data connection for %s%s.",
47010275Ssam 		    name, sizebuf);
47110321Ssam 		usedefault = 1;
47210275Ssam 		return (fdopen(data, mode));
47310275Ssam 	}
47410566Ssam 	if (usedefault)
47510422Ssam 		data_dest = his_addr;
47610422Ssam 	usedefault = 1;
47710275Ssam 	file = getdatasock(mode);
47810275Ssam 	if (file == NULL) {
47910275Ssam 		reply(425, "Can't create data socket (%s,%d): %s.",
48013247Ssam 		    inet_ntoa(data_source.sin_addr),
48110275Ssam 		    ntohs(data_source.sin_port),
48210275Ssam 		    sys_errlist[errno]);
48310275Ssam 		return (NULL);
48410275Ssam 	}
48510275Ssam 	data = fileno(file);
48626044Sminshall 	while (connect(data, &data_dest, sizeof (data_dest)) < 0) {
48711653Ssam 		if (errno == EADDRINUSE && retry < swaitmax) {
48826493Sminshall 			sleep((unsigned) swaitint);
48911653Ssam 			retry += swaitint;
49011653Ssam 			continue;
49111653Ssam 		}
49210275Ssam 		reply(425, "Can't build data connection: %s.",
49310275Ssam 		    sys_errlist[errno]);
49410275Ssam 		(void) fclose(file);
49510275Ssam 		data = -1;
49610275Ssam 		return (NULL);
49710275Ssam 	}
49836235Skarels 	reply(150, "Opening %s mode data connection for %s%s.",
49936235Skarels 	     type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf);
50010275Ssam 	return (file);
50110275Ssam }
50210275Ssam 
50310275Ssam /*
50410275Ssam  * Tranfer the contents of "instr" to
50510275Ssam  * "outstr" peer using the appropriate
50610275Ssam  * encapulation of the date subject
50710275Ssam  * to Mode, Structure, and Type.
50810275Ssam  *
50910275Ssam  * NB: Form isn't handled.
51010275Ssam  */
51110275Ssam send_data(instr, outstr)
51210275Ssam 	FILE *instr, *outstr;
51310275Ssam {
51410275Ssam 	register int c;
51510275Ssam 	int netfd, filefd, cnt;
51610275Ssam 	char buf[BUFSIZ];
51710275Ssam 
51826044Sminshall 	transflag++;
51926044Sminshall 	if (setjmp(urgcatch)) {
52026044Sminshall 		transflag = 0;
52126044Sminshall 		return(-1);
52226044Sminshall 	}
52310275Ssam 	switch (type) {
52410275Ssam 
52510275Ssam 	case TYPE_A:
52610275Ssam 		while ((c = getc(instr)) != EOF) {
52711220Ssam 			if (c == '\n') {
52826044Sminshall 				if (ferror (outstr)) {
52926044Sminshall 					transflag = 0;
53011220Ssam 					return (1);
53126044Sminshall 				}
53227750Sminshall 				(void) putc('\r', outstr);
53311220Ssam 			}
53427750Sminshall 			(void) putc(c, outstr);
53526044Sminshall 		/*	if (c == '\r')			*/
53626044Sminshall 		/*		putc ('\0', outstr);	*/
53710275Ssam 		}
53826044Sminshall 		transflag = 0;
53926044Sminshall 		if (ferror (instr) || ferror (outstr)) {
54011220Ssam 			return (1);
54126044Sminshall 		}
54210275Ssam 		return (0);
54310275Ssam 
54410275Ssam 	case TYPE_I:
54510275Ssam 	case TYPE_L:
54610275Ssam 		netfd = fileno(outstr);
54710275Ssam 		filefd = fileno(instr);
54810275Ssam 
54926044Sminshall 		while ((cnt = read(filefd, buf, sizeof (buf))) > 0) {
55026044Sminshall 			if (write(netfd, buf, cnt) < 0) {
55126044Sminshall 				transflag = 0;
55210275Ssam 				return (1);
55326044Sminshall 			}
55426044Sminshall 		}
55526044Sminshall 		transflag = 0;
55610275Ssam 		return (cnt < 0);
55710275Ssam 	}
55827106Smckusick 	reply(550, "Unimplemented TYPE %d in send_data", type);
55926044Sminshall 	transflag = 0;
56027106Smckusick 	return (-1);
56110275Ssam }
56210275Ssam 
56310275Ssam /*
56410275Ssam  * Transfer data from peer to
56510275Ssam  * "outstr" using the appropriate
56610275Ssam  * encapulation of the data subject
56710275Ssam  * to Mode, Structure, and Type.
56810275Ssam  *
56910275Ssam  * N.B.: Form isn't handled.
57010275Ssam  */
57110275Ssam receive_data(instr, outstr)
57210275Ssam 	FILE *instr, *outstr;
57310275Ssam {
57410275Ssam 	register int c;
57511220Ssam 	int cnt;
57610275Ssam 	char buf[BUFSIZ];
57710275Ssam 
57810275Ssam 
57926044Sminshall 	transflag++;
58026044Sminshall 	if (setjmp(urgcatch)) {
58126044Sminshall 		transflag = 0;
58226044Sminshall 		return(-1);
58326044Sminshall 	}
58410275Ssam 	switch (type) {
58510275Ssam 
58610275Ssam 	case TYPE_I:
58710275Ssam 	case TYPE_L:
58826044Sminshall 		while ((cnt = read(fileno(instr), buf, sizeof buf)) > 0) {
58926044Sminshall 			if (write(fileno(outstr), buf, cnt) < 0) {
59026044Sminshall 				transflag = 0;
59110275Ssam 				return (1);
59226044Sminshall 			}
59326044Sminshall 		}
59426044Sminshall 		transflag = 0;
59510275Ssam 		return (cnt < 0);
59610275Ssam 
59710275Ssam 	case TYPE_E:
59827106Smckusick 		reply(553, "TYPE E not implemented.");
59926044Sminshall 		transflag = 0;
60027106Smckusick 		return (-1);
60110275Ssam 
60210275Ssam 	case TYPE_A:
60310275Ssam 		while ((c = getc(instr)) != EOF) {
60427750Sminshall 			while (c == '\r') {
60526044Sminshall 				if (ferror (outstr)) {
60626044Sminshall 					transflag = 0;
60711220Ssam 					return (1);
60826044Sminshall 				}
60911220Ssam 				if ((c = getc(instr)) != '\n')
61027750Sminshall 					(void) putc ('\r', outstr);
61126044Sminshall 			/*	if (c == '\0')			*/
61226044Sminshall 			/*		continue;		*/
61310275Ssam 			}
61427750Sminshall 			(void) putc (c, outstr);
61510275Ssam 		}
61626044Sminshall 		transflag = 0;
61711220Ssam 		if (ferror (instr) || ferror (outstr))
61811220Ssam 			return (1);
61910275Ssam 		return (0);
62010275Ssam 	}
62126044Sminshall 	transflag = 0;
62210275Ssam 	fatal("Unknown type in receive_data.");
62310275Ssam 	/*NOTREACHED*/
62410275Ssam }
62510275Ssam 
62610275Ssam fatal(s)
62710275Ssam 	char *s;
62810275Ssam {
62910275Ssam 	reply(451, "Error in server: %s\n", s);
63010275Ssam 	reply(221, "Closing connection due to server error.");
63113247Ssam 	dologout(0);
63210275Ssam }
63310275Ssam 
63432110Smckusick reply(n, s, p0, p1, p2, p3, p4)
63510275Ssam 	int n;
63610275Ssam 	char *s;
63710275Ssam {
63810275Ssam 
63910275Ssam 	printf("%d ", n);
64032110Smckusick 	printf(s, p0, p1, p2, p3, p4);
64110275Ssam 	printf("\r\n");
64226493Sminshall 	(void) fflush(stdout);
64310275Ssam 	if (debug) {
64426493Sminshall 		syslog(LOG_DEBUG, "<--- %d ", n);
64532110Smckusick 		syslog(LOG_DEBUG, s, p0, p1, p2, p3, p4);
64610275Ssam 	}
64710275Ssam }
64810275Ssam 
64932110Smckusick lreply(n, s, p0, p1, p2, p3, p4)
65010275Ssam 	int n;
65110275Ssam 	char *s;
65210275Ssam {
65310275Ssam 	printf("%d-", n);
65432110Smckusick 	printf(s, p0, p1, p2, p3, p4);
65510275Ssam 	printf("\r\n");
65626493Sminshall 	(void) fflush(stdout);
65710275Ssam 	if (debug) {
65826493Sminshall 		syslog(LOG_DEBUG, "<--- %d- ", n);
65932110Smckusick 		syslog(LOG_DEBUG, s, p0, p1, p2, p3, p4);
66010275Ssam 	}
66110275Ssam }
66210275Ssam 
66310275Ssam ack(s)
66410275Ssam 	char *s;
66510275Ssam {
66627106Smckusick 	reply(250, "%s command successful.", s);
66710275Ssam }
66810275Ssam 
66910275Ssam nack(s)
67010275Ssam 	char *s;
67110275Ssam {
67210275Ssam 	reply(502, "%s command not implemented.", s);
67310275Ssam }
67410275Ssam 
67526493Sminshall yyerror(s)
67626493Sminshall 	char *s;
67710275Ssam {
67826044Sminshall 	char *cp;
67926044Sminshall 
68026044Sminshall 	cp = index(cbuf,'\n');
68126044Sminshall 	*cp = '\0';
68226044Sminshall 	reply(500, "'%s': command not understood.",cbuf);
68310275Ssam }
68410275Ssam 
68510275Ssam delete(name)
68610275Ssam 	char *name;
68710275Ssam {
68810275Ssam 	struct stat st;
68910275Ssam 
69010275Ssam 	if (stat(name, &st) < 0) {
69110275Ssam 		reply(550, "%s: %s.", name, sys_errlist[errno]);
69210275Ssam 		return;
69310275Ssam 	}
69410275Ssam 	if ((st.st_mode&S_IFMT) == S_IFDIR) {
69510275Ssam 		if (rmdir(name) < 0) {
69610275Ssam 			reply(550, "%s: %s.", name, sys_errlist[errno]);
69710275Ssam 			return;
69810275Ssam 		}
69910275Ssam 		goto done;
70010275Ssam 	}
70110275Ssam 	if (unlink(name) < 0) {
70210275Ssam 		reply(550, "%s: %s.", name, sys_errlist[errno]);
70310275Ssam 		return;
70410275Ssam 	}
70510275Ssam done:
70610275Ssam 	ack("DELE");
70710275Ssam }
70810275Ssam 
70910275Ssam cwd(path)
71010275Ssam 	char *path;
71110275Ssam {
71210275Ssam 
71310275Ssam 	if (chdir(path) < 0) {
71410275Ssam 		reply(550, "%s: %s.", path, sys_errlist[errno]);
71510275Ssam 		return;
71610275Ssam 	}
71710275Ssam 	ack("CWD");
71810275Ssam }
71910275Ssam 
72010303Ssam makedir(name)
72110275Ssam 	char *name;
72210275Ssam {
723*36276Sbostic 	uid_t	oldeuid;
724*36276Sbostic 
725*36276Sbostic 	oldeuid = geteuid();
726*36276Sbostic 	seteuid(pw->pw_uid);
727*36276Sbostic 	if (mkdir(name, 0777) < 0)
72810275Ssam 		reply(550, "%s: %s.", name, sys_errlist[errno]);
729*36276Sbostic 	else
730*36276Sbostic 		reply(257, "MKD command successful.");
731*36276Sbostic 	seteuid(oldeuid);
73210275Ssam }
73310275Ssam 
73410303Ssam removedir(name)
73510275Ssam 	char *name;
73610275Ssam {
73710275Ssam 
73810275Ssam 	if (rmdir(name) < 0) {
73910275Ssam 		reply(550, "%s: %s.", name, sys_errlist[errno]);
74010275Ssam 		return;
74110275Ssam 	}
74227106Smckusick 	ack("RMD");
74310275Ssam }
74410275Ssam 
74510303Ssam pwd()
74610275Ssam {
74710303Ssam 	char path[MAXPATHLEN + 1];
74810275Ssam 
74910275Ssam 	if (getwd(path) == NULL) {
75027106Smckusick 		reply(550, "%s.", path);
75110275Ssam 		return;
75210275Ssam 	}
75327106Smckusick 	reply(257, "\"%s\" is current directory.", path);
75410275Ssam }
75510275Ssam 
75610275Ssam char *
75710275Ssam renamefrom(name)
75810275Ssam 	char *name;
75910275Ssam {
76010275Ssam 	struct stat st;
76110275Ssam 
76210275Ssam 	if (stat(name, &st) < 0) {
76310275Ssam 		reply(550, "%s: %s.", name, sys_errlist[errno]);
76410275Ssam 		return ((char *)0);
76510275Ssam 	}
76610303Ssam 	reply(350, "File exists, ready for destination name");
76710275Ssam 	return (name);
76810275Ssam }
76910275Ssam 
77010275Ssam renamecmd(from, to)
77110275Ssam 	char *from, *to;
77210275Ssam {
77310275Ssam 
77410275Ssam 	if (rename(from, to) < 0) {
77510275Ssam 		reply(550, "rename: %s.", sys_errlist[errno]);
77610275Ssam 		return;
77710275Ssam 	}
77810275Ssam 	ack("RNTO");
77910275Ssam }
78010275Ssam 
78110275Ssam dolog(sin)
78210275Ssam 	struct sockaddr_in *sin;
78310275Ssam {
78410275Ssam 	struct hostent *hp = gethostbyaddr(&sin->sin_addr,
78510275Ssam 		sizeof (struct in_addr), AF_INET);
78610275Ssam 	time_t t;
78726493Sminshall 	extern char *ctime();
78810275Ssam 
78913247Ssam 	if (hp) {
79026493Sminshall 		(void) strncpy(remotehost, hp->h_name, sizeof (remotehost));
79113247Ssam 		endhostent();
79213247Ssam 	} else
79326493Sminshall 		(void) strncpy(remotehost, inet_ntoa(sin->sin_addr),
79413247Ssam 		    sizeof (remotehost));
79513247Ssam 	if (!logging)
79613247Ssam 		return;
79726493Sminshall 	t = time((time_t *) 0);
79826493Sminshall 	syslog(LOG_INFO,"FTPD: connection from %s at %s", remotehost, ctime(&t));
79910275Ssam }
80010695Ssam 
80110695Ssam /*
80213247Ssam  * Record logout in wtmp file
80313247Ssam  * and exit with supplied status.
80413247Ssam  */
80513247Ssam dologout(status)
80613247Ssam 	int status;
80713247Ssam {
80817580Ssam 	if (logged_in) {
80917580Ssam 		(void) seteuid(0);
81035672Sbostic 		logwtmp(ttyline, "", "");
81113247Ssam 	}
81214436Ssam 	/* beware of flushing buffers after a SIGPIPE */
81314436Ssam 	_exit(status);
81413247Ssam }
81513247Ssam 
81613247Ssam /*
81710695Ssam  * Check user requesting login priviledges.
81828864Smckusick  * Disallow anyone who does not have a standard
81928864Smckusick  * shell returned by getusershell() (/etc/shells).
82010695Ssam  * Disallow anyone mentioned in the file FTPUSERS
82110695Ssam  * to allow people such as uucp to be avoided.
82210695Ssam  */
82310695Ssam checkuser(name)
82410695Ssam 	register char *name;
82510695Ssam {
82628864Smckusick 	register char *cp;
82710695Ssam 	FILE *fd;
82836185Sbostic 	struct passwd *p;
82936185Sbostic 	char *shell;
83010695Ssam 	int found = 0;
83136185Sbostic 	char line[BUFSIZ], *index(), *getusershell();
83210695Ssam 
83336185Sbostic 	if ((p = getpwnam(name)) == NULL)
83428864Smckusick 		return (0);
83536185Sbostic 	if ((shell = p->pw_shell) == NULL || *shell == 0)
83636185Sbostic 		shell = "/bin/sh";
83728864Smckusick 	while ((cp = getusershell()) != NULL)
83836185Sbostic 		if (strcmp(cp, shell) == 0)
83928864Smckusick 			break;
84028864Smckusick 	endusershell();
84128864Smckusick 	if (cp == NULL)
84228864Smckusick 		return (0);
84336185Sbostic 	if ((fd = fopen(FTPUSERS, "r")) == NULL)
84410695Ssam 		return (1);
84510695Ssam 	while (fgets(line, sizeof (line), fd) != NULL) {
84636185Sbostic 		if ((cp = index(line, '\n')) != NULL)
84710695Ssam 			*cp = '\0';
84810695Ssam 		if (strcmp(line, name) == 0) {
84910695Ssam 			found++;
85010695Ssam 			break;
85110695Ssam 		}
85210695Ssam 	}
85326493Sminshall 	(void) fclose(fd);
85410695Ssam 	return (!found);
85510695Ssam }
85626044Sminshall 
85726044Sminshall myoob()
85826044Sminshall {
85927750Sminshall 	char *cp;
86026044Sminshall 
86127750Sminshall 	/* only process if transfer occurring */
86226044Sminshall 	if (!transflag) {
86326044Sminshall 		return;
86426044Sminshall 	}
86527750Sminshall 	cp = tmpline;
86627750Sminshall 	if (getline(cp, 7, stdin) == NULL) {
86727750Sminshall 		reply(221, "You could at least say goodby.");
86827750Sminshall 		dologout(0);
86926044Sminshall 	}
87026044Sminshall 	upper(cp);
87126227Ssam 	if (strcmp(cp, "ABOR\r\n"))
87226044Sminshall 		return;
87326044Sminshall 	tmpline[0] = '\0';
87426044Sminshall 	reply(426,"Transfer aborted. Data connection closed.");
87526044Sminshall 	reply(226,"Abort successful");
87626044Sminshall 	longjmp(urgcatch, 1);
87726044Sminshall }
87826044Sminshall 
87927106Smckusick /*
88027106Smckusick  * Note: The 530 reply codes could be 4xx codes, except nothing is
88127106Smckusick  * given in the state tables except 421 which implies an exit.  (RFC959)
88227106Smckusick  */
88326044Sminshall passive()
88426044Sminshall {
88526044Sminshall 	int len;
88626044Sminshall 	struct sockaddr_in tmp;
88726044Sminshall 	register char *p, *a;
88826044Sminshall 
88926044Sminshall 	pdata = socket(AF_INET, SOCK_STREAM, 0);
89026044Sminshall 	if (pdata < 0) {
89127106Smckusick 		reply(530, "Can't open passive connection");
89226044Sminshall 		return;
89326044Sminshall 	}
89426044Sminshall 	tmp = ctrl_addr;
89526044Sminshall 	tmp.sin_port = 0;
89626044Sminshall 	seteuid(0);
89726493Sminshall 	if (bind(pdata, (struct sockaddr *) &tmp, sizeof(tmp)) < 0) {
89826044Sminshall 		seteuid(pw->pw_uid);
89926044Sminshall 		(void) close(pdata);
90026044Sminshall 		pdata = -1;
90127106Smckusick 		reply(530, "Can't open passive connection");
90226044Sminshall 		return;
90326044Sminshall 	}
90426044Sminshall 	seteuid(pw->pw_uid);
90526044Sminshall 	len = sizeof(tmp);
90626044Sminshall 	if (getsockname(pdata, (char *) &tmp, &len) < 0) {
90726044Sminshall 		(void) close(pdata);
90826044Sminshall 		pdata = -1;
90927106Smckusick 		reply(530, "Can't open passive connection");
91026044Sminshall 		return;
91126044Sminshall 	}
91226044Sminshall 	if (listen(pdata, 1) < 0) {
91326044Sminshall 		(void) close(pdata);
91426044Sminshall 		pdata = -1;
91527106Smckusick 		reply(530, "Can't open passive connection");
91626044Sminshall 		return;
91726044Sminshall 	}
91826044Sminshall 	a = (char *) &tmp.sin_addr;
91926044Sminshall 	p = (char *) &tmp.sin_port;
92026044Sminshall 
92126044Sminshall #define UC(b) (((int) b) & 0xff)
92226044Sminshall 
92326044Sminshall 	reply(227, "Entering Passive Mode (%d,%d,%d,%d,%d,%d)", UC(a[0]),
92426044Sminshall 		UC(a[1]), UC(a[2]), UC(a[3]), UC(p[0]), UC(p[1]));
92526044Sminshall }
92626044Sminshall 
92726044Sminshall char *
92826044Sminshall gunique(local)
92926044Sminshall 	char *local;
93026044Sminshall {
93126044Sminshall 	static char new[MAXPATHLEN];
93226044Sminshall 	char *cp = rindex(local, '/');
93326044Sminshall 	int d, count=0;
93426044Sminshall 	char ext = '1';
93526044Sminshall 
93626044Sminshall 	if (cp) {
93726044Sminshall 		*cp = '\0';
93826044Sminshall 	}
93926044Sminshall 	d = access(cp ? local : ".", 2);
94026044Sminshall 	if (cp) {
94126044Sminshall 		*cp = '/';
94226044Sminshall 	}
94326044Sminshall 	if (d < 0) {
94426493Sminshall 		syslog(LOG_ERR, "%s: %m", local);
94526044Sminshall 		return((char *) 0);
94626044Sminshall 	}
94726044Sminshall 	(void) strcpy(new, local);
94826044Sminshall 	cp = new + strlen(new);
94926044Sminshall 	*cp++ = '.';
95026044Sminshall 	while (!d) {
95126044Sminshall 		if (++count == 100) {
95227106Smckusick 			reply(452, "Unique file name not cannot be created.");
95326044Sminshall 			return((char *) 0);
95426044Sminshall 		}
95526044Sminshall 		*cp++ = ext;
95626044Sminshall 		*cp = '\0';
95726044Sminshall 		if (ext == '9') {
95826044Sminshall 			ext = '0';
95926044Sminshall 		}
96026044Sminshall 		else {
96126044Sminshall 			ext++;
96226044Sminshall 		}
96326044Sminshall 		if ((d = access(new, 0)) < 0) {
96426044Sminshall 			break;
96526044Sminshall 		}
96626044Sminshall 		if (ext != '0') {
96726044Sminshall 			cp--;
96826044Sminshall 		}
96926044Sminshall 		else if (*(cp - 2) == '.') {
97026044Sminshall 			*(cp - 1) = '1';
97126044Sminshall 		}
97226044Sminshall 		else {
97326044Sminshall 			*(cp - 2) = *(cp - 2) + 1;
97426044Sminshall 			cp--;
97526044Sminshall 		}
97626044Sminshall 	}
97726044Sminshall 	return(new);
97826044Sminshall }
979