ftpcmd.y revision 1.2
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 char	*copy __P((char *));
812static void	 help __P((struct tab *, char *));
813static struct tab *
814		 lookup __P((struct tab *, char *));
815static void	 sizecmd __P((char *));
816static void	 toolong __P((int));
817static int	 yylex __P((void));
818
819static struct tab *
820lookup(p, cmd)
821	struct tab *p;
822	char *cmd;
823{
824
825	for (; p->name != NULL; p++)
826		if (strcmp(cmd, p->name) == 0)
827			return (p);
828	return (0);
829}
830
831#include <arpa/telnet.h>
832
833/*
834 * getline - a hacked up version of fgets to ignore TELNET escape codes.
835 */
836char *
837getline(s, n, iop)
838	char *s;
839	int n;
840	FILE *iop;
841{
842	int c;
843	register char *cs;
844
845	cs = s;
846/* tmpline may contain saved command from urgent mode interruption */
847	for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
848		*cs++ = tmpline[c];
849		if (tmpline[c] == '\n') {
850			*cs++ = '\0';
851			if (debug)
852				syslog(LOG_DEBUG, "command: %s", s);
853			tmpline[0] = '\0';
854			return(s);
855		}
856		if (c == 0)
857			tmpline[0] = '\0';
858	}
859	while ((c = getc(iop)) != EOF) {
860		c &= 0377;
861		if (c == IAC) {
862		    if ((c = getc(iop)) != EOF) {
863			c &= 0377;
864			switch (c) {
865			case WILL:
866			case WONT:
867				c = getc(iop);
868				printf("%c%c%c", IAC, DONT, 0377&c);
869				(void) fflush(stdout);
870				continue;
871			case DO:
872			case DONT:
873				c = getc(iop);
874				printf("%c%c%c", IAC, WONT, 0377&c);
875				(void) fflush(stdout);
876				continue;
877			case IAC:
878				break;
879			default:
880				continue;	/* ignore command */
881			}
882		    }
883		}
884		*cs++ = c;
885		if (--n <= 0 || c == '\n')
886			break;
887	}
888	if (c == EOF && cs == s)
889		return (NULL);
890	*cs++ = '\0';
891	if (debug) {
892		if (!guest && strncasecmp("pass ", s, 5) == 0) {
893			/* Don't syslog passwords */
894			syslog(LOG_DEBUG, "command: %.5s ???", s);
895		} else {
896			register char *cp;
897			register int len;
898
899			/* Don't syslog trailing CR-LF */
900			len = strlen(s);
901			cp = s + len - 1;
902			while (cp >= s && (*cp == '\n' || *cp == '\r')) {
903				--cp;
904				--len;
905			}
906			syslog(LOG_DEBUG, "command: %.*s", len, s);
907		}
908	}
909	return (s);
910}
911
912static void
913toolong(signo)
914	int signo;
915{
916
917	reply(421,
918	    "Timeout (%d seconds): closing control connection.", timeout);
919	if (logging)
920		syslog(LOG_INFO, "User %s timed out after %d seconds",
921		    (pw ? pw -> pw_name : "unknown"), timeout);
922	dologout(1);
923}
924
925static int
926yylex()
927{
928	static int cpos, state;
929	char *cp, *cp2;
930	struct tab *p;
931	int n;
932	char c;
933
934	for (;;) {
935		switch (state) {
936
937		case CMD:
938			(void) signal(SIGALRM, toolong);
939			(void) alarm((unsigned) timeout);
940			if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
941				reply(221, "You could at least say goodbye.");
942				dologout(0);
943			}
944			(void) alarm(0);
945#ifdef HASSETPROCTITLE
946			if (strncasecmp(cbuf, "PASS", 4) != NULL)
947				setproctitle("%s: %s", proctitle, cbuf);
948#endif /* HASSETPROCTITLE */
949			if ((cp = strchr(cbuf, '\r'))) {
950				*cp++ = '\n';
951				*cp = '\0';
952			}
953			if ((cp = strpbrk(cbuf, " \n")))
954				cpos = cp - cbuf;
955			if (cpos == 0)
956				cpos = 4;
957			c = cbuf[cpos];
958			cbuf[cpos] = '\0';
959			upper(cbuf);
960			p = lookup(cmdtab, cbuf);
961			cbuf[cpos] = c;
962			if (p != 0) {
963				if (p->implemented == 0) {
964					nack(p->name);
965					longjmp(errcatch,0);
966					/* NOTREACHED */
967				}
968				state = p->state;
969				yylval.s = p->name;
970				return (p->token);
971			}
972			break;
973
974		case SITECMD:
975			if (cbuf[cpos] == ' ') {
976				cpos++;
977				return (SP);
978			}
979			cp = &cbuf[cpos];
980			if ((cp2 = strpbrk(cp, " \n")))
981				cpos = cp2 - cbuf;
982			c = cbuf[cpos];
983			cbuf[cpos] = '\0';
984			upper(cp);
985			p = lookup(sitetab, cp);
986			cbuf[cpos] = c;
987			if (p != 0) {
988				if (p->implemented == 0) {
989					state = CMD;
990					nack(p->name);
991					longjmp(errcatch,0);
992					/* NOTREACHED */
993				}
994				state = p->state;
995				yylval.s = p->name;
996				return (p->token);
997			}
998			state = CMD;
999			break;
1000
1001		case OSTR:
1002			if (cbuf[cpos] == '\n') {
1003				state = CMD;
1004				return (CRLF);
1005			}
1006			/* FALLTHROUGH */
1007
1008		case STR1:
1009		case ZSTR1:
1010		dostr1:
1011			if (cbuf[cpos] == ' ') {
1012				cpos++;
1013				state = state == OSTR ? STR2 : ++state;
1014				return (SP);
1015			}
1016			break;
1017
1018		case ZSTR2:
1019			if (cbuf[cpos] == '\n') {
1020				state = CMD;
1021				return (CRLF);
1022			}
1023			/* FALLTHROUGH */
1024
1025		case STR2:
1026			cp = &cbuf[cpos];
1027			n = strlen(cp);
1028			cpos += n - 1;
1029			/*
1030			 * Make sure the string is nonempty and \n terminated.
1031			 */
1032			if (n > 1 && cbuf[cpos] == '\n') {
1033				cbuf[cpos] = '\0';
1034				yylval.s = copy(cp);
1035				cbuf[cpos] = '\n';
1036				state = ARGS;
1037				return (STRING);
1038			}
1039			break;
1040
1041		case NSTR:
1042			if (cbuf[cpos] == ' ') {
1043				cpos++;
1044				return (SP);
1045			}
1046			if (isdigit(cbuf[cpos])) {
1047				cp = &cbuf[cpos];
1048				while (isdigit(cbuf[++cpos]))
1049					;
1050				c = cbuf[cpos];
1051				cbuf[cpos] = '\0';
1052				yylval.i = atoi(cp);
1053				cbuf[cpos] = c;
1054				state = STR1;
1055				return (NUMBER);
1056			}
1057			state = STR1;
1058			goto dostr1;
1059
1060		case ARGS:
1061			if (isdigit(cbuf[cpos])) {
1062				cp = &cbuf[cpos];
1063				while (isdigit(cbuf[++cpos]))
1064					;
1065				c = cbuf[cpos];
1066				cbuf[cpos] = '\0';
1067				yylval.i = atoi(cp);
1068				cbuf[cpos] = c;
1069				return (NUMBER);
1070			}
1071			switch (cbuf[cpos++]) {
1072
1073			case '\n':
1074				state = CMD;
1075				return (CRLF);
1076
1077			case ' ':
1078				return (SP);
1079
1080			case ',':
1081				return (COMMA);
1082
1083			case 'A':
1084			case 'a':
1085				return (A);
1086
1087			case 'B':
1088			case 'b':
1089				return (B);
1090
1091			case 'C':
1092			case 'c':
1093				return (C);
1094
1095			case 'E':
1096			case 'e':
1097				return (E);
1098
1099			case 'F':
1100			case 'f':
1101				return (F);
1102
1103			case 'I':
1104			case 'i':
1105				return (I);
1106
1107			case 'L':
1108			case 'l':
1109				return (L);
1110
1111			case 'N':
1112			case 'n':
1113				return (N);
1114
1115			case 'P':
1116			case 'p':
1117				return (P);
1118
1119			case 'R':
1120			case 'r':
1121				return (R);
1122
1123			case 'S':
1124			case 's':
1125				return (S);
1126
1127			case 'T':
1128			case 't':
1129				return (T);
1130
1131			}
1132			break;
1133
1134		default:
1135			fatal("Unknown state in scanner.");
1136		}
1137		yyerror((char *) 0);
1138		state = CMD;
1139		longjmp(errcatch,0);
1140	}
1141}
1142
1143void
1144upper(s)
1145	char *s;
1146{
1147	while (*s != '\0') {
1148		if (islower(*s))
1149			*s = toupper(*s);
1150		s++;
1151	}
1152}
1153
1154static char *
1155copy(s)
1156	char *s;
1157{
1158	char *p;
1159
1160	p = malloc((unsigned) strlen(s) + 1);
1161	if (p == NULL)
1162		fatal("Ran out of memory.");
1163	(void) strcpy(p, s);
1164	return (p);
1165}
1166
1167static void
1168help(ctab, s)
1169	struct tab *ctab;
1170	char *s;
1171{
1172	struct tab *c;
1173	int width, NCMDS;
1174	char *type;
1175
1176	if (ctab == sitetab)
1177		type = "SITE ";
1178	else
1179		type = "";
1180	width = 0, NCMDS = 0;
1181	for (c = ctab; c->name != NULL; c++) {
1182		int len = strlen(c->name);
1183
1184		if (len > width)
1185			width = len;
1186		NCMDS++;
1187	}
1188	width = (width + 8) &~ 7;
1189	if (s == 0) {
1190		int i, j, w;
1191		int columns, lines;
1192
1193		lreply(214, "The following %scommands are recognized %s.",
1194		    type, "(* =>'s unimplemented)");
1195		columns = 76 / width;
1196		if (columns == 0)
1197			columns = 1;
1198		lines = (NCMDS + columns - 1) / columns;
1199		for (i = 0; i < lines; i++) {
1200			printf("   ");
1201			for (j = 0; j < columns; j++) {
1202				c = ctab + j * lines + i;
1203				printf("%s%c", c->name,
1204					c->implemented ? ' ' : '*');
1205				if (c + lines >= &ctab[NCMDS])
1206					break;
1207				w = strlen(c->name) + 1;
1208				while (w < width) {
1209					putchar(' ');
1210					w++;
1211				}
1212			}
1213			printf("\r\n");
1214		}
1215		(void) fflush(stdout);
1216		reply(214, "Direct comments to ftp-bugs@%s.", hostname);
1217		return;
1218	}
1219	upper(s);
1220	c = lookup(ctab, s);
1221	if (c == (struct tab *)0) {
1222		reply(502, "Unknown command %s.", s);
1223		return;
1224	}
1225	if (c->implemented)
1226		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
1227	else
1228		reply(214, "%s%-*s\t%s; unimplemented.", type, width,
1229		    c->name, c->help);
1230}
1231
1232static void
1233sizecmd(filename)
1234	char *filename;
1235{
1236	switch (type) {
1237	case TYPE_L:
1238	case TYPE_I: {
1239		struct stat stbuf;
1240		if (stat(filename, &stbuf) < 0 || !S_ISREG(stbuf.st_mode))
1241			reply(550, "%s: not a plain file.", filename);
1242		else
1243			reply(213, "%qu", stbuf.st_size);
1244		break; }
1245	case TYPE_A: {
1246		FILE *fin;
1247		int c;
1248		off_t count;
1249		struct stat stbuf;
1250		fin = fopen(filename, "r");
1251		if (fin == NULL) {
1252			perror_reply(550, filename);
1253			return;
1254		}
1255		if (fstat(fileno(fin), &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) {
1256			reply(550, "%s: not a plain file.", filename);
1257			(void) fclose(fin);
1258			return;
1259		}
1260
1261		count = 0;
1262		while((c=getc(fin)) != EOF) {
1263			if (c == '\n')	/* will get expanded to \r\n */
1264				count++;
1265			count++;
1266		}
1267		(void) fclose(fin);
1268
1269		reply(213, "%qd", count);
1270		break; }
1271	default:
1272		reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
1273	}
1274}
1275