ftpcmd.y revision 1.30
11556Srgrimes/*	$OpenBSD: ftpcmd.y,v 1.30 2002/01/08 01:55:27 millert Exp $	*/
21556Srgrimes/*	$NetBSD: ftpcmd.y,v 1.7 1996/04/08 19:03:11 jtc Exp $	*/
31556Srgrimes
41556Srgrimes/*
51556Srgrimes * Copyright (c) 1985, 1988, 1993, 1994
61556Srgrimes *	The Regents of the University of California.  All rights reserved.
71556Srgrimes *
81556Srgrimes * Redistribution and use in source and binary forms, with or without
91556Srgrimes * modification, are permitted provided that the following conditions
101556Srgrimes * are met:
111556Srgrimes * 1. Redistributions of source code must retain the above copyright
121556Srgrimes *    notice, this list of conditions and the following disclaimer.
131556Srgrimes * 2. Redistributions in binary form must reproduce the above copyright
141556Srgrimes *    notice, this list of conditions and the following disclaimer in the
151556Srgrimes *    documentation and/or other materials provided with the distribution.
161556Srgrimes * 3. All advertising materials mentioning features or use of this software
171556Srgrimes *    must display the following acknowledgement:
181556Srgrimes *	This product includes software developed by the University of
191556Srgrimes *	California, Berkeley and its contributors.
201556Srgrimes * 4. Neither the name of the University nor the names of its contributors
211556Srgrimes *    may be used to endorse or promote products derived from this software
221556Srgrimes *    without specific prior written permission.
231556Srgrimes *
241556Srgrimes * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
251556Srgrimes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
261556Srgrimes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
271556Srgrimes * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
281556Srgrimes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
291556Srgrimes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
301556Srgrimes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
311556Srgrimes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
321556Srgrimes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
331556Srgrimes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
341556Srgrimes * SUCH DAMAGE.
353044Sdg *
3620742Ssteve *	@(#)ftpcmd.y	8.3 (Berkeley) 4/6/94
371556Srgrimes */
381556Srgrimes
391556Srgrimes/*
4020425Ssteve * Grammar for FTP commands.
411556Srgrimes * See RFC 959.
421556Srgrimes */
4317987Speter
4417987Speter%{
4517987Speter
4617987Speter#ifndef lint
471556Srgrimes#if 0
481556Srgrimesstatic char sccsid[] = "@(#)ftpcmd.y	8.3 (Berkeley) 4/6/94";
491556Srgrimes#else
501556Srgrimesstatic char rcsid[] = "$OpenBSD: ftpcmd.y,v 1.30 2002/01/08 01:55:27 millert Exp $";
511556Srgrimes#endif
521556Srgrimes#endif /* not lint */
531556Srgrimes
541556Srgrimes#include <sys/param.h>
551556Srgrimes#include <sys/socket.h>
561556Srgrimes#include <sys/stat.h>
571556Srgrimes
581556Srgrimes#include <netinet/in.h>
591556Srgrimes#include <arpa/ftp.h>
601556Srgrimes
6117987Speter#include <ctype.h>
6217987Speter#include <errno.h>
6317987Speter#include <glob.h>
641556Srgrimes#include <pwd.h>
651556Srgrimes#include <signal.h>
661556Srgrimes#include <tzfile.h>
671556Srgrimes#include <stdio.h>
681556Srgrimes#include <stdlib.h>
691556Srgrimes#include <string.h>
701556Srgrimes#include <syslog.h>
711556Srgrimes#include <time.h>
721556Srgrimes#include <unistd.h>
731556Srgrimes#include <netdb.h>
7417987Speter
7517987Speter#include "extern.h"
7617987Speter
7720425Ssteveextern	union sockunion data_dest;
781556Srgrimesextern	int logged_in;
791556Srgrimesextern	struct passwd *pw;
801556Srgrimesextern	int guest;
811556Srgrimesextern	int logging;
821556Srgrimesextern	int type;
831556Srgrimesextern	int form;
841556Srgrimesextern	int debug;
851556Srgrimesextern	int timeout;
8617987Speterextern	int maxtimeout;
871556Srgrimesextern  int pdata;
8817987Speterextern	char hostname[], remotehost[];
891556Srgrimesextern	char proctitle[];
901556Srgrimesextern	int usedefault;
911556Srgrimesextern  int transflag;
921556Srgrimesextern  char tmpline[];
931556Srgrimesextern	int portcheck;
941556Srgrimesextern	union sockunion his_addr;
951556Srgrimesextern	int umaskchange;
9619240Ssteve
971556Srgrimesoff_t	restart_point;
981556Srgrimes
991556Srgrimesstatic	int cmd_type;
1001556Srgrimesstatic	int cmd_form;
1011556Srgrimesstatic	int cmd_bytesz;
1021556Srgrimesstatic	int state;
1031556Srgrimeschar	cbuf[512];
1041556Srgrimeschar	*fromname;
1051556Srgrimes
1061556Srgrimes%}
1071556Srgrimes
1081556Srgrimes%union {
1091556Srgrimes	int	i;
1101556Srgrimes	char   *s;
1111556Srgrimes}
11220425Ssteve
11320742Ssteve%token
11411111Sjoerg	A	B	C	E	F	I
11520742Ssteve	L	N	P	R	S	T
1161556Srgrimes	ALL
11720742Ssteve
1181556Srgrimes	SP	CRLF	COMMA
1191556Srgrimes
1201556Srgrimes	USER	PASS	ACCT	REIN	QUIT	PORT
1211556Srgrimes	PASV	TYPE	STRU	MODE	RETR	STOR
1221556Srgrimes	APPE	MLFL	MAIL	MSND	MSOM	MSAM
1231556Srgrimes	MRSQ	MRCP	ALLO	REST	RNFR	RNTO
1241556Srgrimes	ABOR	DELE	CWD	LIST	NLST	SITE
1251556Srgrimes	STAT	HELP	NOOP	MKD	RMD	PWD
1261556Srgrimes	CDUP	STOU	SMNT	SYST	SIZE	MDTM
12717987Speter
12817987Speter	LPRT	LPSV	EPRT	EPSV
12917987Speter
1301556Srgrimes	UMASK	IDLE	CHMOD
13117987Speter
1321556Srgrimes	LEXERR
13317987Speter
1341556Srgrimes%token	<s> STRING
1351556Srgrimes%token	<s> ALL
1361556Srgrimes%token	<i> NUMBER
1371556Srgrimes
1381556Srgrimes%type	<i> check_login check_login_epsvall octal_number byte_size
1391556Srgrimes%type	<i> struct_code mode_code type_code form_code
1401556Srgrimes%type	<s> pathstring pathname password username
1411556Srgrimes%type	<i> host_port host_long_port4 host_long_port6
1421556Srgrimes
14320425Ssteve%start	cmd_list
14417987Speter
14517987Speter%%
1461556Srgrimes
1471556Srgrimescmd_list
1481556Srgrimes	: /* empty */
1491556Srgrimes	| cmd_list cmd
1501556Srgrimes		{
1511556Srgrimes			if (fromname) {
1521556Srgrimes				free(fromname);
1531556Srgrimes				fromname = NULL;
1541556Srgrimes			}
1551556Srgrimes			restart_point = (off_t) 0;
15617987Speter		}
1571556Srgrimes	| cmd_list rcmd
1581556Srgrimes	;
1591556Srgrimes
1601556Srgrimescmd
1611556Srgrimes	: USER SP username CRLF
1621556Srgrimes		{
16317987Speter			user($3);
1641556Srgrimes			free($3);
1651556Srgrimes		}
1661556Srgrimes	| PASS SP password CRLF
1671556Srgrimes		{
1681556Srgrimes			pass($3);
1691556Srgrimes			memset($3, 0, strlen($3));
1701556Srgrimes			free($3);
1711556Srgrimes		}
1721556Srgrimes	| PORT check_login_epsvall SP host_port CRLF
1731556Srgrimes		{
1741556Srgrimes			if ($2) {
1751556Srgrimes				if ($4) {
1761556Srgrimes					usedefault = 1;
1771556Srgrimes					reply(500,
1781556Srgrimes					    "Illegal PORT rejected (range errors).");
1791556Srgrimes				} else if (portcheck &&
1801556Srgrimes				    ntohs(data_dest.su_sin.sin_port) < IPPORT_RESERVED) {
1811556Srgrimes					usedefault = 1;
1821556Srgrimes					reply(500,
1831556Srgrimes					    "Illegal PORT rejected (reserved port).");
1841556Srgrimes				} else if (portcheck &&
1851556Srgrimes				    memcmp(&data_dest.su_sin.sin_addr,
1861556Srgrimes				    &his_addr.su_sin.sin_addr,
1871556Srgrimes				    sizeof data_dest.su_sin.sin_addr)) {
1881556Srgrimes					usedefault = 1;
1891556Srgrimes					reply(500,
1901556Srgrimes					    "Illegal PORT rejected (address wrong).");
19119240Ssteve				} else {
19219240Ssteve					usedefault = 0;
19319240Ssteve					if (pdata >= 0) {
19419240Ssteve						(void) close(pdata);
1951556Srgrimes						pdata = -1;
1961556Srgrimes					}
1971556Srgrimes					reply(200, "PORT command successful.");
1981556Srgrimes				}
1991556Srgrimes			}
2001556Srgrimes		}
2011556Srgrimes	| LPRT check_login_epsvall SP host_long_port4 CRLF
2021556Srgrimes		{
2031556Srgrimes			if ($2) {
2041556Srgrimes				/* reject invalid host_long_port4 */
2051556Srgrimes				if ($4) {
2061556Srgrimes					reply(500,
2071556Srgrimes					    "Illegal LPRT command rejected");
2081556Srgrimes					usedefault = 1;
2091556Srgrimes				} else {
2101556Srgrimes					usedefault = 0;
2111556Srgrimes					if (pdata >= 0) {
2121556Srgrimes						(void) close(pdata);
2131556Srgrimes						pdata = -1;
2141556Srgrimes					}
2151556Srgrimes					reply(200, "LPRT command successful.");
21619240Ssteve				}
21719240Ssteve			}
21819240Ssteve		}
21919240Ssteve
2201556Srgrimes	| LPRT check_login_epsvall SP host_long_port6 CRLF
2211556Srgrimes		{
2221556Srgrimes			if ($2) {
2231556Srgrimes				/* reject invalid host_long_port6 */
2241556Srgrimes				if ($4) {
2251556Srgrimes					reply(500,
2261556Srgrimes					    "Illegal LPRT command rejected");
2278855Srgrimes					usedefault = 1;
2281556Srgrimes				} else {
2291556Srgrimes					usedefault = 0;
2301556Srgrimes					if (pdata >= 0) {
2311556Srgrimes						(void) close(pdata);
2321556Srgrimes						pdata = -1;
2331556Srgrimes					}
2341556Srgrimes					reply(200, "LPRT command successful.");
2351556Srgrimes				}
2361556Srgrimes			}
2371556Srgrimes		}
2381556Srgrimes
2391556Srgrimes	| EPRT check_login_epsvall SP STRING CRLF
2401556Srgrimes		{
2411556Srgrimes			if ($2)
2421556Srgrimes				extended_port($4);
2431556Srgrimes			free($4);
2441556Srgrimes		}
2451556Srgrimes
2461556Srgrimes	| PASV check_login_epsvall CRLF
2471556Srgrimes		{
2481556Srgrimes			if ($2)
2491556Srgrimes				passive();
2501556Srgrimes		}
2511556Srgrimes	| LPSV check_login_epsvall CRLF
2521556Srgrimes		{
2531556Srgrimes			if ($2)
2541556Srgrimes				long_passive("LPSV", PF_UNSPEC);
2551556Srgrimes		}
2561556Srgrimes	| EPSV check_login SP NUMBER CRLF
2571556Srgrimes		{
2581556Srgrimes			if ($2)
2591556Srgrimes				long_passive("EPSV", epsvproto2af($4));
2601556Srgrimes		}
2611556Srgrimes	| EPSV check_login SP ALL CRLF
2621556Srgrimes		{
2631556Srgrimes			if ($2) {
2641556Srgrimes				reply(200, "EPSV ALL command successful.");
2651556Srgrimes				epsvall++;
2661556Srgrimes			}
2671556Srgrimes		}
2681556Srgrimes	| EPSV check_login CRLF
2691556Srgrimes		{
2701556Srgrimes			if ($2)
2711556Srgrimes				long_passive("EPSV", PF_UNSPEC);
2721556Srgrimes		}
2731556Srgrimes	| TYPE check_login SP type_code CRLF
2741556Srgrimes		{
2751556Srgrimes			if ($2) {
2761556Srgrimes				switch (cmd_type) {
2771556Srgrimes
2781556Srgrimes				case TYPE_A:
2791556Srgrimes					if (cmd_form == FORM_N) {
2801556Srgrimes						reply(200, "Type set to A.");
2811556Srgrimes						type = cmd_type;
2821556Srgrimes						form = cmd_form;
2831556Srgrimes					} else
2841556Srgrimes						reply(504, "Form must be N.");
2851556Srgrimes					break;
2861556Srgrimes
2871556Srgrimes				case TYPE_E:
2881556Srgrimes					reply(504, "Type E not implemented.");
28920742Ssteve					break;
2901556Srgrimes
2911556Srgrimes				case TYPE_I:
2921556Srgrimes					reply(200, "Type set to I.");
2931556Srgrimes					type = cmd_type;
2941556Srgrimes					break;
2951556Srgrimes
2961556Srgrimes				case TYPE_L:
2971556Srgrimes					if (cmd_bytesz == 8) {
2981556Srgrimes					       reply(200,
2991556Srgrimes					       "Type set to L (byte size 8).");
3001556Srgrimes					       type = cmd_type;
3011556Srgrimes					} else
3021556Srgrimes					    reply(504, "Byte size must be 8.");
3031556Srgrimes
3041556Srgrimes				}
3051556Srgrimes			}
3061556Srgrimes		}
3071556Srgrimes	| STRU check_login SP struct_code CRLF
3081556Srgrimes		{
3091556Srgrimes			if ($2) {
3101556Srgrimes				switch ($4) {
3111556Srgrimes
3121556Srgrimes				case STRU_F:
3131556Srgrimes					reply(200, "STRU F ok.");
3141556Srgrimes					break;
3151556Srgrimes
31617987Speter				default:
31717987Speter					reply(504, "Unimplemented STRU type.");
31817987Speter				}
31920425Ssteve			}
32017987Speter		}
3211556Srgrimes	| MODE check_login SP mode_code CRLF
3221556Srgrimes		{
3231556Srgrimes			if ($2) {
3241556Srgrimes				switch ($4) {
3251556Srgrimes
3261556Srgrimes				case MODE_S:
3271556Srgrimes					reply(200, "MODE S ok.");
3281556Srgrimes					break;
3291556Srgrimes
3301556Srgrimes				default:
3311556Srgrimes					reply(502, "Unimplemented MODE type.");
3321556Srgrimes				}
3331556Srgrimes			}
3341556Srgrimes		}
3351556Srgrimes	| ALLO check_login SP NUMBER CRLF
3361556Srgrimes		{
3371556Srgrimes			if ($2) {
3381556Srgrimes				reply(202, "ALLO command ignored.");
3391556Srgrimes			}
3401556Srgrimes		}
3411556Srgrimes	| ALLO check_login SP NUMBER SP R SP NUMBER CRLF
3421556Srgrimes		{
3431556Srgrimes			if ($2) {
3441556Srgrimes				reply(202, "ALLO command ignored.");
3451556Srgrimes			}
3461556Srgrimes		}
3471556Srgrimes	| RETR check_login SP pathname CRLF
34817987Speter		{
34917987Speter			if ($2 && $4 != NULL)
35017987Speter				retrieve(NULL, $4);
35120425Ssteve			if ($4 != NULL)
35217987Speter				free($4);
3531556Srgrimes		}
3541556Srgrimes	| STOR check_login SP pathname CRLF
3551556Srgrimes		{
3561556Srgrimes			if ($2 && $4 != NULL)
3571556Srgrimes				store($4, "w", 0);
3581556Srgrimes			if ($4 != NULL)
3591556Srgrimes				free($4);
3601556Srgrimes		}
3611556Srgrimes	| APPE check_login SP pathname CRLF
3621556Srgrimes		{
3631556Srgrimes			if ($2 && $4 != NULL)
3641556Srgrimes				store($4, "a", 0);
3651556Srgrimes			if ($4 != NULL)
36620425Ssteve				free($4);
36720425Ssteve		}
36820425Ssteve	| NLST check_login CRLF
36920425Ssteve		{
37020425Ssteve			if ($2)
37120425Ssteve				send_file_list(".");
37220425Ssteve		}
37320425Ssteve	| NLST check_login SP STRING CRLF
37420425Ssteve		{
37520425Ssteve			if ($2 && $4 != NULL)
3761556Srgrimes				send_file_list($4);
3771556Srgrimes			free($4);
3781556Srgrimes		}
3791556Srgrimes	| LIST check_login CRLF
3801556Srgrimes		{
3811556Srgrimes			if ($2)
3821556Srgrimes				retrieve("/bin/ls -lgA", "");
38317987Speter		}
38417987Speter	| LIST check_login SP pathname CRLF
38517987Speter		{
38620425Ssteve			if ($2 && $4 != NULL)
38717987Speter				retrieve("/bin/ls -lgA %s", $4);
38820425Ssteve			if ($4 != NULL)
38920425Ssteve				free($4);
39020425Ssteve		}
39120425Ssteve	| STAT check_login SP pathname CRLF
39220425Ssteve		{
39320425Ssteve			if ($2 && $4 != NULL)
39420425Ssteve				statfilecmd($4);
39520425Ssteve			if ($4 != NULL)
39620425Ssteve				free($4);
39720425Ssteve		}
39820425Ssteve	| STAT check_login CRLF
39920425Ssteve		{
40020425Ssteve			if ($2)
40120425Ssteve				statcmd();
40220425Ssteve		}
40320425Ssteve	| DELE check_login SP pathname CRLF
40420425Ssteve		{
40520425Ssteve			if ($2 && $4 != NULL)
40620425Ssteve				delete($4);
40720425Ssteve			if ($4 != NULL)
40820425Ssteve				free($4);
40920425Ssteve		}
41020425Ssteve	| RNTO check_login SP pathname CRLF
41120425Ssteve		{
41220425Ssteve			if ($2) {
41320425Ssteve				if (fromname) {
41420425Ssteve					renamecmd(fromname, $4);
4151556Srgrimes					free(fromname);
41620425Ssteve					fromname = NULL;
41720425Ssteve				} else {
41820425Ssteve					reply(503,
41920425Ssteve					  "Bad sequence of commands.");
4201556Srgrimes				}
4211556Srgrimes			}
42220425Ssteve			free($4);
42320425Ssteve		}
42420425Ssteve	| ABOR check_login CRLF
42520425Ssteve		{
42620425Ssteve			if ($2)
4271556Srgrimes				reply(225, "ABOR command successful.");
4281556Srgrimes		}
42920742Ssteve	| CWD check_login CRLF
43020425Ssteve		{
43120742Ssteve			if ($2)
43220425Ssteve				cwd(pw->pw_dir);
43320425Ssteve		}
4341556Srgrimes	| CWD check_login SP pathname CRLF
43520425Ssteve		{
4361556Srgrimes			if ($2 && $4 != NULL)
4371556Srgrimes				cwd($4);
4381556Srgrimes			if ($4 != NULL)
43920425Ssteve				free($4);
4401556Srgrimes		}
44120425Ssteve	| HELP CRLF
4421556Srgrimes		{
44320425Ssteve			help(cmdtab, NULL);
44420425Ssteve		}
44520425Ssteve	| HELP SP STRING CRLF
44620425Ssteve		{
44720425Ssteve			char *cp = $3;
44820425Ssteve
44920425Ssteve			if (strncasecmp(cp, "SITE", 4) == 0) {
45020425Ssteve				cp = $3 + 4;
45120425Ssteve				if (*cp == ' ')
4521556Srgrimes					cp++;
45320425Ssteve				if (*cp)
4541556Srgrimes					help(sitetab, cp);
4551556Srgrimes				else
4561556Srgrimes					help(sitetab, NULL);
4571556Srgrimes			} else
45820425Ssteve				help(cmdtab, $3);
4591556Srgrimes			free ($3);
46020425Ssteve		}
46120425Ssteve	| NOOP CRLF
46220425Ssteve		{
46320425Ssteve			reply(200, "NOOP command successful.");
46420425Ssteve		}
46520425Ssteve	| MKD check_login SP pathname CRLF
46620425Ssteve		{
46720425Ssteve			if ($2 && $4 != NULL)
46820425Ssteve				makedir($4);
46920425Ssteve			if ($4 != NULL)
47020425Ssteve				free($4);
47120425Ssteve		}
47220425Ssteve	| RMD check_login SP pathname CRLF
4731556Srgrimes		{
47420425Ssteve			if ($2 && $4 != NULL)
47520425Ssteve				removedir($4);
47620425Ssteve			if ($4 != NULL)
47720425Ssteve				free($4);
4781556Srgrimes		}
4791556Srgrimes	| PWD check_login CRLF
48020425Ssteve		{
48120425Ssteve			if ($2)
48220425Ssteve				pwd();
48320425Ssteve		}
48420425Ssteve	| CDUP check_login CRLF
48520425Ssteve		{
48620425Ssteve			if ($2)
48720425Ssteve				cwd("..");
48820425Ssteve		}
4891556Srgrimes	| SITE SP HELP CRLF
49020425Ssteve		{
49120425Ssteve			help(sitetab, NULL);
49220425Ssteve		}
4931556Srgrimes	| SITE SP HELP SP STRING CRLF
4941556Srgrimes		{
49520425Ssteve			help(sitetab, $5);
49620425Ssteve			free ($5);
49720425Ssteve		}
49820425Ssteve	| SITE SP UMASK check_login CRLF
49920425Ssteve		{
50020425Ssteve			int oldmask;
50120425Ssteve
50220425Ssteve			if ($4) {
5031556Srgrimes				oldmask = umask(0);
5041556Srgrimes				(void) umask(oldmask);
5051556Srgrimes				reply(200, "Current UMASK is %03o", oldmask);
5061556Srgrimes			}
5071556Srgrimes		}
5081556Srgrimes	| SITE SP UMASK check_login SP octal_number CRLF
5091556Srgrimes		{
5101556Srgrimes			int oldmask;
5111556Srgrimes
5121556Srgrimes			if ($4) {
5131556Srgrimes				if (($6 == -1) || ($6 > 0777)) {
5141556Srgrimes					reply(501, "Bad UMASK value");
5151556Srgrimes				} else if (!umaskchange) {
5161556Srgrimes					reply(550,
5171556Srgrimes					    "No permission to change umask.");
5181556Srgrimes				} else {
5191556Srgrimes					oldmask = umask($6);
5201556Srgrimes					reply(200,
5211556Srgrimes					    "UMASK set to %03o (was %03o)",
5221556Srgrimes					    $6, oldmask);
5231556Srgrimes				}
5241556Srgrimes			}
5251556Srgrimes		}
5261556Srgrimes	| SITE SP CHMOD check_login SP octal_number SP pathname CRLF
5271556Srgrimes		{
5281556Srgrimes			if ($4 && ($8 != NULL)) {
5291556Srgrimes				if ($6 > 0777)
5301556Srgrimes					reply(501,
5311556Srgrimes					    "CHMOD: Mode value must be between "
5321556Srgrimes					    "0 and 0777");
5331556Srgrimes				else if (!umaskchange)
5341556Srgrimes					reply(550,
5351556Srgrimes					    "No permission to change mode of %s.",
5361556Srgrimes					    $8);
5371556Srgrimes				else if (chmod($8, $6) < 0)
5381556Srgrimes					perror_reply(550, $8);
5391556Srgrimes				else
5401556Srgrimes					reply(200,
5411556Srgrimes					    "CHMOD command successful.");
5421556Srgrimes			}
5431556Srgrimes			if ($8 != NULL)
5441556Srgrimes				free($8);
5451556Srgrimes		}
5461556Srgrimes	| SITE SP check_login IDLE CRLF
547		{
548			if ($3)
549			  reply(200,
550	       		    "Current IDLE time limit is %d seconds; max %d",
551				timeout, maxtimeout);
552		}
553	| SITE SP check_login IDLE SP NUMBER CRLF
554		{
555			if ($3) {
556				if ($6 < 30 || $6 > maxtimeout) {
557				reply(501,
558				    "Maximum IDLE time must be between "
559				    "30 and %d seconds",
560				    maxtimeout);
561				} else {
562					timeout = $6;
563					(void) alarm((unsigned) timeout);
564					reply(200,
565					    "Maximum IDLE time set to %d seconds",
566					    timeout);
567				}
568			}
569		}
570	| STOU check_login SP pathname CRLF
571		{
572			if ($2 && $4 != NULL)
573				store($4, "w", 1);
574			if ($4 != NULL)
575				free($4);
576		}
577	| SYST check_login CRLF
578		{
579			if ($2)
580#ifdef unix
581#ifdef BSD
582			reply(215, "UNIX Type: L%d Version: BSD-%d",
583				NBBY, BSD);
584#else /* BSD */
585			reply(215, "UNIX Type: L%d", NBBY);
586#endif /* BSD */
587#else /* unix */
588			reply(215, "UNKNOWN Type: L%d", NBBY);
589#endif /* unix */
590		}
591
592		/*
593		 * SIZE is not in RFC959, but Postel has blessed it and
594		 * it will be in the updated RFC.
595		 *
596		 * Return size of file in a format suitable for
597		 * using with RESTART (we just count bytes).
598		 */
599	| SIZE check_login SP pathname CRLF
600		{
601			if ($2 && $4 != NULL)
602				sizecmd($4);
603			if ($4 != NULL)
604				free($4);
605		}
606
607		/*
608		 * MDTM is not in RFC959, but Postel has blessed it and
609		 * it will be in the updated RFC.
610		 *
611		 * Return modification time of file as an ISO 3307
612		 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
613		 * where xxx is the fractional second (of any precision,
614		 * not necessarily 3 digits)
615		 */
616	| MDTM check_login SP pathname CRLF
617		{
618			if ($2 && $4 != NULL) {
619				struct stat stbuf;
620				if (stat($4, &stbuf) < 0)
621					reply(550, "%s: %s",
622					    $4, strerror(errno));
623				else if (!S_ISREG(stbuf.st_mode)) {
624					reply(550, "%s: not a plain file.", $4);
625				} else {
626					struct tm *t;
627					t = gmtime(&stbuf.st_mtime);
628					reply(213,
629					    "%04d%02d%02d%02d%02d%02d",
630					    TM_YEAR_BASE + t->tm_year,
631					    t->tm_mon+1, t->tm_mday,
632					    t->tm_hour, t->tm_min, t->tm_sec);
633				}
634			}
635			if ($4 != NULL)
636				free($4);
637		}
638	| QUIT CRLF
639		{
640			reply(221, "Goodbye.");
641			dologout(0);
642		}
643	| error
644		{
645			yyclearin;		/* discard lookahead data */
646			yyerrok;		/* clear error condition */
647			state = 0;		/* reset lexer state */
648		}
649	;
650rcmd
651	: RNFR check_login SP pathname CRLF
652		{
653			restart_point = (off_t) 0;
654			if ($2 && $4) {
655				if (fromname)
656					free(fromname);
657				fromname = renamefrom($4);
658				if (fromname == NULL)
659					free($4);
660			} else if ($4) {
661				free ($4);
662			}
663		}
664
665	| REST check_login SP byte_size CRLF
666		{
667			if ($2) {
668			    if (fromname) {
669				    free(fromname);
670				    fromname = NULL;
671			    }
672			    restart_point = $4;	/* XXX $4 is only "int" */
673			    reply(350, "Restarting at %qd. %s", restart_point,
674			       "Send STORE or RETRIEVE to initiate transfer.");
675			}
676		}
677	;
678
679username
680	: STRING
681	;
682
683password
684	: /* empty */
685		{
686			$$ = (char *)calloc(1, sizeof(char));
687		}
688	| STRING
689	;
690
691byte_size
692	: NUMBER
693	;
694
695host_port
696	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
697		NUMBER COMMA NUMBER
698		{
699			char *a, *p;
700
701			if ($1 < 0 || $1 > 255 || $3 < 0 || $3 > 255 ||
702			    $5 < 0 || $5 > 255 || $7 < 0 || $7 > 255 ||
703			    $9 < 0 || $9 > 255 || $11 < 0 || $11 > 255) {
704				$$ = 1;
705			} else {
706				data_dest.su_sin.sin_len = sizeof(struct sockaddr_in);
707				data_dest.su_sin.sin_family = AF_INET;
708				p = (char *)&data_dest.su_sin.sin_port;
709				p[0] = $9; p[1] = $11;
710				a = (char *)&data_dest.su_sin.sin_addr;
711				a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7;
712				$$ = 0;
713			}
714		}
715	;
716
717host_long_port4
718	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
719		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
720		NUMBER
721		{
722			char *a, *p;
723
724			/* reject invalid LPRT command */
725			if ($1 != 4 || $3 != 4
726			 || $5 < 0 || $5 > 255 || $7 < 0 || $7 > 255
727			 || $9 < 0 || $9 > 255 || $11 < 0 || $11 > 255
728			 || $13 != 2
729			 || $15 < 0 || $15 > 255 || $17 < 0 || $17 > 255) {
730				$$ = 1;
731			} else {
732				data_dest.su_sin.sin_len =
733					sizeof(struct sockaddr_in);
734				data_dest.su_family = AF_INET;
735				p = (char *)&data_dest.su_port;
736				p[0] = $15; p[1] = $17;
737				a = (char *)&data_dest.su_sin.sin_addr;
738				a[0] = $5; a[1] = $7; a[2] = $9; a[3] = $11;
739				$$ = 0;
740			}
741		}
742	;
743
744host_long_port6
745	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
746		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
747		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
748		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
749		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
750		NUMBER
751		{
752			char *a, *p;
753
754			/* reject invalid LPRT command */
755			if ($1 != 6 || $3 != 16
756			 || $5 < 0 || $5 > 255 || $7 < 0 || $7 > 255
757			 || $9 < 0 || $9 > 255 || $11 < 0 || $11 > 255
758			 || $13 < 0 || $13 > 255 || $15 < 0 || $15 > 255
759			 || $17 < 0 || $17 > 255 || $19 < 0 || $19 > 255
760			 || $21 < 0 || $21 > 255 || $23 < 0 || $23 > 255
761			 || $25 < 0 || $25 > 255 || $27 < 0 || $27 > 255
762			 || $29 < 0 || $29 > 255 || $31 < 0 || $31 > 255
763			 || $33 < 0 || $33 > 255 || $35 < 0 || $35 > 255
764			 || $37 != 2
765			 || $39 < 0 || $39 > 255 || $41 < 0 || $41 > 255) {
766				$$ = 1;
767			} else {
768				data_dest.su_sin6.sin6_len =
769					sizeof(struct sockaddr_in6);
770				data_dest.su_family = AF_INET6;
771				p = (char *)&data_dest.su_port;
772				p[0] = $39; p[1] = $41;
773				a = (char *)&data_dest.su_sin6.sin6_addr;
774				 a[0] =  $5;  a[1] =  $7;
775				 a[2] =  $9;  a[3] = $11;
776				 a[4] = $13;  a[5] = $15;
777				 a[6] = $17;  a[7] = $19;
778				 a[8] = $21;  a[9] = $23;
779				a[10] = $25; a[11] = $27;
780				a[12] = $29; a[13] = $31;
781				a[14] = $33; a[15] = $35;
782				if (his_addr.su_family == AF_INET6) {
783					/* XXX more sanity checks! */
784					data_dest.su_sin6.sin6_scope_id =
785					    his_addr.su_sin6.sin6_scope_id;
786				}
787
788				$$ = 0;
789			}
790		}
791	;
792
793form_code
794	: N
795		{
796			$$ = FORM_N;
797		}
798	| T
799		{
800			$$ = FORM_T;
801		}
802	| C
803		{
804			$$ = FORM_C;
805		}
806	;
807
808type_code
809	: A
810		{
811			cmd_type = TYPE_A;
812			cmd_form = FORM_N;
813		}
814	| A SP form_code
815		{
816			cmd_type = TYPE_A;
817			cmd_form = $3;
818		}
819	| E
820		{
821			cmd_type = TYPE_E;
822			cmd_form = FORM_N;
823		}
824	| E SP form_code
825		{
826			cmd_type = TYPE_E;
827			cmd_form = $3;
828		}
829	| I
830		{
831			cmd_type = TYPE_I;
832		}
833	| L
834		{
835			cmd_type = TYPE_L;
836			cmd_bytesz = NBBY;
837		}
838	| L SP byte_size
839		{
840			cmd_type = TYPE_L;
841			cmd_bytesz = $3;
842		}
843		/* this is for a bug in the BBN ftp */
844	| L byte_size
845		{
846			cmd_type = TYPE_L;
847			cmd_bytesz = $2;
848		}
849	;
850
851struct_code
852	: F
853		{
854			$$ = STRU_F;
855		}
856	| R
857		{
858			$$ = STRU_R;
859		}
860	| P
861		{
862			$$ = STRU_P;
863		}
864	;
865
866mode_code
867	: S
868		{
869			$$ = MODE_S;
870		}
871	| B
872		{
873			$$ = MODE_B;
874		}
875	| C
876		{
877			$$ = MODE_C;
878		}
879	;
880
881pathname
882	: pathstring
883		{
884			/*
885			 * Problem: this production is used for all pathname
886			 * processing, but only gives a 550 error reply.
887			 * This is a valid reply in some cases but not in others.
888			 */
889			if (logged_in && $1 && strchr($1, '~') != NULL) {
890				glob_t gl;
891				int flags =
892				 GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE;
893				char *pptr = $1;
894
895				/*
896				 * glob() will only find a leading ~, but
897				 * Netscape kindly puts a slash in front of
898				 * it for publish URLs.  There needs to be
899				 * a flag for glob() that expands tildes
900				 * anywhere in the string.
901				 */
902				if ((pptr[0] == '/') && (pptr[1] == '~'))
903					pptr++;
904
905				memset(&gl, 0, sizeof(gl));
906				if (glob(pptr, flags, NULL, &gl) ||
907				    gl.gl_pathc == 0) {
908					reply(550, "not found");
909					$$ = NULL;
910				} else {
911					$$ = strdup(gl.gl_pathv[0]);
912				}
913				globfree(&gl);
914				free($1);
915			} else
916				$$ = $1;
917		}
918	;
919
920pathstring
921	: STRING
922	;
923
924octal_number
925	: NUMBER
926		{
927			int ret, dec, multby, digit;
928
929			/*
930			 * Convert a number that was read as decimal number
931			 * to what it would be if it had been read as octal.
932			 */
933			dec = $1;
934			multby = 1;
935			ret = 0;
936			while (dec) {
937				digit = dec%10;
938				if (digit > 7) {
939					ret = -1;
940					break;
941				}
942				ret += digit * multby;
943				multby *= 8;
944				dec /= 10;
945			}
946			$$ = ret;
947		}
948	;
949
950
951check_login
952	: /* empty */
953		{
954			if (logged_in)
955				$$ = 1;
956			else {
957				reply(530, "Please login with USER and PASS.");
958				$$ = 0;
959			}
960		}
961	;
962
963check_login_epsvall
964	: /* empty */
965		{
966			if (!logged_in) {
967				reply(530, "Please login with USER and PASS.");
968				$$ = 0;
969			} else if (epsvall) {
970				reply(501, "the command is disallowed "
971				    "after EPSV ALL");
972				usedefault = 1;
973				$$ = 0;
974			} else
975				$$ = 1;
976		}
977	;
978
979%%
980
981#define	CMD	0	/* beginning of command */
982#define	ARGS	1	/* expect miscellaneous arguments */
983#define	STR1	2	/* expect SP followed by STRING */
984#define	STR2	3	/* expect STRING */
985#define	OSTR	4	/* optional SP then STRING */
986#define	ZSTR1	5	/* SP then optional STRING */
987#define	ZSTR2	6	/* optional STRING after SP */
988#define	SITECMD	7	/* SITE command */
989#define	NSTR	8	/* Number followed by a string */
990
991struct tab {
992	char	*name;
993	short	token;
994	short	state;
995	short	implemented;	/* 1 if command is implemented */
996	char	*help;
997};
998
999struct tab cmdtab[] = {		/* In order defined in RFC 765 */
1000	{ "USER", USER, STR1, 1,	"<sp> username" },
1001	{ "PASS", PASS, ZSTR1, 1,	"<sp> password" },
1002	{ "ACCT", ACCT, STR1, 0,	"(specify account)" },
1003	{ "SMNT", SMNT, ARGS, 0,	"(structure mount)" },
1004	{ "REIN", REIN, ARGS, 0,	"(reinitialize server state)" },
1005	{ "QUIT", QUIT, ARGS, 1,	"(terminate service)", },
1006	{ "PORT", PORT, ARGS, 1,	"<sp> b0, b1, b2, b3, b4" },
1007	{ "LPRT", LPRT, ARGS, 1,	"<sp> af, hal, h1, h2, h3,..., pal, p1, p2..." },
1008	{ "EPRT", EPRT, STR1, 1,	"<sp> |af|addr|port|" },
1009	{ "PASV", PASV, ARGS, 1,	"(set server in passive mode)" },
1010	{ "LPSV", LPSV, ARGS, 1,	"(set server in passive mode)" },
1011	{ "EPSV", EPSV, ARGS, 1,	"[<sp> af|ALL]" },
1012	{ "TYPE", TYPE, ARGS, 1,	"<sp> [ A | E | I | L ]" },
1013	{ "STRU", STRU, ARGS, 1,	"(specify file structure)" },
1014	{ "MODE", MODE, ARGS, 1,	"(specify transfer mode)" },
1015	{ "RETR", RETR, STR1, 1,	"<sp> file-name" },
1016	{ "STOR", STOR, STR1, 1,	"<sp> file-name" },
1017	{ "APPE", APPE, STR1, 1,	"<sp> file-name" },
1018	{ "MLFL", MLFL, OSTR, 0,	"(mail file)" },
1019	{ "MAIL", MAIL, OSTR, 0,	"(mail to user)" },
1020	{ "MSND", MSND, OSTR, 0,	"(mail send to terminal)" },
1021	{ "MSOM", MSOM, OSTR, 0,	"(mail send to terminal or mailbox)" },
1022	{ "MSAM", MSAM, OSTR, 0,	"(mail send to terminal and mailbox)" },
1023	{ "MRSQ", MRSQ, OSTR, 0,	"(mail recipient scheme question)" },
1024	{ "MRCP", MRCP, STR1, 0,	"(mail recipient)" },
1025	{ "ALLO", ALLO, ARGS, 1,	"allocate storage (vacuously)" },
1026	{ "REST", REST, ARGS, 1,	"<sp> offset (restart command)" },
1027	{ "RNFR", RNFR, STR1, 1,	"<sp> file-name" },
1028	{ "RNTO", RNTO, STR1, 1,	"<sp> file-name" },
1029	{ "ABOR", ABOR, ARGS, 1,	"(abort operation)" },
1030	{ "DELE", DELE, STR1, 1,	"<sp> file-name" },
1031	{ "CWD",  CWD,  OSTR, 1,	"[ <sp> directory-name ]" },
1032	{ "XCWD", CWD,	OSTR, 1,	"[ <sp> directory-name ]" },
1033	{ "LIST", LIST, OSTR, 1,	"[ <sp> path-name ]" },
1034	{ "NLST", NLST, OSTR, 1,	"[ <sp> path-name ]" },
1035	{ "SITE", SITE, SITECMD, 1,	"site-cmd [ <sp> arguments ]" },
1036	{ "SYST", SYST, ARGS, 1,	"(get type of operating system)" },
1037	{ "STAT", STAT, OSTR, 1,	"[ <sp> path-name ]" },
1038	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
1039	{ "NOOP", NOOP, ARGS, 1,	"" },
1040	{ "MKD",  MKD,  STR1, 1,	"<sp> path-name" },
1041	{ "XMKD", MKD,  STR1, 1,	"<sp> path-name" },
1042	{ "RMD",  RMD,  STR1, 1,	"<sp> path-name" },
1043	{ "XRMD", RMD,  STR1, 1,	"<sp> path-name" },
1044	{ "PWD",  PWD,  ARGS, 1,	"(return current directory)" },
1045	{ "XPWD", PWD,  ARGS, 1,	"(return current directory)" },
1046	{ "CDUP", CDUP, ARGS, 1,	"(change to parent directory)" },
1047	{ "XCUP", CDUP, ARGS, 1,	"(change to parent directory)" },
1048	{ "STOU", STOU, STR1, 1,	"<sp> file-name" },
1049	{ "SIZE", SIZE, OSTR, 1,	"<sp> path-name" },
1050	{ "MDTM", MDTM, OSTR, 1,	"<sp> path-name" },
1051	{ NULL,   0,    0,    0,	0 }
1052};
1053
1054struct tab sitetab[] = {
1055	{ "UMASK", UMASK, ARGS, 1,	"[ <sp> umask ]" },
1056	{ "IDLE", IDLE, ARGS, 1,	"[ <sp> maximum-idle-time ]" },
1057	{ "CHMOD", CHMOD, NSTR, 1,	"<sp> mode <sp> file-name" },
1058	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
1059	{ NULL,   0,    0,    0,	0 }
1060};
1061
1062static void	 help __P((struct tab *, char *));
1063static struct tab *
1064		 lookup __P((struct tab *, char *));
1065static void	 sizecmd __P((char *));
1066static int	 yylex __P((void));
1067
1068extern int epsvall;
1069
1070static struct tab *
1071lookup(p, cmd)
1072	struct tab *p;
1073	char *cmd;
1074{
1075
1076	for (; p->name != NULL; p++)
1077		if (strcmp(cmd, p->name) == 0)
1078			return (p);
1079	return (0);
1080}
1081
1082#include <arpa/telnet.h>
1083
1084/*
1085 * getline - a hacked up version of fgets to ignore TELNET escape codes.
1086 */
1087char *
1088getline(s, n, iop)
1089	char *s;
1090	int n;
1091	FILE *iop;
1092{
1093	int c;
1094	char *cs;
1095
1096	cs = s;
1097/* tmpline may contain saved command from urgent mode interruption */
1098	for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
1099		*cs++ = tmpline[c];
1100		if (tmpline[c] == '\n') {
1101			*cs++ = '\0';
1102			if (debug)
1103				syslog(LOG_DEBUG, "command: %s", s);
1104			tmpline[0] = '\0';
1105			return(s);
1106		}
1107		if (c == 0)
1108			tmpline[0] = '\0';
1109	}
1110	while ((c = getc(iop)) != EOF) {
1111		c &= 0377;
1112		if (c == IAC) {
1113		    if ((c = getc(iop)) != EOF) {
1114			c &= 0377;
1115			switch (c) {
1116			case WILL:
1117			case WONT:
1118				c = getc(iop);
1119				printf("%c%c%c", IAC, DONT, 0377&c);
1120				(void) fflush(stdout);
1121				continue;
1122			case DO:
1123			case DONT:
1124				c = getc(iop);
1125				printf("%c%c%c", IAC, WONT, 0377&c);
1126				(void) fflush(stdout);
1127				continue;
1128			case IAC:
1129				break;
1130			default:
1131				continue;	/* ignore command */
1132			}
1133		    }
1134		}
1135		*cs++ = c;
1136		if (--n <= 0 || c == '\n')
1137			break;
1138	}
1139	if (c == EOF && cs == s)
1140		return (NULL);
1141	*cs++ = '\0';
1142	if (debug) {
1143		if (!guest && strncasecmp("pass ", s, 5) == 0) {
1144			/* Don't syslog passwords */
1145			syslog(LOG_DEBUG, "command: %.5s ???", s);
1146		} else {
1147			char *cp;
1148			int len;
1149
1150			/* Don't syslog trailing CR-LF */
1151			len = strlen(s);
1152			cp = s + len - 1;
1153			while (cp >= s && (*cp == '\n' || *cp == '\r')) {
1154				--cp;
1155				--len;
1156			}
1157			syslog(LOG_DEBUG, "command: %.*s", len, s);
1158		}
1159	}
1160	return (s);
1161}
1162
1163void
1164toolong(signo)
1165	int signo;
1166{
1167	struct syslog_data sdata = SYSLOG_DATA_INIT;
1168
1169	/* XXX signal races */
1170	reply(421,
1171	    "Timeout (%d seconds): closing control connection.", timeout);
1172	if (logging)
1173		syslog_r(LOG_INFO, &sdata, "User %s timed out after %d seconds",
1174		    (pw ? pw -> pw_name : "unknown"), timeout);
1175	dologout(1);
1176}
1177
1178static int
1179yylex()
1180{
1181	static int cpos;
1182	char *cp, *cp2;
1183	struct tab *p;
1184	int n;
1185	char c;
1186
1187	for (;;) {
1188		switch (state) {
1189
1190		case CMD:
1191			(void) signal(SIGALRM, toolong);
1192			(void) alarm((unsigned) timeout);
1193			if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
1194				reply(221, "You could at least say goodbye.");
1195				dologout(0);
1196			}
1197			(void) alarm(0);
1198			if ((cp = strchr(cbuf, '\r'))) {
1199				*cp++ = '\n';
1200				*cp = '\0';
1201			}
1202#ifdef HASSETPROCTITLE
1203			if (strncasecmp(cbuf, "PASS", 4) != 0) {
1204				if ((cp = strpbrk(cbuf, "\n"))) {
1205					c = *cp;
1206					*cp = '\0';
1207					setproctitle("%s: %s", proctitle, cbuf);
1208					*cp = c;
1209				}
1210			}
1211#endif /* HASSETPROCTITLE */
1212			if ((cp = strpbrk(cbuf, " \n")))
1213				cpos = cp - cbuf;
1214			if (cpos == 0)
1215				cpos = 4;
1216			c = cbuf[cpos];
1217			cbuf[cpos] = '\0';
1218			upper(cbuf);
1219			p = lookup(cmdtab, cbuf);
1220			cbuf[cpos] = c;
1221			if (p != 0) {
1222				if (p->implemented == 0) {
1223					nack(p->name);
1224					return (LEXERR);
1225				}
1226				state = p->state;
1227				yylval.s = p->name;
1228				return (p->token);
1229			}
1230			break;
1231
1232		case SITECMD:
1233			if (cbuf[cpos] == ' ') {
1234				cpos++;
1235				return (SP);
1236			}
1237			cp = &cbuf[cpos];
1238			if ((cp2 = strpbrk(cp, " \n")))
1239				cpos = cp2 - cbuf;
1240			c = cbuf[cpos];
1241			cbuf[cpos] = '\0';
1242			upper(cp);
1243			p = lookup(sitetab, cp);
1244			cbuf[cpos] = c;
1245			if (p != 0) {
1246				if (p->implemented == 0) {
1247					state = CMD;
1248					nack(p->name);
1249					return (LEXERR);
1250				}
1251				state = p->state;
1252				yylval.s = p->name;
1253				return (p->token);
1254			}
1255			state = CMD;
1256			break;
1257
1258		case OSTR:
1259			if (cbuf[cpos] == '\n') {
1260				state = CMD;
1261				return (CRLF);
1262			}
1263			/* FALLTHROUGH */
1264
1265		case STR1:
1266		case ZSTR1:
1267		dostr1:
1268			if (cbuf[cpos] == ' ') {
1269				cpos++;
1270				state = state == OSTR ? STR2 : state+1;
1271				return (SP);
1272			}
1273			break;
1274
1275		case ZSTR2:
1276			if (cbuf[cpos] == '\n') {
1277				state = CMD;
1278				return (CRLF);
1279			}
1280			/* FALLTHROUGH */
1281
1282		case STR2:
1283			cp = &cbuf[cpos];
1284			n = strlen(cp);
1285			cpos += n - 1;
1286			/*
1287			 * Make sure the string is nonempty and \n terminated.
1288			 */
1289			if (n > 1 && cbuf[cpos] == '\n') {
1290				cbuf[cpos] = '\0';
1291				yylval.s = strdup(cp);
1292				if (yylval.s == NULL)
1293					fatal("Ran out of memory.");
1294				cbuf[cpos] = '\n';
1295				state = ARGS;
1296				return (STRING);
1297			}
1298			break;
1299
1300		case NSTR:
1301			if (cbuf[cpos] == ' ') {
1302				cpos++;
1303				return (SP);
1304			}
1305			if (isdigit(cbuf[cpos])) {
1306				cp = &cbuf[cpos];
1307				while (isdigit(cbuf[++cpos]))
1308					;
1309				c = cbuf[cpos];
1310				cbuf[cpos] = '\0';
1311				yylval.i = atoi(cp);
1312				cbuf[cpos] = c;
1313				state = STR1;
1314				return (NUMBER);
1315			}
1316			state = STR1;
1317			goto dostr1;
1318
1319		case ARGS:
1320			if (isdigit(cbuf[cpos])) {
1321				cp = &cbuf[cpos];
1322				while (isdigit(cbuf[++cpos]))
1323					;
1324				c = cbuf[cpos];
1325				cbuf[cpos] = '\0';
1326				yylval.i = atoi(cp);
1327				cbuf[cpos] = c;
1328				return (NUMBER);
1329			}
1330			if (strncasecmp(&cbuf[cpos], "ALL", 3) == 0
1331			 && !isalnum(cbuf[cpos + 3])) {
1332				yylval.s = strdup("ALL");
1333				cpos += 3;
1334				return ALL;
1335			}
1336			switch (cbuf[cpos++]) {
1337
1338			case '\n':
1339				state = CMD;
1340				return (CRLF);
1341
1342			case ' ':
1343				return (SP);
1344
1345			case ',':
1346				return (COMMA);
1347
1348			case 'A':
1349			case 'a':
1350				return (A);
1351
1352			case 'B':
1353			case 'b':
1354				return (B);
1355
1356			case 'C':
1357			case 'c':
1358				return (C);
1359
1360			case 'E':
1361			case 'e':
1362				return (E);
1363
1364			case 'F':
1365			case 'f':
1366				return (F);
1367
1368			case 'I':
1369			case 'i':
1370				return (I);
1371
1372			case 'L':
1373			case 'l':
1374				return (L);
1375
1376			case 'N':
1377			case 'n':
1378				return (N);
1379
1380			case 'P':
1381			case 'p':
1382				return (P);
1383
1384			case 'R':
1385			case 'r':
1386				return (R);
1387
1388			case 'S':
1389			case 's':
1390				return (S);
1391
1392			case 'T':
1393			case 't':
1394				return (T);
1395
1396			}
1397			break;
1398
1399		default:
1400			fatal("Unknown state in scanner.");
1401		}
1402		state = CMD;
1403		return (LEXERR);
1404	}
1405}
1406
1407void
1408upper(s)
1409	char *s;
1410{
1411	while (*s != '\0') {
1412		if (islower(*s))
1413			*s = toupper(*s);
1414		s++;
1415	}
1416}
1417
1418static void
1419help(ctab, s)
1420	struct tab *ctab;
1421	char *s;
1422{
1423	struct tab *c;
1424	int width, NCMDS;
1425	char *type;
1426
1427	if (ctab == sitetab)
1428		type = "SITE ";
1429	else
1430		type = "";
1431	width = 0, NCMDS = 0;
1432	for (c = ctab; c->name != NULL; c++) {
1433		int len = strlen(c->name);
1434
1435		if (len > width)
1436			width = len;
1437		NCMDS++;
1438	}
1439	width = (width + 8) &~ 7;
1440	if (s == 0) {
1441		int i, j, w;
1442		int columns, lines;
1443
1444		lreply(214, "The following %scommands are recognized %s.",
1445		    type, "(* =>'s unimplemented)");
1446		columns = 76 / width;
1447		if (columns == 0)
1448			columns = 1;
1449		lines = (NCMDS + columns - 1) / columns;
1450		for (i = 0; i < lines; i++) {
1451			printf("   ");
1452			for (j = 0; j < columns; j++) {
1453				c = ctab + j * lines + i;
1454				printf("%s%c", c->name,
1455					c->implemented ? ' ' : '*');
1456				if (c + lines >= &ctab[NCMDS])
1457					break;
1458				w = strlen(c->name) + 1;
1459				while (w < width) {
1460					putchar(' ');
1461					w++;
1462				}
1463			}
1464			printf("\r\n");
1465		}
1466		(void) fflush(stdout);
1467		reply(214, "Direct comments to ftp-bugs@%s.", hostname);
1468		return;
1469	}
1470	upper(s);
1471	c = lookup(ctab, s);
1472	if (c == (struct tab *)0) {
1473		reply(502, "Unknown command %s.", s);
1474		return;
1475	}
1476	if (c->implemented)
1477		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
1478	else
1479		reply(214, "%s%-*s\t%s; unimplemented.", type, width,
1480		    c->name, c->help);
1481}
1482
1483static void
1484sizecmd(filename)
1485	char *filename;
1486{
1487	switch (type) {
1488	case TYPE_L:
1489	case TYPE_I: {
1490		struct stat stbuf;
1491		if (stat(filename, &stbuf) < 0 || !S_ISREG(stbuf.st_mode))
1492			reply(550, "%s: not a plain file.", filename);
1493		else
1494			reply(213, "%qu", stbuf.st_size);
1495		break; }
1496	case TYPE_A: {
1497		FILE *fin;
1498		int c;
1499		off_t count;
1500		struct stat stbuf;
1501		fin = fopen(filename, "r");
1502		if (fin == NULL) {
1503			perror_reply(550, filename);
1504			return;
1505		}
1506		if (fstat(fileno(fin), &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) {
1507			reply(550, "%s: not a plain file.", filename);
1508			(void) fclose(fin);
1509			return;
1510		}
1511
1512		count = 0;
1513		while((c=getc(fin)) != EOF) {
1514			if (c == '\n')	/* will get expanded to \r\n */
1515				count++;
1516			count++;
1517		}
1518		(void) fclose(fin);
1519
1520		reply(213, "%qd", count);
1521		break; }
1522	default:
1523		reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
1524	}
1525}
1526