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