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