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