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