Home | History | Annotate | Line # | Download | only in ftpd
ftpcmd.y revision 1.41
      1 /*	$NetBSD: ftpcmd.y,v 1.41 1999/12/12 14:05:54 lukem Exp $	*/
      2 
      3 /*-
      4  * Copyright (c) 1997-1999 The NetBSD Foundation, Inc.
      5  * All rights reserved.
      6  *
      7  * This code is derived from software contributed to The NetBSD Foundation
      8  * by Luke Mewburn.
      9  *
     10  * Redistribution and use in source and binary forms, with or without
     11  * modification, are permitted provided that the following conditions
     12  * are met:
     13  * 1. Redistributions of source code must retain the above copyright
     14  *    notice, this list of conditions and the following disclaimer.
     15  * 2. Redistributions in binary form must reproduce the above copyright
     16  *    notice, this list of conditions and the following disclaimer in the
     17  *    documentation and/or other materials provided with the distribution.
     18  * 3. All advertising materials mentioning features or use of this software
     19  *    must display the following acknowledgement:
     20  *        This product includes software developed by the NetBSD
     21  *        Foundation, Inc. and its contributors.
     22  * 4. Neither the name of The NetBSD Foundation nor the names of its
     23  *    contributors may be used to endorse or promote products derived
     24  *    from this software without specific prior written permission.
     25  *
     26  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
     27  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     28  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     29  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
     30  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     31  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     32  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     33  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     34  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     35  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     36  * POSSIBILITY OF SUCH DAMAGE.
     37  */
     38 
     39 /*
     40  * Copyright (c) 1985, 1988, 1993, 1994
     41  *	The Regents of the University of California.  All rights reserved.
     42  *
     43  * Redistribution and use in source and binary forms, with or without
     44  * modification, are permitted provided that the following conditions
     45  * are met:
     46  * 1. Redistributions of source code must retain the above copyright
     47  *    notice, this list of conditions and the following disclaimer.
     48  * 2. Redistributions in binary form must reproduce the above copyright
     49  *    notice, this list of conditions and the following disclaimer in the
     50  *    documentation and/or other materials provided with the distribution.
     51  * 3. All advertising materials mentioning features or use of this software
     52  *    must display the following acknowledgement:
     53  *	This product includes software developed by the University of
     54  *	California, Berkeley and its contributors.
     55  * 4. Neither the name of the University nor the names of its contributors
     56  *    may be used to endorse or promote products derived from this software
     57  *    without specific prior written permission.
     58  *
     59  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
     60  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     61  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     62  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
     63  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     64  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     65  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     66  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     67  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     68  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     69  * SUCH DAMAGE.
     70  *
     71  *	@(#)ftpcmd.y	8.3 (Berkeley) 4/6/94
     72  */
     73 
     74 /*
     75  * Grammar for FTP commands.
     76  * See RFC 959.
     77  */
     78 
     79 %{
     80 #include <sys/cdefs.h>
     81 
     82 #ifndef lint
     83 #if 0
     84 static char sccsid[] = "@(#)ftpcmd.y	8.3 (Berkeley) 4/6/94";
     85 #else
     86 __RCSID("$NetBSD: ftpcmd.y,v 1.41 1999/12/12 14:05:54 lukem Exp $");
     87 #endif
     88 #endif /* not lint */
     89 
     90 #include <sys/param.h>
     91 #include <sys/socket.h>
     92 #include <sys/stat.h>
     93 
     94 #include <netinet/in.h>
     95 #include <arpa/ftp.h>
     96 #include <arpa/inet.h>
     97 
     98 #include <ctype.h>
     99 #include <errno.h>
    100 #include <glob.h>
    101 #include <pwd.h>
    102 #include <setjmp.h>
    103 #include <signal.h>
    104 #include <stdio.h>
    105 #include <stdlib.h>
    106 #include <string.h>
    107 #include <syslog.h>
    108 #include <time.h>
    109 #include <tzfile.h>
    110 #include <unistd.h>
    111 #include <netdb.h>
    112 
    113 #ifdef KERBEROS5
    114 #include <krb5/krb5.h>
    115 #endif
    116 
    117 #include "extern.h"
    118 
    119 off_t	restart_point;
    120 
    121 static	int cmd_type;
    122 static	int cmd_form;
    123 static	int cmd_bytesz;
    124 char	cbuf[512];
    125 char	*fromname;
    126 int	hasyyerrored;
    127 
    128 extern	jmp_buf		errcatch;
    129 
    130 %}
    131 
    132 %union {
    133 	int	i;
    134 	char   *s;
    135 }
    136 
    137 %token
    138 	A	B	C	E	F	I
    139 	L	N	P	R	S	T
    140 	ALL
    141 
    142 	SP	CRLF	COMMA
    143 
    144 	USER	PASS	ACCT	CWD	CDUP	SMNT
    145 	QUIT	REIN	PORT	PASV	TYPE	STRU
    146 	MODE	RETR	STOR	STOU	APPE	ALLO
    147 	REST	RNFR	RNTO	ABOR	DELE	RMD
    148 	MKD	PWD	LIST	NLST	SITE	SYST
    149 	STAT	HELP	NOOP
    150 
    151 	AUTH	ADAT	PROT	PBSZ	CCC	MIC
    152 	CONF	ENC
    153 
    154 	FEAT	OPTS
    155 
    156 	SIZE	MDTM
    157 
    158 	LPRT	LPSV	EPRT	EPSV
    159 
    160 	MAIL	MLFL	MRCP	MRSQ	MSAM	MSND
    161 	MSOM
    162 
    163 	CHMOD	IDLE	RATEGET	RATEPUT	UMASK
    164 
    165 	LEXERR
    166 
    167 %token	<s> STRING
    168 %token	<s> ALL
    169 %token	<i> NUMBER
    170 
    171 %type	<i> check_login check_modify check_upload octal_number byte_size
    172 %type	<i> struct_code mode_code type_code form_code decimal_integer
    173 %type	<s> pathstring pathname password username
    174 %type	<s> mechanism_name base64data prot_code
    175 
    176 %start	cmd_list
    177 
    178 %%
    179 
    180 cmd_list
    181 	: /* empty */
    182 
    183 	| cmd_list cmd
    184 		{
    185 			fromname = NULL;
    186 			restart_point = (off_t) 0;
    187 		}
    188 
    189 	| cmd_list rcmd
    190 
    191 	;
    192 
    193 cmd
    194 						/* RFC 959 */
    195 	: USER SP username CRLF
    196 		{
    197 			user($3);
    198 			free($3);
    199 		}
    200 
    201 	| PASS SP password CRLF
    202 		{
    203 			pass($3);
    204 			free($3);
    205 		}
    206 
    207 	| CWD check_login CRLF
    208 		{
    209 			if ($2)
    210 				cwd(pw->pw_dir);
    211 		}
    212 
    213 	| CWD check_login SP pathname CRLF
    214 		{
    215 			if ($2 && $4 != NULL)
    216 				cwd($4);
    217 			if ($4 != NULL)
    218 				free($4);
    219 		}
    220 
    221 	| CDUP check_login CRLF
    222 		{
    223 			if ($2)
    224 				cwd("..");
    225 		}
    226 
    227 	| QUIT CRLF
    228 		{
    229 			if (logged_in) {
    230 				lreply(221, "");
    231 				lreply(0,
    232 	    "Data traffic for this session was %qd byte%s in %qd file%s.",
    233 				    (qdfmt_t)total_data, PLURAL(total_data),
    234 				    (qdfmt_t)total_files, PLURAL(total_files));
    235 				lreply(0,
    236 	    "Total traffic for this session was %qd byte%s in %qd transfer%s.",
    237 				    (qdfmt_t)total_bytes, PLURAL(total_bytes),
    238 				    (qdfmt_t)total_xfers, PLURAL(total_xfers));
    239 			}
    240 			reply(221,
    241 			    "Thank you for using the FTP service on %s.",
    242 			    hostname);
    243 			if (logged_in) {
    244 				syslog(LOG_INFO,
    245 				    "Data traffic: %qd byte%s in %qd file%s",
    246 				    (qdfmt_t)total_data, PLURAL(total_data),
    247 				    (qdfmt_t)total_files, PLURAL(total_files));
    248 				syslog(LOG_INFO,
    249 				  "Total traffic: %qd byte%s in %qd transfer%s",
    250 				    (qdfmt_t)total_bytes, PLURAL(total_bytes),
    251 				    (qdfmt_t)total_xfers, PLURAL(total_xfers));
    252 			}
    253 
    254 			dologout(0);
    255 		}
    256 
    257 	| PORT check_login SP host_port CRLF
    258 		{
    259 			if ($2) {
    260 					/* be paranoid, if told so */
    261 			if (curclass.checkportcmd &&
    262 			    ((ntohs(data_dest.su_port) < IPPORT_RESERVED) ||
    263 			    memcmp(&data_dest.su_sin.sin_addr,
    264 			    &his_addr.su_sin.sin_addr,
    265 			    sizeof(data_dest.su_sin.sin_addr)) != 0)) {
    266 				reply(500,
    267 				    "Illegal PORT command rejected");
    268 			} else if (epsvall) {
    269 				reply(501, "PORT disallowed after EPSV ALL");
    270 			} else {
    271 				usedefault = 0;
    272 				if (pdata >= 0) {
    273 					(void) close(pdata);
    274 					pdata = -1;
    275 				}
    276 				reply(200, "PORT command successful.");
    277 			}
    278 
    279 			}
    280 		}
    281 
    282 	| LPRT check_login SP host_long_port4 CRLF
    283 		{
    284 			/* reject invalid host_long_port4 */
    285 			if (data_dest.su_family != AF_INET) {
    286 				reply(500, "Illegal LPRT command rejected");
    287 				return (NULL);
    288 			}
    289 			/* be paranoid, if told so */
    290 			if (curclass.checkportcmd &&
    291 			    ((ntohs(data_dest.su_port) < IPPORT_RESERVED) ||
    292 			     memcmp(&data_dest.su_sin.sin_addr,
    293 				    &his_addr.su_sin.sin_addr,
    294 			     sizeof(data_dest.su_sin.sin_addr)) != 0)) {
    295 				reply(500, "Illegal LPRT command rejected");
    296 				return (NULL);
    297 			}
    298 			if (epsvall)
    299 				reply(501, "LPRT disallowed after EPSV ALL");
    300 			else {
    301 				usedefault = 0;
    302 				if (pdata >= 0) {
    303 					(void) close(pdata);
    304 					pdata = -1;
    305 				}
    306 				reply(200, "LPRT command successful.");
    307 			}
    308 		}
    309 
    310 	| LPRT check_login SP host_long_port6 CRLF
    311 		{
    312 			/* reject invalid host_long_port6 */
    313 			if (data_dest.su_family != AF_INET6) {
    314 				reply(500, "Illegal LPRT command rejected");
    315 				return (NULL);
    316 			}
    317 			/* be paranoid, if told so */
    318 			if (curclass.checkportcmd &&
    319 			    ((ntohs(data_dest.su_port) < IPPORT_RESERVED) ||
    320 			     memcmp(&data_dest.su_sin6.sin6_addr,
    321 				    &his_addr.su_sin6.sin6_addr,
    322 			     sizeof(data_dest.su_sin6.sin6_addr)) != 0)) {
    323 				reply(500, "Illegal LPRT command rejected");
    324 				return (NULL);
    325 			}
    326 			if (epsvall)
    327 				reply(501, "LPRT disallowed after EPSV ALL");
    328 			else {
    329 				usedefault = 0;
    330 				if (pdata >= 0) {
    331 					(void) close(pdata);
    332 					pdata = -1;
    333 				}
    334 				reply(200, "LPRT command successful.");
    335 			}
    336 		}
    337 
    338 	| EPRT check_login SP STRING CRLF
    339 		{
    340 			char *tmp = NULL;
    341 			char *result[3];
    342 			char *p, *q;
    343 			char delim;
    344 			struct addrinfo hints;
    345 			struct addrinfo *res;
    346 			int i;
    347 
    348 			if (epsvall) {
    349 				reply(501, "EPRT disallowed after EPSV ALL");
    350 				goto eprt_done;
    351 			}
    352 			usedefault = 0;
    353 			if (pdata >= 0) {
    354 				(void) close(pdata);
    355 				pdata = -1;
    356 			}
    357 
    358 			/*XXX checks for login */
    359 
    360 			tmp = xstrdup($4);
    361 			p = tmp;
    362 			delim = p[0];
    363 			p++;
    364 			memset(result, 0, sizeof(result));
    365 			for (i = 0; i < 3; i++) {
    366 				q = strchr(p, delim);
    367 				if (!q || *q != delim) {
    368 		parsefail:
    369 					reply(500, "Invalid argument, rejected.");
    370 					if (tmp)
    371 						free(tmp);
    372 					usedefault = 1;
    373 					goto eprt_done;
    374 				}
    375 				*q++ = '\0';
    376 				result[i] = p;
    377 				p = q;
    378 			}
    379 
    380 			/* some more sanity check */
    381 			p = result[0];
    382 			while (*p) {
    383 				if (!isdigit(*p))
    384 					goto parsefail;
    385 				p++;
    386 			}
    387 			p = result[2];
    388 			while (*p) {
    389 				if (!isdigit(*p))
    390 					goto parsefail;
    391 				p++;
    392 			}
    393 
    394 			memset(&hints, 0, sizeof(hints));
    395 			if (atoi(result[0]) == 1)
    396 				hints.ai_family = PF_INET;
    397 			if (atoi(result[0]) == 2)
    398 				hints.ai_family = PF_INET6;
    399 			else
    400 				hints.ai_family = PF_UNSPEC;	/*XXX*/
    401 			hints.ai_socktype = SOCK_STREAM;
    402 			if (getaddrinfo(result[1], result[2], &hints, &res))
    403 				goto parsefail;
    404 			memcpy(&data_dest, res->ai_addr, res->ai_addrlen);
    405 			if (his_addr.su_family == AF_INET6
    406 			 && data_dest.su_family == AF_INET6) {
    407 				/* XXX more sanity checks! */
    408 				data_dest.su_sin6.sin6_scope_id =
    409 					his_addr.su_sin6.sin6_scope_id;
    410 			}
    411 			/* be paranoid, if told so */
    412 			if (curclass.checkportcmd) {
    413 				int fail;
    414 				fail = 0;
    415 				if (ntohs(data_dest.su_port) < IPPORT_RESERVED)
    416 					fail++;
    417 				if (data_dest.su_family != his_addr.su_family)
    418 					fail++;
    419 				if (data_dest.su_len != his_addr.su_len)
    420 					fail++;
    421 				switch (data_dest.su_family) {
    422 				case AF_INET:
    423 					fail += memcmp(&data_dest.su_sin.sin_addr,
    424 					    &his_addr.su_sin.sin_addr,
    425 					    sizeof(data_dest.su_sin.sin_addr));
    426 					break;
    427 				case AF_INET6:
    428 					fail += memcmp(&data_dest.su_sin6.sin6_addr,
    429 					    &his_addr.su_sin6.sin6_addr,
    430 					    sizeof(data_dest.su_sin6.sin6_addr));
    431 					break;
    432 				default:
    433 					fail++;
    434 				}
    435 				if (fail) {
    436 					reply(500,
    437 					    "Illegal EPRT command rejected");
    438 					return (NULL);
    439 				}
    440 			}
    441 			free(tmp);
    442 			tmp = NULL;
    443 			if (pdata >= 0) {
    444 				(void) close(pdata);
    445 				pdata = -1;
    446 			}
    447 			reply(200, "EPRT command successful.");
    448 		eprt_done:;
    449 		}
    450 
    451 	| PASV check_login CRLF
    452 		{
    453 			if (curclass.passive) {
    454 				passive();
    455 			} else {
    456 				reply(500, "PASV mode not available.");
    457 			}
    458 		}
    459 
    460 	| LPSV CRLF
    461 		{
    462 			if (epsvall)
    463 				reply(501, "LPSV disallowed after EPSV ALL");
    464 			else
    465 				long_passive("LPSV", PF_UNSPEC);
    466 		}
    467 
    468 	| EPSV SP NUMBER CRLF
    469 		{
    470 			int pf;
    471 			switch ($3) {
    472 			case 1:
    473 				pf = PF_INET;
    474 				break;
    475 			case 2:
    476 				pf = PF_INET6;
    477 				break;
    478 			default:
    479 				pf = -1;	/*junk*/
    480 				break;
    481 			}
    482 			long_passive("EPSV", pf);
    483 		}
    484 
    485 	| EPSV SP ALL CRLF
    486 		{
    487 			if (!logged_in) {
    488 				syslog(LOG_NOTICE, "long passive but not logged in");
    489 				reply(503, "Login with USER first.");
    490 			} else {
    491 				reply(200, "EPSV ALL command successful.");
    492 				epsvall++;
    493 			}
    494 		}
    495 
    496 	| EPSV CRLF
    497 		{
    498 			long_passive("EPSV", PF_UNSPEC);
    499 		}
    500 
    501 	| TYPE SP type_code CRLF
    502 		{
    503 			switch (cmd_type) {
    504 
    505 			case TYPE_A:
    506 				if (cmd_form == FORM_N) {
    507 					reply(200, "Type set to A.");
    508 					type = cmd_type;
    509 					form = cmd_form;
    510 				} else
    511 					reply(504, "Form must be N.");
    512 				break;
    513 
    514 			case TYPE_E:
    515 				reply(504, "Type E not implemented.");
    516 				break;
    517 
    518 			case TYPE_I:
    519 				reply(200, "Type set to I.");
    520 				type = cmd_type;
    521 				break;
    522 
    523 			case TYPE_L:
    524 #if NBBY == 8
    525 				if (cmd_bytesz == 8) {
    526 					reply(200,
    527 					    "Type set to L (byte size 8).");
    528 					type = cmd_type;
    529 				} else
    530 					reply(504, "Byte size must be 8.");
    531 #else /* NBBY == 8 */
    532 				UNIMPLEMENTED for NBBY != 8
    533 #endif /* NBBY == 8 */
    534 			}
    535 		}
    536 
    537 	| STRU SP struct_code CRLF
    538 		{
    539 			switch ($3) {
    540 
    541 			case STRU_F:
    542 				reply(200, "STRU F ok.");
    543 				break;
    544 
    545 			default:
    546 				reply(504, "Unimplemented STRU type.");
    547 			}
    548 		}
    549 
    550 	| MODE SP mode_code CRLF
    551 		{
    552 			switch ($3) {
    553 
    554 			case MODE_S:
    555 				reply(200, "MODE S ok.");
    556 				break;
    557 
    558 			default:
    559 				reply(502, "Unimplemented MODE type.");
    560 			}
    561 		}
    562 
    563 	| RETR check_login SP pathname CRLF
    564 		{
    565 			if ($2 && $4 != NULL)
    566 				retrieve(NULL, $4);
    567 			if ($4 != NULL)
    568 				free($4);
    569 		}
    570 
    571 	| STOR check_upload SP pathname CRLF
    572 		{
    573 			if ($2 && $4 != NULL)
    574 				store($4, "w", 0);
    575 			if ($4 != NULL)
    576 				free($4);
    577 		}
    578 
    579 	| STOU check_upload SP pathname CRLF
    580 		{
    581 			if ($2 && $4 != NULL)
    582 				store($4, "w", 1);
    583 			if ($4 != NULL)
    584 				free($4);
    585 		}
    586 
    587 	| APPE check_upload SP pathname CRLF
    588 		{
    589 			if ($2 && $4 != NULL)
    590 				store($4, "a", 0);
    591 			if ($4 != NULL)
    592 				free($4);
    593 		}
    594 
    595 	| ALLO SP NUMBER CRLF
    596 		{
    597 			reply(202, "ALLO command ignored.");
    598 		}
    599 
    600 	| ALLO SP NUMBER SP R SP NUMBER CRLF
    601 		{
    602 			reply(202, "ALLO command ignored.");
    603 		}
    604 
    605 	| RNTO SP pathname CRLF
    606 		{
    607 			if (fromname) {
    608 				renamecmd(fromname, $3);
    609 				free(fromname);
    610 				fromname = NULL;
    611 			} else {
    612 				reply(503, "Bad sequence of commands.");
    613 			}
    614 			free($3);
    615 		}
    616 
    617 	| ABOR CRLF
    618 		{
    619 			reply(225, "ABOR command successful.");
    620 		}
    621 
    622 	| DELE check_modify SP pathname CRLF
    623 		{
    624 			if ($2 && $4 != NULL)
    625 				delete($4);
    626 			if ($4 != NULL)
    627 				free($4);
    628 		}
    629 
    630 	| RMD check_modify SP pathname CRLF
    631 		{
    632 			if ($2 && $4 != NULL)
    633 				removedir($4);
    634 			if ($4 != NULL)
    635 				free($4);
    636 		}
    637 
    638 	| MKD check_modify SP pathname CRLF
    639 		{
    640 			if ($2 && $4 != NULL)
    641 				makedir($4);
    642 			if ($4 != NULL)
    643 				free($4);
    644 		}
    645 
    646 	| PWD check_login CRLF
    647 		{
    648 			if ($2)
    649 				pwd();
    650 		}
    651 
    652 	| LIST check_login CRLF
    653 		{
    654 			char *argv[] = { INTERNAL_LS, "-lgA", NULL };
    655 
    656 			if ($2)
    657 				retrieve(argv, "");
    658 		}
    659 
    660 	| LIST check_login SP pathname CRLF
    661 		{
    662 			char *argv[] = { INTERNAL_LS, "-lgA", NULL, NULL };
    663 
    664 			if ($2 && $4 != NULL) {
    665 				argv[2] = $4;
    666 				retrieve(argv, $4);
    667 			}
    668 			if ($4 != NULL)
    669 				free($4);
    670 		}
    671 
    672 	| NLST check_login CRLF
    673 		{
    674 			if ($2)
    675 				send_file_list(".");
    676 		}
    677 
    678 	| NLST check_login SP STRING CRLF
    679 		{
    680 			if ($2 && $4 != NULL)
    681 				send_file_list($4);
    682 			if ($4 != NULL)
    683 				free($4);
    684 		}
    685 
    686 	| SITE SP HELP CRLF
    687 		{
    688 			help(sitetab, NULL);
    689 		}
    690 
    691 	| SITE SP CHMOD check_modify SP octal_number SP pathname CRLF
    692 		{
    693 			if ($4 && ($8 != NULL)) {
    694 				if ($6 > 0777)
    695 					reply(501,
    696 				"CHMOD: Mode value must be between 0 and 0777");
    697 				else if (chmod($8, $6) < 0)
    698 					perror_reply(550, $8);
    699 				else
    700 					reply(200, "CHMOD command successful.");
    701 			}
    702 			if ($8 != NULL)
    703 				free($8);
    704 		}
    705 
    706 	| SITE SP HELP SP STRING CRLF
    707 		{
    708 			help(sitetab, $5);
    709 		}
    710 
    711 	| SITE SP IDLE CRLF
    712 		{
    713 			reply(200,
    714 			    "Current IDLE time limit is %d seconds; max %d",
    715 				curclass.timeout, curclass.maxtimeout);
    716 		}
    717 
    718 	| SITE SP IDLE SP NUMBER CRLF
    719 		{
    720 			if ($5 < 30 || $5 > curclass.maxtimeout) {
    721 				reply(501,
    722 			"IDLE time limit must be between 30 and %d seconds",
    723 				    curclass.maxtimeout);
    724 			} else {
    725 				curclass.timeout = $5;
    726 				(void) alarm(curclass.timeout);
    727 				reply(200,
    728 				    "IDLE time limit set to %d seconds",
    729 				    curclass.timeout);
    730 			}
    731 		}
    732 
    733 	| SITE SP RATEGET CRLF
    734 		{
    735 			reply(200, "Current RATEGET is %d bytes/sec",
    736 			    curclass.rateget);
    737 		}
    738 
    739 	| SITE SP RATEGET SP STRING CRLF
    740 		{
    741 			char *p = $5;
    742 			int rate;
    743 
    744 			rate = strsuftoi(p);
    745 			if (rate == -1)
    746 				reply(501, "Invalid RATEGET %s", p);
    747 			else if (curclass.maxrateget &&
    748 			    rate > curclass.maxrateget)
    749 				reply(501,
    750 			    "RATEGET %d is larger than maximum RATEGET %d",
    751 				    rate, curclass.maxrateget);
    752 			else {
    753 				curclass.rateget = rate;
    754 				reply(200, "RATEGET set to %d bytes/sec",
    755 				    curclass.rateget);
    756 			}
    757 		}
    758 
    759 	| SITE SP RATEPUT CRLF
    760 		{
    761 			reply(200, "Current RATEPUT is %d bytes/sec",
    762 			    curclass.rateput);
    763 		}
    764 
    765 	| SITE SP RATEPUT SP STRING CRLF
    766 		{
    767 			char *p = $5;
    768 			int rate;
    769 
    770 			rate = strsuftoi(p);
    771 			if (rate == -1)
    772 				reply(501, "Invalid RATEPUT %s", p);
    773 			else if (curclass.maxrateput &&
    774 			    rate > curclass.maxrateput)
    775 				reply(501,
    776 			    "RATEPUT %d is larger than maximum RATEPUT %d",
    777 				    rate, curclass.maxrateput);
    778 			else {
    779 				curclass.rateput = rate;
    780 				reply(200, "RATEPUT set to %d bytes/sec",
    781 				    curclass.rateput);
    782 			}
    783 		}
    784 
    785 	| SITE SP UMASK check_login CRLF
    786 		{
    787 			int oldmask;
    788 
    789 			if ($4) {
    790 				oldmask = umask(0);
    791 				(void) umask(oldmask);
    792 				reply(200, "Current UMASK is %03o", oldmask);
    793 			}
    794 		}
    795 
    796 	| SITE SP UMASK check_modify SP octal_number CRLF
    797 		{
    798 			int oldmask;
    799 
    800 			if ($4) {
    801 				if (($6 == -1) || ($6 > 0777)) {
    802 					reply(501, "Bad UMASK value");
    803 				} else {
    804 					oldmask = umask($6);
    805 					reply(200,
    806 					    "UMASK set to %03o (was %03o)",
    807 					    $6, oldmask);
    808 				}
    809 			}
    810 		}
    811 
    812 	| SYST CRLF
    813 		{
    814 			reply(215, "UNIX Type: L%d %s", NBBY, version);
    815 		}
    816 
    817 	| STAT check_login SP pathname CRLF
    818 		{
    819 			if ($2 && $4 != NULL)
    820 				statfilecmd($4);
    821 			if ($4 != NULL)
    822 				free($4);
    823 		}
    824 
    825 	| STAT CRLF
    826 		{
    827 			statcmd();
    828 		}
    829 
    830 	| HELP CRLF
    831 		{
    832 			help(cmdtab, NULL);
    833 		}
    834 
    835 	| HELP SP STRING CRLF
    836 		{
    837 			char *cp = $3;
    838 
    839 			if (strncasecmp(cp, "SITE", 4) == 0) {
    840 				cp = $3 + 4;
    841 				if (*cp == ' ')
    842 					cp++;
    843 				if (*cp)
    844 					help(sitetab, cp);
    845 				else
    846 					help(sitetab, NULL);
    847 			} else
    848 				help(cmdtab, $3);
    849 		}
    850 
    851 	| NOOP CRLF
    852 		{
    853 			reply(200, "NOOP command successful.");
    854 		}
    855 
    856 						/* RFC 2228 */
    857 	| AUTH SP mechanism_name CRLF
    858 		{
    859 			reply(502, "RFC 2228 authentication not implemented.");
    860 		}
    861 
    862 	| ADAT SP base64data CRLF
    863 		{
    864 			reply(503,
    865 			    "Please set authentication state with AUTH.");
    866 		}
    867 
    868 	| PROT SP prot_code CRLF
    869 		{
    870 			reply(503,
    871 			    "Please set protection buffer size with PBSZ.");
    872 		}
    873 
    874 	| PBSZ SP decimal_integer CRLF
    875 		{
    876 			reply(503,
    877 			    "Please set authentication state with AUTH.");
    878 		}
    879 
    880 	| CCC CRLF
    881 		{
    882 			reply(533, "No protection enabled.");
    883 		}
    884 
    885 	| MIC SP base64data CRLF
    886 		{
    887 			reply(502, "RFC 2228 authentication not implemented.");
    888 		}
    889 
    890 	| CONF SP base64data CRLF
    891 		{
    892 			reply(502, "RFC 2228 authentication not implemented.");
    893 		}
    894 
    895 	| ENC SP base64data CRLF
    896 		{
    897 			reply(502, "RFC 2228 authentication not implemented.");
    898 		}
    899 
    900 						/* RFC 2389 */
    901 	| FEAT CRLF
    902 		{
    903 			lreply(211, "Features supported");
    904 			lreply(-1,  " MDTM");
    905 			lreply(-1,  " REST STREAM");
    906 			lreply(-1,  " SIZE");
    907 			reply(211,  "End");
    908 		}
    909 
    910 	| OPTS SP STRING CRLF
    911 		{
    912 
    913 			opts($3);
    914 		}
    915 
    916 
    917 						/* BSD extensions */
    918 
    919 		/*
    920 		 * SIZE is not in RFC 959, but Postel has blessed it and
    921 		 * it will be in the updated RFC.
    922 		 *
    923 		 * Return size of file in a format suitable for
    924 		 * using with RESTART (we just count bytes).
    925 		 */
    926 	| SIZE check_login SP pathname CRLF
    927 		{
    928 			if ($2 && $4 != NULL)
    929 				sizecmd($4);
    930 			if ($4 != NULL)
    931 				free($4);
    932 		}
    933 
    934 		/*
    935 		 * MDTM is not in RFC 959, but Postel has blessed it and
    936 		 * it will be in the updated RFC.
    937 		 *
    938 		 * Return modification time of file as an ISO 3307
    939 		 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
    940 		 * where xxx is the fractional second (of any precision,
    941 		 * not necessarily 3 digits)
    942 		 */
    943 	| MDTM check_login SP pathname CRLF
    944 		{
    945 			if ($2 && $4 != NULL) {
    946 				struct stat stbuf;
    947 				if (stat($4, &stbuf) < 0)
    948 					perror_reply(550, $4);
    949 				else if (!S_ISREG(stbuf.st_mode)) {
    950 					reply(550, "%s: not a plain file.", $4);
    951 				} else {
    952 					struct tm *t;
    953 					t = gmtime(&stbuf.st_mtime);
    954 					reply(213,
    955 					    "%04d%02d%02d%02d%02d%02d",
    956 					    TM_YEAR_BASE + t->tm_year,
    957 					    t->tm_mon+1, t->tm_mday,
    958 					    t->tm_hour, t->tm_min, t->tm_sec);
    959 				}
    960 			}
    961 			if ($4 != NULL)
    962 				free($4);
    963 		}
    964 
    965 	| error CRLF
    966 		{
    967 			yyerrok;
    968 		}
    969 	;
    970 
    971 rcmd
    972 	: REST SP byte_size CRLF
    973 		{
    974 			fromname = NULL;
    975 			restart_point = $3;	/* XXX $3 is only "int" */
    976 			reply(350, "Restarting at %qd. %s",
    977 			    (qdfmt_t)restart_point,
    978 			    "Send STORE or RETRIEVE to initiate transfer.");
    979 		}
    980 	| RNFR check_modify SP pathname CRLF
    981 		{
    982 			restart_point = (off_t) 0;
    983 			if ($2 && $4) {
    984 				fromname = renamefrom($4);
    985 				if (fromname == NULL && $4) {
    986 					free($4);
    987 				}
    988 			}
    989 		}
    990 	;
    991 
    992 username
    993 	: STRING
    994 	;
    995 
    996 password
    997 	: /* empty */
    998 		{
    999 			$$ = (char *)calloc(1, sizeof(char));
   1000 		}
   1001 
   1002 	| STRING
   1003 	;
   1004 
   1005 byte_size
   1006 	: NUMBER
   1007 	;
   1008 
   1009 host_port
   1010 	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
   1011 		NUMBER COMMA NUMBER
   1012 		{
   1013 			char *a, *p;
   1014 
   1015 			data_dest.su_len = sizeof(struct sockaddr_in);
   1016 			data_dest.su_family = AF_INET;
   1017 			p = (char *)&data_dest.su_sin.sin_port;
   1018 			p[0] = $9; p[1] = $11;
   1019 			a = (char *)&data_dest.su_sin.sin_addr;
   1020 			a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7;
   1021 		}
   1022 	;
   1023 
   1024 host_long_port4
   1025 	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
   1026 		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
   1027 		NUMBER
   1028 		{
   1029 			char *a, *p;
   1030 
   1031 			data_dest.su_sin.sin_len =
   1032 				sizeof(struct sockaddr_in);
   1033 			data_dest.su_family = AF_INET;
   1034 			p = (char *)&data_dest.su_port;
   1035 			p[0] = $15; p[1] = $17;
   1036 			a = (char *)&data_dest.su_sin.sin_addr;
   1037 			a[0] =  $5;  a[1] =  $7;  a[2] =  $9;  a[3] = $11;
   1038 
   1039 			/* reject invalid LPRT command */
   1040 			if ($1 != 4 || $3 != 4 || $13 != 2)
   1041 				memset(&data_dest, 0, sizeof(data_dest));
   1042 		}
   1043 	;
   1044 
   1045 host_long_port6
   1046 	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
   1047 		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
   1048 		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
   1049 		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
   1050 		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
   1051 		NUMBER
   1052 		{
   1053 			char *a, *p;
   1054 
   1055 			data_dest.su_sin6.sin6_len =
   1056 				sizeof(struct sockaddr_in6);
   1057 			data_dest.su_family = AF_INET6;
   1058 			p = (char *)&data_dest.su_port;
   1059 			p[0] = $39; p[1] = $41;
   1060 			a = (char *)&data_dest.su_sin6.sin6_addr;
   1061 			 a[0] =  $5;  a[1] =  $7;  a[2] =  $9;  a[3] = $11;
   1062 			 a[4] = $13;  a[5] = $15;  a[6] = $17;  a[7] = $19;
   1063 			 a[8] = $21;  a[9] = $23; a[10] = $25; a[11] = $27;
   1064 			a[12] = $29; a[13] = $31; a[14] = $33; a[15] = $35;
   1065 			if (his_addr.su_family == AF_INET6) {
   1066 				/* XXX more sanity checks! */
   1067 				data_dest.su_sin6.sin6_scope_id =
   1068 					his_addr.su_sin6.sin6_scope_id;
   1069 			}
   1070 
   1071 			/* reject invalid LPRT command */
   1072 			if ($1 != 6 || $3 != 16 || $37 != 2)
   1073 				memset(&data_dest, 0, sizeof(data_dest));
   1074 		}
   1075 	;
   1076 
   1077 form_code
   1078 	: N
   1079 		{
   1080 			$$ = FORM_N;
   1081 		}
   1082 
   1083 	| T
   1084 		{
   1085 			$$ = FORM_T;
   1086 		}
   1087 
   1088 	| C
   1089 		{
   1090 			$$ = FORM_C;
   1091 		}
   1092 	;
   1093 
   1094 type_code
   1095 	: A
   1096 		{
   1097 			cmd_type = TYPE_A;
   1098 			cmd_form = FORM_N;
   1099 		}
   1100 
   1101 	| A SP form_code
   1102 		{
   1103 			cmd_type = TYPE_A;
   1104 			cmd_form = $3;
   1105 		}
   1106 
   1107 	| E
   1108 		{
   1109 			cmd_type = TYPE_E;
   1110 			cmd_form = FORM_N;
   1111 		}
   1112 
   1113 	| E SP form_code
   1114 		{
   1115 			cmd_type = TYPE_E;
   1116 			cmd_form = $3;
   1117 		}
   1118 
   1119 	| I
   1120 		{
   1121 			cmd_type = TYPE_I;
   1122 		}
   1123 
   1124 	| L
   1125 		{
   1126 			cmd_type = TYPE_L;
   1127 			cmd_bytesz = NBBY;
   1128 		}
   1129 
   1130 	| L SP byte_size
   1131 		{
   1132 			cmd_type = TYPE_L;
   1133 			cmd_bytesz = $3;
   1134 		}
   1135 
   1136 		/* this is for a bug in the BBN ftp */
   1137 	| L byte_size
   1138 		{
   1139 			cmd_type = TYPE_L;
   1140 			cmd_bytesz = $2;
   1141 		}
   1142 	;
   1143 
   1144 struct_code
   1145 	: F
   1146 		{
   1147 			$$ = STRU_F;
   1148 		}
   1149 
   1150 	| R
   1151 		{
   1152 			$$ = STRU_R;
   1153 		}
   1154 
   1155 	| P
   1156 		{
   1157 			$$ = STRU_P;
   1158 		}
   1159 	;
   1160 
   1161 mode_code
   1162 	: S
   1163 		{
   1164 			$$ = MODE_S;
   1165 		}
   1166 
   1167 	| B
   1168 		{
   1169 			$$ = MODE_B;
   1170 		}
   1171 
   1172 	| C
   1173 		{
   1174 			$$ = MODE_C;
   1175 		}
   1176 	;
   1177 
   1178 pathname
   1179 	: pathstring
   1180 		{
   1181 			/*
   1182 			 * Problem: this production is used for all pathname
   1183 			 * processing, but only gives a 550 error reply.
   1184 			 * This is a valid reply in some cases but not in
   1185 			 * others.
   1186 			 */
   1187 			if (logged_in && $1 && *$1 == '~') {
   1188 				glob_t gl;
   1189 				int flags =
   1190 				 GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE;
   1191 
   1192 				if ($1[1] == '\0')
   1193 					$$ = xstrdup(pw->pw_dir);
   1194 				else {
   1195 					memset(&gl, 0, sizeof(gl));
   1196 					if (glob($1, flags, NULL, &gl) ||
   1197 					    gl.gl_pathc == 0) {
   1198 						reply(550, "not found");
   1199 						$$ = NULL;
   1200 					} else
   1201 						$$ = xstrdup(gl.gl_pathv[0]);
   1202 					globfree(&gl);
   1203 				}
   1204 				free($1);
   1205 			} else
   1206 				$$ = $1;
   1207 		}
   1208 	;
   1209 
   1210 pathstring
   1211 	: STRING
   1212 	;
   1213 
   1214 octal_number
   1215 	: NUMBER
   1216 		{
   1217 			int ret, dec, multby, digit;
   1218 
   1219 			/*
   1220 			 * Convert a number that was read as decimal number
   1221 			 * to what it would be if it had been read as octal.
   1222 			 */
   1223 			dec = $1;
   1224 			multby = 1;
   1225 			ret = 0;
   1226 			while (dec) {
   1227 				digit = dec%10;
   1228 				if (digit > 7) {
   1229 					ret = -1;
   1230 					break;
   1231 				}
   1232 				ret += digit * multby;
   1233 				multby *= 8;
   1234 				dec /= 10;
   1235 			}
   1236 			$$ = ret;
   1237 		}
   1238 	;
   1239 
   1240 mechanism_name
   1241 	: STRING
   1242 	;
   1243 
   1244 base64data
   1245 	: STRING
   1246 	;
   1247 
   1248 prot_code
   1249 	: STRING
   1250 	;
   1251 
   1252 decimal_integer
   1253 	: NUMBER
   1254 	;
   1255 
   1256 check_login
   1257 	: /* empty */
   1258 		{
   1259 			if (logged_in)
   1260 				$$ = 1;
   1261 			else {
   1262 				reply(530, "Please login with USER and PASS.");
   1263 				$$ = 0;
   1264 				hasyyerrored = 1;
   1265 			}
   1266 		}
   1267 	;
   1268 
   1269 check_modify
   1270 	: /* empty */
   1271 		{
   1272 			if (logged_in) {
   1273 				if (curclass.modify)
   1274 					$$ = 1;
   1275 				else {
   1276 					reply(502,
   1277 					"No permission to use this command.");
   1278 					$$ = 0;
   1279 					hasyyerrored = 1;
   1280 				}
   1281 			} else {
   1282 				reply(530, "Please login with USER and PASS.");
   1283 				$$ = 0;
   1284 				hasyyerrored = 1;
   1285 			}
   1286 		}
   1287 
   1288 check_upload
   1289 	: /* empty */
   1290 		{
   1291 			if (logged_in) {
   1292 				if (curclass.upload)
   1293 					$$ = 1;
   1294 				else {
   1295 					reply(502,
   1296 					"No permission to use this command.");
   1297 					$$ = 0;
   1298 					hasyyerrored = 1;
   1299 				}
   1300 			} else {
   1301 				reply(530, "Please login with USER and PASS.");
   1302 				$$ = 0;
   1303 				hasyyerrored = 1;
   1304 			}
   1305 		}
   1306 
   1307 
   1308 %%
   1309 
   1310 #define	CMD	0	/* beginning of command */
   1311 #define	ARGS	1	/* expect miscellaneous arguments */
   1312 #define	STR1	2	/* expect SP followed by STRING */
   1313 #define	STR2	3	/* expect STRING */
   1314 #define	OSTR	4	/* optional SP then STRING */
   1315 #define	ZSTR1	5	/* SP then optional STRING */
   1316 #define	ZSTR2	6	/* optional STRING after SP */
   1317 #define	SITECMD	7	/* SITE command */
   1318 #define	NSTR	8	/* Number followed by a string */
   1319 #define NOARGS	9	/* No arguments allowed */
   1320 
   1321 struct tab {
   1322 	char	*name;
   1323 	short	 token;
   1324 	short	 state;
   1325 	short	 implemented;	/* 1 if command is implemented */
   1326 	short	 hasopts;	/* 1 if command takes options */
   1327 	char	*help;
   1328 	char	*options;
   1329 };
   1330 
   1331 struct tab cmdtab[] = {
   1332 				/* From RFC 959, in order defined (5.3.1) */
   1333 	{ "USER", USER, STR1,	1, 0,	"<sp> username" },
   1334 	{ "PASS", PASS, ZSTR1,	1, 0,	"<sp> password" },
   1335 	{ "ACCT", ACCT, STR1,	0, 0,	"(specify account)" },
   1336 	{ "CWD",  CWD,  OSTR,	1, 0,	"[ <sp> directory-name ]" },
   1337 	{ "CDUP", CDUP, NOARGS,	1, 0,	"(change to parent directory)" },
   1338 	{ "SMNT", SMNT, ARGS,	0, 0,	"(structure mount)" },
   1339 	{ "QUIT", QUIT, NOARGS,	1, 0,	"(terminate service)" },
   1340 	{ "REIN", REIN, NOARGS,	0, 0,	"(reinitialize server state)" },
   1341 	{ "PORT", PORT, ARGS,	1, 0,	"<sp> b0, b1, b2, b3, b4" },
   1342 	{ "LPRT", LPRT, ARGS,	1, 0,	"<sp> af, hal, h1, h2, h3,..., pal, p1, p2..." },
   1343 	{ "EPRT", EPRT, STR1,	1, 0,	"<sp> |af|addr|port|" },
   1344 	{ "PASV", PASV, NOARGS,	1, 0,	"(set server in passive mode)" },
   1345 	{ "LPSV", LPSV, ARGS,	1, 0,	"(set server in passive mode)" },
   1346 	{ "EPSV", EPSV, ARGS,	1, 0,	"[<sp> af|ALL]" },
   1347 	{ "TYPE", TYPE, ARGS,	1, 0,	"<sp> [ A | E | I | L ]" },
   1348 	{ "STRU", STRU, ARGS,	1, 0,	"(specify file structure)" },
   1349 	{ "MODE", MODE, ARGS,	1, 0,	"(specify transfer mode)" },
   1350 	{ "RETR", RETR, STR1,	1, 0,	"<sp> file-name" },
   1351 	{ "STOR", STOR, STR1,	1, 0,	"<sp> file-name" },
   1352 	{ "STOU", STOU, STR1,	1, 0,	"<sp> file-name" },
   1353 	{ "APPE", APPE, STR1,	1, 0,	"<sp> file-name" },
   1354 	{ "ALLO", ALLO, ARGS,	1, 0,	"allocate storage (vacuously)" },
   1355 	{ "REST", REST, ARGS,	1, 0,	"<sp> offset (restart command)" },
   1356 	{ "RNFR", RNFR, STR1,	1, 0,	"<sp> file-name" },
   1357 	{ "RNTO", RNTO, STR1,	1, 0,	"<sp> file-name" },
   1358 	{ "ABOR", ABOR, NOARGS,	1, 0,	"(abort operation)" },
   1359 	{ "DELE", DELE, STR1,	1, 0,	"<sp> file-name" },
   1360 	{ "RMD",  RMD,  STR1,	1, 0,	"<sp> path-name" },
   1361 	{ "MKD",  MKD,  STR1,	1, 0,	"<sp> path-name" },
   1362 	{ "PWD",  PWD,  NOARGS,	1, 0,	"(return current directory)" },
   1363 	{ "LIST", LIST, OSTR,	1, 0,	"[ <sp> path-name ]" },
   1364 	{ "NLST", NLST, OSTR,	1, 0,	"[ <sp> path-name ]" },
   1365 	{ "SITE", SITE, SITECMD, 1, 0,	"site-cmd [ <sp> arguments ]" },
   1366 	{ "SYST", SYST, NOARGS,	1, 0,	"(get type of operating system)" },
   1367 	{ "STAT", STAT, OSTR,	1, 0,	"[ <sp> path-name ]" },
   1368 	{ "HELP", HELP, OSTR,	1, 0,	"[ <sp> <string> ]" },
   1369 	{ "NOOP", NOOP, NOARGS,	1, 1,	"" },
   1370 
   1371 				/* From RFC 2228, in order defined */
   1372 	{ "AUTH", AUTH, STR1,	1, 0,	"<sp> mechanism-name" },
   1373 	{ "ADAT", ADAT, STR1,	1, 0,	"<sp> base-64-data" },
   1374 	{ "PROT", PROT, STR1,	1, 0,	"<sp> prot-code" },
   1375 	{ "PBSZ", PBSZ, ARGS,	1, 0,	"<sp> decimal-integer" },
   1376 	{ "CCC",  CCC,  NOARGS,	1, 0,	"(Disable data protection)" },
   1377 	{ "MIC",  MIC,  STR1,	1, 0,	"<sp> base64data" },
   1378 	{ "CONF", CONF, STR1,	1, 0,	"<sp> base64data" },
   1379 	{ "ENC",  ENC,  STR1,	1, 0,	"<sp> base64data" },
   1380 
   1381 				/* From RFC 2389, in order defined */
   1382 	{ "FEAT", FEAT, NOARGS,	1, 0,	"(display extended features)" },
   1383 	{ "OPTS", OPTS, STR1,	1, 0,	"<sp> command [ <sp> options ]" },
   1384 
   1385 				/* Non standardized extensions */
   1386 	{ "SIZE", SIZE, OSTR,	1, 0,	"<sp> path-name" },
   1387 	{ "MDTM", MDTM, OSTR,	1, 0,	"<sp> path-name" },
   1388 
   1389 				/* obsolete commands */
   1390 	{ "MAIL", MAIL, OSTR,	0, 0,	"(mail to user)" },
   1391 	{ "MLFL", MLFL, OSTR,	0, 0,	"(mail file)" },
   1392 	{ "MRCP", MRCP, STR1,	0, 0,	"(mail recipient)" },
   1393 	{ "MRSQ", MRSQ, OSTR,	0, 0,	"(mail recipient scheme question)" },
   1394 	{ "MSAM", MSAM, OSTR,	0, 0,	"(mail send to terminal and mailbox)" },
   1395 	{ "MSND", MSND, OSTR,	0, 0,	"(mail send to terminal)" },
   1396 	{ "MSOM", MSOM, OSTR,	0, 0,	"(mail send to terminal or mailbox)" },
   1397 	{ "XCUP", CDUP, NOARGS,	1, 0,	"(change to parent directory)" },
   1398 	{ "XCWD", CWD,  OSTR,	1, 0,	"[ <sp> directory-name ]" },
   1399 	{ "XMKD", MKD,  STR1,	1, 0,	"<sp> path-name" },
   1400 	{ "XPWD", PWD,  NOARGS,	1, 0,	"(return current directory)" },
   1401 	{ "XRMD", RMD,  STR1,	1, 0,	"<sp> path-name" },
   1402 
   1403 	{ NULL,   0,    0,	0, 0,	0 }
   1404 };
   1405 
   1406 struct tab sitetab[] = {
   1407 	{ "CHMOD", CHMOD, NSTR,	1, 0,	"<sp> mode <sp> file-name" },
   1408 	{ "HELP",  HELP,  OSTR,	1, 0,	"[ <sp> <string> ]" },
   1409 	{ "IDLE",  IDLE,  ARGS,	1, 0,	"[ <sp> maximum-idle-time ]" },
   1410 	{ "RATEGET", RATEGET, OSTR, 1,0,"[ <sp> get-throttle-rate ]" },
   1411 	{ "RATEPUT", RATEPUT, OSTR, 1,0,"[ <sp> put-throttle-rate ]" },
   1412 	{ "UMASK", UMASK, ARGS,	1, 0,	"[ <sp> umask ]" },
   1413 	{ NULL,    0,     0,	0, 0,	0 }
   1414 };
   1415 
   1416 static	void	 	help __P((struct tab *, char *));
   1417 static	struct tab     *lookup __P((struct tab *, const char *));
   1418 static	void		opts __P((const char *));
   1419 static	void		sizecmd __P((char *));
   1420 static	void		toolong __P((int));
   1421 static	int		yylex __P((void));
   1422 
   1423 extern int epsvall;
   1424 
   1425 static struct tab *
   1426 lookup(p, cmd)
   1427 	struct tab *p;
   1428 	const char *cmd;
   1429 {
   1430 
   1431 	for (; p->name != NULL; p++)
   1432 		if (strcasecmp(cmd, p->name) == 0)
   1433 			return (p);
   1434 	return (0);
   1435 }
   1436 
   1437 #include <arpa/telnet.h>
   1438 
   1439 /*
   1440  * getline - a hacked up version of fgets to ignore TELNET escape codes.
   1441  */
   1442 char *
   1443 getline(s, n, iop)
   1444 	char *s;
   1445 	int n;
   1446 	FILE *iop;
   1447 {
   1448 	off_t b;
   1449 	int c;
   1450 	char *cs;
   1451 
   1452 	cs = s;
   1453 /* tmpline may contain saved command from urgent mode interruption */
   1454 	for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
   1455 		*cs++ = tmpline[c];
   1456 		if (tmpline[c] == '\n') {
   1457 			*cs++ = '\0';
   1458 			if (debug)
   1459 				syslog(LOG_DEBUG, "command: %s", s);
   1460 			tmpline[0] = '\0';
   1461 			return(s);
   1462 		}
   1463 		if (c == 0)
   1464 			tmpline[0] = '\0';
   1465 	}
   1466 	while ((c = getc(iop)) != EOF) {
   1467 		total_bytes++;
   1468 		total_bytes_in++;
   1469 		c &= 0377;
   1470 		if (c == IAC) {
   1471 		    if ((c = getc(iop)) != EOF) {
   1472 			total_bytes++;
   1473 			total_bytes_in++;
   1474 			c &= 0377;
   1475 			switch (c) {
   1476 			case WILL:
   1477 			case WONT:
   1478 				c = getc(iop);
   1479 				total_bytes++;
   1480 				total_bytes_in++;
   1481 				b = printf("%c%c%c", IAC, DONT, 0377&c);
   1482 				total_bytes += b;
   1483 				total_bytes_out += b;
   1484 				(void) fflush(stdout);
   1485 				continue;
   1486 			case DO:
   1487 			case DONT:
   1488 				c = getc(iop);
   1489 				total_bytes++;
   1490 				total_bytes_in++;
   1491 				b = printf("%c%c%c", IAC, WONT, 0377&c);
   1492 				total_bytes += b;
   1493 				total_bytes_out += b;
   1494 				(void) fflush(stdout);
   1495 				continue;
   1496 			case IAC:
   1497 				break;
   1498 			default:
   1499 				continue;	/* ignore command */
   1500 			}
   1501 		    }
   1502 		}
   1503 		*cs++ = c;
   1504 		if (--n <= 0 || c == '\n')
   1505 			break;
   1506 	}
   1507 	if (c == EOF && cs == s)
   1508 		return (NULL);
   1509 	*cs++ = '\0';
   1510 	if (debug) {
   1511 		if (curclass.type != CLASS_GUEST &&
   1512 		    strncasecmp("pass ", s, 5) == 0) {
   1513 			/* Don't syslog passwords */
   1514 			syslog(LOG_DEBUG, "command: %.5s ???", s);
   1515 		} else {
   1516 			char *cp;
   1517 			int len;
   1518 
   1519 			/* Don't syslog trailing CR-LF */
   1520 			len = strlen(s);
   1521 			cp = s + len - 1;
   1522 			while (cp >= s && (*cp == '\n' || *cp == '\r')) {
   1523 				--cp;
   1524 				--len;
   1525 			}
   1526 			syslog(LOG_DEBUG, "command: %.*s", len, s);
   1527 		}
   1528 	}
   1529 	return (s);
   1530 }
   1531 
   1532 static void
   1533 toolong(signo)
   1534 	int signo;
   1535 {
   1536 
   1537 	reply(421,
   1538 	    "Timeout (%d seconds): closing control connection.",
   1539 	    curclass.timeout);
   1540 	if (logging)
   1541 		syslog(LOG_INFO, "User %s timed out after %d seconds",
   1542 		    (pw ? pw->pw_name : "unknown"), curclass.timeout);
   1543 	dologout(1);
   1544 }
   1545 
   1546 static int
   1547 yylex()
   1548 {
   1549 	static int cpos, state;
   1550 	char *cp, *cp2;
   1551 	struct tab *p;
   1552 	int n;
   1553 	char c;
   1554 
   1555 	switch (state) {
   1556 
   1557 	case CMD:
   1558 		hasyyerrored = 0;
   1559 		(void) signal(SIGALRM, toolong);
   1560 		(void) alarm(curclass.timeout);
   1561 		if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
   1562 			reply(221, "You could at least say goodbye.");
   1563 			dologout(0);
   1564 		}
   1565 		(void) alarm(0);
   1566 #ifdef HASSETPROCTITLE
   1567 		if (strncasecmp(cbuf, "PASS", 4) != 0)
   1568 			setproctitle("%s: %s", proctitle, cbuf);
   1569 #endif /* HASSETPROCTITLE */
   1570 		if ((cp = strchr(cbuf, '\r'))) {
   1571 			*cp++ = '\n';
   1572 			*cp = '\0';
   1573 		}
   1574 		if ((cp = strpbrk(cbuf, " \n")))
   1575 			cpos = cp - cbuf;
   1576 		if (cpos == 0)
   1577 			cpos = 4;
   1578 		c = cbuf[cpos];
   1579 		cbuf[cpos] = '\0';
   1580 		p = lookup(cmdtab, cbuf);
   1581 		cbuf[cpos] = c;
   1582 		if (p != NULL) {
   1583 			if (p->implemented == 0) {
   1584 				reply(502, "%s command not implemented.",
   1585 				    p->name);
   1586 				hasyyerrored = 1;
   1587 				break;
   1588 			}
   1589 			state = p->state;
   1590 			yylval.s = p->name;
   1591 			return (p->token);
   1592 		}
   1593 		break;
   1594 
   1595 	case SITECMD:
   1596 		if (cbuf[cpos] == ' ') {
   1597 			cpos++;
   1598 			return (SP);
   1599 		}
   1600 		cp = &cbuf[cpos];
   1601 		if ((cp2 = strpbrk(cp, " \n")))
   1602 			cpos = cp2 - cbuf;
   1603 		c = cbuf[cpos];
   1604 		cbuf[cpos] = '\0';
   1605 		p = lookup(sitetab, cp);
   1606 		cbuf[cpos] = c;
   1607 		if (p != NULL) {
   1608 			if (p->implemented == 0) {
   1609 				reply(502, "SITE %s command not implemented.",
   1610 				    p->name);
   1611 				hasyyerrored = 1;
   1612 				break;
   1613 			}
   1614 			state = p->state;
   1615 			yylval.s = p->name;
   1616 			return (p->token);
   1617 		}
   1618 		break;
   1619 
   1620 	case OSTR:
   1621 		if (cbuf[cpos] == '\n') {
   1622 			state = CMD;
   1623 			return (CRLF);
   1624 		}
   1625 		/* FALLTHROUGH */
   1626 
   1627 	case STR1:
   1628 	case ZSTR1:
   1629 	dostr1:
   1630 		if (cbuf[cpos] == ' ') {
   1631 			cpos++;
   1632 			state = state == OSTR ? STR2 : state+1;
   1633 			return (SP);
   1634 		}
   1635 		break;
   1636 
   1637 	case ZSTR2:
   1638 		if (cbuf[cpos] == '\n') {
   1639 			state = CMD;
   1640 			return (CRLF);
   1641 		}
   1642 		/* FALLTHROUGH */
   1643 
   1644 	case STR2:
   1645 		cp = &cbuf[cpos];
   1646 		n = strlen(cp);
   1647 		cpos += n - 1;
   1648 		/*
   1649 		 * Make sure the string is nonempty and \n terminated.
   1650 		 */
   1651 		if (n > 1 && cbuf[cpos] == '\n') {
   1652 			cbuf[cpos] = '\0';
   1653 			yylval.s = xstrdup(cp);
   1654 			cbuf[cpos] = '\n';
   1655 			state = ARGS;
   1656 			return (STRING);
   1657 		}
   1658 		break;
   1659 
   1660 	case NSTR:
   1661 		if (cbuf[cpos] == ' ') {
   1662 			cpos++;
   1663 			return (SP);
   1664 		}
   1665 		if (isdigit(cbuf[cpos])) {
   1666 			cp = &cbuf[cpos];
   1667 			while (isdigit(cbuf[++cpos]))
   1668 				;
   1669 			c = cbuf[cpos];
   1670 			cbuf[cpos] = '\0';
   1671 			yylval.i = atoi(cp);
   1672 			cbuf[cpos] = c;
   1673 			state = STR1;
   1674 			return (NUMBER);
   1675 		}
   1676 		state = STR1;
   1677 		goto dostr1;
   1678 
   1679 	case ARGS:
   1680 		if (isdigit(cbuf[cpos])) {
   1681 			cp = &cbuf[cpos];
   1682 			while (isdigit(cbuf[++cpos]))
   1683 				;
   1684 			c = cbuf[cpos];
   1685 			cbuf[cpos] = '\0';
   1686 			yylval.i = atoi(cp);
   1687 			cbuf[cpos] = c;
   1688 			return (NUMBER);
   1689 		}
   1690 		if (strncasecmp(&cbuf[cpos], "ALL", 3) == 0
   1691 		 && !isalnum(cbuf[cpos + 3])) {
   1692 			yylval.s = xstrdup("ALL");
   1693 			cpos += 3;
   1694 			return ALL;
   1695 		}
   1696 		switch (cbuf[cpos++]) {
   1697 
   1698 		case '\n':
   1699 			state = CMD;
   1700 			return (CRLF);
   1701 
   1702 		case ' ':
   1703 			return (SP);
   1704 
   1705 		case ',':
   1706 			return (COMMA);
   1707 
   1708 		case 'A':
   1709 		case 'a':
   1710 			return (A);
   1711 
   1712 		case 'B':
   1713 		case 'b':
   1714 			return (B);
   1715 
   1716 		case 'C':
   1717 		case 'c':
   1718 			return (C);
   1719 
   1720 		case 'E':
   1721 		case 'e':
   1722 			return (E);
   1723 
   1724 		case 'F':
   1725 		case 'f':
   1726 			return (F);
   1727 
   1728 		case 'I':
   1729 		case 'i':
   1730 			return (I);
   1731 
   1732 		case 'L':
   1733 		case 'l':
   1734 			return (L);
   1735 
   1736 		case 'N':
   1737 		case 'n':
   1738 			return (N);
   1739 
   1740 		case 'P':
   1741 		case 'p':
   1742 			return (P);
   1743 
   1744 		case 'R':
   1745 		case 'r':
   1746 			return (R);
   1747 
   1748 		case 'S':
   1749 		case 's':
   1750 			return (S);
   1751 
   1752 		case 'T':
   1753 		case 't':
   1754 			return (T);
   1755 
   1756 		}
   1757 		break;
   1758 
   1759 	case NOARGS:
   1760 		if (cbuf[cpos] == '\n') {
   1761 			state = CMD;
   1762 			return (CRLF);
   1763 		}
   1764 		c = cbuf[cpos];
   1765 		cbuf[cpos] = '\0';
   1766 		reply(501, "'%s' command does not take any arguments.", cbuf);
   1767 		hasyyerrored = 1;
   1768 		cbuf[cpos] = c;
   1769 		break;
   1770 
   1771 	default:
   1772 		fatal("Unknown state in scanner.");
   1773 	}
   1774 	yyerror(NULL);
   1775 	state = CMD;
   1776 	longjmp(errcatch, 0);
   1777 	/* NOTREACHED */
   1778 }
   1779 
   1780 /* ARGSUSED */
   1781 void
   1782 yyerror(s)
   1783 	char *s;
   1784 {
   1785 	char *cp;
   1786 
   1787 	if (hasyyerrored)
   1788 		return;
   1789 	if ((cp = strchr(cbuf,'\n')) != NULL)
   1790 		*cp = '\0';
   1791 	reply(500, "'%s': command not understood.", cbuf);
   1792 	hasyyerrored = 1;
   1793 }
   1794 
   1795 static void
   1796 help(ctab, s)
   1797 	struct tab *ctab;
   1798 	char *s;
   1799 {
   1800 	struct tab *c;
   1801 	int width, NCMDS;
   1802 	off_t b;
   1803 	char *type;
   1804 
   1805 	if (ctab == sitetab)
   1806 		type = "SITE ";
   1807 	else
   1808 		type = "";
   1809 	width = 0, NCMDS = 0;
   1810 	for (c = ctab; c->name != NULL; c++) {
   1811 		int len = strlen(c->name);
   1812 
   1813 		if (len > width)
   1814 			width = len;
   1815 		NCMDS++;
   1816 	}
   1817 	width = (width + 8) &~ 7;
   1818 	if (s == 0) {
   1819 		int i, j, w;
   1820 		int columns, lines;
   1821 
   1822 		lreply(214, "");
   1823 		lreply(0, "The following %scommands are recognized.", type);
   1824 		lreply(0, "(`-' = not implemented, `+' = supports options)");
   1825 		columns = 76 / width;
   1826 		if (columns == 0)
   1827 			columns = 1;
   1828 		lines = (NCMDS + columns - 1) / columns;
   1829 		for (i = 0; i < lines; i++) {
   1830 			b = printf("    ");
   1831 			total_bytes += b;
   1832 			total_bytes_out += b;
   1833 			for (j = 0; j < columns; j++) {
   1834 				c = ctab + j * lines + i;
   1835 				b = printf("%s", c->name);
   1836 				total_bytes += b;
   1837 				total_bytes_out += b;
   1838 				w = strlen(c->name);
   1839 				if (! c->implemented) {
   1840 					putchar('-');
   1841 					total_bytes++;
   1842 					total_bytes_out++;
   1843 					w++;
   1844 				}
   1845 				if (c->hasopts) {
   1846 					putchar('+');
   1847 					total_bytes++;
   1848 					total_bytes_out++;
   1849 					w++;
   1850 				}
   1851 				if (c + lines >= &ctab[NCMDS])
   1852 					break;
   1853 				while (w < width) {
   1854 					putchar(' ');
   1855 					total_bytes++;
   1856 					total_bytes_out++;
   1857 					w++;
   1858 				}
   1859 			}
   1860 			b = printf("\r\n");
   1861 			total_bytes += b;
   1862 			total_bytes_out += b;
   1863 		}
   1864 		(void) fflush(stdout);
   1865 		reply(214, "Direct comments to ftp-bugs@%s.", hostname);
   1866 		return;
   1867 	}
   1868 	c = lookup(ctab, s);
   1869 	if (c == (struct tab *)0) {
   1870 		reply(502, "Unknown command %s.", s);
   1871 		return;
   1872 	}
   1873 	if (c->implemented)
   1874 		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
   1875 	else
   1876 		reply(214, "%s%-*s\t%s; not implemented.", type, width,
   1877 		    c->name, c->help);
   1878 }
   1879 
   1880 static void
   1881 sizecmd(filename)
   1882 	char *filename;
   1883 {
   1884 	switch (type) {
   1885 	case TYPE_L:
   1886 	case TYPE_I: {
   1887 		struct stat stbuf;
   1888 		if (stat(filename, &stbuf) < 0 || !S_ISREG(stbuf.st_mode))
   1889 			reply(550, "%s: not a plain file.", filename);
   1890 		else
   1891 			reply(213, "%qu", (qufmt_t)stbuf.st_size);
   1892 		break; }
   1893 	case TYPE_A: {
   1894 		FILE *fin;
   1895 		int c;
   1896 		off_t count;
   1897 		struct stat stbuf;
   1898 		fin = fopen(filename, "r");
   1899 		if (fin == NULL) {
   1900 			perror_reply(550, filename);
   1901 			return;
   1902 		}
   1903 		if (fstat(fileno(fin), &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) {
   1904 			reply(550, "%s: not a plain file.", filename);
   1905 			(void) fclose(fin);
   1906 			return;
   1907 		}
   1908 
   1909 		count = 0;
   1910 		while((c=getc(fin)) != EOF) {
   1911 			if (c == '\n')	/* will get expanded to \r\n */
   1912 				count++;
   1913 			count++;
   1914 		}
   1915 		(void) fclose(fin);
   1916 
   1917 		reply(213, "%qd", (qdfmt_t)count);
   1918 		break; }
   1919 	default:
   1920 		reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
   1921 	}
   1922 }
   1923 
   1924 static void
   1925 opts(command)
   1926 	const char *command;
   1927 {
   1928 	struct tab *c;
   1929 	char *ep;
   1930 
   1931 	if ((ep = strchr(command, ' ')) != NULL)
   1932 		*ep++ = '\0';
   1933 	c = lookup(cmdtab, command);
   1934 	if (c == NULL) {
   1935 		reply(502, "Unknown command %s.", command);
   1936 		return;
   1937 	}
   1938 	if (c->implemented == 0) {
   1939 		reply(502, "%s command not implemented.", c->name);
   1940 		return;
   1941 	}
   1942 	if (c->hasopts == 0) {
   1943 		reply(501, "%s command does not support persistent options.",
   1944 		    c->name);
   1945 		return;
   1946 	}
   1947 
   1948 	if (ep != NULL && *ep != '\0') {
   1949 		if (c->options != NULL)
   1950 			free(c->options);
   1951 		c->options = xstrdup(ep);
   1952 	}
   1953 	if (c->options != NULL)
   1954 		reply(200, "Options for %s are '%s'.", c->name, c->options);
   1955 	else
   1956 		reply(200, "No options defined for %s.", c->name);
   1957 }
   1958