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