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