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