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