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