Home | History | Annotate | Line # | Download | only in ftpd
ftpcmd.y revision 1.13
      1 /*	$NetBSD: ftpcmd.y,v 1.13 1997/06/18 19:05:49 christos 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.13 1997/06/18 19:05:49 christos 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 
     61 #include <ctype.h>
     62 #include <errno.h>
     63 #include <glob.h>
     64 #include <pwd.h>
     65 #include <setjmp.h>
     66 #include <signal.h>
     67 #include <stdio.h>
     68 #include <stdlib.h>
     69 #include <string.h>
     70 #include <syslog.h>
     71 #include <time.h>
     72 #include <unistd.h>
     73 
     74 #include "extern.h"
     75 
     76 extern	struct sockaddr_in data_dest;
     77 extern	int logged_in;
     78 extern	struct passwd *pw;
     79 extern	int guest;
     80 extern	int logging;
     81 extern	int type;
     82 extern	int form;
     83 extern	int debug;
     84 extern  int pdata;
     85 extern	char hostname[], remotehost[];
     86 extern	char proctitle[];
     87 extern	int usedefault;
     88 extern  int transflag;
     89 extern  char tmpline[];
     90 extern	struct ftpclass curclass;
     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_modify 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_modify 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_modify SP pathname CRLF
    351 		{
    352 			if ($2 && $4 != NULL)
    353 				makedir($4);
    354 			if ($4 != NULL)
    355 				free($4);
    356 		}
    357 	| RMD check_modify 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_modify 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_modify 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 				curclass.timeout, curclass.maxtimeout);
    426 		}
    427 	| SITE SP IDLE SP NUMBER CRLF
    428 		{
    429 			if ($5 < 30 || $5 > curclass.maxtimeout) {
    430 				reply(501,
    431 			"IDLE time limit must be between 30 and %d seconds",
    432 				    curclass.maxtimeout);
    433 			} else {
    434 				curclass.timeout = $5;
    435 				(void) alarm(curclass.timeout);
    436 				reply(200,
    437 				    "IDLE time limit set to %d seconds",
    438 				    curclass.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 			restart_point = (off_t) 0;
    522 			if ($2 && $4) {
    523 				fromname = renamefrom($4);
    524 				if (fromname == (char *) 0 && $4) {
    525 					free($4);
    526 				}
    527 			}
    528 		}
    529 	| REST SP byte_size CRLF
    530 		{
    531 			fromname = (char *) 0;
    532 			restart_point = $3;	/* XXX $3 is only "int" */
    533 			reply(350, "Restarting at %qd. %s", restart_point,
    534 			    "Send STORE or RETRIEVE to initiate transfer.");
    535 		}
    536 	;
    537 
    538 username
    539 	: STRING
    540 	;
    541 
    542 password
    543 	: /* empty */
    544 		{
    545 			$$ = (char *)calloc(1, sizeof(char));
    546 		}
    547 	| STRING
    548 	;
    549 
    550 byte_size
    551 	: NUMBER
    552 	;
    553 
    554 host_port
    555 	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
    556 		NUMBER COMMA NUMBER
    557 		{
    558 			char *a, *p;
    559 
    560 			data_dest.sin_len = sizeof(struct sockaddr_in);
    561 			data_dest.sin_family = AF_INET;
    562 			p = (char *)&data_dest.sin_port;
    563 			p[0] = $9; p[1] = $11;
    564 			a = (char *)&data_dest.sin_addr;
    565 			a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7;
    566 		}
    567 	;
    568 
    569 form_code
    570 	: N
    571 		{
    572 			$$ = FORM_N;
    573 		}
    574 	| T
    575 		{
    576 			$$ = FORM_T;
    577 		}
    578 	| C
    579 		{
    580 			$$ = FORM_C;
    581 		}
    582 	;
    583 
    584 type_code
    585 	: A
    586 		{
    587 			cmd_type = TYPE_A;
    588 			cmd_form = FORM_N;
    589 		}
    590 	| A SP form_code
    591 		{
    592 			cmd_type = TYPE_A;
    593 			cmd_form = $3;
    594 		}
    595 	| E
    596 		{
    597 			cmd_type = TYPE_E;
    598 			cmd_form = FORM_N;
    599 		}
    600 	| E SP form_code
    601 		{
    602 			cmd_type = TYPE_E;
    603 			cmd_form = $3;
    604 		}
    605 	| I
    606 		{
    607 			cmd_type = TYPE_I;
    608 		}
    609 	| L
    610 		{
    611 			cmd_type = TYPE_L;
    612 			cmd_bytesz = NBBY;
    613 		}
    614 	| L SP byte_size
    615 		{
    616 			cmd_type = TYPE_L;
    617 			cmd_bytesz = $3;
    618 		}
    619 		/* this is for a bug in the BBN ftp */
    620 	| L byte_size
    621 		{
    622 			cmd_type = TYPE_L;
    623 			cmd_bytesz = $2;
    624 		}
    625 	;
    626 
    627 struct_code
    628 	: F
    629 		{
    630 			$$ = STRU_F;
    631 		}
    632 	| R
    633 		{
    634 			$$ = STRU_R;
    635 		}
    636 	| P
    637 		{
    638 			$$ = STRU_P;
    639 		}
    640 	;
    641 
    642 mode_code
    643 	: S
    644 		{
    645 			$$ = MODE_S;
    646 		}
    647 	| B
    648 		{
    649 			$$ = MODE_B;
    650 		}
    651 	| C
    652 		{
    653 			$$ = MODE_C;
    654 		}
    655 	;
    656 
    657 pathname
    658 	: pathstring
    659 		{
    660 			/*
    661 			 * Problem: this production is used for all pathname
    662 			 * processing, but only gives a 550 error reply.
    663 			 * This is a valid reply in some cases but not in
    664 			 * others.
    665 			 */
    666 			if (logged_in && $1 && *$1 == '~') {
    667 				glob_t gl;
    668 				int flags =
    669 				 GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE;
    670 
    671 				if ($1[1] == '\0')
    672 					$$ = strdup(pw->pw_dir);
    673 				else {
    674 					memset(&gl, 0, sizeof(gl));
    675 					if (glob($1, flags, NULL, &gl) ||
    676 					    gl.gl_pathc == 0) {
    677 						reply(550, "not found");
    678 						$$ = NULL;
    679 					} else
    680 						$$ = strdup(gl.gl_pathv[0]);
    681 					globfree(&gl);
    682 				}
    683 				free($1);
    684 			} else
    685 				$$ = $1;
    686 		}
    687 	;
    688 
    689 pathstring
    690 	: STRING
    691 	;
    692 
    693 octal_number
    694 	: NUMBER
    695 		{
    696 			int ret, dec, multby, digit;
    697 
    698 			/*
    699 			 * Convert a number that was read as decimal number
    700 			 * to what it would be if it had been read as octal.
    701 			 */
    702 			dec = $1;
    703 			multby = 1;
    704 			ret = 0;
    705 			while (dec) {
    706 				digit = dec%10;
    707 				if (digit > 7) {
    708 					ret = -1;
    709 					break;
    710 				}
    711 				ret += digit * multby;
    712 				multby *= 8;
    713 				dec /= 10;
    714 			}
    715 			$$ = ret;
    716 		}
    717 	;
    718 
    719 
    720 check_login
    721 	: /* empty */
    722 		{
    723 			if (logged_in)
    724 				$$ = 1;
    725 			else {
    726 				reply(530, "Please login with USER and PASS.");
    727 				$$ = 0;
    728 			}
    729 		}
    730 	;
    731 check_modify
    732 	: /* empty */
    733 		{
    734 			if (logged_in)  {
    735 				if (curclass.modify) {
    736 					$$ = 1;
    737 				} else
    738 					reply(502,
    739 					"No permission to use this command.");
    740 					$$ = 0;
    741 			} else {
    742 				reply(530, "Please login with USER and PASS.");
    743 				$$ = 0;
    744 			}
    745 		}
    746 
    747 %%
    748 
    749 extern jmp_buf errcatch;
    750 
    751 #define	CMD	0	/* beginning of command */
    752 #define	ARGS	1	/* expect miscellaneous arguments */
    753 #define	STR1	2	/* expect SP followed by STRING */
    754 #define	STR2	3	/* expect STRING */
    755 #define	OSTR	4	/* optional SP then STRING */
    756 #define	ZSTR1	5	/* SP then optional STRING */
    757 #define	ZSTR2	6	/* optional STRING after SP */
    758 #define	SITECMD	7	/* SITE command */
    759 #define	NSTR	8	/* Number followed by a string */
    760 
    761 struct tab {
    762 	char	*name;
    763 	short	token;
    764 	short	state;
    765 	short	implemented;	/* 1 if command is implemented */
    766 	char	*help;
    767 };
    768 
    769 struct tab cmdtab[] = {		/* In order defined in RFC 765 */
    770 	{ "USER", USER, STR1, 1,	"<sp> username" },
    771 	{ "PASS", PASS, ZSTR1, 1,	"<sp> password" },
    772 	{ "ACCT", ACCT, STR1, 0,	"(specify account)" },
    773 	{ "SMNT", SMNT, ARGS, 0,	"(structure mount)" },
    774 	{ "REIN", REIN, ARGS, 0,	"(reinitialize server state)" },
    775 	{ "QUIT", QUIT, ARGS, 1,	"(terminate service)", },
    776 	{ "PORT", PORT, ARGS, 1,	"<sp> b0, b1, b2, b3, b4" },
    777 	{ "PASV", PASV, ARGS, 1,	"(set server in passive mode)" },
    778 	{ "TYPE", TYPE, ARGS, 1,	"<sp> [ A | E | I | L ]" },
    779 	{ "STRU", STRU, ARGS, 1,	"(specify file structure)" },
    780 	{ "MODE", MODE, ARGS, 1,	"(specify transfer mode)" },
    781 	{ "RETR", RETR, STR1, 1,	"<sp> file-name" },
    782 	{ "STOR", STOR, STR1, 1,	"<sp> file-name" },
    783 	{ "APPE", APPE, STR1, 1,	"<sp> file-name" },
    784 	{ "MLFL", MLFL, OSTR, 0,	"(mail file)" },
    785 	{ "MAIL", MAIL, OSTR, 0,	"(mail to user)" },
    786 	{ "MSND", MSND, OSTR, 0,	"(mail send to terminal)" },
    787 	{ "MSOM", MSOM, OSTR, 0,	"(mail send to terminal or mailbox)" },
    788 	{ "MSAM", MSAM, OSTR, 0,	"(mail send to terminal and mailbox)" },
    789 	{ "MRSQ", MRSQ, OSTR, 0,	"(mail recipient scheme question)" },
    790 	{ "MRCP", MRCP, STR1, 0,	"(mail recipient)" },
    791 	{ "ALLO", ALLO, ARGS, 1,	"allocate storage (vacuously)" },
    792 	{ "REST", REST, ARGS, 1,	"<sp> offset (restart command)" },
    793 	{ "RNFR", RNFR, STR1, 1,	"<sp> file-name" },
    794 	{ "RNTO", RNTO, STR1, 1,	"<sp> file-name" },
    795 	{ "ABOR", ABOR, ARGS, 1,	"(abort operation)" },
    796 	{ "DELE", DELE, STR1, 1,	"<sp> file-name" },
    797 	{ "CWD",  CWD,  OSTR, 1,	"[ <sp> directory-name ]" },
    798 	{ "XCWD", CWD,	OSTR, 1,	"[ <sp> directory-name ]" },
    799 	{ "LIST", LIST, OSTR, 1,	"[ <sp> path-name ]" },
    800 	{ "NLST", NLST, OSTR, 1,	"[ <sp> path-name ]" },
    801 	{ "SITE", SITE, SITECMD, 1,	"site-cmd [ <sp> arguments ]" },
    802 	{ "SYST", SYST, ARGS, 1,	"(get type of operating system)" },
    803 	{ "STAT", STAT, OSTR, 1,	"[ <sp> path-name ]" },
    804 	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
    805 	{ "NOOP", NOOP, ARGS, 1,	"" },
    806 	{ "MKD",  MKD,  STR1, 1,	"<sp> path-name" },
    807 	{ "XMKD", MKD,  STR1, 1,	"<sp> path-name" },
    808 	{ "RMD",  RMD,  STR1, 1,	"<sp> path-name" },
    809 	{ "XRMD", RMD,  STR1, 1,	"<sp> path-name" },
    810 	{ "PWD",  PWD,  ARGS, 1,	"(return current directory)" },
    811 	{ "XPWD", PWD,  ARGS, 1,	"(return current directory)" },
    812 	{ "CDUP", CDUP, ARGS, 1,	"(change to parent directory)" },
    813 	{ "XCUP", CDUP, ARGS, 1,	"(change to parent directory)" },
    814 	{ "STOU", STOU, STR1, 1,	"<sp> file-name" },
    815 	{ "SIZE", SIZE, OSTR, 1,	"<sp> path-name" },
    816 	{ "MDTM", MDTM, OSTR, 1,	"<sp> path-name" },
    817 	{ NULL,   0,    0,    0,	0 }
    818 };
    819 
    820 struct tab sitetab[] = {
    821 	{ "UMASK", UMASK, ARGS, 1,	"[ <sp> umask ]" },
    822 	{ "IDLE", IDLE, ARGS, 1,	"[ <sp> maximum-idle-time ]" },
    823 	{ "CHMOD", CHMOD, NSTR, 1,	"<sp> mode <sp> file-name" },
    824 	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
    825 	{ NULL,   0,    0,    0,	0 }
    826 };
    827 
    828 static char	*copy __P((char *));
    829 static void	 help __P((struct tab *, char *));
    830 static struct tab *
    831 		 lookup __P((struct tab *, char *));
    832 static void	 sizecmd __P((char *));
    833 static void	 toolong __P((int));
    834 static int	 yylex __P((void));
    835 
    836 static struct tab *
    837 lookup(p, cmd)
    838 	struct tab *p;
    839 	char *cmd;
    840 {
    841 
    842 	for (; p->name != NULL; p++)
    843 		if (strcmp(cmd, p->name) == 0)
    844 			return (p);
    845 	return (0);
    846 }
    847 
    848 #include <arpa/telnet.h>
    849 
    850 /*
    851  * getline - a hacked up version of fgets to ignore TELNET escape codes.
    852  */
    853 char *
    854 getline(s, n, iop)
    855 	char *s;
    856 	int n;
    857 	FILE *iop;
    858 {
    859 	int c;
    860 	register char *cs;
    861 
    862 	cs = s;
    863 /* tmpline may contain saved command from urgent mode interruption */
    864 	for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
    865 		*cs++ = tmpline[c];
    866 		if (tmpline[c] == '\n') {
    867 			*cs++ = '\0';
    868 			if (debug)
    869 				syslog(LOG_DEBUG, "command: %s", s);
    870 			tmpline[0] = '\0';
    871 			return(s);
    872 		}
    873 		if (c == 0)
    874 			tmpline[0] = '\0';
    875 	}
    876 	while ((c = getc(iop)) != EOF) {
    877 		c &= 0377;
    878 		if (c == IAC) {
    879 		    if ((c = getc(iop)) != EOF) {
    880 			c &= 0377;
    881 			switch (c) {
    882 			case WILL:
    883 			case WONT:
    884 				c = getc(iop);
    885 				printf("%c%c%c", IAC, DONT, 0377&c);
    886 				(void) fflush(stdout);
    887 				continue;
    888 			case DO:
    889 			case DONT:
    890 				c = getc(iop);
    891 				printf("%c%c%c", IAC, WONT, 0377&c);
    892 				(void) fflush(stdout);
    893 				continue;
    894 			case IAC:
    895 				break;
    896 			default:
    897 				continue;	/* ignore command */
    898 			}
    899 		    }
    900 		}
    901 		*cs++ = c;
    902 		if (--n <= 0 || c == '\n')
    903 			break;
    904 	}
    905 	if (c == EOF && cs == s)
    906 		return (NULL);
    907 	*cs++ = '\0';
    908 	if (debug) {
    909 		if (!guest && strncasecmp("pass ", s, 5) == 0) {
    910 			/* Don't syslog passwords */
    911 			syslog(LOG_DEBUG, "command: %.5s ???", s);
    912 		} else {
    913 			register char *cp;
    914 			register int len;
    915 
    916 			/* Don't syslog trailing CR-LF */
    917 			len = strlen(s);
    918 			cp = s + len - 1;
    919 			while (cp >= s && (*cp == '\n' || *cp == '\r')) {
    920 				--cp;
    921 				--len;
    922 			}
    923 			syslog(LOG_DEBUG, "command: %.*s", len, s);
    924 		}
    925 	}
    926 	return (s);
    927 }
    928 
    929 static void
    930 toolong(signo)
    931 	int signo;
    932 {
    933 
    934 	reply(421,
    935 	    "Timeout (%d seconds): closing control connection.",
    936 	    curclass.timeout);
    937 	if (logging)
    938 		syslog(LOG_INFO, "User %s timed out after %d seconds",
    939 		    (pw ? pw -> pw_name : "unknown"), curclass.timeout);
    940 	dologout(1);
    941 }
    942 
    943 static int
    944 yylex()
    945 {
    946 	static int cpos, state;
    947 	char *cp, *cp2;
    948 	struct tab *p;
    949 	int n;
    950 	char c;
    951 
    952 	for (;;) {
    953 		switch (state) {
    954 
    955 		case CMD:
    956 			(void) signal(SIGALRM, toolong);
    957 			(void) alarm(curclass.timeout);
    958 			if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
    959 				reply(221, "You could at least say goodbye.");
    960 				dologout(0);
    961 			}
    962 			(void) alarm(0);
    963 #ifdef HASSETPROCTITLE
    964 			if (strncasecmp(cbuf, "PASS", 4) != 0)
    965 				setproctitle("%s: %s", proctitle, cbuf);
    966 #endif /* HASSETPROCTITLE */
    967 			if ((cp = strchr(cbuf, '\r'))) {
    968 				*cp++ = '\n';
    969 				*cp = '\0';
    970 			}
    971 			if ((cp = strpbrk(cbuf, " \n")))
    972 				cpos = cp - cbuf;
    973 			if (cpos == 0)
    974 				cpos = 4;
    975 			c = cbuf[cpos];
    976 			cbuf[cpos] = '\0';
    977 			upper(cbuf);
    978 			p = lookup(cmdtab, cbuf);
    979 			cbuf[cpos] = c;
    980 			if (p != 0) {
    981 				if (p->implemented == 0) {
    982 					nack(p->name);
    983 					longjmp(errcatch,0);
    984 					/* NOTREACHED */
    985 				}
    986 				state = p->state;
    987 				yylval.s = p->name;
    988 				return (p->token);
    989 			}
    990 			break;
    991 
    992 		case SITECMD:
    993 			if (cbuf[cpos] == ' ') {
    994 				cpos++;
    995 				return (SP);
    996 			}
    997 			cp = &cbuf[cpos];
    998 			if ((cp2 = strpbrk(cp, " \n")))
    999 				cpos = cp2 - cbuf;
   1000 			c = cbuf[cpos];
   1001 			cbuf[cpos] = '\0';
   1002 			upper(cp);
   1003 			p = lookup(sitetab, cp);
   1004 			cbuf[cpos] = c;
   1005 			if (p != 0) {
   1006 				if (p->implemented == 0) {
   1007 					state = CMD;
   1008 					nack(p->name);
   1009 					longjmp(errcatch,0);
   1010 					/* NOTREACHED */
   1011 				}
   1012 				state = p->state;
   1013 				yylval.s = p->name;
   1014 				return (p->token);
   1015 			}
   1016 			state = CMD;
   1017 			break;
   1018 
   1019 		case OSTR:
   1020 			if (cbuf[cpos] == '\n') {
   1021 				state = CMD;
   1022 				return (CRLF);
   1023 			}
   1024 			/* FALLTHROUGH */
   1025 
   1026 		case STR1:
   1027 		case ZSTR1:
   1028 		dostr1:
   1029 			if (cbuf[cpos] == ' ') {
   1030 				cpos++;
   1031 				state = state == OSTR ? STR2 : ++state;
   1032 				return (SP);
   1033 			}
   1034 			break;
   1035 
   1036 		case ZSTR2:
   1037 			if (cbuf[cpos] == '\n') {
   1038 				state = CMD;
   1039 				return (CRLF);
   1040 			}
   1041 			/* FALLTHROUGH */
   1042 
   1043 		case STR2:
   1044 			cp = &cbuf[cpos];
   1045 			n = strlen(cp);
   1046 			cpos += n - 1;
   1047 			/*
   1048 			 * Make sure the string is nonempty and \n terminated.
   1049 			 */
   1050 			if (n > 1 && cbuf[cpos] == '\n') {
   1051 				cbuf[cpos] = '\0';
   1052 				yylval.s = copy(cp);
   1053 				cbuf[cpos] = '\n';
   1054 				state = ARGS;
   1055 				return (STRING);
   1056 			}
   1057 			break;
   1058 
   1059 		case NSTR:
   1060 			if (cbuf[cpos] == ' ') {
   1061 				cpos++;
   1062 				return (SP);
   1063 			}
   1064 			if (isdigit(cbuf[cpos])) {
   1065 				cp = &cbuf[cpos];
   1066 				while (isdigit(cbuf[++cpos]))
   1067 					;
   1068 				c = cbuf[cpos];
   1069 				cbuf[cpos] = '\0';
   1070 				yylval.i = atoi(cp);
   1071 				cbuf[cpos] = c;
   1072 				state = STR1;
   1073 				return (NUMBER);
   1074 			}
   1075 			state = STR1;
   1076 			goto dostr1;
   1077 
   1078 		case ARGS:
   1079 			if (isdigit(cbuf[cpos])) {
   1080 				cp = &cbuf[cpos];
   1081 				while (isdigit(cbuf[++cpos]))
   1082 					;
   1083 				c = cbuf[cpos];
   1084 				cbuf[cpos] = '\0';
   1085 				yylval.i = atoi(cp);
   1086 				cbuf[cpos] = c;
   1087 				return (NUMBER);
   1088 			}
   1089 			switch (cbuf[cpos++]) {
   1090 
   1091 			case '\n':
   1092 				state = CMD;
   1093 				return (CRLF);
   1094 
   1095 			case ' ':
   1096 				return (SP);
   1097 
   1098 			case ',':
   1099 				return (COMMA);
   1100 
   1101 			case 'A':
   1102 			case 'a':
   1103 				return (A);
   1104 
   1105 			case 'B':
   1106 			case 'b':
   1107 				return (B);
   1108 
   1109 			case 'C':
   1110 			case 'c':
   1111 				return (C);
   1112 
   1113 			case 'E':
   1114 			case 'e':
   1115 				return (E);
   1116 
   1117 			case 'F':
   1118 			case 'f':
   1119 				return (F);
   1120 
   1121 			case 'I':
   1122 			case 'i':
   1123 				return (I);
   1124 
   1125 			case 'L':
   1126 			case 'l':
   1127 				return (L);
   1128 
   1129 			case 'N':
   1130 			case 'n':
   1131 				return (N);
   1132 
   1133 			case 'P':
   1134 			case 'p':
   1135 				return (P);
   1136 
   1137 			case 'R':
   1138 			case 'r':
   1139 				return (R);
   1140 
   1141 			case 'S':
   1142 			case 's':
   1143 				return (S);
   1144 
   1145 			case 'T':
   1146 			case 't':
   1147 				return (T);
   1148 
   1149 			}
   1150 			break;
   1151 
   1152 		default:
   1153 			fatal("Unknown state in scanner.");
   1154 		}
   1155 		yyerror((char *) 0);
   1156 		state = CMD;
   1157 		longjmp(errcatch,0);
   1158 	}
   1159 }
   1160 
   1161 void
   1162 upper(s)
   1163 	char *s;
   1164 {
   1165 	while (*s != '\0') {
   1166 		*s = toupper(*s);
   1167 		s++;
   1168 	}
   1169 }
   1170 
   1171 static char *
   1172 copy(s)
   1173 	char *s;
   1174 {
   1175 	char *p;
   1176 
   1177 	p = malloc((unsigned) strlen(s) + 1);
   1178 	if (p == NULL)
   1179 		fatal("Ran out of memory.");
   1180 	(void) strcpy(p, s);
   1181 	return (p);
   1182 }
   1183 
   1184 static void
   1185 help(ctab, s)
   1186 	struct tab *ctab;
   1187 	char *s;
   1188 {
   1189 	struct tab *c;
   1190 	int width, NCMDS;
   1191 	char *type;
   1192 
   1193 	if (ctab == sitetab)
   1194 		type = "SITE ";
   1195 	else
   1196 		type = "";
   1197 	width = 0, NCMDS = 0;
   1198 	for (c = ctab; c->name != NULL; c++) {
   1199 		int len = strlen(c->name);
   1200 
   1201 		if (len > width)
   1202 			width = len;
   1203 		NCMDS++;
   1204 	}
   1205 	width = (width + 8) &~ 7;
   1206 	if (s == 0) {
   1207 		int i, j, w;
   1208 		int columns, lines;
   1209 
   1210 		lreply(214, "The following %scommands are recognized %s.",
   1211 		    type, "(* =>'s unimplemented)");
   1212 		columns = 76 / width;
   1213 		if (columns == 0)
   1214 			columns = 1;
   1215 		lines = (NCMDS + columns - 1) / columns;
   1216 		for (i = 0; i < lines; i++) {
   1217 			printf("   ");
   1218 			for (j = 0; j < columns; j++) {
   1219 				c = ctab + j * lines + i;
   1220 				printf("%s%c", c->name,
   1221 					c->implemented ? ' ' : '*');
   1222 				if (c + lines >= &ctab[NCMDS])
   1223 					break;
   1224 				w = strlen(c->name) + 1;
   1225 				while (w < width) {
   1226 					putchar(' ');
   1227 					w++;
   1228 				}
   1229 			}
   1230 			printf("\r\n");
   1231 		}
   1232 		(void) fflush(stdout);
   1233 		reply(214, "Direct comments to ftp-bugs@%s.", hostname);
   1234 		return;
   1235 	}
   1236 	upper(s);
   1237 	c = lookup(ctab, s);
   1238 	if (c == (struct tab *)0) {
   1239 		reply(502, "Unknown command %s.", s);
   1240 		return;
   1241 	}
   1242 	if (c->implemented)
   1243 		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
   1244 	else
   1245 		reply(214, "%s%-*s\t%s; unimplemented.", type, width,
   1246 		    c->name, c->help);
   1247 }
   1248 
   1249 static void
   1250 sizecmd(filename)
   1251 	char *filename;
   1252 {
   1253 	switch (type) {
   1254 	case TYPE_L:
   1255 	case TYPE_I: {
   1256 		struct stat stbuf;
   1257 		if (stat(filename, &stbuf) < 0 || !S_ISREG(stbuf.st_mode))
   1258 			reply(550, "%s: not a plain file.", filename);
   1259 		else
   1260 			reply(213, "%qu", stbuf.st_size);
   1261 		break; }
   1262 	case TYPE_A: {
   1263 		FILE *fin;
   1264 		int c;
   1265 		off_t count;
   1266 		struct stat stbuf;
   1267 		fin = fopen(filename, "r");
   1268 		if (fin == NULL) {
   1269 			perror_reply(550, filename);
   1270 			return;
   1271 		}
   1272 		if (fstat(fileno(fin), &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) {
   1273 			reply(550, "%s: not a plain file.", filename);
   1274 			(void) fclose(fin);
   1275 			return;
   1276 		}
   1277 
   1278 		count = 0;
   1279 		while((c=getc(fin)) != EOF) {
   1280 			if (c == '\n')	/* will get expanded to \r\n */
   1281 				count++;
   1282 			count++;
   1283 		}
   1284 		(void) fclose(fin);
   1285 
   1286 		reply(213, "%qd", count);
   1287 		break; }
   1288 	default:
   1289 		reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
   1290 	}
   1291 }
   1292