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