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