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