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