1/*	$NetBSD: ftpcmd.y,v 1.92 2011/07/01 02:46:15 joerg 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
73static char sccsid[] = "@(#)ftpcmd.y	8.3 (Berkeley) 4/6/94";
74#else
75__RCSID("$NetBSD: ftpcmd.y,v 1.92 2011/07/01 02:46:15 joerg 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
106static	int cmd_type;
107static	int cmd_form;
108static	int cmd_bytesz;
109
110char	cbuf[FTP_BUFLEN];
111char	*cmdp;
112char	*fromname;
113
114extern int	epsvall;
115struct tab	sitetab[];
116
117static	int	check_write(const char *, int);
118static	void	help(struct tab *, const char *);
119static	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
174cmd_sel
175	: cmd
176		{
177			REASSIGN(fromname, NULL);
178			restart_point = (off_t) 0;
179		}
180
181	| rcmd
182
183	;
184
185cmd
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			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
880rcmd
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
904username
905	: STRING
906	;
907
908password
909	: /* empty */
910		{
911			$$ = (char *)calloc(1, sizeof(char));
912		}
913
914	| STRING
915	;
916
917byte_size
918	: NUMBER
919		{
920			$$ = $1.i;
921		}
922	;
923
924host_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
940host_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
961host_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
1001form_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
1018type_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
1068struct_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
1085mode_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
1102pathname
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
1145pathstring
1146	: STRING
1147	;
1148
1149octal_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
1175mechanism_name
1176	: STRING
1177	;
1178
1179base64data
1180	: STRING
1181	;
1182
1183prot_code
1184	: STRING
1185	;
1186
1187decimal_integer
1188	: NUMBER
1189		{
1190			$$ = $1.i;
1191		}
1192	;
1193
1194check_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
1221struct 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
1298struct 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 */
1314static int
1315check_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
1352struct tab *
1353lookup(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 */
1371int
1372get_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
1468void
1469ftp_handle_line(char *cp)
1470{
1471
1472	cmdp = cp;
1473	yyparse();
1474}
1475
1476void
1477ftp_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
1497int
1498yylex(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 */
1733void
1734yyerror(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
1746static void
1747help(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 */
1821static void
1822port_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