xref: /dragonfly/libexec/ftpd/ftpcmd.y (revision b40e316c)
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  * @(#)ftpcmd.y	8.3 (Berkeley) 4/6/94
36  * $FreeBSD: src/libexec/ftpd/ftpcmd.y,v 1.16.2.19 2003/02/11 14:28:28 yar Exp $
37  * $DragonFly: src/libexec/ftpd/ftpcmd.y,v 1.4 2004/06/19 20:36:04 joerg Exp $
38  */
39 
40 /*
41  * Grammar for FTP commands.
42  * See RFC 959.
43  */
44 
45 %{
46 
47 #include <sys/param.h>
48 #include <sys/socket.h>
49 #include <sys/stat.h>
50 
51 #include <netinet/in.h>
52 #include <arpa/ftp.h>
53 
54 #include <ctype.h>
55 #include <errno.h>
56 #include <glob.h>
57 #include <libutil.h>
58 #include <limits.h>
59 #include <md5.h>
60 #include <netdb.h>
61 #include <pwd.h>
62 #include <signal.h>
63 #include <stdio.h>
64 #include <stdlib.h>
65 #include <string.h>
66 #include <syslog.h>
67 #include <time.h>
68 #include <unistd.h>
69 
70 #include "extern.h"
71 #include "pathnames.h"
72 
73 extern	union sockunion data_dest, his_addr;
74 extern	int hostinfo;
75 extern	int logged_in;
76 extern	struct passwd *pw;
77 extern	int guest;
78 extern	char *homedir;
79 extern 	int paranoid;
80 extern	int logging;
81 extern	int type;
82 extern	int form;
83 extern	int ftpdebug;
84 extern	int timeout;
85 extern	int maxtimeout;
86 extern  int pdata;
87 extern	char *hostname;
88 extern	char remotehost[];
89 extern	char proctitle[];
90 extern	int usedefault;
91 extern  int transflag;
92 extern  char tmpline[];
93 extern	int readonly;
94 extern	int noepsv;
95 extern	int noretr;
96 extern	int noguestretr;
97 extern	char *typenames[]; /* defined in <arpa/ftp.h> included from ftpd.c */
98 
99 off_t	restart_point;
100 
101 static	int cmd_type;
102 static	int cmd_form;
103 static	int cmd_bytesz;
104 static	int state;
105 char	cbuf[512];
106 char	*fromname = (char *) 0;
107 
108 extern int epsvall;
109 
110 %}
111 
112 %union {
113 	struct {
114 		off_t	o;
115 		int	i;
116 	} u;
117 	char   *s;
118 }
119 
120 %token
121 	A	B	C	E	F	I
122 	L	N	P	R	S	T
123 	ALL
124 
125 	SP	CRLF	COMMA
126 
127 	USER	PASS	ACCT	REIN	QUIT	PORT
128 	PASV	TYPE	STRU	MODE	RETR	STOR
129 	APPE	MLFL	MAIL	MSND	MSOM	MSAM
130 	MRSQ	MRCP	ALLO	REST	RNFR	RNTO
131 	ABOR	DELE	CWD	LIST	NLST	SITE
132 	STAT	HELP	NOOP	MKD	RMD	PWD
133 	CDUP	STOU	SMNT	SYST	SIZE	MDTM
134 	LPRT	LPSV	EPRT	EPSV
135 
136 	UMASK	IDLE	CHMOD	MDFIVE
137 
138 	LEXERR	NOTIMPL
139 
140 %token	<s> STRING
141 %token	<u> NUMBER
142 
143 %type	<u.i> check_login octal_number byte_size
144 %type	<u.i> check_login_ro check_login_epsv
145 %type	<u.i> struct_code mode_code type_code form_code
146 %type	<s> pathstring pathname password username
147 %type	<s> ALL NOTIMPL
148 
149 %start	cmd_list
150 
151 %%
152 
153 cmd_list
154 	: /* empty */
155 	| cmd_list cmd
156 		{
157 			if (fromname)
158 				free(fromname);
159 			fromname = (char *) 0;
160 			restart_point = (off_t) 0;
161 		}
162 	| cmd_list rcmd
163 	;
164 
165 cmd
166 	: USER SP username CRLF
167 		{
168 			user($3);
169 			free($3);
170 		}
171 	| PASS SP password CRLF
172 		{
173 			pass($3);
174 			free($3);
175 		}
176 	| PASS CRLF
177 		{
178 			pass("");
179 		}
180 	| PORT check_login SP host_port CRLF
181 		{
182 			if (epsvall) {
183 				reply(501, "no PORT allowed after EPSV ALL");
184 				goto port_done;
185 			}
186 			if (!$2)
187 				goto port_done;
188 			if (port_check("PORT") == 1)
189 				goto port_done;
190 #ifdef INET6
191 			if ((his_addr.su_family != AF_INET6 ||
192 			     !IN6_IS_ADDR_V4MAPPED(&his_addr.su_sin6.sin6_addr))) {
193 				/* shoud never happen */
194 				usedefault = 1;
195 				reply(500, "Invalid address rejected.");
196 				goto port_done;
197 			}
198 			port_check_v6("pcmd");
199 #endif
200 		port_done:
201 			;
202 		}
203 	| LPRT check_login SP host_long_port CRLF
204 		{
205 			if (epsvall) {
206 				reply(501, "no LPRT allowed after EPSV ALL");
207 				goto lprt_done;
208 			}
209 			if (!$2)
210 				goto lprt_done;
211 			if (port_check("LPRT") == 1)
212 				goto lprt_done;
213 #ifdef INET6
214 			if (his_addr.su_family != AF_INET6) {
215 				usedefault = 1;
216 				reply(500, "Invalid address rejected.");
217 				goto lprt_done;
218 			}
219 			if (port_check_v6("LPRT") == 1)
220 				goto lprt_done;
221 #endif
222 		lprt_done:
223 			;
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 NBBY == 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 /* NBBY == 8 */
405 					UNIMPLEMENTED for NBBY != 8
406 #endif /* NBBY == 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 #ifdef unix
691 #ifdef BSD
692 			reply(215, "UNIX Type: L%d Version: BSD-%d",
693 				NBBY, BSD);
694 #else /* BSD */
695 			reply(215, "UNIX Type: L%d", NBBY);
696 #endif /* BSD */
697 #else /* unix */
698 			reply(215, "UNKNOWN Type: L%d", NBBY);
699 #endif /* unix */
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 = NBBY;
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 			/*
966 			 * Problem: this production is used for all pathname
967 			 * processing, but only gives a 550 error reply.
968 			 * This is a valid reply in some cases but not in others.
969 			 */
970 			if (logged_in && $1) {
971 				glob_t gl;
972 				char *p, **pp;
973 				int flags =
974 				 GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE;
975 				int n;
976 
977 				memset(&gl, 0, sizeof(gl));
978 				flags |= GLOB_LIMIT;
979 				gl.gl_matchc = MAXGLOBARGS;
980 				if (glob($1, flags, NULL, &gl) ||
981 				    gl.gl_pathc == 0) {
982 					reply(550, "wildcard expansion error");
983 					$$ = NULL;
984 				} else {
985 					n = 0;
986 					for (pp = gl.gl_pathv; *pp; pp++)
987 						if (strcspn(*pp, "\r\n") ==
988 						    strlen(*pp)) {
989 							p = *pp;
990 							n++;
991 						}
992 					if (n == 0)
993 						$$ = strdup($1);
994 					else if (n == 1)
995 						$$ = strdup(p);
996 					else {
997 						reply(550, "ambiguous");
998 						$$ = NULL;
999 					}
1000 				}
1001 				globfree(&gl);
1002 				free($1);
1003 			} else
1004 				$$ = $1;
1005 		}
1006 	;
1007 
1008 pathstring
1009 	: STRING
1010 	;
1011 
1012 octal_number
1013 	: NUMBER
1014 		{
1015 			int ret, dec, multby, digit;
1016 
1017 			/*
1018 			 * Convert a number that was read as decimal number
1019 			 * to what it would be if it had been read as octal.
1020 			 */
1021 			dec = $1.i;
1022 			multby = 1;
1023 			ret = 0;
1024 			while (dec) {
1025 				digit = dec%10;
1026 				if (digit > 7) {
1027 					ret = -1;
1028 					break;
1029 				}
1030 				ret += digit * multby;
1031 				multby *= 8;
1032 				dec /= 10;
1033 			}
1034 			$$ = ret;
1035 		}
1036 	;
1037 
1038 
1039 check_login
1040 	: /* empty */
1041 		{
1042 		$$ = check_login1();
1043 		}
1044 	;
1045 
1046 check_login_epsv
1047 	: /* empty */
1048 		{
1049 		if (noepsv) {
1050 			reply(500, "EPSV command disabled");
1051 			$$ = 0;
1052 		}
1053 		else
1054 			$$ = check_login1();
1055 		}
1056 	;
1057 
1058 check_login_ro
1059 	: /* empty */
1060 		{
1061 		if (readonly) {
1062 			reply(550, "Permission denied.");
1063 			$$ = 0;
1064 		}
1065 		else
1066 			$$ = check_login1();
1067 		}
1068 	;
1069 
1070 %%
1071 
1072 #define	CMD	0	/* beginning of command */
1073 #define	ARGS	1	/* expect miscellaneous arguments */
1074 #define	STR1	2	/* expect SP followed by STRING */
1075 #define	STR2	3	/* expect STRING */
1076 #define	OSTR	4	/* optional SP then STRING */
1077 #define	ZSTR1	5	/* optional SP then optional STRING */
1078 #define	ZSTR2	6	/* optional STRING after SP */
1079 #define	SITECMD	7	/* SITE command */
1080 #define	NSTR	8	/* Number followed by a string */
1081 
1082 #define	MAXGLOBARGS	1000
1083 
1084 #define	MAXASIZE	10240	/* Deny ASCII SIZE on files larger than that */
1085 
1086 struct tab {
1087 	char	*name;
1088 	short	token;
1089 	short	state;
1090 	short	implemented;	/* 1 if command is implemented */
1091 	char	*help;
1092 };
1093 
1094 struct tab cmdtab[] = {		/* In order defined in RFC 765 */
1095 	{ "USER", USER, STR1, 1,	"<sp> username" },
1096 	{ "PASS", PASS, ZSTR1, 1,	"[<sp> [password]]" },
1097 	{ "ACCT", ACCT, STR1, 0,	"(specify account)" },
1098 	{ "SMNT", SMNT, ARGS, 0,	"(structure mount)" },
1099 	{ "REIN", REIN, ARGS, 0,	"(reinitialize server state)" },
1100 	{ "QUIT", QUIT, ARGS, 1,	"(terminate service)", },
1101 	{ "PORT", PORT, ARGS, 1,	"<sp> b0, b1, b2, b3, b4, b5" },
1102 	{ "LPRT", LPRT, ARGS, 1,	"<sp> af, hal, h1, h2, h3,..., pal, p1, p2..." },
1103 	{ "EPRT", EPRT, STR1, 1,	"<sp> |af|addr|port|" },
1104 	{ "PASV", PASV, ARGS, 1,	"(set server in passive mode)" },
1105 	{ "LPSV", LPSV, ARGS, 1,	"(set server in passive mode)" },
1106 	{ "EPSV", EPSV, ARGS, 1,	"[<sp> af|ALL]" },
1107 	{ "TYPE", TYPE, ARGS, 1,	"<sp> { A | E | I | L }" },
1108 	{ "STRU", STRU, ARGS, 1,	"(specify file structure)" },
1109 	{ "MODE", MODE, ARGS, 1,	"(specify transfer mode)" },
1110 	{ "RETR", RETR, STR1, 1,	"<sp> file-name" },
1111 	{ "STOR", STOR, STR1, 1,	"<sp> file-name" },
1112 	{ "APPE", APPE, STR1, 1,	"<sp> file-name" },
1113 	{ "MLFL", MLFL, OSTR, 0,	"(mail file)" },
1114 	{ "MAIL", MAIL, OSTR, 0,	"(mail to user)" },
1115 	{ "MSND", MSND, OSTR, 0,	"(mail send to terminal)" },
1116 	{ "MSOM", MSOM, OSTR, 0,	"(mail send to terminal or mailbox)" },
1117 	{ "MSAM", MSAM, OSTR, 0,	"(mail send to terminal and mailbox)" },
1118 	{ "MRSQ", MRSQ, OSTR, 0,	"(mail recipient scheme question)" },
1119 	{ "MRCP", MRCP, STR1, 0,	"(mail recipient)" },
1120 	{ "ALLO", ALLO, ARGS, 1,	"allocate storage (vacuously)" },
1121 	{ "REST", REST, ARGS, 1,	"<sp> offset (restart command)" },
1122 	{ "RNFR", RNFR, STR1, 1,	"<sp> file-name" },
1123 	{ "RNTO", RNTO, STR1, 1,	"<sp> file-name" },
1124 	{ "ABOR", ABOR, ARGS, 1,	"(abort operation)" },
1125 	{ "DELE", DELE, STR1, 1,	"<sp> file-name" },
1126 	{ "CWD",  CWD,  OSTR, 1,	"[ <sp> directory-name ]" },
1127 	{ "XCWD", CWD,	OSTR, 1,	"[ <sp> directory-name ]" },
1128 	{ "LIST", LIST, OSTR, 1,	"[ <sp> path-name ]" },
1129 	{ "NLST", NLST, OSTR, 1,	"[ <sp> path-name ]" },
1130 	{ "SITE", SITE, SITECMD, 1,	"site-cmd [ <sp> arguments ]" },
1131 	{ "SYST", SYST, ARGS, 1,	"(get type of operating system)" },
1132 	{ "STAT", STAT, OSTR, 1,	"[ <sp> path-name ]" },
1133 	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
1134 	{ "NOOP", NOOP, ARGS, 1,	"" },
1135 	{ "MKD",  MKD,  STR1, 1,	"<sp> path-name" },
1136 	{ "XMKD", MKD,  STR1, 1,	"<sp> path-name" },
1137 	{ "RMD",  RMD,  STR1, 1,	"<sp> path-name" },
1138 	{ "XRMD", RMD,  STR1, 1,	"<sp> path-name" },
1139 	{ "PWD",  PWD,  ARGS, 1,	"(return current directory)" },
1140 	{ "XPWD", PWD,  ARGS, 1,	"(return current directory)" },
1141 	{ "CDUP", CDUP, ARGS, 1,	"(change to parent directory)" },
1142 	{ "XCUP", CDUP, ARGS, 1,	"(change to parent directory)" },
1143 	{ "STOU", STOU, STR1, 1,	"<sp> file-name" },
1144 	{ "SIZE", SIZE, OSTR, 1,	"<sp> path-name" },
1145 	{ "MDTM", MDTM, OSTR, 1,	"<sp> path-name" },
1146 	{ NULL,   0,    0,    0,	0 }
1147 };
1148 
1149 struct tab sitetab[] = {
1150 	{ "MD5", MDFIVE, STR1, 1,	"[ <sp> file-name ]" },
1151 	{ "UMASK", UMASK, ARGS, 1,	"[ <sp> umask ]" },
1152 	{ "IDLE", IDLE, ARGS, 1,	"[ <sp> maximum-idle-time ]" },
1153 	{ "CHMOD", CHMOD, NSTR, 1,	"<sp> mode <sp> file-name" },
1154 	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
1155 	{ NULL,   0,    0,    0,	0 }
1156 };
1157 
1158 static char	*copy (char *);
1159 static void	 help (struct tab *, char *);
1160 static struct tab *
1161 		 lookup (struct tab *, char *);
1162 static int	 port_check (const char *);
1163 static int	 port_check_v6 (const char *);
1164 static void	 sizecmd (char *);
1165 static void	 toolong (int);
1166 static void	 v4map_data_dest (void);
1167 static int	 yylex (void);
1168 
1169 static struct tab *
1170 lookup(p, cmd)
1171 	struct tab *p;
1172 	char *cmd;
1173 {
1174 
1175 	for (; p->name != NULL; p++)
1176 		if (strcmp(cmd, p->name) == 0)
1177 			return (p);
1178 	return (0);
1179 }
1180 
1181 #include <arpa/telnet.h>
1182 
1183 /*
1184  * getline - a hacked up version of fgets to ignore TELNET escape codes.
1185  */
1186 char *
1187 getline(s, n, iop)
1188 	char *s;
1189 	int n;
1190 	FILE *iop;
1191 {
1192 	int c;
1193 	register char *cs;
1194 
1195 	cs = s;
1196 /* tmpline may contain saved command from urgent mode interruption */
1197 	for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
1198 		*cs++ = tmpline[c];
1199 		if (tmpline[c] == '\n') {
1200 			*cs++ = '\0';
1201 			if (ftpdebug)
1202 				syslog(LOG_DEBUG, "command: %s", s);
1203 			tmpline[0] = '\0';
1204 			return(s);
1205 		}
1206 		if (c == 0)
1207 			tmpline[0] = '\0';
1208 	}
1209 	while ((c = getc(iop)) != EOF) {
1210 		c &= 0377;
1211 		if (c == IAC) {
1212 		    if ((c = getc(iop)) != EOF) {
1213 			c &= 0377;
1214 			switch (c) {
1215 			case WILL:
1216 			case WONT:
1217 				c = getc(iop);
1218 				printf("%c%c%c", IAC, DONT, 0377&c);
1219 				(void) fflush(stdout);
1220 				continue;
1221 			case DO:
1222 			case DONT:
1223 				c = getc(iop);
1224 				printf("%c%c%c", IAC, WONT, 0377&c);
1225 				(void) fflush(stdout);
1226 				continue;
1227 			case IAC:
1228 				break;
1229 			default:
1230 				continue;	/* ignore command */
1231 			}
1232 		    }
1233 		}
1234 		*cs++ = c;
1235 		if (--n <= 0 || c == '\n')
1236 			break;
1237 	}
1238 	if (c == EOF && cs == s)
1239 		return (NULL);
1240 	*cs++ = '\0';
1241 	if (ftpdebug) {
1242 		if (!guest && strncasecmp("pass ", s, 5) == 0) {
1243 			/* Don't syslog passwords */
1244 			syslog(LOG_DEBUG, "command: %.5s ???", s);
1245 		} else {
1246 			register char *cp;
1247 			register int len;
1248 
1249 			/* Don't syslog trailing CR-LF */
1250 			len = strlen(s);
1251 			cp = s + len - 1;
1252 			while (cp >= s && (*cp == '\n' || *cp == '\r')) {
1253 				--cp;
1254 				--len;
1255 			}
1256 			syslog(LOG_DEBUG, "command: %.*s", len, s);
1257 		}
1258 	}
1259 	return (s);
1260 }
1261 
1262 static void
1263 toolong(signo)
1264 	int signo;
1265 {
1266 
1267 	reply(421,
1268 	    "Timeout (%d seconds): closing control connection.", timeout);
1269 	if (logging)
1270 		syslog(LOG_INFO, "User %s timed out after %d seconds",
1271 		    (pw ? pw -> pw_name : "unknown"), timeout);
1272 	dologout(1);
1273 }
1274 
1275 static int
1276 yylex()
1277 {
1278 	static int cpos;
1279 	char *cp, *cp2;
1280 	struct tab *p;
1281 	int n;
1282 	char c;
1283 
1284 	for (;;) {
1285 		switch (state) {
1286 
1287 		case CMD:
1288 			(void) signal(SIGALRM, toolong);
1289 			(void) alarm((unsigned) timeout);
1290 			if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
1291 				reply(221, "You could at least say goodbye.");
1292 				dologout(0);
1293 			}
1294 			(void) alarm(0);
1295 #ifdef SETPROCTITLE
1296 			if (strncasecmp(cbuf, "PASS", 4) != 0)
1297 				setproctitle("%s: %s", proctitle, cbuf);
1298 #endif /* SETPROCTITLE */
1299 			if ((cp = strchr(cbuf, '\r'))) {
1300 				*cp++ = '\n';
1301 				*cp = '\0';
1302 			}
1303 			if ((cp = strpbrk(cbuf, " \n")))
1304 				cpos = cp - cbuf;
1305 			if (cpos == 0)
1306 				cpos = 4;
1307 			c = cbuf[cpos];
1308 			cbuf[cpos] = '\0';
1309 			upper(cbuf);
1310 			p = lookup(cmdtab, cbuf);
1311 			cbuf[cpos] = c;
1312 			if (p != 0) {
1313 				yylval.s = p->name;
1314 				if (!p->implemented)
1315 					return (NOTIMPL); /* state remains CMD */
1316 				state = p->state;
1317 				return (p->token);
1318 			}
1319 			break;
1320 
1321 		case SITECMD:
1322 			if (cbuf[cpos] == ' ') {
1323 				cpos++;
1324 				return (SP);
1325 			}
1326 			cp = &cbuf[cpos];
1327 			if ((cp2 = strpbrk(cp, " \n")))
1328 				cpos = cp2 - cbuf;
1329 			c = cbuf[cpos];
1330 			cbuf[cpos] = '\0';
1331 			upper(cp);
1332 			p = lookup(sitetab, cp);
1333 			cbuf[cpos] = c;
1334 			if (guest == 0 && p != 0) {
1335 				yylval.s = p->name;
1336 				if (!p->implemented) {
1337 					state = CMD;
1338 					return (NOTIMPL);
1339 				}
1340 				state = p->state;
1341 				return (p->token);
1342 			}
1343 			state = CMD;
1344 			break;
1345 
1346 		case ZSTR1:
1347 		case OSTR:
1348 			if (cbuf[cpos] == '\n') {
1349 				state = CMD;
1350 				return (CRLF);
1351 			}
1352 			/* FALLTHROUGH */
1353 
1354 		case STR1:
1355 		dostr1:
1356 			if (cbuf[cpos] == ' ') {
1357 				cpos++;
1358 				state = state == OSTR ? STR2 : state+1;
1359 				return (SP);
1360 			}
1361 			break;
1362 
1363 		case ZSTR2:
1364 			if (cbuf[cpos] == '\n') {
1365 				state = CMD;
1366 				return (CRLF);
1367 			}
1368 			/* FALLTHROUGH */
1369 
1370 		case STR2:
1371 			cp = &cbuf[cpos];
1372 			n = strlen(cp);
1373 			cpos += n - 1;
1374 			/*
1375 			 * Make sure the string is nonempty and \n terminated.
1376 			 */
1377 			if (n > 1 && cbuf[cpos] == '\n') {
1378 				cbuf[cpos] = '\0';
1379 				yylval.s = copy(cp);
1380 				cbuf[cpos] = '\n';
1381 				state = ARGS;
1382 				return (STRING);
1383 			}
1384 			break;
1385 
1386 		case NSTR:
1387 			if (cbuf[cpos] == ' ') {
1388 				cpos++;
1389 				return (SP);
1390 			}
1391 			if (isdigit(cbuf[cpos])) {
1392 				cp = &cbuf[cpos];
1393 				while (isdigit(cbuf[++cpos]))
1394 					;
1395 				c = cbuf[cpos];
1396 				cbuf[cpos] = '\0';
1397 				yylval.u.i = atoi(cp);
1398 				cbuf[cpos] = c;
1399 				state = STR1;
1400 				return (NUMBER);
1401 			}
1402 			state = STR1;
1403 			goto dostr1;
1404 
1405 		case ARGS:
1406 			if (isdigit(cbuf[cpos])) {
1407 				cp = &cbuf[cpos];
1408 				while (isdigit(cbuf[++cpos]))
1409 					;
1410 				c = cbuf[cpos];
1411 				cbuf[cpos] = '\0';
1412 				yylval.u.i = atoi(cp);
1413 				yylval.u.o = strtoull(cp, (char **)NULL, 10);
1414 				cbuf[cpos] = c;
1415 				return (NUMBER);
1416 			}
1417 			if (strncasecmp(&cbuf[cpos], "ALL", 3) == 0
1418 			 && !isalnum(cbuf[cpos + 3])) {
1419 				cpos += 3;
1420 				return ALL;
1421 			}
1422 			switch (cbuf[cpos++]) {
1423 
1424 			case '\n':
1425 				state = CMD;
1426 				return (CRLF);
1427 
1428 			case ' ':
1429 				return (SP);
1430 
1431 			case ',':
1432 				return (COMMA);
1433 
1434 			case 'A':
1435 			case 'a':
1436 				return (A);
1437 
1438 			case 'B':
1439 			case 'b':
1440 				return (B);
1441 
1442 			case 'C':
1443 			case 'c':
1444 				return (C);
1445 
1446 			case 'E':
1447 			case 'e':
1448 				return (E);
1449 
1450 			case 'F':
1451 			case 'f':
1452 				return (F);
1453 
1454 			case 'I':
1455 			case 'i':
1456 				return (I);
1457 
1458 			case 'L':
1459 			case 'l':
1460 				return (L);
1461 
1462 			case 'N':
1463 			case 'n':
1464 				return (N);
1465 
1466 			case 'P':
1467 			case 'p':
1468 				return (P);
1469 
1470 			case 'R':
1471 			case 'r':
1472 				return (R);
1473 
1474 			case 'S':
1475 			case 's':
1476 				return (S);
1477 
1478 			case 'T':
1479 			case 't':
1480 				return (T);
1481 
1482 			}
1483 			break;
1484 
1485 		default:
1486 			fatalerror("Unknown state in scanner.");
1487 		}
1488 		state = CMD;
1489 		return (LEXERR);
1490 	}
1491 }
1492 
1493 void
1494 upper(s)
1495 	char *s;
1496 {
1497 	while (*s != '\0') {
1498 		if (islower(*s))
1499 			*s = toupper(*s);
1500 		s++;
1501 	}
1502 }
1503 
1504 static char *
1505 copy(s)
1506 	char *s;
1507 {
1508 	char *p;
1509 
1510 	p = malloc((unsigned) strlen(s) + 1);
1511 	if (p == NULL)
1512 		fatalerror("Ran out of memory.");
1513 	(void) strcpy(p, s);
1514 	return (p);
1515 }
1516 
1517 static void
1518 help(ctab, s)
1519 	struct tab *ctab;
1520 	char *s;
1521 {
1522 	struct tab *c;
1523 	int width, NCMDS;
1524 	char *type;
1525 
1526 	if (ctab == sitetab)
1527 		type = "SITE ";
1528 	else
1529 		type = "";
1530 	width = 0, NCMDS = 0;
1531 	for (c = ctab; c->name != NULL; c++) {
1532 		int len = strlen(c->name);
1533 
1534 		if (len > width)
1535 			width = len;
1536 		NCMDS++;
1537 	}
1538 	width = (width + 8) &~ 7;
1539 	if (s == 0) {
1540 		int i, j, w;
1541 		int columns, lines;
1542 
1543 		lreply(214, "The following %scommands are recognized %s.",
1544 		    type, "(* =>'s unimplemented)");
1545 		columns = 76 / width;
1546 		if (columns == 0)
1547 			columns = 1;
1548 		lines = (NCMDS + columns - 1) / columns;
1549 		for (i = 0; i < lines; i++) {
1550 			printf("   ");
1551 			for (j = 0; j < columns; j++) {
1552 				c = ctab + j * lines + i;
1553 				printf("%s%c", c->name,
1554 					c->implemented ? ' ' : '*');
1555 				if (c + lines >= &ctab[NCMDS])
1556 					break;
1557 				w = strlen(c->name) + 1;
1558 				while (w < width) {
1559 					putchar(' ');
1560 					w++;
1561 				}
1562 			}
1563 			printf("\r\n");
1564 		}
1565 		(void) fflush(stdout);
1566 		if (hostinfo)
1567 			reply(214, "Direct comments to ftp-bugs@%s.", hostname);
1568 		else
1569 			reply(214, "End.");
1570 		return;
1571 	}
1572 	upper(s);
1573 	c = lookup(ctab, s);
1574 	if (c == (struct tab *)0) {
1575 		reply(502, "Unknown command %s.", s);
1576 		return;
1577 	}
1578 	if (c->implemented)
1579 		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
1580 	else
1581 		reply(214, "%s%-*s\t%s; unimplemented.", type, width,
1582 		    c->name, c->help);
1583 }
1584 
1585 static void
1586 sizecmd(filename)
1587 	char *filename;
1588 {
1589 	switch (type) {
1590 	case TYPE_L:
1591 	case TYPE_I: {
1592 		struct stat stbuf;
1593 		if (stat(filename, &stbuf) < 0)
1594 			perror_reply(550, filename);
1595 		else if (!S_ISREG(stbuf.st_mode))
1596 			reply(550, "%s: not a plain file.", filename);
1597 		else
1598 			reply(213, "%qu", stbuf.st_size);
1599 		break; }
1600 	case TYPE_A: {
1601 		FILE *fin;
1602 		int c;
1603 		off_t count;
1604 		struct stat stbuf;
1605 		fin = fopen(filename, "r");
1606 		if (fin == NULL) {
1607 			perror_reply(550, filename);
1608 			return;
1609 		}
1610 		if (fstat(fileno(fin), &stbuf) < 0) {
1611 			perror_reply(550, filename);
1612 			(void) fclose(fin);
1613 			return;
1614 		} else if (!S_ISREG(stbuf.st_mode)) {
1615 			reply(550, "%s: not a plain file.", filename);
1616 			(void) fclose(fin);
1617 			return;
1618 		} else if (stbuf.st_size > MAXASIZE) {
1619 			reply(550, "%s: too large for type A SIZE.", filename);
1620 			(void) fclose(fin);
1621 			return;
1622 		}
1623 
1624 		count = 0;
1625 		while((c=getc(fin)) != EOF) {
1626 			if (c == '\n')	/* will get expanded to \r\n */
1627 				count++;
1628 			count++;
1629 		}
1630 		(void) fclose(fin);
1631 
1632 		reply(213, "%qd", count);
1633 		break; }
1634 	default:
1635 		reply(504, "SIZE not implemented for type %s.",
1636 		           typenames[type]);
1637 	}
1638 }
1639 
1640 /* Return 1, if port check is done. Return 0, if not yet. */
1641 static int
1642 port_check(pcmd)
1643 	const char *pcmd;
1644 {
1645 	if (his_addr.su_family == AF_INET) {
1646 		if (data_dest.su_family != AF_INET) {
1647 			usedefault = 1;
1648 			reply(500, "Invalid address rejected.");
1649 			return 1;
1650 		}
1651 		if (paranoid &&
1652 		    ((ntohs(data_dest.su_port) < IPPORT_RESERVED) ||
1653 		     memcmp(&data_dest.su_sin.sin_addr,
1654 			    &his_addr.su_sin.sin_addr,
1655 			    sizeof(data_dest.su_sin.sin_addr)))) {
1656 			usedefault = 1;
1657 			reply(500, "Illegal PORT range rejected.");
1658 		} else {
1659 			usedefault = 0;
1660 			if (pdata >= 0) {
1661 				(void) close(pdata);
1662 				pdata = -1;
1663 			}
1664 			reply(200, "%s command successful.", pcmd);
1665 		}
1666 		return 1;
1667 	}
1668 	return 0;
1669 }
1670 
1671 static int
1672 check_login1()
1673 {
1674 	if (logged_in)
1675 		return 1;
1676 	else {
1677 		reply(530, "Please login with USER and PASS.");
1678 		return 0;
1679 	}
1680 }
1681 
1682 #ifdef INET6
1683 /* Return 1, if port check is done. Return 0, if not yet. */
1684 static int
1685 port_check_v6(pcmd)
1686 	const char *pcmd;
1687 {
1688 	if (his_addr.su_family == AF_INET6) {
1689 		if (IN6_IS_ADDR_V4MAPPED(&his_addr.su_sin6.sin6_addr))
1690 			/* Convert data_dest into v4 mapped sockaddr.*/
1691 			v4map_data_dest();
1692 		if (data_dest.su_family != AF_INET6) {
1693 			usedefault = 1;
1694 			reply(500, "Invalid address rejected.");
1695 			return 1;
1696 		}
1697 		if (paranoid &&
1698 		    ((ntohs(data_dest.su_port) < IPPORT_RESERVED) ||
1699 		     memcmp(&data_dest.su_sin6.sin6_addr,
1700 			    &his_addr.su_sin6.sin6_addr,
1701 			    sizeof(data_dest.su_sin6.sin6_addr)))) {
1702 			usedefault = 1;
1703 			reply(500, "Illegal PORT range rejected.");
1704 		} else {
1705 			usedefault = 0;
1706 			if (pdata >= 0) {
1707 				(void) close(pdata);
1708 				pdata = -1;
1709 			}
1710 			reply(200, "%s command successful.", pcmd);
1711 		}
1712 		return 1;
1713 	}
1714 	return 0;
1715 }
1716 
1717 static void
1718 v4map_data_dest()
1719 {
1720 	struct in_addr savedaddr;
1721 	int savedport;
1722 
1723 	if (data_dest.su_family != AF_INET) {
1724 		usedefault = 1;
1725 		reply(500, "Invalid address rejected.");
1726 		return;
1727 	}
1728 
1729 	savedaddr = data_dest.su_sin.sin_addr;
1730 	savedport = data_dest.su_port;
1731 
1732 	memset(&data_dest, 0, sizeof(data_dest));
1733 	data_dest.su_sin6.sin6_len = sizeof(struct sockaddr_in6);
1734 	data_dest.su_sin6.sin6_family = AF_INET6;
1735 	data_dest.su_sin6.sin6_port = savedport;
1736 	memset((caddr_t)&data_dest.su_sin6.sin6_addr.s6_addr[10], 0xff, 2);
1737 	memcpy((caddr_t)&data_dest.su_sin6.sin6_addr.s6_addr[12],
1738 	       (caddr_t)&savedaddr, sizeof(savedaddr));
1739 }
1740 #endif
1741