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