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