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