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