1/*	$NetBSD: ftpcmd.y,v 1.6 1995/06/03 22:46:45 mycroft Exp $	*/
2
3/*
4 * Copyright (c) 1985, 1988, 1993, 1994
5 *	The Regents of the University of California.  All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 * 3. All advertising materials mentioning features or use of this software
16 *    must display the following acknowledgement:
17 *	This product includes software developed by the University of
18 *	California, Berkeley and its contributors.
19 * 4. Neither the name of the University nor the names of its contributors
20 *    may be used to endorse or promote products derived from this software
21 *    without specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
34 *
35 *	@(#)ftpcmd.y	8.3 (Berkeley) 4/6/94
36 */
37
38/*
39 * Grammar for FTP commands.
40 * See RFC 959.
41 */
42
43%{
44
45#include "ftpd_locl.h"
46RCSID("$Id$");
47
48off_t	restart_point;
49
50static	int hasyyerrored;
51
52
53static	int cmd_type;
54static	int cmd_form;
55static	int cmd_bytesz;
56char	cbuf[64*1024];
57char	*fromname;
58
59struct tab {
60	char	*name;
61	short	token;
62	short	state;
63	short	implemented;	/* 1 if command is implemented */
64	char	*help;
65};
66
67extern struct tab cmdtab[];
68extern struct tab sitetab[];
69
70static char		*copy (char *);
71static void		 help (struct tab *, char *);
72static struct tab *
73			 lookup (struct tab *, char *);
74static void		 sizecmd (char *);
75static RETSIGTYPE	 toolong (int);
76static int		 yylex (void);
77
78/* This is for bison */
79
80#if !defined(alloca) && !defined(HAVE_ALLOCA)
81#define alloca(x) malloc(x)
82#endif
83
84%}
85
86%union {
87	int	i;
88	char   *s;
89}
90
91%token
92	A	B	C	E	F	I
93	L	N	P	R	S	T
94
95	SP	CRLF	COMMA
96
97	USER	PASS	ACCT	REIN	QUIT	PORT
98	PASV	TYPE	STRU	MODE	RETR	STOR
99	APPE	MLFL	MAIL	MSND	MSOM	MSAM
100	MRSQ	MRCP	ALLO	REST	RNFR	RNTO
101	ABOR	DELE	CWD	LIST	NLST	SITE
102	sTAT	HELP	NOOP	MKD	RMD	PWD
103	CDUP	STOU	SMNT	SYST	SIZE	MDTM
104	EPRT	EPSV
105
106	UMASK	IDLE	CHMOD
107
108	AUTH	ADAT	PROT	PBSZ	CCC	MIC
109	CONF	ENC
110
111	KAUTH	KLIST	KDESTROY KRBTKFILE AFSLOG
112	LOCATE	URL
113
114	FEAT	OPTS
115
116	LEXERR
117
118%token	<s> STRING
119%token	<i> NUMBER
120
121%type	<i> check_login check_login_no_guest check_secure octal_number byte_size
122%type	<i> struct_code mode_code type_code form_code
123%type	<s> pathstring pathname password username
124
125%start	cmd_list
126
127%%
128
129cmd_list
130	: /* empty */
131	| cmd_list cmd
132		{
133			fromname = (char *) 0;
134			restart_point = (off_t) 0;
135		}
136	| cmd_list rcmd
137	;
138
139cmd
140	: USER SP username CRLF check_secure
141		{
142		    if ($5)
143			user($3);
144		    free($3);
145		}
146	| PASS SP password CRLF check_secure
147		{
148		    if ($5)
149			pass($3);
150		    memset ($3, 0, strlen($3));
151		    free($3);
152		}
153
154	| PORT SP host_port CRLF check_secure
155		{
156		    if ($5) {
157			if (paranoid &&
158			    (data_dest->sa_family != his_addr->sa_family ||
159			     (socket_get_port(data_dest) < IPPORT_RESERVED) ||
160			     memcmp(socket_get_address(data_dest),
161				    socket_get_address(his_addr),
162				    socket_addr_size(his_addr)) != 0)) {
163			    usedefault = 1;
164			    reply(500, "Illegal PORT range rejected.");
165			} else {
166			    usedefault = 0;
167			    if (pdata >= 0) {
168				close(pdata);
169				pdata = -1;
170			    }
171			    reply(200, "PORT command successful.");
172			}
173		    }
174		}
175	| EPRT SP STRING CRLF check_secure
176		{
177		    if ($5)
178			eprt ($3);
179		    free ($3);
180		}
181	| PASV CRLF check_login
182		{
183		    if($3)
184			pasv ();
185		}
186	| EPSV CRLF check_login
187		{
188		    if($3)
189			epsv (NULL);
190		}
191	| EPSV SP STRING CRLF check_login
192		{
193		    if($5)
194			epsv ($3);
195		    free ($3);
196		}
197	| TYPE SP type_code CRLF check_secure
198		{
199		    if ($5) {
200			switch (cmd_type) {
201
202			case TYPE_A:
203				if (cmd_form == FORM_N) {
204					reply(200, "Type set to A.");
205					type = cmd_type;
206					form = cmd_form;
207				} else
208					reply(504, "Form must be N.");
209				break;
210
211			case TYPE_E:
212				reply(504, "Type E not implemented.");
213				break;
214
215			case TYPE_I:
216				reply(200, "Type set to I.");
217				type = cmd_type;
218				break;
219
220			case TYPE_L:
221#if NBBY == 8
222				if (cmd_bytesz == 8) {
223					reply(200,
224					    "Type set to L (byte size 8).");
225					type = cmd_type;
226				} else
227					reply(504, "Byte size must be 8.");
228#else /* NBBY == 8 */
229				UNIMPLEMENTED for NBBY != 8
230#endif /* NBBY == 8 */
231			}
232		    }
233		}
234	| STRU SP struct_code CRLF check_secure
235		{
236		    if ($5) {
237			switch ($3) {
238
239			case STRU_F:
240				reply(200, "STRU F ok.");
241				break;
242
243			default:
244				reply(504, "Unimplemented STRU type.");
245			}
246		    }
247		}
248	| MODE SP mode_code CRLF check_secure
249		{
250		    if ($5) {
251			switch ($3) {
252
253			case MODE_S:
254				reply(200, "MODE S ok.");
255				break;
256
257			default:
258				reply(502, "Unimplemented MODE type.");
259			}
260		    }
261		}
262	| ALLO SP NUMBER CRLF check_secure
263		{
264		    if ($5) {
265			reply(202, "ALLO command ignored.");
266		    }
267		}
268	| ALLO SP NUMBER SP R SP NUMBER CRLF check_secure
269		{
270		    if ($9) {
271			reply(202, "ALLO command ignored.");
272		    }
273		}
274	| RETR SP pathname CRLF check_login
275		{
276			char *name = $3;
277
278			if ($5 && name != NULL)
279				retrieve(0, name);
280			if (name != NULL)
281				free(name);
282		}
283	| STOR SP pathname CRLF check_login
284		{
285			char *name = $3;
286
287			if ($5 && name != NULL)
288				do_store(name, "w", 0);
289			if (name != NULL)
290				free(name);
291		}
292	| APPE SP pathname CRLF check_login
293		{
294			char *name = $3;
295
296			if ($5 && name != NULL)
297				do_store(name, "a", 0);
298			if (name != NULL)
299				free(name);
300		}
301	| NLST CRLF check_login
302		{
303			if ($3)
304				send_file_list(".");
305		}
306	| NLST SP STRING CRLF check_login
307		{
308			char *name = $3;
309
310			if ($5 && name != NULL)
311				send_file_list(name);
312			if (name != NULL)
313				free(name);
314		}
315	| LIST CRLF check_login
316		{
317		    if($3)
318			list_file(".");
319		}
320	| LIST SP pathname CRLF check_login
321		{
322		    if($5)
323			list_file($3);
324		    free($3);
325		}
326	| sTAT SP pathname CRLF check_login
327		{
328			if ($5 && $3 != NULL)
329				statfilecmd($3);
330			if ($3 != NULL)
331				free($3);
332		}
333	| sTAT CRLF check_secure
334		{
335		    if ($3)
336			statcmd();
337		}
338	| DELE SP pathname CRLF check_login_no_guest
339		{
340			if ($5 && $3 != NULL)
341				do_delete($3);
342			if ($3 != NULL)
343				free($3);
344		}
345	| RNTO SP pathname CRLF check_login_no_guest
346		{
347			if($5){
348				if (fromname) {
349					renamecmd(fromname, $3);
350					free(fromname);
351					fromname = (char *) 0;
352				} else {
353					reply(503, "Bad sequence of commands.");
354				}
355			}
356			if ($3 != NULL)
357				free($3);
358		}
359	| ABOR CRLF check_secure
360		{
361		    if ($3)
362			reply(225, "ABOR command successful.");
363		}
364	| CWD CRLF check_login
365		{
366			if ($3) {
367				const char *path = pw->pw_dir;
368				if (dochroot || guest)
369					path = "/";
370				cwd(path);
371			}
372		}
373	| CWD SP pathname CRLF check_login
374		{
375			if ($5 && $3 != NULL)
376				cwd($3);
377			if ($3 != NULL)
378				free($3);
379		}
380	| HELP CRLF check_secure
381		{
382		    if ($3)
383			help(cmdtab, (char *) 0);
384		}
385	| HELP SP STRING CRLF check_secure
386		{
387		    if ($5) {
388			char *cp = $3;
389
390			if (strncasecmp(cp, "SITE", 4) == 0) {
391				cp = $3 + 4;
392				if (*cp == ' ')
393					cp++;
394				if (*cp)
395					help(sitetab, cp);
396				else
397					help(sitetab, (char *) 0);
398			} else
399				help(cmdtab, $3);
400		    }
401		}
402	| NOOP CRLF check_secure
403		{
404		    if ($3)
405			reply(200, "NOOP command successful.");
406		}
407	| MKD SP pathname CRLF check_login
408		{
409			if ($5 && $3 != NULL)
410				makedir($3);
411			if ($3 != NULL)
412				free($3);
413		}
414	| RMD SP pathname CRLF check_login_no_guest
415		{
416			if ($5 && $3 != NULL)
417				removedir($3);
418			if ($3 != NULL)
419				free($3);
420		}
421	| PWD CRLF check_login
422		{
423			if ($3)
424				pwd();
425		}
426	| CDUP CRLF check_login
427		{
428			if ($3)
429				cwd("..");
430		}
431	| FEAT CRLF check_secure
432		{
433		    if ($3) {
434			lreply(211, "Supported features:");
435			lreply(0, " MDTM");
436			lreply(0, " REST STREAM");
437			lreply(0, " SIZE");
438			reply(211, "End");
439		    }
440		}
441	| OPTS SP STRING CRLF check_secure
442		{
443		    if ($5)
444			reply(501, "Bad options");
445		    free ($3);
446		}
447
448	| SITE SP HELP CRLF check_secure
449		{
450		    if ($5)
451			help(sitetab, (char *) 0);
452		}
453	| SITE SP HELP SP STRING CRLF check_secure
454		{
455		    if ($7)
456			help(sitetab, $5);
457		}
458	| SITE SP UMASK CRLF check_login
459		{
460			if ($5) {
461				int oldmask = umask(0);
462				umask(oldmask);
463				reply(200, "Current UMASK is %03o", oldmask);
464			}
465		}
466	| SITE SP UMASK SP octal_number CRLF check_login_no_guest
467		{
468			if ($7) {
469				if (($5 == -1) || ($5 > 0777)) {
470					reply(501, "Bad UMASK value");
471				} else {
472					int oldmask = umask($5);
473					reply(200,
474					      "UMASK set to %03o (was %03o)",
475					      $5, oldmask);
476				}
477			}
478		}
479	| SITE SP CHMOD SP octal_number SP pathname CRLF check_login_no_guest
480		{
481			if ($9 && $7 != NULL) {
482				if ($5 > 0777)
483					reply(501,
484				"CHMOD: Mode value must be between 0 and 0777");
485				else if (chmod($7, $5) < 0)
486					perror_reply(550, $7);
487				else
488					reply(200, "CHMOD command successful.");
489			}
490			if ($7 != NULL)
491				free($7);
492		}
493	| SITE SP IDLE CRLF check_secure
494		{
495		    if ($5)
496			reply(200,
497			    "Current IDLE time limit is %d seconds; max %d",
498				ftpd_timeout, maxtimeout);
499		}
500	| SITE SP IDLE SP NUMBER CRLF check_secure
501		{
502		    if ($7) {
503			if ($5 < 30 || $5 > maxtimeout) {
504				reply(501,
505			"Maximum IDLE time must be between 30 and %d seconds",
506				    maxtimeout);
507			} else {
508				ftpd_timeout = $5;
509				alarm((unsigned) ftpd_timeout);
510				reply(200,
511				    "Maximum IDLE time set to %d seconds",
512				    ftpd_timeout);
513			}
514		    }
515		}
516
517	| SITE SP KAUTH SP STRING CRLF check_login
518		{
519			reply(500, "Command not implemented.");
520		}
521	| SITE SP KLIST CRLF check_login
522		{
523		    if($5)
524			klist();
525		}
526	| SITE SP KDESTROY CRLF check_login
527		{
528		    reply(500, "Command not implemented.");
529		}
530	| SITE SP KRBTKFILE SP STRING CRLF check_login
531		{
532		    reply(500, "Command not implemented.");
533		}
534	| SITE SP AFSLOG CRLF check_login
535		{
536#if defined(KRB5)
537		    if(guest)
538			reply(500, "Can't be done as guest.");
539		    else if($5)
540			afslog(NULL, 0);
541#else
542		    reply(500, "Command not implemented.");
543#endif
544		}
545	| SITE SP AFSLOG SP STRING CRLF check_login
546		{
547#if defined(KRB5)
548		    if(guest)
549			reply(500, "Can't be done as guest.");
550		    else if($7)
551			afslog($5, 0);
552		    if($5)
553			free($5);
554#else
555		    reply(500, "Command not implemented.");
556#endif
557		}
558	| SITE SP LOCATE SP STRING CRLF check_login
559		{
560		    if($7 && $5 != NULL)
561			find($5);
562		    if($5 != NULL)
563			free($5);
564		}
565	| SITE SP URL CRLF check_secure
566		{
567		    if ($5)
568			reply(200, "http://www.pdc.kth.se/heimdal/");
569		}
570	| STOU SP pathname CRLF check_login
571		{
572			if ($5 && $3 != NULL)
573				do_store($3, "w", 1);
574			if ($3 != NULL)
575				free($3);
576		}
577	| SYST CRLF check_secure
578		{
579		    if ($3) {
580#if !defined(WIN32) && !defined(__EMX__) && !defined(__OS2__) && !defined(__CYGWIN32__)
581			reply(215, "UNIX Type: L%d", NBBY);
582#else
583			reply(215, "UNKNOWN Type: L%d", NBBY);
584#endif
585		    }
586		}
587
588		/*
589		 * SIZE is not in RFC959, but Postel has blessed it and
590		 * it will be in the updated RFC.
591		 *
592		 * Return size of file in a format suitable for
593		 * using with RESTART (we just count bytes).
594		 */
595	| SIZE SP pathname CRLF check_login
596		{
597			if ($5 && $3 != NULL)
598				sizecmd($3);
599			if ($3 != NULL)
600				free($3);
601		}
602
603		/*
604		 * MDTM is not in RFC959, but Postel has blessed it and
605		 * it will be in the updated RFC.
606		 *
607		 * Return modification time of file as an ISO 3307
608		 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
609		 * where xxx is the fractional second (of any precision,
610		 * not necessarily 3 digits)
611		 */
612	| MDTM SP pathname CRLF check_login
613		{
614			if ($5 && $3 != NULL) {
615				struct stat stbuf;
616				if (stat($3, &stbuf) < 0)
617					reply(550, "%s: %s",
618					    $3, strerror(errno));
619				else if (!S_ISREG(stbuf.st_mode)) {
620					reply(550,
621					      "%s: not a plain file.", $3);
622				} else {
623					struct tm *t;
624					time_t mtime = stbuf.st_mtime;
625
626					t = gmtime(&mtime);
627					reply(213,
628					      "%04d%02d%02d%02d%02d%02d",
629					      t->tm_year + 1900,
630					      t->tm_mon + 1,
631					      t->tm_mday,
632					      t->tm_hour,
633					      t->tm_min,
634					      t->tm_sec);
635				}
636			}
637			if ($3 != NULL)
638				free($3);
639		}
640	| QUIT CRLF check_secure
641		{
642		    if ($3) {
643			reply(221, "Goodbye.");
644			dologout(0);
645		    }
646		}
647	| error CRLF
648		{
649			yyerrok;
650		}
651	;
652rcmd
653	: RNFR SP pathname CRLF check_login_no_guest
654		{
655			restart_point = (off_t) 0;
656			if ($5 && $3) {
657				fromname = renamefrom($3);
658				if (fromname == (char *) 0 && $3) {
659					free($3);
660				}
661			}
662		}
663	| REST SP byte_size CRLF check_secure
664		{
665		    if ($5) {
666			fromname = (char *) 0;
667			restart_point = $3;	/* XXX $3 is only "int" */
668			reply(350, "Restarting at %ld. %s",
669			      (long)restart_point,
670			      "Send STORE or RETRIEVE to initiate transfer.");
671		    }
672		}
673	| AUTH SP STRING CRLF
674		{
675			auth($3);
676			free($3);
677		}
678	| ADAT SP STRING CRLF
679		{
680			adat($3);
681			free($3);
682		}
683	| PBSZ SP NUMBER CRLF check_secure
684		{
685		    if ($5)
686			pbsz($3);
687		}
688	| PROT SP STRING CRLF check_secure
689		{
690		    if ($5)
691			prot($3);
692		}
693	| CCC CRLF check_secure
694		{
695		    if ($3)
696			ccc();
697		}
698	| MIC SP STRING CRLF
699		{
700			mec($3, prot_safe);
701			free($3);
702		}
703	| CONF SP STRING CRLF
704		{
705			mec($3, prot_confidential);
706			free($3);
707		}
708	| ENC SP STRING CRLF
709		{
710			mec($3, prot_private);
711			free($3);
712		}
713	;
714
715username
716	: STRING
717	;
718
719password
720	: /* empty */
721		{
722			$$ = (char *)calloc(1, sizeof(char));
723		}
724	| STRING
725	;
726
727byte_size
728	: NUMBER
729	;
730
731host_port
732	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
733		NUMBER COMMA NUMBER
734		{
735			struct sockaddr_in *sin4 = (struct sockaddr_in *)data_dest;
736
737			sin4->sin_family = AF_INET;
738			sin4->sin_port = htons($9 * 256 + $11);
739			sin4->sin_addr.s_addr =
740			    htonl(($1 << 24) | ($3 << 16) | ($5 << 8) | $7);
741		}
742	;
743
744form_code
745	: N
746		{
747			$$ = FORM_N;
748		}
749	| T
750		{
751			$$ = FORM_T;
752		}
753	| C
754		{
755			$$ = FORM_C;
756		}
757	;
758
759type_code
760	: A
761		{
762			cmd_type = TYPE_A;
763			cmd_form = FORM_N;
764		}
765	| A SP form_code
766		{
767			cmd_type = TYPE_A;
768			cmd_form = $3;
769		}
770	| E
771		{
772			cmd_type = TYPE_E;
773			cmd_form = FORM_N;
774		}
775	| E SP form_code
776		{
777			cmd_type = TYPE_E;
778			cmd_form = $3;
779		}
780	| I
781		{
782			cmd_type = TYPE_I;
783		}
784	| L
785		{
786			cmd_type = TYPE_L;
787			cmd_bytesz = NBBY;
788		}
789	| L SP byte_size
790		{
791			cmd_type = TYPE_L;
792			cmd_bytesz = $3;
793		}
794		/* this is for a bug in the BBN ftp */
795	| L byte_size
796		{
797			cmd_type = TYPE_L;
798			cmd_bytesz = $2;
799		}
800	;
801
802struct_code
803	: F
804		{
805			$$ = STRU_F;
806		}
807	| R
808		{
809			$$ = STRU_R;
810		}
811	| P
812		{
813			$$ = STRU_P;
814		}
815	;
816
817mode_code
818	: S
819		{
820			$$ = MODE_S;
821		}
822	| B
823		{
824			$$ = MODE_B;
825		}
826	| C
827		{
828			$$ = MODE_C;
829		}
830	;
831
832pathname
833	: pathstring
834		{
835			/*
836			 * Problem: this production is used for all pathname
837			 * processing, but only gives a 550 error reply.
838			 * This is a valid reply in some cases but not in others.
839			 */
840			if (logged_in && $1 && *$1 == '~') {
841				glob_t gl;
842				int flags =
843				 GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE;
844
845				memset(&gl, 0, sizeof(gl));
846				if (glob($1, flags, NULL, &gl) ||
847				    gl.gl_pathc == 0) {
848					reply(550, "not found");
849					$$ = NULL;
850				} else {
851					$$ = strdup(gl.gl_pathv[0]);
852				}
853				globfree(&gl);
854				free($1);
855			} else
856				$$ = $1;
857		}
858	;
859
860pathstring
861	: STRING
862	;
863
864octal_number
865	: NUMBER
866		{
867			int ret, dec, multby, digit;
868
869			/*
870			 * Convert a number that was read as decimal number
871			 * to what it would be if it had been read as octal.
872			 */
873			dec = $1;
874			multby = 1;
875			ret = 0;
876			while (dec) {
877				digit = dec%10;
878				if (digit > 7) {
879					ret = -1;
880					break;
881				}
882				ret += digit * multby;
883				multby *= 8;
884				dec /= 10;
885			}
886			$$ = ret;
887		}
888	;
889
890
891check_login_no_guest : check_login
892		{
893			$$ = $1 && !guest;
894			if($1 && !$$)
895				reply(550, "Permission denied");
896		}
897	;
898
899check_login : check_secure
900		{
901		    if($1) {
902			if(($$ = logged_in) == 0)
903			    reply(530, "Please login with USER and PASS.");
904		    } else
905			$$ = 0;
906		}
907	;
908
909check_secure : /* empty */
910		{
911		    $$ = 1;
912		    if(sec_complete && !ccc_passed && !secure_command()) {
913			$$ = 0;
914			reply(533, "Command protection level denied "
915			      "for paranoid reasons.");
916		    }
917		}
918	;
919
920%%
921
922#define	CMD	0	/* beginning of command */
923#define	ARGS	1	/* expect miscellaneous arguments */
924#define	STR1	2	/* expect SP followed by STRING */
925#define	STR2	3	/* expect STRING */
926#define	OSTR	4	/* optional SP then STRING */
927#define	ZSTR1	5	/* SP then optional STRING */
928#define	ZSTR2	6	/* optional STRING after SP */
929#define	SITECMD	7	/* SITE command */
930#define	NSTR	8	/* Number followed by a string */
931
932struct tab cmdtab[] = {		/* In order defined in RFC 765 */
933	{ "USER", USER, STR1, 1,	"<sp> username" },
934	{ "PASS", PASS, ZSTR1, 1,	"<sp> password" },
935	{ "ACCT", ACCT, STR1, 0,	"(specify account)" },
936	{ "SMNT", SMNT, ARGS, 0,	"(structure mount)" },
937	{ "REIN", REIN, ARGS, 0,	"(reinitialize server state)" },
938	{ "QUIT", QUIT, ARGS, 1,	"(terminate service)", },
939	{ "PORT", PORT, ARGS, 1,	"<sp> b0, b1, b2, b3, b4" },
940	{ "EPRT", EPRT, STR1, 1,	"<sp> string" },
941	{ "PASV", PASV, ARGS, 1,	"(set server in passive mode)" },
942	{ "EPSV", EPSV, OSTR, 1,	"[<sp> foo]" },
943	{ "TYPE", TYPE, ARGS, 1,	"<sp> [ A | E | I | L ]" },
944	{ "STRU", STRU, ARGS, 1,	"(specify file structure)" },
945	{ "MODE", MODE, ARGS, 1,	"(specify transfer mode)" },
946	{ "RETR", RETR, STR1, 1,	"<sp> file-name" },
947	{ "STOR", STOR, STR1, 1,	"<sp> file-name" },
948	{ "APPE", APPE, STR1, 1,	"<sp> file-name" },
949	{ "MLFL", MLFL, OSTR, 0,	"(mail file)" },
950	{ "MAIL", MAIL, OSTR, 0,	"(mail to user)" },
951	{ "MSND", MSND, OSTR, 0,	"(mail send to terminal)" },
952	{ "MSOM", MSOM, OSTR, 0,	"(mail send to terminal or mailbox)" },
953	{ "MSAM", MSAM, OSTR, 0,	"(mail send to terminal and mailbox)" },
954	{ "MRSQ", MRSQ, OSTR, 0,	"(mail recipient scheme question)" },
955	{ "MRCP", MRCP, STR1, 0,	"(mail recipient)" },
956	{ "ALLO", ALLO, ARGS, 1,	"allocate storage (vacuously)" },
957	{ "REST", REST, ARGS, 1,	"<sp> offset (restart command)" },
958	{ "RNFR", RNFR, STR1, 1,	"<sp> file-name" },
959	{ "RNTO", RNTO, STR1, 1,	"<sp> file-name" },
960	{ "ABOR", ABOR, ARGS, 1,	"(abort operation)" },
961	{ "DELE", DELE, STR1, 1,	"<sp> file-name" },
962	{ "CWD",  CWD,  OSTR, 1,	"[ <sp> directory-name ]" },
963	{ "XCWD", CWD,	OSTR, 1,	"[ <sp> directory-name ]" },
964	{ "LIST", LIST, OSTR, 1,	"[ <sp> path-name ]" },
965	{ "NLST", NLST, OSTR, 1,	"[ <sp> path-name ]" },
966	{ "SITE", SITE, SITECMD, 1,	"site-cmd [ <sp> arguments ]" },
967	{ "SYST", SYST, ARGS, 1,	"(get type of operating system)" },
968	{ "STAT", sTAT, OSTR, 1,	"[ <sp> path-name ]" },
969	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
970	{ "NOOP", NOOP, ARGS, 1,	"" },
971	{ "MKD",  MKD,  STR1, 1,	"<sp> path-name" },
972	{ "XMKD", MKD,  STR1, 1,	"<sp> path-name" },
973	{ "RMD",  RMD,  STR1, 1,	"<sp> path-name" },
974	{ "XRMD", RMD,  STR1, 1,	"<sp> path-name" },
975	{ "PWD",  PWD,  ARGS, 1,	"(return current directory)" },
976	{ "XPWD", PWD,  ARGS, 1,	"(return current directory)" },
977	{ "CDUP", CDUP, ARGS, 1,	"(change to parent directory)" },
978	{ "XCUP", CDUP, ARGS, 1,	"(change to parent directory)" },
979	{ "STOU", STOU, STR1, 1,	"<sp> file-name" },
980	{ "SIZE", SIZE, OSTR, 1,	"<sp> path-name" },
981	{ "MDTM", MDTM, OSTR, 1,	"<sp> path-name" },
982
983	/* extensions from RFC2228 */
984	{ "AUTH", AUTH,	STR1, 1,	"<sp> auth-type" },
985	{ "ADAT", ADAT,	STR1, 1,	"<sp> auth-data" },
986	{ "PBSZ", PBSZ,	ARGS, 1,	"<sp> buffer-size" },
987	{ "PROT", PROT,	STR1, 1,	"<sp> prot-level" },
988	{ "CCC",  CCC,	ARGS, 1,	"" },
989	{ "MIC",  MIC,	STR1, 1,	"<sp> integrity command" },
990	{ "CONF", CONF,	STR1, 1,	"<sp> confidentiality command" },
991	{ "ENC",  ENC,	STR1, 1,	"<sp> privacy command" },
992
993	/* RFC2389 */
994	{ "FEAT", FEAT, ARGS, 1,	"" },
995	{ "OPTS", OPTS, ARGS, 1,	"<sp> command [<sp> options]" },
996
997	{ NULL,   0,    0,    0,	0 }
998};
999
1000struct tab sitetab[] = {
1001	{ "UMASK", UMASK, ARGS, 1,	"[ <sp> umask ]" },
1002	{ "IDLE", IDLE, ARGS, 1,	"[ <sp> maximum-idle-time ]" },
1003	{ "CHMOD", CHMOD, NSTR, 1,	"<sp> mode <sp> file-name" },
1004	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
1005
1006	{ "KAUTH", KAUTH, STR1, 1,	"<sp> principal [ <sp> ticket ]" },
1007	{ "KLIST", KLIST, ARGS, 1,	"(show ticket file)" },
1008	{ "KDESTROY", KDESTROY, ARGS, 1, "(destroy tickets)" },
1009	{ "KRBTKFILE", KRBTKFILE, STR1, 1, "<sp> ticket-file" },
1010	{ "AFSLOG", AFSLOG, OSTR, 1,	"[<sp> cell]" },
1011
1012	{ "LOCATE", LOCATE, STR1, 1,	"<sp> globexpr" },
1013	{ "FIND", LOCATE, STR1, 1,	"<sp> globexpr" },
1014
1015	{ "URL",  URL,  ARGS, 1,	"?" },
1016
1017	{ NULL,   0,    0,    0,	0 }
1018};
1019
1020static struct tab *
1021lookup(struct tab *p, char *cmd)
1022{
1023
1024	for (; p->name != NULL; p++)
1025		if (strcmp(cmd, p->name) == 0)
1026			return (p);
1027	return (0);
1028}
1029
1030/*
1031 * ftpd_getline - a hacked up version of fgets to ignore TELNET escape codes.
1032 */
1033char *
1034ftpd_getline(char *s, int n)
1035{
1036	int c;
1037	char *cs;
1038
1039	cs = s;
1040
1041	/* might still be data within the security MIC/CONF/ENC */
1042	if(ftp_command){
1043	    strlcpy(s, ftp_command, n);
1044	    if (debug)
1045		syslog(LOG_DEBUG, "command: %s", s);
1046	    return s;
1047	}
1048	while ((c = getc(stdin)) != EOF) {
1049		c &= 0377;
1050		if (c == IAC) {
1051		    if ((c = getc(stdin)) != EOF) {
1052			c &= 0377;
1053			switch (c) {
1054			case WILL:
1055			case WONT:
1056				c = getc(stdin);
1057				printf("%c%c%c", IAC, DONT, 0377&c);
1058				fflush(stdout);
1059				continue;
1060			case DO:
1061			case DONT:
1062				c = getc(stdin);
1063				printf("%c%c%c", IAC, WONT, 0377&c);
1064				fflush(stdout);
1065				continue;
1066			case IAC:
1067				break;
1068			default:
1069				continue;	/* ignore command */
1070			}
1071		    }
1072		}
1073		*cs++ = c;
1074		if (--n <= 0 || c == '\n')
1075			break;
1076	}
1077	if (c == EOF && cs == s)
1078		return (NULL);
1079	*cs++ = '\0';
1080	if (debug) {
1081		if (!guest && strncasecmp("pass ", s, 5) == 0) {
1082			/* Don't syslog passwords */
1083			syslog(LOG_DEBUG, "command: %.5s ???", s);
1084		} else {
1085			char *cp;
1086			int len;
1087
1088			/* Don't syslog trailing CR-LF */
1089			len = strlen(s);
1090			cp = s + len - 1;
1091			while (cp >= s && (*cp == '\n' || *cp == '\r')) {
1092				--cp;
1093				--len;
1094			}
1095			syslog(LOG_DEBUG, "command: %.*s", len, s);
1096		}
1097	}
1098#ifdef XXX
1099	fprintf(stderr, "%s\n", s);
1100#endif
1101	return (s);
1102}
1103
1104static RETSIGTYPE
1105toolong(int signo)
1106{
1107
1108	reply(421,
1109	    "Timeout (%d seconds): closing control connection.",
1110	      ftpd_timeout);
1111	if (logging)
1112		syslog(LOG_INFO, "User %s timed out after %d seconds",
1113		    (pw ? pw -> pw_name : "unknown"), ftpd_timeout);
1114	dologout(1);
1115	SIGRETURN(0);
1116}
1117
1118static int
1119yylex(void)
1120{
1121	static int cpos, state;
1122	char *cp, *cp2;
1123	struct tab *p;
1124	int n;
1125	char c;
1126
1127	for (;;) {
1128		switch (state) {
1129
1130		case CMD:
1131			hasyyerrored = 0;
1132
1133			signal(SIGALRM, toolong);
1134			alarm((unsigned) ftpd_timeout);
1135			if (ftpd_getline(cbuf, sizeof(cbuf)-1) == NULL) {
1136				reply(221, "You could at least say goodbye.");
1137				dologout(0);
1138			}
1139			alarm(0);
1140#ifdef HAVE_SETPROCTITLE
1141			if (strncasecmp(cbuf, "PASS", 4) != 0)
1142				setproctitle("%s: %s", proctitle, cbuf);
1143#endif /* HAVE_SETPROCTITLE */
1144			if ((cp = strchr(cbuf, '\r'))) {
1145				*cp++ = '\n';
1146				*cp = '\0';
1147			}
1148			if ((cp = strpbrk(cbuf, " \n")))
1149				cpos = cp - cbuf;
1150			if (cpos == 0)
1151				cpos = 4;
1152			c = cbuf[cpos];
1153			cbuf[cpos] = '\0';
1154			strupr(cbuf);
1155			p = lookup(cmdtab, cbuf);
1156			cbuf[cpos] = c;
1157			if (p != 0) {
1158				if (p->implemented == 0) {
1159					nack(p->name);
1160					hasyyerrored = 1;
1161					break;
1162				}
1163				state = p->state;
1164				yylval.s = p->name;
1165				return (p->token);
1166			}
1167			break;
1168
1169		case SITECMD:
1170			if (cbuf[cpos] == ' ') {
1171				cpos++;
1172				return (SP);
1173			}
1174			cp = &cbuf[cpos];
1175			if ((cp2 = strpbrk(cp, " \n")))
1176				cpos = cp2 - cbuf;
1177			c = cbuf[cpos];
1178			cbuf[cpos] = '\0';
1179			strupr(cp);
1180			p = lookup(sitetab, cp);
1181			cbuf[cpos] = c;
1182			if (p != 0) {
1183				if (p->implemented == 0) {
1184					state = CMD;
1185					nack(p->name);
1186					hasyyerrored = 1;
1187					break;
1188				}
1189				state = p->state;
1190				yylval.s = p->name;
1191				return (p->token);
1192			}
1193			state = CMD;
1194			break;
1195
1196		case OSTR:
1197			if (cbuf[cpos] == '\n') {
1198				state = CMD;
1199				return (CRLF);
1200			}
1201			/* FALLTHROUGH */
1202
1203		case STR1:
1204		case ZSTR1:
1205		dostr1:
1206			if (cbuf[cpos] == ' ') {
1207				cpos++;
1208				if(state == OSTR)
1209				    state = STR2;
1210				else
1211				    state++;
1212				return (SP);
1213			}
1214			break;
1215
1216		case ZSTR2:
1217			if (cbuf[cpos] == '\n') {
1218				state = CMD;
1219				return (CRLF);
1220			}
1221			/* FALLTHROUGH */
1222
1223		case STR2:
1224			cp = &cbuf[cpos];
1225			n = strlen(cp);
1226			cpos += n - 1;
1227			/*
1228			 * Make sure the string is nonempty and \n terminated.
1229			 */
1230			if (n > 1 && cbuf[cpos] == '\n') {
1231				cbuf[cpos] = '\0';
1232				yylval.s = copy(cp);
1233				cbuf[cpos] = '\n';
1234				state = ARGS;
1235				return (STRING);
1236			}
1237			break;
1238
1239		case NSTR:
1240			if (cbuf[cpos] == ' ') {
1241				cpos++;
1242				return (SP);
1243			}
1244			if (isdigit((unsigned char)cbuf[cpos])) {
1245				cp = &cbuf[cpos];
1246				while (isdigit((unsigned char)cbuf[++cpos]))
1247					;
1248				c = cbuf[cpos];
1249				cbuf[cpos] = '\0';
1250				yylval.i = atoi(cp);
1251				cbuf[cpos] = c;
1252				state = STR1;
1253				return (NUMBER);
1254			}
1255			state = STR1;
1256			goto dostr1;
1257
1258		case ARGS:
1259			if (isdigit((unsigned char)cbuf[cpos])) {
1260				cp = &cbuf[cpos];
1261				while (isdigit((unsigned char)cbuf[++cpos]))
1262					;
1263				c = cbuf[cpos];
1264				cbuf[cpos] = '\0';
1265				yylval.i = atoi(cp);
1266				cbuf[cpos] = c;
1267				return (NUMBER);
1268			}
1269			switch (cbuf[cpos++]) {
1270
1271			case '\n':
1272				state = CMD;
1273				return (CRLF);
1274
1275			case ' ':
1276				return (SP);
1277
1278			case ',':
1279				return (COMMA);
1280
1281			case 'A':
1282			case 'a':
1283				return (A);
1284
1285			case 'B':
1286			case 'b':
1287				return (B);
1288
1289			case 'C':
1290			case 'c':
1291				return (C);
1292
1293			case 'E':
1294			case 'e':
1295				return (E);
1296
1297			case 'F':
1298			case 'f':
1299				return (F);
1300
1301			case 'I':
1302			case 'i':
1303				return (I);
1304
1305			case 'L':
1306			case 'l':
1307				return (L);
1308
1309			case 'N':
1310			case 'n':
1311				return (N);
1312
1313			case 'P':
1314			case 'p':
1315				return (P);
1316
1317			case 'R':
1318			case 'r':
1319				return (R);
1320
1321			case 'S':
1322			case 's':
1323				return (S);
1324
1325			case 'T':
1326			case 't':
1327				return (T);
1328
1329			}
1330			break;
1331
1332		default:
1333			fatal("Unknown state in scanner.");
1334		}
1335		yyerror(NULL);
1336		state = CMD;
1337		return (0);
1338	}
1339}
1340
1341/* ARGSUSED */
1342void
1343yyerror(char *s)
1344{
1345	char *cp;
1346
1347	if (hasyyerrored)
1348	    return;
1349
1350	if ((cp = strchr(cbuf,'\n')))
1351		*cp = '\0';
1352	reply(500, "'%s': command not understood.", cbuf);
1353	hasyyerrored = 1;
1354}
1355
1356static char *
1357copy(char *s)
1358{
1359	char *p;
1360
1361	p = strdup(s);
1362	if (p == NULL)
1363		fatal("Ran out of memory.");
1364	return p;
1365}
1366
1367static void
1368help(struct tab *ctab, char *s)
1369{
1370	struct tab *c;
1371	int width, NCMDS;
1372	char *t;
1373	char buf[1024];
1374
1375	if (ctab == sitetab)
1376		t = "SITE ";
1377	else
1378		t = "";
1379	width = 0, NCMDS = 0;
1380	for (c = ctab; c->name != NULL; c++) {
1381		int len = strlen(c->name);
1382
1383		if (len > width)
1384			width = len;
1385		NCMDS++;
1386	}
1387	width = (width + 8) &~ 7;
1388	if (s == 0) {
1389		int i, j, w;
1390		int columns, lines;
1391
1392		lreply(214, "The following %scommands are recognized %s.",
1393		    t, "(* =>'s unimplemented)");
1394		columns = 76 / width;
1395		if (columns == 0)
1396			columns = 1;
1397		lines = (NCMDS + columns - 1) / columns;
1398		for (i = 0; i < lines; i++) {
1399		    strlcpy (buf, "   ", sizeof(buf));
1400		    for (j = 0; j < columns; j++) {
1401			c = ctab + j * lines + i;
1402			snprintf (buf + strlen(buf),
1403				  sizeof(buf) - strlen(buf),
1404				  "%s%c",
1405				  c->name,
1406				  c->implemented ? ' ' : '*');
1407			if (c + lines >= &ctab[NCMDS])
1408			    break;
1409			w = strlen(c->name) + 1;
1410			while (w < width) {
1411			    strlcat (buf,
1412					     " ",
1413					     sizeof(buf));
1414			    w++;
1415			}
1416		    }
1417		    lreply(214, "%s", buf);
1418		}
1419		reply(214, "Direct comments to kth-krb-bugs@pdc.kth.se");
1420		return;
1421	}
1422	strupr(s);
1423	c = lookup(ctab, s);
1424	if (c == (struct tab *)0) {
1425		reply(502, "Unknown command %s.", s);
1426		return;
1427	}
1428	if (c->implemented)
1429		reply(214, "Syntax: %s%s %s", t, c->name, c->help);
1430	else
1431		reply(214, "%s%-*s\t%s; unimplemented.", t, width,
1432		    c->name, c->help);
1433}
1434
1435static void
1436sizecmd(char *filename)
1437{
1438	switch (type) {
1439	case TYPE_L:
1440	case TYPE_I: {
1441		struct stat stbuf;
1442		if (stat(filename, &stbuf) < 0 || !S_ISREG(stbuf.st_mode))
1443			reply(550, "%s: not a plain file.", filename);
1444		else
1445			reply(213, "%lu", (unsigned long)stbuf.st_size);
1446		break;
1447	}
1448	case TYPE_A: {
1449		FILE *fin;
1450		int c;
1451		size_t count;
1452		struct stat stbuf;
1453		fin = fopen(filename, "r");
1454		if (fin == NULL) {
1455			perror_reply(550, filename);
1456			return;
1457		}
1458		if (fstat(fileno(fin), &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) {
1459			reply(550, "%s: not a plain file.", filename);
1460			fclose(fin);
1461			return;
1462		}
1463
1464		count = 0;
1465		while((c=getc(fin)) != EOF) {
1466			if (c == '\n')	/* will get expanded to \r\n */
1467				count++;
1468			count++;
1469		}
1470		fclose(fin);
1471
1472		reply(213, "%lu", (unsigned long)count);
1473		break;
1474	}
1475	default:
1476		reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
1477	}
1478}
1479