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