xref: /original-bsd/libexec/ftpd/ftpcmd.y (revision e58c8952)
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.3 (Berkeley) 04/06/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.3 (Berkeley) 04/06/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;	/* XXX $3 is only "int" */
502 			reply(350, "Restarting at %qd. %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 =
636 				 GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE;
637 
638 				memset(&gl, 0, sizeof(gl));
639 				if (glob($1, flags, NULL, &gl) ||
640 				    gl.gl_pathc == 0) {
641 					reply(550, "not found");
642 					$$ = NULL;
643 				} else {
644 					$$ = strdup(gl.gl_pathv[0]);
645 				}
646 				globfree(&gl);
647 				free($1);
648 			} else
649 				$$ = $1;
650 		}
651 	;
652 
653 pathstring
654 	: STRING
655 	;
656 
657 octal_number
658 	: NUMBER
659 		{
660 			int ret, dec, multby, digit;
661 
662 			/*
663 			 * Convert a number that was read as decimal number
664 			 * to what it would be if it had been read as octal.
665 			 */
666 			dec = $1;
667 			multby = 1;
668 			ret = 0;
669 			while (dec) {
670 				digit = dec%10;
671 				if (digit > 7) {
672 					ret = -1;
673 					break;
674 				}
675 				ret += digit * multby;
676 				multby *= 8;
677 				dec /= 10;
678 			}
679 			$$ = ret;
680 		}
681 	;
682 
683 
684 check_login
685 	: /* empty */
686 		{
687 			if (logged_in)
688 				$$ = 1;
689 			else {
690 				reply(530, "Please login with USER and PASS.");
691 				$$ = 0;
692 			}
693 		}
694 	;
695 
696 %%
697 
698 extern jmp_buf errcatch;
699 
700 #define	CMD	0	/* beginning of command */
701 #define	ARGS	1	/* expect miscellaneous arguments */
702 #define	STR1	2	/* expect SP followed by STRING */
703 #define	STR2	3	/* expect STRING */
704 #define	OSTR	4	/* optional SP then STRING */
705 #define	ZSTR1	5	/* SP then optional STRING */
706 #define	ZSTR2	6	/* optional STRING after SP */
707 #define	SITECMD	7	/* SITE command */
708 #define	NSTR	8	/* Number followed by a string */
709 
710 struct tab {
711 	char	*name;
712 	short	token;
713 	short	state;
714 	short	implemented;	/* 1 if command is implemented */
715 	char	*help;
716 };
717 
718 struct tab cmdtab[] = {		/* In order defined in RFC 765 */
719 	{ "USER", USER, STR1, 1,	"<sp> username" },
720 	{ "PASS", PASS, ZSTR1, 1,	"<sp> password" },
721 	{ "ACCT", ACCT, STR1, 0,	"(specify account)" },
722 	{ "SMNT", SMNT, ARGS, 0,	"(structure mount)" },
723 	{ "REIN", REIN, ARGS, 0,	"(reinitialize server state)" },
724 	{ "QUIT", QUIT, ARGS, 1,	"(terminate service)", },
725 	{ "PORT", PORT, ARGS, 1,	"<sp> b0, b1, b2, b3, b4" },
726 	{ "PASV", PASV, ARGS, 1,	"(set server in passive mode)" },
727 	{ "TYPE", TYPE, ARGS, 1,	"<sp> [ A | E | I | L ]" },
728 	{ "STRU", STRU, ARGS, 1,	"(specify file structure)" },
729 	{ "MODE", MODE, ARGS, 1,	"(specify transfer mode)" },
730 	{ "RETR", RETR, STR1, 1,	"<sp> file-name" },
731 	{ "STOR", STOR, STR1, 1,	"<sp> file-name" },
732 	{ "APPE", APPE, STR1, 1,	"<sp> file-name" },
733 	{ "MLFL", MLFL, OSTR, 0,	"(mail file)" },
734 	{ "MAIL", MAIL, OSTR, 0,	"(mail to user)" },
735 	{ "MSND", MSND, OSTR, 0,	"(mail send to terminal)" },
736 	{ "MSOM", MSOM, OSTR, 0,	"(mail send to terminal or mailbox)" },
737 	{ "MSAM", MSAM, OSTR, 0,	"(mail send to terminal and mailbox)" },
738 	{ "MRSQ", MRSQ, OSTR, 0,	"(mail recipient scheme question)" },
739 	{ "MRCP", MRCP, STR1, 0,	"(mail recipient)" },
740 	{ "ALLO", ALLO, ARGS, 1,	"allocate storage (vacuously)" },
741 	{ "REST", REST, ARGS, 1,	"<sp> offset (restart command)" },
742 	{ "RNFR", RNFR, STR1, 1,	"<sp> file-name" },
743 	{ "RNTO", RNTO, STR1, 1,	"<sp> file-name" },
744 	{ "ABOR", ABOR, ARGS, 1,	"(abort operation)" },
745 	{ "DELE", DELE, STR1, 1,	"<sp> file-name" },
746 	{ "CWD",  CWD,  OSTR, 1,	"[ <sp> directory-name ]" },
747 	{ "XCWD", CWD,	OSTR, 1,	"[ <sp> directory-name ]" },
748 	{ "LIST", LIST, OSTR, 1,	"[ <sp> path-name ]" },
749 	{ "NLST", NLST, OSTR, 1,	"[ <sp> path-name ]" },
750 	{ "SITE", SITE, SITECMD, 1,	"site-cmd [ <sp> arguments ]" },
751 	{ "SYST", SYST, ARGS, 1,	"(get type of operating system)" },
752 	{ "STAT", STAT, OSTR, 1,	"[ <sp> path-name ]" },
753 	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
754 	{ "NOOP", NOOP, ARGS, 1,	"" },
755 	{ "MKD",  MKD,  STR1, 1,	"<sp> path-name" },
756 	{ "XMKD", MKD,  STR1, 1,	"<sp> path-name" },
757 	{ "RMD",  RMD,  STR1, 1,	"<sp> path-name" },
758 	{ "XRMD", RMD,  STR1, 1,	"<sp> path-name" },
759 	{ "PWD",  PWD,  ARGS, 1,	"(return current directory)" },
760 	{ "XPWD", PWD,  ARGS, 1,	"(return current directory)" },
761 	{ "CDUP", CDUP, ARGS, 1,	"(change to parent directory)" },
762 	{ "XCUP", CDUP, ARGS, 1,	"(change to parent directory)" },
763 	{ "STOU", STOU, STR1, 1,	"<sp> file-name" },
764 	{ "SIZE", SIZE, OSTR, 1,	"<sp> path-name" },
765 	{ "MDTM", MDTM, OSTR, 1,	"<sp> path-name" },
766 	{ NULL,   0,    0,    0,	0 }
767 };
768 
769 struct tab sitetab[] = {
770 	{ "UMASK", UMASK, ARGS, 1,	"[ <sp> umask ]" },
771 	{ "IDLE", IDLE, ARGS, 1,	"[ <sp> maximum-idle-time ]" },
772 	{ "CHMOD", CHMOD, NSTR, 1,	"<sp> mode <sp> file-name" },
773 	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
774 	{ NULL,   0,    0,    0,	0 }
775 };
776 
777 static char	*copy __P((char *));
778 static void	 help __P((struct tab *, char *));
779 static struct tab *
780 		 lookup __P((struct tab *, char *));
781 static void	 sizecmd __P((char *));
782 static void	 toolong __P((int));
783 static int	 yylex __P((void));
784 
785 static struct tab *
786 lookup(p, cmd)
787 	struct tab *p;
788 	char *cmd;
789 {
790 
791 	for (; p->name != NULL; p++)
792 		if (strcmp(cmd, p->name) == 0)
793 			return (p);
794 	return (0);
795 }
796 
797 #include <arpa/telnet.h>
798 
799 /*
800  * getline - a hacked up version of fgets to ignore TELNET escape codes.
801  */
802 char *
803 getline(s, n, iop)
804 	char *s;
805 	int n;
806 	FILE *iop;
807 {
808 	int c;
809 	register char *cs;
810 
811 	cs = s;
812 /* tmpline may contain saved command from urgent mode interruption */
813 	for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
814 		*cs++ = tmpline[c];
815 		if (tmpline[c] == '\n') {
816 			*cs++ = '\0';
817 			if (debug)
818 				syslog(LOG_DEBUG, "command: %s", s);
819 			tmpline[0] = '\0';
820 			return(s);
821 		}
822 		if (c == 0)
823 			tmpline[0] = '\0';
824 	}
825 	while ((c = getc(iop)) != EOF) {
826 		c &= 0377;
827 		if (c == IAC) {
828 		    if ((c = getc(iop)) != EOF) {
829 			c &= 0377;
830 			switch (c) {
831 			case WILL:
832 			case WONT:
833 				c = getc(iop);
834 				printf("%c%c%c", IAC, DONT, 0377&c);
835 				(void) fflush(stdout);
836 				continue;
837 			case DO:
838 			case DONT:
839 				c = getc(iop);
840 				printf("%c%c%c", IAC, WONT, 0377&c);
841 				(void) fflush(stdout);
842 				continue;
843 			case IAC:
844 				break;
845 			default:
846 				continue;	/* ignore command */
847 			}
848 		    }
849 		}
850 		*cs++ = c;
851 		if (--n <= 0 || c == '\n')
852 			break;
853 	}
854 	if (c == EOF && cs == s)
855 		return (NULL);
856 	*cs++ = '\0';
857 	if (debug) {
858 		if (!guest && strncasecmp("pass ", s, 5) == 0) {
859 			/* Don't syslog passwords */
860 			syslog(LOG_DEBUG, "command: %.5s ???", s);
861 		} else {
862 			register char *cp;
863 			register int len;
864 
865 			/* Don't syslog trailing CR-LF */
866 			len = strlen(s);
867 			cp = s + len - 1;
868 			while (cp >= s && (*cp == '\n' || *cp == '\r')) {
869 				--cp;
870 				--len;
871 			}
872 			syslog(LOG_DEBUG, "command: %.*s", len, s);
873 		}
874 	}
875 	return (s);
876 }
877 
878 static void
879 toolong(signo)
880 	int signo;
881 {
882 
883 	reply(421,
884 	    "Timeout (%d seconds): closing control connection.", timeout);
885 	if (logging)
886 		syslog(LOG_INFO, "User %s timed out after %d seconds",
887 		    (pw ? pw -> pw_name : "unknown"), timeout);
888 	dologout(1);
889 }
890 
891 static int
892 yylex()
893 {
894 	static int cpos, state;
895 	char *cp, *cp2;
896 	struct tab *p;
897 	int n;
898 	char c;
899 
900 	for (;;) {
901 		switch (state) {
902 
903 		case CMD:
904 			(void) signal(SIGALRM, toolong);
905 			(void) alarm((unsigned) timeout);
906 			if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
907 				reply(221, "You could at least say goodbye.");
908 				dologout(0);
909 			}
910 			(void) alarm(0);
911 #ifdef SETPROCTITLE
912 			if (strncasecmp(cbuf, "PASS", 4) != NULL)
913 				setproctitle("%s: %s", proctitle, cbuf);
914 #endif /* SETPROCTITLE */
915 			if ((cp = strchr(cbuf, '\r'))) {
916 				*cp++ = '\n';
917 				*cp = '\0';
918 			}
919 			if ((cp = strpbrk(cbuf, " \n")))
920 				cpos = cp - cbuf;
921 			if (cpos == 0)
922 				cpos = 4;
923 			c = cbuf[cpos];
924 			cbuf[cpos] = '\0';
925 			upper(cbuf);
926 			p = lookup(cmdtab, cbuf);
927 			cbuf[cpos] = c;
928 			if (p != 0) {
929 				if (p->implemented == 0) {
930 					nack(p->name);
931 					longjmp(errcatch,0);
932 					/* NOTREACHED */
933 				}
934 				state = p->state;
935 				yylval.s = p->name;
936 				return (p->token);
937 			}
938 			break;
939 
940 		case SITECMD:
941 			if (cbuf[cpos] == ' ') {
942 				cpos++;
943 				return (SP);
944 			}
945 			cp = &cbuf[cpos];
946 			if ((cp2 = strpbrk(cp, " \n")))
947 				cpos = cp2 - cbuf;
948 			c = cbuf[cpos];
949 			cbuf[cpos] = '\0';
950 			upper(cp);
951 			p = lookup(sitetab, cp);
952 			cbuf[cpos] = c;
953 			if (p != 0) {
954 				if (p->implemented == 0) {
955 					state = CMD;
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 			state = CMD;
965 			break;
966 
967 		case OSTR:
968 			if (cbuf[cpos] == '\n') {
969 				state = CMD;
970 				return (CRLF);
971 			}
972 			/* FALLTHROUGH */
973 
974 		case STR1:
975 		case ZSTR1:
976 		dostr1:
977 			if (cbuf[cpos] == ' ') {
978 				cpos++;
979 				state = state == OSTR ? STR2 : ++state;
980 				return (SP);
981 			}
982 			break;
983 
984 		case ZSTR2:
985 			if (cbuf[cpos] == '\n') {
986 				state = CMD;
987 				return (CRLF);
988 			}
989 			/* FALLTHROUGH */
990 
991 		case STR2:
992 			cp = &cbuf[cpos];
993 			n = strlen(cp);
994 			cpos += n - 1;
995 			/*
996 			 * Make sure the string is nonempty and \n terminated.
997 			 */
998 			if (n > 1 && cbuf[cpos] == '\n') {
999 				cbuf[cpos] = '\0';
1000 				yylval.s = copy(cp);
1001 				cbuf[cpos] = '\n';
1002 				state = ARGS;
1003 				return (STRING);
1004 			}
1005 			break;
1006 
1007 		case NSTR:
1008 			if (cbuf[cpos] == ' ') {
1009 				cpos++;
1010 				return (SP);
1011 			}
1012 			if (isdigit(cbuf[cpos])) {
1013 				cp = &cbuf[cpos];
1014 				while (isdigit(cbuf[++cpos]))
1015 					;
1016 				c = cbuf[cpos];
1017 				cbuf[cpos] = '\0';
1018 				yylval.i = atoi(cp);
1019 				cbuf[cpos] = c;
1020 				state = STR1;
1021 				return (NUMBER);
1022 			}
1023 			state = STR1;
1024 			goto dostr1;
1025 
1026 		case ARGS:
1027 			if (isdigit(cbuf[cpos])) {
1028 				cp = &cbuf[cpos];
1029 				while (isdigit(cbuf[++cpos]))
1030 					;
1031 				c = cbuf[cpos];
1032 				cbuf[cpos] = '\0';
1033 				yylval.i = atoi(cp);
1034 				cbuf[cpos] = c;
1035 				return (NUMBER);
1036 			}
1037 			switch (cbuf[cpos++]) {
1038 
1039 			case '\n':
1040 				state = CMD;
1041 				return (CRLF);
1042 
1043 			case ' ':
1044 				return (SP);
1045 
1046 			case ',':
1047 				return (COMMA);
1048 
1049 			case 'A':
1050 			case 'a':
1051 				return (A);
1052 
1053 			case 'B':
1054 			case 'b':
1055 				return (B);
1056 
1057 			case 'C':
1058 			case 'c':
1059 				return (C);
1060 
1061 			case 'E':
1062 			case 'e':
1063 				return (E);
1064 
1065 			case 'F':
1066 			case 'f':
1067 				return (F);
1068 
1069 			case 'I':
1070 			case 'i':
1071 				return (I);
1072 
1073 			case 'L':
1074 			case 'l':
1075 				return (L);
1076 
1077 			case 'N':
1078 			case 'n':
1079 				return (N);
1080 
1081 			case 'P':
1082 			case 'p':
1083 				return (P);
1084 
1085 			case 'R':
1086 			case 'r':
1087 				return (R);
1088 
1089 			case 'S':
1090 			case 's':
1091 				return (S);
1092 
1093 			case 'T':
1094 			case 't':
1095 				return (T);
1096 
1097 			}
1098 			break;
1099 
1100 		default:
1101 			fatal("Unknown state in scanner.");
1102 		}
1103 		yyerror((char *) 0);
1104 		state = CMD;
1105 		longjmp(errcatch,0);
1106 	}
1107 }
1108 
1109 void
1110 upper(s)
1111 	char *s;
1112 {
1113 	while (*s != '\0') {
1114 		if (islower(*s))
1115 			*s = toupper(*s);
1116 		s++;
1117 	}
1118 }
1119 
1120 static char *
1121 copy(s)
1122 	char *s;
1123 {
1124 	char *p;
1125 
1126 	p = malloc((unsigned) strlen(s) + 1);
1127 	if (p == NULL)
1128 		fatal("Ran out of memory.");
1129 	(void) strcpy(p, s);
1130 	return (p);
1131 }
1132 
1133 static void
1134 help(ctab, s)
1135 	struct tab *ctab;
1136 	char *s;
1137 {
1138 	struct tab *c;
1139 	int width, NCMDS;
1140 	char *type;
1141 
1142 	if (ctab == sitetab)
1143 		type = "SITE ";
1144 	else
1145 		type = "";
1146 	width = 0, NCMDS = 0;
1147 	for (c = ctab; c->name != NULL; c++) {
1148 		int len = strlen(c->name);
1149 
1150 		if (len > width)
1151 			width = len;
1152 		NCMDS++;
1153 	}
1154 	width = (width + 8) &~ 7;
1155 	if (s == 0) {
1156 		int i, j, w;
1157 		int columns, lines;
1158 
1159 		lreply(214, "The following %scommands are recognized %s.",
1160 		    type, "(* =>'s unimplemented)");
1161 		columns = 76 / width;
1162 		if (columns == 0)
1163 			columns = 1;
1164 		lines = (NCMDS + columns - 1) / columns;
1165 		for (i = 0; i < lines; i++) {
1166 			printf("   ");
1167 			for (j = 0; j < columns; j++) {
1168 				c = ctab + j * lines + i;
1169 				printf("%s%c", c->name,
1170 					c->implemented ? ' ' : '*');
1171 				if (c + lines >= &ctab[NCMDS])
1172 					break;
1173 				w = strlen(c->name) + 1;
1174 				while (w < width) {
1175 					putchar(' ');
1176 					w++;
1177 				}
1178 			}
1179 			printf("\r\n");
1180 		}
1181 		(void) fflush(stdout);
1182 		reply(214, "Direct comments to ftp-bugs@%s.", hostname);
1183 		return;
1184 	}
1185 	upper(s);
1186 	c = lookup(ctab, s);
1187 	if (c == (struct tab *)0) {
1188 		reply(502, "Unknown command %s.", s);
1189 		return;
1190 	}
1191 	if (c->implemented)
1192 		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
1193 	else
1194 		reply(214, "%s%-*s\t%s; unimplemented.", type, width,
1195 		    c->name, c->help);
1196 }
1197 
1198 static void
1199 sizecmd(filename)
1200 	char *filename;
1201 {
1202 	switch (type) {
1203 	case TYPE_L:
1204 	case TYPE_I: {
1205 		struct stat stbuf;
1206 		if (stat(filename, &stbuf) < 0 || !S_ISREG(stbuf.st_mode))
1207 			reply(550, "%s: not a plain file.", filename);
1208 		else
1209 			reply(213, "%qu", stbuf.st_size);
1210 		break; }
1211 	case TYPE_A: {
1212 		FILE *fin;
1213 		int c;
1214 		off_t count;
1215 		struct stat stbuf;
1216 		fin = fopen(filename, "r");
1217 		if (fin == NULL) {
1218 			perror_reply(550, filename);
1219 			return;
1220 		}
1221 		if (fstat(fileno(fin), &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) {
1222 			reply(550, "%s: not a plain file.", filename);
1223 			(void) fclose(fin);
1224 			return;
1225 		}
1226 
1227 		count = 0;
1228 		while((c=getc(fin)) != EOF) {
1229 			if (c == '\n')	/* will get expanded to \r\n */
1230 				count++;
1231 			count++;
1232 		}
1233 		(void) fclose(fin);
1234 
1235 		reply(213, "%qd", count);
1236 		break; }
1237 	default:
1238 		reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
1239 	}
1240 }
1241