Home | History | Annotate | Line # | Download | only in ftpd
ftpcmd.y revision 1.6
      1 /*	$NetBSD: ftpcmd.y,v 1.6 1995/06/03 22:46:45 mycroft 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.6 1995/06/03 22:46:45 mycroft 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 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 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 SP pathname CRLF
    351 		{
    352 			if ($2 && $4 != NULL)
    353 				makedir($4);
    354 			if ($4 != NULL)
    355 				free($4);
    356 		}
    357 	| RMD check_login 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 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 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 					    "19%02d%02d%02d%02d%02d%02d",
    500 					    t->tm_year, 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 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 				memset(&gl, 0, sizeof(gl));
    672 				if (glob($1, flags, NULL, &gl) ||
    673 				    gl.gl_pathc == 0) {
    674 					reply(550, "not found");
    675 					$$ = NULL;
    676 				} else {
    677 					$$ = strdup(gl.gl_pathv[0]);
    678 				}
    679 				globfree(&gl);
    680 				free($1);
    681 			} else
    682 				$$ = $1;
    683 		}
    684 	;
    685 
    686 pathstring
    687 	: STRING
    688 	;
    689 
    690 octal_number
    691 	: NUMBER
    692 		{
    693 			int ret, dec, multby, digit;
    694 
    695 			/*
    696 			 * Convert a number that was read as decimal number
    697 			 * to what it would be if it had been read as octal.
    698 			 */
    699 			dec = $1;
    700 			multby = 1;
    701 			ret = 0;
    702 			while (dec) {
    703 				digit = dec%10;
    704 				if (digit > 7) {
    705 					ret = -1;
    706 					break;
    707 				}
    708 				ret += digit * multby;
    709 				multby *= 8;
    710 				dec /= 10;
    711 			}
    712 			$$ = ret;
    713 		}
    714 	;
    715 
    716 
    717 check_login
    718 	: /* empty */
    719 		{
    720 			if (logged_in)
    721 				$$ = 1;
    722 			else {
    723 				reply(530, "Please login with USER and PASS.");
    724 				$$ = 0;
    725 			}
    726 		}
    727 	;
    728 
    729 %%
    730 
    731 extern jmp_buf errcatch;
    732 
    733 #define	CMD	0	/* beginning of command */
    734 #define	ARGS	1	/* expect miscellaneous arguments */
    735 #define	STR1	2	/* expect SP followed by STRING */
    736 #define	STR2	3	/* expect STRING */
    737 #define	OSTR	4	/* optional SP then STRING */
    738 #define	ZSTR1	5	/* SP then optional STRING */
    739 #define	ZSTR2	6	/* optional STRING after SP */
    740 #define	SITECMD	7	/* SITE command */
    741 #define	NSTR	8	/* Number followed by a string */
    742 
    743 struct tab {
    744 	char	*name;
    745 	short	token;
    746 	short	state;
    747 	short	implemented;	/* 1 if command is implemented */
    748 	char	*help;
    749 };
    750 
    751 struct tab cmdtab[] = {		/* In order defined in RFC 765 */
    752 	{ "USER", USER, STR1, 1,	"<sp> username" },
    753 	{ "PASS", PASS, ZSTR1, 1,	"<sp> password" },
    754 	{ "ACCT", ACCT, STR1, 0,	"(specify account)" },
    755 	{ "SMNT", SMNT, ARGS, 0,	"(structure mount)" },
    756 	{ "REIN", REIN, ARGS, 0,	"(reinitialize server state)" },
    757 	{ "QUIT", QUIT, ARGS, 1,	"(terminate service)", },
    758 	{ "PORT", PORT, ARGS, 1,	"<sp> b0, b1, b2, b3, b4" },
    759 	{ "PASV", PASV, ARGS, 1,	"(set server in passive mode)" },
    760 	{ "TYPE", TYPE, ARGS, 1,	"<sp> [ A | E | I | L ]" },
    761 	{ "STRU", STRU, ARGS, 1,	"(specify file structure)" },
    762 	{ "MODE", MODE, ARGS, 1,	"(specify transfer mode)" },
    763 	{ "RETR", RETR, STR1, 1,	"<sp> file-name" },
    764 	{ "STOR", STOR, STR1, 1,	"<sp> file-name" },
    765 	{ "APPE", APPE, STR1, 1,	"<sp> file-name" },
    766 	{ "MLFL", MLFL, OSTR, 0,	"(mail file)" },
    767 	{ "MAIL", MAIL, OSTR, 0,	"(mail to user)" },
    768 	{ "MSND", MSND, OSTR, 0,	"(mail send to terminal)" },
    769 	{ "MSOM", MSOM, OSTR, 0,	"(mail send to terminal or mailbox)" },
    770 	{ "MSAM", MSAM, OSTR, 0,	"(mail send to terminal and mailbox)" },
    771 	{ "MRSQ", MRSQ, OSTR, 0,	"(mail recipient scheme question)" },
    772 	{ "MRCP", MRCP, STR1, 0,	"(mail recipient)" },
    773 	{ "ALLO", ALLO, ARGS, 1,	"allocate storage (vacuously)" },
    774 	{ "REST", REST, ARGS, 1,	"<sp> offset (restart command)" },
    775 	{ "RNFR", RNFR, STR1, 1,	"<sp> file-name" },
    776 	{ "RNTO", RNTO, STR1, 1,	"<sp> file-name" },
    777 	{ "ABOR", ABOR, ARGS, 1,	"(abort operation)" },
    778 	{ "DELE", DELE, STR1, 1,	"<sp> file-name" },
    779 	{ "CWD",  CWD,  OSTR, 1,	"[ <sp> directory-name ]" },
    780 	{ "XCWD", CWD,	OSTR, 1,	"[ <sp> directory-name ]" },
    781 	{ "LIST", LIST, OSTR, 1,	"[ <sp> path-name ]" },
    782 	{ "NLST", NLST, OSTR, 1,	"[ <sp> path-name ]" },
    783 	{ "SITE", SITE, SITECMD, 1,	"site-cmd [ <sp> arguments ]" },
    784 	{ "SYST", SYST, ARGS, 1,	"(get type of operating system)" },
    785 	{ "STAT", STAT, OSTR, 1,	"[ <sp> path-name ]" },
    786 	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
    787 	{ "NOOP", NOOP, ARGS, 1,	"" },
    788 	{ "MKD",  MKD,  STR1, 1,	"<sp> path-name" },
    789 	{ "XMKD", MKD,  STR1, 1,	"<sp> path-name" },
    790 	{ "RMD",  RMD,  STR1, 1,	"<sp> path-name" },
    791 	{ "XRMD", RMD,  STR1, 1,	"<sp> path-name" },
    792 	{ "PWD",  PWD,  ARGS, 1,	"(return current directory)" },
    793 	{ "XPWD", PWD,  ARGS, 1,	"(return current directory)" },
    794 	{ "CDUP", CDUP, ARGS, 1,	"(change to parent directory)" },
    795 	{ "XCUP", CDUP, ARGS, 1,	"(change to parent directory)" },
    796 	{ "STOU", STOU, STR1, 1,	"<sp> file-name" },
    797 	{ "SIZE", SIZE, OSTR, 1,	"<sp> path-name" },
    798 	{ "MDTM", MDTM, OSTR, 1,	"<sp> path-name" },
    799 	{ NULL,   0,    0,    0,	0 }
    800 };
    801 
    802 struct tab sitetab[] = {
    803 	{ "UMASK", UMASK, ARGS, 1,	"[ <sp> umask ]" },
    804 	{ "IDLE", IDLE, ARGS, 1,	"[ <sp> maximum-idle-time ]" },
    805 	{ "CHMOD", CHMOD, NSTR, 1,	"<sp> mode <sp> file-name" },
    806 	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
    807 	{ NULL,   0,    0,    0,	0 }
    808 };
    809 
    810 static char	*copy __P((char *));
    811 static void	 help __P((struct tab *, char *));
    812 static struct tab *
    813 		 lookup __P((struct tab *, char *));
    814 static void	 sizecmd __P((char *));
    815 static void	 toolong __P((int));
    816 static int	 yylex __P((void));
    817 
    818 static struct tab *
    819 lookup(p, cmd)
    820 	struct tab *p;
    821 	char *cmd;
    822 {
    823 
    824 	for (; p->name != NULL; p++)
    825 		if (strcmp(cmd, p->name) == 0)
    826 			return (p);
    827 	return (0);
    828 }
    829 
    830 #include <arpa/telnet.h>
    831 
    832 /*
    833  * getline - a hacked up version of fgets to ignore TELNET escape codes.
    834  */
    835 char *
    836 getline(s, n, iop)
    837 	char *s;
    838 	int n;
    839 	FILE *iop;
    840 {
    841 	int c;
    842 	register char *cs;
    843 
    844 	cs = s;
    845 /* tmpline may contain saved command from urgent mode interruption */
    846 	for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
    847 		*cs++ = tmpline[c];
    848 		if (tmpline[c] == '\n') {
    849 			*cs++ = '\0';
    850 			if (debug)
    851 				syslog(LOG_DEBUG, "command: %s", s);
    852 			tmpline[0] = '\0';
    853 			return(s);
    854 		}
    855 		if (c == 0)
    856 			tmpline[0] = '\0';
    857 	}
    858 	while ((c = getc(iop)) != EOF) {
    859 		c &= 0377;
    860 		if (c == IAC) {
    861 		    if ((c = getc(iop)) != EOF) {
    862 			c &= 0377;
    863 			switch (c) {
    864 			case WILL:
    865 			case WONT:
    866 				c = getc(iop);
    867 				printf("%c%c%c", IAC, DONT, 0377&c);
    868 				(void) fflush(stdout);
    869 				continue;
    870 			case DO:
    871 			case DONT:
    872 				c = getc(iop);
    873 				printf("%c%c%c", IAC, WONT, 0377&c);
    874 				(void) fflush(stdout);
    875 				continue;
    876 			case IAC:
    877 				break;
    878 			default:
    879 				continue;	/* ignore command */
    880 			}
    881 		    }
    882 		}
    883 		*cs++ = c;
    884 		if (--n <= 0 || c == '\n')
    885 			break;
    886 	}
    887 	if (c == EOF && cs == s)
    888 		return (NULL);
    889 	*cs++ = '\0';
    890 	if (debug) {
    891 		if (!guest && strncasecmp("pass ", s, 5) == 0) {
    892 			/* Don't syslog passwords */
    893 			syslog(LOG_DEBUG, "command: %.5s ???", s);
    894 		} else {
    895 			register char *cp;
    896 			register int len;
    897 
    898 			/* Don't syslog trailing CR-LF */
    899 			len = strlen(s);
    900 			cp = s + len - 1;
    901 			while (cp >= s && (*cp == '\n' || *cp == '\r')) {
    902 				--cp;
    903 				--len;
    904 			}
    905 			syslog(LOG_DEBUG, "command: %.*s", len, s);
    906 		}
    907 	}
    908 	return (s);
    909 }
    910 
    911 static void
    912 toolong(signo)
    913 	int signo;
    914 {
    915 
    916 	reply(421,
    917 	    "Timeout (%d seconds): closing control connection.", timeout);
    918 	if (logging)
    919 		syslog(LOG_INFO, "User %s timed out after %d seconds",
    920 		    (pw ? pw -> pw_name : "unknown"), timeout);
    921 	dologout(1);
    922 }
    923 
    924 static int
    925 yylex()
    926 {
    927 	static int cpos, state;
    928 	char *cp, *cp2;
    929 	struct tab *p;
    930 	int n;
    931 	char c;
    932 
    933 	for (;;) {
    934 		switch (state) {
    935 
    936 		case CMD:
    937 			(void) signal(SIGALRM, toolong);
    938 			(void) alarm((unsigned) timeout);
    939 			if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
    940 				reply(221, "You could at least say goodbye.");
    941 				dologout(0);
    942 			}
    943 			(void) alarm(0);
    944 #ifdef HASSETPROCTITLE
    945 			if (strncasecmp(cbuf, "PASS", 4) != NULL)
    946 				setproctitle("%s: %s", proctitle, cbuf);
    947 #endif /* HASSETPROCTITLE */
    948 			if ((cp = strchr(cbuf, '\r'))) {
    949 				*cp++ = '\n';
    950 				*cp = '\0';
    951 			}
    952 			if ((cp = strpbrk(cbuf, " \n")))
    953 				cpos = cp - cbuf;
    954 			if (cpos == 0)
    955 				cpos = 4;
    956 			c = cbuf[cpos];
    957 			cbuf[cpos] = '\0';
    958 			upper(cbuf);
    959 			p = lookup(cmdtab, cbuf);
    960 			cbuf[cpos] = c;
    961 			if (p != 0) {
    962 				if (p->implemented == 0) {
    963 					nack(p->name);
    964 					longjmp(errcatch,0);
    965 					/* NOTREACHED */
    966 				}
    967 				state = p->state;
    968 				yylval.s = p->name;
    969 				return (p->token);
    970 			}
    971 			break;
    972 
    973 		case SITECMD:
    974 			if (cbuf[cpos] == ' ') {
    975 				cpos++;
    976 				return (SP);
    977 			}
    978 			cp = &cbuf[cpos];
    979 			if ((cp2 = strpbrk(cp, " \n")))
    980 				cpos = cp2 - cbuf;
    981 			c = cbuf[cpos];
    982 			cbuf[cpos] = '\0';
    983 			upper(cp);
    984 			p = lookup(sitetab, cp);
    985 			cbuf[cpos] = c;
    986 			if (p != 0) {
    987 				if (p->implemented == 0) {
    988 					state = CMD;
    989 					nack(p->name);
    990 					longjmp(errcatch,0);
    991 					/* NOTREACHED */
    992 				}
    993 				state = p->state;
    994 				yylval.s = p->name;
    995 				return (p->token);
    996 			}
    997 			state = CMD;
    998 			break;
    999 
   1000 		case OSTR:
   1001 			if (cbuf[cpos] == '\n') {
   1002 				state = CMD;
   1003 				return (CRLF);
   1004 			}
   1005 			/* FALLTHROUGH */
   1006 
   1007 		case STR1:
   1008 		case ZSTR1:
   1009 		dostr1:
   1010 			if (cbuf[cpos] == ' ') {
   1011 				cpos++;
   1012 				state = state == OSTR ? STR2 : ++state;
   1013 				return (SP);
   1014 			}
   1015 			break;
   1016 
   1017 		case ZSTR2:
   1018 			if (cbuf[cpos] == '\n') {
   1019 				state = CMD;
   1020 				return (CRLF);
   1021 			}
   1022 			/* FALLTHROUGH */
   1023 
   1024 		case STR2:
   1025 			cp = &cbuf[cpos];
   1026 			n = strlen(cp);
   1027 			cpos += n - 1;
   1028 			/*
   1029 			 * Make sure the string is nonempty and \n terminated.
   1030 			 */
   1031 			if (n > 1 && cbuf[cpos] == '\n') {
   1032 				cbuf[cpos] = '\0';
   1033 				yylval.s = copy(cp);
   1034 				cbuf[cpos] = '\n';
   1035 				state = ARGS;
   1036 				return (STRING);
   1037 			}
   1038 			break;
   1039 
   1040 		case NSTR:
   1041 			if (cbuf[cpos] == ' ') {
   1042 				cpos++;
   1043 				return (SP);
   1044 			}
   1045 			if (isdigit(cbuf[cpos])) {
   1046 				cp = &cbuf[cpos];
   1047 				while (isdigit(cbuf[++cpos]))
   1048 					;
   1049 				c = cbuf[cpos];
   1050 				cbuf[cpos] = '\0';
   1051 				yylval.i = atoi(cp);
   1052 				cbuf[cpos] = c;
   1053 				state = STR1;
   1054 				return (NUMBER);
   1055 			}
   1056 			state = STR1;
   1057 			goto dostr1;
   1058 
   1059 		case ARGS:
   1060 			if (isdigit(cbuf[cpos])) {
   1061 				cp = &cbuf[cpos];
   1062 				while (isdigit(cbuf[++cpos]))
   1063 					;
   1064 				c = cbuf[cpos];
   1065 				cbuf[cpos] = '\0';
   1066 				yylval.i = atoi(cp);
   1067 				cbuf[cpos] = c;
   1068 				return (NUMBER);
   1069 			}
   1070 			switch (cbuf[cpos++]) {
   1071 
   1072 			case '\n':
   1073 				state = CMD;
   1074 				return (CRLF);
   1075 
   1076 			case ' ':
   1077 				return (SP);
   1078 
   1079 			case ',':
   1080 				return (COMMA);
   1081 
   1082 			case 'A':
   1083 			case 'a':
   1084 				return (A);
   1085 
   1086 			case 'B':
   1087 			case 'b':
   1088 				return (B);
   1089 
   1090 			case 'C':
   1091 			case 'c':
   1092 				return (C);
   1093 
   1094 			case 'E':
   1095 			case 'e':
   1096 				return (E);
   1097 
   1098 			case 'F':
   1099 			case 'f':
   1100 				return (F);
   1101 
   1102 			case 'I':
   1103 			case 'i':
   1104 				return (I);
   1105 
   1106 			case 'L':
   1107 			case 'l':
   1108 				return (L);
   1109 
   1110 			case 'N':
   1111 			case 'n':
   1112 				return (N);
   1113 
   1114 			case 'P':
   1115 			case 'p':
   1116 				return (P);
   1117 
   1118 			case 'R':
   1119 			case 'r':
   1120 				return (R);
   1121 
   1122 			case 'S':
   1123 			case 's':
   1124 				return (S);
   1125 
   1126 			case 'T':
   1127 			case 't':
   1128 				return (T);
   1129 
   1130 			}
   1131 			break;
   1132 
   1133 		default:
   1134 			fatal("Unknown state in scanner.");
   1135 		}
   1136 		yyerror((char *) 0);
   1137 		state = CMD;
   1138 		longjmp(errcatch,0);
   1139 	}
   1140 }
   1141 
   1142 void
   1143 upper(s)
   1144 	char *s;
   1145 {
   1146 	while (*s != '\0') {
   1147 		if (islower(*s))
   1148 			*s = toupper(*s);
   1149 		s++;
   1150 	}
   1151 }
   1152 
   1153 static char *
   1154 copy(s)
   1155 	char *s;
   1156 {
   1157 	char *p;
   1158 
   1159 	p = malloc((unsigned) strlen(s) + 1);
   1160 	if (p == NULL)
   1161 		fatal("Ran out of memory.");
   1162 	(void) strcpy(p, s);
   1163 	return (p);
   1164 }
   1165 
   1166 static void
   1167 help(ctab, s)
   1168 	struct tab *ctab;
   1169 	char *s;
   1170 {
   1171 	struct tab *c;
   1172 	int width, NCMDS;
   1173 	char *type;
   1174 
   1175 	if (ctab == sitetab)
   1176 		type = "SITE ";
   1177 	else
   1178 		type = "";
   1179 	width = 0, NCMDS = 0;
   1180 	for (c = ctab; c->name != NULL; c++) {
   1181 		int len = strlen(c->name);
   1182 
   1183 		if (len > width)
   1184 			width = len;
   1185 		NCMDS++;
   1186 	}
   1187 	width = (width + 8) &~ 7;
   1188 	if (s == 0) {
   1189 		int i, j, w;
   1190 		int columns, lines;
   1191 
   1192 		lreply(214, "The following %scommands are recognized %s.",
   1193 		    type, "(* =>'s unimplemented)");
   1194 		columns = 76 / width;
   1195 		if (columns == 0)
   1196 			columns = 1;
   1197 		lines = (NCMDS + columns - 1) / columns;
   1198 		for (i = 0; i < lines; i++) {
   1199 			printf("   ");
   1200 			for (j = 0; j < columns; j++) {
   1201 				c = ctab + j * lines + i;
   1202 				printf("%s%c", c->name,
   1203 					c->implemented ? ' ' : '*');
   1204 				if (c + lines >= &ctab[NCMDS])
   1205 					break;
   1206 				w = strlen(c->name) + 1;
   1207 				while (w < width) {
   1208 					putchar(' ');
   1209 					w++;
   1210 				}
   1211 			}
   1212 			printf("\r\n");
   1213 		}
   1214 		(void) fflush(stdout);
   1215 		reply(214, "Direct comments to ftp-bugs@%s.", hostname);
   1216 		return;
   1217 	}
   1218 	upper(s);
   1219 	c = lookup(ctab, s);
   1220 	if (c == (struct tab *)0) {
   1221 		reply(502, "Unknown command %s.", s);
   1222 		return;
   1223 	}
   1224 	if (c->implemented)
   1225 		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
   1226 	else
   1227 		reply(214, "%s%-*s\t%s; unimplemented.", type, width,
   1228 		    c->name, c->help);
   1229 }
   1230 
   1231 static void
   1232 sizecmd(filename)
   1233 	char *filename;
   1234 {
   1235 	switch (type) {
   1236 	case TYPE_L:
   1237 	case TYPE_I: {
   1238 		struct stat stbuf;
   1239 		if (stat(filename, &stbuf) < 0 || !S_ISREG(stbuf.st_mode))
   1240 			reply(550, "%s: not a plain file.", filename);
   1241 		else
   1242 			reply(213, "%qu", stbuf.st_size);
   1243 		break; }
   1244 	case TYPE_A: {
   1245 		FILE *fin;
   1246 		int c;
   1247 		off_t count;
   1248 		struct stat stbuf;
   1249 		fin = fopen(filename, "r");
   1250 		if (fin == NULL) {
   1251 			perror_reply(550, filename);
   1252 			return;
   1253 		}
   1254 		if (fstat(fileno(fin), &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) {
   1255 			reply(550, "%s: not a plain file.", filename);
   1256 			(void) fclose(fin);
   1257 			return;
   1258 		}
   1259 
   1260 		count = 0;
   1261 		while((c=getc(fin)) != EOF) {
   1262 			if (c == '\n')	/* will get expanded to \r\n */
   1263 				count++;
   1264 			count++;
   1265 		}
   1266 		(void) fclose(fin);
   1267 
   1268 		reply(213, "%qd", count);
   1269 		break; }
   1270 	default:
   1271 		reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
   1272 	}
   1273 }
   1274