ftpcmd.y revision 1.24
1/*	$OpenBSD: ftpcmd.y,v 1.24 2000/11/14 20:27:01 itojun 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.24 2000/11/14 20:27:01 itojun 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 <setjmp.h>
66#include <signal.h>
67#include <tzfile.h>
68#include <stdio.h>
69#include <stdlib.h>
70#include <string.h>
71#include <syslog.h>
72#include <time.h>
73#include <unistd.h>
74#include <netdb.h>
75
76#include "extern.h"
77
78extern	union sockunion data_dest;
79extern	int logged_in;
80extern	struct passwd *pw;
81extern	int guest;
82extern	int logging;
83extern	int type;
84extern	int form;
85extern	int debug;
86extern	int timeout;
87extern	int maxtimeout;
88extern  int pdata;
89extern	char hostname[], remotehost[];
90extern	char proctitle[];
91extern	int usedefault;
92extern  int transflag;
93extern  char tmpline[];
94extern	int portcheck;
95extern	union sockunion his_addr;
96extern	int umaskchange;
97
98off_t	restart_point;
99
100static	int cmd_type;
101static	int cmd_form;
102static	int cmd_bytesz;
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		}
241
242	| PASV check_login_epsvall CRLF
243		{
244			if ($2)
245				passive();
246		}
247	| LPSV check_login_epsvall CRLF
248		{
249			if ($2)
250				long_passive("LPSV", PF_UNSPEC);
251		}
252	| EPSV check_login SP NUMBER CRLF
253		{
254			if ($2)
255				long_passive("EPSV", epsvproto2af($4));
256		}
257	| EPSV check_login SP ALL CRLF
258		{
259			if ($2) {
260				reply(200, "EPSV ALL command successful.");
261				epsvall++;
262			}
263		}
264	| EPSV check_login CRLF
265		{
266			if ($2)
267				long_passive("EPSV", PF_UNSPEC);
268		}
269	| TYPE check_login SP type_code CRLF
270		{
271			if ($2) {
272				switch (cmd_type) {
273
274				case TYPE_A:
275					if (cmd_form == FORM_N) {
276						reply(200, "Type set to A.");
277						type = cmd_type;
278						form = cmd_form;
279					} else
280						reply(504, "Form must be N.");
281					break;
282
283				case TYPE_E:
284					reply(504, "Type E not implemented.");
285					break;
286
287				case TYPE_I:
288					reply(200, "Type set to I.");
289					type = cmd_type;
290					break;
291
292				case TYPE_L:
293					if (cmd_bytesz == 8) {
294					       reply(200,
295					       "Type set to L (byte size 8).");
296					       type = cmd_type;
297					} else
298					    reply(504, "Byte size must be 8.");
299
300				}
301			}
302		}
303	| STRU check_login SP struct_code CRLF
304		{
305			if ($2) {
306				switch ($4) {
307
308				case STRU_F:
309					reply(200, "STRU F ok.");
310					break;
311
312				default:
313					reply(504, "Unimplemented STRU type.");
314				}
315			}
316		}
317	| MODE check_login SP mode_code CRLF
318		{
319			if ($2) {
320				switch ($4) {
321
322				case MODE_S:
323					reply(200, "MODE S ok.");
324					break;
325
326				default:
327					reply(502, "Unimplemented MODE type.");
328				}
329			}
330		}
331	| ALLO check_login SP NUMBER CRLF
332		{
333			if ($2) {
334				reply(202, "ALLO command ignored.");
335			}
336		}
337	| ALLO check_login SP NUMBER SP R SP NUMBER CRLF
338		{
339			if ($2) {
340				reply(202, "ALLO command ignored.");
341			}
342		}
343	| RETR check_login SP pathname CRLF
344		{
345			if ($2 && $4 != NULL)
346				retrieve(NULL, $4);
347			if ($4 != NULL)
348				free($4);
349		}
350	| STOR check_login SP pathname CRLF
351		{
352			if ($2 && $4 != NULL)
353				store($4, "w", 0);
354			if ($4 != NULL)
355				free($4);
356		}
357	| APPE check_login SP pathname CRLF
358		{
359			if ($2 && $4 != NULL)
360				store($4, "a", 0);
361			if ($4 != NULL)
362				free($4);
363		}
364	| NLST check_login CRLF
365		{
366			if ($2)
367				send_file_list(".");
368		}
369	| NLST check_login SP STRING CRLF
370		{
371			if ($2 && $4 != NULL)
372				send_file_list($4);
373			if ($4 != NULL)
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
457			if ($3 != NULL)
458				free ($3);
459		}
460	| NOOP CRLF
461		{
462			reply(200, "NOOP command successful.");
463		}
464	| MKD check_login SP pathname CRLF
465		{
466			if ($2 && $4 != NULL)
467				makedir($4);
468			if ($4 != NULL)
469				free($4);
470		}
471	| RMD check_login SP pathname CRLF
472		{
473			if ($2 && $4 != NULL)
474				removedir($4);
475			if ($4 != NULL)
476				free($4);
477		}
478	| PWD check_login CRLF
479		{
480			if ($2)
481				pwd();
482		}
483	| CDUP check_login CRLF
484		{
485			if ($2)
486				cwd("..");
487		}
488	| SITE SP HELP CRLF
489		{
490			help(sitetab, NULL);
491		}
492	| SITE SP HELP SP STRING CRLF
493		{
494			help(sitetab, $5);
495
496			if ($5 != NULL)
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 > 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 CRLF
645		{
646			yyerrok;
647		}
648	;
649rcmd
650	: RNFR check_login SP pathname CRLF
651		{
652			char *renamefrom();
653
654			restart_point = (off_t) 0;
655			if ($2 && $4) {
656				fromname = renamefrom($4);
657				if (fromname == NULL && $4) {
658					free($4);
659				}
660			} else {
661				if ($4)
662					free ($4);
663			}
664		}
665
666	| REST check_login SP byte_size CRLF
667		{
668			if ($2) {
669			    fromname = NULL;
670			    restart_point = $4;	/* XXX $4 is only "int" */
671			    reply(350, "Restarting at %qd. %s", restart_point,
672			       "Send STORE or RETRIEVE to initiate transfer.");
673			}
674		}
675	;
676
677username
678	: STRING
679	;
680
681password
682	: /* empty */
683		{
684			$$ = (char *)calloc(1, sizeof(char));
685		}
686	| STRING
687	;
688
689byte_size
690	: NUMBER
691	;
692
693host_port
694	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
695		NUMBER COMMA NUMBER
696		{
697			char *a, *p;
698
699			if ($1 < 0 || $1 > 255 || $3 < 0 || $3 > 255 ||
700			    $5 < 0 || $5 > 255 || $7 < 0 || $7 > 255 ||
701			    $9 < 0 || $9 > 255 || $11 < 0 || $11 > 255) {
702				$$ = 1;
703			} else {
704				data_dest.su_sin.sin_len = sizeof(struct sockaddr_in);
705				data_dest.su_sin.sin_family = AF_INET;
706				p = (char *)&data_dest.su_sin.sin_port;
707				p[0] = $9; p[1] = $11;
708				a = (char *)&data_dest.su_sin.sin_addr;
709				a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7;
710				$$ = 0;
711			}
712		}
713	;
714
715host_long_port4
716	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
717		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
718		NUMBER
719		{
720			char *a, *p;
721
722			/* reject invalid LPRT command */
723			if ($1 != 4 || $3 != 4
724			 || $5 < 0 || $5 > 255 || $7 < 0 || $7 > 255
725			 || $9 < 0 || $9 > 255 || $11 < 0 || $11 > 255
726			 || $13 != 2
727			 || $15 < 0 || $15 > 255 || $17 < 0 || $17 > 255) {
728				$$ = 1;
729			} else {
730				data_dest.su_sin.sin_len =
731					sizeof(struct sockaddr_in);
732				data_dest.su_family = AF_INET;
733				p = (char *)&data_dest.su_port;
734				p[0] = $15; p[1] = $17;
735				a = (char *)&data_dest.su_sin.sin_addr;
736				a[0] = $5; a[1] = $7; a[2] = $9; a[3] = $11;
737				$$ = 0;
738			}
739		}
740	;
741
742host_long_port6
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 COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
747		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
748		NUMBER
749		{
750			char *a, *p;
751
752			/* reject invalid LPRT command */
753			if ($1 != 6 || $3 != 16
754			 || $5 < 0 || $5 > 255 || $7 < 0 || $7 > 255
755			 || $9 < 0 || $9 > 255 || $11 < 0 || $11 > 255
756			 || $13 < 0 || $13 > 255 || $15 < 0 || $15 > 255
757			 || $17 < 0 || $17 > 255 || $19 < 0 || $19 > 255
758			 || $21 < 0 || $21 > 255 || $23 < 0 || $23 > 255
759			 || $25 < 0 || $25 > 255 || $27 < 0 || $27 > 255
760			 || $29 < 0 || $29 > 255 || $31 < 0 || $31 > 255
761			 || $33 < 0 || $33 > 255 || $35 < 0 || $35 > 255
762			 || $37 != 2
763			 || $39 < 0 || $39 > 255 || $41 < 0 || $41 > 255) {
764				$$ = 1;
765			} else {
766				data_dest.su_sin6.sin6_len =
767					sizeof(struct sockaddr_in6);
768				data_dest.su_family = AF_INET6;
769				p = (char *)&data_dest.su_port;
770				p[0] = $39; p[1] = $41;
771				a = (char *)&data_dest.su_sin6.sin6_addr;
772				 a[0] =  $5;  a[1] =  $7;
773				 a[2] =  $9;  a[3] = $11;
774				 a[4] = $13;  a[5] = $15;
775				 a[6] = $17;  a[7] = $19;
776				 a[8] = $21;  a[9] = $23;
777				a[10] = $25; a[11] = $27;
778				a[12] = $29; a[13] = $31;
779				a[14] = $33; a[15] = $35;
780				if (his_addr.su_family == AF_INET6) {
781					/* XXX more sanity checks! */
782					data_dest.su_sin6.sin6_scope_id =
783					    his_addr.su_sin6.sin6_scope_id;
784				}
785
786				$$ = 0;
787			}
788		}
789	;
790
791form_code
792	: N
793		{
794			$$ = FORM_N;
795		}
796	| T
797		{
798			$$ = FORM_T;
799		}
800	| C
801		{
802			$$ = FORM_C;
803		}
804	;
805
806type_code
807	: A
808		{
809			cmd_type = TYPE_A;
810			cmd_form = FORM_N;
811		}
812	| A SP form_code
813		{
814			cmd_type = TYPE_A;
815			cmd_form = $3;
816		}
817	| E
818		{
819			cmd_type = TYPE_E;
820			cmd_form = FORM_N;
821		}
822	| E SP form_code
823		{
824			cmd_type = TYPE_E;
825			cmd_form = $3;
826		}
827	| I
828		{
829			cmd_type = TYPE_I;
830		}
831	| L
832		{
833			cmd_type = TYPE_L;
834			cmd_bytesz = NBBY;
835		}
836	| L SP byte_size
837		{
838			cmd_type = TYPE_L;
839			cmd_bytesz = $3;
840		}
841		/* this is for a bug in the BBN ftp */
842	| L byte_size
843		{
844			cmd_type = TYPE_L;
845			cmd_bytesz = $2;
846		}
847	;
848
849struct_code
850	: F
851		{
852			$$ = STRU_F;
853		}
854	| R
855		{
856			$$ = STRU_R;
857		}
858	| P
859		{
860			$$ = STRU_P;
861		}
862	;
863
864mode_code
865	: S
866		{
867			$$ = MODE_S;
868		}
869	| B
870		{
871			$$ = MODE_B;
872		}
873	| C
874		{
875			$$ = MODE_C;
876		}
877	;
878
879pathname
880	: pathstring
881		{
882			/*
883			 * Problem: this production is used for all pathname
884			 * processing, but only gives a 550 error reply.
885			 * This is a valid reply in some cases but not in others.
886			 */
887			if (logged_in && $1 && strchr($1, '~') != NULL) {
888				glob_t gl;
889				int flags =
890				 GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE;
891				char *pptr = $1;
892
893				/*
894				 * glob() will only find a leading ~, but
895				 * Netscape kindly puts a slash in front of
896				 * it for publish URLs.  There needs to be
897				 * a flag for glob() that expands tildes
898				 * anywhere in the string.
899				 */
900				if ((pptr[0] == '/') && (pptr[1] == '~'))
901					pptr++;
902
903				memset(&gl, 0, sizeof(gl));
904				if (glob(pptr, flags, NULL, &gl) ||
905				    gl.gl_pathc == 0) {
906					reply(550, "not found");
907					$$ = NULL;
908				} else {
909					$$ = strdup(gl.gl_pathv[0]);
910				}
911				globfree(&gl);
912				free($1);
913			} else
914				$$ = $1;
915		}
916	;
917
918pathstring
919	: STRING
920	;
921
922octal_number
923	: NUMBER
924		{
925			int ret, dec, multby, digit;
926
927			/*
928			 * Convert a number that was read as decimal number
929			 * to what it would be if it had been read as octal.
930			 */
931			dec = $1;
932			multby = 1;
933			ret = 0;
934			while (dec) {
935				digit = dec%10;
936				if (digit > 7) {
937					ret = -1;
938					break;
939				}
940				ret += digit * multby;
941				multby *= 8;
942				dec /= 10;
943			}
944			$$ = ret;
945		}
946	;
947
948
949check_login
950	: /* empty */
951		{
952			if (logged_in)
953				$$ = 1;
954			else {
955				reply(530, "Please login with USER and PASS.");
956				$$ = 0;
957			}
958		}
959	;
960
961check_login_epsvall
962	: /* empty */
963		{
964			if (!logged_in) {
965				reply(530, "Please login with USER and PASS.");
966				$$ = 0;
967			} else if (epsvall) {
968				reply(501, "the command is disallowed "
969				    "after EPSV ALL");
970				usedefault = 1;
971				$$ = 0;
972			} else
973				$$ = 1;
974		}
975	;
976
977%%
978
979extern jmp_buf errcatch;
980
981#define	CMD	0	/* beginning of command */
982#define	ARGS	1	/* expect miscellaneous arguments */
983#define	STR1	2	/* expect SP followed by STRING */
984#define	STR2	3	/* expect STRING */
985#define	OSTR	4	/* optional SP then STRING */
986#define	ZSTR1	5	/* SP then optional STRING */
987#define	ZSTR2	6	/* optional STRING after SP */
988#define	SITECMD	7	/* SITE command */
989#define	NSTR	8	/* Number followed by a string */
990
991struct tab {
992	char	*name;
993	short	token;
994	short	state;
995	short	implemented;	/* 1 if command is implemented */
996	char	*help;
997};
998
999struct tab cmdtab[] = {		/* In order defined in RFC 765 */
1000	{ "USER", USER, STR1, 1,	"<sp> username" },
1001	{ "PASS", PASS, ZSTR1, 1,	"<sp> password" },
1002	{ "ACCT", ACCT, STR1, 0,	"(specify account)" },
1003	{ "SMNT", SMNT, ARGS, 0,	"(structure mount)" },
1004	{ "REIN", REIN, ARGS, 0,	"(reinitialize server state)" },
1005	{ "QUIT", QUIT, ARGS, 1,	"(terminate service)", },
1006	{ "PORT", PORT, ARGS, 1,	"<sp> b0, b1, b2, b3, b4" },
1007	{ "LPRT", LPRT, ARGS, 1,	"<sp> af, hal, h1, h2, h3,..., pal, p1, p2..." },
1008	{ "EPRT", EPRT, STR1, 1,	"<sp> |af|addr|port|" },
1009	{ "PASV", PASV, ARGS, 1,	"(set server in passive mode)" },
1010	{ "LPSV", LPSV, ARGS, 1,	"(set server in passive mode)" },
1011	{ "EPSV", EPSV, ARGS, 1,	"[<sp> af|ALL]" },
1012	{ "TYPE", TYPE, ARGS, 1,	"<sp> [ A | E | I | L ]" },
1013	{ "STRU", STRU, ARGS, 1,	"(specify file structure)" },
1014	{ "MODE", MODE, ARGS, 1,	"(specify transfer mode)" },
1015	{ "RETR", RETR, STR1, 1,	"<sp> file-name" },
1016	{ "STOR", STOR, STR1, 1,	"<sp> file-name" },
1017	{ "APPE", APPE, STR1, 1,	"<sp> file-name" },
1018	{ "MLFL", MLFL, OSTR, 0,	"(mail file)" },
1019	{ "MAIL", MAIL, OSTR, 0,	"(mail to user)" },
1020	{ "MSND", MSND, OSTR, 0,	"(mail send to terminal)" },
1021	{ "MSOM", MSOM, OSTR, 0,	"(mail send to terminal or mailbox)" },
1022	{ "MSAM", MSAM, OSTR, 0,	"(mail send to terminal and mailbox)" },
1023	{ "MRSQ", MRSQ, OSTR, 0,	"(mail recipient scheme question)" },
1024	{ "MRCP", MRCP, STR1, 0,	"(mail recipient)" },
1025	{ "ALLO", ALLO, ARGS, 1,	"allocate storage (vacuously)" },
1026	{ "REST", REST, ARGS, 1,	"<sp> offset (restart command)" },
1027	{ "RNFR", RNFR, STR1, 1,	"<sp> file-name" },
1028	{ "RNTO", RNTO, STR1, 1,	"<sp> file-name" },
1029	{ "ABOR", ABOR, ARGS, 1,	"(abort operation)" },
1030	{ "DELE", DELE, STR1, 1,	"<sp> file-name" },
1031	{ "CWD",  CWD,  OSTR, 1,	"[ <sp> directory-name ]" },
1032	{ "XCWD", CWD,	OSTR, 1,	"[ <sp> directory-name ]" },
1033	{ "LIST", LIST, OSTR, 1,	"[ <sp> path-name ]" },
1034	{ "NLST", NLST, OSTR, 1,	"[ <sp> path-name ]" },
1035	{ "SITE", SITE, SITECMD, 1,	"site-cmd [ <sp> arguments ]" },
1036	{ "SYST", SYST, ARGS, 1,	"(get type of operating system)" },
1037	{ "STAT", STAT, OSTR, 1,	"[ <sp> path-name ]" },
1038	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
1039	{ "NOOP", NOOP, ARGS, 1,	"" },
1040	{ "MKD",  MKD,  STR1, 1,	"<sp> path-name" },
1041	{ "XMKD", MKD,  STR1, 1,	"<sp> path-name" },
1042	{ "RMD",  RMD,  STR1, 1,	"<sp> path-name" },
1043	{ "XRMD", RMD,  STR1, 1,	"<sp> path-name" },
1044	{ "PWD",  PWD,  ARGS, 1,	"(return current directory)" },
1045	{ "XPWD", PWD,  ARGS, 1,	"(return current directory)" },
1046	{ "CDUP", CDUP, ARGS, 1,	"(change to parent directory)" },
1047	{ "XCUP", CDUP, ARGS, 1,	"(change to parent directory)" },
1048	{ "STOU", STOU, STR1, 1,	"<sp> file-name" },
1049	{ "SIZE", SIZE, OSTR, 1,	"<sp> path-name" },
1050	{ "MDTM", MDTM, OSTR, 1,	"<sp> path-name" },
1051	{ NULL,   0,    0,    0,	0 }
1052};
1053
1054struct tab sitetab[] = {
1055	{ "UMASK", UMASK, ARGS, 1,	"[ <sp> umask ]" },
1056	{ "IDLE", IDLE, ARGS, 1,	"[ <sp> maximum-idle-time ]" },
1057	{ "CHMOD", CHMOD, NSTR, 1,	"<sp> mode <sp> file-name" },
1058	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
1059	{ NULL,   0,    0,    0,	0 }
1060};
1061
1062static void	 help __P((struct tab *, char *));
1063static struct tab *
1064		 lookup __P((struct tab *, char *));
1065static void	 sizecmd __P((char *));
1066static int	 yylex __P((void));
1067
1068extern int epsvall;
1069
1070static struct tab *
1071lookup(p, cmd)
1072	struct tab *p;
1073	char *cmd;
1074{
1075
1076	for (; p->name != NULL; p++)
1077		if (strcmp(cmd, p->name) == 0)
1078			return (p);
1079	return (0);
1080}
1081
1082#include <arpa/telnet.h>
1083
1084/*
1085 * getline - a hacked up version of fgets to ignore TELNET escape codes.
1086 */
1087char *
1088getline(s, n, iop)
1089	char *s;
1090	int n;
1091	FILE *iop;
1092{
1093	int c;
1094	register char *cs;
1095
1096	cs = s;
1097/* tmpline may contain saved command from urgent mode interruption */
1098	for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
1099		*cs++ = tmpline[c];
1100		if (tmpline[c] == '\n') {
1101			*cs++ = '\0';
1102			if (debug)
1103				syslog(LOG_DEBUG, "command: %s", s);
1104			tmpline[0] = '\0';
1105			return(s);
1106		}
1107		if (c == 0)
1108			tmpline[0] = '\0';
1109	}
1110	while ((c = getc(iop)) != EOF) {
1111		c &= 0377;
1112		if (c == IAC) {
1113		    if ((c = getc(iop)) != EOF) {
1114			c &= 0377;
1115			switch (c) {
1116			case WILL:
1117			case WONT:
1118				c = getc(iop);
1119				printf("%c%c%c", IAC, DONT, 0377&c);
1120				(void) fflush(stdout);
1121				continue;
1122			case DO:
1123			case DONT:
1124				c = getc(iop);
1125				printf("%c%c%c", IAC, WONT, 0377&c);
1126				(void) fflush(stdout);
1127				continue;
1128			case IAC:
1129				break;
1130			default:
1131				continue;	/* ignore command */
1132			}
1133		    }
1134		}
1135		*cs++ = c;
1136		if (--n <= 0 || c == '\n')
1137			break;
1138	}
1139	if (c == EOF && cs == s)
1140		return (NULL);
1141	*cs++ = '\0';
1142	if (debug) {
1143		if (!guest && strncasecmp("pass ", s, 5) == 0) {
1144			/* Don't syslog passwords */
1145			syslog(LOG_DEBUG, "command: %.5s ???", s);
1146		} else {
1147			register char *cp;
1148			register int len;
1149
1150			/* Don't syslog trailing CR-LF */
1151			len = strlen(s);
1152			cp = s + len - 1;
1153			while (cp >= s && (*cp == '\n' || *cp == '\r')) {
1154				--cp;
1155				--len;
1156			}
1157			syslog(LOG_DEBUG, "command: %.*s", len, s);
1158		}
1159	}
1160	return (s);
1161}
1162
1163void
1164toolong(signo)
1165	int signo;
1166{
1167
1168	reply(421,
1169	    "Timeout (%d seconds): closing control connection.", timeout);
1170	if (logging)
1171		syslog(LOG_INFO, "User %s timed out after %d seconds",
1172		    (pw ? pw -> pw_name : "unknown"), timeout);
1173	dologout(1);
1174}
1175
1176static int
1177yylex()
1178{
1179	static int cpos, state;
1180	char *cp, *cp2;
1181	struct tab *p;
1182	int n;
1183	char c;
1184
1185	for (;;) {
1186		switch (state) {
1187
1188		case CMD:
1189			(void) signal(SIGALRM, toolong);
1190			(void) alarm((unsigned) timeout);
1191			if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
1192				reply(221, "You could at least say goodbye.");
1193				dologout(0);
1194			}
1195			(void) alarm(0);
1196			if ((cp = strchr(cbuf, '\r'))) {
1197				*cp++ = '\n';
1198				*cp = '\0';
1199			}
1200#ifdef HASSETPROCTITLE
1201			if (strncasecmp(cbuf, "PASS", 4) != 0) {
1202				if ((cp = strpbrk(cbuf, "\n"))) {
1203					c = *cp;
1204					*cp = '\0';
1205					setproctitle("%s: %s", proctitle, cbuf);
1206					*cp = c;
1207				}
1208			}
1209#endif /* HASSETPROCTITLE */
1210			if ((cp = strpbrk(cbuf, " \n")))
1211				cpos = cp - cbuf;
1212			if (cpos == 0)
1213				cpos = 4;
1214			c = cbuf[cpos];
1215			cbuf[cpos] = '\0';
1216			upper(cbuf);
1217			p = lookup(cmdtab, cbuf);
1218			cbuf[cpos] = c;
1219			if (p != 0) {
1220				if (p->implemented == 0) {
1221					nack(p->name);
1222					longjmp(errcatch,0);
1223					/* NOTREACHED */
1224				}
1225				state = p->state;
1226				yylval.s = p->name;
1227				return (p->token);
1228			}
1229			break;
1230
1231		case SITECMD:
1232			if (cbuf[cpos] == ' ') {
1233				cpos++;
1234				return (SP);
1235			}
1236			cp = &cbuf[cpos];
1237			if ((cp2 = strpbrk(cp, " \n")))
1238				cpos = cp2 - cbuf;
1239			c = cbuf[cpos];
1240			cbuf[cpos] = '\0';
1241			upper(cp);
1242			p = lookup(sitetab, cp);
1243			cbuf[cpos] = c;
1244			if (p != 0) {
1245				if (p->implemented == 0) {
1246					state = CMD;
1247					nack(p->name);
1248					longjmp(errcatch,0);
1249					/* NOTREACHED */
1250				}
1251				state = p->state;
1252				yylval.s = p->name;
1253				return (p->token);
1254			}
1255			state = CMD;
1256			break;
1257
1258		case OSTR:
1259			if (cbuf[cpos] == '\n') {
1260				state = CMD;
1261				return (CRLF);
1262			}
1263			/* FALLTHROUGH */
1264
1265		case STR1:
1266		case ZSTR1:
1267		dostr1:
1268			if (cbuf[cpos] == ' ') {
1269				cpos++;
1270				state = state == OSTR ? STR2 : state+1;
1271				return (SP);
1272			}
1273			break;
1274
1275		case ZSTR2:
1276			if (cbuf[cpos] == '\n') {
1277				state = CMD;
1278				return (CRLF);
1279			}
1280			/* FALLTHROUGH */
1281
1282		case STR2:
1283			cp = &cbuf[cpos];
1284			n = strlen(cp);
1285			cpos += n - 1;
1286			/*
1287			 * Make sure the string is nonempty and \n terminated.
1288			 */
1289			if (n > 1 && cbuf[cpos] == '\n') {
1290				cbuf[cpos] = '\0';
1291				yylval.s = strdup(cp);
1292				if (yylval.s == NULL)
1293					fatal("Ran out of memory.");
1294				cbuf[cpos] = '\n';
1295				state = ARGS;
1296				return (STRING);
1297			}
1298			break;
1299
1300		case NSTR:
1301			if (cbuf[cpos] == ' ') {
1302				cpos++;
1303				return (SP);
1304			}
1305			if (isdigit(cbuf[cpos])) {
1306				cp = &cbuf[cpos];
1307				while (isdigit(cbuf[++cpos]))
1308					;
1309				c = cbuf[cpos];
1310				cbuf[cpos] = '\0';
1311				yylval.i = atoi(cp);
1312				cbuf[cpos] = c;
1313				state = STR1;
1314				return (NUMBER);
1315			}
1316			state = STR1;
1317			goto dostr1;
1318
1319		case ARGS:
1320			if (isdigit(cbuf[cpos])) {
1321				cp = &cbuf[cpos];
1322				while (isdigit(cbuf[++cpos]))
1323					;
1324				c = cbuf[cpos];
1325				cbuf[cpos] = '\0';
1326				yylval.i = atoi(cp);
1327				cbuf[cpos] = c;
1328				return (NUMBER);
1329			}
1330			if (strncasecmp(&cbuf[cpos], "ALL", 3) == 0
1331			 && !isalnum(cbuf[cpos + 3])) {
1332				yylval.s = strdup("ALL");
1333				cpos += 3;
1334				return ALL;
1335			}
1336			switch (cbuf[cpos++]) {
1337
1338			case '\n':
1339				state = CMD;
1340				return (CRLF);
1341
1342			case ' ':
1343				return (SP);
1344
1345			case ',':
1346				return (COMMA);
1347
1348			case 'A':
1349			case 'a':
1350				return (A);
1351
1352			case 'B':
1353			case 'b':
1354				return (B);
1355
1356			case 'C':
1357			case 'c':
1358				return (C);
1359
1360			case 'E':
1361			case 'e':
1362				return (E);
1363
1364			case 'F':
1365			case 'f':
1366				return (F);
1367
1368			case 'I':
1369			case 'i':
1370				return (I);
1371
1372			case 'L':
1373			case 'l':
1374				return (L);
1375
1376			case 'N':
1377			case 'n':
1378				return (N);
1379
1380			case 'P':
1381			case 'p':
1382				return (P);
1383
1384			case 'R':
1385			case 'r':
1386				return (R);
1387
1388			case 'S':
1389			case 's':
1390				return (S);
1391
1392			case 'T':
1393			case 't':
1394				return (T);
1395
1396			}
1397			break;
1398
1399		default:
1400			fatal("Unknown state in scanner.");
1401		}
1402		yyerror((char *) 0);
1403		state = CMD;
1404		longjmp(errcatch,0);
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 == (struct tab *)0) {
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
1513		count = 0;
1514		while((c=getc(fin)) != EOF) {
1515			if (c == '\n')	/* will get expanded to \r\n */
1516				count++;
1517			count++;
1518		}
1519		(void) fclose(fin);
1520
1521		reply(213, "%qd", count);
1522		break; }
1523	default:
1524		reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
1525	}
1526}
1527