genericunixprotocols/ftpsrv/src/ftpcmd.y
changeset 0 c6b0df440bee
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/genericunixprotocols/ftpsrv/src/ftpcmd.y	Tue Mar 02 10:33:16 2010 +0530
@@ -0,0 +1,1374 @@
+//
+// Copyright (c) 2005-2009 Nokia Corporation and/or its subsidiary(-ies).
+// All rights reserved.
+// This component and the accompanying materials are made available
+// under the terms of the License "Eclipse Public License v1.0"
+// which accompanies this distribution, and is available
+// at the URL "http://www.eclipse.org/legal/epl-v10.html".
+//
+// Initial Contributors:
+// Nokia Corporation - initial contribution.
+//
+// Contributors:
+//
+// Description:
+//
+
+/*
+ * Copyright (c) 1985, 1988, 1993, 1994
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *	@(#)ftpcmd.y	8.3 (Berkeley) 4/6/94
+ *	NetBSD: ftpcmd.y,v 1.7 1996/04/08 19:03:11 jtc Exp
+ *	OpenBSD: ftpcmd.y,v 1.16 1998/05/22 06:46:09 deraadt Exp
+ */
+
+/*
+ * Grammar for FTP commands.
+ * See RFC 959.
+ */
+
+%{
+
+char ftpcmd_rcsid[] = 
+  "$Id: ftpcmd.y,v 1.11 1999/10/09 02:32:12 dholland Exp $";
+
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+
+#include <netinet/in.h>
+#include <arpa/ftp.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <glob.h>
+#include <pwd.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <time.h>
+#include <unistd.h>
+
+#ifndef __linux__
+#include <tzfile.h>
+#else
+#define TM_YEAR_BASE 1900
+#endif
+
+#include "extern.h"
+
+extern	struct sockaddr_in data_dest;
+extern	int logged_in;
+extern	struct passwd *pw;
+extern	int guest;
+extern	int logging;
+extern	int type;
+extern	int form;
+extern	int debug;
+extern	int timeout;
+extern	int maxtimeout;
+extern  int pdata;
+extern	char hostname[], remotehost[];
+extern	char proctitle[];
+extern	int usedefault;
+extern  int transflag;
+extern  char tmpline[];
+extern	int portcheck;
+extern	struct sockaddr_in his_addr;
+
+off_t	restart_point;
+
+static	int cmd_type;
+static	int cmd_form;
+static	int cmd_bytesz;
+char	cbuf[512];
+char	*fromname;
+
+struct tab;
+static int	 yylex __P((void));
+//static void	 sizecmd __P((char *));
+static void	 help __P((struct tab *, char *));
+
+extern struct tab cmdtab[];
+extern struct tab sitetab[];
+
+%}
+
+%union {
+	int	i;
+	char   *s;
+}
+
+%token
+	A	B	C	E	F	I
+	L	N	P	R	S	T
+
+	SP	CRLF	COMMA
+
+	USER	PASS	ACCT	REIN	QUIT	PORT
+	PASV	TYPE	STRU	MODE	RETR	STOR
+	APPE	MLFL	MAIL	MSND	MSOM	MSAM
+	MRSQ	MRCP	ALLO	REST	RNFR	RNTO
+	ABOR	DELE	CWD	LIST	NLST	SITE
+	STAT	HELP	NOOP	MKD	RMD	PWD
+	CDUP	STOU	SMNT	SYST	SIZE	MDTM
+
+	UMASK	IDLE	CHMOD
+
+	LEXERR
+
+%token	<s> STRING
+%token	<i> NUMBER
+
+%type	<i> check_login octal_number byte_size
+%type	<i> struct_code mode_code type_code form_code
+%type	<s> pathstring pathname password username
+%type	<i> host_port
+
+%start	cmd_list
+
+%%
+
+cmd_list
+	: /* empty */
+	| cmd_list cmd
+		{
+			fromname = (char *) 0;
+			restart_point = (off_t) 0;
+		}
+	| cmd_list rcmd
+	;
+
+cmd
+	: USER SP username CRLF
+		{
+			user($3);
+			free($3);
+		}
+	| PASS SP password CRLF
+		{
+			pass($3);
+			memset($3, 0, strlen($3));
+			free($3);
+		}
+	| PORT check_login SP host_port CRLF
+		{
+			if ($2) {
+				if ($4) {
+					usedefault = 1;
+					reply(500,	
+					    "Illegal PORT rejected (range errors).");
+				} else if (portcheck &&
+				    ntohs(data_dest.sin_port) < IPPORT_RESERVED) {
+					usedefault = 1;
+					reply(500,
+					    "Illegal PORT rejected (reserved port).");
+				} else if (portcheck &&
+				    memcmp(&data_dest.sin_addr,
+				    &his_addr.sin_addr,
+				    sizeof data_dest.sin_addr)) {
+					usedefault = 1;
+					reply(500,
+					    "Illegal PORT rejected (address wrong).");
+				} else {
+					usedefault = 0;
+					if (pdata >= 0) {
+						(void) close(pdata);
+						pdata = -1;
+					}
+					reply(200, "PORT command successful.");
+				}
+			}
+		}
+	| PASV check_login CRLF
+		{
+			if ($2) {
+				passive();
+			}
+		}
+	| TYPE check_login SP type_code CRLF
+		{
+			if ($2) {
+				switch (cmd_type) {
+
+				case TYPE_A:
+					if (cmd_form == FORM_N) {
+						reply(200, "Type set to A.");
+						type = cmd_type;
+						form = cmd_form;
+					} else
+						reply(504, "Form must be N.");
+					break;
+
+				case TYPE_E:
+					reply(504, "Type E not implemented.");
+					break;
+	
+				case TYPE_I:
+					reply(200, "Type set to I.");
+					type = cmd_type;
+					break;
+
+				case TYPE_L:
+					if (cmd_bytesz == 8) {
+					       reply(200,
+					       "Type set to L (byte size 8).");
+					       type = cmd_type;
+					} else
+					    reply(504, "Byte size must be 8.");
+
+				}
+			}
+		}
+	| STRU check_login SP struct_code CRLF
+		{
+			if ($2) {
+				switch ($4) {
+
+				case STRU_F:
+					reply(200, "STRU F ok.");
+					break;
+
+				default:
+					reply(504, "Unimplemented STRU type.");
+				}
+			}
+		}
+	| MODE check_login SP mode_code CRLF
+		{
+			if ($2) {
+				switch ($4) {
+
+				case MODE_S:
+					reply(200, "MODE S ok.");
+					break;
+
+				default:
+					reply(502, "Unimplemented MODE type.");
+				}
+			}
+		}
+	| ALLO check_login SP NUMBER CRLF
+		{
+			if ($2) {
+				reply(202, "ALLO command ignored.");
+			}
+		}
+	| ALLO check_login SP NUMBER SP R SP NUMBER CRLF
+		{
+			if ($2) {
+				reply(202, "ALLO command ignored.");
+			}
+		}
+	| RETR check_login SP pathname CRLF
+		{
+			if ($2 && $4 != NULL)
+				retrieve((char *) 0, $4);
+			if ($4 != NULL)
+				free($4);
+		}
+	| STOR check_login SP pathname CRLF
+		{
+			if ($2 && $4 != NULL)
+				store($4, "w", 0);
+			if ($4 != NULL)
+				free($4);
+		}
+	| APPE check_login SP pathname CRLF
+		{
+			if ($2 && $4 != NULL)
+				store($4, "a", 0);
+			if ($4 != NULL)
+				free($4);
+		}
+	| NLST check_login CRLF
+		{
+			if ($2)
+				send_file_list(".",1);
+		}
+	| NLST check_login SP STRING CRLF
+		{
+			if ($2 && $4 != NULL)
+				send_file_list($4,1);
+			if ($4 != NULL)
+				free($4);
+		}
+	| LIST check_login CRLF
+		{
+			if ($2)
+				send_file_list(".",0);
+				//retrieve("/bin/ls -lgA", "");
+		}
+	| LIST check_login SP pathname CRLF
+		{
+			if ($2 && $4 != NULL)
+				send_file_list($4,0);
+				//retrieve("/bin/ls -lgA %s", $4);
+			if ($4 != NULL)
+				free($4);
+		}
+	| STAT check_login SP pathname CRLF
+		{
+			if ($2 && $4 != NULL)
+				statfilecmd($4);
+			if ($4 != NULL)
+				free($4);
+		}
+	| STAT check_login CRLF
+		{
+			if ($2)
+				statcmd();
+		}
+	| DELE check_login SP pathname CRLF
+		{
+			if ($2 && $4 != NULL)
+				dele($4);
+			if ($4 != NULL)
+				free($4);
+		}
+	| RNTO check_login SP pathname CRLF
+		{
+			if ($2) {
+				if (fromname) {
+					renamecmd(fromname, $4);
+					free(fromname);
+					fromname = (char *) 0;
+				} else {
+					reply(503, 
+					  "Bad sequence of commands.");
+				}
+			}
+			free($4);
+		}
+	| ABOR check_login CRLF
+		{
+			if ($2) 
+				reply(225, "ABOR command successful.");
+		}
+	| CWD check_login CRLF
+		{
+			if ($2)
+				cwd(pw->pw_dir);
+		}
+	| CWD check_login SP pathname CRLF
+		{
+			if ($2 && $4 != NULL)
+				cwd($4);
+			if ($4 != NULL)
+				free($4);
+		}
+	| HELP CRLF
+		{
+			help(cmdtab, (char *) 0);
+		}
+	| HELP SP STRING CRLF
+		{
+			char *cp = $3;
+
+			if (strncasecmp(cp, "SITE", 4) == 0) {
+				cp = $3 + 4;
+				if (*cp == ' ')
+					cp++;
+				if (*cp)
+					help(sitetab, cp);
+				else
+					help(sitetab, (char *) 0);
+			} else
+				help(cmdtab, $3);
+
+			if ($3 != NULL)
+				free ($3);
+		}
+	| NOOP CRLF
+		{
+			reply(200, "NOOP command successful.");
+		}
+	| MKD check_login SP pathname CRLF
+		{
+			if ($2 && $4 != NULL)
+				makedir($4);
+			if ($4 != NULL)
+				free($4);
+		}
+	| RMD check_login SP pathname CRLF
+		{
+			if ($2 && $4 != NULL)
+				removedir($4);
+			if ($4 != NULL)
+				free($4);
+		}
+	| PWD check_login CRLF
+		{
+			if ($2)
+				pwd();
+		}
+	| CDUP check_login CRLF
+		{
+			if ($2)
+				cwd("..");
+		}
+	| SITE SP HELP CRLF
+		{
+			help(sitetab, (char *) 0);
+		}
+	| SITE SP HELP SP STRING CRLF
+		{
+			help(sitetab, $5);
+
+			if ($5 != NULL)
+				free ($5);
+		}
+	| SITE SP UMASK check_login CRLF
+		{
+			int oldmask;
+
+			if ($4) {
+				oldmask = umask(0);
+				(void) umask(oldmask);
+				reply(200, "Current UMASK is %03o", oldmask);
+			}
+		}
+	| SITE SP UMASK check_login SP octal_number CRLF
+		{
+			int oldmask;
+
+			if ($4) {
+				if (($6 == -1) || ($6 > 0777)) {
+					reply(501, "Bad UMASK value");
+				} else {
+					oldmask = umask($6);
+					reply(200,
+					    "UMASK set to %03o (was %03o)",
+					    $6, oldmask);
+				}
+			}
+		}
+	| SITE SP CHMOD check_login SP octal_number SP pathname CRLF
+		{
+			if ($4 && ($8 != NULL)) {
+				if ($6 > 0777)
+					reply(501,
+				"CHMOD: Mode value must be between 0 and 0777");
+				else if (chmod($8, $6) < 0)
+					perror_reply(550, $8);
+				else
+					reply(200, "CHMOD command successful.");
+			}
+			if ($8 != NULL)
+				free($8);
+		}
+	| SITE SP check_login IDLE CRLF
+		{
+			if ($3)
+			  reply(200,
+	       		    "Current IDLE time limit is %d seconds; max %d",
+				timeout, maxtimeout);
+		}
+	| SITE SP check_login IDLE SP NUMBER CRLF
+		{
+			if ($3) {
+				if ($6 < 30 || $6 > maxtimeout) {
+				reply(501,
+	       		 "Maximum IDLE time must be between 30 and %d seconds",
+				    maxtimeout);
+				} else {
+					timeout = $6;
+					(void) alarm((unsigned) timeout);
+					reply(200,
+					 "Maximum IDLE time set to %d seconds",
+					    timeout);
+				}
+			}
+		}
+	| STOU check_login SP pathname CRLF
+		{
+			if ($2 && $4 != NULL)
+				store($4, "w", 1);
+			if ($4 != NULL)
+				free($4);
+		}
+	| SYST check_login CRLF
+		{
+			if ($2)
+#ifdef __linux__
+			reply(215, "UNIX Type: L%d (Linux)", CHAR_BIT);
+#else
+#ifdef unix
+#ifdef BSD
+			reply(215, "UNIX Type: L%d Version: BSD-%d",
+				CHAR_BIT, BSD);
+#else /* BSD */
+			reply(215, "UNIX Type: L%d", CHAR_BIT);
+#endif /* BSD */
+#else /* unix */
+#ifdef __SYMBIAN32__		
+			reply(215, "UNIX Type: L%d", CHAR_BIT);
+#else
+			reply(215, "UNKNOWN Type: L%d", CHAR_BIT);
+#endif /* __symbian32__ */	
+#endif /* unix */
+#endif /* __linux__ */
+		}
+
+		/*
+		 * SIZE is not in RFC959, but Postel has blessed it and
+		 * it will be in the updated RFC.
+		 *
+		 * Return size of file in a format suitable for
+		 * using with RESTART (we just count bytes).
+		 */
+	| SIZE check_login SP pathname CRLF
+		{
+			if ($2 && $4 != NULL)
+				sizecmd($4);
+			if ($4 != NULL)
+				free($4);
+		}
+
+		/*
+		 * MDTM is not in RFC959, but Postel has blessed it and
+		 * it will be in the updated RFC.
+		 *
+		 * Return modification time of file as an ISO 3307
+		 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
+		 * where xxx is the fractional second (of any precision,
+		 * not necessarily 3 digits)
+		 */
+	| MDTM check_login SP pathname CRLF
+		{
+			if ($2 && $4 != NULL) {
+				struct stat stbuf;
+				if (stat($4, &stbuf) < 0)
+					reply(550, "%s: %s",
+					    $4, strerror(errno));
+				else if (!S_ISREG(stbuf.st_mode)) {
+					reply(550, "%s: not a plain file.", $4);
+				} else {
+					struct tm *t;
+					t = gmtime(&stbuf.st_mtime);
+					reply(213,
+					    "%04d%02d%02d%02d%02d%02d",
+					    TM_YEAR_BASE + t->tm_year,
+					    t->tm_mon+1, t->tm_mday,
+					    t->tm_hour, t->tm_min, t->tm_sec);
+				}
+			}
+			if ($4 != NULL)
+				free($4);
+		}
+	| QUIT CRLF
+		{
+			reply(221, "Goodbye.");
+			dologout(0);
+		}
+	| error CRLF
+		{
+			yyerrok;
+		}
+	;
+rcmd
+	: RNFR check_login SP pathname CRLF
+		{
+			restart_point = (off_t) 0;
+			if ($2 && $4) {
+				fromname = renamefrom($4);
+				if (fromname == (char *) 0 && $4) {
+					free($4);
+				}
+			} else {
+				if ($4)
+					free ($4);
+			}
+		}
+
+	| REST check_login SP byte_size CRLF
+		{
+			if ($2) {
+			    fromname = (char *) 0;
+			    restart_point = $4;	/* XXX $4 is only "int" */
+			    reply(350, "Restarting at %qd. %s", 
+			       (quad_t) restart_point,
+			       "Send STORE or RETRIEVE to initiate transfer.");
+			}
+		}
+	;
+
+username
+	: STRING
+	;
+
+password
+	: /* empty */
+		{
+			$$ = (char *)calloc(1, sizeof(char));
+		}
+	| STRING
+	;
+
+byte_size
+	: NUMBER
+	;
+
+host_port
+	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
+		NUMBER COMMA NUMBER
+		{
+			char *a, *p;
+
+			if ($1 < 0 || $1 > 255 || $3 < 0 || $3 > 255 ||
+			    $5 < 0 || $5 > 255 || $7 < 0 || $7 > 255 ||
+			    $9 < 0 || $9 > 255 || $11 < 0 || $11 > 255) {
+				$$ = 1;
+			} else {
+#ifndef __linux__
+				data_dest.sin_len = sizeof(struct sockaddr_in);
+#endif
+				data_dest.sin_family = AF_INET;
+				p = (char *)&data_dest.sin_port;
+				p[0] = $9; p[1] = $11;
+				a = (char *)&data_dest.sin_addr;
+				a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7;
+				$$ = 0;
+			}
+		}
+	;
+
+form_code
+	: N
+		{
+			$$ = FORM_N;
+		}
+	| T
+		{
+			$$ = FORM_T;
+		}
+	| C
+		{
+			$$ = FORM_C;
+		}
+	;
+
+type_code
+	: A
+		{
+			cmd_type = TYPE_A;
+			cmd_form = FORM_N;
+		}
+	| A SP form_code
+		{
+			cmd_type = TYPE_A;
+			cmd_form = $3;
+		}
+	| E
+		{
+			cmd_type = TYPE_E;
+			cmd_form = FORM_N;
+		}
+	| E SP form_code
+		{
+			cmd_type = TYPE_E;
+			cmd_form = $3;
+		}
+	| I
+		{
+			cmd_type = TYPE_I;
+		}
+	| L
+		{
+			cmd_type = TYPE_L;
+			cmd_bytesz = CHAR_BIT;
+		}
+	| L SP byte_size
+		{
+			cmd_type = TYPE_L;
+			cmd_bytesz = $3;
+		}
+		/* this is for a bug in the BBN ftp */
+	| L byte_size
+		{
+			cmd_type = TYPE_L;
+			cmd_bytesz = $2;
+		}
+	;
+
+struct_code
+	: F
+		{
+			$$ = STRU_F;
+		}
+	| R
+		{
+			$$ = STRU_R;
+		}
+	| P
+		{
+			$$ = STRU_P;
+		}
+	;
+
+mode_code
+	: S
+		{
+			$$ = MODE_S;
+		}
+	| B
+		{
+			$$ = MODE_B;
+		}
+	| C
+		{
+			$$ = MODE_C;
+		}
+	;
+
+pathname
+	: pathstring
+		{
+			/*
+			 * Problem: this production is used for all pathname
+			 * processing, but only gives a 550 error reply.
+			 * This is a valid reply in some cases but not in others.
+			 */
+			if (logged_in && $1 && strchr($1, '~') != NULL) {
+				glob_t gl;
+#ifdef __linux__
+				/* see popen.c */
+				int flags = GLOB_NOCHECK;
+#else
+				int flags =
+				 GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE;
+#endif
+				char *pptr = $1;
+
+				/*
+				 * glob() will only find a leading ~, but
+				 * Netscape kindly puts a slash in front of
+				 * it for publish URLs.  There needs to be
+				 * a flag for glob() that expands tildes
+				 * anywhere in the string.
+				 */
+				if ((pptr[0] == '/') && (pptr[1] == '~'))
+					pptr++;
+
+				memset(&gl, 0, sizeof(gl));
+				if (glob(pptr, flags, NULL, &gl) ||
+				    gl.gl_pathc == 0) {
+					reply(550, "not found");
+					$$ = NULL;
+				} else {
+					$$ = strdup(gl.gl_pathv[0]);
+				}
+				globfree(&gl);
+				free($1);
+			} else
+				$$ = $1;
+		}
+	;
+
+pathstring
+	: STRING
+	;
+
+octal_number
+	: NUMBER
+		{
+			int ret, dec, multby, digit;
+
+			/*
+			 * Convert a number that was read as decimal number
+			 * to what it would be if it had been read as octal.
+			 */
+			dec = $1;
+			multby = 1;
+			ret = 0;
+			while (dec) {
+				digit = dec%10;
+				if (digit > 7) {
+					ret = -1;
+					break;
+				}
+				ret += digit * multby;
+				multby *= 8;
+				dec /= 10;
+			}
+			$$ = ret;
+		}
+	;
+
+
+check_login
+	: /* empty */
+		{
+			if (logged_in)
+				$$ = 1;
+			else {
+				reply(530, "Please login with USER and PASS.");
+				$$ = 0;
+			}
+		}
+	;
+
+%%
+
+extern jmp_buf errcatch;
+
+#define	CMD	0	/* beginning of command */
+#define	ARGS	1	/* expect miscellaneous arguments */
+#define	STR1	2	/* expect SP followed by STRING */
+#define	STR2	3	/* expect STRING */
+#define	OSTR	4	/* optional SP then STRING */
+#define	ZSTR1	5	/* SP then optional STRING */
+#define	ZSTR2	6	/* optional STRING after SP */
+#define	SITECMD	7	/* SITE command */
+#define	NSTR	8	/* Number followed by a string */
+
+struct tab {
+	const char	*name;
+	short	token;
+	short	state;
+	short	implemented;	/* 1 if command is implemented */
+	const char	*help;
+};
+
+struct tab cmdtab[] = {		/* In order defined in RFC 765 */
+	{ "USER", USER, STR1, 1,	"<sp> username" },
+	{ "PASS", PASS, ZSTR1, 1,	"<sp> password" },
+	{ "ACCT", ACCT, STR1, 0,	"(specify account)" },
+	{ "SMNT", SMNT, ARGS, 0,	"(structure mount)" },
+	{ "REIN", REIN, ARGS, 0,	"(reinitialize server state)" },
+	{ "QUIT", QUIT, ARGS, 1,	"(terminate service)", },
+	{ "PORT", PORT, ARGS, 1,	"<sp> b0, b1, b2, b3, b4" },
+	{ "PASV", PASV, ARGS, 1,	"(set server in passive mode)" },
+	{ "TYPE", TYPE, ARGS, 1,	"<sp> [ A | E | I | L ]" },
+	{ "STRU", STRU, ARGS, 1,	"(specify file structure)" },
+	{ "MODE", MODE, ARGS, 1,	"(specify transfer mode)" },
+	{ "RETR", RETR, STR1, 1,	"<sp> file-name" },
+	{ "STOR", STOR, STR1, 1,	"<sp> file-name" },
+	{ "APPE", APPE, STR1, 1,	"<sp> file-name" },
+	{ "MLFL", MLFL, OSTR, 0,	"(mail file)" },
+	{ "MAIL", MAIL, OSTR, 0,	"(mail to user)" },
+	{ "MSND", MSND, OSTR, 0,	"(mail send to terminal)" },
+	{ "MSOM", MSOM, OSTR, 0,	"(mail send to terminal or mailbox)" },
+	{ "MSAM", MSAM, OSTR, 0,	"(mail send to terminal and mailbox)" },
+	{ "MRSQ", MRSQ, OSTR, 0,	"(mail recipient scheme question)" },
+	{ "MRCP", MRCP, STR1, 0,	"(mail recipient)" },
+	{ "ALLO", ALLO, ARGS, 1,	"allocate storage (vacuously)" },
+	{ "REST", REST, ARGS, 1,	"<sp> offset (restart command)" },
+	{ "RNFR", RNFR, STR1, 1,	"<sp> file-name" },
+	{ "RNTO", RNTO, STR1, 1,	"<sp> file-name" },
+	{ "ABOR", ABOR, ARGS, 1,	"(abort operation)" },
+	{ "DELE", DELE, STR1, 1,	"<sp> file-name" },
+	{ "CWD",  CWD,  OSTR, 1,	"[ <sp> directory-name ]" },
+	{ "XCWD", CWD,	OSTR, 1,	"[ <sp> directory-name ]" },
+	{ "LIST", LIST, OSTR, 1,	"[ <sp> path-name ]" },
+	{ "NLST", NLST, OSTR, 1,	"[ <sp> path-name ]" },
+	{ "SITE", SITE, SITECMD, 1,	"site-cmd [ <sp> arguments ]" },
+	{ "SYST", SYST, ARGS, 1,	"(get type of operating system)" },
+	{ "STAT", STAT, OSTR, 1,	"[ <sp> path-name ]" },
+	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
+	{ "NOOP", NOOP, ARGS, 1,	"" },
+	{ "MKD",  MKD,  STR1, 1,	"<sp> path-name" },
+	{ "XMKD", MKD,  STR1, 1,	"<sp> path-name" },
+	{ "RMD",  RMD,  STR1, 1,	"<sp> path-name" },
+	{ "XRMD", RMD,  STR1, 1,	"<sp> path-name" },
+	{ "PWD",  PWD,  ARGS, 1,	"(return current directory)" },
+	{ "XPWD", PWD,  ARGS, 1,	"(return current directory)" },
+	{ "CDUP", CDUP, ARGS, 1,	"(change to parent directory)" },
+	{ "XCUP", CDUP, ARGS, 1,	"(change to parent directory)" },
+	{ "STOU", STOU, STR1, 1,	"<sp> file-name" },
+	{ "SIZE", SIZE, OSTR, 1,	"<sp> path-name" },
+	{ "MDTM", MDTM, OSTR, 1,	"<sp> path-name" },
+	{ NULL,   0,    0,    0,	0 }
+};
+
+struct tab sitetab[] = {
+	{ "UMASK", UMASK, ARGS, 1,	"[ <sp> umask ]" },
+	{ "IDLE", IDLE, ARGS, 1,	"[ <sp> maximum-idle-time ]" },
+	{ "CHMOD", CHMOD, NSTR, 1,	"<sp> mode <sp> file-name" },
+	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
+	{ NULL,   0,    0,    0,	0 }
+};
+
+static void	 help __P((struct tab *, char *));
+static struct tab *
+		 lookup __P((struct tab *, char *));
+static void	 sizecmd __P((char *));
+static int	 yylex __P((void));
+
+static struct tab *lookup(struct tab *p, char *cmd)
+{
+
+	for (; p->name != NULL; p++)
+		if (strcmp(cmd, p->name) == 0)
+			return (p);
+	return (0);
+}
+
+#include <arpa/telnet.h>
+
+/*
+ * getline - a hacked up version of fgets to ignore TELNET escape codes.
+ */
+char * ftpd_getline(char *s, int n, FILE *iop)
+{
+	int c;
+	register char *cs;
+
+	cs = s;
+/* tmpline may contain saved command from urgent mode interruption */
+	for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
+		*cs++ = tmpline[c];
+		if (tmpline[c] == '\n') {
+			*cs++ = '\0';
+			if (debug)
+				syslog(LOG_DEBUG, "command: %s", s);
+			tmpline[0] = '\0';
+			return(s);
+		}
+		if (c == 0)
+			tmpline[0] = '\0';
+	}
+	while ((c = getc(iop)) != EOF) {
+		c &= 0377;
+		if (c == IAC) {
+		    if ((c = getc(iop)) != EOF) {
+			c &= 0377;
+			switch (c) {
+			case WILL:
+			case WONT:
+				c = getc(iop);
+				printf("%c%c%c", IAC, DONT, 0377&c);
+				(void) fflush(stdout);
+				continue;
+			case DO:
+			case DONT:
+				c = getc(iop);
+				printf("%c%c%c", IAC, WONT, 0377&c);
+				(void) fflush(stdout);
+				continue;
+			case IAC:
+				break;
+			default:
+				continue;	/* ignore command */
+			}
+		    }
+		}
+		*cs++ = c;
+		if (--n <= 0 || c == '\n')
+			break;
+	}
+	if (c == EOF && cs == s)
+		return (NULL);
+	*cs++ = '\0';
+	if (debug) {
+		if (!guest && strncasecmp("pass ", s, 5) == 0) {
+			/* Don't syslog passwords */
+			syslog(LOG_DEBUG, "command: %.5s ???", s);
+		} else {
+			register char *cp;
+			register int len;
+
+			/* Don't syslog trailing CR-LF */
+			len = strlen(s);
+			cp = s + len - 1;
+			while (cp >= s && (*cp == '\n' || *cp == '\r')) {
+				--cp;
+				--len;
+			}
+			syslog(LOG_DEBUG, "command: %.*s", len, s);
+		}
+	}
+	return (s);
+}
+
+void toolong(int signo)
+{
+	(void)signo;
+
+	reply(421,
+	    "Timeout (%d seconds): closing control connection.", timeout);
+	if (logging)
+		syslog(LOG_INFO, "User %s timed out after %d seconds",
+		    (pw ? pw -> pw_name : "unknown"), timeout);
+	dologout(1);
+}
+
+static int yylex(void)
+{
+	static int cpos, state;
+	char *cp, *cp2;
+	struct tab *p;
+	int n, value;
+	char c;
+
+	for (;;) {
+		switch (state) {
+
+		case CMD:
+			(void) signal(SIGALRM, toolong);
+			(void) alarm((unsigned) timeout);
+			if (ftpd_getline(cbuf, sizeof(cbuf)-1, stdin)==NULL) {
+				reply(221, "You could at least say goodbye.");
+				dologout(0);
+			}
+			(void) alarm(0);
+			cp = strchr(cbuf, '\r');
+			if (cp) {
+				*cp++ = '\n';
+				*cp = '\0';
+			}
+#ifdef HASSETPROCTITLE
+			if (strncasecmp(cbuf, "PASS", 4) != 0) {
+				cp = strpbrk(cbuf, "\n");
+				if (cp) {
+					c = *cp;
+					*cp = '\0';
+					setproctitle("%s: %s", proctitle, cbuf);
+					*cp = c;
+				}
+			}
+#endif /* HASSETPROCTITLE */
+			cp = strpbrk(cbuf, " \n");
+			if (cp)
+				cpos = cp - cbuf;
+			if (cpos == 0)
+				cpos = 4;
+			c = cbuf[cpos];
+			cbuf[cpos] = '\0';
+			upper(cbuf);
+			p = lookup(cmdtab, cbuf);
+			cbuf[cpos] = c;
+			if (p != 0) {
+				if (p->implemented == 0) {
+					nack(p->name);
+					longjmp(errcatch,0);
+					/* NOTREACHED */
+				}
+				state = p->state;
+				yylval.s = (char *)p->name;  /* XXX */
+				return (p->token);
+			}
+			break;
+
+		case SITECMD:
+			if (cbuf[cpos] == ' ') {
+				cpos++;
+				return (SP);
+			}
+			cp = &cbuf[cpos];
+			cp2 = strpbrk(cp, " \n");
+			if (cp2)
+				cpos = cp2 - cbuf;
+			c = cbuf[cpos];
+			cbuf[cpos] = '\0';
+			upper(cp);
+			p = lookup(sitetab, cp);
+			cbuf[cpos] = c;
+			if (p != 0) {
+				if (p->implemented == 0) {
+					state = CMD;
+					nack(p->name);
+					longjmp(errcatch,0);
+					/* NOTREACHED */
+				}
+				state = p->state;
+				yylval.s = (char *) p->name;  /* XXX */
+				return (p->token);
+			}
+			state = CMD;
+			break;
+
+		case OSTR:
+			if (cbuf[cpos] == '\n') {
+				state = CMD;
+				return (CRLF);
+			}
+			/* FALLTHROUGH */
+
+		case STR1:
+		case ZSTR1:
+		dostr1:
+			if (cbuf[cpos] == ' ') {
+				cpos++;
+				/* DOH!!! who wrote this?
+				 * state = ++state; is undefined in C!
+				 * state = state == OSTR ? STR2 : ++state;
+				 * looks elegant but not correct, adding 'value'
+				 */
+				value = state == OSTR ? STR2 : ++state;
+				state = value;
+				return (SP);
+			}
+			break;
+
+		case ZSTR2:
+			if (cbuf[cpos] == '\n') {
+				state = CMD;
+				return (CRLF);
+			}
+			/* FALLTHROUGH */
+
+		case STR2:
+			cp = &cbuf[cpos];
+			n = strlen(cp);
+			cpos += n - 1;
+			/*
+			 * Make sure the string is nonempty and \n terminated.
+			 */
+			if (n > 1 && cbuf[cpos] == '\n') {
+				cbuf[cpos] = '\0';
+				yylval.s = strdup(cp);
+				if (yylval.s == NULL)
+					fatal("Ran out of memory.");
+				cbuf[cpos] = '\n';
+				state = ARGS;
+				return (STRING);
+			}
+			break;
+
+		case NSTR:
+			if (cbuf[cpos] == ' ') {
+				cpos++;
+				return (SP);
+			}
+			if (isdigit(cbuf[cpos])) {
+				cp = &cbuf[cpos];
+				while (isdigit(cbuf[++cpos]))
+					;
+				c = cbuf[cpos];
+				cbuf[cpos] = '\0';
+				yylval.i = atoi(cp);
+				cbuf[cpos] = c;
+				state = STR1;
+				return (NUMBER);
+			}
+			state = STR1;
+			goto dostr1;
+
+		case ARGS:
+			if (isdigit(cbuf[cpos])) {
+				cp = &cbuf[cpos];
+				while (isdigit(cbuf[++cpos]))
+					;
+				c = cbuf[cpos];
+				cbuf[cpos] = '\0';
+				yylval.i = atoi(cp);
+				cbuf[cpos] = c;
+				return (NUMBER);
+			}
+			switch (cbuf[cpos++]) {
+
+			case '\n':
+				state = CMD;
+				return (CRLF);
+
+			case ' ':
+				return (SP);
+
+			case ',':
+				return (COMMA);
+
+			case 'A':
+			case 'a':
+				return (A);
+
+			case 'B':
+			case 'b':
+				return (B);
+
+			case 'C':
+			case 'c':
+				return (C);
+
+			case 'E':
+			case 'e':
+				return (E);
+
+			case 'F':
+			case 'f':
+				return (F);
+
+			case 'I':
+			case 'i':
+				return (I);
+
+			case 'L':
+			case 'l':
+				return (L);
+
+			case 'N':
+			case 'n':
+				return (N);
+
+			case 'P':
+			case 'p':
+				return (P);
+
+			case 'R':
+			case 'r':
+				return (R);
+
+			case 'S':
+			case 's':
+				return (S);
+
+			case 'T':
+			case 't':
+				return (T);
+
+			}
+			break;
+
+		default:
+			fatal("Unknown state in scanner.");
+		}
+		yyerror((char *) 0);
+		state = CMD;
+		longjmp(errcatch,0);
+	}
+}
+
+void upper(char *s)
+{
+	while (*s != '\0') {
+		if (islower(*s))
+			*s = toupper(*s);
+		s++;
+	}
+}
+
+static void help(struct tab *ctab, char *s)
+{
+	struct tab *c;
+	int width, NCMDS;
+	const char *type;
+
+	if (ctab == sitetab)
+		type = "SITE ";
+	else
+		type = "";
+	width = 0, NCMDS = 0;
+	for (c = ctab; c->name != NULL; c++) {
+		int len = strlen(c->name);
+
+		if (len > width)
+			width = len;
+		NCMDS++;
+	}
+	width = (width + 8) &~ 7;
+	if (s == 0) {
+		int i, j, w;
+		int columns, lines;
+
+		lreply(214, "The following %scommands are recognized %s.",
+		    type, "(* =>'s unimplemented)");
+		columns = 76 / width;
+		if (columns == 0)
+			columns = 1;
+		lines = (NCMDS + columns - 1) / columns;
+		for (i = 0; i < lines; i++) {
+			printf("   ");
+			for (j = 0; j < columns; j++) {
+				c = ctab + j * lines + i;
+				printf("%s%c", c->name,
+					c->implemented ? ' ' : '*');
+				if (c + lines >= &ctab[NCMDS])
+					break;
+				w = strlen(c->name) + 1;
+				while (w < width) {
+					putchar(' ');
+					w++;
+				}
+			}
+			printf("\r\n");
+		}
+		(void) fflush(stdout);
+		reply(214, "Direct comments to ftp-bugs@%s.", hostname);
+		return;
+	}
+	upper(s);
+	c = lookup(ctab, s);
+	if (c == (struct tab *)0) {
+		reply(502, "Unknown command %s.", s);
+		return;
+	}
+	if (c->implemented)
+		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
+	else
+		reply(214, "%s%-*s\t%s; unimplemented.", type, width,
+		    c->name, c->help);
+}
+
+static void sizecmd(char *filename)
+{
+	switch (type) {
+	case TYPE_L:
+	case TYPE_I: {
+		struct stat stbuf;
+		if (stat(filename, &stbuf) < 0 || !S_ISREG(stbuf.st_mode))
+			reply(550, "%s: not a plain file.", filename);
+		else
+			reply(213, "%qu", (quad_t) stbuf.st_size);
+		break; }
+	case TYPE_A: {
+		FILE *fin;
+		int c;
+		off_t count;
+		struct stat stbuf;
+		fin = fopen(filename, "r");
+		if (fin == NULL) {
+			perror_reply(550, filename);
+			return;
+		}
+		if (fstat(fileno(fin), &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) {
+			reply(550, "%s: not a plain file.", filename);
+			(void) fclose(fin);
+			return;
+		}
+
+		count = 0;
+		while((c=getc(fin)) != EOF) {
+			if (c == '\n')	/* will get expanded to \r\n */
+				count++;
+			count++;
+		}
+		(void) fclose(fin);
+
+		reply(213, "%qd", (quad_t) count);
+		break; }
+	default:
+		reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
+	}
+}