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