1/*	$OpenBSD: ftpcmd.y,v 1.75 2024/04/28 16:42:53 florian 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					if (t == NULL) {
617						/* invalid time, use epoch */
618						stbuf.st_mtime = 0;
619						t = gmtime(&stbuf.st_mtime);
620					}
621					reply(213,
622					    "%04d%02d%02d%02d%02d%02d",
623					    1900 + t->tm_year,
624					    t->tm_mon+1, t->tm_mday,
625					    t->tm_hour, t->tm_min, t->tm_sec);
626				}
627			}
628			if ($4 != NULL)
629				free($4);
630		}
631	| QUIT CRLF
632		{
633			reply(221, "Goodbye.");
634			dologout(0);
635		}
636	| error
637		{
638			yyclearin;		/* discard lookahead data */
639			yyerrok;		/* clear error condition */
640			state = 0;		/* reset lexer state */
641		}
642	;
643rcmd
644	: RNFR check_login SP pathname CRLF
645		{
646			restart_point = 0;
647			if ($2 && $4) {
648				if (fromname)
649					free(fromname);
650				fromname = renamefrom($4);
651				if (fromname == NULL)
652					free($4);
653			} else if ($4) {
654				free ($4);
655			}
656		}
657
658	| REST check_login SP file_size CRLF
659		{
660			if ($2) {
661				if (fromname) {
662					free(fromname);
663					fromname = NULL;
664				}
665				restart_point = $4;
666				reply(350, "Restarting at %lld. %s",
667				    (long long)restart_point,
668				    "Send STORE or RETRIEVE to initiate transfer.");
669			}
670		}
671	;
672
673username
674	: STRING
675	;
676
677password
678	: /* empty */
679		{
680			$$ = calloc(1, sizeof(char));
681		}
682	| STRING
683	;
684
685byte_size
686	: NUMBER
687	;
688
689file_size
690	: NUMBER
691		{
692			$$ = $1;
693		}
694	| BIGNUM
695		{
696			$$ = $1;
697		}
698	;
699
700host_port
701	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
702		NUMBER COMMA NUMBER
703		{
704			char *a, *p;
705
706			if ($1 < 0 || $1 > 255 || $3 < 0 || $3 > 255 ||
707			    $5 < 0 || $5 > 255 || $7 < 0 || $7 > 255 ||
708			    $9 < 0 || $9 > 255 || $11 < 0 || $11 > 255) {
709				$$ = 1;
710			} else {
711				data_dest.su_sin.sin_len = sizeof(struct sockaddr_in);
712				data_dest.su_sin.sin_family = AF_INET;
713				p = (char *)&data_dest.su_sin.sin_port;
714				p[0] = $9; p[1] = $11;
715				a = (char *)&data_dest.su_sin.sin_addr;
716				a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7;
717				$$ = 0;
718			}
719		}
720	;
721
722host_long_port4
723	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
724		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
725		NUMBER
726		{
727			char *a, *p;
728
729			/* reject invalid LPRT command */
730			if ($1 != 4 || $3 != 4 ||
731			    $5 < 0 || $5 > 255 || $7 < 0 || $7 > 255 ||
732			    $9 < 0 || $9 > 255 || $11 < 0 || $11 > 255 ||
733			    $13 != 2 ||
734			    $15 < 0 || $15 > 255 || $17 < 0 || $17 > 255) {
735				$$ = 1;
736			} else {
737				data_dest.su_sin.sin_len =
738					sizeof(struct sockaddr_in);
739				data_dest.su_family = AF_INET;
740				p = (char *)&data_dest.su_port;
741				p[0] = $15; p[1] = $17;
742				a = (char *)&data_dest.su_sin.sin_addr;
743				a[0] = $5; a[1] = $7; a[2] = $9; a[3] = $11;
744				$$ = 0;
745			}
746		}
747	;
748
749host_long_port6
750	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
751		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
752		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
753		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
754		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
755		NUMBER
756		{
757			char *a, *p;
758
759			/* reject invalid LPRT command */
760			if ($1 != 6 || $3 != 16 ||
761			    $5 < 0 || $5 > 255 || $7 < 0 || $7 > 255 ||
762			    $9 < 0 || $9 > 255 || $11 < 0 || $11 > 255 ||
763			    $13 < 0 || $13 > 255 || $15 < 0 || $15 > 255 ||
764			    $17 < 0 || $17 > 255 || $19 < 0 || $19 > 255 ||
765			    $21 < 0 || $21 > 255 || $23 < 0 || $23 > 255 ||
766			    $25 < 0 || $25 > 255 || $27 < 0 || $27 > 255 ||
767			    $29 < 0 || $29 > 255 || $31 < 0 || $31 > 255 ||
768			    $33 < 0 || $33 > 255 || $35 < 0 || $35 > 255 ||
769			    $37 != 2 ||
770			    $39 < 0 || $39 > 255 || $41 < 0 || $41 > 255) {
771				$$ = 1;
772			} else {
773				data_dest.su_sin6.sin6_len =
774					sizeof(struct sockaddr_in6);
775				data_dest.su_family = AF_INET6;
776				p = (char *)&data_dest.su_port;
777				p[0] = $39; p[1] = $41;
778				a = (char *)&data_dest.su_sin6.sin6_addr;
779				 a[0] =  $5;  a[1] =  $7;
780				 a[2] =  $9;  a[3] = $11;
781				 a[4] = $13;  a[5] = $15;
782				 a[6] = $17;  a[7] = $19;
783				 a[8] = $21;  a[9] = $23;
784				a[10] = $25; a[11] = $27;
785				a[12] = $29; a[13] = $31;
786				a[14] = $33; a[15] = $35;
787				if (his_addr.su_family == AF_INET6) {
788					/* XXX more sanity checks! */
789					data_dest.su_sin6.sin6_scope_id =
790					    his_addr.su_sin6.sin6_scope_id;
791				}
792
793				$$ = 0;
794			}
795		}
796	;
797
798form_code
799	: N
800		{
801			$$ = FORM_N;
802		}
803	| T
804		{
805			$$ = FORM_T;
806		}
807	| C
808		{
809			$$ = FORM_C;
810		}
811	;
812
813type_code
814	: A
815		{
816			cmd_type = TYPE_A;
817			cmd_form = FORM_N;
818		}
819	| A SP form_code
820		{
821			cmd_type = TYPE_A;
822			cmd_form = $3;
823		}
824	| E
825		{
826			cmd_type = TYPE_E;
827			cmd_form = FORM_N;
828		}
829	| E SP form_code
830		{
831			cmd_type = TYPE_E;
832			cmd_form = $3;
833		}
834	| I
835		{
836			cmd_type = TYPE_I;
837		}
838	| L
839		{
840			cmd_type = TYPE_L;
841			cmd_bytesz = 8;
842		}
843	| L SP byte_size
844		{
845			cmd_type = TYPE_L;
846			cmd_bytesz = $3;
847		}
848		/* this is for a bug in the BBN ftp */
849	| L byte_size
850		{
851			cmd_type = TYPE_L;
852			cmd_bytesz = $2;
853		}
854	;
855
856struct_code
857	: F
858		{
859			$$ = STRU_F;
860		}
861	| R
862		{
863			$$ = STRU_R;
864		}
865	| P
866		{
867			$$ = STRU_P;
868		}
869	;
870
871mode_code
872	: S
873		{
874			$$ = MODE_S;
875		}
876	| B
877		{
878			$$ = MODE_B;
879		}
880	| C
881		{
882			$$ = MODE_C;
883		}
884	;
885
886pathname
887	: pathstring
888		{
889			/*
890			 * Problem: this production is used for all pathname
891			 * processing, but only gives a 550 error reply.
892			 * This is a valid reply in some cases but not in others.
893			 */
894			if (logged_in && $1 && strchr($1, '~') != NULL) {
895				glob_t gl;
896				int flags =
897				 GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE;
898				char *pptr = $1;
899
900				/*
901				 * glob() will only find a leading ~, but
902				 * Netscape kindly puts a slash in front of
903				 * it for publish URLs.  There needs to be
904				 * a flag for glob() that expands tildes
905				 * anywhere in the string.
906				 */
907				if ((pptr[0] == '/') && (pptr[1] == '~'))
908					pptr++;
909
910				memset(&gl, 0, sizeof(gl));
911				if (glob(pptr, flags, NULL, &gl) ||
912				    gl.gl_pathc == 0) {
913					reply(550, "not found");
914					$$ = NULL;
915				} else {
916					$$ = strdup(gl.gl_pathv[0]);
917				}
918				globfree(&gl);
919				free($1);
920			} else
921				$$ = $1;
922		}
923	;
924
925pathstring
926	: STRING
927	;
928
929octal_number
930	: NUMBER
931		{
932			int ret, dec, multby, digit;
933
934			/*
935			 * Convert a number that was read as decimal number
936			 * to what it would be if it had been read as octal.
937			 */
938			dec = $1;
939			multby = 1;
940			ret = 0;
941			while (dec) {
942				digit = dec%10;
943				if (digit > 7) {
944					ret = -1;
945					break;
946				}
947				ret += digit * multby;
948				multby *= 8;
949				dec /= 10;
950			}
951			$$ = ret;
952		}
953	;
954
955
956check_login
957	: /* empty */
958		{
959			if (logged_in)
960				$$ = 1;
961			else {
962				reply(530, "Please login with USER and PASS.");
963				$$ = 0;
964				state = 0;
965				YYABORT;
966			}
967		}
968	;
969
970check_login_epsvall
971	: /* empty */
972		{
973			if (!logged_in) {
974				reply(530, "Please login with USER and PASS.");
975				$$ = 0;
976				state = 0;
977				YYABORT;
978			} else if (epsvall) {
979				reply(501, "the command is disallowed "
980				    "after EPSV ALL");
981				usedefault = 1;
982				$$ = 0;
983			} else
984				$$ = 1;
985		}
986	;
987
988%%
989
990#define	CMD	0	/* beginning of command */
991#define	ARGS	1	/* expect miscellaneous arguments */
992#define	STR1	2	/* expect SP followed by STRING */
993#define	STR2	3	/* expect STRING */
994#define	OSTR	4	/* optional SP then STRING */
995#define	ZSTR1	5	/* SP then optional STRING */
996#define	ZSTR2	6	/* optional STRING after SP */
997#define	SITECMD	7	/* SITE command */
998#define	NSTR	8	/* Number followed by a string */
999
1000struct tab {
1001	char	*name;
1002	short	token;
1003	short	state;
1004	short	implemented;	/* 1 if command is implemented */
1005	char	*help;
1006};
1007
1008struct tab cmdtab[] = {		/* In order defined in RFC 765 */
1009	{ "USER", USER, STR1, 1,	"<sp> username" },
1010	{ "PASS", PASS, ZSTR1, 1,	"<sp> password" },
1011	{ "ACCT", ACCT, STR1, 0,	"(specify account)" },
1012	{ "SMNT", SMNT, ARGS, 0,	"(structure mount)" },
1013	{ "REIN", REIN, ARGS, 0,	"(reinitialize server state)" },
1014	{ "QUIT", QUIT, ARGS, 1,	"(terminate service)", },
1015	{ "PORT", PORT, ARGS, 1,	"<sp> b0, b1, b2, b3, b4" },
1016	{ "LPRT", LPRT, ARGS, 1,	"<sp> af, hal, h1, h2, h3,..., pal, p1, p2..." },
1017	{ "EPRT", EPRT, STR1, 1,	"<sp> |af|addr|port|" },
1018	{ "PASV", PASV, ARGS, 1,	"(set server in passive mode)" },
1019	{ "LPSV", LPSV, ARGS, 1,	"(set server in passive mode)" },
1020	{ "EPSV", EPSV, ARGS, 1,	"[<sp> af|ALL]" },
1021	{ "TYPE", TYPE, ARGS, 1,	"<sp> [ A | E | I | L ]" },
1022	{ "STRU", STRU, ARGS, 1,	"(specify file structure)" },
1023	{ "MODE", MODE, ARGS, 1,	"(specify transfer mode)" },
1024	{ "RETR", RETR, STR1, 1,	"<sp> file-name" },
1025	{ "STOR", STOR, STR1, 1,	"<sp> file-name" },
1026	{ "APPE", APPE, STR1, 1,	"<sp> file-name" },
1027	{ "MLFL", MLFL, OSTR, 0,	"(mail file)" },
1028	{ "MAIL", MAIL, OSTR, 0,	"(mail to user)" },
1029	{ "MSND", MSND, OSTR, 0,	"(mail send to terminal)" },
1030	{ "MSOM", MSOM, OSTR, 0,	"(mail send to terminal or mailbox)" },
1031	{ "MSAM", MSAM, OSTR, 0,	"(mail send to terminal and mailbox)" },
1032	{ "MRSQ", MRSQ, OSTR, 0,	"(mail recipient scheme question)" },
1033	{ "MRCP", MRCP, STR1, 0,	"(mail recipient)" },
1034	{ "ALLO", ALLO, ARGS, 1,	"allocate storage (vacuously)" },
1035	{ "REST", REST, ARGS, 1,	"<sp> offset (restart command)" },
1036	{ "RNFR", RNFR, STR1, 1,	"<sp> file-name" },
1037	{ "RNTO", RNTO, STR1, 1,	"<sp> file-name" },
1038	{ "ABOR", ABOR, ARGS, 1,	"(abort operation)" },
1039	{ "DELE", DELE, STR1, 1,	"<sp> file-name" },
1040	{ "CWD",  CWD,  OSTR, 1,	"[ <sp> directory-name ]" },
1041	{ "XCWD", CWD,	OSTR, 1,	"[ <sp> directory-name ]" },
1042	{ "LIST", LIST, OSTR, 1,	"[ <sp> path-name ]" },
1043	{ "NLST", NLST, OSTR, 1,	"[ <sp> path-name ]" },
1044	{ "SITE", SITE, SITECMD, 1,	"site-cmd [ <sp> arguments ]" },
1045	{ "SYST", SYST, ARGS, 1,	"(get type of operating system)" },
1046	{ "STAT", STAT, OSTR, 1,	"[ <sp> path-name ]" },
1047	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
1048	{ "NOOP", NOOP, ARGS, 1,	"" },
1049	{ "MKD",  MKD,  STR1, 1,	"<sp> path-name" },
1050	{ "XMKD", MKD,  STR1, 1,	"<sp> path-name" },
1051	{ "RMD",  RMD,  STR1, 1,	"<sp> path-name" },
1052	{ "XRMD", RMD,  STR1, 1,	"<sp> path-name" },
1053	{ "PWD",  PWD,  ARGS, 1,	"(return current directory)" },
1054	{ "XPWD", PWD,  ARGS, 1,	"(return current directory)" },
1055	{ "CDUP", CDUP, ARGS, 1,	"(change to parent directory)" },
1056	{ "XCUP", CDUP, ARGS, 1,	"(change to parent directory)" },
1057	{ "STOU", STOU, STR1, 1,	"<sp> file-name" },
1058	{ "SIZE", SIZE, OSTR, 1,	"<sp> path-name" },
1059	{ "MDTM", MDTM, OSTR, 1,	"<sp> path-name" },
1060	{ NULL,   0,    0,    0,	0 }
1061};
1062
1063struct tab sitetab[] = {
1064	{ "UMASK", UMASK, ARGS, 1,	"[ <sp> umask ]" },
1065	{ "IDLE", IDLE, ARGS, 1,	"[ <sp> maximum-idle-time ]" },
1066	{ "CHMOD", CHMOD, NSTR, 1,	"<sp> mode <sp> file-name" },
1067	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
1068	{ NULL,   0,    0,    0,	0 }
1069};
1070
1071static void	 help(struct tab *, char *);
1072static struct tab *
1073		 lookup(struct tab *, const char *);
1074static void	 sizecmd(const char *);
1075static int	 yylex(void);
1076
1077extern int epsvall;
1078
1079static struct tab *
1080lookup(struct tab *p, const char *cmd)
1081{
1082
1083	for (; p->name != NULL; p++)
1084		if (strcmp(cmd, p->name) == 0)
1085			return (p);
1086	return (NULL);
1087}
1088
1089#include <arpa/telnet.h>
1090
1091/*
1092 * get_line - a hacked up version of fgets to ignore TELNET escape codes.
1093 */
1094int
1095get_line(char *s, int n)
1096{
1097	int c;
1098	char *cs;
1099
1100	cs = s;
1101/* tmpline may contain saved command from urgent mode interruption */
1102	for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
1103		*cs++ = tmpline[c];
1104		if (tmpline[c] == '\n') {
1105			*cs++ = '\0';
1106			if (debug)
1107				syslog(LOG_DEBUG, "command: %s", s);
1108			tmpline[0] = '\0';
1109			return(0);
1110		}
1111		if (c == 0)
1112			tmpline[0] = '\0';
1113	}
1114	while ((c = getc(stdin)) != EOF) {
1115		c &= 0377;
1116		if (c == IAC) {
1117		    if ((c = getc(stdin)) != EOF) {
1118			c &= 0377;
1119			switch (c) {
1120			case WILL:
1121			case WONT:
1122				c = getc(stdin);
1123				printf("%c%c%c", IAC, DONT, 0377&c);
1124				(void) fflush(stdout);
1125				continue;
1126			case DO:
1127			case DONT:
1128				c = getc(stdin);
1129				printf("%c%c%c", IAC, WONT, 0377&c);
1130				(void) fflush(stdout);
1131				continue;
1132			case IAC:
1133				break;
1134			default:
1135				continue;	/* ignore command */
1136			}
1137		    }
1138		}
1139		*cs++ = c;
1140		if (--n <= 0) {
1141			/*
1142			 * If command doesn't fit into buffer, discard the
1143			 * rest of the command and indicate truncation.
1144			 * This prevents the command to be split up into
1145			 * multiple commands.
1146			 */
1147			while (c != '\n' && (c = getc(stdin)) != EOF)
1148				;
1149			return (-2);
1150		}
1151		if (c == '\n')
1152			break;
1153	}
1154	if (c == EOF && cs == s)
1155		return (-1);
1156	*cs++ = '\0';
1157	if (debug) {
1158		if (!guest && strncasecmp("pass ", s, 5) == 0) {
1159			/* Don't syslog passwords */
1160			syslog(LOG_DEBUG, "command: %.5s ???", s);
1161		} else {
1162			char *cp;
1163			int len;
1164
1165			/* Don't syslog trailing CR-LF */
1166			len = strlen(s);
1167			cp = s + len - 1;
1168			while (cp >= s && (*cp == '\n' || *cp == '\r')) {
1169				--cp;
1170				--len;
1171			}
1172			syslog(LOG_DEBUG, "command: %.*s", len, s);
1173		}
1174	}
1175	return (0);
1176}
1177
1178void
1179toolong(int signo)
1180{
1181	struct syslog_data sdata = SYSLOG_DATA_INIT;
1182
1183	reply_r(421,
1184	    "Timeout (%d seconds): closing control connection.", timeout);
1185	if (logging)
1186		syslog_r(LOG_INFO, &sdata, "User %s timed out after %d seconds",
1187		    (pw ? pw -> pw_name : "unknown"), timeout);
1188	dologout(1);
1189}
1190
1191static int
1192yylex(void)
1193{
1194	static int cpos;
1195	char *cp, *cp2;
1196	struct tab *p;
1197	int n;
1198	char c;
1199
1200	for (;;) {
1201		switch (state) {
1202
1203		case CMD:
1204			(void) alarm((unsigned) timeout);
1205			n = get_line(cbuf, sizeof(cbuf)-1);
1206			if (n == -1) {
1207				reply(221, "You could at least say goodbye.");
1208				dologout(0);
1209			} else if (n == -2) {
1210				reply(500, "Command too long.");
1211				alarm(0);
1212				continue;
1213			}
1214			(void) alarm(0);
1215			if ((cp = strchr(cbuf, '\r'))) {
1216				*cp++ = '\n';
1217				*cp = '\0';
1218			}
1219			if (strncasecmp(cbuf, "PASS", 4) != 0) {
1220				if ((cp = strpbrk(cbuf, "\n"))) {
1221					c = *cp;
1222					*cp = '\0';
1223					setproctitle("%s: %s", proctitle, cbuf);
1224					*cp = c;
1225				}
1226			}
1227			if ((cp = strpbrk(cbuf, " \n")))
1228				cpos = cp - cbuf;
1229			if (cpos == 0)
1230				cpos = 4;
1231			c = cbuf[cpos];
1232			cbuf[cpos] = '\0';
1233			upper(cbuf);
1234			p = lookup(cmdtab, cbuf);
1235			cbuf[cpos] = c;
1236			if (p != NULL) {
1237				if (p->implemented == 0) {
1238					nack(p->name);
1239					return (LEXERR);
1240				}
1241				state = p->state;
1242				yylval.s = p->name;
1243				return (p->token);
1244			}
1245			break;
1246
1247		case SITECMD:
1248			if (cbuf[cpos] == ' ') {
1249				cpos++;
1250				return (SP);
1251			}
1252			cp = &cbuf[cpos];
1253			if ((cp2 = strpbrk(cp, " \n")))
1254				cpos = cp2 - cbuf;
1255			c = cbuf[cpos];
1256			cbuf[cpos] = '\0';
1257			upper(cp);
1258			p = lookup(sitetab, cp);
1259			cbuf[cpos] = c;
1260			if (p != NULL) {
1261				if (p->implemented == 0) {
1262					state = CMD;
1263					nack(p->name);
1264					return (LEXERR);
1265				}
1266				state = p->state;
1267				yylval.s = p->name;
1268				return (p->token);
1269			}
1270			state = CMD;
1271			break;
1272
1273		case OSTR:
1274			if (cbuf[cpos] == '\n') {
1275				state = CMD;
1276				return (CRLF);
1277			}
1278			/* FALLTHROUGH */
1279
1280		case STR1:
1281		case ZSTR1:
1282		dostr1:
1283			if (cbuf[cpos] == ' ') {
1284				cpos++;
1285				state = state == OSTR ? STR2 : state+1;
1286				return (SP);
1287			}
1288			break;
1289
1290		case ZSTR2:
1291			if (cbuf[cpos] == '\n') {
1292				state = CMD;
1293				return (CRLF);
1294			}
1295			/* FALLTHROUGH */
1296
1297		case STR2:
1298			cp = &cbuf[cpos];
1299			n = strlen(cp);
1300			cpos += n - 1;
1301			/*
1302			 * Make sure the string is nonempty and \n terminated.
1303			 */
1304			if (n > 1 && cbuf[cpos] == '\n') {
1305				cbuf[cpos] = '\0';
1306				yylval.s = strdup(cp);
1307				if (yylval.s == NULL)
1308					fatal("Ran out of memory.");
1309				cbuf[cpos] = '\n';
1310				state = ARGS;
1311				return (STRING);
1312			}
1313			break;
1314
1315		case NSTR:
1316			if (cbuf[cpos] == ' ') {
1317				cpos++;
1318				return (SP);
1319			}
1320			if (isdigit((unsigned char)cbuf[cpos])) {
1321				cp = &cbuf[cpos];
1322				while (isdigit((unsigned char)cbuf[++cpos]))
1323					;
1324				c = cbuf[cpos];
1325				cbuf[cpos] = '\0';
1326				yylval.i = atoi(cp);
1327				cbuf[cpos] = c;
1328				state = STR1;
1329				return (NUMBER);
1330			}
1331			state = STR1;
1332			goto dostr1;
1333
1334		case ARGS:
1335			if (isdigit((unsigned char)cbuf[cpos])) {
1336				long long llval;
1337
1338				cp = &cbuf[cpos];
1339				errno = 0;
1340				llval = strtoll(cp, &cp2, 10);
1341				if (llval < 0 ||
1342				    (errno == ERANGE && llval == LLONG_MAX))
1343					break;
1344
1345				cpos = (int)(cp2 - cbuf);
1346				if (llval > INT_MAX) {
1347					yylval.o = llval;
1348					return (BIGNUM);
1349				} else {
1350					yylval.i = (int)llval;
1351					return (NUMBER);
1352				}
1353			}
1354			if (strncasecmp(&cbuf[cpos], "ALL", 3) == 0 &&
1355			    !isalnum((unsigned char)cbuf[cpos + 3])) {
1356				cpos += 3;
1357				return ALL;
1358			}
1359			switch (cbuf[cpos++]) {
1360
1361			case '\n':
1362				state = CMD;
1363				return (CRLF);
1364
1365			case ' ':
1366				return (SP);
1367
1368			case ',':
1369				return (COMMA);
1370
1371			case 'A':
1372			case 'a':
1373				return (A);
1374
1375			case 'B':
1376			case 'b':
1377				return (B);
1378
1379			case 'C':
1380			case 'c':
1381				return (C);
1382
1383			case 'E':
1384			case 'e':
1385				return (E);
1386
1387			case 'F':
1388			case 'f':
1389				return (F);
1390
1391			case 'I':
1392			case 'i':
1393				return (I);
1394
1395			case 'L':
1396			case 'l':
1397				return (L);
1398
1399			case 'N':
1400			case 'n':
1401				return (N);
1402
1403			case 'P':
1404			case 'p':
1405				return (P);
1406
1407			case 'R':
1408			case 'r':
1409				return (R);
1410
1411			case 'S':
1412			case 's':
1413				return (S);
1414
1415			case 'T':
1416			case 't':
1417				return (T);
1418
1419			}
1420			break;
1421
1422		default:
1423			fatal("Unknown state in scanner.");
1424		}
1425		state = CMD;
1426		return (LEXERR);
1427	}
1428}
1429
1430void
1431upper(char *s)
1432{
1433	char *p;
1434
1435	for (p = s; *p; p++)
1436		*p = (char)toupper((unsigned char)*p);
1437}
1438
1439static void
1440help(struct tab *ctab, char *s)
1441{
1442	struct tab *c;
1443	int width, NCMDS;
1444	char *type;
1445
1446	if (ctab == sitetab)
1447		type = "SITE ";
1448	else
1449		type = "";
1450	width = 0, NCMDS = 0;
1451	for (c = ctab; c->name != NULL; c++) {
1452		int len = strlen(c->name);
1453
1454		if (len > width)
1455			width = len;
1456		NCMDS++;
1457	}
1458	width = (width + 8) &~ 7;
1459	if (s == NULL) {
1460		int i, j, w;
1461		int columns, lines;
1462
1463		lreply(214, "The following %scommands are recognized %s.",
1464		    type, "(* =>'s unimplemented)");
1465		columns = 76 / width;
1466		if (columns == 0)
1467			columns = 1;
1468		lines = (NCMDS + columns - 1) / columns;
1469		for (i = 0; i < lines; i++) {
1470			printf("   ");
1471			for (j = 0; j < columns; j++) {
1472				c = ctab + j * lines + i;
1473				printf("%s%c", c->name,
1474					c->implemented ? ' ' : '*');
1475				if (c + lines >= &ctab[NCMDS])
1476					break;
1477				w = strlen(c->name) + 1;
1478				while (w < width) {
1479					putchar(' ');
1480					w++;
1481				}
1482			}
1483			printf("\r\n");
1484		}
1485		(void) fflush(stdout);
1486		reply(214, "Direct comments to ftp-bugs@%s.", hostname);
1487		return;
1488	}
1489	upper(s);
1490	c = lookup(ctab, s);
1491	if (c == NULL) {
1492		reply(502, "Unknown command %s.", s);
1493		return;
1494	}
1495	if (c->implemented)
1496		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
1497	else
1498		reply(214, "%s%-*s\t%s; unimplemented.", type, width,
1499		    c->name, c->help);
1500}
1501
1502static void
1503sizecmd(const char *filename)
1504{
1505	switch (type) {
1506	case TYPE_L:
1507	case TYPE_I: {
1508		struct stat stbuf;
1509		if (stat(filename, &stbuf) == -1 || !S_ISREG(stbuf.st_mode))
1510			reply(550, "%s: not a plain file.", filename);
1511		else
1512			reply(213, "%lld", (long long)stbuf.st_size);
1513		break; }
1514	case TYPE_A: {
1515		FILE *fin;
1516		int c;
1517		off_t count;
1518		struct stat stbuf;
1519		fin = fopen(filename, "r");
1520		if (fin == NULL) {
1521			perror_reply(550, filename);
1522			return;
1523		}
1524		if (fstat(fileno(fin), &stbuf) == -1 || !S_ISREG(stbuf.st_mode)) {
1525			reply(550, "%s: not a plain file.", filename);
1526			(void) fclose(fin);
1527			return;
1528		}
1529		if (stbuf.st_size > 10240) {
1530			reply(550, "%s: file too large for SIZE.", filename);
1531			(void) fclose(fin);
1532			return;
1533		}
1534
1535		count = 0;
1536		while((c = getc(fin)) != EOF) {
1537			if (c == '\n')	/* will get expanded to \r\n */
1538				count++;
1539			count++;
1540		}
1541		(void) fclose(fin);
1542
1543		reply(213, "%lld", (long long)count);
1544		break; }
1545	default:
1546		reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
1547	}
1548}
1549