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