1331722Seadler/*
21592Srgrimes * Copyright (c) 1985, 1988, 1993, 1994
31592Srgrimes *	The Regents of the University of California.  All rights reserved.
41592Srgrimes *
51592Srgrimes * Redistribution and use in source and binary forms, with or without
61592Srgrimes * modification, are permitted provided that the following conditions
71592Srgrimes * are met:
81592Srgrimes * 1. Redistributions of source code must retain the above copyright
91592Srgrimes *    notice, this list of conditions and the following disclaimer.
101592Srgrimes * 2. Redistributions in binary form must reproduce the above copyright
111592Srgrimes *    notice, this list of conditions and the following disclaimer in the
121592Srgrimes *    documentation and/or other materials provided with the distribution.
13262136Sbrueffer * 3. Neither the name of the University nor the names of its contributors
141592Srgrimes *    may be used to endorse or promote products derived from this software
151592Srgrimes *    without specific prior written permission.
161592Srgrimes *
171592Srgrimes * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
181592Srgrimes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
191592Srgrimes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
201592Srgrimes * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
211592Srgrimes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
221592Srgrimes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
231592Srgrimes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
241592Srgrimes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
251592Srgrimes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
261592Srgrimes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
271592Srgrimes * SUCH DAMAGE.
281592Srgrimes *
291592Srgrimes *	@(#)ftpcmd.y	8.3 (Berkeley) 4/6/94
301592Srgrimes */
311592Srgrimes
321592Srgrimes/*
331592Srgrimes * Grammar for FTP commands.
341592Srgrimes * See RFC 959.
351592Srgrimes */
361592Srgrimes
371592Srgrimes%{
381592Srgrimes
391592Srgrimes#ifndef lint
4031329Scharnier#if 0
411592Srgrimesstatic char sccsid[] = "@(#)ftpcmd.y	8.3 (Berkeley) 4/6/94";
4231329Scharnier#endif
431592Srgrimes#endif /* not lint */
441592Srgrimes
45137859Syar#include <sys/cdefs.h>
46137859Syar__FBSDID("$FreeBSD: stable/11/libexec/ftpd/ftpcmd.y 363536 2020-07-25 23:08:51Z truckman $");
47137859Syar
481592Srgrimes#include <sys/param.h>
491592Srgrimes#include <sys/socket.h>
501592Srgrimes#include <sys/stat.h>
511592Srgrimes
521592Srgrimes#include <netinet/in.h>
531592Srgrimes#include <arpa/ftp.h>
541592Srgrimes
551592Srgrimes#include <ctype.h>
561592Srgrimes#include <errno.h>
571592Srgrimes#include <glob.h>
5892090Smaxim#include <libutil.h>
5992272Smaxim#include <limits.h>
6092090Smaxim#include <md5.h>
6156668Sshin#include <netdb.h>
621592Srgrimes#include <pwd.h>
631592Srgrimes#include <signal.h>
64132929Syar#include <stdint.h>
651592Srgrimes#include <stdio.h>
661592Srgrimes#include <stdlib.h>
671592Srgrimes#include <string.h>
681592Srgrimes#include <syslog.h>
691592Srgrimes#include <time.h>
701592Srgrimes#include <unistd.h>
711592Srgrimes
721592Srgrimes#include "extern.h"
73109380Syar#include "pathnames.h"
741592Srgrimes
75363536Struckman#define	yylex	ftpcmd_yylex
76363536Struckman
771592Srgrimesoff_t	restart_point;
781592Srgrimes
791592Srgrimesstatic	int cmd_type;
801592Srgrimesstatic	int cmd_form;
811592Srgrimesstatic	int cmd_bytesz;
8289935Syarstatic	int state;
831592Srgrimeschar	cbuf[512];
84132931Syarchar	*fromname = NULL;
851592Srgrimes
861592Srgrimes%}
871592Srgrimes
881592Srgrimes%union {
8992272Smaxim	struct {
9092272Smaxim		off_t	o;
9192272Smaxim		int	i;
9292272Smaxim	} u;
931592Srgrimes	char   *s;
941592Srgrimes}
951592Srgrimes
961592Srgrimes%token
971592Srgrimes	A	B	C	E	F	I
981592Srgrimes	L	N	P	R	S	T
9956668Sshin	ALL
1001592Srgrimes
1011592Srgrimes	SP	CRLF	COMMA
1021592Srgrimes
1031592Srgrimes	USER	PASS	ACCT	REIN	QUIT	PORT
1041592Srgrimes	PASV	TYPE	STRU	MODE	RETR	STOR
1051592Srgrimes	APPE	MLFL	MAIL	MSND	MSOM	MSAM
1061592Srgrimes	MRSQ	MRCP	ALLO	REST	RNFR	RNTO
1071592Srgrimes	ABOR	DELE	CWD	LIST	NLST	SITE
1081592Srgrimes	STAT	HELP	NOOP	MKD	RMD	PWD
1091592Srgrimes	CDUP	STOU	SMNT	SYST	SIZE	MDTM
110168849Syar	LPRT	LPSV	EPRT	EPSV	FEAT
1111592Srgrimes
11275535Sphk	UMASK	IDLE	CHMOD	MDFIVE
1131592Srgrimes
114102565Syar	LEXERR	NOTIMPL
1151592Srgrimes
1161592Srgrimes%token	<s> STRING
11792272Smaxim%token	<u> NUMBER
1181592Srgrimes
11992272Smaxim%type	<u.i> check_login octal_number byte_size
12092272Smaxim%type	<u.i> check_login_ro check_login_epsv
12192272Smaxim%type	<u.i> struct_code mode_code type_code form_code
12275567Speter%type	<s> pathstring pathname password username
123102565Syar%type	<s> ALL NOTIMPL
1241592Srgrimes
1251592Srgrimes%start	cmd_list
1261592Srgrimes
1271592Srgrimes%%
1281592Srgrimes
1291592Srgrimescmd_list
1301592Srgrimes	: /* empty */
1311592Srgrimes	| cmd_list cmd
1321592Srgrimes		{
13388935Sdwmalone			if (fromname)
13488935Sdwmalone				free(fromname);
135132931Syar			fromname = NULL;
136132930Syar			restart_point = 0;
1371592Srgrimes		}
1381592Srgrimes	| cmd_list rcmd
1391592Srgrimes	;
1401592Srgrimes
1411592Srgrimescmd
1421592Srgrimes	: USER SP username CRLF
1431592Srgrimes		{
1441592Srgrimes			user($3);
1451592Srgrimes			free($3);
1461592Srgrimes		}
1471592Srgrimes	| PASS SP password CRLF
1481592Srgrimes		{
1491592Srgrimes			pass($3);
1501592Srgrimes			free($3);
1511592Srgrimes		}
15275556Sgreen	| PASS CRLF
15375556Sgreen		{
15475556Sgreen			pass("");
15575556Sgreen		}
15617433Spst	| PORT check_login SP host_port CRLF
1571592Srgrimes		{
15856668Sshin			if (epsvall) {
159137852Syar				reply(501, "No PORT allowed after EPSV ALL.");
16056668Sshin				goto port_done;
16156668Sshin			}
16256668Sshin			if (!$2)
16356668Sshin				goto port_done;
16456668Sshin			if (port_check("PORT") == 1)
16556668Sshin				goto port_done;
16656668Sshin#ifdef INET6
16756668Sshin			if ((his_addr.su_family != AF_INET6 ||
16856668Sshin			     !IN6_IS_ADDR_V4MAPPED(&his_addr.su_sin6.sin6_addr))) {
16956668Sshin				/* shoud never happen */
17056668Sshin				usedefault = 1;
17156668Sshin				reply(500, "Invalid address rejected.");
17256668Sshin				goto port_done;
17356668Sshin			}
17456668Sshin			port_check_v6("pcmd");
17556668Sshin#endif
17656668Sshin		port_done:
177132925Syar			;
17856668Sshin		}
17956668Sshin	| LPRT check_login SP host_long_port CRLF
18056668Sshin		{
18156668Sshin			if (epsvall) {
182137852Syar				reply(501, "No LPRT allowed after EPSV ALL.");
18356668Sshin				goto lprt_done;
18456668Sshin			}
18556668Sshin			if (!$2)
18656668Sshin				goto lprt_done;
18756668Sshin			if (port_check("LPRT") == 1)
18856668Sshin				goto lprt_done;
18956668Sshin#ifdef INET6
19056668Sshin			if (his_addr.su_family != AF_INET6) {
19156668Sshin				usedefault = 1;
19256668Sshin				reply(500, "Invalid address rejected.");
19356668Sshin				goto lprt_done;
19456668Sshin			}
19556668Sshin			if (port_check_v6("LPRT") == 1)
19656668Sshin				goto lprt_done;
19756668Sshin#endif
19856668Sshin		lprt_done:
199132925Syar			;
20056668Sshin		}
20156668Sshin	| EPRT check_login SP STRING CRLF
20256668Sshin		{
20356668Sshin			char delim;
20456668Sshin			char *tmp = NULL;
20556668Sshin			char *p, *q;
20656668Sshin			char *result[3];
20756668Sshin			struct addrinfo hints;
20856668Sshin			struct addrinfo *res;
20956668Sshin			int i;
21056668Sshin
21156668Sshin			if (epsvall) {
212137852Syar				reply(501, "No EPRT allowed after EPSV ALL.");
21356668Sshin				goto eprt_done;
21456668Sshin			}
21556668Sshin			if (!$2)
21656668Sshin				goto eprt_done;
21756668Sshin
21856668Sshin			memset(&data_dest, 0, sizeof(data_dest));
21956668Sshin			tmp = strdup($4);
22076096Smarkm			if (ftpdebug)
22156668Sshin				syslog(LOG_DEBUG, "%s", tmp);
22256668Sshin			if (!tmp) {
22376096Smarkm				fatalerror("not enough core");
22456668Sshin				/*NOTREACHED*/
22556668Sshin			}
22656668Sshin			p = tmp;
22756668Sshin			delim = p[0];
22856668Sshin			p++;
22956668Sshin			memset(result, 0, sizeof(result));
23056668Sshin			for (i = 0; i < 3; i++) {
23156668Sshin				q = strchr(p, delim);
23256668Sshin				if (!q || *q != delim) {
23356668Sshin		parsefail:
23456668Sshin					reply(500,
23556668Sshin						"Invalid argument, rejected.");
23656668Sshin					if (tmp)
23756668Sshin						free(tmp);
23817433Spst					usedefault = 1;
23956668Sshin					goto eprt_done;
24017433Spst				}
24156668Sshin				*q++ = '\0';
24256668Sshin				result[i] = p;
24376096Smarkm				if (ftpdebug)
24456668Sshin					syslog(LOG_DEBUG, "%d: %s", i, p);
24556668Sshin				p = q;
2461592Srgrimes			}
24756668Sshin
24856668Sshin			/* some more sanity check */
24956668Sshin			p = result[0];
25056668Sshin			while (*p) {
25156668Sshin				if (!isdigit(*p))
25256668Sshin					goto parsefail;
25356668Sshin				p++;
25456668Sshin			}
25556668Sshin			p = result[2];
25656668Sshin			while (*p) {
25756668Sshin				if (!isdigit(*p))
25856668Sshin					goto parsefail;
25956668Sshin				p++;
26056668Sshin			}
26156668Sshin
26256668Sshin			/* grab address */
26356668Sshin			memset(&hints, 0, sizeof(hints));
26456668Sshin			if (atoi(result[0]) == 1)
26556668Sshin				hints.ai_family = PF_INET;
26656668Sshin#ifdef INET6
26756668Sshin			else if (atoi(result[0]) == 2)
26856668Sshin				hints.ai_family = PF_INET6;
26956668Sshin#endif
27056668Sshin			else
27156668Sshin				hints.ai_family = PF_UNSPEC;	/*XXX*/
27256668Sshin			hints.ai_socktype = SOCK_STREAM;
27356668Sshin			i = getaddrinfo(result[1], result[2], &hints, &res);
27456668Sshin			if (i)
27556668Sshin				goto parsefail;
27656668Sshin			memcpy(&data_dest, res->ai_addr, res->ai_addrlen);
27756668Sshin#ifdef INET6
27856668Sshin			if (his_addr.su_family == AF_INET6
27956668Sshin			    && data_dest.su_family == AF_INET6) {
28056668Sshin				/* XXX more sanity checks! */
28156668Sshin				data_dest.su_sin6.sin6_scope_id =
28256668Sshin					his_addr.su_sin6.sin6_scope_id;
28356668Sshin			}
28456668Sshin#endif
28556668Sshin			free(tmp);
28656668Sshin			tmp = NULL;
28756668Sshin
28856668Sshin			if (port_check("EPRT") == 1)
28956668Sshin				goto eprt_done;
29056668Sshin#ifdef INET6
29156668Sshin			if (his_addr.su_family != AF_INET6) {
29256668Sshin				usedefault = 1;
29356668Sshin				reply(500, "Invalid address rejected.");
29456668Sshin				goto eprt_done;
29556668Sshin			}
29656668Sshin			if (port_check_v6("EPRT") == 1)
29756668Sshin				goto eprt_done;
29856668Sshin#endif
29988935Sdwmalone		eprt_done:
30088935Sdwmalone			free($4);
3011592Srgrimes		}
30217433Spst	| PASV check_login CRLF
3031592Srgrimes		{
30456668Sshin			if (epsvall)
305137852Syar				reply(501, "No PASV allowed after EPSV ALL.");
30656668Sshin			else if ($2)
30717433Spst				passive();
3081592Srgrimes		}
30956668Sshin	| LPSV check_login CRLF
31056668Sshin		{
31156668Sshin			if (epsvall)
312137852Syar				reply(501, "No LPSV allowed after EPSV ALL.");
31356668Sshin			else if ($2)
31456668Sshin				long_passive("LPSV", PF_UNSPEC);
31556668Sshin		}
31670102Sphk	| EPSV check_login_epsv SP NUMBER CRLF
31756668Sshin		{
31856668Sshin			if ($2) {
31956668Sshin				int pf;
32092272Smaxim				switch ($4.i) {
32156668Sshin				case 1:
32256668Sshin					pf = PF_INET;
32356668Sshin					break;
32456668Sshin#ifdef INET6
32556668Sshin				case 2:
32656668Sshin					pf = PF_INET6;
32756668Sshin					break;
32856668Sshin#endif
32956668Sshin				default:
33056668Sshin					pf = -1;	/*junk value*/
33156668Sshin					break;
33256668Sshin				}
33356668Sshin				long_passive("EPSV", pf);
33456668Sshin			}
33556668Sshin		}
33670102Sphk	| EPSV check_login_epsv SP ALL CRLF
33756668Sshin		{
33856668Sshin			if ($2) {
339137852Syar				reply(200, "EPSV ALL command successful.");
34056668Sshin				epsvall++;
34156668Sshin			}
34256668Sshin		}
34370102Sphk	| EPSV check_login_epsv CRLF
34456668Sshin		{
34556668Sshin			if ($2)
34656668Sshin				long_passive("EPSV", PF_UNSPEC);
34756668Sshin		}
34871278Sjedgar	| TYPE check_login SP type_code CRLF
3491592Srgrimes		{
35071278Sjedgar			if ($2) {
35171278Sjedgar				switch (cmd_type) {
3521592Srgrimes
35371278Sjedgar				case TYPE_A:
35471278Sjedgar					if (cmd_form == FORM_N) {
35571278Sjedgar						reply(200, "Type set to A.");
35671278Sjedgar						type = cmd_type;
35771278Sjedgar						form = cmd_form;
35871278Sjedgar					} else
35971278Sjedgar						reply(504, "Form must be N.");
36071278Sjedgar					break;
3611592Srgrimes
36271278Sjedgar				case TYPE_E:
36371278Sjedgar					reply(504, "Type E not implemented.");
36471278Sjedgar					break;
3651592Srgrimes
36671278Sjedgar				case TYPE_I:
36771278Sjedgar					reply(200, "Type set to I.");
36871278Sjedgar					type = cmd_type;
36971278Sjedgar					break;
3701592Srgrimes
37171278Sjedgar				case TYPE_L:
372103949Smike#if CHAR_BIT == 8
37371278Sjedgar					if (cmd_bytesz == 8) {
37471278Sjedgar						reply(200,
37571278Sjedgar						    "Type set to L (byte size 8).");
37671278Sjedgar						type = cmd_type;
37771278Sjedgar					} else
37871278Sjedgar						reply(504, "Byte size must be 8.");
379103949Smike#else /* CHAR_BIT == 8 */
380103949Smike					UNIMPLEMENTED for CHAR_BIT != 8
381103949Smike#endif /* CHAR_BIT == 8 */
38271278Sjedgar				}
3831592Srgrimes			}
3841592Srgrimes		}
38571278Sjedgar	| STRU check_login SP struct_code CRLF
3861592Srgrimes		{
38771278Sjedgar			if ($2) {
38871278Sjedgar				switch ($4) {
3891592Srgrimes
39071278Sjedgar				case STRU_F:
391137852Syar					reply(200, "STRU F accepted.");
39271278Sjedgar					break;
3931592Srgrimes
39471278Sjedgar				default:
39571278Sjedgar					reply(504, "Unimplemented STRU type.");
39671278Sjedgar				}
3971592Srgrimes			}
3981592Srgrimes		}
39971278Sjedgar	| MODE check_login SP mode_code CRLF
4001592Srgrimes		{
40171278Sjedgar			if ($2) {
40271278Sjedgar				switch ($4) {
4031592Srgrimes
40471278Sjedgar				case MODE_S:
405137852Syar					reply(200, "MODE S accepted.");
40671278Sjedgar					break;
40771278Sjedgar
40871278Sjedgar				default:
40971278Sjedgar					reply(502, "Unimplemented MODE type.");
41071278Sjedgar				}
4111592Srgrimes			}
4121592Srgrimes		}
41371278Sjedgar	| ALLO check_login SP NUMBER CRLF
4141592Srgrimes		{
41571278Sjedgar			if ($2) {
41671278Sjedgar				reply(202, "ALLO command ignored.");
41771278Sjedgar			}
4181592Srgrimes		}
41971278Sjedgar	| ALLO check_login SP NUMBER SP R SP NUMBER CRLF
4201592Srgrimes		{
42171278Sjedgar			if ($2) {
42271278Sjedgar				reply(202, "ALLO command ignored.");
42371278Sjedgar			}
4241592Srgrimes		}
4251592Srgrimes	| RETR check_login SP pathname CRLF
4261592Srgrimes		{
42782796Ssheldonh			if (noretr || (guest && noguestretr))
428137852Syar				reply(500, "RETR command disabled.");
42982460Snik			else if ($2 && $4 != NULL)
430132931Syar				retrieve(NULL, $4);
43182460Snik
4321592Srgrimes			if ($4 != NULL)
4331592Srgrimes				free($4);
4341592Srgrimes		}
43570102Sphk	| STOR check_login_ro SP pathname CRLF
4361592Srgrimes		{
4371592Srgrimes			if ($2 && $4 != NULL)
4381592Srgrimes				store($4, "w", 0);
4391592Srgrimes			if ($4 != NULL)
4401592Srgrimes				free($4);
4411592Srgrimes		}
44270102Sphk	| APPE check_login_ro SP pathname CRLF
4431592Srgrimes		{
4441592Srgrimes			if ($2 && $4 != NULL)
4451592Srgrimes				store($4, "a", 0);
4461592Srgrimes			if ($4 != NULL)
4471592Srgrimes				free($4);
4481592Srgrimes		}
4491592Srgrimes	| NLST check_login CRLF
4501592Srgrimes		{
4511592Srgrimes			if ($2)
4521592Srgrimes				send_file_list(".");
4531592Srgrimes		}
454101395Syar	| NLST check_login SP pathstring CRLF
4551592Srgrimes		{
456101395Syar			if ($2)
4571592Srgrimes				send_file_list($4);
458101395Syar			free($4);
4591592Srgrimes		}
4601592Srgrimes	| LIST check_login CRLF
4611592Srgrimes		{
4621592Srgrimes			if ($2)
463109380Syar				retrieve(_PATH_LS " -lgA", "");
4641592Srgrimes		}
46575567Speter	| LIST check_login SP pathstring CRLF
4661592Srgrimes		{
467101395Syar			if ($2)
468109380Syar				retrieve(_PATH_LS " -lgA %s", $4);
469101395Syar			free($4);
4701592Srgrimes		}
4711592Srgrimes	| STAT check_login SP pathname CRLF
4721592Srgrimes		{
4731592Srgrimes			if ($2 && $4 != NULL)
4741592Srgrimes				statfilecmd($4);
4751592Srgrimes			if ($4 != NULL)
4761592Srgrimes				free($4);
4771592Srgrimes		}
47871278Sjedgar	| STAT check_login CRLF
4791592Srgrimes		{
48071278Sjedgar			if ($2) {
48171278Sjedgar				statcmd();
48271278Sjedgar			}
4831592Srgrimes		}
48470102Sphk	| DELE check_login_ro SP pathname CRLF
4851592Srgrimes		{
4861592Srgrimes			if ($2 && $4 != NULL)
4871592Srgrimes				delete($4);
4881592Srgrimes			if ($4 != NULL)
4891592Srgrimes				free($4);
4901592Srgrimes		}
49170102Sphk	| RNTO check_login_ro SP pathname CRLF
4921592Srgrimes		{
493101379Syar			if ($2 && $4 != NULL) {
49417433Spst				if (fromname) {
49517433Spst					renamecmd(fromname, $4);
49617433Spst					free(fromname);
497132931Syar					fromname = NULL;
49817433Spst				} else {
49917433Spst					reply(503, "Bad sequence of commands.");
50017433Spst				}
5011592Srgrimes			}
502101379Syar			if ($4 != NULL)
503101379Syar				free($4);
5041592Srgrimes		}
50571278Sjedgar	| ABOR check_login CRLF
5061592Srgrimes		{
50771278Sjedgar			if ($2)
50871278Sjedgar				reply(225, "ABOR command successful.");
5091592Srgrimes		}
5101592Srgrimes	| CWD check_login CRLF
5111592Srgrimes		{
51269234Sdanny			if ($2) {
513110036Syar				cwd(homedir);
51469234Sdanny			}
5151592Srgrimes		}
5161592Srgrimes	| CWD check_login SP pathname CRLF
5171592Srgrimes		{
5181592Srgrimes			if ($2 && $4 != NULL)
5191592Srgrimes				cwd($4);
5201592Srgrimes			if ($4 != NULL)
5211592Srgrimes				free($4);
5221592Srgrimes		}
5231592Srgrimes	| HELP CRLF
5241592Srgrimes		{
525132931Syar			help(cmdtab, NULL);
5261592Srgrimes		}
5271592Srgrimes	| HELP SP STRING CRLF
5281592Srgrimes		{
5291592Srgrimes			char *cp = $3;
5301592Srgrimes
5311592Srgrimes			if (strncasecmp(cp, "SITE", 4) == 0) {
5321592Srgrimes				cp = $3 + 4;
5331592Srgrimes				if (*cp == ' ')
5341592Srgrimes					cp++;
5351592Srgrimes				if (*cp)
5361592Srgrimes					help(sitetab, cp);
5371592Srgrimes				else
538132931Syar					help(sitetab, NULL);
5391592Srgrimes			} else
5401592Srgrimes				help(cmdtab, $3);
54188935Sdwmalone			free($3);
5421592Srgrimes		}
5431592Srgrimes	| NOOP CRLF
5441592Srgrimes		{
5451592Srgrimes			reply(200, "NOOP command successful.");
5461592Srgrimes		}
54770102Sphk	| MKD check_login_ro SP pathname CRLF
5481592Srgrimes		{
5491592Srgrimes			if ($2 && $4 != NULL)
5501592Srgrimes				makedir($4);
5511592Srgrimes			if ($4 != NULL)
5521592Srgrimes				free($4);
5531592Srgrimes		}
55470102Sphk	| RMD check_login_ro SP pathname CRLF
5551592Srgrimes		{
5561592Srgrimes			if ($2 && $4 != NULL)
5571592Srgrimes				removedir($4);
5581592Srgrimes			if ($4 != NULL)
5591592Srgrimes				free($4);
5601592Srgrimes		}
5611592Srgrimes	| PWD check_login CRLF
5621592Srgrimes		{
5631592Srgrimes			if ($2)
5641592Srgrimes				pwd();
5651592Srgrimes		}
5661592Srgrimes	| CDUP check_login CRLF
5671592Srgrimes		{
5681592Srgrimes			if ($2)
5691592Srgrimes				cwd("..");
5701592Srgrimes		}
5711592Srgrimes	| SITE SP HELP CRLF
5721592Srgrimes		{
573132931Syar			help(sitetab, NULL);
5741592Srgrimes		}
5751592Srgrimes	| SITE SP HELP SP STRING CRLF
5761592Srgrimes		{
5771592Srgrimes			help(sitetab, $5);
57888935Sdwmalone			free($5);
5791592Srgrimes		}
58075535Sphk	| SITE SP MDFIVE check_login SP pathname CRLF
58175535Sphk		{
58275535Sphk			char p[64], *q;
58375535Sphk
584101379Syar			if ($4 && $6) {
58575535Sphk				q = MD5File($6, p);
58675535Sphk				if (q != NULL)
58775535Sphk					reply(200, "MD5(%s) = %s", $6, p);
58875535Sphk				else
58975535Sphk					perror_reply(550, $6);
59075535Sphk			}
59188935Sdwmalone			if ($6)
59288935Sdwmalone				free($6);
59375535Sphk		}
5941592Srgrimes	| SITE SP UMASK check_login CRLF
5951592Srgrimes		{
5961592Srgrimes			int oldmask;
5971592Srgrimes
5981592Srgrimes			if ($4) {
5991592Srgrimes				oldmask = umask(0);
6001592Srgrimes				(void) umask(oldmask);
601137852Syar				reply(200, "Current UMASK is %03o.", oldmask);
6021592Srgrimes			}
6031592Srgrimes		}
6041592Srgrimes	| SITE SP UMASK check_login SP octal_number CRLF
6051592Srgrimes		{
6061592Srgrimes			int oldmask;
6071592Srgrimes
6081592Srgrimes			if ($4) {
6091592Srgrimes				if (($6 == -1) || ($6 > 0777)) {
610137852Syar					reply(501, "Bad UMASK value.");
6111592Srgrimes				} else {
6121592Srgrimes					oldmask = umask($6);
6131592Srgrimes					reply(200,
614137852Syar					    "UMASK set to %03o (was %03o).",
6151592Srgrimes					    $6, oldmask);
6161592Srgrimes				}
6171592Srgrimes			}
6181592Srgrimes		}
61970102Sphk	| SITE SP CHMOD check_login_ro SP octal_number SP pathname CRLF
6201592Srgrimes		{
6211592Srgrimes			if ($4 && ($8 != NULL)) {
622101378Syar				if (($6 == -1 ) || ($6 > 0777))
623137852Syar					reply(501, "Bad mode value.");
6241592Srgrimes				else if (chmod($8, $6) < 0)
6251592Srgrimes					perror_reply(550, $8);
6261592Srgrimes				else
6271592Srgrimes					reply(200, "CHMOD command successful.");
6281592Srgrimes			}
6291592Srgrimes			if ($8 != NULL)
6301592Srgrimes				free($8);
6311592Srgrimes		}
63271278Sjedgar	| SITE SP check_login IDLE CRLF
6331592Srgrimes		{
63471278Sjedgar			if ($3)
63571278Sjedgar				reply(200,
636137852Syar			    	    "Current IDLE time limit is %d seconds; max %d.",
63771278Sjedgar				    timeout, maxtimeout);
6381592Srgrimes		}
63971278Sjedgar	| SITE SP check_login IDLE SP NUMBER CRLF
6401592Srgrimes		{
64171278Sjedgar			if ($3) {
64292272Smaxim				if ($6.i < 30 || $6.i > maxtimeout) {
64371278Sjedgar					reply(501,
644137852Syar					    "Maximum IDLE time must be between 30 and %d seconds.",
64571278Sjedgar					    maxtimeout);
64671278Sjedgar				} else {
64792272Smaxim					timeout = $6.i;
648137659Syar					(void) alarm(timeout);
64971278Sjedgar					reply(200,
650137852Syar					    "Maximum IDLE time set to %d seconds.",
65171278Sjedgar					    timeout);
65271278Sjedgar				}
6531592Srgrimes			}
6541592Srgrimes		}
65570102Sphk	| STOU check_login_ro SP pathname CRLF
6561592Srgrimes		{
6571592Srgrimes			if ($2 && $4 != NULL)
6581592Srgrimes				store($4, "w", 1);
6591592Srgrimes			if ($4 != NULL)
6601592Srgrimes				free($4);
6611592Srgrimes		}
662168849Syar	| FEAT CRLF
663168849Syar		{
664168849Syar			lreply(211, "Extensions supported:");
665168849Syar#if 0
666168849Syar			/* XXX these two keywords are non-standard */
667168849Syar			printf(" EPRT\r\n");
668168849Syar			if (!noepsv)
669168849Syar				printf(" EPSV\r\n");
670168849Syar#endif
671168849Syar			printf(" MDTM\r\n");
672168849Syar			printf(" REST STREAM\r\n");
673168849Syar			printf(" SIZE\r\n");
674168849Syar			if (assumeutf8) {
675168849Syar				/* TVFS requires UTF8, see RFC 3659 */
676168849Syar				printf(" TVFS\r\n");
677168849Syar				printf(" UTF8\r\n");
678168849Syar			}
679168849Syar			reply(211, "End.");
680168849Syar		}
68171278Sjedgar	| SYST check_login CRLF
6821592Srgrimes		{
683116439Syar			if ($2) {
684116439Syar				if (hostinfo)
6851592Srgrimes#ifdef BSD
686116439Syar					reply(215, "UNIX Type: L%d Version: BSD-%d",
687116439Syar					      CHAR_BIT, BSD);
6881592Srgrimes#else /* BSD */
689116439Syar					reply(215, "UNIX Type: L%d", CHAR_BIT);
6901592Srgrimes#endif /* BSD */
691116439Syar				else
692116439Syar					reply(215, "UNKNOWN Type: L%d", CHAR_BIT);
693116439Syar			}
6941592Srgrimes		}
6951592Srgrimes
6961592Srgrimes		/*
6971592Srgrimes		 * SIZE is not in RFC959, but Postel has blessed it and
6981592Srgrimes		 * it will be in the updated RFC.
6991592Srgrimes		 *
7001592Srgrimes		 * Return size of file in a format suitable for
7011592Srgrimes		 * using with RESTART (we just count bytes).
7021592Srgrimes		 */
7031592Srgrimes	| SIZE check_login SP pathname CRLF
7041592Srgrimes		{
7051592Srgrimes			if ($2 && $4 != NULL)
7061592Srgrimes				sizecmd($4);
7071592Srgrimes			if ($4 != NULL)
7081592Srgrimes				free($4);
7091592Srgrimes		}
7101592Srgrimes
7111592Srgrimes		/*
7121592Srgrimes		 * MDTM is not in RFC959, but Postel has blessed it and
7131592Srgrimes		 * it will be in the updated RFC.
7141592Srgrimes		 *
7151592Srgrimes		 * Return modification time of file as an ISO 3307
7161592Srgrimes		 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
7171592Srgrimes		 * where xxx is the fractional second (of any precision,
7181592Srgrimes		 * not necessarily 3 digits)
7191592Srgrimes		 */
7201592Srgrimes	| MDTM check_login SP pathname CRLF
7211592Srgrimes		{
7221592Srgrimes			if ($2 && $4 != NULL) {
7231592Srgrimes				struct stat stbuf;
7241592Srgrimes				if (stat($4, &stbuf) < 0)
725137850Syar					perror_reply(550, $4);
7261592Srgrimes				else if (!S_ISREG(stbuf.st_mode)) {
7271592Srgrimes					reply(550, "%s: not a plain file.", $4);
7281592Srgrimes				} else {
7291592Srgrimes					struct tm *t;
7301592Srgrimes					t = gmtime(&stbuf.st_mtime);
7311592Srgrimes					reply(213,
73217435Spst					    "%04d%02d%02d%02d%02d%02d",
73317435Spst					    1900 + t->tm_year,
73417435Spst					    t->tm_mon+1, t->tm_mday,
7351592Srgrimes					    t->tm_hour, t->tm_min, t->tm_sec);
7361592Srgrimes				}
7371592Srgrimes			}
7381592Srgrimes			if ($4 != NULL)
7391592Srgrimes				free($4);
7401592Srgrimes		}
7411592Srgrimes	| QUIT CRLF
7421592Srgrimes		{
7431592Srgrimes			reply(221, "Goodbye.");
7441592Srgrimes			dologout(0);
7451592Srgrimes		}
746102565Syar	| NOTIMPL
747102565Syar		{
748102565Syar			nack($1);
749102565Syar		}
75089935Syar	| error
7511592Srgrimes		{
75289935Syar			yyclearin;		/* discard lookahead data */
75389935Syar			yyerrok;		/* clear error condition */
754102565Syar			state = CMD;		/* reset lexer state */
7551592Srgrimes		}
7561592Srgrimes	;
7571592Srgrimesrcmd
75870102Sphk	: RNFR check_login_ro SP pathname CRLF
7591592Srgrimes		{
760132930Syar			restart_point = 0;
7611592Srgrimes			if ($2 && $4) {
76288935Sdwmalone				if (fromname)
76388935Sdwmalone					free(fromname);
764132931Syar				fromname = NULL;
76588935Sdwmalone				if (renamefrom($4))
76688935Sdwmalone					fromname = $4;
76788935Sdwmalone				else
7681592Srgrimes					free($4);
76988935Sdwmalone			} else if ($4) {
77088935Sdwmalone				free($4);
7711592Srgrimes			}
7721592Srgrimes		}
77392272Smaxim	| REST check_login SP NUMBER CRLF
7741592Srgrimes		{
77571278Sjedgar			if ($2) {
77688935Sdwmalone				if (fromname)
77788935Sdwmalone					free(fromname);
778132931Syar				fromname = NULL;
77992272Smaxim				restart_point = $4.o;
780132929Syar				reply(350, "Restarting at %jd. %s",
781132929Syar				    (intmax_t)restart_point,
78271278Sjedgar				    "Send STORE or RETRIEVE to initiate transfer.");
78371278Sjedgar			}
7841592Srgrimes		}
7851592Srgrimes	;
7861592Srgrimes
7871592Srgrimesusername
7881592Srgrimes	: STRING
7891592Srgrimes	;
7901592Srgrimes
7911592Srgrimespassword
7921592Srgrimes	: /* empty */
7931592Srgrimes		{
7941592Srgrimes			$$ = (char *)calloc(1, sizeof(char));
7951592Srgrimes		}
7961592Srgrimes	| STRING
7971592Srgrimes	;
7981592Srgrimes
7991592Srgrimesbyte_size
8001592Srgrimes	: NUMBER
80192272Smaxim		{
80292272Smaxim			$$ = $1.i;
80392272Smaxim		}
8041592Srgrimes	;
8051592Srgrimes
8061592Srgrimeshost_port
8071592Srgrimes	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
8081592Srgrimes		NUMBER COMMA NUMBER
8091592Srgrimes		{
8101592Srgrimes			char *a, *p;
8111592Srgrimes
81256668Sshin			data_dest.su_len = sizeof(struct sockaddr_in);
81356668Sshin			data_dest.su_family = AF_INET;
81456668Sshin			p = (char *)&data_dest.su_sin.sin_port;
81592272Smaxim			p[0] = $9.i; p[1] = $11.i;
81656668Sshin			a = (char *)&data_dest.su_sin.sin_addr;
81792272Smaxim			a[0] = $1.i; a[1] = $3.i; a[2] = $5.i; a[3] = $7.i;
8181592Srgrimes		}
8191592Srgrimes	;
8201592Srgrimes
82156668Sshinhost_long_port
82256668Sshin	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
82356668Sshin		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
82456668Sshin		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
82556668Sshin		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
82656668Sshin		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
82756668Sshin		NUMBER
82856668Sshin		{
82956668Sshin			char *a, *p;
83056668Sshin
83156668Sshin			memset(&data_dest, 0, sizeof(data_dest));
83256668Sshin			data_dest.su_len = sizeof(struct sockaddr_in6);
83356668Sshin			data_dest.su_family = AF_INET6;
83456668Sshin			p = (char *)&data_dest.su_port;
83592272Smaxim			p[0] = $39.i; p[1] = $41.i;
83656668Sshin			a = (char *)&data_dest.su_sin6.sin6_addr;
83792272Smaxim			a[0] = $5.i; a[1] = $7.i; a[2] = $9.i; a[3] = $11.i;
83892272Smaxim			a[4] = $13.i; a[5] = $15.i; a[6] = $17.i; a[7] = $19.i;
83992272Smaxim			a[8] = $21.i; a[9] = $23.i; a[10] = $25.i; a[11] = $27.i;
84092272Smaxim			a[12] = $29.i; a[13] = $31.i; a[14] = $33.i; a[15] = $35.i;
84156668Sshin			if (his_addr.su_family == AF_INET6) {
84256668Sshin				/* XXX more sanity checks! */
84356668Sshin				data_dest.su_sin6.sin6_scope_id =
84456668Sshin					his_addr.su_sin6.sin6_scope_id;
84556668Sshin			}
84692272Smaxim			if ($1.i != 6 || $3.i != 16 || $37.i != 2)
84756668Sshin				memset(&data_dest, 0, sizeof(data_dest));
84856668Sshin		}
84956668Sshin	| NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
85056668Sshin		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
85156668Sshin		NUMBER
85256668Sshin		{
85356668Sshin			char *a, *p;
85456668Sshin
85556668Sshin			memset(&data_dest, 0, sizeof(data_dest));
85656668Sshin			data_dest.su_sin.sin_len = sizeof(struct sockaddr_in);
85756668Sshin			data_dest.su_family = AF_INET;
85856668Sshin			p = (char *)&data_dest.su_port;
85992272Smaxim			p[0] = $15.i; p[1] = $17.i;
86056668Sshin			a = (char *)&data_dest.su_sin.sin_addr;
86192272Smaxim			a[0] =  $5.i; a[1] = $7.i; a[2] = $9.i; a[3] = $11.i;
86292272Smaxim			if ($1.i != 4 || $3.i != 4 || $13.i != 2)
86356668Sshin				memset(&data_dest, 0, sizeof(data_dest));
86456668Sshin		}
86556668Sshin	;
86656668Sshin
8671592Srgrimesform_code
8681592Srgrimes	: N
8691592Srgrimes		{
8701592Srgrimes			$$ = FORM_N;
8711592Srgrimes		}
8721592Srgrimes	| T
8731592Srgrimes		{
8741592Srgrimes			$$ = FORM_T;
8751592Srgrimes		}
8761592Srgrimes	| C
8771592Srgrimes		{
8781592Srgrimes			$$ = FORM_C;
8791592Srgrimes		}
8801592Srgrimes	;
8811592Srgrimes
8821592Srgrimestype_code
8831592Srgrimes	: A
8841592Srgrimes		{
8851592Srgrimes			cmd_type = TYPE_A;
8861592Srgrimes			cmd_form = FORM_N;
8871592Srgrimes		}
8881592Srgrimes	| A SP form_code
8891592Srgrimes		{
8901592Srgrimes			cmd_type = TYPE_A;
8911592Srgrimes			cmd_form = $3;
8921592Srgrimes		}
8931592Srgrimes	| E
8941592Srgrimes		{
8951592Srgrimes			cmd_type = TYPE_E;
8961592Srgrimes			cmd_form = FORM_N;
8971592Srgrimes		}
8981592Srgrimes	| E SP form_code
8991592Srgrimes		{
9001592Srgrimes			cmd_type = TYPE_E;
9011592Srgrimes			cmd_form = $3;
9021592Srgrimes		}
9031592Srgrimes	| I
9041592Srgrimes		{
9051592Srgrimes			cmd_type = TYPE_I;
9061592Srgrimes		}
9071592Srgrimes	| L
9081592Srgrimes		{
9091592Srgrimes			cmd_type = TYPE_L;
910103949Smike			cmd_bytesz = CHAR_BIT;
9111592Srgrimes		}
9121592Srgrimes	| L SP byte_size
9131592Srgrimes		{
9141592Srgrimes			cmd_type = TYPE_L;
9151592Srgrimes			cmd_bytesz = $3;
9161592Srgrimes		}
9171592Srgrimes		/* this is for a bug in the BBN ftp */
9181592Srgrimes	| L byte_size
9191592Srgrimes		{
9201592Srgrimes			cmd_type = TYPE_L;
9211592Srgrimes			cmd_bytesz = $2;
9221592Srgrimes		}
9231592Srgrimes	;
9241592Srgrimes
9251592Srgrimesstruct_code
9261592Srgrimes	: F
9271592Srgrimes		{
9281592Srgrimes			$$ = STRU_F;
9291592Srgrimes		}
9301592Srgrimes	| R
9311592Srgrimes		{
9321592Srgrimes			$$ = STRU_R;
9331592Srgrimes		}
9341592Srgrimes	| P
9351592Srgrimes		{
9361592Srgrimes			$$ = STRU_P;
9371592Srgrimes		}
9381592Srgrimes	;
9391592Srgrimes
9401592Srgrimesmode_code
9411592Srgrimes	: S
9421592Srgrimes		{
9431592Srgrimes			$$ = MODE_S;
9441592Srgrimes		}
9451592Srgrimes	| B
9461592Srgrimes		{
9471592Srgrimes			$$ = MODE_B;
9481592Srgrimes		}
9491592Srgrimes	| C
9501592Srgrimes		{
9511592Srgrimes			$$ = MODE_C;
9521592Srgrimes		}
9531592Srgrimes	;
9541592Srgrimes
9551592Srgrimespathname
9561592Srgrimes	: pathstring
9571592Srgrimes		{
95875567Speter			if (logged_in && $1) {
959110340Syar				char *p;
9601592Srgrimes
961110340Syar				/*
962110340Syar				 * Expand ~user manually since glob(3)
963110340Syar				 * will return the unexpanded pathname
964110340Syar				 * if the corresponding file/directory
965110340Syar				 * doesn't exist yet.  Using sole glob(3)
966110340Syar				 * would break natural commands like
967110340Syar				 * MKD ~user/newdir
968110340Syar				 * or
969110340Syar				 * RNTO ~/newfile
970110340Syar				 */
971110340Syar				if ((p = exptilde($1)) != NULL) {
972110340Syar					$$ = expglob(p);
973110340Syar					free(p);
974110340Syar				} else
9751592Srgrimes					$$ = NULL;
9761592Srgrimes				free($1);
9771592Srgrimes			} else
9781592Srgrimes				$$ = $1;
9791592Srgrimes		}
9801592Srgrimes	;
9811592Srgrimes
9821592Srgrimespathstring
9831592Srgrimes	: STRING
9841592Srgrimes	;
9851592Srgrimes
9861592Srgrimesoctal_number
9871592Srgrimes	: NUMBER
9881592Srgrimes		{
9891592Srgrimes			int ret, dec, multby, digit;
9901592Srgrimes
9911592Srgrimes			/*
9921592Srgrimes			 * Convert a number that was read as decimal number
9931592Srgrimes			 * to what it would be if it had been read as octal.
9941592Srgrimes			 */
99592272Smaxim			dec = $1.i;
9961592Srgrimes			multby = 1;
9971592Srgrimes			ret = 0;
9981592Srgrimes			while (dec) {
9991592Srgrimes				digit = dec%10;
10001592Srgrimes				if (digit > 7) {
10011592Srgrimes					ret = -1;
10021592Srgrimes					break;
10031592Srgrimes				}
10041592Srgrimes				ret += digit * multby;
10051592Srgrimes				multby *= 8;
10061592Srgrimes				dec /= 10;
10071592Srgrimes			}
10081592Srgrimes			$$ = ret;
10091592Srgrimes		}
10101592Srgrimes	;
10111592Srgrimes
10121592Srgrimes
10131592Srgrimescheck_login
10141592Srgrimes	: /* empty */
10151592Srgrimes		{
101670102Sphk		$$ = check_login1();
10171592Srgrimes		}
10181592Srgrimes	;
10191592Srgrimes
102070102Sphkcheck_login_epsv
102170102Sphk	: /* empty */
102270102Sphk		{
102370102Sphk		if (noepsv) {
1024137852Syar			reply(500, "EPSV command disabled.");
102570102Sphk			$$ = 0;
102670102Sphk		}
102770102Sphk		else
102870102Sphk			$$ = check_login1();
102970102Sphk		}
103070102Sphk	;
103170102Sphk
103270102Sphkcheck_login_ro
103370102Sphk	: /* empty */
103470102Sphk		{
103570102Sphk		if (readonly) {
103672710Sdes			reply(550, "Permission denied.");
103770102Sphk			$$ = 0;
103870102Sphk		}
103970102Sphk		else
104070102Sphk			$$ = check_login1();
104170102Sphk		}
104270102Sphk	;
104370102Sphk
10441592Srgrimes%%
10451592Srgrimes
10461592Srgrimes#define	CMD	0	/* beginning of command */
10471592Srgrimes#define	ARGS	1	/* expect miscellaneous arguments */
10481592Srgrimes#define	STR1	2	/* expect SP followed by STRING */
10491592Srgrimes#define	STR2	3	/* expect STRING */
10501592Srgrimes#define	OSTR	4	/* optional SP then STRING */
105175556Sgreen#define	ZSTR1	5	/* optional SP then optional STRING */
10521592Srgrimes#define	ZSTR2	6	/* optional STRING after SP */
10531592Srgrimes#define	SITECMD	7	/* SITE command */
10541592Srgrimes#define	NSTR	8	/* Number followed by a string */
10551592Srgrimes
105675560Sjedgar#define	MAXGLOBARGS	1000
105775560Sjedgar
1058101034Syar#define	MAXASIZE	10240	/* Deny ASCII SIZE on files larger than that */
1059101034Syar
10601592Srgrimesstruct tab {
10611592Srgrimes	char	*name;
10621592Srgrimes	short	token;
10631592Srgrimes	short	state;
10641592Srgrimes	short	implemented;	/* 1 if command is implemented */
10651592Srgrimes	char	*help;
10661592Srgrimes};
10671592Srgrimes
10681592Srgrimesstruct tab cmdtab[] = {		/* In order defined in RFC 765 */
10691592Srgrimes	{ "USER", USER, STR1, 1,	"<sp> username" },
107075556Sgreen	{ "PASS", PASS, ZSTR1, 1,	"[<sp> [password]]" },
10711592Srgrimes	{ "ACCT", ACCT, STR1, 0,	"(specify account)" },
10721592Srgrimes	{ "SMNT", SMNT, ARGS, 0,	"(structure mount)" },
10731592Srgrimes	{ "REIN", REIN, ARGS, 0,	"(reinitialize server state)" },
10741592Srgrimes	{ "QUIT", QUIT, ARGS, 1,	"(terminate service)", },
1075101806Syar	{ "PORT", PORT, ARGS, 1,	"<sp> b0, b1, b2, b3, b4, b5" },
107656668Sshin	{ "LPRT", LPRT, ARGS, 1,	"<sp> af, hal, h1, h2, h3,..., pal, p1, p2..." },
107756668Sshin	{ "EPRT", EPRT, STR1, 1,	"<sp> |af|addr|port|" },
10781592Srgrimes	{ "PASV", PASV, ARGS, 1,	"(set server in passive mode)" },
107956668Sshin	{ "LPSV", LPSV, ARGS, 1,	"(set server in passive mode)" },
108056668Sshin	{ "EPSV", EPSV, ARGS, 1,	"[<sp> af|ALL]" },
1081101806Syar	{ "TYPE", TYPE, ARGS, 1,	"<sp> { A | E | I | L }" },
10821592Srgrimes	{ "STRU", STRU, ARGS, 1,	"(specify file structure)" },
10831592Srgrimes	{ "MODE", MODE, ARGS, 1,	"(specify transfer mode)" },
10841592Srgrimes	{ "RETR", RETR, STR1, 1,	"<sp> file-name" },
10851592Srgrimes	{ "STOR", STOR, STR1, 1,	"<sp> file-name" },
10861592Srgrimes	{ "APPE", APPE, STR1, 1,	"<sp> file-name" },
10871592Srgrimes	{ "MLFL", MLFL, OSTR, 0,	"(mail file)" },
10881592Srgrimes	{ "MAIL", MAIL, OSTR, 0,	"(mail to user)" },
10891592Srgrimes	{ "MSND", MSND, OSTR, 0,	"(mail send to terminal)" },
10901592Srgrimes	{ "MSOM", MSOM, OSTR, 0,	"(mail send to terminal or mailbox)" },
10911592Srgrimes	{ "MSAM", MSAM, OSTR, 0,	"(mail send to terminal and mailbox)" },
10921592Srgrimes	{ "MRSQ", MRSQ, OSTR, 0,	"(mail recipient scheme question)" },
10931592Srgrimes	{ "MRCP", MRCP, STR1, 0,	"(mail recipient)" },
10941592Srgrimes	{ "ALLO", ALLO, ARGS, 1,	"allocate storage (vacuously)" },
10951592Srgrimes	{ "REST", REST, ARGS, 1,	"<sp> offset (restart command)" },
10961592Srgrimes	{ "RNFR", RNFR, STR1, 1,	"<sp> file-name" },
10971592Srgrimes	{ "RNTO", RNTO, STR1, 1,	"<sp> file-name" },
10981592Srgrimes	{ "ABOR", ABOR, ARGS, 1,	"(abort operation)" },
10991592Srgrimes	{ "DELE", DELE, STR1, 1,	"<sp> file-name" },
11001592Srgrimes	{ "CWD",  CWD,  OSTR, 1,	"[ <sp> directory-name ]" },
11011592Srgrimes	{ "XCWD", CWD,	OSTR, 1,	"[ <sp> directory-name ]" },
11021592Srgrimes	{ "LIST", LIST, OSTR, 1,	"[ <sp> path-name ]" },
11031592Srgrimes	{ "NLST", NLST, OSTR, 1,	"[ <sp> path-name ]" },
11041592Srgrimes	{ "SITE", SITE, SITECMD, 1,	"site-cmd [ <sp> arguments ]" },
11051592Srgrimes	{ "SYST", SYST, ARGS, 1,	"(get type of operating system)" },
1106168849Syar	{ "FEAT", FEAT, ARGS, 1,	"(get extended features)" },
11071592Srgrimes	{ "STAT", STAT, OSTR, 1,	"[ <sp> path-name ]" },
11081592Srgrimes	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
11091592Srgrimes	{ "NOOP", NOOP, ARGS, 1,	"" },
11101592Srgrimes	{ "MKD",  MKD,  STR1, 1,	"<sp> path-name" },
11111592Srgrimes	{ "XMKD", MKD,  STR1, 1,	"<sp> path-name" },
11121592Srgrimes	{ "RMD",  RMD,  STR1, 1,	"<sp> path-name" },
11131592Srgrimes	{ "XRMD", RMD,  STR1, 1,	"<sp> path-name" },
11141592Srgrimes	{ "PWD",  PWD,  ARGS, 1,	"(return current directory)" },
11151592Srgrimes	{ "XPWD", PWD,  ARGS, 1,	"(return current directory)" },
11161592Srgrimes	{ "CDUP", CDUP, ARGS, 1,	"(change to parent directory)" },
11171592Srgrimes	{ "XCUP", CDUP, ARGS, 1,	"(change to parent directory)" },
11181592Srgrimes	{ "STOU", STOU, STR1, 1,	"<sp> file-name" },
11191592Srgrimes	{ "SIZE", SIZE, OSTR, 1,	"<sp> path-name" },
11201592Srgrimes	{ "MDTM", MDTM, OSTR, 1,	"<sp> path-name" },
11211592Srgrimes	{ NULL,   0,    0,    0,	0 }
11221592Srgrimes};
11231592Srgrimes
11241592Srgrimesstruct tab sitetab[] = {
112575535Sphk	{ "MD5", MDFIVE, STR1, 1,	"[ <sp> file-name ]" },
11261592Srgrimes	{ "UMASK", UMASK, ARGS, 1,	"[ <sp> umask ]" },
11271592Srgrimes	{ "IDLE", IDLE, ARGS, 1,	"[ <sp> maximum-idle-time ]" },
11281592Srgrimes	{ "CHMOD", CHMOD, NSTR, 1,	"<sp> mode <sp> file-name" },
11291592Srgrimes	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
11301592Srgrimes	{ NULL,   0,    0,    0,	0 }
11311592Srgrimes};
11321592Srgrimes
113390148Simpstatic char	*copy(char *);
1134110340Syarstatic char	*expglob(char *);
1135110340Syarstatic char	*exptilde(char *);
113690148Simpstatic void	 help(struct tab *, char *);
11371592Srgrimesstatic struct tab *
113890148Simp		 lookup(struct tab *, char *);
113990148Simpstatic int	 port_check(const char *);
1140159276Syar#ifdef INET6
114190148Simpstatic int	 port_check_v6(const char *);
1142159276Syar#endif
114390148Simpstatic void	 sizecmd(char *);
114490148Simpstatic void	 toolong(int);
1145159276Syar#ifdef INET6
114690148Simpstatic void	 v4map_data_dest(void);
1147159276Syar#endif
114890148Simpstatic int	 yylex(void);
11491592Srgrimes
11501592Srgrimesstatic struct tab *
115190148Simplookup(struct tab *p, char *cmd)
11521592Srgrimes{
11531592Srgrimes
11541592Srgrimes	for (; p->name != NULL; p++)
11551592Srgrimes		if (strcmp(cmd, p->name) == 0)
11561592Srgrimes			return (p);
11571592Srgrimes	return (0);
11581592Srgrimes}
11591592Srgrimes
11601592Srgrimes#include <arpa/telnet.h>
11611592Srgrimes
11621592Srgrimes/*
1163299356Sbapt * get_line - a hacked up version of fgets to ignore TELNET escape codes.
11641592Srgrimes */
1165186405Scpercivaint
1166299356Sbaptget_line(char *s, int n, FILE *iop)
11671592Srgrimes{
11681592Srgrimes	int c;
11691592Srgrimes	register char *cs;
1170117352Syar	sigset_t sset, osset;
11711592Srgrimes
11721592Srgrimes	cs = s;
11731592Srgrimes/* tmpline may contain saved command from urgent mode interruption */
11741592Srgrimes	for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
11751592Srgrimes		*cs++ = tmpline[c];
11761592Srgrimes		if (tmpline[c] == '\n') {
11771592Srgrimes			*cs++ = '\0';
117876096Smarkm			if (ftpdebug)
11791592Srgrimes				syslog(LOG_DEBUG, "command: %s", s);
11801592Srgrimes			tmpline[0] = '\0';
1181186405Scperciva			return(0);
11821592Srgrimes		}
11831592Srgrimes		if (c == 0)
11841592Srgrimes			tmpline[0] = '\0';
11851592Srgrimes	}
1186117352Syar	/* SIGURG would interrupt stdio if not blocked during the read loop */
1187117352Syar	sigemptyset(&sset);
1188117352Syar	sigaddset(&sset, SIGURG);
1189117352Syar	sigprocmask(SIG_BLOCK, &sset, &osset);
11901592Srgrimes	while ((c = getc(iop)) != EOF) {
11911592Srgrimes		c &= 0377;
11921592Srgrimes		if (c == IAC) {
1193117351Syar			if ((c = getc(iop)) == EOF)
1194117351Syar				goto got_eof;
11951592Srgrimes			c &= 0377;
11961592Srgrimes			switch (c) {
11971592Srgrimes			case WILL:
11981592Srgrimes			case WONT:
1199117351Syar				if ((c = getc(iop)) == EOF)
1200117351Syar					goto got_eof;
12011592Srgrimes				printf("%c%c%c", IAC, DONT, 0377&c);
12021592Srgrimes				(void) fflush(stdout);
12031592Srgrimes				continue;
12041592Srgrimes			case DO:
12051592Srgrimes			case DONT:
1206117351Syar				if ((c = getc(iop)) == EOF)
1207117351Syar					goto got_eof;
12081592Srgrimes				printf("%c%c%c", IAC, WONT, 0377&c);
12091592Srgrimes				(void) fflush(stdout);
12101592Srgrimes				continue;
12111592Srgrimes			case IAC:
12121592Srgrimes				break;
12131592Srgrimes			default:
12141592Srgrimes				continue;	/* ignore command */
12151592Srgrimes			}
12161592Srgrimes		}
12171592Srgrimes		*cs++ = c;
1218186405Scperciva		if (--n <= 0) {
1219186405Scperciva			/*
1220186405Scperciva			 * If command doesn't fit into buffer, discard the
1221186405Scperciva			 * rest of the command and indicate truncation.
1222186405Scperciva			 * This prevents the command to be split up into
1223186405Scperciva			 * multiple commands.
1224186405Scperciva			 */
1225186405Scperciva			while (c != '\n' && (c = getc(iop)) != EOF)
1226186405Scperciva				;
1227186405Scperciva			return (-2);
1228186405Scperciva		}
1229186405Scperciva		if (c == '\n')
12301592Srgrimes			break;
12311592Srgrimes	}
1232117351Syargot_eof:
1233117352Syar	sigprocmask(SIG_SETMASK, &osset, NULL);
12341592Srgrimes	if (c == EOF && cs == s)
1235186405Scperciva		return (-1);
12361592Srgrimes	*cs++ = '\0';
123776096Smarkm	if (ftpdebug) {
12381592Srgrimes		if (!guest && strncasecmp("pass ", s, 5) == 0) {
12391592Srgrimes			/* Don't syslog passwords */
12401592Srgrimes			syslog(LOG_DEBUG, "command: %.5s ???", s);
12411592Srgrimes		} else {
12421592Srgrimes			register char *cp;
12431592Srgrimes			register int len;
12441592Srgrimes
12451592Srgrimes			/* Don't syslog trailing CR-LF */
12461592Srgrimes			len = strlen(s);
12471592Srgrimes			cp = s + len - 1;
12481592Srgrimes			while (cp >= s && (*cp == '\n' || *cp == '\r')) {
12491592Srgrimes				--cp;
12501592Srgrimes				--len;
12511592Srgrimes			}
12521592Srgrimes			syslog(LOG_DEBUG, "command: %.*s", len, s);
12531592Srgrimes		}
12541592Srgrimes	}
1255186405Scperciva	return (0);
12561592Srgrimes}
12571592Srgrimes
12581592Srgrimesstatic void
125990148Simptoolong(int signo)
12601592Srgrimes{
12611592Srgrimes
12621592Srgrimes	reply(421,
12631592Srgrimes	    "Timeout (%d seconds): closing control connection.", timeout);
12641592Srgrimes	if (logging)
12651592Srgrimes		syslog(LOG_INFO, "User %s timed out after %d seconds",
12661592Srgrimes		    (pw ? pw -> pw_name : "unknown"), timeout);
12671592Srgrimes	dologout(1);
12681592Srgrimes}
12691592Srgrimes
12701592Srgrimesstatic int
127190148Simpyylex(void)
12721592Srgrimes{
127389935Syar	static int cpos;
12741592Srgrimes	char *cp, *cp2;
12751592Srgrimes	struct tab *p;
12761592Srgrimes	int n;
12771592Srgrimes	char c;
12781592Srgrimes
12791592Srgrimes	for (;;) {
12801592Srgrimes		switch (state) {
12811592Srgrimes
12821592Srgrimes		case CMD:
12831592Srgrimes			(void) signal(SIGALRM, toolong);
1284137659Syar			(void) alarm(timeout);
1285299356Sbapt			n = get_line(cbuf, sizeof(cbuf)-1, stdin);
1286186405Scperciva			if (n == -1) {
12871592Srgrimes				reply(221, "You could at least say goodbye.");
12881592Srgrimes				dologout(0);
1289186405Scperciva			} else if (n == -2) {
1290186405Scperciva				reply(500, "Command too long.");
1291186405Scperciva				(void) alarm(0);
1292186405Scperciva				continue;
12931592Srgrimes			}
12941592Srgrimes			(void) alarm(0);
12951592Srgrimes#ifdef SETPROCTITLE
129629574Sphk			if (strncasecmp(cbuf, "PASS", 4) != 0)
12971592Srgrimes				setproctitle("%s: %s", proctitle, cbuf);
12981592Srgrimes#endif /* SETPROCTITLE */
12991592Srgrimes			if ((cp = strchr(cbuf, '\r'))) {
13001592Srgrimes				*cp++ = '\n';
13011592Srgrimes				*cp = '\0';
13021592Srgrimes			}
13031592Srgrimes			if ((cp = strpbrk(cbuf, " \n")))
13041592Srgrimes				cpos = cp - cbuf;
13051592Srgrimes			if (cpos == 0)
13061592Srgrimes				cpos = 4;
13071592Srgrimes			c = cbuf[cpos];
13081592Srgrimes			cbuf[cpos] = '\0';
13091592Srgrimes			upper(cbuf);
13101592Srgrimes			p = lookup(cmdtab, cbuf);
13111592Srgrimes			cbuf[cpos] = c;
13123776Spst			if (p != 0) {
1313102565Syar				yylval.s = p->name;
1314102565Syar				if (!p->implemented)
1315102565Syar					return (NOTIMPL); /* state remains CMD */
13161592Srgrimes				state = p->state;
13171592Srgrimes				return (p->token);
13181592Srgrimes			}
13191592Srgrimes			break;
13201592Srgrimes
13211592Srgrimes		case SITECMD:
13221592Srgrimes			if (cbuf[cpos] == ' ') {
13231592Srgrimes				cpos++;
13241592Srgrimes				return (SP);
13251592Srgrimes			}
13261592Srgrimes			cp = &cbuf[cpos];
13271592Srgrimes			if ((cp2 = strpbrk(cp, " \n")))
13281592Srgrimes				cpos = cp2 - cbuf;
13291592Srgrimes			c = cbuf[cpos];
13301592Srgrimes			cbuf[cpos] = '\0';
13311592Srgrimes			upper(cp);
13321592Srgrimes			p = lookup(sitetab, cp);
13331592Srgrimes			cbuf[cpos] = c;
13343777Spst			if (guest == 0 && p != 0) {
1335102565Syar				yylval.s = p->name;
1336102565Syar				if (!p->implemented) {
13371592Srgrimes					state = CMD;
1338102565Syar					return (NOTIMPL);
13391592Srgrimes				}
13401592Srgrimes				state = p->state;
13411592Srgrimes				return (p->token);
13421592Srgrimes			}
13431592Srgrimes			state = CMD;
13441592Srgrimes			break;
13451592Srgrimes
134675556Sgreen		case ZSTR1:
13471592Srgrimes		case OSTR:
13481592Srgrimes			if (cbuf[cpos] == '\n') {
13491592Srgrimes				state = CMD;
13501592Srgrimes				return (CRLF);
13511592Srgrimes			}
13521592Srgrimes			/* FALLTHROUGH */
13531592Srgrimes
13541592Srgrimes		case STR1:
13551592Srgrimes		dostr1:
13561592Srgrimes			if (cbuf[cpos] == ' ') {
13571592Srgrimes				cpos++;
135851979Salfred				state = state == OSTR ? STR2 : state+1;
13591592Srgrimes				return (SP);
13601592Srgrimes			}
13611592Srgrimes			break;
13621592Srgrimes
13631592Srgrimes		case ZSTR2:
13641592Srgrimes			if (cbuf[cpos] == '\n') {
13651592Srgrimes				state = CMD;
13661592Srgrimes				return (CRLF);
13671592Srgrimes			}
13681592Srgrimes			/* FALLTHROUGH */
13691592Srgrimes
13701592Srgrimes		case STR2:
13711592Srgrimes			cp = &cbuf[cpos];
13721592Srgrimes			n = strlen(cp);
13731592Srgrimes			cpos += n - 1;
13741592Srgrimes			/*
13751592Srgrimes			 * Make sure the string is nonempty and \n terminated.
13761592Srgrimes			 */
13771592Srgrimes			if (n > 1 && cbuf[cpos] == '\n') {
13781592Srgrimes				cbuf[cpos] = '\0';
13791592Srgrimes				yylval.s = copy(cp);
13801592Srgrimes				cbuf[cpos] = '\n';
13811592Srgrimes				state = ARGS;
13821592Srgrimes				return (STRING);
13831592Srgrimes			}
13841592Srgrimes			break;
13851592Srgrimes
13861592Srgrimes		case NSTR:
13871592Srgrimes			if (cbuf[cpos] == ' ') {
13881592Srgrimes				cpos++;
13891592Srgrimes				return (SP);
13901592Srgrimes			}
13911592Srgrimes			if (isdigit(cbuf[cpos])) {
13921592Srgrimes				cp = &cbuf[cpos];
13931592Srgrimes				while (isdigit(cbuf[++cpos]))
13941592Srgrimes					;
13951592Srgrimes				c = cbuf[cpos];
13961592Srgrimes				cbuf[cpos] = '\0';
139792272Smaxim				yylval.u.i = atoi(cp);
13981592Srgrimes				cbuf[cpos] = c;
13991592Srgrimes				state = STR1;
14001592Srgrimes				return (NUMBER);
14011592Srgrimes			}
14021592Srgrimes			state = STR1;
14031592Srgrimes			goto dostr1;
14041592Srgrimes
14051592Srgrimes		case ARGS:
14061592Srgrimes			if (isdigit(cbuf[cpos])) {
14071592Srgrimes				cp = &cbuf[cpos];
14081592Srgrimes				while (isdigit(cbuf[++cpos]))
14091592Srgrimes					;
14101592Srgrimes				c = cbuf[cpos];
14111592Srgrimes				cbuf[cpos] = '\0';
141292272Smaxim				yylval.u.i = atoi(cp);
1413137811Syar				yylval.u.o = strtoull(cp, NULL, 10);
14141592Srgrimes				cbuf[cpos] = c;
14151592Srgrimes				return (NUMBER);
14161592Srgrimes			}
141756668Sshin			if (strncasecmp(&cbuf[cpos], "ALL", 3) == 0
141856668Sshin			 && !isalnum(cbuf[cpos + 3])) {
141956668Sshin				cpos += 3;
142056668Sshin				return ALL;
142156668Sshin			}
14221592Srgrimes			switch (cbuf[cpos++]) {
14231592Srgrimes
14241592Srgrimes			case '\n':
14251592Srgrimes				state = CMD;
14261592Srgrimes				return (CRLF);
14271592Srgrimes
14281592Srgrimes			case ' ':
14291592Srgrimes				return (SP);
14301592Srgrimes
14311592Srgrimes			case ',':
14321592Srgrimes				return (COMMA);
14331592Srgrimes
14341592Srgrimes			case 'A':
14351592Srgrimes			case 'a':
14361592Srgrimes				return (A);
14371592Srgrimes
14381592Srgrimes			case 'B':
14391592Srgrimes			case 'b':
14401592Srgrimes				return (B);
14411592Srgrimes
14421592Srgrimes			case 'C':
14431592Srgrimes			case 'c':
14441592Srgrimes				return (C);
14451592Srgrimes
14461592Srgrimes			case 'E':
14471592Srgrimes			case 'e':
14481592Srgrimes				return (E);
14491592Srgrimes
14501592Srgrimes			case 'F':
14511592Srgrimes			case 'f':
14521592Srgrimes				return (F);
14531592Srgrimes
14541592Srgrimes			case 'I':
14551592Srgrimes			case 'i':
14561592Srgrimes				return (I);
14571592Srgrimes
14581592Srgrimes			case 'L':
14591592Srgrimes			case 'l':
14601592Srgrimes				return (L);
14611592Srgrimes
14621592Srgrimes			case 'N':
14631592Srgrimes			case 'n':
14641592Srgrimes				return (N);
14651592Srgrimes
14661592Srgrimes			case 'P':
14671592Srgrimes			case 'p':
14681592Srgrimes				return (P);
14691592Srgrimes
14701592Srgrimes			case 'R':
14711592Srgrimes			case 'r':
14721592Srgrimes				return (R);
14731592Srgrimes
14741592Srgrimes			case 'S':
14751592Srgrimes			case 's':
14761592Srgrimes				return (S);
14771592Srgrimes
14781592Srgrimes			case 'T':
14791592Srgrimes			case 't':
14801592Srgrimes				return (T);
14811592Srgrimes
14821592Srgrimes			}
14831592Srgrimes			break;
14841592Srgrimes
14851592Srgrimes		default:
148676096Smarkm			fatalerror("Unknown state in scanner.");
14871592Srgrimes		}
14881592Srgrimes		state = CMD;
148989935Syar		return (LEXERR);
14901592Srgrimes	}
14911592Srgrimes}
14921592Srgrimes
14931592Srgrimesvoid
149490148Simpupper(char *s)
14951592Srgrimes{
14961592Srgrimes	while (*s != '\0') {
14971592Srgrimes		if (islower(*s))
14981592Srgrimes			*s = toupper(*s);
14991592Srgrimes		s++;
15001592Srgrimes	}
15011592Srgrimes}
15021592Srgrimes
15031592Srgrimesstatic char *
150490148Simpcopy(char *s)
15051592Srgrimes{
15061592Srgrimes	char *p;
15071592Srgrimes
1508137659Syar	p = malloc(strlen(s) + 1);
15091592Srgrimes	if (p == NULL)
151076096Smarkm		fatalerror("Ran out of memory.");
15111592Srgrimes	(void) strcpy(p, s);
15121592Srgrimes	return (p);
15131592Srgrimes}
15141592Srgrimes
15151592Srgrimesstatic void
151690148Simphelp(struct tab *ctab, char *s)
15171592Srgrimes{
15181592Srgrimes	struct tab *c;
15191592Srgrimes	int width, NCMDS;
15201592Srgrimes	char *type;
15211592Srgrimes
15221592Srgrimes	if (ctab == sitetab)
15231592Srgrimes		type = "SITE ";
15241592Srgrimes	else
15251592Srgrimes		type = "";
15261592Srgrimes	width = 0, NCMDS = 0;
15271592Srgrimes	for (c = ctab; c->name != NULL; c++) {
15281592Srgrimes		int len = strlen(c->name);
15291592Srgrimes
15301592Srgrimes		if (len > width)
15311592Srgrimes			width = len;
15321592Srgrimes		NCMDS++;
15331592Srgrimes	}
15341592Srgrimes	width = (width + 8) &~ 7;
15351592Srgrimes	if (s == 0) {
15361592Srgrimes		int i, j, w;
15371592Srgrimes		int columns, lines;
15381592Srgrimes
15391592Srgrimes		lreply(214, "The following %scommands are recognized %s.",
15401592Srgrimes		    type, "(* =>'s unimplemented)");
15411592Srgrimes		columns = 76 / width;
15421592Srgrimes		if (columns == 0)
15431592Srgrimes			columns = 1;
15441592Srgrimes		lines = (NCMDS + columns - 1) / columns;
15451592Srgrimes		for (i = 0; i < lines; i++) {
15461592Srgrimes			printf("   ");
15471592Srgrimes			for (j = 0; j < columns; j++) {
15481592Srgrimes				c = ctab + j * lines + i;
15491592Srgrimes				printf("%s%c", c->name,
15501592Srgrimes					c->implemented ? ' ' : '*');
15511592Srgrimes				if (c + lines >= &ctab[NCMDS])
15521592Srgrimes					break;
15531592Srgrimes				w = strlen(c->name) + 1;
15541592Srgrimes				while (w < width) {
15551592Srgrimes					putchar(' ');
15561592Srgrimes					w++;
15571592Srgrimes				}
15581592Srgrimes			}
15591592Srgrimes			printf("\r\n");
15601592Srgrimes		}
15611592Srgrimes		(void) fflush(stdout);
1562110037Syar		if (hostinfo)
1563110037Syar			reply(214, "Direct comments to ftp-bugs@%s.", hostname);
1564110037Syar		else
1565110037Syar			reply(214, "End.");
15661592Srgrimes		return;
15671592Srgrimes	}
15681592Srgrimes	upper(s);
15691592Srgrimes	c = lookup(ctab, s);
1570132931Syar	if (c == NULL) {
15711592Srgrimes		reply(502, "Unknown command %s.", s);
15721592Srgrimes		return;
15731592Srgrimes	}
15741592Srgrimes	if (c->implemented)
15751592Srgrimes		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
15761592Srgrimes	else
15771592Srgrimes		reply(214, "%s%-*s\t%s; unimplemented.", type, width,
15781592Srgrimes		    c->name, c->help);
15791592Srgrimes}
15801592Srgrimes
15811592Srgrimesstatic void
158290148Simpsizecmd(char *filename)
15831592Srgrimes{
15841592Srgrimes	switch (type) {
15851592Srgrimes	case TYPE_L:
15861592Srgrimes	case TYPE_I: {
15871592Srgrimes		struct stat stbuf;
158863350Sdes		if (stat(filename, &stbuf) < 0)
158963350Sdes			perror_reply(550, filename);
159063350Sdes		else if (!S_ISREG(stbuf.st_mode))
15911592Srgrimes			reply(550, "%s: not a plain file.", filename);
15921592Srgrimes		else
1593132929Syar			reply(213, "%jd", (intmax_t)stbuf.st_size);
15941592Srgrimes		break; }
15951592Srgrimes	case TYPE_A: {
15961592Srgrimes		FILE *fin;
15971592Srgrimes		int c;
15981592Srgrimes		off_t count;
15991592Srgrimes		struct stat stbuf;
16001592Srgrimes		fin = fopen(filename, "r");
16011592Srgrimes		if (fin == NULL) {
16021592Srgrimes			perror_reply(550, filename);
16031592Srgrimes			return;
16041592Srgrimes		}
160563350Sdes		if (fstat(fileno(fin), &stbuf) < 0) {
160663350Sdes			perror_reply(550, filename);
160763350Sdes			(void) fclose(fin);
160863350Sdes			return;
160963350Sdes		} else if (!S_ISREG(stbuf.st_mode)) {
16101592Srgrimes			reply(550, "%s: not a plain file.", filename);
16111592Srgrimes			(void) fclose(fin);
16121592Srgrimes			return;
1613101034Syar		} else if (stbuf.st_size > MAXASIZE) {
1614101034Syar			reply(550, "%s: too large for type A SIZE.", filename);
1615101034Syar			(void) fclose(fin);
1616101034Syar			return;
16171592Srgrimes		}
16181592Srgrimes
16191592Srgrimes		count = 0;
16201592Srgrimes		while((c=getc(fin)) != EOF) {
16211592Srgrimes			if (c == '\n')	/* will get expanded to \r\n */
16221592Srgrimes				count++;
16231592Srgrimes			count++;
16241592Srgrimes		}
16251592Srgrimes		(void) fclose(fin);
16261592Srgrimes
1627132929Syar		reply(213, "%jd", (intmax_t)count);
16281592Srgrimes		break; }
16291592Srgrimes	default:
1630100684Syar		reply(504, "SIZE not implemented for type %s.",
1631100684Syar		           typenames[type]);
16321592Srgrimes	}
16331592Srgrimes}
163456668Sshin
163556668Sshin/* Return 1, if port check is done. Return 0, if not yet. */
163656668Sshinstatic int
163790148Simpport_check(const char *pcmd)
163856668Sshin{
163956668Sshin	if (his_addr.su_family == AF_INET) {
164056668Sshin		if (data_dest.su_family != AF_INET) {
164156668Sshin			usedefault = 1;
164256668Sshin			reply(500, "Invalid address rejected.");
164356668Sshin			return 1;
164456668Sshin		}
164556668Sshin		if (paranoid &&
164656668Sshin		    ((ntohs(data_dest.su_port) < IPPORT_RESERVED) ||
164756668Sshin		     memcmp(&data_dest.su_sin.sin_addr,
164856668Sshin			    &his_addr.su_sin.sin_addr,
164956668Sshin			    sizeof(data_dest.su_sin.sin_addr)))) {
165056668Sshin			usedefault = 1;
165156668Sshin			reply(500, "Illegal PORT range rejected.");
165256668Sshin		} else {
165356668Sshin			usedefault = 0;
165456668Sshin			if (pdata >= 0) {
165556668Sshin				(void) close(pdata);
165656668Sshin				pdata = -1;
165756668Sshin			}
165856668Sshin			reply(200, "%s command successful.", pcmd);
165956668Sshin		}
166056668Sshin		return 1;
166156668Sshin	}
166256668Sshin	return 0;
166356668Sshin}
166456668Sshin
166570102Sphkstatic int
166690148Simpcheck_login1(void)
166770102Sphk{
166870102Sphk	if (logged_in)
166970102Sphk		return 1;
167070102Sphk	else {
167170102Sphk		reply(530, "Please login with USER and PASS.");
167270102Sphk		return 0;
167370102Sphk	}
167470102Sphk}
167570102Sphk
1676110340Syar/*
1677110340Syar * Replace leading "~user" in a pathname by the user's login directory.
1678110340Syar * Returned string will be in a freshly malloced buffer unless it's NULL.
1679110340Syar */
1680110340Syarstatic char *
1681110340Syarexptilde(char *s)
1682110340Syar{
1683110340Syar	char *p, *q;
1684110340Syar	char *path, *user;
1685110340Syar	struct passwd *ppw;
1686110340Syar
1687110340Syar	if ((p = strdup(s)) == NULL)
1688110340Syar		return (NULL);
1689110340Syar	if (*p != '~')
1690110340Syar		return (p);
1691110340Syar
1692110340Syar	user = p + 1;	/* skip tilde */
1693110340Syar	if ((path = strchr(p, '/')) != NULL)
1694110340Syar		*(path++) = '\0'; /* separate ~user from the rest of path */
1695110378Syar	if (*user == '\0') /* no user specified, use the current user */
1696110378Syar		user = pw->pw_name;
1697110378Syar	/* read passwd even for the current user since we may be chrooted */
1698110378Syar	if ((ppw = getpwnam(user)) != NULL) {
1699110340Syar		/* user found, substitute login directory for ~user */
1700110340Syar		if (path)
1701110340Syar			asprintf(&q, "%s/%s", ppw->pw_dir, path);
1702110340Syar		else
1703110340Syar			q = strdup(ppw->pw_dir);
1704110340Syar		free(p);
1705110340Syar		p = q;
1706110340Syar	} else {
1707110340Syar		/* user not found, undo the damage */
1708110340Syar		if (path)
1709110340Syar			path[-1] = '/';
1710110340Syar	}
1711110340Syar	return (p);
1712110340Syar}
1713110340Syar
1714110340Syar/*
1715110340Syar * Expand glob(3) patterns possibly present in a pathname.
1716110340Syar * Avoid expanding to a pathname including '\r' or '\n' in order to
1717110340Syar * not disrupt the FTP protocol.
1718110340Syar * The expansion found must be unique.
1719229780Suqs * Return the result as a malloced string, or NULL if an error occurred.
1720110340Syar *
1721110340Syar * Problem: this production is used for all pathname
1722110340Syar * processing, but only gives a 550 error reply.
1723110340Syar * This is a valid reply in some cases but not in others.
1724110340Syar */
1725110340Syarstatic char *
1726110340Syarexpglob(char *s)
1727110340Syar{
1728110340Syar	char *p, **pp, *rval;
1729110340Syar	int flags = GLOB_BRACE | GLOB_NOCHECK;
1730110340Syar	int n;
1731110340Syar	glob_t gl;
1732110340Syar
1733110340Syar	memset(&gl, 0, sizeof(gl));
1734110340Syar	flags |= GLOB_LIMIT;
1735110340Syar	gl.gl_matchc = MAXGLOBARGS;
1736110340Syar	if (glob(s, flags, NULL, &gl) == 0 && gl.gl_pathc != 0) {
1737110340Syar		for (pp = gl.gl_pathv, p = NULL, n = 0; *pp; pp++)
1738110340Syar			if (*(*pp + strcspn(*pp, "\r\n")) == '\0') {
1739110340Syar				p = *pp;
1740110340Syar				n++;
1741110340Syar			}
1742110340Syar		if (n == 0)
1743110340Syar			rval = strdup(s);
1744110340Syar		else if (n == 1)
1745110340Syar			rval = strdup(p);
1746110340Syar		else {
1747137852Syar			reply(550, "Wildcard is ambiguous.");
1748110340Syar			rval = NULL;
1749110340Syar		}
1750110340Syar	} else {
1751137852Syar		reply(550, "Wildcard expansion error.");
1752110340Syar		rval = NULL;
1753110340Syar	}
1754110340Syar	globfree(&gl);
1755110340Syar	return (rval);
1756110340Syar}
1757110340Syar
175856668Sshin#ifdef INET6
175956668Sshin/* Return 1, if port check is done. Return 0, if not yet. */
176056668Sshinstatic int
176190148Simpport_check_v6(const char *pcmd)
176256668Sshin{
176356668Sshin	if (his_addr.su_family == AF_INET6) {
176456668Sshin		if (IN6_IS_ADDR_V4MAPPED(&his_addr.su_sin6.sin6_addr))
176556668Sshin			/* Convert data_dest into v4 mapped sockaddr.*/
176656668Sshin			v4map_data_dest();
176756668Sshin		if (data_dest.su_family != AF_INET6) {
176856668Sshin			usedefault = 1;
176956668Sshin			reply(500, "Invalid address rejected.");
177056668Sshin			return 1;
177156668Sshin		}
177256668Sshin		if (paranoid &&
177356668Sshin		    ((ntohs(data_dest.su_port) < IPPORT_RESERVED) ||
177456668Sshin		     memcmp(&data_dest.su_sin6.sin6_addr,
177556668Sshin			    &his_addr.su_sin6.sin6_addr,
177656668Sshin			    sizeof(data_dest.su_sin6.sin6_addr)))) {
177756668Sshin			usedefault = 1;
177856668Sshin			reply(500, "Illegal PORT range rejected.");
177956668Sshin		} else {
178056668Sshin			usedefault = 0;
178156668Sshin			if (pdata >= 0) {
178256668Sshin				(void) close(pdata);
178356668Sshin				pdata = -1;
178456668Sshin			}
178556668Sshin			reply(200, "%s command successful.", pcmd);
178656668Sshin		}
178756668Sshin		return 1;
178856668Sshin	}
178956668Sshin	return 0;
179056668Sshin}
179156668Sshin
179256668Sshinstatic void
179390148Simpv4map_data_dest(void)
179456668Sshin{
179556668Sshin	struct in_addr savedaddr;
179656668Sshin	int savedport;
179756668Sshin
179856668Sshin	if (data_dest.su_family != AF_INET) {
179956668Sshin		usedefault = 1;
180056668Sshin		reply(500, "Invalid address rejected.");
180156668Sshin		return;
180256668Sshin	}
180356668Sshin
180456668Sshin	savedaddr = data_dest.su_sin.sin_addr;
180556668Sshin	savedport = data_dest.su_port;
180656668Sshin
180756668Sshin	memset(&data_dest, 0, sizeof(data_dest));
180856668Sshin	data_dest.su_sin6.sin6_len = sizeof(struct sockaddr_in6);
180956668Sshin	data_dest.su_sin6.sin6_family = AF_INET6;
181056668Sshin	data_dest.su_sin6.sin6_port = savedport;
181156668Sshin	memset((caddr_t)&data_dest.su_sin6.sin6_addr.s6_addr[10], 0xff, 2);
181256668Sshin	memcpy((caddr_t)&data_dest.su_sin6.sin6_addr.s6_addr[12],
181356668Sshin	       (caddr_t)&savedaddr, sizeof(savedaddr));
181456668Sshin}
181556668Sshin#endif
1816