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