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