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