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