1161764Sobrien/*	$NetBSD: ftpcmd.y,v 1.84 2006/02/01 14:20:12 christos Exp $	*/
279968Sobrien
379968Sobrien/*-
4161764Sobrien * Copyright (c) 1997-2005 The NetBSD Foundation, Inc.
579968Sobrien * All rights reserved.
679968Sobrien *
779968Sobrien * This code is derived from software contributed to The NetBSD Foundation
879968Sobrien * by Luke Mewburn.
979968Sobrien *
1079968Sobrien * Redistribution and use in source and binary forms, with or without
1179968Sobrien * modification, are permitted provided that the following conditions
1279968Sobrien * are met:
1379968Sobrien * 1. Redistributions of source code must retain the above copyright
1479968Sobrien *    notice, this list of conditions and the following disclaimer.
1579968Sobrien * 2. Redistributions in binary form must reproduce the above copyright
1679968Sobrien *    notice, this list of conditions and the following disclaimer in the
1779968Sobrien *    documentation and/or other materials provided with the distribution.
1879968Sobrien * 3. All advertising materials mentioning features or use of this software
1979968Sobrien *    must display the following acknowledgement:
2079968Sobrien *        This product includes software developed by the NetBSD
2179968Sobrien *        Foundation, Inc. and its contributors.
2279968Sobrien * 4. Neither the name of The NetBSD Foundation nor the names of its
2379968Sobrien *    contributors may be used to endorse or promote products derived
2479968Sobrien *    from this software without specific prior written permission.
2579968Sobrien *
2679968Sobrien * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
2779968Sobrien * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
2879968Sobrien * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
2979968Sobrien * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
3079968Sobrien * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
3179968Sobrien * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
3279968Sobrien * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
3379968Sobrien * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
3479968Sobrien * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
3579968Sobrien * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
3679968Sobrien * POSSIBILITY OF SUCH DAMAGE.
3779968Sobrien */
3879968Sobrien
3979968Sobrien/*
4079968Sobrien * Copyright (c) 1985, 1988, 1993, 1994
4179968Sobrien *	The Regents of the University of California.  All rights reserved.
4279968Sobrien *
4379968Sobrien * Redistribution and use in source and binary forms, with or without
4479968Sobrien * modification, are permitted provided that the following conditions
4579968Sobrien * are met:
4679968Sobrien * 1. Redistributions of source code must retain the above copyright
4779968Sobrien *    notice, this list of conditions and the following disclaimer.
4879968Sobrien * 2. Redistributions in binary form must reproduce the above copyright
4979968Sobrien *    notice, this list of conditions and the following disclaimer in the
5079968Sobrien *    documentation and/or other materials provided with the distribution.
51133936Sobrien * 3. Neither the name of the University nor the names of its contributors
5279968Sobrien *    may be used to endorse or promote products derived from this software
5379968Sobrien *    without specific prior written permission.
5479968Sobrien *
5579968Sobrien * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
5679968Sobrien * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
5779968Sobrien * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
5879968Sobrien * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
5979968Sobrien * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
6079968Sobrien * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
6179968Sobrien * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
6279968Sobrien * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
6379968Sobrien * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
6479968Sobrien * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
6579968Sobrien * SUCH DAMAGE.
6679968Sobrien *
6779968Sobrien *	@(#)ftpcmd.y	8.3 (Berkeley) 4/6/94
6879968Sobrien */
6979968Sobrien
7079968Sobrien/*
7179968Sobrien * Grammar for FTP commands.
7279968Sobrien * See RFC 959.
7379968Sobrien */
7479968Sobrien
7579968Sobrien%{
76108746Sobrien#include <sys/cdefs.h>
7779968Sobrien
78108746Sobrien#ifndef lint
79108746Sobrien#if 0
80108746Sobrienstatic char sccsid[] = "@(#)ftpcmd.y	8.3 (Berkeley) 4/6/94";
81108746Sobrien#else
82161764Sobrien__RCSID("$NetBSD: ftpcmd.y,v 1.84 2006/02/01 14:20:12 christos Exp $");
83108746Sobrien#endif
84108746Sobrien#endif /* not lint */
85108746Sobrien
86108746Sobrien#include <sys/param.h>
87108746Sobrien#include <sys/socket.h>
88108746Sobrien#include <sys/stat.h>
89108746Sobrien
90108746Sobrien#include <netinet/in.h>
91108746Sobrien#include <arpa/ftp.h>
92108746Sobrien#include <arpa/inet.h>
93108746Sobrien
94108746Sobrien#include <ctype.h>
95108746Sobrien#include <errno.h>
96108746Sobrien#include <pwd.h>
97108746Sobrien#include <stdio.h>
98108746Sobrien#include <stdlib.h>
99108746Sobrien#include <string.h>
100108746Sobrien#include <syslog.h>
101108746Sobrien#include <time.h>
102108746Sobrien#include <tzfile.h>
103108746Sobrien#include <unistd.h>
104108746Sobrien#include <netdb.h>
105108746Sobrien
106108746Sobrien#ifdef KERBEROS5
107108746Sobrien#include <krb5/krb5.h>
108108746Sobrien#endif
109108746Sobrien
11079968Sobrien#include "extern.h"
11179968Sobrien#include "version.h"
11279968Sobrien
11379968Sobrienstatic	int cmd_type;
11479968Sobrienstatic	int cmd_form;
11579968Sobrienstatic	int cmd_bytesz;
11679968Sobrien
11779968Sobrienchar	cbuf[FTP_BUFLEN];
11879968Sobrienchar	*cmdp;
11979968Sobrienchar	*fromname;
12079968Sobrien
121161764Sobrienextern int	epsvall;
122161764Sobrienstruct tab	sitetab[];
123161764Sobrien
124161764Sobrienstatic	int	check_write(const char *, int);
125161764Sobrienstatic	void	help(struct tab *, const char *);
126161764Sobrienstatic	void	port_check(const char *, int);
127161764Sobrien	int	yylex(void);
128161764Sobrien
12979968Sobrien%}
13079968Sobrien
13179968Sobrien%union {
132108746Sobrien	struct {
133133936Sobrien		LLT	ll;
134108746Sobrien		int	i;
135108746Sobrien	} u;
13679968Sobrien	char   *s;
13779968Sobrien}
13879968Sobrien
13979968Sobrien%token
14079968Sobrien	A	B	C	E	F	I
14179968Sobrien	L	N	P	R	S	T
14279968Sobrien
143133936Sobrien	SP	CRLF	COMMA	ALL
14479968Sobrien
14579968Sobrien	USER	PASS	ACCT	CWD	CDUP	SMNT
14679968Sobrien	QUIT	REIN	PORT	PASV	TYPE	STRU
14779968Sobrien	MODE	RETR	STOR	STOU	APPE	ALLO
14879968Sobrien	REST	RNFR	RNTO	ABOR	DELE	RMD
14979968Sobrien	MKD	PWD	LIST	NLST	SITE	SYST
15079968Sobrien	STAT	HELP	NOOP
15179968Sobrien
15279968Sobrien	AUTH	ADAT	PROT	PBSZ	CCC	MIC
15379968Sobrien	CONF	ENC
15479968Sobrien
15579968Sobrien	FEAT	OPTS
15679968Sobrien
15779968Sobrien	SIZE	MDTM	MLST	MLSD
15879968Sobrien
15979968Sobrien	LPRT	LPSV	EPRT	EPSV
16079968Sobrien
16179968Sobrien	MAIL	MLFL	MRCP	MRSQ	MSAM	MSND
16279968Sobrien	MSOM
16379968Sobrien
16479968Sobrien	CHMOD	IDLE	RATEGET	RATEPUT	UMASK
16579968Sobrien
16679968Sobrien	LEXERR
16779968Sobrien
16879968Sobrien%token	<s> STRING
169108746Sobrien%token	<u> NUMBER
17079968Sobrien
171108746Sobrien%type	<u.i> check_login octal_number byte_size
172108746Sobrien%type	<u.i> struct_code mode_code type_code form_code decimal_integer
17379968Sobrien%type	<s> pathstring pathname password username
17479968Sobrien%type	<s> mechanism_name base64data prot_code
17579968Sobrien
17679968Sobrien%start	cmd_sel
17779968Sobrien
17879968Sobrien%%
17979968Sobrien
18079968Sobriencmd_sel
18179968Sobrien	: cmd
18279968Sobrien		{
183133936Sobrien			REASSIGN(fromname, NULL);
18479968Sobrien			restart_point = (off_t) 0;
18579968Sobrien		}
18679968Sobrien
18779968Sobrien	| rcmd
18879968Sobrien
18979968Sobrien	;
19079968Sobrien
19179968Sobriencmd
19279968Sobrien						/* RFC 959 */
19379968Sobrien	: USER SP username CRLF
19479968Sobrien		{
19579968Sobrien			user($3);
19679968Sobrien			free($3);
19779968Sobrien		}
19879968Sobrien
19979968Sobrien	| PASS SP password CRLF
20079968Sobrien		{
20179968Sobrien			pass($3);
20279968Sobrien			memset($3, 0, strlen($3));
20379968Sobrien			free($3);
20479968Sobrien		}
20579968Sobrien
20679968Sobrien	| CWD check_login CRLF
20779968Sobrien		{
20879968Sobrien			if ($2)
20979968Sobrien				cwd(homedir);
21079968Sobrien		}
21179968Sobrien
21279968Sobrien	| CWD check_login SP pathname CRLF
21379968Sobrien		{
21479968Sobrien			if ($2 && $4 != NULL)
21579968Sobrien				cwd($4);
21679968Sobrien			if ($4 != NULL)
21779968Sobrien				free($4);
21879968Sobrien		}
21979968Sobrien
22079968Sobrien	| CDUP check_login CRLF
22179968Sobrien		{
22279968Sobrien			if ($2)
22379968Sobrien				cwd("..");
22479968Sobrien		}
22579968Sobrien
22679968Sobrien	| QUIT CRLF
22779968Sobrien		{
22879968Sobrien			if (logged_in) {
22979968Sobrien				reply(-221, "%s", "");
23079968Sobrien				reply(0,
23179968Sobrien "Data traffic for this session was " LLF " byte%s in " LLF " file%s.",
23279968Sobrien				    (LLT)total_data, PLURAL(total_data),
23379968Sobrien				    (LLT)total_files, PLURAL(total_files));
23479968Sobrien				reply(0,
23579968Sobrien "Total traffic for this session was " LLF " byte%s in " LLF " transfer%s.",
23679968Sobrien				    (LLT)total_bytes, PLURAL(total_bytes),
23779968Sobrien				    (LLT)total_xfers, PLURAL(total_xfers));
23879968Sobrien			}
23979968Sobrien			reply(221,
24079968Sobrien			    "Thank you for using the FTP service on %s.",
24179968Sobrien			    hostname);
24279968Sobrien			if (logged_in && logging) {
24379968Sobrien				syslog(LOG_INFO,
24479968Sobrien		"Data traffic: " LLF " byte%s in " LLF " file%s",
24579968Sobrien				    (LLT)total_data, PLURAL(total_data),
24679968Sobrien				    (LLT)total_files, PLURAL(total_files));
24779968Sobrien				syslog(LOG_INFO,
24879968Sobrien		"Total traffic: " LLF " byte%s in " LLF " transfer%s",
24979968Sobrien				    (LLT)total_bytes, PLURAL(total_bytes),
25079968Sobrien				    (LLT)total_xfers, PLURAL(total_xfers));
25179968Sobrien			}
25279968Sobrien
25379968Sobrien			dologout(0);
25479968Sobrien		}
25579968Sobrien
25679968Sobrien	| PORT check_login SP host_port CRLF
25779968Sobrien		{
25879968Sobrien			if ($2)
25979968Sobrien				port_check("PORT", AF_INET);
26079968Sobrien		}
26179968Sobrien
26279968Sobrien	| LPRT check_login SP host_long_port4 CRLF
26379968Sobrien		{
26479968Sobrien			if ($2)
26579968Sobrien				port_check("LPRT", AF_INET);
26679968Sobrien		}
26779968Sobrien
26879968Sobrien	| LPRT check_login SP host_long_port6 CRLF
26979968Sobrien		{
27079968Sobrien#ifdef INET6
27179968Sobrien			if ($2)
27279968Sobrien				port_check("LPRT", AF_INET6);
27379968Sobrien#else
27479968Sobrien			reply(500, "IPv6 support not available.");
27579968Sobrien#endif
27679968Sobrien		}
27779968Sobrien
27879968Sobrien	| EPRT check_login SP STRING CRLF
27979968Sobrien		{
28079968Sobrien			if ($2) {
28179968Sobrien				if (extended_port($4) == 0)
28279968Sobrien					port_check("EPRT", -1);
28379968Sobrien			}
28479968Sobrien			free($4);
28579968Sobrien		}
28679968Sobrien
28779968Sobrien	| PASV check_login CRLF
28879968Sobrien		{
28979968Sobrien			if ($2) {
29079968Sobrien				if (CURCLASS_FLAGS_ISSET(passive))
29179968Sobrien					passive();
29279968Sobrien				else
29379968Sobrien					reply(500, "PASV mode not available.");
29479968Sobrien			}
29579968Sobrien		}
29679968Sobrien
29779968Sobrien	| LPSV check_login CRLF
29879968Sobrien		{
29979968Sobrien			if ($2) {
300108746Sobrien				if (CURCLASS_FLAGS_ISSET(passive)) {
301108746Sobrien					if (epsvall)
302108746Sobrien						reply(501,
303108746Sobrien						    "LPSV disallowed after EPSV ALL");
304108746Sobrien					else
305108746Sobrien						long_passive("LPSV", PF_UNSPEC);
306108746Sobrien				} else
307108746Sobrien					reply(500, "LPSV mode not available.");
30879968Sobrien			}
30979968Sobrien		}
31079968Sobrien
31179968Sobrien	| EPSV check_login SP NUMBER CRLF
31279968Sobrien		{
313108746Sobrien			if ($2) {
314108746Sobrien				if (CURCLASS_FLAGS_ISSET(passive))
315108746Sobrien					long_passive("EPSV",
316108746Sobrien					    epsvproto2af($4.i));
317108746Sobrien				else
318108746Sobrien					reply(500, "EPSV mode not available.");
319108746Sobrien			}
32079968Sobrien		}
32179968Sobrien
32279968Sobrien	| EPSV check_login SP ALL CRLF
32379968Sobrien		{
32479968Sobrien			if ($2) {
325108746Sobrien				if (CURCLASS_FLAGS_ISSET(passive)) {
326108746Sobrien					reply(200,
327108746Sobrien					    "EPSV ALL command successful.");
328108746Sobrien					epsvall++;
329108746Sobrien				} else
330108746Sobrien					reply(500, "EPSV mode not available.");
33179968Sobrien			}
33279968Sobrien		}
33379968Sobrien
33479968Sobrien	| EPSV check_login CRLF
33579968Sobrien		{
336108746Sobrien			if ($2) {
337108746Sobrien				if (CURCLASS_FLAGS_ISSET(passive))
338108746Sobrien					long_passive("EPSV", PF_UNSPEC);
339108746Sobrien				else
340108746Sobrien					reply(500, "EPSV mode not available.");
341108746Sobrien			}
34279968Sobrien		}
34379968Sobrien
34479968Sobrien	| TYPE check_login SP type_code CRLF
34579968Sobrien		{
34679968Sobrien			if ($2) {
34779968Sobrien
34879968Sobrien			switch (cmd_type) {
34979968Sobrien
35079968Sobrien			case TYPE_A:
35179968Sobrien				if (cmd_form == FORM_N) {
35279968Sobrien					reply(200, "Type set to A.");
35379968Sobrien					type = cmd_type;
35479968Sobrien					form = cmd_form;
35579968Sobrien				} else
35679968Sobrien					reply(504, "Form must be N.");
35779968Sobrien				break;
35879968Sobrien
35979968Sobrien			case TYPE_E:
36079968Sobrien				reply(504, "Type E not implemented.");
36179968Sobrien				break;
36279968Sobrien
36379968Sobrien			case TYPE_I:
36479968Sobrien				reply(200, "Type set to I.");
36579968Sobrien				type = cmd_type;
36679968Sobrien				break;
36779968Sobrien
36879968Sobrien			case TYPE_L:
36979968Sobrien#if NBBY == 8
37079968Sobrien				if (cmd_bytesz == 8) {
37179968Sobrien					reply(200,
37279968Sobrien					    "Type set to L (byte size 8).");
37379968Sobrien					type = cmd_type;
37479968Sobrien				} else
37579968Sobrien					reply(504, "Byte size must be 8.");
37679968Sobrien#else /* NBBY == 8 */
37779968Sobrien				UNIMPLEMENTED for NBBY != 8
37879968Sobrien#endif /* NBBY == 8 */
37979968Sobrien			}
38079968Sobrien
38179968Sobrien			}
38279968Sobrien		}
38379968Sobrien
38479968Sobrien	| STRU check_login SP struct_code CRLF
38579968Sobrien		{
38679968Sobrien			if ($2) {
38779968Sobrien				switch ($4) {
38879968Sobrien
38979968Sobrien				case STRU_F:
39079968Sobrien					reply(200, "STRU F ok.");
39179968Sobrien					break;
39279968Sobrien
39379968Sobrien				default:
39479968Sobrien					reply(504, "Unimplemented STRU type.");
39579968Sobrien				}
39679968Sobrien			}
39779968Sobrien		}
39879968Sobrien
39979968Sobrien	| MODE check_login SP mode_code CRLF
40079968Sobrien		{
40179968Sobrien			if ($2) {
40279968Sobrien				switch ($4) {
40379968Sobrien
40479968Sobrien				case MODE_S:
40579968Sobrien					reply(200, "MODE S ok.");
40679968Sobrien					break;
40779968Sobrien
40879968Sobrien				default:
40979968Sobrien					reply(502, "Unimplemented MODE type.");
41079968Sobrien				}
41179968Sobrien			}
41279968Sobrien		}
41379968Sobrien
41479968Sobrien	| RETR check_login SP pathname CRLF
41579968Sobrien		{
41679968Sobrien			if ($2 && $4 != NULL)
41779968Sobrien				retrieve(NULL, $4);
41879968Sobrien			if ($4 != NULL)
41979968Sobrien				free($4);
42079968Sobrien		}
42179968Sobrien
42279968Sobrien	| STOR SP pathname CRLF
42379968Sobrien		{
42479968Sobrien			if (check_write($3, 1))
42579968Sobrien				store($3, "w", 0);
42679968Sobrien			if ($3 != NULL)
42779968Sobrien				free($3);
42879968Sobrien		}
42979968Sobrien
43079968Sobrien	| STOU SP pathname CRLF
43179968Sobrien		{
43279968Sobrien			if (check_write($3, 1))
43379968Sobrien				store($3, "w", 1);
43479968Sobrien			if ($3 != NULL)
43579968Sobrien				free($3);
43679968Sobrien		}
43779968Sobrien
43879968Sobrien	| APPE SP pathname CRLF
43979968Sobrien		{
44079968Sobrien			if (check_write($3, 1))
44179968Sobrien				store($3, "a", 0);
44279968Sobrien			if ($3 != NULL)
44379968Sobrien				free($3);
44479968Sobrien		}
44579968Sobrien
44679968Sobrien	| ALLO check_login SP NUMBER CRLF
44779968Sobrien		{
44879968Sobrien			if ($2)
44979968Sobrien				reply(202, "ALLO command ignored.");
45079968Sobrien		}
45179968Sobrien
45279968Sobrien	| ALLO check_login SP NUMBER SP R SP NUMBER CRLF
45379968Sobrien		{
45479968Sobrien			if ($2)
45579968Sobrien				reply(202, "ALLO command ignored.");
45679968Sobrien		}
45779968Sobrien
45879968Sobrien	| RNTO SP pathname CRLF
45979968Sobrien		{
46079968Sobrien			if (check_write($3, 0)) {
46179968Sobrien				if (fromname) {
46279968Sobrien					renamecmd(fromname, $3);
463133936Sobrien					REASSIGN(fromname, NULL);
46479968Sobrien				} else {
46579968Sobrien					reply(503, "Bad sequence of commands.");
46679968Sobrien				}
46779968Sobrien			}
46879968Sobrien			if ($3 != NULL)
46979968Sobrien				free($3);
47079968Sobrien		}
47179968Sobrien
47279968Sobrien	| ABOR check_login CRLF
47379968Sobrien		{
47479968Sobrien			if (is_oob)
47579968Sobrien				abor();
47679968Sobrien			else if ($2)
47779968Sobrien				reply(225, "ABOR command successful.");
47879968Sobrien		}
47979968Sobrien
48079968Sobrien	| DELE SP pathname CRLF
48179968Sobrien		{
48279968Sobrien			if (check_write($3, 0))
48379968Sobrien				delete($3);
48479968Sobrien			if ($3 != NULL)
48579968Sobrien				free($3);
48679968Sobrien		}
48779968Sobrien
48879968Sobrien	| RMD SP pathname CRLF
48979968Sobrien		{
49079968Sobrien			if (check_write($3, 0))
49179968Sobrien				removedir($3);
49279968Sobrien			if ($3 != NULL)
49379968Sobrien				free($3);
49479968Sobrien		}
49579968Sobrien
49679968Sobrien	| MKD SP pathname CRLF
49779968Sobrien		{
49879968Sobrien			if (check_write($3, 0))
49979968Sobrien				makedir($3);
50079968Sobrien			if ($3 != NULL)
50179968Sobrien				free($3);
50279968Sobrien		}
50379968Sobrien
50479968Sobrien	| PWD check_login CRLF
50579968Sobrien		{
50679968Sobrien			if ($2)
50779968Sobrien				pwd();
50879968Sobrien		}
50979968Sobrien
51079968Sobrien	| LIST check_login CRLF
51179968Sobrien		{
51279968Sobrien			char *argv[] = { INTERNAL_LS, "-lgA", NULL };
51379968Sobrien
514161764Sobrien			if (CURCLASS_FLAGS_ISSET(hidesymlinks))
515161764Sobrien				argv[1] = "-LlgA";
51679968Sobrien			if ($2)
51779968Sobrien				retrieve(argv, "");
51879968Sobrien		}
51979968Sobrien
52079968Sobrien	| LIST check_login SP pathname CRLF
52179968Sobrien		{
52279968Sobrien			char *argv[] = { INTERNAL_LS, "-lgA", NULL, NULL };
52379968Sobrien
524161764Sobrien			if (CURCLASS_FLAGS_ISSET(hidesymlinks))
525161764Sobrien				argv[1] = "-LlgA";
52679968Sobrien			if ($2 && $4 != NULL) {
52779968Sobrien				argv[2] = $4;
52879968Sobrien				retrieve(argv, $4);
52979968Sobrien			}
53079968Sobrien			if ($4 != NULL)
53179968Sobrien				free($4);
53279968Sobrien		}
53379968Sobrien
53479968Sobrien	| NLST check_login CRLF
53579968Sobrien		{
53679968Sobrien			if ($2)
53779968Sobrien				send_file_list(".");
53879968Sobrien		}
53979968Sobrien
54079968Sobrien	| NLST check_login SP pathname CRLF
54179968Sobrien		{
54279968Sobrien			if ($2)
54379968Sobrien				send_file_list($4);
54479968Sobrien			free($4);
54579968Sobrien		}
54679968Sobrien
54779968Sobrien	| SITE SP HELP CRLF
54879968Sobrien		{
54979968Sobrien			help(sitetab, NULL);
55079968Sobrien		}
55179968Sobrien
55279968Sobrien	| SITE SP CHMOD SP octal_number SP pathname CRLF
55379968Sobrien		{
55479968Sobrien			if (check_write($7, 0)) {
555133936Sobrien				if (($5 == -1) || ($5 > 0777))
55679968Sobrien					reply(501,
55779968Sobrien				"CHMOD: Mode value must be between 0 and 0777");
55879968Sobrien				else if (chmod($7, $5) < 0)
55979968Sobrien					perror_reply(550, $7);
56079968Sobrien				else
56179968Sobrien					reply(200, "CHMOD command successful.");
56279968Sobrien			}
56379968Sobrien			if ($7 != NULL)
56479968Sobrien				free($7);
56579968Sobrien		}
56679968Sobrien
56779968Sobrien	| SITE SP HELP SP STRING CRLF
56879968Sobrien		{
56979968Sobrien			help(sitetab, $5);
57079968Sobrien			free($5);
57179968Sobrien		}
57279968Sobrien
57379968Sobrien	| SITE SP IDLE check_login CRLF
57479968Sobrien		{
57579968Sobrien			if ($4) {
57679968Sobrien				reply(200,
577108746Sobrien				    "Current IDLE time limit is " LLF
578108746Sobrien				    " seconds; max " LLF,
579108746Sobrien				    (LLT)curclass.timeout,
580108746Sobrien				    (LLT)curclass.maxtimeout);
58179968Sobrien			}
58279968Sobrien		}
58379968Sobrien
58479968Sobrien	| SITE SP IDLE check_login SP NUMBER CRLF
58579968Sobrien		{
58679968Sobrien			if ($4) {
587108746Sobrien				if ($6.i < 30 || $6.i > curclass.maxtimeout) {
58879968Sobrien					reply(501,
589108746Sobrien				"IDLE time limit must be between 30 and "
590108746Sobrien					    LLF " seconds",
591108746Sobrien					    (LLT)curclass.maxtimeout);
59279968Sobrien				} else {
593108746Sobrien					curclass.timeout = $6.i;
59479968Sobrien					(void) alarm(curclass.timeout);
59579968Sobrien					reply(200,
596108746Sobrien					    "IDLE time limit set to "
597108746Sobrien					    LLF " seconds",
598108746Sobrien					    (LLT)curclass.timeout);
59979968Sobrien				}
60079968Sobrien			}
60179968Sobrien		}
60279968Sobrien
60379968Sobrien	| SITE SP RATEGET check_login CRLF
60479968Sobrien		{
60579968Sobrien			if ($4) {
60679968Sobrien				reply(200,
60779968Sobrien				    "Current RATEGET is " LLF " bytes/sec",
60879968Sobrien				    (LLT)curclass.rateget);
60979968Sobrien			}
61079968Sobrien		}
61179968Sobrien
61279968Sobrien	| SITE SP RATEGET check_login SP STRING CRLF
61379968Sobrien		{
614108746Sobrien			char errbuf[100];
61579968Sobrien			char *p = $6;
61679968Sobrien			LLT rate;
61779968Sobrien
61879968Sobrien			if ($4) {
619108746Sobrien				rate = strsuftollx("RATEGET", p, 0,
620108746Sobrien				    curclass.maxrateget
621108746Sobrien				    ? curclass.maxrateget
622108746Sobrien				    : LLTMAX, errbuf, sizeof(errbuf));
623108746Sobrien				if (errbuf[0])
624108746Sobrien					reply(501, "%s", errbuf);
62579968Sobrien				else {
62679968Sobrien					curclass.rateget = rate;
62779968Sobrien					reply(200,
62879968Sobrien					    "RATEGET set to " LLF " bytes/sec",
62979968Sobrien					    (LLT)curclass.rateget);
63079968Sobrien				}
63179968Sobrien			}
63279968Sobrien			free($6);
63379968Sobrien		}
63479968Sobrien
63579968Sobrien	| SITE SP RATEPUT check_login CRLF
63679968Sobrien		{
63779968Sobrien			if ($4) {
63879968Sobrien				reply(200,
63979968Sobrien				    "Current RATEPUT is " LLF " bytes/sec",
64079968Sobrien				    (LLT)curclass.rateput);
64179968Sobrien			}
64279968Sobrien		}
64379968Sobrien
64479968Sobrien	| SITE SP RATEPUT check_login SP STRING CRLF
64579968Sobrien		{
646108746Sobrien			char errbuf[100];
64779968Sobrien			char *p = $6;
64879968Sobrien			LLT rate;
64979968Sobrien
65079968Sobrien			if ($4) {
651108746Sobrien				rate = strsuftollx("RATEPUT", p, 0,
652108746Sobrien				    curclass.maxrateput
653108746Sobrien				    ? curclass.maxrateput
654108746Sobrien				    : LLTMAX, errbuf, sizeof(errbuf));
655108746Sobrien				if (errbuf[0])
656108746Sobrien					reply(501, "%s", errbuf);
65779968Sobrien				else {
65879968Sobrien					curclass.rateput = rate;
65979968Sobrien					reply(200,
66079968Sobrien					    "RATEPUT set to " LLF " bytes/sec",
66179968Sobrien					    (LLT)curclass.rateput);
66279968Sobrien				}
66379968Sobrien			}
66479968Sobrien			free($6);
66579968Sobrien		}
66679968Sobrien
66779968Sobrien	| SITE SP UMASK check_login CRLF
66879968Sobrien		{
66979968Sobrien			int oldmask;
67079968Sobrien
67179968Sobrien			if ($4) {
67279968Sobrien				oldmask = umask(0);
67379968Sobrien				(void) umask(oldmask);
67479968Sobrien				reply(200, "Current UMASK is %03o", oldmask);
67579968Sobrien			}
67679968Sobrien		}
67779968Sobrien
67879968Sobrien	| SITE SP UMASK check_login SP octal_number CRLF
67979968Sobrien		{
68079968Sobrien			int oldmask;
68179968Sobrien
682108746Sobrien			if ($4 && check_write("", 0)) {
68379968Sobrien				if (($6 == -1) || ($6 > 0777)) {
68479968Sobrien					reply(501, "Bad UMASK value");
68579968Sobrien				} else {
68679968Sobrien					oldmask = umask($6);
68779968Sobrien					reply(200,
68879968Sobrien					    "UMASK set to %03o (was %03o)",
68979968Sobrien					    $6, oldmask);
69079968Sobrien				}
69179968Sobrien			}
69279968Sobrien		}
69379968Sobrien
69479968Sobrien	| SYST CRLF
69579968Sobrien		{
69679968Sobrien			if (EMPTYSTR(version))
69779968Sobrien				reply(215, "UNIX Type: L%d", NBBY);
69879968Sobrien			else
69979968Sobrien				reply(215, "UNIX Type: L%d Version: %s", NBBY,
70079968Sobrien				    version);
70179968Sobrien		}
70279968Sobrien
70379968Sobrien	| STAT check_login SP pathname CRLF
70479968Sobrien		{
70579968Sobrien			if ($2 && $4 != NULL)
70679968Sobrien				statfilecmd($4);
70779968Sobrien			if ($4 != NULL)
70879968Sobrien				free($4);
70979968Sobrien		}
71079968Sobrien
71179968Sobrien	| STAT CRLF
71279968Sobrien		{
71379968Sobrien			if (is_oob)
71479968Sobrien				statxfer();
71579968Sobrien			else
71679968Sobrien				statcmd();
71779968Sobrien		}
71879968Sobrien
71979968Sobrien	| HELP CRLF
72079968Sobrien		{
72179968Sobrien			help(cmdtab, NULL);
72279968Sobrien		}
72379968Sobrien
72479968Sobrien	| HELP SP STRING CRLF
72579968Sobrien		{
72679968Sobrien			char *cp = $3;
72779968Sobrien
72879968Sobrien			if (strncasecmp(cp, "SITE", 4) == 0) {
72979968Sobrien				cp = $3 + 4;
73079968Sobrien				if (*cp == ' ')
73179968Sobrien					cp++;
73279968Sobrien				if (*cp)
73379968Sobrien					help(sitetab, cp);
73479968Sobrien				else
73579968Sobrien					help(sitetab, NULL);
73679968Sobrien			} else
73779968Sobrien				help(cmdtab, $3);
73879968Sobrien			free($3);
73979968Sobrien		}
74079968Sobrien
74179968Sobrien	| NOOP CRLF
74279968Sobrien		{
74379968Sobrien			reply(200, "NOOP command successful.");
74479968Sobrien		}
74579968Sobrien
74679968Sobrien						/* RFC 2228 */
74779968Sobrien	| AUTH SP mechanism_name CRLF
74879968Sobrien		{
74979968Sobrien			reply(502, "RFC 2228 authentication not implemented.");
75079968Sobrien			free($3);
75179968Sobrien		}
75279968Sobrien
75379968Sobrien	| ADAT SP base64data CRLF
75479968Sobrien		{
75579968Sobrien			reply(503,
75679968Sobrien			    "Please set authentication state with AUTH.");
75779968Sobrien			free($3);
75879968Sobrien		}
75979968Sobrien
76079968Sobrien	| PROT SP prot_code CRLF
76179968Sobrien		{
76279968Sobrien			reply(503,
76379968Sobrien			    "Please set protection buffer size with PBSZ.");
76479968Sobrien			free($3);
76579968Sobrien		}
76679968Sobrien
76779968Sobrien	| PBSZ SP decimal_integer CRLF
76879968Sobrien		{
76979968Sobrien			reply(503,
77079968Sobrien			    "Please set authentication state with AUTH.");
77179968Sobrien		}
77279968Sobrien
77379968Sobrien	| CCC CRLF
77479968Sobrien		{
77579968Sobrien			reply(533, "No protection enabled.");
77679968Sobrien		}
77779968Sobrien
77879968Sobrien	| MIC SP base64data CRLF
77979968Sobrien		{
78079968Sobrien			reply(502, "RFC 2228 authentication not implemented.");
78179968Sobrien			free($3);
78279968Sobrien		}
78379968Sobrien
78479968Sobrien	| CONF SP base64data CRLF
78579968Sobrien		{
78679968Sobrien			reply(502, "RFC 2228 authentication not implemented.");
78779968Sobrien			free($3);
78879968Sobrien		}
78979968Sobrien
79079968Sobrien	| ENC SP base64data CRLF
79179968Sobrien		{
79279968Sobrien			reply(502, "RFC 2228 authentication not implemented.");
79379968Sobrien			free($3);
79479968Sobrien		}
79579968Sobrien
79679968Sobrien						/* RFC 2389 */
79779968Sobrien	| FEAT CRLF
79879968Sobrien		{
79979968Sobrien
80079968Sobrien			feat();
80179968Sobrien		}
80279968Sobrien
80379968Sobrien	| OPTS SP STRING CRLF
80479968Sobrien		{
80579968Sobrien
80679968Sobrien			opts($3);
80779968Sobrien			free($3);
80879968Sobrien		}
80979968Sobrien
81079968Sobrien
81179968Sobrien				/* extensions from draft-ietf-ftpext-mlst-11 */
81279968Sobrien
81379968Sobrien		/*
81479968Sobrien		 * Return size of file in a format suitable for
81579968Sobrien		 * using with RESTART (we just count bytes).
81679968Sobrien		 */
81779968Sobrien	| SIZE check_login SP pathname CRLF
81879968Sobrien		{
81979968Sobrien			if ($2 && $4 != NULL)
82079968Sobrien				sizecmd($4);
82179968Sobrien			if ($4 != NULL)
82279968Sobrien				free($4);
82379968Sobrien		}
82479968Sobrien
82579968Sobrien		/*
82679968Sobrien		 * Return modification time of file as an ISO 3307
82779968Sobrien		 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
82879968Sobrien		 * where xxx is the fractional second (of any precision,
82979968Sobrien		 * not necessarily 3 digits)
83079968Sobrien		 */
83179968Sobrien	| MDTM check_login SP pathname CRLF
83279968Sobrien		{
83379968Sobrien			if ($2 && $4 != NULL) {
83479968Sobrien				struct stat stbuf;
83579968Sobrien				if (stat($4, &stbuf) < 0)
83679968Sobrien					perror_reply(550, $4);
83779968Sobrien				else if (!S_ISREG(stbuf.st_mode)) {
83879968Sobrien					reply(550, "%s: not a plain file.", $4);
83979968Sobrien				} else {
84079968Sobrien					struct tm *t;
84179968Sobrien
84279968Sobrien					t = gmtime(&stbuf.st_mtime);
84379968Sobrien					reply(213,
84479968Sobrien					    "%04d%02d%02d%02d%02d%02d",
84579968Sobrien					    TM_YEAR_BASE + t->tm_year,
84679968Sobrien					    t->tm_mon+1, t->tm_mday,
84779968Sobrien					    t->tm_hour, t->tm_min, t->tm_sec);
84879968Sobrien				}
84979968Sobrien			}
85079968Sobrien			if ($4 != NULL)
85179968Sobrien				free($4);
85279968Sobrien		}
85379968Sobrien
85479968Sobrien	| MLST check_login SP pathname CRLF
85579968Sobrien		{
85679968Sobrien			if ($2 && $4 != NULL)
85779968Sobrien				mlst($4);
85879968Sobrien			if ($4 != NULL)
85979968Sobrien				free($4);
86079968Sobrien		}
86179968Sobrien
86279968Sobrien	| MLST check_login CRLF
86379968Sobrien		{
86479968Sobrien			mlst(NULL);
86579968Sobrien		}
86679968Sobrien
86779968Sobrien	| MLSD check_login SP pathname CRLF
86879968Sobrien		{
86979968Sobrien			if ($2 && $4 != NULL)
87079968Sobrien				mlsd($4);
87179968Sobrien			if ($4 != NULL)
87279968Sobrien				free($4);
87379968Sobrien		}
87479968Sobrien
87579968Sobrien	| MLSD check_login CRLF
87679968Sobrien		{
87779968Sobrien			mlsd(NULL);
87879968Sobrien		}
87979968Sobrien
88079968Sobrien	| error CRLF
88179968Sobrien		{
88279968Sobrien			yyerrok;
88379968Sobrien		}
88479968Sobrien	;
88579968Sobrien
88679968Sobrienrcmd
887108746Sobrien	: REST check_login SP NUMBER CRLF
88879968Sobrien		{
88979968Sobrien			if ($2) {
890133936Sobrien				REASSIGN(fromname, NULL);
891133936Sobrien				restart_point = (off_t)$4.ll;
89279968Sobrien				reply(350,
89379968Sobrien    "Restarting at " LLF ". Send STORE or RETRIEVE to initiate transfer.",
89479968Sobrien				    (LLT)restart_point);
89579968Sobrien			}
89679968Sobrien		}
89779968Sobrien
89879968Sobrien	| RNFR SP pathname CRLF
89979968Sobrien		{
90079968Sobrien			restart_point = (off_t) 0;
901133936Sobrien			if (check_write($3, 0)) {
902133936Sobrien				REASSIGN(fromname, NULL);
90379968Sobrien				fromname = renamefrom($3);
904133936Sobrien			}
90579968Sobrien			if ($3 != NULL)
90679968Sobrien				free($3);
90779968Sobrien		}
90879968Sobrien	;
90979968Sobrien
91079968Sobrienusername
91179968Sobrien	: STRING
91279968Sobrien	;
91379968Sobrien
91479968Sobrienpassword
91579968Sobrien	: /* empty */
91679968Sobrien		{
91779968Sobrien			$$ = (char *)calloc(1, sizeof(char));
91879968Sobrien		}
91979968Sobrien
92079968Sobrien	| STRING
92179968Sobrien	;
92279968Sobrien
92379968Sobrienbyte_size
92479968Sobrien	: NUMBER
925108746Sobrien		{
926108746Sobrien			$$ = $1.i;
927108746Sobrien		}
92879968Sobrien	;
92979968Sobrien
93079968Sobrienhost_port
93179968Sobrien	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
93279968Sobrien		NUMBER COMMA NUMBER
93379968Sobrien		{
93479968Sobrien			char *a, *p;
93579968Sobrien
93679968Sobrien			memset(&data_dest, 0, sizeof(data_dest));
93779968Sobrien			data_dest.su_len = sizeof(struct sockaddr_in);
93879968Sobrien			data_dest.su_family = AF_INET;
93979968Sobrien			p = (char *)&data_dest.su_port;
940108746Sobrien			p[0] = $9.i; p[1] = $11.i;
94179968Sobrien			a = (char *)&data_dest.su_addr;
942108746Sobrien			a[0] = $1.i; a[1] = $3.i; a[2] = $5.i; a[3] = $7.i;
94379968Sobrien		}
94479968Sobrien	;
94579968Sobrien
94679968Sobrienhost_long_port4
94779968Sobrien	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
94879968Sobrien		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
94979968Sobrien		NUMBER
95079968Sobrien		{
95179968Sobrien			char *a, *p;
95279968Sobrien
95379968Sobrien			memset(&data_dest, 0, sizeof(data_dest));
95479968Sobrien			data_dest.su_len = sizeof(struct sockaddr_in);
95579968Sobrien			data_dest.su_family = AF_INET;
95679968Sobrien			p = (char *)&data_dest.su_port;
957108746Sobrien			p[0] = $15.i; p[1] = $17.i;
95879968Sobrien			a = (char *)&data_dest.su_addr;
959108746Sobrien			a[0] = $5.i; a[1] = $7.i; a[2] = $9.i; a[3] = $11.i;
96079968Sobrien
96179968Sobrien			/* reject invalid LPRT command */
962108746Sobrien			if ($1.i != 4 || $3.i != 4 || $13.i != 2)
96379968Sobrien				memset(&data_dest, 0, sizeof(data_dest));
96479968Sobrien		}
96579968Sobrien	;
96679968Sobrien
96779968Sobrienhost_long_port6
96879968Sobrien	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
96979968Sobrien		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
97079968Sobrien		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
97179968Sobrien		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
97279968Sobrien		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
97379968Sobrien		NUMBER
97479968Sobrien		{
97579968Sobrien#ifdef INET6
97679968Sobrien			char *a, *p;
97779968Sobrien
97879968Sobrien			memset(&data_dest, 0, sizeof(data_dest));
97979968Sobrien			data_dest.su_len = sizeof(struct sockaddr_in6);
98079968Sobrien			data_dest.su_family = AF_INET6;
98179968Sobrien			p = (char *)&data_dest.su_port;
982108746Sobrien			p[0] = $39.i; p[1] = $41.i;
98379968Sobrien			a = (char *)&data_dest.si_su.su_sin6.sin6_addr;
984108746Sobrien			a[0] = $5.i; a[1] = $7.i; a[2] = $9.i; a[3] = $11.i;
985108746Sobrien			a[4] = $13.i; a[5] = $15.i; a[6] = $17.i; a[7] = $19.i;
986108746Sobrien			a[8] = $21.i; a[9] = $23.i; a[10] = $25.i; a[11] = $27.i;
987108746Sobrien			a[12] = $29.i; a[13] = $31.i; a[14] = $33.i; a[15] = $35.i;
98879968Sobrien			if (his_addr.su_family == AF_INET6) {
98979968Sobrien				/* XXX: more sanity checks! */
99079968Sobrien				data_dest.su_scope_id = his_addr.su_scope_id;
99179968Sobrien			}
99279968Sobrien#else
99379968Sobrien			memset(&data_dest, 0, sizeof(data_dest));
99479968Sobrien#endif /* INET6 */
99579968Sobrien			/* reject invalid LPRT command */
996133936Sobrien			if ($1.i != 6 || $3.i != 16 || $37.i != 2)
99779968Sobrien				memset(&data_dest, 0, sizeof(data_dest));
99879968Sobrien		}
99979968Sobrien	;
100079968Sobrien
100179968Sobrienform_code
100279968Sobrien	: N
100379968Sobrien		{
100479968Sobrien			$$ = FORM_N;
100579968Sobrien		}
100679968Sobrien
100779968Sobrien	| T
100879968Sobrien		{
100979968Sobrien			$$ = FORM_T;
101079968Sobrien		}
101179968Sobrien
101279968Sobrien	| C
101379968Sobrien		{
101479968Sobrien			$$ = FORM_C;
101579968Sobrien		}
101679968Sobrien	;
101779968Sobrien
101879968Sobrientype_code
101979968Sobrien	: A
102079968Sobrien		{
102179968Sobrien			cmd_type = TYPE_A;
102279968Sobrien			cmd_form = FORM_N;
102379968Sobrien		}
102479968Sobrien
102579968Sobrien	| A SP form_code
102679968Sobrien		{
102779968Sobrien			cmd_type = TYPE_A;
102879968Sobrien			cmd_form = $3;
102979968Sobrien		}
103079968Sobrien
103179968Sobrien	| E
103279968Sobrien		{
103379968Sobrien			cmd_type = TYPE_E;
103479968Sobrien			cmd_form = FORM_N;
103579968Sobrien		}
103679968Sobrien
103779968Sobrien	| E SP form_code
103879968Sobrien		{
103979968Sobrien			cmd_type = TYPE_E;
104079968Sobrien			cmd_form = $3;
104179968Sobrien		}
104279968Sobrien
104379968Sobrien	| I
104479968Sobrien		{
104579968Sobrien			cmd_type = TYPE_I;
104679968Sobrien		}
104779968Sobrien
104879968Sobrien	| L
104979968Sobrien		{
105079968Sobrien			cmd_type = TYPE_L;
105179968Sobrien			cmd_bytesz = NBBY;
105279968Sobrien		}
105379968Sobrien
105479968Sobrien	| L SP byte_size
105579968Sobrien		{
105679968Sobrien			cmd_type = TYPE_L;
105779968Sobrien			cmd_bytesz = $3;
105879968Sobrien		}
105979968Sobrien
106079968Sobrien		/* this is for a bug in the BBN ftp */
106179968Sobrien	| L byte_size
106279968Sobrien		{
106379968Sobrien			cmd_type = TYPE_L;
106479968Sobrien			cmd_bytesz = $2;
106579968Sobrien		}
106679968Sobrien	;
106779968Sobrien
106879968Sobrienstruct_code
106979968Sobrien	: F
107079968Sobrien		{
107179968Sobrien			$$ = STRU_F;
107279968Sobrien		}
107379968Sobrien
107479968Sobrien	| R
107579968Sobrien		{
107679968Sobrien			$$ = STRU_R;
107779968Sobrien		}
107879968Sobrien
107979968Sobrien	| P
108079968Sobrien		{
108179968Sobrien			$$ = STRU_P;
108279968Sobrien		}
108379968Sobrien	;
108479968Sobrien
108579968Sobrienmode_code
108679968Sobrien	: S
108779968Sobrien		{
108879968Sobrien			$$ = MODE_S;
108979968Sobrien		}
109079968Sobrien
109179968Sobrien	| B
109279968Sobrien		{
109379968Sobrien			$$ = MODE_B;
109479968Sobrien		}
109579968Sobrien
109679968Sobrien	| C
109779968Sobrien		{
109879968Sobrien			$$ = MODE_C;
109979968Sobrien		}
110079968Sobrien	;
110179968Sobrien
110279968Sobrienpathname
110379968Sobrien	: pathstring
110479968Sobrien		{
110579968Sobrien			/*
110679968Sobrien			 * Problem: this production is used for all pathname
110779968Sobrien			 * processing, but only gives a 550 error reply.
110879968Sobrien			 * This is a valid reply in some cases but not in
110979968Sobrien			 * others.
111079968Sobrien			 */
111179968Sobrien			if (logged_in && $1 && *$1 == '~') {
111279968Sobrien				char	*path, *home, *result;
111379968Sobrien				size_t	len;
111479968Sobrien
111579968Sobrien				path = strchr($1 + 1, '/');
111679968Sobrien				if (path != NULL)
111779968Sobrien					*path++ = '\0';
111879968Sobrien				if ($1[1] == '\0')
111979968Sobrien					home = homedir;
112079968Sobrien				else {
112192282Sobrien					struct passwd	*hpw;
112279968Sobrien
112392282Sobrien					if ((hpw = getpwnam($1 + 1)) != NULL)
112492282Sobrien						home = hpw->pw_dir;
112579968Sobrien					else
112679968Sobrien						home = $1;
112779968Sobrien				}
112879968Sobrien				len = strlen(home) + 1;
112979968Sobrien				if (path != NULL)
113079968Sobrien					len += strlen(path) + 1;
113179968Sobrien				if ((result = malloc(len)) == NULL)
113279968Sobrien					fatal("Local resource failure: malloc");
113379968Sobrien				strlcpy(result, home, len);
113479968Sobrien				if (path != NULL) {
113579968Sobrien					strlcat(result, "/", len);
113679968Sobrien					strlcat(result, path, len);
113779968Sobrien				}
113879968Sobrien				$$ = result;
113979968Sobrien				free($1);
114079968Sobrien			} else
114179968Sobrien				$$ = $1;
114279968Sobrien		}
114379968Sobrien	;
114479968Sobrien
114579968Sobrienpathstring
114679968Sobrien	: STRING
114779968Sobrien	;
114879968Sobrien
114979968Sobrienoctal_number
115079968Sobrien	: NUMBER
115179968Sobrien		{
115279968Sobrien			int ret, dec, multby, digit;
115379968Sobrien
115479968Sobrien			/*
115579968Sobrien			 * Convert a number that was read as decimal number
115679968Sobrien			 * to what it would be if it had been read as octal.
115779968Sobrien			 */
1158108746Sobrien			dec = $1.i;
115979968Sobrien			multby = 1;
116079968Sobrien			ret = 0;
116179968Sobrien			while (dec) {
116279968Sobrien				digit = dec%10;
116379968Sobrien				if (digit > 7) {
116479968Sobrien					ret = -1;
116579968Sobrien					break;
116679968Sobrien				}
116779968Sobrien				ret += digit * multby;
116879968Sobrien				multby *= 8;
116979968Sobrien				dec /= 10;
117079968Sobrien			}
117179968Sobrien			$$ = ret;
117279968Sobrien		}
117379968Sobrien	;
117479968Sobrien
117579968Sobrienmechanism_name
117679968Sobrien	: STRING
117779968Sobrien	;
117879968Sobrien
117979968Sobrienbase64data
118079968Sobrien	: STRING
118179968Sobrien	;
118279968Sobrien
118379968Sobrienprot_code
118479968Sobrien	: STRING
118579968Sobrien	;
118679968Sobrien
118779968Sobriendecimal_integer
118879968Sobrien	: NUMBER
1189108746Sobrien		{
1190108746Sobrien			$$ = $1.i;
1191108746Sobrien		}
119279968Sobrien	;
119379968Sobrien
119479968Sobriencheck_login
119579968Sobrien	: /* empty */
119679968Sobrien		{
119779968Sobrien			if (logged_in)
119879968Sobrien				$$ = 1;
119979968Sobrien			else {
120079968Sobrien				reply(530, "Please login with USER and PASS.");
120179968Sobrien				$$ = 0;
120279968Sobrien				hasyyerrored = 1;
120379968Sobrien			}
120479968Sobrien		}
120579968Sobrien	;
120679968Sobrien
120779968Sobrien%%
120879968Sobrien
120979968Sobrien#define	CMD	0	/* beginning of command */
121079968Sobrien#define	ARGS	1	/* expect miscellaneous arguments */
121179968Sobrien#define	STR1	2	/* expect SP followed by STRING */
121279968Sobrien#define	STR2	3	/* expect STRING */
121379968Sobrien#define	OSTR	4	/* optional SP then STRING */
121479968Sobrien#define	ZSTR1	5	/* SP then optional STRING */
121579968Sobrien#define	ZSTR2	6	/* optional STRING after SP */
121679968Sobrien#define	SITECMD	7	/* SITE command */
121779968Sobrien#define	NSTR	8	/* Number followed by a string */
121879968Sobrien#define NOARGS	9	/* No arguments allowed */
121979968Sobrien#define EOLN	10	/* End of line */
122079968Sobrien
122179968Sobrienstruct tab cmdtab[] = {
122279968Sobrien				/* From RFC 959, in order defined (5.3.1) */
122379968Sobrien	{ "USER", USER, STR1,	1,	"<sp> username" },
122479968Sobrien	{ "PASS", PASS, ZSTR1,	1,	"<sp> password" },
122579968Sobrien	{ "ACCT", ACCT, STR1,	0,	"(specify account)" },
122679968Sobrien	{ "CWD",  CWD,  OSTR,	1,	"[ <sp> directory-name ]" },
122779968Sobrien	{ "CDUP", CDUP, NOARGS,	1,	"(change to parent directory)" },
122879968Sobrien	{ "SMNT", SMNT, ARGS,	0,	"(structure mount)" },
122979968Sobrien	{ "QUIT", QUIT, NOARGS,	1,	"(terminate service)" },
123079968Sobrien	{ "REIN", REIN, NOARGS,	0,	"(reinitialize server state)" },
1231110242Sobrien	{ "PORT", PORT, ARGS,	1,	"<sp> b0, b1, b2, b3, b4, b5" },
123279968Sobrien	{ "LPRT", LPRT, ARGS,	1,	"<sp> af, hal, h1, h2, h3,..., pal, p1, p2..." },
123379968Sobrien	{ "EPRT", EPRT, STR1,	1,	"<sp> |af|addr|port|" },
123479968Sobrien	{ "PASV", PASV, NOARGS,	1,	"(set server in passive mode)" },
123579968Sobrien	{ "LPSV", LPSV, ARGS,	1,	"(set server in passive mode)" },
123679968Sobrien	{ "EPSV", EPSV, ARGS,	1,	"[<sp> af|ALL]" },
123779968Sobrien	{ "TYPE", TYPE, ARGS,	1,	"<sp> [ A | E | I | L ]" },
123879968Sobrien	{ "STRU", STRU, ARGS,	1,	"(specify file structure)" },
123979968Sobrien	{ "MODE", MODE, ARGS,	1,	"(specify transfer mode)" },
124079968Sobrien	{ "RETR", RETR, STR1,	1,	"<sp> file-name" },
124179968Sobrien	{ "STOR", STOR, STR1,	1,	"<sp> file-name" },
124279968Sobrien	{ "STOU", STOU, STR1,	1,	"<sp> file-name" },
124379968Sobrien	{ "APPE", APPE, STR1,	1,	"<sp> file-name" },
124479968Sobrien	{ "ALLO", ALLO, ARGS,	1,	"allocate storage (vacuously)" },
124579968Sobrien	{ "REST", REST, ARGS,	1,	"<sp> offset (restart command)" },
124679968Sobrien	{ "RNFR", RNFR, STR1,	1,	"<sp> file-name" },
124779968Sobrien	{ "RNTO", RNTO, STR1,	1,	"<sp> file-name" },
124879968Sobrien	{ "ABOR", ABOR, NOARGS,	4,	"(abort operation)" },
124979968Sobrien	{ "DELE", DELE, STR1,	1,	"<sp> file-name" },
125079968Sobrien	{ "RMD",  RMD,  STR1,	1,	"<sp> path-name" },
125179968Sobrien	{ "MKD",  MKD,  STR1,	1,	"<sp> path-name" },
125279968Sobrien	{ "PWD",  PWD,  NOARGS,	1,	"(return current directory)" },
125379968Sobrien	{ "LIST", LIST, OSTR,	1,	"[ <sp> path-name ]" },
125479968Sobrien	{ "NLST", NLST, OSTR,	1,	"[ <sp> path-name ]" },
125579968Sobrien	{ "SITE", SITE, SITECMD, 1,	"site-cmd [ <sp> arguments ]" },
125679968Sobrien	{ "SYST", SYST, NOARGS,	1,	"(get type of operating system)" },
125779968Sobrien	{ "STAT", STAT, OSTR,	4,	"[ <sp> path-name ]" },
125879968Sobrien	{ "HELP", HELP, OSTR,	1,	"[ <sp> <string> ]" },
125979968Sobrien	{ "NOOP", NOOP, NOARGS,	2,	"" },
126079968Sobrien
126179968Sobrien				/* From RFC 2228, in order defined */
126279968Sobrien	{ "AUTH", AUTH, STR1,	1,	"<sp> mechanism-name" },
126379968Sobrien	{ "ADAT", ADAT, STR1,	1,	"<sp> base-64-data" },
126479968Sobrien	{ "PROT", PROT, STR1,	1,	"<sp> prot-code" },
126579968Sobrien	{ "PBSZ", PBSZ, ARGS,	1,	"<sp> decimal-integer" },
126679968Sobrien	{ "CCC",  CCC,  NOARGS,	1,	"(Disable data protection)" },
126779968Sobrien	{ "MIC",  MIC,  STR1,	4,	"<sp> base64data" },
126879968Sobrien	{ "CONF", CONF, STR1,	4,	"<sp> base64data" },
126979968Sobrien	{ "ENC",  ENC,  STR1,	4,	"<sp> base64data" },
127079968Sobrien
127179968Sobrien				/* From RFC 2389, in order defined */
127279968Sobrien	{ "FEAT", FEAT, NOARGS,	1,	"(display extended features)" },
127379968Sobrien	{ "OPTS", OPTS, STR1,	1,	"<sp> command [ <sp> options ]" },
127479968Sobrien
127579968Sobrien				/* from draft-ietf-ftpext-mlst-11 */
127679968Sobrien	{ "MDTM", MDTM, OSTR,	1,	"<sp> path-name" },
127779968Sobrien	{ "SIZE", SIZE, OSTR,	1,	"<sp> path-name" },
127879968Sobrien	{ "MLST", MLST, OSTR,	2,	"[ <sp> path-name ]" },
127979968Sobrien	{ "MLSD", MLSD, OSTR,	1,	"[ <sp> directory-name ]" },
128079968Sobrien
128179968Sobrien				/* obsolete commands */
128279968Sobrien	{ "MAIL", MAIL, OSTR,	0,	"(mail to user)" },
128379968Sobrien	{ "MLFL", MLFL, OSTR,	0,	"(mail file)" },
128479968Sobrien	{ "MRCP", MRCP, STR1,	0,	"(mail recipient)" },
128579968Sobrien	{ "MRSQ", MRSQ, OSTR,	0,	"(mail recipient scheme question)" },
128679968Sobrien	{ "MSAM", MSAM, OSTR,	0,	"(mail send to terminal and mailbox)" },
128779968Sobrien	{ "MSND", MSND, OSTR,	0,	"(mail send to terminal)" },
128879968Sobrien	{ "MSOM", MSOM, OSTR,	0,	"(mail send to terminal or mailbox)" },
128979968Sobrien	{ "XCUP", CDUP, NOARGS,	1,	"(change to parent directory)" },
129079968Sobrien	{ "XCWD", CWD,  OSTR,	1,	"[ <sp> directory-name ]" },
129179968Sobrien	{ "XMKD", MKD,  STR1,	1,	"<sp> path-name" },
129279968Sobrien	{ "XPWD", PWD,  NOARGS,	1,	"(return current directory)" },
129379968Sobrien	{ "XRMD", RMD,  STR1,	1,	"<sp> path-name" },
129479968Sobrien
129579968Sobrien	{  NULL,  0,	0,	0,	0 }
129679968Sobrien};
129779968Sobrien
129879968Sobrienstruct tab sitetab[] = {
1299108746Sobrien	{ "CHMOD",	CHMOD,	NSTR,	1,	"<sp> mode <sp> file-name" },
1300108746Sobrien	{ "HELP",	HELP,	OSTR,	1,	"[ <sp> <string> ]" },
1301108746Sobrien	{ "IDLE",	IDLE,	ARGS,	1,	"[ <sp> maximum-idle-time ]" },
1302108746Sobrien	{ "RATEGET",	RATEGET,OSTR,	1,	"[ <sp> get-throttle-rate ]" },
1303108746Sobrien	{ "RATEPUT",	RATEPUT,OSTR,	1,	"[ <sp> put-throttle-rate ]" },
1304108746Sobrien	{ "UMASK",	UMASK,	ARGS,	1,	"[ <sp> umask ]" },
1305108746Sobrien	{ NULL,		0,	0,	0,	NULL }
130679968Sobrien};
130779968Sobrien
130879968Sobrien/*
130979968Sobrien * Check if a filename is allowed to be modified (isupload == 0) or
131079968Sobrien * uploaded (isupload == 1), and if necessary, check the filename is `sane'.
1311108746Sobrien * If the filename is NULL, fail.
1312108746Sobrien * If the filename is "", don't do the sane name check.
131379968Sobrien */
131479968Sobrienstatic int
131579968Sobriencheck_write(const char *file, int isupload)
131679968Sobrien{
131779968Sobrien	if (file == NULL)
131879968Sobrien		return (0);
131979968Sobrien	if (! logged_in) {
132079968Sobrien		reply(530, "Please login with USER and PASS.");
132179968Sobrien		return (0);
132279968Sobrien	}
132379968Sobrien		/* checking modify */
132479968Sobrien	if (! isupload && ! CURCLASS_FLAGS_ISSET(modify)) {
132579968Sobrien		reply(502, "No permission to use this command.");
132679968Sobrien		return (0);
132779968Sobrien	}
132879968Sobrien		/* checking upload */
132979968Sobrien	if (isupload && ! CURCLASS_FLAGS_ISSET(upload)) {
133079968Sobrien		reply(502, "No permission to use this command.");
133179968Sobrien		return (0);
133279968Sobrien	}
1333108746Sobrien
133479968Sobrien		/* checking sanenames */
1335108746Sobrien	if (file[0] != '\0' && CURCLASS_FLAGS_ISSET(sanenames)) {
133679968Sobrien		const char *p;
133779968Sobrien
133879968Sobrien		if (file[0] == '.')
133979968Sobrien			goto insane_name;
134079968Sobrien		for (p = file; *p; p++) {
1341161764Sobrien			if (isalnum((unsigned char)*p) || *p == '-' || *p == '+' ||
134279968Sobrien			    *p == ',' || *p == '.' || *p == '_')
134379968Sobrien				continue;
134479968Sobrien insane_name:
134579968Sobrien			reply(553, "File name `%s' not allowed.", file);
134679968Sobrien			return (0);
134779968Sobrien		}
134879968Sobrien	}
134979968Sobrien	return (1);
135079968Sobrien}
135179968Sobrien
135279968Sobrienstruct tab *
135379968Sobrienlookup(struct tab *p, const char *cmd)
135479968Sobrien{
135579968Sobrien
135679968Sobrien	for (; p->name != NULL; p++)
135779968Sobrien		if (strcasecmp(cmd, p->name) == 0)
135879968Sobrien			return (p);
135979968Sobrien	return (0);
136079968Sobrien}
136179968Sobrien
136279968Sobrien#include <arpa/telnet.h>
136379968Sobrien
136479968Sobrien/*
136579968Sobrien * getline - a hacked up version of fgets to ignore TELNET escape codes.
1366186872Ssimon *	`s' is the buffer to read into.
1367186872Ssimon *	`n' is the 1 less than the size of the buffer, to allow trailing NUL
1368186872Ssimon *	`iop' is the FILE to read from.
1369186872Ssimon *	Returns 0 on success, -1 on EOF, -2 if the command was too long.
137079968Sobrien */
1371186872Ssimonint
137279968Sobriengetline(char *s, int n, FILE *iop)
137379968Sobrien{
137479968Sobrien	int c;
137579968Sobrien	char *cs;
137679968Sobrien
137779968Sobrien	cs = s;
137879968Sobrien/* tmpline may contain saved command from urgent mode interruption */
137979968Sobrien	for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
138079968Sobrien		*cs++ = tmpline[c];
138179968Sobrien		if (tmpline[c] == '\n') {
138279968Sobrien			*cs++ = '\0';
1383161764Sobrien			if (ftpd_debug)
138479968Sobrien				syslog(LOG_DEBUG, "command: %s", s);
138579968Sobrien			tmpline[0] = '\0';
1386186872Ssimon			return(0);
138779968Sobrien		}
138879968Sobrien		if (c == 0)
138979968Sobrien			tmpline[0] = '\0';
139079968Sobrien	}
139179968Sobrien	while ((c = getc(iop)) != EOF) {
139279968Sobrien		total_bytes++;
139379968Sobrien		total_bytes_in++;
139479968Sobrien		c &= 0377;
139579968Sobrien		if (c == IAC) {
139679968Sobrien		    if ((c = getc(iop)) != EOF) {
139779968Sobrien			total_bytes++;
139879968Sobrien			total_bytes_in++;
139979968Sobrien			c &= 0377;
140079968Sobrien			switch (c) {
140179968Sobrien			case WILL:
140279968Sobrien			case WONT:
140379968Sobrien				c = getc(iop);
140479968Sobrien				total_bytes++;
140579968Sobrien				total_bytes_in++;
140679968Sobrien				cprintf(stdout, "%c%c%c", IAC, DONT, 0377&c);
140779968Sobrien				(void) fflush(stdout);
140879968Sobrien				continue;
140979968Sobrien			case DO:
141079968Sobrien			case DONT:
141179968Sobrien				c = getc(iop);
141279968Sobrien				total_bytes++;
141379968Sobrien				total_bytes_in++;
141479968Sobrien				cprintf(stdout, "%c%c%c", IAC, WONT, 0377&c);
141579968Sobrien				(void) fflush(stdout);
141679968Sobrien				continue;
141779968Sobrien			case IAC:
141879968Sobrien				break;
141979968Sobrien			default:
142079968Sobrien				continue;	/* ignore command */
142179968Sobrien			}
142279968Sobrien		    }
142379968Sobrien		}
142479968Sobrien		*cs++ = c;
1425186872Ssimon		if (--n <= 0) {
1426186872Ssimon			/*
1427186872Ssimon			 * If command doesn't fit into buffer, discard the
1428186872Ssimon			 * rest of the command and indicate truncation.
1429186872Ssimon			 * This prevents the command to be split up into
1430186872Ssimon			 * multiple commands.
1431186872Ssimon			 */
1432186872Ssimon			if (ftpd_debug)
1433186872Ssimon				syslog(LOG_DEBUG,
1434186872Ssimon				    "command too long, last char: %d", c);
1435186872Ssimon			while (c != '\n' && (c = getc(iop)) != EOF)
1436186872Ssimon				continue;
1437186872Ssimon			return (-2);
1438186872Ssimon		}
1439186872Ssimon		if (c == '\n')
144079968Sobrien			break;
144179968Sobrien	}
144279968Sobrien	if (c == EOF && cs == s)
1443186872Ssimon		return (-1);
144479968Sobrien	*cs++ = '\0';
1445161764Sobrien	if (ftpd_debug) {
144679968Sobrien		if ((curclass.type != CLASS_GUEST &&
144779968Sobrien		    strncasecmp(s, "PASS ", 5) == 0) ||
144879968Sobrien		    strncasecmp(s, "ACCT ", 5) == 0) {
144979968Sobrien			/* Don't syslog passwords */
145079968Sobrien			syslog(LOG_DEBUG, "command: %.4s ???", s);
145179968Sobrien		} else {
145279968Sobrien			char *cp;
145379968Sobrien			int len;
145479968Sobrien
145579968Sobrien			/* Don't syslog trailing CR-LF */
145679968Sobrien			len = strlen(s);
145779968Sobrien			cp = s + len - 1;
145879968Sobrien			while (cp >= s && (*cp == '\n' || *cp == '\r')) {
145979968Sobrien				--cp;
146079968Sobrien				--len;
146179968Sobrien			}
146279968Sobrien			syslog(LOG_DEBUG, "command: %.*s", len, s);
146379968Sobrien		}
146479968Sobrien	}
1465186872Ssimon	return (0);
146679968Sobrien}
146779968Sobrien
146879968Sobrienvoid
146979968Sobrienftp_handle_line(char *cp)
147079968Sobrien{
147179968Sobrien
147279968Sobrien	cmdp = cp;
147379968Sobrien	yyparse();
147479968Sobrien}
147579968Sobrien
147679968Sobrienvoid
147779968Sobrienftp_loop(void)
147879968Sobrien{
1479186872Ssimon	int ret;
148079968Sobrien
148179968Sobrien	while (1) {
148279968Sobrien		(void) alarm(curclass.timeout);
1483186872Ssimon		ret = getline(cbuf, sizeof(cbuf)-1, stdin);
1484186872Ssimon		(void) alarm(0);
1485186872Ssimon		if (ret == -1) {
148679968Sobrien			reply(221, "You could at least say goodbye.");
148779968Sobrien			dologout(0);
1488186872Ssimon		} else if (ret == -2) {
1489186872Ssimon			reply(500, "Command too long.");
1490186872Ssimon		} else {
1491186872Ssimon			ftp_handle_line(cbuf);
149279968Sobrien		}
149379968Sobrien	}
149479968Sobrien	/*NOTREACHED*/
149579968Sobrien}
149679968Sobrien
1497133936Sobrienint
149879968Sobrienyylex(void)
149979968Sobrien{
150079968Sobrien	static int cpos, state;
150179968Sobrien	char *cp, *cp2;
150279968Sobrien	struct tab *p;
150379968Sobrien	int n;
150479968Sobrien	char c;
150579968Sobrien
150679968Sobrien	switch (state) {
150779968Sobrien
150879968Sobrien	case CMD:
150979968Sobrien		hasyyerrored = 0;
151079968Sobrien		if ((cp = strchr(cmdp, '\r'))) {
151179968Sobrien			*cp = '\0';
151279968Sobrien#if HAVE_SETPROCTITLE
151379968Sobrien			if (strncasecmp(cmdp, "PASS", 4) != 0 &&
151479968Sobrien			    strncasecmp(cmdp, "ACCT", 4) != 0)
151579968Sobrien				setproctitle("%s: %s", proctitle, cmdp);
151679968Sobrien#endif /* HAVE_SETPROCTITLE */
151779968Sobrien			*cp++ = '\n';
151879968Sobrien			*cp = '\0';
151979968Sobrien		}
152079968Sobrien		if ((cp = strpbrk(cmdp, " \n")))
152179968Sobrien			cpos = cp - cmdp;
152279968Sobrien		if (cpos == 0)
152379968Sobrien			cpos = 4;
152479968Sobrien		c = cmdp[cpos];
152579968Sobrien		cmdp[cpos] = '\0';
152679968Sobrien		p = lookup(cmdtab, cmdp);
152779968Sobrien		cmdp[cpos] = c;
152879968Sobrien		if (p != NULL) {
152979968Sobrien			if (is_oob && ! CMD_OOB(p)) {
153079968Sobrien				/* command will be handled in-band */
153179968Sobrien				return (0);
153279968Sobrien			} else if (! CMD_IMPLEMENTED(p)) {
153379968Sobrien				reply(502, "%s command not implemented.",
153479968Sobrien				    p->name);
153579968Sobrien				hasyyerrored = 1;
153679968Sobrien				break;
153779968Sobrien			}
153879968Sobrien			state = p->state;
153979968Sobrien			yylval.s = p->name;
154079968Sobrien			return (p->token);
154179968Sobrien		}
154279968Sobrien		break;
154379968Sobrien
154479968Sobrien	case SITECMD:
154579968Sobrien		if (cmdp[cpos] == ' ') {
154679968Sobrien			cpos++;
154779968Sobrien			return (SP);
154879968Sobrien		}
154979968Sobrien		cp = &cmdp[cpos];
155079968Sobrien		if ((cp2 = strpbrk(cp, " \n")))
155179968Sobrien			cpos = cp2 - cmdp;
155279968Sobrien		c = cmdp[cpos];
155379968Sobrien		cmdp[cpos] = '\0';
155479968Sobrien		p = lookup(sitetab, cp);
155579968Sobrien		cmdp[cpos] = c;
155679968Sobrien		if (p != NULL) {
155779968Sobrien			if (!CMD_IMPLEMENTED(p)) {
155879968Sobrien				reply(502, "SITE %s command not implemented.",
155979968Sobrien				    p->name);
156079968Sobrien				hasyyerrored = 1;
156179968Sobrien				break;
156279968Sobrien			}
156379968Sobrien			state = p->state;
156479968Sobrien			yylval.s = p->name;
156579968Sobrien			return (p->token);
156679968Sobrien		}
156779968Sobrien		break;
156879968Sobrien
156979968Sobrien	case OSTR:
157079968Sobrien		if (cmdp[cpos] == '\n') {
157179968Sobrien			state = EOLN;
157279968Sobrien			return (CRLF);
157379968Sobrien		}
157479968Sobrien		/* FALLTHROUGH */
157579968Sobrien
157679968Sobrien	case STR1:
157779968Sobrien	case ZSTR1:
157879968Sobrien	dostr1:
157979968Sobrien		if (cmdp[cpos] == ' ') {
158079968Sobrien			cpos++;
158179968Sobrien			state = state == OSTR ? STR2 : state+1;
158279968Sobrien			return (SP);
158379968Sobrien		}
158479968Sobrien		break;
158579968Sobrien
158679968Sobrien	case ZSTR2:
158779968Sobrien		if (cmdp[cpos] == '\n') {
158879968Sobrien			state = EOLN;
158979968Sobrien			return (CRLF);
159079968Sobrien		}
159179968Sobrien		/* FALLTHROUGH */
159279968Sobrien
159379968Sobrien	case STR2:
159479968Sobrien		cp = &cmdp[cpos];
159579968Sobrien		n = strlen(cp);
159679968Sobrien		cpos += n - 1;
159779968Sobrien		/*
159879968Sobrien		 * Make sure the string is nonempty and \n terminated.
159979968Sobrien		 */
160079968Sobrien		if (n > 1 && cmdp[cpos] == '\n') {
160179968Sobrien			cmdp[cpos] = '\0';
1602161764Sobrien			yylval.s = ftpd_strdup(cp);
160379968Sobrien			cmdp[cpos] = '\n';
160479968Sobrien			state = ARGS;
160579968Sobrien			return (STRING);
160679968Sobrien		}
160779968Sobrien		break;
160879968Sobrien
160979968Sobrien	case NSTR:
161079968Sobrien		if (cmdp[cpos] == ' ') {
161179968Sobrien			cpos++;
161279968Sobrien			return (SP);
161379968Sobrien		}
1614161764Sobrien		if (isdigit((unsigned char)cmdp[cpos])) {
161579968Sobrien			cp = &cmdp[cpos];
1616161764Sobrien			while (isdigit((unsigned char)cmdp[++cpos]))
161779968Sobrien				;
161879968Sobrien			c = cmdp[cpos];
161979968Sobrien			cmdp[cpos] = '\0';
1620108746Sobrien			yylval.u.i = atoi(cp);
162179968Sobrien			cmdp[cpos] = c;
162279968Sobrien			state = STR1;
162379968Sobrien			return (NUMBER);
162479968Sobrien		}
162579968Sobrien		state = STR1;
162679968Sobrien		goto dostr1;
162779968Sobrien
162879968Sobrien	case ARGS:
1629161764Sobrien		if (isdigit((unsigned char)cmdp[cpos])) {
163079968Sobrien			cp = &cmdp[cpos];
1631161764Sobrien			while (isdigit((unsigned char)cmdp[++cpos]))
163279968Sobrien				;
163379968Sobrien			c = cmdp[cpos];
163479968Sobrien			cmdp[cpos] = '\0';
1635108746Sobrien			yylval.u.i = atoi(cp);
1636133936Sobrien			yylval.u.ll = STRTOLL(cp, (char **)NULL, 10);
163779968Sobrien			cmdp[cpos] = c;
163879968Sobrien			return (NUMBER);
163979968Sobrien		}
164079968Sobrien		if (strncasecmp(&cmdp[cpos], "ALL", 3) == 0
1641161764Sobrien		    && !isalnum((unsigned char)cmdp[cpos + 3])) {
164279968Sobrien			cpos += 3;
1643133936Sobrien			return (ALL);
164479968Sobrien		}
164579968Sobrien		switch (cmdp[cpos++]) {
164679968Sobrien
164779968Sobrien		case '\n':
164879968Sobrien			state = EOLN;
164979968Sobrien			return (CRLF);
165079968Sobrien
165179968Sobrien		case ' ':
165279968Sobrien			return (SP);
165379968Sobrien
165479968Sobrien		case ',':
165579968Sobrien			return (COMMA);
165679968Sobrien
165779968Sobrien		case 'A':
165879968Sobrien		case 'a':
165979968Sobrien			return (A);
166079968Sobrien
166179968Sobrien		case 'B':
166279968Sobrien		case 'b':
166379968Sobrien			return (B);
166479968Sobrien
166579968Sobrien		case 'C':
166679968Sobrien		case 'c':
166779968Sobrien			return (C);
166879968Sobrien
166979968Sobrien		case 'E':
167079968Sobrien		case 'e':
167179968Sobrien			return (E);
167279968Sobrien
167379968Sobrien		case 'F':
167479968Sobrien		case 'f':
167579968Sobrien			return (F);
167679968Sobrien
167779968Sobrien		case 'I':
167879968Sobrien		case 'i':
167979968Sobrien			return (I);
168079968Sobrien
168179968Sobrien		case 'L':
168279968Sobrien		case 'l':
168379968Sobrien			return (L);
168479968Sobrien
168579968Sobrien		case 'N':
168679968Sobrien		case 'n':
168779968Sobrien			return (N);
168879968Sobrien
168979968Sobrien		case 'P':
169079968Sobrien		case 'p':
169179968Sobrien			return (P);
169279968Sobrien
169379968Sobrien		case 'R':
169479968Sobrien		case 'r':
169579968Sobrien			return (R);
169679968Sobrien
169779968Sobrien		case 'S':
169879968Sobrien		case 's':
169979968Sobrien			return (S);
170079968Sobrien
170179968Sobrien		case 'T':
170279968Sobrien		case 't':
170379968Sobrien			return (T);
170479968Sobrien
170579968Sobrien		}
170679968Sobrien		break;
170779968Sobrien
170879968Sobrien	case NOARGS:
170979968Sobrien		if (cmdp[cpos] == '\n') {
171079968Sobrien			state = EOLN;
171179968Sobrien			return (CRLF);
171279968Sobrien		}
171379968Sobrien		c = cmdp[cpos];
171479968Sobrien		cmdp[cpos] = '\0';
171579968Sobrien		reply(501, "'%s' command does not take any arguments.", cmdp);
171679968Sobrien		hasyyerrored = 1;
171779968Sobrien		cmdp[cpos] = c;
171879968Sobrien		break;
171979968Sobrien
172079968Sobrien	case EOLN:
172179968Sobrien		state = CMD;
172279968Sobrien		return (0);
172379968Sobrien
172479968Sobrien	default:
172579968Sobrien		fatal("Unknown state in scanner.");
172679968Sobrien	}
172779968Sobrien	yyerror(NULL);
172879968Sobrien	state = CMD;
1729133936Sobrien	return (0);
173079968Sobrien}
173179968Sobrien
173279968Sobrien/* ARGSUSED */
173379968Sobrienvoid
173479968Sobrienyyerror(char *s)
173579968Sobrien{
173679968Sobrien	char *cp;
173779968Sobrien
173879968Sobrien	if (hasyyerrored || is_oob)
173979968Sobrien		return;
174079968Sobrien	if ((cp = strchr(cmdp,'\n')) != NULL)
174179968Sobrien		*cp = '\0';
174279968Sobrien	reply(500, "'%s': command not understood.", cmdp);
174379968Sobrien	hasyyerrored = 1;
174479968Sobrien}
174579968Sobrien
174679968Sobrienstatic void
174779968Sobrienhelp(struct tab *ctab, const char *s)
174879968Sobrien{
174979968Sobrien	struct tab *c;
175079968Sobrien	int width, NCMDS;
175192282Sobrien	char *htype;
175279968Sobrien
175379968Sobrien	if (ctab == sitetab)
175492282Sobrien		htype = "SITE ";
175579968Sobrien	else
175692282Sobrien		htype = "";
175779968Sobrien	width = 0, NCMDS = 0;
175879968Sobrien	for (c = ctab; c->name != NULL; c++) {
175979968Sobrien		int len = strlen(c->name);
176079968Sobrien
176179968Sobrien		if (len > width)
176279968Sobrien			width = len;
176379968Sobrien		NCMDS++;
176479968Sobrien	}
176579968Sobrien	width = (width + 8) &~ 7;
176679968Sobrien	if (s == 0) {
176779968Sobrien		int i, j, w;
176879968Sobrien		int columns, lines;
176979968Sobrien
177079968Sobrien		reply(-214, "%s", "");
177192282Sobrien		reply(0, "The following %scommands are recognized.", htype);
177279968Sobrien		reply(0, "(`-' = not implemented, `+' = supports options)");
177379968Sobrien		columns = 76 / width;
177479968Sobrien		if (columns == 0)
177579968Sobrien			columns = 1;
177679968Sobrien		lines = (NCMDS + columns - 1) / columns;
177779968Sobrien		for (i = 0; i < lines; i++) {
177879968Sobrien			cprintf(stdout, "    ");
177979968Sobrien			for (j = 0; j < columns; j++) {
178079968Sobrien				c = ctab + j * lines + i;
178179968Sobrien				cprintf(stdout, "%s", c->name);
178279968Sobrien				w = strlen(c->name);
178379968Sobrien				if (! CMD_IMPLEMENTED(c)) {
178479968Sobrien					CPUTC('-', stdout);
178579968Sobrien					w++;
178679968Sobrien				}
178779968Sobrien				if (CMD_HAS_OPTIONS(c)) {
178879968Sobrien					CPUTC('+', stdout);
178979968Sobrien					w++;
179079968Sobrien				}
179179968Sobrien				if (c + lines >= &ctab[NCMDS])
179279968Sobrien					break;
179379968Sobrien				while (w < width) {
179479968Sobrien					CPUTC(' ', stdout);
179579968Sobrien					w++;
179679968Sobrien				}
179779968Sobrien			}
179879968Sobrien			cprintf(stdout, "\r\n");
179979968Sobrien		}
180079968Sobrien		(void) fflush(stdout);
180179968Sobrien		reply(214, "Direct comments to ftp-bugs@%s.", hostname);
180279968Sobrien		return;
180379968Sobrien	}
180479968Sobrien	c = lookup(ctab, s);
180579968Sobrien	if (c == (struct tab *)0) {
1806108746Sobrien		reply(502, "Unknown command '%s'.", s);
180779968Sobrien		return;
180879968Sobrien	}
180979968Sobrien	if (CMD_IMPLEMENTED(c))
181092282Sobrien		reply(214, "Syntax: %s%s %s", htype, c->name, c->help);
181179968Sobrien	else
1812108746Sobrien		reply(504, "%s%-*s\t%s; not implemented.", htype, width,
181379968Sobrien		    c->name, c->help);
181479968Sobrien}
181579968Sobrien
181679968Sobrien/*
181779968Sobrien * Check that the structures used for a PORT, LPRT or EPRT command are
181879968Sobrien * valid (data_dest, his_addr), and if necessary, detect ftp bounce attacks.
181979968Sobrien * If family != -1 check that his_addr.su_family == family.
182079968Sobrien */
182179968Sobrienstatic void
182279968Sobrienport_check(const char *cmd, int family)
182379968Sobrien{
182479968Sobrien	char h1[NI_MAXHOST], h2[NI_MAXHOST];
182579968Sobrien	char s1[NI_MAXHOST], s2[NI_MAXHOST];
182679968Sobrien#ifdef NI_WITHSCOPEID
182779968Sobrien	const int niflags = NI_NUMERICHOST | NI_NUMERICSERV | NI_WITHSCOPEID;
182879968Sobrien#else
182979968Sobrien	const int niflags = NI_NUMERICHOST | NI_NUMERICSERV;
183079968Sobrien#endif
183179968Sobrien
183279968Sobrien	if (epsvall) {
183379968Sobrien		reply(501, "%s disallowed after EPSV ALL", cmd);
183479968Sobrien		return;
183579968Sobrien	}
183679968Sobrien
183779968Sobrien	if (family != -1 && his_addr.su_family != family) {
183879968Sobrien port_check_fail:
183979968Sobrien		reply(500, "Illegal %s command rejected", cmd);
184079968Sobrien		return;
184179968Sobrien	}
184279968Sobrien
184379968Sobrien	if (data_dest.su_family != his_addr.su_family)
184479968Sobrien		goto port_check_fail;
184579968Sobrien
184679968Sobrien			/* be paranoid, if told so */
184779968Sobrien	if (CURCLASS_FLAGS_ISSET(checkportcmd)) {
184879968Sobrien#ifdef INET6
184979968Sobrien		/*
185079968Sobrien		 * be paranoid, there are getnameinfo implementation that does
185179968Sobrien		 * not present scopeid portion
185279968Sobrien		 */
185379968Sobrien		if (data_dest.su_family == AF_INET6 &&
185479968Sobrien		    data_dest.su_scope_id != his_addr.su_scope_id)
185579968Sobrien			goto port_check_fail;
185679968Sobrien#endif
185779968Sobrien
185879968Sobrien		if (getnameinfo((struct sockaddr *)&data_dest, data_dest.su_len,
185979968Sobrien		    h1, sizeof(h1), s1, sizeof(s1), niflags))
186079968Sobrien			goto port_check_fail;
186179968Sobrien		if (getnameinfo((struct sockaddr *)&his_addr, his_addr.su_len,
186279968Sobrien		    h2, sizeof(h2), s2, sizeof(s2), niflags))
186379968Sobrien			goto port_check_fail;
186479968Sobrien
186579968Sobrien		if (atoi(s1) < IPPORT_RESERVED || strcmp(h1, h2) != 0)
186679968Sobrien			goto port_check_fail;
186779968Sobrien	}
186879968Sobrien
186979968Sobrien	usedefault = 0;
187079968Sobrien	if (pdata >= 0) {
187179968Sobrien		(void) close(pdata);
187279968Sobrien		pdata = -1;
187379968Sobrien	}
187479968Sobrien	reply(200, "%s command successful.", cmd);
187579968Sobrien}
1876