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