ftpcmd.y revision 1.41
1/*	$OpenBSD: ftpcmd.y,v 1.41 2002/07/02 18:09:53 danh 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 const char sccsid[] = "@(#)ftpcmd.y	8.3 (Berkeley) 4/6/94";
49#else
50static const char rcsid[] =
51    "$OpenBSD: ftpcmd.y,v 1.41 2002/07/02 18:09:53 danh Exp $";
52#endif
53#endif /* not lint */
54
55#include <sys/param.h>
56#include <sys/socket.h>
57#include <sys/stat.h>
58
59#include <netinet/in.h>
60#include <arpa/ftp.h>
61
62#include <ctype.h>
63#include <errno.h>
64#include <glob.h>
65#include <pwd.h>
66#include <signal.h>
67#include <tzfile.h>
68#include <stdio.h>
69#include <stdlib.h>
70#include <string.h>
71#include <syslog.h>
72#include <time.h>
73#include <unistd.h>
74#include <netdb.h>
75
76#include "extern.h"
77
78extern	union sockunion data_dest;
79extern	int logged_in;
80extern	struct passwd *pw;
81extern	int guest;
82extern	int logging;
83extern	int type;
84extern	int form;
85extern	int debug;
86extern	int timeout;
87extern	int maxtimeout;
88extern  int pdata;
89extern	char hostname[], remotehost[];
90extern	char proctitle[];
91extern	int usedefault;
92extern  int transflag;
93extern  char tmpline[];
94extern	int portcheck;
95extern	union sockunion his_addr;
96extern	int umaskchange;
97
98off_t	restart_point;
99
100static	int cmd_type;
101static	int cmd_form;
102static	int cmd_bytesz;
103static	int state;
104char	cbuf[512];
105char	*fromname;
106
107%}
108
109%union {
110	int	i;
111	char   *s;
112}
113
114%token
115	A	B	C	E	F	I
116	L	N	P	R	S	T
117
118	SP	CRLF	COMMA	ALL
119
120	USER	PASS	ACCT	REIN	QUIT	PORT
121	PASV	TYPE	STRU	MODE	RETR	STOR
122	APPE	MLFL	MAIL	MSND	MSOM	MSAM
123	MRSQ	MRCP	ALLO	REST	RNFR	RNTO
124	ABOR	DELE	CWD	LIST	NLST	SITE
125	STAT	HELP	NOOP	MKD	RMD	PWD
126	CDUP	STOU	SMNT	SYST	SIZE	MDTM
127
128	LPRT	LPSV	EPRT	EPSV
129
130	UMASK	IDLE	CHMOD
131
132	LEXERR
133
134%token	<s> STRING
135%token	<i> NUMBER
136
137%type	<i> check_login check_login_epsvall 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			if (fromname) {
151				free(fromname);
152				fromname = NULL;
153			}
154			restart_point = (off_t) 0;
155		}
156	| cmd_list rcmd
157	;
158
159cmd
160	: USER SP username CRLF
161		{
162			user($3);
163			free($3);
164		}
165	| PASS SP password CRLF
166		{
167			pass($3);
168			memset($3, 0, strlen($3));
169			free($3);
170		}
171	| PORT check_login_epsvall SP host_port CRLF
172		{
173			if ($2) {
174				if ($4) {
175					usedefault = 1;
176					reply(500,
177					    "Illegal PORT rejected (range errors).");
178				} else if (portcheck &&
179				    ntohs(data_dest.su_sin.sin_port) < IPPORT_RESERVED) {
180					usedefault = 1;
181					reply(500,
182					    "Illegal PORT rejected (reserved port).");
183				} else if (portcheck &&
184				    memcmp(&data_dest.su_sin.sin_addr,
185				    &his_addr.su_sin.sin_addr,
186				    sizeof data_dest.su_sin.sin_addr)) {
187					usedefault = 1;
188					reply(500,
189					    "Illegal PORT rejected (address wrong).");
190				} else {
191					usedefault = 0;
192					if (pdata >= 0) {
193						(void) close(pdata);
194						pdata = -1;
195					}
196					reply(200, "PORT command successful.");
197				}
198			}
199		}
200	| LPRT check_login_epsvall SP host_long_port4 CRLF
201		{
202			if ($2) {
203				/* reject invalid host_long_port4 */
204				if ($4) {
205					reply(500,
206					    "Illegal LPRT command rejected");
207					usedefault = 1;
208				} else {
209					usedefault = 0;
210					if (pdata >= 0) {
211						(void) close(pdata);
212						pdata = -1;
213					}
214					reply(200, "LPRT command successful.");
215				}
216			}
217		}
218
219	| LPRT check_login_epsvall SP host_long_port6 CRLF
220		{
221			if ($2) {
222				/* reject invalid host_long_port6 */
223				if ($4) {
224					reply(500,
225					    "Illegal LPRT command rejected");
226					usedefault = 1;
227				} else {
228					usedefault = 0;
229					if (pdata >= 0) {
230						(void) close(pdata);
231						pdata = -1;
232					}
233					reply(200, "LPRT command successful.");
234				}
235			}
236		}
237
238	| EPRT check_login_epsvall SP STRING CRLF
239		{
240			if ($2)
241				extended_port($4);
242			free($4);
243		}
244
245	| PASV check_login_epsvall CRLF
246		{
247			if ($2)
248				passive();
249		}
250	| LPSV check_login_epsvall CRLF
251		{
252			if ($2)
253				long_passive("LPSV", PF_UNSPEC);
254		}
255	| EPSV check_login SP NUMBER CRLF
256		{
257			if ($2)
258				long_passive("EPSV", epsvproto2af($4));
259		}
260	| EPSV check_login SP ALL CRLF
261		{
262			if ($2) {
263				reply(200, "EPSV ALL command successful.");
264				epsvall++;
265			}
266		}
267	| EPSV check_login CRLF
268		{
269			if ($2)
270				long_passive("EPSV", PF_UNSPEC);
271		}
272	| TYPE check_login SP type_code CRLF
273		{
274			if ($2) {
275				switch (cmd_type) {
276
277				case TYPE_A:
278					if (cmd_form == FORM_N) {
279						reply(200, "Type set to A.");
280						type = cmd_type;
281						form = cmd_form;
282					} else
283						reply(504, "Form must be N.");
284					break;
285
286				case TYPE_E:
287					reply(504, "Type E not implemented.");
288					break;
289
290				case TYPE_I:
291					reply(200, "Type set to I.");
292					type = cmd_type;
293					break;
294
295				case TYPE_L:
296					if (cmd_bytesz == 8) {
297					       reply(200,
298					       "Type set to L (byte size 8).");
299					       type = cmd_type;
300					} else
301					    reply(504, "Byte size must be 8.");
302
303				}
304			}
305		}
306	| STRU check_login SP struct_code CRLF
307		{
308			if ($2) {
309				switch ($4) {
310
311				case STRU_F:
312					reply(200, "STRU F ok.");
313					break;
314
315				default:
316					reply(504, "Unimplemented STRU type.");
317				}
318			}
319		}
320	| MODE check_login SP mode_code CRLF
321		{
322			if ($2) {
323				switch ($4) {
324
325				case MODE_S:
326					reply(200, "MODE S ok.");
327					break;
328
329				default:
330					reply(502, "Unimplemented MODE type.");
331				}
332			}
333		}
334	| ALLO check_login SP NUMBER CRLF
335		{
336			if ($2) {
337				reply(202, "ALLO command ignored.");
338			}
339		}
340	| ALLO check_login SP NUMBER SP R SP NUMBER CRLF
341		{
342			if ($2) {
343				reply(202, "ALLO command ignored.");
344			}
345		}
346	| RETR check_login SP pathname CRLF
347		{
348			if ($2 && $4 != NULL)
349				retrieve(NULL, $4);
350			if ($4 != NULL)
351				free($4);
352		}
353	| STOR check_login SP pathname CRLF
354		{
355			if ($2 && $4 != NULL)
356				store($4, "w", 0);
357			if ($4 != NULL)
358				free($4);
359		}
360	| APPE check_login SP pathname CRLF
361		{
362			if ($2 && $4 != NULL)
363				store($4, "a", 0);
364			if ($4 != NULL)
365				free($4);
366		}
367	| NLST check_login CRLF
368		{
369			if ($2)
370				send_file_list(".");
371		}
372	| NLST check_login SP STRING CRLF
373		{
374			if ($2 && $4 != NULL)
375				send_file_list($4);
376			free($4);
377		}
378	| LIST check_login CRLF
379		{
380			if ($2)
381				retrieve("/bin/ls -lgA", "");
382		}
383	| LIST check_login SP pathname CRLF
384		{
385			if ($2 && $4 != NULL)
386				retrieve("/bin/ls -lgA %s", $4);
387			if ($4 != NULL)
388				free($4);
389		}
390	| STAT check_login SP pathname CRLF
391		{
392			if ($2 && $4 != NULL)
393				statfilecmd($4);
394			if ($4 != NULL)
395				free($4);
396		}
397	| STAT check_login CRLF
398		{
399			if ($2)
400				statcmd();
401		}
402	| DELE check_login SP pathname CRLF
403		{
404			if ($2 && $4 != NULL)
405				delete($4);
406			if ($4 != NULL)
407				free($4);
408		}
409	| RNTO check_login SP pathname CRLF
410		{
411			if ($2 && $4 != NULL) {
412				if (fromname) {
413					renamecmd(fromname, $4);
414					free(fromname);
415					fromname = NULL;
416				} else {
417					reply(503,
418					  "Bad sequence of commands.");
419				}
420			}
421			if ($4 != NULL)
422				free($4);
423		}
424	| ABOR check_login CRLF
425		{
426			if ($2)
427				reply(225, "ABOR command successful.");
428		}
429	| CWD check_login CRLF
430		{
431			if ($2)
432				cwd(pw->pw_dir);
433		}
434	| CWD check_login SP pathname CRLF
435		{
436			if ($2 && $4 != NULL)
437				cwd($4);
438			if ($4 != NULL)
439				free($4);
440		}
441	| HELP CRLF
442		{
443			help(cmdtab, NULL);
444		}
445	| HELP SP STRING CRLF
446		{
447			char *cp = $3;
448
449			if (strncasecmp(cp, "SITE", 4) == 0) {
450				cp = $3 + 4;
451				if (*cp == ' ')
452					cp++;
453				if (*cp)
454					help(sitetab, cp);
455				else
456					help(sitetab, NULL);
457			} else
458				help(cmdtab, $3);
459			free ($3);
460		}
461	| NOOP CRLF
462		{
463			reply(200, "NOOP command successful.");
464		}
465	| MKD check_login SP pathname CRLF
466		{
467			if ($2 && $4 != NULL)
468				makedir($4);
469			if ($4 != NULL)
470				free($4);
471		}
472	| RMD check_login SP pathname CRLF
473		{
474			if ($2 && $4 != NULL)
475				removedir($4);
476			if ($4 != NULL)
477				free($4);
478		}
479	| PWD check_login CRLF
480		{
481			if ($2)
482				pwd();
483		}
484	| CDUP check_login CRLF
485		{
486			if ($2)
487				cwd("..");
488		}
489	| SITE SP HELP CRLF
490		{
491			help(sitetab, NULL);
492		}
493	| SITE SP HELP SP STRING CRLF
494		{
495			help(sitetab, $5);
496			free ($5);
497		}
498	| SITE SP UMASK check_login CRLF
499		{
500			int oldmask;
501
502			if ($4) {
503				oldmask = umask(0);
504				(void) umask(oldmask);
505				reply(200, "Current UMASK is %03o", oldmask);
506			}
507		}
508	| SITE SP UMASK check_login SP octal_number CRLF
509		{
510			int oldmask;
511
512			if ($4) {
513				if (($6 == -1) || ($6 > 0777)) {
514					reply(501, "Bad UMASK value");
515				} else if (!umaskchange) {
516					reply(550,
517					    "No permission to change umask.");
518				} else {
519					oldmask = umask($6);
520					reply(200,
521					    "UMASK set to %03o (was %03o)",
522					    $6, oldmask);
523				}
524			}
525		}
526	| SITE SP CHMOD check_login SP octal_number SP pathname CRLF
527		{
528			if ($4 && ($8 != NULL)) {
529				if (($6 == -1) || ($6 > 0777))
530					reply(501,
531					    "CHMOD: Mode value must be between "
532					    "0 and 0777");
533				else if (!umaskchange)
534					reply(550,
535					    "No permission to change mode of %s.",
536					    $8);
537				else if (chmod($8, $6) < 0)
538					perror_reply(550, $8);
539				else
540					reply(200,
541					    "CHMOD command successful.");
542			}
543			if ($8 != NULL)
544				free($8);
545		}
546	| SITE SP check_login IDLE CRLF
547		{
548			if ($3)
549			  reply(200,
550	       		    "Current IDLE time limit is %d seconds; max %d",
551				timeout, maxtimeout);
552		}
553	| SITE SP check_login IDLE SP NUMBER CRLF
554		{
555			if ($3) {
556				if ($6 < 30 || $6 > maxtimeout) {
557				reply(501,
558				    "Maximum IDLE time must be between "
559				    "30 and %d seconds",
560				    maxtimeout);
561				} else {
562					timeout = $6;
563					(void) alarm((unsigned) timeout);
564					reply(200,
565					    "Maximum IDLE time set to %d seconds",
566					    timeout);
567				}
568			}
569		}
570	| STOU check_login SP pathname CRLF
571		{
572			if ($2 && $4 != NULL)
573				store($4, "w", 1);
574			if ($4 != NULL)
575				free($4);
576		}
577	| SYST check_login CRLF
578		{
579			if ($2)
580#ifdef unix
581#ifdef BSD
582			reply(215, "UNIX Type: L%d Version: BSD-%d",
583				NBBY, BSD);
584#else /* BSD */
585			reply(215, "UNIX Type: L%d", NBBY);
586#endif /* BSD */
587#else /* unix */
588			reply(215, "UNKNOWN Type: L%d", NBBY);
589#endif /* unix */
590		}
591
592		/*
593		 * SIZE is not in RFC959, but Postel has blessed it and
594		 * it will be in the updated RFC.
595		 *
596		 * Return size of file in a format suitable for
597		 * using with RESTART (we just count bytes).
598		 */
599	| SIZE check_login SP pathname CRLF
600		{
601			if ($2 && $4 != NULL)
602				sizecmd($4);
603			if ($4 != NULL)
604				free($4);
605		}
606
607		/*
608		 * MDTM is not in RFC959, but Postel has blessed it and
609		 * it will be in the updated RFC.
610		 *
611		 * Return modification time of file as an ISO 3307
612		 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
613		 * where xxx is the fractional second (of any precision,
614		 * not necessarily 3 digits)
615		 */
616	| MDTM check_login SP pathname CRLF
617		{
618			if ($2 && $4 != NULL) {
619				struct stat stbuf;
620				if (stat($4, &stbuf) < 0)
621					reply(550, "%s: %s",
622					    $4, strerror(errno));
623				else if (!S_ISREG(stbuf.st_mode)) {
624					reply(550, "%s: not a plain file.", $4);
625				} else {
626					struct tm *t;
627					t = gmtime(&stbuf.st_mtime);
628					reply(213,
629					    "%04d%02d%02d%02d%02d%02d",
630					    TM_YEAR_BASE + t->tm_year,
631					    t->tm_mon+1, t->tm_mday,
632					    t->tm_hour, t->tm_min, t->tm_sec);
633				}
634			}
635			if ($4 != NULL)
636				free($4);
637		}
638	| QUIT CRLF
639		{
640			reply(221, "Goodbye.");
641			dologout(0);
642		}
643	| error
644		{
645			yyclearin;		/* discard lookahead data */
646			yyerrok;		/* clear error condition */
647			state = 0;		/* reset lexer state */
648		}
649	;
650rcmd
651	: RNFR check_login SP pathname CRLF
652		{
653			restart_point = (off_t) 0;
654			if ($2 && $4) {
655				if (fromname)
656					free(fromname);
657				fromname = renamefrom($4);
658				if (fromname == NULL)
659					free($4);
660			} else if ($4) {
661				free ($4);
662			}
663		}
664
665	| REST check_login SP byte_size CRLF
666		{
667			if ($2) {
668			    if (fromname) {
669				    free(fromname);
670				    fromname = NULL;
671			    }
672			    restart_point = $4;	/* XXX $4 is only "int" */
673			    reply(350, "Restarting at %qd. %s", restart_point,
674			       "Send STORE or RETRIEVE to initiate transfer.");
675			}
676		}
677	;
678
679username
680	: STRING
681	;
682
683password
684	: /* empty */
685		{
686			$$ = (char *)calloc(1, sizeof(char));
687		}
688	| STRING
689	;
690
691byte_size
692	: NUMBER
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 = NBBY;
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			}
960		}
961	;
962
963check_login_epsvall
964	: /* empty */
965		{
966			if (!logged_in) {
967				reply(530, "Please login with USER and PASS.");
968				$$ = 0;
969			} else if (epsvall) {
970				reply(501, "the command is disallowed "
971				    "after EPSV ALL");
972				usedefault = 1;
973				$$ = 0;
974			} else
975				$$ = 1;
976		}
977	;
978
979%%
980
981#define	CMD	0	/* beginning of command */
982#define	ARGS	1	/* expect miscellaneous arguments */
983#define	STR1	2	/* expect SP followed by STRING */
984#define	STR2	3	/* expect STRING */
985#define	OSTR	4	/* optional SP then STRING */
986#define	ZSTR1	5	/* SP then optional STRING */
987#define	ZSTR2	6	/* optional STRING after SP */
988#define	SITECMD	7	/* SITE command */
989#define	NSTR	8	/* Number followed by a string */
990
991struct tab {
992	char	*name;
993	short	token;
994	short	state;
995	short	implemented;	/* 1 if command is implemented */
996	char	*help;
997};
998
999struct tab cmdtab[] = {		/* In order defined in RFC 765 */
1000	{ "USER", USER, STR1, 1,	"<sp> username" },
1001	{ "PASS", PASS, ZSTR1, 1,	"<sp> password" },
1002	{ "ACCT", ACCT, STR1, 0,	"(specify account)" },
1003	{ "SMNT", SMNT, ARGS, 0,	"(structure mount)" },
1004	{ "REIN", REIN, ARGS, 0,	"(reinitialize server state)" },
1005	{ "QUIT", QUIT, ARGS, 1,	"(terminate service)", },
1006	{ "PORT", PORT, ARGS, 1,	"<sp> b0, b1, b2, b3, b4" },
1007	{ "LPRT", LPRT, ARGS, 1,	"<sp> af, hal, h1, h2, h3,..., pal, p1, p2..." },
1008	{ "EPRT", EPRT, STR1, 1,	"<sp> |af|addr|port|" },
1009	{ "PASV", PASV, ARGS, 1,	"(set server in passive mode)" },
1010	{ "LPSV", LPSV, ARGS, 1,	"(set server in passive mode)" },
1011	{ "EPSV", EPSV, ARGS, 1,	"[<sp> af|ALL]" },
1012	{ "TYPE", TYPE, ARGS, 1,	"<sp> [ A | E | I | L ]" },
1013	{ "STRU", STRU, ARGS, 1,	"(specify file structure)" },
1014	{ "MODE", MODE, ARGS, 1,	"(specify transfer mode)" },
1015	{ "RETR", RETR, STR1, 1,	"<sp> file-name" },
1016	{ "STOR", STOR, STR1, 1,	"<sp> file-name" },
1017	{ "APPE", APPE, STR1, 1,	"<sp> file-name" },
1018	{ "MLFL", MLFL, OSTR, 0,	"(mail file)" },
1019	{ "MAIL", MAIL, OSTR, 0,	"(mail to user)" },
1020	{ "MSND", MSND, OSTR, 0,	"(mail send to terminal)" },
1021	{ "MSOM", MSOM, OSTR, 0,	"(mail send to terminal or mailbox)" },
1022	{ "MSAM", MSAM, OSTR, 0,	"(mail send to terminal and mailbox)" },
1023	{ "MRSQ", MRSQ, OSTR, 0,	"(mail recipient scheme question)" },
1024	{ "MRCP", MRCP, STR1, 0,	"(mail recipient)" },
1025	{ "ALLO", ALLO, ARGS, 1,	"allocate storage (vacuously)" },
1026	{ "REST", REST, ARGS, 1,	"<sp> offset (restart command)" },
1027	{ "RNFR", RNFR, STR1, 1,	"<sp> file-name" },
1028	{ "RNTO", RNTO, STR1, 1,	"<sp> file-name" },
1029	{ "ABOR", ABOR, ARGS, 1,	"(abort operation)" },
1030	{ "DELE", DELE, STR1, 1,	"<sp> file-name" },
1031	{ "CWD",  CWD,  OSTR, 1,	"[ <sp> directory-name ]" },
1032	{ "XCWD", CWD,	OSTR, 1,	"[ <sp> directory-name ]" },
1033	{ "LIST", LIST, OSTR, 1,	"[ <sp> path-name ]" },
1034	{ "NLST", NLST, OSTR, 1,	"[ <sp> path-name ]" },
1035	{ "SITE", SITE, SITECMD, 1,	"site-cmd [ <sp> arguments ]" },
1036	{ "SYST", SYST, ARGS, 1,	"(get type of operating system)" },
1037	{ "STAT", STAT, OSTR, 1,	"[ <sp> path-name ]" },
1038	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
1039	{ "NOOP", NOOP, ARGS, 1,	"" },
1040	{ "MKD",  MKD,  STR1, 1,	"<sp> path-name" },
1041	{ "XMKD", MKD,  STR1, 1,	"<sp> path-name" },
1042	{ "RMD",  RMD,  STR1, 1,	"<sp> path-name" },
1043	{ "XRMD", RMD,  STR1, 1,	"<sp> path-name" },
1044	{ "PWD",  PWD,  ARGS, 1,	"(return current directory)" },
1045	{ "XPWD", PWD,  ARGS, 1,	"(return current directory)" },
1046	{ "CDUP", CDUP, ARGS, 1,	"(change to parent directory)" },
1047	{ "XCUP", CDUP, ARGS, 1,	"(change to parent directory)" },
1048	{ "STOU", STOU, STR1, 1,	"<sp> file-name" },
1049	{ "SIZE", SIZE, OSTR, 1,	"<sp> path-name" },
1050	{ "MDTM", MDTM, OSTR, 1,	"<sp> path-name" },
1051	{ NULL,   0,    0,    0,	0 }
1052};
1053
1054struct tab sitetab[] = {
1055	{ "UMASK", UMASK, ARGS, 1,	"[ <sp> umask ]" },
1056	{ "IDLE", IDLE, ARGS, 1,	"[ <sp> maximum-idle-time ]" },
1057	{ "CHMOD", CHMOD, NSTR, 1,	"<sp> mode <sp> file-name" },
1058	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
1059	{ NULL,   0,    0,    0,	0 }
1060};
1061
1062static void	 help(struct tab *, char *);
1063static struct tab *
1064		 lookup(struct tab *, char *);
1065static void	 sizecmd(char *);
1066static int	 yylex(void);
1067
1068extern int epsvall;
1069
1070static struct tab *
1071lookup(p, cmd)
1072	struct tab *p;
1073	char *cmd;
1074{
1075
1076	for (; p->name != NULL; p++)
1077		if (strcmp(cmd, p->name) == 0)
1078			return (p);
1079	return (NULL);
1080}
1081
1082#include <arpa/telnet.h>
1083
1084/*
1085 * getline - a hacked up version of fgets to ignore TELNET escape codes.
1086 */
1087char *
1088getline(s, n, iop)
1089	char *s;
1090	int n;
1091	FILE *iop;
1092{
1093	int c;
1094	char *cs;
1095
1096	cs = s;
1097/* tmpline may contain saved command from urgent mode interruption */
1098	for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
1099		*cs++ = tmpline[c];
1100		if (tmpline[c] == '\n') {
1101			*cs++ = '\0';
1102			if (debug)
1103				syslog(LOG_DEBUG, "command: %s", s);
1104			tmpline[0] = '\0';
1105			return(s);
1106		}
1107		if (c == 0)
1108			tmpline[0] = '\0';
1109	}
1110	while ((c = getc(iop)) != EOF) {
1111		c &= 0377;
1112		if (c == IAC) {
1113		    if ((c = getc(iop)) != EOF) {
1114			c &= 0377;
1115			switch (c) {
1116			case WILL:
1117			case WONT:
1118				c = getc(iop);
1119				printf("%c%c%c", IAC, DONT, 0377&c);
1120				(void) fflush(stdout);
1121				continue;
1122			case DO:
1123			case DONT:
1124				c = getc(iop);
1125				printf("%c%c%c", IAC, WONT, 0377&c);
1126				(void) fflush(stdout);
1127				continue;
1128			case IAC:
1129				break;
1130			default:
1131				continue;	/* ignore command */
1132			}
1133		    }
1134		}
1135		*cs++ = c;
1136		if (--n <= 0 || c == '\n')
1137			break;
1138	}
1139	if (c == EOF && cs == s)
1140		return (NULL);
1141	*cs++ = '\0';
1142	if (debug) {
1143		if (!guest && strncasecmp("pass ", s, 5) == 0) {
1144			/* Don't syslog passwords */
1145			syslog(LOG_DEBUG, "command: %.5s ???", s);
1146		} else {
1147			char *cp;
1148			int len;
1149
1150			/* Don't syslog trailing CR-LF */
1151			len = strlen(s);
1152			cp = s + len - 1;
1153			while (cp >= s && (*cp == '\n' || *cp == '\r')) {
1154				--cp;
1155				--len;
1156			}
1157			syslog(LOG_DEBUG, "command: %.*s", len, s);
1158		}
1159	}
1160	return (s);
1161}
1162
1163void
1164toolong(signo)
1165	int signo;
1166{
1167	struct syslog_data sdata = SYSLOG_DATA_INIT;
1168
1169	/* XXX signal races */
1170	reply(421,
1171	    "Timeout (%d seconds): closing control connection.", timeout);
1172	if (logging)
1173		syslog_r(LOG_INFO, &sdata, "User %s timed out after %d seconds",
1174		    (pw ? pw -> pw_name : "unknown"), timeout);
1175	dologout(1);
1176}
1177
1178static int
1179yylex()
1180{
1181	static int cpos;
1182	char *cp, *cp2;
1183	struct tab *p;
1184	int n;
1185	char c;
1186
1187	for (;;) {
1188		switch (state) {
1189
1190		case CMD:
1191			(void) alarm((unsigned) timeout);
1192			if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
1193				reply(221, "You could at least say goodbye.");
1194				dologout(0);
1195			}
1196			(void) alarm(0);
1197			if ((cp = strchr(cbuf, '\r'))) {
1198				*cp++ = '\n';
1199				*cp = '\0';
1200			}
1201#ifdef HASSETPROCTITLE
1202			if (strncasecmp(cbuf, "PASS", 4) != 0) {
1203				if ((cp = strpbrk(cbuf, "\n"))) {
1204					c = *cp;
1205					*cp = '\0';
1206					setproctitle("%s: %s", proctitle, cbuf);
1207					*cp = c;
1208				}
1209			}
1210#endif /* HASSETPROCTITLE */
1211			if ((cp = strpbrk(cbuf, " \n")))
1212				cpos = cp - cbuf;
1213			if (cpos == 0)
1214				cpos = 4;
1215			c = cbuf[cpos];
1216			cbuf[cpos] = '\0';
1217			upper(cbuf);
1218			p = lookup(cmdtab, cbuf);
1219			cbuf[cpos] = c;
1220			if (p != NULL) {
1221				if (p->implemented == 0) {
1222					nack(p->name);
1223					return (LEXERR);
1224				}
1225				state = p->state;
1226				yylval.s = p->name;
1227				return (p->token);
1228			}
1229			break;
1230
1231		case SITECMD:
1232			if (cbuf[cpos] == ' ') {
1233				cpos++;
1234				return (SP);
1235			}
1236			cp = &cbuf[cpos];
1237			if ((cp2 = strpbrk(cp, " \n")))
1238				cpos = cp2 - cbuf;
1239			c = cbuf[cpos];
1240			cbuf[cpos] = '\0';
1241			upper(cp);
1242			p = lookup(sitetab, cp);
1243			cbuf[cpos] = c;
1244			if (p != NULL) {
1245				if (p->implemented == 0) {
1246					state = CMD;
1247					nack(p->name);
1248					return (LEXERR);
1249				}
1250				state = p->state;
1251				yylval.s = p->name;
1252				return (p->token);
1253			}
1254			state = CMD;
1255			break;
1256
1257		case OSTR:
1258			if (cbuf[cpos] == '\n') {
1259				state = CMD;
1260				return (CRLF);
1261			}
1262			/* FALLTHROUGH */
1263
1264		case STR1:
1265		case ZSTR1:
1266		dostr1:
1267			if (cbuf[cpos] == ' ') {
1268				cpos++;
1269				state = state == OSTR ? STR2 : state+1;
1270				return (SP);
1271			}
1272			break;
1273
1274		case ZSTR2:
1275			if (cbuf[cpos] == '\n') {
1276				state = CMD;
1277				return (CRLF);
1278			}
1279			/* FALLTHROUGH */
1280
1281		case STR2:
1282			cp = &cbuf[cpos];
1283			n = strlen(cp);
1284			cpos += n - 1;
1285			/*
1286			 * Make sure the string is nonempty and \n terminated.
1287			 */
1288			if (n > 1 && cbuf[cpos] == '\n') {
1289				cbuf[cpos] = '\0';
1290				yylval.s = strdup(cp);
1291				if (yylval.s == NULL)
1292					fatal("Ran out of memory.");
1293				cbuf[cpos] = '\n';
1294				state = ARGS;
1295				return (STRING);
1296			}
1297			break;
1298
1299		case NSTR:
1300			if (cbuf[cpos] == ' ') {
1301				cpos++;
1302				return (SP);
1303			}
1304			if (isdigit(cbuf[cpos])) {
1305				cp = &cbuf[cpos];
1306				while (isdigit(cbuf[++cpos]))
1307					;
1308				c = cbuf[cpos];
1309				cbuf[cpos] = '\0';
1310				yylval.i = atoi(cp);
1311				cbuf[cpos] = c;
1312				state = STR1;
1313				return (NUMBER);
1314			}
1315			state = STR1;
1316			goto dostr1;
1317
1318		case ARGS:
1319			if (isdigit(cbuf[cpos])) {
1320				cp = &cbuf[cpos];
1321				while (isdigit(cbuf[++cpos]))
1322					;
1323				c = cbuf[cpos];
1324				cbuf[cpos] = '\0';
1325				yylval.i = atoi(cp);
1326				cbuf[cpos] = c;
1327				return (NUMBER);
1328			}
1329			if (strncasecmp(&cbuf[cpos], "ALL", 3) == 0
1330			 && !isalnum(cbuf[cpos + 3])) {
1331				cpos += 3;
1332				return ALL;
1333			}
1334			switch (cbuf[cpos++]) {
1335
1336			case '\n':
1337				state = CMD;
1338				return (CRLF);
1339
1340			case ' ':
1341				return (SP);
1342
1343			case ',':
1344				return (COMMA);
1345
1346			case 'A':
1347			case 'a':
1348				return (A);
1349
1350			case 'B':
1351			case 'b':
1352				return (B);
1353
1354			case 'C':
1355			case 'c':
1356				return (C);
1357
1358			case 'E':
1359			case 'e':
1360				return (E);
1361
1362			case 'F':
1363			case 'f':
1364				return (F);
1365
1366			case 'I':
1367			case 'i':
1368				return (I);
1369
1370			case 'L':
1371			case 'l':
1372				return (L);
1373
1374			case 'N':
1375			case 'n':
1376				return (N);
1377
1378			case 'P':
1379			case 'p':
1380				return (P);
1381
1382			case 'R':
1383			case 'r':
1384				return (R);
1385
1386			case 'S':
1387			case 's':
1388				return (S);
1389
1390			case 'T':
1391			case 't':
1392				return (T);
1393
1394			}
1395			break;
1396
1397		default:
1398			fatal("Unknown state in scanner.");
1399		}
1400		state = CMD;
1401		return (LEXERR);
1402	}
1403}
1404
1405void
1406upper(s)
1407	char *s;
1408{
1409	char *p;
1410
1411	for (p = s; *p; p++) {
1412		if (islower(*p))
1413			*p = toupper(*p);
1414	}
1415}
1416
1417static void
1418help(ctab, s)
1419	struct tab *ctab;
1420	char *s;
1421{
1422	struct tab *c;
1423	int width, NCMDS;
1424	char *type;
1425
1426	if (ctab == sitetab)
1427		type = "SITE ";
1428	else
1429		type = "";
1430	width = 0, NCMDS = 0;
1431	for (c = ctab; c->name != NULL; c++) {
1432		int len = strlen(c->name);
1433
1434		if (len > width)
1435			width = len;
1436		NCMDS++;
1437	}
1438	width = (width + 8) &~ 7;
1439	if (s == NULL) {
1440		int i, j, w;
1441		int columns, lines;
1442
1443		lreply(214, "The following %scommands are recognized %s.",
1444		    type, "(* =>'s unimplemented)");
1445		columns = 76 / width;
1446		if (columns == 0)
1447			columns = 1;
1448		lines = (NCMDS + columns - 1) / columns;
1449		for (i = 0; i < lines; i++) {
1450			printf("   ");
1451			for (j = 0; j < columns; j++) {
1452				c = ctab + j * lines + i;
1453				printf("%s%c", c->name,
1454					c->implemented ? ' ' : '*');
1455				if (c + lines >= &ctab[NCMDS])
1456					break;
1457				w = strlen(c->name) + 1;
1458				while (w < width) {
1459					putchar(' ');
1460					w++;
1461				}
1462			}
1463			printf("\r\n");
1464		}
1465		(void) fflush(stdout);
1466		reply(214, "Direct comments to ftp-bugs@%s.", hostname);
1467		return;
1468	}
1469	upper(s);
1470	c = lookup(ctab, s);
1471	if (c == NULL) {
1472		reply(502, "Unknown command %s.", s);
1473		return;
1474	}
1475	if (c->implemented)
1476		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
1477	else
1478		reply(214, "%s%-*s\t%s; unimplemented.", type, width,
1479		    c->name, c->help);
1480}
1481
1482static void
1483sizecmd(filename)
1484	char *filename;
1485{
1486	switch (type) {
1487	case TYPE_L:
1488	case TYPE_I: {
1489		struct stat stbuf;
1490		if (stat(filename, &stbuf) < 0 || !S_ISREG(stbuf.st_mode))
1491			reply(550, "%s: not a plain file.", filename);
1492		else
1493			reply(213, "%qu", stbuf.st_size);
1494		break; }
1495	case TYPE_A: {
1496		FILE *fin;
1497		int c;
1498		off_t count;
1499		struct stat stbuf;
1500		fin = fopen(filename, "r");
1501		if (fin == NULL) {
1502			perror_reply(550, filename);
1503			return;
1504		}
1505		if (fstat(fileno(fin), &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) {
1506			reply(550, "%s: not a plain file.", filename);
1507			(void) fclose(fin);
1508			return;
1509		}
1510		if (stbuf.st_size > 10240) {
1511			reply(550, "%s: file too large for SIZE.", filename);
1512			(void) fclose(fin);
1513			return;
1514		}
1515
1516		count = 0;
1517		while((c = getc(fin)) != EOF) {
1518			if (c == '\n')	/* will get expanded to \r\n */
1519				count++;
1520			count++;
1521		}
1522		(void) fclose(fin);
1523
1524		reply(213, "%qd", count);
1525		break; }
1526	default:
1527		reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
1528	}
1529}
1530