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