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