ftpcmd.y revision 1.8
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 IDLE check_login SP NUMBER CRLF
452		{
453			if ($4) {
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			} else
989				setproctitle("%s: %s", proctitle, cbuf);
990#endif /* HASSETPROCTITLE */
991			if ((cp = strpbrk(cbuf, " \n")))
992				cpos = cp - cbuf;
993			if (cpos == 0)
994				cpos = 4;
995			c = cbuf[cpos];
996			cbuf[cpos] = '\0';
997			upper(cbuf);
998			p = lookup(cmdtab, cbuf);
999			cbuf[cpos] = c;
1000			if (p != 0) {
1001				if (p->implemented == 0) {
1002					nack(p->name);
1003					longjmp(errcatch,0);
1004					/* NOTREACHED */
1005				}
1006				state = p->state;
1007				yylval.s = p->name;
1008				return (p->token);
1009			}
1010			break;
1011
1012		case SITECMD:
1013			if (cbuf[cpos] == ' ') {
1014				cpos++;
1015				return (SP);
1016			}
1017			cp = &cbuf[cpos];
1018			if ((cp2 = strpbrk(cp, " \n")))
1019				cpos = cp2 - cbuf;
1020			c = cbuf[cpos];
1021			cbuf[cpos] = '\0';
1022			upper(cp);
1023			p = lookup(sitetab, cp);
1024			cbuf[cpos] = c;
1025			if (p != 0) {
1026				if (p->implemented == 0) {
1027					state = CMD;
1028					nack(p->name);
1029					longjmp(errcatch,0);
1030					/* NOTREACHED */
1031				}
1032				state = p->state;
1033				yylval.s = p->name;
1034				return (p->token);
1035			}
1036			state = CMD;
1037			break;
1038
1039		case OSTR:
1040			if (cbuf[cpos] == '\n') {
1041				state = CMD;
1042				return (CRLF);
1043			}
1044			/* FALLTHROUGH */
1045
1046		case STR1:
1047		case ZSTR1:
1048		dostr1:
1049			if (cbuf[cpos] == ' ') {
1050				cpos++;
1051				state = state == OSTR ? STR2 : ++state;
1052				return (SP);
1053			}
1054			break;
1055
1056		case ZSTR2:
1057			if (cbuf[cpos] == '\n') {
1058				state = CMD;
1059				return (CRLF);
1060			}
1061			/* FALLTHROUGH */
1062
1063		case STR2:
1064			cp = &cbuf[cpos];
1065			n = strlen(cp);
1066			cpos += n - 1;
1067			/*
1068			 * Make sure the string is nonempty and \n terminated.
1069			 */
1070			if (n > 1 && cbuf[cpos] == '\n') {
1071				cbuf[cpos] = '\0';
1072				yylval.s = strdup(cp);
1073				if (yylval.s == NULL)
1074					fatal("Ran out of memory.");
1075				cbuf[cpos] = '\n';
1076				state = ARGS;
1077				return (STRING);
1078			}
1079			break;
1080
1081		case NSTR:
1082			if (cbuf[cpos] == ' ') {
1083				cpos++;
1084				return (SP);
1085			}
1086			if (isdigit(cbuf[cpos])) {
1087				cp = &cbuf[cpos];
1088				while (isdigit(cbuf[++cpos]))
1089					;
1090				c = cbuf[cpos];
1091				cbuf[cpos] = '\0';
1092				yylval.i = atoi(cp);
1093				cbuf[cpos] = c;
1094				state = STR1;
1095				return (NUMBER);
1096			}
1097			state = STR1;
1098			goto dostr1;
1099
1100		case ARGS:
1101			if (isdigit(cbuf[cpos])) {
1102				cp = &cbuf[cpos];
1103				while (isdigit(cbuf[++cpos]))
1104					;
1105				c = cbuf[cpos];
1106				cbuf[cpos] = '\0';
1107				yylval.i = atoi(cp);
1108				cbuf[cpos] = c;
1109				return (NUMBER);
1110			}
1111			switch (cbuf[cpos++]) {
1112
1113			case '\n':
1114				state = CMD;
1115				return (CRLF);
1116
1117			case ' ':
1118				return (SP);
1119
1120			case ',':
1121				return (COMMA);
1122
1123			case 'A':
1124			case 'a':
1125				return (A);
1126
1127			case 'B':
1128			case 'b':
1129				return (B);
1130
1131			case 'C':
1132			case 'c':
1133				return (C);
1134
1135			case 'E':
1136			case 'e':
1137				return (E);
1138
1139			case 'F':
1140			case 'f':
1141				return (F);
1142
1143			case 'I':
1144			case 'i':
1145				return (I);
1146
1147			case 'L':
1148			case 'l':
1149				return (L);
1150
1151			case 'N':
1152			case 'n':
1153				return (N);
1154
1155			case 'P':
1156			case 'p':
1157				return (P);
1158
1159			case 'R':
1160			case 'r':
1161				return (R);
1162
1163			case 'S':
1164			case 's':
1165				return (S);
1166
1167			case 'T':
1168			case 't':
1169				return (T);
1170
1171			}
1172			break;
1173
1174		default:
1175			fatal("Unknown state in scanner.");
1176		}
1177		yyerror((char *) 0);
1178		state = CMD;
1179		longjmp(errcatch,0);
1180	}
1181}
1182
1183void
1184upper(s)
1185	char *s;
1186{
1187	while (*s != '\0') {
1188		if (islower(*s))
1189			*s = toupper(*s);
1190		s++;
1191	}
1192}
1193
1194static void
1195help(ctab, s)
1196	struct tab *ctab;
1197	char *s;
1198{
1199	struct tab *c;
1200	int width, NCMDS;
1201	char *type;
1202
1203	if (ctab == sitetab)
1204		type = "SITE ";
1205	else
1206		type = "";
1207	width = 0, NCMDS = 0;
1208	for (c = ctab; c->name != NULL; c++) {
1209		int len = strlen(c->name);
1210
1211		if (len > width)
1212			width = len;
1213		NCMDS++;
1214	}
1215	width = (width + 8) &~ 7;
1216	if (s == 0) {
1217		int i, j, w;
1218		int columns, lines;
1219
1220		lreply(214, "The following %scommands are recognized %s.",
1221		    type, "(* =>'s unimplemented)");
1222		columns = 76 / width;
1223		if (columns == 0)
1224			columns = 1;
1225		lines = (NCMDS + columns - 1) / columns;
1226		for (i = 0; i < lines; i++) {
1227			printf("   ");
1228			for (j = 0; j < columns; j++) {
1229				c = ctab + j * lines + i;
1230				printf("%s%c", c->name,
1231					c->implemented ? ' ' : '*');
1232				if (c + lines >= &ctab[NCMDS])
1233					break;
1234				w = strlen(c->name) + 1;
1235				while (w < width) {
1236					putchar(' ');
1237					w++;
1238				}
1239			}
1240			printf("\r\n");
1241		}
1242		(void) fflush(stdout);
1243		reply(214, "Direct comments to ftp-bugs@%s.", hostname);
1244		return;
1245	}
1246	upper(s);
1247	c = lookup(ctab, s);
1248	if (c == (struct tab *)0) {
1249		reply(502, "Unknown command %s.", s);
1250		return;
1251	}
1252	if (c->implemented)
1253		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
1254	else
1255		reply(214, "%s%-*s\t%s; unimplemented.", type, width,
1256		    c->name, c->help);
1257}
1258
1259static void
1260sizecmd(filename)
1261	char *filename;
1262{
1263	switch (type) {
1264	case TYPE_L:
1265	case TYPE_I: {
1266		struct stat stbuf;
1267		if (stat(filename, &stbuf) < 0 || !S_ISREG(stbuf.st_mode))
1268			reply(550, "%s: not a plain file.", filename);
1269		else
1270			reply(213, "%qu", stbuf.st_size);
1271		break; }
1272	case TYPE_A: {
1273		FILE *fin;
1274		int c;
1275		off_t count;
1276		struct stat stbuf;
1277		fin = fopen(filename, "r");
1278		if (fin == NULL) {
1279			perror_reply(550, filename);
1280			return;
1281		}
1282		if (fstat(fileno(fin), &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) {
1283			reply(550, "%s: not a plain file.", filename);
1284			(void) fclose(fin);
1285			return;
1286		}
1287
1288		count = 0;
1289		while((c=getc(fin)) != EOF) {
1290			if (c == '\n')	/* will get expanded to \r\n */
1291				count++;
1292			count++;
1293		}
1294		(void) fclose(fin);
1295
1296		reply(213, "%qd", count);
1297		break; }
1298	default:
1299		reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
1300	}
1301}
1302