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