xref: /freebsd/libexec/ftpd/ftpcmd.y (revision a3e8fd0b7f663db7eafff527d5c3ca3bcfa8a537)
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 static const char rcsid[] =
48   "$FreeBSD$";
49 #endif /* not lint */
50 
51 #include <sys/param.h>
52 #include <sys/socket.h>
53 #include <sys/stat.h>
54 
55 #include <netinet/in.h>
56 #include <arpa/ftp.h>
57 
58 #include <ctype.h>
59 #include <errno.h>
60 #include <glob.h>
61 #include <libutil.h>
62 #include <limits.h>
63 #include <md5.h>
64 #include <netdb.h>
65 #include <pwd.h>
66 #include <signal.h>
67 #include <stdio.h>
68 #include <stdlib.h>
69 #include <string.h>
70 #include <syslog.h>
71 #include <time.h>
72 #include <unistd.h>
73 
74 #include "extern.h"
75 
76 extern	union sockunion data_dest, his_addr;
77 extern	int logged_in;
78 extern	struct passwd *pw;
79 extern	int guest;
80 extern 	int paranoid;
81 extern	int logging;
82 extern	int type;
83 extern	int form;
84 extern	int ftpdebug;
85 extern	int timeout;
86 extern	int maxtimeout;
87 extern  int pdata;
88 extern	char *hostname;
89 extern	char remotehost[];
90 extern	char proctitle[];
91 extern	int usedefault;
92 extern  int transflag;
93 extern  char tmpline[];
94 extern	int readonly;
95 extern	int noepsv;
96 extern	int noretr;
97 extern	int noguestretr;
98 extern	char *typenames[]; /* defined in <arpa/ftp.h> included from ftpd.c */
99 
100 off_t	restart_point;
101 
102 static	int cmd_type;
103 static	int cmd_form;
104 static	int cmd_bytesz;
105 static	int state;
106 char	cbuf[512];
107 char	*fromname = (char *) 0;
108 
109 extern int epsvall;
110 
111 %}
112 
113 %union {
114 	struct {
115 		off_t	o;
116 		int	i;
117 	} u;
118 	char   *s;
119 }
120 
121 %token
122 	A	B	C	E	F	I
123 	L	N	P	R	S	T
124 	ALL
125 
126 	SP	CRLF	COMMA
127 
128 	USER	PASS	ACCT	REIN	QUIT	PORT
129 	PASV	TYPE	STRU	MODE	RETR	STOR
130 	APPE	MLFL	MAIL	MSND	MSOM	MSAM
131 	MRSQ	MRCP	ALLO	REST	RNFR	RNTO
132 	ABOR	DELE	CWD	LIST	NLST	SITE
133 	STAT	HELP	NOOP	MKD	RMD	PWD
134 	CDUP	STOU	SMNT	SYST	SIZE	MDTM
135 	LPRT	LPSV	EPRT	EPSV
136 
137 	UMASK	IDLE	CHMOD	MDFIVE
138 
139 	LEXERR	NOTIMPL
140 
141 %token	<s> STRING
142 %token	<u> NUMBER
143 
144 %type	<u.i> check_login octal_number byte_size
145 %type	<u.i> check_login_ro check_login_epsv
146 %type	<u.i> struct_code mode_code type_code form_code
147 %type	<s> pathstring pathname password username
148 %type	<s> ALL NOTIMPL
149 
150 %start	cmd_list
151 
152 %%
153 
154 cmd_list
155 	: /* empty */
156 	| cmd_list cmd
157 		{
158 			if (fromname)
159 				free(fromname);
160 			fromname = (char *) 0;
161 			restart_point = (off_t) 0;
162 		}
163 	| cmd_list rcmd
164 	;
165 
166 cmd
167 	: USER SP username CRLF
168 		{
169 			user($3);
170 			free($3);
171 		}
172 	| PASS SP password CRLF
173 		{
174 			pass($3);
175 			free($3);
176 		}
177 	| PASS CRLF
178 		{
179 			pass("");
180 		}
181 	| PORT check_login SP host_port CRLF
182 		{
183 			if (epsvall) {
184 				reply(501, "no PORT allowed after EPSV ALL");
185 				goto port_done;
186 			}
187 			if (!$2)
188 				goto port_done;
189 			if (port_check("PORT") == 1)
190 				goto port_done;
191 #ifdef INET6
192 			if ((his_addr.su_family != AF_INET6 ||
193 			     !IN6_IS_ADDR_V4MAPPED(&his_addr.su_sin6.sin6_addr))) {
194 				/* shoud never happen */
195 				usedefault = 1;
196 				reply(500, "Invalid address rejected.");
197 				goto port_done;
198 			}
199 			port_check_v6("pcmd");
200 #endif
201 		port_done:
202 		}
203 	| LPRT check_login SP host_long_port CRLF
204 		{
205 			if (epsvall) {
206 				reply(501, "no LPRT allowed after EPSV ALL");
207 				goto lprt_done;
208 			}
209 			if (!$2)
210 				goto lprt_done;
211 			if (port_check("LPRT") == 1)
212 				goto lprt_done;
213 #ifdef INET6
214 			if (his_addr.su_family != AF_INET6) {
215 				usedefault = 1;
216 				reply(500, "Invalid address rejected.");
217 				goto lprt_done;
218 			}
219 			if (port_check_v6("LPRT") == 1)
220 				goto lprt_done;
221 #endif
222 		lprt_done:
223 		}
224 	| EPRT check_login SP STRING CRLF
225 		{
226 			char delim;
227 			char *tmp = NULL;
228 			char *p, *q;
229 			char *result[3];
230 			struct addrinfo hints;
231 			struct addrinfo *res;
232 			int i;
233 
234 			if (epsvall) {
235 				reply(501, "no EPRT allowed after EPSV ALL");
236 				goto eprt_done;
237 			}
238 			if (!$2)
239 				goto eprt_done;
240 
241 			memset(&data_dest, 0, sizeof(data_dest));
242 			tmp = strdup($4);
243 			if (ftpdebug)
244 				syslog(LOG_DEBUG, "%s", tmp);
245 			if (!tmp) {
246 				fatalerror("not enough core");
247 				/*NOTREACHED*/
248 			}
249 			p = tmp;
250 			delim = p[0];
251 			p++;
252 			memset(result, 0, sizeof(result));
253 			for (i = 0; i < 3; i++) {
254 				q = strchr(p, delim);
255 				if (!q || *q != delim) {
256 		parsefail:
257 					reply(500,
258 						"Invalid argument, rejected.");
259 					if (tmp)
260 						free(tmp);
261 					usedefault = 1;
262 					goto eprt_done;
263 				}
264 				*q++ = '\0';
265 				result[i] = p;
266 				if (ftpdebug)
267 					syslog(LOG_DEBUG, "%d: %s", i, p);
268 				p = q;
269 			}
270 
271 			/* some more sanity check */
272 			p = result[0];
273 			while (*p) {
274 				if (!isdigit(*p))
275 					goto parsefail;
276 				p++;
277 			}
278 			p = result[2];
279 			while (*p) {
280 				if (!isdigit(*p))
281 					goto parsefail;
282 				p++;
283 			}
284 
285 			/* grab address */
286 			memset(&hints, 0, sizeof(hints));
287 			if (atoi(result[0]) == 1)
288 				hints.ai_family = PF_INET;
289 #ifdef INET6
290 			else if (atoi(result[0]) == 2)
291 				hints.ai_family = PF_INET6;
292 #endif
293 			else
294 				hints.ai_family = PF_UNSPEC;	/*XXX*/
295 			hints.ai_socktype = SOCK_STREAM;
296 			i = getaddrinfo(result[1], result[2], &hints, &res);
297 			if (i)
298 				goto parsefail;
299 			memcpy(&data_dest, res->ai_addr, res->ai_addrlen);
300 #ifdef INET6
301 			if (his_addr.su_family == AF_INET6
302 			    && data_dest.su_family == AF_INET6) {
303 				/* XXX more sanity checks! */
304 				data_dest.su_sin6.sin6_scope_id =
305 					his_addr.su_sin6.sin6_scope_id;
306 			}
307 #endif
308 			free(tmp);
309 			tmp = NULL;
310 
311 			if (port_check("EPRT") == 1)
312 				goto eprt_done;
313 #ifdef INET6
314 			if (his_addr.su_family != AF_INET6) {
315 				usedefault = 1;
316 				reply(500, "Invalid address rejected.");
317 				goto eprt_done;
318 			}
319 			if (port_check_v6("EPRT") == 1)
320 				goto eprt_done;
321 #endif
322 		eprt_done:
323 			free($4);
324 		}
325 	| PASV check_login CRLF
326 		{
327 			if (epsvall)
328 				reply(501, "no PASV allowed after EPSV ALL");
329 			else if ($2)
330 				passive();
331 		}
332 	| LPSV check_login CRLF
333 		{
334 			if (epsvall)
335 				reply(501, "no LPSV allowed after EPSV ALL");
336 			else if ($2)
337 				long_passive("LPSV", PF_UNSPEC);
338 		}
339 	| EPSV check_login_epsv SP NUMBER CRLF
340 		{
341 			if ($2) {
342 				int pf;
343 				switch ($4.i) {
344 				case 1:
345 					pf = PF_INET;
346 					break;
347 #ifdef INET6
348 				case 2:
349 					pf = PF_INET6;
350 					break;
351 #endif
352 				default:
353 					pf = -1;	/*junk value*/
354 					break;
355 				}
356 				long_passive("EPSV", pf);
357 			}
358 		}
359 	| EPSV check_login_epsv SP ALL CRLF
360 		{
361 			if ($2) {
362 				reply(200,
363 				      "EPSV ALL command successful.");
364 				epsvall++;
365 			}
366 		}
367 	| EPSV check_login_epsv CRLF
368 		{
369 			if ($2)
370 				long_passive("EPSV", PF_UNSPEC);
371 		}
372 	| TYPE check_login SP type_code CRLF
373 		{
374 			if ($2) {
375 				switch (cmd_type) {
376 
377 				case TYPE_A:
378 					if (cmd_form == FORM_N) {
379 						reply(200, "Type set to A.");
380 						type = cmd_type;
381 						form = cmd_form;
382 					} else
383 						reply(504, "Form must be N.");
384 					break;
385 
386 				case TYPE_E:
387 					reply(504, "Type E not implemented.");
388 					break;
389 
390 				case TYPE_I:
391 					reply(200, "Type set to I.");
392 					type = cmd_type;
393 					break;
394 
395 				case TYPE_L:
396 #if CHAR_BIT == 8
397 					if (cmd_bytesz == 8) {
398 						reply(200,
399 						    "Type set to L (byte size 8).");
400 						type = cmd_type;
401 					} else
402 						reply(504, "Byte size must be 8.");
403 #else /* CHAR_BIT == 8 */
404 					UNIMPLEMENTED for CHAR_BIT != 8
405 #endif /* CHAR_BIT == 8 */
406 				}
407 			}
408 		}
409 	| STRU check_login SP struct_code CRLF
410 		{
411 			if ($2) {
412 				switch ($4) {
413 
414 				case STRU_F:
415 					reply(200, "STRU F ok.");
416 					break;
417 
418 				default:
419 					reply(504, "Unimplemented STRU type.");
420 				}
421 			}
422 		}
423 	| MODE check_login SP mode_code CRLF
424 		{
425 			if ($2) {
426 				switch ($4) {
427 
428 				case MODE_S:
429 					reply(200, "MODE S ok.");
430 					break;
431 
432 				default:
433 					reply(502, "Unimplemented MODE type.");
434 				}
435 			}
436 		}
437 	| ALLO check_login SP NUMBER CRLF
438 		{
439 			if ($2) {
440 				reply(202, "ALLO command ignored.");
441 			}
442 		}
443 	| ALLO check_login SP NUMBER SP R SP NUMBER CRLF
444 		{
445 			if ($2) {
446 				reply(202, "ALLO command ignored.");
447 			}
448 		}
449 	| RETR check_login SP pathname CRLF
450 		{
451 			if (noretr || (guest && noguestretr))
452 				reply(500, "RETR command is disabled");
453 			else if ($2 && $4 != NULL)
454 				retrieve((char *) 0, $4);
455 
456 			if ($4 != NULL)
457 				free($4);
458 		}
459 	| STOR check_login_ro SP pathname CRLF
460 		{
461 			if ($2 && $4 != NULL)
462 				store($4, "w", 0);
463 			if ($4 != NULL)
464 				free($4);
465 		}
466 	| APPE check_login_ro SP pathname CRLF
467 		{
468 			if ($2 && $4 != NULL)
469 				store($4, "a", 0);
470 			if ($4 != NULL)
471 				free($4);
472 		}
473 	| NLST check_login CRLF
474 		{
475 			if ($2)
476 				send_file_list(".");
477 		}
478 	| NLST check_login SP pathstring CRLF
479 		{
480 			if ($2)
481 				send_file_list($4);
482 			free($4);
483 		}
484 	| LIST check_login CRLF
485 		{
486 			if ($2)
487 				retrieve("/bin/ls -lgA", "");
488 		}
489 	| LIST check_login SP pathstring CRLF
490 		{
491 			if ($2)
492 				retrieve("/bin/ls -lgA %s", $4);
493 			free($4);
494 		}
495 	| STAT check_login SP pathname CRLF
496 		{
497 			if ($2 && $4 != NULL)
498 				statfilecmd($4);
499 			if ($4 != NULL)
500 				free($4);
501 		}
502 	| STAT check_login CRLF
503 		{
504 			if ($2) {
505 				statcmd();
506 			}
507 		}
508 	| DELE check_login_ro SP pathname CRLF
509 		{
510 			if ($2 && $4 != NULL)
511 				delete($4);
512 			if ($4 != NULL)
513 				free($4);
514 		}
515 	| RNTO check_login_ro SP pathname CRLF
516 		{
517 			if ($2 && $4 != NULL) {
518 				if (fromname) {
519 					renamecmd(fromname, $4);
520 					free(fromname);
521 					fromname = (char *) 0;
522 				} else {
523 					reply(503, "Bad sequence of commands.");
524 				}
525 			}
526 			if ($4 != NULL)
527 				free($4);
528 		}
529 	| ABOR check_login CRLF
530 		{
531 			if ($2)
532 				reply(225, "ABOR command successful.");
533 		}
534 	| CWD check_login CRLF
535 		{
536 			if ($2) {
537 				if (guest)
538 					cwd("/");
539 				else
540 					cwd(pw->pw_dir);
541 			}
542 		}
543 	| CWD check_login SP pathname CRLF
544 		{
545 			if ($2 && $4 != NULL)
546 				cwd($4);
547 			if ($4 != NULL)
548 				free($4);
549 		}
550 	| HELP CRLF
551 		{
552 			help(cmdtab, (char *) 0);
553 		}
554 	| HELP SP STRING CRLF
555 		{
556 			char *cp = $3;
557 
558 			if (strncasecmp(cp, "SITE", 4) == 0) {
559 				cp = $3 + 4;
560 				if (*cp == ' ')
561 					cp++;
562 				if (*cp)
563 					help(sitetab, cp);
564 				else
565 					help(sitetab, (char *) 0);
566 			} else
567 				help(cmdtab, $3);
568 			free($3);
569 		}
570 	| NOOP CRLF
571 		{
572 			reply(200, "NOOP command successful.");
573 		}
574 	| MKD check_login_ro SP pathname CRLF
575 		{
576 			if ($2 && $4 != NULL)
577 				makedir($4);
578 			if ($4 != NULL)
579 				free($4);
580 		}
581 	| RMD check_login_ro SP pathname CRLF
582 		{
583 			if ($2 && $4 != NULL)
584 				removedir($4);
585 			if ($4 != NULL)
586 				free($4);
587 		}
588 	| PWD check_login CRLF
589 		{
590 			if ($2)
591 				pwd();
592 		}
593 	| CDUP check_login CRLF
594 		{
595 			if ($2)
596 				cwd("..");
597 		}
598 	| SITE SP HELP CRLF
599 		{
600 			help(sitetab, (char *) 0);
601 		}
602 	| SITE SP HELP SP STRING CRLF
603 		{
604 			help(sitetab, $5);
605 			free($5);
606 		}
607 	| SITE SP MDFIVE check_login SP pathname CRLF
608 		{
609 			char p[64], *q;
610 
611 			if ($4 && $6) {
612 				q = MD5File($6, p);
613 				if (q != NULL)
614 					reply(200, "MD5(%s) = %s", $6, p);
615 				else
616 					perror_reply(550, $6);
617 			}
618 			if ($6)
619 				free($6);
620 		}
621 	| SITE SP UMASK check_login CRLF
622 		{
623 			int oldmask;
624 
625 			if ($4) {
626 				oldmask = umask(0);
627 				(void) umask(oldmask);
628 				reply(200, "Current UMASK is %03o", oldmask);
629 			}
630 		}
631 	| SITE SP UMASK check_login SP octal_number CRLF
632 		{
633 			int oldmask;
634 
635 			if ($4) {
636 				if (($6 == -1) || ($6 > 0777)) {
637 					reply(501, "Bad UMASK value");
638 				} else {
639 					oldmask = umask($6);
640 					reply(200,
641 					    "UMASK set to %03o (was %03o)",
642 					    $6, oldmask);
643 				}
644 			}
645 		}
646 	| SITE SP CHMOD check_login_ro SP octal_number SP pathname CRLF
647 		{
648 			if ($4 && ($8 != NULL)) {
649 				if (($6 == -1 ) || ($6 > 0777))
650 					reply(501, "Bad mode value");
651 				else if (chmod($8, $6) < 0)
652 					perror_reply(550, $8);
653 				else
654 					reply(200, "CHMOD command successful.");
655 			}
656 			if ($8 != NULL)
657 				free($8);
658 		}
659 	| SITE SP check_login IDLE CRLF
660 		{
661 			if ($3)
662 				reply(200,
663 			    	    "Current IDLE time limit is %d seconds; max %d",
664 				    timeout, maxtimeout);
665 		}
666 	| SITE SP check_login IDLE SP NUMBER CRLF
667 		{
668 			if ($3) {
669 				if ($6.i < 30 || $6.i > maxtimeout) {
670 					reply(501,
671 					    "Maximum IDLE time must be between 30 and %d seconds",
672 					    maxtimeout);
673 				} else {
674 					timeout = $6.i;
675 					(void) alarm((unsigned) timeout);
676 					reply(200,
677 					    "Maximum IDLE time set to %d seconds",
678 					    timeout);
679 				}
680 			}
681 		}
682 	| STOU check_login_ro SP pathname CRLF
683 		{
684 			if ($2 && $4 != NULL)
685 				store($4, "w", 1);
686 			if ($4 != NULL)
687 				free($4);
688 		}
689 	| SYST check_login CRLF
690 		{
691 			if ($2)
692 #ifdef unix
693 #ifdef BSD
694 			reply(215, "UNIX Type: L%d Version: BSD-%d",
695 				CHAR_BIT, BSD);
696 #else /* BSD */
697 			reply(215, "UNIX Type: L%d", CHAR_BIT);
698 #endif /* BSD */
699 #else /* unix */
700 			reply(215, "UNKNOWN Type: L%d", CHAR_BIT);
701 #endif /* unix */
702 		}
703 
704 		/*
705 		 * SIZE is not in RFC959, but Postel has blessed it and
706 		 * it will be in the updated RFC.
707 		 *
708 		 * Return size of file in a format suitable for
709 		 * using with RESTART (we just count bytes).
710 		 */
711 	| SIZE check_login SP pathname CRLF
712 		{
713 			if ($2 && $4 != NULL)
714 				sizecmd($4);
715 			if ($4 != NULL)
716 				free($4);
717 		}
718 
719 		/*
720 		 * MDTM is not in RFC959, but Postel has blessed it and
721 		 * it will be in the updated RFC.
722 		 *
723 		 * Return modification time of file as an ISO 3307
724 		 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
725 		 * where xxx is the fractional second (of any precision,
726 		 * not necessarily 3 digits)
727 		 */
728 	| MDTM check_login SP pathname CRLF
729 		{
730 			if ($2 && $4 != NULL) {
731 				struct stat stbuf;
732 				if (stat($4, &stbuf) < 0)
733 					reply(550, "%s: %s",
734 					    $4, strerror(errno));
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 = (off_t) 0;
770 			if ($2 && $4) {
771 				if (fromname)
772 					free(fromname);
773 				fromname = (char *) 0;
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 = (char *) 0;
788 				restart_point = $4.o;
789 				reply(350, "Restarting at %llu. %s",
790 				    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 			/*
968 			 * Problem: this production is used for all pathname
969 			 * processing, but only gives a 550 error reply.
970 			 * This is a valid reply in some cases but not in others.
971 			 */
972 			if (logged_in && $1) {
973 				glob_t gl;
974 				int flags =
975 				 GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE;
976 
977 				memset(&gl, 0, sizeof(gl));
978 				flags |= GLOB_MAXPATH;
979 				gl.gl_matchc = MAXGLOBARGS;
980 				if (glob($1, flags, NULL, &gl) ||
981 				    gl.gl_pathc == 0) {
982 					reply(550, "wildcard expansion error");
983 					$$ = NULL;
984 				} else if (gl.gl_pathc > 1) {
985 					reply(550, "ambiguous");
986 					$$ = NULL;
987 				} else {
988 					$$ = strdup(gl.gl_pathv[0]);
989 				}
990 				globfree(&gl);
991 				free($1);
992 			} else
993 				$$ = $1;
994 		}
995 	;
996 
997 pathstring
998 	: STRING
999 	;
1000 
1001 octal_number
1002 	: NUMBER
1003 		{
1004 			int ret, dec, multby, digit;
1005 
1006 			/*
1007 			 * Convert a number that was read as decimal number
1008 			 * to what it would be if it had been read as octal.
1009 			 */
1010 			dec = $1.i;
1011 			multby = 1;
1012 			ret = 0;
1013 			while (dec) {
1014 				digit = dec%10;
1015 				if (digit > 7) {
1016 					ret = -1;
1017 					break;
1018 				}
1019 				ret += digit * multby;
1020 				multby *= 8;
1021 				dec /= 10;
1022 			}
1023 			$$ = ret;
1024 		}
1025 	;
1026 
1027 
1028 check_login
1029 	: /* empty */
1030 		{
1031 		$$ = check_login1();
1032 		}
1033 	;
1034 
1035 check_login_epsv
1036 	: /* empty */
1037 		{
1038 		if (noepsv) {
1039 			reply(500, "EPSV command disabled");
1040 			$$ = 0;
1041 		}
1042 		else
1043 			$$ = check_login1();
1044 		}
1045 	;
1046 
1047 check_login_ro
1048 	: /* empty */
1049 		{
1050 		if (readonly) {
1051 			reply(550, "Permission denied.");
1052 			$$ = 0;
1053 		}
1054 		else
1055 			$$ = check_login1();
1056 		}
1057 	;
1058 
1059 %%
1060 
1061 #define	CMD	0	/* beginning of command */
1062 #define	ARGS	1	/* expect miscellaneous arguments */
1063 #define	STR1	2	/* expect SP followed by STRING */
1064 #define	STR2	3	/* expect STRING */
1065 #define	OSTR	4	/* optional SP then STRING */
1066 #define	ZSTR1	5	/* optional SP then optional STRING */
1067 #define	ZSTR2	6	/* optional STRING after SP */
1068 #define	SITECMD	7	/* SITE command */
1069 #define	NSTR	8	/* Number followed by a string */
1070 
1071 #define	MAXGLOBARGS	1000
1072 
1073 #define	MAXASIZE	10240	/* Deny ASCII SIZE on files larger than that */
1074 
1075 struct tab {
1076 	char	*name;
1077 	short	token;
1078 	short	state;
1079 	short	implemented;	/* 1 if command is implemented */
1080 	char	*help;
1081 };
1082 
1083 struct tab cmdtab[] = {		/* In order defined in RFC 765 */
1084 	{ "USER", USER, STR1, 1,	"<sp> username" },
1085 	{ "PASS", PASS, ZSTR1, 1,	"[<sp> [password]]" },
1086 	{ "ACCT", ACCT, STR1, 0,	"(specify account)" },
1087 	{ "SMNT", SMNT, ARGS, 0,	"(structure mount)" },
1088 	{ "REIN", REIN, ARGS, 0,	"(reinitialize server state)" },
1089 	{ "QUIT", QUIT, ARGS, 1,	"(terminate service)", },
1090 	{ "PORT", PORT, ARGS, 1,	"<sp> b0, b1, b2, b3, b4, b5" },
1091 	{ "LPRT", LPRT, ARGS, 1,	"<sp> af, hal, h1, h2, h3,..., pal, p1, p2..." },
1092 	{ "EPRT", EPRT, STR1, 1,	"<sp> |af|addr|port|" },
1093 	{ "PASV", PASV, ARGS, 1,	"(set server in passive mode)" },
1094 	{ "LPSV", LPSV, ARGS, 1,	"(set server in passive mode)" },
1095 	{ "EPSV", EPSV, ARGS, 1,	"[<sp> af|ALL]" },
1096 	{ "TYPE", TYPE, ARGS, 1,	"<sp> { A | E | I | L }" },
1097 	{ "STRU", STRU, ARGS, 1,	"(specify file structure)" },
1098 	{ "MODE", MODE, ARGS, 1,	"(specify transfer mode)" },
1099 	{ "RETR", RETR, STR1, 1,	"<sp> file-name" },
1100 	{ "STOR", STOR, STR1, 1,	"<sp> file-name" },
1101 	{ "APPE", APPE, STR1, 1,	"<sp> file-name" },
1102 	{ "MLFL", MLFL, OSTR, 0,	"(mail file)" },
1103 	{ "MAIL", MAIL, OSTR, 0,	"(mail to user)" },
1104 	{ "MSND", MSND, OSTR, 0,	"(mail send to terminal)" },
1105 	{ "MSOM", MSOM, OSTR, 0,	"(mail send to terminal or mailbox)" },
1106 	{ "MSAM", MSAM, OSTR, 0,	"(mail send to terminal and mailbox)" },
1107 	{ "MRSQ", MRSQ, OSTR, 0,	"(mail recipient scheme question)" },
1108 	{ "MRCP", MRCP, STR1, 0,	"(mail recipient)" },
1109 	{ "ALLO", ALLO, ARGS, 1,	"allocate storage (vacuously)" },
1110 	{ "REST", REST, ARGS, 1,	"<sp> offset (restart command)" },
1111 	{ "RNFR", RNFR, STR1, 1,	"<sp> file-name" },
1112 	{ "RNTO", RNTO, STR1, 1,	"<sp> file-name" },
1113 	{ "ABOR", ABOR, ARGS, 1,	"(abort operation)" },
1114 	{ "DELE", DELE, STR1, 1,	"<sp> file-name" },
1115 	{ "CWD",  CWD,  OSTR, 1,	"[ <sp> directory-name ]" },
1116 	{ "XCWD", CWD,	OSTR, 1,	"[ <sp> directory-name ]" },
1117 	{ "LIST", LIST, OSTR, 1,	"[ <sp> path-name ]" },
1118 	{ "NLST", NLST, OSTR, 1,	"[ <sp> path-name ]" },
1119 	{ "SITE", SITE, SITECMD, 1,	"site-cmd [ <sp> arguments ]" },
1120 	{ "SYST", SYST, ARGS, 1,	"(get type of operating system)" },
1121 	{ "STAT", STAT, OSTR, 1,	"[ <sp> path-name ]" },
1122 	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
1123 	{ "NOOP", NOOP, ARGS, 1,	"" },
1124 	{ "MKD",  MKD,  STR1, 1,	"<sp> path-name" },
1125 	{ "XMKD", MKD,  STR1, 1,	"<sp> path-name" },
1126 	{ "RMD",  RMD,  STR1, 1,	"<sp> path-name" },
1127 	{ "XRMD", RMD,  STR1, 1,	"<sp> path-name" },
1128 	{ "PWD",  PWD,  ARGS, 1,	"(return current directory)" },
1129 	{ "XPWD", PWD,  ARGS, 1,	"(return current directory)" },
1130 	{ "CDUP", CDUP, ARGS, 1,	"(change to parent directory)" },
1131 	{ "XCUP", CDUP, ARGS, 1,	"(change to parent directory)" },
1132 	{ "STOU", STOU, STR1, 1,	"<sp> file-name" },
1133 	{ "SIZE", SIZE, OSTR, 1,	"<sp> path-name" },
1134 	{ "MDTM", MDTM, OSTR, 1,	"<sp> path-name" },
1135 	{ NULL,   0,    0,    0,	0 }
1136 };
1137 
1138 struct tab sitetab[] = {
1139 	{ "MD5", MDFIVE, STR1, 1,	"[ <sp> file-name ]" },
1140 	{ "UMASK", UMASK, ARGS, 1,	"[ <sp> umask ]" },
1141 	{ "IDLE", IDLE, ARGS, 1,	"[ <sp> maximum-idle-time ]" },
1142 	{ "CHMOD", CHMOD, NSTR, 1,	"<sp> mode <sp> file-name" },
1143 	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
1144 	{ NULL,   0,    0,    0,	0 }
1145 };
1146 
1147 static char	*copy(char *);
1148 static void	 help(struct tab *, char *);
1149 static struct tab *
1150 		 lookup(struct tab *, char *);
1151 static int	 port_check(const char *);
1152 static int	 port_check_v6(const char *);
1153 static void	 sizecmd(char *);
1154 static void	 toolong(int);
1155 static void	 v4map_data_dest(void);
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 
1179 	cs = s;
1180 /* tmpline may contain saved command from urgent mode interruption */
1181 	for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
1182 		*cs++ = tmpline[c];
1183 		if (tmpline[c] == '\n') {
1184 			*cs++ = '\0';
1185 			if (ftpdebug)
1186 				syslog(LOG_DEBUG, "command: %s", s);
1187 			tmpline[0] = '\0';
1188 			return(s);
1189 		}
1190 		if (c == 0)
1191 			tmpline[0] = '\0';
1192 	}
1193 	while ((c = getc(iop)) != EOF) {
1194 		c &= 0377;
1195 		if (c == IAC) {
1196 		    if ((c = getc(iop)) != EOF) {
1197 			c &= 0377;
1198 			switch (c) {
1199 			case WILL:
1200 			case WONT:
1201 				c = getc(iop);
1202 				printf("%c%c%c", IAC, DONT, 0377&c);
1203 				(void) fflush(stdout);
1204 				continue;
1205 			case DO:
1206 			case DONT:
1207 				c = getc(iop);
1208 				printf("%c%c%c", IAC, WONT, 0377&c);
1209 				(void) fflush(stdout);
1210 				continue;
1211 			case IAC:
1212 				break;
1213 			default:
1214 				continue;	/* ignore command */
1215 			}
1216 		    }
1217 		}
1218 		*cs++ = c;
1219 		if (--n <= 0 || c == '\n')
1220 			break;
1221 	}
1222 	if (c == EOF && cs == s)
1223 		return (NULL);
1224 	*cs++ = '\0';
1225 	if (ftpdebug) {
1226 		if (!guest && strncasecmp("pass ", s, 5) == 0) {
1227 			/* Don't syslog passwords */
1228 			syslog(LOG_DEBUG, "command: %.5s ???", s);
1229 		} else {
1230 			register char *cp;
1231 			register int len;
1232 
1233 			/* Don't syslog trailing CR-LF */
1234 			len = strlen(s);
1235 			cp = s + len - 1;
1236 			while (cp >= s && (*cp == '\n' || *cp == '\r')) {
1237 				--cp;
1238 				--len;
1239 			}
1240 			syslog(LOG_DEBUG, "command: %.*s", len, s);
1241 		}
1242 	}
1243 	return (s);
1244 }
1245 
1246 static void
1247 toolong(int signo)
1248 {
1249 
1250 	reply(421,
1251 	    "Timeout (%d seconds): closing control connection.", timeout);
1252 	if (logging)
1253 		syslog(LOG_INFO, "User %s timed out after %d seconds",
1254 		    (pw ? pw -> pw_name : "unknown"), timeout);
1255 	dologout(1);
1256 }
1257 
1258 static int
1259 yylex(void)
1260 {
1261 	static int cpos;
1262 	char *cp, *cp2;
1263 	struct tab *p;
1264 	int n;
1265 	char c;
1266 
1267 	for (;;) {
1268 		switch (state) {
1269 
1270 		case CMD:
1271 			(void) signal(SIGALRM, toolong);
1272 			(void) alarm((unsigned) timeout);
1273 			if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
1274 				reply(221, "You could at least say goodbye.");
1275 				dologout(0);
1276 			}
1277 			(void) alarm(0);
1278 #ifdef SETPROCTITLE
1279 			if (strncasecmp(cbuf, "PASS", 4) != 0)
1280 				setproctitle("%s: %s", proctitle, cbuf);
1281 #endif /* SETPROCTITLE */
1282 			if ((cp = strchr(cbuf, '\r'))) {
1283 				*cp++ = '\n';
1284 				*cp = '\0';
1285 			}
1286 			if ((cp = strpbrk(cbuf, " \n")))
1287 				cpos = cp - cbuf;
1288 			if (cpos == 0)
1289 				cpos = 4;
1290 			c = cbuf[cpos];
1291 			cbuf[cpos] = '\0';
1292 			upper(cbuf);
1293 			p = lookup(cmdtab, cbuf);
1294 			cbuf[cpos] = c;
1295 			if (p != 0) {
1296 				yylval.s = p->name;
1297 				if (!p->implemented)
1298 					return (NOTIMPL); /* state remains CMD */
1299 				state = p->state;
1300 				return (p->token);
1301 			}
1302 			break;
1303 
1304 		case SITECMD:
1305 			if (cbuf[cpos] == ' ') {
1306 				cpos++;
1307 				return (SP);
1308 			}
1309 			cp = &cbuf[cpos];
1310 			if ((cp2 = strpbrk(cp, " \n")))
1311 				cpos = cp2 - cbuf;
1312 			c = cbuf[cpos];
1313 			cbuf[cpos] = '\0';
1314 			upper(cp);
1315 			p = lookup(sitetab, cp);
1316 			cbuf[cpos] = c;
1317 			if (guest == 0 && p != 0) {
1318 				yylval.s = p->name;
1319 				if (!p->implemented) {
1320 					state = CMD;
1321 					return (NOTIMPL);
1322 				}
1323 				state = p->state;
1324 				return (p->token);
1325 			}
1326 			state = CMD;
1327 			break;
1328 
1329 		case ZSTR1:
1330 		case OSTR:
1331 			if (cbuf[cpos] == '\n') {
1332 				state = CMD;
1333 				return (CRLF);
1334 			}
1335 			/* FALLTHROUGH */
1336 
1337 		case STR1:
1338 		dostr1:
1339 			if (cbuf[cpos] == ' ') {
1340 				cpos++;
1341 				state = state == OSTR ? STR2 : state+1;
1342 				return (SP);
1343 			}
1344 			break;
1345 
1346 		case ZSTR2:
1347 			if (cbuf[cpos] == '\n') {
1348 				state = CMD;
1349 				return (CRLF);
1350 			}
1351 			/* FALLTHROUGH */
1352 
1353 		case STR2:
1354 			cp = &cbuf[cpos];
1355 			n = strlen(cp);
1356 			cpos += n - 1;
1357 			/*
1358 			 * Make sure the string is nonempty and \n terminated.
1359 			 */
1360 			if (n > 1 && cbuf[cpos] == '\n') {
1361 				cbuf[cpos] = '\0';
1362 				yylval.s = copy(cp);
1363 				cbuf[cpos] = '\n';
1364 				state = ARGS;
1365 				return (STRING);
1366 			}
1367 			break;
1368 
1369 		case NSTR:
1370 			if (cbuf[cpos] == ' ') {
1371 				cpos++;
1372 				return (SP);
1373 			}
1374 			if (isdigit(cbuf[cpos])) {
1375 				cp = &cbuf[cpos];
1376 				while (isdigit(cbuf[++cpos]))
1377 					;
1378 				c = cbuf[cpos];
1379 				cbuf[cpos] = '\0';
1380 				yylval.u.i = atoi(cp);
1381 				cbuf[cpos] = c;
1382 				state = STR1;
1383 				return (NUMBER);
1384 			}
1385 			state = STR1;
1386 			goto dostr1;
1387 
1388 		case ARGS:
1389 			if (isdigit(cbuf[cpos])) {
1390 				cp = &cbuf[cpos];
1391 				while (isdigit(cbuf[++cpos]))
1392 					;
1393 				c = cbuf[cpos];
1394 				cbuf[cpos] = '\0';
1395 				yylval.u.i = atoi(cp);
1396 				yylval.u.o = strtoull(cp, (char **)NULL, 10);
1397 				cbuf[cpos] = c;
1398 				return (NUMBER);
1399 			}
1400 			if (strncasecmp(&cbuf[cpos], "ALL", 3) == 0
1401 			 && !isalnum(cbuf[cpos + 3])) {
1402 				cpos += 3;
1403 				return ALL;
1404 			}
1405 			switch (cbuf[cpos++]) {
1406 
1407 			case '\n':
1408 				state = CMD;
1409 				return (CRLF);
1410 
1411 			case ' ':
1412 				return (SP);
1413 
1414 			case ',':
1415 				return (COMMA);
1416 
1417 			case 'A':
1418 			case 'a':
1419 				return (A);
1420 
1421 			case 'B':
1422 			case 'b':
1423 				return (B);
1424 
1425 			case 'C':
1426 			case 'c':
1427 				return (C);
1428 
1429 			case 'E':
1430 			case 'e':
1431 				return (E);
1432 
1433 			case 'F':
1434 			case 'f':
1435 				return (F);
1436 
1437 			case 'I':
1438 			case 'i':
1439 				return (I);
1440 
1441 			case 'L':
1442 			case 'l':
1443 				return (L);
1444 
1445 			case 'N':
1446 			case 'n':
1447 				return (N);
1448 
1449 			case 'P':
1450 			case 'p':
1451 				return (P);
1452 
1453 			case 'R':
1454 			case 'r':
1455 				return (R);
1456 
1457 			case 'S':
1458 			case 's':
1459 				return (S);
1460 
1461 			case 'T':
1462 			case 't':
1463 				return (T);
1464 
1465 			}
1466 			break;
1467 
1468 		default:
1469 			fatalerror("Unknown state in scanner.");
1470 		}
1471 		state = CMD;
1472 		return (LEXERR);
1473 	}
1474 }
1475 
1476 void
1477 upper(char *s)
1478 {
1479 	while (*s != '\0') {
1480 		if (islower(*s))
1481 			*s = toupper(*s);
1482 		s++;
1483 	}
1484 }
1485 
1486 static char *
1487 copy(char *s)
1488 {
1489 	char *p;
1490 
1491 	p = malloc((unsigned) strlen(s) + 1);
1492 	if (p == NULL)
1493 		fatalerror("Ran out of memory.");
1494 	(void) strcpy(p, s);
1495 	return (p);
1496 }
1497 
1498 static void
1499 help(struct tab *ctab, char *s)
1500 {
1501 	struct tab *c;
1502 	int width, NCMDS;
1503 	char *type;
1504 
1505 	if (ctab == sitetab)
1506 		type = "SITE ";
1507 	else
1508 		type = "";
1509 	width = 0, NCMDS = 0;
1510 	for (c = ctab; c->name != NULL; c++) {
1511 		int len = strlen(c->name);
1512 
1513 		if (len > width)
1514 			width = len;
1515 		NCMDS++;
1516 	}
1517 	width = (width + 8) &~ 7;
1518 	if (s == 0) {
1519 		int i, j, w;
1520 		int columns, lines;
1521 
1522 		lreply(214, "The following %scommands are recognized %s.",
1523 		    type, "(* =>'s unimplemented)");
1524 		columns = 76 / width;
1525 		if (columns == 0)
1526 			columns = 1;
1527 		lines = (NCMDS + columns - 1) / columns;
1528 		for (i = 0; i < lines; i++) {
1529 			printf("   ");
1530 			for (j = 0; j < columns; j++) {
1531 				c = ctab + j * lines + i;
1532 				printf("%s%c", c->name,
1533 					c->implemented ? ' ' : '*');
1534 				if (c + lines >= &ctab[NCMDS])
1535 					break;
1536 				w = strlen(c->name) + 1;
1537 				while (w < width) {
1538 					putchar(' ');
1539 					w++;
1540 				}
1541 			}
1542 			printf("\r\n");
1543 		}
1544 		(void) fflush(stdout);
1545 		reply(214, "Direct comments to ftp-bugs@%s.", hostname);
1546 		return;
1547 	}
1548 	upper(s);
1549 	c = lookup(ctab, s);
1550 	if (c == (struct tab *)0) {
1551 		reply(502, "Unknown command %s.", s);
1552 		return;
1553 	}
1554 	if (c->implemented)
1555 		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
1556 	else
1557 		reply(214, "%s%-*s\t%s; unimplemented.", type, width,
1558 		    c->name, c->help);
1559 }
1560 
1561 static void
1562 sizecmd(char *filename)
1563 {
1564 	switch (type) {
1565 	case TYPE_L:
1566 	case TYPE_I: {
1567 		struct stat stbuf;
1568 		if (stat(filename, &stbuf) < 0)
1569 			perror_reply(550, filename);
1570 		else if (!S_ISREG(stbuf.st_mode))
1571 			reply(550, "%s: not a plain file.", filename);
1572 		else
1573 			reply(213, "%qu", stbuf.st_size);
1574 		break; }
1575 	case TYPE_A: {
1576 		FILE *fin;
1577 		int c;
1578 		off_t count;
1579 		struct stat stbuf;
1580 		fin = fopen(filename, "r");
1581 		if (fin == NULL) {
1582 			perror_reply(550, filename);
1583 			return;
1584 		}
1585 		if (fstat(fileno(fin), &stbuf) < 0) {
1586 			perror_reply(550, filename);
1587 			(void) fclose(fin);
1588 			return;
1589 		} else if (!S_ISREG(stbuf.st_mode)) {
1590 			reply(550, "%s: not a plain file.", filename);
1591 			(void) fclose(fin);
1592 			return;
1593 		} else if (stbuf.st_size > MAXASIZE) {
1594 			reply(550, "%s: too large for type A SIZE.", filename);
1595 			(void) fclose(fin);
1596 			return;
1597 		}
1598 
1599 		count = 0;
1600 		while((c=getc(fin)) != EOF) {
1601 			if (c == '\n')	/* will get expanded to \r\n */
1602 				count++;
1603 			count++;
1604 		}
1605 		(void) fclose(fin);
1606 
1607 		reply(213, "%qd", count);
1608 		break; }
1609 	default:
1610 		reply(504, "SIZE not implemented for type %s.",
1611 		           typenames[type]);
1612 	}
1613 }
1614 
1615 /* Return 1, if port check is done. Return 0, if not yet. */
1616 static int
1617 port_check(const char *pcmd)
1618 {
1619 	if (his_addr.su_family == AF_INET) {
1620 		if (data_dest.su_family != AF_INET) {
1621 			usedefault = 1;
1622 			reply(500, "Invalid address rejected.");
1623 			return 1;
1624 		}
1625 		if (paranoid &&
1626 		    ((ntohs(data_dest.su_port) < IPPORT_RESERVED) ||
1627 		     memcmp(&data_dest.su_sin.sin_addr,
1628 			    &his_addr.su_sin.sin_addr,
1629 			    sizeof(data_dest.su_sin.sin_addr)))) {
1630 			usedefault = 1;
1631 			reply(500, "Illegal PORT range rejected.");
1632 		} else {
1633 			usedefault = 0;
1634 			if (pdata >= 0) {
1635 				(void) close(pdata);
1636 				pdata = -1;
1637 			}
1638 			reply(200, "%s command successful.", pcmd);
1639 		}
1640 		return 1;
1641 	}
1642 	return 0;
1643 }
1644 
1645 static int
1646 check_login1(void)
1647 {
1648 	if (logged_in)
1649 		return 1;
1650 	else {
1651 		reply(530, "Please login with USER and PASS.");
1652 		return 0;
1653 	}
1654 }
1655 
1656 #ifdef INET6
1657 /* Return 1, if port check is done. Return 0, if not yet. */
1658 static int
1659 port_check_v6(const char *pcmd)
1660 {
1661 	if (his_addr.su_family == AF_INET6) {
1662 		if (IN6_IS_ADDR_V4MAPPED(&his_addr.su_sin6.sin6_addr))
1663 			/* Convert data_dest into v4 mapped sockaddr.*/
1664 			v4map_data_dest();
1665 		if (data_dest.su_family != AF_INET6) {
1666 			usedefault = 1;
1667 			reply(500, "Invalid address rejected.");
1668 			return 1;
1669 		}
1670 		if (paranoid &&
1671 		    ((ntohs(data_dest.su_port) < IPPORT_RESERVED) ||
1672 		     memcmp(&data_dest.su_sin6.sin6_addr,
1673 			    &his_addr.su_sin6.sin6_addr,
1674 			    sizeof(data_dest.su_sin6.sin6_addr)))) {
1675 			usedefault = 1;
1676 			reply(500, "Illegal PORT range rejected.");
1677 		} else {
1678 			usedefault = 0;
1679 			if (pdata >= 0) {
1680 				(void) close(pdata);
1681 				pdata = -1;
1682 			}
1683 			reply(200, "%s command successful.", pcmd);
1684 		}
1685 		return 1;
1686 	}
1687 	return 0;
1688 }
1689 
1690 static void
1691 v4map_data_dest(void)
1692 {
1693 	struct in_addr savedaddr;
1694 	int savedport;
1695 
1696 	if (data_dest.su_family != AF_INET) {
1697 		usedefault = 1;
1698 		reply(500, "Invalid address rejected.");
1699 		return;
1700 	}
1701 
1702 	savedaddr = data_dest.su_sin.sin_addr;
1703 	savedport = data_dest.su_port;
1704 
1705 	memset(&data_dest, 0, sizeof(data_dest));
1706 	data_dest.su_sin6.sin6_len = sizeof(struct sockaddr_in6);
1707 	data_dest.su_sin6.sin6_family = AF_INET6;
1708 	data_dest.su_sin6.sin6_port = savedport;
1709 	memset((caddr_t)&data_dest.su_sin6.sin6_addr.s6_addr[10], 0xff, 2);
1710 	memcpy((caddr_t)&data_dest.su_sin6.sin6_addr.s6_addr[12],
1711 	       (caddr_t)&savedaddr, sizeof(savedaddr));
1712 }
1713 #endif
1714