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