xref: /csrg-svn/libexec/ftpd/ftpcmd.y (revision 11652)
1 /*
2  * Grammar for FTP commands.
3  * See RFC 765.
4  */
5 
6 %{
7 
8 #ifndef lint
9 static char sccsid[] = "@(#)ftpcmd.y	4.9 83/03/23";
10 #endif
11 
12 #include <sys/types.h>
13 #include <sys/socket.h>
14 
15 #include <netinet/in.h>
16 
17 #include <stdio.h>
18 #include <signal.h>
19 #include <ctype.h>
20 #include <pwd.h>
21 #include <setjmp.h>
22 #include "ftp.h"
23 
24 extern	struct sockaddr_in data_dest;
25 extern	int logged_in;
26 extern	struct passwd *pw;
27 extern	int guest;
28 extern	int logging;
29 extern	int type;
30 extern	int form;
31 extern	int debug;
32 extern	int timeout;
33 extern	char hostname[];
34 extern	char *globerr;
35 extern	int usedefault;
36 char	**glob();
37 
38 static	int cmd_type;
39 static	int cmd_form;
40 static	int cmd_bytesz;
41 
42 char	*index();
43 %}
44 
45 %token
46 	A	B	C	E	F	I
47 	L	N	P	R	S	T
48 
49 	SP	CRLF	COMMA	STRING	NUMBER
50 
51 	USER	PASS	ACCT	REIN	QUIT	PORT
52 	PASV	TYPE	STRU	MODE	RETR	STOR
53 	APPE	MLFL	MAIL	MSND	MSOM	MSAM
54 	MRSQ	MRCP	ALLO	REST	RNFR	RNTO
55 	ABOR	DELE	CWD	LIST	NLST	SITE
56 	STAT	HELP	NOOP	XMKD	XRMD	XPWD
57 	XCUP
58 
59 	LEXERR
60 
61 %start	cmd_list
62 
63 %%
64 
65 cmd_list:	/* empty */
66 	|	cmd_list cmd
67 	;
68 
69 cmd:		USER SP username CRLF
70 		= {
71 			extern struct passwd *getpwnam();
72 
73 			if (strcmp($3, "ftp") == 0 ||
74 			  strcmp($3, "anonymous") == 0) {
75 				if ((pw = getpwnam("ftp")) != NULL) {
76 					guest = 1;
77 					reply(331,
78 				  "Guest login ok, send ident as password.");
79 				}
80 			} else if (checkuser($3)) {
81 				guest = 0;
82 				pw = getpwnam($3);
83 				reply(331, "Password required for %s.", $3);
84 			}
85 			if (pw == NULL)
86 				reply(530, "User %s unknown.", $3);
87 			free($3);
88 		}
89 	|	PASS SP password CRLF
90 		= {
91 			pass($3);
92 			free($3);
93 		}
94 	|	PORT SP host_port CRLF
95 		= {
96 			usedefault = 0;
97 			ack($1);
98 		}
99 	|	TYPE SP type_code CRLF
100 		= {
101 			switch (cmd_type) {
102 
103 			case TYPE_A:
104 				if (cmd_form == FORM_N) {
105 					reply(200, "Type set to A.");
106 					type = cmd_type;
107 					form = cmd_form;
108 				} else
109 					reply(504, "Form must be N.");
110 				break;
111 
112 			case TYPE_E:
113 				reply(504, "Type E not implemented.");
114 				break;
115 
116 			case TYPE_I:
117 				reply(200, "Type set to I.");
118 				type = cmd_type;
119 				break;
120 
121 			case TYPE_L:
122 				if (cmd_bytesz == 8) {
123 					reply(200,
124 					    "Type set to L (byte size 8).");
125 					type = cmd_type;
126 				} else
127 					reply(504, "Byte size must be 8.");
128 			}
129 		}
130 	|	STRU SP struct_code CRLF
131 		= {
132 			switch ($3) {
133 
134 			case STRU_F:
135 				reply(200, "STRU F ok.");
136 				break;
137 
138 			default:
139 				reply(502, "Unimplemented STRU type.");
140 			}
141 		}
142 	|	MODE SP mode_code CRLF
143 		= {
144 			switch ($3) {
145 
146 			case MODE_S:
147 				reply(200, "MODE S ok.");
148 				break;
149 
150 			default:
151 				reply(502, "Unimplemented MODE type.");
152 			}
153 		}
154 	|	ALLO SP NUMBER CRLF
155 		= {
156 			ack($1);
157 		}
158 	|	RETR check_login SP pathname CRLF
159 		= {
160 			if ($2 && $4 != NULL)
161 				retrieve(0, $4);
162 			if ($4 != NULL)
163 				free($4);
164 		}
165 	|	STOR check_login SP pathname CRLF
166 		= {
167 			if ($2 && $4 != NULL)
168 				store($4, "w");
169 			if ($4 != NULL)
170 				free($4);
171 		}
172 	|	APPE check_login SP pathname CRLF
173 		= {
174 			if ($2 && $4 != NULL)
175 				store($4, "a");
176 			if ($4 != NULL)
177 				free($4);
178 		}
179 	|	NLST check_login CRLF
180 		= {
181 			if ($2)
182 				retrieve("/bin/ls", "");
183 		}
184 	|	NLST check_login SP pathname CRLF
185 		= {
186 			if ($2 && $4 != NULL)
187 				retrieve("/bin/ls %s", $4);
188 			if ($4 != NULL)
189 				free($4);
190 		}
191 	|	LIST check_login CRLF
192 		= {
193 			if ($2)
194 				retrieve("/bin/ls -lg", "");
195 		}
196 	|	LIST check_login SP pathname CRLF
197 		= {
198 			if ($2 && $4 != NULL)
199 				retrieve("/bin/ls -lg %s", $4);
200 			if ($4 != NULL)
201 				free($4);
202 		}
203 	|	DELE check_login SP pathname CRLF
204 		= {
205 			if ($2 && $4 != NULL)
206 				delete($4);
207 			if ($4 != NULL)
208 				free($4);
209 		}
210 	|	CWD check_login CRLF
211 		= {
212 			if ($2)
213 				cwd(pw->pw_dir);
214 		}
215 	|	CWD check_login SP pathname CRLF
216 		= {
217 			if ($2 && $4 != NULL)
218 				cwd($4);
219 			if ($4 != NULL)
220 				free($4);
221 		}
222 	|	rename_cmd
223 	|	HELP CRLF
224 		= {
225 			help(0);
226 		}
227 	|	HELP SP STRING CRLF
228 		= {
229 			help($3);
230 		}
231 	|	NOOP CRLF
232 		= {
233 			ack($1);
234 		}
235 	|	XMKD check_login SP pathname CRLF
236 		= {
237 			if ($2 && $4 != NULL)
238 				makedir($4);
239 			if ($4 != NULL)
240 				free($4);
241 		}
242 	|	XRMD check_login SP pathname CRLF
243 		= {
244 			if ($2 && $4 != NULL)
245 				removedir($4);
246 			if ($4 != NULL)
247 				free($4);
248 		}
249 	|	XPWD check_login CRLF
250 		= {
251 			if ($2)
252 				pwd();
253 		}
254 	|	XCUP check_login CRLF
255 		= {
256 			if ($2)
257 				cwd("..");
258 		}
259 	|	QUIT CRLF
260 		= {
261 			reply(221, "Goodbye.");
262 			exit(0);
263 		}
264 	|	error CRLF
265 		= {
266 			yyerrok;
267 		}
268 	;
269 
270 username:	STRING
271 	;
272 
273 password:	STRING
274 	;
275 
276 byte_size:	NUMBER
277 	;
278 
279 host_port:	NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
280 		NUMBER COMMA NUMBER
281 		= {
282 			register char *a, *p;
283 
284 			a = (char *)&data_dest.sin_addr;
285 			a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7;
286 			p = (char *)&data_dest.sin_port;
287 			p[0] = $9; p[1] = $11;
288 			data_dest.sin_family = AF_INET;
289 		}
290 	;
291 
292 form_code:	N
293 	= {
294 		$$ = FORM_N;
295 	}
296 	|	T
297 	= {
298 		$$ = FORM_T;
299 	}
300 	|	C
301 	= {
302 		$$ = FORM_C;
303 	}
304 	;
305 
306 type_code:	A
307 	= {
308 		cmd_type = TYPE_A;
309 		cmd_form = FORM_N;
310 	}
311 	|	A SP form_code
312 	= {
313 		cmd_type = TYPE_A;
314 		cmd_form = $3;
315 	}
316 	|	E
317 	= {
318 		cmd_type = TYPE_E;
319 		cmd_form = FORM_N;
320 	}
321 	|	E SP form_code
322 	= {
323 		cmd_type = TYPE_E;
324 		cmd_form = $3;
325 	}
326 	|	I
327 	= {
328 		cmd_type = TYPE_I;
329 	}
330 	|	L
331 	= {
332 		cmd_type = TYPE_L;
333 		cmd_bytesz = 8;
334 	}
335 	|	L SP byte_size
336 	= {
337 		cmd_type = TYPE_L;
338 		cmd_bytesz = $3;
339 	}
340 	/* this is for a bug in the BBN ftp */
341 	|	L byte_size
342 	= {
343 		cmd_type = TYPE_L;
344 		cmd_bytesz = $2;
345 	}
346 	;
347 
348 struct_code:	F
349 	= {
350 		$$ = STRU_F;
351 	}
352 	|	R
353 	= {
354 		$$ = STRU_R;
355 	}
356 	|	P
357 	= {
358 		$$ = STRU_P;
359 	}
360 	;
361 
362 mode_code:	S
363 	= {
364 		$$ = MODE_S;
365 	}
366 	|	B
367 	= {
368 		$$ = MODE_B;
369 	}
370 	|	C
371 	= {
372 		$$ = MODE_C;
373 	}
374 	;
375 
376 pathname:	pathstring
377 	= {
378 		if ($1 && strncmp($1, "~", 1) == 0) {
379 			$$ = (int)*glob($1);
380 			if (globerr != NULL) {
381 				reply(550, globerr);
382 				$$ = NULL;
383 			}
384 			free($1);
385 		} else
386 			$$ = $1;
387 	}
388 	;
389 
390 pathstring:	STRING
391 	;
392 
393 rename_cmd:	rename_from rename_to
394 	= {
395 		if ($1 && $2)
396 			renamecmd($1, $2);
397 		else
398 			reply(503, "Bad sequence of commands.");
399 		if ($1)
400 			free($1);
401 		if ($2)
402 			free($2);
403 	}
404 	;
405 
406 rename_from:	RNFR check_login SP pathname CRLF
407 	= {
408 		char *from = 0, *renamefrom();
409 
410 		if ($2 && $4)
411 			from = renamefrom($4);
412 		if (from == 0 && $4)
413 			free($4);
414 		$$ = (int)from;
415 	}
416 	;
417 
418 rename_to:	RNTO SP pathname CRLF
419 	= {
420 		$$ = $3;
421 	}
422 	;
423 
424 check_login:	/* empty */
425 	= {
426 		if (logged_in)
427 			$$ = 1;
428 		else {
429 			reply(530, "Please login with USER and PASS.");
430 			$$ = 0;
431 		}
432 	}
433 	;
434 
435 %%
436 
437 extern jmp_buf errcatch;
438 
439 #define	CMD	0	/* beginning of command */
440 #define	ARGS	1	/* expect miscellaneous arguments */
441 #define	STR1	2	/* expect SP followed by STRING */
442 #define	STR2	3	/* expect STRING */
443 #define	OSTR	4	/* optional STRING */
444 
445 struct tab {
446 	char	*name;
447 	short	token;
448 	short	state;
449 	short	implemented;	/* 1 if command is implemented */
450 	char	*help;
451 };
452 
453 struct tab cmdtab[] = {		/* In order defined in RFC 765 */
454 	{ "USER", USER, STR1, 1,	"<sp> username" },
455 	{ "PASS", PASS, STR1, 1,	"<sp> password" },
456 	{ "ACCT", ACCT, STR1, 0,	"(specify account)" },
457 	{ "REIN", REIN, ARGS, 0,	"(reinitialize server state)" },
458 	{ "QUIT", QUIT, ARGS, 1,	"(terminate service)", },
459 	{ "PORT", PORT, ARGS, 1,	"<sp> b0, b1, b2, b3, b4" },
460 	{ "PASV", PASV, ARGS, 0,	"(set server in passive mode)" },
461 	{ "TYPE", TYPE, ARGS, 1,	"<sp> [ A | E | I | L ]" },
462 	{ "STRU", STRU, ARGS, 1,	"(specify file structure)" },
463 	{ "MODE", MODE, ARGS, 1,	"(specify transfer mode)" },
464 	{ "RETR", RETR, STR1, 1,	"<sp> file-name" },
465 	{ "STOR", STOR, STR1, 1,	"<sp> file-name" },
466 	{ "APPE", APPE, STR1, 1,	"<sp> file-name" },
467 	{ "MLFL", MLFL, OSTR, 0,	"(mail file)" },
468 	{ "MAIL", MAIL, OSTR, 0,	"(mail to user)" },
469 	{ "MSND", MSND, OSTR, 0,	"(mail send to terminal)" },
470 	{ "MSOM", MSOM, OSTR, 0,	"(mail send to terminal or mailbox)" },
471 	{ "MSAM", MSAM, OSTR, 0,	"(mail send to terminal and mailbox)" },
472 	{ "MRSQ", MRSQ, OSTR, 0,	"(mail recipient scheme question)" },
473 	{ "MRCP", MRCP, STR1, 0,	"(mail recipient)" },
474 	{ "ALLO", ALLO, ARGS, 1,	"allocate storage (vacuously)" },
475 	{ "REST", REST, STR1, 0,	"(restart command)" },
476 	{ "RNFR", RNFR, STR1, 1,	"<sp> file-name" },
477 	{ "RNTO", RNTO, STR1, 1,	"<sp> file-name" },
478 	{ "ABOR", ABOR, ARGS, 0,	"(abort operation)" },
479 	{ "DELE", DELE, STR1, 1,	"<sp> file-name" },
480 	{ "CWD",  CWD,  OSTR, 1,	"[ <sp> directory-name]" },
481 	{ "XCWD", CWD,	OSTR, 1,	"[ <sp> directory-name ]" },
482 	{ "LIST", LIST, OSTR, 1,	"[ <sp> path-name ]" },
483 	{ "NLST", NLST, OSTR, 1,	"[ <sp> path-name ]" },
484 	{ "SITE", SITE, STR1, 0,	"(get site parameters)" },
485 	{ "STAT", STAT, OSTR, 0,	"(get server status)" },
486 	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
487 	{ "NOOP", NOOP, ARGS, 1,	"" },
488 	{ "XMKD", XMKD, STR1, 1,	"<sp> path-name" },
489 	{ "XRMD", XRMD, STR1, 1,	"<sp> path-name" },
490 	{ "XPWD", XPWD, ARGS, 1,	"(return current directory)" },
491 	{ "XCUP", XCUP, ARGS, 1,	"(change to parent directory)" },
492 	{ NULL,   0,    0,    0,	0 }
493 };
494 
495 struct tab *
496 lookup(cmd)
497 	char *cmd;
498 {
499 	register struct tab *p;
500 
501 	for (p = cmdtab; p->name != NULL; p++)
502 		if (strcmp(cmd, p->name) == 0)
503 			return (p);
504 	return (0);
505 }
506 
507 #include "../telnet/telnet.h"
508 
509 /*
510  * getline - a hacked up version of fgets to ignore TELNET escape codes.
511  */
512 char *
513 getline(s, n, iop)
514 	char *s;
515 	register FILE *iop;
516 {
517 	register c;
518 	register char *cs;
519 
520 	cs = s;
521 	while (--n > 0 && (c = getc(iop)) >= 0) {
522 		while (c == IAC) {
523 			c = getc(iop);	/* skip command */
524 			c = getc(iop);	/* try next char */
525 		}
526 		*cs++ = c;
527 		if (c=='\n')
528 			break;
529 	}
530 	if (c < 0 && cs == s)
531 		return (NULL);
532 	*cs++ = '\0';
533 	if (debug) {
534 		fprintf(stderr, "FTPD: command: %s", s);
535 		if (c != '\n')
536 			putc('\n', stderr);
537 		fflush(stderr);
538 	}
539 	return (s);
540 }
541 
542 static int
543 toolong()
544 {
545 	long now;
546 	extern char *ctime();
547 
548 	reply(421,
549 	  "Timeout (%d seconds): closing control connection.", timeout);
550 	time(&now);
551 	if (logging) {
552 		fprintf(stderr,
553 			"FTPD: User %s timed out after %d seconds at %s",
554 			(pw ? pw -> pw_name : "unknown"), timeout, ctime(&now));
555 		fflush(stderr);
556 	}
557 	exit(1);
558 }
559 
560 yylex()
561 {
562 	static char cbuf[512];
563 	static int cpos, state;
564 	register char *cp;
565 	register struct tab *p;
566 	int n;
567 	char c;
568 
569 	for (;;) {
570 		switch (state) {
571 
572 		case CMD:
573 			signal(SIGALRM, toolong);
574 			alarm(timeout);
575 			if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
576 				reply(221, "You could at least say goodbye.");
577 				exit(0);
578 			}
579 			alarm(0);
580 			if (index(cbuf, '\r')) {
581 				cp = index(cbuf, '\r');
582 				cp[0] = '\n'; cp[1] = 0;
583 			}
584 			if (index(cbuf, ' '))
585 				cpos = index(cbuf, ' ') - cbuf;
586 			else
587 				cpos = 4;
588 			c = cbuf[cpos];
589 			cbuf[cpos] = '\0';
590 			upper(cbuf);
591 			p = lookup(cbuf);
592 			cbuf[cpos] = c;
593 			if (p != 0) {
594 				if (p->implemented == 0) {
595 					nack(p->name);
596 					longjmp(errcatch);
597 					/* NOTREACHED */
598 				}
599 				state = p->state;
600 				yylval = (int) p->name;
601 				return (p->token);
602 			}
603 			break;
604 
605 		case OSTR:
606 			if (cbuf[cpos] == '\n') {
607 				state = CMD;
608 				return (CRLF);
609 			}
610 			/* FALL THRU */
611 
612 		case STR1:
613 			if (cbuf[cpos] == ' ') {
614 				cpos++;
615 				state = STR2;
616 				return (SP);
617 			}
618 			break;
619 
620 		case STR2:
621 			cp = &cbuf[cpos];
622 			n = strlen(cp);
623 			cpos += n - 1;
624 			/*
625 			 * Make sure the string is nonempty and \n terminated.
626 			 */
627 			if (n > 1 && cbuf[cpos] == '\n') {
628 				cbuf[cpos] = '\0';
629 				yylval = copy(cp);
630 				cbuf[cpos] = '\n';
631 				state = ARGS;
632 				return (STRING);
633 			}
634 			break;
635 
636 		case ARGS:
637 			if (isdigit(cbuf[cpos])) {
638 				cp = &cbuf[cpos];
639 				while (isdigit(cbuf[++cpos]))
640 					;
641 				c = cbuf[cpos];
642 				cbuf[cpos] = '\0';
643 				yylval = atoi(cp);
644 				cbuf[cpos] = c;
645 				return (NUMBER);
646 			}
647 			switch (cbuf[cpos++]) {
648 
649 			case '\n':
650 				state = CMD;
651 				return (CRLF);
652 
653 			case ' ':
654 				return (SP);
655 
656 			case ',':
657 				return (COMMA);
658 
659 			case 'A':
660 			case 'a':
661 				return (A);
662 
663 			case 'B':
664 			case 'b':
665 				return (B);
666 
667 			case 'C':
668 			case 'c':
669 				return (C);
670 
671 			case 'E':
672 			case 'e':
673 				return (E);
674 
675 			case 'F':
676 			case 'f':
677 				return (F);
678 
679 			case 'I':
680 			case 'i':
681 				return (I);
682 
683 			case 'L':
684 			case 'l':
685 				return (L);
686 
687 			case 'N':
688 			case 'n':
689 				return (N);
690 
691 			case 'P':
692 			case 'p':
693 				return (P);
694 
695 			case 'R':
696 			case 'r':
697 				return (R);
698 
699 			case 'S':
700 			case 's':
701 				return (S);
702 
703 			case 'T':
704 			case 't':
705 				return (T);
706 
707 			}
708 			break;
709 
710 		default:
711 			fatal("Unknown state in scanner.");
712 		}
713 		yyerror();
714 		state = CMD;
715 		longjmp(errcatch);
716 	}
717 }
718 
719 upper(s)
720 	char *s;
721 {
722 	while (*s != '\0') {
723 		if (islower(*s))
724 			*s = toupper(*s);
725 		s++;
726 	}
727 }
728 
729 copy(s)
730 	char *s;
731 {
732 	char *p;
733 	extern char *malloc();
734 
735 	p = malloc(strlen(s) + 1);
736 	if (p == NULL)
737 		fatal("Ran out of memory.");
738 	strcpy(p, s);
739 	return ((int)p);
740 }
741 
742 help(s)
743 	char *s;
744 {
745 	register struct tab *c;
746 	register int width, NCMDS;
747 
748 	width = 0, NCMDS = 0;
749 	for (c = cmdtab; c->name != NULL; c++) {
750 		int len = strlen(c->name);
751 
752 		if (c->implemented == 0)
753 			len++;
754 		if (len > width)
755 			width = len;
756 		NCMDS++;
757 	}
758 	width = (width + 8) &~ 7;
759 	if (s == 0) {
760 		register int i, j, w;
761 		int columns, lines;
762 
763 		lreply(214,
764 	  "The following commands are recognized (* =>'s unimplemented).");
765 		columns = 76 / width;
766 		if (columns == 0)
767 			columns = 1;
768 		lines = (NCMDS + columns - 1) / columns;
769 		for (i = 0; i < lines; i++) {
770 			printf("    ");
771 			for (j = 0; j < columns; j++) {
772 				c = cmdtab + j * lines + i;
773 				printf("%s%c", c->name,
774 					c->implemented ? ' ' : '*');
775 				if (c + lines >= &cmdtab[NCMDS])
776 					break;
777 				w = strlen(c->name);
778 				while (w < width) {
779 					putchar(' ');
780 					w++;
781 				}
782 			}
783 			printf("\r\n");
784 		}
785 		fflush(stdout);
786 		reply(214, "Direct comments to ftp-bugs@%s.", hostname);
787 		return;
788 	}
789 	upper(s);
790 	c = lookup(s);
791 	if (c == (struct tab *)0) {
792 		reply(504, "Unknown command %s.", s);
793 		return;
794 	}
795 	if (c->implemented)
796 		reply(214, "Syntax: %s %s", c->name, c->help);
797 	else
798 		reply(214, "%-*s\t%s; unimplemented.", width, c->name, c->help);
799 }
800