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