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