Home | History | Annotate | Line # | Download | only in ftpd
ftpcmd.y revision 1.57
      1 /*	$NetBSD: ftpcmd.y,v 1.57 2000/11/28 09:31:29 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.57 2000/11/28 09:31:29 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("pass ", s, 5) == 0) {
   1388 			/* Don't syslog passwords */
   1389 			syslog(LOG_DEBUG, "command: %.5s ???", s);
   1390 		} else {
   1391 			char *cp;
   1392 			int len;
   1393 
   1394 			/* Don't syslog trailing CR-LF */
   1395 			len = strlen(s);
   1396 			cp = s + len - 1;
   1397 			while (cp >= s && (*cp == '\n' || *cp == '\r')) {
   1398 				--cp;
   1399 				--len;
   1400 			}
   1401 			syslog(LOG_DEBUG, "command: %.*s", len, s);
   1402 		}
   1403 	}
   1404 	return (s);
   1405 }
   1406 
   1407 static void
   1408 toolong(int signo)
   1409 {
   1410 
   1411 	reply(421,
   1412 	    "Timeout (%d seconds): closing control connection.",
   1413 	    curclass.timeout);
   1414 	if (logging)
   1415 		syslog(LOG_INFO, "User %s timed out after %d seconds",
   1416 		    (pw ? pw->pw_name : "unknown"), curclass.timeout);
   1417 	dologout(1);
   1418 }
   1419 
   1420 static int
   1421 yylex(void)
   1422 {
   1423 	static int cpos, state;
   1424 	char *cp, *cp2;
   1425 	struct tab *p;
   1426 	int n;
   1427 	char c;
   1428 
   1429 	switch (state) {
   1430 
   1431 	case CMD:
   1432 		hasyyerrored = 0;
   1433 		(void) signal(SIGALRM, toolong);
   1434 		(void) alarm(curclass.timeout);
   1435 		if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
   1436 			reply(221, "You could at least say goodbye.");
   1437 			dologout(0);
   1438 		}
   1439 		(void) alarm(0);
   1440 		if ((cp = strchr(cbuf, '\r'))) {
   1441 			*cp = '\0';
   1442 #ifdef HASSETPROCTITLE
   1443 			if (strncasecmp(cbuf, "PASS", 4) != 0)
   1444 				setproctitle("%s: %s", proctitle, cbuf);
   1445 #endif /* HASSETPROCTITLE */
   1446 			*cp++ = '\n';
   1447 			*cp = '\0';
   1448 		}
   1449 		if ((cp = strpbrk(cbuf, " \n")))
   1450 			cpos = cp - cbuf;
   1451 		if (cpos == 0)
   1452 			cpos = 4;
   1453 		c = cbuf[cpos];
   1454 		cbuf[cpos] = '\0';
   1455 		p = lookup(cmdtab, cbuf);
   1456 		cbuf[cpos] = c;
   1457 		if (p != NULL) {
   1458 			if (! CMD_IMPLEMENTED(p)) {
   1459 				reply(502, "%s command not implemented.",
   1460 				    p->name);
   1461 				hasyyerrored = 1;
   1462 				break;
   1463 			}
   1464 			state = p->state;
   1465 			yylval.s = p->name;
   1466 			return (p->token);
   1467 		}
   1468 		break;
   1469 
   1470 	case SITECMD:
   1471 		if (cbuf[cpos] == ' ') {
   1472 			cpos++;
   1473 			return (SP);
   1474 		}
   1475 		cp = &cbuf[cpos];
   1476 		if ((cp2 = strpbrk(cp, " \n")))
   1477 			cpos = cp2 - cbuf;
   1478 		c = cbuf[cpos];
   1479 		cbuf[cpos] = '\0';
   1480 		p = lookup(sitetab, cp);
   1481 		cbuf[cpos] = c;
   1482 		if (p != NULL) {
   1483 			if (!CMD_IMPLEMENTED(p)) {
   1484 				reply(502, "SITE %s command not implemented.",
   1485 				    p->name);
   1486 				hasyyerrored = 1;
   1487 				break;
   1488 			}
   1489 			state = p->state;
   1490 			yylval.s = p->name;
   1491 			return (p->token);
   1492 		}
   1493 		break;
   1494 
   1495 	case OSTR:
   1496 		if (cbuf[cpos] == '\n') {
   1497 			state = CMD;
   1498 			return (CRLF);
   1499 		}
   1500 		/* FALLTHROUGH */
   1501 
   1502 	case STR1:
   1503 	case ZSTR1:
   1504 	dostr1:
   1505 		if (cbuf[cpos] == ' ') {
   1506 			cpos++;
   1507 			state = state == OSTR ? STR2 : state+1;
   1508 			return (SP);
   1509 		}
   1510 		break;
   1511 
   1512 	case ZSTR2:
   1513 		if (cbuf[cpos] == '\n') {
   1514 			state = CMD;
   1515 			return (CRLF);
   1516 		}
   1517 		/* FALLTHROUGH */
   1518 
   1519 	case STR2:
   1520 		cp = &cbuf[cpos];
   1521 		n = strlen(cp);
   1522 		cpos += n - 1;
   1523 		/*
   1524 		 * Make sure the string is nonempty and \n terminated.
   1525 		 */
   1526 		if (n > 1 && cbuf[cpos] == '\n') {
   1527 			cbuf[cpos] = '\0';
   1528 			yylval.s = xstrdup(cp);
   1529 			cbuf[cpos] = '\n';
   1530 			state = ARGS;
   1531 			return (STRING);
   1532 		}
   1533 		break;
   1534 
   1535 	case NSTR:
   1536 		if (cbuf[cpos] == ' ') {
   1537 			cpos++;
   1538 			return (SP);
   1539 		}
   1540 		if (isdigit(cbuf[cpos])) {
   1541 			cp = &cbuf[cpos];
   1542 			while (isdigit(cbuf[++cpos]))
   1543 				;
   1544 			c = cbuf[cpos];
   1545 			cbuf[cpos] = '\0';
   1546 			yylval.i = atoi(cp);
   1547 			cbuf[cpos] = c;
   1548 			state = STR1;
   1549 			return (NUMBER);
   1550 		}
   1551 		state = STR1;
   1552 		goto dostr1;
   1553 
   1554 	case ARGS:
   1555 		if (isdigit(cbuf[cpos])) {
   1556 			cp = &cbuf[cpos];
   1557 			while (isdigit(cbuf[++cpos]))
   1558 				;
   1559 			c = cbuf[cpos];
   1560 			cbuf[cpos] = '\0';
   1561 			yylval.i = atoi(cp);
   1562 			cbuf[cpos] = c;
   1563 			return (NUMBER);
   1564 		}
   1565 		if (strncasecmp(&cbuf[cpos], "ALL", 3) == 0
   1566 		 && !isalnum(cbuf[cpos + 3])) {
   1567 			yylval.s = xstrdup("ALL");
   1568 			cpos += 3;
   1569 			return ALL;
   1570 		}
   1571 		switch (cbuf[cpos++]) {
   1572 
   1573 		case '\n':
   1574 			state = CMD;
   1575 			return (CRLF);
   1576 
   1577 		case ' ':
   1578 			return (SP);
   1579 
   1580 		case ',':
   1581 			return (COMMA);
   1582 
   1583 		case 'A':
   1584 		case 'a':
   1585 			return (A);
   1586 
   1587 		case 'B':
   1588 		case 'b':
   1589 			return (B);
   1590 
   1591 		case 'C':
   1592 		case 'c':
   1593 			return (C);
   1594 
   1595 		case 'E':
   1596 		case 'e':
   1597 			return (E);
   1598 
   1599 		case 'F':
   1600 		case 'f':
   1601 			return (F);
   1602 
   1603 		case 'I':
   1604 		case 'i':
   1605 			return (I);
   1606 
   1607 		case 'L':
   1608 		case 'l':
   1609 			return (L);
   1610 
   1611 		case 'N':
   1612 		case 'n':
   1613 			return (N);
   1614 
   1615 		case 'P':
   1616 		case 'p':
   1617 			return (P);
   1618 
   1619 		case 'R':
   1620 		case 'r':
   1621 			return (R);
   1622 
   1623 		case 'S':
   1624 		case 's':
   1625 			return (S);
   1626 
   1627 		case 'T':
   1628 		case 't':
   1629 			return (T);
   1630 
   1631 		}
   1632 		break;
   1633 
   1634 	case NOARGS:
   1635 		if (cbuf[cpos] == '\n') {
   1636 			state = CMD;
   1637 			return (CRLF);
   1638 		}
   1639 		c = cbuf[cpos];
   1640 		cbuf[cpos] = '\0';
   1641 		reply(501, "'%s' command does not take any arguments.", cbuf);
   1642 		hasyyerrored = 1;
   1643 		cbuf[cpos] = c;
   1644 		break;
   1645 
   1646 	default:
   1647 		fatal("Unknown state in scanner.");
   1648 	}
   1649 	yyerror(NULL);
   1650 	state = CMD;
   1651 	longjmp(errcatch, 0);
   1652 	/* NOTREACHED */
   1653 }
   1654 
   1655 /* ARGSUSED */
   1656 void
   1657 yyerror(char *s)
   1658 {
   1659 	char *cp;
   1660 
   1661 	if (hasyyerrored)
   1662 		return;
   1663 	if ((cp = strchr(cbuf,'\n')) != NULL)
   1664 		*cp = '\0';
   1665 	reply(500, "'%s': command not understood.", cbuf);
   1666 	hasyyerrored = 1;
   1667 }
   1668 
   1669 static void
   1670 help(struct tab *ctab, const char *s)
   1671 {
   1672 	struct tab *c;
   1673 	int width, NCMDS;
   1674 	char *type;
   1675 
   1676 	if (ctab == sitetab)
   1677 		type = "SITE ";
   1678 	else
   1679 		type = "";
   1680 	width = 0, NCMDS = 0;
   1681 	for (c = ctab; c->name != NULL; c++) {
   1682 		int len = strlen(c->name);
   1683 
   1684 		if (len > width)
   1685 			width = len;
   1686 		NCMDS++;
   1687 	}
   1688 	width = (width + 8) &~ 7;
   1689 	if (s == 0) {
   1690 		int i, j, w;
   1691 		int columns, lines;
   1692 
   1693 		reply(-214, "%s", "");
   1694 		reply(0, "The following %scommands are recognized.", type);
   1695 		reply(0, "(`-' = not implemented, `+' = supports options)");
   1696 		columns = 76 / width;
   1697 		if (columns == 0)
   1698 			columns = 1;
   1699 		lines = (NCMDS + columns - 1) / columns;
   1700 		for (i = 0; i < lines; i++) {
   1701 			cprintf(stdout, "    ");
   1702 			for (j = 0; j < columns; j++) {
   1703 				c = ctab + j * lines + i;
   1704 				cprintf(stdout, "%s", c->name);
   1705 				w = strlen(c->name);
   1706 				if (! CMD_IMPLEMENTED(c)) {
   1707 					CPUTC('-', stdout);
   1708 					w++;
   1709 				}
   1710 				if (CMD_HAS_OPTIONS(c)) {
   1711 					CPUTC('+', stdout);
   1712 					w++;
   1713 				}
   1714 				if (c + lines >= &ctab[NCMDS])
   1715 					break;
   1716 				while (w < width) {
   1717 					CPUTC(' ', stdout);
   1718 					w++;
   1719 				}
   1720 			}
   1721 			cprintf(stdout, "\r\n");
   1722 		}
   1723 		(void) fflush(stdout);
   1724 		reply(214, "Direct comments to ftp-bugs@%s.", hostname);
   1725 		return;
   1726 	}
   1727 	c = lookup(ctab, s);
   1728 	if (c == (struct tab *)0) {
   1729 		reply(502, "Unknown command %s.", s);
   1730 		return;
   1731 	}
   1732 	if (CMD_IMPLEMENTED(c))
   1733 		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
   1734 	else
   1735 		reply(214, "%s%-*s\t%s; not implemented.", type, width,
   1736 		    c->name, c->help);
   1737 }
   1738 
   1739 /*
   1740  * Check that the structures used for a PORT, LPRT or EPRT command are
   1741  * valid (data_dest, his_addr), and if necessary, detect ftp bounce attacks.
   1742  * If family != -1 check that his_addr.su_family == family.
   1743  */
   1744 static void
   1745 port_check(const char *cmd, int family)
   1746 {
   1747 
   1748 	if (epsvall) {
   1749 		reply(501, "%s disallowed after EPSV ALL", cmd);
   1750 		return;
   1751 	}
   1752 
   1753 	if (family != -1 && his_addr.su_family != family) {
   1754  port_check_fail:
   1755 		reply(500, "Illegal %s command rejected", cmd);
   1756 		return;
   1757 	}
   1758 
   1759 	if (data_dest.su_family != his_addr.su_family)
   1760 		goto port_check_fail;
   1761 
   1762 			/* be paranoid, if told so */
   1763 	if (CURCLASS_FLAGS_ISSET(checkportcmd)) {
   1764 		if ((ntohs(data_dest.su_port) < IPPORT_RESERVED) ||
   1765 		    (data_dest.su_len != his_addr.su_len))
   1766 			goto port_check_fail;
   1767 		switch (data_dest.su_family) {
   1768 		case AF_INET:
   1769 			if (memcmp(&data_dest.su_addr, &his_addr.su_addr,
   1770 			    data_dest.su_len) != 0)
   1771 				goto port_check_fail;
   1772 			break;
   1773 #ifdef INET6
   1774 		case AF_INET6:
   1775 			if (memcmp(&data_dest.su_6addr, &his_addr.su_6addr,
   1776 			    sizeof(data_dest.su_6addr)) != 0)
   1777 				goto port_check_fail;
   1778 			if (data_dest.su_scope_id != his_addr.su_scope_id)
   1779 				goto port_check_fail;
   1780 			break;
   1781 #endif
   1782 		default:
   1783 			goto port_check_fail;
   1784 		}
   1785 	}
   1786 
   1787 	usedefault = 0;
   1788 	if (pdata >= 0) {
   1789 		(void) close(pdata);
   1790 		pdata = -1;
   1791 	}
   1792 	reply(200, "%s command successful.", cmd);
   1793 }
   1794