Home | History | Annotate | Line # | Download | only in ftpd
ftpcmd.y revision 1.51
      1 /*	$NetBSD: ftpcmd.y,v 1.51 2000/07/17 02:30:53 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.51 2000/07/17 02:30:53 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(homedir);
    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 pathname 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-11 */
    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 = GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE;
   1263 
   1264 				if ($1[1] == '\0')
   1265 					$$ = xstrdup(homedir);
   1266 				else {
   1267 					memset(&gl, 0, sizeof(gl));
   1268 					if (glob($1, flags, NULL, &gl) ||
   1269 					    gl.gl_pathc == 0) {
   1270 						reply(550, "not found");
   1271 						$$ = NULL;
   1272 					} else
   1273 						$$ = xstrdup(gl.gl_pathv[0]);
   1274 					globfree(&gl);
   1275 				}
   1276 				free($1);
   1277 			} else
   1278 				$$ = $1;
   1279 		}
   1280 	;
   1281 
   1282 pathstring
   1283 	: STRING
   1284 	;
   1285 
   1286 octal_number
   1287 	: NUMBER
   1288 		{
   1289 			int ret, dec, multby, digit;
   1290 
   1291 			/*
   1292 			 * Convert a number that was read as decimal number
   1293 			 * to what it would be if it had been read as octal.
   1294 			 */
   1295 			dec = $1;
   1296 			multby = 1;
   1297 			ret = 0;
   1298 			while (dec) {
   1299 				digit = dec%10;
   1300 				if (digit > 7) {
   1301 					ret = -1;
   1302 					break;
   1303 				}
   1304 				ret += digit * multby;
   1305 				multby *= 8;
   1306 				dec /= 10;
   1307 			}
   1308 			$$ = ret;
   1309 		}
   1310 	;
   1311 
   1312 mechanism_name
   1313 	: STRING
   1314 	;
   1315 
   1316 base64data
   1317 	: STRING
   1318 	;
   1319 
   1320 prot_code
   1321 	: STRING
   1322 	;
   1323 
   1324 decimal_integer
   1325 	: NUMBER
   1326 	;
   1327 
   1328 check_login
   1329 	: /* empty */
   1330 		{
   1331 			if (logged_in)
   1332 				$$ = 1;
   1333 			else {
   1334 				reply(530, "Please login with USER and PASS.");
   1335 				$$ = 0;
   1336 				hasyyerrored = 1;
   1337 			}
   1338 		}
   1339 	;
   1340 
   1341 check_modify
   1342 	: /* empty */
   1343 		{
   1344 			if (logged_in) {
   1345 				if (curclass.modify)
   1346 					$$ = 1;
   1347 				else {
   1348 					reply(502,
   1349 					"No permission to use this command.");
   1350 					$$ = 0;
   1351 					hasyyerrored = 1;
   1352 				}
   1353 			} else {
   1354 				reply(530, "Please login with USER and PASS.");
   1355 				$$ = 0;
   1356 				hasyyerrored = 1;
   1357 			}
   1358 		}
   1359 
   1360 check_upload
   1361 	: /* empty */
   1362 		{
   1363 			if (logged_in) {
   1364 				if (curclass.upload)
   1365 					$$ = 1;
   1366 				else {
   1367 					reply(502,
   1368 					"No permission to use this command.");
   1369 					$$ = 0;
   1370 					hasyyerrored = 1;
   1371 				}
   1372 			} else {
   1373 				reply(530, "Please login with USER and PASS.");
   1374 				$$ = 0;
   1375 				hasyyerrored = 1;
   1376 			}
   1377 		}
   1378 
   1379 
   1380 %%
   1381 
   1382 #define	CMD	0	/* beginning of command */
   1383 #define	ARGS	1	/* expect miscellaneous arguments */
   1384 #define	STR1	2	/* expect SP followed by STRING */
   1385 #define	STR2	3	/* expect STRING */
   1386 #define	OSTR	4	/* optional SP then STRING */
   1387 #define	ZSTR1	5	/* SP then optional STRING */
   1388 #define	ZSTR2	6	/* optional STRING after SP */
   1389 #define	SITECMD	7	/* SITE command */
   1390 #define	NSTR	8	/* Number followed by a string */
   1391 #define NOARGS	9	/* No arguments allowed */
   1392 
   1393 struct tab cmdtab[] = {
   1394 				/* From RFC 959, in order defined (5.3.1) */
   1395 	{ "USER", USER, STR1,	1,	"<sp> username" },
   1396 	{ "PASS", PASS, ZSTR1,	1,	"<sp> password" },
   1397 	{ "ACCT", ACCT, STR1,	0,	"(specify account)" },
   1398 	{ "CWD",  CWD,  OSTR,	1,	"[ <sp> directory-name ]" },
   1399 	{ "CDUP", CDUP, NOARGS,	1,	"(change to parent directory)" },
   1400 	{ "SMNT", SMNT, ARGS,	0,	"(structure mount)" },
   1401 	{ "QUIT", QUIT, NOARGS,	1,	"(terminate service)" },
   1402 	{ "REIN", REIN, NOARGS,	0,	"(reinitialize server state)" },
   1403 	{ "PORT", PORT, ARGS,	1,	"<sp> b0, b1, b2, b3, b4" },
   1404 	{ "LPRT", LPRT, ARGS,	1,	"<sp> af, hal, h1, h2, h3,..., pal, p1, p2..." },
   1405 	{ "EPRT", EPRT, STR1,	1,	"<sp> |af|addr|port|" },
   1406 	{ "PASV", PASV, NOARGS,	1,	"(set server in passive mode)" },
   1407 	{ "LPSV", LPSV, ARGS,	1,	"(set server in passive mode)" },
   1408 	{ "EPSV", EPSV, ARGS,	1,	"[<sp> af|ALL]" },
   1409 	{ "TYPE", TYPE, ARGS,	1,	"<sp> [ A | E | I | L ]" },
   1410 	{ "STRU", STRU, ARGS,	1,	"(specify file structure)" },
   1411 	{ "MODE", MODE, ARGS,	1,	"(specify transfer mode)" },
   1412 	{ "RETR", RETR, STR1,	1,	"<sp> file-name" },
   1413 	{ "STOR", STOR, STR1,	1,	"<sp> file-name" },
   1414 	{ "STOU", STOU, STR1,	1,	"<sp> file-name" },
   1415 	{ "APPE", APPE, STR1,	1,	"<sp> file-name" },
   1416 	{ "ALLO", ALLO, ARGS,	1,	"allocate storage (vacuously)" },
   1417 	{ "REST", REST, ARGS,	1,	"<sp> offset (restart command)" },
   1418 	{ "RNFR", RNFR, STR1,	1,	"<sp> file-name" },
   1419 	{ "RNTO", RNTO, STR1,	1,	"<sp> file-name" },
   1420 	{ "ABOR", ABOR, NOARGS,	1,	"(abort operation)" },
   1421 	{ "DELE", DELE, STR1,	1,	"<sp> file-name" },
   1422 	{ "RMD",  RMD,  STR1,	1,	"<sp> path-name" },
   1423 	{ "MKD",  MKD,  STR1,	1,	"<sp> path-name" },
   1424 	{ "PWD",  PWD,  NOARGS,	1,	"(return current directory)" },
   1425 	{ "LIST", LIST, OSTR,	1,	"[ <sp> path-name ]" },
   1426 	{ "NLST", NLST, OSTR,	1,	"[ <sp> path-name ]" },
   1427 	{ "SITE", SITE, SITECMD, 1,	"site-cmd [ <sp> arguments ]" },
   1428 	{ "SYST", SYST, NOARGS,	1,	"(get type of operating system)" },
   1429 	{ "STAT", STAT, OSTR,	1,	"[ <sp> path-name ]" },
   1430 	{ "HELP", HELP, OSTR,	1,	"[ <sp> <string> ]" },
   1431 	{ "NOOP", NOOP, NOARGS,	2,	"" },
   1432 
   1433 				/* From RFC 2228, in order defined */
   1434 	{ "AUTH", AUTH, STR1,	1,	"<sp> mechanism-name" },
   1435 	{ "ADAT", ADAT, STR1,	1,	"<sp> base-64-data" },
   1436 	{ "PROT", PROT, STR1,	1,	"<sp> prot-code" },
   1437 	{ "PBSZ", PBSZ, ARGS,	1,	"<sp> decimal-integer" },
   1438 	{ "CCC",  CCC,  NOARGS,	1,	"(Disable data protection)" },
   1439 	{ "MIC",  MIC,  STR1,	1,	"<sp> base64data" },
   1440 	{ "CONF", CONF, STR1,	1,	"<sp> base64data" },
   1441 	{ "ENC",  ENC,  STR1,	1,	"<sp> base64data" },
   1442 
   1443 				/* From RFC 2389, in order defined */
   1444 	{ "FEAT", FEAT, NOARGS,	1,	"(display extended features)" },
   1445 	{ "OPTS", OPTS, STR1,	1,	"<sp> command [ <sp> options ]" },
   1446 
   1447 				/* from draft-ietf-ftpext-mlst-11 */
   1448 	{ "MDTM", MDTM, OSTR,	1,	"<sp> path-name" },
   1449 	{ "SIZE", SIZE, OSTR,	1,	"<sp> path-name" },
   1450 	{ "MLST", MLST, OSTR,	2,	"[ <sp> path-name ]" },
   1451 	{ "MLSD", MLSD, OSTR,	1,	"[ <sp> directory-name ]" },
   1452 
   1453 				/* obsolete commands */
   1454 	{ "MAIL", MAIL, OSTR,	0,	"(mail to user)" },
   1455 	{ "MLFL", MLFL, OSTR,	0,	"(mail file)" },
   1456 	{ "MRCP", MRCP, STR1,	0,	"(mail recipient)" },
   1457 	{ "MRSQ", MRSQ, OSTR,	0,	"(mail recipient scheme question)" },
   1458 	{ "MSAM", MSAM, OSTR,	0,	"(mail send to terminal and mailbox)" },
   1459 	{ "MSND", MSND, OSTR,	0,	"(mail send to terminal)" },
   1460 	{ "MSOM", MSOM, OSTR,	0,	"(mail send to terminal or mailbox)" },
   1461 	{ "XCUP", CDUP, NOARGS,	1,	"(change to parent directory)" },
   1462 	{ "XCWD", CWD,  OSTR,	1,	"[ <sp> directory-name ]" },
   1463 	{ "XMKD", MKD,  STR1,	1,	"<sp> path-name" },
   1464 	{ "XPWD", PWD,  NOARGS,	1,	"(return current directory)" },
   1465 	{ "XRMD", RMD,  STR1,	1,	"<sp> path-name" },
   1466 
   1467 	{  NULL,  0,	0,	0,	0 }
   1468 };
   1469 
   1470 struct tab sitetab[] = {
   1471 	{ "CHMOD",   	CHMOD,	NSTR, 1,	"<sp> mode <sp> file-name" },
   1472 	{ "HELP",    	HELP,	OSTR, 1,	"[ <sp> <string> ]" },
   1473 	{ "IDLE",    	IDLE,	ARGS, 1,	"[ <sp> maximum-idle-time ]" },
   1474 	{ "RATEGET", 	RATEGET,OSTR, 1,	"[ <sp> get-throttle-rate ]" },
   1475 	{ "RATEPUT", 	RATEPUT,OSTR, 1,	"[ <sp> put-throttle-rate ]" },
   1476 	{ "UMASK",   	UMASK,	ARGS, 1,	"[ <sp> umask ]" },
   1477 	{ NULL,		0,     0,     0,	NULL }
   1478 };
   1479 
   1480 static	void		help(struct tab *, const char *);
   1481 static	void		toolong(int);
   1482 static	int		yylex(void);
   1483 
   1484 extern int epsvall;
   1485 
   1486 struct tab *
   1487 lookup(struct tab *p, const char *cmd)
   1488 {
   1489 
   1490 	for (; p->name != NULL; p++)
   1491 		if (strcasecmp(cmd, p->name) == 0)
   1492 			return (p);
   1493 	return (0);
   1494 }
   1495 
   1496 #include <arpa/telnet.h>
   1497 
   1498 /*
   1499  * getline - a hacked up version of fgets to ignore TELNET escape codes.
   1500  */
   1501 char *
   1502 getline(char *s, int n, FILE *iop)
   1503 {
   1504 	int c;
   1505 	char *cs;
   1506 
   1507 	cs = s;
   1508 /* tmpline may contain saved command from urgent mode interruption */
   1509 	for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
   1510 		*cs++ = tmpline[c];
   1511 		if (tmpline[c] == '\n') {
   1512 			*cs++ = '\0';
   1513 			if (debug)
   1514 				syslog(LOG_DEBUG, "command: %s", s);
   1515 			tmpline[0] = '\0';
   1516 			return(s);
   1517 		}
   1518 		if (c == 0)
   1519 			tmpline[0] = '\0';
   1520 	}
   1521 	while ((c = getc(iop)) != EOF) {
   1522 		total_bytes++;
   1523 		total_bytes_in++;
   1524 		c &= 0377;
   1525 		if (c == IAC) {
   1526 		    if ((c = getc(iop)) != EOF) {
   1527 			total_bytes++;
   1528 			total_bytes_in++;
   1529 			c &= 0377;
   1530 			switch (c) {
   1531 			case WILL:
   1532 			case WONT:
   1533 				c = getc(iop);
   1534 				total_bytes++;
   1535 				total_bytes_in++;
   1536 				cprintf(stdout, "%c%c%c", IAC, DONT, 0377&c);
   1537 				(void) fflush(stdout);
   1538 				continue;
   1539 			case DO:
   1540 			case DONT:
   1541 				c = getc(iop);
   1542 				total_bytes++;
   1543 				total_bytes_in++;
   1544 				cprintf(stdout, "%c%c%c", IAC, WONT, 0377&c);
   1545 				(void) fflush(stdout);
   1546 				continue;
   1547 			case IAC:
   1548 				break;
   1549 			default:
   1550 				continue;	/* ignore command */
   1551 			}
   1552 		    }
   1553 		}
   1554 		*cs++ = c;
   1555 		if (--n <= 0 || c == '\n')
   1556 			break;
   1557 	}
   1558 	if (c == EOF && cs == s)
   1559 		return (NULL);
   1560 	*cs++ = '\0';
   1561 	if (debug) {
   1562 		if (curclass.type != CLASS_GUEST &&
   1563 		    strncasecmp("pass ", s, 5) == 0) {
   1564 			/* Don't syslog passwords */
   1565 			syslog(LOG_DEBUG, "command: %.5s ???", s);
   1566 		} else {
   1567 			char *cp;
   1568 			int len;
   1569 
   1570 			/* Don't syslog trailing CR-LF */
   1571 			len = strlen(s);
   1572 			cp = s + len - 1;
   1573 			while (cp >= s && (*cp == '\n' || *cp == '\r')) {
   1574 				--cp;
   1575 				--len;
   1576 			}
   1577 			syslog(LOG_DEBUG, "command: %.*s", len, s);
   1578 		}
   1579 	}
   1580 	return (s);
   1581 }
   1582 
   1583 static void
   1584 toolong(int signo)
   1585 {
   1586 
   1587 	reply(421,
   1588 	    "Timeout (%d seconds): closing control connection.",
   1589 	    curclass.timeout);
   1590 	if (logging)
   1591 		syslog(LOG_INFO, "User %s timed out after %d seconds",
   1592 		    (pw ? pw->pw_name : "unknown"), curclass.timeout);
   1593 	dologout(1);
   1594 }
   1595 
   1596 static int
   1597 yylex(void)
   1598 {
   1599 	static int cpos, state;
   1600 	char *cp, *cp2;
   1601 	struct tab *p;
   1602 	int n;
   1603 	char c;
   1604 
   1605 	switch (state) {
   1606 
   1607 	case CMD:
   1608 		hasyyerrored = 0;
   1609 		(void) signal(SIGALRM, toolong);
   1610 		(void) alarm(curclass.timeout);
   1611 		if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
   1612 			reply(221, "You could at least say goodbye.");
   1613 			dologout(0);
   1614 		}
   1615 		(void) alarm(0);
   1616 		if ((cp = strchr(cbuf, '\r'))) {
   1617 			*cp = '\0';
   1618 #ifdef HASSETPROCTITLE
   1619 			if (strncasecmp(cbuf, "PASS", 4) != 0)
   1620 				setproctitle("%s: %s", proctitle, cbuf);
   1621 #endif /* HASSETPROCTITLE */
   1622 			*cp++ = '\n';
   1623 			*cp = '\0';
   1624 		}
   1625 		if ((cp = strpbrk(cbuf, " \n")))
   1626 			cpos = cp - cbuf;
   1627 		if (cpos == 0)
   1628 			cpos = 4;
   1629 		c = cbuf[cpos];
   1630 		cbuf[cpos] = '\0';
   1631 		p = lookup(cmdtab, cbuf);
   1632 		cbuf[cpos] = c;
   1633 		if (p != NULL) {
   1634 			if (! CMD_IMPLEMENTED(p)) {
   1635 				reply(502, "%s command not implemented.",
   1636 				    p->name);
   1637 				hasyyerrored = 1;
   1638 				break;
   1639 			}
   1640 			state = p->state;
   1641 			yylval.s = p->name;
   1642 			return (p->token);
   1643 		}
   1644 		break;
   1645 
   1646 	case SITECMD:
   1647 		if (cbuf[cpos] == ' ') {
   1648 			cpos++;
   1649 			return (SP);
   1650 		}
   1651 		cp = &cbuf[cpos];
   1652 		if ((cp2 = strpbrk(cp, " \n")))
   1653 			cpos = cp2 - cbuf;
   1654 		c = cbuf[cpos];
   1655 		cbuf[cpos] = '\0';
   1656 		p = lookup(sitetab, cp);
   1657 		cbuf[cpos] = c;
   1658 		if (p != NULL) {
   1659 			if (!CMD_IMPLEMENTED(p)) {
   1660 				reply(502, "SITE %s command not implemented.",
   1661 				    p->name);
   1662 				hasyyerrored = 1;
   1663 				break;
   1664 			}
   1665 			state = p->state;
   1666 			yylval.s = p->name;
   1667 			return (p->token);
   1668 		}
   1669 		break;
   1670 
   1671 	case OSTR:
   1672 		if (cbuf[cpos] == '\n') {
   1673 			state = CMD;
   1674 			return (CRLF);
   1675 		}
   1676 		/* FALLTHROUGH */
   1677 
   1678 	case STR1:
   1679 	case ZSTR1:
   1680 	dostr1:
   1681 		if (cbuf[cpos] == ' ') {
   1682 			cpos++;
   1683 			state = state == OSTR ? STR2 : state+1;
   1684 			return (SP);
   1685 		}
   1686 		break;
   1687 
   1688 	case ZSTR2:
   1689 		if (cbuf[cpos] == '\n') {
   1690 			state = CMD;
   1691 			return (CRLF);
   1692 		}
   1693 		/* FALLTHROUGH */
   1694 
   1695 	case STR2:
   1696 		cp = &cbuf[cpos];
   1697 		n = strlen(cp);
   1698 		cpos += n - 1;
   1699 		/*
   1700 		 * Make sure the string is nonempty and \n terminated.
   1701 		 */
   1702 		if (n > 1 && cbuf[cpos] == '\n') {
   1703 			cbuf[cpos] = '\0';
   1704 			yylval.s = xstrdup(cp);
   1705 			cbuf[cpos] = '\n';
   1706 			state = ARGS;
   1707 			return (STRING);
   1708 		}
   1709 		break;
   1710 
   1711 	case NSTR:
   1712 		if (cbuf[cpos] == ' ') {
   1713 			cpos++;
   1714 			return (SP);
   1715 		}
   1716 		if (isdigit(cbuf[cpos])) {
   1717 			cp = &cbuf[cpos];
   1718 			while (isdigit(cbuf[++cpos]))
   1719 				;
   1720 			c = cbuf[cpos];
   1721 			cbuf[cpos] = '\0';
   1722 			yylval.i = atoi(cp);
   1723 			cbuf[cpos] = c;
   1724 			state = STR1;
   1725 			return (NUMBER);
   1726 		}
   1727 		state = STR1;
   1728 		goto dostr1;
   1729 
   1730 	case ARGS:
   1731 		if (isdigit(cbuf[cpos])) {
   1732 			cp = &cbuf[cpos];
   1733 			while (isdigit(cbuf[++cpos]))
   1734 				;
   1735 			c = cbuf[cpos];
   1736 			cbuf[cpos] = '\0';
   1737 			yylval.i = atoi(cp);
   1738 			cbuf[cpos] = c;
   1739 			return (NUMBER);
   1740 		}
   1741 		if (strncasecmp(&cbuf[cpos], "ALL", 3) == 0
   1742 		 && !isalnum(cbuf[cpos + 3])) {
   1743 			yylval.s = xstrdup("ALL");
   1744 			cpos += 3;
   1745 			return ALL;
   1746 		}
   1747 		switch (cbuf[cpos++]) {
   1748 
   1749 		case '\n':
   1750 			state = CMD;
   1751 			return (CRLF);
   1752 
   1753 		case ' ':
   1754 			return (SP);
   1755 
   1756 		case ',':
   1757 			return (COMMA);
   1758 
   1759 		case 'A':
   1760 		case 'a':
   1761 			return (A);
   1762 
   1763 		case 'B':
   1764 		case 'b':
   1765 			return (B);
   1766 
   1767 		case 'C':
   1768 		case 'c':
   1769 			return (C);
   1770 
   1771 		case 'E':
   1772 		case 'e':
   1773 			return (E);
   1774 
   1775 		case 'F':
   1776 		case 'f':
   1777 			return (F);
   1778 
   1779 		case 'I':
   1780 		case 'i':
   1781 			return (I);
   1782 
   1783 		case 'L':
   1784 		case 'l':
   1785 			return (L);
   1786 
   1787 		case 'N':
   1788 		case 'n':
   1789 			return (N);
   1790 
   1791 		case 'P':
   1792 		case 'p':
   1793 			return (P);
   1794 
   1795 		case 'R':
   1796 		case 'r':
   1797 			return (R);
   1798 
   1799 		case 'S':
   1800 		case 's':
   1801 			return (S);
   1802 
   1803 		case 'T':
   1804 		case 't':
   1805 			return (T);
   1806 
   1807 		}
   1808 		break;
   1809 
   1810 	case NOARGS:
   1811 		if (cbuf[cpos] == '\n') {
   1812 			state = CMD;
   1813 			return (CRLF);
   1814 		}
   1815 		c = cbuf[cpos];
   1816 		cbuf[cpos] = '\0';
   1817 		reply(501, "'%s' command does not take any arguments.", cbuf);
   1818 		hasyyerrored = 1;
   1819 		cbuf[cpos] = c;
   1820 		break;
   1821 
   1822 	default:
   1823 		fatal("Unknown state in scanner.");
   1824 	}
   1825 	yyerror(NULL);
   1826 	state = CMD;
   1827 	longjmp(errcatch, 0);
   1828 	/* NOTREACHED */
   1829 }
   1830 
   1831 /* ARGSUSED */
   1832 void
   1833 yyerror(char *s)
   1834 {
   1835 	char *cp;
   1836 
   1837 	if (hasyyerrored)
   1838 		return;
   1839 	if ((cp = strchr(cbuf,'\n')) != NULL)
   1840 		*cp = '\0';
   1841 	reply(500, "'%s': command not understood.", cbuf);
   1842 	hasyyerrored = 1;
   1843 }
   1844 
   1845 static void
   1846 help(struct tab *ctab, const char *s)
   1847 {
   1848 	struct tab *c;
   1849 	int width, NCMDS;
   1850 	char *type;
   1851 
   1852 	if (ctab == sitetab)
   1853 		type = "SITE ";
   1854 	else
   1855 		type = "";
   1856 	width = 0, NCMDS = 0;
   1857 	for (c = ctab; c->name != NULL; c++) {
   1858 		int len = strlen(c->name);
   1859 
   1860 		if (len > width)
   1861 			width = len;
   1862 		NCMDS++;
   1863 	}
   1864 	width = (width + 8) &~ 7;
   1865 	if (s == 0) {
   1866 		int i, j, w;
   1867 		int columns, lines;
   1868 
   1869 		reply(-214, "%s", "");
   1870 		reply(0, "The following %scommands are recognized.", type);
   1871 		reply(0, "(`-' = not implemented, `+' = supports options)");
   1872 		columns = 76 / width;
   1873 		if (columns == 0)
   1874 			columns = 1;
   1875 		lines = (NCMDS + columns - 1) / columns;
   1876 		for (i = 0; i < lines; i++) {
   1877 			cprintf(stdout, "    ");
   1878 			for (j = 0; j < columns; j++) {
   1879 				c = ctab + j * lines + i;
   1880 				cprintf(stdout, "%s", c->name);
   1881 				w = strlen(c->name);
   1882 				if (! CMD_IMPLEMENTED(c)) {
   1883 					CPUTC('-', stdout);
   1884 					w++;
   1885 				}
   1886 				if (CMD_HAS_OPTIONS(c)) {
   1887 					CPUTC('+', stdout);
   1888 					w++;
   1889 				}
   1890 				if (c + lines >= &ctab[NCMDS])
   1891 					break;
   1892 				while (w < width) {
   1893 					CPUTC(' ', stdout);
   1894 					w++;
   1895 				}
   1896 			}
   1897 			cprintf(stdout, "\r\n");
   1898 		}
   1899 		(void) fflush(stdout);
   1900 		reply(214, "Direct comments to ftp-bugs@%s.", hostname);
   1901 		return;
   1902 	}
   1903 	c = lookup(ctab, s);
   1904 	if (c == (struct tab *)0) {
   1905 		reply(502, "Unknown command %s.", s);
   1906 		return;
   1907 	}
   1908 	if (CMD_IMPLEMENTED(c))
   1909 		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
   1910 	else
   1911 		reply(214, "%s%-*s\t%s; not implemented.", type, width,
   1912 		    c->name, c->help);
   1913 }
   1914