xref: /original-bsd/libexec/ftpd/ftpcmd.y (revision 753853ba)
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.25 (Berkeley) 03/18/92
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.25 (Berkeley) 03/18/92";
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 		if (!guest && strncasecmp("pass ", s, 5) == 0) {
813 			/* Don't syslog passwords */
814 			syslog(LOG_DEBUG, "command: %.5s ???", s);
815 		} else {
816 			register char *cp;
817 			register int len;
818 
819 			/* Don't syslog trailing CR-LF */
820 			len = strlen(s);
821 			cp = s + len - 1;
822 			while (cp >= s && (*cp == '\n' || *cp == '\r')) {
823 				--cp;
824 				--len;
825 			}
826 			syslog(LOG_DEBUG, "command: %.*s", len, s);
827 		}
828 	}
829 	return (s);
830 }
831 
832 static void
833 toolong()
834 {
835 
836 	reply(421,
837 	    "Timeout (%d seconds): closing control connection.", timeout);
838 	if (logging)
839 		syslog(LOG_INFO, "User %s timed out after %d seconds",
840 		    (pw ? pw -> pw_name : "unknown"), timeout);
841 	dologout(1);
842 }
843 
844 yylex()
845 {
846 	static int cpos, state;
847 	register char *cp, *cp2;
848 	register struct tab *p;
849 	int n;
850 	char c, *copy();
851 
852 	for (;;) {
853 		switch (state) {
854 
855 		case CMD:
856 			(void) signal(SIGALRM, toolong);
857 			(void) alarm((unsigned) timeout);
858 			if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
859 				reply(221, "You could at least say goodbye.");
860 				dologout(0);
861 			}
862 			(void) alarm(0);
863 #ifdef SETPROCTITLE
864 			if (strncasecmp(cbuf, "PASS", 4) != NULL)
865 				setproctitle("%s: %s", proctitle, cbuf);
866 #endif /* SETPROCTITLE */
867 			if ((cp = index(cbuf, '\r'))) {
868 				*cp++ = '\n';
869 				*cp = '\0';
870 			}
871 			if ((cp = strpbrk(cbuf, " \n")))
872 				cpos = cp - cbuf;
873 			if (cpos == 0)
874 				cpos = 4;
875 			c = cbuf[cpos];
876 			cbuf[cpos] = '\0';
877 			upper(cbuf);
878 			p = lookup(cmdtab, cbuf);
879 			cbuf[cpos] = c;
880 			if (p != 0) {
881 				if (p->implemented == 0) {
882 					nack(p->name);
883 					longjmp(errcatch,0);
884 					/* NOTREACHED */
885 				}
886 				state = p->state;
887 				*(char **)&yylval = p->name;
888 				return (p->token);
889 			}
890 			break;
891 
892 		case SITECMD:
893 			if (cbuf[cpos] == ' ') {
894 				cpos++;
895 				return (SP);
896 			}
897 			cp = &cbuf[cpos];
898 			if ((cp2 = strpbrk(cp, " \n")))
899 				cpos = cp2 - cbuf;
900 			c = cbuf[cpos];
901 			cbuf[cpos] = '\0';
902 			upper(cp);
903 			p = lookup(sitetab, cp);
904 			cbuf[cpos] = c;
905 			if (p != 0) {
906 				if (p->implemented == 0) {
907 					state = CMD;
908 					nack(p->name);
909 					longjmp(errcatch,0);
910 					/* NOTREACHED */
911 				}
912 				state = p->state;
913 				*(char **)&yylval = p->name;
914 				return (p->token);
915 			}
916 			state = CMD;
917 			break;
918 
919 		case OSTR:
920 			if (cbuf[cpos] == '\n') {
921 				state = CMD;
922 				return (CRLF);
923 			}
924 			/* FALLTHROUGH */
925 
926 		case STR1:
927 		case ZSTR1:
928 		dostr1:
929 			if (cbuf[cpos] == ' ') {
930 				cpos++;
931 				state = state == OSTR ? STR2 : ++state;
932 				return (SP);
933 			}
934 			break;
935 
936 		case ZSTR2:
937 			if (cbuf[cpos] == '\n') {
938 				state = CMD;
939 				return (CRLF);
940 			}
941 			/* FALLTHROUGH */
942 
943 		case STR2:
944 			cp = &cbuf[cpos];
945 			n = strlen(cp);
946 			cpos += n - 1;
947 			/*
948 			 * Make sure the string is nonempty and \n terminated.
949 			 */
950 			if (n > 1 && cbuf[cpos] == '\n') {
951 				cbuf[cpos] = '\0';
952 				*(char **)&yylval = copy(cp);
953 				cbuf[cpos] = '\n';
954 				state = ARGS;
955 				return (STRING);
956 			}
957 			break;
958 
959 		case NSTR:
960 			if (cbuf[cpos] == ' ') {
961 				cpos++;
962 				return (SP);
963 			}
964 			if (isdigit(cbuf[cpos])) {
965 				cp = &cbuf[cpos];
966 				while (isdigit(cbuf[++cpos]))
967 					;
968 				c = cbuf[cpos];
969 				cbuf[cpos] = '\0';
970 				yylval = atoi(cp);
971 				cbuf[cpos] = c;
972 				state = STR1;
973 				return (NUMBER);
974 			}
975 			state = STR1;
976 			goto dostr1;
977 
978 		case ARGS:
979 			if (isdigit(cbuf[cpos])) {
980 				cp = &cbuf[cpos];
981 				while (isdigit(cbuf[++cpos]))
982 					;
983 				c = cbuf[cpos];
984 				cbuf[cpos] = '\0';
985 				yylval = atoi(cp);
986 				cbuf[cpos] = c;
987 				return (NUMBER);
988 			}
989 			switch (cbuf[cpos++]) {
990 
991 			case '\n':
992 				state = CMD;
993 				return (CRLF);
994 
995 			case ' ':
996 				return (SP);
997 
998 			case ',':
999 				return (COMMA);
1000 
1001 			case 'A':
1002 			case 'a':
1003 				return (A);
1004 
1005 			case 'B':
1006 			case 'b':
1007 				return (B);
1008 
1009 			case 'C':
1010 			case 'c':
1011 				return (C);
1012 
1013 			case 'E':
1014 			case 'e':
1015 				return (E);
1016 
1017 			case 'F':
1018 			case 'f':
1019 				return (F);
1020 
1021 			case 'I':
1022 			case 'i':
1023 				return (I);
1024 
1025 			case 'L':
1026 			case 'l':
1027 				return (L);
1028 
1029 			case 'N':
1030 			case 'n':
1031 				return (N);
1032 
1033 			case 'P':
1034 			case 'p':
1035 				return (P);
1036 
1037 			case 'R':
1038 			case 'r':
1039 				return (R);
1040 
1041 			case 'S':
1042 			case 's':
1043 				return (S);
1044 
1045 			case 'T':
1046 			case 't':
1047 				return (T);
1048 
1049 			}
1050 			break;
1051 
1052 		default:
1053 			fatal("Unknown state in scanner.");
1054 		}
1055 		yyerror((char *) 0);
1056 		state = CMD;
1057 		longjmp(errcatch,0);
1058 	}
1059 }
1060 
1061 upper(s)
1062 	register char *s;
1063 {
1064 	while (*s != '\0') {
1065 		if (islower(*s))
1066 			*s = toupper(*s);
1067 		s++;
1068 	}
1069 }
1070 
1071 char *
1072 copy(s)
1073 	char *s;
1074 {
1075 	char *p;
1076 
1077 	p = malloc((unsigned) strlen(s) + 1);
1078 	if (p == NULL)
1079 		fatal("Ran out of memory.");
1080 	(void) strcpy(p, s);
1081 	return (p);
1082 }
1083 
1084 help(ctab, s)
1085 	struct tab *ctab;
1086 	char *s;
1087 {
1088 	register struct tab *c;
1089 	register int width, NCMDS;
1090 	char *type;
1091 
1092 	if (ctab == sitetab)
1093 		type = "SITE ";
1094 	else
1095 		type = "";
1096 	width = 0, NCMDS = 0;
1097 	for (c = ctab; c->name != NULL; c++) {
1098 		int len = strlen(c->name);
1099 
1100 		if (len > width)
1101 			width = len;
1102 		NCMDS++;
1103 	}
1104 	width = (width + 8) &~ 7;
1105 	if (s == 0) {
1106 		register int i, j, w;
1107 		int columns, lines;
1108 
1109 		lreply(214, "The following %scommands are recognized %s.",
1110 		    type, "(* =>'s unimplemented)");
1111 		columns = 76 / width;
1112 		if (columns == 0)
1113 			columns = 1;
1114 		lines = (NCMDS + columns - 1) / columns;
1115 		for (i = 0; i < lines; i++) {
1116 			printf("   ");
1117 			for (j = 0; j < columns; j++) {
1118 				c = ctab + j * lines + i;
1119 				printf("%s%c", c->name,
1120 					c->implemented ? ' ' : '*');
1121 				if (c + lines >= &ctab[NCMDS])
1122 					break;
1123 				w = strlen(c->name) + 1;
1124 				while (w < width) {
1125 					putchar(' ');
1126 					w++;
1127 				}
1128 			}
1129 			printf("\r\n");
1130 		}
1131 		(void) fflush(stdout);
1132 		reply(214, "Direct comments to ftp-bugs@%s.", hostname);
1133 		return;
1134 	}
1135 	upper(s);
1136 	c = lookup(ctab, s);
1137 	if (c == (struct tab *)0) {
1138 		reply(502, "Unknown command %s.", s);
1139 		return;
1140 	}
1141 	if (c->implemented)
1142 		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
1143 	else
1144 		reply(214, "%s%-*s\t%s; unimplemented.", type, width,
1145 		    c->name, c->help);
1146 }
1147 
1148 sizecmd(filename)
1149 char *filename;
1150 {
1151 	switch (type) {
1152 	case TYPE_L:
1153 	case TYPE_I: {
1154 		struct stat stbuf;
1155 		if (stat(filename, &stbuf) < 0 ||
1156 		    (stbuf.st_mode&S_IFMT) != S_IFREG)
1157 			reply(550, "%s: not a plain file.", filename);
1158 		else
1159 			reply(213, "%lu", stbuf.st_size);
1160 		break;}
1161 	case TYPE_A: {
1162 		FILE *fin;
1163 		register int c;
1164 		register long count;
1165 		struct stat stbuf;
1166 		fin = fopen(filename, "r");
1167 		if (fin == NULL) {
1168 			perror_reply(550, filename);
1169 			return;
1170 		}
1171 		if (fstat(fileno(fin), &stbuf) < 0 ||
1172 		    (stbuf.st_mode&S_IFMT) != S_IFREG) {
1173 			reply(550, "%s: not a plain file.", filename);
1174 			(void) fclose(fin);
1175 			return;
1176 		}
1177 
1178 		count = 0;
1179 		while((c=getc(fin)) != EOF) {
1180 			if (c == '\n')	/* will get expanded to \r\n */
1181 				count++;
1182 			count++;
1183 		}
1184 		(void) fclose(fin);
1185 
1186 		reply(213, "%ld", count);
1187 		break;}
1188 	default:
1189 		reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
1190 	}
1191 }
1192