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