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