Home | History | Annotate | Line # | Download | only in ftpd
ftpcmd.y revision 1.27
      1 /*	$NetBSD: ftpcmd.y,v 1.27 1999/05/17 15:14:54 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.27 1999/05/17 15:14:54 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 	    "Data traffic for this session was %qd byte%s in %qd file%s.",
    191 				    total_data, PLURAL(total_data),
    192 				    total_files, PLURAL(total_files));
    193 				lreply(221,
    194 	    "Total traffic for this session was %qd byte%s in %qd transfer%s.",
    195 				    total_bytes, PLURAL(total_bytes),
    196 				    total_xfers, PLURAL(total_xfers));
    197 				syslog(LOG_INFO,
    198 				    "Data traffic: %qd byte%s in %qd file%s",
    199 				    total_data, PLURAL(total_data),
    200 				    total_files, PLURAL(total_files));
    201 				syslog(LOG_INFO,
    202 				  "Total traffic: %qd byte%s in %qd transfer%s",
    203 				    total_bytes, PLURAL(total_bytes),
    204 				    total_xfers, PLURAL(total_xfers));
    205 			}
    206 			lreply(211,
    207 			    "Thank you for using the FTP service on %s.",
    208 			    hostname);
    209 			reply(221, "Goodbye.");
    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", restart_point,
    661 			    "Send STORE or RETRIEVE to initiate transfer.");
    662 		}
    663 	| RNFR check_modify SP pathname CRLF
    664 		{
    665 			restart_point = (off_t) 0;
    666 			if ($2 && $4) {
    667 				fromname = renamefrom($4);
    668 				if (fromname == NULL && $4) {
    669 					free($4);
    670 				}
    671 			}
    672 		}
    673 	;
    674 
    675 username
    676 	: STRING
    677 	;
    678 
    679 password
    680 	: /* empty */
    681 		{
    682 			$$ = (char *)calloc(1, sizeof(char));
    683 		}
    684 
    685 	| STRING
    686 	;
    687 
    688 byte_size
    689 	: NUMBER
    690 	;
    691 
    692 host_port
    693 	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
    694 		NUMBER COMMA NUMBER
    695 		{
    696 			char *a, *p;
    697 
    698 			data_dest.sin_len = sizeof(struct sockaddr_in);
    699 			data_dest.sin_family = AF_INET;
    700 			p = (char *)&data_dest.sin_port;
    701 			p[0] = $9; p[1] = $11;
    702 			a = (char *)&data_dest.sin_addr;
    703 			a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7;
    704 		}
    705 	;
    706 
    707 form_code
    708 	: N
    709 		{
    710 			$$ = FORM_N;
    711 		}
    712 
    713 	| T
    714 		{
    715 			$$ = FORM_T;
    716 		}
    717 
    718 	| C
    719 		{
    720 			$$ = FORM_C;
    721 		}
    722 	;
    723 
    724 type_code
    725 	: A
    726 		{
    727 			cmd_type = TYPE_A;
    728 			cmd_form = FORM_N;
    729 		}
    730 
    731 	| A SP form_code
    732 		{
    733 			cmd_type = TYPE_A;
    734 			cmd_form = $3;
    735 		}
    736 
    737 	| E
    738 		{
    739 			cmd_type = TYPE_E;
    740 			cmd_form = FORM_N;
    741 		}
    742 
    743 	| E SP form_code
    744 		{
    745 			cmd_type = TYPE_E;
    746 			cmd_form = $3;
    747 		}
    748 
    749 	| I
    750 		{
    751 			cmd_type = TYPE_I;
    752 		}
    753 
    754 	| L
    755 		{
    756 			cmd_type = TYPE_L;
    757 			cmd_bytesz = NBBY;
    758 		}
    759 
    760 	| L SP byte_size
    761 		{
    762 			cmd_type = TYPE_L;
    763 			cmd_bytesz = $3;
    764 		}
    765 
    766 		/* this is for a bug in the BBN ftp */
    767 	| L byte_size
    768 		{
    769 			cmd_type = TYPE_L;
    770 			cmd_bytesz = $2;
    771 		}
    772 	;
    773 
    774 struct_code
    775 	: F
    776 		{
    777 			$$ = STRU_F;
    778 		}
    779 
    780 	| R
    781 		{
    782 			$$ = STRU_R;
    783 		}
    784 
    785 	| P
    786 		{
    787 			$$ = STRU_P;
    788 		}
    789 	;
    790 
    791 mode_code
    792 	: S
    793 		{
    794 			$$ = MODE_S;
    795 		}
    796 
    797 	| B
    798 		{
    799 			$$ = MODE_B;
    800 		}
    801 
    802 	| C
    803 		{
    804 			$$ = MODE_C;
    805 		}
    806 	;
    807 
    808 pathname
    809 	: pathstring
    810 		{
    811 			/*
    812 			 * Problem: this production is used for all pathname
    813 			 * processing, but only gives a 550 error reply.
    814 			 * This is a valid reply in some cases but not in
    815 			 * others.
    816 			 */
    817 			if (logged_in && $1 && *$1 == '~') {
    818 				glob_t gl;
    819 				int flags =
    820 				 GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE;
    821 
    822 				if ($1[1] == '\0')
    823 					$$ = xstrdup(pw->pw_dir);
    824 				else {
    825 					memset(&gl, 0, sizeof(gl));
    826 					if (glob($1, flags, NULL, &gl) ||
    827 					    gl.gl_pathc == 0) {
    828 						reply(550, "not found");
    829 						$$ = NULL;
    830 					} else
    831 						$$ = xstrdup(gl.gl_pathv[0]);
    832 					globfree(&gl);
    833 				}
    834 				free($1);
    835 			} else
    836 				$$ = $1;
    837 		}
    838 	;
    839 
    840 pathstring
    841 	: STRING
    842 	;
    843 
    844 octal_number
    845 	: NUMBER
    846 		{
    847 			int ret, dec, multby, digit;
    848 
    849 			/*
    850 			 * Convert a number that was read as decimal number
    851 			 * to what it would be if it had been read as octal.
    852 			 */
    853 			dec = $1;
    854 			multby = 1;
    855 			ret = 0;
    856 			while (dec) {
    857 				digit = dec%10;
    858 				if (digit > 7) {
    859 					ret = -1;
    860 					break;
    861 				}
    862 				ret += digit * multby;
    863 				multby *= 8;
    864 				dec /= 10;
    865 			}
    866 			$$ = ret;
    867 		}
    868 	;
    869 
    870 mechanism_name
    871 	: STRING
    872 	;
    873 
    874 base64data
    875 	: STRING
    876 	;
    877 
    878 prot_code
    879 	: STRING
    880 	;
    881 
    882 decimal_integer
    883 	: NUMBER
    884 	;
    885 
    886 check_login
    887 	: /* empty */
    888 		{
    889 			if (logged_in)
    890 				$$ = 1;
    891 			else {
    892 				reply(530, "Please login with USER and PASS.");
    893 				$$ = 0;
    894 				hasyyerrored = 1;
    895 			}
    896 		}
    897 	;
    898 
    899 check_modify
    900 	: /* empty */
    901 		{
    902 			if (logged_in) {
    903 				if (curclass.modify)
    904 					$$ = 1;
    905 				else {
    906 					reply(502,
    907 					"No permission to use this command.");
    908 					$$ = 0;
    909 					hasyyerrored = 1;
    910 				}
    911 			} else {
    912 				reply(530, "Please login with USER and PASS.");
    913 				$$ = 0;
    914 				hasyyerrored = 1;
    915 			}
    916 		}
    917 
    918 %%
    919 
    920 #define	CMD	0	/* beginning of command */
    921 #define	ARGS	1	/* expect miscellaneous arguments */
    922 #define	STR1	2	/* expect SP followed by STRING */
    923 #define	STR2	3	/* expect STRING */
    924 #define	OSTR	4	/* optional SP then STRING */
    925 #define	ZSTR1	5	/* SP then optional STRING */
    926 #define	ZSTR2	6	/* optional STRING after SP */
    927 #define	SITECMD	7	/* SITE command */
    928 #define	NSTR	8	/* Number followed by a string */
    929 #define NOARGS	9	/* No arguments allowed */
    930 
    931 struct tab {
    932 	char	*name;
    933 	short	 token;
    934 	short	 state;
    935 	short	 implemented;	/* 1 if command is implemented */
    936 	short	 hasopts;	/* 1 if command takes options */
    937 	char	*help;
    938 	char	*options;
    939 };
    940 
    941 struct tab cmdtab[] = {
    942 				/* From RFC 959, in order defined (5.3.1) */
    943 	{ "USER", USER, STR1,	1, 0,	"<sp> username" },
    944 	{ "PASS", PASS, ZSTR1,	1, 0,	"<sp> password" },
    945 	{ "ACCT", ACCT, STR1,	0, 0,	"(specify account)" },
    946 	{ "CWD",  CWD,  OSTR,	1, 0,	"[ <sp> directory-name ]" },
    947 	{ "CDUP", CDUP, NOARGS,	1, 0,	"(change to parent directory)" },
    948 	{ "SMNT", SMNT, ARGS,	0, 0,	"(structure mount)" },
    949 	{ "QUIT", QUIT, NOARGS,	1, 0,	"(terminate service)", },
    950 	{ "REIN", REIN, NOARGS,	0, 0,	"(reinitialize server state)" },
    951 	{ "PORT", PORT, ARGS,	1, 0,	"<sp> b0, b1, b2, b3, b4" },
    952 	{ "PASV", PASV, NOARGS,	1, 0,	"(set server in passive mode)" },
    953 	{ "TYPE", TYPE, ARGS,	1, 0,	"<sp> [ A | E | I | L ]" },
    954 	{ "STRU", STRU, ARGS,	1, 0,	"(specify file structure)" },
    955 	{ "MODE", MODE, ARGS,	1, 0,	"(specify transfer mode)" },
    956 	{ "RETR", RETR, STR1,	1, 0,	"<sp> file-name" },
    957 	{ "STOR", STOR, STR1,	1, 0,	"<sp> file-name" },
    958 	{ "STOU", STOU, STR1,	1, 0,	"<sp> file-name" },
    959 	{ "APPE", APPE, STR1,	1, 0,	"<sp> file-name" },
    960 	{ "ALLO", ALLO, ARGS,	1, 0,	"allocate storage (vacuously)" },
    961 	{ "REST", REST, ARGS,	1, 0,	"<sp> offset (restart command)" },
    962 	{ "RNFR", RNFR, STR1,	1, 0,	"<sp> file-name" },
    963 	{ "RNTO", RNTO, STR1,	1, 0,	"<sp> file-name" },
    964 	{ "ABOR", ABOR, NOARGS,	1, 0,	"(abort operation)" },
    965 	{ "DELE", DELE, STR1,	1, 0,	"<sp> file-name" },
    966 	{ "RMD",  RMD,  STR1,	1, 0,	"<sp> path-name" },
    967 	{ "MKD",  MKD,  STR1,	1, 0,	"<sp> path-name" },
    968 	{ "PWD",  PWD,  NOARGS,	1, 0,	"(return current directory)" },
    969 	{ "LIST", LIST, OSTR,	1, 0,	"[ <sp> path-name ]" },
    970 	{ "NLST", NLST, OSTR,	1, 0,	"[ <sp> path-name ]" },
    971 	{ "SITE", SITE, SITECMD, 1, 0,	"site-cmd [ <sp> arguments ]" },
    972 	{ "SYST", SYST, NOARGS,	1, 0,	"(get type of operating system)" },
    973 	{ "STAT", STAT, OSTR,	1, 0,	"[ <sp> path-name ]" },
    974 	{ "HELP", HELP, OSTR,	1, 0,	"[ <sp> <string> ]" },
    975 	{ "NOOP", NOOP, NOARGS,	1, 1,	"" },
    976 
    977 				/* From RFC 2228, in order defined */
    978 	{ "AUTH", AUTH, STR1,	1, 0,	"<sp> mechanism-name" },
    979 	{ "ADAT", ADAT, STR1,	1, 0,	"<sp> base-64-data" },
    980 	{ "PROT", PROT, STR1,	1, 0,	"<sp> prot-code" },
    981 	{ "PBSZ", PBSZ, ARGS,	1, 0,	"<sp> decimal-integer" },
    982 	{ "CCC",  CCC,  NOARGS,	1, 0,	"(Disable data protection)" },
    983 	{ "MIC",  MIC,  STR1,	1, 0,	"<sp> base64data" },
    984 	{ "CONF", CONF, STR1,	1, 0,	"<sp> base64data" },
    985 	{ "ENC",  ENC,  STR1,	1, 0,	"<sp> base64data" },
    986 
    987 				/* From RFC 2389, in order defined */
    988 	{ "FEAT", FEAT, NOARGS,	1, 0,	"(display extended features)" },
    989 	{ "OPTS", OPTS, STR1,	1, 0,	"<sp> command [ <sp> options ]" },
    990 
    991 				/* Non standardized extensions */
    992 	{ "SIZE", SIZE, OSTR,	1, 0,	"<sp> path-name" },
    993 	{ "MDTM", MDTM, OSTR,	1, 0,	"<sp> path-name" },
    994 
    995 				/* obsolete commands */
    996 	{ "MAIL", MAIL, OSTR,	0, 0,	"(mail to user)" },
    997 	{ "MLFL", MLFL, OSTR,	0, 0,	"(mail file)" },
    998 	{ "MRCP", MRCP, STR1,	0, 0,	"(mail recipient)" },
    999 	{ "MRSQ", MRSQ, OSTR,	0, 0,	"(mail recipient scheme question)" },
   1000 	{ "MSAM", MSAM, OSTR,	0, 0,	"(mail send to terminal and mailbox)" },
   1001 	{ "MSND", MSND, OSTR,	0, 0,	"(mail send to terminal)" },
   1002 	{ "MSOM", MSOM, OSTR,	0, 0,	"(mail send to terminal or mailbox)" },
   1003 	{ "XCUP", CDUP, NOARGS,	1, 0,	"(change to parent directory)" },
   1004 	{ "XCWD", CWD,	OSTR,	1, 0,	"[ <sp> directory-name ]" },
   1005 	{ "XMKD", MKD,  STR1,	1, 0,	"<sp> path-name" },
   1006 	{ "XPWD", PWD,  NOARGS,	1, 0,	"(return current directory)" },
   1007 	{ "XRMD", RMD,  STR1,	1, 0,	"<sp> path-name" },
   1008 
   1009 	{ NULL,   0,    0,	0, 0,	0 }
   1010 };
   1011 
   1012 struct tab sitetab[] = {
   1013 	{ "UMASK", UMASK, ARGS,	1, 0,	"[ <sp> umask ]" },
   1014 	{ "IDLE",  IDLE,  ARGS,	1, 0,	"[ <sp> maximum-idle-time ]" },
   1015 	{ "CHMOD", CHMOD, NSTR,	1, 0,	"<sp> mode <sp> file-name" },
   1016 	{ "HELP",  HELP,  OSTR,	1, 0,	"[ <sp> <string> ]" },
   1017 	{ NULL,    0,     0,	0, 0,	0 }
   1018 };
   1019 
   1020 static	void	 	help __P((struct tab *, char *));
   1021 static	struct tab     *lookup __P((struct tab *, const char *));
   1022 static	void		opts __P((const char *));
   1023 static	void		sizecmd __P((char *));
   1024 static	void		toolong __P((int));
   1025 static	int		yylex __P((void));
   1026 
   1027 static struct tab *
   1028 lookup(p, cmd)
   1029 	struct tab *p;
   1030 	const char *cmd;
   1031 {
   1032 
   1033 	for (; p->name != NULL; p++)
   1034 		if (strcasecmp(cmd, p->name) == 0)
   1035 			return (p);
   1036 	return (0);
   1037 }
   1038 
   1039 #include <arpa/telnet.h>
   1040 
   1041 /*
   1042  * getline - a hacked up version of fgets to ignore TELNET escape codes.
   1043  */
   1044 char *
   1045 getline(s, n, iop)
   1046 	char *s;
   1047 	int n;
   1048 	FILE *iop;
   1049 {
   1050 	off_t b;
   1051 	int c;
   1052 	char *cs;
   1053 
   1054 	cs = s;
   1055 /* tmpline may contain saved command from urgent mode interruption */
   1056 	for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
   1057 		*cs++ = tmpline[c];
   1058 		if (tmpline[c] == '\n') {
   1059 			*cs++ = '\0';
   1060 			if (debug)
   1061 				syslog(LOG_DEBUG, "command: %s", s);
   1062 			tmpline[0] = '\0';
   1063 			return(s);
   1064 		}
   1065 		if (c == 0)
   1066 			tmpline[0] = '\0';
   1067 	}
   1068 	while ((c = getc(iop)) != EOF) {
   1069 		total_bytes++;
   1070 		total_bytes_in++;
   1071 		c &= 0377;
   1072 		if (c == IAC) {
   1073 		    if ((c = getc(iop)) != EOF) {
   1074 			total_bytes++;
   1075 			total_bytes_in++;
   1076 			c &= 0377;
   1077 			switch (c) {
   1078 			case WILL:
   1079 			case WONT:
   1080 				c = getc(iop);
   1081 				total_bytes++;
   1082 				total_bytes_in++;
   1083 				b = printf("%c%c%c", IAC, DONT, 0377&c);
   1084 				total_bytes += b;
   1085 				total_bytes_out += b;
   1086 				(void) fflush(stdout);
   1087 				continue;
   1088 			case DO:
   1089 			case DONT:
   1090 				c = getc(iop);
   1091 				total_bytes++;
   1092 				total_bytes_in++;
   1093 				b = printf("%c%c%c", IAC, WONT, 0377&c);
   1094 				total_bytes += b;
   1095 				total_bytes_out += b;
   1096 				(void) fflush(stdout);
   1097 				continue;
   1098 			case IAC:
   1099 				break;
   1100 			default:
   1101 				continue;	/* ignore command */
   1102 			}
   1103 		    }
   1104 		}
   1105 		*cs++ = c;
   1106 		if (--n <= 0 || c == '\n')
   1107 			break;
   1108 	}
   1109 	if (c == EOF && cs == s)
   1110 		return (NULL);
   1111 	*cs++ = '\0';
   1112 	if (debug) {
   1113 		if (!guest && strncasecmp("pass ", s, 5) == 0) {
   1114 			/* Don't syslog passwords */
   1115 			syslog(LOG_DEBUG, "command: %.5s ???", s);
   1116 		} else {
   1117 			char *cp;
   1118 			int len;
   1119 
   1120 			/* Don't syslog trailing CR-LF */
   1121 			len = strlen(s);
   1122 			cp = s + len - 1;
   1123 			while (cp >= s && (*cp == '\n' || *cp == '\r')) {
   1124 				--cp;
   1125 				--len;
   1126 			}
   1127 			syslog(LOG_DEBUG, "command: %.*s", len, s);
   1128 		}
   1129 	}
   1130 	return (s);
   1131 }
   1132 
   1133 static void
   1134 toolong(signo)
   1135 	int signo;
   1136 {
   1137 
   1138 	reply(421,
   1139 	    "Timeout (%d seconds): closing control connection.",
   1140 	    curclass.timeout);
   1141 	if (logging)
   1142 		syslog(LOG_INFO, "User %s timed out after %d seconds",
   1143 		    (pw ? pw -> pw_name : "unknown"), curclass.timeout);
   1144 	dologout(1);
   1145 }
   1146 
   1147 static int
   1148 yylex()
   1149 {
   1150 	static int cpos, state;
   1151 	char *cp, *cp2;
   1152 	struct tab *p;
   1153 	int n;
   1154 	char c;
   1155 
   1156 	switch (state) {
   1157 
   1158 	case CMD:
   1159 		hasyyerrored = 0;
   1160 		(void) signal(SIGALRM, toolong);
   1161 		(void) alarm(curclass.timeout);
   1162 		if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
   1163 			reply(221, "You could at least say goodbye.");
   1164 			dologout(0);
   1165 		}
   1166 		(void) alarm(0);
   1167 #ifdef HASSETPROCTITLE
   1168 		if (strncasecmp(cbuf, "PASS", 4) != 0)
   1169 			setproctitle("%s: %s", proctitle, cbuf);
   1170 #endif /* HASSETPROCTITLE */
   1171 		if ((cp = strchr(cbuf, '\r'))) {
   1172 			*cp++ = '\n';
   1173 			*cp = '\0';
   1174 		}
   1175 		if ((cp = strpbrk(cbuf, " \n")))
   1176 			cpos = cp - cbuf;
   1177 		if (cpos == 0)
   1178 			cpos = 4;
   1179 		c = cbuf[cpos];
   1180 		cbuf[cpos] = '\0';
   1181 		p = lookup(cmdtab, cbuf);
   1182 		cbuf[cpos] = c;
   1183 		if (p != NULL) {
   1184 			if (p->implemented == 0) {
   1185 				reply(502, "%s command not implemented.",
   1186 				    p->name);
   1187 				hasyyerrored = 1;
   1188 				break;
   1189 			}
   1190 			state = p->state;
   1191 			yylval.s = p->name;
   1192 			return (p->token);
   1193 		}
   1194 		break;
   1195 
   1196 	case SITECMD:
   1197 		if (cbuf[cpos] == ' ') {
   1198 			cpos++;
   1199 			return (SP);
   1200 		}
   1201 		cp = &cbuf[cpos];
   1202 		if ((cp2 = strpbrk(cp, " \n")))
   1203 			cpos = cp2 - cbuf;
   1204 		c = cbuf[cpos];
   1205 		cbuf[cpos] = '\0';
   1206 		p = lookup(sitetab, cp);
   1207 		cbuf[cpos] = c;
   1208 		if (p != NULL) {
   1209 			if (p->implemented == 0) {
   1210 				reply(502, "SITE %s command not implemented.",
   1211 				    p->name);
   1212 				hasyyerrored = 1;
   1213 				break;
   1214 			}
   1215 			state = p->state;
   1216 			yylval.s = p->name;
   1217 			return (p->token);
   1218 		}
   1219 		break;
   1220 
   1221 	case OSTR:
   1222 		if (cbuf[cpos] == '\n') {
   1223 			state = CMD;
   1224 			return (CRLF);
   1225 		}
   1226 		/* FALLTHROUGH */
   1227 
   1228 	case STR1:
   1229 	case ZSTR1:
   1230 	dostr1:
   1231 		if (cbuf[cpos] == ' ') {
   1232 			cpos++;
   1233 			state = state == OSTR ? STR2 : ++state;
   1234 			return (SP);
   1235 		}
   1236 		break;
   1237 
   1238 	case ZSTR2:
   1239 		if (cbuf[cpos] == '\n') {
   1240 			state = CMD;
   1241 			return (CRLF);
   1242 		}
   1243 		/* FALLTHROUGH */
   1244 
   1245 	case STR2:
   1246 		cp = &cbuf[cpos];
   1247 		n = strlen(cp);
   1248 		cpos += n - 1;
   1249 		/*
   1250 		 * Make sure the string is nonempty and \n terminated.
   1251 		 */
   1252 		if (n > 1 && cbuf[cpos] == '\n') {
   1253 			cbuf[cpos] = '\0';
   1254 			yylval.s = xstrdup(cp);
   1255 			cbuf[cpos] = '\n';
   1256 			state = ARGS;
   1257 			return (STRING);
   1258 		}
   1259 		break;
   1260 
   1261 	case NSTR:
   1262 		if (cbuf[cpos] == ' ') {
   1263 			cpos++;
   1264 			return (SP);
   1265 		}
   1266 		if (isdigit(cbuf[cpos])) {
   1267 			cp = &cbuf[cpos];
   1268 			while (isdigit(cbuf[++cpos]))
   1269 				;
   1270 			c = cbuf[cpos];
   1271 			cbuf[cpos] = '\0';
   1272 			yylval.i = atoi(cp);
   1273 			cbuf[cpos] = c;
   1274 			state = STR1;
   1275 			return (NUMBER);
   1276 		}
   1277 		state = STR1;
   1278 		goto dostr1;
   1279 
   1280 	case ARGS:
   1281 		if (isdigit(cbuf[cpos])) {
   1282 			cp = &cbuf[cpos];
   1283 			while (isdigit(cbuf[++cpos]))
   1284 				;
   1285 			c = cbuf[cpos];
   1286 			cbuf[cpos] = '\0';
   1287 			yylval.i = atoi(cp);
   1288 			cbuf[cpos] = c;
   1289 			return (NUMBER);
   1290 		}
   1291 		switch (cbuf[cpos++]) {
   1292 
   1293 		case '\n':
   1294 			state = CMD;
   1295 			return (CRLF);
   1296 
   1297 		case ' ':
   1298 			return (SP);
   1299 
   1300 		case ',':
   1301 			return (COMMA);
   1302 
   1303 		case 'A':
   1304 		case 'a':
   1305 			return (A);
   1306 
   1307 		case 'B':
   1308 		case 'b':
   1309 			return (B);
   1310 
   1311 		case 'C':
   1312 		case 'c':
   1313 			return (C);
   1314 
   1315 		case 'E':
   1316 		case 'e':
   1317 			return (E);
   1318 
   1319 		case 'F':
   1320 		case 'f':
   1321 			return (F);
   1322 
   1323 		case 'I':
   1324 		case 'i':
   1325 			return (I);
   1326 
   1327 		case 'L':
   1328 		case 'l':
   1329 			return (L);
   1330 
   1331 		case 'N':
   1332 		case 'n':
   1333 			return (N);
   1334 
   1335 		case 'P':
   1336 		case 'p':
   1337 			return (P);
   1338 
   1339 		case 'R':
   1340 		case 'r':
   1341 			return (R);
   1342 
   1343 		case 'S':
   1344 		case 's':
   1345 			return (S);
   1346 
   1347 		case 'T':
   1348 		case 't':
   1349 			return (T);
   1350 
   1351 		}
   1352 		break;
   1353 
   1354 	case NOARGS:
   1355 		if (cbuf[cpos] == '\n') {
   1356 			state = CMD;
   1357 			return (CRLF);
   1358 		}
   1359 		c = cbuf[cpos];
   1360 		cbuf[cpos] = '\0';
   1361 		reply(501, "'%s' command does not take any arguments.", cbuf);
   1362 		hasyyerrored = 1;
   1363 		cbuf[cpos] = c;
   1364 		break;
   1365 
   1366 	default:
   1367 		fatal("Unknown state in scanner.");
   1368 	}
   1369 	yyerror(NULL);
   1370 	state = CMD;
   1371 	longjmp(errcatch, 0);
   1372 	/* NOTREACHED */
   1373 }
   1374 
   1375 /* ARGSUSED */
   1376 void
   1377 yyerror(s)
   1378 	char *s;
   1379 {
   1380 	char *cp;
   1381 
   1382 	if (hasyyerrored)
   1383 		return;
   1384 	if ((cp = strchr(cbuf,'\n')) != NULL)
   1385 		*cp = '\0';
   1386 	reply(500, "'%s': command not understood.", cbuf);
   1387 	hasyyerrored = 1;
   1388 }
   1389 
   1390 static void
   1391 help(ctab, s)
   1392 	struct tab *ctab;
   1393 	char *s;
   1394 {
   1395 	struct tab *c;
   1396 	int width, NCMDS;
   1397 	off_t b;
   1398 	char *type;
   1399 
   1400 	if (ctab == sitetab)
   1401 		type = "SITE ";
   1402 	else
   1403 		type = "";
   1404 	width = 0, NCMDS = 0;
   1405 	for (c = ctab; c->name != NULL; c++) {
   1406 		int len = strlen(c->name);
   1407 
   1408 		if (len > width)
   1409 			width = len;
   1410 		NCMDS++;
   1411 	}
   1412 	width = (width + 8) &~ 7;
   1413 	if (s == 0) {
   1414 		int i, j, w;
   1415 		int columns, lines;
   1416 
   1417 		lreply(214, "The following %scommands are recognized.", type);
   1418 		lreply(0, "(`-' = not implemented, `+' = supports options)");
   1419 		columns = 76 / width;
   1420 		if (columns == 0)
   1421 			columns = 1;
   1422 		lines = (NCMDS + columns - 1) / columns;
   1423 		for (i = 0; i < lines; i++) {
   1424 			b = printf("   ");
   1425 			total_bytes += b;
   1426 			total_bytes_out += b;
   1427 			for (j = 0; j < columns; j++) {
   1428 				c = ctab + j * lines + i;
   1429 				b = printf("%s", c->name);
   1430 				total_bytes += b;
   1431 				total_bytes_out += b;
   1432 				w = strlen(c->name);
   1433 				if (! c->implemented) {
   1434 					putchar('-');
   1435 					w++;
   1436 				}
   1437 				if (c->hasopts) {
   1438 					putchar('+');
   1439 					w++;
   1440 				}
   1441 				if (c + lines >= &ctab[NCMDS])
   1442 					break;
   1443 				while (w < width) {
   1444 					putchar(' ');
   1445 					w++;
   1446 				}
   1447 			}
   1448 			b = printf("\r\n");
   1449 			total_bytes += b;
   1450 			total_bytes_out += b;
   1451 		}
   1452 		(void) fflush(stdout);
   1453 		reply(214, "Direct comments to ftp-bugs@%s.", hostname);
   1454 		return;
   1455 	}
   1456 	c = lookup(ctab, s);
   1457 	if (c == (struct tab *)0) {
   1458 		reply(502, "Unknown command %s.", s);
   1459 		return;
   1460 	}
   1461 	if (c->implemented)
   1462 		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
   1463 	else
   1464 		reply(214, "%s%-*s\t%s; not implemented.", type, width,
   1465 		    c->name, c->help);
   1466 }
   1467 
   1468 static void
   1469 sizecmd(filename)
   1470 	char *filename;
   1471 {
   1472 	switch (type) {
   1473 	case TYPE_L:
   1474 	case TYPE_I: {
   1475 		struct stat stbuf;
   1476 		if (stat(filename, &stbuf) < 0 || !S_ISREG(stbuf.st_mode))
   1477 			reply(550, "%s: not a plain file.", filename);
   1478 		else
   1479 			reply(213, "%qu", stbuf.st_size);
   1480 		break; }
   1481 	case TYPE_A: {
   1482 		FILE *fin;
   1483 		int c;
   1484 		off_t count;
   1485 		struct stat stbuf;
   1486 		fin = fopen(filename, "r");
   1487 		if (fin == NULL) {
   1488 			perror_reply(550, filename);
   1489 			return;
   1490 		}
   1491 		if (fstat(fileno(fin), &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) {
   1492 			reply(550, "%s: not a plain file.", filename);
   1493 			(void) fclose(fin);
   1494 			return;
   1495 		}
   1496 
   1497 		count = 0;
   1498 		while((c=getc(fin)) != EOF) {
   1499 			if (c == '\n')	/* will get expanded to \r\n */
   1500 				count++;
   1501 			count++;
   1502 		}
   1503 		(void) fclose(fin);
   1504 
   1505 		reply(213, "%qd", count);
   1506 		break; }
   1507 	default:
   1508 		reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
   1509 	}
   1510 }
   1511 
   1512 static void
   1513 opts(command)
   1514 	const char *command;
   1515 {
   1516 	struct tab *c;
   1517 	char *ep;
   1518 
   1519 	if ((ep = strchr(command, ' ')) != NULL)
   1520 		*ep++ = '\0';
   1521 	c = lookup(cmdtab, command);
   1522 	if (c == NULL) {
   1523 		reply(502, "Unknown command %s.", command);
   1524 		return;
   1525 	}
   1526 	if (c->implemented == 0) {
   1527 		reply(502, "%s command not implemented.", c->name);
   1528 		return;
   1529 	}
   1530 	if (c->hasopts == 0) {
   1531 		reply(501, "%s command does not support persistent options.",
   1532 		    c->name);
   1533 		return;
   1534 	}
   1535 
   1536 	if (ep != NULL && *ep != '\0') {
   1537 		if (c->options != NULL)
   1538 			free(c->options);
   1539 		c->options = xstrdup(ep);
   1540 	}
   1541 	if (c->options != NULL)
   1542 		reply(200, "Options for %s are '%s'.", c->name, c->options);
   1543 	else
   1544 		reply(200, "No options defined for %s.", c->name);
   1545 }
   1546