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