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