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