xref: /freebsd/libexec/ftpd/ftpcmd.y (revision df7f5d4de4592a8948a25ce01e5bddfbb7ce39dc)
1 /*
2  * Copyright (c) 1985, 1988, 1993, 1994
3  *	The Regents of the University of California.  All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. All advertising materials mentioning features or use of this software
14  *    must display the following acknowledgement:
15  *	This product includes software developed by the University of
16  *	California, Berkeley and its contributors.
17  * 4. Neither the name of the University nor the names of its contributors
18  *    may be used to endorse or promote products derived from this software
19  *    without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  *
33  *	@(#)ftpcmd.y	8.3 (Berkeley) 4/6/94
34  *	$Id$
35  */
36 
37 /*
38  * Grammar for FTP commands.
39  * See RFC 959.
40  */
41 
42 %{
43 
44 #ifndef lint
45 static char sccsid[] = "@(#)ftpcmd.y	8.3 (Berkeley) 4/6/94";
46 #endif /* not lint */
47 
48 #include <sys/param.h>
49 #include <sys/socket.h>
50 #include <sys/stat.h>
51 
52 #include <netinet/in.h>
53 #include <arpa/ftp.h>
54 
55 #include <ctype.h>
56 #include <errno.h>
57 #include <glob.h>
58 #include <pwd.h>
59 #include <setjmp.h>
60 #include <signal.h>
61 #include <stdio.h>
62 #include <stdlib.h>
63 #include <string.h>
64 #include <syslog.h>
65 #include <time.h>
66 #include <unistd.h>
67 #include <libutil.h>
68 
69 #include "extern.h"
70 
71 extern	struct sockaddr_in data_dest, his_addr;
72 extern	int logged_in;
73 extern	struct passwd *pw;
74 extern	int guest;
75 extern 	int paranoid;
76 extern	int logging;
77 extern	int type;
78 extern	int form;
79 extern	int debug;
80 extern	int timeout;
81 extern	int maxtimeout;
82 extern  int pdata;
83 extern	char hostname[], remotehost[];
84 extern	char proctitle[];
85 extern	int usedefault;
86 extern  int transflag;
87 extern  char tmpline[];
88 
89 off_t	restart_point;
90 
91 static	int cmd_type;
92 static	int cmd_form;
93 static	int cmd_bytesz;
94 char	cbuf[512];
95 char	*fromname;
96 
97 %}
98 
99 %union {
100 	int	i;
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
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 	UMASK	IDLE	CHMOD
119 
120 	LEXERR
121 
122 %token	<s> STRING
123 %token	<i> NUMBER
124 
125 %type	<i> check_login octal_number byte_size
126 %type	<i> struct_code mode_code type_code form_code
127 %type	<s> pathstring pathname password username
128 
129 %start	cmd_list
130 
131 %%
132 
133 cmd_list
134 	: /* empty */
135 	| cmd_list cmd
136 		{
137 			fromname = (char *) 0;
138 			restart_point = (off_t) 0;
139 		}
140 	| cmd_list rcmd
141 	;
142 
143 cmd
144 	: USER SP username CRLF
145 		{
146 			user($3);
147 			free($3);
148 		}
149 	| PASS SP password CRLF
150 		{
151 			pass($3);
152 			free($3);
153 		}
154 	| PORT check_login SP host_port CRLF
155 		{
156 			if ($2) {
157 				if (paranoid &&
158 				    ((ntohs(data_dest.sin_port) <
159 				      IPPORT_RESERVED) ||
160 				     memcmp(&data_dest.sin_addr,
161 					    &his_addr.sin_addr,
162 					    sizeof(data_dest.sin_addr)))) {
163 					usedefault = 1;
164 					reply(500,
165 					      "Illegal PORT range rejected.");
166 				} else {
167 					usedefault = 0;
168 					if (pdata >= 0) {
169 						(void) close(pdata);
170 						pdata = -1;
171 					}
172 					reply(200, "PORT command successful.");
173 				}
174 			}
175 		}
176 	| PASV check_login CRLF
177 		{
178 			if ($2)
179 				passive();
180 		}
181 	| TYPE SP type_code CRLF
182 		{
183 			switch (cmd_type) {
184 
185 			case TYPE_A:
186 				if (cmd_form == FORM_N) {
187 					reply(200, "Type set to A.");
188 					type = cmd_type;
189 					form = cmd_form;
190 				} else
191 					reply(504, "Form must be N.");
192 				break;
193 
194 			case TYPE_E:
195 				reply(504, "Type E not implemented.");
196 				break;
197 
198 			case TYPE_I:
199 				reply(200, "Type set to I.");
200 				type = cmd_type;
201 				break;
202 
203 			case TYPE_L:
204 #if NBBY == 8
205 				if (cmd_bytesz == 8) {
206 					reply(200,
207 					    "Type set to L (byte size 8).");
208 					type = cmd_type;
209 				} else
210 					reply(504, "Byte size must be 8.");
211 #else /* NBBY == 8 */
212 				UNIMPLEMENTED for NBBY != 8
213 #endif /* NBBY == 8 */
214 			}
215 		}
216 	| STRU SP struct_code CRLF
217 		{
218 			switch ($3) {
219 
220 			case STRU_F:
221 				reply(200, "STRU F ok.");
222 				break;
223 
224 			default:
225 				reply(504, "Unimplemented STRU type.");
226 			}
227 		}
228 	| MODE SP mode_code CRLF
229 		{
230 			switch ($3) {
231 
232 			case MODE_S:
233 				reply(200, "MODE S ok.");
234 				break;
235 
236 			default:
237 				reply(502, "Unimplemented MODE type.");
238 			}
239 		}
240 	| ALLO SP NUMBER CRLF
241 		{
242 			reply(202, "ALLO command ignored.");
243 		}
244 	| ALLO SP NUMBER SP R SP NUMBER CRLF
245 		{
246 			reply(202, "ALLO command ignored.");
247 		}
248 	| RETR check_login SP pathname CRLF
249 		{
250 			if ($2 && $4 != NULL)
251 				retrieve((char *) 0, $4);
252 			if ($4 != NULL)
253 				free($4);
254 		}
255 	| STOR check_login SP pathname CRLF
256 		{
257 			if ($2 && $4 != NULL)
258 				store($4, "w", 0);
259 			if ($4 != NULL)
260 				free($4);
261 		}
262 	| APPE check_login SP pathname CRLF
263 		{
264 			if ($2 && $4 != NULL)
265 				store($4, "a", 0);
266 			if ($4 != NULL)
267 				free($4);
268 		}
269 	| NLST check_login CRLF
270 		{
271 			if ($2)
272 				send_file_list(".");
273 		}
274 	| NLST check_login SP STRING CRLF
275 		{
276 			if ($2 && $4 != NULL)
277 				send_file_list($4);
278 			if ($4 != NULL)
279 				free($4);
280 		}
281 	| LIST check_login CRLF
282 		{
283 			if ($2)
284 				retrieve("/bin/ls -lgA", "");
285 		}
286 	| LIST check_login SP pathname CRLF
287 		{
288 			if ($2 && $4 != NULL)
289 				retrieve("/bin/ls -lgA %s", $4);
290 			if ($4 != NULL)
291 				free($4);
292 		}
293 	| STAT check_login SP pathname CRLF
294 		{
295 			if ($2 && $4 != NULL)
296 				statfilecmd($4);
297 			if ($4 != NULL)
298 				free($4);
299 		}
300 	| STAT CRLF
301 		{
302 			statcmd();
303 		}
304 	| DELE check_login SP pathname CRLF
305 		{
306 			if ($2 && $4 != NULL)
307 				delete($4);
308 			if ($4 != NULL)
309 				free($4);
310 		}
311 	| RNTO check_login SP pathname CRLF
312 		{
313 			if ($2) {
314 				if (fromname) {
315 					renamecmd(fromname, $4);
316 					free(fromname);
317 					fromname = (char *) 0;
318 				} else {
319 					reply(503, "Bad sequence of commands.");
320 				}
321 			}
322 			free($4);
323 		}
324 	| ABOR CRLF
325 		{
326 			reply(225, "ABOR command successful.");
327 		}
328 	| CWD check_login CRLF
329 		{
330 			if ($2)
331 				cwd(pw->pw_dir);
332 		}
333 	| CWD check_login SP pathname CRLF
334 		{
335 			if ($2 && $4 != NULL)
336 				cwd($4);
337 			if ($4 != NULL)
338 				free($4);
339 		}
340 	| HELP CRLF
341 		{
342 			help(cmdtab, (char *) 0);
343 		}
344 	| HELP SP STRING CRLF
345 		{
346 			char *cp = $3;
347 
348 			if (strncasecmp(cp, "SITE", 4) == 0) {
349 				cp = $3 + 4;
350 				if (*cp == ' ')
351 					cp++;
352 				if (*cp)
353 					help(sitetab, cp);
354 				else
355 					help(sitetab, (char *) 0);
356 			} else
357 				help(cmdtab, $3);
358 		}
359 	| NOOP CRLF
360 		{
361 			reply(200, "NOOP command successful.");
362 		}
363 	| MKD check_login SP pathname CRLF
364 		{
365 			if ($2 && $4 != NULL)
366 				makedir($4);
367 			if ($4 != NULL)
368 				free($4);
369 		}
370 	| RMD check_login SP pathname CRLF
371 		{
372 			if ($2 && $4 != NULL)
373 				removedir($4);
374 			if ($4 != NULL)
375 				free($4);
376 		}
377 	| PWD check_login CRLF
378 		{
379 			if ($2)
380 				pwd();
381 		}
382 	| CDUP check_login CRLF
383 		{
384 			if ($2)
385 				cwd("..");
386 		}
387 	| SITE SP HELP CRLF
388 		{
389 			help(sitetab, (char *) 0);
390 		}
391 	| SITE SP HELP SP STRING CRLF
392 		{
393 			help(sitetab, $5);
394 		}
395 	| SITE SP UMASK check_login CRLF
396 		{
397 			int oldmask;
398 
399 			if ($4) {
400 				oldmask = umask(0);
401 				(void) umask(oldmask);
402 				reply(200, "Current UMASK is %03o", oldmask);
403 			}
404 		}
405 	| SITE SP UMASK check_login SP octal_number CRLF
406 		{
407 			int oldmask;
408 
409 			if ($4) {
410 				if (($6 == -1) || ($6 > 0777)) {
411 					reply(501, "Bad UMASK value");
412 				} else {
413 					oldmask = umask($6);
414 					reply(200,
415 					    "UMASK set to %03o (was %03o)",
416 					    $6, oldmask);
417 				}
418 			}
419 		}
420 	| SITE SP CHMOD check_login SP octal_number SP pathname CRLF
421 		{
422 			if ($4 && ($8 != NULL)) {
423 				if ($6 > 0777)
424 					reply(501,
425 				"CHMOD: Mode value must be between 0 and 0777");
426 				else if (chmod($8, $6) < 0)
427 					perror_reply(550, $8);
428 				else
429 					reply(200, "CHMOD command successful.");
430 			}
431 			if ($8 != NULL)
432 				free($8);
433 		}
434 	| SITE SP IDLE CRLF
435 		{
436 			reply(200,
437 			    "Current IDLE time limit is %d seconds; max %d",
438 				timeout, maxtimeout);
439 		}
440 	| SITE SP IDLE SP NUMBER CRLF
441 		{
442 			if ($5 < 30 || $5 > maxtimeout) {
443 				reply(501,
444 			"Maximum IDLE time must be between 30 and %d seconds",
445 				    maxtimeout);
446 			} else {
447 				timeout = $5;
448 				(void) alarm((unsigned) timeout);
449 				reply(200,
450 				    "Maximum IDLE time set to %d seconds",
451 				    timeout);
452 			}
453 		}
454 	| STOU check_login SP pathname CRLF
455 		{
456 			if ($2 && $4 != NULL)
457 				store($4, "w", 1);
458 			if ($4 != NULL)
459 				free($4);
460 		}
461 	| SYST CRLF
462 		{
463 #ifdef unix
464 #ifdef BSD
465 			reply(215, "UNIX Type: L%d Version: BSD-%d",
466 				NBBY, BSD);
467 #else /* BSD */
468 			reply(215, "UNIX Type: L%d", NBBY);
469 #endif /* BSD */
470 #else /* unix */
471 			reply(215, "UNKNOWN Type: L%d", NBBY);
472 #endif /* unix */
473 		}
474 
475 		/*
476 		 * SIZE is not in RFC959, but Postel has blessed it and
477 		 * it will be in the updated RFC.
478 		 *
479 		 * Return size of file in a format suitable for
480 		 * using with RESTART (we just count bytes).
481 		 */
482 	| SIZE check_login SP pathname CRLF
483 		{
484 			if ($2 && $4 != NULL)
485 				sizecmd($4);
486 			if ($4 != NULL)
487 				free($4);
488 		}
489 
490 		/*
491 		 * MDTM is not in RFC959, but Postel has blessed it and
492 		 * it will be in the updated RFC.
493 		 *
494 		 * Return modification time of file as an ISO 3307
495 		 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
496 		 * where xxx is the fractional second (of any precision,
497 		 * not necessarily 3 digits)
498 		 */
499 	| MDTM check_login SP pathname CRLF
500 		{
501 			if ($2 && $4 != NULL) {
502 				struct stat stbuf;
503 				if (stat($4, &stbuf) < 0)
504 					reply(550, "%s: %s",
505 					    $4, strerror(errno));
506 				else if (!S_ISREG(stbuf.st_mode)) {
507 					reply(550, "%s: not a plain file.", $4);
508 				} else {
509 					struct tm *t;
510 					t = gmtime(&stbuf.st_mtime);
511 					reply(213,
512 					    "%04d%02d%02d%02d%02d%02d",
513 					    1900 + t->tm_year,
514 					    t->tm_mon+1, t->tm_mday,
515 					    t->tm_hour, t->tm_min, t->tm_sec);
516 				}
517 			}
518 			if ($4 != NULL)
519 				free($4);
520 		}
521 	| QUIT CRLF
522 		{
523 			reply(221, "Goodbye.");
524 			dologout(0);
525 		}
526 	| error CRLF
527 		{
528 			yyerrok;
529 		}
530 	;
531 rcmd
532 	: RNFR check_login SP pathname CRLF
533 		{
534 			char *renamefrom();
535 
536 			restart_point = (off_t) 0;
537 			if ($2 && $4) {
538 				fromname = renamefrom($4);
539 				if (fromname == (char *) 0 && $4) {
540 					free($4);
541 				}
542 			}
543 		}
544 	| REST SP byte_size CRLF
545 		{
546 			fromname = (char *) 0;
547 			restart_point = $3;	/* XXX $3 is only "int" */
548 			reply(350, "Restarting at %qd. %s", restart_point,
549 			    "Send STORE or RETRIEVE to initiate transfer.");
550 		}
551 	;
552 
553 username
554 	: STRING
555 	;
556 
557 password
558 	: /* empty */
559 		{
560 			$$ = (char *)calloc(1, sizeof(char));
561 		}
562 	| STRING
563 	;
564 
565 byte_size
566 	: NUMBER
567 	;
568 
569 host_port
570 	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
571 		NUMBER COMMA NUMBER
572 		{
573 			char *a, *p;
574 
575 			data_dest.sin_len = sizeof(struct sockaddr_in);
576 			data_dest.sin_family = AF_INET;
577 			p = (char *)&data_dest.sin_port;
578 			p[0] = $9; p[1] = $11;
579 			a = (char *)&data_dest.sin_addr;
580 			a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7;
581 		}
582 	;
583 
584 form_code
585 	: N
586 		{
587 			$$ = FORM_N;
588 		}
589 	| T
590 		{
591 			$$ = FORM_T;
592 		}
593 	| C
594 		{
595 			$$ = FORM_C;
596 		}
597 	;
598 
599 type_code
600 	: A
601 		{
602 			cmd_type = TYPE_A;
603 			cmd_form = FORM_N;
604 		}
605 	| A SP form_code
606 		{
607 			cmd_type = TYPE_A;
608 			cmd_form = $3;
609 		}
610 	| E
611 		{
612 			cmd_type = TYPE_E;
613 			cmd_form = FORM_N;
614 		}
615 	| E SP form_code
616 		{
617 			cmd_type = TYPE_E;
618 			cmd_form = $3;
619 		}
620 	| I
621 		{
622 			cmd_type = TYPE_I;
623 		}
624 	| L
625 		{
626 			cmd_type = TYPE_L;
627 			cmd_bytesz = NBBY;
628 		}
629 	| L SP byte_size
630 		{
631 			cmd_type = TYPE_L;
632 			cmd_bytesz = $3;
633 		}
634 		/* this is for a bug in the BBN ftp */
635 	| L byte_size
636 		{
637 			cmd_type = TYPE_L;
638 			cmd_bytesz = $2;
639 		}
640 	;
641 
642 struct_code
643 	: F
644 		{
645 			$$ = STRU_F;
646 		}
647 	| R
648 		{
649 			$$ = STRU_R;
650 		}
651 	| P
652 		{
653 			$$ = STRU_P;
654 		}
655 	;
656 
657 mode_code
658 	: S
659 		{
660 			$$ = MODE_S;
661 		}
662 	| B
663 		{
664 			$$ = MODE_B;
665 		}
666 	| C
667 		{
668 			$$ = MODE_C;
669 		}
670 	;
671 
672 pathname
673 	: pathstring
674 		{
675 			/*
676 			 * Problem: this production is used for all pathname
677 			 * processing, but only gives a 550 error reply.
678 			 * This is a valid reply in some cases but not in others.
679 			 */
680 			if (logged_in && $1 && *$1 == '~') {
681 				glob_t gl;
682 				int flags =
683 				 GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE;
684 
685 				memset(&gl, 0, sizeof(gl));
686 				if (glob($1, flags, NULL, &gl) ||
687 				    gl.gl_pathc == 0) {
688 					reply(550, "not found");
689 					$$ = NULL;
690 				} else {
691 					$$ = strdup(gl.gl_pathv[0]);
692 				}
693 				globfree(&gl);
694 				free($1);
695 			} else
696 				$$ = $1;
697 		}
698 	;
699 
700 pathstring
701 	: STRING
702 	;
703 
704 octal_number
705 	: NUMBER
706 		{
707 			int ret, dec, multby, digit;
708 
709 			/*
710 			 * Convert a number that was read as decimal number
711 			 * to what it would be if it had been read as octal.
712 			 */
713 			dec = $1;
714 			multby = 1;
715 			ret = 0;
716 			while (dec) {
717 				digit = dec%10;
718 				if (digit > 7) {
719 					ret = -1;
720 					break;
721 				}
722 				ret += digit * multby;
723 				multby *= 8;
724 				dec /= 10;
725 			}
726 			$$ = ret;
727 		}
728 	;
729 
730 
731 check_login
732 	: /* empty */
733 		{
734 			if (logged_in)
735 				$$ = 1;
736 			else {
737 				reply(530, "Please login with USER and PASS.");
738 				$$ = 0;
739 			}
740 		}
741 	;
742 
743 %%
744 
745 extern jmp_buf errcatch;
746 
747 #define	CMD	0	/* beginning of command */
748 #define	ARGS	1	/* expect miscellaneous arguments */
749 #define	STR1	2	/* expect SP followed by STRING */
750 #define	STR2	3	/* expect STRING */
751 #define	OSTR	4	/* optional SP then STRING */
752 #define	ZSTR1	5	/* SP then optional STRING */
753 #define	ZSTR2	6	/* optional STRING after SP */
754 #define	SITECMD	7	/* SITE command */
755 #define	NSTR	8	/* Number followed by a string */
756 
757 struct tab {
758 	char	*name;
759 	short	token;
760 	short	state;
761 	short	implemented;	/* 1 if command is implemented */
762 	char	*help;
763 };
764 
765 struct tab cmdtab[] = {		/* In order defined in RFC 765 */
766 	{ "USER", USER, STR1, 1,	"<sp> username" },
767 	{ "PASS", PASS, ZSTR1, 1,	"<sp> password" },
768 	{ "ACCT", ACCT, STR1, 0,	"(specify account)" },
769 	{ "SMNT", SMNT, ARGS, 0,	"(structure mount)" },
770 	{ "REIN", REIN, ARGS, 0,	"(reinitialize server state)" },
771 	{ "QUIT", QUIT, ARGS, 1,	"(terminate service)", },
772 	{ "PORT", PORT, ARGS, 1,	"<sp> b0, b1, b2, b3, b4" },
773 	{ "PASV", PASV, ARGS, 1,	"(set server in passive mode)" },
774 	{ "TYPE", TYPE, ARGS, 1,	"<sp> [ A | E | I | L ]" },
775 	{ "STRU", STRU, ARGS, 1,	"(specify file structure)" },
776 	{ "MODE", MODE, ARGS, 1,	"(specify transfer mode)" },
777 	{ "RETR", RETR, STR1, 1,	"<sp> file-name" },
778 	{ "STOR", STOR, STR1, 1,	"<sp> file-name" },
779 	{ "APPE", APPE, STR1, 1,	"<sp> file-name" },
780 	{ "MLFL", MLFL, OSTR, 0,	"(mail file)" },
781 	{ "MAIL", MAIL, OSTR, 0,	"(mail to user)" },
782 	{ "MSND", MSND, OSTR, 0,	"(mail send to terminal)" },
783 	{ "MSOM", MSOM, OSTR, 0,	"(mail send to terminal or mailbox)" },
784 	{ "MSAM", MSAM, OSTR, 0,	"(mail send to terminal and mailbox)" },
785 	{ "MRSQ", MRSQ, OSTR, 0,	"(mail recipient scheme question)" },
786 	{ "MRCP", MRCP, STR1, 0,	"(mail recipient)" },
787 	{ "ALLO", ALLO, ARGS, 1,	"allocate storage (vacuously)" },
788 	{ "REST", REST, ARGS, 1,	"<sp> offset (restart command)" },
789 	{ "RNFR", RNFR, STR1, 1,	"<sp> file-name" },
790 	{ "RNTO", RNTO, STR1, 1,	"<sp> file-name" },
791 	{ "ABOR", ABOR, ARGS, 1,	"(abort operation)" },
792 	{ "DELE", DELE, STR1, 1,	"<sp> file-name" },
793 	{ "CWD",  CWD,  OSTR, 1,	"[ <sp> directory-name ]" },
794 	{ "XCWD", CWD,	OSTR, 1,	"[ <sp> directory-name ]" },
795 	{ "LIST", LIST, OSTR, 1,	"[ <sp> path-name ]" },
796 	{ "NLST", NLST, OSTR, 1,	"[ <sp> path-name ]" },
797 	{ "SITE", SITE, SITECMD, 1,	"site-cmd [ <sp> arguments ]" },
798 	{ "SYST", SYST, ARGS, 1,	"(get type of operating system)" },
799 	{ "STAT", STAT, OSTR, 1,	"[ <sp> path-name ]" },
800 	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
801 	{ "NOOP", NOOP, ARGS, 1,	"" },
802 	{ "MKD",  MKD,  STR1, 1,	"<sp> path-name" },
803 	{ "XMKD", MKD,  STR1, 1,	"<sp> path-name" },
804 	{ "RMD",  RMD,  STR1, 1,	"<sp> path-name" },
805 	{ "XRMD", RMD,  STR1, 1,	"<sp> path-name" },
806 	{ "PWD",  PWD,  ARGS, 1,	"(return current directory)" },
807 	{ "XPWD", PWD,  ARGS, 1,	"(return current directory)" },
808 	{ "CDUP", CDUP, ARGS, 1,	"(change to parent directory)" },
809 	{ "XCUP", CDUP, ARGS, 1,	"(change to parent directory)" },
810 	{ "STOU", STOU, STR1, 1,	"<sp> file-name" },
811 	{ "SIZE", SIZE, OSTR, 1,	"<sp> path-name" },
812 	{ "MDTM", MDTM, OSTR, 1,	"<sp> path-name" },
813 	{ NULL,   0,    0,    0,	0 }
814 };
815 
816 struct tab sitetab[] = {
817 	{ "UMASK", UMASK, ARGS, 1,	"[ <sp> umask ]" },
818 	{ "IDLE", IDLE, ARGS, 1,	"[ <sp> maximum-idle-time ]" },
819 	{ "CHMOD", CHMOD, NSTR, 1,	"<sp> mode <sp> file-name" },
820 	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
821 	{ NULL,   0,    0,    0,	0 }
822 };
823 
824 static char	*copy __P((char *));
825 static void	 help __P((struct tab *, char *));
826 static struct tab *
827 		 lookup __P((struct tab *, char *));
828 static void	 sizecmd __P((char *));
829 static void	 toolong __P((int));
830 static int	 yylex __P((void));
831 
832 static struct tab *
833 lookup(p, cmd)
834 	struct tab *p;
835 	char *cmd;
836 {
837 
838 	for (; p->name != NULL; p++)
839 		if (strcmp(cmd, p->name) == 0)
840 			return (p);
841 	return (0);
842 }
843 
844 #include <arpa/telnet.h>
845 
846 /*
847  * getline - a hacked up version of fgets to ignore TELNET escape codes.
848  */
849 char *
850 getline(s, n, iop)
851 	char *s;
852 	int n;
853 	FILE *iop;
854 {
855 	int c;
856 	register char *cs;
857 
858 	cs = s;
859 /* tmpline may contain saved command from urgent mode interruption */
860 	for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
861 		*cs++ = tmpline[c];
862 		if (tmpline[c] == '\n') {
863 			*cs++ = '\0';
864 			if (debug)
865 				syslog(LOG_DEBUG, "command: %s", s);
866 			tmpline[0] = '\0';
867 			return(s);
868 		}
869 		if (c == 0)
870 			tmpline[0] = '\0';
871 	}
872 	while ((c = getc(iop)) != EOF) {
873 		c &= 0377;
874 		if (c == IAC) {
875 		    if ((c = getc(iop)) != EOF) {
876 			c &= 0377;
877 			switch (c) {
878 			case WILL:
879 			case WONT:
880 				c = getc(iop);
881 				printf("%c%c%c", IAC, DONT, 0377&c);
882 				(void) fflush(stdout);
883 				continue;
884 			case DO:
885 			case DONT:
886 				c = getc(iop);
887 				printf("%c%c%c", IAC, WONT, 0377&c);
888 				(void) fflush(stdout);
889 				continue;
890 			case IAC:
891 				break;
892 			default:
893 				continue;	/* ignore command */
894 			}
895 		    }
896 		}
897 		*cs++ = c;
898 		if (--n <= 0 || c == '\n')
899 			break;
900 	}
901 	if (c == EOF && cs == s)
902 		return (NULL);
903 	*cs++ = '\0';
904 	if (debug) {
905 		if (!guest && strncasecmp("pass ", s, 5) == 0) {
906 			/* Don't syslog passwords */
907 			syslog(LOG_DEBUG, "command: %.5s ???", s);
908 		} else {
909 			register char *cp;
910 			register int len;
911 
912 			/* Don't syslog trailing CR-LF */
913 			len = strlen(s);
914 			cp = s + len - 1;
915 			while (cp >= s && (*cp == '\n' || *cp == '\r')) {
916 				--cp;
917 				--len;
918 			}
919 			syslog(LOG_DEBUG, "command: %.*s", len, s);
920 		}
921 	}
922 	return (s);
923 }
924 
925 static void
926 toolong(signo)
927 	int signo;
928 {
929 
930 	reply(421,
931 	    "Timeout (%d seconds): closing control connection.", timeout);
932 	if (logging)
933 		syslog(LOG_INFO, "User %s timed out after %d seconds",
934 		    (pw ? pw -> pw_name : "unknown"), timeout);
935 	dologout(1);
936 }
937 
938 static int
939 yylex()
940 {
941 	static int cpos, state;
942 	char *cp, *cp2;
943 	struct tab *p;
944 	int n;
945 	char c;
946 
947 	for (;;) {
948 		switch (state) {
949 
950 		case CMD:
951 			(void) signal(SIGALRM, toolong);
952 			(void) alarm((unsigned) timeout);
953 			if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
954 				reply(221, "You could at least say goodbye.");
955 				dologout(0);
956 			}
957 			(void) alarm(0);
958 #ifdef SETPROCTITLE
959 			if (strncasecmp(cbuf, "PASS", 4) != NULL)
960 				setproctitle("%s: %s", proctitle, cbuf);
961 #endif /* SETPROCTITLE */
962 			if ((cp = strchr(cbuf, '\r'))) {
963 				*cp++ = '\n';
964 				*cp = '\0';
965 			}
966 			if ((cp = strpbrk(cbuf, " \n")))
967 				cpos = cp - cbuf;
968 			if (cpos == 0)
969 				cpos = 4;
970 			c = cbuf[cpos];
971 			cbuf[cpos] = '\0';
972 			upper(cbuf);
973 			p = lookup(cmdtab, cbuf);
974 			cbuf[cpos] = c;
975 			if (p != 0) {
976 				if (p->implemented == 0) {
977 					nack(p->name);
978 					longjmp(errcatch,0);
979 					/* NOTREACHED */
980 				}
981 				state = p->state;
982 				yylval.s = p->name;
983 				return (p->token);
984 			}
985 			break;
986 
987 		case SITECMD:
988 			if (cbuf[cpos] == ' ') {
989 				cpos++;
990 				return (SP);
991 			}
992 			cp = &cbuf[cpos];
993 			if ((cp2 = strpbrk(cp, " \n")))
994 				cpos = cp2 - cbuf;
995 			c = cbuf[cpos];
996 			cbuf[cpos] = '\0';
997 			upper(cp);
998 			p = lookup(sitetab, cp);
999 			cbuf[cpos] = c;
1000 			if (guest == 0 && p != 0) {
1001 				if (p->implemented == 0) {
1002 					state = CMD;
1003 					nack(p->name);
1004 					longjmp(errcatch,0);
1005 					/* NOTREACHED */
1006 				}
1007 				state = p->state;
1008 				yylval.s = p->name;
1009 				return (p->token);
1010 			}
1011 			state = CMD;
1012 			break;
1013 
1014 		case OSTR:
1015 			if (cbuf[cpos] == '\n') {
1016 				state = CMD;
1017 				return (CRLF);
1018 			}
1019 			/* FALLTHROUGH */
1020 
1021 		case STR1:
1022 		case ZSTR1:
1023 		dostr1:
1024 			if (cbuf[cpos] == ' ') {
1025 				cpos++;
1026 				state = state == OSTR ? STR2 : ++state;
1027 				return (SP);
1028 			}
1029 			break;
1030 
1031 		case ZSTR2:
1032 			if (cbuf[cpos] == '\n') {
1033 				state = CMD;
1034 				return (CRLF);
1035 			}
1036 			/* FALLTHROUGH */
1037 
1038 		case STR2:
1039 			cp = &cbuf[cpos];
1040 			n = strlen(cp);
1041 			cpos += n - 1;
1042 			/*
1043 			 * Make sure the string is nonempty and \n terminated.
1044 			 */
1045 			if (n > 1 && cbuf[cpos] == '\n') {
1046 				cbuf[cpos] = '\0';
1047 				yylval.s = copy(cp);
1048 				cbuf[cpos] = '\n';
1049 				state = ARGS;
1050 				return (STRING);
1051 			}
1052 			break;
1053 
1054 		case NSTR:
1055 			if (cbuf[cpos] == ' ') {
1056 				cpos++;
1057 				return (SP);
1058 			}
1059 			if (isdigit(cbuf[cpos])) {
1060 				cp = &cbuf[cpos];
1061 				while (isdigit(cbuf[++cpos]))
1062 					;
1063 				c = cbuf[cpos];
1064 				cbuf[cpos] = '\0';
1065 				yylval.i = atoi(cp);
1066 				cbuf[cpos] = c;
1067 				state = STR1;
1068 				return (NUMBER);
1069 			}
1070 			state = STR1;
1071 			goto dostr1;
1072 
1073 		case ARGS:
1074 			if (isdigit(cbuf[cpos])) {
1075 				cp = &cbuf[cpos];
1076 				while (isdigit(cbuf[++cpos]))
1077 					;
1078 				c = cbuf[cpos];
1079 				cbuf[cpos] = '\0';
1080 				yylval.i = atoi(cp);
1081 				cbuf[cpos] = c;
1082 				return (NUMBER);
1083 			}
1084 			switch (cbuf[cpos++]) {
1085 
1086 			case '\n':
1087 				state = CMD;
1088 				return (CRLF);
1089 
1090 			case ' ':
1091 				return (SP);
1092 
1093 			case ',':
1094 				return (COMMA);
1095 
1096 			case 'A':
1097 			case 'a':
1098 				return (A);
1099 
1100 			case 'B':
1101 			case 'b':
1102 				return (B);
1103 
1104 			case 'C':
1105 			case 'c':
1106 				return (C);
1107 
1108 			case 'E':
1109 			case 'e':
1110 				return (E);
1111 
1112 			case 'F':
1113 			case 'f':
1114 				return (F);
1115 
1116 			case 'I':
1117 			case 'i':
1118 				return (I);
1119 
1120 			case 'L':
1121 			case 'l':
1122 				return (L);
1123 
1124 			case 'N':
1125 			case 'n':
1126 				return (N);
1127 
1128 			case 'P':
1129 			case 'p':
1130 				return (P);
1131 
1132 			case 'R':
1133 			case 'r':
1134 				return (R);
1135 
1136 			case 'S':
1137 			case 's':
1138 				return (S);
1139 
1140 			case 'T':
1141 			case 't':
1142 				return (T);
1143 
1144 			}
1145 			break;
1146 
1147 		default:
1148 			fatal("Unknown state in scanner.");
1149 		}
1150 		yyerror((char *) 0);
1151 		state = CMD;
1152 		longjmp(errcatch,0);
1153 	}
1154 }
1155 
1156 void
1157 upper(s)
1158 	char *s;
1159 {
1160 	while (*s != '\0') {
1161 		if (islower(*s))
1162 			*s = toupper(*s);
1163 		s++;
1164 	}
1165 }
1166 
1167 static char *
1168 copy(s)
1169 	char *s;
1170 {
1171 	char *p;
1172 
1173 	p = malloc((unsigned) strlen(s) + 1);
1174 	if (p == NULL)
1175 		fatal("Ran out of memory.");
1176 	(void) strcpy(p, s);
1177 	return (p);
1178 }
1179 
1180 static void
1181 help(ctab, s)
1182 	struct tab *ctab;
1183 	char *s;
1184 {
1185 	struct tab *c;
1186 	int width, NCMDS;
1187 	char *type;
1188 
1189 	if (ctab == sitetab)
1190 		type = "SITE ";
1191 	else
1192 		type = "";
1193 	width = 0, NCMDS = 0;
1194 	for (c = ctab; c->name != NULL; c++) {
1195 		int len = strlen(c->name);
1196 
1197 		if (len > width)
1198 			width = len;
1199 		NCMDS++;
1200 	}
1201 	width = (width + 8) &~ 7;
1202 	if (s == 0) {
1203 		int i, j, w;
1204 		int columns, lines;
1205 
1206 		lreply(214, "The following %scommands are recognized %s.",
1207 		    type, "(* =>'s unimplemented)");
1208 		columns = 76 / width;
1209 		if (columns == 0)
1210 			columns = 1;
1211 		lines = (NCMDS + columns - 1) / columns;
1212 		for (i = 0; i < lines; i++) {
1213 			printf("   ");
1214 			for (j = 0; j < columns; j++) {
1215 				c = ctab + j * lines + i;
1216 				printf("%s%c", c->name,
1217 					c->implemented ? ' ' : '*');
1218 				if (c + lines >= &ctab[NCMDS])
1219 					break;
1220 				w = strlen(c->name) + 1;
1221 				while (w < width) {
1222 					putchar(' ');
1223 					w++;
1224 				}
1225 			}
1226 			printf("\r\n");
1227 		}
1228 		(void) fflush(stdout);
1229 		reply(214, "Direct comments to ftp-bugs@%s.", hostname);
1230 		return;
1231 	}
1232 	upper(s);
1233 	c = lookup(ctab, s);
1234 	if (c == (struct tab *)0) {
1235 		reply(502, "Unknown command %s.", s);
1236 		return;
1237 	}
1238 	if (c->implemented)
1239 		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
1240 	else
1241 		reply(214, "%s%-*s\t%s; unimplemented.", type, width,
1242 		    c->name, c->help);
1243 }
1244 
1245 static void
1246 sizecmd(filename)
1247 	char *filename;
1248 {
1249 	switch (type) {
1250 	case TYPE_L:
1251 	case TYPE_I: {
1252 		struct stat stbuf;
1253 		if (stat(filename, &stbuf) < 0 || !S_ISREG(stbuf.st_mode))
1254 			reply(550, "%s: not a plain file.", filename);
1255 		else
1256 			reply(213, "%qu", stbuf.st_size);
1257 		break; }
1258 	case TYPE_A: {
1259 		FILE *fin;
1260 		int c;
1261 		off_t count;
1262 		struct stat stbuf;
1263 		fin = fopen(filename, "r");
1264 		if (fin == NULL) {
1265 			perror_reply(550, filename);
1266 			return;
1267 		}
1268 		if (fstat(fileno(fin), &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) {
1269 			reply(550, "%s: not a plain file.", filename);
1270 			(void) fclose(fin);
1271 			return;
1272 		}
1273 
1274 		count = 0;
1275 		while((c=getc(fin)) != EOF) {
1276 			if (c == '\n')	/* will get expanded to \r\n */
1277 				count++;
1278 			count++;
1279 		}
1280 		(void) fclose(fin);
1281 
1282 		reply(213, "%qd", count);
1283 		break; }
1284 	default:
1285 		reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
1286 	}
1287 }
1288