ftpcmd.y revision 29574
11592Srgrimes/*
21592Srgrimes * Copyright (c) 1985, 1988, 1993, 1994
31592Srgrimes *	The Regents of the University of California.  All rights reserved.
41592Srgrimes *
51592Srgrimes * Redistribution and use in source and binary forms, with or without
61592Srgrimes * modification, are permitted provided that the following conditions
71592Srgrimes * are met:
81592Srgrimes * 1. Redistributions of source code must retain the above copyright
91592Srgrimes *    notice, this list of conditions and the following disclaimer.
101592Srgrimes * 2. Redistributions in binary form must reproduce the above copyright
111592Srgrimes *    notice, this list of conditions and the following disclaimer in the
121592Srgrimes *    documentation and/or other materials provided with the distribution.
131592Srgrimes * 3. All advertising materials mentioning features or use of this software
141592Srgrimes *    must display the following acknowledgement:
151592Srgrimes *	This product includes software developed by the University of
161592Srgrimes *	California, Berkeley and its contributors.
171592Srgrimes * 4. Neither the name of the University nor the names of its contributors
181592Srgrimes *    may be used to endorse or promote products derived from this software
191592Srgrimes *    without specific prior written permission.
201592Srgrimes *
211592Srgrimes * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
221592Srgrimes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
231592Srgrimes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
241592Srgrimes * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
251592Srgrimes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
261592Srgrimes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
271592Srgrimes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
281592Srgrimes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
291592Srgrimes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
301592Srgrimes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
311592Srgrimes * SUCH DAMAGE.
321592Srgrimes *
331592Srgrimes *	@(#)ftpcmd.y	8.3 (Berkeley) 4/6/94
3429574Sphk *	$Id: ftpcmd.y,v 1.11 1997/07/24 09:26:10 davidn Exp $
351592Srgrimes */
361592Srgrimes
371592Srgrimes/*
381592Srgrimes * Grammar for FTP commands.
391592Srgrimes * See RFC 959.
401592Srgrimes */
411592Srgrimes
421592Srgrimes%{
431592Srgrimes
441592Srgrimes#ifndef lint
451592Srgrimesstatic char sccsid[] = "@(#)ftpcmd.y	8.3 (Berkeley) 4/6/94";
461592Srgrimes#endif /* not lint */
471592Srgrimes
481592Srgrimes#include <sys/param.h>
491592Srgrimes#include <sys/socket.h>
501592Srgrimes#include <sys/stat.h>
511592Srgrimes
521592Srgrimes#include <netinet/in.h>
531592Srgrimes#include <arpa/ftp.h>
541592Srgrimes
551592Srgrimes#include <ctype.h>
561592Srgrimes#include <errno.h>
571592Srgrimes#include <glob.h>
581592Srgrimes#include <pwd.h>
591592Srgrimes#include <setjmp.h>
601592Srgrimes#include <signal.h>
611592Srgrimes#include <stdio.h>
621592Srgrimes#include <stdlib.h>
631592Srgrimes#include <string.h>
641592Srgrimes#include <syslog.h>
651592Srgrimes#include <time.h>
661592Srgrimes#include <unistd.h>
6713139Speter#include <libutil.h>
681592Srgrimes
691592Srgrimes#include "extern.h"
701592Srgrimes
7117433Spstextern	struct sockaddr_in data_dest, his_addr;
721592Srgrimesextern	int logged_in;
731592Srgrimesextern	struct passwd *pw;
741592Srgrimesextern	int guest;
7517435Spstextern 	int paranoid;
761592Srgrimesextern	int logging;
771592Srgrimesextern	int type;
781592Srgrimesextern	int form;
791592Srgrimesextern	int debug;
801592Srgrimesextern	int timeout;
811592Srgrimesextern	int maxtimeout;
821592Srgrimesextern  int pdata;
8327650Sdavidnextern	char *hostname;
8427650Sdavidnextern	char remotehost[];
851592Srgrimesextern	char proctitle[];
861592Srgrimesextern	int usedefault;
871592Srgrimesextern  int transflag;
881592Srgrimesextern  char tmpline[];
891592Srgrimes
901592Srgrimesoff_t	restart_point;
911592Srgrimes
921592Srgrimesstatic	int cmd_type;
931592Srgrimesstatic	int cmd_form;
941592Srgrimesstatic	int cmd_bytesz;
951592Srgrimeschar	cbuf[512];
961592Srgrimeschar	*fromname;
971592Srgrimes
981592Srgrimes%}
991592Srgrimes
1001592Srgrimes%union {
1011592Srgrimes	int	i;
1021592Srgrimes	char   *s;
1031592Srgrimes}
1041592Srgrimes
1051592Srgrimes%token
1061592Srgrimes	A	B	C	E	F	I
1071592Srgrimes	L	N	P	R	S	T
1081592Srgrimes
1091592Srgrimes	SP	CRLF	COMMA
1101592Srgrimes
1111592Srgrimes	USER	PASS	ACCT	REIN	QUIT	PORT
1121592Srgrimes	PASV	TYPE	STRU	MODE	RETR	STOR
1131592Srgrimes	APPE	MLFL	MAIL	MSND	MSOM	MSAM
1141592Srgrimes	MRSQ	MRCP	ALLO	REST	RNFR	RNTO
1151592Srgrimes	ABOR	DELE	CWD	LIST	NLST	SITE
1161592Srgrimes	STAT	HELP	NOOP	MKD	RMD	PWD
1171592Srgrimes	CDUP	STOU	SMNT	SYST	SIZE	MDTM
1181592Srgrimes
1191592Srgrimes	UMASK	IDLE	CHMOD
1201592Srgrimes
1211592Srgrimes	LEXERR
1221592Srgrimes
1231592Srgrimes%token	<s> STRING
1241592Srgrimes%token	<i> NUMBER
1251592Srgrimes
1261592Srgrimes%type	<i> check_login octal_number byte_size
1271592Srgrimes%type	<i> struct_code mode_code type_code form_code
1281592Srgrimes%type	<s> pathstring pathname password username
1291592Srgrimes
1301592Srgrimes%start	cmd_list
1311592Srgrimes
1321592Srgrimes%%
1331592Srgrimes
1341592Srgrimescmd_list
1351592Srgrimes	: /* empty */
1361592Srgrimes	| cmd_list cmd
1371592Srgrimes		{
1381592Srgrimes			fromname = (char *) 0;
1391592Srgrimes			restart_point = (off_t) 0;
1401592Srgrimes		}
1411592Srgrimes	| cmd_list rcmd
1421592Srgrimes	;
1431592Srgrimes
1441592Srgrimescmd
1451592Srgrimes	: USER SP username CRLF
1461592Srgrimes		{
1471592Srgrimes			user($3);
1481592Srgrimes			free($3);
1491592Srgrimes		}
1501592Srgrimes	| PASS SP password CRLF
1511592Srgrimes		{
1521592Srgrimes			pass($3);
1531592Srgrimes			free($3);
1541592Srgrimes		}
15517433Spst	| PORT check_login SP host_port CRLF
1561592Srgrimes		{
15717433Spst			if ($2) {
15817435Spst				if (paranoid &&
15917435Spst				    ((ntohs(data_dest.sin_port) <
16017435Spst				      IPPORT_RESERVED) ||
16117435Spst				     memcmp(&data_dest.sin_addr,
16217435Spst					    &his_addr.sin_addr,
16317435Spst					    sizeof(data_dest.sin_addr)))) {
16417433Spst					usedefault = 1;
16517433Spst					reply(500,
16617433Spst					      "Illegal PORT range rejected.");
16717435Spst				} else {
16817433Spst					usedefault = 0;
16917433Spst					if (pdata >= 0) {
17017433Spst						(void) close(pdata);
17117433Spst						pdata = -1;
17217433Spst					}
17317433Spst					reply(200, "PORT command successful.");
17417433Spst				}
1751592Srgrimes			}
1761592Srgrimes		}
17717433Spst	| PASV check_login CRLF
1781592Srgrimes		{
17917433Spst			if ($2)
18017433Spst				passive();
1811592Srgrimes		}
1821592Srgrimes	| TYPE SP type_code CRLF
1831592Srgrimes		{
1841592Srgrimes			switch (cmd_type) {
1851592Srgrimes
1861592Srgrimes			case TYPE_A:
1871592Srgrimes				if (cmd_form == FORM_N) {
1881592Srgrimes					reply(200, "Type set to A.");
1891592Srgrimes					type = cmd_type;
1901592Srgrimes					form = cmd_form;
1911592Srgrimes				} else
1921592Srgrimes					reply(504, "Form must be N.");
1931592Srgrimes				break;
1941592Srgrimes
1951592Srgrimes			case TYPE_E:
1961592Srgrimes				reply(504, "Type E not implemented.");
1971592Srgrimes				break;
1981592Srgrimes
1991592Srgrimes			case TYPE_I:
2001592Srgrimes				reply(200, "Type set to I.");
2011592Srgrimes				type = cmd_type;
2021592Srgrimes				break;
2031592Srgrimes
2041592Srgrimes			case TYPE_L:
2051592Srgrimes#if NBBY == 8
2061592Srgrimes				if (cmd_bytesz == 8) {
2071592Srgrimes					reply(200,
2081592Srgrimes					    "Type set to L (byte size 8).");
2091592Srgrimes					type = cmd_type;
2101592Srgrimes				} else
2111592Srgrimes					reply(504, "Byte size must be 8.");
2121592Srgrimes#else /* NBBY == 8 */
2131592Srgrimes				UNIMPLEMENTED for NBBY != 8
2141592Srgrimes#endif /* NBBY == 8 */
2151592Srgrimes			}
2161592Srgrimes		}
2171592Srgrimes	| STRU SP struct_code CRLF
2181592Srgrimes		{
2191592Srgrimes			switch ($3) {
2201592Srgrimes
2211592Srgrimes			case STRU_F:
2221592Srgrimes				reply(200, "STRU F ok.");
2231592Srgrimes				break;
2241592Srgrimes
2251592Srgrimes			default:
2261592Srgrimes				reply(504, "Unimplemented STRU type.");
2271592Srgrimes			}
2281592Srgrimes		}
2291592Srgrimes	| MODE SP mode_code CRLF
2301592Srgrimes		{
2311592Srgrimes			switch ($3) {
2321592Srgrimes
2331592Srgrimes			case MODE_S:
2341592Srgrimes				reply(200, "MODE S ok.");
2351592Srgrimes				break;
2361592Srgrimes
2371592Srgrimes			default:
2381592Srgrimes				reply(502, "Unimplemented MODE type.");
2391592Srgrimes			}
2401592Srgrimes		}
2411592Srgrimes	| ALLO SP NUMBER CRLF
2421592Srgrimes		{
2431592Srgrimes			reply(202, "ALLO command ignored.");
2441592Srgrimes		}
2451592Srgrimes	| ALLO SP NUMBER SP R SP NUMBER CRLF
2461592Srgrimes		{
2471592Srgrimes			reply(202, "ALLO command ignored.");
2481592Srgrimes		}
2491592Srgrimes	| RETR check_login SP pathname CRLF
2501592Srgrimes		{
2511592Srgrimes			if ($2 && $4 != NULL)
2521592Srgrimes				retrieve((char *) 0, $4);
2531592Srgrimes			if ($4 != NULL)
2541592Srgrimes				free($4);
2551592Srgrimes		}
2561592Srgrimes	| STOR check_login SP pathname CRLF
2571592Srgrimes		{
2581592Srgrimes			if ($2 && $4 != NULL)
2591592Srgrimes				store($4, "w", 0);
2601592Srgrimes			if ($4 != NULL)
2611592Srgrimes				free($4);
2621592Srgrimes		}
2631592Srgrimes	| APPE check_login SP pathname CRLF
2641592Srgrimes		{
2651592Srgrimes			if ($2 && $4 != NULL)
2661592Srgrimes				store($4, "a", 0);
2671592Srgrimes			if ($4 != NULL)
2681592Srgrimes				free($4);
2691592Srgrimes		}
2701592Srgrimes	| NLST check_login CRLF
2711592Srgrimes		{
2721592Srgrimes			if ($2)
2731592Srgrimes				send_file_list(".");
2741592Srgrimes		}
2751592Srgrimes	| NLST check_login SP STRING CRLF
2761592Srgrimes		{
2771592Srgrimes			if ($2 && $4 != NULL)
2781592Srgrimes				send_file_list($4);
2791592Srgrimes			if ($4 != NULL)
2801592Srgrimes				free($4);
2811592Srgrimes		}
2821592Srgrimes	| LIST check_login CRLF
2831592Srgrimes		{
2841592Srgrimes			if ($2)
2851592Srgrimes				retrieve("/bin/ls -lgA", "");
2861592Srgrimes		}
2871592Srgrimes	| LIST check_login SP pathname CRLF
2881592Srgrimes		{
2891592Srgrimes			if ($2 && $4 != NULL)
2901592Srgrimes				retrieve("/bin/ls -lgA %s", $4);
2911592Srgrimes			if ($4 != NULL)
2921592Srgrimes				free($4);
2931592Srgrimes		}
2941592Srgrimes	| STAT check_login SP pathname CRLF
2951592Srgrimes		{
2961592Srgrimes			if ($2 && $4 != NULL)
2971592Srgrimes				statfilecmd($4);
2981592Srgrimes			if ($4 != NULL)
2991592Srgrimes				free($4);
3001592Srgrimes		}
3011592Srgrimes	| STAT CRLF
3021592Srgrimes		{
3031592Srgrimes			statcmd();
3041592Srgrimes		}
3051592Srgrimes	| DELE check_login SP pathname CRLF
3061592Srgrimes		{
3071592Srgrimes			if ($2 && $4 != NULL)
3081592Srgrimes				delete($4);
3091592Srgrimes			if ($4 != NULL)
3101592Srgrimes				free($4);
3111592Srgrimes		}
31217433Spst	| RNTO check_login SP pathname CRLF
3131592Srgrimes		{
31417433Spst			if ($2) {
31517433Spst				if (fromname) {
31617433Spst					renamecmd(fromname, $4);
31717433Spst					free(fromname);
31817433Spst					fromname = (char *) 0;
31917433Spst				} else {
32017433Spst					reply(503, "Bad sequence of commands.");
32117433Spst				}
3221592Srgrimes			}
32317433Spst			free($4);
3241592Srgrimes		}
3251592Srgrimes	| ABOR CRLF
3261592Srgrimes		{
3271592Srgrimes			reply(225, "ABOR command successful.");
3281592Srgrimes		}
3291592Srgrimes	| CWD check_login CRLF
3301592Srgrimes		{
3311592Srgrimes			if ($2)
3321592Srgrimes				cwd(pw->pw_dir);
3331592Srgrimes		}
3341592Srgrimes	| CWD check_login SP pathname CRLF
3351592Srgrimes		{
3361592Srgrimes			if ($2 && $4 != NULL)
3371592Srgrimes				cwd($4);
3381592Srgrimes			if ($4 != NULL)
3391592Srgrimes				free($4);
3401592Srgrimes		}
3411592Srgrimes	| HELP CRLF
3421592Srgrimes		{
3431592Srgrimes			help(cmdtab, (char *) 0);
3441592Srgrimes		}
3451592Srgrimes	| HELP SP STRING CRLF
3461592Srgrimes		{
3471592Srgrimes			char *cp = $3;
3481592Srgrimes
3491592Srgrimes			if (strncasecmp(cp, "SITE", 4) == 0) {
3501592Srgrimes				cp = $3 + 4;
3511592Srgrimes				if (*cp == ' ')
3521592Srgrimes					cp++;
3531592Srgrimes				if (*cp)
3541592Srgrimes					help(sitetab, cp);
3551592Srgrimes				else
3561592Srgrimes					help(sitetab, (char *) 0);
3571592Srgrimes			} else
3581592Srgrimes				help(cmdtab, $3);
3591592Srgrimes		}
3601592Srgrimes	| NOOP CRLF
3611592Srgrimes		{
3621592Srgrimes			reply(200, "NOOP command successful.");
3631592Srgrimes		}
3641592Srgrimes	| MKD check_login SP pathname CRLF
3651592Srgrimes		{
3661592Srgrimes			if ($2 && $4 != NULL)
3671592Srgrimes				makedir($4);
3681592Srgrimes			if ($4 != NULL)
3691592Srgrimes				free($4);
3701592Srgrimes		}
3711592Srgrimes	| RMD check_login SP pathname CRLF
3721592Srgrimes		{
3731592Srgrimes			if ($2 && $4 != NULL)
3741592Srgrimes				removedir($4);
3751592Srgrimes			if ($4 != NULL)
3761592Srgrimes				free($4);
3771592Srgrimes		}
3781592Srgrimes	| PWD check_login CRLF
3791592Srgrimes		{
3801592Srgrimes			if ($2)
3811592Srgrimes				pwd();
3821592Srgrimes		}
3831592Srgrimes	| CDUP check_login CRLF
3841592Srgrimes		{
3851592Srgrimes			if ($2)
3861592Srgrimes				cwd("..");
3871592Srgrimes		}
3881592Srgrimes	| SITE SP HELP CRLF
3891592Srgrimes		{
3901592Srgrimes			help(sitetab, (char *) 0);
3911592Srgrimes		}
3921592Srgrimes	| SITE SP HELP SP STRING CRLF
3931592Srgrimes		{
3941592Srgrimes			help(sitetab, $5);
3951592Srgrimes		}
3961592Srgrimes	| SITE SP UMASK check_login CRLF
3971592Srgrimes		{
3981592Srgrimes			int oldmask;
3991592Srgrimes
4001592Srgrimes			if ($4) {
4011592Srgrimes				oldmask = umask(0);
4021592Srgrimes				(void) umask(oldmask);
4031592Srgrimes				reply(200, "Current UMASK is %03o", oldmask);
4041592Srgrimes			}
4051592Srgrimes		}
4061592Srgrimes	| SITE SP UMASK check_login SP octal_number CRLF
4071592Srgrimes		{
4081592Srgrimes			int oldmask;
4091592Srgrimes
4101592Srgrimes			if ($4) {
4111592Srgrimes				if (($6 == -1) || ($6 > 0777)) {
4121592Srgrimes					reply(501, "Bad UMASK value");
4131592Srgrimes				} else {
4141592Srgrimes					oldmask = umask($6);
4151592Srgrimes					reply(200,
4161592Srgrimes					    "UMASK set to %03o (was %03o)",
4171592Srgrimes					    $6, oldmask);
4181592Srgrimes				}
4191592Srgrimes			}
4201592Srgrimes		}
4211592Srgrimes	| SITE SP CHMOD check_login SP octal_number SP pathname CRLF
4221592Srgrimes		{
4231592Srgrimes			if ($4 && ($8 != NULL)) {
4241592Srgrimes				if ($6 > 0777)
4251592Srgrimes					reply(501,
4261592Srgrimes				"CHMOD: Mode value must be between 0 and 0777");
4271592Srgrimes				else if (chmod($8, $6) < 0)
4281592Srgrimes					perror_reply(550, $8);
4291592Srgrimes				else
4301592Srgrimes					reply(200, "CHMOD command successful.");
4311592Srgrimes			}
4321592Srgrimes			if ($8 != NULL)
4331592Srgrimes				free($8);
4341592Srgrimes		}
4351592Srgrimes	| SITE SP IDLE CRLF
4361592Srgrimes		{
4371592Srgrimes			reply(200,
4381592Srgrimes			    "Current IDLE time limit is %d seconds; max %d",
4391592Srgrimes				timeout, maxtimeout);
4401592Srgrimes		}
4411592Srgrimes	| SITE SP IDLE SP NUMBER CRLF
4421592Srgrimes		{
4431592Srgrimes			if ($5 < 30 || $5 > maxtimeout) {
4441592Srgrimes				reply(501,
4451592Srgrimes			"Maximum IDLE time must be between 30 and %d seconds",
4461592Srgrimes				    maxtimeout);
4471592Srgrimes			} else {
4481592Srgrimes				timeout = $5;
4491592Srgrimes				(void) alarm((unsigned) timeout);
4501592Srgrimes				reply(200,
4511592Srgrimes				    "Maximum IDLE time set to %d seconds",
4521592Srgrimes				    timeout);
4531592Srgrimes			}
4541592Srgrimes		}
4551592Srgrimes	| STOU check_login SP pathname CRLF
4561592Srgrimes		{
4571592Srgrimes			if ($2 && $4 != NULL)
4581592Srgrimes				store($4, "w", 1);
4591592Srgrimes			if ($4 != NULL)
4601592Srgrimes				free($4);
4611592Srgrimes		}
4621592Srgrimes	| SYST CRLF
4631592Srgrimes		{
4641592Srgrimes#ifdef unix
4651592Srgrimes#ifdef BSD
4661592Srgrimes			reply(215, "UNIX Type: L%d Version: BSD-%d",
4671592Srgrimes				NBBY, BSD);
4681592Srgrimes#else /* BSD */
4691592Srgrimes			reply(215, "UNIX Type: L%d", NBBY);
4701592Srgrimes#endif /* BSD */
4711592Srgrimes#else /* unix */
4721592Srgrimes			reply(215, "UNKNOWN Type: L%d", NBBY);
4731592Srgrimes#endif /* unix */
4741592Srgrimes		}
4751592Srgrimes
4761592Srgrimes		/*
4771592Srgrimes		 * SIZE is not in RFC959, but Postel has blessed it and
4781592Srgrimes		 * it will be in the updated RFC.
4791592Srgrimes		 *
4801592Srgrimes		 * Return size of file in a format suitable for
4811592Srgrimes		 * using with RESTART (we just count bytes).
4821592Srgrimes		 */
4831592Srgrimes	| SIZE check_login SP pathname CRLF
4841592Srgrimes		{
4851592Srgrimes			if ($2 && $4 != NULL)
4861592Srgrimes				sizecmd($4);
4871592Srgrimes			if ($4 != NULL)
4881592Srgrimes				free($4);
4891592Srgrimes		}
4901592Srgrimes
4911592Srgrimes		/*
4921592Srgrimes		 * MDTM is not in RFC959, but Postel has blessed it and
4931592Srgrimes		 * it will be in the updated RFC.
4941592Srgrimes		 *
4951592Srgrimes		 * Return modification time of file as an ISO 3307
4961592Srgrimes		 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
4971592Srgrimes		 * where xxx is the fractional second (of any precision,
4981592Srgrimes		 * not necessarily 3 digits)
4991592Srgrimes		 */
5001592Srgrimes	| MDTM check_login SP pathname CRLF
5011592Srgrimes		{
5021592Srgrimes			if ($2 && $4 != NULL) {
5031592Srgrimes				struct stat stbuf;
5041592Srgrimes				if (stat($4, &stbuf) < 0)
5051592Srgrimes					reply(550, "%s: %s",
5061592Srgrimes					    $4, strerror(errno));
5071592Srgrimes				else if (!S_ISREG(stbuf.st_mode)) {
5081592Srgrimes					reply(550, "%s: not a plain file.", $4);
5091592Srgrimes				} else {
5101592Srgrimes					struct tm *t;
5111592Srgrimes					t = gmtime(&stbuf.st_mtime);
5121592Srgrimes					reply(213,
51317435Spst					    "%04d%02d%02d%02d%02d%02d",
51417435Spst					    1900 + t->tm_year,
51517435Spst					    t->tm_mon+1, t->tm_mday,
5161592Srgrimes					    t->tm_hour, t->tm_min, t->tm_sec);
5171592Srgrimes				}
5181592Srgrimes			}
5191592Srgrimes			if ($4 != NULL)
5201592Srgrimes				free($4);
5211592Srgrimes		}
5221592Srgrimes	| QUIT CRLF
5231592Srgrimes		{
5241592Srgrimes			reply(221, "Goodbye.");
5251592Srgrimes			dologout(0);
5261592Srgrimes		}
5271592Srgrimes	| error CRLF
5281592Srgrimes		{
5291592Srgrimes			yyerrok;
5301592Srgrimes		}
5311592Srgrimes	;
5321592Srgrimesrcmd
5331592Srgrimes	: RNFR check_login SP pathname CRLF
5341592Srgrimes		{
5351592Srgrimes			char *renamefrom();
5361592Srgrimes
5371592Srgrimes			restart_point = (off_t) 0;
5381592Srgrimes			if ($2 && $4) {
5391592Srgrimes				fromname = renamefrom($4);
5401592Srgrimes				if (fromname == (char *) 0 && $4) {
5411592Srgrimes					free($4);
5421592Srgrimes				}
5431592Srgrimes			}
5441592Srgrimes		}
5451592Srgrimes	| REST SP byte_size CRLF
5461592Srgrimes		{
5471592Srgrimes			fromname = (char *) 0;
5481592Srgrimes			restart_point = $3;	/* XXX $3 is only "int" */
5491592Srgrimes			reply(350, "Restarting at %qd. %s", restart_point,
5501592Srgrimes			    "Send STORE or RETRIEVE to initiate transfer.");
5511592Srgrimes		}
5521592Srgrimes	;
5531592Srgrimes
5541592Srgrimesusername
5551592Srgrimes	: STRING
5561592Srgrimes	;
5571592Srgrimes
5581592Srgrimespassword
5591592Srgrimes	: /* empty */
5601592Srgrimes		{
5611592Srgrimes			$$ = (char *)calloc(1, sizeof(char));
5621592Srgrimes		}
5631592Srgrimes	| STRING
5641592Srgrimes	;
5651592Srgrimes
5661592Srgrimesbyte_size
5671592Srgrimes	: NUMBER
5681592Srgrimes	;
5691592Srgrimes
5701592Srgrimeshost_port
5711592Srgrimes	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
5721592Srgrimes		NUMBER COMMA NUMBER
5731592Srgrimes		{
5741592Srgrimes			char *a, *p;
5751592Srgrimes
57617435Spst			data_dest.sin_len = sizeof(struct sockaddr_in);
57717435Spst			data_dest.sin_family = AF_INET;
57817435Spst			p = (char *)&data_dest.sin_port;
57917435Spst			p[0] = $9; p[1] = $11;
5801592Srgrimes			a = (char *)&data_dest.sin_addr;
5811592Srgrimes			a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7;
5821592Srgrimes		}
5831592Srgrimes	;
5841592Srgrimes
5851592Srgrimesform_code
5861592Srgrimes	: N
5871592Srgrimes		{
5881592Srgrimes			$$ = FORM_N;
5891592Srgrimes		}
5901592Srgrimes	| T
5911592Srgrimes		{
5921592Srgrimes			$$ = FORM_T;
5931592Srgrimes		}
5941592Srgrimes	| C
5951592Srgrimes		{
5961592Srgrimes			$$ = FORM_C;
5971592Srgrimes		}
5981592Srgrimes	;
5991592Srgrimes
6001592Srgrimestype_code
6011592Srgrimes	: A
6021592Srgrimes		{
6031592Srgrimes			cmd_type = TYPE_A;
6041592Srgrimes			cmd_form = FORM_N;
6051592Srgrimes		}
6061592Srgrimes	| A SP form_code
6071592Srgrimes		{
6081592Srgrimes			cmd_type = TYPE_A;
6091592Srgrimes			cmd_form = $3;
6101592Srgrimes		}
6111592Srgrimes	| E
6121592Srgrimes		{
6131592Srgrimes			cmd_type = TYPE_E;
6141592Srgrimes			cmd_form = FORM_N;
6151592Srgrimes		}
6161592Srgrimes	| E SP form_code
6171592Srgrimes		{
6181592Srgrimes			cmd_type = TYPE_E;
6191592Srgrimes			cmd_form = $3;
6201592Srgrimes		}
6211592Srgrimes	| I
6221592Srgrimes		{
6231592Srgrimes			cmd_type = TYPE_I;
6241592Srgrimes		}
6251592Srgrimes	| L
6261592Srgrimes		{
6271592Srgrimes			cmd_type = TYPE_L;
6281592Srgrimes			cmd_bytesz = NBBY;
6291592Srgrimes		}
6301592Srgrimes	| L SP byte_size
6311592Srgrimes		{
6321592Srgrimes			cmd_type = TYPE_L;
6331592Srgrimes			cmd_bytesz = $3;
6341592Srgrimes		}
6351592Srgrimes		/* this is for a bug in the BBN ftp */
6361592Srgrimes	| L byte_size
6371592Srgrimes		{
6381592Srgrimes			cmd_type = TYPE_L;
6391592Srgrimes			cmd_bytesz = $2;
6401592Srgrimes		}
6411592Srgrimes	;
6421592Srgrimes
6431592Srgrimesstruct_code
6441592Srgrimes	: F
6451592Srgrimes		{
6461592Srgrimes			$$ = STRU_F;
6471592Srgrimes		}
6481592Srgrimes	| R
6491592Srgrimes		{
6501592Srgrimes			$$ = STRU_R;
6511592Srgrimes		}
6521592Srgrimes	| P
6531592Srgrimes		{
6541592Srgrimes			$$ = STRU_P;
6551592Srgrimes		}
6561592Srgrimes	;
6571592Srgrimes
6581592Srgrimesmode_code
6591592Srgrimes	: S
6601592Srgrimes		{
6611592Srgrimes			$$ = MODE_S;
6621592Srgrimes		}
6631592Srgrimes	| B
6641592Srgrimes		{
6651592Srgrimes			$$ = MODE_B;
6661592Srgrimes		}
6671592Srgrimes	| C
6681592Srgrimes		{
6691592Srgrimes			$$ = MODE_C;
6701592Srgrimes		}
6711592Srgrimes	;
6721592Srgrimes
6731592Srgrimespathname
6741592Srgrimes	: pathstring
6751592Srgrimes		{
6761592Srgrimes			/*
6771592Srgrimes			 * Problem: this production is used for all pathname
6781592Srgrimes			 * processing, but only gives a 550 error reply.
6791592Srgrimes			 * This is a valid reply in some cases but not in others.
6801592Srgrimes			 */
6811592Srgrimes			if (logged_in && $1 && *$1 == '~') {
6821592Srgrimes				glob_t gl;
6831592Srgrimes				int flags =
6841592Srgrimes				 GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE;
6851592Srgrimes
6861592Srgrimes				memset(&gl, 0, sizeof(gl));
6871592Srgrimes				if (glob($1, flags, NULL, &gl) ||
6881592Srgrimes				    gl.gl_pathc == 0) {
6891592Srgrimes					reply(550, "not found");
6901592Srgrimes					$$ = NULL;
6911592Srgrimes				} else {
6921592Srgrimes					$$ = strdup(gl.gl_pathv[0]);
6931592Srgrimes				}
6941592Srgrimes				globfree(&gl);
6951592Srgrimes				free($1);
6961592Srgrimes			} else
6971592Srgrimes				$$ = $1;
6981592Srgrimes		}
6991592Srgrimes	;
7001592Srgrimes
7011592Srgrimespathstring
7021592Srgrimes	: STRING
7031592Srgrimes	;
7041592Srgrimes
7051592Srgrimesoctal_number
7061592Srgrimes	: NUMBER
7071592Srgrimes		{
7081592Srgrimes			int ret, dec, multby, digit;
7091592Srgrimes
7101592Srgrimes			/*
7111592Srgrimes			 * Convert a number that was read as decimal number
7121592Srgrimes			 * to what it would be if it had been read as octal.
7131592Srgrimes			 */
7141592Srgrimes			dec = $1;
7151592Srgrimes			multby = 1;
7161592Srgrimes			ret = 0;
7171592Srgrimes			while (dec) {
7181592Srgrimes				digit = dec%10;
7191592Srgrimes				if (digit > 7) {
7201592Srgrimes					ret = -1;
7211592Srgrimes					break;
7221592Srgrimes				}
7231592Srgrimes				ret += digit * multby;
7241592Srgrimes				multby *= 8;
7251592Srgrimes				dec /= 10;
7261592Srgrimes			}
7271592Srgrimes			$$ = ret;
7281592Srgrimes		}
7291592Srgrimes	;
7301592Srgrimes
7311592Srgrimes
7321592Srgrimescheck_login
7331592Srgrimes	: /* empty */
7341592Srgrimes		{
7351592Srgrimes			if (logged_in)
7361592Srgrimes				$$ = 1;
7371592Srgrimes			else {
7381592Srgrimes				reply(530, "Please login with USER and PASS.");
7391592Srgrimes				$$ = 0;
7401592Srgrimes			}
7411592Srgrimes		}
7421592Srgrimes	;
7431592Srgrimes
7441592Srgrimes%%
7451592Srgrimes
7461592Srgrimesextern jmp_buf errcatch;
7471592Srgrimes
7481592Srgrimes#define	CMD	0	/* beginning of command */
7491592Srgrimes#define	ARGS	1	/* expect miscellaneous arguments */
7501592Srgrimes#define	STR1	2	/* expect SP followed by STRING */
7511592Srgrimes#define	STR2	3	/* expect STRING */
7521592Srgrimes#define	OSTR	4	/* optional SP then STRING */
7531592Srgrimes#define	ZSTR1	5	/* SP then optional STRING */
7541592Srgrimes#define	ZSTR2	6	/* optional STRING after SP */
7551592Srgrimes#define	SITECMD	7	/* SITE command */
7561592Srgrimes#define	NSTR	8	/* Number followed by a string */
7571592Srgrimes
7581592Srgrimesstruct tab {
7591592Srgrimes	char	*name;
7601592Srgrimes	short	token;
7611592Srgrimes	short	state;
7621592Srgrimes	short	implemented;	/* 1 if command is implemented */
7631592Srgrimes	char	*help;
7641592Srgrimes};
7651592Srgrimes
7661592Srgrimesstruct tab cmdtab[] = {		/* In order defined in RFC 765 */
7671592Srgrimes	{ "USER", USER, STR1, 1,	"<sp> username" },
7681592Srgrimes	{ "PASS", PASS, ZSTR1, 1,	"<sp> password" },
7691592Srgrimes	{ "ACCT", ACCT, STR1, 0,	"(specify account)" },
7701592Srgrimes	{ "SMNT", SMNT, ARGS, 0,	"(structure mount)" },
7711592Srgrimes	{ "REIN", REIN, ARGS, 0,	"(reinitialize server state)" },
7721592Srgrimes	{ "QUIT", QUIT, ARGS, 1,	"(terminate service)", },
7731592Srgrimes	{ "PORT", PORT, ARGS, 1,	"<sp> b0, b1, b2, b3, b4" },
7741592Srgrimes	{ "PASV", PASV, ARGS, 1,	"(set server in passive mode)" },
7751592Srgrimes	{ "TYPE", TYPE, ARGS, 1,	"<sp> [ A | E | I | L ]" },
7761592Srgrimes	{ "STRU", STRU, ARGS, 1,	"(specify file structure)" },
7771592Srgrimes	{ "MODE", MODE, ARGS, 1,	"(specify transfer mode)" },
7781592Srgrimes	{ "RETR", RETR, STR1, 1,	"<sp> file-name" },
7791592Srgrimes	{ "STOR", STOR, STR1, 1,	"<sp> file-name" },
7801592Srgrimes	{ "APPE", APPE, STR1, 1,	"<sp> file-name" },
7811592Srgrimes	{ "MLFL", MLFL, OSTR, 0,	"(mail file)" },
7821592Srgrimes	{ "MAIL", MAIL, OSTR, 0,	"(mail to user)" },
7831592Srgrimes	{ "MSND", MSND, OSTR, 0,	"(mail send to terminal)" },
7841592Srgrimes	{ "MSOM", MSOM, OSTR, 0,	"(mail send to terminal or mailbox)" },
7851592Srgrimes	{ "MSAM", MSAM, OSTR, 0,	"(mail send to terminal and mailbox)" },
7861592Srgrimes	{ "MRSQ", MRSQ, OSTR, 0,	"(mail recipient scheme question)" },
7871592Srgrimes	{ "MRCP", MRCP, STR1, 0,	"(mail recipient)" },
7881592Srgrimes	{ "ALLO", ALLO, ARGS, 1,	"allocate storage (vacuously)" },
7891592Srgrimes	{ "REST", REST, ARGS, 1,	"<sp> offset (restart command)" },
7901592Srgrimes	{ "RNFR", RNFR, STR1, 1,	"<sp> file-name" },
7911592Srgrimes	{ "RNTO", RNTO, STR1, 1,	"<sp> file-name" },
7921592Srgrimes	{ "ABOR", ABOR, ARGS, 1,	"(abort operation)" },
7931592Srgrimes	{ "DELE", DELE, STR1, 1,	"<sp> file-name" },
7941592Srgrimes	{ "CWD",  CWD,  OSTR, 1,	"[ <sp> directory-name ]" },
7951592Srgrimes	{ "XCWD", CWD,	OSTR, 1,	"[ <sp> directory-name ]" },
7961592Srgrimes	{ "LIST", LIST, OSTR, 1,	"[ <sp> path-name ]" },
7971592Srgrimes	{ "NLST", NLST, OSTR, 1,	"[ <sp> path-name ]" },
7981592Srgrimes	{ "SITE", SITE, SITECMD, 1,	"site-cmd [ <sp> arguments ]" },
7991592Srgrimes	{ "SYST", SYST, ARGS, 1,	"(get type of operating system)" },
8001592Srgrimes	{ "STAT", STAT, OSTR, 1,	"[ <sp> path-name ]" },
8011592Srgrimes	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
8021592Srgrimes	{ "NOOP", NOOP, ARGS, 1,	"" },
8031592Srgrimes	{ "MKD",  MKD,  STR1, 1,	"<sp> path-name" },
8041592Srgrimes	{ "XMKD", MKD,  STR1, 1,	"<sp> path-name" },
8051592Srgrimes	{ "RMD",  RMD,  STR1, 1,	"<sp> path-name" },
8061592Srgrimes	{ "XRMD", RMD,  STR1, 1,	"<sp> path-name" },
8071592Srgrimes	{ "PWD",  PWD,  ARGS, 1,	"(return current directory)" },
8081592Srgrimes	{ "XPWD", PWD,  ARGS, 1,	"(return current directory)" },
8091592Srgrimes	{ "CDUP", CDUP, ARGS, 1,	"(change to parent directory)" },
8101592Srgrimes	{ "XCUP", CDUP, ARGS, 1,	"(change to parent directory)" },
8111592Srgrimes	{ "STOU", STOU, STR1, 1,	"<sp> file-name" },
8121592Srgrimes	{ "SIZE", SIZE, OSTR, 1,	"<sp> path-name" },
8131592Srgrimes	{ "MDTM", MDTM, OSTR, 1,	"<sp> path-name" },
8141592Srgrimes	{ NULL,   0,    0,    0,	0 }
8151592Srgrimes};
8161592Srgrimes
8171592Srgrimesstruct tab sitetab[] = {
8181592Srgrimes	{ "UMASK", UMASK, ARGS, 1,	"[ <sp> umask ]" },
8191592Srgrimes	{ "IDLE", IDLE, ARGS, 1,	"[ <sp> maximum-idle-time ]" },
8201592Srgrimes	{ "CHMOD", CHMOD, NSTR, 1,	"<sp> mode <sp> file-name" },
8211592Srgrimes	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
8221592Srgrimes	{ NULL,   0,    0,    0,	0 }
8231592Srgrimes};
8241592Srgrimes
8251592Srgrimesstatic char	*copy __P((char *));
8261592Srgrimesstatic void	 help __P((struct tab *, char *));
8271592Srgrimesstatic struct tab *
8281592Srgrimes		 lookup __P((struct tab *, char *));
8291592Srgrimesstatic void	 sizecmd __P((char *));
8301592Srgrimesstatic void	 toolong __P((int));
8311592Srgrimesstatic int	 yylex __P((void));
8321592Srgrimes
8331592Srgrimesstatic struct tab *
8341592Srgrimeslookup(p, cmd)
8351592Srgrimes	struct tab *p;
8361592Srgrimes	char *cmd;
8371592Srgrimes{
8381592Srgrimes
8391592Srgrimes	for (; p->name != NULL; p++)
8401592Srgrimes		if (strcmp(cmd, p->name) == 0)
8411592Srgrimes			return (p);
8421592Srgrimes	return (0);
8431592Srgrimes}
8441592Srgrimes
8451592Srgrimes#include <arpa/telnet.h>
8461592Srgrimes
8471592Srgrimes/*
8481592Srgrimes * getline - a hacked up version of fgets to ignore TELNET escape codes.
8491592Srgrimes */
8501592Srgrimeschar *
8511592Srgrimesgetline(s, n, iop)
8521592Srgrimes	char *s;
8531592Srgrimes	int n;
8541592Srgrimes	FILE *iop;
8551592Srgrimes{
8561592Srgrimes	int c;
8571592Srgrimes	register char *cs;
8581592Srgrimes
8591592Srgrimes	cs = s;
8601592Srgrimes/* tmpline may contain saved command from urgent mode interruption */
8611592Srgrimes	for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
8621592Srgrimes		*cs++ = tmpline[c];
8631592Srgrimes		if (tmpline[c] == '\n') {
8641592Srgrimes			*cs++ = '\0';
8651592Srgrimes			if (debug)
8661592Srgrimes				syslog(LOG_DEBUG, "command: %s", s);
8671592Srgrimes			tmpline[0] = '\0';
8681592Srgrimes			return(s);
8691592Srgrimes		}
8701592Srgrimes		if (c == 0)
8711592Srgrimes			tmpline[0] = '\0';
8721592Srgrimes	}
8731592Srgrimes	while ((c = getc(iop)) != EOF) {
8741592Srgrimes		c &= 0377;
8751592Srgrimes		if (c == IAC) {
8761592Srgrimes		    if ((c = getc(iop)) != EOF) {
8771592Srgrimes			c &= 0377;
8781592Srgrimes			switch (c) {
8791592Srgrimes			case WILL:
8801592Srgrimes			case WONT:
8811592Srgrimes				c = getc(iop);
8821592Srgrimes				printf("%c%c%c", IAC, DONT, 0377&c);
8831592Srgrimes				(void) fflush(stdout);
8841592Srgrimes				continue;
8851592Srgrimes			case DO:
8861592Srgrimes			case DONT:
8871592Srgrimes				c = getc(iop);
8881592Srgrimes				printf("%c%c%c", IAC, WONT, 0377&c);
8891592Srgrimes				(void) fflush(stdout);
8901592Srgrimes				continue;
8911592Srgrimes			case IAC:
8921592Srgrimes				break;
8931592Srgrimes			default:
8941592Srgrimes				continue;	/* ignore command */
8951592Srgrimes			}
8961592Srgrimes		    }
8971592Srgrimes		}
8981592Srgrimes		*cs++ = c;
8991592Srgrimes		if (--n <= 0 || c == '\n')
9001592Srgrimes			break;
9011592Srgrimes	}
9021592Srgrimes	if (c == EOF && cs == s)
9031592Srgrimes		return (NULL);
9041592Srgrimes	*cs++ = '\0';
9051592Srgrimes	if (debug) {
9061592Srgrimes		if (!guest && strncasecmp("pass ", s, 5) == 0) {
9071592Srgrimes			/* Don't syslog passwords */
9081592Srgrimes			syslog(LOG_DEBUG, "command: %.5s ???", s);
9091592Srgrimes		} else {
9101592Srgrimes			register char *cp;
9111592Srgrimes			register int len;
9121592Srgrimes
9131592Srgrimes			/* Don't syslog trailing CR-LF */
9141592Srgrimes			len = strlen(s);
9151592Srgrimes			cp = s + len - 1;
9161592Srgrimes			while (cp >= s && (*cp == '\n' || *cp == '\r')) {
9171592Srgrimes				--cp;
9181592Srgrimes				--len;
9191592Srgrimes			}
9201592Srgrimes			syslog(LOG_DEBUG, "command: %.*s", len, s);
9211592Srgrimes		}
9221592Srgrimes	}
9231592Srgrimes	return (s);
9241592Srgrimes}
9251592Srgrimes
9261592Srgrimesstatic void
9271592Srgrimestoolong(signo)
9281592Srgrimes	int signo;
9291592Srgrimes{
9301592Srgrimes
9311592Srgrimes	reply(421,
9321592Srgrimes	    "Timeout (%d seconds): closing control connection.", timeout);
9331592Srgrimes	if (logging)
9341592Srgrimes		syslog(LOG_INFO, "User %s timed out after %d seconds",
9351592Srgrimes		    (pw ? pw -> pw_name : "unknown"), timeout);
9361592Srgrimes	dologout(1);
9371592Srgrimes}
9381592Srgrimes
9391592Srgrimesstatic int
9401592Srgrimesyylex()
9411592Srgrimes{
9421592Srgrimes	static int cpos, state;
9431592Srgrimes	char *cp, *cp2;
9441592Srgrimes	struct tab *p;
9451592Srgrimes	int n;
9461592Srgrimes	char c;
9471592Srgrimes
9481592Srgrimes	for (;;) {
9491592Srgrimes		switch (state) {
9501592Srgrimes
9511592Srgrimes		case CMD:
9521592Srgrimes			(void) signal(SIGALRM, toolong);
9531592Srgrimes			(void) alarm((unsigned) timeout);
9541592Srgrimes			if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
9551592Srgrimes				reply(221, "You could at least say goodbye.");
9561592Srgrimes				dologout(0);
9571592Srgrimes			}
9581592Srgrimes			(void) alarm(0);
9591592Srgrimes#ifdef SETPROCTITLE
96029574Sphk			if (strncasecmp(cbuf, "PASS", 4) != 0)
9611592Srgrimes				setproctitle("%s: %s", proctitle, cbuf);
9621592Srgrimes#endif /* SETPROCTITLE */
9631592Srgrimes			if ((cp = strchr(cbuf, '\r'))) {
9641592Srgrimes				*cp++ = '\n';
9651592Srgrimes				*cp = '\0';
9661592Srgrimes			}
9671592Srgrimes			if ((cp = strpbrk(cbuf, " \n")))
9681592Srgrimes				cpos = cp - cbuf;
9691592Srgrimes			if (cpos == 0)
9701592Srgrimes				cpos = 4;
9711592Srgrimes			c = cbuf[cpos];
9721592Srgrimes			cbuf[cpos] = '\0';
9731592Srgrimes			upper(cbuf);
9741592Srgrimes			p = lookup(cmdtab, cbuf);
9751592Srgrimes			cbuf[cpos] = c;
9763776Spst			if (p != 0) {
9771592Srgrimes				if (p->implemented == 0) {
9781592Srgrimes					nack(p->name);
9791592Srgrimes					longjmp(errcatch,0);
9801592Srgrimes					/* NOTREACHED */
9811592Srgrimes				}
9821592Srgrimes				state = p->state;
9831592Srgrimes				yylval.s = p->name;
9841592Srgrimes				return (p->token);
9851592Srgrimes			}
9861592Srgrimes			break;
9871592Srgrimes
9881592Srgrimes		case SITECMD:
9891592Srgrimes			if (cbuf[cpos] == ' ') {
9901592Srgrimes				cpos++;
9911592Srgrimes				return (SP);
9921592Srgrimes			}
9931592Srgrimes			cp = &cbuf[cpos];
9941592Srgrimes			if ((cp2 = strpbrk(cp, " \n")))
9951592Srgrimes				cpos = cp2 - cbuf;
9961592Srgrimes			c = cbuf[cpos];
9971592Srgrimes			cbuf[cpos] = '\0';
9981592Srgrimes			upper(cp);
9991592Srgrimes			p = lookup(sitetab, cp);
10001592Srgrimes			cbuf[cpos] = c;
10013777Spst			if (guest == 0 && p != 0) {
10021592Srgrimes				if (p->implemented == 0) {
10031592Srgrimes					state = CMD;
10041592Srgrimes					nack(p->name);
10051592Srgrimes					longjmp(errcatch,0);
10061592Srgrimes					/* NOTREACHED */
10071592Srgrimes				}
10081592Srgrimes				state = p->state;
10091592Srgrimes				yylval.s = p->name;
10101592Srgrimes				return (p->token);
10111592Srgrimes			}
10121592Srgrimes			state = CMD;
10131592Srgrimes			break;
10141592Srgrimes
10151592Srgrimes		case OSTR:
10161592Srgrimes			if (cbuf[cpos] == '\n') {
10171592Srgrimes				state = CMD;
10181592Srgrimes				return (CRLF);
10191592Srgrimes			}
10201592Srgrimes			/* FALLTHROUGH */
10211592Srgrimes
10221592Srgrimes		case STR1:
10231592Srgrimes		case ZSTR1:
10241592Srgrimes		dostr1:
10251592Srgrimes			if (cbuf[cpos] == ' ') {
10261592Srgrimes				cpos++;
10271592Srgrimes				state = state == OSTR ? STR2 : ++state;
10281592Srgrimes				return (SP);
10291592Srgrimes			}
10301592Srgrimes			break;
10311592Srgrimes
10321592Srgrimes		case ZSTR2:
10331592Srgrimes			if (cbuf[cpos] == '\n') {
10341592Srgrimes				state = CMD;
10351592Srgrimes				return (CRLF);
10361592Srgrimes			}
10371592Srgrimes			/* FALLTHROUGH */
10381592Srgrimes
10391592Srgrimes		case STR2:
10401592Srgrimes			cp = &cbuf[cpos];
10411592Srgrimes			n = strlen(cp);
10421592Srgrimes			cpos += n - 1;
10431592Srgrimes			/*
10441592Srgrimes			 * Make sure the string is nonempty and \n terminated.
10451592Srgrimes			 */
10461592Srgrimes			if (n > 1 && cbuf[cpos] == '\n') {
10471592Srgrimes				cbuf[cpos] = '\0';
10481592Srgrimes				yylval.s = copy(cp);
10491592Srgrimes				cbuf[cpos] = '\n';
10501592Srgrimes				state = ARGS;
10511592Srgrimes				return (STRING);
10521592Srgrimes			}
10531592Srgrimes			break;
10541592Srgrimes
10551592Srgrimes		case NSTR:
10561592Srgrimes			if (cbuf[cpos] == ' ') {
10571592Srgrimes				cpos++;
10581592Srgrimes				return (SP);
10591592Srgrimes			}
10601592Srgrimes			if (isdigit(cbuf[cpos])) {
10611592Srgrimes				cp = &cbuf[cpos];
10621592Srgrimes				while (isdigit(cbuf[++cpos]))
10631592Srgrimes					;
10641592Srgrimes				c = cbuf[cpos];
10651592Srgrimes				cbuf[cpos] = '\0';
10661592Srgrimes				yylval.i = atoi(cp);
10671592Srgrimes				cbuf[cpos] = c;
10681592Srgrimes				state = STR1;
10691592Srgrimes				return (NUMBER);
10701592Srgrimes			}
10711592Srgrimes			state = STR1;
10721592Srgrimes			goto dostr1;
10731592Srgrimes
10741592Srgrimes		case ARGS:
10751592Srgrimes			if (isdigit(cbuf[cpos])) {
10761592Srgrimes				cp = &cbuf[cpos];
10771592Srgrimes				while (isdigit(cbuf[++cpos]))
10781592Srgrimes					;
10791592Srgrimes				c = cbuf[cpos];
10801592Srgrimes				cbuf[cpos] = '\0';
10811592Srgrimes				yylval.i = atoi(cp);
10821592Srgrimes				cbuf[cpos] = c;
10831592Srgrimes				return (NUMBER);
10841592Srgrimes			}
10851592Srgrimes			switch (cbuf[cpos++]) {
10861592Srgrimes
10871592Srgrimes			case '\n':
10881592Srgrimes				state = CMD;
10891592Srgrimes				return (CRLF);
10901592Srgrimes
10911592Srgrimes			case ' ':
10921592Srgrimes				return (SP);
10931592Srgrimes
10941592Srgrimes			case ',':
10951592Srgrimes				return (COMMA);
10961592Srgrimes
10971592Srgrimes			case 'A':
10981592Srgrimes			case 'a':
10991592Srgrimes				return (A);
11001592Srgrimes
11011592Srgrimes			case 'B':
11021592Srgrimes			case 'b':
11031592Srgrimes				return (B);
11041592Srgrimes
11051592Srgrimes			case 'C':
11061592Srgrimes			case 'c':
11071592Srgrimes				return (C);
11081592Srgrimes
11091592Srgrimes			case 'E':
11101592Srgrimes			case 'e':
11111592Srgrimes				return (E);
11121592Srgrimes
11131592Srgrimes			case 'F':
11141592Srgrimes			case 'f':
11151592Srgrimes				return (F);
11161592Srgrimes
11171592Srgrimes			case 'I':
11181592Srgrimes			case 'i':
11191592Srgrimes				return (I);
11201592Srgrimes
11211592Srgrimes			case 'L':
11221592Srgrimes			case 'l':
11231592Srgrimes				return (L);
11241592Srgrimes
11251592Srgrimes			case 'N':
11261592Srgrimes			case 'n':
11271592Srgrimes				return (N);
11281592Srgrimes
11291592Srgrimes			case 'P':
11301592Srgrimes			case 'p':
11311592Srgrimes				return (P);
11321592Srgrimes
11331592Srgrimes			case 'R':
11341592Srgrimes			case 'r':
11351592Srgrimes				return (R);
11361592Srgrimes
11371592Srgrimes			case 'S':
11381592Srgrimes			case 's':
11391592Srgrimes				return (S);
11401592Srgrimes
11411592Srgrimes			case 'T':
11421592Srgrimes			case 't':
11431592Srgrimes				return (T);
11441592Srgrimes
11451592Srgrimes			}
11461592Srgrimes			break;
11471592Srgrimes
11481592Srgrimes		default:
11491592Srgrimes			fatal("Unknown state in scanner.");
11501592Srgrimes		}
11511592Srgrimes		yyerror((char *) 0);
11521592Srgrimes		state = CMD;
11531592Srgrimes		longjmp(errcatch,0);
11541592Srgrimes	}
11551592Srgrimes}
11561592Srgrimes
11571592Srgrimesvoid
11581592Srgrimesupper(s)
11591592Srgrimes	char *s;
11601592Srgrimes{
11611592Srgrimes	while (*s != '\0') {
11621592Srgrimes		if (islower(*s))
11631592Srgrimes			*s = toupper(*s);
11641592Srgrimes		s++;
11651592Srgrimes	}
11661592Srgrimes}
11671592Srgrimes
11681592Srgrimesstatic char *
11691592Srgrimescopy(s)
11701592Srgrimes	char *s;
11711592Srgrimes{
11721592Srgrimes	char *p;
11731592Srgrimes
11741592Srgrimes	p = malloc((unsigned) strlen(s) + 1);
11751592Srgrimes	if (p == NULL)
11761592Srgrimes		fatal("Ran out of memory.");
11771592Srgrimes	(void) strcpy(p, s);
11781592Srgrimes	return (p);
11791592Srgrimes}
11801592Srgrimes
11811592Srgrimesstatic void
11821592Srgrimeshelp(ctab, s)
11831592Srgrimes	struct tab *ctab;
11841592Srgrimes	char *s;
11851592Srgrimes{
11861592Srgrimes	struct tab *c;
11871592Srgrimes	int width, NCMDS;
11881592Srgrimes	char *type;
11891592Srgrimes
11901592Srgrimes	if (ctab == sitetab)
11911592Srgrimes		type = "SITE ";
11921592Srgrimes	else
11931592Srgrimes		type = "";
11941592Srgrimes	width = 0, NCMDS = 0;
11951592Srgrimes	for (c = ctab; c->name != NULL; c++) {
11961592Srgrimes		int len = strlen(c->name);
11971592Srgrimes
11981592Srgrimes		if (len > width)
11991592Srgrimes			width = len;
12001592Srgrimes		NCMDS++;
12011592Srgrimes	}
12021592Srgrimes	width = (width + 8) &~ 7;
12031592Srgrimes	if (s == 0) {
12041592Srgrimes		int i, j, w;
12051592Srgrimes		int columns, lines;
12061592Srgrimes
12071592Srgrimes		lreply(214, "The following %scommands are recognized %s.",
12081592Srgrimes		    type, "(* =>'s unimplemented)");
12091592Srgrimes		columns = 76 / width;
12101592Srgrimes		if (columns == 0)
12111592Srgrimes			columns = 1;
12121592Srgrimes		lines = (NCMDS + columns - 1) / columns;
12131592Srgrimes		for (i = 0; i < lines; i++) {
12141592Srgrimes			printf("   ");
12151592Srgrimes			for (j = 0; j < columns; j++) {
12161592Srgrimes				c = ctab + j * lines + i;
12171592Srgrimes				printf("%s%c", c->name,
12181592Srgrimes					c->implemented ? ' ' : '*');
12191592Srgrimes				if (c + lines >= &ctab[NCMDS])
12201592Srgrimes					break;
12211592Srgrimes				w = strlen(c->name) + 1;
12221592Srgrimes				while (w < width) {
12231592Srgrimes					putchar(' ');
12241592Srgrimes					w++;
12251592Srgrimes				}
12261592Srgrimes			}
12271592Srgrimes			printf("\r\n");
12281592Srgrimes		}
12291592Srgrimes		(void) fflush(stdout);
12301592Srgrimes		reply(214, "Direct comments to ftp-bugs@%s.", hostname);
12311592Srgrimes		return;
12321592Srgrimes	}
12331592Srgrimes	upper(s);
12341592Srgrimes	c = lookup(ctab, s);
12351592Srgrimes	if (c == (struct tab *)0) {
12361592Srgrimes		reply(502, "Unknown command %s.", s);
12371592Srgrimes		return;
12381592Srgrimes	}
12391592Srgrimes	if (c->implemented)
12401592Srgrimes		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
12411592Srgrimes	else
12421592Srgrimes		reply(214, "%s%-*s\t%s; unimplemented.", type, width,
12431592Srgrimes		    c->name, c->help);
12441592Srgrimes}
12451592Srgrimes
12461592Srgrimesstatic void
12471592Srgrimessizecmd(filename)
12481592Srgrimes	char *filename;
12491592Srgrimes{
12501592Srgrimes	switch (type) {
12511592Srgrimes	case TYPE_L:
12521592Srgrimes	case TYPE_I: {
12531592Srgrimes		struct stat stbuf;
12541592Srgrimes		if (stat(filename, &stbuf) < 0 || !S_ISREG(stbuf.st_mode))
12551592Srgrimes			reply(550, "%s: not a plain file.", filename);
12561592Srgrimes		else
12571592Srgrimes			reply(213, "%qu", stbuf.st_size);
12581592Srgrimes		break; }
12591592Srgrimes	case TYPE_A: {
12601592Srgrimes		FILE *fin;
12611592Srgrimes		int c;
12621592Srgrimes		off_t count;
12631592Srgrimes		struct stat stbuf;
12641592Srgrimes		fin = fopen(filename, "r");
12651592Srgrimes		if (fin == NULL) {
12661592Srgrimes			perror_reply(550, filename);
12671592Srgrimes			return;
12681592Srgrimes		}
12691592Srgrimes		if (fstat(fileno(fin), &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) {
12701592Srgrimes			reply(550, "%s: not a plain file.", filename);
12711592Srgrimes			(void) fclose(fin);
12721592Srgrimes			return;
12731592Srgrimes		}
12741592Srgrimes
12751592Srgrimes		count = 0;
12761592Srgrimes		while((c=getc(fin)) != EOF) {
12771592Srgrimes			if (c == '\n')	/* will get expanded to \r\n */
12781592Srgrimes				count++;
12791592Srgrimes			count++;
12801592Srgrimes		}
12811592Srgrimes		(void) fclose(fin);
12821592Srgrimes
12831592Srgrimes		reply(213, "%qd", count);
12841592Srgrimes		break; }
12851592Srgrimes	default:
12861592Srgrimes		reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
12871592Srgrimes	}
12881592Srgrimes}
1289