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