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