xref: /csrg-svn/libexec/ftpd/ftpd.c (revision 11653)
110275Ssam #ifndef lint
2*11653Ssam static char sccsid[] = "@(#)ftpd.c	4.18 (Berkeley) 03/23/83";
310275Ssam #endif
410275Ssam 
510275Ssam /*
610275Ssam  * FTP server.
710275Ssam  */
810303Ssam #include <sys/param.h>
910275Ssam #include <sys/stat.h>
1010275Ssam #include <sys/ioctl.h>
1110275Ssam #include <sys/socket.h>
1210275Ssam 
1310275Ssam #include <netinet/in.h>
1410275Ssam 
1510275Ssam #include <stdio.h>
1610275Ssam #include <signal.h>
1710275Ssam #include <wait.h>
1810275Ssam #include <pwd.h>
1910275Ssam #include <setjmp.h>
2010275Ssam #include <netdb.h>
2110423Ssam #include <errno.h>
2210275Ssam 
2310275Ssam #include "ftp.h"
2410275Ssam 
2510695Ssam /*
2610695Ssam  * File containing login names
2710695Ssam  * NOT to be used on this machine.
2810695Ssam  * Commonly used to disallow uucp.
2910695Ssam  */
3010695Ssam #define	FTPUSERS	"/etc/ftpusers"
3110695Ssam 
3210275Ssam extern	int errno;
3310275Ssam extern	char *sys_errlist[];
3410275Ssam extern	char *crypt();
3510275Ssam extern	char version[];
3610275Ssam extern	char *home;		/* pointer to home directory for glob */
3710275Ssam extern	FILE *popen(), *fopen();
3810275Ssam extern	int pclose(), fclose();
3910275Ssam 
4010275Ssam struct	sockaddr_in ctrl_addr;
4110275Ssam struct	sockaddr_in data_source;
4210275Ssam struct	sockaddr_in data_dest;
4310275Ssam struct	sockaddr_in his_addr;
4410275Ssam 
4510275Ssam struct	hostent *hp;
4610275Ssam 
4710275Ssam int	data;
4810275Ssam jmp_buf	errcatch;
4910275Ssam int	logged_in;
5010275Ssam struct	passwd *pw;
5110275Ssam int	debug;
52*11653Ssam int	timeout;
5310275Ssam int	logging = 1;
5410275Ssam int	guest;
5510275Ssam int	type;
5610275Ssam int	form;
5710275Ssam int	stru;			/* avoid C keyword */
5810275Ssam int	mode;
5910321Ssam int	usedefault = 1;		/* for data transfers */
6010275Ssam char	hostname[32];
6110275Ssam char	*remotehost;
6210321Ssam struct	servent *sp;
6310275Ssam 
64*11653Ssam /*
65*11653Ssam  * Timeout intervals for retrying connections
66*11653Ssam  * to hosts that don't accept PORT cmds.  This
67*11653Ssam  * is a kludge, but given the problems with TCP...
68*11653Ssam  */
69*11653Ssam #define	SWAITMAX	90	/* wait at most 90 seconds */
70*11653Ssam #define	SWAITINT	5	/* interval between retries */
71*11653Ssam 
72*11653Ssam int	swaitmax = SWAITMAX;
73*11653Ssam int	swaitint = SWAITINT;
74*11653Ssam 
7510275Ssam int	lostconn();
7610419Ssam int	reapchild();
7710275Ssam FILE	*getdatasock(), *dataconn();
7810275Ssam char	*ntoa();
7910275Ssam 
8010275Ssam main(argc, argv)
8110275Ssam 	int argc;
8210275Ssam 	char *argv[];
8310275Ssam {
8410275Ssam 	int ctrl, s, options = 0;
8510275Ssam 	char *cp;
8610275Ssam 
8710275Ssam 	sp = getservbyname("ftp", "tcp");
8810275Ssam 	if (sp == 0) {
8911220Ssam 		fprintf(stderr, "ftpd: ftp/tcp: unknown service\n");
9010275Ssam 		exit(1);
9110275Ssam 	}
9210275Ssam 	ctrl_addr.sin_port = sp->s_port;
9310275Ssam 	data_source.sin_port = htons(ntohs(sp->s_port) - 1);
9410275Ssam 	signal(SIGPIPE, lostconn);
9510275Ssam 	debug = 0;
9610275Ssam 	argc--, argv++;
9710275Ssam 	while (argc > 0 && *argv[0] == '-') {
9810275Ssam 		for (cp = &argv[0][1]; *cp; cp++) switch (*cp) {
9910275Ssam 
100*11653Ssam 		case 'v':
101*11653Ssam 			debug = 1;
102*11653Ssam 			break;
103*11653Ssam 
10410275Ssam 		case 'd':
10510275Ssam 			debug = 1;
10610275Ssam 			options |= SO_DEBUG;
10710275Ssam 			break;
10810275Ssam 
109*11653Ssam 		case 't':
110*11653Ssam 			timeout = atoi(++cp);
111*11653Ssam 			goto nextopt;
112*11653Ssam 			break;
113*11653Ssam 
11410275Ssam 		default:
115*11653Ssam 			fprintf(stderr, "Unknown flag -%c ignored.\n", *cp);
11610275Ssam 			break;
11710275Ssam 		}
118*11653Ssam nextopt:
11910275Ssam 		argc--, argv++;
12010275Ssam 	}
12110275Ssam #ifndef DEBUG
12210275Ssam 	if (fork())
12310275Ssam 		exit(0);
12410275Ssam 	for (s = 0; s < 10; s++)
125*11653Ssam 		if (!logging || (s != 2))
126*11653Ssam 			(void) close(s);
12710317Ssam 		(void) close(s);
12811220Ssam 	(void) open("/", 0);
12910275Ssam 	(void) dup2(0, 1);
130*11653Ssam 	if (!logging)
131*11653Ssam 		(void) dup2(0, 2);
13210275Ssam 	{ int tt = open("/dev/tty", 2);
13310275Ssam 	  if (tt > 0) {
13410275Ssam 		ioctl(tt, TIOCNOTTY, 0);
13510275Ssam 		close(tt);
13610275Ssam 	  }
13710275Ssam 	}
13810275Ssam #endif
13910303Ssam 	while ((s = socket(AF_INET, SOCK_STREAM, 0, 0)) < 0) {
14010275Ssam 		perror("ftpd: socket");
14110275Ssam 		sleep(5);
14210275Ssam 	}
14310419Ssam 	if (options & SO_DEBUG)
14410419Ssam 		if (setsockopt(s, SOL_SOCKET, SO_DEBUG, 0, 0) < 0)
14510419Ssam 			perror("ftpd: setsockopt (SO_DEBUG)");
14610419Ssam #ifdef notdef
14710419Ssam 	if (setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, 0, 0) < 0)
14810419Ssam 		perror("ftpd: setsockopt (SO_KEEPALIVE)");
14910419Ssam #endif
15010275Ssam 	while (bind(s, &ctrl_addr, sizeof (ctrl_addr), 0) < 0) {
15110275Ssam 		perror("ftpd: bind");
15210275Ssam 		sleep(5);
15310275Ssam 	}
15410588Ssam 	sigset(SIGCHLD, reapchild);
15510303Ssam 	listen(s, 10);
15610275Ssam 	for (;;) {
15710275Ssam 		int hisaddrlen = sizeof (his_addr);
15810275Ssam 
15910275Ssam 		ctrl = accept(s, &his_addr, &hisaddrlen, 0);
16010275Ssam 		if (ctrl < 0) {
16110419Ssam 			if (errno == EINTR)
16210419Ssam 				continue;
16310275Ssam 			perror("ftpd: accept");
16410275Ssam 			continue;
16510275Ssam 		}
16610275Ssam 		if (fork() == 0) {
16711220Ssam 			signal (SIGCHLD, SIG_IGN);
16810275Ssam 			if (logging)
16910275Ssam 				dolog(&his_addr);
17010275Ssam 			close(s);
17110275Ssam 			dup2(ctrl, 0), close(ctrl), dup2(0, 1);
17210275Ssam 			/* do telnet option negotiation here */
17310303Ssam 			/*
17410303Ssam 			 * Set up default state
17510303Ssam 			 */
17610275Ssam 			logged_in = 0;
17710275Ssam 			data = -1;
17810303Ssam 			type = TYPE_A;
17910303Ssam 			form = FORM_N;
18010303Ssam 			stru = STRU_F;
18110303Ssam 			mode = MODE_S;
18210275Ssam 			gethostname(hostname, sizeof (hostname));
18310275Ssam 			reply(220, "%s FTP server (%s) ready.",
18410275Ssam 				hostname, version);
18510275Ssam 			for (;;) {
18610275Ssam 				setjmp(errcatch);
18710275Ssam 				yyparse();
18810275Ssam 			}
18910275Ssam 		}
19010275Ssam 		close(ctrl);
19110275Ssam 	}
19210275Ssam }
19310275Ssam 
19410419Ssam reapchild()
19510419Ssam {
19610419Ssam 	union wait status;
19710419Ssam 
19810419Ssam 	while (wait3(&status, WNOHANG, 0) > 0)
19910419Ssam 		;
20010419Ssam }
20110419Ssam 
20210275Ssam lostconn()
20310275Ssam {
20410275Ssam 
20510275Ssam 	fatal("Connection closed.");
20610275Ssam }
20710275Ssam 
20810275Ssam pass(passwd)
20910275Ssam 	char *passwd;
21010275Ssam {
21110303Ssam 	char *xpasswd, *savestr();
21210303Ssam 	static struct passwd save;
21310275Ssam 
21410275Ssam 	if (logged_in || pw == NULL) {
21510275Ssam 		reply(503, "Login with USER first.");
21610275Ssam 		return;
21710275Ssam 	}
21810275Ssam 	if (!guest) {		/* "ftp" is only account allowed no password */
21910275Ssam 		xpasswd = crypt(passwd, pw->pw_passwd);
22010275Ssam 		if (strcmp(xpasswd, pw->pw_passwd) != 0) {
22110275Ssam 			reply(530, "Login incorrect.");
22210275Ssam 			pw = NULL;
22310275Ssam 			return;
22410275Ssam 		}
22510275Ssam 	}
22610303Ssam 	setegid(pw->pw_gid);
22710275Ssam 	initgroups(pw->pw_name, pw->pw_gid);
22810275Ssam 	if (chdir(pw->pw_dir)) {
22910275Ssam 		reply(550, "User %s: can't change directory to $s.",
23010275Ssam 			pw->pw_name, pw->pw_dir);
23110303Ssam 		goto bad;
23210275Ssam 	}
23310303Ssam 	if (guest && chroot(pw->pw_dir) < 0) {
23410275Ssam 		reply(550, "Can't set guest privileges.");
23510303Ssam 		goto bad;
23610275Ssam 	}
23710275Ssam 	if (!guest)
23810275Ssam 		reply(230, "User %s logged in.", pw->pw_name);
23910275Ssam 	else
24010275Ssam 		reply(230, "Guest login ok, access restrictions apply.");
24110275Ssam 	logged_in = 1;
24210303Ssam 	seteuid(pw->pw_uid);
24310303Ssam 	/*
24410303Ssam 	 * Save everything so globbing doesn't
24510303Ssam 	 * clobber the fields.
24610303Ssam 	 */
24710303Ssam 	save = *pw;
24810303Ssam 	save.pw_name = savestr(pw->pw_name);
24910303Ssam 	save.pw_passwd = savestr(pw->pw_passwd);
25010303Ssam 	save.pw_comment = savestr(pw->pw_comment);
25110303Ssam 	save.pw_gecos = savestr(pw->pw_gecos, &save.pw_gecos);
25210303Ssam 	save.pw_dir = savestr(pw->pw_dir);
25310303Ssam 	save.pw_shell = savestr(pw->pw_shell);
25410303Ssam 	pw = &save;
25510303Ssam 	home = pw->pw_dir;		/* home dir for globbing */
25610303Ssam 	return;
25710303Ssam bad:
25810303Ssam 	seteuid(0);
25910303Ssam 	pw = NULL;
26010275Ssam }
26110275Ssam 
26210303Ssam char *
26310303Ssam savestr(s)
26410303Ssam 	char *s;
26510303Ssam {
26610303Ssam 	char *malloc();
26710303Ssam 	char *new = malloc(strlen(s) + 1);
26810303Ssam 
26910303Ssam 	if (new != NULL)
27010303Ssam 		strcpy(new, s);
27111347Ssam 	return (new);
27210303Ssam }
27310303Ssam 
27410275Ssam retrieve(cmd, name)
27510275Ssam 	char *cmd, *name;
27610275Ssam {
27710275Ssam 	FILE *fin, *dout;
27810275Ssam 	struct stat st;
27910275Ssam 	int (*closefunc)();
28010275Ssam 
28110275Ssam 	if (cmd == 0) {
28210317Ssam #ifdef notdef
28310317Ssam 		/* no remote command execution -- it's a security hole */
284*11653Ssam 		if (*name == '|')
28510275Ssam 			fin = popen(name + 1, "r"), closefunc = pclose;
28610275Ssam 		else
28710317Ssam #endif
28810275Ssam 			fin = fopen(name, "r"), closefunc = fclose;
28910275Ssam 	} else {
29010275Ssam 		char line[BUFSIZ];
29110275Ssam 
29210422Ssam 		sprintf(line, cmd, name), name = line;
29310275Ssam 		fin = popen(line, "r"), closefunc = pclose;
29410275Ssam 	}
29510275Ssam 	if (fin == NULL) {
29610275Ssam 		reply(550, "%s: %s.", name, sys_errlist[errno]);
29710275Ssam 		return;
29810275Ssam 	}
29910275Ssam 	st.st_size = 0;
30010275Ssam 	if (cmd == 0 &&
30110275Ssam 	    (stat(name, &st) < 0 || (st.st_mode&S_IFMT) != S_IFREG)) {
30210275Ssam 		reply(550, "%s: not a plain file.", name);
30310275Ssam 		goto done;
30410275Ssam 	}
30510275Ssam 	dout = dataconn(name, st.st_size, "w");
30610275Ssam 	if (dout == NULL)
30710275Ssam 		goto done;
30810303Ssam 	if (send_data(fin, dout) || ferror(dout))
30910275Ssam 		reply(550, "%s: %s.", name, sys_errlist[errno]);
31010275Ssam 	else
31110275Ssam 		reply(226, "Transfer complete.");
31210303Ssam 	fclose(dout), data = -1;
31310275Ssam done:
31410275Ssam 	(*closefunc)(fin);
31510275Ssam }
31610275Ssam 
31710275Ssam store(name, mode)
31810275Ssam 	char *name, *mode;
31910275Ssam {
32010275Ssam 	FILE *fout, *din;
32110303Ssam 	int (*closefunc)(), dochown = 0;
32210275Ssam 
32310317Ssam #ifdef notdef
32410317Ssam 	/* no remote command execution -- it's a security hole */
325*11653Ssam 	if (name[0] == '|')
32610275Ssam 		fout = popen(&name[1], "w"), closefunc = pclose;
32710317Ssam 	else
32810317Ssam #endif
32910317Ssam 	{
33010303Ssam 		struct stat st;
33110303Ssam 
33210303Ssam 		if (stat(name, &st) < 0)
33310303Ssam 			dochown++;
33410275Ssam 		fout = fopen(name, mode), closefunc = fclose;
33510303Ssam 	}
33610275Ssam 	if (fout == NULL) {
33710275Ssam 		reply(550, "%s: %s.", name, sys_errlist[errno]);
33810275Ssam 		return;
33910275Ssam 	}
340*11653Ssam 	din = dataconn(name, (off_t)-1, "r");
34110275Ssam 	if (din == NULL)
34210275Ssam 		goto done;
34310303Ssam 	if (receive_data(din, fout) || ferror(fout))
34410275Ssam 		reply(550, "%s: %s.", name, sys_errlist[errno]);
34510275Ssam 	else
34610275Ssam 		reply(226, "Transfer complete.");
34710275Ssam 	fclose(din), data = -1;
34810275Ssam done:
34910303Ssam 	if (dochown)
35010303Ssam 		(void) chown(name, pw->pw_uid, -1);
35110275Ssam 	(*closefunc)(fout);
35210275Ssam }
35310275Ssam 
35410275Ssam FILE *
35510275Ssam getdatasock(mode)
35610275Ssam 	char *mode;
35710275Ssam {
35810602Ssam 	int s;
35910275Ssam 
36010275Ssam 	if (data >= 0)
36110275Ssam 		return (fdopen(data, mode));
36210602Ssam 	s = socket(AF_INET, SOCK_STREAM, 0, 0);
36310602Ssam 	if (s < 0)
36410275Ssam 		return (NULL);
36510275Ssam 	seteuid(0);
36610602Ssam 	if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, 0, 0) < 0)
36710602Ssam 		goto bad;
36810602Ssam 	if (bind(s, &data_source, sizeof (data_source), 0) < 0)
36910602Ssam 		goto bad;
37010311Ssam 	seteuid(pw->pw_uid);
37110275Ssam 	return (fdopen(s, mode));
37210602Ssam bad:
37310602Ssam 	seteuid(pw->pw_uid);
37410602Ssam 	close(s);
37510602Ssam 	return (NULL);
37610275Ssam }
37710275Ssam 
37810275Ssam FILE *
37910275Ssam dataconn(name, size, mode)
38010275Ssam 	char *name;
381*11653Ssam 	off_t size;
38210275Ssam 	char *mode;
38310275Ssam {
38410275Ssam 	char sizebuf[32];
38510275Ssam 	FILE *file;
386*11653Ssam 	int retry = 0;
38710275Ssam 
38810275Ssam 	if (size >= 0)
389*11653Ssam 		sprintf (sizebuf, " (%ld bytes)", size);
39010275Ssam 	else
39110275Ssam 		(void) strcpy(sizebuf, "");
39210275Ssam 	if (data >= 0) {
39310275Ssam 		reply(125, "Using existing data connection for %s%s.",
39410275Ssam 		    name, sizebuf);
39510321Ssam 		usedefault = 1;
39610275Ssam 		return (fdopen(data, mode));
39710275Ssam 	}
39810566Ssam 	if (usedefault)
39910422Ssam 		data_dest = his_addr;
40010422Ssam 	usedefault = 1;
40110275Ssam 	file = getdatasock(mode);
40210275Ssam 	if (file == NULL) {
40310275Ssam 		reply(425, "Can't create data socket (%s,%d): %s.",
40410275Ssam 		    ntoa(data_source.sin_addr),
40510275Ssam 		    ntohs(data_source.sin_port),
40610275Ssam 		    sys_errlist[errno]);
40710275Ssam 		return (NULL);
40810275Ssam 	}
40910602Ssam 	reply(150, "Opening data connection for %s (%s,%d)%s.",
41010602Ssam 	    name, ntoa(data_dest.sin_addr.s_addr),
41110602Ssam 	    ntohs(data_dest.sin_port), sizebuf);
41210275Ssam 	data = fileno(file);
413*11653Ssam 	while (connect(data, &data_dest, sizeof (data_dest), 0) < 0) {
414*11653Ssam 		if (errno == EADDRINUSE && retry < swaitmax) {
415*11653Ssam 			sleep(swaitint);
416*11653Ssam 			retry += swaitint;
417*11653Ssam 			continue;
418*11653Ssam 		}
41910275Ssam 		reply(425, "Can't build data connection: %s.",
42010275Ssam 		    sys_errlist[errno]);
42110275Ssam 		(void) fclose(file);
42210275Ssam 		data = -1;
42310275Ssam 		return (NULL);
42410275Ssam 	}
42510275Ssam 	return (file);
42610275Ssam }
42710275Ssam 
42810275Ssam /*
42910275Ssam  * Tranfer the contents of "instr" to
43010275Ssam  * "outstr" peer using the appropriate
43110275Ssam  * encapulation of the date subject
43210275Ssam  * to Mode, Structure, and Type.
43310275Ssam  *
43410275Ssam  * NB: Form isn't handled.
43510275Ssam  */
43610275Ssam send_data(instr, outstr)
43710275Ssam 	FILE *instr, *outstr;
43810275Ssam {
43910275Ssam 	register int c;
44010275Ssam 	int netfd, filefd, cnt;
44110275Ssam 	char buf[BUFSIZ];
44210275Ssam 
44310275Ssam 	switch (type) {
44410275Ssam 
44510275Ssam 	case TYPE_A:
44610275Ssam 		while ((c = getc(instr)) != EOF) {
44711220Ssam 			if (c == '\n') {
44811220Ssam 				if (ferror (outstr))
44911220Ssam 					return (1);
45010275Ssam 				putc('\r', outstr);
45111220Ssam 			}
45211220Ssam 			putc(c, outstr);
45311220Ssam 			if (c == '\r')
45411220Ssam 				putc ('\0', outstr);
45510275Ssam 		}
45611220Ssam 		if (ferror (instr) || ferror (outstr))
45711220Ssam 			return (1);
45810275Ssam 		return (0);
45910275Ssam 
46010275Ssam 	case TYPE_I:
46110275Ssam 	case TYPE_L:
46210275Ssam 		netfd = fileno(outstr);
46310275Ssam 		filefd = fileno(instr);
46410275Ssam 
46510303Ssam 		while ((cnt = read(filefd, buf, sizeof (buf))) > 0)
46610275Ssam 			if (write(netfd, buf, cnt) < 0)
46710275Ssam 				return (1);
46810275Ssam 		return (cnt < 0);
46910275Ssam 	}
47010275Ssam 	reply(504,"Unimplemented TYPE %d in send_data", type);
47110275Ssam 	return (1);
47210275Ssam }
47310275Ssam 
47410275Ssam /*
47510275Ssam  * Transfer data from peer to
47610275Ssam  * "outstr" using the appropriate
47710275Ssam  * encapulation of the data subject
47810275Ssam  * to Mode, Structure, and Type.
47910275Ssam  *
48010275Ssam  * N.B.: Form isn't handled.
48110275Ssam  */
48210275Ssam receive_data(instr, outstr)
48310275Ssam 	FILE *instr, *outstr;
48410275Ssam {
48510275Ssam 	register int c;
48611220Ssam 	int cnt;
48710275Ssam 	char buf[BUFSIZ];
48810275Ssam 
48910275Ssam 
49010275Ssam 	switch (type) {
49110275Ssam 
49210275Ssam 	case TYPE_I:
49310275Ssam 	case TYPE_L:
49410616Ssam 		while ((cnt = read(fileno(instr), buf, sizeof buf)) > 0)
49510616Ssam 			if (write(fileno(outstr), buf, cnt) < 0)
49610275Ssam 				return (1);
49710275Ssam 		return (cnt < 0);
49810275Ssam 
49910275Ssam 	case TYPE_E:
50010275Ssam 		reply(504, "TYPE E not implemented.");
50110275Ssam 		return (1);
50210275Ssam 
50310275Ssam 	case TYPE_A:
50410275Ssam 		while ((c = getc(instr)) != EOF) {
50510275Ssam 			if (c == '\r') {
50611220Ssam 				if (ferror (outstr))
50711220Ssam 					return (1);
50811220Ssam 				if ((c = getc(instr)) != '\n')
50911220Ssam 					putc ('\r', outstr);
51011220Ssam 				if (c == '\0')
51111220Ssam 					continue;
51210275Ssam 			}
51311220Ssam 			putc (c, outstr);
51410275Ssam 		}
51511220Ssam 		if (ferror (instr) || ferror (outstr))
51611220Ssam 			return (1);
51710275Ssam 		return (0);
51810275Ssam 	}
51910275Ssam 	fatal("Unknown type in receive_data.");
52010275Ssam 	/*NOTREACHED*/
52110275Ssam }
52210275Ssam 
52310275Ssam fatal(s)
52410275Ssam 	char *s;
52510275Ssam {
52610275Ssam 	reply(451, "Error in server: %s\n", s);
52710275Ssam 	reply(221, "Closing connection due to server error.");
52810275Ssam 	exit(0);
52910275Ssam }
53010275Ssam 
53110275Ssam reply(n, s, args)
53210275Ssam 	int n;
53310275Ssam 	char *s;
53410275Ssam {
53510275Ssam 
53610275Ssam 	printf("%d ", n);
53710275Ssam 	_doprnt(s, &args, stdout);
53810275Ssam 	printf("\r\n");
53910275Ssam 	fflush(stdout);
54010275Ssam 	if (debug) {
54110275Ssam 		fprintf(stderr, "<--- %d ", n);
54210275Ssam 		_doprnt(s, &args, stderr);
54310275Ssam 		fprintf(stderr, "\n");
54410275Ssam 		fflush(stderr);
54510275Ssam 	}
54610275Ssam }
54710275Ssam 
54810275Ssam lreply(n, s, args)
54910275Ssam 	int n;
55010275Ssam 	char *s;
55110275Ssam {
55210275Ssam 	printf("%d-", n);
55310275Ssam 	_doprnt(s, &args, stdout);
55410275Ssam 	printf("\r\n");
55510275Ssam 	fflush(stdout);
55610275Ssam 	if (debug) {
55710275Ssam 		fprintf(stderr, "<--- %d-", n);
55810275Ssam 		_doprnt(s, &args, stderr);
55910275Ssam 		fprintf(stderr, "\n");
56010275Ssam 	}
56110275Ssam }
56210275Ssam 
56310275Ssam replystr(s)
56410275Ssam 	char *s;
56510275Ssam {
56610275Ssam 	printf("%s\r\n", s);
56710275Ssam 	fflush(stdout);
56810275Ssam 	if (debug)
56910275Ssam 		fprintf(stderr, "<--- %s\n", s);
57010275Ssam }
57110275Ssam 
57210275Ssam ack(s)
57310275Ssam 	char *s;
57410275Ssam {
57510275Ssam 	reply(200, "%s command okay.", s);
57610275Ssam }
57710275Ssam 
57810275Ssam nack(s)
57910275Ssam 	char *s;
58010275Ssam {
58110275Ssam 	reply(502, "%s command not implemented.", s);
58210275Ssam }
58310275Ssam 
58410275Ssam yyerror()
58510275Ssam {
58610275Ssam 	reply(500, "Command not understood.");
58710275Ssam }
58810275Ssam 
58910275Ssam delete(name)
59010275Ssam 	char *name;
59110275Ssam {
59210275Ssam 	struct stat st;
59310275Ssam 
59410275Ssam 	if (stat(name, &st) < 0) {
59510275Ssam 		reply(550, "%s: %s.", name, sys_errlist[errno]);
59610275Ssam 		return;
59710275Ssam 	}
59810275Ssam 	if ((st.st_mode&S_IFMT) == S_IFDIR) {
59910275Ssam 		if (rmdir(name) < 0) {
60010275Ssam 			reply(550, "%s: %s.", name, sys_errlist[errno]);
60110275Ssam 			return;
60210275Ssam 		}
60310275Ssam 		goto done;
60410275Ssam 	}
60510275Ssam 	if (unlink(name) < 0) {
60610275Ssam 		reply(550, "%s: %s.", name, sys_errlist[errno]);
60710275Ssam 		return;
60810275Ssam 	}
60910275Ssam done:
61010275Ssam 	ack("DELE");
61110275Ssam }
61210275Ssam 
61310275Ssam cwd(path)
61410275Ssam 	char *path;
61510275Ssam {
61610275Ssam 
61710275Ssam 	if (chdir(path) < 0) {
61810275Ssam 		reply(550, "%s: %s.", path, sys_errlist[errno]);
61910275Ssam 		return;
62010275Ssam 	}
62110275Ssam 	ack("CWD");
62210275Ssam }
62310275Ssam 
62410303Ssam makedir(name)
62510275Ssam 	char *name;
62610275Ssam {
62710303Ssam 	struct stat st;
62810303Ssam 	int dochown = stat(name, &st) < 0;
62910275Ssam 
63010275Ssam 	if (mkdir(name, 0777) < 0) {
63110275Ssam 		reply(550, "%s: %s.", name, sys_errlist[errno]);
63210275Ssam 		return;
63310275Ssam 	}
63410303Ssam 	if (dochown)
63510303Ssam 		(void) chown(name, pw->pw_uid, -1);
63610275Ssam 	ack("MKDIR");
63710275Ssam }
63810275Ssam 
63910303Ssam removedir(name)
64010275Ssam 	char *name;
64110275Ssam {
64210275Ssam 
64310275Ssam 	if (rmdir(name) < 0) {
64410275Ssam 		reply(550, "%s: %s.", name, sys_errlist[errno]);
64510275Ssam 		return;
64610275Ssam 	}
64710275Ssam 	ack("RMDIR");
64810275Ssam }
64910275Ssam 
65010303Ssam pwd()
65110275Ssam {
65210303Ssam 	char path[MAXPATHLEN + 1];
65310275Ssam 
65410275Ssam 	if (getwd(path) == NULL) {
65510275Ssam 		reply(451, "%s.", path);
65610275Ssam 		return;
65710275Ssam 	}
65810275Ssam 	reply(251, "\"%s\" is current directory.", path);
65910275Ssam }
66010275Ssam 
66110275Ssam char *
66210275Ssam renamefrom(name)
66310275Ssam 	char *name;
66410275Ssam {
66510275Ssam 	struct stat st;
66610275Ssam 
66710275Ssam 	if (stat(name, &st) < 0) {
66810275Ssam 		reply(550, "%s: %s.", name, sys_errlist[errno]);
66910275Ssam 		return ((char *)0);
67010275Ssam 	}
67110303Ssam 	reply(350, "File exists, ready for destination name");
67210275Ssam 	return (name);
67310275Ssam }
67410275Ssam 
67510275Ssam renamecmd(from, to)
67610275Ssam 	char *from, *to;
67710275Ssam {
67810275Ssam 
67910275Ssam 	if (rename(from, to) < 0) {
68010275Ssam 		reply(550, "rename: %s.", sys_errlist[errno]);
68110275Ssam 		return;
68210275Ssam 	}
68310275Ssam 	ack("RNTO");
68410275Ssam }
68510275Ssam 
68610275Ssam /*
68710275Ssam  * Convert network-format internet address
68810275Ssam  * to base 256 d.d.d.d representation.
68910275Ssam  */
69010275Ssam char *
69110275Ssam ntoa(in)
69210275Ssam 	struct in_addr in;
69310275Ssam {
69410275Ssam 	static char b[18];
69510275Ssam 	register char *p;
69610275Ssam 
69710275Ssam 	p = (char *)&in;
69810275Ssam #define	UC(b)	(((int)b)&0xff)
69910275Ssam 	sprintf(b, "%d.%d.%d.%d", UC(p[0]), UC(p[1]), UC(p[2]), UC(p[3]));
70010275Ssam 	return (b);
70110275Ssam }
70210275Ssam 
70310275Ssam dolog(sin)
70410275Ssam 	struct sockaddr_in *sin;
70510275Ssam {
70610275Ssam 	struct hostent *hp = gethostbyaddr(&sin->sin_addr,
70710275Ssam 		sizeof (struct in_addr), AF_INET);
70810275Ssam 	char *remotehost;
70910275Ssam 	time_t t;
71010275Ssam 
71110275Ssam 	if (hp)
71210275Ssam 		remotehost = hp->h_name;
71310275Ssam 	else
71410275Ssam 		remotehost = "UNKNOWNHOST";
71510275Ssam 	t = time(0);
71610303Ssam 	fprintf(stderr,"FTP: connection from %s at %s", remotehost, ctime(&t));
71710275Ssam 	fflush(stderr);
71810275Ssam }
71910695Ssam 
72010695Ssam /*
72110695Ssam  * Special version of popen which avoids
72210695Ssam  * call to shell.  This insures noone may
72310695Ssam  * create a pipe to a hidden program as a side
72410695Ssam  * effect of a list or dir command.
72510695Ssam  */
72610695Ssam #define	tst(a,b)	(*mode == 'r'? (b) : (a))
72710695Ssam #define	RDR	0
72810695Ssam #define	WTR	1
72910695Ssam static	int popen_pid[5];
73010695Ssam 
73110695Ssam static char *
73210695Ssam nextarg(cpp)
73310695Ssam 	char *cpp;
73410695Ssam {
73510695Ssam 	register char *cp = cpp;
73610695Ssam 
73710695Ssam 	if (cp == 0)
73810695Ssam 		return (cp);
73910695Ssam 	while (*cp && *cp != ' ' && *cp != '\t')
74010695Ssam 		cp++;
74110695Ssam 	if (*cp == ' ' || *cp == '\t') {
74210695Ssam 		*cp++ = '\0';
74310695Ssam 		while (*cp == ' ' || *cp == '\t')
74410695Ssam 			cp++;
74510695Ssam 	}
74610695Ssam 	if (cp == cpp)
74710695Ssam 		return ((char *)0);
74810695Ssam 	return (cp);
74910695Ssam }
75010695Ssam 
75110695Ssam FILE *
75210695Ssam popen(cmd, mode)
75310695Ssam 	char *cmd, *mode;
75410695Ssam {
75510695Ssam 	int p[2], ac;
75610695Ssam 	register myside, hisside, pid;
75710695Ssam 	char *av[10];
75810695Ssam 	register char *cp;
75910695Ssam 
76010695Ssam 	if (pipe(p) < 0)
76110695Ssam 		return (NULL);
76210695Ssam 	cp = cmd, ac = 0;
76310695Ssam 	do {
76410695Ssam 		av[ac++] = cp;
76510695Ssam 		cp = nextarg(cp);
76610695Ssam 	} while (cp && *cp);
76710695Ssam 	av[ac] = (char *)0;
76810695Ssam 	myside = tst(p[WTR], p[RDR]);
76910695Ssam 	hisside = tst(p[RDR], p[WTR]);
77010695Ssam 	if ((pid = fork()) == 0) {
77110695Ssam 		/* myside and hisside reverse roles in child */
77210695Ssam 		close(myside);
77310695Ssam 		dup2(hisside, tst(0, 1));
77410695Ssam 		close(hisside);
77510695Ssam 		execv(av[0], av);
77610695Ssam 		_exit(1);
77710695Ssam 	}
77810695Ssam 	if (pid == -1)
77910695Ssam 		return (NULL);
78010695Ssam 	popen_pid[myside] = pid;
78110695Ssam 	close(hisside);
78210695Ssam 	return (fdopen(myside, mode));
78310695Ssam }
78410695Ssam 
78510695Ssam pclose(ptr)
78610695Ssam 	FILE *ptr;
78710695Ssam {
78810695Ssam 	register f, r, (*hstat)(), (*istat)(), (*qstat)();
78910695Ssam 	int status;
79010695Ssam 
79110695Ssam 	f = fileno(ptr);
79210695Ssam 	fclose(ptr);
79310695Ssam 	istat = signal(SIGINT, SIG_IGN);
79410695Ssam 	qstat = signal(SIGQUIT, SIG_IGN);
79510695Ssam 	hstat = signal(SIGHUP, SIG_IGN);
79610695Ssam 	while ((r = wait(&status)) != popen_pid[f] && r != -1)
79710695Ssam 		;
79810695Ssam 	if (r == -1)
79910695Ssam 		status = -1;
80010695Ssam 	signal(SIGINT, istat);
80110695Ssam 	signal(SIGQUIT, qstat);
80210695Ssam 	signal(SIGHUP, hstat);
80310695Ssam 	return (status);
80410695Ssam }
80510695Ssam 
80610695Ssam /*
80710695Ssam  * Check user requesting login priviledges.
80810695Ssam  * Disallow anyone mentioned in the file FTPUSERS
80910695Ssam  * to allow people such as uucp to be avoided.
81010695Ssam  */
81110695Ssam checkuser(name)
81210695Ssam 	register char *name;
81310695Ssam {
81410695Ssam 	char line[BUFSIZ], *index();
81510695Ssam 	FILE *fd;
81610695Ssam 	int found = 0;
81710695Ssam 
81810695Ssam 	fd = fopen(FTPUSERS, "r");
81910695Ssam 	if (fd == NULL)
82010695Ssam 		return (1);
82110695Ssam 	while (fgets(line, sizeof (line), fd) != NULL) {
82210695Ssam 		register char *cp = index(line, '\n');
82310695Ssam 
82410695Ssam 		if (cp)
82510695Ssam 			*cp = '\0';
82610695Ssam 		if (strcmp(line, name) == 0) {
82710695Ssam 			found++;
82810695Ssam 			break;
82910695Ssam 		}
83010695Ssam 	}
83110695Ssam 	fclose(fd);
83210695Ssam 	return (!found);
83310695Ssam }
834