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