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