xref: /original-bsd/libexec/ftpd/ftpd.c (revision 23a40993)
1 #ifndef lint
2 static char sccsid[] = "@(#)ftpd.c	4.23 (Berkeley) 06/22/83";
3 #endif
4 
5 /*
6  * FTP server.
7  */
8 #include <sys/param.h>
9 #include <sys/stat.h>
10 #include <sys/ioctl.h>
11 #include <sys/socket.h>
12 #include <sys/file.h>
13 
14 #include <netinet/in.h>
15 
16 #include <arpa/ftp.h>
17 #include <arpa/inet.h>
18 
19 #include <stdio.h>
20 #include <signal.h>
21 #include <wait.h>
22 #include <pwd.h>
23 #include <setjmp.h>
24 #include <netdb.h>
25 #include <errno.h>
26 
27 /*
28  * File containing login names
29  * NOT to be used on this machine.
30  * Commonly used to disallow uucp.
31  */
32 #define	FTPUSERS	"/etc/ftpusers"
33 
34 extern	int errno;
35 extern	char *sys_errlist[];
36 extern	char *crypt();
37 extern	char version[];
38 extern	char *home;		/* pointer to home directory for glob */
39 extern	FILE *popen(), *fopen();
40 extern	int pclose(), fclose();
41 
42 struct	sockaddr_in ctrl_addr;
43 struct	sockaddr_in data_source;
44 struct	sockaddr_in data_dest;
45 struct	sockaddr_in his_addr;
46 
47 struct	hostent *hp;
48 
49 int	data;
50 jmp_buf	errcatch;
51 int	logged_in;
52 struct	passwd *pw;
53 int	debug;
54 int	timeout;
55 int	logging;
56 int	guest;
57 int	type;
58 int	form;
59 int	stru;			/* avoid C keyword */
60 int	mode;
61 int	usedefault = 1;		/* for data transfers */
62 char	hostname[32];
63 char	remotehost[32];
64 struct	servent *sp;
65 
66 /*
67  * Timeout intervals for retrying connections
68  * to hosts that don't accept PORT cmds.  This
69  * is a kludge, but given the problems with TCP...
70  */
71 #define	SWAITMAX	90	/* wait at most 90 seconds */
72 #define	SWAITINT	5	/* interval between retries */
73 
74 int	swaitmax = SWAITMAX;
75 int	swaitint = SWAITINT;
76 
77 int	lostconn();
78 int	reapchild();
79 FILE	*getdatasock(), *dataconn();
80 
81 main(argc, argv)
82 	int argc;
83 	char *argv[];
84 {
85 	int ctrl, s, options = 0;
86 	char *cp;
87 
88 	sp = getservbyname("ftp", "tcp");
89 	if (sp == 0) {
90 		fprintf(stderr, "ftpd: ftp/tcp: unknown service\n");
91 		exit(1);
92 	}
93 	ctrl_addr.sin_port = sp->s_port;
94 	data_source.sin_port = htons(ntohs(sp->s_port) - 1);
95 	signal(SIGPIPE, lostconn);
96 	debug = 0;
97 	argc--, argv++;
98 	while (argc > 0 && *argv[0] == '-') {
99 		for (cp = &argv[0][1]; *cp; cp++) switch (*cp) {
100 
101 		case 'v':
102 			debug = 1;
103 			break;
104 
105 		case 'd':
106 			debug = 1;
107 			options |= SO_DEBUG;
108 			break;
109 
110 		case 'l':
111 			logging = 1;
112 			break;
113 
114 		case 't':
115 			timeout = atoi(++cp);
116 			goto nextopt;
117 			break;
118 
119 		default:
120 			fprintf(stderr, "Unknown flag -%c ignored.\n", *cp);
121 			break;
122 		}
123 nextopt:
124 		argc--, argv++;
125 	}
126 #ifndef DEBUG
127 	if (fork())
128 		exit(0);
129 	for (s = 0; s < 10; s++)
130 		if (!logging || (s != 2))
131 			(void) close(s);
132 	(void) open("/", O_RDONLY);
133 	(void) dup2(0, 1);
134 	if (!logging)
135 		(void) dup2(0, 2);
136 	{ int tt = open("/dev/tty", O_RDWR);
137 	  if (tt > 0) {
138 		ioctl(tt, TIOCNOTTY, 0);
139 		close(tt);
140 	  }
141 	}
142 #endif
143 	while ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
144 		perror("ftpd: socket");
145 		sleep(5);
146 	}
147 	if (options & SO_DEBUG)
148 		if (setsockopt(s, SOL_SOCKET, SO_DEBUG, 0, 0) < 0)
149 			perror("ftpd: setsockopt (SO_DEBUG)");
150 	if (setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, 0, 0) < 0)
151 		perror("ftpd: setsockopt (SO_KEEPALIVE)");
152 	while (bind(s, &ctrl_addr, sizeof (ctrl_addr), 0) < 0) {
153 		perror("ftpd: bind");
154 		sleep(5);
155 	}
156 	signal(SIGCHLD, reapchild);
157 	listen(s, 10);
158 	for (;;) {
159 		int hisaddrlen = sizeof (his_addr);
160 
161 		ctrl = accept(s, &his_addr, &hisaddrlen, 0);
162 		if (ctrl < 0) {
163 			if (errno == EINTR)
164 				continue;
165 			perror("ftpd: accept");
166 			continue;
167 		}
168 		if (fork() == 0) {
169 			signal (SIGCHLD, SIG_IGN);
170 			dolog(&his_addr);
171 			close(s);
172 			dup2(ctrl, 0), close(ctrl), dup2(0, 1);
173 			/* do telnet option negotiation here */
174 			/*
175 			 * Set up default state
176 			 */
177 			logged_in = 0;
178 			data = -1;
179 			type = TYPE_A;
180 			form = FORM_N;
181 			stru = STRU_F;
182 			mode = MODE_S;
183 			(void) getsockname(0, &ctrl_addr, sizeof (ctrl_addr));
184 			gethostname(hostname, sizeof (hostname));
185 			reply(220, "%s FTP server (%s) ready.",
186 				hostname, version);
187 			for (;;) {
188 				setjmp(errcatch);
189 				yyparse();
190 			}
191 		}
192 		close(ctrl);
193 	}
194 }
195 
196 reapchild()
197 {
198 	union wait status;
199 
200 	while (wait3(&status, WNOHANG, 0) > 0)
201 		;
202 }
203 
204 lostconn()
205 {
206 
207 	fatal("Connection closed.");
208 }
209 
210 pass(passwd)
211 	char *passwd;
212 {
213 	char *xpasswd, *savestr();
214 	static struct passwd save;
215 
216 	if (logged_in || pw == NULL) {
217 		reply(503, "Login with USER first.");
218 		return;
219 	}
220 	if (!guest) {		/* "ftp" is only account allowed no password */
221 		xpasswd = crypt(passwd, pw->pw_passwd);
222 		if (strcmp(xpasswd, pw->pw_passwd) != 0) {
223 			reply(530, "Login incorrect.");
224 			pw = NULL;
225 			return;
226 		}
227 	}
228 	setegid(pw->pw_gid);
229 	initgroups(pw->pw_name, pw->pw_gid);
230 	if (chdir(pw->pw_dir)) {
231 		reply(550, "User %s: can't change directory to $s.",
232 			pw->pw_name, pw->pw_dir);
233 		goto bad;
234 	}
235 	if (guest && chroot(pw->pw_dir) < 0) {
236 		reply(550, "Can't set guest privileges.");
237 		goto bad;
238 	}
239 	if (!guest)
240 		reply(230, "User %s logged in.", pw->pw_name);
241 	else
242 		reply(230, "Guest login ok, access restrictions apply.");
243 	logged_in = 1;
244 	dologin(pw);
245 	seteuid(pw->pw_uid);
246 	/*
247 	 * Save everything so globbing doesn't
248 	 * clobber the fields.
249 	 */
250 	save = *pw;
251 	save.pw_name = savestr(pw->pw_name);
252 	save.pw_passwd = savestr(pw->pw_passwd);
253 	save.pw_comment = savestr(pw->pw_comment);
254 	save.pw_gecos = savestr(pw->pw_gecos, &save.pw_gecos);
255 	save.pw_dir = savestr(pw->pw_dir);
256 	save.pw_shell = savestr(pw->pw_shell);
257 	pw = &save;
258 	home = pw->pw_dir;		/* home dir for globbing */
259 	return;
260 bad:
261 	seteuid(0);
262 	pw = NULL;
263 }
264 
265 char *
266 savestr(s)
267 	char *s;
268 {
269 	char *malloc();
270 	char *new = malloc(strlen(s) + 1);
271 
272 	if (new != NULL)
273 		strcpy(new, s);
274 	return (new);
275 }
276 
277 retrieve(cmd, name)
278 	char *cmd, *name;
279 {
280 	FILE *fin, *dout;
281 	struct stat st;
282 	int (*closefunc)();
283 
284 	if (cmd == 0) {
285 #ifdef notdef
286 		/* no remote command execution -- it's a security hole */
287 		if (*name == '|')
288 			fin = popen(name + 1, "r"), closefunc = pclose;
289 		else
290 #endif
291 			fin = fopen(name, "r"), closefunc = fclose;
292 	} else {
293 		char line[BUFSIZ];
294 
295 		sprintf(line, cmd, name), name = line;
296 		fin = popen(line, "r"), closefunc = pclose;
297 	}
298 	if (fin == NULL) {
299 		if (errno != 0)
300 			reply(550, "%s: %s.", name, sys_errlist[errno]);
301 		return;
302 	}
303 	st.st_size = 0;
304 	if (cmd == 0 &&
305 	    (stat(name, &st) < 0 || (st.st_mode&S_IFMT) != S_IFREG)) {
306 		reply(550, "%s: not a plain file.", name);
307 		goto done;
308 	}
309 	dout = dataconn(name, st.st_size, "w");
310 	if (dout == NULL)
311 		goto done;
312 	if (send_data(fin, dout) || ferror(dout))
313 		reply(550, "%s: %s.", name, sys_errlist[errno]);
314 	else
315 		reply(226, "Transfer complete.");
316 	fclose(dout), data = -1;
317 done:
318 	(*closefunc)(fin);
319 }
320 
321 store(name, mode)
322 	char *name, *mode;
323 {
324 	FILE *fout, *din;
325 	int (*closefunc)(), dochown = 0;
326 
327 #ifdef notdef
328 	/* no remote command execution -- it's a security hole */
329 	if (name[0] == '|')
330 		fout = popen(&name[1], "w"), closefunc = pclose;
331 	else
332 #endif
333 	{
334 		struct stat st;
335 
336 		if (stat(name, &st) < 0)
337 			dochown++;
338 		fout = fopen(name, mode), closefunc = fclose;
339 	}
340 	if (fout == NULL) {
341 		reply(550, "%s: %s.", name, sys_errlist[errno]);
342 		return;
343 	}
344 	din = dataconn(name, (off_t)-1, "r");
345 	if (din == NULL)
346 		goto done;
347 	if (receive_data(din, fout) || ferror(fout))
348 		reply(550, "%s: %s.", name, sys_errlist[errno]);
349 	else
350 		reply(226, "Transfer complete.");
351 	fclose(din), data = -1;
352 done:
353 	if (dochown)
354 		(void) chown(name, pw->pw_uid, -1);
355 	(*closefunc)(fout);
356 }
357 
358 FILE *
359 getdatasock(mode)
360 	char *mode;
361 {
362 	int s;
363 
364 	if (data >= 0)
365 		return (fdopen(data, mode));
366 	s = socket(AF_INET, SOCK_STREAM, 0);
367 	if (s < 0)
368 		return (NULL);
369 	seteuid(0);
370 	if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, 0, 0) < 0)
371 		goto bad;
372 	/* anchor socket to avoid multi-homing problems */
373 	data_source.sin_family = AF_INET;
374 	data_source.sin_addr = ctrl_addr.sin_addr;
375 	if (bind(s, &data_source, sizeof (data_source), 0) < 0)
376 		goto bad;
377 	seteuid(pw->pw_uid);
378 	return (fdopen(s, mode));
379 bad:
380 	seteuid(pw->pw_uid);
381 	close(s);
382 	return (NULL);
383 }
384 
385 FILE *
386 dataconn(name, size, mode)
387 	char *name;
388 	off_t size;
389 	char *mode;
390 {
391 	char sizebuf[32];
392 	FILE *file;
393 	int retry = 0;
394 
395 	if (size >= 0)
396 		sprintf (sizebuf, " (%ld bytes)", size);
397 	else
398 		(void) strcpy(sizebuf, "");
399 	if (data >= 0) {
400 		reply(125, "Using existing data connection for %s%s.",
401 		    name, sizebuf);
402 		usedefault = 1;
403 		return (fdopen(data, mode));
404 	}
405 	if (usedefault)
406 		data_dest = his_addr;
407 	usedefault = 1;
408 	file = getdatasock(mode);
409 	if (file == NULL) {
410 		reply(425, "Can't create data socket (%s,%d): %s.",
411 		    inet_ntoa(data_source.sin_addr),
412 		    ntohs(data_source.sin_port),
413 		    sys_errlist[errno]);
414 		return (NULL);
415 	}
416 	reply(150, "Opening data connection for %s (%s,%d)%s.",
417 	    name, inet_ntoa(data_dest.sin_addr.s_addr),
418 	    ntohs(data_dest.sin_port), sizebuf);
419 	data = fileno(file);
420 	while (connect(data, &data_dest, sizeof (data_dest), 0) < 0) {
421 		if (errno == EADDRINUSE && retry < swaitmax) {
422 			sleep(swaitint);
423 			retry += swaitint;
424 			continue;
425 		}
426 		reply(425, "Can't build data connection: %s.",
427 		    sys_errlist[errno]);
428 		(void) fclose(file);
429 		data = -1;
430 		return (NULL);
431 	}
432 	return (file);
433 }
434 
435 /*
436  * Tranfer the contents of "instr" to
437  * "outstr" peer using the appropriate
438  * encapulation of the date subject
439  * to Mode, Structure, and Type.
440  *
441  * NB: Form isn't handled.
442  */
443 send_data(instr, outstr)
444 	FILE *instr, *outstr;
445 {
446 	register int c;
447 	int netfd, filefd, cnt;
448 	char buf[BUFSIZ];
449 
450 	switch (type) {
451 
452 	case TYPE_A:
453 		while ((c = getc(instr)) != EOF) {
454 			if (c == '\n') {
455 				if (ferror (outstr))
456 					return (1);
457 				putc('\r', outstr);
458 			}
459 			putc(c, outstr);
460 			if (c == '\r')
461 				putc ('\0', outstr);
462 		}
463 		if (ferror (instr) || ferror (outstr))
464 			return (1);
465 		return (0);
466 
467 	case TYPE_I:
468 	case TYPE_L:
469 		netfd = fileno(outstr);
470 		filefd = fileno(instr);
471 
472 		while ((cnt = read(filefd, buf, sizeof (buf))) > 0)
473 			if (write(netfd, buf, cnt) < 0)
474 				return (1);
475 		return (cnt < 0);
476 	}
477 	reply(504,"Unimplemented TYPE %d in send_data", type);
478 	return (1);
479 }
480 
481 /*
482  * Transfer data from peer to
483  * "outstr" using the appropriate
484  * encapulation of the data subject
485  * to Mode, Structure, and Type.
486  *
487  * N.B.: Form isn't handled.
488  */
489 receive_data(instr, outstr)
490 	FILE *instr, *outstr;
491 {
492 	register int c;
493 	int cnt;
494 	char buf[BUFSIZ];
495 
496 
497 	switch (type) {
498 
499 	case TYPE_I:
500 	case TYPE_L:
501 		while ((cnt = read(fileno(instr), buf, sizeof buf)) > 0)
502 			if (write(fileno(outstr), buf, cnt) < 0)
503 				return (1);
504 		return (cnt < 0);
505 
506 	case TYPE_E:
507 		reply(504, "TYPE E not implemented.");
508 		return (1);
509 
510 	case TYPE_A:
511 		while ((c = getc(instr)) != EOF) {
512 			if (c == '\r') {
513 				if (ferror (outstr))
514 					return (1);
515 				if ((c = getc(instr)) != '\n')
516 					putc ('\r', outstr);
517 				if (c == '\0')
518 					continue;
519 			}
520 			putc (c, outstr);
521 		}
522 		if (ferror (instr) || ferror (outstr))
523 			return (1);
524 		return (0);
525 	}
526 	fatal("Unknown type in receive_data.");
527 	/*NOTREACHED*/
528 }
529 
530 fatal(s)
531 	char *s;
532 {
533 	reply(451, "Error in server: %s\n", s);
534 	reply(221, "Closing connection due to server error.");
535 	dologout(0);
536 }
537 
538 reply(n, s, args)
539 	int n;
540 	char *s;
541 {
542 
543 	printf("%d ", n);
544 	_doprnt(s, &args, stdout);
545 	printf("\r\n");
546 	fflush(stdout);
547 	if (debug) {
548 		fprintf(stderr, "<--- %d ", n);
549 		_doprnt(s, &args, stderr);
550 		fprintf(stderr, "\n");
551 		fflush(stderr);
552 	}
553 }
554 
555 lreply(n, s, args)
556 	int n;
557 	char *s;
558 {
559 	printf("%d-", n);
560 	_doprnt(s, &args, stdout);
561 	printf("\r\n");
562 	fflush(stdout);
563 	if (debug) {
564 		fprintf(stderr, "<--- %d-", n);
565 		_doprnt(s, &args, stderr);
566 		fprintf(stderr, "\n");
567 	}
568 }
569 
570 replystr(s)
571 	char *s;
572 {
573 	printf("%s\r\n", s);
574 	fflush(stdout);
575 	if (debug)
576 		fprintf(stderr, "<--- %s\n", s);
577 }
578 
579 ack(s)
580 	char *s;
581 {
582 	reply(200, "%s command okay.", s);
583 }
584 
585 nack(s)
586 	char *s;
587 {
588 	reply(502, "%s command not implemented.", s);
589 }
590 
591 yyerror()
592 {
593 	reply(500, "Command not understood.");
594 }
595 
596 delete(name)
597 	char *name;
598 {
599 	struct stat st;
600 
601 	if (stat(name, &st) < 0) {
602 		reply(550, "%s: %s.", name, sys_errlist[errno]);
603 		return;
604 	}
605 	if ((st.st_mode&S_IFMT) == S_IFDIR) {
606 		if (rmdir(name) < 0) {
607 			reply(550, "%s: %s.", name, sys_errlist[errno]);
608 			return;
609 		}
610 		goto done;
611 	}
612 	if (unlink(name) < 0) {
613 		reply(550, "%s: %s.", name, sys_errlist[errno]);
614 		return;
615 	}
616 done:
617 	ack("DELE");
618 }
619 
620 cwd(path)
621 	char *path;
622 {
623 
624 	if (chdir(path) < 0) {
625 		reply(550, "%s: %s.", path, sys_errlist[errno]);
626 		return;
627 	}
628 	ack("CWD");
629 }
630 
631 makedir(name)
632 	char *name;
633 {
634 	struct stat st;
635 	int dochown = stat(name, &st) < 0;
636 
637 	if (mkdir(name, 0777) < 0) {
638 		reply(550, "%s: %s.", name, sys_errlist[errno]);
639 		return;
640 	}
641 	if (dochown)
642 		(void) chown(name, pw->pw_uid, -1);
643 	ack("MKDIR");
644 }
645 
646 removedir(name)
647 	char *name;
648 {
649 
650 	if (rmdir(name) < 0) {
651 		reply(550, "%s: %s.", name, sys_errlist[errno]);
652 		return;
653 	}
654 	ack("RMDIR");
655 }
656 
657 pwd()
658 {
659 	char path[MAXPATHLEN + 1];
660 
661 	if (getwd(path) == NULL) {
662 		reply(451, "%s.", path);
663 		return;
664 	}
665 	reply(251, "\"%s\" is current directory.", path);
666 }
667 
668 char *
669 renamefrom(name)
670 	char *name;
671 {
672 	struct stat st;
673 
674 	if (stat(name, &st) < 0) {
675 		reply(550, "%s: %s.", name, sys_errlist[errno]);
676 		return ((char *)0);
677 	}
678 	reply(350, "File exists, ready for destination name");
679 	return (name);
680 }
681 
682 renamecmd(from, to)
683 	char *from, *to;
684 {
685 
686 	if (rename(from, to) < 0) {
687 		reply(550, "rename: %s.", sys_errlist[errno]);
688 		return;
689 	}
690 	ack("RNTO");
691 }
692 
693 dolog(sin)
694 	struct sockaddr_in *sin;
695 {
696 	struct hostent *hp = gethostbyaddr(&sin->sin_addr,
697 		sizeof (struct in_addr), AF_INET);
698 	time_t t;
699 
700 	if (hp) {
701 		strncpy(remotehost, hp->h_name, sizeof (remotehost));
702 		endhostent();
703 	} else
704 		strncpy(remotehost, inet_ntoa(sin->sin_addr),
705 		    sizeof (remotehost));
706 	if (!logging)
707 		return;
708 	t = time(0);
709 	fprintf(stderr,"FTPD: connection from %s at %s", remotehost, ctime(&t));
710 	fflush(stderr);
711 }
712 
713 #include <utmp.h>
714 
715 #define	SCPYN(a, b)	strncpy(a, b, sizeof (a))
716 struct	utmp utmp;
717 
718 /*
719  * Record login in wtmp file.
720  */
721 dologin(pw)
722 	struct passwd *pw;
723 {
724 	int wtmp;
725 	char line[32];
726 
727 	wtmp = open("/usr/adm/wtmp", O_WRONLY|O_APPEND);
728 	if (wtmp >= 0) {
729 		/* hack, but must be unique and no tty line */
730 		sprintf(line, "ftp%d", getpid());
731 		SCPYN(utmp.ut_line, line);
732 		SCPYN(utmp.ut_name, pw->pw_name);
733 		SCPYN(utmp.ut_host, remotehost);
734 		utmp.ut_time = time(0);
735 		(void) write(wtmp, (char *)&utmp, sizeof (utmp));
736 		(void) close(wtmp);
737 	}
738 }
739 
740 /*
741  * Record logout in wtmp file
742  * and exit with supplied status.
743  */
744 dologout(status)
745 	int status;
746 {
747 	int wtmp;
748 
749 	if (!logged_in)
750 		return;
751 	seteuid(0);
752 	wtmp = open("/usr/adm/wtmp", O_WRONLY|O_APPEND);
753 	if (wtmp >= 0) {
754 		SCPYN(utmp.ut_name, "");
755 		SCPYN(utmp.ut_host, "");
756 		utmp.ut_time = time(0);
757 		(void) write(wtmp, (char *)&utmp, sizeof (utmp));
758 		(void) close(wtmp);
759 	}
760 	exit(status);
761 }
762 
763 /*
764  * Special version of popen which avoids
765  * call to shell.  This insures noone may
766  * create a pipe to a hidden program as a side
767  * effect of a list or dir command.
768  */
769 #define	tst(a,b)	(*mode == 'r'? (b) : (a))
770 #define	RDR	0
771 #define	WTR	1
772 static	int popen_pid[5];
773 
774 static char *
775 nextarg(cpp)
776 	char *cpp;
777 {
778 	register char *cp = cpp;
779 
780 	if (cp == 0)
781 		return (cp);
782 	while (*cp && *cp != ' ' && *cp != '\t')
783 		cp++;
784 	if (*cp == ' ' || *cp == '\t') {
785 		*cp++ = '\0';
786 		while (*cp == ' ' || *cp == '\t')
787 			cp++;
788 	}
789 	if (cp == cpp)
790 		return ((char *)0);
791 	return (cp);
792 }
793 
794 FILE *
795 popen(cmd, mode)
796 	char *cmd, *mode;
797 {
798 	int p[2], ac, gac;
799 	register myside, hisside, pid;
800 	char *av[20], *gav[512];
801 	register char *cp;
802 
803 	if (pipe(p) < 0)
804 		return (NULL);
805 	cp = cmd, ac = 0;
806 	/* break up string into pieces */
807 	do {
808 		av[ac++] = cp;
809 		cp = nextarg(cp);
810 	} while (cp && *cp && ac < 20);
811 	av[ac] = (char *)0;
812 	gav[0] = av[0];
813 	/* glob each piece */
814 	for (gac = ac = 1; av[ac] != NULL; ac++) {
815 		char **pop;
816 		extern char **glob();
817 
818 		pop = glob(av[ac]);
819 		if (pop) {
820 			av[ac] = (char *)pop;		/* save to free later */
821 			while (*pop && gac < 512)
822 				gav[gac++] = *pop++;
823 		}
824 	}
825 	gav[gac] = (char *)0;
826 	myside = tst(p[WTR], p[RDR]);
827 	hisside = tst(p[RDR], p[WTR]);
828 	if ((pid = fork()) == 0) {
829 		/* myside and hisside reverse roles in child */
830 		close(myside);
831 		dup2(hisside, tst(0, 1));
832 		close(hisside);
833 		execv(gav[0], gav);
834 		_exit(1);
835 	}
836 	for (ac = 1; av[ac] != NULL; ac++)
837 		blkfree((char **)av[ac]);
838 	if (pid == -1)
839 		return (NULL);
840 	popen_pid[myside] = pid;
841 	close(hisside);
842 	return (fdopen(myside, mode));
843 }
844 
845 pclose(ptr)
846 	FILE *ptr;
847 {
848 	register f, r, (*hstat)(), (*istat)(), (*qstat)();
849 	int status;
850 
851 	f = fileno(ptr);
852 	fclose(ptr);
853 	istat = signal(SIGINT, SIG_IGN);
854 	qstat = signal(SIGQUIT, SIG_IGN);
855 	hstat = signal(SIGHUP, SIG_IGN);
856 	while ((r = wait(&status)) != popen_pid[f] && r != -1)
857 		;
858 	if (r == -1)
859 		status = -1;
860 	signal(SIGINT, istat);
861 	signal(SIGQUIT, qstat);
862 	signal(SIGHUP, hstat);
863 	return (status);
864 }
865 
866 /*
867  * Check user requesting login priviledges.
868  * Disallow anyone mentioned in the file FTPUSERS
869  * to allow people such as uucp to be avoided.
870  */
871 checkuser(name)
872 	register char *name;
873 {
874 	char line[BUFSIZ], *index();
875 	FILE *fd;
876 	int found = 0;
877 
878 	fd = fopen(FTPUSERS, "r");
879 	if (fd == NULL)
880 		return (1);
881 	while (fgets(line, sizeof (line), fd) != NULL) {
882 		register char *cp = index(line, '\n');
883 
884 		if (cp)
885 			*cp = '\0';
886 		if (strcmp(line, name) == 0) {
887 			found++;
888 			break;
889 		}
890 	}
891 	fclose(fd);
892 	return (!found);
893 }
894