Home | History | Annotate | Line # | Download | only in ftpd
ftpcmd.y revision 1.26
      1 /*	$NetBSD: ftpcmd.y,v 1.26 1999/02/24 16:45:13 explorer 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.26 1999/02/24 16:45:13 explorer 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 
     76 #ifdef KERBEROS5
     77 #include <krb5.h>
     78 #endif
     79 
     80 #include "extern.h"
     81 
     82 off_t	restart_point;
     83 
     84 static	int cmd_type;
     85 static	int cmd_form;
     86 static	int cmd_bytesz;
     87 char	cbuf[512];
     88 char	*fromname;
     89 int	hasyyerrored;
     90 
     91 extern	jmp_buf		errcatch;
     92 
     93 %}
     94 
     95 %union {
     96 	int	i;
     97 	char   *s;
     98 }
     99 
    100 %token
    101 	A	B	C	E	F	I
    102 	L	N	P	R	S	T
    103 
    104 	SP	CRLF	COMMA
    105 
    106 	USER	PASS	ACCT	CWD	CDUP	SMNT
    107 	QUIT	REIN	PORT	PASV	TYPE	STRU
    108 	MODE	RETR	STOR	STOU	APPE	ALLO
    109 	REST	RNFR	RNTO	ABOR	DELE	RMD
    110 	MKD	PWD	LIST	NLST	SITE	SYST
    111 	STAT	HELP	NOOP
    112 
    113 	AUTH	ADAT	PROT	PBSZ	CCC	MIC
    114 	CONF	ENC
    115 
    116 	FEAT	OPTS
    117 
    118 	SIZE	MDTM
    119 
    120 	MAIL	MLFL	MRCP	MRSQ	MSAM	MSND
    121 	MSOM
    122 
    123 	UMASK	IDLE	CHMOD
    124 
    125 	LEXERR
    126 
    127 %token	<s> STRING
    128 %token	<i> NUMBER
    129 
    130 %type	<i> check_login check_modify octal_number byte_size
    131 %type	<i> struct_code mode_code type_code form_code decimal_integer
    132 %type	<s> pathstring pathname password username
    133 %type	<s> mechanism_name base64data prot_code
    134 
    135 %start	cmd_list
    136 
    137 %%
    138 
    139 cmd_list
    140 	: /* empty */
    141 
    142 	| cmd_list cmd
    143 		{
    144 			fromname = NULL;
    145 			restart_point = (off_t) 0;
    146 		}
    147 
    148 	| cmd_list rcmd
    149 
    150 	;
    151 
    152 cmd
    153 						/* RFC 959 */
    154 	: USER SP username CRLF
    155 		{
    156 			user($3);
    157 			free($3);
    158 		}
    159 
    160 	| PASS SP password CRLF
    161 		{
    162 			pass($3);
    163 			free($3);
    164 		}
    165 
    166 	| CWD check_login CRLF
    167 		{
    168 			if ($2)
    169 				cwd(pw->pw_dir);
    170 		}
    171 
    172 	| CWD check_login SP pathname CRLF
    173 		{
    174 			if ($2 && $4 != NULL)
    175 				cwd($4);
    176 			if ($4 != NULL)
    177 				free($4);
    178 		}
    179 
    180 	| CDUP check_login CRLF
    181 		{
    182 			if ($2)
    183 				cwd("..");
    184 		}
    185 
    186 	| QUIT CRLF
    187 		{
    188 			reply(221, "Goodbye.");
    189 			dologout(0);
    190 		}
    191 
    192 	| PORT check_login SP host_port CRLF
    193 		{
    194 			if ($2) {
    195 					/* be paranoid, if told so */
    196 			if (curclass.checkportcmd &&
    197 			    ((ntohs(data_dest.sin_port) < IPPORT_RESERVED) ||
    198 			    memcmp(&data_dest.sin_addr, &his_addr.sin_addr,
    199 			    sizeof(data_dest.sin_addr)) != 0)) {
    200 				reply(500,
    201 				    "Illegal PORT command rejected");
    202 			} else {
    203 				usedefault = 0;
    204 				if (pdata >= 0) {
    205 					(void) close(pdata);
    206 					pdata = -1;
    207 				}
    208 				reply(200, "PORT command successful.");
    209 			}
    210 			}
    211 		}
    212 
    213 	| PASV check_login CRLF
    214 		{
    215 			if (curclass.passive) {
    216 				passive();
    217 			} else {
    218 				reply(500, "PASV mode not available.");
    219 			}
    220 		}
    221 
    222 	| TYPE SP type_code CRLF
    223 		{
    224 			switch (cmd_type) {
    225 
    226 			case TYPE_A:
    227 				if (cmd_form == FORM_N) {
    228 					reply(200, "Type set to A.");
    229 					type = cmd_type;
    230 					form = cmd_form;
    231 				} else
    232 					reply(504, "Form must be N.");
    233 				break;
    234 
    235 			case TYPE_E:
    236 				reply(504, "Type E not implemented.");
    237 				break;
    238 
    239 			case TYPE_I:
    240 				reply(200, "Type set to I.");
    241 				type = cmd_type;
    242 				break;
    243 
    244 			case TYPE_L:
    245 #if NBBY == 8
    246 				if (cmd_bytesz == 8) {
    247 					reply(200,
    248 					    "Type set to L (byte size 8).");
    249 					type = cmd_type;
    250 				} else
    251 					reply(504, "Byte size must be 8.");
    252 #else /* NBBY == 8 */
    253 				UNIMPLEMENTED for NBBY != 8
    254 #endif /* NBBY == 8 */
    255 			}
    256 		}
    257 
    258 	| STRU SP struct_code CRLF
    259 		{
    260 			switch ($3) {
    261 
    262 			case STRU_F:
    263 				reply(200, "STRU F ok.");
    264 				break;
    265 
    266 			default:
    267 				reply(504, "Unimplemented STRU type.");
    268 			}
    269 		}
    270 
    271 	| MODE SP mode_code CRLF
    272 		{
    273 			switch ($3) {
    274 
    275 			case MODE_S:
    276 				reply(200, "MODE S ok.");
    277 				break;
    278 
    279 			default:
    280 				reply(502, "Unimplemented MODE type.");
    281 			}
    282 		}
    283 
    284 	| RETR check_login SP pathname CRLF
    285 		{
    286 			if ($2 && $4 != NULL)
    287 				retrieve(NULL, $4);
    288 			if ($4 != NULL)
    289 				free($4);
    290 		}
    291 
    292 	| STOR check_login SP pathname CRLF
    293 		{
    294 			if ($2 && $4 != NULL)
    295 				store($4, "w", 0);
    296 			if ($4 != NULL)
    297 				free($4);
    298 		}
    299 
    300 	| STOU check_login SP pathname CRLF
    301 		{
    302 			if ($2 && $4 != NULL)
    303 				store($4, "w", 1);
    304 			if ($4 != NULL)
    305 				free($4);
    306 		}
    307 
    308 	| APPE check_login SP pathname CRLF
    309 		{
    310 			if ($2 && $4 != NULL)
    311 				store($4, "a", 0);
    312 			if ($4 != NULL)
    313 				free($4);
    314 		}
    315 
    316 	| ALLO SP NUMBER CRLF
    317 		{
    318 			reply(202, "ALLO command ignored.");
    319 		}
    320 
    321 	| ALLO SP NUMBER SP R SP NUMBER CRLF
    322 		{
    323 			reply(202, "ALLO command ignored.");
    324 		}
    325 
    326 	| RNTO SP pathname CRLF
    327 		{
    328 			if (fromname) {
    329 				renamecmd(fromname, $3);
    330 				free(fromname);
    331 				fromname = NULL;
    332 			} else {
    333 				reply(503, "Bad sequence of commands.");
    334 			}
    335 			free($3);
    336 		}
    337 
    338 	| ABOR CRLF
    339 		{
    340 			reply(225, "ABOR command successful.");
    341 		}
    342 
    343 	| DELE check_modify SP pathname CRLF
    344 		{
    345 			if ($2 && $4 != NULL)
    346 				delete($4);
    347 			if ($4 != NULL)
    348 				free($4);
    349 		}
    350 
    351 	| RMD check_modify SP pathname CRLF
    352 		{
    353 			if ($2 && $4 != NULL)
    354 				removedir($4);
    355 			if ($4 != NULL)
    356 				free($4);
    357 		}
    358 
    359 	| MKD check_modify SP pathname CRLF
    360 		{
    361 			if ($2 && $4 != NULL)
    362 				makedir($4);
    363 			if ($4 != NULL)
    364 				free($4);
    365 		}
    366 
    367 	| PWD check_login CRLF
    368 		{
    369 			if ($2)
    370 				pwd();
    371 		}
    372 
    373 	| LIST check_login CRLF
    374 		{
    375 			if ($2)
    376 				retrieve("/bin/ls -lgA", "");
    377 		}
    378 
    379 	| LIST check_login SP pathname CRLF
    380 		{
    381 			if ($2 && $4 != NULL)
    382 				retrieve("/bin/ls -lgA %s", $4);
    383 			if ($4 != NULL)
    384 				free($4);
    385 		}
    386 
    387 	| NLST check_login CRLF
    388 		{
    389 			if ($2)
    390 				send_file_list(".");
    391 		}
    392 
    393 	| NLST check_login SP STRING CRLF
    394 		{
    395 			if ($2 && $4 != NULL)
    396 				send_file_list($4);
    397 			if ($4 != NULL)
    398 				free($4);
    399 		}
    400 
    401 	| SITE SP HELP CRLF
    402 		{
    403 			help(sitetab, NULL);
    404 		}
    405 
    406 	| SITE SP HELP SP STRING CRLF
    407 		{
    408 			help(sitetab, $5);
    409 		}
    410 
    411 	| SITE SP UMASK check_login CRLF
    412 		{
    413 			int oldmask;
    414 
    415 			if ($4) {
    416 				oldmask = umask(0);
    417 				(void) umask(oldmask);
    418 				reply(200, "Current UMASK is %03o", oldmask);
    419 			}
    420 		}
    421 
    422 	| SITE SP UMASK check_modify SP octal_number CRLF
    423 		{
    424 			int oldmask;
    425 
    426 			if ($4) {
    427 				if (($6 == -1) || ($6 > 0777)) {
    428 					reply(501, "Bad UMASK value");
    429 				} else {
    430 					oldmask = umask($6);
    431 					reply(200,
    432 					    "UMASK set to %03o (was %03o)",
    433 					    $6, oldmask);
    434 				}
    435 			}
    436 		}
    437 
    438 	| SITE SP CHMOD check_modify SP octal_number SP pathname CRLF
    439 		{
    440 			if ($4 && ($8 != NULL)) {
    441 				if ($6 > 0777)
    442 					reply(501,
    443 				"CHMOD: Mode value must be between 0 and 0777");
    444 				else if (chmod($8, $6) < 0)
    445 					perror_reply(550, $8);
    446 				else
    447 					reply(200, "CHMOD command successful.");
    448 			}
    449 			if ($8 != NULL)
    450 				free($8);
    451 		}
    452 
    453 	| SITE SP IDLE CRLF
    454 		{
    455 			reply(200,
    456 			    "Current IDLE time limit is %d seconds; max %d",
    457 				curclass.timeout, curclass.maxtimeout);
    458 		}
    459 
    460 	| SITE SP IDLE SP NUMBER CRLF
    461 		{
    462 			if ($5 < 30 || $5 > curclass.maxtimeout) {
    463 				reply(501,
    464 			"IDLE time limit must be between 30 and %d seconds",
    465 				    curclass.maxtimeout);
    466 			} else {
    467 				curclass.timeout = $5;
    468 				(void) alarm(curclass.timeout);
    469 				reply(200,
    470 				    "IDLE time limit set to %d seconds",
    471 				    curclass.timeout);
    472 			}
    473 		}
    474 
    475 	| SYST CRLF
    476 		{
    477 			reply(215, "UNIX Type: L%d %s", NBBY, version);
    478 		}
    479 
    480 	| STAT check_login SP pathname CRLF
    481 		{
    482 			if ($2 && $4 != NULL)
    483 				statfilecmd($4);
    484 			if ($4 != NULL)
    485 				free($4);
    486 		}
    487 
    488 	| STAT CRLF
    489 		{
    490 			statcmd();
    491 		}
    492 
    493 	| HELP CRLF
    494 		{
    495 			help(cmdtab, NULL);
    496 		}
    497 
    498 	| HELP SP STRING CRLF
    499 		{
    500 			char *cp = $3;
    501 
    502 			if (strncasecmp(cp, "SITE", 4) == 0) {
    503 				cp = $3 + 4;
    504 				if (*cp == ' ')
    505 					cp++;
    506 				if (*cp)
    507 					help(sitetab, cp);
    508 				else
    509 					help(sitetab, NULL);
    510 			} else
    511 				help(cmdtab, $3);
    512 		}
    513 
    514 	| NOOP CRLF
    515 		{
    516 			reply(200, "NOOP command successful.");
    517 		}
    518 
    519 						/* RFC 2228 */
    520 	| AUTH SP mechanism_name CRLF
    521 		{
    522 			reply(502, "RFC 2228 authentication not implemented.");
    523 		}
    524 
    525 	| ADAT SP base64data CRLF
    526 		{
    527 			reply(503,
    528 			    "Please set authentication state with AUTH.");
    529 		}
    530 
    531 	| PROT SP prot_code CRLF
    532 		{
    533 			reply(503,
    534 			    "Please set protection buffer size with PBSZ.");
    535 		}
    536 
    537 	| PBSZ SP decimal_integer CRLF
    538 		{
    539 			reply(503,
    540 			    "Please set authentication state with AUTH.");
    541 		}
    542 
    543 	| CCC CRLF
    544 		{
    545 			reply(533, "No protection enabled.");
    546 		}
    547 
    548 	| MIC SP base64data CRLF
    549 		{
    550 			reply(502, "RFC 2228 authentication not implemented.");
    551 		}
    552 
    553 	| CONF SP base64data CRLF
    554 		{
    555 			reply(502, "RFC 2228 authentication not implemented.");
    556 		}
    557 
    558 	| ENC SP base64data CRLF
    559 		{
    560 			reply(502, "RFC 2228 authentication not implemented.");
    561 		}
    562 
    563 						/* RFC 2389 */
    564 	| FEAT CRLF
    565 		{
    566 			lreply(211, "Features supported");
    567 			printf(" MDTM\r\n");
    568 			printf(" REST STREAM\r\n");
    569 			printf(" SIZE\r\n");
    570 			reply(211, "End");
    571 		}
    572 
    573 	| OPTS SP STRING CRLF
    574 		{
    575 
    576 			opts($3);
    577 		}
    578 
    579 
    580 						/* BSD extensions */
    581 
    582 		/*
    583 		 * SIZE is not in RFC 959, but Postel has blessed it and
    584 		 * it will be in the updated RFC.
    585 		 *
    586 		 * Return size of file in a format suitable for
    587 		 * using with RESTART (we just count bytes).
    588 		 */
    589 	| SIZE check_login SP pathname CRLF
    590 		{
    591 			if ($2 && $4 != NULL)
    592 				sizecmd($4);
    593 			if ($4 != NULL)
    594 				free($4);
    595 		}
    596 
    597 		/*
    598 		 * MDTM is not in RFC 959, but Postel has blessed it and
    599 		 * it will be in the updated RFC.
    600 		 *
    601 		 * Return modification time of file as an ISO 3307
    602 		 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
    603 		 * where xxx is the fractional second (of any precision,
    604 		 * not necessarily 3 digits)
    605 		 */
    606 	| MDTM check_login SP pathname CRLF
    607 		{
    608 			if ($2 && $4 != NULL) {
    609 				struct stat stbuf;
    610 				if (stat($4, &stbuf) < 0)
    611 					perror_reply(550, $4);
    612 				else if (!S_ISREG(stbuf.st_mode)) {
    613 					reply(550, "%s: not a plain file.", $4);
    614 				} else {
    615 					struct tm *t;
    616 					t = gmtime(&stbuf.st_mtime);
    617 					reply(213,
    618 					    "%04d%02d%02d%02d%02d%02d",
    619 					    TM_YEAR_BASE + t->tm_year,
    620 					    t->tm_mon+1, t->tm_mday,
    621 					    t->tm_hour, t->tm_min, t->tm_sec);
    622 				}
    623 			}
    624 			if ($4 != NULL)
    625 				free($4);
    626 		}
    627 
    628 	| error CRLF
    629 		{
    630 			yyerrok;
    631 		}
    632 	;
    633 
    634 rcmd
    635 	: REST SP byte_size CRLF
    636 		{
    637 			fromname = NULL;
    638 			restart_point = $3;	/* XXX $3 is only "int" */
    639 			reply(350, "Restarting at %qd. %s", restart_point,
    640 			    "Send STORE or RETRIEVE to initiate transfer.");
    641 		}
    642 	| RNFR check_modify SP pathname CRLF
    643 		{
    644 			restart_point = (off_t) 0;
    645 			if ($2 && $4) {
    646 				fromname = renamefrom($4);
    647 				if (fromname == NULL && $4) {
    648 					free($4);
    649 				}
    650 			}
    651 		}
    652 	;
    653 
    654 username
    655 	: STRING
    656 	;
    657 
    658 password
    659 	: /* empty */
    660 		{
    661 			$$ = (char *)calloc(1, sizeof(char));
    662 		}
    663 
    664 	| STRING
    665 	;
    666 
    667 byte_size
    668 	: NUMBER
    669 	;
    670 
    671 host_port
    672 	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
    673 		NUMBER COMMA NUMBER
    674 		{
    675 			char *a, *p;
    676 
    677 			data_dest.sin_len = sizeof(struct sockaddr_in);
    678 			data_dest.sin_family = AF_INET;
    679 			p = (char *)&data_dest.sin_port;
    680 			p[0] = $9; p[1] = $11;
    681 			a = (char *)&data_dest.sin_addr;
    682 			a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7;
    683 		}
    684 	;
    685 
    686 form_code
    687 	: N
    688 		{
    689 			$$ = FORM_N;
    690 		}
    691 
    692 	| T
    693 		{
    694 			$$ = FORM_T;
    695 		}
    696 
    697 	| C
    698 		{
    699 			$$ = FORM_C;
    700 		}
    701 	;
    702 
    703 type_code
    704 	: A
    705 		{
    706 			cmd_type = TYPE_A;
    707 			cmd_form = FORM_N;
    708 		}
    709 
    710 	| A SP form_code
    711 		{
    712 			cmd_type = TYPE_A;
    713 			cmd_form = $3;
    714 		}
    715 
    716 	| E
    717 		{
    718 			cmd_type = TYPE_E;
    719 			cmd_form = FORM_N;
    720 		}
    721 
    722 	| E SP form_code
    723 		{
    724 			cmd_type = TYPE_E;
    725 			cmd_form = $3;
    726 		}
    727 
    728 	| I
    729 		{
    730 			cmd_type = TYPE_I;
    731 		}
    732 
    733 	| L
    734 		{
    735 			cmd_type = TYPE_L;
    736 			cmd_bytesz = NBBY;
    737 		}
    738 
    739 	| L SP byte_size
    740 		{
    741 			cmd_type = TYPE_L;
    742 			cmd_bytesz = $3;
    743 		}
    744 
    745 		/* this is for a bug in the BBN ftp */
    746 	| L byte_size
    747 		{
    748 			cmd_type = TYPE_L;
    749 			cmd_bytesz = $2;
    750 		}
    751 	;
    752 
    753 struct_code
    754 	: F
    755 		{
    756 			$$ = STRU_F;
    757 		}
    758 
    759 	| R
    760 		{
    761 			$$ = STRU_R;
    762 		}
    763 
    764 	| P
    765 		{
    766 			$$ = STRU_P;
    767 		}
    768 	;
    769 
    770 mode_code
    771 	: S
    772 		{
    773 			$$ = MODE_S;
    774 		}
    775 
    776 	| B
    777 		{
    778 			$$ = MODE_B;
    779 		}
    780 
    781 	| C
    782 		{
    783 			$$ = MODE_C;
    784 		}
    785 	;
    786 
    787 pathname
    788 	: pathstring
    789 		{
    790 			/*
    791 			 * Problem: this production is used for all pathname
    792 			 * processing, but only gives a 550 error reply.
    793 			 * This is a valid reply in some cases but not in
    794 			 * others.
    795 			 */
    796 			if (logged_in && $1 && *$1 == '~') {
    797 				glob_t gl;
    798 				int flags =
    799 				 GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE;
    800 
    801 				if ($1[1] == '\0')
    802 					$$ = xstrdup(pw->pw_dir);
    803 				else {
    804 					memset(&gl, 0, sizeof(gl));
    805 					if (glob($1, flags, NULL, &gl) ||
    806 					    gl.gl_pathc == 0) {
    807 						reply(550, "not found");
    808 						$$ = NULL;
    809 					} else
    810 						$$ = xstrdup(gl.gl_pathv[0]);
    811 					globfree(&gl);
    812 				}
    813 				free($1);
    814 			} else
    815 				$$ = $1;
    816 		}
    817 	;
    818 
    819 pathstring
    820 	: STRING
    821 	;
    822 
    823 octal_number
    824 	: NUMBER
    825 		{
    826 			int ret, dec, multby, digit;
    827 
    828 			/*
    829 			 * Convert a number that was read as decimal number
    830 			 * to what it would be if it had been read as octal.
    831 			 */
    832 			dec = $1;
    833 			multby = 1;
    834 			ret = 0;
    835 			while (dec) {
    836 				digit = dec%10;
    837 				if (digit > 7) {
    838 					ret = -1;
    839 					break;
    840 				}
    841 				ret += digit * multby;
    842 				multby *= 8;
    843 				dec /= 10;
    844 			}
    845 			$$ = ret;
    846 		}
    847 	;
    848 
    849 mechanism_name
    850 	: STRING
    851 	;
    852 
    853 base64data
    854 	: STRING
    855 	;
    856 
    857 prot_code
    858 	: STRING
    859 	;
    860 
    861 decimal_integer
    862 	: NUMBER
    863 	;
    864 
    865 check_login
    866 	: /* empty */
    867 		{
    868 			if (logged_in)
    869 				$$ = 1;
    870 			else {
    871 				reply(530, "Please login with USER and PASS.");
    872 				$$ = 0;
    873 				hasyyerrored = 1;
    874 			}
    875 		}
    876 	;
    877 
    878 check_modify
    879 	: /* empty */
    880 		{
    881 			if (logged_in) {
    882 				if (curclass.modify)
    883 					$$ = 1;
    884 				else {
    885 					reply(502,
    886 					"No permission to use this command.");
    887 					$$ = 0;
    888 					hasyyerrored = 1;
    889 				}
    890 			} else {
    891 				reply(530, "Please login with USER and PASS.");
    892 				$$ = 0;
    893 				hasyyerrored = 1;
    894 			}
    895 		}
    896 
    897 %%
    898 
    899 #define	CMD	0	/* beginning of command */
    900 #define	ARGS	1	/* expect miscellaneous arguments */
    901 #define	STR1	2	/* expect SP followed by STRING */
    902 #define	STR2	3	/* expect STRING */
    903 #define	OSTR	4	/* optional SP then STRING */
    904 #define	ZSTR1	5	/* SP then optional STRING */
    905 #define	ZSTR2	6	/* optional STRING after SP */
    906 #define	SITECMD	7	/* SITE command */
    907 #define	NSTR	8	/* Number followed by a string */
    908 #define NOARGS	9	/* No arguments allowed */
    909 
    910 struct tab {
    911 	char	*name;
    912 	short	 token;
    913 	short	 state;
    914 	short	 implemented;	/* 1 if command is implemented */
    915 	short	 hasopts;	/* 1 if command takes options */
    916 	char	*help;
    917 	char	*options;
    918 };
    919 
    920 struct tab cmdtab[] = {
    921 				/* From RFC 959, in order defined (5.3.1) */
    922 	{ "USER", USER, STR1,	1, 0,	"<sp> username" },
    923 	{ "PASS", PASS, ZSTR1,	1, 0,	"<sp> password" },
    924 	{ "ACCT", ACCT, STR1,	0, 0,	"(specify account)" },
    925 	{ "CWD",  CWD,  OSTR,	1, 0,	"[ <sp> directory-name ]" },
    926 	{ "CDUP", CDUP, NOARGS,	1, 0,	"(change to parent directory)" },
    927 	{ "SMNT", SMNT, ARGS,	0, 0,	"(structure mount)" },
    928 	{ "QUIT", QUIT, NOARGS,	1, 0,	"(terminate service)", },
    929 	{ "REIN", REIN, NOARGS,	0, 0,	"(reinitialize server state)" },
    930 	{ "PORT", PORT, ARGS,	1, 0,	"<sp> b0, b1, b2, b3, b4" },
    931 	{ "PASV", PASV, NOARGS,	1, 0,	"(set server in passive mode)" },
    932 	{ "TYPE", TYPE, ARGS,	1, 0,	"<sp> [ A | E | I | L ]" },
    933 	{ "STRU", STRU, ARGS,	1, 0,	"(specify file structure)" },
    934 	{ "MODE", MODE, ARGS,	1, 0,	"(specify transfer mode)" },
    935 	{ "RETR", RETR, STR1,	1, 0,	"<sp> file-name" },
    936 	{ "STOR", STOR, STR1,	1, 0,	"<sp> file-name" },
    937 	{ "STOU", STOU, STR1,	1, 0,	"<sp> file-name" },
    938 	{ "APPE", APPE, STR1,	1, 0,	"<sp> file-name" },
    939 	{ "ALLO", ALLO, ARGS,	1, 0,	"allocate storage (vacuously)" },
    940 	{ "REST", REST, ARGS,	1, 0,	"<sp> offset (restart command)" },
    941 	{ "RNFR", RNFR, STR1,	1, 0,	"<sp> file-name" },
    942 	{ "RNTO", RNTO, STR1,	1, 0,	"<sp> file-name" },
    943 	{ "ABOR", ABOR, NOARGS,	1, 0,	"(abort operation)" },
    944 	{ "DELE", DELE, STR1,	1, 0,	"<sp> file-name" },
    945 	{ "RMD",  RMD,  STR1,	1, 0,	"<sp> path-name" },
    946 	{ "MKD",  MKD,  STR1,	1, 0,	"<sp> path-name" },
    947 	{ "PWD",  PWD,  NOARGS,	1, 0,	"(return current directory)" },
    948 	{ "LIST", LIST, OSTR,	1, 0,	"[ <sp> path-name ]" },
    949 	{ "NLST", NLST, OSTR,	1, 0,	"[ <sp> path-name ]" },
    950 	{ "SITE", SITE, SITECMD, 1, 0,	"site-cmd [ <sp> arguments ]" },
    951 	{ "SYST", SYST, NOARGS,	1, 0,	"(get type of operating system)" },
    952 	{ "STAT", STAT, OSTR,	1, 0,	"[ <sp> path-name ]" },
    953 	{ "HELP", HELP, OSTR,	1, 0,	"[ <sp> <string> ]" },
    954 	{ "NOOP", NOOP, NOARGS,	1, 1,	"" },
    955 
    956 				/* From RFC 2228, in order defined */
    957 	{ "AUTH", AUTH, STR1,	1, 0,	"<sp> mechanism-name" },
    958 	{ "ADAT", ADAT, STR1,	1, 0,	"<sp> base-64-data" },
    959 	{ "PROT", PROT, STR1,	1, 0,	"<sp> prot-code" },
    960 	{ "PBSZ", PBSZ, ARGS,	1, 0,	"<sp> decimal-integer" },
    961 	{ "CCC",  CCC,  NOARGS,	1, 0,	"(Disable data protection)" },
    962 	{ "MIC",  MIC,  STR1,	1, 0,	"<sp> base64data" },
    963 	{ "CONF", CONF, STR1,	1, 0,	"<sp> base64data" },
    964 	{ "ENC",  ENC,  STR1,	1, 0,	"<sp> base64data" },
    965 
    966 				/* From RFC 2389, in order defined */
    967 	{ "FEAT", FEAT, NOARGS,	1, 0,	"(display extended features)" },
    968 	{ "OPTS", OPTS, STR1,	1, 0,	"<sp> command [ <sp> options ]" },
    969 
    970 				/* Non standardized extensions */
    971 	{ "SIZE", SIZE, OSTR,	1, 0,	"<sp> path-name" },
    972 	{ "MDTM", MDTM, OSTR,	1, 0,	"<sp> path-name" },
    973 
    974 				/* obsolete commands */
    975 	{ "MAIL", MAIL, OSTR,	0, 0,	"(mail to user)" },
    976 	{ "MLFL", MLFL, OSTR,	0, 0,	"(mail file)" },
    977 	{ "MRCP", MRCP, STR1,	0, 0,	"(mail recipient)" },
    978 	{ "MRSQ", MRSQ, OSTR,	0, 0,	"(mail recipient scheme question)" },
    979 	{ "MSAM", MSAM, OSTR,	0, 0,	"(mail send to terminal and mailbox)" },
    980 	{ "MSND", MSND, OSTR,	0, 0,	"(mail send to terminal)" },
    981 	{ "MSOM", MSOM, OSTR,	0, 0,	"(mail send to terminal or mailbox)" },
    982 	{ "XCUP", CDUP, NOARGS,	1, 0,	"(change to parent directory)" },
    983 	{ "XCWD", CWD,	OSTR,	1, 0,	"[ <sp> directory-name ]" },
    984 	{ "XMKD", MKD,  STR1,	1, 0,	"<sp> path-name" },
    985 	{ "XPWD", PWD,  NOARGS,	1, 0,	"(return current directory)" },
    986 	{ "XRMD", RMD,  STR1,	1, 0,	"<sp> path-name" },
    987 
    988 	{ NULL,   0,    0,	0, 0,	0 }
    989 };
    990 
    991 struct tab sitetab[] = {
    992 	{ "UMASK", UMASK, ARGS,	1, 0,	"[ <sp> umask ]" },
    993 	{ "IDLE",  IDLE,  ARGS,	1, 0,	"[ <sp> maximum-idle-time ]" },
    994 	{ "CHMOD", CHMOD, NSTR,	1, 0,	"<sp> mode <sp> file-name" },
    995 	{ "HELP",  HELP,  OSTR,	1, 0,	"[ <sp> <string> ]" },
    996 	{ NULL,    0,     0,	0, 0,	0 }
    997 };
    998 
    999 static	void	 	help __P((struct tab *, char *));
   1000 static	struct tab     *lookup __P((struct tab *, const char *));
   1001 static	void		opts __P((const char *));
   1002 static	void		sizecmd __P((char *));
   1003 static	void		toolong __P((int));
   1004 static	int		yylex __P((void));
   1005 
   1006 static struct tab *
   1007 lookup(p, cmd)
   1008 	struct tab *p;
   1009 	const char *cmd;
   1010 {
   1011 
   1012 	for (; p->name != NULL; p++)
   1013 		if (strcasecmp(cmd, p->name) == 0)
   1014 			return (p);
   1015 	return (0);
   1016 }
   1017 
   1018 #include <arpa/telnet.h>
   1019 
   1020 /*
   1021  * getline - a hacked up version of fgets to ignore TELNET escape codes.
   1022  */
   1023 char *
   1024 getline(s, n, iop)
   1025 	char *s;
   1026 	int n;
   1027 	FILE *iop;
   1028 {
   1029 	int c;
   1030 	char *cs;
   1031 
   1032 	cs = s;
   1033 /* tmpline may contain saved command from urgent mode interruption */
   1034 	for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
   1035 		*cs++ = tmpline[c];
   1036 		if (tmpline[c] == '\n') {
   1037 			*cs++ = '\0';
   1038 			if (debug)
   1039 				syslog(LOG_DEBUG, "command: %s", s);
   1040 			tmpline[0] = '\0';
   1041 			return(s);
   1042 		}
   1043 		if (c == 0)
   1044 			tmpline[0] = '\0';
   1045 	}
   1046 	while ((c = getc(iop)) != EOF) {
   1047 		c &= 0377;
   1048 		if (c == IAC) {
   1049 		    if ((c = getc(iop)) != EOF) {
   1050 			c &= 0377;
   1051 			switch (c) {
   1052 			case WILL:
   1053 			case WONT:
   1054 				c = getc(iop);
   1055 				printf("%c%c%c", IAC, DONT, 0377&c);
   1056 				(void) fflush(stdout);
   1057 				continue;
   1058 			case DO:
   1059 			case DONT:
   1060 				c = getc(iop);
   1061 				printf("%c%c%c", IAC, WONT, 0377&c);
   1062 				(void) fflush(stdout);
   1063 				continue;
   1064 			case IAC:
   1065 				break;
   1066 			default:
   1067 				continue;	/* ignore command */
   1068 			}
   1069 		    }
   1070 		}
   1071 		*cs++ = c;
   1072 		if (--n <= 0 || c == '\n')
   1073 			break;
   1074 	}
   1075 	if (c == EOF && cs == s)
   1076 		return (NULL);
   1077 	*cs++ = '\0';
   1078 	if (debug) {
   1079 		if (!guest && strncasecmp("pass ", s, 5) == 0) {
   1080 			/* Don't syslog passwords */
   1081 			syslog(LOG_DEBUG, "command: %.5s ???", s);
   1082 		} else {
   1083 			char *cp;
   1084 			int len;
   1085 
   1086 			/* Don't syslog trailing CR-LF */
   1087 			len = strlen(s);
   1088 			cp = s + len - 1;
   1089 			while (cp >= s && (*cp == '\n' || *cp == '\r')) {
   1090 				--cp;
   1091 				--len;
   1092 			}
   1093 			syslog(LOG_DEBUG, "command: %.*s", len, s);
   1094 		}
   1095 	}
   1096 	return (s);
   1097 }
   1098 
   1099 static void
   1100 toolong(signo)
   1101 	int signo;
   1102 {
   1103 
   1104 	reply(421,
   1105 	    "Timeout (%d seconds): closing control connection.",
   1106 	    curclass.timeout);
   1107 	if (logging)
   1108 		syslog(LOG_INFO, "User %s timed out after %d seconds",
   1109 		    (pw ? pw -> pw_name : "unknown"), curclass.timeout);
   1110 	dologout(1);
   1111 }
   1112 
   1113 static int
   1114 yylex()
   1115 {
   1116 	static int cpos, state;
   1117 	char *cp, *cp2;
   1118 	struct tab *p;
   1119 	int n;
   1120 	char c;
   1121 
   1122 	switch (state) {
   1123 
   1124 	case CMD:
   1125 		hasyyerrored = 0;
   1126 		(void) signal(SIGALRM, toolong);
   1127 		(void) alarm(curclass.timeout);
   1128 		if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
   1129 			reply(221, "You could at least say goodbye.");
   1130 			dologout(0);
   1131 		}
   1132 		(void) alarm(0);
   1133 #ifdef HASSETPROCTITLE
   1134 		if (strncasecmp(cbuf, "PASS", 4) != 0)
   1135 			setproctitle("%s: %s", proctitle, cbuf);
   1136 #endif /* HASSETPROCTITLE */
   1137 		if ((cp = strchr(cbuf, '\r'))) {
   1138 			*cp++ = '\n';
   1139 			*cp = '\0';
   1140 		}
   1141 		if ((cp = strpbrk(cbuf, " \n")))
   1142 			cpos = cp - cbuf;
   1143 		if (cpos == 0)
   1144 			cpos = 4;
   1145 		c = cbuf[cpos];
   1146 		cbuf[cpos] = '\0';
   1147 		p = lookup(cmdtab, cbuf);
   1148 		cbuf[cpos] = c;
   1149 		if (p != NULL) {
   1150 			if (p->implemented == 0) {
   1151 				reply(502, "%s command not implemented.",
   1152 				    p->name);
   1153 				hasyyerrored = 1;
   1154 				break;
   1155 			}
   1156 			state = p->state;
   1157 			yylval.s = p->name;
   1158 			return (p->token);
   1159 		}
   1160 		break;
   1161 
   1162 	case SITECMD:
   1163 		if (cbuf[cpos] == ' ') {
   1164 			cpos++;
   1165 			return (SP);
   1166 		}
   1167 		cp = &cbuf[cpos];
   1168 		if ((cp2 = strpbrk(cp, " \n")))
   1169 			cpos = cp2 - cbuf;
   1170 		c = cbuf[cpos];
   1171 		cbuf[cpos] = '\0';
   1172 		p = lookup(sitetab, cp);
   1173 		cbuf[cpos] = c;
   1174 		if (p != NULL) {
   1175 			if (p->implemented == 0) {
   1176 				reply(502, "SITE %s command not implemented.",
   1177 				    p->name);
   1178 				hasyyerrored = 1;
   1179 				break;
   1180 			}
   1181 			state = p->state;
   1182 			yylval.s = p->name;
   1183 			return (p->token);
   1184 		}
   1185 		break;
   1186 
   1187 	case OSTR:
   1188 		if (cbuf[cpos] == '\n') {
   1189 			state = CMD;
   1190 			return (CRLF);
   1191 		}
   1192 		/* FALLTHROUGH */
   1193 
   1194 	case STR1:
   1195 	case ZSTR1:
   1196 	dostr1:
   1197 		if (cbuf[cpos] == ' ') {
   1198 			cpos++;
   1199 			state = state == OSTR ? STR2 : ++state;
   1200 			return (SP);
   1201 		}
   1202 		break;
   1203 
   1204 	case ZSTR2:
   1205 		if (cbuf[cpos] == '\n') {
   1206 			state = CMD;
   1207 			return (CRLF);
   1208 		}
   1209 		/* FALLTHROUGH */
   1210 
   1211 	case STR2:
   1212 		cp = &cbuf[cpos];
   1213 		n = strlen(cp);
   1214 		cpos += n - 1;
   1215 		/*
   1216 		 * Make sure the string is nonempty and \n terminated.
   1217 		 */
   1218 		if (n > 1 && cbuf[cpos] == '\n') {
   1219 			cbuf[cpos] = '\0';
   1220 			yylval.s = xstrdup(cp);
   1221 			cbuf[cpos] = '\n';
   1222 			state = ARGS;
   1223 			return (STRING);
   1224 		}
   1225 		break;
   1226 
   1227 	case NSTR:
   1228 		if (cbuf[cpos] == ' ') {
   1229 			cpos++;
   1230 			return (SP);
   1231 		}
   1232 		if (isdigit(cbuf[cpos])) {
   1233 			cp = &cbuf[cpos];
   1234 			while (isdigit(cbuf[++cpos]))
   1235 				;
   1236 			c = cbuf[cpos];
   1237 			cbuf[cpos] = '\0';
   1238 			yylval.i = atoi(cp);
   1239 			cbuf[cpos] = c;
   1240 			state = STR1;
   1241 			return (NUMBER);
   1242 		}
   1243 		state = STR1;
   1244 		goto dostr1;
   1245 
   1246 	case ARGS:
   1247 		if (isdigit(cbuf[cpos])) {
   1248 			cp = &cbuf[cpos];
   1249 			while (isdigit(cbuf[++cpos]))
   1250 				;
   1251 			c = cbuf[cpos];
   1252 			cbuf[cpos] = '\0';
   1253 			yylval.i = atoi(cp);
   1254 			cbuf[cpos] = c;
   1255 			return (NUMBER);
   1256 		}
   1257 		switch (cbuf[cpos++]) {
   1258 
   1259 		case '\n':
   1260 			state = CMD;
   1261 			return (CRLF);
   1262 
   1263 		case ' ':
   1264 			return (SP);
   1265 
   1266 		case ',':
   1267 			return (COMMA);
   1268 
   1269 		case 'A':
   1270 		case 'a':
   1271 			return (A);
   1272 
   1273 		case 'B':
   1274 		case 'b':
   1275 			return (B);
   1276 
   1277 		case 'C':
   1278 		case 'c':
   1279 			return (C);
   1280 
   1281 		case 'E':
   1282 		case 'e':
   1283 			return (E);
   1284 
   1285 		case 'F':
   1286 		case 'f':
   1287 			return (F);
   1288 
   1289 		case 'I':
   1290 		case 'i':
   1291 			return (I);
   1292 
   1293 		case 'L':
   1294 		case 'l':
   1295 			return (L);
   1296 
   1297 		case 'N':
   1298 		case 'n':
   1299 			return (N);
   1300 
   1301 		case 'P':
   1302 		case 'p':
   1303 			return (P);
   1304 
   1305 		case 'R':
   1306 		case 'r':
   1307 			return (R);
   1308 
   1309 		case 'S':
   1310 		case 's':
   1311 			return (S);
   1312 
   1313 		case 'T':
   1314 		case 't':
   1315 			return (T);
   1316 
   1317 		}
   1318 		break;
   1319 
   1320 	case NOARGS:
   1321 		if (cbuf[cpos] == '\n') {
   1322 			state = CMD;
   1323 			return (CRLF);
   1324 		}
   1325 		c = cbuf[cpos];
   1326 		cbuf[cpos] = '\0';
   1327 		reply(501, "'%s' command does not take any arguments.", cbuf);
   1328 		hasyyerrored = 1;
   1329 		cbuf[cpos] = c;
   1330 		break;
   1331 
   1332 	default:
   1333 		fatal("Unknown state in scanner.");
   1334 	}
   1335 	yyerror(NULL);
   1336 	state = CMD;
   1337 	longjmp(errcatch, 0);
   1338 	/* NOTREACHED */
   1339 }
   1340 
   1341 /* ARGSUSED */
   1342 void
   1343 yyerror(s)
   1344 	char *s;
   1345 {
   1346 	char *cp;
   1347 
   1348 	if (hasyyerrored)
   1349 		return;
   1350 	if ((cp = strchr(cbuf,'\n')) != NULL)
   1351 		*cp = '\0';
   1352 	reply(500, "'%s': command not understood.", cbuf);
   1353 	hasyyerrored = 1;
   1354 }
   1355 
   1356 static void
   1357 help(ctab, s)
   1358 	struct tab *ctab;
   1359 	char *s;
   1360 {
   1361 	struct tab *c;
   1362 	int width, NCMDS;
   1363 	char *type;
   1364 
   1365 	if (ctab == sitetab)
   1366 		type = "SITE ";
   1367 	else
   1368 		type = "";
   1369 	width = 0, NCMDS = 0;
   1370 	for (c = ctab; c->name != NULL; c++) {
   1371 		int len = strlen(c->name);
   1372 
   1373 		if (len > width)
   1374 			width = len;
   1375 		NCMDS++;
   1376 	}
   1377 	width = (width + 8) &~ 7;
   1378 	if (s == 0) {
   1379 		int i, j, w;
   1380 		int columns, lines;
   1381 
   1382 		lreply(214, "The following %scommands are recognized.", type);
   1383 		printf(
   1384 		    "    (`-' = not implemented, `+' = supports options)\r\n");
   1385 		columns = 76 / width;
   1386 		if (columns == 0)
   1387 			columns = 1;
   1388 		lines = (NCMDS + columns - 1) / columns;
   1389 		for (i = 0; i < lines; i++) {
   1390 			printf("   ");
   1391 			for (j = 0; j < columns; j++) {
   1392 				c = ctab + j * lines + i;
   1393 				fputs(c->name, stdout);
   1394 				w = strlen(c->name);
   1395 				if (! c->implemented) {
   1396 					putchar('-');
   1397 					w++;
   1398 				}
   1399 				if (c->hasopts) {
   1400 					putchar('+');
   1401 					w++;
   1402 				}
   1403 				if (c + lines >= &ctab[NCMDS])
   1404 					break;
   1405 				while (w < width) {
   1406 					putchar(' ');
   1407 					w++;
   1408 				}
   1409 			}
   1410 			printf("\r\n");
   1411 		}
   1412 		(void) fflush(stdout);
   1413 		reply(214, "Direct comments to ftp-bugs@%s.", hostname);
   1414 		return;
   1415 	}
   1416 	c = lookup(ctab, s);
   1417 	if (c == (struct tab *)0) {
   1418 		reply(502, "Unknown command %s.", s);
   1419 		return;
   1420 	}
   1421 	if (c->implemented)
   1422 		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
   1423 	else
   1424 		reply(214, "%s%-*s\t%s; not implemented.", type, width,
   1425 		    c->name, c->help);
   1426 }
   1427 
   1428 static void
   1429 sizecmd(filename)
   1430 	char *filename;
   1431 {
   1432 	switch (type) {
   1433 	case TYPE_L:
   1434 	case TYPE_I: {
   1435 		struct stat stbuf;
   1436 		if (stat(filename, &stbuf) < 0 || !S_ISREG(stbuf.st_mode))
   1437 			reply(550, "%s: not a plain file.", filename);
   1438 		else
   1439 			reply(213, "%qu", stbuf.st_size);
   1440 		break; }
   1441 	case TYPE_A: {
   1442 		FILE *fin;
   1443 		int c;
   1444 		off_t count;
   1445 		struct stat stbuf;
   1446 		fin = fopen(filename, "r");
   1447 		if (fin == NULL) {
   1448 			perror_reply(550, filename);
   1449 			return;
   1450 		}
   1451 		if (fstat(fileno(fin), &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) {
   1452 			reply(550, "%s: not a plain file.", filename);
   1453 			(void) fclose(fin);
   1454 			return;
   1455 		}
   1456 
   1457 		count = 0;
   1458 		while((c=getc(fin)) != EOF) {
   1459 			if (c == '\n')	/* will get expanded to \r\n */
   1460 				count++;
   1461 			count++;
   1462 		}
   1463 		(void) fclose(fin);
   1464 
   1465 		reply(213, "%qd", count);
   1466 		break; }
   1467 	default:
   1468 		reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
   1469 	}
   1470 }
   1471 
   1472 static void
   1473 opts(command)
   1474 	const char *command;
   1475 {
   1476 	struct tab *c;
   1477 	char *ep;
   1478 
   1479 	if ((ep = strchr(command, ' ')) != NULL)
   1480 		*ep++ = '\0';
   1481 	c = lookup(cmdtab, command);
   1482 	if (c == NULL) {
   1483 		reply(502, "Unknown command %s.", command);
   1484 		return;
   1485 	}
   1486 	if (c->implemented == 0) {
   1487 		reply(502, "%s command not implemented.", c->name);
   1488 		return;
   1489 	}
   1490 	if (c->hasopts == 0) {
   1491 		reply(501, "%s command does not support persistent options.",
   1492 		    c->name);
   1493 		return;
   1494 	}
   1495 
   1496 	if (ep != NULL && *ep != '\0') {
   1497 		if (c->options != NULL)
   1498 			free(c->options);
   1499 		c->options = xstrdup(ep);
   1500 	}
   1501 	if (c->options != NULL)
   1502 		reply(200, "Options for %s are '%s'.", c->name, c->options);
   1503 	else
   1504 		reply(200, "No options defined for %s.", c->name);
   1505 }
   1506