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