1147072Sbrooks/*	$NetBSD: ftp.y,v 1.1.1.4 2011/09/10 21:22:06 christos Exp $	*/
2147072Sbrooks
3147072Sbrooks/*
4147072Sbrooks * Copyright (c) 1985, 1988 Regents of the University of California.
5147072Sbrooks * All rights reserved.
6147072Sbrooks *
7147072Sbrooks * Redistribution and use in source and binary forms are permitted
8147072Sbrooks * provided that the above copyright notice and this paragraph are
9147072Sbrooks * duplicated in all such forms and that any documentation,
10147072Sbrooks * advertising materials, and other materials related to such
11147072Sbrooks * distribution and use acknowledge that the software was developed
12147072Sbrooks * by the University of California, Berkeley.  The name of the
13147072Sbrooks * University may not be used to endorse or promote products derived
14147072Sbrooks * from this software without specific prior written permission.
15147072Sbrooks * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
16147072Sbrooks * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
17147072Sbrooks * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
18147072Sbrooks *
19147072Sbrooks *	from: @(#)ftpcmd.y	5.20.1.1 (Berkeley) 3/2/89
20147072Sbrooks *	$NetBSD: ftp.y,v 1.3 2010/12/24 02:58:21 christos Exp $
21147072Sbrooks */
22147072Sbrooks
23147072Sbrooks/*
24147072Sbrooks * Grammar for FTP commands.
25147072Sbrooks * See RFC 959.
26147072Sbrooks */
27147072Sbrooks
28147072Sbrooks%{
29147072Sbrooks
30147072Sbrooks#ifndef lint
31147072Sbrooksstatic char sccsid[] = "@(#)ftpcmd.y	5.20.1.1 (Berkeley) 3/2/89";
32147072Sbrooksstatic char rcsid[] = "$NetBSD: ftp.y,v 1.3 2010/12/24 02:58:21 christos Exp $";
33147072Sbrooks#endif /* not lint */
34147072Sbrooks
35147072Sbrooks#include <sys/param.h>
36147072Sbrooks#include <sys/socket.h>
37147072Sbrooks
38147072Sbrooks#include <netinet/in.h>
39147072Sbrooks
40147072Sbrooks#include <arpa/ftp.h>
41147072Sbrooks
42147072Sbrooks#include <stdlib.h>
43149399Sbrooks#include <unistd.h>
44149399Sbrooks#include <stdio.h>
45149399Sbrooks#include <signal.h>
46147072Sbrooks#include <ctype.h>
47147072Sbrooks#include <pwd.h>
48147072Sbrooks#include <setjmp.h>
49147072Sbrooks#include <syslog.h>
50147072Sbrooks#include <sys/stat.h>
51147072Sbrooks#include <string.h>
52147072Sbrooks#include <time.h>
53147072Sbrooks#include <assert.h>
54147072Sbrooks
55147072Sbrooksextern	struct sockaddr_in data_dest;
56147072Sbrooksextern	int logged_in;
57147072Sbrooksextern	struct passwd *pw;
58147072Sbrooksextern	int guest;
59147072Sbrooksextern	int logging;
60147072Sbrooksextern	int type;
61147072Sbrooksextern	int form;
62147072Sbrooksextern	int debug;
63147072Sbrooksextern	int timeout;
64147072Sbrooksextern	int maxtimeout;
65147072Sbrooksextern  int pdata;
66147072Sbrooksextern	char hostname[], remotehost[];
67147072Sbrooksextern	char proctitle[];
68147072Sbrooksextern	char *globerr;
69147072Sbrooksextern	int usedefault;
70147072Sbrooksextern  int transflag;
71147072Sbrooksextern  char tmpline[];
72147072Sbrooks
73147072Sbrooksextern char **glob(char *);
74147072Sbrooksextern char *renamefrom(char *);
75147072Sbrooksextern void cwd(const char *);
76147072Sbrooks
77147072Sbrooksextern void dologout(int);
78147072Sbrooksextern void fatal(const char *);
79147072Sbrooksextern void makedir(const char *);
80147072Sbrooksextern void nack(const char *);
81147072Sbrooksextern void pass(const char *);
82147072Sbrooksextern void passive(void);
83147072Sbrooksextern void pwd(void);
84147072Sbrooksextern void removedir(char *);
85147072Sbrooksextern void renamecmd(char *, char *);
86226345Sdesextern void retrieve(const char *, const char *);
87226345Sdesextern void send_file_list(const char *);
88147072Sbrooksextern void statcmd(void);
89147072Sbrooksextern void statfilecmd(const char *);
90147072Sbrooksextern void store(char *, const char *, int);
91147072Sbrooksextern void user(const char *);
92147072Sbrooks
93147072Sbrooksextern void perror_reply(int, const char *, ...);
94147072Sbrooksextern void reply(int, const char *, ...);
95147072Sbrooksextern void lreply(int, const char *, ...);
96147072Sbrooks
97147072Sbrooksstatic	int cmd_type;
98147072Sbrooksstatic	int cmd_form;
99147072Sbrooksstatic	int cmd_bytesz;
100147072Sbrookschar	cbuf[512];
101147072Sbrookschar	*fromname;
102147072Sbrooks
103147072Sbrooks
104147072Sbrooks
105147072Sbrooksstatic char * copy(const char *);
106147072Sbrooks
107147072Sbrooksstatic void
108147072Sbrooksyyerror(const char *msg)
109147072Sbrooks{
110147072Sbrooks	perror(msg);
111147072Sbrooks}
112147072Sbrooks%}
113147072Sbrooks
114147072Sbrooks%token
115147072Sbrooks	A	B	C	E	F	I
116147072Sbrooks	L	N	P	R	S	T
117147072Sbrooks
118147072Sbrooks	SP	CRLF	COMMA	STRING	NUMBER
119147072Sbrooks
120147072Sbrooks	USER	PASS	ACCT	REIN	QUIT	PORT
121147072Sbrooks	PASV	TYPE	STRU	MODE	RETR	STOR
122147072Sbrooks	APPE	MLFL	MAIL	MSND	MSOM	MSAM
123147072Sbrooks	MRSQ	MRCP	ALLO	REST	RNFR	RNTO
124147072Sbrooks	ABOR	DELE	CWD	LIST	NLST	SITE
125147072Sbrooks	STAT	HELP	NOOP	MKD	RMD	PWD
126147072Sbrooks	CDUP	STOU	SMNT	SYST	SIZE	MDTM
127147072Sbrooks
128147072Sbrooks	UMASK	IDLE	CHMOD
129147072Sbrooks
130147072Sbrooks	LEXERR
131147072Sbrooks
132147072Sbrooks%start	cmd_list
133147072Sbrooks
134147072Sbrooks%%
135147072Sbrooks
136147072Sbrookscmd_list:	/* empty */
137147072Sbrooks	|	cmd_list cmd
138147072Sbrooks		= {
139147072Sbrooks			fromname = (char *) 0;
140147072Sbrooks		}
141147072Sbrooks	|	cmd_list rcmd
142147072Sbrooks	;
143147072Sbrooks
144147072Sbrookscmd:		USER SP username CRLF
145147072Sbrooks		= {
146147072Sbrooks			user((char *) $3);
147147072Sbrooks			free((char *) $3);
148147072Sbrooks		}
149147072Sbrooks	|	PASS SP password CRLF
150147072Sbrooks		= {
151147072Sbrooks			pass((char *) $3);
152147072Sbrooks			free((char *) $3);
153147072Sbrooks		}
154147072Sbrooks	|	PORT SP host_port CRLF
155147072Sbrooks		= {
156147072Sbrooks			usedefault = 0;
157147072Sbrooks			if (pdata >= 0) {
158147072Sbrooks				(void) close(pdata);
159147072Sbrooks				pdata = -1;
160147072Sbrooks			}
161147072Sbrooks			reply(200, "PORT command successful.");
162147072Sbrooks		}
163147072Sbrooks	|	PASV CRLF
164147072Sbrooks		= {
165147072Sbrooks			passive();
166147072Sbrooks		}
167147072Sbrooks	|	TYPE SP type_code CRLF
168147072Sbrooks		= {
169147072Sbrooks			switch (cmd_type) {
170147072Sbrooks
171147072Sbrooks			case TYPE_A:
172147072Sbrooks				if (cmd_form == FORM_N) {
173147072Sbrooks					reply(200, "Type set to A.");
174147072Sbrooks					type = cmd_type;
175147072Sbrooks					form = cmd_form;
176147072Sbrooks				} else
177147072Sbrooks					reply(504, "Form must be N.");
178147072Sbrooks				break;
179147072Sbrooks
180147072Sbrooks			case TYPE_E:
181147072Sbrooks				reply(504, "Type E not implemented.");
182147072Sbrooks				break;
183147072Sbrooks
184147072Sbrooks			case TYPE_I:
185147072Sbrooks				reply(200, "Type set to I.");
186147072Sbrooks				type = cmd_type;
187147072Sbrooks				break;
188147072Sbrooks
189147072Sbrooks			case TYPE_L:
190147072Sbrooks#if NBBY == 8
191147072Sbrooks				if (cmd_bytesz == 8) {
192147072Sbrooks					reply(200,
193147072Sbrooks					    "Type set to L (byte size 8).");
194147072Sbrooks					type = cmd_type;
195147072Sbrooks				} else
196147072Sbrooks					reply(504, "Byte size must be 8.");
197147072Sbrooks#else /* NBBY == 8 */
198147072Sbrooks				UNIMPLEMENTED for NBBY != 8
199147072Sbrooks#endif /* NBBY == 8 */
200147072Sbrooks			}
201147072Sbrooks		}
202147072Sbrooks	|	STRU SP struct_code CRLF
203147072Sbrooks		= {
204147072Sbrooks			switch ($3) {
205147072Sbrooks
206147072Sbrooks			case STRU_F:
207147072Sbrooks				reply(200, "STRU F ok.");
208147072Sbrooks				break;
209147072Sbrooks
210147072Sbrooks			default:
211147072Sbrooks				reply(504, "Unimplemented STRU type.");
212147072Sbrooks			}
213147072Sbrooks		}
214147072Sbrooks	|	MODE SP mode_code CRLF
215147072Sbrooks		= {
216147072Sbrooks			switch ($3) {
217147072Sbrooks
218147072Sbrooks			case MODE_S:
219147072Sbrooks				reply(200, "MODE S ok.");
220147072Sbrooks				break;
221147072Sbrooks
222147072Sbrooks			default:
223147072Sbrooks				reply(502, "Unimplemented MODE type.");
224147072Sbrooks			}
225147072Sbrooks		}
226147072Sbrooks	|	ALLO SP NUMBER CRLF
227147072Sbrooks		= {
228147072Sbrooks			reply(202, "ALLO command ignored.");
229147072Sbrooks		}
230147072Sbrooks	|	ALLO SP NUMBER SP R SP NUMBER CRLF
231147072Sbrooks		= {
232147072Sbrooks			reply(202, "ALLO command ignored.");
233147072Sbrooks		}
234147072Sbrooks	|	RETR check_login SP pathname CRLF
235147072Sbrooks		= {
236147072Sbrooks			if ($2 && $4 != 0)
237147072Sbrooks				retrieve((char *) 0, (char *) $4);
238147072Sbrooks			if ($4 != 0)
239147072Sbrooks				free((char *) $4);
240		}
241	|	STOR check_login SP pathname CRLF
242		= {
243			if ($2 && $4 != 0)
244				store((char *) $4, "w", 0);
245			if ($4 != 0)
246				free((char *) $4);
247		}
248	|	APPE check_login SP pathname CRLF
249		= {
250			if ($2 && $4 != 0)
251				store((char *) $4, "a", 0);
252			if ($4 != 0)
253				free((char *) $4);
254		}
255	|	NLST check_login CRLF
256		= {
257			if ($2)
258				send_file_list(".");
259		}
260	|	NLST check_login SP STRING CRLF
261		= {
262			if ($2 && $4 != 0)
263				send_file_list((char *) $4);
264			if ($4 != 0)
265				free((char *) $4);
266		}
267	|	LIST check_login CRLF
268		= {
269			if ($2)
270				retrieve("/bin/ls -lgA", "");
271		}
272	|	LIST check_login SP pathname CRLF
273		= {
274			if ($2 && $4 != 0)
275				retrieve("/bin/ls -lgA %s", (char *) $4);
276			if ($4 != 0)
277				free((char *) $4);
278		}
279	|	STAT check_login SP pathname CRLF
280		= {
281			if ($2 && $4 != 0)
282				statfilecmd((char *) $4);
283			if ($4 != 0)
284				free((char *) $4);
285		}
286	|	STAT CRLF
287		= {
288			statcmd();
289		}
290	|	DELE check_login SP pathname CRLF
291		= {
292			if ($2 && $4 != 0)
293				remove((char *) $4);
294			if ($4 != 0)
295				free((char *) $4);
296		}
297	|	RNTO SP pathname CRLF
298		= {
299			if (fromname) {
300				renamecmd(fromname, (char *) $3);
301				free(fromname);
302				fromname = (char *) 0;
303			} else {
304				reply(503, "Bad sequence of commands.");
305			}
306			free((char *) $3);
307		}
308	|	ABOR CRLF
309		= {
310			reply(225, "ABOR command successful.");
311		}
312	|	CWD check_login CRLF
313		= {
314			if ($2)
315				cwd(pw->pw_dir);
316		}
317	|	CWD check_login SP pathname CRLF
318		= {
319			if ($2 && $4 != 0)
320				cwd((char *) $4);
321			if ($4 != 0)
322				free((char *) $4);
323		}
324	|	HELP CRLF
325		= {
326			help(cmdtab, (char *) 0);
327		}
328	|	HELP SP STRING CRLF
329		= {
330			register char *cp = (char *)$3;
331
332			if (strncasecmp(cp, "SITE", 4) == 0) {
333				cp = (char *)$3 + 4;
334				if (*cp == ' ')
335					cp++;
336				if (*cp)
337					help(sitetab, cp);
338				else
339					help(sitetab, (char *) 0);
340			} else
341				help(cmdtab, (char *) $3);
342		}
343	|	NOOP CRLF
344		= {
345			reply(200, "NOOP command successful.");
346		}
347	|	MKD check_login SP pathname CRLF
348		= {
349			if ($2 && $4 != 0)
350				makedir((char *) $4);
351			if ($4 != 0)
352				free((char *) $4);
353		}
354	|	RMD check_login SP pathname CRLF
355		= {
356			if ($2 && $4 != 0)
357				removedir((char *) $4);
358			if ($4 != 0)
359				free((char *) $4);
360		}
361	|	PWD check_login CRLF
362		= {
363			if ($2)
364				pwd();
365		}
366	|	CDUP check_login CRLF
367		= {
368			if ($2)
369				cwd("..");
370		}
371	|	SITE SP HELP CRLF
372		= {
373			help(sitetab, (char *) 0);
374		}
375	|	SITE SP HELP SP STRING CRLF
376		= {
377			help(sitetab, (char *) $5);
378		}
379	|	SITE SP UMASK check_login CRLF
380		= {
381			int oldmask;
382
383			if ($4) {
384				oldmask = umask(0);
385				(void) umask(oldmask);
386				reply(200, "Current UMASK is %03o", oldmask);
387			}
388		}
389	|	SITE SP UMASK check_login SP octal_number CRLF
390		= {
391			int oldmask;
392
393			if ($4) {
394				if (($6 == -1) || ($6 > 0777)) {
395					reply(501, "Bad UMASK value");
396				} else {
397					oldmask = umask($6);
398					reply(200,
399					    "UMASK set to %03o (was %03o)",
400					    $6, oldmask);
401				}
402			}
403		}
404	|	SITE SP CHMOD check_login SP octal_number SP pathname CRLF
405		= {
406			if ($4 && ($8 != 0)) {
407				if ($6 > 0777)
408					reply(501,
409				"CHMOD: Mode value must be between 0 and 0777");
410				else if (chmod((char *) $8, $6) < 0)
411					perror_reply(550, (char *) $8);
412				else
413					reply(200, "CHMOD command successful.");
414			}
415			if ($8 != 0)
416				free((char *) $8);
417		}
418	|	SITE SP IDLE CRLF
419		= {
420			reply(200,
421			    "Current IDLE time limit is %d seconds; max %d",
422				timeout, maxtimeout);
423		}
424	|	SITE SP IDLE SP NUMBER CRLF
425		= {
426			if ($5 < 30 || $5 > maxtimeout) {
427				reply(501,
428			"Maximum IDLE time must be between 30 and %d seconds",
429				    maxtimeout);
430			} else {
431				timeout = $5;
432				(void) alarm((unsigned) timeout);
433				reply(200,
434				    "Maximum IDLE time set to %d seconds",
435				    timeout);
436			}
437		}
438	|	STOU check_login SP pathname CRLF
439		= {
440			if ($2 && $4 != 0)
441				store((char *) $4, "w", 1);
442			if ($4 != 0)
443				free((char *) $4);
444		}
445	|	SYST CRLF
446		= {
447#ifdef unix
448#ifdef BSD
449			reply(215, "UNIX Type: L%d Version: BSD-%d",
450				NBBY, BSD);
451#else /* BSD */
452			reply(215, "UNIX Type: L%d", NBBY);
453#endif /* BSD */
454#else /* unix */
455			reply(215, "UNKNOWN Type: L%d", NBBY);
456#endif /* unix */
457		}
458
459		/*
460		 * SIZE is not in RFC959, but Postel has blessed it and
461		 * it will be in the updated RFC.
462		 *
463		 * Return size of file in a format suitable for
464		 * using with RESTART (we just count bytes).
465		 */
466	|	SIZE check_login SP pathname CRLF
467		= {
468			if ($2 && $4 != 0)
469				sizecmd((char *) $4);
470			if ($4 != 0)
471				free((char *) $4);
472		}
473
474		/*
475		 * MDTM is not in RFC959, but Postel has blessed it and
476		 * it will be in the updated RFC.
477		 *
478		 * Return modification time of file as an ISO 3307
479		 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
480		 * where xxx is the fractional second (of any precision,
481		 * not necessarily 3 digits)
482		 */
483	|	MDTM check_login SP pathname CRLF
484		= {
485			if ($2 && $4 != 0) {
486				struct stat stbuf;
487				if (stat((char *) $4, &stbuf) < 0)
488					perror_reply(550, "%s", (char *) $4);
489				else if ((stbuf.st_mode&S_IFMT) != S_IFREG) {
490					reply(550, "%s: not a plain file.",
491						(char *) $4);
492				} else {
493					register struct tm *t;
494					t = gmtime(&stbuf.st_mtime);
495					reply(213,
496					    "%04d%02d%02d%02d%02d%02d",
497					    1900 + t->tm_year,
498					    t->tm_mon+1, t->tm_mday,
499					    t->tm_hour, t->tm_min, t->tm_sec);
500				}
501			}
502			if ($4 != 0)
503				free((char *) $4);
504		}
505	|	QUIT CRLF
506		= {
507			reply(221, "Goodbye.");
508			dologout(0);
509		}
510	|	error CRLF
511		= {
512			yyerrok;
513		}
514	;
515rcmd:		RNFR check_login SP pathname CRLF
516		= {
517			if ($2 && $4) {
518				fromname = renamefrom((char *) $4);
519				if (fromname == (char *) 0 && $4) {
520					free((char *) $4);
521				}
522			}
523		}
524	;
525
526username:	STRING
527	;
528
529password:	/* empty */
530		= {
531			*(const char **)(&($$)) = "";
532		}
533	|	STRING
534	;
535
536byte_size:	NUMBER
537	;
538
539host_port:	NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
540		NUMBER COMMA NUMBER
541		= {
542			register char *a, *p;
543
544			a = (char *)&data_dest.sin_addr;
545			a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7;
546			p = (char *)&data_dest.sin_port;
547			p[0] = $9; p[1] = $11;
548			data_dest.sin_family = AF_INET;
549		}
550	;
551
552form_code:	N
553	= {
554		$$ = FORM_N;
555	}
556	|	T
557	= {
558		$$ = FORM_T;
559	}
560	|	C
561	= {
562		$$ = FORM_C;
563	}
564	;
565
566type_code:	A
567	= {
568		cmd_type = TYPE_A;
569		cmd_form = FORM_N;
570	}
571	|	A SP form_code
572	= {
573		cmd_type = TYPE_A;
574		cmd_form = $3;
575	}
576	|	E
577	= {
578		cmd_type = TYPE_E;
579		cmd_form = FORM_N;
580	}
581	|	E SP form_code
582	= {
583		cmd_type = TYPE_E;
584		cmd_form = $3;
585	}
586	|	I
587	= {
588		cmd_type = TYPE_I;
589	}
590	|	L
591	= {
592		cmd_type = TYPE_L;
593		cmd_bytesz = NBBY;
594	}
595	|	L SP byte_size
596	= {
597		cmd_type = TYPE_L;
598		cmd_bytesz = $3;
599	}
600	/* this is for a bug in the BBN ftp */
601	|	L byte_size
602	= {
603		cmd_type = TYPE_L;
604		cmd_bytesz = $2;
605	}
606	;
607
608struct_code:	F
609	= {
610		$$ = STRU_F;
611	}
612	|	R
613	= {
614		$$ = STRU_R;
615	}
616	|	P
617	= {
618		$$ = STRU_P;
619	}
620	;
621
622mode_code:	S
623	= {
624		$$ = MODE_S;
625	}
626	|	B
627	= {
628		$$ = MODE_B;
629	}
630	|	C
631	= {
632		$$ = MODE_C;
633	}
634	;
635
636pathname:	pathstring
637	= {
638		/*
639		 * Problem: this production is used for all pathname
640		 * processing, but only gives a 550 error reply.
641		 * This is a valid reply in some cases but not in others.
642		 */
643		if (logged_in && $1 && strncmp((char *) $1, "~", 1) == 0) {
644			*(char **)&($$) = *glob((char *) $1);
645			if (globerr != 0) {
646				reply(550, globerr);
647				$$ = 0;
648			}
649			free((char *) $1);
650		} else
651			$$ = $1;
652	}
653	;
654
655pathstring:	STRING
656	;
657
658octal_number:	NUMBER
659	= {
660		register int ret, dec, multby, digit;
661
662		/*
663		 * Convert a number that was read as decimal number
664		 * to what it would be if it had been read as octal.
665		 */
666		dec = $1;
667		multby = 1;
668		ret = 0;
669		while (dec) {
670			digit = dec%10;
671			if (digit > 7) {
672				ret = -1;
673				break;
674			}
675			ret += digit * multby;
676			multby *= 8;
677			dec /= 10;
678		}
679		$$ = ret;
680	}
681	;
682
683check_login:	/* empty */
684	= {
685		if (logged_in)
686			$$ = 1;
687		else {
688			reply(530, "Please login with USER and PASS.");
689			$$ = 0;
690		}
691	}
692	;
693
694%%
695
696#ifdef YYBYACC
697extern int YYLEX_DECL();
698static void YYERROR_DECL();
699#endif
700
701extern jmp_buf errcatch;
702
703static void upper(char *);
704
705#define	CMD	0	/* beginning of command */
706#define	ARGS	1	/* expect miscellaneous arguments */
707#define	STR1	2	/* expect SP followed by STRING */
708#define	STR2	3	/* expect STRING */
709#define	OSTR	4	/* optional SP then STRING */
710#define	ZSTR1	5	/* SP then optional STRING */
711#define	ZSTR2	6	/* optional STRING after SP */
712#define	SITECMD	7	/* SITE command */
713#define	NSTR	8	/* Number followed by a string */
714
715struct tab {
716	const char *name;
717	short	token;
718	short	state;
719	short	implemented;	/* 1 if command is implemented */
720	const char *help;
721};
722
723struct tab cmdtab[] = {		/* In order defined in RFC 765 */
724	{ "USER", USER, STR1, 1,	"<sp> username" },
725	{ "PASS", PASS, ZSTR1, 1,	"<sp> password" },
726	{ "ACCT", ACCT, STR1, 0,	"(specify account)" },
727	{ "SMNT", SMNT, ARGS, 0,	"(structure mount)" },
728	{ "REIN", REIN, ARGS, 0,	"(reinitialize server state)" },
729	{ "QUIT", QUIT, ARGS, 1,	"(terminate service)", },
730	{ "PORT", PORT, ARGS, 1,	"<sp> b0, b1, b2, b3, b4" },
731	{ "PASV", PASV, ARGS, 1,	"(set server in passive mode)" },
732	{ "TYPE", TYPE, ARGS, 1,	"<sp> [ A | E | I | L ]" },
733	{ "STRU", STRU, ARGS, 1,	"(specify file structure)" },
734	{ "MODE", MODE, ARGS, 1,	"(specify transfer mode)" },
735	{ "RETR", RETR, STR1, 1,	"<sp> file-name" },
736	{ "STOR", STOR, STR1, 1,	"<sp> file-name" },
737	{ "APPE", APPE, STR1, 1,	"<sp> file-name" },
738	{ "MLFL", MLFL, OSTR, 0,	"(mail file)" },
739	{ "MAIL", MAIL, OSTR, 0,	"(mail to user)" },
740	{ "MSND", MSND, OSTR, 0,	"(mail send to terminal)" },
741	{ "MSOM", MSOM, OSTR, 0,	"(mail send to terminal or mailbox)" },
742	{ "MSAM", MSAM, OSTR, 0,	"(mail send to terminal and mailbox)" },
743	{ "MRSQ", MRSQ, OSTR, 0,	"(mail recipient scheme question)" },
744	{ "MRCP", MRCP, STR1, 0,	"(mail recipient)" },
745	{ "ALLO", ALLO, ARGS, 1,	"allocate storage (vacuously)" },
746	{ "REST", REST, ARGS, 0,	"(restart command)" },
747	{ "RNFR", RNFR, STR1, 1,	"<sp> file-name" },
748	{ "RNTO", RNTO, STR1, 1,	"<sp> file-name" },
749	{ "ABOR", ABOR, ARGS, 1,	"(abort operation)" },
750	{ "DELE", DELE, STR1, 1,	"<sp> file-name" },
751	{ "CWD",  CWD,  OSTR, 1,	"[ <sp> directory-name ]" },
752	{ "XCWD", CWD,	OSTR, 1,	"[ <sp> directory-name ]" },
753	{ "LIST", LIST, OSTR, 1,	"[ <sp> path-name ]" },
754	{ "NLST", NLST, OSTR, 1,	"[ <sp> path-name ]" },
755	{ "SITE", SITE, SITECMD, 1,	"site-cmd [ <sp> arguments ]" },
756	{ "SYST", SYST, ARGS, 1,	"(get type of operating system)" },
757	{ "STAT", STAT, OSTR, 1,	"[ <sp> path-name ]" },
758	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
759	{ "NOOP", NOOP, ARGS, 1,	"" },
760	{ "MKD",  MKD,  STR1, 1,	"<sp> path-name" },
761	{ "XMKD", MKD,  STR1, 1,	"<sp> path-name" },
762	{ "RMD",  RMD,  STR1, 1,	"<sp> path-name" },
763	{ "XRMD", RMD,  STR1, 1,	"<sp> path-name" },
764	{ "PWD",  PWD,  ARGS, 1,	"(return current directory)" },
765	{ "XPWD", PWD,  ARGS, 1,	"(return current directory)" },
766	{ "CDUP", CDUP, ARGS, 1,	"(change to parent directory)" },
767	{ "XCUP", CDUP, ARGS, 1,	"(change to parent directory)" },
768	{ "STOU", STOU, STR1, 1,	"<sp> file-name" },
769	{ "SIZE", SIZE, OSTR, 1,	"<sp> path-name" },
770	{ "MDTM", MDTM, OSTR, 1,	"<sp> path-name" },
771	{ 0,   0,    0,    0,	0 }
772};
773
774struct tab sitetab[] = {
775	{ "UMASK", UMASK, ARGS, 1,	"[ <sp> umask ]" },
776	{ "IDLE", IDLE, ARGS, 1,	"[ <sp> maximum-idle-time ]" },
777	{ "CHMOD", CHMOD, NSTR, 1,	"<sp> mode <sp> file-name" },
778	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
779	{ 0,   0,    0,    0,	0 }
780};
781
782static struct tab *
783lookup(struct tab *p, char *cmd)
784{
785
786	for (; p->name != 0; p++)
787		if (strcmp(cmd, p->name) == 0)
788			return (p);
789	return (0);
790}
791
792#include <arpa/telnet.h>
793
794/*
795 * get_line - a hacked up version of fgets to ignore TELNET escape codes.
796 */
797static char *
798get_line(char *s, int n, FILE *iop)
799{
800	register int c;
801	register char *cs;
802
803	cs = s;
804/* tmpline may contain saved command from urgent mode interruption */
805	for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
806		*cs++ = tmpline[c];
807		if (tmpline[c] == '\n') {
808			*cs = '\0';
809			if (debug)
810				syslog(LOG_DEBUG, "command: %s", s);
811			tmpline[0] = '\0';
812			return(s);
813		}
814		if (c == 0)
815			tmpline[0] = '\0';
816	}
817	while ((c = getc(iop)) != EOF) {
818		c &= 0377;
819		if (c == IAC) {
820		    if ((c = getc(iop)) != EOF) {
821			c &= 0377;
822			switch (c) {
823			case WILL:
824			case WONT:
825				c = getc(iop);
826				printf("%c%c%c", IAC, DONT, 0377&c);
827				(void) fflush(stdout);
828				continue;
829			case DO:
830			case DONT:
831				c = getc(iop);
832				printf("%c%c%c", IAC, WONT, 0377&c);
833				(void) fflush(stdout);
834				continue;
835			case IAC:
836				break;
837			default:
838				continue;	/* ignore command */
839			}
840		    }
841		}
842		*cs++ = c;
843		if (--n <= 0 || c == '\n')
844			break;
845	}
846	if (c == EOF && cs == s)
847		return (0);
848	*cs = '\0';
849	if (debug)
850		syslog(LOG_DEBUG, "command: %s", s);
851	return (s);
852}
853
854static void
855toolong(int sig)
856{
857	time_t now;
858
859	(void) sig;
860	reply(421,
861	  "Timeout (%d seconds): closing control connection.", timeout);
862	(void) time(&now);
863	if (logging) {
864		syslog(LOG_INFO,
865			"User %s timed out after %d seconds at %s",
866			(pw ? pw -> pw_name : "unknown"), timeout, ctime(&now));
867	}
868	dologout(1);
869}
870
871int
872yylex(void)
873{
874	static int cpos, state;
875	register char *cp, *cp2;
876	register struct tab *p;
877	int n;
878	char c;
879
880	for (;;) {
881		switch (state) {
882
883		case CMD:
884			(void) signal(SIGALRM, toolong);
885			(void) alarm((unsigned) timeout);
886			if (get_line(cbuf, sizeof(cbuf)-1, stdin) == 0) {
887				reply(221, "You could at least say goodbye.");
888				dologout(0);
889			}
890			(void) alarm(0);
891#ifdef SETPROCTITLE
892			if (strncasecmp(cbuf, "PASS", 4) != 0)
893				setproctitle("%s: %s", proctitle, cbuf);
894#endif /* SETPROCTITLE */
895			if ((cp = strchr(cbuf, '\r'))) {
896				*cp++ = '\n';
897				*cp = '\0';
898			}
899			if ((cp = strpbrk(cbuf, " \n")))
900				cpos = cp - cbuf;
901			if (cpos == 0)
902				cpos = 4;
903			c = cbuf[cpos];
904			cbuf[cpos] = '\0';
905			upper(cbuf);
906			p = lookup(cmdtab, cbuf);
907			cbuf[cpos] = c;
908			if (p != 0) {
909				if (p->implemented == 0) {
910					nack(p->name);
911					longjmp(errcatch,0);
912					/* NOTREACHED */
913				}
914				state = p->state;
915				*(const char **)(&yylval) = p->name;
916				return (p->token);
917			}
918			break;
919
920		case SITECMD:
921			if (cbuf[cpos] == ' ') {
922				cpos++;
923				return (SP);
924			}
925			cp = &cbuf[cpos];
926			if ((cp2 = strpbrk(cp, " \n")))
927				cpos = cp2 - cbuf;
928			c = cbuf[cpos];
929			cbuf[cpos] = '\0';
930			upper(cp);
931			p = lookup(sitetab, cp);
932			cbuf[cpos] = c;
933			if (p != 0) {
934				if (p->implemented == 0) {
935					state = CMD;
936					nack(p->name);
937					longjmp(errcatch,0);
938					/* NOTREACHED */
939				}
940				state = p->state;
941				*(const char **)(&yylval) = p->name;
942				return (p->token);
943			}
944			state = CMD;
945			break;
946
947		case OSTR:
948			if (cbuf[cpos] == '\n') {
949				state = CMD;
950				return (CRLF);
951			}
952			/* FALLTHROUGH */
953
954		case STR1:
955		case ZSTR1:
956		dostr1:
957			if (cbuf[cpos] == ' ') {
958				cpos++;
959				if (state == OSTR)
960					state = STR2;
961				else
962					++state;
963				return (SP);
964			}
965			break;
966
967		case ZSTR2:
968			if (cbuf[cpos] == '\n') {
969				state = CMD;
970				return (CRLF);
971			}
972			/* FALLTHROUGH */
973
974		case STR2:
975			cp = &cbuf[cpos];
976			n = strlen(cp);
977			cpos += n - 1;
978			/*
979			 * Make sure the string is nonempty and \n terminated.
980			 */
981			if (n > 1 && cbuf[cpos] == '\n') {
982				cbuf[cpos] = '\0';
983				*(char **)&yylval = copy(cp);
984				cbuf[cpos] = '\n';
985				state = ARGS;
986				return (STRING);
987			}
988			break;
989
990		case NSTR:
991			if (cbuf[cpos] == ' ') {
992				cpos++;
993				return (SP);
994			}
995			if (isdigit(cbuf[cpos])) {
996				cp = &cbuf[cpos];
997				while (isdigit(cbuf[++cpos]))
998					;
999				c = cbuf[cpos];
1000				cbuf[cpos] = '\0';
1001				yylval = atoi(cp);
1002				cbuf[cpos] = c;
1003				state = STR1;
1004				return (NUMBER);
1005			}
1006			state = STR1;
1007			goto dostr1;
1008
1009		case ARGS:
1010			if (isdigit(cbuf[cpos])) {
1011				cp = &cbuf[cpos];
1012				while (isdigit(cbuf[++cpos]))
1013					;
1014				c = cbuf[cpos];
1015				cbuf[cpos] = '\0';
1016				yylval = atoi(cp);
1017				cbuf[cpos] = c;
1018				return (NUMBER);
1019			}
1020			switch (cbuf[cpos++]) {
1021
1022			case '\n':
1023				state = CMD;
1024				return (CRLF);
1025
1026			case ' ':
1027				return (SP);
1028
1029			case ',':
1030				return (COMMA);
1031
1032			case 'A':
1033			case 'a':
1034				return (A);
1035
1036			case 'B':
1037			case 'b':
1038				return (B);
1039
1040			case 'C':
1041			case 'c':
1042				return (C);
1043
1044			case 'E':
1045			case 'e':
1046				return (E);
1047
1048			case 'F':
1049			case 'f':
1050				return (F);
1051
1052			case 'I':
1053			case 'i':
1054				return (I);
1055
1056			case 'L':
1057			case 'l':
1058				return (L);
1059
1060			case 'N':
1061			case 'n':
1062				return (N);
1063
1064			case 'P':
1065			case 'p':
1066				return (P);
1067
1068			case 'R':
1069			case 'r':
1070				return (R);
1071
1072			case 'S':
1073			case 's':
1074				return (S);
1075
1076			case 'T':
1077			case 't':
1078				return (T);
1079
1080			}
1081			break;
1082
1083		default:
1084			fatal("Unknown state in scanner.");
1085		}
1086		yyerror((char *) 0);
1087		state = CMD;
1088		longjmp(errcatch,0);
1089	}
1090}
1091
1092static void
1093upper(char *s)
1094{
1095	while (*s != '\0') {
1096		if (islower(*s))
1097			*s = toupper(*s);
1098		s++;
1099	}
1100}
1101
1102static char *
1103copy(const char *s)
1104{
1105	char *p;
1106
1107	p = (char * )malloc(strlen(s) + 1);
1108	if (p == 0)
1109		fatal("Ran out of memory.");
1110	else
1111		(void) strcpy(p, s);
1112	return (p);
1113}
1114
1115static void
1116help(struct tab *ctab, char *s)
1117{
1118	register struct tab *c;
1119	register int width, NCMDS;
1120	const char *help_type;
1121
1122	if (ctab == sitetab)
1123		help_type = "SITE ";
1124	else
1125		help_type = "";
1126	width = 0, NCMDS = 0;
1127	for (c = ctab; c->name != 0; c++) {
1128		int len = strlen(c->name);
1129
1130		if (len > width)
1131			width = len;
1132		NCMDS++;
1133	}
1134	width = (width + 8) &~ 7;
1135	if (s == 0) {
1136		register int i, j, w;
1137		int columns, lines;
1138
1139		lreply(214, "The following %scommands are recognized %s.",
1140		    help_type, "(* =>'s unimplemented)");
1141		columns = 76 / width;
1142		if (columns == 0)
1143			columns = 1;
1144		lines = (NCMDS + columns - 1) / columns;
1145		for (i = 0; i < lines; i++) {
1146			printf("   ");
1147			for (j = 0; j < columns; j++) {
1148				c = ctab + j * lines + i;
1149				assert(c->name != 0);
1150				printf("%s%c", c->name,
1151					c->implemented ? ' ' : '*');
1152				if (c + lines >= &ctab[NCMDS])
1153					break;
1154				w = strlen(c->name) + 1;
1155				while (w < width) {
1156					putchar(' ');
1157					w++;
1158				}
1159			}
1160			printf("\r\n");
1161		}
1162		(void) fflush(stdout);
1163		reply(214, "Direct comments to ftp-bugs@%s.", hostname);
1164		return;
1165	}
1166	upper(s);
1167	c = lookup(ctab, s);
1168	if (c == (struct tab *)0) {
1169		reply(502, "Unknown command %s.", s);
1170		return;
1171	}
1172	if (c->implemented)
1173		reply(214, "Syntax: %s%s %s", help_type, c->name, c->help);
1174	else
1175		reply(214, "%s%-*s\t%s; unimplemented.", help_type, width,
1176		    c->name, c->help);
1177}
1178
1179static void
1180sizecmd(char *filename)
1181{
1182	switch (type) {
1183	case TYPE_L:
1184	case TYPE_I: {
1185		struct stat stbuf;
1186		if (stat(filename, &stbuf) < 0 ||
1187		    (stbuf.st_mode&S_IFMT) != S_IFREG)
1188			reply(550, "%s: not a plain file.", filename);
1189		else
1190#ifdef HAVE_LONG_LONG
1191			reply(213, "%llu", (long long) stbuf.st_size);
1192#else
1193			reply(213, "%lu", stbuf.st_size);
1194#endif
1195		break;}
1196	case TYPE_A: {
1197		FILE *fin;
1198		register int c, count;
1199		struct stat stbuf;
1200		fin = fopen(filename, "r");
1201		if (fin == 0) {
1202			perror_reply(550, filename);
1203			return;
1204		}
1205		if (fstat(fileno(fin), &stbuf) < 0 ||
1206		    (stbuf.st_mode&S_IFMT) != S_IFREG) {
1207			reply(550, "%s: not a plain file.", filename);
1208			(void) fclose(fin);
1209			return;
1210		}
1211
1212		count = 0;
1213		while((c=getc(fin)) != EOF) {
1214			if (c == '\n')	/* will get expanded to \r\n */
1215				count++;
1216			count++;
1217		}
1218		(void) fclose(fin);
1219
1220		reply(213, "%ld", count);
1221		break;}
1222	default:
1223		reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
1224	}
1225}
1226