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