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