ftpcmd.y revision 137852
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 137852 2004-11-18 11:45:13Z 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) {
186137852Syar				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) {
209137852Syar				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) {
239137852Syar				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)
332137852Syar				reply(501, "No PASV allowed after EPSV ALL.");
33356668Sshin			else if ($2)
33417433Spst				passive();
3351592Srgrimes		}
33656668Sshin	| LPSV check_login CRLF
33756668Sshin		{
33856668Sshin			if (epsvall)
339137852Syar				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) {
366137852Syar				reply(200, "EPSV ALL command successful.");
36756668Sshin				epsvall++;
36856668Sshin			}
36956668Sshin		}
37070102Sphk	| EPSV check_login_epsv CRLF
37156668Sshin		{
37256668Sshin			if ($2)
37356668Sshin				long_passive("EPSV", PF_UNSPEC);
37456668Sshin		}
37571278Sjedgar	| TYPE check_login SP type_code CRLF
3761592Srgrimes		{
37771278Sjedgar			if ($2) {
37871278Sjedgar				switch (cmd_type) {
3791592Srgrimes
38071278Sjedgar				case TYPE_A:
38171278Sjedgar					if (cmd_form == FORM_N) {
38271278Sjedgar						reply(200, "Type set to A.");
38371278Sjedgar						type = cmd_type;
38471278Sjedgar						form = cmd_form;
38571278Sjedgar					} else
38671278Sjedgar						reply(504, "Form must be N.");
38771278Sjedgar					break;
3881592Srgrimes
38971278Sjedgar				case TYPE_E:
39071278Sjedgar					reply(504, "Type E not implemented.");
39171278Sjedgar					break;
3921592Srgrimes
39371278Sjedgar				case TYPE_I:
39471278Sjedgar					reply(200, "Type set to I.");
39571278Sjedgar					type = cmd_type;
39671278Sjedgar					break;
3971592Srgrimes
39871278Sjedgar				case TYPE_L:
399103949Smike#if CHAR_BIT == 8
40071278Sjedgar					if (cmd_bytesz == 8) {
40171278Sjedgar						reply(200,
40271278Sjedgar						    "Type set to L (byte size 8).");
40371278Sjedgar						type = cmd_type;
40471278Sjedgar					} else
40571278Sjedgar						reply(504, "Byte size must be 8.");
406103949Smike#else /* CHAR_BIT == 8 */
407103949Smike					UNIMPLEMENTED for CHAR_BIT != 8
408103949Smike#endif /* CHAR_BIT == 8 */
40971278Sjedgar				}
4101592Srgrimes			}
4111592Srgrimes		}
41271278Sjedgar	| STRU check_login SP struct_code CRLF
4131592Srgrimes		{
41471278Sjedgar			if ($2) {
41571278Sjedgar				switch ($4) {
4161592Srgrimes
41771278Sjedgar				case STRU_F:
418137852Syar					reply(200, "STRU F accepted.");
41971278Sjedgar					break;
4201592Srgrimes
42171278Sjedgar				default:
42271278Sjedgar					reply(504, "Unimplemented STRU type.");
42371278Sjedgar				}
4241592Srgrimes			}
4251592Srgrimes		}
42671278Sjedgar	| MODE check_login SP mode_code CRLF
4271592Srgrimes		{
42871278Sjedgar			if ($2) {
42971278Sjedgar				switch ($4) {
4301592Srgrimes
43171278Sjedgar				case MODE_S:
432137852Syar					reply(200, "MODE S accepted.");
43371278Sjedgar					break;
43471278Sjedgar
43571278Sjedgar				default:
43671278Sjedgar					reply(502, "Unimplemented MODE type.");
43771278Sjedgar				}
4381592Srgrimes			}
4391592Srgrimes		}
44071278Sjedgar	| ALLO check_login SP NUMBER CRLF
4411592Srgrimes		{
44271278Sjedgar			if ($2) {
44371278Sjedgar				reply(202, "ALLO command ignored.");
44471278Sjedgar			}
4451592Srgrimes		}
44671278Sjedgar	| ALLO check_login SP NUMBER SP R SP NUMBER CRLF
4471592Srgrimes		{
44871278Sjedgar			if ($2) {
44971278Sjedgar				reply(202, "ALLO command ignored.");
45071278Sjedgar			}
4511592Srgrimes		}
4521592Srgrimes	| RETR check_login SP pathname CRLF
4531592Srgrimes		{
45482796Ssheldonh			if (noretr || (guest && noguestretr))
455137852Syar				reply(500, "RETR command disabled.");
45682460Snik			else if ($2 && $4 != NULL)
457132931Syar				retrieve(NULL, $4);
45882460Snik
4591592Srgrimes			if ($4 != NULL)
4601592Srgrimes				free($4);
4611592Srgrimes		}
46270102Sphk	| STOR check_login_ro SP pathname CRLF
4631592Srgrimes		{
4641592Srgrimes			if ($2 && $4 != NULL)
4651592Srgrimes				store($4, "w", 0);
4661592Srgrimes			if ($4 != NULL)
4671592Srgrimes				free($4);
4681592Srgrimes		}
46970102Sphk	| APPE check_login_ro SP pathname CRLF
4701592Srgrimes		{
4711592Srgrimes			if ($2 && $4 != NULL)
4721592Srgrimes				store($4, "a", 0);
4731592Srgrimes			if ($4 != NULL)
4741592Srgrimes				free($4);
4751592Srgrimes		}
4761592Srgrimes	| NLST check_login CRLF
4771592Srgrimes		{
4781592Srgrimes			if ($2)
4791592Srgrimes				send_file_list(".");
4801592Srgrimes		}
481101395Syar	| NLST check_login SP pathstring CRLF
4821592Srgrimes		{
483101395Syar			if ($2)
4841592Srgrimes				send_file_list($4);
485101395Syar			free($4);
4861592Srgrimes		}
4871592Srgrimes	| LIST check_login CRLF
4881592Srgrimes		{
4891592Srgrimes			if ($2)
490109380Syar				retrieve(_PATH_LS " -lgA", "");
4911592Srgrimes		}
49275567Speter	| LIST check_login SP pathstring CRLF
4931592Srgrimes		{
494101395Syar			if ($2)
495109380Syar				retrieve(_PATH_LS " -lgA %s", $4);
496101395Syar			free($4);
4971592Srgrimes		}
4981592Srgrimes	| STAT check_login SP pathname CRLF
4991592Srgrimes		{
5001592Srgrimes			if ($2 && $4 != NULL)
5011592Srgrimes				statfilecmd($4);
5021592Srgrimes			if ($4 != NULL)
5031592Srgrimes				free($4);
5041592Srgrimes		}
50571278Sjedgar	| STAT check_login CRLF
5061592Srgrimes		{
50771278Sjedgar			if ($2) {
50871278Sjedgar				statcmd();
50971278Sjedgar			}
5101592Srgrimes		}
51170102Sphk	| DELE check_login_ro SP pathname CRLF
5121592Srgrimes		{
5131592Srgrimes			if ($2 && $4 != NULL)
5141592Srgrimes				delete($4);
5151592Srgrimes			if ($4 != NULL)
5161592Srgrimes				free($4);
5171592Srgrimes		}
51870102Sphk	| RNTO check_login_ro SP pathname CRLF
5191592Srgrimes		{
520101379Syar			if ($2 && $4 != NULL) {
52117433Spst				if (fromname) {
52217433Spst					renamecmd(fromname, $4);
52317433Spst					free(fromname);
524132931Syar					fromname = NULL;
52517433Spst				} else {
52617433Spst					reply(503, "Bad sequence of commands.");
52717433Spst				}
5281592Srgrimes			}
529101379Syar			if ($4 != NULL)
530101379Syar				free($4);
5311592Srgrimes		}
53271278Sjedgar	| ABOR check_login CRLF
5331592Srgrimes		{
53471278Sjedgar			if ($2)
53571278Sjedgar				reply(225, "ABOR command successful.");
5361592Srgrimes		}
5371592Srgrimes	| CWD check_login CRLF
5381592Srgrimes		{
53969234Sdanny			if ($2) {
540110036Syar				cwd(homedir);
54169234Sdanny			}
5421592Srgrimes		}
5431592Srgrimes	| CWD check_login SP pathname CRLF
5441592Srgrimes		{
5451592Srgrimes			if ($2 && $4 != NULL)
5461592Srgrimes				cwd($4);
5471592Srgrimes			if ($4 != NULL)
5481592Srgrimes				free($4);
5491592Srgrimes		}
5501592Srgrimes	| HELP CRLF
5511592Srgrimes		{
552132931Syar			help(cmdtab, NULL);
5531592Srgrimes		}
5541592Srgrimes	| HELP SP STRING CRLF
5551592Srgrimes		{
5561592Srgrimes			char *cp = $3;
5571592Srgrimes
5581592Srgrimes			if (strncasecmp(cp, "SITE", 4) == 0) {
5591592Srgrimes				cp = $3 + 4;
5601592Srgrimes				if (*cp == ' ')
5611592Srgrimes					cp++;
5621592Srgrimes				if (*cp)
5631592Srgrimes					help(sitetab, cp);
5641592Srgrimes				else
565132931Syar					help(sitetab, NULL);
5661592Srgrimes			} else
5671592Srgrimes				help(cmdtab, $3);
56888935Sdwmalone			free($3);
5691592Srgrimes		}
5701592Srgrimes	| NOOP CRLF
5711592Srgrimes		{
5721592Srgrimes			reply(200, "NOOP command successful.");
5731592Srgrimes		}
57470102Sphk	| MKD check_login_ro SP pathname CRLF
5751592Srgrimes		{
5761592Srgrimes			if ($2 && $4 != NULL)
5771592Srgrimes				makedir($4);
5781592Srgrimes			if ($4 != NULL)
5791592Srgrimes				free($4);
5801592Srgrimes		}
58170102Sphk	| RMD check_login_ro SP pathname CRLF
5821592Srgrimes		{
5831592Srgrimes			if ($2 && $4 != NULL)
5841592Srgrimes				removedir($4);
5851592Srgrimes			if ($4 != NULL)
5861592Srgrimes				free($4);
5871592Srgrimes		}
5881592Srgrimes	| PWD check_login CRLF
5891592Srgrimes		{
5901592Srgrimes			if ($2)
5911592Srgrimes				pwd();
5921592Srgrimes		}
5931592Srgrimes	| CDUP check_login CRLF
5941592Srgrimes		{
5951592Srgrimes			if ($2)
5961592Srgrimes				cwd("..");
5971592Srgrimes		}
5981592Srgrimes	| SITE SP HELP CRLF
5991592Srgrimes		{
600132931Syar			help(sitetab, NULL);
6011592Srgrimes		}
6021592Srgrimes	| SITE SP HELP SP STRING CRLF
6031592Srgrimes		{
6041592Srgrimes			help(sitetab, $5);
60588935Sdwmalone			free($5);
6061592Srgrimes		}
60775535Sphk	| SITE SP MDFIVE check_login SP pathname CRLF
60875535Sphk		{
60975535Sphk			char p[64], *q;
61075535Sphk
611101379Syar			if ($4 && $6) {
61275535Sphk				q = MD5File($6, p);
61375535Sphk				if (q != NULL)
61475535Sphk					reply(200, "MD5(%s) = %s", $6, p);
61575535Sphk				else
61675535Sphk					perror_reply(550, $6);
61775535Sphk			}
61888935Sdwmalone			if ($6)
61988935Sdwmalone				free($6);
62075535Sphk		}
6211592Srgrimes	| SITE SP UMASK check_login CRLF
6221592Srgrimes		{
6231592Srgrimes			int oldmask;
6241592Srgrimes
6251592Srgrimes			if ($4) {
6261592Srgrimes				oldmask = umask(0);
6271592Srgrimes				(void) umask(oldmask);
628137852Syar				reply(200, "Current UMASK is %03o.", oldmask);
6291592Srgrimes			}
6301592Srgrimes		}
6311592Srgrimes	| SITE SP UMASK check_login SP octal_number CRLF
6321592Srgrimes		{
6331592Srgrimes			int oldmask;
6341592Srgrimes
6351592Srgrimes			if ($4) {
6361592Srgrimes				if (($6 == -1) || ($6 > 0777)) {
637137852Syar					reply(501, "Bad UMASK value.");
6381592Srgrimes				} else {
6391592Srgrimes					oldmask = umask($6);
6401592Srgrimes					reply(200,
641137852Syar					    "UMASK set to %03o (was %03o).",
6421592Srgrimes					    $6, oldmask);
6431592Srgrimes				}
6441592Srgrimes			}
6451592Srgrimes		}
64670102Sphk	| SITE SP CHMOD check_login_ro SP octal_number SP pathname CRLF
6471592Srgrimes		{
6481592Srgrimes			if ($4 && ($8 != NULL)) {
649101378Syar				if (($6 == -1 ) || ($6 > 0777))
650137852Syar					reply(501, "Bad mode value.");
6511592Srgrimes				else if (chmod($8, $6) < 0)
6521592Srgrimes					perror_reply(550, $8);
6531592Srgrimes				else
6541592Srgrimes					reply(200, "CHMOD command successful.");
6551592Srgrimes			}
6561592Srgrimes			if ($8 != NULL)
6571592Srgrimes				free($8);
6581592Srgrimes		}
65971278Sjedgar	| SITE SP check_login IDLE CRLF
6601592Srgrimes		{
66171278Sjedgar			if ($3)
66271278Sjedgar				reply(200,
663137852Syar			    	    "Current IDLE time limit is %d seconds; max %d.",
66471278Sjedgar				    timeout, maxtimeout);
6651592Srgrimes		}
66671278Sjedgar	| SITE SP check_login IDLE SP NUMBER CRLF
6671592Srgrimes		{
66871278Sjedgar			if ($3) {
66992272Smaxim				if ($6.i < 30 || $6.i > maxtimeout) {
67071278Sjedgar					reply(501,
671137852Syar					    "Maximum IDLE time must be between 30 and %d seconds.",
67271278Sjedgar					    maxtimeout);
67371278Sjedgar				} else {
67492272Smaxim					timeout = $6.i;
675137659Syar					(void) alarm(timeout);
67671278Sjedgar					reply(200,
677137852Syar					    "Maximum IDLE time set to %d seconds.",
67871278Sjedgar					    timeout);
67971278Sjedgar				}
6801592Srgrimes			}
6811592Srgrimes		}
68270102Sphk	| STOU check_login_ro SP pathname CRLF
6831592Srgrimes		{
6841592Srgrimes			if ($2 && $4 != NULL)
6851592Srgrimes				store($4, "w", 1);
6861592Srgrimes			if ($4 != NULL)
6871592Srgrimes				free($4);
6881592Srgrimes		}
68971278Sjedgar	| SYST check_login CRLF
6901592Srgrimes		{
691116439Syar			if ($2) {
692116439Syar				if (hostinfo)
6931592Srgrimes#ifdef BSD
694116439Syar					reply(215, "UNIX Type: L%d Version: BSD-%d",
695116439Syar					      CHAR_BIT, BSD);
6961592Srgrimes#else /* BSD */
697116439Syar					reply(215, "UNIX Type: L%d", CHAR_BIT);
6981592Srgrimes#endif /* BSD */
699116439Syar				else
700116439Syar					reply(215, "UNKNOWN Type: L%d", CHAR_BIT);
701116439Syar			}
7021592Srgrimes		}
7031592Srgrimes
7041592Srgrimes		/*
7051592Srgrimes		 * SIZE is not in RFC959, but Postel has blessed it and
7061592Srgrimes		 * it will be in the updated RFC.
7071592Srgrimes		 *
7081592Srgrimes		 * Return size of file in a format suitable for
7091592Srgrimes		 * using with RESTART (we just count bytes).
7101592Srgrimes		 */
7111592Srgrimes	| SIZE check_login SP pathname CRLF
7121592Srgrimes		{
7131592Srgrimes			if ($2 && $4 != NULL)
7141592Srgrimes				sizecmd($4);
7151592Srgrimes			if ($4 != NULL)
7161592Srgrimes				free($4);
7171592Srgrimes		}
7181592Srgrimes
7191592Srgrimes		/*
7201592Srgrimes		 * MDTM is not in RFC959, but Postel has blessed it and
7211592Srgrimes		 * it will be in the updated RFC.
7221592Srgrimes		 *
7231592Srgrimes		 * Return modification time of file as an ISO 3307
7241592Srgrimes		 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
7251592Srgrimes		 * where xxx is the fractional second (of any precision,
7261592Srgrimes		 * not necessarily 3 digits)
7271592Srgrimes		 */
7281592Srgrimes	| MDTM check_login SP pathname CRLF
7291592Srgrimes		{
7301592Srgrimes			if ($2 && $4 != NULL) {
7311592Srgrimes				struct stat stbuf;
7321592Srgrimes				if (stat($4, &stbuf) < 0)
733137850Syar					perror_reply(550, $4);
7341592Srgrimes				else if (!S_ISREG(stbuf.st_mode)) {
7351592Srgrimes					reply(550, "%s: not a plain file.", $4);
7361592Srgrimes				} else {
7371592Srgrimes					struct tm *t;
7381592Srgrimes					t = gmtime(&stbuf.st_mtime);
7391592Srgrimes					reply(213,
74017435Spst					    "%04d%02d%02d%02d%02d%02d",
74117435Spst					    1900 + t->tm_year,
74217435Spst					    t->tm_mon+1, t->tm_mday,
7431592Srgrimes					    t->tm_hour, t->tm_min, t->tm_sec);
7441592Srgrimes				}
7451592Srgrimes			}
7461592Srgrimes			if ($4 != NULL)
7471592Srgrimes				free($4);
7481592Srgrimes		}
7491592Srgrimes	| QUIT CRLF
7501592Srgrimes		{
7511592Srgrimes			reply(221, "Goodbye.");
7521592Srgrimes			dologout(0);
7531592Srgrimes		}
754102565Syar	| NOTIMPL
755102565Syar		{
756102565Syar			nack($1);
757102565Syar		}
75889935Syar	| error
7591592Srgrimes		{
76089935Syar			yyclearin;		/* discard lookahead data */
76189935Syar			yyerrok;		/* clear error condition */
762102565Syar			state = CMD;		/* reset lexer state */
7631592Srgrimes		}
7641592Srgrimes	;
7651592Srgrimesrcmd
76670102Sphk	: RNFR check_login_ro SP pathname CRLF
7671592Srgrimes		{
768132930Syar			restart_point = 0;
7691592Srgrimes			if ($2 && $4) {
77088935Sdwmalone				if (fromname)
77188935Sdwmalone					free(fromname);
772132931Syar				fromname = NULL;
77388935Sdwmalone				if (renamefrom($4))
77488935Sdwmalone					fromname = $4;
77588935Sdwmalone				else
7761592Srgrimes					free($4);
77788935Sdwmalone			} else if ($4) {
77888935Sdwmalone				free($4);
7791592Srgrimes			}
7801592Srgrimes		}
78192272Smaxim	| REST check_login SP NUMBER CRLF
7821592Srgrimes		{
78371278Sjedgar			if ($2) {
78488935Sdwmalone				if (fromname)
78588935Sdwmalone					free(fromname);
786132931Syar				fromname = NULL;
78792272Smaxim				restart_point = $4.o;
788132929Syar				reply(350, "Restarting at %jd. %s",
789132929Syar				    (intmax_t)restart_point,
79071278Sjedgar				    "Send STORE or RETRIEVE to initiate transfer.");
79171278Sjedgar			}
7921592Srgrimes		}
7931592Srgrimes	;
7941592Srgrimes
7951592Srgrimesusername
7961592Srgrimes	: STRING
7971592Srgrimes	;
7981592Srgrimes
7991592Srgrimespassword
8001592Srgrimes	: /* empty */
8011592Srgrimes		{
8021592Srgrimes			$$ = (char *)calloc(1, sizeof(char));
8031592Srgrimes		}
8041592Srgrimes	| STRING
8051592Srgrimes	;
8061592Srgrimes
8071592Srgrimesbyte_size
8081592Srgrimes	: NUMBER
80992272Smaxim		{
81092272Smaxim			$$ = $1.i;
81192272Smaxim		}
8121592Srgrimes	;
8131592Srgrimes
8141592Srgrimeshost_port
8151592Srgrimes	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
8161592Srgrimes		NUMBER COMMA NUMBER
8171592Srgrimes		{
8181592Srgrimes			char *a, *p;
8191592Srgrimes
82056668Sshin			data_dest.su_len = sizeof(struct sockaddr_in);
82156668Sshin			data_dest.su_family = AF_INET;
82256668Sshin			p = (char *)&data_dest.su_sin.sin_port;
82392272Smaxim			p[0] = $9.i; p[1] = $11.i;
82456668Sshin			a = (char *)&data_dest.su_sin.sin_addr;
82592272Smaxim			a[0] = $1.i; a[1] = $3.i; a[2] = $5.i; a[3] = $7.i;
8261592Srgrimes		}
8271592Srgrimes	;
8281592Srgrimes
82956668Sshinhost_long_port
83056668Sshin	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
83156668Sshin		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
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
83656668Sshin		{
83756668Sshin			char *a, *p;
83856668Sshin
83956668Sshin			memset(&data_dest, 0, sizeof(data_dest));
84056668Sshin			data_dest.su_len = sizeof(struct sockaddr_in6);
84156668Sshin			data_dest.su_family = AF_INET6;
84256668Sshin			p = (char *)&data_dest.su_port;
84392272Smaxim			p[0] = $39.i; p[1] = $41.i;
84456668Sshin			a = (char *)&data_dest.su_sin6.sin6_addr;
84592272Smaxim			a[0] = $5.i; a[1] = $7.i; a[2] = $9.i; a[3] = $11.i;
84692272Smaxim			a[4] = $13.i; a[5] = $15.i; a[6] = $17.i; a[7] = $19.i;
84792272Smaxim			a[8] = $21.i; a[9] = $23.i; a[10] = $25.i; a[11] = $27.i;
84892272Smaxim			a[12] = $29.i; a[13] = $31.i; a[14] = $33.i; a[15] = $35.i;
84956668Sshin			if (his_addr.su_family == AF_INET6) {
85056668Sshin				/* XXX more sanity checks! */
85156668Sshin				data_dest.su_sin6.sin6_scope_id =
85256668Sshin					his_addr.su_sin6.sin6_scope_id;
85356668Sshin			}
85492272Smaxim			if ($1.i != 6 || $3.i != 16 || $37.i != 2)
85556668Sshin				memset(&data_dest, 0, sizeof(data_dest));
85656668Sshin		}
85756668Sshin	| NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
85856668Sshin		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
85956668Sshin		NUMBER
86056668Sshin		{
86156668Sshin			char *a, *p;
86256668Sshin
86356668Sshin			memset(&data_dest, 0, sizeof(data_dest));
86456668Sshin			data_dest.su_sin.sin_len = sizeof(struct sockaddr_in);
86556668Sshin			data_dest.su_family = AF_INET;
86656668Sshin			p = (char *)&data_dest.su_port;
86792272Smaxim			p[0] = $15.i; p[1] = $17.i;
86856668Sshin			a = (char *)&data_dest.su_sin.sin_addr;
86992272Smaxim			a[0] =  $5.i; a[1] = $7.i; a[2] = $9.i; a[3] = $11.i;
87092272Smaxim			if ($1.i != 4 || $3.i != 4 || $13.i != 2)
87156668Sshin				memset(&data_dest, 0, sizeof(data_dest));
87256668Sshin		}
87356668Sshin	;
87456668Sshin
8751592Srgrimesform_code
8761592Srgrimes	: N
8771592Srgrimes		{
8781592Srgrimes			$$ = FORM_N;
8791592Srgrimes		}
8801592Srgrimes	| T
8811592Srgrimes		{
8821592Srgrimes			$$ = FORM_T;
8831592Srgrimes		}
8841592Srgrimes	| C
8851592Srgrimes		{
8861592Srgrimes			$$ = FORM_C;
8871592Srgrimes		}
8881592Srgrimes	;
8891592Srgrimes
8901592Srgrimestype_code
8911592Srgrimes	: A
8921592Srgrimes		{
8931592Srgrimes			cmd_type = TYPE_A;
8941592Srgrimes			cmd_form = FORM_N;
8951592Srgrimes		}
8961592Srgrimes	| A SP form_code
8971592Srgrimes		{
8981592Srgrimes			cmd_type = TYPE_A;
8991592Srgrimes			cmd_form = $3;
9001592Srgrimes		}
9011592Srgrimes	| E
9021592Srgrimes		{
9031592Srgrimes			cmd_type = TYPE_E;
9041592Srgrimes			cmd_form = FORM_N;
9051592Srgrimes		}
9061592Srgrimes	| E SP form_code
9071592Srgrimes		{
9081592Srgrimes			cmd_type = TYPE_E;
9091592Srgrimes			cmd_form = $3;
9101592Srgrimes		}
9111592Srgrimes	| I
9121592Srgrimes		{
9131592Srgrimes			cmd_type = TYPE_I;
9141592Srgrimes		}
9151592Srgrimes	| L
9161592Srgrimes		{
9171592Srgrimes			cmd_type = TYPE_L;
918103949Smike			cmd_bytesz = CHAR_BIT;
9191592Srgrimes		}
9201592Srgrimes	| L SP byte_size
9211592Srgrimes		{
9221592Srgrimes			cmd_type = TYPE_L;
9231592Srgrimes			cmd_bytesz = $3;
9241592Srgrimes		}
9251592Srgrimes		/* this is for a bug in the BBN ftp */
9261592Srgrimes	| L byte_size
9271592Srgrimes		{
9281592Srgrimes			cmd_type = TYPE_L;
9291592Srgrimes			cmd_bytesz = $2;
9301592Srgrimes		}
9311592Srgrimes	;
9321592Srgrimes
9331592Srgrimesstruct_code
9341592Srgrimes	: F
9351592Srgrimes		{
9361592Srgrimes			$$ = STRU_F;
9371592Srgrimes		}
9381592Srgrimes	| R
9391592Srgrimes		{
9401592Srgrimes			$$ = STRU_R;
9411592Srgrimes		}
9421592Srgrimes	| P
9431592Srgrimes		{
9441592Srgrimes			$$ = STRU_P;
9451592Srgrimes		}
9461592Srgrimes	;
9471592Srgrimes
9481592Srgrimesmode_code
9491592Srgrimes	: S
9501592Srgrimes		{
9511592Srgrimes			$$ = MODE_S;
9521592Srgrimes		}
9531592Srgrimes	| B
9541592Srgrimes		{
9551592Srgrimes			$$ = MODE_B;
9561592Srgrimes		}
9571592Srgrimes	| C
9581592Srgrimes		{
9591592Srgrimes			$$ = MODE_C;
9601592Srgrimes		}
9611592Srgrimes	;
9621592Srgrimes
9631592Srgrimespathname
9641592Srgrimes	: pathstring
9651592Srgrimes		{
96675567Speter			if (logged_in && $1) {
967110340Syar				char *p;
9681592Srgrimes
969110340Syar				/*
970110340Syar				 * Expand ~user manually since glob(3)
971110340Syar				 * will return the unexpanded pathname
972110340Syar				 * if the corresponding file/directory
973110340Syar				 * doesn't exist yet.  Using sole glob(3)
974110340Syar				 * would break natural commands like
975110340Syar				 * MKD ~user/newdir
976110340Syar				 * or
977110340Syar				 * RNTO ~/newfile
978110340Syar				 */
979110340Syar				if ((p = exptilde($1)) != NULL) {
980110340Syar					$$ = expglob(p);
981110340Syar					free(p);
982110340Syar				} else
9831592Srgrimes					$$ = NULL;
9841592Srgrimes				free($1);
9851592Srgrimes			} else
9861592Srgrimes				$$ = $1;
9871592Srgrimes		}
9881592Srgrimes	;
9891592Srgrimes
9901592Srgrimespathstring
9911592Srgrimes	: STRING
9921592Srgrimes	;
9931592Srgrimes
9941592Srgrimesoctal_number
9951592Srgrimes	: NUMBER
9961592Srgrimes		{
9971592Srgrimes			int ret, dec, multby, digit;
9981592Srgrimes
9991592Srgrimes			/*
10001592Srgrimes			 * Convert a number that was read as decimal number
10011592Srgrimes			 * to what it would be if it had been read as octal.
10021592Srgrimes			 */
100392272Smaxim			dec = $1.i;
10041592Srgrimes			multby = 1;
10051592Srgrimes			ret = 0;
10061592Srgrimes			while (dec) {
10071592Srgrimes				digit = dec%10;
10081592Srgrimes				if (digit > 7) {
10091592Srgrimes					ret = -1;
10101592Srgrimes					break;
10111592Srgrimes				}
10121592Srgrimes				ret += digit * multby;
10131592Srgrimes				multby *= 8;
10141592Srgrimes				dec /= 10;
10151592Srgrimes			}
10161592Srgrimes			$$ = ret;
10171592Srgrimes		}
10181592Srgrimes	;
10191592Srgrimes
10201592Srgrimes
10211592Srgrimescheck_login
10221592Srgrimes	: /* empty */
10231592Srgrimes		{
102470102Sphk		$$ = check_login1();
10251592Srgrimes		}
10261592Srgrimes	;
10271592Srgrimes
102870102Sphkcheck_login_epsv
102970102Sphk	: /* empty */
103070102Sphk		{
103170102Sphk		if (noepsv) {
1032137852Syar			reply(500, "EPSV command disabled.");
103370102Sphk			$$ = 0;
103470102Sphk		}
103570102Sphk		else
103670102Sphk			$$ = check_login1();
103770102Sphk		}
103870102Sphk	;
103970102Sphk
104070102Sphkcheck_login_ro
104170102Sphk	: /* empty */
104270102Sphk		{
104370102Sphk		if (readonly) {
104472710Sdes			reply(550, "Permission denied.");
104570102Sphk			$$ = 0;
104670102Sphk		}
104770102Sphk		else
104870102Sphk			$$ = check_login1();
104970102Sphk		}
105070102Sphk	;
105170102Sphk
10521592Srgrimes%%
10531592Srgrimes
10541592Srgrimes#define	CMD	0	/* beginning of command */
10551592Srgrimes#define	ARGS	1	/* expect miscellaneous arguments */
10561592Srgrimes#define	STR1	2	/* expect SP followed by STRING */
10571592Srgrimes#define	STR2	3	/* expect STRING */
10581592Srgrimes#define	OSTR	4	/* optional SP then STRING */
105975556Sgreen#define	ZSTR1	5	/* optional SP then optional STRING */
10601592Srgrimes#define	ZSTR2	6	/* optional STRING after SP */
10611592Srgrimes#define	SITECMD	7	/* SITE command */
10621592Srgrimes#define	NSTR	8	/* Number followed by a string */
10631592Srgrimes
106475560Sjedgar#define	MAXGLOBARGS	1000
106575560Sjedgar
1066101034Syar#define	MAXASIZE	10240	/* Deny ASCII SIZE on files larger than that */
1067101034Syar
10681592Srgrimesstruct tab {
10691592Srgrimes	char	*name;
10701592Srgrimes	short	token;
10711592Srgrimes	short	state;
10721592Srgrimes	short	implemented;	/* 1 if command is implemented */
10731592Srgrimes	char	*help;
10741592Srgrimes};
10751592Srgrimes
10761592Srgrimesstruct tab cmdtab[] = {		/* In order defined in RFC 765 */
10771592Srgrimes	{ "USER", USER, STR1, 1,	"<sp> username" },
107875556Sgreen	{ "PASS", PASS, ZSTR1, 1,	"[<sp> [password]]" },
10791592Srgrimes	{ "ACCT", ACCT, STR1, 0,	"(specify account)" },
10801592Srgrimes	{ "SMNT", SMNT, ARGS, 0,	"(structure mount)" },
10811592Srgrimes	{ "REIN", REIN, ARGS, 0,	"(reinitialize server state)" },
10821592Srgrimes	{ "QUIT", QUIT, ARGS, 1,	"(terminate service)", },
1083101806Syar	{ "PORT", PORT, ARGS, 1,	"<sp> b0, b1, b2, b3, b4, b5" },
108456668Sshin	{ "LPRT", LPRT, ARGS, 1,	"<sp> af, hal, h1, h2, h3,..., pal, p1, p2..." },
108556668Sshin	{ "EPRT", EPRT, STR1, 1,	"<sp> |af|addr|port|" },
10861592Srgrimes	{ "PASV", PASV, ARGS, 1,	"(set server in passive mode)" },
108756668Sshin	{ "LPSV", LPSV, ARGS, 1,	"(set server in passive mode)" },
108856668Sshin	{ "EPSV", EPSV, ARGS, 1,	"[<sp> af|ALL]" },
1089101806Syar	{ "TYPE", TYPE, ARGS, 1,	"<sp> { A | E | I | L }" },
10901592Srgrimes	{ "STRU", STRU, ARGS, 1,	"(specify file structure)" },
10911592Srgrimes	{ "MODE", MODE, ARGS, 1,	"(specify transfer mode)" },
10921592Srgrimes	{ "RETR", RETR, STR1, 1,	"<sp> file-name" },
10931592Srgrimes	{ "STOR", STOR, STR1, 1,	"<sp> file-name" },
10941592Srgrimes	{ "APPE", APPE, STR1, 1,	"<sp> file-name" },
10951592Srgrimes	{ "MLFL", MLFL, OSTR, 0,	"(mail file)" },
10961592Srgrimes	{ "MAIL", MAIL, OSTR, 0,	"(mail to user)" },
10971592Srgrimes	{ "MSND", MSND, OSTR, 0,	"(mail send to terminal)" },
10981592Srgrimes	{ "MSOM", MSOM, OSTR, 0,	"(mail send to terminal or mailbox)" },
10991592Srgrimes	{ "MSAM", MSAM, OSTR, 0,	"(mail send to terminal and mailbox)" },
11001592Srgrimes	{ "MRSQ", MRSQ, OSTR, 0,	"(mail recipient scheme question)" },
11011592Srgrimes	{ "MRCP", MRCP, STR1, 0,	"(mail recipient)" },
11021592Srgrimes	{ "ALLO", ALLO, ARGS, 1,	"allocate storage (vacuously)" },
11031592Srgrimes	{ "REST", REST, ARGS, 1,	"<sp> offset (restart command)" },
11041592Srgrimes	{ "RNFR", RNFR, STR1, 1,	"<sp> file-name" },
11051592Srgrimes	{ "RNTO", RNTO, STR1, 1,	"<sp> file-name" },
11061592Srgrimes	{ "ABOR", ABOR, ARGS, 1,	"(abort operation)" },
11071592Srgrimes	{ "DELE", DELE, STR1, 1,	"<sp> file-name" },
11081592Srgrimes	{ "CWD",  CWD,  OSTR, 1,	"[ <sp> directory-name ]" },
11091592Srgrimes	{ "XCWD", CWD,	OSTR, 1,	"[ <sp> directory-name ]" },
11101592Srgrimes	{ "LIST", LIST, OSTR, 1,	"[ <sp> path-name ]" },
11111592Srgrimes	{ "NLST", NLST, OSTR, 1,	"[ <sp> path-name ]" },
11121592Srgrimes	{ "SITE", SITE, SITECMD, 1,	"site-cmd [ <sp> arguments ]" },
11131592Srgrimes	{ "SYST", SYST, ARGS, 1,	"(get type of operating system)" },
11141592Srgrimes	{ "STAT", STAT, OSTR, 1,	"[ <sp> path-name ]" },
11151592Srgrimes	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
11161592Srgrimes	{ "NOOP", NOOP, ARGS, 1,	"" },
11171592Srgrimes	{ "MKD",  MKD,  STR1, 1,	"<sp> path-name" },
11181592Srgrimes	{ "XMKD", MKD,  STR1, 1,	"<sp> path-name" },
11191592Srgrimes	{ "RMD",  RMD,  STR1, 1,	"<sp> path-name" },
11201592Srgrimes	{ "XRMD", RMD,  STR1, 1,	"<sp> path-name" },
11211592Srgrimes	{ "PWD",  PWD,  ARGS, 1,	"(return current directory)" },
11221592Srgrimes	{ "XPWD", PWD,  ARGS, 1,	"(return current directory)" },
11231592Srgrimes	{ "CDUP", CDUP, ARGS, 1,	"(change to parent directory)" },
11241592Srgrimes	{ "XCUP", CDUP, ARGS, 1,	"(change to parent directory)" },
11251592Srgrimes	{ "STOU", STOU, STR1, 1,	"<sp> file-name" },
11261592Srgrimes	{ "SIZE", SIZE, OSTR, 1,	"<sp> path-name" },
11271592Srgrimes	{ "MDTM", MDTM, OSTR, 1,	"<sp> path-name" },
11281592Srgrimes	{ NULL,   0,    0,    0,	0 }
11291592Srgrimes};
11301592Srgrimes
11311592Srgrimesstruct tab sitetab[] = {
113275535Sphk	{ "MD5", MDFIVE, STR1, 1,	"[ <sp> file-name ]" },
11331592Srgrimes	{ "UMASK", UMASK, ARGS, 1,	"[ <sp> umask ]" },
11341592Srgrimes	{ "IDLE", IDLE, ARGS, 1,	"[ <sp> maximum-idle-time ]" },
11351592Srgrimes	{ "CHMOD", CHMOD, NSTR, 1,	"<sp> mode <sp> file-name" },
11361592Srgrimes	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
11371592Srgrimes	{ NULL,   0,    0,    0,	0 }
11381592Srgrimes};
11391592Srgrimes
114090148Simpstatic char	*copy(char *);
1141110340Syarstatic char	*expglob(char *);
1142110340Syarstatic char	*exptilde(char *);
114390148Simpstatic void	 help(struct tab *, char *);
11441592Srgrimesstatic struct tab *
114590148Simp		 lookup(struct tab *, char *);
114690148Simpstatic int	 port_check(const char *);
114790148Simpstatic int	 port_check_v6(const char *);
114890148Simpstatic void	 sizecmd(char *);
114990148Simpstatic void	 toolong(int);
115090148Simpstatic void	 v4map_data_dest(void);
115190148Simpstatic int	 yylex(void);
11521592Srgrimes
11531592Srgrimesstatic struct tab *
115490148Simplookup(struct tab *p, char *cmd)
11551592Srgrimes{
11561592Srgrimes
11571592Srgrimes	for (; p->name != NULL; p++)
11581592Srgrimes		if (strcmp(cmd, p->name) == 0)
11591592Srgrimes			return (p);
11601592Srgrimes	return (0);
11611592Srgrimes}
11621592Srgrimes
11631592Srgrimes#include <arpa/telnet.h>
11641592Srgrimes
11651592Srgrimes/*
11661592Srgrimes * getline - a hacked up version of fgets to ignore TELNET escape codes.
11671592Srgrimes */
11681592Srgrimeschar *
116990148Simpgetline(char *s, int n, FILE *iop)
11701592Srgrimes{
11711592Srgrimes	int c;
11721592Srgrimes	register char *cs;
1173117352Syar	sigset_t sset, osset;
11741592Srgrimes
11751592Srgrimes	cs = s;
11761592Srgrimes/* tmpline may contain saved command from urgent mode interruption */
11771592Srgrimes	for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
11781592Srgrimes		*cs++ = tmpline[c];
11791592Srgrimes		if (tmpline[c] == '\n') {
11801592Srgrimes			*cs++ = '\0';
118176096Smarkm			if (ftpdebug)
11821592Srgrimes				syslog(LOG_DEBUG, "command: %s", s);
11831592Srgrimes			tmpline[0] = '\0';
11841592Srgrimes			return(s);
11851592Srgrimes		}
11861592Srgrimes		if (c == 0)
11871592Srgrimes			tmpline[0] = '\0';
11881592Srgrimes	}
1189117352Syar	/* SIGURG would interrupt stdio if not blocked during the read loop */
1190117352Syar	sigemptyset(&sset);
1191117352Syar	sigaddset(&sset, SIGURG);
1192117352Syar	sigprocmask(SIG_BLOCK, &sset, &osset);
11931592Srgrimes	while ((c = getc(iop)) != EOF) {
11941592Srgrimes		c &= 0377;
11951592Srgrimes		if (c == IAC) {
1196117351Syar			if ((c = getc(iop)) == EOF)
1197117351Syar				goto got_eof;
11981592Srgrimes			c &= 0377;
11991592Srgrimes			switch (c) {
12001592Srgrimes			case WILL:
12011592Srgrimes			case WONT:
1202117351Syar				if ((c = getc(iop)) == EOF)
1203117351Syar					goto got_eof;
12041592Srgrimes				printf("%c%c%c", IAC, DONT, 0377&c);
12051592Srgrimes				(void) fflush(stdout);
12061592Srgrimes				continue;
12071592Srgrimes			case DO:
12081592Srgrimes			case DONT:
1209117351Syar				if ((c = getc(iop)) == EOF)
1210117351Syar					goto got_eof;
12111592Srgrimes				printf("%c%c%c", IAC, WONT, 0377&c);
12121592Srgrimes				(void) fflush(stdout);
12131592Srgrimes				continue;
12141592Srgrimes			case IAC:
12151592Srgrimes				break;
12161592Srgrimes			default:
12171592Srgrimes				continue;	/* ignore command */
12181592Srgrimes			}
12191592Srgrimes		}
12201592Srgrimes		*cs++ = c;
12211592Srgrimes		if (--n <= 0 || c == '\n')
12221592Srgrimes			break;
12231592Srgrimes	}
1224117351Syargot_eof:
1225117352Syar	sigprocmask(SIG_SETMASK, &osset, NULL);
12261592Srgrimes	if (c == EOF && cs == s)
12271592Srgrimes		return (NULL);
12281592Srgrimes	*cs++ = '\0';
122976096Smarkm	if (ftpdebug) {
12301592Srgrimes		if (!guest && strncasecmp("pass ", s, 5) == 0) {
12311592Srgrimes			/* Don't syslog passwords */
12321592Srgrimes			syslog(LOG_DEBUG, "command: %.5s ???", s);
12331592Srgrimes		} else {
12341592Srgrimes			register char *cp;
12351592Srgrimes			register int len;
12361592Srgrimes
12371592Srgrimes			/* Don't syslog trailing CR-LF */
12381592Srgrimes			len = strlen(s);
12391592Srgrimes			cp = s + len - 1;
12401592Srgrimes			while (cp >= s && (*cp == '\n' || *cp == '\r')) {
12411592Srgrimes				--cp;
12421592Srgrimes				--len;
12431592Srgrimes			}
12441592Srgrimes			syslog(LOG_DEBUG, "command: %.*s", len, s);
12451592Srgrimes		}
12461592Srgrimes	}
12471592Srgrimes	return (s);
12481592Srgrimes}
12491592Srgrimes
12501592Srgrimesstatic void
125190148Simptoolong(int signo)
12521592Srgrimes{
12531592Srgrimes
12541592Srgrimes	reply(421,
12551592Srgrimes	    "Timeout (%d seconds): closing control connection.", timeout);
12561592Srgrimes	if (logging)
12571592Srgrimes		syslog(LOG_INFO, "User %s timed out after %d seconds",
12581592Srgrimes		    (pw ? pw -> pw_name : "unknown"), timeout);
12591592Srgrimes	dologout(1);
12601592Srgrimes}
12611592Srgrimes
12621592Srgrimesstatic int
126390148Simpyylex(void)
12641592Srgrimes{
126589935Syar	static int cpos;
12661592Srgrimes	char *cp, *cp2;
12671592Srgrimes	struct tab *p;
12681592Srgrimes	int n;
12691592Srgrimes	char c;
12701592Srgrimes
12711592Srgrimes	for (;;) {
12721592Srgrimes		switch (state) {
12731592Srgrimes
12741592Srgrimes		case CMD:
12751592Srgrimes			(void) signal(SIGALRM, toolong);
1276137659Syar			(void) alarm(timeout);
12771592Srgrimes			if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
12781592Srgrimes				reply(221, "You could at least say goodbye.");
12791592Srgrimes				dologout(0);
12801592Srgrimes			}
12811592Srgrimes			(void) alarm(0);
12821592Srgrimes#ifdef SETPROCTITLE
128329574Sphk			if (strncasecmp(cbuf, "PASS", 4) != 0)
12841592Srgrimes				setproctitle("%s: %s", proctitle, cbuf);
12851592Srgrimes#endif /* SETPROCTITLE */
12861592Srgrimes			if ((cp = strchr(cbuf, '\r'))) {
12871592Srgrimes				*cp++ = '\n';
12881592Srgrimes				*cp = '\0';
12891592Srgrimes			}
12901592Srgrimes			if ((cp = strpbrk(cbuf, " \n")))
12911592Srgrimes				cpos = cp - cbuf;
12921592Srgrimes			if (cpos == 0)
12931592Srgrimes				cpos = 4;
12941592Srgrimes			c = cbuf[cpos];
12951592Srgrimes			cbuf[cpos] = '\0';
12961592Srgrimes			upper(cbuf);
12971592Srgrimes			p = lookup(cmdtab, cbuf);
12981592Srgrimes			cbuf[cpos] = c;
12993776Spst			if (p != 0) {
1300102565Syar				yylval.s = p->name;
1301102565Syar				if (!p->implemented)
1302102565Syar					return (NOTIMPL); /* state remains CMD */
13031592Srgrimes				state = p->state;
13041592Srgrimes				return (p->token);
13051592Srgrimes			}
13061592Srgrimes			break;
13071592Srgrimes
13081592Srgrimes		case SITECMD:
13091592Srgrimes			if (cbuf[cpos] == ' ') {
13101592Srgrimes				cpos++;
13111592Srgrimes				return (SP);
13121592Srgrimes			}
13131592Srgrimes			cp = &cbuf[cpos];
13141592Srgrimes			if ((cp2 = strpbrk(cp, " \n")))
13151592Srgrimes				cpos = cp2 - cbuf;
13161592Srgrimes			c = cbuf[cpos];
13171592Srgrimes			cbuf[cpos] = '\0';
13181592Srgrimes			upper(cp);
13191592Srgrimes			p = lookup(sitetab, cp);
13201592Srgrimes			cbuf[cpos] = c;
13213777Spst			if (guest == 0 && p != 0) {
1322102565Syar				yylval.s = p->name;
1323102565Syar				if (!p->implemented) {
13241592Srgrimes					state = CMD;
1325102565Syar					return (NOTIMPL);
13261592Srgrimes				}
13271592Srgrimes				state = p->state;
13281592Srgrimes				return (p->token);
13291592Srgrimes			}
13301592Srgrimes			state = CMD;
13311592Srgrimes			break;
13321592Srgrimes
133375556Sgreen		case ZSTR1:
13341592Srgrimes		case OSTR:
13351592Srgrimes			if (cbuf[cpos] == '\n') {
13361592Srgrimes				state = CMD;
13371592Srgrimes				return (CRLF);
13381592Srgrimes			}
13391592Srgrimes			/* FALLTHROUGH */
13401592Srgrimes
13411592Srgrimes		case STR1:
13421592Srgrimes		dostr1:
13431592Srgrimes			if (cbuf[cpos] == ' ') {
13441592Srgrimes				cpos++;
134551979Salfred				state = state == OSTR ? STR2 : state+1;
13461592Srgrimes				return (SP);
13471592Srgrimes			}
13481592Srgrimes			break;
13491592Srgrimes
13501592Srgrimes		case ZSTR2:
13511592Srgrimes			if (cbuf[cpos] == '\n') {
13521592Srgrimes				state = CMD;
13531592Srgrimes				return (CRLF);
13541592Srgrimes			}
13551592Srgrimes			/* FALLTHROUGH */
13561592Srgrimes
13571592Srgrimes		case STR2:
13581592Srgrimes			cp = &cbuf[cpos];
13591592Srgrimes			n = strlen(cp);
13601592Srgrimes			cpos += n - 1;
13611592Srgrimes			/*
13621592Srgrimes			 * Make sure the string is nonempty and \n terminated.
13631592Srgrimes			 */
13641592Srgrimes			if (n > 1 && cbuf[cpos] == '\n') {
13651592Srgrimes				cbuf[cpos] = '\0';
13661592Srgrimes				yylval.s = copy(cp);
13671592Srgrimes				cbuf[cpos] = '\n';
13681592Srgrimes				state = ARGS;
13691592Srgrimes				return (STRING);
13701592Srgrimes			}
13711592Srgrimes			break;
13721592Srgrimes
13731592Srgrimes		case NSTR:
13741592Srgrimes			if (cbuf[cpos] == ' ') {
13751592Srgrimes				cpos++;
13761592Srgrimes				return (SP);
13771592Srgrimes			}
13781592Srgrimes			if (isdigit(cbuf[cpos])) {
13791592Srgrimes				cp = &cbuf[cpos];
13801592Srgrimes				while (isdigit(cbuf[++cpos]))
13811592Srgrimes					;
13821592Srgrimes				c = cbuf[cpos];
13831592Srgrimes				cbuf[cpos] = '\0';
138492272Smaxim				yylval.u.i = atoi(cp);
13851592Srgrimes				cbuf[cpos] = c;
13861592Srgrimes				state = STR1;
13871592Srgrimes				return (NUMBER);
13881592Srgrimes			}
13891592Srgrimes			state = STR1;
13901592Srgrimes			goto dostr1;
13911592Srgrimes
13921592Srgrimes		case ARGS:
13931592Srgrimes			if (isdigit(cbuf[cpos])) {
13941592Srgrimes				cp = &cbuf[cpos];
13951592Srgrimes				while (isdigit(cbuf[++cpos]))
13961592Srgrimes					;
13971592Srgrimes				c = cbuf[cpos];
13981592Srgrimes				cbuf[cpos] = '\0';
139992272Smaxim				yylval.u.i = atoi(cp);
1400137811Syar				yylval.u.o = strtoull(cp, NULL, 10);
14011592Srgrimes				cbuf[cpos] = c;
14021592Srgrimes				return (NUMBER);
14031592Srgrimes			}
140456668Sshin			if (strncasecmp(&cbuf[cpos], "ALL", 3) == 0
140556668Sshin			 && !isalnum(cbuf[cpos + 3])) {
140656668Sshin				cpos += 3;
140756668Sshin				return ALL;
140856668Sshin			}
14091592Srgrimes			switch (cbuf[cpos++]) {
14101592Srgrimes
14111592Srgrimes			case '\n':
14121592Srgrimes				state = CMD;
14131592Srgrimes				return (CRLF);
14141592Srgrimes
14151592Srgrimes			case ' ':
14161592Srgrimes				return (SP);
14171592Srgrimes
14181592Srgrimes			case ',':
14191592Srgrimes				return (COMMA);
14201592Srgrimes
14211592Srgrimes			case 'A':
14221592Srgrimes			case 'a':
14231592Srgrimes				return (A);
14241592Srgrimes
14251592Srgrimes			case 'B':
14261592Srgrimes			case 'b':
14271592Srgrimes				return (B);
14281592Srgrimes
14291592Srgrimes			case 'C':
14301592Srgrimes			case 'c':
14311592Srgrimes				return (C);
14321592Srgrimes
14331592Srgrimes			case 'E':
14341592Srgrimes			case 'e':
14351592Srgrimes				return (E);
14361592Srgrimes
14371592Srgrimes			case 'F':
14381592Srgrimes			case 'f':
14391592Srgrimes				return (F);
14401592Srgrimes
14411592Srgrimes			case 'I':
14421592Srgrimes			case 'i':
14431592Srgrimes				return (I);
14441592Srgrimes
14451592Srgrimes			case 'L':
14461592Srgrimes			case 'l':
14471592Srgrimes				return (L);
14481592Srgrimes
14491592Srgrimes			case 'N':
14501592Srgrimes			case 'n':
14511592Srgrimes				return (N);
14521592Srgrimes
14531592Srgrimes			case 'P':
14541592Srgrimes			case 'p':
14551592Srgrimes				return (P);
14561592Srgrimes
14571592Srgrimes			case 'R':
14581592Srgrimes			case 'r':
14591592Srgrimes				return (R);
14601592Srgrimes
14611592Srgrimes			case 'S':
14621592Srgrimes			case 's':
14631592Srgrimes				return (S);
14641592Srgrimes
14651592Srgrimes			case 'T':
14661592Srgrimes			case 't':
14671592Srgrimes				return (T);
14681592Srgrimes
14691592Srgrimes			}
14701592Srgrimes			break;
14711592Srgrimes
14721592Srgrimes		default:
147376096Smarkm			fatalerror("Unknown state in scanner.");
14741592Srgrimes		}
14751592Srgrimes		state = CMD;
147689935Syar		return (LEXERR);
14771592Srgrimes	}
14781592Srgrimes}
14791592Srgrimes
14801592Srgrimesvoid
148190148Simpupper(char *s)
14821592Srgrimes{
14831592Srgrimes	while (*s != '\0') {
14841592Srgrimes		if (islower(*s))
14851592Srgrimes			*s = toupper(*s);
14861592Srgrimes		s++;
14871592Srgrimes	}
14881592Srgrimes}
14891592Srgrimes
14901592Srgrimesstatic char *
149190148Simpcopy(char *s)
14921592Srgrimes{
14931592Srgrimes	char *p;
14941592Srgrimes
1495137659Syar	p = malloc(strlen(s) + 1);
14961592Srgrimes	if (p == NULL)
149776096Smarkm		fatalerror("Ran out of memory.");
14981592Srgrimes	(void) strcpy(p, s);
14991592Srgrimes	return (p);
15001592Srgrimes}
15011592Srgrimes
15021592Srgrimesstatic void
150390148Simphelp(struct tab *ctab, char *s)
15041592Srgrimes{
15051592Srgrimes	struct tab *c;
15061592Srgrimes	int width, NCMDS;
15071592Srgrimes	char *type;
15081592Srgrimes
15091592Srgrimes	if (ctab == sitetab)
15101592Srgrimes		type = "SITE ";
15111592Srgrimes	else
15121592Srgrimes		type = "";
15131592Srgrimes	width = 0, NCMDS = 0;
15141592Srgrimes	for (c = ctab; c->name != NULL; c++) {
15151592Srgrimes		int len = strlen(c->name);
15161592Srgrimes
15171592Srgrimes		if (len > width)
15181592Srgrimes			width = len;
15191592Srgrimes		NCMDS++;
15201592Srgrimes	}
15211592Srgrimes	width = (width + 8) &~ 7;
15221592Srgrimes	if (s == 0) {
15231592Srgrimes		int i, j, w;
15241592Srgrimes		int columns, lines;
15251592Srgrimes
15261592Srgrimes		lreply(214, "The following %scommands are recognized %s.",
15271592Srgrimes		    type, "(* =>'s unimplemented)");
15281592Srgrimes		columns = 76 / width;
15291592Srgrimes		if (columns == 0)
15301592Srgrimes			columns = 1;
15311592Srgrimes		lines = (NCMDS + columns - 1) / columns;
15321592Srgrimes		for (i = 0; i < lines; i++) {
15331592Srgrimes			printf("   ");
15341592Srgrimes			for (j = 0; j < columns; j++) {
15351592Srgrimes				c = ctab + j * lines + i;
15361592Srgrimes				printf("%s%c", c->name,
15371592Srgrimes					c->implemented ? ' ' : '*');
15381592Srgrimes				if (c + lines >= &ctab[NCMDS])
15391592Srgrimes					break;
15401592Srgrimes				w = strlen(c->name) + 1;
15411592Srgrimes				while (w < width) {
15421592Srgrimes					putchar(' ');
15431592Srgrimes					w++;
15441592Srgrimes				}
15451592Srgrimes			}
15461592Srgrimes			printf("\r\n");
15471592Srgrimes		}
15481592Srgrimes		(void) fflush(stdout);
1549110037Syar		if (hostinfo)
1550110037Syar			reply(214, "Direct comments to ftp-bugs@%s.", hostname);
1551110037Syar		else
1552110037Syar			reply(214, "End.");
15531592Srgrimes		return;
15541592Srgrimes	}
15551592Srgrimes	upper(s);
15561592Srgrimes	c = lookup(ctab, s);
1557132931Syar	if (c == NULL) {
15581592Srgrimes		reply(502, "Unknown command %s.", s);
15591592Srgrimes		return;
15601592Srgrimes	}
15611592Srgrimes	if (c->implemented)
15621592Srgrimes		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
15631592Srgrimes	else
15641592Srgrimes		reply(214, "%s%-*s\t%s; unimplemented.", type, width,
15651592Srgrimes		    c->name, c->help);
15661592Srgrimes}
15671592Srgrimes
15681592Srgrimesstatic void
156990148Simpsizecmd(char *filename)
15701592Srgrimes{
15711592Srgrimes	switch (type) {
15721592Srgrimes	case TYPE_L:
15731592Srgrimes	case TYPE_I: {
15741592Srgrimes		struct stat stbuf;
157563350Sdes		if (stat(filename, &stbuf) < 0)
157663350Sdes			perror_reply(550, filename);
157763350Sdes		else if (!S_ISREG(stbuf.st_mode))
15781592Srgrimes			reply(550, "%s: not a plain file.", filename);
15791592Srgrimes		else
1580132929Syar			reply(213, "%jd", (intmax_t)stbuf.st_size);
15811592Srgrimes		break; }
15821592Srgrimes	case TYPE_A: {
15831592Srgrimes		FILE *fin;
15841592Srgrimes		int c;
15851592Srgrimes		off_t count;
15861592Srgrimes		struct stat stbuf;
15871592Srgrimes		fin = fopen(filename, "r");
15881592Srgrimes		if (fin == NULL) {
15891592Srgrimes			perror_reply(550, filename);
15901592Srgrimes			return;
15911592Srgrimes		}
159263350Sdes		if (fstat(fileno(fin), &stbuf) < 0) {
159363350Sdes			perror_reply(550, filename);
159463350Sdes			(void) fclose(fin);
159563350Sdes			return;
159663350Sdes		} else if (!S_ISREG(stbuf.st_mode)) {
15971592Srgrimes			reply(550, "%s: not a plain file.", filename);
15981592Srgrimes			(void) fclose(fin);
15991592Srgrimes			return;
1600101034Syar		} else if (stbuf.st_size > MAXASIZE) {
1601101034Syar			reply(550, "%s: too large for type A SIZE.", filename);
1602101034Syar			(void) fclose(fin);
1603101034Syar			return;
16041592Srgrimes		}
16051592Srgrimes
16061592Srgrimes		count = 0;
16071592Srgrimes		while((c=getc(fin)) != EOF) {
16081592Srgrimes			if (c == '\n')	/* will get expanded to \r\n */
16091592Srgrimes				count++;
16101592Srgrimes			count++;
16111592Srgrimes		}
16121592Srgrimes		(void) fclose(fin);
16131592Srgrimes
1614132929Syar		reply(213, "%jd", (intmax_t)count);
16151592Srgrimes		break; }
16161592Srgrimes	default:
1617100684Syar		reply(504, "SIZE not implemented for type %s.",
1618100684Syar		           typenames[type]);
16191592Srgrimes	}
16201592Srgrimes}
162156668Sshin
162256668Sshin/* Return 1, if port check is done. Return 0, if not yet. */
162356668Sshinstatic int
162490148Simpport_check(const char *pcmd)
162556668Sshin{
162656668Sshin	if (his_addr.su_family == AF_INET) {
162756668Sshin		if (data_dest.su_family != AF_INET) {
162856668Sshin			usedefault = 1;
162956668Sshin			reply(500, "Invalid address rejected.");
163056668Sshin			return 1;
163156668Sshin		}
163256668Sshin		if (paranoid &&
163356668Sshin		    ((ntohs(data_dest.su_port) < IPPORT_RESERVED) ||
163456668Sshin		     memcmp(&data_dest.su_sin.sin_addr,
163556668Sshin			    &his_addr.su_sin.sin_addr,
163656668Sshin			    sizeof(data_dest.su_sin.sin_addr)))) {
163756668Sshin			usedefault = 1;
163856668Sshin			reply(500, "Illegal PORT range rejected.");
163956668Sshin		} else {
164056668Sshin			usedefault = 0;
164156668Sshin			if (pdata >= 0) {
164256668Sshin				(void) close(pdata);
164356668Sshin				pdata = -1;
164456668Sshin			}
164556668Sshin			reply(200, "%s command successful.", pcmd);
164656668Sshin		}
164756668Sshin		return 1;
164856668Sshin	}
164956668Sshin	return 0;
165056668Sshin}
165156668Sshin
165270102Sphkstatic int
165390148Simpcheck_login1(void)
165470102Sphk{
165570102Sphk	if (logged_in)
165670102Sphk		return 1;
165770102Sphk	else {
165870102Sphk		reply(530, "Please login with USER and PASS.");
165970102Sphk		return 0;
166070102Sphk	}
166170102Sphk}
166270102Sphk
1663110340Syar/*
1664110340Syar * Replace leading "~user" in a pathname by the user's login directory.
1665110340Syar * Returned string will be in a freshly malloced buffer unless it's NULL.
1666110340Syar */
1667110340Syarstatic char *
1668110340Syarexptilde(char *s)
1669110340Syar{
1670110340Syar	char *p, *q;
1671110340Syar	char *path, *user;
1672110340Syar	struct passwd *ppw;
1673110340Syar
1674110340Syar	if ((p = strdup(s)) == NULL)
1675110340Syar		return (NULL);
1676110340Syar	if (*p != '~')
1677110340Syar		return (p);
1678110340Syar
1679110340Syar	user = p + 1;	/* skip tilde */
1680110340Syar	if ((path = strchr(p, '/')) != NULL)
1681110340Syar		*(path++) = '\0'; /* separate ~user from the rest of path */
1682110378Syar	if (*user == '\0') /* no user specified, use the current user */
1683110378Syar		user = pw->pw_name;
1684110378Syar	/* read passwd even for the current user since we may be chrooted */
1685110378Syar	if ((ppw = getpwnam(user)) != NULL) {
1686110340Syar		/* user found, substitute login directory for ~user */
1687110340Syar		if (path)
1688110340Syar			asprintf(&q, "%s/%s", ppw->pw_dir, path);
1689110340Syar		else
1690110340Syar			q = strdup(ppw->pw_dir);
1691110340Syar		free(p);
1692110340Syar		p = q;
1693110340Syar	} else {
1694110340Syar		/* user not found, undo the damage */
1695110340Syar		if (path)
1696110340Syar			path[-1] = '/';
1697110340Syar	}
1698110340Syar	return (p);
1699110340Syar}
1700110340Syar
1701110340Syar/*
1702110340Syar * Expand glob(3) patterns possibly present in a pathname.
1703110340Syar * Avoid expanding to a pathname including '\r' or '\n' in order to
1704110340Syar * not disrupt the FTP protocol.
1705110340Syar * The expansion found must be unique.
1706110340Syar * Return the result as a malloced string, or NULL if an error occured.
1707110340Syar *
1708110340Syar * Problem: this production is used for all pathname
1709110340Syar * processing, but only gives a 550 error reply.
1710110340Syar * This is a valid reply in some cases but not in others.
1711110340Syar */
1712110340Syarstatic char *
1713110340Syarexpglob(char *s)
1714110340Syar{
1715110340Syar	char *p, **pp, *rval;
1716110340Syar	int flags = GLOB_BRACE | GLOB_NOCHECK;
1717110340Syar	int n;
1718110340Syar	glob_t gl;
1719110340Syar
1720110340Syar	memset(&gl, 0, sizeof(gl));
1721110340Syar	flags |= GLOB_LIMIT;
1722110340Syar	gl.gl_matchc = MAXGLOBARGS;
1723110340Syar	if (glob(s, flags, NULL, &gl) == 0 && gl.gl_pathc != 0) {
1724110340Syar		for (pp = gl.gl_pathv, p = NULL, n = 0; *pp; pp++)
1725110340Syar			if (*(*pp + strcspn(*pp, "\r\n")) == '\0') {
1726110340Syar				p = *pp;
1727110340Syar				n++;
1728110340Syar			}
1729110340Syar		if (n == 0)
1730110340Syar			rval = strdup(s);
1731110340Syar		else if (n == 1)
1732110340Syar			rval = strdup(p);
1733110340Syar		else {
1734137852Syar			reply(550, "Wildcard is ambiguous.");
1735110340Syar			rval = NULL;
1736110340Syar		}
1737110340Syar	} else {
1738137852Syar		reply(550, "Wildcard expansion error.");
1739110340Syar		rval = NULL;
1740110340Syar	}
1741110340Syar	globfree(&gl);
1742110340Syar	return (rval);
1743110340Syar}
1744110340Syar
174556668Sshin#ifdef INET6
174656668Sshin/* Return 1, if port check is done. Return 0, if not yet. */
174756668Sshinstatic int
174890148Simpport_check_v6(const char *pcmd)
174956668Sshin{
175056668Sshin	if (his_addr.su_family == AF_INET6) {
175156668Sshin		if (IN6_IS_ADDR_V4MAPPED(&his_addr.su_sin6.sin6_addr))
175256668Sshin			/* Convert data_dest into v4 mapped sockaddr.*/
175356668Sshin			v4map_data_dest();
175456668Sshin		if (data_dest.su_family != AF_INET6) {
175556668Sshin			usedefault = 1;
175656668Sshin			reply(500, "Invalid address rejected.");
175756668Sshin			return 1;
175856668Sshin		}
175956668Sshin		if (paranoid &&
176056668Sshin		    ((ntohs(data_dest.su_port) < IPPORT_RESERVED) ||
176156668Sshin		     memcmp(&data_dest.su_sin6.sin6_addr,
176256668Sshin			    &his_addr.su_sin6.sin6_addr,
176356668Sshin			    sizeof(data_dest.su_sin6.sin6_addr)))) {
176456668Sshin			usedefault = 1;
176556668Sshin			reply(500, "Illegal PORT range rejected.");
176656668Sshin		} else {
176756668Sshin			usedefault = 0;
176856668Sshin			if (pdata >= 0) {
176956668Sshin				(void) close(pdata);
177056668Sshin				pdata = -1;
177156668Sshin			}
177256668Sshin			reply(200, "%s command successful.", pcmd);
177356668Sshin		}
177456668Sshin		return 1;
177556668Sshin	}
177656668Sshin	return 0;
177756668Sshin}
177856668Sshin
177956668Sshinstatic void
178090148Simpv4map_data_dest(void)
178156668Sshin{
178256668Sshin	struct in_addr savedaddr;
178356668Sshin	int savedport;
178456668Sshin
178556668Sshin	if (data_dest.su_family != AF_INET) {
178656668Sshin		usedefault = 1;
178756668Sshin		reply(500, "Invalid address rejected.");
178856668Sshin		return;
178956668Sshin	}
179056668Sshin
179156668Sshin	savedaddr = data_dest.su_sin.sin_addr;
179256668Sshin	savedport = data_dest.su_port;
179356668Sshin
179456668Sshin	memset(&data_dest, 0, sizeof(data_dest));
179556668Sshin	data_dest.su_sin6.sin6_len = sizeof(struct sockaddr_in6);
179656668Sshin	data_dest.su_sin6.sin6_family = AF_INET6;
179756668Sshin	data_dest.su_sin6.sin6_port = savedport;
179856668Sshin	memset((caddr_t)&data_dest.su_sin6.sin6_addr.s6_addr[10], 0xff, 2);
179956668Sshin	memcpy((caddr_t)&data_dest.su_sin6.sin6_addr.s6_addr[12],
180056668Sshin	       (caddr_t)&savedaddr, sizeof(savedaddr));
180156668Sshin}
180256668Sshin#endif
1803