Home | History | Annotate | Line # | Download | only in ftpd
ftpcmd.y revision 1.22
      1 /*	$NetBSD: ftpcmd.y,v 1.22 1998/09/06 10:39:40 lukem Exp $	*/
      2 
      3 /*
      4  * Copyright (c) 1985, 1988, 1993, 1994
      5  *	The Regents of the University of California.  All rights reserved.
      6  *
      7  * Redistribution and use in source and binary forms, with or without
      8  * modification, are permitted provided that the following conditions
      9  * are met:
     10  * 1. Redistributions of source code must retain the above copyright
     11  *    notice, this list of conditions and the following disclaimer.
     12  * 2. Redistributions in binary form must reproduce the above copyright
     13  *    notice, this list of conditions and the following disclaimer in the
     14  *    documentation and/or other materials provided with the distribution.
     15  * 3. All advertising materials mentioning features or use of this software
     16  *    must display the following acknowledgement:
     17  *	This product includes software developed by the University of
     18  *	California, Berkeley and its contributors.
     19  * 4. Neither the name of the University nor the names of its contributors
     20  *    may be used to endorse or promote products derived from this software
     21  *    without specific prior written permission.
     22  *
     23  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
     24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     25  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     26  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
     27  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     29  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     32  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     33  * SUCH DAMAGE.
     34  *
     35  *	@(#)ftpcmd.y	8.3 (Berkeley) 4/6/94
     36  */
     37 
     38 /*
     39  * Grammar for FTP commands.
     40  * See RFC 959.
     41  */
     42 
     43 %{
     44 #include <sys/cdefs.h>
     45 
     46 #ifndef lint
     47 #if 0
     48 static char sccsid[] = "@(#)ftpcmd.y	8.3 (Berkeley) 4/6/94";
     49 #else
     50 __RCSID("$NetBSD: ftpcmd.y,v 1.22 1998/09/06 10:39:40 lukem Exp $");
     51 #endif
     52 #endif /* not lint */
     53 
     54 #include <sys/param.h>
     55 #include <sys/socket.h>
     56 #include <sys/stat.h>
     57 
     58 #include <netinet/in.h>
     59 #include <arpa/ftp.h>
     60 #include <arpa/inet.h>
     61 
     62 #include <ctype.h>
     63 #include <errno.h>
     64 #include <glob.h>
     65 #include <pwd.h>
     66 #include <setjmp.h>
     67 #include <signal.h>
     68 #include <stdio.h>
     69 #include <stdlib.h>
     70 #include <string.h>
     71 #include <syslog.h>
     72 #include <time.h>
     73 #include <tzfile.h>
     74 #include <unistd.h>
     75 
     76 #include "extern.h"
     77 
     78 off_t	restart_point;
     79 
     80 static	int cmd_type;
     81 static	int cmd_form;
     82 static	int cmd_bytesz;
     83 char	cbuf[512];
     84 char	*fromname;
     85 int	hasyyerrored;
     86 
     87 %}
     88 
     89 %union {
     90 	int	i;
     91 	char   *s;
     92 }
     93 
     94 %token
     95 	A	B	C	E	F	I
     96 	L	N	P	R	S	T
     97 
     98 	SP	CRLF	COMMA
     99 
    100 	USER	PASS	ACCT	REIN	QUIT	PORT
    101 	PASV	TYPE	STRU	MODE	RETR	STOR
    102 	APPE	MLFL	MAIL	MSND	MSOM	MSAM
    103 	MRSQ	MRCP	ALLO	REST	RNFR	RNTO
    104 	ABOR	DELE	CWD	LIST	NLST	SITE
    105 	STAT	HELP	NOOP	MKD	RMD	PWD
    106 	CDUP	STOU	SMNT	SYST
    107 
    108 	SIZE	MDTM
    109 
    110 	UMASK	IDLE	CHMOD
    111 
    112 	LEXERR
    113 
    114 %token	<s> STRING
    115 %token	<i> NUMBER
    116 
    117 %type	<i> check_login check_modify octal_number byte_size
    118 %type	<i> struct_code mode_code type_code form_code
    119 %type	<s> pathstring pathname password username
    120 
    121 %start	cmd_list
    122 
    123 %%
    124 
    125 cmd_list
    126 	: /* empty */
    127 	| cmd_list cmd
    128 		{
    129 			fromname = NULL;
    130 			restart_point = (off_t) 0;
    131 		}
    132 	| cmd_list rcmd
    133 	;
    134 
    135 cmd
    136 	: USER SP username CRLF
    137 		{
    138 			user($3);
    139 			free($3);
    140 		}
    141 	| PASS SP password CRLF
    142 		{
    143 			pass($3);
    144 			free($3);
    145 		}
    146 	| PORT check_login SP host_port CRLF
    147 		{
    148 			if ($2) {
    149 					/* be paranoid, if told so */
    150 			if (curclass.checkportcmd &&
    151 			    ((ntohs(data_dest.sin_port) < IPPORT_RESERVED) ||
    152 			    memcmp(&data_dest.sin_addr, &his_addr.sin_addr,
    153 			    sizeof(data_dest.sin_addr)) != 0)) {
    154 				reply(500,
    155 				    "Illegal PORT command rejected");
    156 			} else {
    157 				usedefault = 0;
    158 				if (pdata >= 0) {
    159 					(void) close(pdata);
    160 					pdata = -1;
    161 				}
    162 				reply(200, "PORT command successful.");
    163 			}
    164 			}
    165 		}
    166 	| PASV check_login CRLF
    167 		{
    168 			if (curclass.passive) {
    169 				passive();
    170 			} else {
    171 				reply(500, "PASV mode not available.");
    172 			}
    173 		}
    174 	| TYPE SP type_code CRLF
    175 		{
    176 			switch (cmd_type) {
    177 
    178 			case TYPE_A:
    179 				if (cmd_form == FORM_N) {
    180 					reply(200, "Type set to A.");
    181 					type = cmd_type;
    182 					form = cmd_form;
    183 				} else
    184 					reply(504, "Form must be N.");
    185 				break;
    186 
    187 			case TYPE_E:
    188 				reply(504, "Type E not implemented.");
    189 				break;
    190 
    191 			case TYPE_I:
    192 				reply(200, "Type set to I.");
    193 				type = cmd_type;
    194 				break;
    195 
    196 			case TYPE_L:
    197 #if NBBY == 8
    198 				if (cmd_bytesz == 8) {
    199 					reply(200,
    200 					    "Type set to L (byte size 8).");
    201 					type = cmd_type;
    202 				} else
    203 					reply(504, "Byte size must be 8.");
    204 #else /* NBBY == 8 */
    205 				UNIMPLEMENTED for NBBY != 8
    206 #endif /* NBBY == 8 */
    207 			}
    208 		}
    209 	| STRU SP struct_code CRLF
    210 		{
    211 			switch ($3) {
    212 
    213 			case STRU_F:
    214 				reply(200, "STRU F ok.");
    215 				break;
    216 
    217 			default:
    218 				reply(504, "Unimplemented STRU type.");
    219 			}
    220 		}
    221 	| MODE SP mode_code CRLF
    222 		{
    223 			switch ($3) {
    224 
    225 			case MODE_S:
    226 				reply(200, "MODE S ok.");
    227 				break;
    228 
    229 			default:
    230 				reply(502, "Unimplemented MODE type.");
    231 			}
    232 		}
    233 	| ALLO SP NUMBER CRLF
    234 		{
    235 			reply(202, "ALLO command ignored.");
    236 		}
    237 	| ALLO SP NUMBER SP R SP NUMBER CRLF
    238 		{
    239 			reply(202, "ALLO command ignored.");
    240 		}
    241 	| RETR check_login SP pathname CRLF
    242 		{
    243 			if ($2 && $4 != NULL)
    244 				retrieve(NULL, $4);
    245 			if ($4 != NULL)
    246 				free($4);
    247 		}
    248 	| STOR check_login SP pathname CRLF
    249 		{
    250 			if ($2 && $4 != NULL)
    251 				store($4, "w", 0);
    252 			if ($4 != NULL)
    253 				free($4);
    254 		}
    255 	| APPE check_login SP pathname CRLF
    256 		{
    257 			if ($2 && $4 != NULL)
    258 				store($4, "a", 0);
    259 			if ($4 != NULL)
    260 				free($4);
    261 		}
    262 	| NLST check_login CRLF
    263 		{
    264 			if ($2)
    265 				send_file_list(".");
    266 		}
    267 	| NLST check_login SP STRING CRLF
    268 		{
    269 			if ($2 && $4 != NULL)
    270 				send_file_list($4);
    271 			if ($4 != NULL)
    272 				free($4);
    273 		}
    274 	| LIST check_login CRLF
    275 		{
    276 			if ($2)
    277 				retrieve("/bin/ls -lgA", "");
    278 		}
    279 	| LIST check_login SP pathname CRLF
    280 		{
    281 			if ($2 && $4 != NULL)
    282 				retrieve("/bin/ls -lgA %s", $4);
    283 			if ($4 != NULL)
    284 				free($4);
    285 		}
    286 	| STAT check_login SP pathname CRLF
    287 		{
    288 			if ($2 && $4 != NULL)
    289 				statfilecmd($4);
    290 			if ($4 != NULL)
    291 				free($4);
    292 		}
    293 	| STAT CRLF
    294 		{
    295 			statcmd();
    296 		}
    297 	| DELE check_modify SP pathname CRLF
    298 		{
    299 			if ($2 && $4 != NULL)
    300 				delete($4);
    301 			if ($4 != NULL)
    302 				free($4);
    303 		}
    304 	| RNTO SP pathname CRLF
    305 		{
    306 			if (fromname) {
    307 				renamecmd(fromname, $3);
    308 				free(fromname);
    309 				fromname = NULL;
    310 			} else {
    311 				reply(503, "Bad sequence of commands.");
    312 			}
    313 			free($3);
    314 		}
    315 	| ABOR CRLF
    316 		{
    317 			reply(225, "ABOR command successful.");
    318 		}
    319 	| CWD check_login CRLF
    320 		{
    321 			if ($2)
    322 				cwd(pw->pw_dir);
    323 		}
    324 	| CWD check_login SP pathname CRLF
    325 		{
    326 			if ($2 && $4 != NULL)
    327 				cwd($4);
    328 			if ($4 != NULL)
    329 				free($4);
    330 		}
    331 	| HELP CRLF
    332 		{
    333 			help(cmdtab, NULL);
    334 		}
    335 	| HELP SP STRING CRLF
    336 		{
    337 			char *cp = $3;
    338 
    339 			if (strncasecmp(cp, "SITE", 4) == 0) {
    340 				cp = $3 + 4;
    341 				if (*cp == ' ')
    342 					cp++;
    343 				if (*cp)
    344 					help(sitetab, cp);
    345 				else
    346 					help(sitetab, NULL);
    347 			} else
    348 				help(cmdtab, $3);
    349 		}
    350 	| NOOP CRLF
    351 		{
    352 			reply(200, "NOOP command successful.");
    353 		}
    354 	| MKD check_modify SP pathname CRLF
    355 		{
    356 			if ($2 && $4 != NULL)
    357 				makedir($4);
    358 			if ($4 != NULL)
    359 				free($4);
    360 		}
    361 	| RMD check_modify SP pathname CRLF
    362 		{
    363 			if ($2 && $4 != NULL)
    364 				removedir($4);
    365 			if ($4 != NULL)
    366 				free($4);
    367 		}
    368 	| PWD check_login CRLF
    369 		{
    370 			if ($2)
    371 				pwd();
    372 		}
    373 	| CDUP check_login CRLF
    374 		{
    375 			if ($2)
    376 				cwd("..");
    377 		}
    378 	| SITE SP HELP CRLF
    379 		{
    380 			help(sitetab, NULL);
    381 		}
    382 	| SITE SP HELP SP STRING CRLF
    383 		{
    384 			help(sitetab, $5);
    385 		}
    386 	| SITE SP UMASK check_login CRLF
    387 		{
    388 			int oldmask;
    389 
    390 			if ($4) {
    391 				oldmask = umask(0);
    392 				(void) umask(oldmask);
    393 				reply(200, "Current UMASK is %03o", oldmask);
    394 			}
    395 		}
    396 	| SITE SP UMASK check_modify SP octal_number CRLF
    397 		{
    398 			int oldmask;
    399 
    400 			if ($4) {
    401 				if (($6 == -1) || ($6 > 0777)) {
    402 					reply(501, "Bad UMASK value");
    403 				} else {
    404 					oldmask = umask($6);
    405 					reply(200,
    406 					    "UMASK set to %03o (was %03o)",
    407 					    $6, oldmask);
    408 				}
    409 			}
    410 		}
    411 	| SITE SP CHMOD check_modify SP octal_number SP pathname CRLF
    412 		{
    413 			if ($4 && ($8 != NULL)) {
    414 				if ($6 > 0777)
    415 					reply(501,
    416 				"CHMOD: Mode value must be between 0 and 0777");
    417 				else if (chmod($8, $6) < 0)
    418 					perror_reply(550, $8);
    419 				else
    420 					reply(200, "CHMOD command successful.");
    421 			}
    422 			if ($8 != NULL)
    423 				free($8);
    424 		}
    425 	| SITE SP IDLE CRLF
    426 		{
    427 			reply(200,
    428 			    "Current IDLE time limit is %d seconds; max %d",
    429 				curclass.timeout, curclass.maxtimeout);
    430 		}
    431 	| SITE SP IDLE SP NUMBER CRLF
    432 		{
    433 			if ($5 < 30 || $5 > curclass.maxtimeout) {
    434 				reply(501,
    435 			"IDLE time limit must be between 30 and %d seconds",
    436 				    curclass.maxtimeout);
    437 			} else {
    438 				curclass.timeout = $5;
    439 				(void) alarm(curclass.timeout);
    440 				reply(200,
    441 				    "IDLE time limit set to %d seconds",
    442 				    curclass.timeout);
    443 			}
    444 		}
    445 	| STOU check_login SP pathname CRLF
    446 		{
    447 			if ($2 && $4 != NULL)
    448 				store($4, "w", 1);
    449 			if ($4 != NULL)
    450 				free($4);
    451 		}
    452 	| SYST CRLF
    453 		{
    454 			reply(215, "UNIX Type: L%d %s", NBBY, version);
    455 		}
    456 
    457 		/*
    458 		 * SIZE is not in RFC 959, 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 RFC 959, 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 					perror_reply(550, $4);
    487 				else if (!S_ISREG(stbuf.st_mode)) {
    488 					reply(550, "%s: not a plain file.", $4);
    489 				} else {
    490 					struct tm *t;
    491 					t = gmtime(&stbuf.st_mtime);
    492 					reply(213,
    493 					    "%04d%02d%02d%02d%02d%02d",
    494 					    TM_YEAR_BASE + t->tm_year,
    495 					    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 
    513 rcmd
    514 	: RNFR check_modify SP pathname CRLF
    515 		{
    516 			restart_point = (off_t) 0;
    517 			if ($2 && $4) {
    518 				fromname = renamefrom($4);
    519 				if (fromname == NULL && $4) {
    520 					free($4);
    521 				}
    522 			}
    523 		}
    524 	| REST SP byte_size CRLF
    525 		{
    526 			fromname = NULL;
    527 			restart_point = $3;	/* XXX $3 is only "int" */
    528 			reply(350, "Restarting at %qd. %s", restart_point,
    529 			    "Send STORE or RETRIEVE to initiate transfer.");
    530 		}
    531 	;
    532 
    533 username
    534 	: STRING
    535 	;
    536 
    537 password
    538 	: /* empty */
    539 		{
    540 			$$ = (char *)calloc(1, sizeof(char));
    541 		}
    542 	| STRING
    543 	;
    544 
    545 byte_size
    546 	: NUMBER
    547 	;
    548 
    549 host_port
    550 	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
    551 		NUMBER COMMA NUMBER
    552 		{
    553 			char *a, *p;
    554 
    555 			data_dest.sin_len = sizeof(struct sockaddr_in);
    556 			data_dest.sin_family = AF_INET;
    557 			p = (char *)&data_dest.sin_port;
    558 			p[0] = $9; p[1] = $11;
    559 			a = (char *)&data_dest.sin_addr;
    560 			a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7;
    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
    659 			 * others.
    660 			 */
    661 			if (logged_in && $1 && *$1 == '~') {
    662 				glob_t gl;
    663 				int flags =
    664 				 GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE;
    665 
    666 				if ($1[1] == '\0')
    667 					$$ = strdup(pw->pw_dir);
    668 				else {
    669 					memset(&gl, 0, sizeof(gl));
    670 					if (glob($1, flags, NULL, &gl) ||
    671 					    gl.gl_pathc == 0) {
    672 						reply(550, "not found");
    673 						$$ = NULL;
    674 					} else
    675 						$$ = strdup(gl.gl_pathv[0]);
    676 					globfree(&gl);
    677 				}
    678 				free($1);
    679 			} else
    680 				$$ = $1;
    681 		}
    682 	;
    683 
    684 pathstring
    685 	: STRING
    686 	;
    687 
    688 octal_number
    689 	: NUMBER
    690 		{
    691 			int ret, dec, multby, digit;
    692 
    693 			/*
    694 			 * Convert a number that was read as decimal number
    695 			 * to what it would be if it had been read as octal.
    696 			 */
    697 			dec = $1;
    698 			multby = 1;
    699 			ret = 0;
    700 			while (dec) {
    701 				digit = dec%10;
    702 				if (digit > 7) {
    703 					ret = -1;
    704 					break;
    705 				}
    706 				ret += digit * multby;
    707 				multby *= 8;
    708 				dec /= 10;
    709 			}
    710 			$$ = ret;
    711 		}
    712 	;
    713 
    714 check_login
    715 	: /* empty */
    716 		{
    717 			if (logged_in)
    718 				$$ = 1;
    719 			else {
    720 				reply(530, "Please login with USER and PASS.");
    721 				$$ = 0;
    722 				hasyyerrored = 1;
    723 			}
    724 		}
    725 	;
    726 
    727 check_modify
    728 	: /* empty */
    729 		{
    730 			if (logged_in) {
    731 				if (curclass.modify)
    732 					$$ = 1;
    733 				else {
    734 					reply(502,
    735 					"No permission to use this command.");
    736 					$$ = 0;
    737 					hasyyerrored = 1;
    738 				}
    739 			} else {
    740 				reply(530, "Please login with USER and PASS.");
    741 				$$ = 0;
    742 				hasyyerrored = 1;
    743 			}
    744 		}
    745 
    746 %%
    747 
    748 #define	CMD	0	/* beginning of command */
    749 #define	ARGS	1	/* expect miscellaneous arguments */
    750 #define	STR1	2	/* expect SP followed by STRING */
    751 #define	STR2	3	/* expect STRING */
    752 #define	OSTR	4	/* optional SP then STRING */
    753 #define	ZSTR1	5	/* SP then optional STRING */
    754 #define	ZSTR2	6	/* optional STRING after SP */
    755 #define	SITECMD	7	/* SITE command */
    756 #define	NSTR	8	/* Number followed by a string */
    757 #define NOARGS	9	/* No arguments allowed */
    758 
    759 struct tab {
    760 	char	*name;
    761 	short	token;
    762 	short	state;
    763 	short	implemented;	/* 1 if command is implemented */
    764 	char	*help;
    765 };
    766 
    767 struct tab cmdtab[] = {
    768 				/* From RFC 959, in order defined (5.3.1) */
    769 	{ "USER", USER, STR1, 1,	"<sp> username" },
    770 	{ "PASS", PASS, ZSTR1, 1,	"<sp> password" },
    771 	{ "ACCT", ACCT, STR1, 0,	"(specify account)" },
    772 	{ "CWD",  CWD,  OSTR, 1,	"[ <sp> directory-name ]" },
    773 	{ "CDUP", CDUP, NOARGS, 1,	"(change to parent directory)" },
    774 	{ "SMNT", SMNT, ARGS, 0,	"(structure mount)" },
    775 	{ "QUIT", QUIT, NOARGS, 1,	"(terminate service)", },
    776 	{ "REIN", REIN, NOARGS, 0,	"(reinitialize server state)" },
    777 	{ "PORT", PORT, ARGS, 1,	"<sp> b0, b1, b2, b3, b4" },
    778 	{ "PASV", PASV, NOARGS, 1,	"(set server in passive mode)" },
    779 	{ "TYPE", TYPE, ARGS, 1,	"<sp> [ A | E | I | L ]" },
    780 	{ "STRU", STRU, ARGS, 1,	"(specify file structure)" },
    781 	{ "MODE", MODE, ARGS, 1,	"(specify transfer mode)" },
    782 	{ "RETR", RETR, STR1, 1,	"<sp> file-name" },
    783 	{ "STOR", STOR, STR1, 1,	"<sp> file-name" },
    784 	{ "STOU", STOU, STR1, 1,	"<sp> file-name" },
    785 	{ "APPE", APPE, STR1, 1,	"<sp> file-name" },
    786 	{ "ALLO", ALLO, ARGS, 1,	"allocate storage (vacuously)" },
    787 	{ "REST", REST, ARGS, 1,	"<sp> offset (restart command)" },
    788 	{ "RNFR", RNFR, STR1, 1,	"<sp> file-name" },
    789 	{ "RNTO", RNTO, STR1, 1,	"<sp> file-name" },
    790 	{ "ABOR", ABOR, NOARGS, 1,	"(abort operation)" },
    791 	{ "DELE", DELE, STR1, 1,	"<sp> file-name" },
    792 	{ "RMD",  RMD,  STR1, 1,	"<sp> path-name" },
    793 	{ "MKD",  MKD,  STR1, 1,	"<sp> path-name" },
    794 	{ "PWD",  PWD,  NOARGS, 1,	"(return current directory)" },
    795 	{ "LIST", LIST, OSTR, 1,	"[ <sp> path-name ]" },
    796 	{ "NLST", NLST, OSTR, 1,	"[ <sp> path-name ]" },
    797 	{ "SITE", SITE, SITECMD, 1,	"site-cmd [ <sp> arguments ]" },
    798 	{ "SYST", SYST, NOARGS, 1,	"(get type of operating system)" },
    799 	{ "STAT", STAT, OSTR, 1,	"[ <sp> path-name ]" },
    800 	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
    801 	{ "NOOP", NOOP, NOARGS, 1,	"" },
    802 
    803 	{ "SIZE", SIZE, OSTR, 1,	"<sp> path-name" },
    804 	{ "MDTM", MDTM, OSTR, 1,	"<sp> path-name" },
    805 
    806 				/* obsolete commands */
    807 	{ "MLFL", MLFL, OSTR, 0,	"(mail file)" },
    808 	{ "MAIL", MAIL, OSTR, 0,	"(mail to user)" },
    809 	{ "MSND", MSND, OSTR, 0,	"(mail send to terminal)" },
    810 	{ "MSOM", MSOM, OSTR, 0,	"(mail send to terminal or mailbox)" },
    811 	{ "MSAM", MSAM, OSTR, 0,	"(mail send to terminal and mailbox)" },
    812 	{ "MRSQ", MRSQ, OSTR, 0,	"(mail recipient scheme question)" },
    813 	{ "MRCP", MRCP, STR1, 0,	"(mail recipient)" },
    814 	{ "XCWD", CWD,	OSTR, 1,	"[ <sp> directory-name ]" },
    815 	{ "XCUP", CDUP, NOARGS, 1,	"(change to parent directory)" },
    816 	{ "XRMD", RMD,  STR1, 1,	"<sp> path-name" },
    817 	{ "XMKD", MKD,  STR1, 1,	"<sp> path-name" },
    818 	{ "XPWD", PWD,  NOARGS, 1,	"(return current directory)" },
    819 
    820 	{ NULL,   0,    0,    0,	0 }
    821 };
    822 
    823 struct tab sitetab[] = {
    824 	{ "UMASK", UMASK, ARGS, 1,	"[ <sp> umask ]" },
    825 	{ "IDLE", IDLE, ARGS, 1,	"[ <sp> maximum-idle-time ]" },
    826 	{ "CHMOD", CHMOD, NSTR, 1,	"<sp> mode <sp> file-name" },
    827 	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
    828 	{ NULL,   0,    0,    0,	0 }
    829 };
    830 
    831 static char	*copy __P((char *));
    832 static void	 help __P((struct tab *, char *));
    833 static struct tab *
    834 		 lookup __P((struct tab *, char *));
    835 static void	 sizecmd __P((char *));
    836 static void	 toolong __P((int));
    837 static int	 yylex __P((void));
    838 
    839 static struct tab *
    840 lookup(p, cmd)
    841 	struct tab *p;
    842 	char *cmd;
    843 {
    844 
    845 	for (; p->name != NULL; p++)
    846 		if (strcmp(cmd, p->name) == 0)
    847 			return (p);
    848 	return (0);
    849 }
    850 
    851 #include <arpa/telnet.h>
    852 
    853 /*
    854  * getline - a hacked up version of fgets to ignore TELNET escape codes.
    855  */
    856 char *
    857 getline(s, n, iop)
    858 	char *s;
    859 	int n;
    860 	FILE *iop;
    861 {
    862 	int c;
    863 	char *cs;
    864 
    865 	cs = s;
    866 /* tmpline may contain saved command from urgent mode interruption */
    867 	for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
    868 		*cs++ = tmpline[c];
    869 		if (tmpline[c] == '\n') {
    870 			*cs++ = '\0';
    871 			if (debug)
    872 				syslog(LOG_DEBUG, "command: %s", s);
    873 			tmpline[0] = '\0';
    874 			return(s);
    875 		}
    876 		if (c == 0)
    877 			tmpline[0] = '\0';
    878 	}
    879 	while ((c = getc(iop)) != EOF) {
    880 		c &= 0377;
    881 		if (c == IAC) {
    882 		    if ((c = getc(iop)) != EOF) {
    883 			c &= 0377;
    884 			switch (c) {
    885 			case WILL:
    886 			case WONT:
    887 				c = getc(iop);
    888 				printf("%c%c%c", IAC, DONT, 0377&c);
    889 				(void) fflush(stdout);
    890 				continue;
    891 			case DO:
    892 			case DONT:
    893 				c = getc(iop);
    894 				printf("%c%c%c", IAC, WONT, 0377&c);
    895 				(void) fflush(stdout);
    896 				continue;
    897 			case IAC:
    898 				break;
    899 			default:
    900 				continue;	/* ignore command */
    901 			}
    902 		    }
    903 		}
    904 		*cs++ = c;
    905 		if (--n <= 0 || c == '\n')
    906 			break;
    907 	}
    908 	if (c == EOF && cs == s)
    909 		return (NULL);
    910 	*cs++ = '\0';
    911 	if (debug) {
    912 		if (!guest && strncasecmp("pass ", s, 5) == 0) {
    913 			/* Don't syslog passwords */
    914 			syslog(LOG_DEBUG, "command: %.5s ???", s);
    915 		} else {
    916 			char *cp;
    917 			int len;
    918 
    919 			/* Don't syslog trailing CR-LF */
    920 			len = strlen(s);
    921 			cp = s + len - 1;
    922 			while (cp >= s && (*cp == '\n' || *cp == '\r')) {
    923 				--cp;
    924 				--len;
    925 			}
    926 			syslog(LOG_DEBUG, "command: %.*s", len, s);
    927 		}
    928 	}
    929 	return (s);
    930 }
    931 
    932 static void
    933 toolong(signo)
    934 	int signo;
    935 {
    936 
    937 	reply(421,
    938 	    "Timeout (%d seconds): closing control connection.",
    939 	    curclass.timeout);
    940 	if (logging)
    941 		syslog(LOG_INFO, "User %s timed out after %d seconds",
    942 		    (pw ? pw -> pw_name : "unknown"), curclass.timeout);
    943 	dologout(1);
    944 }
    945 
    946 static int
    947 yylex()
    948 {
    949 	static int cpos, state;
    950 	char *cp, *cp2;
    951 	struct tab *p;
    952 	int n;
    953 	char c;
    954 
    955 	for (;;) {
    956 		switch (state) {
    957 
    958 		case CMD:
    959 			hasyyerrored = 0;
    960 			(void) signal(SIGALRM, toolong);
    961 			(void) alarm(curclass.timeout);
    962 			if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
    963 				reply(221, "You could at least say goodbye.");
    964 				dologout(0);
    965 			}
    966 			(void) alarm(0);
    967 #ifdef HASSETPROCTITLE
    968 			if (strncasecmp(cbuf, "PASS", 4) != 0)
    969 				setproctitle("%s: %s", proctitle, cbuf);
    970 #endif /* HASSETPROCTITLE */
    971 			if ((cp = strchr(cbuf, '\r'))) {
    972 				*cp++ = '\n';
    973 				*cp = '\0';
    974 			}
    975 			if ((cp = strpbrk(cbuf, " \n")))
    976 				cpos = cp - cbuf;
    977 			if (cpos == 0)
    978 				cpos = 4;
    979 			c = cbuf[cpos];
    980 			cbuf[cpos] = '\0';
    981 			upper(cbuf);
    982 			p = lookup(cmdtab, cbuf);
    983 			cbuf[cpos] = c;
    984 			if (p != 0) {
    985 				if (p->implemented == 0) {
    986 					nack(p->name);
    987 					break;
    988 				}
    989 				state = p->state;
    990 				yylval.s = p->name;
    991 				return (p->token);
    992 			}
    993 			break;
    994 
    995 		case SITECMD:
    996 			if (cbuf[cpos] == ' ') {
    997 				cpos++;
    998 				return (SP);
    999 			}
   1000 			cp = &cbuf[cpos];
   1001 			if ((cp2 = strpbrk(cp, " \n")))
   1002 				cpos = cp2 - cbuf;
   1003 			c = cbuf[cpos];
   1004 			cbuf[cpos] = '\0';
   1005 			upper(cp);
   1006 			p = lookup(sitetab, cp);
   1007 			cbuf[cpos] = c;
   1008 			if (p != 0) {
   1009 				if (p->implemented == 0) {
   1010 					nack(p->name);
   1011 					break;
   1012 				}
   1013 				state = p->state;
   1014 				yylval.s = p->name;
   1015 				return (p->token);
   1016 			}
   1017 			state = CMD;
   1018 			break;
   1019 
   1020 		case OSTR:
   1021 			if (cbuf[cpos] == '\n') {
   1022 				state = CMD;
   1023 				return (CRLF);
   1024 			}
   1025 			/* FALLTHROUGH */
   1026 
   1027 		case STR1:
   1028 		case ZSTR1:
   1029 		dostr1:
   1030 			if (cbuf[cpos] == ' ') {
   1031 				cpos++;
   1032 				state = state == OSTR ? STR2 : ++state;
   1033 				return (SP);
   1034 			}
   1035 			break;
   1036 
   1037 		case ZSTR2:
   1038 			if (cbuf[cpos] == '\n') {
   1039 				state = CMD;
   1040 				return (CRLF);
   1041 			}
   1042 			/* FALLTHROUGH */
   1043 
   1044 		case STR2:
   1045 			cp = &cbuf[cpos];
   1046 			n = strlen(cp);
   1047 			cpos += n - 1;
   1048 			/*
   1049 			 * Make sure the string is nonempty and \n terminated.
   1050 			 */
   1051 			if (n > 1 && cbuf[cpos] == '\n') {
   1052 				cbuf[cpos] = '\0';
   1053 				yylval.s = copy(cp);
   1054 				cbuf[cpos] = '\n';
   1055 				state = ARGS;
   1056 				return (STRING);
   1057 			}
   1058 			break;
   1059 
   1060 		case NSTR:
   1061 			if (cbuf[cpos] == ' ') {
   1062 				cpos++;
   1063 				return (SP);
   1064 			}
   1065 			if (isdigit(cbuf[cpos])) {
   1066 				cp = &cbuf[cpos];
   1067 				while (isdigit(cbuf[++cpos]))
   1068 					;
   1069 				c = cbuf[cpos];
   1070 				cbuf[cpos] = '\0';
   1071 				yylval.i = atoi(cp);
   1072 				cbuf[cpos] = c;
   1073 				state = STR1;
   1074 				return (NUMBER);
   1075 			}
   1076 			state = STR1;
   1077 			goto dostr1;
   1078 
   1079 		case ARGS:
   1080 			if (isdigit(cbuf[cpos])) {
   1081 				cp = &cbuf[cpos];
   1082 				while (isdigit(cbuf[++cpos]))
   1083 					;
   1084 				c = cbuf[cpos];
   1085 				cbuf[cpos] = '\0';
   1086 				yylval.i = atoi(cp);
   1087 				cbuf[cpos] = c;
   1088 				return (NUMBER);
   1089 			}
   1090 			switch (cbuf[cpos++]) {
   1091 
   1092 			case '\n':
   1093 				state = CMD;
   1094 				return (CRLF);
   1095 
   1096 			case ' ':
   1097 				return (SP);
   1098 
   1099 			case ',':
   1100 				return (COMMA);
   1101 
   1102 			case 'A':
   1103 			case 'a':
   1104 				return (A);
   1105 
   1106 			case 'B':
   1107 			case 'b':
   1108 				return (B);
   1109 
   1110 			case 'C':
   1111 			case 'c':
   1112 				return (C);
   1113 
   1114 			case 'E':
   1115 			case 'e':
   1116 				return (E);
   1117 
   1118 			case 'F':
   1119 			case 'f':
   1120 				return (F);
   1121 
   1122 			case 'I':
   1123 			case 'i':
   1124 				return (I);
   1125 
   1126 			case 'L':
   1127 			case 'l':
   1128 				return (L);
   1129 
   1130 			case 'N':
   1131 			case 'n':
   1132 				return (N);
   1133 
   1134 			case 'P':
   1135 			case 'p':
   1136 				return (P);
   1137 
   1138 			case 'R':
   1139 			case 'r':
   1140 				return (R);
   1141 
   1142 			case 'S':
   1143 			case 's':
   1144 				return (S);
   1145 
   1146 			case 'T':
   1147 			case 't':
   1148 				return (T);
   1149 
   1150 			}
   1151 			break;
   1152 
   1153 		case NOARGS:
   1154 			if (cbuf[cpos] == '\n') {
   1155 				state = CMD;
   1156 				return (CRLF);
   1157 			}
   1158 			c = cbuf[cpos];
   1159 			cbuf[cpos] = '\0';
   1160 			reply(501, "'%s' command does not take any arguments.",
   1161 			    cbuf);
   1162 			cbuf[cpos] = c;
   1163 			break;
   1164 
   1165 		default:
   1166 			fatal("Unknown state in scanner.");
   1167 		}
   1168 		yyerror(NULL);
   1169 		state = CMD;
   1170 		longjmp(errcatch, 0);
   1171 		/* NOTREACHED */
   1172 	}
   1173 }
   1174 
   1175 /* ARGSUSED */
   1176 void
   1177 yyerror(s)
   1178 	char *s;
   1179 {
   1180 	char *cp;
   1181 
   1182 	if (hasyyerrored)
   1183 		return;
   1184 	if ((cp = strchr(cbuf,'\n')) != NULL)
   1185 		*cp = '\0';
   1186 	reply(500, "'%s': command not understood.", cbuf);
   1187 	hasyyerrored = 1;
   1188 }
   1189 
   1190 void
   1191 upper(s)
   1192 	char *s;
   1193 {
   1194 	while (*s != '\0') {
   1195 		*s = toupper(*s);
   1196 		s++;
   1197 	}
   1198 }
   1199 
   1200 static char *
   1201 copy(s)
   1202 	char *s;
   1203 {
   1204 	char *p;
   1205 
   1206 	p = strdup(s);
   1207 	if (p == NULL)
   1208 		fatal("Ran out of memory.");
   1209 	return (p);
   1210 }
   1211 
   1212 static void
   1213 help(ctab, s)
   1214 	struct tab *ctab;
   1215 	char *s;
   1216 {
   1217 	struct tab *c;
   1218 	int width, NCMDS;
   1219 	char *type;
   1220 
   1221 	if (ctab == sitetab)
   1222 		type = "SITE ";
   1223 	else
   1224 		type = "";
   1225 	width = 0, NCMDS = 0;
   1226 	for (c = ctab; c->name != NULL; c++) {
   1227 		int len = strlen(c->name);
   1228 
   1229 		if (len > width)
   1230 			width = len;
   1231 		NCMDS++;
   1232 	}
   1233 	width = (width + 8) &~ 7;
   1234 	if (s == 0) {
   1235 		int i, j, w;
   1236 		int columns, lines;
   1237 
   1238 		lreply(214, "The following %scommands are recognized %s.",
   1239 		    type, "(* =>'s unimplemented)");
   1240 		columns = 76 / width;
   1241 		if (columns == 0)
   1242 			columns = 1;
   1243 		lines = (NCMDS + columns - 1) / columns;
   1244 		for (i = 0; i < lines; i++) {
   1245 			printf("   ");
   1246 			for (j = 0; j < columns; j++) {
   1247 				c = ctab + j * lines + i;
   1248 				printf("%s%c", c->name,
   1249 					c->implemented ? ' ' : '*');
   1250 				if (c + lines >= &ctab[NCMDS])
   1251 					break;
   1252 				w = strlen(c->name) + 1;
   1253 				while (w < width) {
   1254 					putchar(' ');
   1255 					w++;
   1256 				}
   1257 			}
   1258 			printf("\r\n");
   1259 		}
   1260 		(void) fflush(stdout);
   1261 		reply(214, "Direct comments to ftp-bugs@%s.", hostname);
   1262 		return;
   1263 	}
   1264 	upper(s);
   1265 	c = lookup(ctab, s);
   1266 	if (c == (struct tab *)0) {
   1267 		reply(502, "Unknown command %s.", s);
   1268 		return;
   1269 	}
   1270 	if (c->implemented)
   1271 		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
   1272 	else
   1273 		reply(214, "%s%-*s\t%s; unimplemented.", type, width,
   1274 		    c->name, c->help);
   1275 }
   1276 
   1277 static void
   1278 sizecmd(filename)
   1279 	char *filename;
   1280 {
   1281 	switch (type) {
   1282 	case TYPE_L:
   1283 	case TYPE_I: {
   1284 		struct stat stbuf;
   1285 		if (stat(filename, &stbuf) < 0 || !S_ISREG(stbuf.st_mode))
   1286 			reply(550, "%s: not a plain file.", filename);
   1287 		else
   1288 			reply(213, "%qu", stbuf.st_size);
   1289 		break; }
   1290 	case TYPE_A: {
   1291 		FILE *fin;
   1292 		int c;
   1293 		off_t count;
   1294 		struct stat stbuf;
   1295 		fin = fopen(filename, "r");
   1296 		if (fin == NULL) {
   1297 			perror_reply(550, filename);
   1298 			return;
   1299 		}
   1300 		if (fstat(fileno(fin), &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) {
   1301 			reply(550, "%s: not a plain file.", filename);
   1302 			(void) fclose(fin);
   1303 			return;
   1304 		}
   1305 
   1306 		count = 0;
   1307 		while((c=getc(fin)) != EOF) {
   1308 			if (c == '\n')	/* will get expanded to \r\n */
   1309 				count++;
   1310 			count++;
   1311 		}
   1312 		(void) fclose(fin);
   1313 
   1314 		reply(213, "%qd", count);
   1315 		break; }
   1316 	default:
   1317 		reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
   1318 	}
   1319 }
   1320 
   1321