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