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