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