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