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