xref: /csrg-svn/libexec/ftpd/ftpd.c (revision 16033)
110275Ssam #ifndef lint
2*16033Sralph static char sccsid[] = "@(#)ftpd.c	4.29 (Berkeley) 02/10/84";
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>
1213247Ssam #include <sys/file.h>
1313595Ssam #include <sys/wait.h>
1410275Ssam 
1510275Ssam #include <netinet/in.h>
1610275Ssam 
1713034Ssam #include <arpa/ftp.h>
1813211Sroot #include <arpa/inet.h>
1913034Ssam 
2010275Ssam #include <stdio.h>
2110275Ssam #include <signal.h>
2210275Ssam #include <pwd.h>
2310275Ssam #include <setjmp.h>
2410275Ssam #include <netdb.h>
2510423Ssam #include <errno.h>
2610275Ssam 
2710695Ssam /*
2810695Ssam  * File containing login names
2910695Ssam  * NOT to be used on this machine.
3010695Ssam  * Commonly used to disallow uucp.
3110695Ssam  */
3210695Ssam #define	FTPUSERS	"/etc/ftpusers"
3310695Ssam 
3410275Ssam extern	int errno;
3510275Ssam extern	char *sys_errlist[];
3610275Ssam extern	char *crypt();
3710275Ssam extern	char version[];
3810275Ssam extern	char *home;		/* pointer to home directory for glob */
3910275Ssam extern	FILE *popen(), *fopen();
4010275Ssam extern	int pclose(), fclose();
4110275Ssam 
4210275Ssam struct	sockaddr_in ctrl_addr;
4310275Ssam struct	sockaddr_in data_source;
4410275Ssam struct	sockaddr_in data_dest;
4510275Ssam struct	sockaddr_in his_addr;
4610275Ssam 
4710275Ssam struct	hostent *hp;
4810275Ssam 
4910275Ssam int	data;
5010275Ssam jmp_buf	errcatch;
5110275Ssam int	logged_in;
5210275Ssam struct	passwd *pw;
5310275Ssam int	debug;
5411653Ssam int	timeout;
5511757Ssam int	logging;
5610275Ssam int	guest;
57*16033Sralph int	wtmp;
5810275Ssam int	type;
5910275Ssam int	form;
6010275Ssam int	stru;			/* avoid C keyword */
6110275Ssam int	mode;
6210321Ssam int	usedefault = 1;		/* for data transfers */
6310275Ssam char	hostname[32];
6413247Ssam char	remotehost[32];
6510321Ssam struct	servent *sp;
6610275Ssam 
6711653Ssam /*
6811653Ssam  * Timeout intervals for retrying connections
6911653Ssam  * to hosts that don't accept PORT cmds.  This
7011653Ssam  * is a kludge, but given the problems with TCP...
7111653Ssam  */
7211653Ssam #define	SWAITMAX	90	/* wait at most 90 seconds */
7311653Ssam #define	SWAITINT	5	/* interval between retries */
7411653Ssam 
7511653Ssam int	swaitmax = SWAITMAX;
7611653Ssam int	swaitint = SWAITINT;
7711653Ssam 
7810275Ssam int	lostconn();
7910419Ssam int	reapchild();
8010275Ssam FILE	*getdatasock(), *dataconn();
8110275Ssam 
8210275Ssam main(argc, argv)
8310275Ssam 	int argc;
8410275Ssam 	char *argv[];
8510275Ssam {
8610275Ssam 	int ctrl, s, options = 0;
8710275Ssam 	char *cp;
8810275Ssam 
8910275Ssam 	sp = getservbyname("ftp", "tcp");
9010275Ssam 	if (sp == 0) {
9111220Ssam 		fprintf(stderr, "ftpd: ftp/tcp: unknown service\n");
9210275Ssam 		exit(1);
9310275Ssam 	}
9410275Ssam 	ctrl_addr.sin_port = sp->s_port;
9510275Ssam 	data_source.sin_port = htons(ntohs(sp->s_port) - 1);
9610275Ssam 	signal(SIGPIPE, lostconn);
9710275Ssam 	debug = 0;
9810275Ssam 	argc--, argv++;
9910275Ssam 	while (argc > 0 && *argv[0] == '-') {
10010275Ssam 		for (cp = &argv[0][1]; *cp; cp++) switch (*cp) {
10110275Ssam 
10211653Ssam 		case 'v':
10311653Ssam 			debug = 1;
10411653Ssam 			break;
10511653Ssam 
10610275Ssam 		case 'd':
10710275Ssam 			debug = 1;
10810275Ssam 			options |= SO_DEBUG;
10910275Ssam 			break;
11010275Ssam 
11111757Ssam 		case 'l':
11211757Ssam 			logging = 1;
11311757Ssam 			break;
11411757Ssam 
11511653Ssam 		case 't':
11611653Ssam 			timeout = atoi(++cp);
11711653Ssam 			goto nextopt;
11811653Ssam 			break;
11911653Ssam 
12010275Ssam 		default:
12111653Ssam 			fprintf(stderr, "Unknown flag -%c ignored.\n", *cp);
12210275Ssam 			break;
12310275Ssam 		}
12411653Ssam nextopt:
12510275Ssam 		argc--, argv++;
12610275Ssam 	}
12710275Ssam #ifndef DEBUG
12810275Ssam 	if (fork())
12910275Ssam 		exit(0);
13010275Ssam 	for (s = 0; s < 10; s++)
13111653Ssam 		if (!logging || (s != 2))
13211653Ssam 			(void) close(s);
13313247Ssam 	(void) open("/", O_RDONLY);
13410275Ssam 	(void) dup2(0, 1);
13511653Ssam 	if (!logging)
13611653Ssam 		(void) dup2(0, 2);
13713247Ssam 	{ int tt = open("/dev/tty", O_RDWR);
13810275Ssam 	  if (tt > 0) {
13910275Ssam 		ioctl(tt, TIOCNOTTY, 0);
14010275Ssam 		close(tt);
14110275Ssam 	  }
14210275Ssam 	}
14310275Ssam #endif
14413247Ssam 	while ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
14510275Ssam 		perror("ftpd: socket");
14610275Ssam 		sleep(5);
14710275Ssam 	}
14810419Ssam 	if (options & SO_DEBUG)
14910419Ssam 		if (setsockopt(s, SOL_SOCKET, SO_DEBUG, 0, 0) < 0)
15010419Ssam 			perror("ftpd: setsockopt (SO_DEBUG)");
15110419Ssam 	if (setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, 0, 0) < 0)
15210419Ssam 		perror("ftpd: setsockopt (SO_KEEPALIVE)");
15310275Ssam 	while (bind(s, &ctrl_addr, sizeof (ctrl_addr), 0) < 0) {
15410275Ssam 		perror("ftpd: bind");
15510275Ssam 		sleep(5);
15610275Ssam 	}
15713034Ssam 	signal(SIGCHLD, reapchild);
15810303Ssam 	listen(s, 10);
15910275Ssam 	for (;;) {
16010275Ssam 		int hisaddrlen = sizeof (his_addr);
16110275Ssam 
16210275Ssam 		ctrl = accept(s, &his_addr, &hisaddrlen, 0);
16310275Ssam 		if (ctrl < 0) {
16410419Ssam 			if (errno == EINTR)
16510419Ssam 				continue;
16610275Ssam 			perror("ftpd: accept");
16710275Ssam 			continue;
16810275Ssam 		}
16910275Ssam 		if (fork() == 0) {
17011220Ssam 			signal (SIGCHLD, SIG_IGN);
17113247Ssam 			dolog(&his_addr);
17210275Ssam 			close(s);
17310275Ssam 			dup2(ctrl, 0), close(ctrl), dup2(0, 1);
17410275Ssam 			/* do telnet option negotiation here */
17510303Ssam 			/*
17610303Ssam 			 * Set up default state
17710303Ssam 			 */
17810275Ssam 			logged_in = 0;
17910275Ssam 			data = -1;
18010303Ssam 			type = TYPE_A;
18110303Ssam 			form = FORM_N;
18210303Ssam 			stru = STRU_F;
18310303Ssam 			mode = MODE_S;
18413152Ssam 			(void) getsockname(0, &ctrl_addr, sizeof (ctrl_addr));
18510275Ssam 			gethostname(hostname, sizeof (hostname));
18610275Ssam 			reply(220, "%s FTP server (%s) ready.",
18710275Ssam 				hostname, version);
18810275Ssam 			for (;;) {
18910275Ssam 				setjmp(errcatch);
19010275Ssam 				yyparse();
19110275Ssam 			}
19210275Ssam 		}
19310275Ssam 		close(ctrl);
19410275Ssam 	}
19510275Ssam }
19610275Ssam 
19710419Ssam reapchild()
19810419Ssam {
19910419Ssam 	union wait status;
20010419Ssam 
20110419Ssam 	while (wait3(&status, WNOHANG, 0) > 0)
20210419Ssam 		;
20310419Ssam }
20410419Ssam 
20510275Ssam lostconn()
20610275Ssam {
20710275Ssam 
20814089Ssam 	if (debug)
20914089Ssam 		fprintf(stderr, "Lost connection.\n");
21014089Ssam 	dologout(-1);
21110275Ssam }
21210275Ssam 
21310275Ssam pass(passwd)
21410275Ssam 	char *passwd;
21510275Ssam {
21610303Ssam 	char *xpasswd, *savestr();
21710303Ssam 	static struct passwd save;
21810275Ssam 
21910275Ssam 	if (logged_in || pw == NULL) {
22010275Ssam 		reply(503, "Login with USER first.");
22110275Ssam 		return;
22210275Ssam 	}
22310275Ssam 	if (!guest) {		/* "ftp" is only account allowed no password */
22410275Ssam 		xpasswd = crypt(passwd, pw->pw_passwd);
22515050Ssam 		if (*pw->pw_passwd == '\0' || strcmp(xpasswd, pw->pw_passwd)) {
22610275Ssam 			reply(530, "Login incorrect.");
22710275Ssam 			pw = NULL;
22810275Ssam 			return;
22910275Ssam 		}
23010275Ssam 	}
23110303Ssam 	setegid(pw->pw_gid);
23210275Ssam 	initgroups(pw->pw_name, pw->pw_gid);
23310275Ssam 	if (chdir(pw->pw_dir)) {
23410275Ssam 		reply(550, "User %s: can't change directory to $s.",
23510275Ssam 			pw->pw_name, pw->pw_dir);
23610303Ssam 		goto bad;
23710275Ssam 	}
238*16033Sralph 
239*16033Sralph 	if (guest)			/* grab wtmp before chroot */
240*16033Sralph 		wtmp = open("/usr/adm/wtmp", O_WRONLY|O_APPEND);
241*16033Sralph 
24210303Ssam 	if (guest && chroot(pw->pw_dir) < 0) {
24310275Ssam 		reply(550, "Can't set guest privileges.");
24410303Ssam 		goto bad;
24510275Ssam 	}
24610275Ssam 	if (!guest)
24710275Ssam 		reply(230, "User %s logged in.", pw->pw_name);
24810275Ssam 	else
24910275Ssam 		reply(230, "Guest login ok, access restrictions apply.");
25010275Ssam 	logged_in = 1;
25113247Ssam 	dologin(pw);
25210303Ssam 	seteuid(pw->pw_uid);
25310303Ssam 	/*
25410303Ssam 	 * Save everything so globbing doesn't
25510303Ssam 	 * clobber the fields.
25610303Ssam 	 */
25710303Ssam 	save = *pw;
25810303Ssam 	save.pw_name = savestr(pw->pw_name);
25910303Ssam 	save.pw_passwd = savestr(pw->pw_passwd);
26010303Ssam 	save.pw_comment = savestr(pw->pw_comment);
26110303Ssam 	save.pw_gecos = savestr(pw->pw_gecos, &save.pw_gecos);
26210303Ssam 	save.pw_dir = savestr(pw->pw_dir);
26310303Ssam 	save.pw_shell = savestr(pw->pw_shell);
26410303Ssam 	pw = &save;
26510303Ssam 	home = pw->pw_dir;		/* home dir for globbing */
26610303Ssam 	return;
26710303Ssam bad:
26810303Ssam 	seteuid(0);
26910303Ssam 	pw = NULL;
27010275Ssam }
27110275Ssam 
27210303Ssam char *
27310303Ssam savestr(s)
27410303Ssam 	char *s;
27510303Ssam {
27610303Ssam 	char *malloc();
27710303Ssam 	char *new = malloc(strlen(s) + 1);
27810303Ssam 
27910303Ssam 	if (new != NULL)
28010303Ssam 		strcpy(new, s);
28111347Ssam 	return (new);
28210303Ssam }
28310303Ssam 
28410275Ssam retrieve(cmd, name)
28510275Ssam 	char *cmd, *name;
28610275Ssam {
28710275Ssam 	FILE *fin, *dout;
28810275Ssam 	struct stat st;
28910275Ssam 	int (*closefunc)();
29010275Ssam 
29110275Ssam 	if (cmd == 0) {
29210317Ssam #ifdef notdef
29310317Ssam 		/* no remote command execution -- it's a security hole */
29411653Ssam 		if (*name == '|')
29510275Ssam 			fin = popen(name + 1, "r"), closefunc = pclose;
29610275Ssam 		else
29710317Ssam #endif
29810275Ssam 			fin = fopen(name, "r"), closefunc = fclose;
29910275Ssam 	} else {
30010275Ssam 		char line[BUFSIZ];
30110275Ssam 
30210422Ssam 		sprintf(line, cmd, name), name = line;
30310275Ssam 		fin = popen(line, "r"), closefunc = pclose;
30410275Ssam 	}
30510275Ssam 	if (fin == NULL) {
30613152Ssam 		if (errno != 0)
30713152Ssam 			reply(550, "%s: %s.", name, sys_errlist[errno]);
30810275Ssam 		return;
30910275Ssam 	}
31010275Ssam 	st.st_size = 0;
31110275Ssam 	if (cmd == 0 &&
31210275Ssam 	    (stat(name, &st) < 0 || (st.st_mode&S_IFMT) != S_IFREG)) {
31310275Ssam 		reply(550, "%s: not a plain file.", name);
31410275Ssam 		goto done;
31510275Ssam 	}
31610275Ssam 	dout = dataconn(name, st.st_size, "w");
31710275Ssam 	if (dout == NULL)
31810275Ssam 		goto done;
31910303Ssam 	if (send_data(fin, dout) || ferror(dout))
32010275Ssam 		reply(550, "%s: %s.", name, sys_errlist[errno]);
32110275Ssam 	else
32210275Ssam 		reply(226, "Transfer complete.");
32310303Ssam 	fclose(dout), data = -1;
32410275Ssam done:
32510275Ssam 	(*closefunc)(fin);
32610275Ssam }
32710275Ssam 
32810275Ssam store(name, mode)
32910275Ssam 	char *name, *mode;
33010275Ssam {
33110275Ssam 	FILE *fout, *din;
33210303Ssam 	int (*closefunc)(), dochown = 0;
33310275Ssam 
33410317Ssam #ifdef notdef
33510317Ssam 	/* no remote command execution -- it's a security hole */
33611653Ssam 	if (name[0] == '|')
33710275Ssam 		fout = popen(&name[1], "w"), closefunc = pclose;
33810317Ssam 	else
33910317Ssam #endif
34010317Ssam 	{
34110303Ssam 		struct stat st;
34210303Ssam 
34310303Ssam 		if (stat(name, &st) < 0)
34410303Ssam 			dochown++;
34510275Ssam 		fout = fopen(name, mode), closefunc = fclose;
34610303Ssam 	}
34710275Ssam 	if (fout == NULL) {
34810275Ssam 		reply(550, "%s: %s.", name, sys_errlist[errno]);
34910275Ssam 		return;
35010275Ssam 	}
35111653Ssam 	din = dataconn(name, (off_t)-1, "r");
35210275Ssam 	if (din == NULL)
35310275Ssam 		goto done;
35410303Ssam 	if (receive_data(din, fout) || ferror(fout))
35510275Ssam 		reply(550, "%s: %s.", name, sys_errlist[errno]);
35610275Ssam 	else
35710275Ssam 		reply(226, "Transfer complete.");
35810275Ssam 	fclose(din), data = -1;
35910275Ssam done:
36010303Ssam 	if (dochown)
36110303Ssam 		(void) chown(name, pw->pw_uid, -1);
36210275Ssam 	(*closefunc)(fout);
36310275Ssam }
36410275Ssam 
36510275Ssam FILE *
36610275Ssam getdatasock(mode)
36710275Ssam 	char *mode;
36810275Ssam {
36913247Ssam 	int s;
37010275Ssam 
37110275Ssam 	if (data >= 0)
37210275Ssam 		return (fdopen(data, mode));
37313247Ssam 	s = socket(AF_INET, SOCK_STREAM, 0);
37410602Ssam 	if (s < 0)
37510275Ssam 		return (NULL);
37610275Ssam 	seteuid(0);
37710602Ssam 	if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, 0, 0) < 0)
37810602Ssam 		goto bad;
37913152Ssam 	/* anchor socket to avoid multi-homing problems */
38013152Ssam 	data_source.sin_family = AF_INET;
38113152Ssam 	data_source.sin_addr = ctrl_addr.sin_addr;
38210602Ssam 	if (bind(s, &data_source, sizeof (data_source), 0) < 0)
38310602Ssam 		goto bad;
38410311Ssam 	seteuid(pw->pw_uid);
38510275Ssam 	return (fdopen(s, mode));
38610602Ssam bad:
38710602Ssam 	seteuid(pw->pw_uid);
38810602Ssam 	close(s);
38910602Ssam 	return (NULL);
39010275Ssam }
39110275Ssam 
39210275Ssam FILE *
39310275Ssam dataconn(name, size, mode)
39410275Ssam 	char *name;
39511653Ssam 	off_t size;
39610275Ssam 	char *mode;
39710275Ssam {
39810275Ssam 	char sizebuf[32];
39910275Ssam 	FILE *file;
40011653Ssam 	int retry = 0;
40110275Ssam 
40210275Ssam 	if (size >= 0)
40311653Ssam 		sprintf (sizebuf, " (%ld bytes)", size);
40410275Ssam 	else
40510275Ssam 		(void) strcpy(sizebuf, "");
40610275Ssam 	if (data >= 0) {
40710275Ssam 		reply(125, "Using existing data connection for %s%s.",
40810275Ssam 		    name, sizebuf);
40910321Ssam 		usedefault = 1;
41010275Ssam 		return (fdopen(data, mode));
41110275Ssam 	}
41210566Ssam 	if (usedefault)
41310422Ssam 		data_dest = his_addr;
41410422Ssam 	usedefault = 1;
41510275Ssam 	file = getdatasock(mode);
41610275Ssam 	if (file == NULL) {
41710275Ssam 		reply(425, "Can't create data socket (%s,%d): %s.",
41813247Ssam 		    inet_ntoa(data_source.sin_addr),
41910275Ssam 		    ntohs(data_source.sin_port),
42010275Ssam 		    sys_errlist[errno]);
42110275Ssam 		return (NULL);
42210275Ssam 	}
42310602Ssam 	reply(150, "Opening data connection for %s (%s,%d)%s.",
42413247Ssam 	    name, inet_ntoa(data_dest.sin_addr.s_addr),
42510602Ssam 	    ntohs(data_dest.sin_port), sizebuf);
42610275Ssam 	data = fileno(file);
42711653Ssam 	while (connect(data, &data_dest, sizeof (data_dest), 0) < 0) {
42811653Ssam 		if (errno == EADDRINUSE && retry < swaitmax) {
42911653Ssam 			sleep(swaitint);
43011653Ssam 			retry += swaitint;
43111653Ssam 			continue;
43211653Ssam 		}
43310275Ssam 		reply(425, "Can't build data connection: %s.",
43410275Ssam 		    sys_errlist[errno]);
43510275Ssam 		(void) fclose(file);
43610275Ssam 		data = -1;
43710275Ssam 		return (NULL);
43810275Ssam 	}
43910275Ssam 	return (file);
44010275Ssam }
44110275Ssam 
44210275Ssam /*
44310275Ssam  * Tranfer the contents of "instr" to
44410275Ssam  * "outstr" peer using the appropriate
44510275Ssam  * encapulation of the date subject
44610275Ssam  * to Mode, Structure, and Type.
44710275Ssam  *
44810275Ssam  * NB: Form isn't handled.
44910275Ssam  */
45010275Ssam send_data(instr, outstr)
45110275Ssam 	FILE *instr, *outstr;
45210275Ssam {
45310275Ssam 	register int c;
45410275Ssam 	int netfd, filefd, cnt;
45510275Ssam 	char buf[BUFSIZ];
45610275Ssam 
45710275Ssam 	switch (type) {
45810275Ssam 
45910275Ssam 	case TYPE_A:
46010275Ssam 		while ((c = getc(instr)) != EOF) {
46111220Ssam 			if (c == '\n') {
46211220Ssam 				if (ferror (outstr))
46311220Ssam 					return (1);
46410275Ssam 				putc('\r', outstr);
46511220Ssam 			}
46611220Ssam 			putc(c, outstr);
46711220Ssam 			if (c == '\r')
46811220Ssam 				putc ('\0', outstr);
46910275Ssam 		}
47011220Ssam 		if (ferror (instr) || ferror (outstr))
47111220Ssam 			return (1);
47210275Ssam 		return (0);
47310275Ssam 
47410275Ssam 	case TYPE_I:
47510275Ssam 	case TYPE_L:
47610275Ssam 		netfd = fileno(outstr);
47710275Ssam 		filefd = fileno(instr);
47810275Ssam 
47910303Ssam 		while ((cnt = read(filefd, buf, sizeof (buf))) > 0)
48010275Ssam 			if (write(netfd, buf, cnt) < 0)
48110275Ssam 				return (1);
48210275Ssam 		return (cnt < 0);
48310275Ssam 	}
48410275Ssam 	reply(504,"Unimplemented TYPE %d in send_data", type);
48510275Ssam 	return (1);
48610275Ssam }
48710275Ssam 
48810275Ssam /*
48910275Ssam  * Transfer data from peer to
49010275Ssam  * "outstr" using the appropriate
49110275Ssam  * encapulation of the data subject
49210275Ssam  * to Mode, Structure, and Type.
49310275Ssam  *
49410275Ssam  * N.B.: Form isn't handled.
49510275Ssam  */
49610275Ssam receive_data(instr, outstr)
49710275Ssam 	FILE *instr, *outstr;
49810275Ssam {
49910275Ssam 	register int c;
50011220Ssam 	int cnt;
50110275Ssam 	char buf[BUFSIZ];
50210275Ssam 
50310275Ssam 
50410275Ssam 	switch (type) {
50510275Ssam 
50610275Ssam 	case TYPE_I:
50710275Ssam 	case TYPE_L:
50810616Ssam 		while ((cnt = read(fileno(instr), buf, sizeof buf)) > 0)
50910616Ssam 			if (write(fileno(outstr), buf, cnt) < 0)
51010275Ssam 				return (1);
51110275Ssam 		return (cnt < 0);
51210275Ssam 
51310275Ssam 	case TYPE_E:
51410275Ssam 		reply(504, "TYPE E not implemented.");
51510275Ssam 		return (1);
51610275Ssam 
51710275Ssam 	case TYPE_A:
51810275Ssam 		while ((c = getc(instr)) != EOF) {
51910275Ssam 			if (c == '\r') {
52011220Ssam 				if (ferror (outstr))
52111220Ssam 					return (1);
52211220Ssam 				if ((c = getc(instr)) != '\n')
52311220Ssam 					putc ('\r', outstr);
52411220Ssam 				if (c == '\0')
52511220Ssam 					continue;
52610275Ssam 			}
52711220Ssam 			putc (c, outstr);
52810275Ssam 		}
52911220Ssam 		if (ferror (instr) || ferror (outstr))
53011220Ssam 			return (1);
53110275Ssam 		return (0);
53210275Ssam 	}
53310275Ssam 	fatal("Unknown type in receive_data.");
53410275Ssam 	/*NOTREACHED*/
53510275Ssam }
53610275Ssam 
53710275Ssam fatal(s)
53810275Ssam 	char *s;
53910275Ssam {
54010275Ssam 	reply(451, "Error in server: %s\n", s);
54110275Ssam 	reply(221, "Closing connection due to server error.");
54213247Ssam 	dologout(0);
54310275Ssam }
54410275Ssam 
54510275Ssam reply(n, s, args)
54610275Ssam 	int n;
54710275Ssam 	char *s;
54810275Ssam {
54910275Ssam 
55010275Ssam 	printf("%d ", n);
55110275Ssam 	_doprnt(s, &args, stdout);
55210275Ssam 	printf("\r\n");
55310275Ssam 	fflush(stdout);
55410275Ssam 	if (debug) {
55510275Ssam 		fprintf(stderr, "<--- %d ", n);
55610275Ssam 		_doprnt(s, &args, stderr);
55710275Ssam 		fprintf(stderr, "\n");
55810275Ssam 		fflush(stderr);
55910275Ssam 	}
56010275Ssam }
56110275Ssam 
56210275Ssam lreply(n, s, args)
56310275Ssam 	int n;
56410275Ssam 	char *s;
56510275Ssam {
56610275Ssam 	printf("%d-", n);
56710275Ssam 	_doprnt(s, &args, stdout);
56810275Ssam 	printf("\r\n");
56910275Ssam 	fflush(stdout);
57010275Ssam 	if (debug) {
57110275Ssam 		fprintf(stderr, "<--- %d-", n);
57210275Ssam 		_doprnt(s, &args, stderr);
57310275Ssam 		fprintf(stderr, "\n");
57410275Ssam 	}
57510275Ssam }
57610275Ssam 
57710275Ssam replystr(s)
57810275Ssam 	char *s;
57910275Ssam {
58010275Ssam 	printf("%s\r\n", s);
58110275Ssam 	fflush(stdout);
58210275Ssam 	if (debug)
58310275Ssam 		fprintf(stderr, "<--- %s\n", s);
58410275Ssam }
58510275Ssam 
58610275Ssam ack(s)
58710275Ssam 	char *s;
58810275Ssam {
58910275Ssam 	reply(200, "%s command okay.", s);
59010275Ssam }
59110275Ssam 
59210275Ssam nack(s)
59310275Ssam 	char *s;
59410275Ssam {
59510275Ssam 	reply(502, "%s command not implemented.", s);
59610275Ssam }
59710275Ssam 
59810275Ssam yyerror()
59910275Ssam {
60010275Ssam 	reply(500, "Command not understood.");
60110275Ssam }
60210275Ssam 
60310275Ssam delete(name)
60410275Ssam 	char *name;
60510275Ssam {
60610275Ssam 	struct stat st;
60710275Ssam 
60810275Ssam 	if (stat(name, &st) < 0) {
60910275Ssam 		reply(550, "%s: %s.", name, sys_errlist[errno]);
61010275Ssam 		return;
61110275Ssam 	}
61210275Ssam 	if ((st.st_mode&S_IFMT) == S_IFDIR) {
61310275Ssam 		if (rmdir(name) < 0) {
61410275Ssam 			reply(550, "%s: %s.", name, sys_errlist[errno]);
61510275Ssam 			return;
61610275Ssam 		}
61710275Ssam 		goto done;
61810275Ssam 	}
61910275Ssam 	if (unlink(name) < 0) {
62010275Ssam 		reply(550, "%s: %s.", name, sys_errlist[errno]);
62110275Ssam 		return;
62210275Ssam 	}
62310275Ssam done:
62410275Ssam 	ack("DELE");
62510275Ssam }
62610275Ssam 
62710275Ssam cwd(path)
62810275Ssam 	char *path;
62910275Ssam {
63010275Ssam 
63110275Ssam 	if (chdir(path) < 0) {
63210275Ssam 		reply(550, "%s: %s.", path, sys_errlist[errno]);
63310275Ssam 		return;
63410275Ssam 	}
63510275Ssam 	ack("CWD");
63610275Ssam }
63710275Ssam 
63810303Ssam makedir(name)
63910275Ssam 	char *name;
64010275Ssam {
64110303Ssam 	struct stat st;
64210303Ssam 	int dochown = stat(name, &st) < 0;
64310275Ssam 
64410275Ssam 	if (mkdir(name, 0777) < 0) {
64510275Ssam 		reply(550, "%s: %s.", name, sys_errlist[errno]);
64610275Ssam 		return;
64710275Ssam 	}
64810303Ssam 	if (dochown)
64910303Ssam 		(void) chown(name, pw->pw_uid, -1);
65010275Ssam 	ack("MKDIR");
65110275Ssam }
65210275Ssam 
65310303Ssam removedir(name)
65410275Ssam 	char *name;
65510275Ssam {
65610275Ssam 
65710275Ssam 	if (rmdir(name) < 0) {
65810275Ssam 		reply(550, "%s: %s.", name, sys_errlist[errno]);
65910275Ssam 		return;
66010275Ssam 	}
66110275Ssam 	ack("RMDIR");
66210275Ssam }
66310275Ssam 
66410303Ssam pwd()
66510275Ssam {
66610303Ssam 	char path[MAXPATHLEN + 1];
66710275Ssam 
66810275Ssam 	if (getwd(path) == NULL) {
66910275Ssam 		reply(451, "%s.", path);
67010275Ssam 		return;
67110275Ssam 	}
67210275Ssam 	reply(251, "\"%s\" is current directory.", path);
67310275Ssam }
67410275Ssam 
67510275Ssam char *
67610275Ssam renamefrom(name)
67710275Ssam 	char *name;
67810275Ssam {
67910275Ssam 	struct stat st;
68010275Ssam 
68110275Ssam 	if (stat(name, &st) < 0) {
68210275Ssam 		reply(550, "%s: %s.", name, sys_errlist[errno]);
68310275Ssam 		return ((char *)0);
68410275Ssam 	}
68510303Ssam 	reply(350, "File exists, ready for destination name");
68610275Ssam 	return (name);
68710275Ssam }
68810275Ssam 
68910275Ssam renamecmd(from, to)
69010275Ssam 	char *from, *to;
69110275Ssam {
69210275Ssam 
69310275Ssam 	if (rename(from, to) < 0) {
69410275Ssam 		reply(550, "rename: %s.", sys_errlist[errno]);
69510275Ssam 		return;
69610275Ssam 	}
69710275Ssam 	ack("RNTO");
69810275Ssam }
69910275Ssam 
70010275Ssam dolog(sin)
70110275Ssam 	struct sockaddr_in *sin;
70210275Ssam {
70310275Ssam 	struct hostent *hp = gethostbyaddr(&sin->sin_addr,
70410275Ssam 		sizeof (struct in_addr), AF_INET);
70510275Ssam 	time_t t;
70610275Ssam 
70713247Ssam 	if (hp) {
70813247Ssam 		strncpy(remotehost, hp->h_name, sizeof (remotehost));
70913247Ssam 		endhostent();
71013247Ssam 	} else
71113247Ssam 		strncpy(remotehost, inet_ntoa(sin->sin_addr),
71213247Ssam 		    sizeof (remotehost));
71313247Ssam 	if (!logging)
71413247Ssam 		return;
71510275Ssam 	t = time(0);
71611757Ssam 	fprintf(stderr,"FTPD: connection from %s at %s", remotehost, ctime(&t));
71710275Ssam 	fflush(stderr);
71810275Ssam }
71910695Ssam 
72013247Ssam #include <utmp.h>
72113247Ssam 
72213247Ssam #define	SCPYN(a, b)	strncpy(a, b, sizeof (a))
72313247Ssam struct	utmp utmp;
72413247Ssam 
72510695Ssam /*
72613247Ssam  * Record login in wtmp file.
72713247Ssam  */
72813247Ssam dologin(pw)
72913247Ssam 	struct passwd *pw;
73013247Ssam {
73113247Ssam 	char line[32];
73213247Ssam 
733*16033Sralph 	if (guest && (wtmp >= 0))
734*16033Sralph 		lseek(wtmp, 0, L_XTND);
735*16033Sralph 	else
736*16033Sralph 		wtmp = open("/usr/adm/wtmp", O_WRONLY|O_APPEND);
73713247Ssam 	if (wtmp >= 0) {
73813247Ssam 		/* hack, but must be unique and no tty line */
73913247Ssam 		sprintf(line, "ftp%d", getpid());
74013247Ssam 		SCPYN(utmp.ut_line, line);
74113247Ssam 		SCPYN(utmp.ut_name, pw->pw_name);
74213247Ssam 		SCPYN(utmp.ut_host, remotehost);
74313247Ssam 		utmp.ut_time = time(0);
74413247Ssam 		(void) write(wtmp, (char *)&utmp, sizeof (utmp));
745*16033Sralph 		if (!guest)
746*16033Sralph 			(void) close(wtmp);
74713247Ssam 	}
74813247Ssam }
74913247Ssam 
75013247Ssam /*
75113247Ssam  * Record logout in wtmp file
75213247Ssam  * and exit with supplied status.
75313247Ssam  */
75413247Ssam dologout(status)
75513247Ssam 	int status;
75613247Ssam {
75713247Ssam 	if (!logged_in)
75814811Skarels 		_exit(status);
75913247Ssam 	seteuid(0);
760*16033Sralph 	if (guest && (wtmp >= 0))
761*16033Sralph 		lseek(wtmp, 0, L_XTND);
762*16033Sralph 	else
763*16033Sralph 		wtmp = open("/usr/adm/wtmp", O_WRONLY|O_APPEND);
76413247Ssam 	if (wtmp >= 0) {
76513247Ssam 		SCPYN(utmp.ut_name, "");
76613247Ssam 		SCPYN(utmp.ut_host, "");
76713247Ssam 		utmp.ut_time = time(0);
76813247Ssam 		(void) write(wtmp, (char *)&utmp, sizeof (utmp));
76913247Ssam 		(void) close(wtmp);
77013247Ssam 	}
77114436Ssam 	/* beware of flushing buffers after a SIGPIPE */
77214436Ssam 	_exit(status);
77313247Ssam }
77413247Ssam 
77513247Ssam /*
77610695Ssam  * Special version of popen which avoids
77710695Ssam  * call to shell.  This insures noone may
77810695Ssam  * create a pipe to a hidden program as a side
77910695Ssam  * effect of a list or dir command.
78010695Ssam  */
78110695Ssam #define	tst(a,b)	(*mode == 'r'? (b) : (a))
78210695Ssam #define	RDR	0
78310695Ssam #define	WTR	1
78410695Ssam static	int popen_pid[5];
78510695Ssam 
78610695Ssam static char *
78710695Ssam nextarg(cpp)
78810695Ssam 	char *cpp;
78910695Ssam {
79010695Ssam 	register char *cp = cpp;
79110695Ssam 
79210695Ssam 	if (cp == 0)
79310695Ssam 		return (cp);
79410695Ssam 	while (*cp && *cp != ' ' && *cp != '\t')
79510695Ssam 		cp++;
79610695Ssam 	if (*cp == ' ' || *cp == '\t') {
79710695Ssam 		*cp++ = '\0';
79810695Ssam 		while (*cp == ' ' || *cp == '\t')
79910695Ssam 			cp++;
80010695Ssam 	}
80110695Ssam 	if (cp == cpp)
80210695Ssam 		return ((char *)0);
80310695Ssam 	return (cp);
80410695Ssam }
80510695Ssam 
80610695Ssam FILE *
80710695Ssam popen(cmd, mode)
80810695Ssam 	char *cmd, *mode;
80910695Ssam {
81013211Sroot 	int p[2], ac, gac;
81110695Ssam 	register myside, hisside, pid;
81213211Sroot 	char *av[20], *gav[512];
81310695Ssam 	register char *cp;
81410695Ssam 
81510695Ssam 	if (pipe(p) < 0)
81610695Ssam 		return (NULL);
81710695Ssam 	cp = cmd, ac = 0;
81813211Sroot 	/* break up string into pieces */
81910695Ssam 	do {
82010695Ssam 		av[ac++] = cp;
82110695Ssam 		cp = nextarg(cp);
82213211Sroot 	} while (cp && *cp && ac < 20);
82310695Ssam 	av[ac] = (char *)0;
82413211Sroot 	gav[0] = av[0];
82513211Sroot 	/* glob each piece */
82613211Sroot 	for (gac = ac = 1; av[ac] != NULL; ac++) {
82713211Sroot 		char **pop;
82813211Sroot 		extern char **glob();
82913211Sroot 
83013211Sroot 		pop = glob(av[ac]);
83113211Sroot 		if (pop) {
83213211Sroot 			av[ac] = (char *)pop;		/* save to free later */
83313211Sroot 			while (*pop && gac < 512)
83413211Sroot 				gav[gac++] = *pop++;
83513211Sroot 		}
83611757Ssam 	}
83713211Sroot 	gav[gac] = (char *)0;
83810695Ssam 	myside = tst(p[WTR], p[RDR]);
83910695Ssam 	hisside = tst(p[RDR], p[WTR]);
84010695Ssam 	if ((pid = fork()) == 0) {
84110695Ssam 		/* myside and hisside reverse roles in child */
84210695Ssam 		close(myside);
84310695Ssam 		dup2(hisside, tst(0, 1));
84410695Ssam 		close(hisside);
84513211Sroot 		execv(gav[0], gav);
84610695Ssam 		_exit(1);
84710695Ssam 	}
84813211Sroot 	for (ac = 1; av[ac] != NULL; ac++)
84913211Sroot 		blkfree((char **)av[ac]);
85010695Ssam 	if (pid == -1)
85110695Ssam 		return (NULL);
85210695Ssam 	popen_pid[myside] = pid;
85310695Ssam 	close(hisside);
85410695Ssam 	return (fdopen(myside, mode));
85510695Ssam }
85610695Ssam 
85710695Ssam pclose(ptr)
85810695Ssam 	FILE *ptr;
85910695Ssam {
86010695Ssam 	register f, r, (*hstat)(), (*istat)(), (*qstat)();
86110695Ssam 	int status;
86210695Ssam 
86310695Ssam 	f = fileno(ptr);
86410695Ssam 	fclose(ptr);
86510695Ssam 	istat = signal(SIGINT, SIG_IGN);
86610695Ssam 	qstat = signal(SIGQUIT, SIG_IGN);
86710695Ssam 	hstat = signal(SIGHUP, SIG_IGN);
86810695Ssam 	while ((r = wait(&status)) != popen_pid[f] && r != -1)
86910695Ssam 		;
87010695Ssam 	if (r == -1)
87110695Ssam 		status = -1;
87210695Ssam 	signal(SIGINT, istat);
87310695Ssam 	signal(SIGQUIT, qstat);
87410695Ssam 	signal(SIGHUP, hstat);
87510695Ssam 	return (status);
87610695Ssam }
87710695Ssam 
87810695Ssam /*
87910695Ssam  * Check user requesting login priviledges.
88010695Ssam  * Disallow anyone mentioned in the file FTPUSERS
88110695Ssam  * to allow people such as uucp to be avoided.
88210695Ssam  */
88310695Ssam checkuser(name)
88410695Ssam 	register char *name;
88510695Ssam {
88610695Ssam 	char line[BUFSIZ], *index();
88710695Ssam 	FILE *fd;
88810695Ssam 	int found = 0;
88910695Ssam 
89010695Ssam 	fd = fopen(FTPUSERS, "r");
89110695Ssam 	if (fd == NULL)
89210695Ssam 		return (1);
89310695Ssam 	while (fgets(line, sizeof (line), fd) != NULL) {
89410695Ssam 		register char *cp = index(line, '\n');
89510695Ssam 
89610695Ssam 		if (cp)
89710695Ssam 			*cp = '\0';
89810695Ssam 		if (strcmp(line, name) == 0) {
89910695Ssam 			found++;
90010695Ssam 			break;
90110695Ssam 		}
90210695Ssam 	}
90310695Ssam 	fclose(fd);
90410695Ssam 	return (!found);
90510695Ssam }
906