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