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