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