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