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