ftpcmd.y revision 17435
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 paranoid;
75extern	int logging;
76extern	int type;
77extern	int form;
78extern	int debug;
79extern	int timeout;
80extern	int maxtimeout;
81extern  int pdata;
82extern	char hostname[], remotehost[];
83extern	char proctitle[];
84extern	int usedefault;
85extern  int transflag;
86extern  char tmpline[];
87
88off_t	restart_point;
89
90static	int cmd_type;
91static	int cmd_form;
92static	int cmd_bytesz;
93char	cbuf[512];
94char	*fromname;
95
96%}
97
98%union {
99	int	i;
100	char   *s;
101}
102
103%token
104	A	B	C	E	F	I
105	L	N	P	R	S	T
106
107	SP	CRLF	COMMA
108
109	USER	PASS	ACCT	REIN	QUIT	PORT
110	PASV	TYPE	STRU	MODE	RETR	STOR
111	APPE	MLFL	MAIL	MSND	MSOM	MSAM
112	MRSQ	MRCP	ALLO	REST	RNFR	RNTO
113	ABOR	DELE	CWD	LIST	NLST	SITE
114	STAT	HELP	NOOP	MKD	RMD	PWD
115	CDUP	STOU	SMNT	SYST	SIZE	MDTM
116
117	UMASK	IDLE	CHMOD
118
119	LEXERR
120
121%token	<s> STRING
122%token	<i> NUMBER
123
124%type	<i> check_login octal_number byte_size
125%type	<i> struct_code mode_code type_code form_code
126%type	<s> pathstring pathname password username
127
128%start	cmd_list
129
130%%
131
132cmd_list
133	: /* empty */
134	| cmd_list cmd
135		{
136			fromname = (char *) 0;
137			restart_point = (off_t) 0;
138		}
139	| cmd_list rcmd
140	;
141
142cmd
143	: USER SP username CRLF
144		{
145			user($3);
146			free($3);
147		}
148	| PASS SP password CRLF
149		{
150			pass($3);
151			free($3);
152		}
153	| PORT check_login SP host_port CRLF
154		{
155			if ($2) {
156				if (paranoid &&
157				    ((ntohs(data_dest.sin_port) <
158				      IPPORT_RESERVED) ||
159				     memcmp(&data_dest.sin_addr,
160					    &his_addr.sin_addr,
161					    sizeof(data_dest.sin_addr)))) {
162					usedefault = 1;
163					reply(500,
164					      "Illegal PORT range rejected.");
165				} else {
166					usedefault = 0;
167					if (pdata >= 0) {
168						(void) close(pdata);
169						pdata = -1;
170					}
171					reply(200, "PORT command successful.");
172				}
173			}
174		}
175	| PASV check_login CRLF
176		{
177			if ($2)
178				passive();
179		}
180	| TYPE SP type_code CRLF
181		{
182			switch (cmd_type) {
183
184			case TYPE_A:
185				if (cmd_form == FORM_N) {
186					reply(200, "Type set to A.");
187					type = cmd_type;
188					form = cmd_form;
189				} else
190					reply(504, "Form must be N.");
191				break;
192
193			case TYPE_E:
194				reply(504, "Type E not implemented.");
195				break;
196
197			case TYPE_I:
198				reply(200, "Type set to I.");
199				type = cmd_type;
200				break;
201
202			case TYPE_L:
203#if NBBY == 8
204				if (cmd_bytesz == 8) {
205					reply(200,
206					    "Type set to L (byte size 8).");
207					type = cmd_type;
208				} else
209					reply(504, "Byte size must be 8.");
210#else /* NBBY == 8 */
211				UNIMPLEMENTED for NBBY != 8
212#endif /* NBBY == 8 */
213			}
214		}
215	| STRU SP struct_code CRLF
216		{
217			switch ($3) {
218
219			case STRU_F:
220				reply(200, "STRU F ok.");
221				break;
222
223			default:
224				reply(504, "Unimplemented STRU type.");
225			}
226		}
227	| MODE SP mode_code CRLF
228		{
229			switch ($3) {
230
231			case MODE_S:
232				reply(200, "MODE S ok.");
233				break;
234
235			default:
236				reply(502, "Unimplemented MODE type.");
237			}
238		}
239	| ALLO SP NUMBER CRLF
240		{
241			reply(202, "ALLO command ignored.");
242		}
243	| ALLO SP NUMBER SP R SP NUMBER CRLF
244		{
245			reply(202, "ALLO command ignored.");
246		}
247	| RETR check_login SP pathname CRLF
248		{
249			if ($2 && $4 != NULL)
250				retrieve((char *) 0, $4);
251			if ($4 != NULL)
252				free($4);
253		}
254	| STOR check_login SP pathname CRLF
255		{
256			if ($2 && $4 != NULL)
257				store($4, "w", 0);
258			if ($4 != NULL)
259				free($4);
260		}
261	| APPE check_login SP pathname CRLF
262		{
263			if ($2 && $4 != NULL)
264				store($4, "a", 0);
265			if ($4 != NULL)
266				free($4);
267		}
268	| NLST check_login CRLF
269		{
270			if ($2)
271				send_file_list(".");
272		}
273	| NLST check_login SP STRING CRLF
274		{
275			if ($2 && $4 != NULL)
276				send_file_list($4);
277			if ($4 != NULL)
278				free($4);
279		}
280	| LIST check_login CRLF
281		{
282			if ($2)
283				retrieve("/bin/ls -lgA", "");
284		}
285	| LIST check_login SP pathname CRLF
286		{
287			if ($2 && $4 != NULL)
288				retrieve("/bin/ls -lgA %s", $4);
289			if ($4 != NULL)
290				free($4);
291		}
292	| STAT check_login SP pathname CRLF
293		{
294			if ($2 && $4 != NULL)
295				statfilecmd($4);
296			if ($4 != NULL)
297				free($4);
298		}
299	| STAT CRLF
300		{
301			statcmd();
302		}
303	| DELE check_login SP pathname CRLF
304		{
305			if ($2 && $4 != NULL)
306				delete($4);
307			if ($4 != NULL)
308				free($4);
309		}
310	| RNTO check_login SP pathname CRLF
311		{
312			if ($2) {
313				if (fromname) {
314					renamecmd(fromname, $4);
315					free(fromname);
316					fromname = (char *) 0;
317				} else {
318					reply(503, "Bad sequence of commands.");
319				}
320			}
321			free($4);
322		}
323	| ABOR CRLF
324		{
325			reply(225, "ABOR command successful.");
326		}
327	| CWD check_login CRLF
328		{
329			if ($2)
330				cwd(pw->pw_dir);
331		}
332	| CWD check_login SP pathname CRLF
333		{
334			if ($2 && $4 != NULL)
335				cwd($4);
336			if ($4 != NULL)
337				free($4);
338		}
339	| HELP CRLF
340		{
341			help(cmdtab, (char *) 0);
342		}
343	| HELP SP STRING CRLF
344		{
345			char *cp = $3;
346
347			if (strncasecmp(cp, "SITE", 4) == 0) {
348				cp = $3 + 4;
349				if (*cp == ' ')
350					cp++;
351				if (*cp)
352					help(sitetab, cp);
353				else
354					help(sitetab, (char *) 0);
355			} else
356				help(cmdtab, $3);
357		}
358	| NOOP CRLF
359		{
360			reply(200, "NOOP command successful.");
361		}
362	| MKD check_login SP pathname CRLF
363		{
364			if ($2 && $4 != NULL)
365				makedir($4);
366			if ($4 != NULL)
367				free($4);
368		}
369	| RMD check_login SP pathname CRLF
370		{
371			if ($2 && $4 != NULL)
372				removedir($4);
373			if ($4 != NULL)
374				free($4);
375		}
376	| PWD check_login CRLF
377		{
378			if ($2)
379				pwd();
380		}
381	| CDUP check_login CRLF
382		{
383			if ($2)
384				cwd("..");
385		}
386	| SITE SP HELP CRLF
387		{
388			help(sitetab, (char *) 0);
389		}
390	| SITE SP HELP SP STRING CRLF
391		{
392			help(sitetab, $5);
393		}
394	| SITE SP UMASK check_login CRLF
395		{
396			int oldmask;
397
398			if ($4) {
399				oldmask = umask(0);
400				(void) umask(oldmask);
401				reply(200, "Current UMASK is %03o", oldmask);
402			}
403		}
404	| SITE SP UMASK check_login SP octal_number CRLF
405		{
406			int oldmask;
407
408			if ($4) {
409				if (($6 == -1) || ($6 > 0777)) {
410					reply(501, "Bad UMASK value");
411				} else {
412					oldmask = umask($6);
413					reply(200,
414					    "UMASK set to %03o (was %03o)",
415					    $6, oldmask);
416				}
417			}
418		}
419	| SITE SP CHMOD check_login SP octal_number SP pathname CRLF
420		{
421			if ($4 && ($8 != NULL)) {
422				if ($6 > 0777)
423					reply(501,
424				"CHMOD: Mode value must be between 0 and 0777");
425				else if (chmod($8, $6) < 0)
426					perror_reply(550, $8);
427				else
428					reply(200, "CHMOD command successful.");
429			}
430			if ($8 != NULL)
431				free($8);
432		}
433	| SITE SP IDLE CRLF
434		{
435			reply(200,
436			    "Current IDLE time limit is %d seconds; max %d",
437				timeout, maxtimeout);
438		}
439	| SITE SP IDLE SP NUMBER CRLF
440		{
441			if ($5 < 30 || $5 > maxtimeout) {
442				reply(501,
443			"Maximum IDLE time must be between 30 and %d seconds",
444				    maxtimeout);
445			} else {
446				timeout = $5;
447				(void) alarm((unsigned) timeout);
448				reply(200,
449				    "Maximum IDLE time set to %d seconds",
450				    timeout);
451			}
452		}
453	| STOU check_login SP pathname CRLF
454		{
455			if ($2 && $4 != NULL)
456				store($4, "w", 1);
457			if ($4 != NULL)
458				free($4);
459		}
460	| SYST CRLF
461		{
462#ifdef unix
463#ifdef BSD
464			reply(215, "UNIX Type: L%d Version: BSD-%d",
465				NBBY, BSD);
466#else /* BSD */
467			reply(215, "UNIX Type: L%d", NBBY);
468#endif /* BSD */
469#else /* unix */
470			reply(215, "UNKNOWN Type: L%d", NBBY);
471#endif /* unix */
472		}
473
474		/*
475		 * SIZE is not in RFC959, but Postel has blessed it and
476		 * it will be in the updated RFC.
477		 *
478		 * Return size of file in a format suitable for
479		 * using with RESTART (we just count bytes).
480		 */
481	| SIZE check_login SP pathname CRLF
482		{
483			if ($2 && $4 != NULL)
484				sizecmd($4);
485			if ($4 != NULL)
486				free($4);
487		}
488
489		/*
490		 * MDTM is not in RFC959, but Postel has blessed it and
491		 * it will be in the updated RFC.
492		 *
493		 * Return modification time of file as an ISO 3307
494		 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
495		 * where xxx is the fractional second (of any precision,
496		 * not necessarily 3 digits)
497		 */
498	| MDTM check_login SP pathname CRLF
499		{
500			if ($2 && $4 != NULL) {
501				struct stat stbuf;
502				if (stat($4, &stbuf) < 0)
503					reply(550, "%s: %s",
504					    $4, strerror(errno));
505				else if (!S_ISREG(stbuf.st_mode)) {
506					reply(550, "%s: not a plain file.", $4);
507				} else {
508					struct tm *t;
509					t = gmtime(&stbuf.st_mtime);
510					reply(213,
511					    "%04d%02d%02d%02d%02d%02d",
512					    1900 + t->tm_year,
513					    t->tm_mon+1, t->tm_mday,
514					    t->tm_hour, t->tm_min, t->tm_sec);
515				}
516			}
517			if ($4 != NULL)
518				free($4);
519		}
520	| QUIT CRLF
521		{
522			reply(221, "Goodbye.");
523			dologout(0);
524		}
525	| error CRLF
526		{
527			yyerrok;
528		}
529	;
530rcmd
531	: RNFR check_login SP pathname CRLF
532		{
533			char *renamefrom();
534
535			restart_point = (off_t) 0;
536			if ($2 && $4) {
537				fromname = renamefrom($4);
538				if (fromname == (char *) 0 && $4) {
539					free($4);
540				}
541			}
542		}
543	| REST SP byte_size CRLF
544		{
545			fromname = (char *) 0;
546			restart_point = $3;	/* XXX $3 is only "int" */
547			reply(350, "Restarting at %qd. %s", restart_point,
548			    "Send STORE or RETRIEVE to initiate transfer.");
549		}
550	;
551
552username
553	: STRING
554	;
555
556password
557	: /* empty */
558		{
559			$$ = (char *)calloc(1, sizeof(char));
560		}
561	| STRING
562	;
563
564byte_size
565	: NUMBER
566	;
567
568host_port
569	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
570		NUMBER COMMA NUMBER
571		{
572			char *a, *p;
573
574			data_dest.sin_len = sizeof(struct sockaddr_in);
575			data_dest.sin_family = AF_INET;
576			p = (char *)&data_dest.sin_port;
577			p[0] = $9; p[1] = $11;
578			a = (char *)&data_dest.sin_addr;
579			a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7;
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