xref: /freebsd/libexec/ftpd/ftpcmd.y (revision 7bd6fde3)
1 /*
2  * Copyright (c) 1985, 1988, 1993, 1994
3  *	The Regents of the University of California.  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  *	@(#)ftpcmd.y	8.3 (Berkeley) 4/6/94
34  */
35 
36 /*
37  * Grammar for FTP commands.
38  * See RFC 959.
39  */
40 
41 %{
42 
43 #ifndef lint
44 #if 0
45 static char sccsid[] = "@(#)ftpcmd.y	8.3 (Berkeley) 4/6/94";
46 #endif
47 #endif /* not lint */
48 
49 #include <sys/cdefs.h>
50 __FBSDID("$FreeBSD$");
51 
52 #include <sys/param.h>
53 #include <sys/socket.h>
54 #include <sys/stat.h>
55 
56 #include <netinet/in.h>
57 #include <arpa/ftp.h>
58 
59 #include <ctype.h>
60 #include <errno.h>
61 #include <glob.h>
62 #include <libutil.h>
63 #include <limits.h>
64 #include <md5.h>
65 #include <netdb.h>
66 #include <pwd.h>
67 #include <signal.h>
68 #include <stdint.h>
69 #include <stdio.h>
70 #include <stdlib.h>
71 #include <string.h>
72 #include <syslog.h>
73 #include <time.h>
74 #include <unistd.h>
75 
76 #include "extern.h"
77 #include "pathnames.h"
78 
79 extern	union sockunion data_dest, his_addr;
80 extern	int hostinfo;
81 extern	int logged_in;
82 extern	struct passwd *pw;
83 extern	int guest;
84 extern	char *homedir;
85 extern 	int paranoid;
86 extern	int logging;
87 extern	int type;
88 extern	int form;
89 extern	int ftpdebug;
90 extern	int timeout;
91 extern	int maxtimeout;
92 extern  int pdata;
93 extern	char *hostname;
94 extern	char proctitle[];
95 extern	int usedefault;
96 extern  char tmpline[];
97 extern	int readonly;
98 extern	int noepsv;
99 extern	int noretr;
100 extern	int noguestretr;
101 extern	char *typenames[]; /* defined in <arpa/ftp.h> included from ftpd.c */
102 
103 off_t	restart_point;
104 
105 static	int cmd_type;
106 static	int cmd_form;
107 static	int cmd_bytesz;
108 static	int state;
109 char	cbuf[512];
110 char	*fromname = NULL;
111 
112 extern int epsvall;
113 
114 %}
115 
116 %union {
117 	struct {
118 		off_t	o;
119 		int	i;
120 	} u;
121 	char   *s;
122 }
123 
124 %token
125 	A	B	C	E	F	I
126 	L	N	P	R	S	T
127 	ALL
128 
129 	SP	CRLF	COMMA
130 
131 	USER	PASS	ACCT	REIN	QUIT	PORT
132 	PASV	TYPE	STRU	MODE	RETR	STOR
133 	APPE	MLFL	MAIL	MSND	MSOM	MSAM
134 	MRSQ	MRCP	ALLO	REST	RNFR	RNTO
135 	ABOR	DELE	CWD	LIST	NLST	SITE
136 	STAT	HELP	NOOP	MKD	RMD	PWD
137 	CDUP	STOU	SMNT	SYST	SIZE	MDTM
138 	LPRT	LPSV	EPRT	EPSV
139 
140 	UMASK	IDLE	CHMOD	MDFIVE
141 
142 	LEXERR	NOTIMPL
143 
144 %token	<s> STRING
145 %token	<u> NUMBER
146 
147 %type	<u.i> check_login octal_number byte_size
148 %type	<u.i> check_login_ro check_login_epsv
149 %type	<u.i> struct_code mode_code type_code form_code
150 %type	<s> pathstring pathname password username
151 %type	<s> ALL NOTIMPL
152 
153 %start	cmd_list
154 
155 %%
156 
157 cmd_list
158 	: /* empty */
159 	| cmd_list cmd
160 		{
161 			if (fromname)
162 				free(fromname);
163 			fromname = NULL;
164 			restart_point = 0;
165 		}
166 	| cmd_list rcmd
167 	;
168 
169 cmd
170 	: USER SP username CRLF
171 		{
172 			user($3);
173 			free($3);
174 		}
175 	| PASS SP password CRLF
176 		{
177 			pass($3);
178 			free($3);
179 		}
180 	| PASS CRLF
181 		{
182 			pass("");
183 		}
184 	| PORT check_login SP host_port CRLF
185 		{
186 			if (epsvall) {
187 				reply(501, "No PORT allowed after EPSV ALL.");
188 				goto port_done;
189 			}
190 			if (!$2)
191 				goto port_done;
192 			if (port_check("PORT") == 1)
193 				goto port_done;
194 #ifdef INET6
195 			if ((his_addr.su_family != AF_INET6 ||
196 			     !IN6_IS_ADDR_V4MAPPED(&his_addr.su_sin6.sin6_addr))) {
197 				/* shoud never happen */
198 				usedefault = 1;
199 				reply(500, "Invalid address rejected.");
200 				goto port_done;
201 			}
202 			port_check_v6("pcmd");
203 #endif
204 		port_done:
205 			;
206 		}
207 	| LPRT check_login SP host_long_port CRLF
208 		{
209 			if (epsvall) {
210 				reply(501, "No LPRT allowed after EPSV ALL.");
211 				goto lprt_done;
212 			}
213 			if (!$2)
214 				goto lprt_done;
215 			if (port_check("LPRT") == 1)
216 				goto lprt_done;
217 #ifdef INET6
218 			if (his_addr.su_family != AF_INET6) {
219 				usedefault = 1;
220 				reply(500, "Invalid address rejected.");
221 				goto lprt_done;
222 			}
223 			if (port_check_v6("LPRT") == 1)
224 				goto lprt_done;
225 #endif
226 		lprt_done:
227 			;
228 		}
229 	| EPRT check_login SP STRING CRLF
230 		{
231 			char delim;
232 			char *tmp = NULL;
233 			char *p, *q;
234 			char *result[3];
235 			struct addrinfo hints;
236 			struct addrinfo *res;
237 			int i;
238 
239 			if (epsvall) {
240 				reply(501, "No EPRT allowed after EPSV ALL.");
241 				goto eprt_done;
242 			}
243 			if (!$2)
244 				goto eprt_done;
245 
246 			memset(&data_dest, 0, sizeof(data_dest));
247 			tmp = strdup($4);
248 			if (ftpdebug)
249 				syslog(LOG_DEBUG, "%s", tmp);
250 			if (!tmp) {
251 				fatalerror("not enough core");
252 				/*NOTREACHED*/
253 			}
254 			p = tmp;
255 			delim = p[0];
256 			p++;
257 			memset(result, 0, sizeof(result));
258 			for (i = 0; i < 3; i++) {
259 				q = strchr(p, delim);
260 				if (!q || *q != delim) {
261 		parsefail:
262 					reply(500,
263 						"Invalid argument, rejected.");
264 					if (tmp)
265 						free(tmp);
266 					usedefault = 1;
267 					goto eprt_done;
268 				}
269 				*q++ = '\0';
270 				result[i] = p;
271 				if (ftpdebug)
272 					syslog(LOG_DEBUG, "%d: %s", i, p);
273 				p = q;
274 			}
275 
276 			/* some more sanity check */
277 			p = result[0];
278 			while (*p) {
279 				if (!isdigit(*p))
280 					goto parsefail;
281 				p++;
282 			}
283 			p = result[2];
284 			while (*p) {
285 				if (!isdigit(*p))
286 					goto parsefail;
287 				p++;
288 			}
289 
290 			/* grab address */
291 			memset(&hints, 0, sizeof(hints));
292 			if (atoi(result[0]) == 1)
293 				hints.ai_family = PF_INET;
294 #ifdef INET6
295 			else if (atoi(result[0]) == 2)
296 				hints.ai_family = PF_INET6;
297 #endif
298 			else
299 				hints.ai_family = PF_UNSPEC;	/*XXX*/
300 			hints.ai_socktype = SOCK_STREAM;
301 			i = getaddrinfo(result[1], result[2], &hints, &res);
302 			if (i)
303 				goto parsefail;
304 			memcpy(&data_dest, res->ai_addr, res->ai_addrlen);
305 #ifdef INET6
306 			if (his_addr.su_family == AF_INET6
307 			    && data_dest.su_family == AF_INET6) {
308 				/* XXX more sanity checks! */
309 				data_dest.su_sin6.sin6_scope_id =
310 					his_addr.su_sin6.sin6_scope_id;
311 			}
312 #endif
313 			free(tmp);
314 			tmp = NULL;
315 
316 			if (port_check("EPRT") == 1)
317 				goto eprt_done;
318 #ifdef INET6
319 			if (his_addr.su_family != AF_INET6) {
320 				usedefault = 1;
321 				reply(500, "Invalid address rejected.");
322 				goto eprt_done;
323 			}
324 			if (port_check_v6("EPRT") == 1)
325 				goto eprt_done;
326 #endif
327 		eprt_done:
328 			free($4);
329 		}
330 	| PASV check_login CRLF
331 		{
332 			if (epsvall)
333 				reply(501, "No PASV allowed after EPSV ALL.");
334 			else if ($2)
335 				passive();
336 		}
337 	| LPSV check_login CRLF
338 		{
339 			if (epsvall)
340 				reply(501, "No LPSV allowed after EPSV ALL.");
341 			else if ($2)
342 				long_passive("LPSV", PF_UNSPEC);
343 		}
344 	| EPSV check_login_epsv SP NUMBER CRLF
345 		{
346 			if ($2) {
347 				int pf;
348 				switch ($4.i) {
349 				case 1:
350 					pf = PF_INET;
351 					break;
352 #ifdef INET6
353 				case 2:
354 					pf = PF_INET6;
355 					break;
356 #endif
357 				default:
358 					pf = -1;	/*junk value*/
359 					break;
360 				}
361 				long_passive("EPSV", pf);
362 			}
363 		}
364 	| EPSV check_login_epsv SP ALL CRLF
365 		{
366 			if ($2) {
367 				reply(200, "EPSV ALL command successful.");
368 				epsvall++;
369 			}
370 		}
371 	| EPSV check_login_epsv CRLF
372 		{
373 			if ($2)
374 				long_passive("EPSV", PF_UNSPEC);
375 		}
376 	| TYPE check_login SP type_code CRLF
377 		{
378 			if ($2) {
379 				switch (cmd_type) {
380 
381 				case TYPE_A:
382 					if (cmd_form == FORM_N) {
383 						reply(200, "Type set to A.");
384 						type = cmd_type;
385 						form = cmd_form;
386 					} else
387 						reply(504, "Form must be N.");
388 					break;
389 
390 				case TYPE_E:
391 					reply(504, "Type E not implemented.");
392 					break;
393 
394 				case TYPE_I:
395 					reply(200, "Type set to I.");
396 					type = cmd_type;
397 					break;
398 
399 				case TYPE_L:
400 #if CHAR_BIT == 8
401 					if (cmd_bytesz == 8) {
402 						reply(200,
403 						    "Type set to L (byte size 8).");
404 						type = cmd_type;
405 					} else
406 						reply(504, "Byte size must be 8.");
407 #else /* CHAR_BIT == 8 */
408 					UNIMPLEMENTED for CHAR_BIT != 8
409 #endif /* CHAR_BIT == 8 */
410 				}
411 			}
412 		}
413 	| STRU check_login SP struct_code CRLF
414 		{
415 			if ($2) {
416 				switch ($4) {
417 
418 				case STRU_F:
419 					reply(200, "STRU F accepted.");
420 					break;
421 
422 				default:
423 					reply(504, "Unimplemented STRU type.");
424 				}
425 			}
426 		}
427 	| MODE check_login SP mode_code CRLF
428 		{
429 			if ($2) {
430 				switch ($4) {
431 
432 				case MODE_S:
433 					reply(200, "MODE S accepted.");
434 					break;
435 
436 				default:
437 					reply(502, "Unimplemented MODE type.");
438 				}
439 			}
440 		}
441 	| ALLO check_login SP NUMBER CRLF
442 		{
443 			if ($2) {
444 				reply(202, "ALLO command ignored.");
445 			}
446 		}
447 	| ALLO check_login SP NUMBER SP R SP NUMBER CRLF
448 		{
449 			if ($2) {
450 				reply(202, "ALLO command ignored.");
451 			}
452 		}
453 	| RETR check_login SP pathname CRLF
454 		{
455 			if (noretr || (guest && noguestretr))
456 				reply(500, "RETR command disabled.");
457 			else if ($2 && $4 != NULL)
458 				retrieve(NULL, $4);
459 
460 			if ($4 != NULL)
461 				free($4);
462 		}
463 	| STOR check_login_ro SP pathname CRLF
464 		{
465 			if ($2 && $4 != NULL)
466 				store($4, "w", 0);
467 			if ($4 != NULL)
468 				free($4);
469 		}
470 	| APPE check_login_ro SP pathname CRLF
471 		{
472 			if ($2 && $4 != NULL)
473 				store($4, "a", 0);
474 			if ($4 != NULL)
475 				free($4);
476 		}
477 	| NLST check_login CRLF
478 		{
479 			if ($2)
480 				send_file_list(".");
481 		}
482 	| NLST check_login SP pathstring CRLF
483 		{
484 			if ($2)
485 				send_file_list($4);
486 			free($4);
487 		}
488 	| LIST check_login CRLF
489 		{
490 			if ($2)
491 				retrieve(_PATH_LS " -lgA", "");
492 		}
493 	| LIST check_login SP pathstring CRLF
494 		{
495 			if ($2)
496 				retrieve(_PATH_LS " -lgA %s", $4);
497 			free($4);
498 		}
499 	| STAT check_login SP pathname CRLF
500 		{
501 			if ($2 && $4 != NULL)
502 				statfilecmd($4);
503 			if ($4 != NULL)
504 				free($4);
505 		}
506 	| STAT check_login CRLF
507 		{
508 			if ($2) {
509 				statcmd();
510 			}
511 		}
512 	| DELE check_login_ro SP pathname CRLF
513 		{
514 			if ($2 && $4 != NULL)
515 				delete($4);
516 			if ($4 != NULL)
517 				free($4);
518 		}
519 	| RNTO check_login_ro SP pathname CRLF
520 		{
521 			if ($2 && $4 != NULL) {
522 				if (fromname) {
523 					renamecmd(fromname, $4);
524 					free(fromname);
525 					fromname = NULL;
526 				} else {
527 					reply(503, "Bad sequence of commands.");
528 				}
529 			}
530 			if ($4 != NULL)
531 				free($4);
532 		}
533 	| ABOR check_login CRLF
534 		{
535 			if ($2)
536 				reply(225, "ABOR command successful.");
537 		}
538 	| CWD check_login CRLF
539 		{
540 			if ($2) {
541 				cwd(homedir);
542 			}
543 		}
544 	| CWD check_login SP pathname CRLF
545 		{
546 			if ($2 && $4 != NULL)
547 				cwd($4);
548 			if ($4 != NULL)
549 				free($4);
550 		}
551 	| HELP CRLF
552 		{
553 			help(cmdtab, NULL);
554 		}
555 	| HELP SP STRING CRLF
556 		{
557 			char *cp = $3;
558 
559 			if (strncasecmp(cp, "SITE", 4) == 0) {
560 				cp = $3 + 4;
561 				if (*cp == ' ')
562 					cp++;
563 				if (*cp)
564 					help(sitetab, cp);
565 				else
566 					help(sitetab, NULL);
567 			} else
568 				help(cmdtab, $3);
569 			free($3);
570 		}
571 	| NOOP CRLF
572 		{
573 			reply(200, "NOOP command successful.");
574 		}
575 	| MKD check_login_ro SP pathname CRLF
576 		{
577 			if ($2 && $4 != NULL)
578 				makedir($4);
579 			if ($4 != NULL)
580 				free($4);
581 		}
582 	| RMD check_login_ro SP pathname CRLF
583 		{
584 			if ($2 && $4 != NULL)
585 				removedir($4);
586 			if ($4 != NULL)
587 				free($4);
588 		}
589 	| PWD check_login CRLF
590 		{
591 			if ($2)
592 				pwd();
593 		}
594 	| CDUP check_login CRLF
595 		{
596 			if ($2)
597 				cwd("..");
598 		}
599 	| SITE SP HELP CRLF
600 		{
601 			help(sitetab, NULL);
602 		}
603 	| SITE SP HELP SP STRING CRLF
604 		{
605 			help(sitetab, $5);
606 			free($5);
607 		}
608 	| SITE SP MDFIVE check_login SP pathname CRLF
609 		{
610 			char p[64], *q;
611 
612 			if ($4 && $6) {
613 				q = MD5File($6, p);
614 				if (q != NULL)
615 					reply(200, "MD5(%s) = %s", $6, p);
616 				else
617 					perror_reply(550, $6);
618 			}
619 			if ($6)
620 				free($6);
621 		}
622 	| SITE SP UMASK check_login CRLF
623 		{
624 			int oldmask;
625 
626 			if ($4) {
627 				oldmask = umask(0);
628 				(void) umask(oldmask);
629 				reply(200, "Current UMASK is %03o.", oldmask);
630 			}
631 		}
632 	| SITE SP UMASK check_login SP octal_number CRLF
633 		{
634 			int oldmask;
635 
636 			if ($4) {
637 				if (($6 == -1) || ($6 > 0777)) {
638 					reply(501, "Bad UMASK value.");
639 				} else {
640 					oldmask = umask($6);
641 					reply(200,
642 					    "UMASK set to %03o (was %03o).",
643 					    $6, oldmask);
644 				}
645 			}
646 		}
647 	| SITE SP CHMOD check_login_ro SP octal_number SP pathname CRLF
648 		{
649 			if ($4 && ($8 != NULL)) {
650 				if (($6 == -1 ) || ($6 > 0777))
651 					reply(501, "Bad mode value.");
652 				else if (chmod($8, $6) < 0)
653 					perror_reply(550, $8);
654 				else
655 					reply(200, "CHMOD command successful.");
656 			}
657 			if ($8 != NULL)
658 				free($8);
659 		}
660 	| SITE SP check_login IDLE CRLF
661 		{
662 			if ($3)
663 				reply(200,
664 			    	    "Current IDLE time limit is %d seconds; max %d.",
665 				    timeout, maxtimeout);
666 		}
667 	| SITE SP check_login IDLE SP NUMBER CRLF
668 		{
669 			if ($3) {
670 				if ($6.i < 30 || $6.i > maxtimeout) {
671 					reply(501,
672 					    "Maximum IDLE time must be between 30 and %d seconds.",
673 					    maxtimeout);
674 				} else {
675 					timeout = $6.i;
676 					(void) alarm(timeout);
677 					reply(200,
678 					    "Maximum IDLE time set to %d seconds.",
679 					    timeout);
680 				}
681 			}
682 		}
683 	| STOU check_login_ro SP pathname CRLF
684 		{
685 			if ($2 && $4 != NULL)
686 				store($4, "w", 1);
687 			if ($4 != NULL)
688 				free($4);
689 		}
690 	| SYST check_login CRLF
691 		{
692 			if ($2) {
693 				if (hostinfo)
694 #ifdef BSD
695 					reply(215, "UNIX Type: L%d Version: BSD-%d",
696 					      CHAR_BIT, BSD);
697 #else /* BSD */
698 					reply(215, "UNIX Type: L%d", CHAR_BIT);
699 #endif /* BSD */
700 				else
701 					reply(215, "UNKNOWN Type: L%d", CHAR_BIT);
702 			}
703 		}
704 
705 		/*
706 		 * SIZE is not in RFC959, but Postel has blessed it and
707 		 * it will be in the updated RFC.
708 		 *
709 		 * Return size of file in a format suitable for
710 		 * using with RESTART (we just count bytes).
711 		 */
712 	| SIZE check_login SP pathname CRLF
713 		{
714 			if ($2 && $4 != NULL)
715 				sizecmd($4);
716 			if ($4 != NULL)
717 				free($4);
718 		}
719 
720 		/*
721 		 * MDTM is not in RFC959, but Postel has blessed it and
722 		 * it will be in the updated RFC.
723 		 *
724 		 * Return modification time of file as an ISO 3307
725 		 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
726 		 * where xxx is the fractional second (of any precision,
727 		 * not necessarily 3 digits)
728 		 */
729 	| MDTM check_login SP pathname CRLF
730 		{
731 			if ($2 && $4 != NULL) {
732 				struct stat stbuf;
733 				if (stat($4, &stbuf) < 0)
734 					perror_reply(550, $4);
735 				else if (!S_ISREG(stbuf.st_mode)) {
736 					reply(550, "%s: not a plain file.", $4);
737 				} else {
738 					struct tm *t;
739 					t = gmtime(&stbuf.st_mtime);
740 					reply(213,
741 					    "%04d%02d%02d%02d%02d%02d",
742 					    1900 + t->tm_year,
743 					    t->tm_mon+1, t->tm_mday,
744 					    t->tm_hour, t->tm_min, t->tm_sec);
745 				}
746 			}
747 			if ($4 != NULL)
748 				free($4);
749 		}
750 	| QUIT CRLF
751 		{
752 			reply(221, "Goodbye.");
753 			dologout(0);
754 		}
755 	| NOTIMPL
756 		{
757 			nack($1);
758 		}
759 	| error
760 		{
761 			yyclearin;		/* discard lookahead data */
762 			yyerrok;		/* clear error condition */
763 			state = CMD;		/* reset lexer state */
764 		}
765 	;
766 rcmd
767 	: RNFR check_login_ro SP pathname CRLF
768 		{
769 			restart_point = 0;
770 			if ($2 && $4) {
771 				if (fromname)
772 					free(fromname);
773 				fromname = NULL;
774 				if (renamefrom($4))
775 					fromname = $4;
776 				else
777 					free($4);
778 			} else if ($4) {
779 				free($4);
780 			}
781 		}
782 	| REST check_login SP NUMBER CRLF
783 		{
784 			if ($2) {
785 				if (fromname)
786 					free(fromname);
787 				fromname = NULL;
788 				restart_point = $4.o;
789 				reply(350, "Restarting at %jd. %s",
790 				    (intmax_t)restart_point,
791 				    "Send STORE or RETRIEVE to initiate transfer.");
792 			}
793 		}
794 	;
795 
796 username
797 	: STRING
798 	;
799 
800 password
801 	: /* empty */
802 		{
803 			$$ = (char *)calloc(1, sizeof(char));
804 		}
805 	| STRING
806 	;
807 
808 byte_size
809 	: NUMBER
810 		{
811 			$$ = $1.i;
812 		}
813 	;
814 
815 host_port
816 	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
817 		NUMBER COMMA NUMBER
818 		{
819 			char *a, *p;
820 
821 			data_dest.su_len = sizeof(struct sockaddr_in);
822 			data_dest.su_family = AF_INET;
823 			p = (char *)&data_dest.su_sin.sin_port;
824 			p[0] = $9.i; p[1] = $11.i;
825 			a = (char *)&data_dest.su_sin.sin_addr;
826 			a[0] = $1.i; a[1] = $3.i; a[2] = $5.i; a[3] = $7.i;
827 		}
828 	;
829 
830 host_long_port
831 	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
832 		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
833 		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
834 		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
835 		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
836 		NUMBER
837 		{
838 			char *a, *p;
839 
840 			memset(&data_dest, 0, sizeof(data_dest));
841 			data_dest.su_len = sizeof(struct sockaddr_in6);
842 			data_dest.su_family = AF_INET6;
843 			p = (char *)&data_dest.su_port;
844 			p[0] = $39.i; p[1] = $41.i;
845 			a = (char *)&data_dest.su_sin6.sin6_addr;
846 			a[0] = $5.i; a[1] = $7.i; a[2] = $9.i; a[3] = $11.i;
847 			a[4] = $13.i; a[5] = $15.i; a[6] = $17.i; a[7] = $19.i;
848 			a[8] = $21.i; a[9] = $23.i; a[10] = $25.i; a[11] = $27.i;
849 			a[12] = $29.i; a[13] = $31.i; a[14] = $33.i; a[15] = $35.i;
850 			if (his_addr.su_family == AF_INET6) {
851 				/* XXX more sanity checks! */
852 				data_dest.su_sin6.sin6_scope_id =
853 					his_addr.su_sin6.sin6_scope_id;
854 			}
855 			if ($1.i != 6 || $3.i != 16 || $37.i != 2)
856 				memset(&data_dest, 0, sizeof(data_dest));
857 		}
858 	| NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
859 		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
860 		NUMBER
861 		{
862 			char *a, *p;
863 
864 			memset(&data_dest, 0, sizeof(data_dest));
865 			data_dest.su_sin.sin_len = sizeof(struct sockaddr_in);
866 			data_dest.su_family = AF_INET;
867 			p = (char *)&data_dest.su_port;
868 			p[0] = $15.i; p[1] = $17.i;
869 			a = (char *)&data_dest.su_sin.sin_addr;
870 			a[0] =  $5.i; a[1] = $7.i; a[2] = $9.i; a[3] = $11.i;
871 			if ($1.i != 4 || $3.i != 4 || $13.i != 2)
872 				memset(&data_dest, 0, sizeof(data_dest));
873 		}
874 	;
875 
876 form_code
877 	: N
878 		{
879 			$$ = FORM_N;
880 		}
881 	| T
882 		{
883 			$$ = FORM_T;
884 		}
885 	| C
886 		{
887 			$$ = FORM_C;
888 		}
889 	;
890 
891 type_code
892 	: A
893 		{
894 			cmd_type = TYPE_A;
895 			cmd_form = FORM_N;
896 		}
897 	| A SP form_code
898 		{
899 			cmd_type = TYPE_A;
900 			cmd_form = $3;
901 		}
902 	| E
903 		{
904 			cmd_type = TYPE_E;
905 			cmd_form = FORM_N;
906 		}
907 	| E SP form_code
908 		{
909 			cmd_type = TYPE_E;
910 			cmd_form = $3;
911 		}
912 	| I
913 		{
914 			cmd_type = TYPE_I;
915 		}
916 	| L
917 		{
918 			cmd_type = TYPE_L;
919 			cmd_bytesz = CHAR_BIT;
920 		}
921 	| L SP byte_size
922 		{
923 			cmd_type = TYPE_L;
924 			cmd_bytesz = $3;
925 		}
926 		/* this is for a bug in the BBN ftp */
927 	| L byte_size
928 		{
929 			cmd_type = TYPE_L;
930 			cmd_bytesz = $2;
931 		}
932 	;
933 
934 struct_code
935 	: F
936 		{
937 			$$ = STRU_F;
938 		}
939 	| R
940 		{
941 			$$ = STRU_R;
942 		}
943 	| P
944 		{
945 			$$ = STRU_P;
946 		}
947 	;
948 
949 mode_code
950 	: S
951 		{
952 			$$ = MODE_S;
953 		}
954 	| B
955 		{
956 			$$ = MODE_B;
957 		}
958 	| C
959 		{
960 			$$ = MODE_C;
961 		}
962 	;
963 
964 pathname
965 	: pathstring
966 		{
967 			if (logged_in && $1) {
968 				char *p;
969 
970 				/*
971 				 * Expand ~user manually since glob(3)
972 				 * will return the unexpanded pathname
973 				 * if the corresponding file/directory
974 				 * doesn't exist yet.  Using sole glob(3)
975 				 * would break natural commands like
976 				 * MKD ~user/newdir
977 				 * or
978 				 * RNTO ~/newfile
979 				 */
980 				if ((p = exptilde($1)) != NULL) {
981 					$$ = expglob(p);
982 					free(p);
983 				} else
984 					$$ = NULL;
985 				free($1);
986 			} else
987 				$$ = $1;
988 		}
989 	;
990 
991 pathstring
992 	: STRING
993 	;
994 
995 octal_number
996 	: NUMBER
997 		{
998 			int ret, dec, multby, digit;
999 
1000 			/*
1001 			 * Convert a number that was read as decimal number
1002 			 * to what it would be if it had been read as octal.
1003 			 */
1004 			dec = $1.i;
1005 			multby = 1;
1006 			ret = 0;
1007 			while (dec) {
1008 				digit = dec%10;
1009 				if (digit > 7) {
1010 					ret = -1;
1011 					break;
1012 				}
1013 				ret += digit * multby;
1014 				multby *= 8;
1015 				dec /= 10;
1016 			}
1017 			$$ = ret;
1018 		}
1019 	;
1020 
1021 
1022 check_login
1023 	: /* empty */
1024 		{
1025 		$$ = check_login1();
1026 		}
1027 	;
1028 
1029 check_login_epsv
1030 	: /* empty */
1031 		{
1032 		if (noepsv) {
1033 			reply(500, "EPSV command disabled.");
1034 			$$ = 0;
1035 		}
1036 		else
1037 			$$ = check_login1();
1038 		}
1039 	;
1040 
1041 check_login_ro
1042 	: /* empty */
1043 		{
1044 		if (readonly) {
1045 			reply(550, "Permission denied.");
1046 			$$ = 0;
1047 		}
1048 		else
1049 			$$ = check_login1();
1050 		}
1051 	;
1052 
1053 %%
1054 
1055 #define	CMD	0	/* beginning of command */
1056 #define	ARGS	1	/* expect miscellaneous arguments */
1057 #define	STR1	2	/* expect SP followed by STRING */
1058 #define	STR2	3	/* expect STRING */
1059 #define	OSTR	4	/* optional SP then STRING */
1060 #define	ZSTR1	5	/* optional SP then optional STRING */
1061 #define	ZSTR2	6	/* optional STRING after SP */
1062 #define	SITECMD	7	/* SITE command */
1063 #define	NSTR	8	/* Number followed by a string */
1064 
1065 #define	MAXGLOBARGS	1000
1066 
1067 #define	MAXASIZE	10240	/* Deny ASCII SIZE on files larger than that */
1068 
1069 struct tab {
1070 	char	*name;
1071 	short	token;
1072 	short	state;
1073 	short	implemented;	/* 1 if command is implemented */
1074 	char	*help;
1075 };
1076 
1077 struct tab cmdtab[] = {		/* In order defined in RFC 765 */
1078 	{ "USER", USER, STR1, 1,	"<sp> username" },
1079 	{ "PASS", PASS, ZSTR1, 1,	"[<sp> [password]]" },
1080 	{ "ACCT", ACCT, STR1, 0,	"(specify account)" },
1081 	{ "SMNT", SMNT, ARGS, 0,	"(structure mount)" },
1082 	{ "REIN", REIN, ARGS, 0,	"(reinitialize server state)" },
1083 	{ "QUIT", QUIT, ARGS, 1,	"(terminate service)", },
1084 	{ "PORT", PORT, ARGS, 1,	"<sp> b0, b1, b2, b3, b4, b5" },
1085 	{ "LPRT", LPRT, ARGS, 1,	"<sp> af, hal, h1, h2, h3,..., pal, p1, p2..." },
1086 	{ "EPRT", EPRT, STR1, 1,	"<sp> |af|addr|port|" },
1087 	{ "PASV", PASV, ARGS, 1,	"(set server in passive mode)" },
1088 	{ "LPSV", LPSV, ARGS, 1,	"(set server in passive mode)" },
1089 	{ "EPSV", EPSV, ARGS, 1,	"[<sp> af|ALL]" },
1090 	{ "TYPE", TYPE, ARGS, 1,	"<sp> { A | E | I | L }" },
1091 	{ "STRU", STRU, ARGS, 1,	"(specify file structure)" },
1092 	{ "MODE", MODE, ARGS, 1,	"(specify transfer mode)" },
1093 	{ "RETR", RETR, STR1, 1,	"<sp> file-name" },
1094 	{ "STOR", STOR, STR1, 1,	"<sp> file-name" },
1095 	{ "APPE", APPE, STR1, 1,	"<sp> file-name" },
1096 	{ "MLFL", MLFL, OSTR, 0,	"(mail file)" },
1097 	{ "MAIL", MAIL, OSTR, 0,	"(mail to user)" },
1098 	{ "MSND", MSND, OSTR, 0,	"(mail send to terminal)" },
1099 	{ "MSOM", MSOM, OSTR, 0,	"(mail send to terminal or mailbox)" },
1100 	{ "MSAM", MSAM, OSTR, 0,	"(mail send to terminal and mailbox)" },
1101 	{ "MRSQ", MRSQ, OSTR, 0,	"(mail recipient scheme question)" },
1102 	{ "MRCP", MRCP, STR1, 0,	"(mail recipient)" },
1103 	{ "ALLO", ALLO, ARGS, 1,	"allocate storage (vacuously)" },
1104 	{ "REST", REST, ARGS, 1,	"<sp> offset (restart command)" },
1105 	{ "RNFR", RNFR, STR1, 1,	"<sp> file-name" },
1106 	{ "RNTO", RNTO, STR1, 1,	"<sp> file-name" },
1107 	{ "ABOR", ABOR, ARGS, 1,	"(abort operation)" },
1108 	{ "DELE", DELE, STR1, 1,	"<sp> file-name" },
1109 	{ "CWD",  CWD,  OSTR, 1,	"[ <sp> directory-name ]" },
1110 	{ "XCWD", CWD,	OSTR, 1,	"[ <sp> directory-name ]" },
1111 	{ "LIST", LIST, OSTR, 1,	"[ <sp> path-name ]" },
1112 	{ "NLST", NLST, OSTR, 1,	"[ <sp> path-name ]" },
1113 	{ "SITE", SITE, SITECMD, 1,	"site-cmd [ <sp> arguments ]" },
1114 	{ "SYST", SYST, ARGS, 1,	"(get type of operating system)" },
1115 	{ "STAT", STAT, OSTR, 1,	"[ <sp> path-name ]" },
1116 	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
1117 	{ "NOOP", NOOP, ARGS, 1,	"" },
1118 	{ "MKD",  MKD,  STR1, 1,	"<sp> path-name" },
1119 	{ "XMKD", MKD,  STR1, 1,	"<sp> path-name" },
1120 	{ "RMD",  RMD,  STR1, 1,	"<sp> path-name" },
1121 	{ "XRMD", RMD,  STR1, 1,	"<sp> path-name" },
1122 	{ "PWD",  PWD,  ARGS, 1,	"(return current directory)" },
1123 	{ "XPWD", PWD,  ARGS, 1,	"(return current directory)" },
1124 	{ "CDUP", CDUP, ARGS, 1,	"(change to parent directory)" },
1125 	{ "XCUP", CDUP, ARGS, 1,	"(change to parent directory)" },
1126 	{ "STOU", STOU, STR1, 1,	"<sp> file-name" },
1127 	{ "SIZE", SIZE, OSTR, 1,	"<sp> path-name" },
1128 	{ "MDTM", MDTM, OSTR, 1,	"<sp> path-name" },
1129 	{ NULL,   0,    0,    0,	0 }
1130 };
1131 
1132 struct tab sitetab[] = {
1133 	{ "MD5", MDFIVE, STR1, 1,	"[ <sp> file-name ]" },
1134 	{ "UMASK", UMASK, ARGS, 1,	"[ <sp> umask ]" },
1135 	{ "IDLE", IDLE, ARGS, 1,	"[ <sp> maximum-idle-time ]" },
1136 	{ "CHMOD", CHMOD, NSTR, 1,	"<sp> mode <sp> file-name" },
1137 	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
1138 	{ NULL,   0,    0,    0,	0 }
1139 };
1140 
1141 static char	*copy(char *);
1142 static char	*expglob(char *);
1143 static char	*exptilde(char *);
1144 static void	 help(struct tab *, char *);
1145 static struct tab *
1146 		 lookup(struct tab *, char *);
1147 static int	 port_check(const char *);
1148 #ifdef INET6
1149 static int	 port_check_v6(const char *);
1150 #endif
1151 static void	 sizecmd(char *);
1152 static void	 toolong(int);
1153 #ifdef INET6
1154 static void	 v4map_data_dest(void);
1155 #endif
1156 static int	 yylex(void);
1157 
1158 static struct tab *
1159 lookup(struct tab *p, char *cmd)
1160 {
1161 
1162 	for (; p->name != NULL; p++)
1163 		if (strcmp(cmd, p->name) == 0)
1164 			return (p);
1165 	return (0);
1166 }
1167 
1168 #include <arpa/telnet.h>
1169 
1170 /*
1171  * getline - a hacked up version of fgets to ignore TELNET escape codes.
1172  */
1173 char *
1174 getline(char *s, int n, FILE *iop)
1175 {
1176 	int c;
1177 	register char *cs;
1178 	sigset_t sset, osset;
1179 
1180 	cs = s;
1181 /* tmpline may contain saved command from urgent mode interruption */
1182 	for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
1183 		*cs++ = tmpline[c];
1184 		if (tmpline[c] == '\n') {
1185 			*cs++ = '\0';
1186 			if (ftpdebug)
1187 				syslog(LOG_DEBUG, "command: %s", s);
1188 			tmpline[0] = '\0';
1189 			return(s);
1190 		}
1191 		if (c == 0)
1192 			tmpline[0] = '\0';
1193 	}
1194 	/* SIGURG would interrupt stdio if not blocked during the read loop */
1195 	sigemptyset(&sset);
1196 	sigaddset(&sset, SIGURG);
1197 	sigprocmask(SIG_BLOCK, &sset, &osset);
1198 	while ((c = getc(iop)) != EOF) {
1199 		c &= 0377;
1200 		if (c == IAC) {
1201 			if ((c = getc(iop)) == EOF)
1202 				goto got_eof;
1203 			c &= 0377;
1204 			switch (c) {
1205 			case WILL:
1206 			case WONT:
1207 				if ((c = getc(iop)) == EOF)
1208 					goto got_eof;
1209 				printf("%c%c%c", IAC, DONT, 0377&c);
1210 				(void) fflush(stdout);
1211 				continue;
1212 			case DO:
1213 			case DONT:
1214 				if ((c = getc(iop)) == EOF)
1215 					goto got_eof;
1216 				printf("%c%c%c", IAC, WONT, 0377&c);
1217 				(void) fflush(stdout);
1218 				continue;
1219 			case IAC:
1220 				break;
1221 			default:
1222 				continue;	/* ignore command */
1223 			}
1224 		}
1225 		*cs++ = c;
1226 		if (--n <= 0 || c == '\n')
1227 			break;
1228 	}
1229 got_eof:
1230 	sigprocmask(SIG_SETMASK, &osset, NULL);
1231 	if (c == EOF && cs == s)
1232 		return (NULL);
1233 	*cs++ = '\0';
1234 	if (ftpdebug) {
1235 		if (!guest && strncasecmp("pass ", s, 5) == 0) {
1236 			/* Don't syslog passwords */
1237 			syslog(LOG_DEBUG, "command: %.5s ???", s);
1238 		} else {
1239 			register char *cp;
1240 			register int len;
1241 
1242 			/* Don't syslog trailing CR-LF */
1243 			len = strlen(s);
1244 			cp = s + len - 1;
1245 			while (cp >= s && (*cp == '\n' || *cp == '\r')) {
1246 				--cp;
1247 				--len;
1248 			}
1249 			syslog(LOG_DEBUG, "command: %.*s", len, s);
1250 		}
1251 	}
1252 	return (s);
1253 }
1254 
1255 static void
1256 toolong(int signo)
1257 {
1258 
1259 	reply(421,
1260 	    "Timeout (%d seconds): closing control connection.", timeout);
1261 	if (logging)
1262 		syslog(LOG_INFO, "User %s timed out after %d seconds",
1263 		    (pw ? pw -> pw_name : "unknown"), timeout);
1264 	dologout(1);
1265 }
1266 
1267 static int
1268 yylex(void)
1269 {
1270 	static int cpos;
1271 	char *cp, *cp2;
1272 	struct tab *p;
1273 	int n;
1274 	char c;
1275 
1276 	for (;;) {
1277 		switch (state) {
1278 
1279 		case CMD:
1280 			(void) signal(SIGALRM, toolong);
1281 			(void) alarm(timeout);
1282 			if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
1283 				reply(221, "You could at least say goodbye.");
1284 				dologout(0);
1285 			}
1286 			(void) alarm(0);
1287 #ifdef SETPROCTITLE
1288 			if (strncasecmp(cbuf, "PASS", 4) != 0)
1289 				setproctitle("%s: %s", proctitle, cbuf);
1290 #endif /* SETPROCTITLE */
1291 			if ((cp = strchr(cbuf, '\r'))) {
1292 				*cp++ = '\n';
1293 				*cp = '\0';
1294 			}
1295 			if ((cp = strpbrk(cbuf, " \n")))
1296 				cpos = cp - cbuf;
1297 			if (cpos == 0)
1298 				cpos = 4;
1299 			c = cbuf[cpos];
1300 			cbuf[cpos] = '\0';
1301 			upper(cbuf);
1302 			p = lookup(cmdtab, cbuf);
1303 			cbuf[cpos] = c;
1304 			if (p != 0) {
1305 				yylval.s = p->name;
1306 				if (!p->implemented)
1307 					return (NOTIMPL); /* state remains CMD */
1308 				state = p->state;
1309 				return (p->token);
1310 			}
1311 			break;
1312 
1313 		case SITECMD:
1314 			if (cbuf[cpos] == ' ') {
1315 				cpos++;
1316 				return (SP);
1317 			}
1318 			cp = &cbuf[cpos];
1319 			if ((cp2 = strpbrk(cp, " \n")))
1320 				cpos = cp2 - cbuf;
1321 			c = cbuf[cpos];
1322 			cbuf[cpos] = '\0';
1323 			upper(cp);
1324 			p = lookup(sitetab, cp);
1325 			cbuf[cpos] = c;
1326 			if (guest == 0 && p != 0) {
1327 				yylval.s = p->name;
1328 				if (!p->implemented) {
1329 					state = CMD;
1330 					return (NOTIMPL);
1331 				}
1332 				state = p->state;
1333 				return (p->token);
1334 			}
1335 			state = CMD;
1336 			break;
1337 
1338 		case ZSTR1:
1339 		case OSTR:
1340 			if (cbuf[cpos] == '\n') {
1341 				state = CMD;
1342 				return (CRLF);
1343 			}
1344 			/* FALLTHROUGH */
1345 
1346 		case STR1:
1347 		dostr1:
1348 			if (cbuf[cpos] == ' ') {
1349 				cpos++;
1350 				state = state == OSTR ? STR2 : state+1;
1351 				return (SP);
1352 			}
1353 			break;
1354 
1355 		case ZSTR2:
1356 			if (cbuf[cpos] == '\n') {
1357 				state = CMD;
1358 				return (CRLF);
1359 			}
1360 			/* FALLTHROUGH */
1361 
1362 		case STR2:
1363 			cp = &cbuf[cpos];
1364 			n = strlen(cp);
1365 			cpos += n - 1;
1366 			/*
1367 			 * Make sure the string is nonempty and \n terminated.
1368 			 */
1369 			if (n > 1 && cbuf[cpos] == '\n') {
1370 				cbuf[cpos] = '\0';
1371 				yylval.s = copy(cp);
1372 				cbuf[cpos] = '\n';
1373 				state = ARGS;
1374 				return (STRING);
1375 			}
1376 			break;
1377 
1378 		case NSTR:
1379 			if (cbuf[cpos] == ' ') {
1380 				cpos++;
1381 				return (SP);
1382 			}
1383 			if (isdigit(cbuf[cpos])) {
1384 				cp = &cbuf[cpos];
1385 				while (isdigit(cbuf[++cpos]))
1386 					;
1387 				c = cbuf[cpos];
1388 				cbuf[cpos] = '\0';
1389 				yylval.u.i = atoi(cp);
1390 				cbuf[cpos] = c;
1391 				state = STR1;
1392 				return (NUMBER);
1393 			}
1394 			state = STR1;
1395 			goto dostr1;
1396 
1397 		case ARGS:
1398 			if (isdigit(cbuf[cpos])) {
1399 				cp = &cbuf[cpos];
1400 				while (isdigit(cbuf[++cpos]))
1401 					;
1402 				c = cbuf[cpos];
1403 				cbuf[cpos] = '\0';
1404 				yylval.u.i = atoi(cp);
1405 				yylval.u.o = strtoull(cp, NULL, 10);
1406 				cbuf[cpos] = c;
1407 				return (NUMBER);
1408 			}
1409 			if (strncasecmp(&cbuf[cpos], "ALL", 3) == 0
1410 			 && !isalnum(cbuf[cpos + 3])) {
1411 				cpos += 3;
1412 				return ALL;
1413 			}
1414 			switch (cbuf[cpos++]) {
1415 
1416 			case '\n':
1417 				state = CMD;
1418 				return (CRLF);
1419 
1420 			case ' ':
1421 				return (SP);
1422 
1423 			case ',':
1424 				return (COMMA);
1425 
1426 			case 'A':
1427 			case 'a':
1428 				return (A);
1429 
1430 			case 'B':
1431 			case 'b':
1432 				return (B);
1433 
1434 			case 'C':
1435 			case 'c':
1436 				return (C);
1437 
1438 			case 'E':
1439 			case 'e':
1440 				return (E);
1441 
1442 			case 'F':
1443 			case 'f':
1444 				return (F);
1445 
1446 			case 'I':
1447 			case 'i':
1448 				return (I);
1449 
1450 			case 'L':
1451 			case 'l':
1452 				return (L);
1453 
1454 			case 'N':
1455 			case 'n':
1456 				return (N);
1457 
1458 			case 'P':
1459 			case 'p':
1460 				return (P);
1461 
1462 			case 'R':
1463 			case 'r':
1464 				return (R);
1465 
1466 			case 'S':
1467 			case 's':
1468 				return (S);
1469 
1470 			case 'T':
1471 			case 't':
1472 				return (T);
1473 
1474 			}
1475 			break;
1476 
1477 		default:
1478 			fatalerror("Unknown state in scanner.");
1479 		}
1480 		state = CMD;
1481 		return (LEXERR);
1482 	}
1483 }
1484 
1485 void
1486 upper(char *s)
1487 {
1488 	while (*s != '\0') {
1489 		if (islower(*s))
1490 			*s = toupper(*s);
1491 		s++;
1492 	}
1493 }
1494 
1495 static char *
1496 copy(char *s)
1497 {
1498 	char *p;
1499 
1500 	p = malloc(strlen(s) + 1);
1501 	if (p == NULL)
1502 		fatalerror("Ran out of memory.");
1503 	(void) strcpy(p, s);
1504 	return (p);
1505 }
1506 
1507 static void
1508 help(struct tab *ctab, char *s)
1509 {
1510 	struct tab *c;
1511 	int width, NCMDS;
1512 	char *type;
1513 
1514 	if (ctab == sitetab)
1515 		type = "SITE ";
1516 	else
1517 		type = "";
1518 	width = 0, NCMDS = 0;
1519 	for (c = ctab; c->name != NULL; c++) {
1520 		int len = strlen(c->name);
1521 
1522 		if (len > width)
1523 			width = len;
1524 		NCMDS++;
1525 	}
1526 	width = (width + 8) &~ 7;
1527 	if (s == 0) {
1528 		int i, j, w;
1529 		int columns, lines;
1530 
1531 		lreply(214, "The following %scommands are recognized %s.",
1532 		    type, "(* =>'s unimplemented)");
1533 		columns = 76 / width;
1534 		if (columns == 0)
1535 			columns = 1;
1536 		lines = (NCMDS + columns - 1) / columns;
1537 		for (i = 0; i < lines; i++) {
1538 			printf("   ");
1539 			for (j = 0; j < columns; j++) {
1540 				c = ctab + j * lines + i;
1541 				printf("%s%c", c->name,
1542 					c->implemented ? ' ' : '*');
1543 				if (c + lines >= &ctab[NCMDS])
1544 					break;
1545 				w = strlen(c->name) + 1;
1546 				while (w < width) {
1547 					putchar(' ');
1548 					w++;
1549 				}
1550 			}
1551 			printf("\r\n");
1552 		}
1553 		(void) fflush(stdout);
1554 		if (hostinfo)
1555 			reply(214, "Direct comments to ftp-bugs@%s.", hostname);
1556 		else
1557 			reply(214, "End.");
1558 		return;
1559 	}
1560 	upper(s);
1561 	c = lookup(ctab, s);
1562 	if (c == NULL) {
1563 		reply(502, "Unknown command %s.", s);
1564 		return;
1565 	}
1566 	if (c->implemented)
1567 		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
1568 	else
1569 		reply(214, "%s%-*s\t%s; unimplemented.", type, width,
1570 		    c->name, c->help);
1571 }
1572 
1573 static void
1574 sizecmd(char *filename)
1575 {
1576 	switch (type) {
1577 	case TYPE_L:
1578 	case TYPE_I: {
1579 		struct stat stbuf;
1580 		if (stat(filename, &stbuf) < 0)
1581 			perror_reply(550, filename);
1582 		else if (!S_ISREG(stbuf.st_mode))
1583 			reply(550, "%s: not a plain file.", filename);
1584 		else
1585 			reply(213, "%jd", (intmax_t)stbuf.st_size);
1586 		break; }
1587 	case TYPE_A: {
1588 		FILE *fin;
1589 		int c;
1590 		off_t count;
1591 		struct stat stbuf;
1592 		fin = fopen(filename, "r");
1593 		if (fin == NULL) {
1594 			perror_reply(550, filename);
1595 			return;
1596 		}
1597 		if (fstat(fileno(fin), &stbuf) < 0) {
1598 			perror_reply(550, filename);
1599 			(void) fclose(fin);
1600 			return;
1601 		} else if (!S_ISREG(stbuf.st_mode)) {
1602 			reply(550, "%s: not a plain file.", filename);
1603 			(void) fclose(fin);
1604 			return;
1605 		} else if (stbuf.st_size > MAXASIZE) {
1606 			reply(550, "%s: too large for type A SIZE.", filename);
1607 			(void) fclose(fin);
1608 			return;
1609 		}
1610 
1611 		count = 0;
1612 		while((c=getc(fin)) != EOF) {
1613 			if (c == '\n')	/* will get expanded to \r\n */
1614 				count++;
1615 			count++;
1616 		}
1617 		(void) fclose(fin);
1618 
1619 		reply(213, "%jd", (intmax_t)count);
1620 		break; }
1621 	default:
1622 		reply(504, "SIZE not implemented for type %s.",
1623 		           typenames[type]);
1624 	}
1625 }
1626 
1627 /* Return 1, if port check is done. Return 0, if not yet. */
1628 static int
1629 port_check(const char *pcmd)
1630 {
1631 	if (his_addr.su_family == AF_INET) {
1632 		if (data_dest.su_family != AF_INET) {
1633 			usedefault = 1;
1634 			reply(500, "Invalid address rejected.");
1635 			return 1;
1636 		}
1637 		if (paranoid &&
1638 		    ((ntohs(data_dest.su_port) < IPPORT_RESERVED) ||
1639 		     memcmp(&data_dest.su_sin.sin_addr,
1640 			    &his_addr.su_sin.sin_addr,
1641 			    sizeof(data_dest.su_sin.sin_addr)))) {
1642 			usedefault = 1;
1643 			reply(500, "Illegal PORT range rejected.");
1644 		} else {
1645 			usedefault = 0;
1646 			if (pdata >= 0) {
1647 				(void) close(pdata);
1648 				pdata = -1;
1649 			}
1650 			reply(200, "%s command successful.", pcmd);
1651 		}
1652 		return 1;
1653 	}
1654 	return 0;
1655 }
1656 
1657 static int
1658 check_login1(void)
1659 {
1660 	if (logged_in)
1661 		return 1;
1662 	else {
1663 		reply(530, "Please login with USER and PASS.");
1664 		return 0;
1665 	}
1666 }
1667 
1668 /*
1669  * Replace leading "~user" in a pathname by the user's login directory.
1670  * Returned string will be in a freshly malloced buffer unless it's NULL.
1671  */
1672 static char *
1673 exptilde(char *s)
1674 {
1675 	char *p, *q;
1676 	char *path, *user;
1677 	struct passwd *ppw;
1678 
1679 	if ((p = strdup(s)) == NULL)
1680 		return (NULL);
1681 	if (*p != '~')
1682 		return (p);
1683 
1684 	user = p + 1;	/* skip tilde */
1685 	if ((path = strchr(p, '/')) != NULL)
1686 		*(path++) = '\0'; /* separate ~user from the rest of path */
1687 	if (*user == '\0') /* no user specified, use the current user */
1688 		user = pw->pw_name;
1689 	/* read passwd even for the current user since we may be chrooted */
1690 	if ((ppw = getpwnam(user)) != NULL) {
1691 		/* user found, substitute login directory for ~user */
1692 		if (path)
1693 			asprintf(&q, "%s/%s", ppw->pw_dir, path);
1694 		else
1695 			q = strdup(ppw->pw_dir);
1696 		free(p);
1697 		p = q;
1698 	} else {
1699 		/* user not found, undo the damage */
1700 		if (path)
1701 			path[-1] = '/';
1702 	}
1703 	return (p);
1704 }
1705 
1706 /*
1707  * Expand glob(3) patterns possibly present in a pathname.
1708  * Avoid expanding to a pathname including '\r' or '\n' in order to
1709  * not disrupt the FTP protocol.
1710  * The expansion found must be unique.
1711  * Return the result as a malloced string, or NULL if an error occured.
1712  *
1713  * Problem: this production is used for all pathname
1714  * processing, but only gives a 550 error reply.
1715  * This is a valid reply in some cases but not in others.
1716  */
1717 static char *
1718 expglob(char *s)
1719 {
1720 	char *p, **pp, *rval;
1721 	int flags = GLOB_BRACE | GLOB_NOCHECK;
1722 	int n;
1723 	glob_t gl;
1724 
1725 	memset(&gl, 0, sizeof(gl));
1726 	flags |= GLOB_LIMIT;
1727 	gl.gl_matchc = MAXGLOBARGS;
1728 	if (glob(s, flags, NULL, &gl) == 0 && gl.gl_pathc != 0) {
1729 		for (pp = gl.gl_pathv, p = NULL, n = 0; *pp; pp++)
1730 			if (*(*pp + strcspn(*pp, "\r\n")) == '\0') {
1731 				p = *pp;
1732 				n++;
1733 			}
1734 		if (n == 0)
1735 			rval = strdup(s);
1736 		else if (n == 1)
1737 			rval = strdup(p);
1738 		else {
1739 			reply(550, "Wildcard is ambiguous.");
1740 			rval = NULL;
1741 		}
1742 	} else {
1743 		reply(550, "Wildcard expansion error.");
1744 		rval = NULL;
1745 	}
1746 	globfree(&gl);
1747 	return (rval);
1748 }
1749 
1750 #ifdef INET6
1751 /* Return 1, if port check is done. Return 0, if not yet. */
1752 static int
1753 port_check_v6(const char *pcmd)
1754 {
1755 	if (his_addr.su_family == AF_INET6) {
1756 		if (IN6_IS_ADDR_V4MAPPED(&his_addr.su_sin6.sin6_addr))
1757 			/* Convert data_dest into v4 mapped sockaddr.*/
1758 			v4map_data_dest();
1759 		if (data_dest.su_family != AF_INET6) {
1760 			usedefault = 1;
1761 			reply(500, "Invalid address rejected.");
1762 			return 1;
1763 		}
1764 		if (paranoid &&
1765 		    ((ntohs(data_dest.su_port) < IPPORT_RESERVED) ||
1766 		     memcmp(&data_dest.su_sin6.sin6_addr,
1767 			    &his_addr.su_sin6.sin6_addr,
1768 			    sizeof(data_dest.su_sin6.sin6_addr)))) {
1769 			usedefault = 1;
1770 			reply(500, "Illegal PORT range rejected.");
1771 		} else {
1772 			usedefault = 0;
1773 			if (pdata >= 0) {
1774 				(void) close(pdata);
1775 				pdata = -1;
1776 			}
1777 			reply(200, "%s command successful.", pcmd);
1778 		}
1779 		return 1;
1780 	}
1781 	return 0;
1782 }
1783 
1784 static void
1785 v4map_data_dest(void)
1786 {
1787 	struct in_addr savedaddr;
1788 	int savedport;
1789 
1790 	if (data_dest.su_family != AF_INET) {
1791 		usedefault = 1;
1792 		reply(500, "Invalid address rejected.");
1793 		return;
1794 	}
1795 
1796 	savedaddr = data_dest.su_sin.sin_addr;
1797 	savedport = data_dest.su_port;
1798 
1799 	memset(&data_dest, 0, sizeof(data_dest));
1800 	data_dest.su_sin6.sin6_len = sizeof(struct sockaddr_in6);
1801 	data_dest.su_sin6.sin6_family = AF_INET6;
1802 	data_dest.su_sin6.sin6_port = savedport;
1803 	memset((caddr_t)&data_dest.su_sin6.sin6_addr.s6_addr[10], 0xff, 2);
1804 	memcpy((caddr_t)&data_dest.su_sin6.sin6_addr.s6_addr[12],
1805 	       (caddr_t)&savedaddr, sizeof(savedaddr));
1806 }
1807 #endif
1808