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