Home | History | Annotate | Line # | Download | only in ftpd
ftpcmd.y revision 1.49
      1 /*	$NetBSD: ftpcmd.y,v 1.49 2000/07/08 18:24:28 sommerfeld 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.49 2000/07/08 18:24:28 sommerfeld 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 				reply(-221, "%s", "");
    229 				reply(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 				reply(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 check_login 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 check_login 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 	int c;
   1506 	char *cs;
   1507 
   1508 	cs = s;
   1509 /* tmpline may contain saved command from urgent mode interruption */
   1510 	for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
   1511 		*cs++ = tmpline[c];
   1512 		if (tmpline[c] == '\n') {
   1513 			*cs++ = '\0';
   1514 			if (debug)
   1515 				syslog(LOG_DEBUG, "command: %s", s);
   1516 			tmpline[0] = '\0';
   1517 			return(s);
   1518 		}
   1519 		if (c == 0)
   1520 			tmpline[0] = '\0';
   1521 	}
   1522 	while ((c = getc(iop)) != EOF) {
   1523 		total_bytes++;
   1524 		total_bytes_in++;
   1525 		c &= 0377;
   1526 		if (c == IAC) {
   1527 		    if ((c = getc(iop)) != EOF) {
   1528 			total_bytes++;
   1529 			total_bytes_in++;
   1530 			c &= 0377;
   1531 			switch (c) {
   1532 			case WILL:
   1533 			case WONT:
   1534 				c = getc(iop);
   1535 				total_bytes++;
   1536 				total_bytes_in++;
   1537 				cprintf(stdout, "%c%c%c", IAC, DONT, 0377&c);
   1538 				(void) fflush(stdout);
   1539 				continue;
   1540 			case DO:
   1541 			case DONT:
   1542 				c = getc(iop);
   1543 				total_bytes++;
   1544 				total_bytes_in++;
   1545 				cprintf(stdout, "%c%c%c", IAC, WONT, 0377&c);
   1546 				(void) fflush(stdout);
   1547 				continue;
   1548 			case IAC:
   1549 				break;
   1550 			default:
   1551 				continue;	/* ignore command */
   1552 			}
   1553 		    }
   1554 		}
   1555 		*cs++ = c;
   1556 		if (--n <= 0 || c == '\n')
   1557 			break;
   1558 	}
   1559 	if (c == EOF && cs == s)
   1560 		return (NULL);
   1561 	*cs++ = '\0';
   1562 	if (debug) {
   1563 		if (curclass.type != CLASS_GUEST &&
   1564 		    strncasecmp("pass ", s, 5) == 0) {
   1565 			/* Don't syslog passwords */
   1566 			syslog(LOG_DEBUG, "command: %.5s ???", s);
   1567 		} else {
   1568 			char *cp;
   1569 			int len;
   1570 
   1571 			/* Don't syslog trailing CR-LF */
   1572 			len = strlen(s);
   1573 			cp = s + len - 1;
   1574 			while (cp >= s && (*cp == '\n' || *cp == '\r')) {
   1575 				--cp;
   1576 				--len;
   1577 			}
   1578 			syslog(LOG_DEBUG, "command: %.*s", len, s);
   1579 		}
   1580 	}
   1581 	return (s);
   1582 }
   1583 
   1584 static void
   1585 toolong(int signo)
   1586 {
   1587 
   1588 	reply(421,
   1589 	    "Timeout (%d seconds): closing control connection.",
   1590 	    curclass.timeout);
   1591 	if (logging)
   1592 		syslog(LOG_INFO, "User %s timed out after %d seconds",
   1593 		    (pw ? pw->pw_name : "unknown"), curclass.timeout);
   1594 	dologout(1);
   1595 }
   1596 
   1597 static int
   1598 yylex(void)
   1599 {
   1600 	static int cpos, state;
   1601 	char *cp, *cp2;
   1602 	struct tab *p;
   1603 	int n;
   1604 	char c;
   1605 
   1606 	switch (state) {
   1607 
   1608 	case CMD:
   1609 		hasyyerrored = 0;
   1610 		(void) signal(SIGALRM, toolong);
   1611 		(void) alarm(curclass.timeout);
   1612 		if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
   1613 			reply(221, "You could at least say goodbye.");
   1614 			dologout(0);
   1615 		}
   1616 		(void) alarm(0);
   1617 		if ((cp = strchr(cbuf, '\r'))) {
   1618 			*cp = '\0';
   1619 #ifdef HASSETPROCTITLE
   1620 			if (strncasecmp(cbuf, "PASS", 4) != 0)
   1621 				setproctitle("%s: %s", proctitle, cbuf);
   1622 #endif /* HASSETPROCTITLE */
   1623 			*cp++ = '\n';
   1624 			*cp = '\0';
   1625 		}
   1626 		if ((cp = strpbrk(cbuf, " \n")))
   1627 			cpos = cp - cbuf;
   1628 		if (cpos == 0)
   1629 			cpos = 4;
   1630 		c = cbuf[cpos];
   1631 		cbuf[cpos] = '\0';
   1632 		p = lookup(cmdtab, cbuf);
   1633 		cbuf[cpos] = c;
   1634 		if (p != NULL) {
   1635 			if (! CMD_IMPLEMENTED(p)) {
   1636 				reply(502, "%s command not implemented.",
   1637 				    p->name);
   1638 				hasyyerrored = 1;
   1639 				break;
   1640 			}
   1641 			state = p->state;
   1642 			yylval.s = p->name;
   1643 			return (p->token);
   1644 		}
   1645 		break;
   1646 
   1647 	case SITECMD:
   1648 		if (cbuf[cpos] == ' ') {
   1649 			cpos++;
   1650 			return (SP);
   1651 		}
   1652 		cp = &cbuf[cpos];
   1653 		if ((cp2 = strpbrk(cp, " \n")))
   1654 			cpos = cp2 - cbuf;
   1655 		c = cbuf[cpos];
   1656 		cbuf[cpos] = '\0';
   1657 		p = lookup(sitetab, cp);
   1658 		cbuf[cpos] = c;
   1659 		if (p != NULL) {
   1660 			if (!CMD_IMPLEMENTED(p)) {
   1661 				reply(502, "SITE %s command not implemented.",
   1662 				    p->name);
   1663 				hasyyerrored = 1;
   1664 				break;
   1665 			}
   1666 			state = p->state;
   1667 			yylval.s = p->name;
   1668 			return (p->token);
   1669 		}
   1670 		break;
   1671 
   1672 	case OSTR:
   1673 		if (cbuf[cpos] == '\n') {
   1674 			state = CMD;
   1675 			return (CRLF);
   1676 		}
   1677 		/* FALLTHROUGH */
   1678 
   1679 	case STR1:
   1680 	case ZSTR1:
   1681 	dostr1:
   1682 		if (cbuf[cpos] == ' ') {
   1683 			cpos++;
   1684 			state = state == OSTR ? STR2 : state+1;
   1685 			return (SP);
   1686 		}
   1687 		break;
   1688 
   1689 	case ZSTR2:
   1690 		if (cbuf[cpos] == '\n') {
   1691 			state = CMD;
   1692 			return (CRLF);
   1693 		}
   1694 		/* FALLTHROUGH */
   1695 
   1696 	case STR2:
   1697 		cp = &cbuf[cpos];
   1698 		n = strlen(cp);
   1699 		cpos += n - 1;
   1700 		/*
   1701 		 * Make sure the string is nonempty and \n terminated.
   1702 		 */
   1703 		if (n > 1 && cbuf[cpos] == '\n') {
   1704 			cbuf[cpos] = '\0';
   1705 			yylval.s = xstrdup(cp);
   1706 			cbuf[cpos] = '\n';
   1707 			state = ARGS;
   1708 			return (STRING);
   1709 		}
   1710 		break;
   1711 
   1712 	case NSTR:
   1713 		if (cbuf[cpos] == ' ') {
   1714 			cpos++;
   1715 			return (SP);
   1716 		}
   1717 		if (isdigit(cbuf[cpos])) {
   1718 			cp = &cbuf[cpos];
   1719 			while (isdigit(cbuf[++cpos]))
   1720 				;
   1721 			c = cbuf[cpos];
   1722 			cbuf[cpos] = '\0';
   1723 			yylval.i = atoi(cp);
   1724 			cbuf[cpos] = c;
   1725 			state = STR1;
   1726 			return (NUMBER);
   1727 		}
   1728 		state = STR1;
   1729 		goto dostr1;
   1730 
   1731 	case ARGS:
   1732 		if (isdigit(cbuf[cpos])) {
   1733 			cp = &cbuf[cpos];
   1734 			while (isdigit(cbuf[++cpos]))
   1735 				;
   1736 			c = cbuf[cpos];
   1737 			cbuf[cpos] = '\0';
   1738 			yylval.i = atoi(cp);
   1739 			cbuf[cpos] = c;
   1740 			return (NUMBER);
   1741 		}
   1742 		if (strncasecmp(&cbuf[cpos], "ALL", 3) == 0
   1743 		 && !isalnum(cbuf[cpos + 3])) {
   1744 			yylval.s = xstrdup("ALL");
   1745 			cpos += 3;
   1746 			return ALL;
   1747 		}
   1748 		switch (cbuf[cpos++]) {
   1749 
   1750 		case '\n':
   1751 			state = CMD;
   1752 			return (CRLF);
   1753 
   1754 		case ' ':
   1755 			return (SP);
   1756 
   1757 		case ',':
   1758 			return (COMMA);
   1759 
   1760 		case 'A':
   1761 		case 'a':
   1762 			return (A);
   1763 
   1764 		case 'B':
   1765 		case 'b':
   1766 			return (B);
   1767 
   1768 		case 'C':
   1769 		case 'c':
   1770 			return (C);
   1771 
   1772 		case 'E':
   1773 		case 'e':
   1774 			return (E);
   1775 
   1776 		case 'F':
   1777 		case 'f':
   1778 			return (F);
   1779 
   1780 		case 'I':
   1781 		case 'i':
   1782 			return (I);
   1783 
   1784 		case 'L':
   1785 		case 'l':
   1786 			return (L);
   1787 
   1788 		case 'N':
   1789 		case 'n':
   1790 			return (N);
   1791 
   1792 		case 'P':
   1793 		case 'p':
   1794 			return (P);
   1795 
   1796 		case 'R':
   1797 		case 'r':
   1798 			return (R);
   1799 
   1800 		case 'S':
   1801 		case 's':
   1802 			return (S);
   1803 
   1804 		case 'T':
   1805 		case 't':
   1806 			return (T);
   1807 
   1808 		}
   1809 		break;
   1810 
   1811 	case NOARGS:
   1812 		if (cbuf[cpos] == '\n') {
   1813 			state = CMD;
   1814 			return (CRLF);
   1815 		}
   1816 		c = cbuf[cpos];
   1817 		cbuf[cpos] = '\0';
   1818 		reply(501, "'%s' command does not take any arguments.", cbuf);
   1819 		hasyyerrored = 1;
   1820 		cbuf[cpos] = c;
   1821 		break;
   1822 
   1823 	default:
   1824 		fatal("Unknown state in scanner.");
   1825 	}
   1826 	yyerror(NULL);
   1827 	state = CMD;
   1828 	longjmp(errcatch, 0);
   1829 	/* NOTREACHED */
   1830 }
   1831 
   1832 /* ARGSUSED */
   1833 void
   1834 yyerror(char *s)
   1835 {
   1836 	char *cp;
   1837 
   1838 	if (hasyyerrored)
   1839 		return;
   1840 	if ((cp = strchr(cbuf,'\n')) != NULL)
   1841 		*cp = '\0';
   1842 	reply(500, "'%s': command not understood.", cbuf);
   1843 	hasyyerrored = 1;
   1844 }
   1845 
   1846 static void
   1847 help(struct tab *ctab, const char *s)
   1848 {
   1849 	struct tab *c;
   1850 	int width, NCMDS;
   1851 	char *type;
   1852 
   1853 	if (ctab == sitetab)
   1854 		type = "SITE ";
   1855 	else
   1856 		type = "";
   1857 	width = 0, NCMDS = 0;
   1858 	for (c = ctab; c->name != NULL; c++) {
   1859 		int len = strlen(c->name);
   1860 
   1861 		if (len > width)
   1862 			width = len;
   1863 		NCMDS++;
   1864 	}
   1865 	width = (width + 8) &~ 7;
   1866 	if (s == 0) {
   1867 		int i, j, w;
   1868 		int columns, lines;
   1869 
   1870 		reply(-214, "%s", "");
   1871 		reply(0, "The following %scommands are recognized.", type);
   1872 		reply(0, "(`-' = not implemented, `+' = supports options)");
   1873 		columns = 76 / width;
   1874 		if (columns == 0)
   1875 			columns = 1;
   1876 		lines = (NCMDS + columns - 1) / columns;
   1877 		for (i = 0; i < lines; i++) {
   1878 			cprintf(stdout, "    ");
   1879 			for (j = 0; j < columns; j++) {
   1880 				c = ctab + j * lines + i;
   1881 				cprintf(stdout, "%s", c->name);
   1882 				w = strlen(c->name);
   1883 				if (! CMD_IMPLEMENTED(c)) {
   1884 					CPUTC('-', stdout);
   1885 					w++;
   1886 				}
   1887 				if (CMD_HAS_OPTIONS(c)) {
   1888 					CPUTC('+', stdout);
   1889 					w++;
   1890 				}
   1891 				if (c + lines >= &ctab[NCMDS])
   1892 					break;
   1893 				while (w < width) {
   1894 					CPUTC(' ', stdout);
   1895 					w++;
   1896 				}
   1897 			}
   1898 			cprintf(stdout, "\r\n");
   1899 		}
   1900 		(void) fflush(stdout);
   1901 		reply(214, "Direct comments to ftp-bugs@%s.", hostname);
   1902 		return;
   1903 	}
   1904 	c = lookup(ctab, s);
   1905 	if (c == (struct tab *)0) {
   1906 		reply(502, "Unknown command %s.", s);
   1907 		return;
   1908 	}
   1909 	if (CMD_IMPLEMENTED(c))
   1910 		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
   1911 	else
   1912 		reply(214, "%s%-*s\t%s; not implemented.", type, width,
   1913 		    c->name, c->help);
   1914 }
   1915