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