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