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