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