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