xref: /freebsd/libexec/ftpd/ftpcmd.y (revision ebbd4fa8c8427d3dd847ba33c45c996e0500e6ff)
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
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
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 NBBY == 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 /* NBBY == 8 */
404 					UNIMPLEMENTED for NBBY != 8
405 #endif /* NBBY == 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 STRING CRLF
479 		{
480 			if ($2 && $4 != NULL)
481 				send_file_list($4);
482 			if ($4 != NULL)
483 				free($4);
484 		}
485 	| LIST check_login CRLF
486 		{
487 			if ($2)
488 				retrieve("/bin/ls -lgA", "");
489 		}
490 	| LIST check_login SP pathstring CRLF
491 		{
492 			if ($2 && $4 != NULL)
493 				retrieve("/bin/ls -lgA %s", $4);
494 			if ($4 != NULL)
495 				free($4);
496 		}
497 	| STAT check_login SP pathname CRLF
498 		{
499 			if ($2 && $4 != NULL)
500 				statfilecmd($4);
501 			if ($4 != NULL)
502 				free($4);
503 		}
504 	| STAT check_login CRLF
505 		{
506 			if ($2) {
507 				statcmd();
508 			}
509 		}
510 	| DELE check_login_ro SP pathname CRLF
511 		{
512 			if ($2 && $4 != NULL)
513 				delete($4);
514 			if ($4 != NULL)
515 				free($4);
516 		}
517 	| RNTO check_login_ro SP pathname CRLF
518 		{
519 			if ($2) {
520 				if (fromname) {
521 					renamecmd(fromname, $4);
522 					free(fromname);
523 					fromname = (char *) 0;
524 				} else {
525 					reply(503, "Bad sequence of commands.");
526 				}
527 			}
528 			free($4);
529 		}
530 	| ABOR check_login CRLF
531 		{
532 			if ($2)
533 				reply(225, "ABOR command successful.");
534 		}
535 	| CWD check_login CRLF
536 		{
537 			if ($2) {
538 				if (guest)
539 					cwd("/");
540 				else
541 					cwd(pw->pw_dir);
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, (char *) 0);
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, (char *) 0);
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, (char *) 0);
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) {
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 > 0777)
651 					reply(501,
652 				"CHMOD: Mode value must be between 0 and 0777");
653 				else if (chmod($8, $6) < 0)
654 					perror_reply(550, $8);
655 				else
656 					reply(200, "CHMOD command successful.");
657 			}
658 			if ($8 != NULL)
659 				free($8);
660 		}
661 	| SITE SP check_login IDLE CRLF
662 		{
663 			if ($3)
664 				reply(200,
665 			    	    "Current IDLE time limit is %d seconds; max %d",
666 				    timeout, maxtimeout);
667 		}
668 	| SITE SP check_login IDLE SP NUMBER CRLF
669 		{
670 			if ($3) {
671 				if ($6.i < 30 || $6.i > maxtimeout) {
672 					reply(501,
673 					    "Maximum IDLE time must be between 30 and %d seconds",
674 					    maxtimeout);
675 				} else {
676 					timeout = $6.i;
677 					(void) alarm((unsigned) timeout);
678 					reply(200,
679 					    "Maximum IDLE time set to %d seconds",
680 					    timeout);
681 				}
682 			}
683 		}
684 	| STOU check_login_ro SP pathname CRLF
685 		{
686 			if ($2 && $4 != NULL)
687 				store($4, "w", 1);
688 			if ($4 != NULL)
689 				free($4);
690 		}
691 	| SYST check_login CRLF
692 		{
693 			if ($2)
694 #ifdef unix
695 #ifdef BSD
696 			reply(215, "UNIX Type: L%d Version: BSD-%d",
697 				NBBY, BSD);
698 #else /* BSD */
699 			reply(215, "UNIX Type: L%d", NBBY);
700 #endif /* BSD */
701 #else /* unix */
702 			reply(215, "UNKNOWN Type: L%d", NBBY);
703 #endif /* unix */
704 		}
705 
706 		/*
707 		 * SIZE is not in RFC959, but Postel has blessed it and
708 		 * it will be in the updated RFC.
709 		 *
710 		 * Return size of file in a format suitable for
711 		 * using with RESTART (we just count bytes).
712 		 */
713 	| SIZE check_login SP pathname CRLF
714 		{
715 			if ($2 && $4 != NULL)
716 				sizecmd($4);
717 			if ($4 != NULL)
718 				free($4);
719 		}
720 
721 		/*
722 		 * MDTM is not in RFC959, but Postel has blessed it and
723 		 * it will be in the updated RFC.
724 		 *
725 		 * Return modification time of file as an ISO 3307
726 		 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
727 		 * where xxx is the fractional second (of any precision,
728 		 * not necessarily 3 digits)
729 		 */
730 	| MDTM check_login SP pathname CRLF
731 		{
732 			if ($2 && $4 != NULL) {
733 				struct stat stbuf;
734 				if (stat($4, &stbuf) < 0)
735 					reply(550, "%s: %s",
736 					    $4, strerror(errno));
737 				else if (!S_ISREG(stbuf.st_mode)) {
738 					reply(550, "%s: not a plain file.", $4);
739 				} else {
740 					struct tm *t;
741 					t = gmtime(&stbuf.st_mtime);
742 					reply(213,
743 					    "%04d%02d%02d%02d%02d%02d",
744 					    1900 + t->tm_year,
745 					    t->tm_mon+1, t->tm_mday,
746 					    t->tm_hour, t->tm_min, t->tm_sec);
747 				}
748 			}
749 			if ($4 != NULL)
750 				free($4);
751 		}
752 	| QUIT CRLF
753 		{
754 			reply(221, "Goodbye.");
755 			dologout(0);
756 		}
757 	| error
758 		{
759 			yyclearin;		/* discard lookahead data */
760 			yyerrok;		/* clear error condition */
761 			state = 0;		/* reset lexer state */
762 		}
763 	;
764 rcmd
765 	: RNFR check_login_ro SP pathname CRLF
766 		{
767 			restart_point = (off_t) 0;
768 			if ($2 && $4) {
769 				if (fromname)
770 					free(fromname);
771 				fromname = (char *) 0;
772 				if (renamefrom($4))
773 					fromname = $4;
774 				else
775 					free($4);
776 			} else if ($4) {
777 				free($4);
778 			}
779 		}
780 	| REST check_login SP NUMBER CRLF
781 		{
782 			if ($2) {
783 				if (fromname)
784 					free(fromname);
785 				fromname = (char *) 0;
786 				restart_point = $4.o;
787 				reply(350, "Restarting at %llu. %s",
788 				    restart_point,
789 				    "Send STORE or RETRIEVE to initiate transfer.");
790 			}
791 		}
792 	;
793 
794 username
795 	: STRING
796 	;
797 
798 password
799 	: /* empty */
800 		{
801 			$$ = (char *)calloc(1, sizeof(char));
802 		}
803 	| STRING
804 	;
805 
806 byte_size
807 	: NUMBER
808 		{
809 			$$ = $1.i;
810 		}
811 	;
812 
813 host_port
814 	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
815 		NUMBER COMMA NUMBER
816 		{
817 			char *a, *p;
818 
819 			data_dest.su_len = sizeof(struct sockaddr_in);
820 			data_dest.su_family = AF_INET;
821 			p = (char *)&data_dest.su_sin.sin_port;
822 			p[0] = $9.i; p[1] = $11.i;
823 			a = (char *)&data_dest.su_sin.sin_addr;
824 			a[0] = $1.i; a[1] = $3.i; a[2] = $5.i; a[3] = $7.i;
825 		}
826 	;
827 
828 host_long_port
829 	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
830 		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
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
835 		{
836 			char *a, *p;
837 
838 			memset(&data_dest, 0, sizeof(data_dest));
839 			data_dest.su_len = sizeof(struct sockaddr_in6);
840 			data_dest.su_family = AF_INET6;
841 			p = (char *)&data_dest.su_port;
842 			p[0] = $39.i; p[1] = $41.i;
843 			a = (char *)&data_dest.su_sin6.sin6_addr;
844 			a[0] = $5.i; a[1] = $7.i; a[2] = $9.i; a[3] = $11.i;
845 			a[4] = $13.i; a[5] = $15.i; a[6] = $17.i; a[7] = $19.i;
846 			a[8] = $21.i; a[9] = $23.i; a[10] = $25.i; a[11] = $27.i;
847 			a[12] = $29.i; a[13] = $31.i; a[14] = $33.i; a[15] = $35.i;
848 			if (his_addr.su_family == AF_INET6) {
849 				/* XXX more sanity checks! */
850 				data_dest.su_sin6.sin6_scope_id =
851 					his_addr.su_sin6.sin6_scope_id;
852 			}
853 			if ($1.i != 6 || $3.i != 16 || $37.i != 2)
854 				memset(&data_dest, 0, sizeof(data_dest));
855 		}
856 	| NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
857 		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
858 		NUMBER
859 		{
860 			char *a, *p;
861 
862 			memset(&data_dest, 0, sizeof(data_dest));
863 			data_dest.su_sin.sin_len = sizeof(struct sockaddr_in);
864 			data_dest.su_family = AF_INET;
865 			p = (char *)&data_dest.su_port;
866 			p[0] = $15.i; p[1] = $17.i;
867 			a = (char *)&data_dest.su_sin.sin_addr;
868 			a[0] =  $5.i; a[1] = $7.i; a[2] = $9.i; a[3] = $11.i;
869 			if ($1.i != 4 || $3.i != 4 || $13.i != 2)
870 				memset(&data_dest, 0, sizeof(data_dest));
871 		}
872 	;
873 
874 form_code
875 	: N
876 		{
877 			$$ = FORM_N;
878 		}
879 	| T
880 		{
881 			$$ = FORM_T;
882 		}
883 	| C
884 		{
885 			$$ = FORM_C;
886 		}
887 	;
888 
889 type_code
890 	: A
891 		{
892 			cmd_type = TYPE_A;
893 			cmd_form = FORM_N;
894 		}
895 	| A SP form_code
896 		{
897 			cmd_type = TYPE_A;
898 			cmd_form = $3;
899 		}
900 	| E
901 		{
902 			cmd_type = TYPE_E;
903 			cmd_form = FORM_N;
904 		}
905 	| E SP form_code
906 		{
907 			cmd_type = TYPE_E;
908 			cmd_form = $3;
909 		}
910 	| I
911 		{
912 			cmd_type = TYPE_I;
913 		}
914 	| L
915 		{
916 			cmd_type = TYPE_L;
917 			cmd_bytesz = NBBY;
918 		}
919 	| L SP byte_size
920 		{
921 			cmd_type = TYPE_L;
922 			cmd_bytesz = $3;
923 		}
924 		/* this is for a bug in the BBN ftp */
925 	| L byte_size
926 		{
927 			cmd_type = TYPE_L;
928 			cmd_bytesz = $2;
929 		}
930 	;
931 
932 struct_code
933 	: F
934 		{
935 			$$ = STRU_F;
936 		}
937 	| R
938 		{
939 			$$ = STRU_R;
940 		}
941 	| P
942 		{
943 			$$ = STRU_P;
944 		}
945 	;
946 
947 mode_code
948 	: S
949 		{
950 			$$ = MODE_S;
951 		}
952 	| B
953 		{
954 			$$ = MODE_B;
955 		}
956 	| C
957 		{
958 			$$ = MODE_C;
959 		}
960 	;
961 
962 pathname
963 	: pathstring
964 		{
965 			/*
966 			 * Problem: this production is used for all pathname
967 			 * processing, but only gives a 550 error reply.
968 			 * This is a valid reply in some cases but not in others.
969 			 */
970 			if (logged_in && $1) {
971 				glob_t gl;
972 				int flags =
973 				 GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE;
974 
975 				memset(&gl, 0, sizeof(gl));
976 				flags |= GLOB_MAXPATH;
977 				gl.gl_matchc = MAXGLOBARGS;
978 				if (glob($1, flags, NULL, &gl) ||
979 				    gl.gl_pathc == 0) {
980 					reply(550, "not found");
981 					$$ = NULL;
982 				} else if (gl.gl_pathc > 1) {
983 					reply(550, "ambiguous");
984 					$$ = NULL;
985 				} else {
986 					$$ = strdup(gl.gl_pathv[0]);
987 				}
988 				globfree(&gl);
989 				free($1);
990 			} else
991 				$$ = $1;
992 		}
993 	;
994 
995 pathstring
996 	: STRING
997 	;
998 
999 octal_number
1000 	: NUMBER
1001 		{
1002 			int ret, dec, multby, digit;
1003 
1004 			/*
1005 			 * Convert a number that was read as decimal number
1006 			 * to what it would be if it had been read as octal.
1007 			 */
1008 			dec = $1.i;
1009 			multby = 1;
1010 			ret = 0;
1011 			while (dec) {
1012 				digit = dec%10;
1013 				if (digit > 7) {
1014 					ret = -1;
1015 					break;
1016 				}
1017 				ret += digit * multby;
1018 				multby *= 8;
1019 				dec /= 10;
1020 			}
1021 			$$ = ret;
1022 		}
1023 	;
1024 
1025 
1026 check_login
1027 	: /* empty */
1028 		{
1029 		$$ = check_login1();
1030 		}
1031 	;
1032 
1033 check_login_epsv
1034 	: /* empty */
1035 		{
1036 		if (noepsv) {
1037 			reply(500, "EPSV command disabled");
1038 			$$ = 0;
1039 		}
1040 		else
1041 			$$ = check_login1();
1042 		}
1043 	;
1044 
1045 check_login_ro
1046 	: /* empty */
1047 		{
1048 		if (readonly) {
1049 			reply(550, "Permission denied.");
1050 			$$ = 0;
1051 		}
1052 		else
1053 			$$ = check_login1();
1054 		}
1055 	;
1056 
1057 %%
1058 
1059 #define	CMD	0	/* beginning of command */
1060 #define	ARGS	1	/* expect miscellaneous arguments */
1061 #define	STR1	2	/* expect SP followed by STRING */
1062 #define	STR2	3	/* expect STRING */
1063 #define	OSTR	4	/* optional SP then STRING */
1064 #define	ZSTR1	5	/* optional SP then optional STRING */
1065 #define	ZSTR2	6	/* optional STRING after SP */
1066 #define	SITECMD	7	/* SITE command */
1067 #define	NSTR	8	/* Number followed by a string */
1068 
1069 #define	MAXGLOBARGS	1000
1070 
1071 #define	MAXASIZE	10240	/* Deny ASCII SIZE on files larger than that */
1072 
1073 struct tab {
1074 	char	*name;
1075 	short	token;
1076 	short	state;
1077 	short	implemented;	/* 1 if command is implemented */
1078 	char	*help;
1079 };
1080 
1081 struct tab cmdtab[] = {		/* In order defined in RFC 765 */
1082 	{ "USER", USER, STR1, 1,	"<sp> username" },
1083 	{ "PASS", PASS, ZSTR1, 1,	"[<sp> [password]]" },
1084 	{ "ACCT", ACCT, STR1, 0,	"(specify account)" },
1085 	{ "SMNT", SMNT, ARGS, 0,	"(structure mount)" },
1086 	{ "REIN", REIN, ARGS, 0,	"(reinitialize server state)" },
1087 	{ "QUIT", QUIT, ARGS, 1,	"(terminate service)", },
1088 	{ "PORT", PORT, ARGS, 1,	"<sp> b0, b1, b2, b3, b4" },
1089 	{ "LPRT", LPRT, ARGS, 1,	"<sp> af, hal, h1, h2, h3,..., pal, p1, p2..." },
1090 	{ "EPRT", EPRT, STR1, 1,	"<sp> |af|addr|port|" },
1091 	{ "PASV", PASV, ARGS, 1,	"(set server in passive mode)" },
1092 	{ "LPSV", LPSV, ARGS, 1,	"(set server in passive mode)" },
1093 	{ "EPSV", EPSV, ARGS, 1,	"[<sp> af|ALL]" },
1094 	{ "TYPE", TYPE, ARGS, 1,	"<sp> [ A | E | I | L ]" },
1095 	{ "STRU", STRU, ARGS, 1,	"(specify file structure)" },
1096 	{ "MODE", MODE, ARGS, 1,	"(specify transfer mode)" },
1097 	{ "RETR", RETR, STR1, 1,	"<sp> file-name" },
1098 	{ "STOR", STOR, STR1, 1,	"<sp> file-name" },
1099 	{ "APPE", APPE, STR1, 1,	"<sp> file-name" },
1100 	{ "MLFL", MLFL, OSTR, 0,	"(mail file)" },
1101 	{ "MAIL", MAIL, OSTR, 0,	"(mail to user)" },
1102 	{ "MSND", MSND, OSTR, 0,	"(mail send to terminal)" },
1103 	{ "MSOM", MSOM, OSTR, 0,	"(mail send to terminal or mailbox)" },
1104 	{ "MSAM", MSAM, OSTR, 0,	"(mail send to terminal and mailbox)" },
1105 	{ "MRSQ", MRSQ, OSTR, 0,	"(mail recipient scheme question)" },
1106 	{ "MRCP", MRCP, STR1, 0,	"(mail recipient)" },
1107 	{ "ALLO", ALLO, ARGS, 1,	"allocate storage (vacuously)" },
1108 	{ "REST", REST, ARGS, 1,	"<sp> offset (restart command)" },
1109 	{ "RNFR", RNFR, STR1, 1,	"<sp> file-name" },
1110 	{ "RNTO", RNTO, STR1, 1,	"<sp> file-name" },
1111 	{ "ABOR", ABOR, ARGS, 1,	"(abort operation)" },
1112 	{ "DELE", DELE, STR1, 1,	"<sp> file-name" },
1113 	{ "CWD",  CWD,  OSTR, 1,	"[ <sp> directory-name ]" },
1114 	{ "XCWD", CWD,	OSTR, 1,	"[ <sp> directory-name ]" },
1115 	{ "LIST", LIST, OSTR, 1,	"[ <sp> path-name ]" },
1116 	{ "NLST", NLST, OSTR, 1,	"[ <sp> path-name ]" },
1117 	{ "SITE", SITE, SITECMD, 1,	"site-cmd [ <sp> arguments ]" },
1118 	{ "SYST", SYST, ARGS, 1,	"(get type of operating system)" },
1119 	{ "STAT", STAT, OSTR, 1,	"[ <sp> path-name ]" },
1120 	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
1121 	{ "NOOP", NOOP, ARGS, 1,	"" },
1122 	{ "MKD",  MKD,  STR1, 1,	"<sp> path-name" },
1123 	{ "XMKD", MKD,  STR1, 1,	"<sp> path-name" },
1124 	{ "RMD",  RMD,  STR1, 1,	"<sp> path-name" },
1125 	{ "XRMD", RMD,  STR1, 1,	"<sp> path-name" },
1126 	{ "PWD",  PWD,  ARGS, 1,	"(return current directory)" },
1127 	{ "XPWD", PWD,  ARGS, 1,	"(return current directory)" },
1128 	{ "CDUP", CDUP, ARGS, 1,	"(change to parent directory)" },
1129 	{ "XCUP", CDUP, ARGS, 1,	"(change to parent directory)" },
1130 	{ "STOU", STOU, STR1, 1,	"<sp> file-name" },
1131 	{ "SIZE", SIZE, OSTR, 1,	"<sp> path-name" },
1132 	{ "MDTM", MDTM, OSTR, 1,	"<sp> path-name" },
1133 	{ NULL,   0,    0,    0,	0 }
1134 };
1135 
1136 struct tab sitetab[] = {
1137 	{ "MD5", MDFIVE, STR1, 1,	"[ <sp> file-name ]" },
1138 	{ "UMASK", UMASK, ARGS, 1,	"[ <sp> umask ]" },
1139 	{ "IDLE", IDLE, ARGS, 1,	"[ <sp> maximum-idle-time ]" },
1140 	{ "CHMOD", CHMOD, NSTR, 1,	"<sp> mode <sp> file-name" },
1141 	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
1142 	{ NULL,   0,    0,    0,	0 }
1143 };
1144 
1145 static char	*copy(char *);
1146 static void	 help(struct tab *, char *);
1147 static struct tab *
1148 		 lookup(struct tab *, char *);
1149 static int	 port_check(const char *);
1150 static int	 port_check_v6(const char *);
1151 static void	 sizecmd(char *);
1152 static void	 toolong(int);
1153 static void	 v4map_data_dest(void);
1154 static int	 yylex(void);
1155 
1156 static struct tab *
1157 lookup(struct tab *p, char *cmd)
1158 {
1159 
1160 	for (; p->name != NULL; p++)
1161 		if (strcmp(cmd, p->name) == 0)
1162 			return (p);
1163 	return (0);
1164 }
1165 
1166 #include <arpa/telnet.h>
1167 
1168 /*
1169  * getline - a hacked up version of fgets to ignore TELNET escape codes.
1170  */
1171 char *
1172 getline(char *s, int n, FILE *iop)
1173 {
1174 	int c;
1175 	register char *cs;
1176 
1177 	cs = s;
1178 /* tmpline may contain saved command from urgent mode interruption */
1179 	for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
1180 		*cs++ = tmpline[c];
1181 		if (tmpline[c] == '\n') {
1182 			*cs++ = '\0';
1183 			if (ftpdebug)
1184 				syslog(LOG_DEBUG, "command: %s", s);
1185 			tmpline[0] = '\0';
1186 			return(s);
1187 		}
1188 		if (c == 0)
1189 			tmpline[0] = '\0';
1190 	}
1191 	while ((c = getc(iop)) != EOF) {
1192 		c &= 0377;
1193 		if (c == IAC) {
1194 		    if ((c = getc(iop)) != EOF) {
1195 			c &= 0377;
1196 			switch (c) {
1197 			case WILL:
1198 			case WONT:
1199 				c = getc(iop);
1200 				printf("%c%c%c", IAC, DONT, 0377&c);
1201 				(void) fflush(stdout);
1202 				continue;
1203 			case DO:
1204 			case DONT:
1205 				c = getc(iop);
1206 				printf("%c%c%c", IAC, WONT, 0377&c);
1207 				(void) fflush(stdout);
1208 				continue;
1209 			case IAC:
1210 				break;
1211 			default:
1212 				continue;	/* ignore command */
1213 			}
1214 		    }
1215 		}
1216 		*cs++ = c;
1217 		if (--n <= 0 || c == '\n')
1218 			break;
1219 	}
1220 	if (c == EOF && cs == s)
1221 		return (NULL);
1222 	*cs++ = '\0';
1223 	if (ftpdebug) {
1224 		if (!guest && strncasecmp("pass ", s, 5) == 0) {
1225 			/* Don't syslog passwords */
1226 			syslog(LOG_DEBUG, "command: %.5s ???", s);
1227 		} else {
1228 			register char *cp;
1229 			register int len;
1230 
1231 			/* Don't syslog trailing CR-LF */
1232 			len = strlen(s);
1233 			cp = s + len - 1;
1234 			while (cp >= s && (*cp == '\n' || *cp == '\r')) {
1235 				--cp;
1236 				--len;
1237 			}
1238 			syslog(LOG_DEBUG, "command: %.*s", len, s);
1239 		}
1240 	}
1241 	return (s);
1242 }
1243 
1244 static void
1245 toolong(int signo)
1246 {
1247 
1248 	reply(421,
1249 	    "Timeout (%d seconds): closing control connection.", timeout);
1250 	if (logging)
1251 		syslog(LOG_INFO, "User %s timed out after %d seconds",
1252 		    (pw ? pw -> pw_name : "unknown"), timeout);
1253 	dologout(1);
1254 }
1255 
1256 static int
1257 yylex(void)
1258 {
1259 	static int cpos;
1260 	char *cp, *cp2;
1261 	struct tab *p;
1262 	int n;
1263 	char c;
1264 
1265 	for (;;) {
1266 		switch (state) {
1267 
1268 		case CMD:
1269 			(void) signal(SIGALRM, toolong);
1270 			(void) alarm((unsigned) timeout);
1271 			if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
1272 				reply(221, "You could at least say goodbye.");
1273 				dologout(0);
1274 			}
1275 			(void) alarm(0);
1276 #ifdef SETPROCTITLE
1277 			if (strncasecmp(cbuf, "PASS", 4) != 0)
1278 				setproctitle("%s: %s", proctitle, cbuf);
1279 #endif /* SETPROCTITLE */
1280 			if ((cp = strchr(cbuf, '\r'))) {
1281 				*cp++ = '\n';
1282 				*cp = '\0';
1283 			}
1284 			if ((cp = strpbrk(cbuf, " \n")))
1285 				cpos = cp - cbuf;
1286 			if (cpos == 0)
1287 				cpos = 4;
1288 			c = cbuf[cpos];
1289 			cbuf[cpos] = '\0';
1290 			upper(cbuf);
1291 			p = lookup(cmdtab, cbuf);
1292 			cbuf[cpos] = c;
1293 			if (p != 0) {
1294 				if (p->implemented == 0) {
1295 					nack(p->name);
1296 					return (LEXERR);
1297 				}
1298 				state = p->state;
1299 				yylval.s = p->name;
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 				if (p->implemented == 0) {
1319 					state = CMD;
1320 					nack(p->name);
1321 					return (LEXERR);
1322 				}
1323 				state = p->state;
1324 				yylval.s = p->name;
1325 				return (p->token);
1326 			}
1327 			state = CMD;
1328 			break;
1329 
1330 		case ZSTR1:
1331 		case OSTR:
1332 			if (cbuf[cpos] == '\n') {
1333 				state = CMD;
1334 				return (CRLF);
1335 			}
1336 			/* FALLTHROUGH */
1337 
1338 		case STR1:
1339 		dostr1:
1340 			if (cbuf[cpos] == ' ') {
1341 				cpos++;
1342 				state = state == OSTR ? STR2 : state+1;
1343 				return (SP);
1344 			}
1345 			break;
1346 
1347 		case ZSTR2:
1348 			if (cbuf[cpos] == '\n') {
1349 				state = CMD;
1350 				return (CRLF);
1351 			}
1352 			/* FALLTHROUGH */
1353 
1354 		case STR2:
1355 			cp = &cbuf[cpos];
1356 			n = strlen(cp);
1357 			cpos += n - 1;
1358 			/*
1359 			 * Make sure the string is nonempty and \n terminated.
1360 			 */
1361 			if (n > 1 && cbuf[cpos] == '\n') {
1362 				cbuf[cpos] = '\0';
1363 				yylval.s = copy(cp);
1364 				cbuf[cpos] = '\n';
1365 				state = ARGS;
1366 				return (STRING);
1367 			}
1368 			break;
1369 
1370 		case NSTR:
1371 			if (cbuf[cpos] == ' ') {
1372 				cpos++;
1373 				return (SP);
1374 			}
1375 			if (isdigit(cbuf[cpos])) {
1376 				cp = &cbuf[cpos];
1377 				while (isdigit(cbuf[++cpos]))
1378 					;
1379 				c = cbuf[cpos];
1380 				cbuf[cpos] = '\0';
1381 				yylval.u.i = atoi(cp);
1382 				cbuf[cpos] = c;
1383 				state = STR1;
1384 				return (NUMBER);
1385 			}
1386 			state = STR1;
1387 			goto dostr1;
1388 
1389 		case ARGS:
1390 			if (isdigit(cbuf[cpos])) {
1391 				cp = &cbuf[cpos];
1392 				while (isdigit(cbuf[++cpos]))
1393 					;
1394 				c = cbuf[cpos];
1395 				cbuf[cpos] = '\0';
1396 				yylval.u.i = atoi(cp);
1397 				yylval.u.o = strtoull(cp, (char **)NULL, 10);
1398 				cbuf[cpos] = c;
1399 				return (NUMBER);
1400 			}
1401 			if (strncasecmp(&cbuf[cpos], "ALL", 3) == 0
1402 			 && !isalnum(cbuf[cpos + 3])) {
1403 				cpos += 3;
1404 				return ALL;
1405 			}
1406 			switch (cbuf[cpos++]) {
1407 
1408 			case '\n':
1409 				state = CMD;
1410 				return (CRLF);
1411 
1412 			case ' ':
1413 				return (SP);
1414 
1415 			case ',':
1416 				return (COMMA);
1417 
1418 			case 'A':
1419 			case 'a':
1420 				return (A);
1421 
1422 			case 'B':
1423 			case 'b':
1424 				return (B);
1425 
1426 			case 'C':
1427 			case 'c':
1428 				return (C);
1429 
1430 			case 'E':
1431 			case 'e':
1432 				return (E);
1433 
1434 			case 'F':
1435 			case 'f':
1436 				return (F);
1437 
1438 			case 'I':
1439 			case 'i':
1440 				return (I);
1441 
1442 			case 'L':
1443 			case 'l':
1444 				return (L);
1445 
1446 			case 'N':
1447 			case 'n':
1448 				return (N);
1449 
1450 			case 'P':
1451 			case 'p':
1452 				return (P);
1453 
1454 			case 'R':
1455 			case 'r':
1456 				return (R);
1457 
1458 			case 'S':
1459 			case 's':
1460 				return (S);
1461 
1462 			case 'T':
1463 			case 't':
1464 				return (T);
1465 
1466 			}
1467 			break;
1468 
1469 		default:
1470 			fatalerror("Unknown state in scanner.");
1471 		}
1472 		state = CMD;
1473 		return (LEXERR);
1474 	}
1475 }
1476 
1477 void
1478 upper(char *s)
1479 {
1480 	while (*s != '\0') {
1481 		if (islower(*s))
1482 			*s = toupper(*s);
1483 		s++;
1484 	}
1485 }
1486 
1487 static char *
1488 copy(char *s)
1489 {
1490 	char *p;
1491 
1492 	p = malloc((unsigned) strlen(s) + 1);
1493 	if (p == NULL)
1494 		fatalerror("Ran out of memory.");
1495 	(void) strcpy(p, s);
1496 	return (p);
1497 }
1498 
1499 static void
1500 help(struct tab *ctab, char *s)
1501 {
1502 	struct tab *c;
1503 	int width, NCMDS;
1504 	char *type;
1505 
1506 	if (ctab == sitetab)
1507 		type = "SITE ";
1508 	else
1509 		type = "";
1510 	width = 0, NCMDS = 0;
1511 	for (c = ctab; c->name != NULL; c++) {
1512 		int len = strlen(c->name);
1513 
1514 		if (len > width)
1515 			width = len;
1516 		NCMDS++;
1517 	}
1518 	width = (width + 8) &~ 7;
1519 	if (s == 0) {
1520 		int i, j, w;
1521 		int columns, lines;
1522 
1523 		lreply(214, "The following %scommands are recognized %s.",
1524 		    type, "(* =>'s unimplemented)");
1525 		columns = 76 / width;
1526 		if (columns == 0)
1527 			columns = 1;
1528 		lines = (NCMDS + columns - 1) / columns;
1529 		for (i = 0; i < lines; i++) {
1530 			printf("   ");
1531 			for (j = 0; j < columns; j++) {
1532 				c = ctab + j * lines + i;
1533 				printf("%s%c", c->name,
1534 					c->implemented ? ' ' : '*');
1535 				if (c + lines >= &ctab[NCMDS])
1536 					break;
1537 				w = strlen(c->name) + 1;
1538 				while (w < width) {
1539 					putchar(' ');
1540 					w++;
1541 				}
1542 			}
1543 			printf("\r\n");
1544 		}
1545 		(void) fflush(stdout);
1546 		reply(214, "Direct comments to ftp-bugs@%s.", hostname);
1547 		return;
1548 	}
1549 	upper(s);
1550 	c = lookup(ctab, s);
1551 	if (c == (struct tab *)0) {
1552 		reply(502, "Unknown command %s.", s);
1553 		return;
1554 	}
1555 	if (c->implemented)
1556 		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
1557 	else
1558 		reply(214, "%s%-*s\t%s; unimplemented.", type, width,
1559 		    c->name, c->help);
1560 }
1561 
1562 static void
1563 sizecmd(char *filename)
1564 {
1565 	switch (type) {
1566 	case TYPE_L:
1567 	case TYPE_I: {
1568 		struct stat stbuf;
1569 		if (stat(filename, &stbuf) < 0)
1570 			perror_reply(550, filename);
1571 		else if (!S_ISREG(stbuf.st_mode))
1572 			reply(550, "%s: not a plain file.", filename);
1573 		else
1574 			reply(213, "%qu", stbuf.st_size);
1575 		break; }
1576 	case TYPE_A: {
1577 		FILE *fin;
1578 		int c;
1579 		off_t count;
1580 		struct stat stbuf;
1581 		fin = fopen(filename, "r");
1582 		if (fin == NULL) {
1583 			perror_reply(550, filename);
1584 			return;
1585 		}
1586 		if (fstat(fileno(fin), &stbuf) < 0) {
1587 			perror_reply(550, filename);
1588 			(void) fclose(fin);
1589 			return;
1590 		} else if (!S_ISREG(stbuf.st_mode)) {
1591 			reply(550, "%s: not a plain file.", filename);
1592 			(void) fclose(fin);
1593 			return;
1594 		} else if (stbuf.st_size > MAXASIZE) {
1595 			reply(550, "%s: too large for type A SIZE.", filename);
1596 			(void) fclose(fin);
1597 			return;
1598 		}
1599 
1600 		count = 0;
1601 		while((c=getc(fin)) != EOF) {
1602 			if (c == '\n')	/* will get expanded to \r\n */
1603 				count++;
1604 			count++;
1605 		}
1606 		(void) fclose(fin);
1607 
1608 		reply(213, "%qd", count);
1609 		break; }
1610 	default:
1611 		reply(504, "SIZE not implemented for type %s.",
1612 		           typenames[type]);
1613 	}
1614 }
1615 
1616 /* Return 1, if port check is done. Return 0, if not yet. */
1617 static int
1618 port_check(const char *pcmd)
1619 {
1620 	if (his_addr.su_family == AF_INET) {
1621 		if (data_dest.su_family != AF_INET) {
1622 			usedefault = 1;
1623 			reply(500, "Invalid address rejected.");
1624 			return 1;
1625 		}
1626 		if (paranoid &&
1627 		    ((ntohs(data_dest.su_port) < IPPORT_RESERVED) ||
1628 		     memcmp(&data_dest.su_sin.sin_addr,
1629 			    &his_addr.su_sin.sin_addr,
1630 			    sizeof(data_dest.su_sin.sin_addr)))) {
1631 			usedefault = 1;
1632 			reply(500, "Illegal PORT range rejected.");
1633 		} else {
1634 			usedefault = 0;
1635 			if (pdata >= 0) {
1636 				(void) close(pdata);
1637 				pdata = -1;
1638 			}
1639 			reply(200, "%s command successful.", pcmd);
1640 		}
1641 		return 1;
1642 	}
1643 	return 0;
1644 }
1645 
1646 static int
1647 check_login1(void)
1648 {
1649 	if (logged_in)
1650 		return 1;
1651 	else {
1652 		reply(530, "Please login with USER and PASS.");
1653 		return 0;
1654 	}
1655 }
1656 
1657 #ifdef INET6
1658 /* Return 1, if port check is done. Return 0, if not yet. */
1659 static int
1660 port_check_v6(const char *pcmd)
1661 {
1662 	if (his_addr.su_family == AF_INET6) {
1663 		if (IN6_IS_ADDR_V4MAPPED(&his_addr.su_sin6.sin6_addr))
1664 			/* Convert data_dest into v4 mapped sockaddr.*/
1665 			v4map_data_dest();
1666 		if (data_dest.su_family != AF_INET6) {
1667 			usedefault = 1;
1668 			reply(500, "Invalid address rejected.");
1669 			return 1;
1670 		}
1671 		if (paranoid &&
1672 		    ((ntohs(data_dest.su_port) < IPPORT_RESERVED) ||
1673 		     memcmp(&data_dest.su_sin6.sin6_addr,
1674 			    &his_addr.su_sin6.sin6_addr,
1675 			    sizeof(data_dest.su_sin6.sin6_addr)))) {
1676 			usedefault = 1;
1677 			reply(500, "Illegal PORT range rejected.");
1678 		} else {
1679 			usedefault = 0;
1680 			if (pdata >= 0) {
1681 				(void) close(pdata);
1682 				pdata = -1;
1683 			}
1684 			reply(200, "%s command successful.", pcmd);
1685 		}
1686 		return 1;
1687 	}
1688 	return 0;
1689 }
1690 
1691 static void
1692 v4map_data_dest(void)
1693 {
1694 	struct in_addr savedaddr;
1695 	int savedport;
1696 
1697 	if (data_dest.su_family != AF_INET) {
1698 		usedefault = 1;
1699 		reply(500, "Invalid address rejected.");
1700 		return;
1701 	}
1702 
1703 	savedaddr = data_dest.su_sin.sin_addr;
1704 	savedport = data_dest.su_port;
1705 
1706 	memset(&data_dest, 0, sizeof(data_dest));
1707 	data_dest.su_sin6.sin6_len = sizeof(struct sockaddr_in6);
1708 	data_dest.su_sin6.sin6_family = AF_INET6;
1709 	data_dest.su_sin6.sin6_port = savedport;
1710 	memset((caddr_t)&data_dest.su_sin6.sin6_addr.s6_addr[10], 0xff, 2);
1711 	memcpy((caddr_t)&data_dest.su_sin6.sin6_addr.s6_addr[12],
1712 	       (caddr_t)&savedaddr, sizeof(savedaddr));
1713 }
1714 #endif
1715