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