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