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