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