Home | History | Annotate | Line # | Download | only in ftpd
ftpcmd.y revision 1.68
      1 /*	$NetBSD: ftpcmd.y,v 1.68 2002/06/15 03:40:28 lukem Exp $	*/
      2 
      3 /*-
      4  * Copyright (c) 1997-2002 The NetBSD Foundation, Inc.
      5  * All rights reserved.
      6  *
      7  * This code is derived from software contributed to The NetBSD Foundation
      8  * by Luke Mewburn.
      9  *
     10  * Redistribution and use in source and binary forms, with or without
     11  * modification, are permitted provided that the following conditions
     12  * are met:
     13  * 1. Redistributions of source code must retain the above copyright
     14  *    notice, this list of conditions and the following disclaimer.
     15  * 2. Redistributions in binary form must reproduce the above copyright
     16  *    notice, this list of conditions and the following disclaimer in the
     17  *    documentation and/or other materials provided with the distribution.
     18  * 3. All advertising materials mentioning features or use of this software
     19  *    must display the following acknowledgement:
     20  *        This product includes software developed by the NetBSD
     21  *        Foundation, Inc. and its contributors.
     22  * 4. Neither the name of The NetBSD Foundation nor the names of its
     23  *    contributors may be used to endorse or promote products derived
     24  *    from this software without specific prior written permission.
     25  *
     26  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
     27  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     28  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     29  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
     30  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     31  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     32  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     33  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     34  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     35  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     36  * POSSIBILITY OF SUCH DAMAGE.
     37  */
     38 
     39 /*
     40  * Copyright (c) 1985, 1988, 1993, 1994
     41  *	The Regents of the University of California.  All rights reserved.
     42  *
     43  * Redistribution and use in source and binary forms, with or without
     44  * modification, are permitted provided that the following conditions
     45  * are met:
     46  * 1. Redistributions of source code must retain the above copyright
     47  *    notice, this list of conditions and the following disclaimer.
     48  * 2. Redistributions in binary form must reproduce the above copyright
     49  *    notice, this list of conditions and the following disclaimer in the
     50  *    documentation and/or other materials provided with the distribution.
     51  * 3. 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.68 2002/06/15 03:40:28 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 && check_write("", 0)) {
    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	*hpw;
   1094 
   1095 					if ((hpw = getpwnam($1 + 1)) != NULL)
   1096 						home = hpw->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  * If the filename is NULL, fail.
   1289  * If the filename is "", don't do the sane name check.
   1290  */
   1291 static int
   1292 check_write(const char *file, int isupload)
   1293 {
   1294 	if (file == NULL)
   1295 		return (0);
   1296 	if (! logged_in) {
   1297 		reply(530, "Please login with USER and PASS.");
   1298 		return (0);
   1299 	}
   1300 		/* checking modify */
   1301 	if (! isupload && ! CURCLASS_FLAGS_ISSET(modify)) {
   1302 		reply(502, "No permission to use this command.");
   1303 		return (0);
   1304 	}
   1305 		/* checking upload */
   1306 	if (isupload && ! CURCLASS_FLAGS_ISSET(upload)) {
   1307 		reply(502, "No permission to use this command.");
   1308 		return (0);
   1309 	}
   1310 
   1311 		/* checking sanenames */
   1312 	if (file[0] != '\0' && CURCLASS_FLAGS_ISSET(sanenames)) {
   1313 		const char *p;
   1314 
   1315 		if (file[0] == '.')
   1316 			goto insane_name;
   1317 		for (p = file; *p; p++) {
   1318 			if (isalnum(*p) || *p == '-' || *p == '+' ||
   1319 			    *p == ',' || *p == '.' || *p == '_')
   1320 				continue;
   1321  insane_name:
   1322 			reply(553, "File name `%s' not allowed.", file);
   1323 			return (0);
   1324 		}
   1325 	}
   1326 	return (1);
   1327 }
   1328 
   1329 struct tab *
   1330 lookup(struct tab *p, const char *cmd)
   1331 {
   1332 
   1333 	for (; p->name != NULL; p++)
   1334 		if (strcasecmp(cmd, p->name) == 0)
   1335 			return (p);
   1336 	return (0);
   1337 }
   1338 
   1339 #include <arpa/telnet.h>
   1340 
   1341 /*
   1342  * getline - a hacked up version of fgets to ignore TELNET escape codes.
   1343  */
   1344 char *
   1345 getline(char *s, int n, FILE *iop)
   1346 {
   1347 	int c;
   1348 	char *cs;
   1349 
   1350 	cs = s;
   1351 /* tmpline may contain saved command from urgent mode interruption */
   1352 	for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
   1353 		*cs++ = tmpline[c];
   1354 		if (tmpline[c] == '\n') {
   1355 			*cs++ = '\0';
   1356 			if (debug)
   1357 				syslog(LOG_DEBUG, "command: %s", s);
   1358 			tmpline[0] = '\0';
   1359 			return(s);
   1360 		}
   1361 		if (c == 0)
   1362 			tmpline[0] = '\0';
   1363 	}
   1364 	while ((c = getc(iop)) != EOF) {
   1365 		total_bytes++;
   1366 		total_bytes_in++;
   1367 		c &= 0377;
   1368 		if (c == IAC) {
   1369 		    if ((c = getc(iop)) != EOF) {
   1370 			total_bytes++;
   1371 			total_bytes_in++;
   1372 			c &= 0377;
   1373 			switch (c) {
   1374 			case WILL:
   1375 			case WONT:
   1376 				c = getc(iop);
   1377 				total_bytes++;
   1378 				total_bytes_in++;
   1379 				cprintf(stdout, "%c%c%c", IAC, DONT, 0377&c);
   1380 				(void) fflush(stdout);
   1381 				continue;
   1382 			case DO:
   1383 			case DONT:
   1384 				c = getc(iop);
   1385 				total_bytes++;
   1386 				total_bytes_in++;
   1387 				cprintf(stdout, "%c%c%c", IAC, WONT, 0377&c);
   1388 				(void) fflush(stdout);
   1389 				continue;
   1390 			case IAC:
   1391 				break;
   1392 			default:
   1393 				continue;	/* ignore command */
   1394 			}
   1395 		    }
   1396 		}
   1397 		*cs++ = c;
   1398 		if (--n <= 0 || c == '\n')
   1399 			break;
   1400 	}
   1401 	if (c == EOF && cs == s)
   1402 		return (NULL);
   1403 	*cs++ = '\0';
   1404 	if (debug) {
   1405 		if ((curclass.type != CLASS_GUEST &&
   1406 		    strncasecmp(s, "PASS ", 5) == 0) ||
   1407 		    strncasecmp(s, "ACCT ", 5) == 0) {
   1408 			/* Don't syslog passwords */
   1409 			syslog(LOG_DEBUG, "command: %.4s ???", s);
   1410 		} else {
   1411 			char *cp;
   1412 			int len;
   1413 
   1414 			/* Don't syslog trailing CR-LF */
   1415 			len = strlen(s);
   1416 			cp = s + len - 1;
   1417 			while (cp >= s && (*cp == '\n' || *cp == '\r')) {
   1418 				--cp;
   1419 				--len;
   1420 			}
   1421 			syslog(LOG_DEBUG, "command: %.*s", len, s);
   1422 		}
   1423 	}
   1424 	return (s);
   1425 }
   1426 
   1427 static void
   1428 toolong(int signo)
   1429 {
   1430 
   1431 	reply(421,
   1432 	    "Timeout (%d seconds): closing control connection.",
   1433 	    curclass.timeout);
   1434 	if (logging)
   1435 		syslog(LOG_INFO, "User %s timed out after %d seconds",
   1436 		    (pw ? pw->pw_name : "unknown"), curclass.timeout);
   1437 	dologout(1);
   1438 }
   1439 
   1440 void
   1441 ftp_handle_line(char *cp)
   1442 {
   1443 
   1444 	cmdp = cp;
   1445 	yyparse();
   1446 }
   1447 
   1448 void
   1449 ftp_loop(void)
   1450 {
   1451 
   1452 	while (1) {
   1453 		(void) signal(SIGALRM, toolong);
   1454 		(void) alarm(curclass.timeout);
   1455 		if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
   1456 			reply(221, "You could at least say goodbye.");
   1457 			dologout(0);
   1458 		}
   1459 		(void) alarm(0);
   1460 		ftp_handle_line(cbuf);
   1461 	}
   1462 	/*NOTREACHED*/
   1463 }
   1464 
   1465 static int
   1466 yylex(void)
   1467 {
   1468 	static int cpos, state;
   1469 	char *cp, *cp2;
   1470 	struct tab *p;
   1471 	int n;
   1472 	char c;
   1473 
   1474 	switch (state) {
   1475 
   1476 	case CMD:
   1477 		hasyyerrored = 0;
   1478 		if ((cp = strchr(cmdp, '\r'))) {
   1479 			*cp = '\0';
   1480 #if HAVE_SETPROCTITLE
   1481 			if (strncasecmp(cmdp, "PASS", 4) != 0 &&
   1482 			    strncasecmp(cmdp, "ACCT", 4) != 0)
   1483 				setproctitle("%s: %s", proctitle, cmdp);
   1484 #endif /* HAVE_SETPROCTITLE */
   1485 			*cp++ = '\n';
   1486 			*cp = '\0';
   1487 		}
   1488 		if ((cp = strpbrk(cmdp, " \n")))
   1489 			cpos = cp - cmdp;
   1490 		if (cpos == 0)
   1491 			cpos = 4;
   1492 		c = cmdp[cpos];
   1493 		cmdp[cpos] = '\0';
   1494 		p = lookup(cmdtab, cmdp);
   1495 		cmdp[cpos] = c;
   1496 		if (p != NULL) {
   1497 			if (is_oob && ! CMD_OOB(p)) {
   1498 				/* command will be handled in-band */
   1499 				return (0);
   1500 			} else if (! CMD_IMPLEMENTED(p)) {
   1501 				reply(502, "%s command not implemented.",
   1502 				    p->name);
   1503 				hasyyerrored = 1;
   1504 				break;
   1505 			}
   1506 			state = p->state;
   1507 			yylval.s = p->name;
   1508 			return (p->token);
   1509 		}
   1510 		break;
   1511 
   1512 	case SITECMD:
   1513 		if (cmdp[cpos] == ' ') {
   1514 			cpos++;
   1515 			return (SP);
   1516 		}
   1517 		cp = &cmdp[cpos];
   1518 		if ((cp2 = strpbrk(cp, " \n")))
   1519 			cpos = cp2 - cmdp;
   1520 		c = cmdp[cpos];
   1521 		cmdp[cpos] = '\0';
   1522 		p = lookup(sitetab, cp);
   1523 		cmdp[cpos] = c;
   1524 		if (p != NULL) {
   1525 			if (!CMD_IMPLEMENTED(p)) {
   1526 				reply(502, "SITE %s command not implemented.",
   1527 				    p->name);
   1528 				hasyyerrored = 1;
   1529 				break;
   1530 			}
   1531 			state = p->state;
   1532 			yylval.s = p->name;
   1533 			return (p->token);
   1534 		}
   1535 		break;
   1536 
   1537 	case OSTR:
   1538 		if (cmdp[cpos] == '\n') {
   1539 			state = EOLN;
   1540 			return (CRLF);
   1541 		}
   1542 		/* FALLTHROUGH */
   1543 
   1544 	case STR1:
   1545 	case ZSTR1:
   1546 	dostr1:
   1547 		if (cmdp[cpos] == ' ') {
   1548 			cpos++;
   1549 			state = state == OSTR ? STR2 : state+1;
   1550 			return (SP);
   1551 		}
   1552 		break;
   1553 
   1554 	case ZSTR2:
   1555 		if (cmdp[cpos] == '\n') {
   1556 			state = EOLN;
   1557 			return (CRLF);
   1558 		}
   1559 		/* FALLTHROUGH */
   1560 
   1561 	case STR2:
   1562 		cp = &cmdp[cpos];
   1563 		n = strlen(cp);
   1564 		cpos += n - 1;
   1565 		/*
   1566 		 * Make sure the string is nonempty and \n terminated.
   1567 		 */
   1568 		if (n > 1 && cmdp[cpos] == '\n') {
   1569 			cmdp[cpos] = '\0';
   1570 			yylval.s = xstrdup(cp);
   1571 			cmdp[cpos] = '\n';
   1572 			state = ARGS;
   1573 			return (STRING);
   1574 		}
   1575 		break;
   1576 
   1577 	case NSTR:
   1578 		if (cmdp[cpos] == ' ') {
   1579 			cpos++;
   1580 			return (SP);
   1581 		}
   1582 		if (isdigit(cmdp[cpos])) {
   1583 			cp = &cmdp[cpos];
   1584 			while (isdigit(cmdp[++cpos]))
   1585 				;
   1586 			c = cmdp[cpos];
   1587 			cmdp[cpos] = '\0';
   1588 			yylval.i = atoi(cp);
   1589 			cmdp[cpos] = c;
   1590 			state = STR1;
   1591 			return (NUMBER);
   1592 		}
   1593 		state = STR1;
   1594 		goto dostr1;
   1595 
   1596 	case ARGS:
   1597 		if (isdigit(cmdp[cpos])) {
   1598 			cp = &cmdp[cpos];
   1599 			while (isdigit(cmdp[++cpos]))
   1600 				;
   1601 			c = cmdp[cpos];
   1602 			cmdp[cpos] = '\0';
   1603 			yylval.i = atoi(cp);
   1604 			cmdp[cpos] = c;
   1605 			return (NUMBER);
   1606 		}
   1607 		if (strncasecmp(&cmdp[cpos], "ALL", 3) == 0
   1608 		 && !isalnum(cmdp[cpos + 3])) {
   1609 			yylval.s = xstrdup("ALL");
   1610 			cpos += 3;
   1611 			return ALL;
   1612 		}
   1613 		switch (cmdp[cpos++]) {
   1614 
   1615 		case '\n':
   1616 			state = EOLN;
   1617 			return (CRLF);
   1618 
   1619 		case ' ':
   1620 			return (SP);
   1621 
   1622 		case ',':
   1623 			return (COMMA);
   1624 
   1625 		case 'A':
   1626 		case 'a':
   1627 			return (A);
   1628 
   1629 		case 'B':
   1630 		case 'b':
   1631 			return (B);
   1632 
   1633 		case 'C':
   1634 		case 'c':
   1635 			return (C);
   1636 
   1637 		case 'E':
   1638 		case 'e':
   1639 			return (E);
   1640 
   1641 		case 'F':
   1642 		case 'f':
   1643 			return (F);
   1644 
   1645 		case 'I':
   1646 		case 'i':
   1647 			return (I);
   1648 
   1649 		case 'L':
   1650 		case 'l':
   1651 			return (L);
   1652 
   1653 		case 'N':
   1654 		case 'n':
   1655 			return (N);
   1656 
   1657 		case 'P':
   1658 		case 'p':
   1659 			return (P);
   1660 
   1661 		case 'R':
   1662 		case 'r':
   1663 			return (R);
   1664 
   1665 		case 'S':
   1666 		case 's':
   1667 			return (S);
   1668 
   1669 		case 'T':
   1670 		case 't':
   1671 			return (T);
   1672 
   1673 		}
   1674 		break;
   1675 
   1676 	case NOARGS:
   1677 		if (cmdp[cpos] == '\n') {
   1678 			state = EOLN;
   1679 			return (CRLF);
   1680 		}
   1681 		c = cmdp[cpos];
   1682 		cmdp[cpos] = '\0';
   1683 		reply(501, "'%s' command does not take any arguments.", cmdp);
   1684 		hasyyerrored = 1;
   1685 		cmdp[cpos] = c;
   1686 		break;
   1687 
   1688 	case EOLN:
   1689 		state = CMD;
   1690 		return (0);
   1691 
   1692 	default:
   1693 		fatal("Unknown state in scanner.");
   1694 	}
   1695 	yyerror(NULL);
   1696 	state = CMD;
   1697 	is_oob = 0;
   1698 	longjmp(errcatch, 0);
   1699 	/* NOTREACHED */
   1700 }
   1701 
   1702 /* ARGSUSED */
   1703 void
   1704 yyerror(char *s)
   1705 {
   1706 	char *cp;
   1707 
   1708 	if (hasyyerrored || is_oob)
   1709 		return;
   1710 	if ((cp = strchr(cmdp,'\n')) != NULL)
   1711 		*cp = '\0';
   1712 	reply(500, "'%s': command not understood.", cmdp);
   1713 	hasyyerrored = 1;
   1714 }
   1715 
   1716 static void
   1717 help(struct tab *ctab, const char *s)
   1718 {
   1719 	struct tab *c;
   1720 	int width, NCMDS;
   1721 	char *htype;
   1722 
   1723 	if (ctab == sitetab)
   1724 		htype = "SITE ";
   1725 	else
   1726 		htype = "";
   1727 	width = 0, NCMDS = 0;
   1728 	for (c = ctab; c->name != NULL; c++) {
   1729 		int len = strlen(c->name);
   1730 
   1731 		if (len > width)
   1732 			width = len;
   1733 		NCMDS++;
   1734 	}
   1735 	width = (width + 8) &~ 7;
   1736 	if (s == 0) {
   1737 		int i, j, w;
   1738 		int columns, lines;
   1739 
   1740 		reply(-214, "%s", "");
   1741 		reply(0, "The following %scommands are recognized.", htype);
   1742 		reply(0, "(`-' = not implemented, `+' = supports options)");
   1743 		columns = 76 / width;
   1744 		if (columns == 0)
   1745 			columns = 1;
   1746 		lines = (NCMDS + columns - 1) / columns;
   1747 		for (i = 0; i < lines; i++) {
   1748 			cprintf(stdout, "    ");
   1749 			for (j = 0; j < columns; j++) {
   1750 				c = ctab + j * lines + i;
   1751 				cprintf(stdout, "%s", c->name);
   1752 				w = strlen(c->name);
   1753 				if (! CMD_IMPLEMENTED(c)) {
   1754 					CPUTC('-', stdout);
   1755 					w++;
   1756 				}
   1757 				if (CMD_HAS_OPTIONS(c)) {
   1758 					CPUTC('+', stdout);
   1759 					w++;
   1760 				}
   1761 				if (c + lines >= &ctab[NCMDS])
   1762 					break;
   1763 				while (w < width) {
   1764 					CPUTC(' ', stdout);
   1765 					w++;
   1766 				}
   1767 			}
   1768 			cprintf(stdout, "\r\n");
   1769 		}
   1770 		(void) fflush(stdout);
   1771 		reply(214, "Direct comments to ftp-bugs@%s.", hostname);
   1772 		return;
   1773 	}
   1774 	c = lookup(ctab, s);
   1775 	if (c == (struct tab *)0) {
   1776 		reply(502, "Unknown command %s.", s);
   1777 		return;
   1778 	}
   1779 	if (CMD_IMPLEMENTED(c))
   1780 		reply(214, "Syntax: %s%s %s", htype, c->name, c->help);
   1781 	else
   1782 		reply(214, "%s%-*s\t%s; not implemented.", htype, width,
   1783 		    c->name, c->help);
   1784 }
   1785 
   1786 /*
   1787  * Check that the structures used for a PORT, LPRT or EPRT command are
   1788  * valid (data_dest, his_addr), and if necessary, detect ftp bounce attacks.
   1789  * If family != -1 check that his_addr.su_family == family.
   1790  */
   1791 static void
   1792 port_check(const char *cmd, int family)
   1793 {
   1794 	char h1[NI_MAXHOST], h2[NI_MAXHOST];
   1795 	char s1[NI_MAXHOST], s2[NI_MAXHOST];
   1796 #ifdef NI_WITHSCOPEID
   1797 	const int niflags = NI_NUMERICHOST | NI_NUMERICSERV | NI_WITHSCOPEID;
   1798 #else
   1799 	const int niflags = NI_NUMERICHOST | NI_NUMERICSERV;
   1800 #endif
   1801 
   1802 	if (epsvall) {
   1803 		reply(501, "%s disallowed after EPSV ALL", cmd);
   1804 		return;
   1805 	}
   1806 
   1807 	if (family != -1 && his_addr.su_family != family) {
   1808  port_check_fail:
   1809 		reply(500, "Illegal %s command rejected", cmd);
   1810 		return;
   1811 	}
   1812 
   1813 	if (data_dest.su_family != his_addr.su_family)
   1814 		goto port_check_fail;
   1815 
   1816 			/* be paranoid, if told so */
   1817 	if (CURCLASS_FLAGS_ISSET(checkportcmd)) {
   1818 #ifdef INET6
   1819 		/*
   1820 		 * be paranoid, there are getnameinfo implementation that does
   1821 		 * not present scopeid portion
   1822 		 */
   1823 		if (data_dest.su_family == AF_INET6 &&
   1824 		    data_dest.su_scope_id != his_addr.su_scope_id)
   1825 			goto port_check_fail;
   1826 #endif
   1827 
   1828 		if (getnameinfo((struct sockaddr *)&data_dest, data_dest.su_len,
   1829 		    h1, sizeof(h1), s1, sizeof(s1), niflags))
   1830 			goto port_check_fail;
   1831 		if (getnameinfo((struct sockaddr *)&his_addr, his_addr.su_len,
   1832 		    h2, sizeof(h2), s2, sizeof(s2), niflags))
   1833 			goto port_check_fail;
   1834 
   1835 		if (atoi(s1) < IPPORT_RESERVED || strcmp(h1, h2) != 0)
   1836 			goto port_check_fail;
   1837 	}
   1838 
   1839 	usedefault = 0;
   1840 	if (pdata >= 0) {
   1841 		(void) close(pdata);
   1842 		pdata = -1;
   1843 	}
   1844 	reply(200, "%s command successful.", cmd);
   1845 }
   1846