xref: /original-bsd/libexec/ftpd/ftpcmd.y (revision c829ecf6)
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.23 (Berkeley) 06/01/90
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.23 (Berkeley) 06/01/90";
19 #endif /* not lint */
20 
21 #include <sys/param.h>
22 #include <sys/socket.h>
23 
24 #include <netinet/in.h>
25 
26 #include <arpa/ftp.h>
27 
28 #include <stdio.h>
29 #include <signal.h>
30 #include <ctype.h>
31 #include <pwd.h>
32 #include <setjmp.h>
33 #include <syslog.h>
34 #include <sys/stat.h>
35 #include <time.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	**glob();
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 char	*index();
65 %}
66 
67 %token
68 	A	B	C	E	F	I
69 	L	N	P	R	S	T
70 
71 	SP	CRLF	COMMA	STRING	NUMBER
72 
73 	USER	PASS	ACCT	REIN	QUIT	PORT
74 	PASV	TYPE	STRU	MODE	RETR	STOR
75 	APPE	MLFL	MAIL	MSND	MSOM	MSAM
76 	MRSQ	MRCP	ALLO	REST	RNFR	RNTO
77 	ABOR	DELE	CWD	LIST	NLST	SITE
78 	STAT	HELP	NOOP	MKD	RMD	PWD
79 	CDUP	STOU	SMNT	SYST	SIZE	MDTM
80 
81 	UMASK	IDLE	CHMOD
82 
83 	LEXERR
84 
85 %start	cmd_list
86 
87 %%
88 
89 cmd_list:	/* empty */
90 	|	cmd_list cmd
91 		= {
92 			fromname = (char *) 0;
93 			restart_point = (off_t) 0;
94 		}
95 	|	cmd_list rcmd
96 	;
97 
98 cmd:		USER SP username CRLF
99 		= {
100 			user((char *) $3);
101 			free((char *) $3);
102 		}
103 	|	PASS SP password CRLF
104 		= {
105 			pass((char *) $3);
106 			free((char *) $3);
107 		}
108 	|	PORT SP host_port CRLF
109 		= {
110 			usedefault = 0;
111 			if (pdata >= 0) {
112 				(void) close(pdata);
113 				pdata = -1;
114 			}
115 			reply(200, "PORT command successful.");
116 		}
117 	|	PASV CRLF
118 		= {
119 			passive();
120 		}
121 	|	TYPE SP type_code CRLF
122 		= {
123 			switch (cmd_type) {
124 
125 			case TYPE_A:
126 				if (cmd_form == FORM_N) {
127 					reply(200, "Type set to A.");
128 					type = cmd_type;
129 					form = cmd_form;
130 				} else
131 					reply(504, "Form must be N.");
132 				break;
133 
134 			case TYPE_E:
135 				reply(504, "Type E not implemented.");
136 				break;
137 
138 			case TYPE_I:
139 				reply(200, "Type set to I.");
140 				type = cmd_type;
141 				break;
142 
143 			case TYPE_L:
144 #if NBBY == 8
145 				if (cmd_bytesz == 8) {
146 					reply(200,
147 					    "Type set to L (byte size 8).");
148 					type = cmd_type;
149 				} else
150 					reply(504, "Byte size must be 8.");
151 #else /* NBBY == 8 */
152 				UNIMPLEMENTED for NBBY != 8
153 #endif /* NBBY == 8 */
154 			}
155 		}
156 	|	STRU SP struct_code CRLF
157 		= {
158 			switch ($3) {
159 
160 			case STRU_F:
161 				reply(200, "STRU F ok.");
162 				break;
163 
164 			default:
165 				reply(504, "Unimplemented STRU type.");
166 			}
167 		}
168 	|	MODE SP mode_code CRLF
169 		= {
170 			switch ($3) {
171 
172 			case MODE_S:
173 				reply(200, "MODE S ok.");
174 				break;
175 
176 			default:
177 				reply(502, "Unimplemented MODE type.");
178 			}
179 		}
180 	|	ALLO SP NUMBER CRLF
181 		= {
182 			reply(202, "ALLO command ignored.");
183 		}
184 	|	ALLO SP NUMBER SP R SP NUMBER CRLF
185 		= {
186 			reply(202, "ALLO command ignored.");
187 		}
188 	|	RETR check_login SP pathname CRLF
189 		= {
190 			if ($2 && $4 != NULL)
191 				retrieve((char *) 0, (char *) $4);
192 			if ($4 != NULL)
193 				free((char *) $4);
194 		}
195 	|	STOR check_login SP pathname CRLF
196 		= {
197 			if ($2 && $4 != NULL)
198 				store((char *) $4, "w", 0);
199 			if ($4 != NULL)
200 				free((char *) $4);
201 		}
202 	|	APPE check_login SP pathname CRLF
203 		= {
204 			if ($2 && $4 != NULL)
205 				store((char *) $4, "a", 0);
206 			if ($4 != NULL)
207 				free((char *) $4);
208 		}
209 	|	NLST check_login CRLF
210 		= {
211 			if ($2)
212 				send_file_list(".");
213 		}
214 	|	NLST check_login SP STRING CRLF
215 		= {
216 			if ($2 && $4 != NULL)
217 				send_file_list((char *) $4);
218 			if ($4 != NULL)
219 				free((char *) $4);
220 		}
221 	|	LIST check_login CRLF
222 		= {
223 			if ($2)
224 				retrieve("/bin/ls -lgA", "");
225 		}
226 	|	LIST check_login SP pathname CRLF
227 		= {
228 			if ($2 && $4 != NULL)
229 				retrieve("/bin/ls -lgA %s", (char *) $4);
230 			if ($4 != NULL)
231 				free((char *) $4);
232 		}
233 	|	STAT check_login SP pathname CRLF
234 		= {
235 			if ($2 && $4 != NULL)
236 				statfilecmd((char *) $4);
237 			if ($4 != NULL)
238 				free((char *) $4);
239 		}
240 	|	STAT CRLF
241 		= {
242 			statcmd();
243 		}
244 	|	DELE check_login SP pathname CRLF
245 		= {
246 			if ($2 && $4 != NULL)
247 				delete((char *) $4);
248 			if ($4 != NULL)
249 				free((char *) $4);
250 		}
251 	|	RNTO SP pathname CRLF
252 		= {
253 			if (fromname) {
254 				renamecmd(fromname, (char *) $3);
255 				free(fromname);
256 				fromname = (char *) 0;
257 			} else {
258 				reply(503, "Bad sequence of commands.");
259 			}
260 			free((char *) $3);
261 		}
262 	|	ABOR CRLF
263 		= {
264 			reply(225, "ABOR command successful.");
265 		}
266 	|	CWD check_login CRLF
267 		= {
268 			if ($2)
269 				cwd(pw->pw_dir);
270 		}
271 	|	CWD check_login SP pathname CRLF
272 		= {
273 			if ($2 && $4 != NULL)
274 				cwd((char *) $4);
275 			if ($4 != NULL)
276 				free((char *) $4);
277 		}
278 	|	HELP CRLF
279 		= {
280 			help(cmdtab, (char *) 0);
281 		}
282 	|	HELP SP STRING CRLF
283 		= {
284 			register char *cp = (char *)$3;
285 
286 			if (strncasecmp(cp, "SITE", 4) == 0) {
287 				cp = (char *)$3 + 4;
288 				if (*cp == ' ')
289 					cp++;
290 				if (*cp)
291 					help(sitetab, cp);
292 				else
293 					help(sitetab, (char *) 0);
294 			} else
295 				help(cmdtab, (char *) $3);
296 		}
297 	|	NOOP CRLF
298 		= {
299 			reply(200, "NOOP command successful.");
300 		}
301 	|	MKD check_login SP pathname CRLF
302 		= {
303 			if ($2 && $4 != NULL)
304 				makedir((char *) $4);
305 			if ($4 != NULL)
306 				free((char *) $4);
307 		}
308 	|	RMD check_login SP pathname CRLF
309 		= {
310 			if ($2 && $4 != NULL)
311 				removedir((char *) $4);
312 			if ($4 != NULL)
313 				free((char *) $4);
314 		}
315 	|	PWD check_login CRLF
316 		= {
317 			if ($2)
318 				pwd();
319 		}
320 	|	CDUP check_login CRLF
321 		= {
322 			if ($2)
323 				cwd("..");
324 		}
325 	|	SITE SP HELP CRLF
326 		= {
327 			help(sitetab, (char *) 0);
328 		}
329 	|	SITE SP HELP SP STRING CRLF
330 		= {
331 			help(sitetab, (char *) $5);
332 		}
333 	|	SITE SP UMASK check_login CRLF
334 		= {
335 			int oldmask;
336 
337 			if ($4) {
338 				oldmask = umask(0);
339 				(void) umask(oldmask);
340 				reply(200, "Current UMASK is %03o", oldmask);
341 			}
342 		}
343 	|	SITE SP UMASK check_login SP octal_number CRLF
344 		= {
345 			int oldmask;
346 
347 			if ($4) {
348 				if (($6 == -1) || ($6 > 0777)) {
349 					reply(501, "Bad UMASK value");
350 				} else {
351 					oldmask = umask($6);
352 					reply(200,
353 					    "UMASK set to %03o (was %03o)",
354 					    $6, oldmask);
355 				}
356 			}
357 		}
358 	|	SITE SP CHMOD check_login SP octal_number SP pathname CRLF
359 		= {
360 			if ($4 && ($8 != NULL)) {
361 				if ($6 > 0777)
362 					reply(501,
363 				"CHMOD: Mode value must be between 0 and 0777");
364 				else if (chmod((char *) $8, $6) < 0)
365 					perror_reply(550, (char *) $8);
366 				else
367 					reply(200, "CHMOD command successful.");
368 			}
369 			if ($8 != NULL)
370 				free((char *) $8);
371 		}
372 	|	SITE SP IDLE CRLF
373 		= {
374 			reply(200,
375 			    "Current IDLE time limit is %d seconds; max %d",
376 				timeout, maxtimeout);
377 		}
378 	|	SITE SP IDLE SP NUMBER CRLF
379 		= {
380 			if ($5 < 30 || $5 > maxtimeout) {
381 				reply(501,
382 			"Maximum IDLE time must be between 30 and %d seconds",
383 				    maxtimeout);
384 			} else {
385 				timeout = $5;
386 				(void) alarm((unsigned) timeout);
387 				reply(200,
388 				    "Maximum IDLE time set to %d seconds",
389 				    timeout);
390 			}
391 		}
392 	|	STOU check_login SP pathname CRLF
393 		= {
394 			if ($2 && $4 != NULL)
395 				store((char *) $4, "w", 1);
396 			if ($4 != NULL)
397 				free((char *) $4);
398 		}
399 	|	SYST CRLF
400 		= {
401 #ifdef unix
402 #ifdef BSD
403 			reply(215, "UNIX Type: L%d Version: BSD-%d",
404 				NBBY, BSD);
405 #else /* BSD */
406 			reply(215, "UNIX Type: L%d", NBBY);
407 #endif /* BSD */
408 #else /* unix */
409 			reply(215, "UNKNOWN Type: L%d", NBBY);
410 #endif /* unix */
411 		}
412 
413 		/*
414 		 * SIZE is not in RFC959, but Postel has blessed it and
415 		 * it will be in the updated RFC.
416 		 *
417 		 * Return size of file in a format suitable for
418 		 * using with RESTART (we just count bytes).
419 		 */
420 	|	SIZE check_login SP pathname CRLF
421 		= {
422 			if ($2 && $4 != NULL)
423 				sizecmd((char *) $4);
424 			if ($4 != NULL)
425 				free((char *) $4);
426 		}
427 
428 		/*
429 		 * MDTM is not in RFC959, but Postel has blessed it and
430 		 * it will be in the updated RFC.
431 		 *
432 		 * Return modification time of file as an ISO 3307
433 		 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
434 		 * where xxx is the fractional second (of any precision,
435 		 * not necessarily 3 digits)
436 		 */
437 	|	MDTM check_login SP pathname CRLF
438 		= {
439 			if ($2 && $4 != NULL) {
440 				struct stat stbuf;
441 				if (stat((char *) $4, &stbuf) < 0)
442 					perror_reply(550, "%s", (char *) $4);
443 				else if ((stbuf.st_mode&S_IFMT) != S_IFREG) {
444 					reply(550, "%s: not a plain file.",
445 						(char *) $4);
446 				} else {
447 					register struct tm *t;
448 					struct tm *gmtime();
449 					t = gmtime(&stbuf.st_mtime);
450 					reply(213,
451 					    "19%02d%02d%02d%02d%02d%02d",
452 					    t->tm_year, t->tm_mon+1, t->tm_mday,
453 					    t->tm_hour, t->tm_min, t->tm_sec);
454 				}
455 			}
456 			if ($4 != NULL)
457 				free((char *) $4);
458 		}
459 	|	QUIT CRLF
460 		= {
461 			reply(221, "Goodbye.");
462 			dologout(0);
463 		}
464 	|	error CRLF
465 		= {
466 			yyerrok;
467 		}
468 	;
469 rcmd:		RNFR check_login SP pathname CRLF
470 		= {
471 			char *renamefrom();
472 
473 			restart_point = (off_t) 0;
474 			if ($2 && $4) {
475 				fromname = renamefrom((char *) $4);
476 				if (fromname == (char *) 0 && $4) {
477 					free((char *) $4);
478 				}
479 			}
480 		}
481 	|	REST SP byte_size CRLF
482 		= {
483 			long atol();
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 **)&($$) = *glob((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,	"(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 struct tab *
742 lookup(p, cmd)
743 	register struct tab *p;
744 	char *cmd;
745 {
746 
747 	for (; p->name != NULL; p++)
748 		if (strcmp(cmd, p->name) == 0)
749 			return (p);
750 	return (0);
751 }
752 
753 #include <arpa/telnet.h>
754 
755 /*
756  * getline - a hacked up version of fgets to ignore TELNET escape codes.
757  */
758 char *
759 getline(s, n, iop)
760 	char *s;
761 	register FILE *iop;
762 {
763 	register c;
764 	register char *cs;
765 
766 	cs = s;
767 /* tmpline may contain saved command from urgent mode interruption */
768 	for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
769 		*cs++ = tmpline[c];
770 		if (tmpline[c] == '\n') {
771 			*cs++ = '\0';
772 			if (debug)
773 				syslog(LOG_DEBUG, "command: %s", s);
774 			tmpline[0] = '\0';
775 			return(s);
776 		}
777 		if (c == 0)
778 			tmpline[0] = '\0';
779 	}
780 	while ((c = getc(iop)) != EOF) {
781 		c &= 0377;
782 		if (c == IAC) {
783 		    if ((c = getc(iop)) != EOF) {
784 			c &= 0377;
785 			switch (c) {
786 			case WILL:
787 			case WONT:
788 				c = getc(iop);
789 				printf("%c%c%c", IAC, DONT, 0377&c);
790 				(void) fflush(stdout);
791 				continue;
792 			case DO:
793 			case DONT:
794 				c = getc(iop);
795 				printf("%c%c%c", IAC, WONT, 0377&c);
796 				(void) fflush(stdout);
797 				continue;
798 			case IAC:
799 				break;
800 			default:
801 				continue;	/* ignore command */
802 			}
803 		    }
804 		}
805 		*cs++ = c;
806 		if (--n <= 0 || c == '\n')
807 			break;
808 	}
809 	if (c == EOF && cs == s)
810 		return (NULL);
811 	*cs++ = '\0';
812 	if (debug)
813 		syslog(LOG_DEBUG, "command: %s", s);
814 	return (s);
815 }
816 
817 static int
818 toolong()
819 {
820 	time_t now;
821 	extern char *ctime();
822 	extern time_t time();
823 
824 	reply(421,
825 	  "Timeout (%d seconds): closing control connection.", timeout);
826 	(void) time(&now);
827 	if (logging) {
828 		syslog(LOG_INFO,
829 			"User %s timed out after %d seconds at %s",
830 			(pw ? pw -> pw_name : "unknown"), timeout, ctime(&now));
831 	}
832 	dologout(1);
833 }
834 
835 yylex()
836 {
837 	static int cpos, state;
838 	register char *cp, *cp2;
839 	register struct tab *p;
840 	int n;
841 	char c, *strpbrk();
842 	char *copy();
843 
844 	for (;;) {
845 		switch (state) {
846 
847 		case CMD:
848 			(void) signal(SIGALRM, toolong);
849 			(void) alarm((unsigned) timeout);
850 			if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
851 				reply(221, "You could at least say goodbye.");
852 				dologout(0);
853 			}
854 			(void) alarm(0);
855 #ifdef SETPROCTITLE
856 			if (strncasecmp(cbuf, "PASS", 4) != NULL)
857 				setproctitle("%s: %s", proctitle, cbuf);
858 #endif /* SETPROCTITLE */
859 			if ((cp = index(cbuf, '\r'))) {
860 				*cp++ = '\n';
861 				*cp = '\0';
862 			}
863 			if ((cp = strpbrk(cbuf, " \n")))
864 				cpos = cp - cbuf;
865 			if (cpos == 0)
866 				cpos = 4;
867 			c = cbuf[cpos];
868 			cbuf[cpos] = '\0';
869 			upper(cbuf);
870 			p = lookup(cmdtab, cbuf);
871 			cbuf[cpos] = c;
872 			if (p != 0) {
873 				if (p->implemented == 0) {
874 					nack(p->name);
875 					longjmp(errcatch,0);
876 					/* NOTREACHED */
877 				}
878 				state = p->state;
879 				*(char **)&yylval = p->name;
880 				return (p->token);
881 			}
882 			break;
883 
884 		case SITECMD:
885 			if (cbuf[cpos] == ' ') {
886 				cpos++;
887 				return (SP);
888 			}
889 			cp = &cbuf[cpos];
890 			if ((cp2 = strpbrk(cp, " \n")))
891 				cpos = cp2 - cbuf;
892 			c = cbuf[cpos];
893 			cbuf[cpos] = '\0';
894 			upper(cp);
895 			p = lookup(sitetab, cp);
896 			cbuf[cpos] = c;
897 			if (p != 0) {
898 				if (p->implemented == 0) {
899 					state = CMD;
900 					nack(p->name);
901 					longjmp(errcatch,0);
902 					/* NOTREACHED */
903 				}
904 				state = p->state;
905 				*(char **)&yylval = p->name;
906 				return (p->token);
907 			}
908 			state = CMD;
909 			break;
910 
911 		case OSTR:
912 			if (cbuf[cpos] == '\n') {
913 				state = CMD;
914 				return (CRLF);
915 			}
916 			/* FALLTHROUGH */
917 
918 		case STR1:
919 		case ZSTR1:
920 		dostr1:
921 			if (cbuf[cpos] == ' ') {
922 				cpos++;
923 				state = state == OSTR ? STR2 : ++state;
924 				return (SP);
925 			}
926 			break;
927 
928 		case ZSTR2:
929 			if (cbuf[cpos] == '\n') {
930 				state = CMD;
931 				return (CRLF);
932 			}
933 			/* FALLTHROUGH */
934 
935 		case STR2:
936 			cp = &cbuf[cpos];
937 			n = strlen(cp);
938 			cpos += n - 1;
939 			/*
940 			 * Make sure the string is nonempty and \n terminated.
941 			 */
942 			if (n > 1 && cbuf[cpos] == '\n') {
943 				cbuf[cpos] = '\0';
944 				*(char **)&yylval = copy(cp);
945 				cbuf[cpos] = '\n';
946 				state = ARGS;
947 				return (STRING);
948 			}
949 			break;
950 
951 		case NSTR:
952 			if (cbuf[cpos] == ' ') {
953 				cpos++;
954 				return (SP);
955 			}
956 			if (isdigit(cbuf[cpos])) {
957 				cp = &cbuf[cpos];
958 				while (isdigit(cbuf[++cpos]))
959 					;
960 				c = cbuf[cpos];
961 				cbuf[cpos] = '\0';
962 				yylval = atoi(cp);
963 				cbuf[cpos] = c;
964 				state = STR1;
965 				return (NUMBER);
966 			}
967 			state = STR1;
968 			goto dostr1;
969 
970 		case ARGS:
971 			if (isdigit(cbuf[cpos])) {
972 				cp = &cbuf[cpos];
973 				while (isdigit(cbuf[++cpos]))
974 					;
975 				c = cbuf[cpos];
976 				cbuf[cpos] = '\0';
977 				yylval = atoi(cp);
978 				cbuf[cpos] = c;
979 				return (NUMBER);
980 			}
981 			switch (cbuf[cpos++]) {
982 
983 			case '\n':
984 				state = CMD;
985 				return (CRLF);
986 
987 			case ' ':
988 				return (SP);
989 
990 			case ',':
991 				return (COMMA);
992 
993 			case 'A':
994 			case 'a':
995 				return (A);
996 
997 			case 'B':
998 			case 'b':
999 				return (B);
1000 
1001 			case 'C':
1002 			case 'c':
1003 				return (C);
1004 
1005 			case 'E':
1006 			case 'e':
1007 				return (E);
1008 
1009 			case 'F':
1010 			case 'f':
1011 				return (F);
1012 
1013 			case 'I':
1014 			case 'i':
1015 				return (I);
1016 
1017 			case 'L':
1018 			case 'l':
1019 				return (L);
1020 
1021 			case 'N':
1022 			case 'n':
1023 				return (N);
1024 
1025 			case 'P':
1026 			case 'p':
1027 				return (P);
1028 
1029 			case 'R':
1030 			case 'r':
1031 				return (R);
1032 
1033 			case 'S':
1034 			case 's':
1035 				return (S);
1036 
1037 			case 'T':
1038 			case 't':
1039 				return (T);
1040 
1041 			}
1042 			break;
1043 
1044 		default:
1045 			fatal("Unknown state in scanner.");
1046 		}
1047 		yyerror((char *) 0);
1048 		state = CMD;
1049 		longjmp(errcatch,0);
1050 	}
1051 }
1052 
1053 upper(s)
1054 	register char *s;
1055 {
1056 	while (*s != '\0') {
1057 		if (islower(*s))
1058 			*s = toupper(*s);
1059 		s++;
1060 	}
1061 }
1062 
1063 char *
1064 copy(s)
1065 	char *s;
1066 {
1067 	char *p;
1068 	extern char *malloc(), *strcpy();
1069 
1070 	p = malloc((unsigned) strlen(s) + 1);
1071 	if (p == NULL)
1072 		fatal("Ran out of memory.");
1073 	(void) strcpy(p, s);
1074 	return (p);
1075 }
1076 
1077 help(ctab, s)
1078 	struct tab *ctab;
1079 	char *s;
1080 {
1081 	register struct tab *c;
1082 	register int width, NCMDS;
1083 	char *type;
1084 
1085 	if (ctab == sitetab)
1086 		type = "SITE ";
1087 	else
1088 		type = "";
1089 	width = 0, NCMDS = 0;
1090 	for (c = ctab; c->name != NULL; c++) {
1091 		int len = strlen(c->name);
1092 
1093 		if (len > width)
1094 			width = len;
1095 		NCMDS++;
1096 	}
1097 	width = (width + 8) &~ 7;
1098 	if (s == 0) {
1099 		register int i, j, w;
1100 		int columns, lines;
1101 
1102 		lreply(214, "The following %scommands are recognized %s.",
1103 		    type, "(* =>'s unimplemented)");
1104 		columns = 76 / width;
1105 		if (columns == 0)
1106 			columns = 1;
1107 		lines = (NCMDS + columns - 1) / columns;
1108 		for (i = 0; i < lines; i++) {
1109 			printf("   ");
1110 			for (j = 0; j < columns; j++) {
1111 				c = ctab + j * lines + i;
1112 				printf("%s%c", c->name,
1113 					c->implemented ? ' ' : '*');
1114 				if (c + lines >= &ctab[NCMDS])
1115 					break;
1116 				w = strlen(c->name) + 1;
1117 				while (w < width) {
1118 					putchar(' ');
1119 					w++;
1120 				}
1121 			}
1122 			printf("\r\n");
1123 		}
1124 		(void) fflush(stdout);
1125 		reply(214, "Direct comments to ftp-bugs@%s.", hostname);
1126 		return;
1127 	}
1128 	upper(s);
1129 	c = lookup(ctab, s);
1130 	if (c == (struct tab *)0) {
1131 		reply(502, "Unknown command %s.", s);
1132 		return;
1133 	}
1134 	if (c->implemented)
1135 		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
1136 	else
1137 		reply(214, "%s%-*s\t%s; unimplemented.", type, width,
1138 		    c->name, c->help);
1139 }
1140 
1141 sizecmd(filename)
1142 char *filename;
1143 {
1144 	switch (type) {
1145 	case TYPE_L:
1146 	case TYPE_I: {
1147 		struct stat stbuf;
1148 		if (stat(filename, &stbuf) < 0 ||
1149 		    (stbuf.st_mode&S_IFMT) != S_IFREG)
1150 			reply(550, "%s: not a plain file.", filename);
1151 		else
1152 			reply(213, "%lu", stbuf.st_size);
1153 		break;}
1154 	case TYPE_A: {
1155 		FILE *fin;
1156 		register int c;
1157 		register long count;
1158 		struct stat stbuf;
1159 		fin = fopen(filename, "r");
1160 		if (fin == NULL) {
1161 			perror_reply(550, filename);
1162 			return;
1163 		}
1164 		if (fstat(fileno(fin), &stbuf) < 0 ||
1165 		    (stbuf.st_mode&S_IFMT) != S_IFREG) {
1166 			reply(550, "%s: not a plain file.", filename);
1167 			(void) fclose(fin);
1168 			return;
1169 		}
1170 
1171 		count = 0;
1172 		while((c=getc(fin)) != EOF) {
1173 			if (c == '\n')	/* will get expanded to \r\n */
1174 				count++;
1175 			count++;
1176 		}
1177 		(void) fclose(fin);
1178 
1179 		reply(213, "%ld", count);
1180 		break;}
1181 	default:
1182 		reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
1183 	}
1184 }
1185