Home | History | Annotate | Line # | Download | only in ftpd
ftpcmd.y revision 1.47
      1 /*	$NetBSD: ftpcmd.y,v 1.47 2000/06/14 13:44:23 lukem Exp $	*/
      2 
      3 /*-
      4  * Copyright (c) 1997-2000 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.47 2000/06/14 13:44:23 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	MLST	MLSD
    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 
    956 			feat();
    957 		}
    958 
    959 	| OPTS SP STRING CRLF
    960 		{
    961 
    962 			opts($3);
    963 			free($3);
    964 		}
    965 
    966 
    967 				/* extensions from draft-ietf-ftpext-mlst-10 */
    968 
    969 		/*
    970 		 * Return size of file in a format suitable for
    971 		 * using with RESTART (we just count bytes).
    972 		 */
    973 	| SIZE check_login SP pathname CRLF
    974 		{
    975 			if ($2 && $4 != NULL)
    976 				sizecmd($4);
    977 			if ($4 != NULL)
    978 				free($4);
    979 		}
    980 
    981 		/*
    982 		 * Return modification time of file as an ISO 3307
    983 		 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
    984 		 * where xxx is the fractional second (of any precision,
    985 		 * not necessarily 3 digits)
    986 		 */
    987 	| MDTM check_login SP pathname CRLF
    988 		{
    989 			if ($2 && $4 != NULL) {
    990 				struct stat stbuf;
    991 				if (stat($4, &stbuf) < 0)
    992 					perror_reply(550, $4);
    993 				else if (!S_ISREG(stbuf.st_mode)) {
    994 					reply(550, "%s: not a plain file.", $4);
    995 				} else {
    996 					struct tm *t;
    997 
    998 					t = gmtime(&stbuf.st_mtime);
    999 					reply(213,
   1000 					    "%04d%02d%02d%02d%02d%02d",
   1001 					    TM_YEAR_BASE + t->tm_year,
   1002 					    t->tm_mon+1, t->tm_mday,
   1003 					    t->tm_hour, t->tm_min, t->tm_sec);
   1004 				}
   1005 			}
   1006 			if ($4 != NULL)
   1007 				free($4);
   1008 		}
   1009 
   1010 	| MLST check_login SP pathname CRLF
   1011 		{
   1012 			if ($2 && $4 != NULL)
   1013 				mlst($4);
   1014 			if ($4 != NULL)
   1015 				free($4);
   1016 		}
   1017 
   1018 	| MLST CRLF
   1019 		{
   1020 			mlst(NULL);
   1021 		}
   1022 
   1023 	| MLSD check_login SP pathname CRLF
   1024 		{
   1025 			if ($2 && $4 != NULL)
   1026 				mlsd($4);
   1027 			if ($4 != NULL)
   1028 				free($4);
   1029 		}
   1030 
   1031 	| MLSD CRLF
   1032 		{
   1033 			mlsd(NULL);
   1034 		}
   1035 
   1036 	| error CRLF
   1037 		{
   1038 			yyerrok;
   1039 		}
   1040 	;
   1041 
   1042 rcmd
   1043 	: REST check_login SP byte_size CRLF
   1044 		{
   1045 			if ($2) {
   1046 				fromname = NULL;
   1047 				restart_point = $4; /* XXX $3 is only "int" */
   1048 				reply(350, "Restarting at %qd. %s",
   1049 				    (qdfmt_t)restart_point,
   1050 			    "Send STORE or RETRIEVE to initiate transfer.");
   1051 			}
   1052 		}
   1053 
   1054 	| RNFR check_modify SP pathname CRLF
   1055 		{
   1056 			restart_point = (off_t) 0;
   1057 			if ($2 && $4) {
   1058 				fromname = renamefrom($4);
   1059 			}
   1060 			if ($4)
   1061 				free($4);
   1062 		}
   1063 	;
   1064 
   1065 username
   1066 	: STRING
   1067 	;
   1068 
   1069 password
   1070 	: /* empty */
   1071 		{
   1072 			$$ = (char *)calloc(1, sizeof(char));
   1073 		}
   1074 
   1075 	| STRING
   1076 	;
   1077 
   1078 byte_size
   1079 	: NUMBER
   1080 	;
   1081 
   1082 host_port
   1083 	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
   1084 		NUMBER COMMA NUMBER
   1085 		{
   1086 			char *a, *p;
   1087 
   1088 			data_dest.su_len = sizeof(struct sockaddr_in);
   1089 			data_dest.su_family = AF_INET;
   1090 			p = (char *)&data_dest.su_sin.sin_port;
   1091 			p[0] = $9; p[1] = $11;
   1092 			a = (char *)&data_dest.su_sin.sin_addr;
   1093 			a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7;
   1094 		}
   1095 	;
   1096 
   1097 host_long_port4
   1098 	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
   1099 		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
   1100 		NUMBER
   1101 		{
   1102 			char *a, *p;
   1103 
   1104 			data_dest.su_sin.sin_len =
   1105 				sizeof(struct sockaddr_in);
   1106 			data_dest.su_family = AF_INET;
   1107 			p = (char *)&data_dest.su_port;
   1108 			p[0] = $15; p[1] = $17;
   1109 			a = (char *)&data_dest.su_sin.sin_addr;
   1110 			a[0] =  $5;  a[1] =  $7;  a[2] =  $9;  a[3] = $11;
   1111 
   1112 			/* reject invalid LPRT command */
   1113 			if ($1 != 4 || $3 != 4 || $13 != 2)
   1114 				memset(&data_dest, 0, sizeof(data_dest));
   1115 		}
   1116 	;
   1117 
   1118 host_long_port6
   1119 	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
   1120 		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
   1121 		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
   1122 		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
   1123 		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
   1124 		NUMBER
   1125 		{
   1126 			char *a, *p;
   1127 
   1128 			data_dest.su_sin6.sin6_len =
   1129 				sizeof(struct sockaddr_in6);
   1130 			data_dest.su_family = AF_INET6;
   1131 			p = (char *)&data_dest.su_port;
   1132 			p[0] = $39; p[1] = $41;
   1133 			a = (char *)&data_dest.su_sin6.sin6_addr;
   1134 			 a[0] =  $5;  a[1] =  $7;  a[2] =  $9;  a[3] = $11;
   1135 			 a[4] = $13;  a[5] = $15;  a[6] = $17;  a[7] = $19;
   1136 			 a[8] = $21;  a[9] = $23; a[10] = $25; a[11] = $27;
   1137 			a[12] = $29; a[13] = $31; a[14] = $33; a[15] = $35;
   1138 			if (his_addr.su_family == AF_INET6) {
   1139 				/* XXX more sanity checks! */
   1140 				data_dest.su_sin6.sin6_scope_id =
   1141 					his_addr.su_sin6.sin6_scope_id;
   1142 			}
   1143 
   1144 			/* reject invalid LPRT command */
   1145 			if ($1 != 6 || $3 != 16 || $37 != 2)
   1146 				memset(&data_dest, 0, sizeof(data_dest));
   1147 		}
   1148 	;
   1149 
   1150 form_code
   1151 	: N
   1152 		{
   1153 			$$ = FORM_N;
   1154 		}
   1155 
   1156 	| T
   1157 		{
   1158 			$$ = FORM_T;
   1159 		}
   1160 
   1161 	| C
   1162 		{
   1163 			$$ = FORM_C;
   1164 		}
   1165 	;
   1166 
   1167 type_code
   1168 	: A
   1169 		{
   1170 			cmd_type = TYPE_A;
   1171 			cmd_form = FORM_N;
   1172 		}
   1173 
   1174 	| A SP form_code
   1175 		{
   1176 			cmd_type = TYPE_A;
   1177 			cmd_form = $3;
   1178 		}
   1179 
   1180 	| E
   1181 		{
   1182 			cmd_type = TYPE_E;
   1183 			cmd_form = FORM_N;
   1184 		}
   1185 
   1186 	| E SP form_code
   1187 		{
   1188 			cmd_type = TYPE_E;
   1189 			cmd_form = $3;
   1190 		}
   1191 
   1192 	| I
   1193 		{
   1194 			cmd_type = TYPE_I;
   1195 		}
   1196 
   1197 	| L
   1198 		{
   1199 			cmd_type = TYPE_L;
   1200 			cmd_bytesz = NBBY;
   1201 		}
   1202 
   1203 	| L SP byte_size
   1204 		{
   1205 			cmd_type = TYPE_L;
   1206 			cmd_bytesz = $3;
   1207 		}
   1208 
   1209 		/* this is for a bug in the BBN ftp */
   1210 	| L byte_size
   1211 		{
   1212 			cmd_type = TYPE_L;
   1213 			cmd_bytesz = $2;
   1214 		}
   1215 	;
   1216 
   1217 struct_code
   1218 	: F
   1219 		{
   1220 			$$ = STRU_F;
   1221 		}
   1222 
   1223 	| R
   1224 		{
   1225 			$$ = STRU_R;
   1226 		}
   1227 
   1228 	| P
   1229 		{
   1230 			$$ = STRU_P;
   1231 		}
   1232 	;
   1233 
   1234 mode_code
   1235 	: S
   1236 		{
   1237 			$$ = MODE_S;
   1238 		}
   1239 
   1240 	| B
   1241 		{
   1242 			$$ = MODE_B;
   1243 		}
   1244 
   1245 	| C
   1246 		{
   1247 			$$ = MODE_C;
   1248 		}
   1249 	;
   1250 
   1251 pathname
   1252 	: pathstring
   1253 		{
   1254 			/*
   1255 			 * Problem: this production is used for all pathname
   1256 			 * processing, but only gives a 550 error reply.
   1257 			 * This is a valid reply in some cases but not in
   1258 			 * others.
   1259 			 */
   1260 			if (logged_in && $1 && *$1 == '~') {
   1261 				glob_t gl;
   1262 				int flags =
   1263 				 GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE;
   1264 
   1265 				if ($1[1] == '\0')
   1266 					$$ = xstrdup(pw->pw_dir);
   1267 				else {
   1268 					memset(&gl, 0, sizeof(gl));
   1269 					if (glob($1, flags, NULL, &gl) ||
   1270 					    gl.gl_pathc == 0) {
   1271 						reply(550, "not found");
   1272 						$$ = NULL;
   1273 					} else
   1274 						$$ = xstrdup(gl.gl_pathv[0]);
   1275 					globfree(&gl);
   1276 				}
   1277 				free($1);
   1278 			} else
   1279 				$$ = $1;
   1280 		}
   1281 	;
   1282 
   1283 pathstring
   1284 	: STRING
   1285 	;
   1286 
   1287 octal_number
   1288 	: NUMBER
   1289 		{
   1290 			int ret, dec, multby, digit;
   1291 
   1292 			/*
   1293 			 * Convert a number that was read as decimal number
   1294 			 * to what it would be if it had been read as octal.
   1295 			 */
   1296 			dec = $1;
   1297 			multby = 1;
   1298 			ret = 0;
   1299 			while (dec) {
   1300 				digit = dec%10;
   1301 				if (digit > 7) {
   1302 					ret = -1;
   1303 					break;
   1304 				}
   1305 				ret += digit * multby;
   1306 				multby *= 8;
   1307 				dec /= 10;
   1308 			}
   1309 			$$ = ret;
   1310 		}
   1311 	;
   1312 
   1313 mechanism_name
   1314 	: STRING
   1315 	;
   1316 
   1317 base64data
   1318 	: STRING
   1319 	;
   1320 
   1321 prot_code
   1322 	: STRING
   1323 	;
   1324 
   1325 decimal_integer
   1326 	: NUMBER
   1327 	;
   1328 
   1329 check_login
   1330 	: /* empty */
   1331 		{
   1332 			if (logged_in)
   1333 				$$ = 1;
   1334 			else {
   1335 				reply(530, "Please login with USER and PASS.");
   1336 				$$ = 0;
   1337 				hasyyerrored = 1;
   1338 			}
   1339 		}
   1340 	;
   1341 
   1342 check_modify
   1343 	: /* empty */
   1344 		{
   1345 			if (logged_in) {
   1346 				if (curclass.modify)
   1347 					$$ = 1;
   1348 				else {
   1349 					reply(502,
   1350 					"No permission to use this command.");
   1351 					$$ = 0;
   1352 					hasyyerrored = 1;
   1353 				}
   1354 			} else {
   1355 				reply(530, "Please login with USER and PASS.");
   1356 				$$ = 0;
   1357 				hasyyerrored = 1;
   1358 			}
   1359 		}
   1360 
   1361 check_upload
   1362 	: /* empty */
   1363 		{
   1364 			if (logged_in) {
   1365 				if (curclass.upload)
   1366 					$$ = 1;
   1367 				else {
   1368 					reply(502,
   1369 					"No permission to use this command.");
   1370 					$$ = 0;
   1371 					hasyyerrored = 1;
   1372 				}
   1373 			} else {
   1374 				reply(530, "Please login with USER and PASS.");
   1375 				$$ = 0;
   1376 				hasyyerrored = 1;
   1377 			}
   1378 		}
   1379 
   1380 
   1381 %%
   1382 
   1383 #define	CMD	0	/* beginning of command */
   1384 #define	ARGS	1	/* expect miscellaneous arguments */
   1385 #define	STR1	2	/* expect SP followed by STRING */
   1386 #define	STR2	3	/* expect STRING */
   1387 #define	OSTR	4	/* optional SP then STRING */
   1388 #define	ZSTR1	5	/* SP then optional STRING */
   1389 #define	ZSTR2	6	/* optional STRING after SP */
   1390 #define	SITECMD	7	/* SITE command */
   1391 #define	NSTR	8	/* Number followed by a string */
   1392 #define NOARGS	9	/* No arguments allowed */
   1393 
   1394 struct tab cmdtab[] = {
   1395 				/* From RFC 959, in order defined (5.3.1) */
   1396 	{ "USER", USER, STR1,	1,	"<sp> username" },
   1397 	{ "PASS", PASS, ZSTR1,	1,	"<sp> password" },
   1398 	{ "ACCT", ACCT, STR1,	0,	"(specify account)" },
   1399 	{ "CWD",  CWD,  OSTR,	1,	"[ <sp> directory-name ]" },
   1400 	{ "CDUP", CDUP, NOARGS,	1,	"(change to parent directory)" },
   1401 	{ "SMNT", SMNT, ARGS,	0,	"(structure mount)" },
   1402 	{ "QUIT", QUIT, NOARGS,	1,	"(terminate service)" },
   1403 	{ "REIN", REIN, NOARGS,	0,	"(reinitialize server state)" },
   1404 	{ "PORT", PORT, ARGS,	1,	"<sp> b0, b1, b2, b3, b4" },
   1405 	{ "LPRT", LPRT, ARGS,	1,	"<sp> af, hal, h1, h2, h3,..., pal, p1, p2..." },
   1406 	{ "EPRT", EPRT, STR1,	1,	"<sp> |af|addr|port|" },
   1407 	{ "PASV", PASV, NOARGS,	1,	"(set server in passive mode)" },
   1408 	{ "LPSV", LPSV, ARGS,	1,	"(set server in passive mode)" },
   1409 	{ "EPSV", EPSV, ARGS,	1,	"[<sp> af|ALL]" },
   1410 	{ "TYPE", TYPE, ARGS,	1,	"<sp> [ A | E | I | L ]" },
   1411 	{ "STRU", STRU, ARGS,	1,	"(specify file structure)" },
   1412 	{ "MODE", MODE, ARGS,	1,	"(specify transfer mode)" },
   1413 	{ "RETR", RETR, STR1,	1,	"<sp> file-name" },
   1414 	{ "STOR", STOR, STR1,	1,	"<sp> file-name" },
   1415 	{ "STOU", STOU, STR1,	1,	"<sp> file-name" },
   1416 	{ "APPE", APPE, STR1,	1,	"<sp> file-name" },
   1417 	{ "ALLO", ALLO, ARGS,	1,	"allocate storage (vacuously)" },
   1418 	{ "REST", REST, ARGS,	1,	"<sp> offset (restart command)" },
   1419 	{ "RNFR", RNFR, STR1,	1,	"<sp> file-name" },
   1420 	{ "RNTO", RNTO, STR1,	1,	"<sp> file-name" },
   1421 	{ "ABOR", ABOR, NOARGS,	1,	"(abort operation)" },
   1422 	{ "DELE", DELE, STR1,	1,	"<sp> file-name" },
   1423 	{ "RMD",  RMD,  STR1,	1,	"<sp> path-name" },
   1424 	{ "MKD",  MKD,  STR1,	1,	"<sp> path-name" },
   1425 	{ "PWD",  PWD,  NOARGS,	1,	"(return current directory)" },
   1426 	{ "LIST", LIST, OSTR,	1,	"[ <sp> path-name ]" },
   1427 	{ "NLST", NLST, OSTR,	1,	"[ <sp> path-name ]" },
   1428 	{ "SITE", SITE, SITECMD, 1,	"site-cmd [ <sp> arguments ]" },
   1429 	{ "SYST", SYST, NOARGS,	1,	"(get type of operating system)" },
   1430 	{ "STAT", STAT, OSTR,	1,	"[ <sp> path-name ]" },
   1431 	{ "HELP", HELP, OSTR,	1,	"[ <sp> <string> ]" },
   1432 	{ "NOOP", NOOP, NOARGS,	2,	"" },
   1433 
   1434 				/* From RFC 2228, in order defined */
   1435 	{ "AUTH", AUTH, STR1,	1,	"<sp> mechanism-name" },
   1436 	{ "ADAT", ADAT, STR1,	1,	"<sp> base-64-data" },
   1437 	{ "PROT", PROT, STR1,	1,	"<sp> prot-code" },
   1438 	{ "PBSZ", PBSZ, ARGS,	1,	"<sp> decimal-integer" },
   1439 	{ "CCC",  CCC,  NOARGS,	1,	"(Disable data protection)" },
   1440 	{ "MIC",  MIC,  STR1,	1,	"<sp> base64data" },
   1441 	{ "CONF", CONF, STR1,	1,	"<sp> base64data" },
   1442 	{ "ENC",  ENC,  STR1,	1,	"<sp> base64data" },
   1443 
   1444 				/* From RFC 2389, in order defined */
   1445 	{ "FEAT", FEAT, NOARGS,	1,	"(display extended features)" },
   1446 	{ "OPTS", OPTS, STR1,	1,	"<sp> command [ <sp> options ]" },
   1447 
   1448 				/* from draft-ietf-ftpext-mlst-10 */
   1449 	{ "MDTM", MDTM, OSTR,	1,	"<sp> path-name" },
   1450 	{ "SIZE", SIZE, OSTR,	1,	"<sp> path-name" },
   1451 	{ "MLST", MLST, OSTR,	2,	"[ <sp> path-name ]" },
   1452 	{ "MLSD", MLSD, OSTR,	1,	"[ <sp> directory-name ]" },
   1453 
   1454 				/* obsolete commands */
   1455 	{ "MAIL", MAIL, OSTR,	0,	"(mail to user)" },
   1456 	{ "MLFL", MLFL, OSTR,	0,	"(mail file)" },
   1457 	{ "MRCP", MRCP, STR1,	0,	"(mail recipient)" },
   1458 	{ "MRSQ", MRSQ, OSTR,	0,	"(mail recipient scheme question)" },
   1459 	{ "MSAM", MSAM, OSTR,	0,	"(mail send to terminal and mailbox)" },
   1460 	{ "MSND", MSND, OSTR,	0,	"(mail send to terminal)" },
   1461 	{ "MSOM", MSOM, OSTR,	0,	"(mail send to terminal or mailbox)" },
   1462 	{ "XCUP", CDUP, NOARGS,	1,	"(change to parent directory)" },
   1463 	{ "XCWD", CWD,  OSTR,	1,	"[ <sp> directory-name ]" },
   1464 	{ "XMKD", MKD,  STR1,	1,	"<sp> path-name" },
   1465 	{ "XPWD", PWD,  NOARGS,	1,	"(return current directory)" },
   1466 	{ "XRMD", RMD,  STR1,	1,	"<sp> path-name" },
   1467 
   1468 	{  NULL,  0,	0,	0,	0 }
   1469 };
   1470 
   1471 struct tab sitetab[] = {
   1472 	{ "CHMOD",   	CHMOD,	NSTR, 1,	"<sp> mode <sp> file-name" },
   1473 	{ "HELP",    	HELP,	OSTR, 1,	"[ <sp> <string> ]" },
   1474 	{ "IDLE",    	IDLE,	ARGS, 1,	"[ <sp> maximum-idle-time ]" },
   1475 	{ "RATEGET", 	RATEGET,OSTR, 1,	"[ <sp> get-throttle-rate ]" },
   1476 	{ "RATEPUT", 	RATEPUT,OSTR, 1,	"[ <sp> put-throttle-rate ]" },
   1477 	{ "UMASK",   	UMASK,	ARGS, 1,	"[ <sp> umask ]" },
   1478 	{ NULL,		0,     0,     0,	NULL }
   1479 };
   1480 
   1481 static	void		help(struct tab *, const char *);
   1482 static	void		toolong(int);
   1483 static	int		yylex(void);
   1484 
   1485 extern int epsvall;
   1486 
   1487 struct tab *
   1488 lookup(struct tab *p, const char *cmd)
   1489 {
   1490 
   1491 	for (; p->name != NULL; p++)
   1492 		if (strcasecmp(cmd, p->name) == 0)
   1493 			return (p);
   1494 	return (0);
   1495 }
   1496 
   1497 #include <arpa/telnet.h>
   1498 
   1499 /*
   1500  * getline - a hacked up version of fgets to ignore TELNET escape codes.
   1501  */
   1502 char *
   1503 getline(char *s, int n, FILE *iop)
   1504 {
   1505 	off_t b;
   1506 	int c;
   1507 	char *cs;
   1508 
   1509 	cs = s;
   1510 /* tmpline may contain saved command from urgent mode interruption */
   1511 	for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
   1512 		*cs++ = tmpline[c];
   1513 		if (tmpline[c] == '\n') {
   1514 			*cs++ = '\0';
   1515 			if (debug)
   1516 				syslog(LOG_DEBUG, "command: %s", s);
   1517 			tmpline[0] = '\0';
   1518 			return(s);
   1519 		}
   1520 		if (c == 0)
   1521 			tmpline[0] = '\0';
   1522 	}
   1523 	while ((c = getc(iop)) != EOF) {
   1524 		total_bytes++;
   1525 		total_bytes_in++;
   1526 		c &= 0377;
   1527 		if (c == IAC) {
   1528 		    if ((c = getc(iop)) != EOF) {
   1529 			total_bytes++;
   1530 			total_bytes_in++;
   1531 			c &= 0377;
   1532 			switch (c) {
   1533 			case WILL:
   1534 			case WONT:
   1535 				c = getc(iop);
   1536 				total_bytes++;
   1537 				total_bytes_in++;
   1538 				b = printf("%c%c%c", IAC, DONT, 0377&c);
   1539 				total_bytes += b;
   1540 				total_bytes_out += b;
   1541 				(void) fflush(stdout);
   1542 				continue;
   1543 			case DO:
   1544 			case DONT:
   1545 				c = getc(iop);
   1546 				total_bytes++;
   1547 				total_bytes_in++;
   1548 				b = printf("%c%c%c", IAC, WONT, 0377&c);
   1549 				total_bytes += b;
   1550 				total_bytes_out += b;
   1551 				(void) fflush(stdout);
   1552 				continue;
   1553 			case IAC:
   1554 				break;
   1555 			default:
   1556 				continue;	/* ignore command */
   1557 			}
   1558 		    }
   1559 		}
   1560 		*cs++ = c;
   1561 		if (--n <= 0 || c == '\n')
   1562 			break;
   1563 	}
   1564 	if (c == EOF && cs == s)
   1565 		return (NULL);
   1566 	*cs++ = '\0';
   1567 	if (debug) {
   1568 		if (curclass.type != CLASS_GUEST &&
   1569 		    strncasecmp("pass ", s, 5) == 0) {
   1570 			/* Don't syslog passwords */
   1571 			syslog(LOG_DEBUG, "command: %.5s ???", s);
   1572 		} else {
   1573 			char *cp;
   1574 			int len;
   1575 
   1576 			/* Don't syslog trailing CR-LF */
   1577 			len = strlen(s);
   1578 			cp = s + len - 1;
   1579 			while (cp >= s && (*cp == '\n' || *cp == '\r')) {
   1580 				--cp;
   1581 				--len;
   1582 			}
   1583 			syslog(LOG_DEBUG, "command: %.*s", len, s);
   1584 		}
   1585 	}
   1586 	return (s);
   1587 }
   1588 
   1589 static void
   1590 toolong(int signo)
   1591 {
   1592 
   1593 	reply(421,
   1594 	    "Timeout (%d seconds): closing control connection.",
   1595 	    curclass.timeout);
   1596 	if (logging)
   1597 		syslog(LOG_INFO, "User %s timed out after %d seconds",
   1598 		    (pw ? pw->pw_name : "unknown"), curclass.timeout);
   1599 	dologout(1);
   1600 }
   1601 
   1602 static int
   1603 yylex(void)
   1604 {
   1605 	static int cpos, state;
   1606 	char *cp, *cp2;
   1607 	struct tab *p;
   1608 	int n;
   1609 	char c;
   1610 
   1611 	switch (state) {
   1612 
   1613 	case CMD:
   1614 		hasyyerrored = 0;
   1615 		(void) signal(SIGALRM, toolong);
   1616 		(void) alarm(curclass.timeout);
   1617 		if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
   1618 			reply(221, "You could at least say goodbye.");
   1619 			dologout(0);
   1620 		}
   1621 		(void) alarm(0);
   1622 		if ((cp = strchr(cbuf, '\r'))) {
   1623 			*cp = '\0';
   1624 #ifdef HASSETPROCTITLE
   1625 			if (strncasecmp(cbuf, "PASS", 4) != 0)
   1626 				setproctitle("%s: %s", proctitle, cbuf);
   1627 #endif /* HASSETPROCTITLE */
   1628 			*cp++ = '\n';
   1629 			*cp = '\0';
   1630 		}
   1631 		if ((cp = strpbrk(cbuf, " \n")))
   1632 			cpos = cp - cbuf;
   1633 		if (cpos == 0)
   1634 			cpos = 4;
   1635 		c = cbuf[cpos];
   1636 		cbuf[cpos] = '\0';
   1637 		p = lookup(cmdtab, cbuf);
   1638 		cbuf[cpos] = c;
   1639 		if (p != NULL) {
   1640 			if (! CMD_IMPLEMENTED(p)) {
   1641 				reply(502, "%s command not implemented.",
   1642 				    p->name);
   1643 				hasyyerrored = 1;
   1644 				break;
   1645 			}
   1646 			state = p->state;
   1647 			yylval.s = p->name;
   1648 			return (p->token);
   1649 		}
   1650 		break;
   1651 
   1652 	case SITECMD:
   1653 		if (cbuf[cpos] == ' ') {
   1654 			cpos++;
   1655 			return (SP);
   1656 		}
   1657 		cp = &cbuf[cpos];
   1658 		if ((cp2 = strpbrk(cp, " \n")))
   1659 			cpos = cp2 - cbuf;
   1660 		c = cbuf[cpos];
   1661 		cbuf[cpos] = '\0';
   1662 		p = lookup(sitetab, cp);
   1663 		cbuf[cpos] = c;
   1664 		if (p != NULL) {
   1665 			if (!CMD_IMPLEMENTED(p)) {
   1666 				reply(502, "SITE %s command not implemented.",
   1667 				    p->name);
   1668 				hasyyerrored = 1;
   1669 				break;
   1670 			}
   1671 			state = p->state;
   1672 			yylval.s = p->name;
   1673 			return (p->token);
   1674 		}
   1675 		break;
   1676 
   1677 	case OSTR:
   1678 		if (cbuf[cpos] == '\n') {
   1679 			state = CMD;
   1680 			return (CRLF);
   1681 		}
   1682 		/* FALLTHROUGH */
   1683 
   1684 	case STR1:
   1685 	case ZSTR1:
   1686 	dostr1:
   1687 		if (cbuf[cpos] == ' ') {
   1688 			cpos++;
   1689 			state = state == OSTR ? STR2 : state+1;
   1690 			return (SP);
   1691 		}
   1692 		break;
   1693 
   1694 	case ZSTR2:
   1695 		if (cbuf[cpos] == '\n') {
   1696 			state = CMD;
   1697 			return (CRLF);
   1698 		}
   1699 		/* FALLTHROUGH */
   1700 
   1701 	case STR2:
   1702 		cp = &cbuf[cpos];
   1703 		n = strlen(cp);
   1704 		cpos += n - 1;
   1705 		/*
   1706 		 * Make sure the string is nonempty and \n terminated.
   1707 		 */
   1708 		if (n > 1 && cbuf[cpos] == '\n') {
   1709 			cbuf[cpos] = '\0';
   1710 			yylval.s = xstrdup(cp);
   1711 			cbuf[cpos] = '\n';
   1712 			state = ARGS;
   1713 			return (STRING);
   1714 		}
   1715 		break;
   1716 
   1717 	case NSTR:
   1718 		if (cbuf[cpos] == ' ') {
   1719 			cpos++;
   1720 			return (SP);
   1721 		}
   1722 		if (isdigit(cbuf[cpos])) {
   1723 			cp = &cbuf[cpos];
   1724 			while (isdigit(cbuf[++cpos]))
   1725 				;
   1726 			c = cbuf[cpos];
   1727 			cbuf[cpos] = '\0';
   1728 			yylval.i = atoi(cp);
   1729 			cbuf[cpos] = c;
   1730 			state = STR1;
   1731 			return (NUMBER);
   1732 		}
   1733 		state = STR1;
   1734 		goto dostr1;
   1735 
   1736 	case ARGS:
   1737 		if (isdigit(cbuf[cpos])) {
   1738 			cp = &cbuf[cpos];
   1739 			while (isdigit(cbuf[++cpos]))
   1740 				;
   1741 			c = cbuf[cpos];
   1742 			cbuf[cpos] = '\0';
   1743 			yylval.i = atoi(cp);
   1744 			cbuf[cpos] = c;
   1745 			return (NUMBER);
   1746 		}
   1747 		if (strncasecmp(&cbuf[cpos], "ALL", 3) == 0
   1748 		 && !isalnum(cbuf[cpos + 3])) {
   1749 			yylval.s = xstrdup("ALL");
   1750 			cpos += 3;
   1751 			return ALL;
   1752 		}
   1753 		switch (cbuf[cpos++]) {
   1754 
   1755 		case '\n':
   1756 			state = CMD;
   1757 			return (CRLF);
   1758 
   1759 		case ' ':
   1760 			return (SP);
   1761 
   1762 		case ',':
   1763 			return (COMMA);
   1764 
   1765 		case 'A':
   1766 		case 'a':
   1767 			return (A);
   1768 
   1769 		case 'B':
   1770 		case 'b':
   1771 			return (B);
   1772 
   1773 		case 'C':
   1774 		case 'c':
   1775 			return (C);
   1776 
   1777 		case 'E':
   1778 		case 'e':
   1779 			return (E);
   1780 
   1781 		case 'F':
   1782 		case 'f':
   1783 			return (F);
   1784 
   1785 		case 'I':
   1786 		case 'i':
   1787 			return (I);
   1788 
   1789 		case 'L':
   1790 		case 'l':
   1791 			return (L);
   1792 
   1793 		case 'N':
   1794 		case 'n':
   1795 			return (N);
   1796 
   1797 		case 'P':
   1798 		case 'p':
   1799 			return (P);
   1800 
   1801 		case 'R':
   1802 		case 'r':
   1803 			return (R);
   1804 
   1805 		case 'S':
   1806 		case 's':
   1807 			return (S);
   1808 
   1809 		case 'T':
   1810 		case 't':
   1811 			return (T);
   1812 
   1813 		}
   1814 		break;
   1815 
   1816 	case NOARGS:
   1817 		if (cbuf[cpos] == '\n') {
   1818 			state = CMD;
   1819 			return (CRLF);
   1820 		}
   1821 		c = cbuf[cpos];
   1822 		cbuf[cpos] = '\0';
   1823 		reply(501, "'%s' command does not take any arguments.", cbuf);
   1824 		hasyyerrored = 1;
   1825 		cbuf[cpos] = c;
   1826 		break;
   1827 
   1828 	default:
   1829 		fatal("Unknown state in scanner.");
   1830 	}
   1831 	yyerror(NULL);
   1832 	state = CMD;
   1833 	longjmp(errcatch, 0);
   1834 	/* NOTREACHED */
   1835 }
   1836 
   1837 /* ARGSUSED */
   1838 void
   1839 yyerror(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(struct tab *ctab, const char *s)
   1853 {
   1854 	struct tab *c;
   1855 	int width, NCMDS;
   1856 	char *type;
   1857 
   1858 	if (ctab == sitetab)
   1859 		type = "SITE ";
   1860 	else
   1861 		type = "";
   1862 	width = 0, NCMDS = 0;
   1863 	for (c = ctab; c->name != NULL; c++) {
   1864 		int len = strlen(c->name);
   1865 
   1866 		if (len > width)
   1867 			width = len;
   1868 		NCMDS++;
   1869 	}
   1870 	width = (width + 8) &~ 7;
   1871 	if (s == 0) {
   1872 		int i, j, w;
   1873 		int columns, lines;
   1874 
   1875 		lreply(214, "");
   1876 		lreply(0, "The following %scommands are recognized.", type);
   1877 		lreply(0, "(`-' = not implemented, `+' = supports options)");
   1878 		columns = 76 / width;
   1879 		if (columns == 0)
   1880 			columns = 1;
   1881 		lines = (NCMDS + columns - 1) / columns;
   1882 		for (i = 0; i < lines; i++) {
   1883 			lreply(-2, "    ");
   1884 			for (j = 0; j < columns; j++) {
   1885 				c = ctab + j * lines + i;
   1886 				lreply(-2, "%s", c->name);
   1887 				w = strlen(c->name);
   1888 				if (! CMD_IMPLEMENTED(c)) {
   1889 					putchar('-');
   1890 					total_bytes++;
   1891 					total_bytes_out++;
   1892 					w++;
   1893 				}
   1894 				if (CMD_HAS_OPTIONS(c)) {
   1895 					putchar('+');
   1896 					total_bytes++;
   1897 					total_bytes_out++;
   1898 					w++;
   1899 				}
   1900 				if (c + lines >= &ctab[NCMDS])
   1901 					break;
   1902 				while (w < width) {
   1903 					putchar(' ');
   1904 					total_bytes++;
   1905 					total_bytes_out++;
   1906 					w++;
   1907 				}
   1908 			}
   1909 			lreply(-2, "\r\n");
   1910 		}
   1911 		(void) fflush(stdout);
   1912 		reply(214, "Direct comments to ftp-bugs@%s.", hostname);
   1913 		return;
   1914 	}
   1915 	c = lookup(ctab, s);
   1916 	if (c == (struct tab *)0) {
   1917 		reply(502, "Unknown command %s.", s);
   1918 		return;
   1919 	}
   1920 	if (CMD_IMPLEMENTED(c))
   1921 		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
   1922 	else
   1923 		reply(214, "%s%-*s\t%s; not implemented.", type, width,
   1924 		    c->name, c->help);
   1925 }
   1926