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