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