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