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