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