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