Home | History | Annotate | Line # | Download | only in ftpd
ftpcmd.y revision 1.11
      1 /*	$NetBSD: ftpcmd.y,v 1.11 1997/05/23 22:09:48 cjs 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 
     45 #ifndef lint
     46 #if 0
     47 static char sccsid[] = "@(#)ftpcmd.y	8.3 (Berkeley) 4/6/94";
     48 #else
     49 static char rcsid[] = "$NetBSD: ftpcmd.y,v 1.11 1997/05/23 22:09:48 cjs Exp $";
     50 #endif
     51 #endif /* not lint */
     52 
     53 #include <sys/param.h>
     54 #include <sys/socket.h>
     55 #include <sys/stat.h>
     56 
     57 #include <netinet/in.h>
     58 #include <arpa/ftp.h>
     59 
     60 #include <ctype.h>
     61 #include <errno.h>
     62 #include <glob.h>
     63 #include <pwd.h>
     64 #include <setjmp.h>
     65 #include <signal.h>
     66 #include <stdio.h>
     67 #include <stdlib.h>
     68 #include <string.h>
     69 #include <syslog.h>
     70 #include <time.h>
     71 #include <unistd.h>
     72 
     73 #include "extern.h"
     74 
     75 extern	struct sockaddr_in data_dest;
     76 extern	int logged_in;
     77 extern	struct passwd *pw;
     78 extern	int guest;
     79 extern	int logging;
     80 extern	int type;
     81 extern	int form;
     82 extern	int debug;
     83 extern	int timeout;
     84 extern	int maxtimeout;
     85 extern  int pdata;
     86 extern	char hostname[], remotehost[];
     87 extern	char proctitle[];
     88 extern	int usedefault;
     89 extern  int transflag;
     90 extern  char tmpline[];
     91 
     92 off_t	restart_point;
     93 
     94 static	int cmd_type;
     95 static	int cmd_form;
     96 static	int cmd_bytesz;
     97 char	cbuf[512];
     98 char	*fromname;
     99 
    100 %}
    101 
    102 %union {
    103 	int	i;
    104 	char   *s;
    105 }
    106 
    107 %token
    108 	A	B	C	E	F	I
    109 	L	N	P	R	S	T
    110 
    111 	SP	CRLF	COMMA
    112 
    113 	USER	PASS	ACCT	REIN	QUIT	PORT
    114 	PASV	TYPE	STRU	MODE	RETR	STOR
    115 	APPE	MLFL	MAIL	MSND	MSOM	MSAM
    116 	MRSQ	MRCP	ALLO	REST	RNFR	RNTO
    117 	ABOR	DELE	CWD	LIST	NLST	SITE
    118 	STAT	HELP	NOOP	MKD	RMD	PWD
    119 	CDUP	STOU	SMNT	SYST	SIZE	MDTM
    120 
    121 	UMASK	IDLE	CHMOD
    122 
    123 	LEXERR
    124 
    125 %token	<s> STRING
    126 %token	<i> NUMBER
    127 
    128 %type	<i> check_login check_login_noguest octal_number byte_size
    129 %type	<i> struct_code mode_code type_code form_code
    130 %type	<s> pathstring pathname password username
    131 
    132 %start	cmd_list
    133 
    134 %%
    135 
    136 cmd_list
    137 	: /* empty */
    138 	| cmd_list cmd
    139 		{
    140 			fromname = (char *) 0;
    141 			restart_point = (off_t) 0;
    142 		}
    143 	| cmd_list rcmd
    144 	;
    145 
    146 cmd
    147 	: USER SP username CRLF
    148 		{
    149 			user($3);
    150 			free($3);
    151 		}
    152 	| PASS SP password CRLF
    153 		{
    154 			pass($3);
    155 			free($3);
    156 		}
    157 	| PORT SP host_port CRLF
    158 		{
    159 			usedefault = 0;
    160 			if (pdata >= 0) {
    161 				(void) close(pdata);
    162 				pdata = -1;
    163 			}
    164 			reply(200, "PORT command successful.");
    165 		}
    166 	| PASV CRLF
    167 		{
    168 			passive();
    169 		}
    170 	| TYPE SP type_code CRLF
    171 		{
    172 			switch (cmd_type) {
    173 
    174 			case TYPE_A:
    175 				if (cmd_form == FORM_N) {
    176 					reply(200, "Type set to A.");
    177 					type = cmd_type;
    178 					form = cmd_form;
    179 				} else
    180 					reply(504, "Form must be N.");
    181 				break;
    182 
    183 			case TYPE_E:
    184 				reply(504, "Type E not implemented.");
    185 				break;
    186 
    187 			case TYPE_I:
    188 				reply(200, "Type set to I.");
    189 				type = cmd_type;
    190 				break;
    191 
    192 			case TYPE_L:
    193 #if NBBY == 8
    194 				if (cmd_bytesz == 8) {
    195 					reply(200,
    196 					    "Type set to L (byte size 8).");
    197 					type = cmd_type;
    198 				} else
    199 					reply(504, "Byte size must be 8.");
    200 #else /* NBBY == 8 */
    201 				UNIMPLEMENTED for NBBY != 8
    202 #endif /* NBBY == 8 */
    203 			}
    204 		}
    205 	| STRU SP struct_code CRLF
    206 		{
    207 			switch ($3) {
    208 
    209 			case STRU_F:
    210 				reply(200, "STRU F ok.");
    211 				break;
    212 
    213 			default:
    214 				reply(504, "Unimplemented STRU type.");
    215 			}
    216 		}
    217 	| MODE SP mode_code CRLF
    218 		{
    219 			switch ($3) {
    220 
    221 			case MODE_S:
    222 				reply(200, "MODE S ok.");
    223 				break;
    224 
    225 			default:
    226 				reply(502, "Unimplemented MODE type.");
    227 			}
    228 		}
    229 	| ALLO SP NUMBER CRLF
    230 		{
    231 			reply(202, "ALLO command ignored.");
    232 		}
    233 	| ALLO SP NUMBER SP R SP NUMBER CRLF
    234 		{
    235 			reply(202, "ALLO command ignored.");
    236 		}
    237 	| RETR check_login SP pathname CRLF
    238 		{
    239 			if ($2 && $4 != NULL)
    240 				retrieve((char *) 0, $4);
    241 			if ($4 != NULL)
    242 				free($4);
    243 		}
    244 	| STOR check_login SP pathname CRLF
    245 		{
    246 			if ($2 && $4 != NULL)
    247 				store($4, "w", 0);
    248 			if ($4 != NULL)
    249 				free($4);
    250 		}
    251 	| APPE check_login SP pathname CRLF
    252 		{
    253 			if ($2 && $4 != NULL)
    254 				store($4, "a", 0);
    255 			if ($4 != NULL)
    256 				free($4);
    257 		}
    258 	| NLST check_login CRLF
    259 		{
    260 			if ($2)
    261 				send_file_list(".");
    262 		}
    263 	| NLST check_login SP STRING CRLF
    264 		{
    265 			if ($2 && $4 != NULL)
    266 				send_file_list($4);
    267 			if ($4 != NULL)
    268 				free($4);
    269 		}
    270 	| LIST check_login CRLF
    271 		{
    272 			if ($2)
    273 				retrieve("/bin/ls -lgA", "");
    274 		}
    275 	| LIST check_login SP pathname CRLF
    276 		{
    277 			if ($2 && $4 != NULL)
    278 				retrieve("/bin/ls -lgA %s", $4);
    279 			if ($4 != NULL)
    280 				free($4);
    281 		}
    282 	| STAT check_login SP pathname CRLF
    283 		{
    284 			if ($2 && $4 != NULL)
    285 				statfilecmd($4);
    286 			if ($4 != NULL)
    287 				free($4);
    288 		}
    289 	| STAT CRLF
    290 		{
    291 			statcmd();
    292 		}
    293 	| DELE check_login_noguest SP pathname CRLF
    294 		{
    295 			if ($2 && $4 != NULL)
    296 				delete($4);
    297 			if ($4 != NULL)
    298 				free($4);
    299 		}
    300 	| RNTO SP pathname CRLF
    301 		{
    302 			if (fromname) {
    303 				renamecmd(fromname, $3);
    304 				free(fromname);
    305 				fromname = (char *) 0;
    306 			} else {
    307 				reply(503, "Bad sequence of commands.");
    308 			}
    309 			free($3);
    310 		}
    311 	| ABOR CRLF
    312 		{
    313 			reply(225, "ABOR command successful.");
    314 		}
    315 	| CWD check_login CRLF
    316 		{
    317 			if ($2)
    318 				cwd(pw->pw_dir);
    319 		}
    320 	| CWD check_login SP pathname CRLF
    321 		{
    322 			if ($2 && $4 != NULL)
    323 				cwd($4);
    324 			if ($4 != NULL)
    325 				free($4);
    326 		}
    327 	| HELP CRLF
    328 		{
    329 			help(cmdtab, (char *) 0);
    330 		}
    331 	| HELP SP STRING CRLF
    332 		{
    333 			char *cp = $3;
    334 
    335 			if (strncasecmp(cp, "SITE", 4) == 0) {
    336 				cp = $3 + 4;
    337 				if (*cp == ' ')
    338 					cp++;
    339 				if (*cp)
    340 					help(sitetab, cp);
    341 				else
    342 					help(sitetab, (char *) 0);
    343 			} else
    344 				help(cmdtab, $3);
    345 		}
    346 	| NOOP CRLF
    347 		{
    348 			reply(200, "NOOP command successful.");
    349 		}
    350 	| MKD check_login_noguest SP pathname CRLF
    351 		{
    352 			if ($2 && $4 != NULL)
    353 				makedir($4);
    354 			if ($4 != NULL)
    355 				free($4);
    356 		}
    357 	| RMD check_login_noguest SP pathname CRLF
    358 		{
    359 			if ($2 && $4 != NULL)
    360 				removedir($4);
    361 			if ($4 != NULL)
    362 				free($4);
    363 		}
    364 	| PWD check_login CRLF
    365 		{
    366 			if ($2)
    367 				pwd();
    368 		}
    369 	| CDUP check_login CRLF
    370 		{
    371 			if ($2)
    372 				cwd("..");
    373 		}
    374 	| SITE SP HELP CRLF
    375 		{
    376 			help(sitetab, (char *) 0);
    377 		}
    378 	| SITE SP HELP SP STRING CRLF
    379 		{
    380 			help(sitetab, $5);
    381 		}
    382 	| SITE SP UMASK check_login CRLF
    383 		{
    384 			int oldmask;
    385 
    386 			if ($4) {
    387 				oldmask = umask(0);
    388 				(void) umask(oldmask);
    389 				reply(200, "Current UMASK is %03o", oldmask);
    390 			}
    391 		}
    392 	| SITE SP UMASK check_login_noguest SP octal_number CRLF
    393 		{
    394 			int oldmask;
    395 
    396 			if ($4) {
    397 				if (($6 == -1) || ($6 > 0777)) {
    398 					reply(501, "Bad UMASK value");
    399 				} else {
    400 					oldmask = umask($6);
    401 					reply(200,
    402 					    "UMASK set to %03o (was %03o)",
    403 					    $6, oldmask);
    404 				}
    405 			}
    406 		}
    407 	| SITE SP CHMOD check_login_noguest SP octal_number SP pathname CRLF
    408 		{
    409 			if ($4 && ($8 != NULL)) {
    410 				if ($6 > 0777)
    411 					reply(501,
    412 				"CHMOD: Mode value must be between 0 and 0777");
    413 				else if (chmod($8, $6) < 0)
    414 					perror_reply(550, $8);
    415 				else
    416 					reply(200, "CHMOD command successful.");
    417 			}
    418 			if ($8 != NULL)
    419 				free($8);
    420 		}
    421 	| SITE SP IDLE CRLF
    422 		{
    423 			reply(200,
    424 			    "Current IDLE time limit is %d seconds; max %d",
    425 				timeout, maxtimeout);
    426 		}
    427 	| SITE SP IDLE SP NUMBER CRLF
    428 		{
    429 			if ($5 < 30 || $5 > maxtimeout) {
    430 				reply(501,
    431 			"Maximum IDLE time must be between 30 and %d seconds",
    432 				    maxtimeout);
    433 			} else {
    434 				timeout = $5;
    435 				(void) alarm((unsigned) timeout);
    436 				reply(200,
    437 				    "Maximum IDLE time set to %d seconds",
    438 				    timeout);
    439 			}
    440 		}
    441 	| STOU check_login SP pathname CRLF
    442 		{
    443 			if ($2 && $4 != NULL)
    444 				store($4, "w", 1);
    445 			if ($4 != NULL)
    446 				free($4);
    447 		}
    448 	| SYST CRLF
    449 		{
    450 #ifdef unix
    451 #ifdef BSD
    452 			reply(215, "UNIX Type: L%d Version: BSD-%d",
    453 				NBBY, BSD);
    454 #else /* BSD */
    455 			reply(215, "UNIX Type: L%d", NBBY);
    456 #endif /* BSD */
    457 #else /* unix */
    458 			reply(215, "UNKNOWN Type: L%d", NBBY);
    459 #endif /* unix */
    460 		}
    461 
    462 		/*
    463 		 * SIZE is not in RFC959, but Postel has blessed it and
    464 		 * it will be in the updated RFC.
    465 		 *
    466 		 * Return size of file in a format suitable for
    467 		 * using with RESTART (we just count bytes).
    468 		 */
    469 	| SIZE check_login SP pathname CRLF
    470 		{
    471 			if ($2 && $4 != NULL)
    472 				sizecmd($4);
    473 			if ($4 != NULL)
    474 				free($4);
    475 		}
    476 
    477 		/*
    478 		 * MDTM is not in RFC959, but Postel has blessed it and
    479 		 * it will be in the updated RFC.
    480 		 *
    481 		 * Return modification time of file as an ISO 3307
    482 		 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
    483 		 * where xxx is the fractional second (of any precision,
    484 		 * not necessarily 3 digits)
    485 		 */
    486 	| MDTM check_login SP pathname CRLF
    487 		{
    488 			if ($2 && $4 != NULL) {
    489 				struct stat stbuf;
    490 				if (stat($4, &stbuf) < 0)
    491 					reply(550, "%s: %s",
    492 					    $4, strerror(errno));
    493 				else if (!S_ISREG(stbuf.st_mode)) {
    494 					reply(550, "%s: not a plain file.", $4);
    495 				} else {
    496 					struct tm *t;
    497 					t = gmtime(&stbuf.st_mtime);
    498 					reply(213,
    499 					    "%04d%02d%02d%02d%02d%02d",
    500 					    1900 + t->tm_year,
    501 					    t->tm_mon+1, t->tm_mday,
    502 					    t->tm_hour, t->tm_min, t->tm_sec);
    503 				}
    504 			}
    505 			if ($4 != NULL)
    506 				free($4);
    507 		}
    508 	| QUIT CRLF
    509 		{
    510 			reply(221, "Goodbye.");
    511 			dologout(0);
    512 		}
    513 	| error CRLF
    514 		{
    515 			yyerrok;
    516 		}
    517 	;
    518 rcmd
    519 	: RNFR check_login SP pathname CRLF
    520 		{
    521 			char *renamefrom();
    522 
    523 			restart_point = (off_t) 0;
    524 			if ($2 && $4) {
    525 				fromname = renamefrom($4);
    526 				if (fromname == (char *) 0 && $4) {
    527 					free($4);
    528 				}
    529 			}
    530 		}
    531 	| REST SP byte_size CRLF
    532 		{
    533 			fromname = (char *) 0;
    534 			restart_point = $3;	/* XXX $3 is only "int" */
    535 			reply(350, "Restarting at %qd. %s", restart_point,
    536 			    "Send STORE or RETRIEVE to initiate transfer.");
    537 		}
    538 	;
    539 
    540 username
    541 	: STRING
    542 	;
    543 
    544 password
    545 	: /* empty */
    546 		{
    547 			$$ = (char *)calloc(1, sizeof(char));
    548 		}
    549 	| STRING
    550 	;
    551 
    552 byte_size
    553 	: NUMBER
    554 	;
    555 
    556 host_port
    557 	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
    558 		NUMBER COMMA NUMBER
    559 		{
    560 			char *a, *p;
    561 
    562 			data_dest.sin_len = sizeof(struct sockaddr_in);
    563 			data_dest.sin_family = AF_INET;
    564 			p = (char *)&data_dest.sin_port;
    565 			p[0] = $9; p[1] = $11;
    566 			a = (char *)&data_dest.sin_addr;
    567 			a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7;
    568 		}
    569 	;
    570 
    571 form_code
    572 	: N
    573 		{
    574 			$$ = FORM_N;
    575 		}
    576 	| T
    577 		{
    578 			$$ = FORM_T;
    579 		}
    580 	| C
    581 		{
    582 			$$ = FORM_C;
    583 		}
    584 	;
    585 
    586 type_code
    587 	: A
    588 		{
    589 			cmd_type = TYPE_A;
    590 			cmd_form = FORM_N;
    591 		}
    592 	| A SP form_code
    593 		{
    594 			cmd_type = TYPE_A;
    595 			cmd_form = $3;
    596 		}
    597 	| E
    598 		{
    599 			cmd_type = TYPE_E;
    600 			cmd_form = FORM_N;
    601 		}
    602 	| E SP form_code
    603 		{
    604 			cmd_type = TYPE_E;
    605 			cmd_form = $3;
    606 		}
    607 	| I
    608 		{
    609 			cmd_type = TYPE_I;
    610 		}
    611 	| L
    612 		{
    613 			cmd_type = TYPE_L;
    614 			cmd_bytesz = NBBY;
    615 		}
    616 	| L SP byte_size
    617 		{
    618 			cmd_type = TYPE_L;
    619 			cmd_bytesz = $3;
    620 		}
    621 		/* this is for a bug in the BBN ftp */
    622 	| L byte_size
    623 		{
    624 			cmd_type = TYPE_L;
    625 			cmd_bytesz = $2;
    626 		}
    627 	;
    628 
    629 struct_code
    630 	: F
    631 		{
    632 			$$ = STRU_F;
    633 		}
    634 	| R
    635 		{
    636 			$$ = STRU_R;
    637 		}
    638 	| P
    639 		{
    640 			$$ = STRU_P;
    641 		}
    642 	;
    643 
    644 mode_code
    645 	: S
    646 		{
    647 			$$ = MODE_S;
    648 		}
    649 	| B
    650 		{
    651 			$$ = MODE_B;
    652 		}
    653 	| C
    654 		{
    655 			$$ = MODE_C;
    656 		}
    657 	;
    658 
    659 pathname
    660 	: pathstring
    661 		{
    662 			/*
    663 			 * Problem: this production is used for all pathname
    664 			 * processing, but only gives a 550 error reply.
    665 			 * This is a valid reply in some cases but not in
    666 			 * others.
    667 			 */
    668 			if (logged_in && $1 && *$1 == '~') {
    669 				glob_t gl;
    670 				int flags =
    671 				 GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE;
    672 
    673 				if ($1[1] == '\0')
    674 					$$ = strdup(pw->pw_dir);
    675 				else {
    676 					memset(&gl, 0, sizeof(gl));
    677 					if (glob($1, flags, NULL, &gl) ||
    678 					    gl.gl_pathc == 0) {
    679 						reply(550, "not found");
    680 						$$ = NULL;
    681 					} else
    682 						$$ = strdup(gl.gl_pathv[0]);
    683 					globfree(&gl);
    684 				}
    685 				free($1);
    686 			} else
    687 				$$ = $1;
    688 		}
    689 	;
    690 
    691 pathstring
    692 	: STRING
    693 	;
    694 
    695 octal_number
    696 	: NUMBER
    697 		{
    698 			int ret, dec, multby, digit;
    699 
    700 			/*
    701 			 * Convert a number that was read as decimal number
    702 			 * to what it would be if it had been read as octal.
    703 			 */
    704 			dec = $1;
    705 			multby = 1;
    706 			ret = 0;
    707 			while (dec) {
    708 				digit = dec%10;
    709 				if (digit > 7) {
    710 					ret = -1;
    711 					break;
    712 				}
    713 				ret += digit * multby;
    714 				multby *= 8;
    715 				dec /= 10;
    716 			}
    717 			$$ = ret;
    718 		}
    719 	;
    720 
    721 
    722 check_login
    723 	: /* empty */
    724 		{
    725 			if (logged_in)
    726 				$$ = 1;
    727 			else {
    728 				reply(530, "Please login with USER and PASS.");
    729 				$$ = 0;
    730 			}
    731 		}
    732 	;
    733 check_login_noguest
    734 	: /* empty */
    735 		{
    736 			if (logged_in)  {
    737 #ifndef INSECURE_GUEST
    738 				if (guest)  {
    739 					reply(502,
    740 				    "Guest users may not use this command.");
    741 					$$ = 0;
    742 				} else
    743 #endif
    744 				$$ = 1;
    745 			} else {
    746 				reply(530, "Please login with USER and PASS.");
    747 				$$ = 0;
    748 			}
    749 		}
    750 
    751 %%
    752 
    753 extern jmp_buf errcatch;
    754 
    755 #define	CMD	0	/* beginning of command */
    756 #define	ARGS	1	/* expect miscellaneous arguments */
    757 #define	STR1	2	/* expect SP followed by STRING */
    758 #define	STR2	3	/* expect STRING */
    759 #define	OSTR	4	/* optional SP then STRING */
    760 #define	ZSTR1	5	/* SP then optional STRING */
    761 #define	ZSTR2	6	/* optional STRING after SP */
    762 #define	SITECMD	7	/* SITE command */
    763 #define	NSTR	8	/* Number followed by a string */
    764 
    765 struct tab {
    766 	char	*name;
    767 	short	token;
    768 	short	state;
    769 	short	implemented;	/* 1 if command is implemented */
    770 	char	*help;
    771 };
    772 
    773 struct tab cmdtab[] = {		/* In order defined in RFC 765 */
    774 	{ "USER", USER, STR1, 1,	"<sp> username" },
    775 	{ "PASS", PASS, ZSTR1, 1,	"<sp> password" },
    776 	{ "ACCT", ACCT, STR1, 0,	"(specify account)" },
    777 	{ "SMNT", SMNT, ARGS, 0,	"(structure mount)" },
    778 	{ "REIN", REIN, ARGS, 0,	"(reinitialize server state)" },
    779 	{ "QUIT", QUIT, ARGS, 1,	"(terminate service)", },
    780 	{ "PORT", PORT, ARGS, 1,	"<sp> b0, b1, b2, b3, b4" },
    781 	{ "PASV", PASV, ARGS, 1,	"(set server in passive mode)" },
    782 	{ "TYPE", TYPE, ARGS, 1,	"<sp> [ A | E | I | L ]" },
    783 	{ "STRU", STRU, ARGS, 1,	"(specify file structure)" },
    784 	{ "MODE", MODE, ARGS, 1,	"(specify transfer mode)" },
    785 	{ "RETR", RETR, STR1, 1,	"<sp> file-name" },
    786 	{ "STOR", STOR, STR1, 1,	"<sp> file-name" },
    787 	{ "APPE", APPE, STR1, 1,	"<sp> file-name" },
    788 	{ "MLFL", MLFL, OSTR, 0,	"(mail file)" },
    789 	{ "MAIL", MAIL, OSTR, 0,	"(mail to user)" },
    790 	{ "MSND", MSND, OSTR, 0,	"(mail send to terminal)" },
    791 	{ "MSOM", MSOM, OSTR, 0,	"(mail send to terminal or mailbox)" },
    792 	{ "MSAM", MSAM, OSTR, 0,	"(mail send to terminal and mailbox)" },
    793 	{ "MRSQ", MRSQ, OSTR, 0,	"(mail recipient scheme question)" },
    794 	{ "MRCP", MRCP, STR1, 0,	"(mail recipient)" },
    795 	{ "ALLO", ALLO, ARGS, 1,	"allocate storage (vacuously)" },
    796 	{ "REST", REST, ARGS, 1,	"<sp> offset (restart command)" },
    797 	{ "RNFR", RNFR, STR1, 1,	"<sp> file-name" },
    798 	{ "RNTO", RNTO, STR1, 1,	"<sp> file-name" },
    799 	{ "ABOR", ABOR, ARGS, 1,	"(abort operation)" },
    800 	{ "DELE", DELE, STR1, 1,	"<sp> file-name" },
    801 	{ "CWD",  CWD,  OSTR, 1,	"[ <sp> directory-name ]" },
    802 	{ "XCWD", CWD,	OSTR, 1,	"[ <sp> directory-name ]" },
    803 	{ "LIST", LIST, OSTR, 1,	"[ <sp> path-name ]" },
    804 	{ "NLST", NLST, OSTR, 1,	"[ <sp> path-name ]" },
    805 	{ "SITE", SITE, SITECMD, 1,	"site-cmd [ <sp> arguments ]" },
    806 	{ "SYST", SYST, ARGS, 1,	"(get type of operating system)" },
    807 	{ "STAT", STAT, OSTR, 1,	"[ <sp> path-name ]" },
    808 	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
    809 	{ "NOOP", NOOP, ARGS, 1,	"" },
    810 	{ "MKD",  MKD,  STR1, 1,	"<sp> path-name" },
    811 	{ "XMKD", MKD,  STR1, 1,	"<sp> path-name" },
    812 	{ "RMD",  RMD,  STR1, 1,	"<sp> path-name" },
    813 	{ "XRMD", RMD,  STR1, 1,	"<sp> path-name" },
    814 	{ "PWD",  PWD,  ARGS, 1,	"(return current directory)" },
    815 	{ "XPWD", PWD,  ARGS, 1,	"(return current directory)" },
    816 	{ "CDUP", CDUP, ARGS, 1,	"(change to parent directory)" },
    817 	{ "XCUP", CDUP, ARGS, 1,	"(change to parent directory)" },
    818 	{ "STOU", STOU, STR1, 1,	"<sp> file-name" },
    819 	{ "SIZE", SIZE, OSTR, 1,	"<sp> path-name" },
    820 	{ "MDTM", MDTM, OSTR, 1,	"<sp> path-name" },
    821 	{ NULL,   0,    0,    0,	0 }
    822 };
    823 
    824 struct tab sitetab[] = {
    825 	{ "UMASK", UMASK, ARGS, 1,	"[ <sp> umask ]" },
    826 	{ "IDLE", IDLE, ARGS, 1,	"[ <sp> maximum-idle-time ]" },
    827 	{ "CHMOD", CHMOD, NSTR, 1,	"<sp> mode <sp> file-name" },
    828 	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
    829 	{ NULL,   0,    0,    0,	0 }
    830 };
    831 
    832 static char	*copy __P((char *));
    833 static void	 help __P((struct tab *, char *));
    834 static struct tab *
    835 		 lookup __P((struct tab *, char *));
    836 static void	 sizecmd __P((char *));
    837 static void	 toolong __P((int));
    838 static int	 yylex __P((void));
    839 
    840 static struct tab *
    841 lookup(p, cmd)
    842 	struct tab *p;
    843 	char *cmd;
    844 {
    845 
    846 	for (; p->name != NULL; p++)
    847 		if (strcmp(cmd, p->name) == 0)
    848 			return (p);
    849 	return (0);
    850 }
    851 
    852 #include <arpa/telnet.h>
    853 
    854 /*
    855  * getline - a hacked up version of fgets to ignore TELNET escape codes.
    856  */
    857 char *
    858 getline(s, n, iop)
    859 	char *s;
    860 	int n;
    861 	FILE *iop;
    862 {
    863 	int c;
    864 	register char *cs;
    865 
    866 	cs = s;
    867 /* tmpline may contain saved command from urgent mode interruption */
    868 	for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
    869 		*cs++ = tmpline[c];
    870 		if (tmpline[c] == '\n') {
    871 			*cs++ = '\0';
    872 			if (debug)
    873 				syslog(LOG_DEBUG, "command: %s", s);
    874 			tmpline[0] = '\0';
    875 			return(s);
    876 		}
    877 		if (c == 0)
    878 			tmpline[0] = '\0';
    879 	}
    880 	while ((c = getc(iop)) != EOF) {
    881 		c &= 0377;
    882 		if (c == IAC) {
    883 		    if ((c = getc(iop)) != EOF) {
    884 			c &= 0377;
    885 			switch (c) {
    886 			case WILL:
    887 			case WONT:
    888 				c = getc(iop);
    889 				printf("%c%c%c", IAC, DONT, 0377&c);
    890 				(void) fflush(stdout);
    891 				continue;
    892 			case DO:
    893 			case DONT:
    894 				c = getc(iop);
    895 				printf("%c%c%c", IAC, WONT, 0377&c);
    896 				(void) fflush(stdout);
    897 				continue;
    898 			case IAC:
    899 				break;
    900 			default:
    901 				continue;	/* ignore command */
    902 			}
    903 		    }
    904 		}
    905 		*cs++ = c;
    906 		if (--n <= 0 || c == '\n')
    907 			break;
    908 	}
    909 	if (c == EOF && cs == s)
    910 		return (NULL);
    911 	*cs++ = '\0';
    912 	if (debug) {
    913 		if (!guest && strncasecmp("pass ", s, 5) == 0) {
    914 			/* Don't syslog passwords */
    915 			syslog(LOG_DEBUG, "command: %.5s ???", s);
    916 		} else {
    917 			register char *cp;
    918 			register int len;
    919 
    920 			/* Don't syslog trailing CR-LF */
    921 			len = strlen(s);
    922 			cp = s + len - 1;
    923 			while (cp >= s && (*cp == '\n' || *cp == '\r')) {
    924 				--cp;
    925 				--len;
    926 			}
    927 			syslog(LOG_DEBUG, "command: %.*s", len, s);
    928 		}
    929 	}
    930 	return (s);
    931 }
    932 
    933 static void
    934 toolong(signo)
    935 	int signo;
    936 {
    937 
    938 	reply(421,
    939 	    "Timeout (%d seconds): closing control connection.", timeout);
    940 	if (logging)
    941 		syslog(LOG_INFO, "User %s timed out after %d seconds",
    942 		    (pw ? pw -> pw_name : "unknown"), timeout);
    943 	dologout(1);
    944 }
    945 
    946 static int
    947 yylex()
    948 {
    949 	static int cpos, state;
    950 	char *cp, *cp2;
    951 	struct tab *p;
    952 	int n;
    953 	char c;
    954 
    955 	for (;;) {
    956 		switch (state) {
    957 
    958 		case CMD:
    959 			(void) signal(SIGALRM, toolong);
    960 			(void) alarm((unsigned) timeout);
    961 			if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
    962 				reply(221, "You could at least say goodbye.");
    963 				dologout(0);
    964 			}
    965 			(void) alarm(0);
    966 #ifdef HASSETPROCTITLE
    967 			if (strncasecmp(cbuf, "PASS", 4) != 0)
    968 				setproctitle("%s: %s", proctitle, cbuf);
    969 #endif /* HASSETPROCTITLE */
    970 			if ((cp = strchr(cbuf, '\r'))) {
    971 				*cp++ = '\n';
    972 				*cp = '\0';
    973 			}
    974 			if ((cp = strpbrk(cbuf, " \n")))
    975 				cpos = cp - cbuf;
    976 			if (cpos == 0)
    977 				cpos = 4;
    978 			c = cbuf[cpos];
    979 			cbuf[cpos] = '\0';
    980 			upper(cbuf);
    981 			p = lookup(cmdtab, cbuf);
    982 			cbuf[cpos] = c;
    983 			if (p != 0) {
    984 				if (p->implemented == 0) {
    985 					nack(p->name);
    986 					longjmp(errcatch,0);
    987 					/* NOTREACHED */
    988 				}
    989 				state = p->state;
    990 				yylval.s = p->name;
    991 				return (p->token);
    992 			}
    993 			break;
    994 
    995 		case SITECMD:
    996 			if (cbuf[cpos] == ' ') {
    997 				cpos++;
    998 				return (SP);
    999 			}
   1000 			cp = &cbuf[cpos];
   1001 			if ((cp2 = strpbrk(cp, " \n")))
   1002 				cpos = cp2 - cbuf;
   1003 			c = cbuf[cpos];
   1004 			cbuf[cpos] = '\0';
   1005 			upper(cp);
   1006 			p = lookup(sitetab, cp);
   1007 			cbuf[cpos] = c;
   1008 			if (p != 0) {
   1009 				if (p->implemented == 0) {
   1010 					state = CMD;
   1011 					nack(p->name);
   1012 					longjmp(errcatch,0);
   1013 					/* NOTREACHED */
   1014 				}
   1015 				state = p->state;
   1016 				yylval.s = p->name;
   1017 				return (p->token);
   1018 			}
   1019 			state = CMD;
   1020 			break;
   1021 
   1022 		case OSTR:
   1023 			if (cbuf[cpos] == '\n') {
   1024 				state = CMD;
   1025 				return (CRLF);
   1026 			}
   1027 			/* FALLTHROUGH */
   1028 
   1029 		case STR1:
   1030 		case ZSTR1:
   1031 		dostr1:
   1032 			if (cbuf[cpos] == ' ') {
   1033 				cpos++;
   1034 				state = state == OSTR ? STR2 : ++state;
   1035 				return (SP);
   1036 			}
   1037 			break;
   1038 
   1039 		case ZSTR2:
   1040 			if (cbuf[cpos] == '\n') {
   1041 				state = CMD;
   1042 				return (CRLF);
   1043 			}
   1044 			/* FALLTHROUGH */
   1045 
   1046 		case STR2:
   1047 			cp = &cbuf[cpos];
   1048 			n = strlen(cp);
   1049 			cpos += n - 1;
   1050 			/*
   1051 			 * Make sure the string is nonempty and \n terminated.
   1052 			 */
   1053 			if (n > 1 && cbuf[cpos] == '\n') {
   1054 				cbuf[cpos] = '\0';
   1055 				yylval.s = copy(cp);
   1056 				cbuf[cpos] = '\n';
   1057 				state = ARGS;
   1058 				return (STRING);
   1059 			}
   1060 			break;
   1061 
   1062 		case NSTR:
   1063 			if (cbuf[cpos] == ' ') {
   1064 				cpos++;
   1065 				return (SP);
   1066 			}
   1067 			if (isdigit(cbuf[cpos])) {
   1068 				cp = &cbuf[cpos];
   1069 				while (isdigit(cbuf[++cpos]))
   1070 					;
   1071 				c = cbuf[cpos];
   1072 				cbuf[cpos] = '\0';
   1073 				yylval.i = atoi(cp);
   1074 				cbuf[cpos] = c;
   1075 				state = STR1;
   1076 				return (NUMBER);
   1077 			}
   1078 			state = STR1;
   1079 			goto dostr1;
   1080 
   1081 		case ARGS:
   1082 			if (isdigit(cbuf[cpos])) {
   1083 				cp = &cbuf[cpos];
   1084 				while (isdigit(cbuf[++cpos]))
   1085 					;
   1086 				c = cbuf[cpos];
   1087 				cbuf[cpos] = '\0';
   1088 				yylval.i = atoi(cp);
   1089 				cbuf[cpos] = c;
   1090 				return (NUMBER);
   1091 			}
   1092 			switch (cbuf[cpos++]) {
   1093 
   1094 			case '\n':
   1095 				state = CMD;
   1096 				return (CRLF);
   1097 
   1098 			case ' ':
   1099 				return (SP);
   1100 
   1101 			case ',':
   1102 				return (COMMA);
   1103 
   1104 			case 'A':
   1105 			case 'a':
   1106 				return (A);
   1107 
   1108 			case 'B':
   1109 			case 'b':
   1110 				return (B);
   1111 
   1112 			case 'C':
   1113 			case 'c':
   1114 				return (C);
   1115 
   1116 			case 'E':
   1117 			case 'e':
   1118 				return (E);
   1119 
   1120 			case 'F':
   1121 			case 'f':
   1122 				return (F);
   1123 
   1124 			case 'I':
   1125 			case 'i':
   1126 				return (I);
   1127 
   1128 			case 'L':
   1129 			case 'l':
   1130 				return (L);
   1131 
   1132 			case 'N':
   1133 			case 'n':
   1134 				return (N);
   1135 
   1136 			case 'P':
   1137 			case 'p':
   1138 				return (P);
   1139 
   1140 			case 'R':
   1141 			case 'r':
   1142 				return (R);
   1143 
   1144 			case 'S':
   1145 			case 's':
   1146 				return (S);
   1147 
   1148 			case 'T':
   1149 			case 't':
   1150 				return (T);
   1151 
   1152 			}
   1153 			break;
   1154 
   1155 		default:
   1156 			fatal("Unknown state in scanner.");
   1157 		}
   1158 		yyerror((char *) 0);
   1159 		state = CMD;
   1160 		longjmp(errcatch,0);
   1161 	}
   1162 }
   1163 
   1164 void
   1165 upper(s)
   1166 	char *s;
   1167 {
   1168 	while (*s != '\0') {
   1169 		*s = toupper(*s);
   1170 		s++;
   1171 	}
   1172 }
   1173 
   1174 static char *
   1175 copy(s)
   1176 	char *s;
   1177 {
   1178 	char *p;
   1179 
   1180 	p = malloc((unsigned) strlen(s) + 1);
   1181 	if (p == NULL)
   1182 		fatal("Ran out of memory.");
   1183 	(void) strcpy(p, s);
   1184 	return (p);
   1185 }
   1186 
   1187 static void
   1188 help(ctab, s)
   1189 	struct tab *ctab;
   1190 	char *s;
   1191 {
   1192 	struct tab *c;
   1193 	int width, NCMDS;
   1194 	char *type;
   1195 
   1196 	if (ctab == sitetab)
   1197 		type = "SITE ";
   1198 	else
   1199 		type = "";
   1200 	width = 0, NCMDS = 0;
   1201 	for (c = ctab; c->name != NULL; c++) {
   1202 		int len = strlen(c->name);
   1203 
   1204 		if (len > width)
   1205 			width = len;
   1206 		NCMDS++;
   1207 	}
   1208 	width = (width + 8) &~ 7;
   1209 	if (s == 0) {
   1210 		int i, j, w;
   1211 		int columns, lines;
   1212 
   1213 		lreply(214, "The following %scommands are recognized %s.",
   1214 		    type, "(* =>'s unimplemented)");
   1215 		columns = 76 / width;
   1216 		if (columns == 0)
   1217 			columns = 1;
   1218 		lines = (NCMDS + columns - 1) / columns;
   1219 		for (i = 0; i < lines; i++) {
   1220 			printf("   ");
   1221 			for (j = 0; j < columns; j++) {
   1222 				c = ctab + j * lines + i;
   1223 				printf("%s%c", c->name,
   1224 					c->implemented ? ' ' : '*');
   1225 				if (c + lines >= &ctab[NCMDS])
   1226 					break;
   1227 				w = strlen(c->name) + 1;
   1228 				while (w < width) {
   1229 					putchar(' ');
   1230 					w++;
   1231 				}
   1232 			}
   1233 			printf("\r\n");
   1234 		}
   1235 		(void) fflush(stdout);
   1236 		reply(214, "Direct comments to ftp-bugs@%s.", hostname);
   1237 		return;
   1238 	}
   1239 	upper(s);
   1240 	c = lookup(ctab, s);
   1241 	if (c == (struct tab *)0) {
   1242 		reply(502, "Unknown command %s.", s);
   1243 		return;
   1244 	}
   1245 	if (c->implemented)
   1246 		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
   1247 	else
   1248 		reply(214, "%s%-*s\t%s; unimplemented.", type, width,
   1249 		    c->name, c->help);
   1250 }
   1251 
   1252 static void
   1253 sizecmd(filename)
   1254 	char *filename;
   1255 {
   1256 	switch (type) {
   1257 	case TYPE_L:
   1258 	case TYPE_I: {
   1259 		struct stat stbuf;
   1260 		if (stat(filename, &stbuf) < 0 || !S_ISREG(stbuf.st_mode))
   1261 			reply(550, "%s: not a plain file.", filename);
   1262 		else
   1263 			reply(213, "%qu", stbuf.st_size);
   1264 		break; }
   1265 	case TYPE_A: {
   1266 		FILE *fin;
   1267 		int c;
   1268 		off_t count;
   1269 		struct stat stbuf;
   1270 		fin = fopen(filename, "r");
   1271 		if (fin == NULL) {
   1272 			perror_reply(550, filename);
   1273 			return;
   1274 		}
   1275 		if (fstat(fileno(fin), &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) {
   1276 			reply(550, "%s: not a plain file.", filename);
   1277 			(void) fclose(fin);
   1278 			return;
   1279 		}
   1280 
   1281 		count = 0;
   1282 		while((c=getc(fin)) != EOF) {
   1283 			if (c == '\n')	/* will get expanded to \r\n */
   1284 				count++;
   1285 			count++;
   1286 		}
   1287 		(void) fclose(fin);
   1288 
   1289 		reply(213, "%qd", count);
   1290 		break; }
   1291 	default:
   1292 		reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
   1293 	}
   1294 }
   1295