xref: /freebsd/libexec/ftpd/ftpcmd.y (revision 87569f75a91f298c52a71823c04d41cf53c88889)
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$");
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 noepsv;
99 extern	int noretr;
100 extern	int noguestretr;
101 extern	char *typenames[]; /* defined in <arpa/ftp.h> included from ftpd.c */
102 
103 off_t	restart_point;
104 
105 static	int cmd_type;
106 static	int cmd_form;
107 static	int cmd_bytesz;
108 static	int state;
109 char	cbuf[512];
110 char	*fromname = NULL;
111 
112 extern int epsvall;
113 
114 %}
115 
116 %union {
117 	struct {
118 		off_t	o;
119 		int	i;
120 	} u;
121 	char   *s;
122 }
123 
124 %token
125 	A	B	C	E	F	I
126 	L	N	P	R	S	T
127 	ALL
128 
129 	SP	CRLF	COMMA
130 
131 	USER	PASS	ACCT	REIN	QUIT	PORT
132 	PASV	TYPE	STRU	MODE	RETR	STOR
133 	APPE	MLFL	MAIL	MSND	MSOM	MSAM
134 	MRSQ	MRCP	ALLO	REST	RNFR	RNTO
135 	ABOR	DELE	CWD	LIST	NLST	SITE
136 	STAT	HELP	NOOP	MKD	RMD	PWD
137 	CDUP	STOU	SMNT	SYST	SIZE	MDTM
138 	LPRT	LPSV	EPRT	EPSV
139 
140 	UMASK	IDLE	CHMOD	MDFIVE
141 
142 	LEXERR	NOTIMPL
143 
144 %token	<s> STRING
145 %token	<u> NUMBER
146 
147 %type	<u.i> check_login octal_number byte_size
148 %type	<u.i> check_login_ro check_login_epsv
149 %type	<u.i> struct_code mode_code type_code form_code
150 %type	<s> pathstring pathname password username
151 %type	<s> ALL NOTIMPL
152 
153 %start	cmd_list
154 
155 %%
156 
157 cmd_list
158 	: /* empty */
159 	| cmd_list cmd
160 		{
161 			if (fromname)
162 				free(fromname);
163 			fromname = NULL;
164 			restart_point = 0;
165 		}
166 	| cmd_list rcmd
167 	;
168 
169 cmd
170 	: USER SP username CRLF
171 		{
172 			user($3);
173 			free($3);
174 		}
175 	| PASS SP password CRLF
176 		{
177 			pass($3);
178 			free($3);
179 		}
180 	| PASS CRLF
181 		{
182 			pass("");
183 		}
184 	| PORT check_login SP host_port CRLF
185 		{
186 			if (epsvall) {
187 				reply(501, "No PORT allowed after EPSV ALL.");
188 				goto port_done;
189 			}
190 			if (!$2)
191 				goto port_done;
192 			if (port_check("PORT") == 1)
193 				goto port_done;
194 #ifdef INET6
195 			if ((his_addr.su_family != AF_INET6 ||
196 			     !IN6_IS_ADDR_V4MAPPED(&his_addr.su_sin6.sin6_addr))) {
197 				/* shoud never happen */
198 				usedefault = 1;
199 				reply(500, "Invalid address rejected.");
200 				goto port_done;
201 			}
202 			port_check_v6("pcmd");
203 #endif
204 		port_done:
205 			;
206 		}
207 	| LPRT check_login SP host_long_port CRLF
208 		{
209 			if (epsvall) {
210 				reply(501, "No LPRT allowed after EPSV ALL.");
211 				goto lprt_done;
212 			}
213 			if (!$2)
214 				goto lprt_done;
215 			if (port_check("LPRT") == 1)
216 				goto lprt_done;
217 #ifdef INET6
218 			if (his_addr.su_family != AF_INET6) {
219 				usedefault = 1;
220 				reply(500, "Invalid address rejected.");
221 				goto lprt_done;
222 			}
223 			if (port_check_v6("LPRT") == 1)
224 				goto lprt_done;
225 #endif
226 		lprt_done:
227 			;
228 		}
229 	| EPRT check_login SP STRING CRLF
230 		{
231 			char delim;
232 			char *tmp = NULL;
233 			char *p, *q;
234 			char *result[3];
235 			struct addrinfo hints;
236 			struct addrinfo *res;
237 			int i;
238 
239 			if (epsvall) {
240 				reply(501, "No EPRT allowed after EPSV ALL.");
241 				goto eprt_done;
242 			}
243 			if (!$2)
244 				goto eprt_done;
245 
246 			memset(&data_dest, 0, sizeof(data_dest));
247 			tmp = strdup($4);
248 			if (ftpdebug)
249 				syslog(LOG_DEBUG, "%s", tmp);
250 			if (!tmp) {
251 				fatalerror("not enough core");
252 				/*NOTREACHED*/
253 			}
254 			p = tmp;
255 			delim = p[0];
256 			p++;
257 			memset(result, 0, sizeof(result));
258 			for (i = 0; i < 3; i++) {
259 				q = strchr(p, delim);
260 				if (!q || *q != delim) {
261 		parsefail:
262 					reply(500,
263 						"Invalid argument, rejected.");
264 					if (tmp)
265 						free(tmp);
266 					usedefault = 1;
267 					goto eprt_done;
268 				}
269 				*q++ = '\0';
270 				result[i] = p;
271 				if (ftpdebug)
272 					syslog(LOG_DEBUG, "%d: %s", i, p);
273 				p = q;
274 			}
275 
276 			/* some more sanity check */
277 			p = result[0];
278 			while (*p) {
279 				if (!isdigit(*p))
280 					goto parsefail;
281 				p++;
282 			}
283 			p = result[2];
284 			while (*p) {
285 				if (!isdigit(*p))
286 					goto parsefail;
287 				p++;
288 			}
289 
290 			/* grab address */
291 			memset(&hints, 0, sizeof(hints));
292 			if (atoi(result[0]) == 1)
293 				hints.ai_family = PF_INET;
294 #ifdef INET6
295 			else if (atoi(result[0]) == 2)
296 				hints.ai_family = PF_INET6;
297 #endif
298 			else
299 				hints.ai_family = PF_UNSPEC;	/*XXX*/
300 			hints.ai_socktype = SOCK_STREAM;
301 			i = getaddrinfo(result[1], result[2], &hints, &res);
302 			if (i)
303 				goto parsefail;
304 			memcpy(&data_dest, res->ai_addr, res->ai_addrlen);
305 #ifdef INET6
306 			if (his_addr.su_family == AF_INET6
307 			    && data_dest.su_family == AF_INET6) {
308 				/* XXX more sanity checks! */
309 				data_dest.su_sin6.sin6_scope_id =
310 					his_addr.su_sin6.sin6_scope_id;
311 			}
312 #endif
313 			free(tmp);
314 			tmp = NULL;
315 
316 			if (port_check("EPRT") == 1)
317 				goto eprt_done;
318 #ifdef INET6
319 			if (his_addr.su_family != AF_INET6) {
320 				usedefault = 1;
321 				reply(500, "Invalid address rejected.");
322 				goto eprt_done;
323 			}
324 			if (port_check_v6("EPRT") == 1)
325 				goto eprt_done;
326 #endif
327 		eprt_done:
328 			free($4);
329 		}
330 	| PASV check_login CRLF
331 		{
332 			if (epsvall)
333 				reply(501, "No PASV allowed after EPSV ALL.");
334 			else if ($2)
335 				passive();
336 		}
337 	| LPSV check_login CRLF
338 		{
339 			if (epsvall)
340 				reply(501, "No LPSV allowed after EPSV ALL.");
341 			else if ($2)
342 				long_passive("LPSV", PF_UNSPEC);
343 		}
344 	| EPSV check_login_epsv SP NUMBER CRLF
345 		{
346 			if ($2) {
347 				int pf;
348 				switch ($4.i) {
349 				case 1:
350 					pf = PF_INET;
351 					break;
352 #ifdef INET6
353 				case 2:
354 					pf = PF_INET6;
355 					break;
356 #endif
357 				default:
358 					pf = -1;	/*junk value*/
359 					break;
360 				}
361 				long_passive("EPSV", pf);
362 			}
363 		}
364 	| EPSV check_login_epsv SP ALL CRLF
365 		{
366 			if ($2) {
367 				reply(200, "EPSV ALL command successful.");
368 				epsvall++;
369 			}
370 		}
371 	| EPSV check_login_epsv CRLF
372 		{
373 			if ($2)
374 				long_passive("EPSV", PF_UNSPEC);
375 		}
376 	| TYPE check_login SP type_code CRLF
377 		{
378 			if ($2) {
379 				switch (cmd_type) {
380 
381 				case TYPE_A:
382 					if (cmd_form == FORM_N) {
383 						reply(200, "Type set to A.");
384 						type = cmd_type;
385 						form = cmd_form;
386 					} else
387 						reply(504, "Form must be N.");
388 					break;
389 
390 				case TYPE_E:
391 					reply(504, "Type E not implemented.");
392 					break;
393 
394 				case TYPE_I:
395 					reply(200, "Type set to I.");
396 					type = cmd_type;
397 					break;
398 
399 				case TYPE_L:
400 #if CHAR_BIT == 8
401 					if (cmd_bytesz == 8) {
402 						reply(200,
403 						    "Type set to L (byte size 8).");
404 						type = cmd_type;
405 					} else
406 						reply(504, "Byte size must be 8.");
407 #else /* CHAR_BIT == 8 */
408 					UNIMPLEMENTED for CHAR_BIT != 8
409 #endif /* CHAR_BIT == 8 */
410 				}
411 			}
412 		}
413 	| STRU check_login SP struct_code CRLF
414 		{
415 			if ($2) {
416 				switch ($4) {
417 
418 				case STRU_F:
419 					reply(200, "STRU F accepted.");
420 					break;
421 
422 				default:
423 					reply(504, "Unimplemented STRU type.");
424 				}
425 			}
426 		}
427 	| MODE check_login SP mode_code CRLF
428 		{
429 			if ($2) {
430 				switch ($4) {
431 
432 				case MODE_S:
433 					reply(200, "MODE S accepted.");
434 					break;
435 
436 				default:
437 					reply(502, "Unimplemented MODE type.");
438 				}
439 			}
440 		}
441 	| ALLO check_login SP NUMBER CRLF
442 		{
443 			if ($2) {
444 				reply(202, "ALLO command ignored.");
445 			}
446 		}
447 	| ALLO check_login SP NUMBER SP R SP NUMBER CRLF
448 		{
449 			if ($2) {
450 				reply(202, "ALLO command ignored.");
451 			}
452 		}
453 	| RETR check_login SP pathname CRLF
454 		{
455 			if (noretr || (guest && noguestretr))
456 				reply(500, "RETR command disabled.");
457 			else if ($2 && $4 != NULL)
458 				retrieve(NULL, $4);
459 
460 			if ($4 != NULL)
461 				free($4);
462 		}
463 	| STOR check_login_ro SP pathname CRLF
464 		{
465 			if ($2 && $4 != NULL)
466 				store($4, "w", 0);
467 			if ($4 != NULL)
468 				free($4);
469 		}
470 	| APPE check_login_ro SP pathname CRLF
471 		{
472 			if ($2 && $4 != NULL)
473 				store($4, "a", 0);
474 			if ($4 != NULL)
475 				free($4);
476 		}
477 	| NLST check_login CRLF
478 		{
479 			if ($2)
480 				send_file_list(".");
481 		}
482 	| NLST check_login SP pathstring CRLF
483 		{
484 			if ($2)
485 				send_file_list($4);
486 			free($4);
487 		}
488 	| LIST check_login CRLF
489 		{
490 			if ($2)
491 				retrieve(_PATH_LS " -lgA", "");
492 		}
493 	| LIST check_login SP pathstring CRLF
494 		{
495 			if ($2)
496 				retrieve(_PATH_LS " -lgA %s", $4);
497 			free($4);
498 		}
499 	| STAT check_login SP pathname CRLF
500 		{
501 			if ($2 && $4 != NULL)
502 				statfilecmd($4);
503 			if ($4 != NULL)
504 				free($4);
505 		}
506 	| STAT check_login CRLF
507 		{
508 			if ($2) {
509 				statcmd();
510 			}
511 		}
512 	| DELE check_login_ro SP pathname CRLF
513 		{
514 			if ($2 && $4 != NULL)
515 				delete($4);
516 			if ($4 != NULL)
517 				free($4);
518 		}
519 	| RNTO check_login_ro SP pathname CRLF
520 		{
521 			if ($2 && $4 != NULL) {
522 				if (fromname) {
523 					renamecmd(fromname, $4);
524 					free(fromname);
525 					fromname = NULL;
526 				} else {
527 					reply(503, "Bad sequence of commands.");
528 				}
529 			}
530 			if ($4 != NULL)
531 				free($4);
532 		}
533 	| ABOR check_login CRLF
534 		{
535 			if ($2)
536 				reply(225, "ABOR command successful.");
537 		}
538 	| CWD check_login CRLF
539 		{
540 			if ($2) {
541 				cwd(homedir);
542 			}
543 		}
544 	| CWD check_login SP pathname CRLF
545 		{
546 			if ($2 && $4 != NULL)
547 				cwd($4);
548 			if ($4 != NULL)
549 				free($4);
550 		}
551 	| HELP CRLF
552 		{
553 			help(cmdtab, NULL);
554 		}
555 	| HELP SP STRING CRLF
556 		{
557 			char *cp = $3;
558 
559 			if (strncasecmp(cp, "SITE", 4) == 0) {
560 				cp = $3 + 4;
561 				if (*cp == ' ')
562 					cp++;
563 				if (*cp)
564 					help(sitetab, cp);
565 				else
566 					help(sitetab, NULL);
567 			} else
568 				help(cmdtab, $3);
569 			free($3);
570 		}
571 	| NOOP CRLF
572 		{
573 			reply(200, "NOOP command successful.");
574 		}
575 	| MKD check_login_ro SP pathname CRLF
576 		{
577 			if ($2 && $4 != NULL)
578 				makedir($4);
579 			if ($4 != NULL)
580 				free($4);
581 		}
582 	| RMD check_login_ro SP pathname CRLF
583 		{
584 			if ($2 && $4 != NULL)
585 				removedir($4);
586 			if ($4 != NULL)
587 				free($4);
588 		}
589 	| PWD check_login CRLF
590 		{
591 			if ($2)
592 				pwd();
593 		}
594 	| CDUP check_login CRLF
595 		{
596 			if ($2)
597 				cwd("..");
598 		}
599 	| SITE SP HELP CRLF
600 		{
601 			help(sitetab, NULL);
602 		}
603 	| SITE SP HELP SP STRING CRLF
604 		{
605 			help(sitetab, $5);
606 			free($5);
607 		}
608 	| SITE SP MDFIVE check_login SP pathname CRLF
609 		{
610 			char p[64], *q;
611 
612 			if ($4 && $6) {
613 				q = MD5File($6, p);
614 				if (q != NULL)
615 					reply(200, "MD5(%s) = %s", $6, p);
616 				else
617 					perror_reply(550, $6);
618 			}
619 			if ($6)
620 				free($6);
621 		}
622 	| SITE SP UMASK check_login CRLF
623 		{
624 			int oldmask;
625 
626 			if ($4) {
627 				oldmask = umask(0);
628 				(void) umask(oldmask);
629 				reply(200, "Current UMASK is %03o.", oldmask);
630 			}
631 		}
632 	| SITE SP UMASK check_login SP octal_number CRLF
633 		{
634 			int oldmask;
635 
636 			if ($4) {
637 				if (($6 == -1) || ($6 > 0777)) {
638 					reply(501, "Bad UMASK value.");
639 				} else {
640 					oldmask = umask($6);
641 					reply(200,
642 					    "UMASK set to %03o (was %03o).",
643 					    $6, oldmask);
644 				}
645 			}
646 		}
647 	| SITE SP CHMOD check_login_ro SP octal_number SP pathname CRLF
648 		{
649 			if ($4 && ($8 != NULL)) {
650 				if (($6 == -1 ) || ($6 > 0777))
651 					reply(501, "Bad mode value.");
652 				else if (chmod($8, $6) < 0)
653 					perror_reply(550, $8);
654 				else
655 					reply(200, "CHMOD command successful.");
656 			}
657 			if ($8 != NULL)
658 				free($8);
659 		}
660 	| SITE SP check_login IDLE CRLF
661 		{
662 			if ($3)
663 				reply(200,
664 			    	    "Current IDLE time limit is %d seconds; max %d.",
665 				    timeout, maxtimeout);
666 		}
667 	| SITE SP check_login IDLE SP NUMBER CRLF
668 		{
669 			if ($3) {
670 				if ($6.i < 30 || $6.i > maxtimeout) {
671 					reply(501,
672 					    "Maximum IDLE time must be between 30 and %d seconds.",
673 					    maxtimeout);
674 				} else {
675 					timeout = $6.i;
676 					(void) alarm(timeout);
677 					reply(200,
678 					    "Maximum IDLE time set to %d seconds.",
679 					    timeout);
680 				}
681 			}
682 		}
683 	| STOU check_login_ro SP pathname CRLF
684 		{
685 			if ($2 && $4 != NULL)
686 				store($4, "w", 1);
687 			if ($4 != NULL)
688 				free($4);
689 		}
690 	| SYST check_login CRLF
691 		{
692 			if ($2) {
693 				if (hostinfo)
694 #ifdef BSD
695 					reply(215, "UNIX Type: L%d Version: BSD-%d",
696 					      CHAR_BIT, BSD);
697 #else /* BSD */
698 					reply(215, "UNIX Type: L%d", CHAR_BIT);
699 #endif /* BSD */
700 				else
701 					reply(215, "UNKNOWN Type: L%d", CHAR_BIT);
702 			}
703 		}
704 
705 		/*
706 		 * SIZE is not in RFC959, but Postel has blessed it and
707 		 * it will be in the updated RFC.
708 		 *
709 		 * Return size of file in a format suitable for
710 		 * using with RESTART (we just count bytes).
711 		 */
712 	| SIZE check_login SP pathname CRLF
713 		{
714 			if ($2 && $4 != NULL)
715 				sizecmd($4);
716 			if ($4 != NULL)
717 				free($4);
718 		}
719 
720 		/*
721 		 * MDTM is not in RFC959, but Postel has blessed it and
722 		 * it will be in the updated RFC.
723 		 *
724 		 * Return modification time of file as an ISO 3307
725 		 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
726 		 * where xxx is the fractional second (of any precision,
727 		 * not necessarily 3 digits)
728 		 */
729 	| MDTM check_login SP pathname CRLF
730 		{
731 			if ($2 && $4 != NULL) {
732 				struct stat stbuf;
733 				if (stat($4, &stbuf) < 0)
734 					perror_reply(550, $4);
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 = 0;
770 			if ($2 && $4) {
771 				if (fromname)
772 					free(fromname);
773 				fromname = NULL;
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 = NULL;
788 				restart_point = $4.o;
789 				reply(350, "Restarting at %jd. %s",
790 				    (intmax_t)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(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, 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(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 == NULL) {
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, "%jd", (intmax_t)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, "%jd", (intmax_t)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, "Wildcard is 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