ftpcmd.y revision 137811
11592Srgrimes/*
21592Srgrimes * Copyright (c) 1985, 1988, 1993, 1994
31592Srgrimes *	The Regents of the University of California.  All rights reserved.
41592Srgrimes *
51592Srgrimes * Redistribution and use in source and binary forms, with or without
61592Srgrimes * modification, are permitted provided that the following conditions
71592Srgrimes * are met:
81592Srgrimes * 1. Redistributions of source code must retain the above copyright
91592Srgrimes *    notice, this list of conditions and the following disclaimer.
101592Srgrimes * 2. Redistributions in binary form must reproduce the above copyright
111592Srgrimes *    notice, this list of conditions and the following disclaimer in the
121592Srgrimes *    documentation and/or other materials provided with the distribution.
131592Srgrimes * 3. All advertising materials mentioning features or use of this software
141592Srgrimes *    must display the following acknowledgement:
151592Srgrimes *	This product includes software developed by the University of
161592Srgrimes *	California, Berkeley and its contributors.
171592Srgrimes * 4. Neither the name of the University nor the names of its contributors
181592Srgrimes *    may be used to endorse or promote products derived from this software
191592Srgrimes *    without specific prior written permission.
201592Srgrimes *
211592Srgrimes * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
221592Srgrimes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
231592Srgrimes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
241592Srgrimes * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
251592Srgrimes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
261592Srgrimes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
271592Srgrimes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
281592Srgrimes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
291592Srgrimes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
301592Srgrimes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
311592Srgrimes * SUCH DAMAGE.
321592Srgrimes *
331592Srgrimes *	@(#)ftpcmd.y	8.3 (Berkeley) 4/6/94
341592Srgrimes */
351592Srgrimes
361592Srgrimes/*
371592Srgrimes * Grammar for FTP commands.
381592Srgrimes * See RFC 959.
391592Srgrimes */
401592Srgrimes
411592Srgrimes%{
421592Srgrimes
431592Srgrimes#ifndef lint
4431329Scharnier#if 0
451592Srgrimesstatic char sccsid[] = "@(#)ftpcmd.y	8.3 (Berkeley) 4/6/94";
4631329Scharnier#endif
4731329Scharnierstatic const char rcsid[] =
4850476Speter  "$FreeBSD: head/libexec/ftpd/ftpcmd.y 137811 2004-11-17 11:52:41Z yar $";
491592Srgrimes#endif /* not lint */
501592Srgrimes
511592Srgrimes#include <sys/param.h>
521592Srgrimes#include <sys/socket.h>
531592Srgrimes#include <sys/stat.h>
541592Srgrimes
551592Srgrimes#include <netinet/in.h>
561592Srgrimes#include <arpa/ftp.h>
571592Srgrimes
581592Srgrimes#include <ctype.h>
591592Srgrimes#include <errno.h>
601592Srgrimes#include <glob.h>
6192090Smaxim#include <libutil.h>
6292272Smaxim#include <limits.h>
6392090Smaxim#include <md5.h>
6456668Sshin#include <netdb.h>
651592Srgrimes#include <pwd.h>
661592Srgrimes#include <signal.h>
67132929Syar#include <stdint.h>
681592Srgrimes#include <stdio.h>
691592Srgrimes#include <stdlib.h>
701592Srgrimes#include <string.h>
711592Srgrimes#include <syslog.h>
721592Srgrimes#include <time.h>
731592Srgrimes#include <unistd.h>
741592Srgrimes
751592Srgrimes#include "extern.h"
76109380Syar#include "pathnames.h"
771592Srgrimes
7856668Sshinextern	union sockunion data_dest, his_addr;
79110037Syarextern	int hostinfo;
801592Srgrimesextern	int logged_in;
811592Srgrimesextern	struct passwd *pw;
821592Srgrimesextern	int guest;
83110036Syarextern	char *homedir;
8417435Spstextern 	int paranoid;
851592Srgrimesextern	int logging;
861592Srgrimesextern	int type;
871592Srgrimesextern	int form;
8876096Smarkmextern	int ftpdebug;
891592Srgrimesextern	int timeout;
901592Srgrimesextern	int maxtimeout;
911592Srgrimesextern  int pdata;
9227650Sdavidnextern	char *hostname;
931592Srgrimesextern	char proctitle[];
941592Srgrimesextern	int usedefault;
951592Srgrimesextern  char tmpline[];
9670102Sphkextern	int readonly;
9770102Sphkextern	int noepsv;
9882460Snikextern	int noretr;
9982796Ssheldonhextern	int noguestretr;
100100684Syarextern	char *typenames[]; /* defined in <arpa/ftp.h> included from ftpd.c */
1011592Srgrimes
1021592Srgrimesoff_t	restart_point;
1031592Srgrimes
1041592Srgrimesstatic	int cmd_type;
1051592Srgrimesstatic	int cmd_form;
1061592Srgrimesstatic	int cmd_bytesz;
10789935Syarstatic	int state;
1081592Srgrimeschar	cbuf[512];
109132931Syarchar	*fromname = NULL;
1101592Srgrimes
11156668Sshinextern int epsvall;
11256668Sshin
1131592Srgrimes%}
1141592Srgrimes
1151592Srgrimes%union {
11692272Smaxim	struct {
11792272Smaxim		off_t	o;
11892272Smaxim		int	i;
11992272Smaxim	} u;
1201592Srgrimes	char   *s;
1211592Srgrimes}
1221592Srgrimes
1231592Srgrimes%token
1241592Srgrimes	A	B	C	E	F	I
1251592Srgrimes	L	N	P	R	S	T
12656668Sshin	ALL
1271592Srgrimes
1281592Srgrimes	SP	CRLF	COMMA
1291592Srgrimes
1301592Srgrimes	USER	PASS	ACCT	REIN	QUIT	PORT
1311592Srgrimes	PASV	TYPE	STRU	MODE	RETR	STOR
1321592Srgrimes	APPE	MLFL	MAIL	MSND	MSOM	MSAM
1331592Srgrimes	MRSQ	MRCP	ALLO	REST	RNFR	RNTO
1341592Srgrimes	ABOR	DELE	CWD	LIST	NLST	SITE
1351592Srgrimes	STAT	HELP	NOOP	MKD	RMD	PWD
1361592Srgrimes	CDUP	STOU	SMNT	SYST	SIZE	MDTM
13756668Sshin	LPRT	LPSV	EPRT	EPSV
1381592Srgrimes
13975535Sphk	UMASK	IDLE	CHMOD	MDFIVE
1401592Srgrimes
141102565Syar	LEXERR	NOTIMPL
1421592Srgrimes
1431592Srgrimes%token	<s> STRING
14492272Smaxim%token	<u> NUMBER
1451592Srgrimes
14692272Smaxim%type	<u.i> check_login octal_number byte_size
14792272Smaxim%type	<u.i> check_login_ro check_login_epsv
14892272Smaxim%type	<u.i> struct_code mode_code type_code form_code
14975567Speter%type	<s> pathstring pathname password username
150102565Syar%type	<s> ALL NOTIMPL
1511592Srgrimes
1521592Srgrimes%start	cmd_list
1531592Srgrimes
1541592Srgrimes%%
1551592Srgrimes
1561592Srgrimescmd_list
1571592Srgrimes	: /* empty */
1581592Srgrimes	| cmd_list cmd
1591592Srgrimes		{
16088935Sdwmalone			if (fromname)
16188935Sdwmalone				free(fromname);
162132931Syar			fromname = NULL;
163132930Syar			restart_point = 0;
1641592Srgrimes		}
1651592Srgrimes	| cmd_list rcmd
1661592Srgrimes	;
1671592Srgrimes
1681592Srgrimescmd
1691592Srgrimes	: USER SP username CRLF
1701592Srgrimes		{
1711592Srgrimes			user($3);
1721592Srgrimes			free($3);
1731592Srgrimes		}
1741592Srgrimes	| PASS SP password CRLF
1751592Srgrimes		{
1761592Srgrimes			pass($3);
1771592Srgrimes			free($3);
1781592Srgrimes		}
17975556Sgreen	| PASS CRLF
18075556Sgreen		{
18175556Sgreen			pass("");
18275556Sgreen		}
18317433Spst	| PORT check_login SP host_port CRLF
1841592Srgrimes		{
18556668Sshin			if (epsvall) {
18656668Sshin				reply(501, "no PORT allowed after EPSV ALL");
18756668Sshin				goto port_done;
18856668Sshin			}
18956668Sshin			if (!$2)
19056668Sshin				goto port_done;
19156668Sshin			if (port_check("PORT") == 1)
19256668Sshin				goto port_done;
19356668Sshin#ifdef INET6
19456668Sshin			if ((his_addr.su_family != AF_INET6 ||
19556668Sshin			     !IN6_IS_ADDR_V4MAPPED(&his_addr.su_sin6.sin6_addr))) {
19656668Sshin				/* shoud never happen */
19756668Sshin				usedefault = 1;
19856668Sshin				reply(500, "Invalid address rejected.");
19956668Sshin				goto port_done;
20056668Sshin			}
20156668Sshin			port_check_v6("pcmd");
20256668Sshin#endif
20356668Sshin		port_done:
204132925Syar			;
20556668Sshin		}
20656668Sshin	| LPRT check_login SP host_long_port CRLF
20756668Sshin		{
20856668Sshin			if (epsvall) {
20956668Sshin				reply(501, "no LPRT allowed after EPSV ALL");
21056668Sshin				goto lprt_done;
21156668Sshin			}
21256668Sshin			if (!$2)
21356668Sshin				goto lprt_done;
21456668Sshin			if (port_check("LPRT") == 1)
21556668Sshin				goto lprt_done;
21656668Sshin#ifdef INET6
21756668Sshin			if (his_addr.su_family != AF_INET6) {
21856668Sshin				usedefault = 1;
21956668Sshin				reply(500, "Invalid address rejected.");
22056668Sshin				goto lprt_done;
22156668Sshin			}
22256668Sshin			if (port_check_v6("LPRT") == 1)
22356668Sshin				goto lprt_done;
22456668Sshin#endif
22556668Sshin		lprt_done:
226132925Syar			;
22756668Sshin		}
22856668Sshin	| EPRT check_login SP STRING CRLF
22956668Sshin		{
23056668Sshin			char delim;
23156668Sshin			char *tmp = NULL;
23256668Sshin			char *p, *q;
23356668Sshin			char *result[3];
23456668Sshin			struct addrinfo hints;
23556668Sshin			struct addrinfo *res;
23656668Sshin			int i;
23756668Sshin
23856668Sshin			if (epsvall) {
23956668Sshin				reply(501, "no EPRT allowed after EPSV ALL");
24056668Sshin				goto eprt_done;
24156668Sshin			}
24256668Sshin			if (!$2)
24356668Sshin				goto eprt_done;
24456668Sshin
24556668Sshin			memset(&data_dest, 0, sizeof(data_dest));
24656668Sshin			tmp = strdup($4);
24776096Smarkm			if (ftpdebug)
24856668Sshin				syslog(LOG_DEBUG, "%s", tmp);
24956668Sshin			if (!tmp) {
25076096Smarkm				fatalerror("not enough core");
25156668Sshin				/*NOTREACHED*/
25256668Sshin			}
25356668Sshin			p = tmp;
25456668Sshin			delim = p[0];
25556668Sshin			p++;
25656668Sshin			memset(result, 0, sizeof(result));
25756668Sshin			for (i = 0; i < 3; i++) {
25856668Sshin				q = strchr(p, delim);
25956668Sshin				if (!q || *q != delim) {
26056668Sshin		parsefail:
26156668Sshin					reply(500,
26256668Sshin						"Invalid argument, rejected.");
26356668Sshin					if (tmp)
26456668Sshin						free(tmp);
26517433Spst					usedefault = 1;
26656668Sshin					goto eprt_done;
26717433Spst				}
26856668Sshin				*q++ = '\0';
26956668Sshin				result[i] = p;
27076096Smarkm				if (ftpdebug)
27156668Sshin					syslog(LOG_DEBUG, "%d: %s", i, p);
27256668Sshin				p = q;
2731592Srgrimes			}
27456668Sshin
27556668Sshin			/* some more sanity check */
27656668Sshin			p = result[0];
27756668Sshin			while (*p) {
27856668Sshin				if (!isdigit(*p))
27956668Sshin					goto parsefail;
28056668Sshin				p++;
28156668Sshin			}
28256668Sshin			p = result[2];
28356668Sshin			while (*p) {
28456668Sshin				if (!isdigit(*p))
28556668Sshin					goto parsefail;
28656668Sshin				p++;
28756668Sshin			}
28856668Sshin
28956668Sshin			/* grab address */
29056668Sshin			memset(&hints, 0, sizeof(hints));
29156668Sshin			if (atoi(result[0]) == 1)
29256668Sshin				hints.ai_family = PF_INET;
29356668Sshin#ifdef INET6
29456668Sshin			else if (atoi(result[0]) == 2)
29556668Sshin				hints.ai_family = PF_INET6;
29656668Sshin#endif
29756668Sshin			else
29856668Sshin				hints.ai_family = PF_UNSPEC;	/*XXX*/
29956668Sshin			hints.ai_socktype = SOCK_STREAM;
30056668Sshin			i = getaddrinfo(result[1], result[2], &hints, &res);
30156668Sshin			if (i)
30256668Sshin				goto parsefail;
30356668Sshin			memcpy(&data_dest, res->ai_addr, res->ai_addrlen);
30456668Sshin#ifdef INET6
30556668Sshin			if (his_addr.su_family == AF_INET6
30656668Sshin			    && data_dest.su_family == AF_INET6) {
30756668Sshin				/* XXX more sanity checks! */
30856668Sshin				data_dest.su_sin6.sin6_scope_id =
30956668Sshin					his_addr.su_sin6.sin6_scope_id;
31056668Sshin			}
31156668Sshin#endif
31256668Sshin			free(tmp);
31356668Sshin			tmp = NULL;
31456668Sshin
31556668Sshin			if (port_check("EPRT") == 1)
31656668Sshin				goto eprt_done;
31756668Sshin#ifdef INET6
31856668Sshin			if (his_addr.su_family != AF_INET6) {
31956668Sshin				usedefault = 1;
32056668Sshin				reply(500, "Invalid address rejected.");
32156668Sshin				goto eprt_done;
32256668Sshin			}
32356668Sshin			if (port_check_v6("EPRT") == 1)
32456668Sshin				goto eprt_done;
32556668Sshin#endif
32688935Sdwmalone		eprt_done:
32788935Sdwmalone			free($4);
3281592Srgrimes		}
32917433Spst	| PASV check_login CRLF
3301592Srgrimes		{
33156668Sshin			if (epsvall)
33256668Sshin				reply(501, "no PASV allowed after EPSV ALL");
33356668Sshin			else if ($2)
33417433Spst				passive();
3351592Srgrimes		}
33656668Sshin	| LPSV check_login CRLF
33756668Sshin		{
33856668Sshin			if (epsvall)
33956668Sshin				reply(501, "no LPSV allowed after EPSV ALL");
34056668Sshin			else if ($2)
34156668Sshin				long_passive("LPSV", PF_UNSPEC);
34256668Sshin		}
34370102Sphk	| EPSV check_login_epsv SP NUMBER CRLF
34456668Sshin		{
34556668Sshin			if ($2) {
34656668Sshin				int pf;
34792272Smaxim				switch ($4.i) {
34856668Sshin				case 1:
34956668Sshin					pf = PF_INET;
35056668Sshin					break;
35156668Sshin#ifdef INET6
35256668Sshin				case 2:
35356668Sshin					pf = PF_INET6;
35456668Sshin					break;
35556668Sshin#endif
35656668Sshin				default:
35756668Sshin					pf = -1;	/*junk value*/
35856668Sshin					break;
35956668Sshin				}
36056668Sshin				long_passive("EPSV", pf);
36156668Sshin			}
36256668Sshin		}
36370102Sphk	| EPSV check_login_epsv SP ALL CRLF
36456668Sshin		{
36556668Sshin			if ($2) {
36656668Sshin				reply(200,
36756668Sshin				      "EPSV ALL command successful.");
36856668Sshin				epsvall++;
36956668Sshin			}
37056668Sshin		}
37170102Sphk	| EPSV check_login_epsv CRLF
37256668Sshin		{
37356668Sshin			if ($2)
37456668Sshin				long_passive("EPSV", PF_UNSPEC);
37556668Sshin		}
37671278Sjedgar	| TYPE check_login SP type_code CRLF
3771592Srgrimes		{
37871278Sjedgar			if ($2) {
37971278Sjedgar				switch (cmd_type) {
3801592Srgrimes
38171278Sjedgar				case TYPE_A:
38271278Sjedgar					if (cmd_form == FORM_N) {
38371278Sjedgar						reply(200, "Type set to A.");
38471278Sjedgar						type = cmd_type;
38571278Sjedgar						form = cmd_form;
38671278Sjedgar					} else
38771278Sjedgar						reply(504, "Form must be N.");
38871278Sjedgar					break;
3891592Srgrimes
39071278Sjedgar				case TYPE_E:
39171278Sjedgar					reply(504, "Type E not implemented.");
39271278Sjedgar					break;
3931592Srgrimes
39471278Sjedgar				case TYPE_I:
39571278Sjedgar					reply(200, "Type set to I.");
39671278Sjedgar					type = cmd_type;
39771278Sjedgar					break;
3981592Srgrimes
39971278Sjedgar				case TYPE_L:
400103949Smike#if CHAR_BIT == 8
40171278Sjedgar					if (cmd_bytesz == 8) {
40271278Sjedgar						reply(200,
40371278Sjedgar						    "Type set to L (byte size 8).");
40471278Sjedgar						type = cmd_type;
40571278Sjedgar					} else
40671278Sjedgar						reply(504, "Byte size must be 8.");
407103949Smike#else /* CHAR_BIT == 8 */
408103949Smike					UNIMPLEMENTED for CHAR_BIT != 8
409103949Smike#endif /* CHAR_BIT == 8 */
41071278Sjedgar				}
4111592Srgrimes			}
4121592Srgrimes		}
41371278Sjedgar	| STRU check_login SP struct_code CRLF
4141592Srgrimes		{
41571278Sjedgar			if ($2) {
41671278Sjedgar				switch ($4) {
4171592Srgrimes
41871278Sjedgar				case STRU_F:
41971278Sjedgar					reply(200, "STRU F ok.");
42071278Sjedgar					break;
4211592Srgrimes
42271278Sjedgar				default:
42371278Sjedgar					reply(504, "Unimplemented STRU type.");
42471278Sjedgar				}
4251592Srgrimes			}
4261592Srgrimes		}
42771278Sjedgar	| MODE check_login SP mode_code CRLF
4281592Srgrimes		{
42971278Sjedgar			if ($2) {
43071278Sjedgar				switch ($4) {
4311592Srgrimes
43271278Sjedgar				case MODE_S:
43371278Sjedgar					reply(200, "MODE S ok.");
43471278Sjedgar					break;
43571278Sjedgar
43671278Sjedgar				default:
43771278Sjedgar					reply(502, "Unimplemented MODE type.");
43871278Sjedgar				}
4391592Srgrimes			}
4401592Srgrimes		}
44171278Sjedgar	| ALLO check_login SP NUMBER CRLF
4421592Srgrimes		{
44371278Sjedgar			if ($2) {
44471278Sjedgar				reply(202, "ALLO command ignored.");
44571278Sjedgar			}
4461592Srgrimes		}
44771278Sjedgar	| ALLO check_login SP NUMBER SP R SP NUMBER CRLF
4481592Srgrimes		{
44971278Sjedgar			if ($2) {
45071278Sjedgar				reply(202, "ALLO command ignored.");
45171278Sjedgar			}
4521592Srgrimes		}
4531592Srgrimes	| RETR check_login SP pathname CRLF
4541592Srgrimes		{
45582796Ssheldonh			if (noretr || (guest && noguestretr))
45682460Snik				reply(500, "RETR command is disabled");
45782460Snik			else if ($2 && $4 != NULL)
458132931Syar				retrieve(NULL, $4);
45982460Snik
4601592Srgrimes			if ($4 != NULL)
4611592Srgrimes				free($4);
4621592Srgrimes		}
46370102Sphk	| STOR check_login_ro SP pathname CRLF
4641592Srgrimes		{
4651592Srgrimes			if ($2 && $4 != NULL)
4661592Srgrimes				store($4, "w", 0);
4671592Srgrimes			if ($4 != NULL)
4681592Srgrimes				free($4);
4691592Srgrimes		}
47070102Sphk	| APPE check_login_ro SP pathname CRLF
4711592Srgrimes		{
4721592Srgrimes			if ($2 && $4 != NULL)
4731592Srgrimes				store($4, "a", 0);
4741592Srgrimes			if ($4 != NULL)
4751592Srgrimes				free($4);
4761592Srgrimes		}
4771592Srgrimes	| NLST check_login CRLF
4781592Srgrimes		{
4791592Srgrimes			if ($2)
4801592Srgrimes				send_file_list(".");
4811592Srgrimes		}
482101395Syar	| NLST check_login SP pathstring CRLF
4831592Srgrimes		{
484101395Syar			if ($2)
4851592Srgrimes				send_file_list($4);
486101395Syar			free($4);
4871592Srgrimes		}
4881592Srgrimes	| LIST check_login CRLF
4891592Srgrimes		{
4901592Srgrimes			if ($2)
491109380Syar				retrieve(_PATH_LS " -lgA", "");
4921592Srgrimes		}
49375567Speter	| LIST check_login SP pathstring CRLF
4941592Srgrimes		{
495101395Syar			if ($2)
496109380Syar				retrieve(_PATH_LS " -lgA %s", $4);
497101395Syar			free($4);
4981592Srgrimes		}
4991592Srgrimes	| STAT check_login SP pathname CRLF
5001592Srgrimes		{
5011592Srgrimes			if ($2 && $4 != NULL)
5021592Srgrimes				statfilecmd($4);
5031592Srgrimes			if ($4 != NULL)
5041592Srgrimes				free($4);
5051592Srgrimes		}
50671278Sjedgar	| STAT check_login CRLF
5071592Srgrimes		{
50871278Sjedgar			if ($2) {
50971278Sjedgar				statcmd();
51071278Sjedgar			}
5111592Srgrimes		}
51270102Sphk	| DELE check_login_ro SP pathname CRLF
5131592Srgrimes		{
5141592Srgrimes			if ($2 && $4 != NULL)
5151592Srgrimes				delete($4);
5161592Srgrimes			if ($4 != NULL)
5171592Srgrimes				free($4);
5181592Srgrimes		}
51970102Sphk	| RNTO check_login_ro SP pathname CRLF
5201592Srgrimes		{
521101379Syar			if ($2 && $4 != NULL) {
52217433Spst				if (fromname) {
52317433Spst					renamecmd(fromname, $4);
52417433Spst					free(fromname);
525132931Syar					fromname = NULL;
52617433Spst				} else {
52717433Spst					reply(503, "Bad sequence of commands.");
52817433Spst				}
5291592Srgrimes			}
530101379Syar			if ($4 != NULL)
531101379Syar				free($4);
5321592Srgrimes		}
53371278Sjedgar	| ABOR check_login CRLF
5341592Srgrimes		{
53571278Sjedgar			if ($2)
53671278Sjedgar				reply(225, "ABOR command successful.");
5371592Srgrimes		}
5381592Srgrimes	| CWD check_login CRLF
5391592Srgrimes		{
54069234Sdanny			if ($2) {
541110036Syar				cwd(homedir);
54269234Sdanny			}
5431592Srgrimes		}
5441592Srgrimes	| CWD check_login SP pathname CRLF
5451592Srgrimes		{
5461592Srgrimes			if ($2 && $4 != NULL)
5471592Srgrimes				cwd($4);
5481592Srgrimes			if ($4 != NULL)
5491592Srgrimes				free($4);
5501592Srgrimes		}
5511592Srgrimes	| HELP CRLF
5521592Srgrimes		{
553132931Syar			help(cmdtab, NULL);
5541592Srgrimes		}
5551592Srgrimes	| HELP SP STRING CRLF
5561592Srgrimes		{
5571592Srgrimes			char *cp = $3;
5581592Srgrimes
5591592Srgrimes			if (strncasecmp(cp, "SITE", 4) == 0) {
5601592Srgrimes				cp = $3 + 4;
5611592Srgrimes				if (*cp == ' ')
5621592Srgrimes					cp++;
5631592Srgrimes				if (*cp)
5641592Srgrimes					help(sitetab, cp);
5651592Srgrimes				else
566132931Syar					help(sitetab, NULL);
5671592Srgrimes			} else
5681592Srgrimes				help(cmdtab, $3);
56988935Sdwmalone			free($3);
5701592Srgrimes		}
5711592Srgrimes	| NOOP CRLF
5721592Srgrimes		{
5731592Srgrimes			reply(200, "NOOP command successful.");
5741592Srgrimes		}
57570102Sphk	| MKD check_login_ro SP pathname CRLF
5761592Srgrimes		{
5771592Srgrimes			if ($2 && $4 != NULL)
5781592Srgrimes				makedir($4);
5791592Srgrimes			if ($4 != NULL)
5801592Srgrimes				free($4);
5811592Srgrimes		}
58270102Sphk	| RMD check_login_ro SP pathname CRLF
5831592Srgrimes		{
5841592Srgrimes			if ($2 && $4 != NULL)
5851592Srgrimes				removedir($4);
5861592Srgrimes			if ($4 != NULL)
5871592Srgrimes				free($4);
5881592Srgrimes		}
5891592Srgrimes	| PWD check_login CRLF
5901592Srgrimes		{
5911592Srgrimes			if ($2)
5921592Srgrimes				pwd();
5931592Srgrimes		}
5941592Srgrimes	| CDUP check_login CRLF
5951592Srgrimes		{
5961592Srgrimes			if ($2)
5971592Srgrimes				cwd("..");
5981592Srgrimes		}
5991592Srgrimes	| SITE SP HELP CRLF
6001592Srgrimes		{
601132931Syar			help(sitetab, NULL);
6021592Srgrimes		}
6031592Srgrimes	| SITE SP HELP SP STRING CRLF
6041592Srgrimes		{
6051592Srgrimes			help(sitetab, $5);
60688935Sdwmalone			free($5);
6071592Srgrimes		}
60875535Sphk	| SITE SP MDFIVE check_login SP pathname CRLF
60975535Sphk		{
61075535Sphk			char p[64], *q;
61175535Sphk
612101379Syar			if ($4 && $6) {
61375535Sphk				q = MD5File($6, p);
61475535Sphk				if (q != NULL)
61575535Sphk					reply(200, "MD5(%s) = %s", $6, p);
61675535Sphk				else
61775535Sphk					perror_reply(550, $6);
61875535Sphk			}
61988935Sdwmalone			if ($6)
62088935Sdwmalone				free($6);
62175535Sphk		}
6221592Srgrimes	| SITE SP UMASK check_login CRLF
6231592Srgrimes		{
6241592Srgrimes			int oldmask;
6251592Srgrimes
6261592Srgrimes			if ($4) {
6271592Srgrimes				oldmask = umask(0);
6281592Srgrimes				(void) umask(oldmask);
6291592Srgrimes				reply(200, "Current UMASK is %03o", oldmask);
6301592Srgrimes			}
6311592Srgrimes		}
6321592Srgrimes	| SITE SP UMASK check_login SP octal_number CRLF
6331592Srgrimes		{
6341592Srgrimes			int oldmask;
6351592Srgrimes
6361592Srgrimes			if ($4) {
6371592Srgrimes				if (($6 == -1) || ($6 > 0777)) {
6381592Srgrimes					reply(501, "Bad UMASK value");
6391592Srgrimes				} else {
6401592Srgrimes					oldmask = umask($6);
6411592Srgrimes					reply(200,
6421592Srgrimes					    "UMASK set to %03o (was %03o)",
6431592Srgrimes					    $6, oldmask);
6441592Srgrimes				}
6451592Srgrimes			}
6461592Srgrimes		}
64770102Sphk	| SITE SP CHMOD check_login_ro SP octal_number SP pathname CRLF
6481592Srgrimes		{
6491592Srgrimes			if ($4 && ($8 != NULL)) {
650101378Syar				if (($6 == -1 ) || ($6 > 0777))
651101378Syar					reply(501, "Bad mode value");
6521592Srgrimes				else if (chmod($8, $6) < 0)
6531592Srgrimes					perror_reply(550, $8);
6541592Srgrimes				else
6551592Srgrimes					reply(200, "CHMOD command successful.");
6561592Srgrimes			}
6571592Srgrimes			if ($8 != NULL)
6581592Srgrimes				free($8);
6591592Srgrimes		}
66071278Sjedgar	| SITE SP check_login IDLE CRLF
6611592Srgrimes		{
66271278Sjedgar			if ($3)
66371278Sjedgar				reply(200,
66471278Sjedgar			    	    "Current IDLE time limit is %d seconds; max %d",
66571278Sjedgar				    timeout, maxtimeout);
6661592Srgrimes		}
66771278Sjedgar	| SITE SP check_login IDLE SP NUMBER CRLF
6681592Srgrimes		{
66971278Sjedgar			if ($3) {
67092272Smaxim				if ($6.i < 30 || $6.i > maxtimeout) {
67171278Sjedgar					reply(501,
67271278Sjedgar					    "Maximum IDLE time must be between 30 and %d seconds",
67371278Sjedgar					    maxtimeout);
67471278Sjedgar				} else {
67592272Smaxim					timeout = $6.i;
676137659Syar					(void) alarm(timeout);
67771278Sjedgar					reply(200,
67871278Sjedgar					    "Maximum IDLE time set to %d seconds",
67971278Sjedgar					    timeout);
68071278Sjedgar				}
6811592Srgrimes			}
6821592Srgrimes		}
68370102Sphk	| STOU check_login_ro SP pathname CRLF
6841592Srgrimes		{
6851592Srgrimes			if ($2 && $4 != NULL)
6861592Srgrimes				store($4, "w", 1);
6871592Srgrimes			if ($4 != NULL)
6881592Srgrimes				free($4);
6891592Srgrimes		}
69071278Sjedgar	| SYST check_login CRLF
6911592Srgrimes		{
692116439Syar			if ($2) {
693116439Syar				if (hostinfo)
6941592Srgrimes#ifdef BSD
695116439Syar					reply(215, "UNIX Type: L%d Version: BSD-%d",
696116439Syar					      CHAR_BIT, BSD);
6971592Srgrimes#else /* BSD */
698116439Syar					reply(215, "UNIX Type: L%d", CHAR_BIT);
6991592Srgrimes#endif /* BSD */
700116439Syar				else
701116439Syar					reply(215, "UNKNOWN Type: L%d", CHAR_BIT);
702116439Syar			}
7031592Srgrimes		}
7041592Srgrimes
7051592Srgrimes		/*
7061592Srgrimes		 * SIZE is not in RFC959, but Postel has blessed it and
7071592Srgrimes		 * it will be in the updated RFC.
7081592Srgrimes		 *
7091592Srgrimes		 * Return size of file in a format suitable for
7101592Srgrimes		 * using with RESTART (we just count bytes).
7111592Srgrimes		 */
7121592Srgrimes	| SIZE check_login SP pathname CRLF
7131592Srgrimes		{
7141592Srgrimes			if ($2 && $4 != NULL)
7151592Srgrimes				sizecmd($4);
7161592Srgrimes			if ($4 != NULL)
7171592Srgrimes				free($4);
7181592Srgrimes		}
7191592Srgrimes
7201592Srgrimes		/*
7211592Srgrimes		 * MDTM is not in RFC959, but Postel has blessed it and
7221592Srgrimes		 * it will be in the updated RFC.
7231592Srgrimes		 *
7241592Srgrimes		 * Return modification time of file as an ISO 3307
7251592Srgrimes		 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
7261592Srgrimes		 * where xxx is the fractional second (of any precision,
7271592Srgrimes		 * not necessarily 3 digits)
7281592Srgrimes		 */
7291592Srgrimes	| MDTM check_login SP pathname CRLF
7301592Srgrimes		{
7311592Srgrimes			if ($2 && $4 != NULL) {
7321592Srgrimes				struct stat stbuf;
7331592Srgrimes				if (stat($4, &stbuf) < 0)
7341592Srgrimes					reply(550, "%s: %s",
7351592Srgrimes					    $4, strerror(errno));
7361592Srgrimes				else if (!S_ISREG(stbuf.st_mode)) {
7371592Srgrimes					reply(550, "%s: not a plain file.", $4);
7381592Srgrimes				} else {
7391592Srgrimes					struct tm *t;
7401592Srgrimes					t = gmtime(&stbuf.st_mtime);
7411592Srgrimes					reply(213,
74217435Spst					    "%04d%02d%02d%02d%02d%02d",
74317435Spst					    1900 + t->tm_year,
74417435Spst					    t->tm_mon+1, t->tm_mday,
7451592Srgrimes					    t->tm_hour, t->tm_min, t->tm_sec);
7461592Srgrimes				}
7471592Srgrimes			}
7481592Srgrimes			if ($4 != NULL)
7491592Srgrimes				free($4);
7501592Srgrimes		}
7511592Srgrimes	| QUIT CRLF
7521592Srgrimes		{
7531592Srgrimes			reply(221, "Goodbye.");
7541592Srgrimes			dologout(0);
7551592Srgrimes		}
756102565Syar	| NOTIMPL
757102565Syar		{
758102565Syar			nack($1);
759102565Syar		}
76089935Syar	| error
7611592Srgrimes		{
76289935Syar			yyclearin;		/* discard lookahead data */
76389935Syar			yyerrok;		/* clear error condition */
764102565Syar			state = CMD;		/* reset lexer state */
7651592Srgrimes		}
7661592Srgrimes	;
7671592Srgrimesrcmd
76870102Sphk	: RNFR check_login_ro SP pathname CRLF
7691592Srgrimes		{
770132930Syar			restart_point = 0;
7711592Srgrimes			if ($2 && $4) {
77288935Sdwmalone				if (fromname)
77388935Sdwmalone					free(fromname);
774132931Syar				fromname = NULL;
77588935Sdwmalone				if (renamefrom($4))
77688935Sdwmalone					fromname = $4;
77788935Sdwmalone				else
7781592Srgrimes					free($4);
77988935Sdwmalone			} else if ($4) {
78088935Sdwmalone				free($4);
7811592Srgrimes			}
7821592Srgrimes		}
78392272Smaxim	| REST check_login SP NUMBER CRLF
7841592Srgrimes		{
78571278Sjedgar			if ($2) {
78688935Sdwmalone				if (fromname)
78788935Sdwmalone					free(fromname);
788132931Syar				fromname = NULL;
78992272Smaxim				restart_point = $4.o;
790132929Syar				reply(350, "Restarting at %jd. %s",
791132929Syar				    (intmax_t)restart_point,
79271278Sjedgar				    "Send STORE or RETRIEVE to initiate transfer.");
79371278Sjedgar			}
7941592Srgrimes		}
7951592Srgrimes	;
7961592Srgrimes
7971592Srgrimesusername
7981592Srgrimes	: STRING
7991592Srgrimes	;
8001592Srgrimes
8011592Srgrimespassword
8021592Srgrimes	: /* empty */
8031592Srgrimes		{
8041592Srgrimes			$$ = (char *)calloc(1, sizeof(char));
8051592Srgrimes		}
8061592Srgrimes	| STRING
8071592Srgrimes	;
8081592Srgrimes
8091592Srgrimesbyte_size
8101592Srgrimes	: NUMBER
81192272Smaxim		{
81292272Smaxim			$$ = $1.i;
81392272Smaxim		}
8141592Srgrimes	;
8151592Srgrimes
8161592Srgrimeshost_port
8171592Srgrimes	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
8181592Srgrimes		NUMBER COMMA NUMBER
8191592Srgrimes		{
8201592Srgrimes			char *a, *p;
8211592Srgrimes
82256668Sshin			data_dest.su_len = sizeof(struct sockaddr_in);
82356668Sshin			data_dest.su_family = AF_INET;
82456668Sshin			p = (char *)&data_dest.su_sin.sin_port;
82592272Smaxim			p[0] = $9.i; p[1] = $11.i;
82656668Sshin			a = (char *)&data_dest.su_sin.sin_addr;
82792272Smaxim			a[0] = $1.i; a[1] = $3.i; a[2] = $5.i; a[3] = $7.i;
8281592Srgrimes		}
8291592Srgrimes	;
8301592Srgrimes
83156668Sshinhost_long_port
83256668Sshin	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
83356668Sshin		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
83456668Sshin		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
83556668Sshin		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
83656668Sshin		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
83756668Sshin		NUMBER
83856668Sshin		{
83956668Sshin			char *a, *p;
84056668Sshin
84156668Sshin			memset(&data_dest, 0, sizeof(data_dest));
84256668Sshin			data_dest.su_len = sizeof(struct sockaddr_in6);
84356668Sshin			data_dest.su_family = AF_INET6;
84456668Sshin			p = (char *)&data_dest.su_port;
84592272Smaxim			p[0] = $39.i; p[1] = $41.i;
84656668Sshin			a = (char *)&data_dest.su_sin6.sin6_addr;
84792272Smaxim			a[0] = $5.i; a[1] = $7.i; a[2] = $9.i; a[3] = $11.i;
84892272Smaxim			a[4] = $13.i; a[5] = $15.i; a[6] = $17.i; a[7] = $19.i;
84992272Smaxim			a[8] = $21.i; a[9] = $23.i; a[10] = $25.i; a[11] = $27.i;
85092272Smaxim			a[12] = $29.i; a[13] = $31.i; a[14] = $33.i; a[15] = $35.i;
85156668Sshin			if (his_addr.su_family == AF_INET6) {
85256668Sshin				/* XXX more sanity checks! */
85356668Sshin				data_dest.su_sin6.sin6_scope_id =
85456668Sshin					his_addr.su_sin6.sin6_scope_id;
85556668Sshin			}
85692272Smaxim			if ($1.i != 6 || $3.i != 16 || $37.i != 2)
85756668Sshin				memset(&data_dest, 0, sizeof(data_dest));
85856668Sshin		}
85956668Sshin	| NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
86056668Sshin		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
86156668Sshin		NUMBER
86256668Sshin		{
86356668Sshin			char *a, *p;
86456668Sshin
86556668Sshin			memset(&data_dest, 0, sizeof(data_dest));
86656668Sshin			data_dest.su_sin.sin_len = sizeof(struct sockaddr_in);
86756668Sshin			data_dest.su_family = AF_INET;
86856668Sshin			p = (char *)&data_dest.su_port;
86992272Smaxim			p[0] = $15.i; p[1] = $17.i;
87056668Sshin			a = (char *)&data_dest.su_sin.sin_addr;
87192272Smaxim			a[0] =  $5.i; a[1] = $7.i; a[2] = $9.i; a[3] = $11.i;
87292272Smaxim			if ($1.i != 4 || $3.i != 4 || $13.i != 2)
87356668Sshin				memset(&data_dest, 0, sizeof(data_dest));
87456668Sshin		}
87556668Sshin	;
87656668Sshin
8771592Srgrimesform_code
8781592Srgrimes	: N
8791592Srgrimes		{
8801592Srgrimes			$$ = FORM_N;
8811592Srgrimes		}
8821592Srgrimes	| T
8831592Srgrimes		{
8841592Srgrimes			$$ = FORM_T;
8851592Srgrimes		}
8861592Srgrimes	| C
8871592Srgrimes		{
8881592Srgrimes			$$ = FORM_C;
8891592Srgrimes		}
8901592Srgrimes	;
8911592Srgrimes
8921592Srgrimestype_code
8931592Srgrimes	: A
8941592Srgrimes		{
8951592Srgrimes			cmd_type = TYPE_A;
8961592Srgrimes			cmd_form = FORM_N;
8971592Srgrimes		}
8981592Srgrimes	| A SP form_code
8991592Srgrimes		{
9001592Srgrimes			cmd_type = TYPE_A;
9011592Srgrimes			cmd_form = $3;
9021592Srgrimes		}
9031592Srgrimes	| E
9041592Srgrimes		{
9051592Srgrimes			cmd_type = TYPE_E;
9061592Srgrimes			cmd_form = FORM_N;
9071592Srgrimes		}
9081592Srgrimes	| E SP form_code
9091592Srgrimes		{
9101592Srgrimes			cmd_type = TYPE_E;
9111592Srgrimes			cmd_form = $3;
9121592Srgrimes		}
9131592Srgrimes	| I
9141592Srgrimes		{
9151592Srgrimes			cmd_type = TYPE_I;
9161592Srgrimes		}
9171592Srgrimes	| L
9181592Srgrimes		{
9191592Srgrimes			cmd_type = TYPE_L;
920103949Smike			cmd_bytesz = CHAR_BIT;
9211592Srgrimes		}
9221592Srgrimes	| L SP byte_size
9231592Srgrimes		{
9241592Srgrimes			cmd_type = TYPE_L;
9251592Srgrimes			cmd_bytesz = $3;
9261592Srgrimes		}
9271592Srgrimes		/* this is for a bug in the BBN ftp */
9281592Srgrimes	| L byte_size
9291592Srgrimes		{
9301592Srgrimes			cmd_type = TYPE_L;
9311592Srgrimes			cmd_bytesz = $2;
9321592Srgrimes		}
9331592Srgrimes	;
9341592Srgrimes
9351592Srgrimesstruct_code
9361592Srgrimes	: F
9371592Srgrimes		{
9381592Srgrimes			$$ = STRU_F;
9391592Srgrimes		}
9401592Srgrimes	| R
9411592Srgrimes		{
9421592Srgrimes			$$ = STRU_R;
9431592Srgrimes		}
9441592Srgrimes	| P
9451592Srgrimes		{
9461592Srgrimes			$$ = STRU_P;
9471592Srgrimes		}
9481592Srgrimes	;
9491592Srgrimes
9501592Srgrimesmode_code
9511592Srgrimes	: S
9521592Srgrimes		{
9531592Srgrimes			$$ = MODE_S;
9541592Srgrimes		}
9551592Srgrimes	| B
9561592Srgrimes		{
9571592Srgrimes			$$ = MODE_B;
9581592Srgrimes		}
9591592Srgrimes	| C
9601592Srgrimes		{
9611592Srgrimes			$$ = MODE_C;
9621592Srgrimes		}
9631592Srgrimes	;
9641592Srgrimes
9651592Srgrimespathname
9661592Srgrimes	: pathstring
9671592Srgrimes		{
96875567Speter			if (logged_in && $1) {
969110340Syar				char *p;
9701592Srgrimes
971110340Syar				/*
972110340Syar				 * Expand ~user manually since glob(3)
973110340Syar				 * will return the unexpanded pathname
974110340Syar				 * if the corresponding file/directory
975110340Syar				 * doesn't exist yet.  Using sole glob(3)
976110340Syar				 * would break natural commands like
977110340Syar				 * MKD ~user/newdir
978110340Syar				 * or
979110340Syar				 * RNTO ~/newfile
980110340Syar				 */
981110340Syar				if ((p = exptilde($1)) != NULL) {
982110340Syar					$$ = expglob(p);
983110340Syar					free(p);
984110340Syar				} else
9851592Srgrimes					$$ = NULL;
9861592Srgrimes				free($1);
9871592Srgrimes			} else
9881592Srgrimes				$$ = $1;
9891592Srgrimes		}
9901592Srgrimes	;
9911592Srgrimes
9921592Srgrimespathstring
9931592Srgrimes	: STRING
9941592Srgrimes	;
9951592Srgrimes
9961592Srgrimesoctal_number
9971592Srgrimes	: NUMBER
9981592Srgrimes		{
9991592Srgrimes			int ret, dec, multby, digit;
10001592Srgrimes
10011592Srgrimes			/*
10021592Srgrimes			 * Convert a number that was read as decimal number
10031592Srgrimes			 * to what it would be if it had been read as octal.
10041592Srgrimes			 */
100592272Smaxim			dec = $1.i;
10061592Srgrimes			multby = 1;
10071592Srgrimes			ret = 0;
10081592Srgrimes			while (dec) {
10091592Srgrimes				digit = dec%10;
10101592Srgrimes				if (digit > 7) {
10111592Srgrimes					ret = -1;
10121592Srgrimes					break;
10131592Srgrimes				}
10141592Srgrimes				ret += digit * multby;
10151592Srgrimes				multby *= 8;
10161592Srgrimes				dec /= 10;
10171592Srgrimes			}
10181592Srgrimes			$$ = ret;
10191592Srgrimes		}
10201592Srgrimes	;
10211592Srgrimes
10221592Srgrimes
10231592Srgrimescheck_login
10241592Srgrimes	: /* empty */
10251592Srgrimes		{
102670102Sphk		$$ = check_login1();
10271592Srgrimes		}
10281592Srgrimes	;
10291592Srgrimes
103070102Sphkcheck_login_epsv
103170102Sphk	: /* empty */
103270102Sphk		{
103370102Sphk		if (noepsv) {
103470102Sphk			reply(500, "EPSV command disabled");
103570102Sphk			$$ = 0;
103670102Sphk		}
103770102Sphk		else
103870102Sphk			$$ = check_login1();
103970102Sphk		}
104070102Sphk	;
104170102Sphk
104270102Sphkcheck_login_ro
104370102Sphk	: /* empty */
104470102Sphk		{
104570102Sphk		if (readonly) {
104672710Sdes			reply(550, "Permission denied.");
104770102Sphk			$$ = 0;
104870102Sphk		}
104970102Sphk		else
105070102Sphk			$$ = check_login1();
105170102Sphk		}
105270102Sphk	;
105370102Sphk
10541592Srgrimes%%
10551592Srgrimes
10561592Srgrimes#define	CMD	0	/* beginning of command */
10571592Srgrimes#define	ARGS	1	/* expect miscellaneous arguments */
10581592Srgrimes#define	STR1	2	/* expect SP followed by STRING */
10591592Srgrimes#define	STR2	3	/* expect STRING */
10601592Srgrimes#define	OSTR	4	/* optional SP then STRING */
106175556Sgreen#define	ZSTR1	5	/* optional SP then optional STRING */
10621592Srgrimes#define	ZSTR2	6	/* optional STRING after SP */
10631592Srgrimes#define	SITECMD	7	/* SITE command */
10641592Srgrimes#define	NSTR	8	/* Number followed by a string */
10651592Srgrimes
106675560Sjedgar#define	MAXGLOBARGS	1000
106775560Sjedgar
1068101034Syar#define	MAXASIZE	10240	/* Deny ASCII SIZE on files larger than that */
1069101034Syar
10701592Srgrimesstruct tab {
10711592Srgrimes	char	*name;
10721592Srgrimes	short	token;
10731592Srgrimes	short	state;
10741592Srgrimes	short	implemented;	/* 1 if command is implemented */
10751592Srgrimes	char	*help;
10761592Srgrimes};
10771592Srgrimes
10781592Srgrimesstruct tab cmdtab[] = {		/* In order defined in RFC 765 */
10791592Srgrimes	{ "USER", USER, STR1, 1,	"<sp> username" },
108075556Sgreen	{ "PASS", PASS, ZSTR1, 1,	"[<sp> [password]]" },
10811592Srgrimes	{ "ACCT", ACCT, STR1, 0,	"(specify account)" },
10821592Srgrimes	{ "SMNT", SMNT, ARGS, 0,	"(structure mount)" },
10831592Srgrimes	{ "REIN", REIN, ARGS, 0,	"(reinitialize server state)" },
10841592Srgrimes	{ "QUIT", QUIT, ARGS, 1,	"(terminate service)", },
1085101806Syar	{ "PORT", PORT, ARGS, 1,	"<sp> b0, b1, b2, b3, b4, b5" },
108656668Sshin	{ "LPRT", LPRT, ARGS, 1,	"<sp> af, hal, h1, h2, h3,..., pal, p1, p2..." },
108756668Sshin	{ "EPRT", EPRT, STR1, 1,	"<sp> |af|addr|port|" },
10881592Srgrimes	{ "PASV", PASV, ARGS, 1,	"(set server in passive mode)" },
108956668Sshin	{ "LPSV", LPSV, ARGS, 1,	"(set server in passive mode)" },
109056668Sshin	{ "EPSV", EPSV, ARGS, 1,	"[<sp> af|ALL]" },
1091101806Syar	{ "TYPE", TYPE, ARGS, 1,	"<sp> { A | E | I | L }" },
10921592Srgrimes	{ "STRU", STRU, ARGS, 1,	"(specify file structure)" },
10931592Srgrimes	{ "MODE", MODE, ARGS, 1,	"(specify transfer mode)" },
10941592Srgrimes	{ "RETR", RETR, STR1, 1,	"<sp> file-name" },
10951592Srgrimes	{ "STOR", STOR, STR1, 1,	"<sp> file-name" },
10961592Srgrimes	{ "APPE", APPE, STR1, 1,	"<sp> file-name" },
10971592Srgrimes	{ "MLFL", MLFL, OSTR, 0,	"(mail file)" },
10981592Srgrimes	{ "MAIL", MAIL, OSTR, 0,	"(mail to user)" },
10991592Srgrimes	{ "MSND", MSND, OSTR, 0,	"(mail send to terminal)" },
11001592Srgrimes	{ "MSOM", MSOM, OSTR, 0,	"(mail send to terminal or mailbox)" },
11011592Srgrimes	{ "MSAM", MSAM, OSTR, 0,	"(mail send to terminal and mailbox)" },
11021592Srgrimes	{ "MRSQ", MRSQ, OSTR, 0,	"(mail recipient scheme question)" },
11031592Srgrimes	{ "MRCP", MRCP, STR1, 0,	"(mail recipient)" },
11041592Srgrimes	{ "ALLO", ALLO, ARGS, 1,	"allocate storage (vacuously)" },
11051592Srgrimes	{ "REST", REST, ARGS, 1,	"<sp> offset (restart command)" },
11061592Srgrimes	{ "RNFR", RNFR, STR1, 1,	"<sp> file-name" },
11071592Srgrimes	{ "RNTO", RNTO, STR1, 1,	"<sp> file-name" },
11081592Srgrimes	{ "ABOR", ABOR, ARGS, 1,	"(abort operation)" },
11091592Srgrimes	{ "DELE", DELE, STR1, 1,	"<sp> file-name" },
11101592Srgrimes	{ "CWD",  CWD,  OSTR, 1,	"[ <sp> directory-name ]" },
11111592Srgrimes	{ "XCWD", CWD,	OSTR, 1,	"[ <sp> directory-name ]" },
11121592Srgrimes	{ "LIST", LIST, OSTR, 1,	"[ <sp> path-name ]" },
11131592Srgrimes	{ "NLST", NLST, OSTR, 1,	"[ <sp> path-name ]" },
11141592Srgrimes	{ "SITE", SITE, SITECMD, 1,	"site-cmd [ <sp> arguments ]" },
11151592Srgrimes	{ "SYST", SYST, ARGS, 1,	"(get type of operating system)" },
11161592Srgrimes	{ "STAT", STAT, OSTR, 1,	"[ <sp> path-name ]" },
11171592Srgrimes	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
11181592Srgrimes	{ "NOOP", NOOP, ARGS, 1,	"" },
11191592Srgrimes	{ "MKD",  MKD,  STR1, 1,	"<sp> path-name" },
11201592Srgrimes	{ "XMKD", MKD,  STR1, 1,	"<sp> path-name" },
11211592Srgrimes	{ "RMD",  RMD,  STR1, 1,	"<sp> path-name" },
11221592Srgrimes	{ "XRMD", RMD,  STR1, 1,	"<sp> path-name" },
11231592Srgrimes	{ "PWD",  PWD,  ARGS, 1,	"(return current directory)" },
11241592Srgrimes	{ "XPWD", PWD,  ARGS, 1,	"(return current directory)" },
11251592Srgrimes	{ "CDUP", CDUP, ARGS, 1,	"(change to parent directory)" },
11261592Srgrimes	{ "XCUP", CDUP, ARGS, 1,	"(change to parent directory)" },
11271592Srgrimes	{ "STOU", STOU, STR1, 1,	"<sp> file-name" },
11281592Srgrimes	{ "SIZE", SIZE, OSTR, 1,	"<sp> path-name" },
11291592Srgrimes	{ "MDTM", MDTM, OSTR, 1,	"<sp> path-name" },
11301592Srgrimes	{ NULL,   0,    0,    0,	0 }
11311592Srgrimes};
11321592Srgrimes
11331592Srgrimesstruct tab sitetab[] = {
113475535Sphk	{ "MD5", MDFIVE, STR1, 1,	"[ <sp> file-name ]" },
11351592Srgrimes	{ "UMASK", UMASK, ARGS, 1,	"[ <sp> umask ]" },
11361592Srgrimes	{ "IDLE", IDLE, ARGS, 1,	"[ <sp> maximum-idle-time ]" },
11371592Srgrimes	{ "CHMOD", CHMOD, NSTR, 1,	"<sp> mode <sp> file-name" },
11381592Srgrimes	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
11391592Srgrimes	{ NULL,   0,    0,    0,	0 }
11401592Srgrimes};
11411592Srgrimes
114290148Simpstatic char	*copy(char *);
1143110340Syarstatic char	*expglob(char *);
1144110340Syarstatic char	*exptilde(char *);
114590148Simpstatic void	 help(struct tab *, char *);
11461592Srgrimesstatic struct tab *
114790148Simp		 lookup(struct tab *, char *);
114890148Simpstatic int	 port_check(const char *);
114990148Simpstatic int	 port_check_v6(const char *);
115090148Simpstatic void	 sizecmd(char *);
115190148Simpstatic void	 toolong(int);
115290148Simpstatic void	 v4map_data_dest(void);
115390148Simpstatic int	 yylex(void);
11541592Srgrimes
11551592Srgrimesstatic struct tab *
115690148Simplookup(struct tab *p, char *cmd)
11571592Srgrimes{
11581592Srgrimes
11591592Srgrimes	for (; p->name != NULL; p++)
11601592Srgrimes		if (strcmp(cmd, p->name) == 0)
11611592Srgrimes			return (p);
11621592Srgrimes	return (0);
11631592Srgrimes}
11641592Srgrimes
11651592Srgrimes#include <arpa/telnet.h>
11661592Srgrimes
11671592Srgrimes/*
11681592Srgrimes * getline - a hacked up version of fgets to ignore TELNET escape codes.
11691592Srgrimes */
11701592Srgrimeschar *
117190148Simpgetline(char *s, int n, FILE *iop)
11721592Srgrimes{
11731592Srgrimes	int c;
11741592Srgrimes	register char *cs;
1175117352Syar	sigset_t sset, osset;
11761592Srgrimes
11771592Srgrimes	cs = s;
11781592Srgrimes/* tmpline may contain saved command from urgent mode interruption */
11791592Srgrimes	for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
11801592Srgrimes		*cs++ = tmpline[c];
11811592Srgrimes		if (tmpline[c] == '\n') {
11821592Srgrimes			*cs++ = '\0';
118376096Smarkm			if (ftpdebug)
11841592Srgrimes				syslog(LOG_DEBUG, "command: %s", s);
11851592Srgrimes			tmpline[0] = '\0';
11861592Srgrimes			return(s);
11871592Srgrimes		}
11881592Srgrimes		if (c == 0)
11891592Srgrimes			tmpline[0] = '\0';
11901592Srgrimes	}
1191117352Syar	/* SIGURG would interrupt stdio if not blocked during the read loop */
1192117352Syar	sigemptyset(&sset);
1193117352Syar	sigaddset(&sset, SIGURG);
1194117352Syar	sigprocmask(SIG_BLOCK, &sset, &osset);
11951592Srgrimes	while ((c = getc(iop)) != EOF) {
11961592Srgrimes		c &= 0377;
11971592Srgrimes		if (c == IAC) {
1198117351Syar			if ((c = getc(iop)) == EOF)
1199117351Syar				goto got_eof;
12001592Srgrimes			c &= 0377;
12011592Srgrimes			switch (c) {
12021592Srgrimes			case WILL:
12031592Srgrimes			case WONT:
1204117351Syar				if ((c = getc(iop)) == EOF)
1205117351Syar					goto got_eof;
12061592Srgrimes				printf("%c%c%c", IAC, DONT, 0377&c);
12071592Srgrimes				(void) fflush(stdout);
12081592Srgrimes				continue;
12091592Srgrimes			case DO:
12101592Srgrimes			case DONT:
1211117351Syar				if ((c = getc(iop)) == EOF)
1212117351Syar					goto got_eof;
12131592Srgrimes				printf("%c%c%c", IAC, WONT, 0377&c);
12141592Srgrimes				(void) fflush(stdout);
12151592Srgrimes				continue;
12161592Srgrimes			case IAC:
12171592Srgrimes				break;
12181592Srgrimes			default:
12191592Srgrimes				continue;	/* ignore command */
12201592Srgrimes			}
12211592Srgrimes		}
12221592Srgrimes		*cs++ = c;
12231592Srgrimes		if (--n <= 0 || c == '\n')
12241592Srgrimes			break;
12251592Srgrimes	}
1226117351Syargot_eof:
1227117352Syar	sigprocmask(SIG_SETMASK, &osset, NULL);
12281592Srgrimes	if (c == EOF && cs == s)
12291592Srgrimes		return (NULL);
12301592Srgrimes	*cs++ = '\0';
123176096Smarkm	if (ftpdebug) {
12321592Srgrimes		if (!guest && strncasecmp("pass ", s, 5) == 0) {
12331592Srgrimes			/* Don't syslog passwords */
12341592Srgrimes			syslog(LOG_DEBUG, "command: %.5s ???", s);
12351592Srgrimes		} else {
12361592Srgrimes			register char *cp;
12371592Srgrimes			register int len;
12381592Srgrimes
12391592Srgrimes			/* Don't syslog trailing CR-LF */
12401592Srgrimes			len = strlen(s);
12411592Srgrimes			cp = s + len - 1;
12421592Srgrimes			while (cp >= s && (*cp == '\n' || *cp == '\r')) {
12431592Srgrimes				--cp;
12441592Srgrimes				--len;
12451592Srgrimes			}
12461592Srgrimes			syslog(LOG_DEBUG, "command: %.*s", len, s);
12471592Srgrimes		}
12481592Srgrimes	}
12491592Srgrimes	return (s);
12501592Srgrimes}
12511592Srgrimes
12521592Srgrimesstatic void
125390148Simptoolong(int signo)
12541592Srgrimes{
12551592Srgrimes
12561592Srgrimes	reply(421,
12571592Srgrimes	    "Timeout (%d seconds): closing control connection.", timeout);
12581592Srgrimes	if (logging)
12591592Srgrimes		syslog(LOG_INFO, "User %s timed out after %d seconds",
12601592Srgrimes		    (pw ? pw -> pw_name : "unknown"), timeout);
12611592Srgrimes	dologout(1);
12621592Srgrimes}
12631592Srgrimes
12641592Srgrimesstatic int
126590148Simpyylex(void)
12661592Srgrimes{
126789935Syar	static int cpos;
12681592Srgrimes	char *cp, *cp2;
12691592Srgrimes	struct tab *p;
12701592Srgrimes	int n;
12711592Srgrimes	char c;
12721592Srgrimes
12731592Srgrimes	for (;;) {
12741592Srgrimes		switch (state) {
12751592Srgrimes
12761592Srgrimes		case CMD:
12771592Srgrimes			(void) signal(SIGALRM, toolong);
1278137659Syar			(void) alarm(timeout);
12791592Srgrimes			if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
12801592Srgrimes				reply(221, "You could at least say goodbye.");
12811592Srgrimes				dologout(0);
12821592Srgrimes			}
12831592Srgrimes			(void) alarm(0);
12841592Srgrimes#ifdef SETPROCTITLE
128529574Sphk			if (strncasecmp(cbuf, "PASS", 4) != 0)
12861592Srgrimes				setproctitle("%s: %s", proctitle, cbuf);
12871592Srgrimes#endif /* SETPROCTITLE */
12881592Srgrimes			if ((cp = strchr(cbuf, '\r'))) {
12891592Srgrimes				*cp++ = '\n';
12901592Srgrimes				*cp = '\0';
12911592Srgrimes			}
12921592Srgrimes			if ((cp = strpbrk(cbuf, " \n")))
12931592Srgrimes				cpos = cp - cbuf;
12941592Srgrimes			if (cpos == 0)
12951592Srgrimes				cpos = 4;
12961592Srgrimes			c = cbuf[cpos];
12971592Srgrimes			cbuf[cpos] = '\0';
12981592Srgrimes			upper(cbuf);
12991592Srgrimes			p = lookup(cmdtab, cbuf);
13001592Srgrimes			cbuf[cpos] = c;
13013776Spst			if (p != 0) {
1302102565Syar				yylval.s = p->name;
1303102565Syar				if (!p->implemented)
1304102565Syar					return (NOTIMPL); /* state remains CMD */
13051592Srgrimes				state = p->state;
13061592Srgrimes				return (p->token);
13071592Srgrimes			}
13081592Srgrimes			break;
13091592Srgrimes
13101592Srgrimes		case SITECMD:
13111592Srgrimes			if (cbuf[cpos] == ' ') {
13121592Srgrimes				cpos++;
13131592Srgrimes				return (SP);
13141592Srgrimes			}
13151592Srgrimes			cp = &cbuf[cpos];
13161592Srgrimes			if ((cp2 = strpbrk(cp, " \n")))
13171592Srgrimes				cpos = cp2 - cbuf;
13181592Srgrimes			c = cbuf[cpos];
13191592Srgrimes			cbuf[cpos] = '\0';
13201592Srgrimes			upper(cp);
13211592Srgrimes			p = lookup(sitetab, cp);
13221592Srgrimes			cbuf[cpos] = c;
13233777Spst			if (guest == 0 && p != 0) {
1324102565Syar				yylval.s = p->name;
1325102565Syar				if (!p->implemented) {
13261592Srgrimes					state = CMD;
1327102565Syar					return (NOTIMPL);
13281592Srgrimes				}
13291592Srgrimes				state = p->state;
13301592Srgrimes				return (p->token);
13311592Srgrimes			}
13321592Srgrimes			state = CMD;
13331592Srgrimes			break;
13341592Srgrimes
133575556Sgreen		case ZSTR1:
13361592Srgrimes		case OSTR:
13371592Srgrimes			if (cbuf[cpos] == '\n') {
13381592Srgrimes				state = CMD;
13391592Srgrimes				return (CRLF);
13401592Srgrimes			}
13411592Srgrimes			/* FALLTHROUGH */
13421592Srgrimes
13431592Srgrimes		case STR1:
13441592Srgrimes		dostr1:
13451592Srgrimes			if (cbuf[cpos] == ' ') {
13461592Srgrimes				cpos++;
134751979Salfred				state = state == OSTR ? STR2 : state+1;
13481592Srgrimes				return (SP);
13491592Srgrimes			}
13501592Srgrimes			break;
13511592Srgrimes
13521592Srgrimes		case ZSTR2:
13531592Srgrimes			if (cbuf[cpos] == '\n') {
13541592Srgrimes				state = CMD;
13551592Srgrimes				return (CRLF);
13561592Srgrimes			}
13571592Srgrimes			/* FALLTHROUGH */
13581592Srgrimes
13591592Srgrimes		case STR2:
13601592Srgrimes			cp = &cbuf[cpos];
13611592Srgrimes			n = strlen(cp);
13621592Srgrimes			cpos += n - 1;
13631592Srgrimes			/*
13641592Srgrimes			 * Make sure the string is nonempty and \n terminated.
13651592Srgrimes			 */
13661592Srgrimes			if (n > 1 && cbuf[cpos] == '\n') {
13671592Srgrimes				cbuf[cpos] = '\0';
13681592Srgrimes				yylval.s = copy(cp);
13691592Srgrimes				cbuf[cpos] = '\n';
13701592Srgrimes				state = ARGS;
13711592Srgrimes				return (STRING);
13721592Srgrimes			}
13731592Srgrimes			break;
13741592Srgrimes
13751592Srgrimes		case NSTR:
13761592Srgrimes			if (cbuf[cpos] == ' ') {
13771592Srgrimes				cpos++;
13781592Srgrimes				return (SP);
13791592Srgrimes			}
13801592Srgrimes			if (isdigit(cbuf[cpos])) {
13811592Srgrimes				cp = &cbuf[cpos];
13821592Srgrimes				while (isdigit(cbuf[++cpos]))
13831592Srgrimes					;
13841592Srgrimes				c = cbuf[cpos];
13851592Srgrimes				cbuf[cpos] = '\0';
138692272Smaxim				yylval.u.i = atoi(cp);
13871592Srgrimes				cbuf[cpos] = c;
13881592Srgrimes				state = STR1;
13891592Srgrimes				return (NUMBER);
13901592Srgrimes			}
13911592Srgrimes			state = STR1;
13921592Srgrimes			goto dostr1;
13931592Srgrimes
13941592Srgrimes		case ARGS:
13951592Srgrimes			if (isdigit(cbuf[cpos])) {
13961592Srgrimes				cp = &cbuf[cpos];
13971592Srgrimes				while (isdigit(cbuf[++cpos]))
13981592Srgrimes					;
13991592Srgrimes				c = cbuf[cpos];
14001592Srgrimes				cbuf[cpos] = '\0';
140192272Smaxim				yylval.u.i = atoi(cp);
1402137811Syar				yylval.u.o = strtoull(cp, NULL, 10);
14031592Srgrimes				cbuf[cpos] = c;
14041592Srgrimes				return (NUMBER);
14051592Srgrimes			}
140656668Sshin			if (strncasecmp(&cbuf[cpos], "ALL", 3) == 0
140756668Sshin			 && !isalnum(cbuf[cpos + 3])) {
140856668Sshin				cpos += 3;
140956668Sshin				return ALL;
141056668Sshin			}
14111592Srgrimes			switch (cbuf[cpos++]) {
14121592Srgrimes
14131592Srgrimes			case '\n':
14141592Srgrimes				state = CMD;
14151592Srgrimes				return (CRLF);
14161592Srgrimes
14171592Srgrimes			case ' ':
14181592Srgrimes				return (SP);
14191592Srgrimes
14201592Srgrimes			case ',':
14211592Srgrimes				return (COMMA);
14221592Srgrimes
14231592Srgrimes			case 'A':
14241592Srgrimes			case 'a':
14251592Srgrimes				return (A);
14261592Srgrimes
14271592Srgrimes			case 'B':
14281592Srgrimes			case 'b':
14291592Srgrimes				return (B);
14301592Srgrimes
14311592Srgrimes			case 'C':
14321592Srgrimes			case 'c':
14331592Srgrimes				return (C);
14341592Srgrimes
14351592Srgrimes			case 'E':
14361592Srgrimes			case 'e':
14371592Srgrimes				return (E);
14381592Srgrimes
14391592Srgrimes			case 'F':
14401592Srgrimes			case 'f':
14411592Srgrimes				return (F);
14421592Srgrimes
14431592Srgrimes			case 'I':
14441592Srgrimes			case 'i':
14451592Srgrimes				return (I);
14461592Srgrimes
14471592Srgrimes			case 'L':
14481592Srgrimes			case 'l':
14491592Srgrimes				return (L);
14501592Srgrimes
14511592Srgrimes			case 'N':
14521592Srgrimes			case 'n':
14531592Srgrimes				return (N);
14541592Srgrimes
14551592Srgrimes			case 'P':
14561592Srgrimes			case 'p':
14571592Srgrimes				return (P);
14581592Srgrimes
14591592Srgrimes			case 'R':
14601592Srgrimes			case 'r':
14611592Srgrimes				return (R);
14621592Srgrimes
14631592Srgrimes			case 'S':
14641592Srgrimes			case 's':
14651592Srgrimes				return (S);
14661592Srgrimes
14671592Srgrimes			case 'T':
14681592Srgrimes			case 't':
14691592Srgrimes				return (T);
14701592Srgrimes
14711592Srgrimes			}
14721592Srgrimes			break;
14731592Srgrimes
14741592Srgrimes		default:
147576096Smarkm			fatalerror("Unknown state in scanner.");
14761592Srgrimes		}
14771592Srgrimes		state = CMD;
147889935Syar		return (LEXERR);
14791592Srgrimes	}
14801592Srgrimes}
14811592Srgrimes
14821592Srgrimesvoid
148390148Simpupper(char *s)
14841592Srgrimes{
14851592Srgrimes	while (*s != '\0') {
14861592Srgrimes		if (islower(*s))
14871592Srgrimes			*s = toupper(*s);
14881592Srgrimes		s++;
14891592Srgrimes	}
14901592Srgrimes}
14911592Srgrimes
14921592Srgrimesstatic char *
149390148Simpcopy(char *s)
14941592Srgrimes{
14951592Srgrimes	char *p;
14961592Srgrimes
1497137659Syar	p = malloc(strlen(s) + 1);
14981592Srgrimes	if (p == NULL)
149976096Smarkm		fatalerror("Ran out of memory.");
15001592Srgrimes	(void) strcpy(p, s);
15011592Srgrimes	return (p);
15021592Srgrimes}
15031592Srgrimes
15041592Srgrimesstatic void
150590148Simphelp(struct tab *ctab, char *s)
15061592Srgrimes{
15071592Srgrimes	struct tab *c;
15081592Srgrimes	int width, NCMDS;
15091592Srgrimes	char *type;
15101592Srgrimes
15111592Srgrimes	if (ctab == sitetab)
15121592Srgrimes		type = "SITE ";
15131592Srgrimes	else
15141592Srgrimes		type = "";
15151592Srgrimes	width = 0, NCMDS = 0;
15161592Srgrimes	for (c = ctab; c->name != NULL; c++) {
15171592Srgrimes		int len = strlen(c->name);
15181592Srgrimes
15191592Srgrimes		if (len > width)
15201592Srgrimes			width = len;
15211592Srgrimes		NCMDS++;
15221592Srgrimes	}
15231592Srgrimes	width = (width + 8) &~ 7;
15241592Srgrimes	if (s == 0) {
15251592Srgrimes		int i, j, w;
15261592Srgrimes		int columns, lines;
15271592Srgrimes
15281592Srgrimes		lreply(214, "The following %scommands are recognized %s.",
15291592Srgrimes		    type, "(* =>'s unimplemented)");
15301592Srgrimes		columns = 76 / width;
15311592Srgrimes		if (columns == 0)
15321592Srgrimes			columns = 1;
15331592Srgrimes		lines = (NCMDS + columns - 1) / columns;
15341592Srgrimes		for (i = 0; i < lines; i++) {
15351592Srgrimes			printf("   ");
15361592Srgrimes			for (j = 0; j < columns; j++) {
15371592Srgrimes				c = ctab + j * lines + i;
15381592Srgrimes				printf("%s%c", c->name,
15391592Srgrimes					c->implemented ? ' ' : '*');
15401592Srgrimes				if (c + lines >= &ctab[NCMDS])
15411592Srgrimes					break;
15421592Srgrimes				w = strlen(c->name) + 1;
15431592Srgrimes				while (w < width) {
15441592Srgrimes					putchar(' ');
15451592Srgrimes					w++;
15461592Srgrimes				}
15471592Srgrimes			}
15481592Srgrimes			printf("\r\n");
15491592Srgrimes		}
15501592Srgrimes		(void) fflush(stdout);
1551110037Syar		if (hostinfo)
1552110037Syar			reply(214, "Direct comments to ftp-bugs@%s.", hostname);
1553110037Syar		else
1554110037Syar			reply(214, "End.");
15551592Srgrimes		return;
15561592Srgrimes	}
15571592Srgrimes	upper(s);
15581592Srgrimes	c = lookup(ctab, s);
1559132931Syar	if (c == NULL) {
15601592Srgrimes		reply(502, "Unknown command %s.", s);
15611592Srgrimes		return;
15621592Srgrimes	}
15631592Srgrimes	if (c->implemented)
15641592Srgrimes		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
15651592Srgrimes	else
15661592Srgrimes		reply(214, "%s%-*s\t%s; unimplemented.", type, width,
15671592Srgrimes		    c->name, c->help);
15681592Srgrimes}
15691592Srgrimes
15701592Srgrimesstatic void
157190148Simpsizecmd(char *filename)
15721592Srgrimes{
15731592Srgrimes	switch (type) {
15741592Srgrimes	case TYPE_L:
15751592Srgrimes	case TYPE_I: {
15761592Srgrimes		struct stat stbuf;
157763350Sdes		if (stat(filename, &stbuf) < 0)
157863350Sdes			perror_reply(550, filename);
157963350Sdes		else if (!S_ISREG(stbuf.st_mode))
15801592Srgrimes			reply(550, "%s: not a plain file.", filename);
15811592Srgrimes		else
1582132929Syar			reply(213, "%jd", (intmax_t)stbuf.st_size);
15831592Srgrimes		break; }
15841592Srgrimes	case TYPE_A: {
15851592Srgrimes		FILE *fin;
15861592Srgrimes		int c;
15871592Srgrimes		off_t count;
15881592Srgrimes		struct stat stbuf;
15891592Srgrimes		fin = fopen(filename, "r");
15901592Srgrimes		if (fin == NULL) {
15911592Srgrimes			perror_reply(550, filename);
15921592Srgrimes			return;
15931592Srgrimes		}
159463350Sdes		if (fstat(fileno(fin), &stbuf) < 0) {
159563350Sdes			perror_reply(550, filename);
159663350Sdes			(void) fclose(fin);
159763350Sdes			return;
159863350Sdes		} else if (!S_ISREG(stbuf.st_mode)) {
15991592Srgrimes			reply(550, "%s: not a plain file.", filename);
16001592Srgrimes			(void) fclose(fin);
16011592Srgrimes			return;
1602101034Syar		} else if (stbuf.st_size > MAXASIZE) {
1603101034Syar			reply(550, "%s: too large for type A SIZE.", filename);
1604101034Syar			(void) fclose(fin);
1605101034Syar			return;
16061592Srgrimes		}
16071592Srgrimes
16081592Srgrimes		count = 0;
16091592Srgrimes		while((c=getc(fin)) != EOF) {
16101592Srgrimes			if (c == '\n')	/* will get expanded to \r\n */
16111592Srgrimes				count++;
16121592Srgrimes			count++;
16131592Srgrimes		}
16141592Srgrimes		(void) fclose(fin);
16151592Srgrimes
1616132929Syar		reply(213, "%jd", (intmax_t)count);
16171592Srgrimes		break; }
16181592Srgrimes	default:
1619100684Syar		reply(504, "SIZE not implemented for type %s.",
1620100684Syar		           typenames[type]);
16211592Srgrimes	}
16221592Srgrimes}
162356668Sshin
162456668Sshin/* Return 1, if port check is done. Return 0, if not yet. */
162556668Sshinstatic int
162690148Simpport_check(const char *pcmd)
162756668Sshin{
162856668Sshin	if (his_addr.su_family == AF_INET) {
162956668Sshin		if (data_dest.su_family != AF_INET) {
163056668Sshin			usedefault = 1;
163156668Sshin			reply(500, "Invalid address rejected.");
163256668Sshin			return 1;
163356668Sshin		}
163456668Sshin		if (paranoid &&
163556668Sshin		    ((ntohs(data_dest.su_port) < IPPORT_RESERVED) ||
163656668Sshin		     memcmp(&data_dest.su_sin.sin_addr,
163756668Sshin			    &his_addr.su_sin.sin_addr,
163856668Sshin			    sizeof(data_dest.su_sin.sin_addr)))) {
163956668Sshin			usedefault = 1;
164056668Sshin			reply(500, "Illegal PORT range rejected.");
164156668Sshin		} else {
164256668Sshin			usedefault = 0;
164356668Sshin			if (pdata >= 0) {
164456668Sshin				(void) close(pdata);
164556668Sshin				pdata = -1;
164656668Sshin			}
164756668Sshin			reply(200, "%s command successful.", pcmd);
164856668Sshin		}
164956668Sshin		return 1;
165056668Sshin	}
165156668Sshin	return 0;
165256668Sshin}
165356668Sshin
165470102Sphkstatic int
165590148Simpcheck_login1(void)
165670102Sphk{
165770102Sphk	if (logged_in)
165870102Sphk		return 1;
165970102Sphk	else {
166070102Sphk		reply(530, "Please login with USER and PASS.");
166170102Sphk		return 0;
166270102Sphk	}
166370102Sphk}
166470102Sphk
1665110340Syar/*
1666110340Syar * Replace leading "~user" in a pathname by the user's login directory.
1667110340Syar * Returned string will be in a freshly malloced buffer unless it's NULL.
1668110340Syar */
1669110340Syarstatic char *
1670110340Syarexptilde(char *s)
1671110340Syar{
1672110340Syar	char *p, *q;
1673110340Syar	char *path, *user;
1674110340Syar	struct passwd *ppw;
1675110340Syar
1676110340Syar	if ((p = strdup(s)) == NULL)
1677110340Syar		return (NULL);
1678110340Syar	if (*p != '~')
1679110340Syar		return (p);
1680110340Syar
1681110340Syar	user = p + 1;	/* skip tilde */
1682110340Syar	if ((path = strchr(p, '/')) != NULL)
1683110340Syar		*(path++) = '\0'; /* separate ~user from the rest of path */
1684110378Syar	if (*user == '\0') /* no user specified, use the current user */
1685110378Syar		user = pw->pw_name;
1686110378Syar	/* read passwd even for the current user since we may be chrooted */
1687110378Syar	if ((ppw = getpwnam(user)) != NULL) {
1688110340Syar		/* user found, substitute login directory for ~user */
1689110340Syar		if (path)
1690110340Syar			asprintf(&q, "%s/%s", ppw->pw_dir, path);
1691110340Syar		else
1692110340Syar			q = strdup(ppw->pw_dir);
1693110340Syar		free(p);
1694110340Syar		p = q;
1695110340Syar	} else {
1696110340Syar		/* user not found, undo the damage */
1697110340Syar		if (path)
1698110340Syar			path[-1] = '/';
1699110340Syar	}
1700110340Syar	return (p);
1701110340Syar}
1702110340Syar
1703110340Syar/*
1704110340Syar * Expand glob(3) patterns possibly present in a pathname.
1705110340Syar * Avoid expanding to a pathname including '\r' or '\n' in order to
1706110340Syar * not disrupt the FTP protocol.
1707110340Syar * The expansion found must be unique.
1708110340Syar * Return the result as a malloced string, or NULL if an error occured.
1709110340Syar *
1710110340Syar * Problem: this production is used for all pathname
1711110340Syar * processing, but only gives a 550 error reply.
1712110340Syar * This is a valid reply in some cases but not in others.
1713110340Syar */
1714110340Syarstatic char *
1715110340Syarexpglob(char *s)
1716110340Syar{
1717110340Syar	char *p, **pp, *rval;
1718110340Syar	int flags = GLOB_BRACE | GLOB_NOCHECK;
1719110340Syar	int n;
1720110340Syar	glob_t gl;
1721110340Syar
1722110340Syar	memset(&gl, 0, sizeof(gl));
1723110340Syar	flags |= GLOB_LIMIT;
1724110340Syar	gl.gl_matchc = MAXGLOBARGS;
1725110340Syar	if (glob(s, flags, NULL, &gl) == 0 && gl.gl_pathc != 0) {
1726110340Syar		for (pp = gl.gl_pathv, p = NULL, n = 0; *pp; pp++)
1727110340Syar			if (*(*pp + strcspn(*pp, "\r\n")) == '\0') {
1728110340Syar				p = *pp;
1729110340Syar				n++;
1730110340Syar			}
1731110340Syar		if (n == 0)
1732110340Syar			rval = strdup(s);
1733110340Syar		else if (n == 1)
1734110340Syar			rval = strdup(p);
1735110340Syar		else {
1736110340Syar			reply(550, "ambiguous");
1737110340Syar			rval = NULL;
1738110340Syar		}
1739110340Syar	} else {
1740110340Syar		reply(550, "wildcard expansion error");
1741110340Syar		rval = NULL;
1742110340Syar	}
1743110340Syar	globfree(&gl);
1744110340Syar	return (rval);
1745110340Syar}
1746110340Syar
174756668Sshin#ifdef INET6
174856668Sshin/* Return 1, if port check is done. Return 0, if not yet. */
174956668Sshinstatic int
175090148Simpport_check_v6(const char *pcmd)
175156668Sshin{
175256668Sshin	if (his_addr.su_family == AF_INET6) {
175356668Sshin		if (IN6_IS_ADDR_V4MAPPED(&his_addr.su_sin6.sin6_addr))
175456668Sshin			/* Convert data_dest into v4 mapped sockaddr.*/
175556668Sshin			v4map_data_dest();
175656668Sshin		if (data_dest.su_family != AF_INET6) {
175756668Sshin			usedefault = 1;
175856668Sshin			reply(500, "Invalid address rejected.");
175956668Sshin			return 1;
176056668Sshin		}
176156668Sshin		if (paranoid &&
176256668Sshin		    ((ntohs(data_dest.su_port) < IPPORT_RESERVED) ||
176356668Sshin		     memcmp(&data_dest.su_sin6.sin6_addr,
176456668Sshin			    &his_addr.su_sin6.sin6_addr,
176556668Sshin			    sizeof(data_dest.su_sin6.sin6_addr)))) {
176656668Sshin			usedefault = 1;
176756668Sshin			reply(500, "Illegal PORT range rejected.");
176856668Sshin		} else {
176956668Sshin			usedefault = 0;
177056668Sshin			if (pdata >= 0) {
177156668Sshin				(void) close(pdata);
177256668Sshin				pdata = -1;
177356668Sshin			}
177456668Sshin			reply(200, "%s command successful.", pcmd);
177556668Sshin		}
177656668Sshin		return 1;
177756668Sshin	}
177856668Sshin	return 0;
177956668Sshin}
178056668Sshin
178156668Sshinstatic void
178290148Simpv4map_data_dest(void)
178356668Sshin{
178456668Sshin	struct in_addr savedaddr;
178556668Sshin	int savedport;
178656668Sshin
178756668Sshin	if (data_dest.su_family != AF_INET) {
178856668Sshin		usedefault = 1;
178956668Sshin		reply(500, "Invalid address rejected.");
179056668Sshin		return;
179156668Sshin	}
179256668Sshin
179356668Sshin	savedaddr = data_dest.su_sin.sin_addr;
179456668Sshin	savedport = data_dest.su_port;
179556668Sshin
179656668Sshin	memset(&data_dest, 0, sizeof(data_dest));
179756668Sshin	data_dest.su_sin6.sin6_len = sizeof(struct sockaddr_in6);
179856668Sshin	data_dest.su_sin6.sin6_family = AF_INET6;
179956668Sshin	data_dest.su_sin6.sin6_port = savedport;
180056668Sshin	memset((caddr_t)&data_dest.su_sin6.sin6_addr.s6_addr[10], 0xff, 2);
180156668Sshin	memcpy((caddr_t)&data_dest.su_sin6.sin6_addr.s6_addr[12],
180256668Sshin	       (caddr_t)&savedaddr, sizeof(savedaddr));
180356668Sshin}
180456668Sshin#endif
1805