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