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