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