xref: /haiku/src/bin/network/ftpd/ftpcmd.y (revision 82a8a20999118b748396cf16a33c47c3b0c0222d)
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  * getline - a hacked up version of fgets to ignore TELNET escape codes.
1198  */
1199 char *
1200 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(s);
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 || c == '\n')
1253 			break;
1254 	}
1255 got_eof:
1256 	sigprocmask(SIG_SETMASK, &osset, NULL);
1257 	if (c == EOF && cs == s)
1258 		return (NULL);
1259 	*cs++ = '\0';
1260 	if (ftpdebug) {
1261 		if (!guest && strncasecmp("pass ", s, 5) == 0) {
1262 			/* Don't syslog passwords */
1263 			syslog(LOG_DEBUG, "command: %.5s ???", s);
1264 		} else {
1265 			register char *cp;
1266 			register int len;
1267 
1268 			/* Don't syslog trailing CR-LF */
1269 			len = strlen(s);
1270 			cp = s + len - 1;
1271 			while (cp >= s && (*cp == '\n' || *cp == '\r')) {
1272 				--cp;
1273 				--len;
1274 			}
1275 			syslog(LOG_DEBUG, "command: %.*s", len, s);
1276 		}
1277 	}
1278 	return (s);
1279 }
1280 
1281 static void
1282 toolong(int signo)
1283 {
1284 
1285 	reply(421,
1286 	    "Timeout (%d seconds): closing control connection.", timeout);
1287 	if (logging)
1288 		syslog(LOG_INFO, "User %s timed out after %d seconds",
1289 		    (pw ? pw -> pw_name : "unknown"), timeout);
1290 	dologout(1);
1291 }
1292 
1293 static int
1294 yylex(void)
1295 {
1296 	static int cpos;
1297 	char *cp, *cp2;
1298 	struct tab *p;
1299 	int n;
1300 	char c;
1301 
1302 	for (;;) {
1303 		switch (state) {
1304 
1305 		case CMD:
1306 			(void) signal(SIGALRM, toolong);
1307 			(void) alarm(timeout);
1308 			if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
1309 				reply(221, "You could at least say goodbye.");
1310 				dologout(0);
1311 			}
1312 			(void) 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 	(void) 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 		(void) 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, "%lld", (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 			(void) fclose(fin);
1626 			return;
1627 		} else if (!S_ISREG(stbuf.st_mode)) {
1628 			reply(550, "%s: not a plain file.", filename);
1629 			(void) fclose(fin);
1630 			return;
1631 		} else if (stbuf.st_size > MAXASIZE) {
1632 			reply(550, "%s: too large for type A SIZE.", filename);
1633 			(void) 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 		(void) fclose(fin);
1644 
1645 		reply(213, "%lld", (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 				(void) 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 				(void) 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