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