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