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