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