xref: /netbsd-src/libexec/ftpd/ftpd.c (revision 5f7096188587a2c7c95fa3c69b78e1ec9c7923d0)
1 /*
2  * Copyright (c) 1985, 1988, 1990 Regents of the University of California.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. All advertising materials mentioning features or use of this software
14  *    must display the following acknowledgement:
15  *	This product includes software developed by the University of
16  *	California, Berkeley and its contributors.
17  * 4. Neither the name of the University nor the names of its contributors
18  *    may be used to endorse or promote products derived from this software
19  *    without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  */
33 
34 #ifndef lint
35 char copyright[] =
36 "@(#) Copyright (c) 1985, 1988, 1990 Regents of the University of California.\n\
37  All rights reserved.\n";
38 #endif /* not lint */
39 
40 #ifndef lint
41 /*static char sccsid[] = "from: @(#)ftpd.c	5.40 (Berkeley) 7/2/91";*/
42 static char rcsid[] = "$Id: ftpd.c,v 1.3 1993/08/01 18:30:47 mycroft Exp $";
43 #endif /* not lint */
44 
45 /*
46  * FTP server.
47  */
48 #include <sys/param.h>
49 #include <sys/stat.h>
50 #include <sys/ioctl.h>
51 #include <sys/socket.h>
52 #include <sys/wait.h>
53 
54 #include <netinet/in.h>
55 #include <netinet/in_systm.h>
56 #include <netinet/ip.h>
57 
58 #define	FTP_NAMES
59 #include <arpa/ftp.h>
60 #include <arpa/inet.h>
61 #include <arpa/telnet.h>
62 
63 #include <signal.h>
64 #include <dirent.h>
65 #include <fcntl.h>
66 #include <time.h>
67 #include <pwd.h>
68 #include <setjmp.h>
69 #include <netdb.h>
70 #include <errno.h>
71 #include <syslog.h>
72 #include <varargs.h>
73 #include <unistd.h>
74 #include <stdio.h>
75 #include <ctype.h>
76 #include <stdlib.h>
77 #include <string.h>
78 #include "pathnames.h"
79 
80 /*
81  * File containing login names
82  * NOT to be used on this machine.
83  * Commonly used to disallow uucp.
84  */
85 extern	int errno;
86 extern	char *crypt();
87 extern	char version[];
88 extern	char *home;		/* pointer to home directory for glob */
89 extern	FILE *ftpd_popen(), *fopen(), *freopen();
90 extern	int  ftpd_pclose(), fclose();
91 extern	char *getline();
92 extern	char cbuf[];
93 extern	off_t restart_point;
94 
95 struct	sockaddr_in ctrl_addr;
96 struct	sockaddr_in data_source;
97 struct	sockaddr_in data_dest;
98 struct	sockaddr_in his_addr;
99 struct	sockaddr_in pasv_addr;
100 
101 int	data;
102 jmp_buf	errcatch, urgcatch;
103 int	logged_in;
104 struct	passwd *pw;
105 int	debug;
106 int	timeout = 900;    /* timeout after 15 minutes of inactivity */
107 int	maxtimeout = 7200;/* don't allow idle time to be set beyond 2 hours */
108 int	logging;
109 int	guest;
110 int	type;
111 int	form;
112 int	stru;			/* avoid C keyword */
113 int	mode;
114 int	usedefault = 1;		/* for data transfers */
115 int	pdata = -1;		/* for passive mode */
116 int	transflag;
117 off_t	file_size;
118 off_t	byte_count;
119 #if !defined(CMASK) || CMASK == 0
120 #undef CMASK
121 #define CMASK 027
122 #endif
123 int	defumask = CMASK;		/* default umask value */
124 char	tmpline[7];
125 char	hostname[MAXHOSTNAMELEN];
126 char	remotehost[MAXHOSTNAMELEN];
127 
128 /*
129  * Timeout intervals for retrying connections
130  * to hosts that don't accept PORT cmds.  This
131  * is a kludge, but given the problems with TCP...
132  */
133 #define	SWAITMAX	90	/* wait at most 90 seconds */
134 #define	SWAITINT	5	/* interval between retries */
135 
136 int	swaitmax = SWAITMAX;
137 int	swaitint = SWAITINT;
138 
139 void	lostconn(), myoob();
140 FILE	*getdatasock(), *dataconn();
141 
142 #ifdef SETPROCTITLE
143 char	**Argv = NULL;		/* pointer to argument vector */
144 char	*LastArgv = NULL;	/* end of argv */
145 char	proctitle[BUFSIZ];	/* initial part of title */
146 #endif /* SETPROCTITLE */
147 
148 main(argc, argv, envp)
149 	int argc;
150 	char *argv[];
151 	char **envp;
152 {
153 	int addrlen, on = 1, tos;
154 	char *cp;
155 
156 	/*
157 	 * LOG_NDELAY sets up the logging connection immediately,
158 	 * necessary for anonymous ftp's that chroot and can't do it later.
159 	 */
160 	openlog("ftpd", LOG_PID | LOG_NDELAY, LOG_DAEMON);
161 	addrlen = sizeof (his_addr);
162 	if (getpeername(0, (struct sockaddr *)&his_addr, &addrlen) < 0) {
163 		syslog(LOG_ERR, "getpeername (%s): %m",argv[0]);
164 		exit(1);
165 	}
166 	addrlen = sizeof (ctrl_addr);
167 	if (getsockname(0, (struct sockaddr *)&ctrl_addr, &addrlen) < 0) {
168 		syslog(LOG_ERR, "getsockname (%s): %m",argv[0]);
169 		exit(1);
170 	}
171 #ifdef IP_TOS
172 	tos = IPTOS_LOWDELAY;
173 	if (setsockopt(0, IPPROTO_IP, IP_TOS, (char *)&tos, sizeof(int)) < 0)
174 		syslog(LOG_WARNING, "setsockopt (IP_TOS): %m");
175 #endif
176 	data_source.sin_port = htons(ntohs(ctrl_addr.sin_port) - 1);
177 	debug = 0;
178 #ifdef SETPROCTITLE
179 	/*
180 	 *  Save start and extent of argv for setproctitle.
181 	 */
182 	Argv = argv;
183 	while (*envp)
184 		envp++;
185 	LastArgv = envp[-1] + strlen(envp[-1]);
186 #endif /* SETPROCTITLE */
187 
188 	argc--, argv++;
189 	while (argc > 0 && *argv[0] == '-') {
190 		for (cp = &argv[0][1]; *cp; cp++) switch (*cp) {
191 
192 		case 'v':
193 			debug = 1;
194 			break;
195 
196 		case 'd':
197 			debug = 1;
198 			break;
199 
200 		case 'l':
201 			logging = 1;
202 			break;
203 
204 		case 't':
205 			timeout = atoi(++cp);
206 			if (maxtimeout < timeout)
207 				maxtimeout = timeout;
208 			goto nextopt;
209 
210 		case 'T':
211 			maxtimeout = atoi(++cp);
212 			if (timeout > maxtimeout)
213 				timeout = maxtimeout;
214 			goto nextopt;
215 
216 		case 'u':
217 		    {
218 			int val = 0;
219 
220 			while (*++cp && *cp >= '0' && *cp <= '9')
221 				val = val*8 + *cp - '0';
222 			if (*cp)
223 				fprintf(stderr, "ftpd: Bad value for -u\n");
224 			else
225 				defumask = val;
226 			goto nextopt;
227 		    }
228 
229 		default:
230 			fprintf(stderr, "ftpd: Unknown flag -%c ignored.\n",
231 			     *cp);
232 			break;
233 		}
234 nextopt:
235 		argc--, argv++;
236 	}
237 	(void) freopen(_PATH_DEVNULL, "w", stderr);
238 	(void) signal(SIGPIPE, lostconn);
239 	(void) signal(SIGCHLD, SIG_IGN);
240 	if ((int)signal(SIGURG, myoob) < 0)
241 		syslog(LOG_ERR, "signal: %m");
242 
243 	/* Try to handle urgent data inline */
244 #ifdef SO_OOBINLINE
245 	if (setsockopt(0, SOL_SOCKET, SO_OOBINLINE, (char *)&on, sizeof(on)) < 0)
246 		syslog(LOG_ERR, "setsockopt: %m");
247 #endif
248 
249 #ifdef	F_SETOWN
250 	if (fcntl(fileno(stdin), F_SETOWN, getpid()) == -1)
251 		syslog(LOG_ERR, "fcntl F_SETOWN: %m");
252 #endif
253 	dolog(&his_addr);
254 	/*
255 	 * Set up default state
256 	 */
257 	data = -1;
258 	type = TYPE_A;
259 	form = FORM_N;
260 	stru = STRU_F;
261 	mode = MODE_S;
262 	tmpline[0] = '\0';
263 	(void) gethostname(hostname, sizeof (hostname));
264 	reply(220, "%s FTP server (%s) ready.", hostname, version);
265 	(void) setjmp(errcatch);
266 	for (;;)
267 		(void) yyparse();
268 	/* NOTREACHED */
269 }
270 
271 void
272 lostconn()
273 {
274 	if (debug)
275 		syslog(LOG_DEBUG, "lost connection");
276 	dologout(-1);
277 }
278 
279 static char ttyline[20];
280 
281 /*
282  * Helper function for sgetpwnam().
283  */
284 char *
285 sgetsave(s)
286 	char *s;
287 {
288 	char *new = malloc((unsigned) strlen(s) + 1);
289 
290 	if (new == NULL) {
291 		perror_reply(421, "Local resource failure: malloc");
292 		dologout(1);
293 		/* NOTREACHED */
294 	}
295 	(void) strcpy(new, s);
296 	return (new);
297 }
298 
299 /*
300  * Save the result of a getpwnam.  Used for USER command, since
301  * the data returned must not be clobbered by any other command
302  * (e.g., globbing).
303  */
304 struct passwd *
305 sgetpwnam(name)
306 	char *name;
307 {
308 	static struct passwd save;
309 	register struct passwd *p;
310 	char *sgetsave();
311 
312 	if ((p = getpwnam(name)) == NULL)
313 		return (p);
314 	if (save.pw_name) {
315 		free(save.pw_name);
316 		free(save.pw_passwd);
317 		free(save.pw_gecos);
318 		free(save.pw_dir);
319 		free(save.pw_shell);
320 	}
321 	save = *p;
322 	save.pw_name = sgetsave(p->pw_name);
323 	save.pw_passwd = sgetsave(p->pw_passwd);
324 	save.pw_gecos = sgetsave(p->pw_gecos);
325 	save.pw_dir = sgetsave(p->pw_dir);
326 	save.pw_shell = sgetsave(p->pw_shell);
327 	return (&save);
328 }
329 
330 int login_attempts;		/* number of failed login attempts */
331 int askpasswd;			/* had user command, ask for passwd */
332 
333 /*
334  * USER command.
335  * Sets global passwd pointer pw if named account exists and is acceptable;
336  * sets askpasswd if a PASS command is expected.  If logged in previously,
337  * need to reset state.  If name is "ftp" or "anonymous", the name is not in
338  * _PATH_FTPUSERS, and ftp account exists, set guest and pw, then just return.
339  * If account doesn't exist, ask for passwd anyway.  Otherwise, check user
340  * requesting login privileges.  Disallow anyone who does not have a standard
341  * shell as returned by getusershell().  Disallow anyone mentioned in the file
342  * _PATH_FTPUSERS to allow people such as root and uucp to be avoided.
343  */
344 user(name)
345 	char *name;
346 {
347 	register char *cp;
348 	char *shell;
349 	char *getusershell();
350 
351 	if (logged_in) {
352 		if (guest) {
353 			reply(530, "Can't change user from guest login.");
354 			return;
355 		}
356 		end_login();
357 	}
358 
359 	guest = 0;
360 	if (strcmp(name, "ftp") == 0 || strcmp(name, "anonymous") == 0) {
361 		if (checkuser("ftp") || checkuser("anonymous"))
362 			reply(530, "User %s access denied.", name);
363 		else if ((pw = sgetpwnam("ftp")) != NULL) {
364 			guest = 1;
365 			askpasswd = 1;
366 			reply(331, "Guest login ok, send ident as password.");
367 		} else
368 			reply(530, "User %s unknown.", name);
369 		return;
370 	}
371 	if (pw = sgetpwnam(name)) {
372 		if ((shell = pw->pw_shell) == NULL || *shell == 0)
373 			shell = _PATH_BSHELL;
374 		while ((cp = getusershell()) != NULL)
375 			if (strcmp(cp, shell) == 0)
376 				break;
377 		endusershell();
378 		if (cp == NULL || checkuser(name)) {
379 			reply(530, "User %s access denied.", name);
380 			if (logging)
381 				syslog(LOG_NOTICE,
382 				    "FTP LOGIN REFUSED FROM %s, %s",
383 				    remotehost, name);
384 			pw = (struct passwd *) NULL;
385 			return;
386 		}
387 	}
388 	reply(331, "Password required for %s.", name);
389 	askpasswd = 1;
390 	/*
391 	 * Delay before reading passwd after first failed
392 	 * attempt to slow down passwd-guessing programs.
393 	 */
394 	if (login_attempts)
395 		sleep((unsigned) login_attempts);
396 }
397 
398 /*
399  * Check if a user is in the file _PATH_FTPUSERS
400  */
401 checkuser(name)
402 	char *name;
403 {
404 	register FILE *fd;
405 	register char *p;
406 	char line[BUFSIZ];
407 
408 	if ((fd = fopen(_PATH_FTPUSERS, "r")) != NULL) {
409 		while (fgets(line, sizeof(line), fd) != NULL)
410 			if ((p = index(line, '\n')) != NULL) {
411 				*p = '\0';
412 				if (line[0] == '#')
413 					continue;
414 				if (strcmp(line, name) == 0)
415 					return (1);
416 			}
417 		(void) fclose(fd);
418 	}
419 	return (0);
420 }
421 
422 /*
423  * Terminate login as previous user, if any, resetting state;
424  * used when USER command is given or login fails.
425  */
426 end_login()
427 {
428 
429 	(void) seteuid((uid_t)0);
430 	if (logged_in)
431 		logwtmp(ttyline, "", "");
432 	pw = NULL;
433 	logged_in = 0;
434 	guest = 0;
435 }
436 
437 pass(passwd)
438 	char *passwd;
439 {
440 	char *xpasswd, *salt;
441 
442 	if (logged_in || askpasswd == 0) {
443 		reply(503, "Login with USER first.");
444 		return;
445 	}
446 	askpasswd = 0;
447 	if (!guest) {		/* "ftp" is only account allowed no password */
448 		if (pw == NULL)
449 			salt = "xx";
450 		else
451 			salt = pw->pw_passwd;
452 		xpasswd = crypt(passwd, salt);
453 		/* The strcmp does not catch null passwords! */
454 		if (pw == NULL || *pw->pw_passwd == '\0' ||
455 		    strcmp(xpasswd, pw->pw_passwd)) {
456 			reply(530, "Login incorrect.");
457 			pw = NULL;
458 			if (login_attempts++ >= 5) {
459 				syslog(LOG_NOTICE,
460 				    "repeated login failures from %s",
461 				    remotehost);
462 				exit(0);
463 			}
464 			return;
465 		}
466 	}
467 	login_attempts = 0;		/* this time successful */
468 	(void) setegid((gid_t)pw->pw_gid);
469 	(void) initgroups(pw->pw_name, pw->pw_gid);
470 
471 	/* open wtmp before chroot */
472 	(void)sprintf(ttyline, "ftp%d", getpid());
473 	logwtmp(ttyline, pw->pw_name, remotehost);
474 	logged_in = 1;
475 
476 	if (guest) {
477 		/*
478 		 * We MUST do a chdir() after the chroot. Otherwise
479 		 * the old current directory will be accessible as "."
480 		 * outside the new root!
481 		 */
482 		if (chroot(pw->pw_dir) < 0 || chdir("/") < 0) {
483 			reply(550, "Can't set guest privileges.");
484 			goto bad;
485 		}
486 	} else if (chdir(pw->pw_dir) < 0) {
487 		if (chdir("/") < 0) {
488 			reply(530, "User %s: can't change directory to %s.",
489 			    pw->pw_name, pw->pw_dir);
490 			goto bad;
491 		} else
492 			lreply(230, "No directory! Logging in with home=/");
493 	}
494 	if (seteuid((uid_t)pw->pw_uid) < 0) {
495 		reply(550, "Can't set uid.");
496 		goto bad;
497 	}
498 	if (guest) {
499 		reply(230, "Guest login ok, access restrictions apply.");
500 #ifdef SETPROCTITLE
501 		sprintf(proctitle, "%s: anonymous/%.*s", remotehost,
502 		    sizeof(proctitle) - sizeof(remotehost) -
503 		    sizeof(": anonymous/"), passwd);
504 		setproctitle(proctitle);
505 #endif /* SETPROCTITLE */
506 		if (logging)
507 			syslog(LOG_INFO, "ANONYMOUS FTP LOGIN FROM %s, %s",
508 			    remotehost, passwd);
509 	} else {
510 		reply(230, "User %s logged in.", pw->pw_name);
511 #ifdef SETPROCTITLE
512 		sprintf(proctitle, "%s: %s", remotehost, pw->pw_name);
513 		setproctitle(proctitle);
514 #endif /* SETPROCTITLE */
515 		if (logging)
516 			syslog(LOG_INFO, "FTP LOGIN FROM %s, %s",
517 			    remotehost, pw->pw_name);
518 	}
519 	home = pw->pw_dir;		/* home dir for globbing */
520 	(void) umask(defumask);
521 	return;
522 bad:
523 	/* Forget all about it... */
524 	end_login();
525 }
526 
527 retrieve(cmd, name)
528 	char *cmd, *name;
529 {
530 	FILE *fin, *dout;
531 	struct stat st;
532 	int (*closefunc)();
533 
534 	if (cmd == 0) {
535 		fin = fopen(name, "r"), closefunc = fclose;
536 		st.st_size = 0;
537 	} else {
538 		char line[BUFSIZ];
539 
540 		(void) sprintf(line, cmd, name), name = line;
541 		fin = ftpd_popen(line, "r"), closefunc = ftpd_pclose;
542 		st.st_size = -1;
543 		st.st_blksize = BUFSIZ;
544 	}
545 	if (fin == NULL) {
546 		if (errno != 0)
547 			perror_reply(550, name);
548 		return;
549 	}
550 	if (cmd == 0 &&
551 	    (fstat(fileno(fin), &st) < 0 || (st.st_mode&S_IFMT) != S_IFREG)) {
552 		reply(550, "%s: not a plain file.", name);
553 		goto done;
554 	}
555 	if (restart_point) {
556 		if (type == TYPE_A) {
557 			register int i, n, c;
558 
559 			n = restart_point;
560 			i = 0;
561 			while (i++ < n) {
562 				if ((c=getc(fin)) == EOF) {
563 					perror_reply(550, name);
564 					goto done;
565 				}
566 				if (c == '\n')
567 					i++;
568 			}
569 		} else if (lseek(fileno(fin), restart_point, L_SET) < 0) {
570 			perror_reply(550, name);
571 			goto done;
572 		}
573 	}
574 	dout = dataconn(name, st.st_size, "w");
575 	if (dout == NULL)
576 		goto done;
577 	send_data(fin, dout, st.st_blksize);
578 	(void) fclose(dout);
579 	data = -1;
580 	pdata = -1;
581 done:
582 	(*closefunc)(fin);
583 }
584 
585 store(name, mode, unique)
586 	char *name, *mode;
587 	int unique;
588 {
589 	FILE *fout, *din;
590 	struct stat st;
591 	int (*closefunc)();
592 	char *gunique();
593 
594 	if (unique && stat(name, &st) == 0 &&
595 	    (name = gunique(name)) == NULL)
596 		return;
597 
598 	if (restart_point)
599 		mode = "r+w";
600 	fout = fopen(name, mode);
601 	closefunc = fclose;
602 	if (fout == NULL) {
603 		perror_reply(553, name);
604 		return;
605 	}
606 	if (restart_point) {
607 		if (type == TYPE_A) {
608 			register int i, n, c;
609 
610 			n = restart_point;
611 			i = 0;
612 			while (i++ < n) {
613 				if ((c=getc(fout)) == EOF) {
614 					perror_reply(550, name);
615 					goto done;
616 				}
617 				if (c == '\n')
618 					i++;
619 			}
620 			/*
621 			 * We must do this seek to "current" position
622 			 * because we are changing from reading to
623 			 * writing.
624 			 */
625 			if (fseek(fout, 0L, L_INCR) < 0) {
626 				perror_reply(550, name);
627 				goto done;
628 			}
629 		} else if (lseek(fileno(fout), restart_point, L_SET) < 0) {
630 			perror_reply(550, name);
631 			goto done;
632 		}
633 	}
634 	din = dataconn(name, (off_t)-1, "r");
635 	if (din == NULL)
636 		goto done;
637 	if (receive_data(din, fout) == 0) {
638 		if (unique)
639 			reply(226, "Transfer complete (unique file name:%s).",
640 			    name);
641 		else
642 			reply(226, "Transfer complete.");
643 	}
644 	(void) fclose(din);
645 	data = -1;
646 	pdata = -1;
647 done:
648 	(*closefunc)(fout);
649 }
650 
651 FILE *
652 getdatasock(mode)
653 	char *mode;
654 {
655 	int s, on = 1, tries;
656 
657 	if (data >= 0)
658 		return (fdopen(data, mode));
659 	(void) seteuid((uid_t)0);
660 	s = socket(AF_INET, SOCK_STREAM, 0);
661 	if (s < 0)
662 		goto bad;
663 	if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
664 	    (char *) &on, sizeof (on)) < 0)
665 		goto bad;
666 	/* anchor socket to avoid multi-homing problems */
667 	data_source.sin_family = AF_INET;
668 	data_source.sin_addr = ctrl_addr.sin_addr;
669 	for (tries = 1; ; tries++) {
670 		if (bind(s, (struct sockaddr *)&data_source,
671 		    sizeof (data_source)) >= 0)
672 			break;
673 		if (errno != EADDRINUSE || tries > 10)
674 			goto bad;
675 		sleep(tries);
676 	}
677 	(void) seteuid((uid_t)pw->pw_uid);
678 #ifdef IP_TOS
679 	on = IPTOS_THROUGHPUT;
680 	if (setsockopt(s, IPPROTO_IP, IP_TOS, (char *)&on, sizeof(int)) < 0)
681 		syslog(LOG_WARNING, "setsockopt (IP_TOS): %m");
682 #endif
683 	return (fdopen(s, mode));
684 bad:
685 	(void) seteuid((uid_t)pw->pw_uid);
686 	(void) close(s);
687 	return (NULL);
688 }
689 
690 FILE *
691 dataconn(name, size, mode)
692 	char *name;
693 	off_t size;
694 	char *mode;
695 {
696 	char sizebuf[32];
697 	FILE *file;
698 	int retry = 0, tos;
699 
700 	file_size = size;
701 	byte_count = 0;
702 	if (size != (off_t) -1)
703 		(void) sprintf (sizebuf, " (%ld bytes)", size);
704 	else
705 		(void) strcpy(sizebuf, "");
706 	if (pdata >= 0) {
707 		struct sockaddr_in from;
708 		int s, fromlen = sizeof(from);
709 
710 		s = accept(pdata, (struct sockaddr *)&from, &fromlen);
711 		if (s < 0) {
712 			reply(425, "Can't open data connection.");
713 			(void) close(pdata);
714 			pdata = -1;
715 			return(NULL);
716 		}
717 		(void) close(pdata);
718 		pdata = s;
719 #ifdef IP_TOS
720 		tos = IPTOS_LOWDELAY;
721 		(void) setsockopt(s, IPPROTO_IP, IP_TOS, (char *)&tos,
722 		    sizeof(int));
723 #endif
724 		reply(150, "Opening %s mode data connection for %s%s.",
725 		     type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf);
726 		return(fdopen(pdata, mode));
727 	}
728 	if (data >= 0) {
729 		reply(125, "Using existing data connection for %s%s.",
730 		    name, sizebuf);
731 		usedefault = 1;
732 		return (fdopen(data, mode));
733 	}
734 	if (usedefault)
735 		data_dest = his_addr;
736 	usedefault = 1;
737 	file = getdatasock(mode);
738 	if (file == NULL) {
739 		reply(425, "Can't create data socket (%s,%d): %s.",
740 		    inet_ntoa(data_source.sin_addr),
741 		    ntohs(data_source.sin_port), strerror(errno));
742 		return (NULL);
743 	}
744 	data = fileno(file);
745 	while (connect(data, (struct sockaddr *)&data_dest,
746 	    sizeof (data_dest)) < 0) {
747 		if (errno == EADDRINUSE && retry < swaitmax) {
748 			sleep((unsigned) swaitint);
749 			retry += swaitint;
750 			continue;
751 		}
752 		perror_reply(425, "Can't build data connection");
753 		(void) fclose(file);
754 		data = -1;
755 		return (NULL);
756 	}
757 	reply(150, "Opening %s mode data connection for %s%s.",
758 	     type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf);
759 	return (file);
760 }
761 
762 /*
763  * Tranfer the contents of "instr" to
764  * "outstr" peer using the appropriate
765  * encapsulation of the data subject
766  * to Mode, Structure, and Type.
767  *
768  * NB: Form isn't handled.
769  */
770 send_data(instr, outstr, blksize)
771 	FILE *instr, *outstr;
772 	off_t blksize;
773 {
774 	register int c, cnt;
775 	register char *buf;
776 	int netfd, filefd;
777 
778 	transflag++;
779 	if (setjmp(urgcatch)) {
780 		transflag = 0;
781 		return;
782 	}
783 	switch (type) {
784 
785 	case TYPE_A:
786 		while ((c = getc(instr)) != EOF) {
787 			byte_count++;
788 			if (c == '\n') {
789 				if (ferror(outstr))
790 					goto data_err;
791 				(void) putc('\r', outstr);
792 			}
793 			(void) putc(c, outstr);
794 		}
795 		fflush(outstr);
796 		transflag = 0;
797 		if (ferror(instr))
798 			goto file_err;
799 		if (ferror(outstr))
800 			goto data_err;
801 		reply(226, "Transfer complete.");
802 		return;
803 
804 	case TYPE_I:
805 	case TYPE_L:
806 		if ((buf = malloc((u_int)blksize)) == NULL) {
807 			transflag = 0;
808 			perror_reply(451, "Local resource failure: malloc");
809 			return;
810 		}
811 		netfd = fileno(outstr);
812 		filefd = fileno(instr);
813 		while ((cnt = read(filefd, buf, (u_int)blksize)) > 0 &&
814 		    write(netfd, buf, cnt) == cnt)
815 			byte_count += cnt;
816 		transflag = 0;
817 		(void)free(buf);
818 		if (cnt != 0) {
819 			if (cnt < 0)
820 				goto file_err;
821 			goto data_err;
822 		}
823 		reply(226, "Transfer complete.");
824 		return;
825 	default:
826 		transflag = 0;
827 		reply(550, "Unimplemented TYPE %d in send_data", type);
828 		return;
829 	}
830 
831 data_err:
832 	transflag = 0;
833 	perror_reply(426, "Data connection");
834 	return;
835 
836 file_err:
837 	transflag = 0;
838 	perror_reply(551, "Error on input file");
839 }
840 
841 /*
842  * Transfer data from peer to
843  * "outstr" using the appropriate
844  * encapulation of the data subject
845  * to Mode, Structure, and Type.
846  *
847  * N.B.: Form isn't handled.
848  */
849 receive_data(instr, outstr)
850 	FILE *instr, *outstr;
851 {
852 	register int c;
853 	int cnt, bare_lfs = 0;
854 	char buf[BUFSIZ];
855 
856 	transflag++;
857 	if (setjmp(urgcatch)) {
858 		transflag = 0;
859 		return (-1);
860 	}
861 	switch (type) {
862 
863 	case TYPE_I:
864 	case TYPE_L:
865 		while ((cnt = read(fileno(instr), buf, sizeof buf)) > 0) {
866 			if (write(fileno(outstr), buf, cnt) != cnt)
867 				goto file_err;
868 			byte_count += cnt;
869 		}
870 		if (cnt < 0)
871 			goto data_err;
872 		transflag = 0;
873 		return (0);
874 
875 	case TYPE_E:
876 		reply(553, "TYPE E not implemented.");
877 		transflag = 0;
878 		return (-1);
879 
880 	case TYPE_A:
881 		while ((c = getc(instr)) != EOF) {
882 			byte_count++;
883 			if (c == '\n')
884 				bare_lfs++;
885 			while (c == '\r') {
886 				if (ferror(outstr))
887 					goto data_err;
888 				if ((c = getc(instr)) != '\n') {
889 					(void) putc ('\r', outstr);
890 					if (c == '\0' || c == EOF)
891 						goto contin2;
892 				}
893 			}
894 			(void) putc(c, outstr);
895 	contin2:	;
896 		}
897 		fflush(outstr);
898 		if (ferror(instr))
899 			goto data_err;
900 		if (ferror(outstr))
901 			goto file_err;
902 		transflag = 0;
903 		if (bare_lfs) {
904 			lreply(230, "WARNING! %d bare linefeeds received in ASCII mode", bare_lfs);
905 			printf("   File may not have transferred correctly.\r\n");
906 		}
907 		return (0);
908 	default:
909 		reply(550, "Unimplemented TYPE %d in receive_data", type);
910 		transflag = 0;
911 		return (-1);
912 	}
913 
914 data_err:
915 	transflag = 0;
916 	perror_reply(426, "Data Connection");
917 	return (-1);
918 
919 file_err:
920 	transflag = 0;
921 	perror_reply(452, "Error writing file");
922 	return (-1);
923 }
924 
925 statfilecmd(filename)
926 	char *filename;
927 {
928 	char line[BUFSIZ];
929 	FILE *fin;
930 	int c;
931 
932 	(void) sprintf(line, "/bin/ls -lgA %s", filename);
933 	fin = ftpd_popen(line, "r");
934 	lreply(211, "status of %s:", filename);
935 	while ((c = getc(fin)) != EOF) {
936 		if (c == '\n') {
937 			if (ferror(stdout)){
938 				perror_reply(421, "control connection");
939 				(void) ftpd_pclose(fin);
940 				dologout(1);
941 				/* NOTREACHED */
942 			}
943 			if (ferror(fin)) {
944 				perror_reply(551, filename);
945 				(void) ftpd_pclose(fin);
946 				return;
947 			}
948 			(void) putc('\r', stdout);
949 		}
950 		(void) putc(c, stdout);
951 	}
952 	(void) ftpd_pclose(fin);
953 	reply(211, "End of Status");
954 }
955 
956 statcmd()
957 {
958 	struct sockaddr_in *sin;
959 	u_char *a, *p;
960 
961 	lreply(211, "%s FTP server status:", hostname, version);
962 	printf("     %s\r\n", version);
963 	printf("     Connected to %s", remotehost);
964 	if (!isdigit(remotehost[0]))
965 		printf(" (%s)", inet_ntoa(his_addr.sin_addr));
966 	printf("\r\n");
967 	if (logged_in) {
968 		if (guest)
969 			printf("     Logged in anonymously\r\n");
970 		else
971 			printf("     Logged in as %s\r\n", pw->pw_name);
972 	} else if (askpasswd)
973 		printf("     Waiting for password\r\n");
974 	else
975 		printf("     Waiting for user name\r\n");
976 	printf("     TYPE: %s", typenames[type]);
977 	if (type == TYPE_A || type == TYPE_E)
978 		printf(", FORM: %s", formnames[form]);
979 	if (type == TYPE_L)
980 #if NBBY == 8
981 		printf(" %d", NBBY);
982 #else
983 		printf(" %d", bytesize);	/* need definition! */
984 #endif
985 	printf("; STRUcture: %s; transfer MODE: %s\r\n",
986 	    strunames[stru], modenames[mode]);
987 	if (data != -1)
988 		printf("     Data connection open\r\n");
989 	else if (pdata != -1) {
990 		printf("     in Passive mode");
991 		sin = &pasv_addr;
992 		goto printaddr;
993 	} else if (usedefault == 0) {
994 		printf("     PORT");
995 		sin = &data_dest;
996 printaddr:
997 		a = (u_char *) &sin->sin_addr;
998 		p = (u_char *) &sin->sin_port;
999 #define UC(b) (((int) b) & 0xff)
1000 		printf(" (%d,%d,%d,%d,%d,%d)\r\n", UC(a[0]),
1001 			UC(a[1]), UC(a[2]), UC(a[3]), UC(p[0]), UC(p[1]));
1002 #undef UC
1003 	} else
1004 		printf("     No data connection\r\n");
1005 	reply(211, "End of status");
1006 }
1007 
1008 fatal(s)
1009 	char *s;
1010 {
1011 	reply(451, "Error in server: %s\n", s);
1012 	reply(221, "Closing connection due to server error.");
1013 	dologout(0);
1014 	/* NOTREACHED */
1015 }
1016 
1017 /* VARARGS2 */
1018 reply(n, fmt, p0, p1, p2, p3, p4, p5)
1019 	int n;
1020 	char *fmt;
1021 {
1022 	printf("%d ", n);
1023 	printf(fmt, p0, p1, p2, p3, p4, p5);
1024 	printf("\r\n");
1025 	(void)fflush(stdout);
1026 	if (debug) {
1027 		syslog(LOG_DEBUG, "<--- %d ", n);
1028 		syslog(LOG_DEBUG, fmt, p0, p1, p2, p3, p4, p5);
1029 }
1030 }
1031 
1032 /* VARARGS2 */
1033 lreply(n, fmt, p0, p1, p2, p3, p4, p5)
1034 	int n;
1035 	char *fmt;
1036 {
1037 	printf("%d- ", n);
1038 	printf(fmt, p0, p1, p2, p3, p4, p5);
1039 	printf("\r\n");
1040 	(void)fflush(stdout);
1041 	if (debug) {
1042 		syslog(LOG_DEBUG, "<--- %d- ", n);
1043 		syslog(LOG_DEBUG, fmt, p0, p1, p2, p3, p4, p5);
1044 	}
1045 }
1046 
1047 ack(s)
1048 	char *s;
1049 {
1050 	reply(250, "%s command successful.", s);
1051 }
1052 
1053 nack(s)
1054 	char *s;
1055 {
1056 	reply(502, "%s command not implemented.", s);
1057 }
1058 
1059 /* ARGSUSED */
1060 yyerror(s)
1061 	char *s;
1062 {
1063 	char *cp;
1064 
1065 	if (cp = index(cbuf,'\n'))
1066 		*cp = '\0';
1067 	reply(500, "'%s': command not understood.", cbuf);
1068 }
1069 
1070 delete(name)
1071 	char *name;
1072 {
1073 	struct stat st;
1074 
1075 	if (stat(name, &st) < 0) {
1076 		perror_reply(550, name);
1077 		return;
1078 	}
1079 	if ((st.st_mode&S_IFMT) == S_IFDIR) {
1080 		if (rmdir(name) < 0) {
1081 			perror_reply(550, name);
1082 			return;
1083 		}
1084 		goto done;
1085 	}
1086 	if (unlink(name) < 0) {
1087 		perror_reply(550, name);
1088 		return;
1089 	}
1090 done:
1091 	ack("DELE");
1092 }
1093 
1094 cwd(path)
1095 	char *path;
1096 {
1097 	if (chdir(path) < 0)
1098 		perror_reply(550, path);
1099 	else
1100 		ack("CWD");
1101 }
1102 
1103 makedir(name)
1104 	char *name;
1105 {
1106 	if (mkdir(name, 0777) < 0)
1107 		perror_reply(550, name);
1108 	else
1109 		reply(257, "MKD command successful.");
1110 }
1111 
1112 removedir(name)
1113 	char *name;
1114 {
1115 	if (rmdir(name) < 0)
1116 		perror_reply(550, name);
1117 	else
1118 		ack("RMD");
1119 }
1120 
1121 pwd()
1122 {
1123 	char path[MAXPATHLEN + 1];
1124 	extern char *getwd();
1125 
1126 	if (getwd(path) == (char *)NULL)
1127 		reply(550, "%s.", path);
1128 	else
1129 		reply(257, "\"%s\" is current directory.", path);
1130 }
1131 
1132 char *
1133 renamefrom(name)
1134 	char *name;
1135 {
1136 	struct stat st;
1137 
1138 	if (stat(name, &st) < 0) {
1139 		perror_reply(550, name);
1140 		return ((char *)0);
1141 	}
1142 	reply(350, "File exists, ready for destination name");
1143 	return (name);
1144 }
1145 
1146 renamecmd(from, to)
1147 	char *from, *to;
1148 {
1149 	if (rename(from, to) < 0)
1150 		perror_reply(550, "rename");
1151 	else
1152 		ack("RNTO");
1153 }
1154 
1155 dolog(sin)
1156 	struct sockaddr_in *sin;
1157 {
1158 	struct hostent *hp = gethostbyaddr((char *)&sin->sin_addr,
1159 		sizeof (struct in_addr), AF_INET);
1160 	time_t t, time();
1161 	extern char *ctime();
1162 
1163 	if (hp)
1164 		(void) strncpy(remotehost, hp->h_name, sizeof (remotehost));
1165 	else
1166 		(void) strncpy(remotehost, inet_ntoa(sin->sin_addr),
1167 		    sizeof (remotehost));
1168 #ifdef SETPROCTITLE
1169 	sprintf(proctitle, "%s: connected", remotehost);
1170 	setproctitle(proctitle);
1171 #endif /* SETPROCTITLE */
1172 
1173 	if (logging) {
1174 		t = time((time_t *) 0);
1175 		syslog(LOG_INFO, "connection from %s at %s",
1176 		    remotehost, ctime(&t));
1177 	}
1178 }
1179 
1180 /*
1181  * Record logout in wtmp file
1182  * and exit with supplied status.
1183  */
1184 dologout(status)
1185 	int status;
1186 {
1187 	if (logged_in) {
1188 		(void) seteuid((uid_t)0);
1189 		logwtmp(ttyline, "", "");
1190 	}
1191 	/* beware of flushing buffers after a SIGPIPE */
1192 	_exit(status);
1193 }
1194 
1195 void
1196 myoob()
1197 {
1198 	char *cp;
1199 
1200 	/* only process if transfer occurring */
1201 	if (!transflag)
1202 		return;
1203 	cp = tmpline;
1204 	if (getline(cp, 7, stdin) == NULL) {
1205 		reply(221, "You could at least say goodbye.");
1206 		dologout(0);
1207 	}
1208 	upper(cp);
1209 	if (strcmp(cp, "ABOR\r\n") == 0) {
1210 		tmpline[0] = '\0';
1211 		reply(426, "Transfer aborted. Data connection closed.");
1212 		reply(226, "Abort successful");
1213 		longjmp(urgcatch, 1);
1214 	}
1215 	if (strcmp(cp, "STAT\r\n") == 0) {
1216 		if (file_size != (off_t) -1)
1217 			reply(213, "Status: %lu of %lu bytes transferred",
1218 			    byte_count, file_size);
1219 		else
1220 			reply(213, "Status: %lu bytes transferred", byte_count);
1221 	}
1222 }
1223 
1224 /*
1225  * Note: a response of 425 is not mentioned as a possible response to
1226  * 	the PASV command in RFC959. However, it has been blessed as
1227  * 	a legitimate response by Jon Postel in a telephone conversation
1228  *	with Rick Adams on 25 Jan 89.
1229  */
1230 passive()
1231 {
1232 	int len;
1233 	register char *p, *a;
1234 
1235 	pdata = socket(AF_INET, SOCK_STREAM, 0);
1236 	if (pdata < 0) {
1237 		perror_reply(425, "Can't open passive connection");
1238 		return;
1239 	}
1240 	pasv_addr = ctrl_addr;
1241 	pasv_addr.sin_port = 0;
1242 	(void) seteuid((uid_t)0);
1243 	if (bind(pdata, (struct sockaddr *)&pasv_addr, sizeof(pasv_addr)) < 0) {
1244 		(void) seteuid((uid_t)pw->pw_uid);
1245 		goto pasv_error;
1246 	}
1247 	(void) seteuid((uid_t)pw->pw_uid);
1248 	len = sizeof(pasv_addr);
1249 	if (getsockname(pdata, (struct sockaddr *) &pasv_addr, &len) < 0)
1250 		goto pasv_error;
1251 	if (listen(pdata, 1) < 0)
1252 		goto pasv_error;
1253 	a = (char *) &pasv_addr.sin_addr;
1254 	p = (char *) &pasv_addr.sin_port;
1255 
1256 #define UC(b) (((int) b) & 0xff)
1257 
1258 	reply(227, "Entering Passive Mode (%d,%d,%d,%d,%d,%d)", UC(a[0]),
1259 		UC(a[1]), UC(a[2]), UC(a[3]), UC(p[0]), UC(p[1]));
1260 	return;
1261 
1262 pasv_error:
1263 	(void) close(pdata);
1264 	pdata = -1;
1265 	perror_reply(425, "Can't open passive connection");
1266 	return;
1267 }
1268 
1269 /*
1270  * Generate unique name for file with basename "local".
1271  * The file named "local" is already known to exist.
1272  * Generates failure reply on error.
1273  */
1274 char *
1275 gunique(local)
1276 	char *local;
1277 {
1278 	static char new[MAXPATHLEN];
1279 	struct stat st;
1280 	char *cp = rindex(local, '/');
1281 	int count = 0;
1282 
1283 	if (cp)
1284 		*cp = '\0';
1285 	if (stat(cp ? local : ".", &st) < 0) {
1286 		perror_reply(553, cp ? local : ".");
1287 		return((char *) 0);
1288 	}
1289 	if (cp)
1290 		*cp = '/';
1291 	(void) strcpy(new, local);
1292 	cp = new + strlen(new);
1293 	*cp++ = '.';
1294 	for (count = 1; count < 100; count++) {
1295 		(void) sprintf(cp, "%d", count);
1296 		if (stat(new, &st) < 0)
1297 			return(new);
1298 	}
1299 	reply(452, "Unique file name cannot be created.");
1300 	return((char *) 0);
1301 }
1302 
1303 /*
1304  * Format and send reply containing system error number.
1305  */
1306 perror_reply(code, string)
1307 	int code;
1308 	char *string;
1309 {
1310 	reply(code, "%s: %s.", string, strerror(errno));
1311 }
1312 
1313 static char *onefile[] = {
1314 	"",
1315 	0
1316 };
1317 
1318 send_file_list(whichfiles)
1319 	char *whichfiles;
1320 {
1321 	struct stat st;
1322 	DIR *dirp = NULL;
1323 	struct dirent *dir;
1324 	FILE *dout = NULL;
1325 	register char **dirlist, *dirname;
1326 	int simple = 0;
1327 	char *strpbrk();
1328 
1329 	if (strpbrk(whichfiles, "~{[*?") != NULL) {
1330 		extern char **ftpglob(), *globerr;
1331 
1332 		globerr = NULL;
1333 		dirlist = ftpglob(whichfiles);
1334 		if (globerr != NULL) {
1335 			reply(550, globerr);
1336 			return;
1337 		} else if (dirlist == NULL) {
1338 			errno = ENOENT;
1339 			perror_reply(550, whichfiles);
1340 			return;
1341 		}
1342 	} else {
1343 		onefile[0] = whichfiles;
1344 		dirlist = onefile;
1345 		simple = 1;
1346 	}
1347 
1348 	if (setjmp(urgcatch)) {
1349 		transflag = 0;
1350 		return;
1351 	}
1352 	while (dirname = *dirlist++) {
1353 		if (stat(dirname, &st) < 0) {
1354 			/*
1355 			 * If user typed "ls -l", etc, and the client
1356 			 * used NLST, do what the user meant.
1357 			 */
1358 			if (dirname[0] == '-' && *dirlist == NULL &&
1359 			    transflag == 0) {
1360 				retrieve("/bin/ls %s", dirname);
1361 				return;
1362 			}
1363 			perror_reply(550, whichfiles);
1364 			if (dout != NULL) {
1365 				(void) fclose(dout);
1366 				transflag = 0;
1367 				data = -1;
1368 				pdata = -1;
1369 			}
1370 			return;
1371 		}
1372 
1373 		if ((st.st_mode&S_IFMT) == S_IFREG) {
1374 			if (dout == NULL) {
1375 				dout = dataconn("file list", (off_t)-1, "w");
1376 				if (dout == NULL)
1377 					return;
1378 				transflag++;
1379 			}
1380 			fprintf(dout, "%s%s\n", dirname,
1381 				type == TYPE_A ? "\r" : "");
1382 			byte_count += strlen(dirname) + 1;
1383 			continue;
1384 		} else if ((st.st_mode&S_IFMT) != S_IFDIR)
1385 			continue;
1386 
1387 		if ((dirp = opendir(dirname)) == NULL)
1388 			continue;
1389 
1390 		while ((dir = readdir(dirp)) != NULL) {
1391 			char nbuf[MAXPATHLEN];
1392 
1393 			if (dir->d_name[0] == '.' && dir->d_namlen == 1)
1394 				continue;
1395 			if (dir->d_name[0] == '.' && dir->d_name[1] == '.' &&
1396 			    dir->d_namlen == 2)
1397 				continue;
1398 
1399 			sprintf(nbuf, "%s/%s", dirname, dir->d_name);
1400 
1401 			/*
1402 			 * We have to do a stat to insure it's
1403 			 * not a directory or special file.
1404 			 */
1405 			if (simple || (stat(nbuf, &st) == 0 &&
1406 			    (st.st_mode&S_IFMT) == S_IFREG)) {
1407 				if (dout == NULL) {
1408 					dout = dataconn("file list", (off_t)-1,
1409 						"w");
1410 					if (dout == NULL)
1411 						return;
1412 					transflag++;
1413 				}
1414 				if (nbuf[0] == '.' && nbuf[1] == '/')
1415 					fprintf(dout, "%s%s\n", &nbuf[2],
1416 						type == TYPE_A ? "\r" : "");
1417 				else
1418 					fprintf(dout, "%s%s\n", nbuf,
1419 						type == TYPE_A ? "\r" : "");
1420 				byte_count += strlen(nbuf) + 1;
1421 			}
1422 		}
1423 		(void) closedir(dirp);
1424 	}
1425 
1426 	if (dout == NULL)
1427 		reply(550, "No files found.");
1428 	else if (ferror(dout) != 0)
1429 		perror_reply(550, "Data connection");
1430 	else
1431 		reply(226, "Transfer complete.");
1432 
1433 	transflag = 0;
1434 	if (dout != NULL)
1435 		(void) fclose(dout);
1436 	data = -1;
1437 	pdata = -1;
1438 }
1439 
1440 #ifdef SETPROCTITLE
1441 /*
1442  * clobber argv so ps will show what we're doing.
1443  * (stolen from sendmail)
1444  * warning, since this is usually started from inetd.conf, it
1445  * often doesn't have much of an environment or arglist to overwrite.
1446  */
1447 
1448 /*VARARGS1*/
1449 setproctitle(fmt, a, b, c)
1450 char *fmt;
1451 {
1452 	register char *p, *bp, ch;
1453 	register int i;
1454 	char buf[BUFSIZ];
1455 
1456 	(void) sprintf(buf, fmt, a, b, c);
1457 
1458 	/* make ps print our process name */
1459 	p = Argv[0];
1460 	*p++ = '-';
1461 
1462 	i = strlen(buf);
1463 	if (i > LastArgv - p - 2) {
1464 		i = LastArgv - p - 2;
1465 		buf[i] = '\0';
1466 	}
1467 	bp = buf;
1468 	while (ch = *bp++)
1469 		if (ch != '\n' && ch != '\r')
1470 			*p++ = ch;
1471 	while (p < LastArgv)
1472 		*p++ = ' ';
1473 }
1474 #endif /* SETPROCTITLE */
1475