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