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