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