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