ftpcmd.y revision 1.42
1/*	$OpenBSD: ftpcmd.y,v 1.42 2003/06/02 19:38:24 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#ifndef lint
43#if 0
44static const char sccsid[] = "@(#)ftpcmd.y	8.3 (Berkeley) 4/6/94";
45#else
46static const char rcsid[] =
47    "$OpenBSD: ftpcmd.y,v 1.42 2003/06/02 19:38:24 millert Exp $";
48#endif
49#endif /* not lint */
50
51#include <sys/param.h>
52#include <sys/socket.h>
53#include <sys/stat.h>
54
55#include <netinet/in.h>
56#include <arpa/ftp.h>
57
58#include <ctype.h>
59#include <errno.h>
60#include <glob.h>
61#include <pwd.h>
62#include <signal.h>
63#include <tzfile.h>
64#include <stdio.h>
65#include <stdlib.h>
66#include <string.h>
67#include <syslog.h>
68#include <time.h>
69#include <unistd.h>
70#include <netdb.h>
71
72#include "extern.h"
73
74extern	union sockunion data_dest;
75extern	int logged_in;
76extern	struct passwd *pw;
77extern	int guest;
78extern	int logging;
79extern	int type;
80extern	int form;
81extern	int debug;
82extern	int timeout;
83extern	int maxtimeout;
84extern  int pdata;
85extern	char hostname[], remotehost[];
86extern	char proctitle[];
87extern	int usedefault;
88extern  int transflag;
89extern  char tmpline[];
90extern	int portcheck;
91extern	union sockunion his_addr;
92extern	int umaskchange;
93
94off_t	restart_point;
95
96static	int cmd_type;
97static	int cmd_form;
98static	int cmd_bytesz;
99static	int state;
100char	cbuf[512];
101char	*fromname;
102
103%}
104
105%union {
106	int	i;
107	char   *s;
108}
109
110%token
111	A	B	C	E	F	I
112	L	N	P	R	S	T
113
114	SP	CRLF	COMMA	ALL
115
116	USER	PASS	ACCT	REIN	QUIT	PORT
117	PASV	TYPE	STRU	MODE	RETR	STOR
118	APPE	MLFL	MAIL	MSND	MSOM	MSAM
119	MRSQ	MRCP	ALLO	REST	RNFR	RNTO
120	ABOR	DELE	CWD	LIST	NLST	SITE
121	STAT	HELP	NOOP	MKD	RMD	PWD
122	CDUP	STOU	SMNT	SYST	SIZE	MDTM
123
124	LPRT	LPSV	EPRT	EPSV
125
126	UMASK	IDLE	CHMOD
127
128	LEXERR
129
130%token	<s> STRING
131%token	<i> NUMBER
132
133%type	<i> check_login check_login_epsvall octal_number byte_size
134%type	<i> struct_code mode_code type_code form_code
135%type	<s> pathstring pathname password username
136%type	<i> host_port host_long_port4 host_long_port6
137
138%start	cmd_list
139
140%%
141
142cmd_list
143	: /* empty */
144	| cmd_list cmd
145		{
146			if (fromname) {
147				free(fromname);
148				fromname = NULL;
149			}
150			restart_point = (off_t) 0;
151		}
152	| cmd_list rcmd
153	;
154
155cmd
156	: USER SP username CRLF
157		{
158			user($3);
159			free($3);
160		}
161	| PASS SP password CRLF
162		{
163			pass($3);
164			memset($3, 0, strlen($3));
165			free($3);
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			int 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			int 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 seconds; max %d",
547				timeout, maxtimeout);
548		}
549	| SITE SP check_login IDLE SP NUMBER CRLF
550		{
551			if ($3) {
552				if ($6 < 30 || $6 > maxtimeout) {
553				reply(501,
554				    "Maximum IDLE time must be between "
555				    "30 and %d seconds",
556				    maxtimeout);
557				} else {
558					timeout = $6;
559					(void) alarm((unsigned) timeout);
560					reply(200,
561					    "Maximum IDLE time set to %d seconds",
562					    timeout);
563				}
564			}
565		}
566	| STOU check_login SP pathname CRLF
567		{
568			if ($2 && $4 != NULL)
569				store($4, "w", 1);
570			if ($4 != NULL)
571				free($4);
572		}
573	| SYST check_login CRLF
574		{
575			if ($2)
576#ifdef unix
577#ifdef BSD
578			reply(215, "UNIX Type: L%d Version: BSD-%d",
579				NBBY, BSD);
580#else /* BSD */
581			reply(215, "UNIX Type: L%d", NBBY);
582#endif /* BSD */
583#else /* unix */
584			reply(215, "UNKNOWN Type: L%d", NBBY);
585#endif /* unix */
586		}
587
588		/*
589		 * SIZE is not in RFC959, but Postel has blessed it and
590		 * it will be in the updated RFC.
591		 *
592		 * Return size of file in a format suitable for
593		 * using with RESTART (we just count bytes).
594		 */
595	| SIZE check_login SP pathname CRLF
596		{
597			if ($2 && $4 != NULL)
598				sizecmd($4);
599			if ($4 != NULL)
600				free($4);
601		}
602
603		/*
604		 * MDTM is not in RFC959, but Postel has blessed it and
605		 * it will be in the updated RFC.
606		 *
607		 * Return modification time of file as an ISO 3307
608		 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
609		 * where xxx is the fractional second (of any precision,
610		 * not necessarily 3 digits)
611		 */
612	| MDTM check_login SP pathname CRLF
613		{
614			if ($2 && $4 != NULL) {
615				struct stat stbuf;
616				if (stat($4, &stbuf) < 0)
617					reply(550, "%s: %s",
618					    $4, strerror(errno));
619				else if (!S_ISREG(stbuf.st_mode)) {
620					reply(550, "%s: not a plain file.", $4);
621				} else {
622					struct tm *t;
623					t = gmtime(&stbuf.st_mtime);
624					reply(213,
625					    "%04d%02d%02d%02d%02d%02d",
626					    TM_YEAR_BASE + t->tm_year,
627					    t->tm_mon+1, t->tm_mday,
628					    t->tm_hour, t->tm_min, t->tm_sec);
629				}
630			}
631			if ($4 != NULL)
632				free($4);
633		}
634	| QUIT CRLF
635		{
636			reply(221, "Goodbye.");
637			dologout(0);
638		}
639	| error
640		{
641			yyclearin;		/* discard lookahead data */
642			yyerrok;		/* clear error condition */
643			state = 0;		/* reset lexer state */
644		}
645	;
646rcmd
647	: RNFR check_login SP pathname CRLF
648		{
649			restart_point = (off_t) 0;
650			if ($2 && $4) {
651				if (fromname)
652					free(fromname);
653				fromname = renamefrom($4);
654				if (fromname == NULL)
655					free($4);
656			} else if ($4) {
657				free ($4);
658			}
659		}
660
661	| REST check_login SP byte_size CRLF
662		{
663			if ($2) {
664			    if (fromname) {
665				    free(fromname);
666				    fromname = NULL;
667			    }
668			    restart_point = $4;	/* XXX $4 is only "int" */
669			    reply(350, "Restarting at %qd. %s", restart_point,
670			       "Send STORE or RETRIEVE to initiate transfer.");
671			}
672		}
673	;
674
675username
676	: STRING
677	;
678
679password
680	: /* empty */
681		{
682			$$ = (char *)calloc(1, sizeof(char));
683		}
684	| STRING
685	;
686
687byte_size
688	: NUMBER
689	;
690
691host_port
692	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
693		NUMBER COMMA NUMBER
694		{
695			char *a, *p;
696
697			if ($1 < 0 || $1 > 255 || $3 < 0 || $3 > 255 ||
698			    $5 < 0 || $5 > 255 || $7 < 0 || $7 > 255 ||
699			    $9 < 0 || $9 > 255 || $11 < 0 || $11 > 255) {
700				$$ = 1;
701			} else {
702				data_dest.su_sin.sin_len = sizeof(struct sockaddr_in);
703				data_dest.su_sin.sin_family = AF_INET;
704				p = (char *)&data_dest.su_sin.sin_port;
705				p[0] = $9; p[1] = $11;
706				a = (char *)&data_dest.su_sin.sin_addr;
707				a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7;
708				$$ = 0;
709			}
710		}
711	;
712
713host_long_port4
714	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
715		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
716		NUMBER
717		{
718			char *a, *p;
719
720			/* reject invalid LPRT command */
721			if ($1 != 4 || $3 != 4
722			 || $5 < 0 || $5 > 255 || $7 < 0 || $7 > 255
723			 || $9 < 0 || $9 > 255 || $11 < 0 || $11 > 255
724			 || $13 != 2
725			 || $15 < 0 || $15 > 255 || $17 < 0 || $17 > 255) {
726				$$ = 1;
727			} else {
728				data_dest.su_sin.sin_len =
729					sizeof(struct sockaddr_in);
730				data_dest.su_family = AF_INET;
731				p = (char *)&data_dest.su_port;
732				p[0] = $15; p[1] = $17;
733				a = (char *)&data_dest.su_sin.sin_addr;
734				a[0] = $5; a[1] = $7; a[2] = $9; a[3] = $11;
735				$$ = 0;
736			}
737		}
738	;
739
740host_long_port6
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 COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
746		NUMBER
747		{
748			char *a, *p;
749
750			/* reject invalid LPRT command */
751			if ($1 != 6 || $3 != 16
752			 || $5 < 0 || $5 > 255 || $7 < 0 || $7 > 255
753			 || $9 < 0 || $9 > 255 || $11 < 0 || $11 > 255
754			 || $13 < 0 || $13 > 255 || $15 < 0 || $15 > 255
755			 || $17 < 0 || $17 > 255 || $19 < 0 || $19 > 255
756			 || $21 < 0 || $21 > 255 || $23 < 0 || $23 > 255
757			 || $25 < 0 || $25 > 255 || $27 < 0 || $27 > 255
758			 || $29 < 0 || $29 > 255 || $31 < 0 || $31 > 255
759			 || $33 < 0 || $33 > 255 || $35 < 0 || $35 > 255
760			 || $37 != 2
761			 || $39 < 0 || $39 > 255 || $41 < 0 || $41 > 255) {
762				$$ = 1;
763			} else {
764				data_dest.su_sin6.sin6_len =
765					sizeof(struct sockaddr_in6);
766				data_dest.su_family = AF_INET6;
767				p = (char *)&data_dest.su_port;
768				p[0] = $39; p[1] = $41;
769				a = (char *)&data_dest.su_sin6.sin6_addr;
770				 a[0] =  $5;  a[1] =  $7;
771				 a[2] =  $9;  a[3] = $11;
772				 a[4] = $13;  a[5] = $15;
773				 a[6] = $17;  a[7] = $19;
774				 a[8] = $21;  a[9] = $23;
775				a[10] = $25; a[11] = $27;
776				a[12] = $29; a[13] = $31;
777				a[14] = $33; a[15] = $35;
778				if (his_addr.su_family == AF_INET6) {
779					/* XXX more sanity checks! */
780					data_dest.su_sin6.sin6_scope_id =
781					    his_addr.su_sin6.sin6_scope_id;
782				}
783
784				$$ = 0;
785			}
786		}
787	;
788
789form_code
790	: N
791		{
792			$$ = FORM_N;
793		}
794	| T
795		{
796			$$ = FORM_T;
797		}
798	| C
799		{
800			$$ = FORM_C;
801		}
802	;
803
804type_code
805	: A
806		{
807			cmd_type = TYPE_A;
808			cmd_form = FORM_N;
809		}
810	| A SP form_code
811		{
812			cmd_type = TYPE_A;
813			cmd_form = $3;
814		}
815	| E
816		{
817			cmd_type = TYPE_E;
818			cmd_form = FORM_N;
819		}
820	| E SP form_code
821		{
822			cmd_type = TYPE_E;
823			cmd_form = $3;
824		}
825	| I
826		{
827			cmd_type = TYPE_I;
828		}
829	| L
830		{
831			cmd_type = TYPE_L;
832			cmd_bytesz = NBBY;
833		}
834	| L SP byte_size
835		{
836			cmd_type = TYPE_L;
837			cmd_bytesz = $3;
838		}
839		/* this is for a bug in the BBN ftp */
840	| L byte_size
841		{
842			cmd_type = TYPE_L;
843			cmd_bytesz = $2;
844		}
845	;
846
847struct_code
848	: F
849		{
850			$$ = STRU_F;
851		}
852	| R
853		{
854			$$ = STRU_R;
855		}
856	| P
857		{
858			$$ = STRU_P;
859		}
860	;
861
862mode_code
863	: S
864		{
865			$$ = MODE_S;
866		}
867	| B
868		{
869			$$ = MODE_B;
870		}
871	| C
872		{
873			$$ = MODE_C;
874		}
875	;
876
877pathname
878	: pathstring
879		{
880			/*
881			 * Problem: this production is used for all pathname
882			 * processing, but only gives a 550 error reply.
883			 * This is a valid reply in some cases but not in others.
884			 */
885			if (logged_in && $1 && strchr($1, '~') != NULL) {
886				glob_t gl;
887				int flags =
888				 GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE;
889				char *pptr = $1;
890
891				/*
892				 * glob() will only find a leading ~, but
893				 * Netscape kindly puts a slash in front of
894				 * it for publish URLs.  There needs to be
895				 * a flag for glob() that expands tildes
896				 * anywhere in the string.
897				 */
898				if ((pptr[0] == '/') && (pptr[1] == '~'))
899					pptr++;
900
901				memset(&gl, 0, sizeof(gl));
902				if (glob(pptr, flags, NULL, &gl) ||
903				    gl.gl_pathc == 0) {
904					reply(550, "not found");
905					$$ = NULL;
906				} else {
907					$$ = strdup(gl.gl_pathv[0]);
908				}
909				globfree(&gl);
910				free($1);
911			} else
912				$$ = $1;
913		}
914	;
915
916pathstring
917	: STRING
918	;
919
920octal_number
921	: NUMBER
922		{
923			int ret, dec, multby, digit;
924
925			/*
926			 * Convert a number that was read as decimal number
927			 * to what it would be if it had been read as octal.
928			 */
929			dec = $1;
930			multby = 1;
931			ret = 0;
932			while (dec) {
933				digit = dec%10;
934				if (digit > 7) {
935					ret = -1;
936					break;
937				}
938				ret += digit * multby;
939				multby *= 8;
940				dec /= 10;
941			}
942			$$ = ret;
943		}
944	;
945
946
947check_login
948	: /* empty */
949		{
950			if (logged_in)
951				$$ = 1;
952			else {
953				reply(530, "Please login with USER and PASS.");
954				$$ = 0;
955			}
956		}
957	;
958
959check_login_epsvall
960	: /* empty */
961		{
962			if (!logged_in) {
963				reply(530, "Please login with USER and PASS.");
964				$$ = 0;
965			} else if (epsvall) {
966				reply(501, "the command is disallowed "
967				    "after EPSV ALL");
968				usedefault = 1;
969				$$ = 0;
970			} else
971				$$ = 1;
972		}
973	;
974
975%%
976
977#define	CMD	0	/* beginning of command */
978#define	ARGS	1	/* expect miscellaneous arguments */
979#define	STR1	2	/* expect SP followed by STRING */
980#define	STR2	3	/* expect STRING */
981#define	OSTR	4	/* optional SP then STRING */
982#define	ZSTR1	5	/* SP then optional STRING */
983#define	ZSTR2	6	/* optional STRING after SP */
984#define	SITECMD	7	/* SITE command */
985#define	NSTR	8	/* Number followed by a string */
986
987struct tab {
988	char	*name;
989	short	token;
990	short	state;
991	short	implemented;	/* 1 if command is implemented */
992	char	*help;
993};
994
995struct tab cmdtab[] = {		/* In order defined in RFC 765 */
996	{ "USER", USER, STR1, 1,	"<sp> username" },
997	{ "PASS", PASS, ZSTR1, 1,	"<sp> password" },
998	{ "ACCT", ACCT, STR1, 0,	"(specify account)" },
999	{ "SMNT", SMNT, ARGS, 0,	"(structure mount)" },
1000	{ "REIN", REIN, ARGS, 0,	"(reinitialize server state)" },
1001	{ "QUIT", QUIT, ARGS, 1,	"(terminate service)", },
1002	{ "PORT", PORT, ARGS, 1,	"<sp> b0, b1, b2, b3, b4" },
1003	{ "LPRT", LPRT, ARGS, 1,	"<sp> af, hal, h1, h2, h3,..., pal, p1, p2..." },
1004	{ "EPRT", EPRT, STR1, 1,	"<sp> |af|addr|port|" },
1005	{ "PASV", PASV, ARGS, 1,	"(set server in passive mode)" },
1006	{ "LPSV", LPSV, ARGS, 1,	"(set server in passive mode)" },
1007	{ "EPSV", EPSV, ARGS, 1,	"[<sp> af|ALL]" },
1008	{ "TYPE", TYPE, ARGS, 1,	"<sp> [ A | E | I | L ]" },
1009	{ "STRU", STRU, ARGS, 1,	"(specify file structure)" },
1010	{ "MODE", MODE, ARGS, 1,	"(specify transfer mode)" },
1011	{ "RETR", RETR, STR1, 1,	"<sp> file-name" },
1012	{ "STOR", STOR, STR1, 1,	"<sp> file-name" },
1013	{ "APPE", APPE, STR1, 1,	"<sp> file-name" },
1014	{ "MLFL", MLFL, OSTR, 0,	"(mail file)" },
1015	{ "MAIL", MAIL, OSTR, 0,	"(mail to user)" },
1016	{ "MSND", MSND, OSTR, 0,	"(mail send to terminal)" },
1017	{ "MSOM", MSOM, OSTR, 0,	"(mail send to terminal or mailbox)" },
1018	{ "MSAM", MSAM, OSTR, 0,	"(mail send to terminal and mailbox)" },
1019	{ "MRSQ", MRSQ, OSTR, 0,	"(mail recipient scheme question)" },
1020	{ "MRCP", MRCP, STR1, 0,	"(mail recipient)" },
1021	{ "ALLO", ALLO, ARGS, 1,	"allocate storage (vacuously)" },
1022	{ "REST", REST, ARGS, 1,	"<sp> offset (restart command)" },
1023	{ "RNFR", RNFR, STR1, 1,	"<sp> file-name" },
1024	{ "RNTO", RNTO, STR1, 1,	"<sp> file-name" },
1025	{ "ABOR", ABOR, ARGS, 1,	"(abort operation)" },
1026	{ "DELE", DELE, STR1, 1,	"<sp> file-name" },
1027	{ "CWD",  CWD,  OSTR, 1,	"[ <sp> directory-name ]" },
1028	{ "XCWD", CWD,	OSTR, 1,	"[ <sp> directory-name ]" },
1029	{ "LIST", LIST, OSTR, 1,	"[ <sp> path-name ]" },
1030	{ "NLST", NLST, OSTR, 1,	"[ <sp> path-name ]" },
1031	{ "SITE", SITE, SITECMD, 1,	"site-cmd [ <sp> arguments ]" },
1032	{ "SYST", SYST, ARGS, 1,	"(get type of operating system)" },
1033	{ "STAT", STAT, OSTR, 1,	"[ <sp> path-name ]" },
1034	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
1035	{ "NOOP", NOOP, ARGS, 1,	"" },
1036	{ "MKD",  MKD,  STR1, 1,	"<sp> path-name" },
1037	{ "XMKD", MKD,  STR1, 1,	"<sp> path-name" },
1038	{ "RMD",  RMD,  STR1, 1,	"<sp> path-name" },
1039	{ "XRMD", RMD,  STR1, 1,	"<sp> path-name" },
1040	{ "PWD",  PWD,  ARGS, 1,	"(return current directory)" },
1041	{ "XPWD", PWD,  ARGS, 1,	"(return current directory)" },
1042	{ "CDUP", CDUP, ARGS, 1,	"(change to parent directory)" },
1043	{ "XCUP", CDUP, ARGS, 1,	"(change to parent directory)" },
1044	{ "STOU", STOU, STR1, 1,	"<sp> file-name" },
1045	{ "SIZE", SIZE, OSTR, 1,	"<sp> path-name" },
1046	{ "MDTM", MDTM, OSTR, 1,	"<sp> path-name" },
1047	{ NULL,   0,    0,    0,	0 }
1048};
1049
1050struct tab sitetab[] = {
1051	{ "UMASK", UMASK, ARGS, 1,	"[ <sp> umask ]" },
1052	{ "IDLE", IDLE, ARGS, 1,	"[ <sp> maximum-idle-time ]" },
1053	{ "CHMOD", CHMOD, NSTR, 1,	"<sp> mode <sp> file-name" },
1054	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
1055	{ NULL,   0,    0,    0,	0 }
1056};
1057
1058static void	 help(struct tab *, char *);
1059static struct tab *
1060		 lookup(struct tab *, char *);
1061static void	 sizecmd(char *);
1062static int	 yylex(void);
1063
1064extern int epsvall;
1065
1066static struct tab *
1067lookup(p, cmd)
1068	struct tab *p;
1069	char *cmd;
1070{
1071
1072	for (; p->name != NULL; p++)
1073		if (strcmp(cmd, p->name) == 0)
1074			return (p);
1075	return (NULL);
1076}
1077
1078#include <arpa/telnet.h>
1079
1080/*
1081 * getline - a hacked up version of fgets to ignore TELNET escape codes.
1082 */
1083char *
1084getline(s, n, iop)
1085	char *s;
1086	int n;
1087	FILE *iop;
1088{
1089	int c;
1090	char *cs;
1091
1092	cs = s;
1093/* tmpline may contain saved command from urgent mode interruption */
1094	for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
1095		*cs++ = tmpline[c];
1096		if (tmpline[c] == '\n') {
1097			*cs++ = '\0';
1098			if (debug)
1099				syslog(LOG_DEBUG, "command: %s", s);
1100			tmpline[0] = '\0';
1101			return(s);
1102		}
1103		if (c == 0)
1104			tmpline[0] = '\0';
1105	}
1106	while ((c = getc(iop)) != EOF) {
1107		c &= 0377;
1108		if (c == IAC) {
1109		    if ((c = getc(iop)) != EOF) {
1110			c &= 0377;
1111			switch (c) {
1112			case WILL:
1113			case WONT:
1114				c = getc(iop);
1115				printf("%c%c%c", IAC, DONT, 0377&c);
1116				(void) fflush(stdout);
1117				continue;
1118			case DO:
1119			case DONT:
1120				c = getc(iop);
1121				printf("%c%c%c", IAC, WONT, 0377&c);
1122				(void) fflush(stdout);
1123				continue;
1124			case IAC:
1125				break;
1126			default:
1127				continue;	/* ignore command */
1128			}
1129		    }
1130		}
1131		*cs++ = c;
1132		if (--n <= 0 || c == '\n')
1133			break;
1134	}
1135	if (c == EOF && cs == s)
1136		return (NULL);
1137	*cs++ = '\0';
1138	if (debug) {
1139		if (!guest && strncasecmp("pass ", s, 5) == 0) {
1140			/* Don't syslog passwords */
1141			syslog(LOG_DEBUG, "command: %.5s ???", s);
1142		} else {
1143			char *cp;
1144			int len;
1145
1146			/* Don't syslog trailing CR-LF */
1147			len = strlen(s);
1148			cp = s + len - 1;
1149			while (cp >= s && (*cp == '\n' || *cp == '\r')) {
1150				--cp;
1151				--len;
1152			}
1153			syslog(LOG_DEBUG, "command: %.*s", len, s);
1154		}
1155	}
1156	return (s);
1157}
1158
1159void
1160toolong(signo)
1161	int signo;
1162{
1163	struct syslog_data sdata = SYSLOG_DATA_INIT;
1164
1165	/* XXX signal races */
1166	reply(421,
1167	    "Timeout (%d seconds): closing control connection.", timeout);
1168	if (logging)
1169		syslog_r(LOG_INFO, &sdata, "User %s timed out after %d seconds",
1170		    (pw ? pw -> pw_name : "unknown"), timeout);
1171	dologout(1);
1172}
1173
1174static int
1175yylex()
1176{
1177	static int cpos;
1178	char *cp, *cp2;
1179	struct tab *p;
1180	int n;
1181	char c;
1182
1183	for (;;) {
1184		switch (state) {
1185
1186		case CMD:
1187			(void) alarm((unsigned) timeout);
1188			if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
1189				reply(221, "You could at least say goodbye.");
1190				dologout(0);
1191			}
1192			(void) alarm(0);
1193			if ((cp = strchr(cbuf, '\r'))) {
1194				*cp++ = '\n';
1195				*cp = '\0';
1196			}
1197#ifdef HASSETPROCTITLE
1198			if (strncasecmp(cbuf, "PASS", 4) != 0) {
1199				if ((cp = strpbrk(cbuf, "\n"))) {
1200					c = *cp;
1201					*cp = '\0';
1202					setproctitle("%s: %s", proctitle, cbuf);
1203					*cp = c;
1204				}
1205			}
1206#endif /* HASSETPROCTITLE */
1207			if ((cp = strpbrk(cbuf, " \n")))
1208				cpos = cp - cbuf;
1209			if (cpos == 0)
1210				cpos = 4;
1211			c = cbuf[cpos];
1212			cbuf[cpos] = '\0';
1213			upper(cbuf);
1214			p = lookup(cmdtab, cbuf);
1215			cbuf[cpos] = c;
1216			if (p != NULL) {
1217				if (p->implemented == 0) {
1218					nack(p->name);
1219					return (LEXERR);
1220				}
1221				state = p->state;
1222				yylval.s = p->name;
1223				return (p->token);
1224			}
1225			break;
1226
1227		case SITECMD:
1228			if (cbuf[cpos] == ' ') {
1229				cpos++;
1230				return (SP);
1231			}
1232			cp = &cbuf[cpos];
1233			if ((cp2 = strpbrk(cp, " \n")))
1234				cpos = cp2 - cbuf;
1235			c = cbuf[cpos];
1236			cbuf[cpos] = '\0';
1237			upper(cp);
1238			p = lookup(sitetab, cp);
1239			cbuf[cpos] = c;
1240			if (p != NULL) {
1241				if (p->implemented == 0) {
1242					state = CMD;
1243					nack(p->name);
1244					return (LEXERR);
1245				}
1246				state = p->state;
1247				yylval.s = p->name;
1248				return (p->token);
1249			}
1250			state = CMD;
1251			break;
1252
1253		case OSTR:
1254			if (cbuf[cpos] == '\n') {
1255				state = CMD;
1256				return (CRLF);
1257			}
1258			/* FALLTHROUGH */
1259
1260		case STR1:
1261		case ZSTR1:
1262		dostr1:
1263			if (cbuf[cpos] == ' ') {
1264				cpos++;
1265				state = state == OSTR ? STR2 : state+1;
1266				return (SP);
1267			}
1268			break;
1269
1270		case ZSTR2:
1271			if (cbuf[cpos] == '\n') {
1272				state = CMD;
1273				return (CRLF);
1274			}
1275			/* FALLTHROUGH */
1276
1277		case STR2:
1278			cp = &cbuf[cpos];
1279			n = strlen(cp);
1280			cpos += n - 1;
1281			/*
1282			 * Make sure the string is nonempty and \n terminated.
1283			 */
1284			if (n > 1 && cbuf[cpos] == '\n') {
1285				cbuf[cpos] = '\0';
1286				yylval.s = strdup(cp);
1287				if (yylval.s == NULL)
1288					fatal("Ran out of memory.");
1289				cbuf[cpos] = '\n';
1290				state = ARGS;
1291				return (STRING);
1292			}
1293			break;
1294
1295		case NSTR:
1296			if (cbuf[cpos] == ' ') {
1297				cpos++;
1298				return (SP);
1299			}
1300			if (isdigit(cbuf[cpos])) {
1301				cp = &cbuf[cpos];
1302				while (isdigit(cbuf[++cpos]))
1303					;
1304				c = cbuf[cpos];
1305				cbuf[cpos] = '\0';
1306				yylval.i = atoi(cp);
1307				cbuf[cpos] = c;
1308				state = STR1;
1309				return (NUMBER);
1310			}
1311			state = STR1;
1312			goto dostr1;
1313
1314		case ARGS:
1315			if (isdigit(cbuf[cpos])) {
1316				cp = &cbuf[cpos];
1317				while (isdigit(cbuf[++cpos]))
1318					;
1319				c = cbuf[cpos];
1320				cbuf[cpos] = '\0';
1321				yylval.i = atoi(cp);
1322				cbuf[cpos] = c;
1323				return (NUMBER);
1324			}
1325			if (strncasecmp(&cbuf[cpos], "ALL", 3) == 0
1326			 && !isalnum(cbuf[cpos + 3])) {
1327				cpos += 3;
1328				return ALL;
1329			}
1330			switch (cbuf[cpos++]) {
1331
1332			case '\n':
1333				state = CMD;
1334				return (CRLF);
1335
1336			case ' ':
1337				return (SP);
1338
1339			case ',':
1340				return (COMMA);
1341
1342			case 'A':
1343			case 'a':
1344				return (A);
1345
1346			case 'B':
1347			case 'b':
1348				return (B);
1349
1350			case 'C':
1351			case 'c':
1352				return (C);
1353
1354			case 'E':
1355			case 'e':
1356				return (E);
1357
1358			case 'F':
1359			case 'f':
1360				return (F);
1361
1362			case 'I':
1363			case 'i':
1364				return (I);
1365
1366			case 'L':
1367			case 'l':
1368				return (L);
1369
1370			case 'N':
1371			case 'n':
1372				return (N);
1373
1374			case 'P':
1375			case 'p':
1376				return (P);
1377
1378			case 'R':
1379			case 'r':
1380				return (R);
1381
1382			case 'S':
1383			case 's':
1384				return (S);
1385
1386			case 'T':
1387			case 't':
1388				return (T);
1389
1390			}
1391			break;
1392
1393		default:
1394			fatal("Unknown state in scanner.");
1395		}
1396		state = CMD;
1397		return (LEXERR);
1398	}
1399}
1400
1401void
1402upper(s)
1403	char *s;
1404{
1405	char *p;
1406
1407	for (p = s; *p; p++) {
1408		if (islower(*p))
1409			*p = toupper(*p);
1410	}
1411}
1412
1413static void
1414help(ctab, s)
1415	struct tab *ctab;
1416	char *s;
1417{
1418	struct tab *c;
1419	int width, NCMDS;
1420	char *type;
1421
1422	if (ctab == sitetab)
1423		type = "SITE ";
1424	else
1425		type = "";
1426	width = 0, NCMDS = 0;
1427	for (c = ctab; c->name != NULL; c++) {
1428		int len = strlen(c->name);
1429
1430		if (len > width)
1431			width = len;
1432		NCMDS++;
1433	}
1434	width = (width + 8) &~ 7;
1435	if (s == NULL) {
1436		int i, j, w;
1437		int columns, lines;
1438
1439		lreply(214, "The following %scommands are recognized %s.",
1440		    type, "(* =>'s unimplemented)");
1441		columns = 76 / width;
1442		if (columns == 0)
1443			columns = 1;
1444		lines = (NCMDS + columns - 1) / columns;
1445		for (i = 0; i < lines; i++) {
1446			printf("   ");
1447			for (j = 0; j < columns; j++) {
1448				c = ctab + j * lines + i;
1449				printf("%s%c", c->name,
1450					c->implemented ? ' ' : '*');
1451				if (c + lines >= &ctab[NCMDS])
1452					break;
1453				w = strlen(c->name) + 1;
1454				while (w < width) {
1455					putchar(' ');
1456					w++;
1457				}
1458			}
1459			printf("\r\n");
1460		}
1461		(void) fflush(stdout);
1462		reply(214, "Direct comments to ftp-bugs@%s.", hostname);
1463		return;
1464	}
1465	upper(s);
1466	c = lookup(ctab, s);
1467	if (c == NULL) {
1468		reply(502, "Unknown command %s.", s);
1469		return;
1470	}
1471	if (c->implemented)
1472		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
1473	else
1474		reply(214, "%s%-*s\t%s; unimplemented.", type, width,
1475		    c->name, c->help);
1476}
1477
1478static void
1479sizecmd(filename)
1480	char *filename;
1481{
1482	switch (type) {
1483	case TYPE_L:
1484	case TYPE_I: {
1485		struct stat stbuf;
1486		if (stat(filename, &stbuf) < 0 || !S_ISREG(stbuf.st_mode))
1487			reply(550, "%s: not a plain file.", filename);
1488		else
1489			reply(213, "%qu", stbuf.st_size);
1490		break; }
1491	case TYPE_A: {
1492		FILE *fin;
1493		int c;
1494		off_t count;
1495		struct stat stbuf;
1496		fin = fopen(filename, "r");
1497		if (fin == NULL) {
1498			perror_reply(550, filename);
1499			return;
1500		}
1501		if (fstat(fileno(fin), &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) {
1502			reply(550, "%s: not a plain file.", filename);
1503			(void) fclose(fin);
1504			return;
1505		}
1506		if (stbuf.st_size > 10240) {
1507			reply(550, "%s: file too large for SIZE.", filename);
1508			(void) fclose(fin);
1509			return;
1510		}
1511
1512		count = 0;
1513		while((c = getc(fin)) != EOF) {
1514			if (c == '\n')	/* will get expanded to \r\n */
1515				count++;
1516			count++;
1517		}
1518		(void) fclose(fin);
1519
1520		reply(213, "%qd", count);
1521		break; }
1522	default:
1523		reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
1524	}
1525}
1526