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