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