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