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