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