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