xref: /csrg-svn/libexec/ftpd/ftpd.c (revision 22499)
1*22499Sdist /*
2*22499Sdist  * Copyright (c) 1983 Regents of the University of California.
3*22499Sdist  * All rights reserved.  The Berkeley software License Agreement
4*22499Sdist  * specifies the terms and conditions for redistribution.
5*22499Sdist  */
6*22499Sdist 
710275Ssam #ifndef lint
8*22499Sdist char copyright[] =
9*22499Sdist "@(#) Copyright (c) 1983 Regents of the University of California.\n\
10*22499Sdist  All rights reserved.\n";
11*22499Sdist #endif not lint
1210275Ssam 
13*22499Sdist #ifndef lint
14*22499Sdist static char sccsid[] = "@(#)ftpd.c	5.1 (Berkeley) 06/06/85";
15*22499Sdist #endif not lint
16*22499Sdist 
1710275Ssam /*
1810275Ssam  * FTP server.
1910275Ssam  */
2010303Ssam #include <sys/param.h>
2110275Ssam #include <sys/stat.h>
2210275Ssam #include <sys/ioctl.h>
2310275Ssam #include <sys/socket.h>
2413247Ssam #include <sys/file.h>
2513595Ssam #include <sys/wait.h>
2610275Ssam 
2710275Ssam #include <netinet/in.h>
2810275Ssam 
2913034Ssam #include <arpa/ftp.h>
3013211Sroot #include <arpa/inet.h>
3113034Ssam 
3210275Ssam #include <stdio.h>
3310275Ssam #include <signal.h>
3410275Ssam #include <pwd.h>
3510275Ssam #include <setjmp.h>
3610275Ssam #include <netdb.h>
3710423Ssam #include <errno.h>
3810275Ssam 
3910695Ssam /*
4010695Ssam  * File containing login names
4110695Ssam  * NOT to be used on this machine.
4210695Ssam  * Commonly used to disallow uucp.
4310695Ssam  */
4410695Ssam #define	FTPUSERS	"/etc/ftpusers"
4510695Ssam 
4610275Ssam extern	int errno;
4710275Ssam extern	char *sys_errlist[];
4810275Ssam extern	char *crypt();
4910275Ssam extern	char version[];
5010275Ssam extern	char *home;		/* pointer to home directory for glob */
5110275Ssam extern	FILE *popen(), *fopen();
5210275Ssam extern	int pclose(), fclose();
5310275Ssam 
5410275Ssam struct	sockaddr_in ctrl_addr;
5510275Ssam struct	sockaddr_in data_source;
5610275Ssam struct	sockaddr_in data_dest;
5710275Ssam struct	sockaddr_in his_addr;
5810275Ssam 
5910275Ssam struct	hostent *hp;
6010275Ssam 
6110275Ssam int	data;
6210275Ssam jmp_buf	errcatch;
6310275Ssam int	logged_in;
6410275Ssam struct	passwd *pw;
6510275Ssam int	debug;
6611653Ssam int	timeout;
6711757Ssam int	logging;
6810275Ssam int	guest;
6916033Sralph int	wtmp;
7010275Ssam int	type;
7110275Ssam int	form;
7210275Ssam int	stru;			/* avoid C keyword */
7310275Ssam int	mode;
7410321Ssam int	usedefault = 1;		/* for data transfers */
7510275Ssam char	hostname[32];
7613247Ssam char	remotehost[32];
7710275Ssam 
7811653Ssam /*
7911653Ssam  * Timeout intervals for retrying connections
8011653Ssam  * to hosts that don't accept PORT cmds.  This
8111653Ssam  * is a kludge, but given the problems with TCP...
8211653Ssam  */
8311653Ssam #define	SWAITMAX	90	/* wait at most 90 seconds */
8411653Ssam #define	SWAITINT	5	/* interval between retries */
8511653Ssam 
8611653Ssam int	swaitmax = SWAITMAX;
8711653Ssam int	swaitint = SWAITINT;
8811653Ssam 
8910275Ssam int	lostconn();
9010419Ssam int	reapchild();
9110275Ssam FILE	*getdatasock(), *dataconn();
9210275Ssam 
9310275Ssam main(argc, argv)
9410275Ssam 	int argc;
9510275Ssam 	char *argv[];
9610275Ssam {
9716339Skarels 	int options = 0, addrlen;
9810275Ssam 	char *cp;
9910275Ssam 
10016339Skarels 	addrlen = sizeof (his_addr);
10116339Skarels 	if (getpeername(0, &his_addr, &addrlen) < 0) {
10216339Skarels 		fprintf(stderr, "%s: ", argv[0]);
10316339Skarels 		perror("getpeername");
10410275Ssam 		exit(1);
10510275Ssam 	}
10616339Skarels 	addrlen = sizeof (ctrl_addr);
10716339Skarels 	if (getsockname(0, &ctrl_addr, &addrlen) < 0) {
10816339Skarels 		fprintf(stderr, "%s: ", argv[0]);
10916339Skarels 		perror("getsockname");
11016339Skarels 		exit(1);
11116339Skarels 	}
11216339Skarels 	data_source.sin_port = htons(ntohs(ctrl_addr.sin_port) - 1);
11310275Ssam 	debug = 0;
11410275Ssam 	argc--, argv++;
11510275Ssam 	while (argc > 0 && *argv[0] == '-') {
11610275Ssam 		for (cp = &argv[0][1]; *cp; cp++) switch (*cp) {
11710275Ssam 
11811653Ssam 		case 'v':
11911653Ssam 			debug = 1;
12011653Ssam 			break;
12111653Ssam 
12210275Ssam 		case 'd':
12310275Ssam 			debug = 1;
12410275Ssam 			options |= SO_DEBUG;
12510275Ssam 			break;
12610275Ssam 
12711757Ssam 		case 'l':
12811757Ssam 			logging = 1;
12911757Ssam 			break;
13011757Ssam 
13111653Ssam 		case 't':
13211653Ssam 			timeout = atoi(++cp);
13311653Ssam 			goto nextopt;
13411653Ssam 			break;
13511653Ssam 
13610275Ssam 		default:
13716339Skarels 			fprintf(stderr, "ftpd: Unknown flag -%c ignored.\n",
13816339Skarels 			     *cp);
13910275Ssam 			break;
14010275Ssam 		}
14111653Ssam nextopt:
14210275Ssam 		argc--, argv++;
14310275Ssam 	}
14416339Skarels 	signal(SIGPIPE, lostconn);
14516339Skarels 	signal(SIGCHLD, SIG_IGN);
14616760Slepreau 	dolog(&his_addr);
14716339Skarels 	/* do telnet option negotiation here */
14816339Skarels 	/*
14916339Skarels 	 * Set up default state
15016339Skarels 	 */
15116339Skarels 	logged_in = 0;
15216339Skarels 	data = -1;
15316339Skarels 	type = TYPE_A;
15416339Skarels 	form = FORM_N;
15516339Skarels 	stru = STRU_F;
15616339Skarels 	mode = MODE_S;
15716339Skarels 	gethostname(hostname, sizeof (hostname));
15816339Skarels 	reply(220, "%s FTP server (%s) ready.",
15916339Skarels 		hostname, version);
16010275Ssam 	for (;;) {
16116339Skarels 		setjmp(errcatch);
16216339Skarels 		yyparse();
16310275Ssam 	}
16410275Ssam }
16510275Ssam 
16610419Ssam reapchild()
16710419Ssam {
16810419Ssam 	union wait status;
16910419Ssam 
17010419Ssam 	while (wait3(&status, WNOHANG, 0) > 0)
17110419Ssam 		;
17210419Ssam }
17310419Ssam 
17410275Ssam lostconn()
17510275Ssam {
17610275Ssam 
17714089Ssam 	if (debug)
17814089Ssam 		fprintf(stderr, "Lost connection.\n");
17914089Ssam 	dologout(-1);
18010275Ssam }
18110275Ssam 
18210275Ssam pass(passwd)
18310275Ssam 	char *passwd;
18410275Ssam {
18510303Ssam 	char *xpasswd, *savestr();
18610303Ssam 	static struct passwd save;
18710275Ssam 
18810275Ssam 	if (logged_in || pw == NULL) {
18910275Ssam 		reply(503, "Login with USER first.");
19010275Ssam 		return;
19110275Ssam 	}
19210275Ssam 	if (!guest) {		/* "ftp" is only account allowed no password */
19310275Ssam 		xpasswd = crypt(passwd, pw->pw_passwd);
19416760Slepreau 		/* The strcmp does not catch null passwords! */
19516760Slepreau 		if (*pw->pw_passwd == '\0' || strcmp(xpasswd, pw->pw_passwd)) {
19610275Ssam 			reply(530, "Login incorrect.");
19710275Ssam 			pw = NULL;
19810275Ssam 			return;
19910275Ssam 		}
20010275Ssam 	}
20110303Ssam 	setegid(pw->pw_gid);
20210275Ssam 	initgroups(pw->pw_name, pw->pw_gid);
20310275Ssam 	if (chdir(pw->pw_dir)) {
20418107Sbloom 		reply(550, "User %s: can't change directory to %s.",
20510275Ssam 			pw->pw_name, pw->pw_dir);
20610303Ssam 		goto bad;
20710275Ssam 	}
20816033Sralph 
20916760Slepreau 	/* grab wtmp before chroot */
21016760Slepreau 	wtmp = open("/usr/adm/wtmp", O_WRONLY|O_APPEND);
21110303Ssam 	if (guest && chroot(pw->pw_dir) < 0) {
21210275Ssam 		reply(550, "Can't set guest privileges.");
21316760Slepreau 		if (wtmp >= 0) {
21416760Slepreau 			(void) close(wtmp);
21516760Slepreau 			wtmp = -1;
21616760Slepreau 		}
21710303Ssam 		goto bad;
21810275Ssam 	}
21910275Ssam 	if (!guest)
22010275Ssam 		reply(230, "User %s logged in.", pw->pw_name);
22110275Ssam 	else
22210275Ssam 		reply(230, "Guest login ok, access restrictions apply.");
22310275Ssam 	logged_in = 1;
22413247Ssam 	dologin(pw);
22510303Ssam 	seteuid(pw->pw_uid);
22610303Ssam 	/*
22710303Ssam 	 * Save everything so globbing doesn't
22810303Ssam 	 * clobber the fields.
22910303Ssam 	 */
23010303Ssam 	save = *pw;
23110303Ssam 	save.pw_name = savestr(pw->pw_name);
23210303Ssam 	save.pw_passwd = savestr(pw->pw_passwd);
23310303Ssam 	save.pw_comment = savestr(pw->pw_comment);
23410303Ssam 	save.pw_gecos = savestr(pw->pw_gecos, &save.pw_gecos);
23510303Ssam 	save.pw_dir = savestr(pw->pw_dir);
23610303Ssam 	save.pw_shell = savestr(pw->pw_shell);
23710303Ssam 	pw = &save;
23810303Ssam 	home = pw->pw_dir;		/* home dir for globbing */
23910303Ssam 	return;
24010303Ssam bad:
24110303Ssam 	seteuid(0);
24210303Ssam 	pw = NULL;
24310275Ssam }
24410275Ssam 
24510303Ssam char *
24610303Ssam savestr(s)
24710303Ssam 	char *s;
24810303Ssam {
24910303Ssam 	char *malloc();
25010303Ssam 	char *new = malloc(strlen(s) + 1);
25110303Ssam 
25210303Ssam 	if (new != NULL)
25310303Ssam 		strcpy(new, s);
25411347Ssam 	return (new);
25510303Ssam }
25610303Ssam 
25710275Ssam retrieve(cmd, name)
25810275Ssam 	char *cmd, *name;
25910275Ssam {
26010275Ssam 	FILE *fin, *dout;
26110275Ssam 	struct stat st;
26210275Ssam 	int (*closefunc)();
26310275Ssam 
26410275Ssam 	if (cmd == 0) {
26510317Ssam #ifdef notdef
26610317Ssam 		/* no remote command execution -- it's a security hole */
26711653Ssam 		if (*name == '|')
26810275Ssam 			fin = popen(name + 1, "r"), closefunc = pclose;
26910275Ssam 		else
27010317Ssam #endif
27110275Ssam 			fin = fopen(name, "r"), closefunc = fclose;
27210275Ssam 	} else {
27310275Ssam 		char line[BUFSIZ];
27410275Ssam 
27510422Ssam 		sprintf(line, cmd, name), name = line;
27610275Ssam 		fin = popen(line, "r"), closefunc = pclose;
27710275Ssam 	}
27810275Ssam 	if (fin == NULL) {
27913152Ssam 		if (errno != 0)
28013152Ssam 			reply(550, "%s: %s.", name, sys_errlist[errno]);
28110275Ssam 		return;
28210275Ssam 	}
28310275Ssam 	st.st_size = 0;
28410275Ssam 	if (cmd == 0 &&
28510275Ssam 	    (stat(name, &st) < 0 || (st.st_mode&S_IFMT) != S_IFREG)) {
28610275Ssam 		reply(550, "%s: not a plain file.", name);
28710275Ssam 		goto done;
28810275Ssam 	}
28910275Ssam 	dout = dataconn(name, st.st_size, "w");
29010275Ssam 	if (dout == NULL)
29110275Ssam 		goto done;
29210303Ssam 	if (send_data(fin, dout) || ferror(dout))
29310275Ssam 		reply(550, "%s: %s.", name, sys_errlist[errno]);
29410275Ssam 	else
29510275Ssam 		reply(226, "Transfer complete.");
29610303Ssam 	fclose(dout), data = -1;
29710275Ssam done:
29810275Ssam 	(*closefunc)(fin);
29910275Ssam }
30010275Ssam 
30110275Ssam store(name, mode)
30210275Ssam 	char *name, *mode;
30310275Ssam {
30410275Ssam 	FILE *fout, *din;
30510303Ssam 	int (*closefunc)(), dochown = 0;
30610275Ssam 
30710317Ssam #ifdef notdef
30810317Ssam 	/* no remote command execution -- it's a security hole */
30911653Ssam 	if (name[0] == '|')
31010275Ssam 		fout = popen(&name[1], "w"), closefunc = pclose;
31110317Ssam 	else
31210317Ssam #endif
31310317Ssam 	{
31410303Ssam 		struct stat st;
31510303Ssam 
31610303Ssam 		if (stat(name, &st) < 0)
31710303Ssam 			dochown++;
31810275Ssam 		fout = fopen(name, mode), closefunc = fclose;
31910303Ssam 	}
32010275Ssam 	if (fout == NULL) {
32110275Ssam 		reply(550, "%s: %s.", name, sys_errlist[errno]);
32210275Ssam 		return;
32310275Ssam 	}
32411653Ssam 	din = dataconn(name, (off_t)-1, "r");
32510275Ssam 	if (din == NULL)
32610275Ssam 		goto done;
32710303Ssam 	if (receive_data(din, fout) || ferror(fout))
32810275Ssam 		reply(550, "%s: %s.", name, sys_errlist[errno]);
32910275Ssam 	else
33010275Ssam 		reply(226, "Transfer complete.");
33110275Ssam 	fclose(din), data = -1;
33210275Ssam done:
33310303Ssam 	if (dochown)
33410303Ssam 		(void) chown(name, pw->pw_uid, -1);
33510275Ssam 	(*closefunc)(fout);
33610275Ssam }
33710275Ssam 
33810275Ssam FILE *
33910275Ssam getdatasock(mode)
34010275Ssam 	char *mode;
34110275Ssam {
34217157Ssam 	int s, on = 1;
34310275Ssam 
34410275Ssam 	if (data >= 0)
34510275Ssam 		return (fdopen(data, mode));
34613247Ssam 	s = socket(AF_INET, SOCK_STREAM, 0);
34710602Ssam 	if (s < 0)
34810275Ssam 		return (NULL);
34910275Ssam 	seteuid(0);
35017157Ssam 	if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (on)) < 0)
35110602Ssam 		goto bad;
35213152Ssam 	/* anchor socket to avoid multi-homing problems */
35313152Ssam 	data_source.sin_family = AF_INET;
35413152Ssam 	data_source.sin_addr = ctrl_addr.sin_addr;
35510602Ssam 	if (bind(s, &data_source, sizeof (data_source), 0) < 0)
35610602Ssam 		goto bad;
35710311Ssam 	seteuid(pw->pw_uid);
35810275Ssam 	return (fdopen(s, mode));
35910602Ssam bad:
36010602Ssam 	seteuid(pw->pw_uid);
36110602Ssam 	close(s);
36210602Ssam 	return (NULL);
36310275Ssam }
36410275Ssam 
36510275Ssam FILE *
36610275Ssam dataconn(name, size, mode)
36710275Ssam 	char *name;
36811653Ssam 	off_t size;
36910275Ssam 	char *mode;
37010275Ssam {
37110275Ssam 	char sizebuf[32];
37210275Ssam 	FILE *file;
37311653Ssam 	int retry = 0;
37410275Ssam 
37510275Ssam 	if (size >= 0)
37611653Ssam 		sprintf (sizebuf, " (%ld bytes)", size);
37710275Ssam 	else
37810275Ssam 		(void) strcpy(sizebuf, "");
37910275Ssam 	if (data >= 0) {
38010275Ssam 		reply(125, "Using existing data connection for %s%s.",
38110275Ssam 		    name, sizebuf);
38210321Ssam 		usedefault = 1;
38310275Ssam 		return (fdopen(data, mode));
38410275Ssam 	}
38510566Ssam 	if (usedefault)
38610422Ssam 		data_dest = his_addr;
38710422Ssam 	usedefault = 1;
38810275Ssam 	file = getdatasock(mode);
38910275Ssam 	if (file == NULL) {
39010275Ssam 		reply(425, "Can't create data socket (%s,%d): %s.",
39113247Ssam 		    inet_ntoa(data_source.sin_addr),
39210275Ssam 		    ntohs(data_source.sin_port),
39310275Ssam 		    sys_errlist[errno]);
39410275Ssam 		return (NULL);
39510275Ssam 	}
39610602Ssam 	reply(150, "Opening data connection for %s (%s,%d)%s.",
39713247Ssam 	    name, inet_ntoa(data_dest.sin_addr.s_addr),
39810602Ssam 	    ntohs(data_dest.sin_port), sizebuf);
39910275Ssam 	data = fileno(file);
40011653Ssam 	while (connect(data, &data_dest, sizeof (data_dest), 0) < 0) {
40111653Ssam 		if (errno == EADDRINUSE && retry < swaitmax) {
40211653Ssam 			sleep(swaitint);
40311653Ssam 			retry += swaitint;
40411653Ssam 			continue;
40511653Ssam 		}
40610275Ssam 		reply(425, "Can't build data connection: %s.",
40710275Ssam 		    sys_errlist[errno]);
40810275Ssam 		(void) fclose(file);
40910275Ssam 		data = -1;
41010275Ssam 		return (NULL);
41110275Ssam 	}
41210275Ssam 	return (file);
41310275Ssam }
41410275Ssam 
41510275Ssam /*
41610275Ssam  * Tranfer the contents of "instr" to
41710275Ssam  * "outstr" peer using the appropriate
41810275Ssam  * encapulation of the date subject
41910275Ssam  * to Mode, Structure, and Type.
42010275Ssam  *
42110275Ssam  * NB: Form isn't handled.
42210275Ssam  */
42310275Ssam send_data(instr, outstr)
42410275Ssam 	FILE *instr, *outstr;
42510275Ssam {
42610275Ssam 	register int c;
42710275Ssam 	int netfd, filefd, cnt;
42810275Ssam 	char buf[BUFSIZ];
42910275Ssam 
43010275Ssam 	switch (type) {
43110275Ssam 
43210275Ssam 	case TYPE_A:
43310275Ssam 		while ((c = getc(instr)) != EOF) {
43411220Ssam 			if (c == '\n') {
43511220Ssam 				if (ferror (outstr))
43611220Ssam 					return (1);
43710275Ssam 				putc('\r', outstr);
43811220Ssam 			}
43911220Ssam 			putc(c, outstr);
44011220Ssam 			if (c == '\r')
44111220Ssam 				putc ('\0', outstr);
44210275Ssam 		}
44311220Ssam 		if (ferror (instr) || ferror (outstr))
44411220Ssam 			return (1);
44510275Ssam 		return (0);
44610275Ssam 
44710275Ssam 	case TYPE_I:
44810275Ssam 	case TYPE_L:
44910275Ssam 		netfd = fileno(outstr);
45010275Ssam 		filefd = fileno(instr);
45110275Ssam 
45210303Ssam 		while ((cnt = read(filefd, buf, sizeof (buf))) > 0)
45310275Ssam 			if (write(netfd, buf, cnt) < 0)
45410275Ssam 				return (1);
45510275Ssam 		return (cnt < 0);
45610275Ssam 	}
45710275Ssam 	reply(504,"Unimplemented TYPE %d in send_data", type);
45810275Ssam 	return (1);
45910275Ssam }
46010275Ssam 
46110275Ssam /*
46210275Ssam  * Transfer data from peer to
46310275Ssam  * "outstr" using the appropriate
46410275Ssam  * encapulation of the data subject
46510275Ssam  * to Mode, Structure, and Type.
46610275Ssam  *
46710275Ssam  * N.B.: Form isn't handled.
46810275Ssam  */
46910275Ssam receive_data(instr, outstr)
47010275Ssam 	FILE *instr, *outstr;
47110275Ssam {
47210275Ssam 	register int c;
47311220Ssam 	int cnt;
47410275Ssam 	char buf[BUFSIZ];
47510275Ssam 
47610275Ssam 
47710275Ssam 	switch (type) {
47810275Ssam 
47910275Ssam 	case TYPE_I:
48010275Ssam 	case TYPE_L:
48110616Ssam 		while ((cnt = read(fileno(instr), buf, sizeof buf)) > 0)
48210616Ssam 			if (write(fileno(outstr), buf, cnt) < 0)
48310275Ssam 				return (1);
48410275Ssam 		return (cnt < 0);
48510275Ssam 
48610275Ssam 	case TYPE_E:
48710275Ssam 		reply(504, "TYPE E not implemented.");
48810275Ssam 		return (1);
48910275Ssam 
49010275Ssam 	case TYPE_A:
49110275Ssam 		while ((c = getc(instr)) != EOF) {
49210275Ssam 			if (c == '\r') {
49311220Ssam 				if (ferror (outstr))
49411220Ssam 					return (1);
49511220Ssam 				if ((c = getc(instr)) != '\n')
49611220Ssam 					putc ('\r', outstr);
49711220Ssam 				if (c == '\0')
49811220Ssam 					continue;
49910275Ssam 			}
50011220Ssam 			putc (c, outstr);
50110275Ssam 		}
50211220Ssam 		if (ferror (instr) || ferror (outstr))
50311220Ssam 			return (1);
50410275Ssam 		return (0);
50510275Ssam 	}
50610275Ssam 	fatal("Unknown type in receive_data.");
50710275Ssam 	/*NOTREACHED*/
50810275Ssam }
50910275Ssam 
51010275Ssam fatal(s)
51110275Ssam 	char *s;
51210275Ssam {
51310275Ssam 	reply(451, "Error in server: %s\n", s);
51410275Ssam 	reply(221, "Closing connection due to server error.");
51513247Ssam 	dologout(0);
51610275Ssam }
51710275Ssam 
51810275Ssam reply(n, s, args)
51910275Ssam 	int n;
52010275Ssam 	char *s;
52110275Ssam {
52210275Ssam 
52310275Ssam 	printf("%d ", n);
52410275Ssam 	_doprnt(s, &args, stdout);
52510275Ssam 	printf("\r\n");
52610275Ssam 	fflush(stdout);
52710275Ssam 	if (debug) {
52810275Ssam 		fprintf(stderr, "<--- %d ", n);
52910275Ssam 		_doprnt(s, &args, stderr);
53010275Ssam 		fprintf(stderr, "\n");
53110275Ssam 		fflush(stderr);
53210275Ssam 	}
53310275Ssam }
53410275Ssam 
53510275Ssam lreply(n, s, args)
53610275Ssam 	int n;
53710275Ssam 	char *s;
53810275Ssam {
53910275Ssam 	printf("%d-", n);
54010275Ssam 	_doprnt(s, &args, stdout);
54110275Ssam 	printf("\r\n");
54210275Ssam 	fflush(stdout);
54310275Ssam 	if (debug) {
54410275Ssam 		fprintf(stderr, "<--- %d-", n);
54510275Ssam 		_doprnt(s, &args, stderr);
54610275Ssam 		fprintf(stderr, "\n");
54710275Ssam 	}
54810275Ssam }
54910275Ssam 
55010275Ssam replystr(s)
55110275Ssam 	char *s;
55210275Ssam {
55310275Ssam 	printf("%s\r\n", s);
55410275Ssam 	fflush(stdout);
55510275Ssam 	if (debug)
55610275Ssam 		fprintf(stderr, "<--- %s\n", s);
55710275Ssam }
55810275Ssam 
55910275Ssam ack(s)
56010275Ssam 	char *s;
56110275Ssam {
56210275Ssam 	reply(200, "%s command okay.", s);
56310275Ssam }
56410275Ssam 
56510275Ssam nack(s)
56610275Ssam 	char *s;
56710275Ssam {
56810275Ssam 	reply(502, "%s command not implemented.", s);
56910275Ssam }
57010275Ssam 
57110275Ssam yyerror()
57210275Ssam {
57310275Ssam 	reply(500, "Command not understood.");
57410275Ssam }
57510275Ssam 
57610275Ssam delete(name)
57710275Ssam 	char *name;
57810275Ssam {
57910275Ssam 	struct stat st;
58010275Ssam 
58110275Ssam 	if (stat(name, &st) < 0) {
58210275Ssam 		reply(550, "%s: %s.", name, sys_errlist[errno]);
58310275Ssam 		return;
58410275Ssam 	}
58510275Ssam 	if ((st.st_mode&S_IFMT) == S_IFDIR) {
58610275Ssam 		if (rmdir(name) < 0) {
58710275Ssam 			reply(550, "%s: %s.", name, sys_errlist[errno]);
58810275Ssam 			return;
58910275Ssam 		}
59010275Ssam 		goto done;
59110275Ssam 	}
59210275Ssam 	if (unlink(name) < 0) {
59310275Ssam 		reply(550, "%s: %s.", name, sys_errlist[errno]);
59410275Ssam 		return;
59510275Ssam 	}
59610275Ssam done:
59710275Ssam 	ack("DELE");
59810275Ssam }
59910275Ssam 
60010275Ssam cwd(path)
60110275Ssam 	char *path;
60210275Ssam {
60310275Ssam 
60410275Ssam 	if (chdir(path) < 0) {
60510275Ssam 		reply(550, "%s: %s.", path, sys_errlist[errno]);
60610275Ssam 		return;
60710275Ssam 	}
60810275Ssam 	ack("CWD");
60910275Ssam }
61010275Ssam 
61110303Ssam makedir(name)
61210275Ssam 	char *name;
61310275Ssam {
61410303Ssam 	struct stat st;
61510303Ssam 	int dochown = stat(name, &st) < 0;
61610275Ssam 
61710275Ssam 	if (mkdir(name, 0777) < 0) {
61810275Ssam 		reply(550, "%s: %s.", name, sys_errlist[errno]);
61910275Ssam 		return;
62010275Ssam 	}
62110303Ssam 	if (dochown)
62210303Ssam 		(void) chown(name, pw->pw_uid, -1);
62310275Ssam 	ack("MKDIR");
62410275Ssam }
62510275Ssam 
62610303Ssam removedir(name)
62710275Ssam 	char *name;
62810275Ssam {
62910275Ssam 
63010275Ssam 	if (rmdir(name) < 0) {
63110275Ssam 		reply(550, "%s: %s.", name, sys_errlist[errno]);
63210275Ssam 		return;
63310275Ssam 	}
63410275Ssam 	ack("RMDIR");
63510275Ssam }
63610275Ssam 
63710303Ssam pwd()
63810275Ssam {
63910303Ssam 	char path[MAXPATHLEN + 1];
64010275Ssam 
64110275Ssam 	if (getwd(path) == NULL) {
64210275Ssam 		reply(451, "%s.", path);
64310275Ssam 		return;
64410275Ssam 	}
64510275Ssam 	reply(251, "\"%s\" is current directory.", path);
64610275Ssam }
64710275Ssam 
64810275Ssam char *
64910275Ssam renamefrom(name)
65010275Ssam 	char *name;
65110275Ssam {
65210275Ssam 	struct stat st;
65310275Ssam 
65410275Ssam 	if (stat(name, &st) < 0) {
65510275Ssam 		reply(550, "%s: %s.", name, sys_errlist[errno]);
65610275Ssam 		return ((char *)0);
65710275Ssam 	}
65810303Ssam 	reply(350, "File exists, ready for destination name");
65910275Ssam 	return (name);
66010275Ssam }
66110275Ssam 
66210275Ssam renamecmd(from, to)
66310275Ssam 	char *from, *to;
66410275Ssam {
66510275Ssam 
66610275Ssam 	if (rename(from, to) < 0) {
66710275Ssam 		reply(550, "rename: %s.", sys_errlist[errno]);
66810275Ssam 		return;
66910275Ssam 	}
67010275Ssam 	ack("RNTO");
67110275Ssam }
67210275Ssam 
67310275Ssam dolog(sin)
67410275Ssam 	struct sockaddr_in *sin;
67510275Ssam {
67610275Ssam 	struct hostent *hp = gethostbyaddr(&sin->sin_addr,
67710275Ssam 		sizeof (struct in_addr), AF_INET);
67810275Ssam 	time_t t;
67910275Ssam 
68013247Ssam 	if (hp) {
68113247Ssam 		strncpy(remotehost, hp->h_name, sizeof (remotehost));
68213247Ssam 		endhostent();
68313247Ssam 	} else
68413247Ssam 		strncpy(remotehost, inet_ntoa(sin->sin_addr),
68513247Ssam 		    sizeof (remotehost));
68613247Ssam 	if (!logging)
68713247Ssam 		return;
68810275Ssam 	t = time(0);
68911757Ssam 	fprintf(stderr,"FTPD: connection from %s at %s", remotehost, ctime(&t));
69010275Ssam 	fflush(stderr);
69110275Ssam }
69210695Ssam 
69313247Ssam #include <utmp.h>
69413247Ssam 
69513247Ssam #define	SCPYN(a, b)	strncpy(a, b, sizeof (a))
69613247Ssam struct	utmp utmp;
69713247Ssam 
69810695Ssam /*
69913247Ssam  * Record login in wtmp file.
70013247Ssam  */
70113247Ssam dologin(pw)
70213247Ssam 	struct passwd *pw;
70313247Ssam {
70413247Ssam 	char line[32];
70513247Ssam 
70613247Ssam 	if (wtmp >= 0) {
70713247Ssam 		/* hack, but must be unique and no tty line */
70813247Ssam 		sprintf(line, "ftp%d", getpid());
70913247Ssam 		SCPYN(utmp.ut_line, line);
71013247Ssam 		SCPYN(utmp.ut_name, pw->pw_name);
71113247Ssam 		SCPYN(utmp.ut_host, remotehost);
71213247Ssam 		utmp.ut_time = time(0);
71313247Ssam 		(void) write(wtmp, (char *)&utmp, sizeof (utmp));
71416760Slepreau 		if (!guest) {		/* anon must hang on */
71516760Slepreau 			(void) close(wtmp);
71616760Slepreau 			wtmp = -1;
71716760Slepreau 		}
71813247Ssam 	}
71913247Ssam }
72013247Ssam 
72113247Ssam /*
72213247Ssam  * Record logout in wtmp file
72313247Ssam  * and exit with supplied status.
72413247Ssam  */
72513247Ssam dologout(status)
72613247Ssam 	int status;
72713247Ssam {
72816339Skarels 
72917580Ssam 	if (logged_in) {
73017580Ssam 		(void) seteuid(0);
73117580Ssam 		if (wtmp < 0)
73217580Ssam 			wtmp = open("/usr/adm/wtmp", O_WRONLY|O_APPEND);
73317580Ssam 		if (wtmp >= 0) {
73417580Ssam 			SCPYN(utmp.ut_name, "");
73517580Ssam 			SCPYN(utmp.ut_host, "");
73617580Ssam 			utmp.ut_time = time(0);
73717580Ssam 			(void) write(wtmp, (char *)&utmp, sizeof (utmp));
73817580Ssam 			(void) close(wtmp);
73917580Ssam 		}
74013247Ssam 	}
74114436Ssam 	/* beware of flushing buffers after a SIGPIPE */
74214436Ssam 	_exit(status);
74313247Ssam }
74413247Ssam 
74513247Ssam /*
74610695Ssam  * Special version of popen which avoids
74710695Ssam  * call to shell.  This insures noone may
74810695Ssam  * create a pipe to a hidden program as a side
74910695Ssam  * effect of a list or dir command.
75010695Ssam  */
75110695Ssam #define	tst(a,b)	(*mode == 'r'? (b) : (a))
75210695Ssam #define	RDR	0
75310695Ssam #define	WTR	1
75410695Ssam static	int popen_pid[5];
75510695Ssam 
75610695Ssam static char *
75710695Ssam nextarg(cpp)
75810695Ssam 	char *cpp;
75910695Ssam {
76010695Ssam 	register char *cp = cpp;
76110695Ssam 
76210695Ssam 	if (cp == 0)
76310695Ssam 		return (cp);
76410695Ssam 	while (*cp && *cp != ' ' && *cp != '\t')
76510695Ssam 		cp++;
76610695Ssam 	if (*cp == ' ' || *cp == '\t') {
76710695Ssam 		*cp++ = '\0';
76810695Ssam 		while (*cp == ' ' || *cp == '\t')
76910695Ssam 			cp++;
77010695Ssam 	}
77110695Ssam 	if (cp == cpp)
77210695Ssam 		return ((char *)0);
77310695Ssam 	return (cp);
77410695Ssam }
77510695Ssam 
77610695Ssam FILE *
77710695Ssam popen(cmd, mode)
77810695Ssam 	char *cmd, *mode;
77910695Ssam {
78013211Sroot 	int p[2], ac, gac;
78110695Ssam 	register myside, hisside, pid;
78213211Sroot 	char *av[20], *gav[512];
78310695Ssam 	register char *cp;
78410695Ssam 
78510695Ssam 	if (pipe(p) < 0)
78610695Ssam 		return (NULL);
78710695Ssam 	cp = cmd, ac = 0;
78813211Sroot 	/* break up string into pieces */
78910695Ssam 	do {
79010695Ssam 		av[ac++] = cp;
79110695Ssam 		cp = nextarg(cp);
79213211Sroot 	} while (cp && *cp && ac < 20);
79310695Ssam 	av[ac] = (char *)0;
79413211Sroot 	gav[0] = av[0];
79513211Sroot 	/* glob each piece */
79613211Sroot 	for (gac = ac = 1; av[ac] != NULL; ac++) {
79713211Sroot 		char **pop;
79822024Ssam 		extern char **glob(), **copyblk();
79913211Sroot 
80013211Sroot 		pop = glob(av[ac]);
80122024Ssam 		if (pop == (char **)NULL) {	/* globbing failed */
80222024Ssam 			char *vv[2];
80322024Ssam 
80422024Ssam 			vv[0] = av[ac];
80522024Ssam 			vv[1] = 0;
80622024Ssam 			pop = copyblk(vv);
80713211Sroot 		}
80822024Ssam 		av[ac] = (char *)pop;		/* save to free later */
80922024Ssam 		while (*pop && gac < 512)
81022024Ssam 			gav[gac++] = *pop++;
81111757Ssam 	}
81213211Sroot 	gav[gac] = (char *)0;
81310695Ssam 	myside = tst(p[WTR], p[RDR]);
81410695Ssam 	hisside = tst(p[RDR], p[WTR]);
81510695Ssam 	if ((pid = fork()) == 0) {
81610695Ssam 		/* myside and hisside reverse roles in child */
81710695Ssam 		close(myside);
81810695Ssam 		dup2(hisside, tst(0, 1));
81910695Ssam 		close(hisside);
82013211Sroot 		execv(gav[0], gav);
82110695Ssam 		_exit(1);
82210695Ssam 	}
82313211Sroot 	for (ac = 1; av[ac] != NULL; ac++)
82413211Sroot 		blkfree((char **)av[ac]);
82510695Ssam 	if (pid == -1)
82610695Ssam 		return (NULL);
82710695Ssam 	popen_pid[myside] = pid;
82810695Ssam 	close(hisside);
82910695Ssam 	return (fdopen(myside, mode));
83010695Ssam }
83110695Ssam 
83210695Ssam pclose(ptr)
83310695Ssam 	FILE *ptr;
83410695Ssam {
83510695Ssam 	register f, r, (*hstat)(), (*istat)(), (*qstat)();
83610695Ssam 	int status;
83710695Ssam 
83810695Ssam 	f = fileno(ptr);
83910695Ssam 	fclose(ptr);
84010695Ssam 	istat = signal(SIGINT, SIG_IGN);
84110695Ssam 	qstat = signal(SIGQUIT, SIG_IGN);
84210695Ssam 	hstat = signal(SIGHUP, SIG_IGN);
84310695Ssam 	while ((r = wait(&status)) != popen_pid[f] && r != -1)
84410695Ssam 		;
84510695Ssam 	if (r == -1)
84610695Ssam 		status = -1;
84710695Ssam 	signal(SIGINT, istat);
84810695Ssam 	signal(SIGQUIT, qstat);
84910695Ssam 	signal(SIGHUP, hstat);
85010695Ssam 	return (status);
85110695Ssam }
85210695Ssam 
85310695Ssam /*
85410695Ssam  * Check user requesting login priviledges.
85510695Ssam  * Disallow anyone mentioned in the file FTPUSERS
85610695Ssam  * to allow people such as uucp to be avoided.
85710695Ssam  */
85810695Ssam checkuser(name)
85910695Ssam 	register char *name;
86010695Ssam {
86110695Ssam 	char line[BUFSIZ], *index();
86210695Ssam 	FILE *fd;
86310695Ssam 	int found = 0;
86410695Ssam 
86510695Ssam 	fd = fopen(FTPUSERS, "r");
86610695Ssam 	if (fd == NULL)
86710695Ssam 		return (1);
86810695Ssam 	while (fgets(line, sizeof (line), fd) != NULL) {
86910695Ssam 		register char *cp = index(line, '\n');
87010695Ssam 
87110695Ssam 		if (cp)
87210695Ssam 			*cp = '\0';
87310695Ssam 		if (strcmp(line, name) == 0) {
87410695Ssam 			found++;
87510695Ssam 			break;
87610695Ssam 		}
87710695Ssam 	}
87810695Ssam 	fclose(fd);
87910695Ssam 	return (!found);
88010695Ssam }
881