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