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