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