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