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