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