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