ftpcmd.y revision 110378
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 110378 2003-02-05 11:11:32Z 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>
671592Srgrimes#include <stdio.h>
681592Srgrimes#include <stdlib.h>
691592Srgrimes#include <string.h>
701592Srgrimes#include <syslog.h>
711592Srgrimes#include <time.h>
721592Srgrimes#include <unistd.h>
731592Srgrimes
741592Srgrimes#include "extern.h"
75109380Syar#include "pathnames.h"
761592Srgrimes
7756668Sshinextern	union sockunion data_dest, his_addr;
78110037Syarextern	int hostinfo;
791592Srgrimesextern	int logged_in;
801592Srgrimesextern	struct passwd *pw;
811592Srgrimesextern	int guest;
82110036Syarextern	char *homedir;
8317435Spstextern 	int paranoid;
841592Srgrimesextern	int logging;
851592Srgrimesextern	int type;
861592Srgrimesextern	int form;
8776096Smarkmextern	int ftpdebug;
881592Srgrimesextern	int timeout;
891592Srgrimesextern	int maxtimeout;
901592Srgrimesextern  int pdata;
9127650Sdavidnextern	char *hostname;
9227650Sdavidnextern	char remotehost[];
931592Srgrimesextern	char proctitle[];
941592Srgrimesextern	int usedefault;
951592Srgrimesextern  int transflag;
961592Srgrimesextern  char tmpline[];
9770102Sphkextern	int readonly;
9870102Sphkextern	int noepsv;
9982460Snikextern	int noretr;
10082796Ssheldonhextern	int noguestretr;
101100684Syarextern	char *typenames[]; /* defined in <arpa/ftp.h> included from ftpd.c */
1021592Srgrimes
1031592Srgrimesoff_t	restart_point;
1041592Srgrimes
1051592Srgrimesstatic	int cmd_type;
1061592Srgrimesstatic	int cmd_form;
1071592Srgrimesstatic	int cmd_bytesz;
10889935Syarstatic	int state;
1091592Srgrimeschar	cbuf[512];
11088935Sdwmalonechar	*fromname = (char *) 0;
1111592Srgrimes
11256668Sshinextern int epsvall;
11356668Sshin
1141592Srgrimes%}
1151592Srgrimes
1161592Srgrimes%union {
11792272Smaxim	struct {
11892272Smaxim		off_t	o;
11992272Smaxim		int	i;
12092272Smaxim	} u;
1211592Srgrimes	char   *s;
1221592Srgrimes}
1231592Srgrimes
1241592Srgrimes%token
1251592Srgrimes	A	B	C	E	F	I
1261592Srgrimes	L	N	P	R	S	T
12756668Sshin	ALL
1281592Srgrimes
1291592Srgrimes	SP	CRLF	COMMA
1301592Srgrimes
1311592Srgrimes	USER	PASS	ACCT	REIN	QUIT	PORT
1321592Srgrimes	PASV	TYPE	STRU	MODE	RETR	STOR
1331592Srgrimes	APPE	MLFL	MAIL	MSND	MSOM	MSAM
1341592Srgrimes	MRSQ	MRCP	ALLO	REST	RNFR	RNTO
1351592Srgrimes	ABOR	DELE	CWD	LIST	NLST	SITE
1361592Srgrimes	STAT	HELP	NOOP	MKD	RMD	PWD
1371592Srgrimes	CDUP	STOU	SMNT	SYST	SIZE	MDTM
13856668Sshin	LPRT	LPSV	EPRT	EPSV
1391592Srgrimes
14075535Sphk	UMASK	IDLE	CHMOD	MDFIVE
1411592Srgrimes
142102565Syar	LEXERR	NOTIMPL
1431592Srgrimes
1441592Srgrimes%token	<s> STRING
14592272Smaxim%token	<u> NUMBER
1461592Srgrimes
14792272Smaxim%type	<u.i> check_login octal_number byte_size
14892272Smaxim%type	<u.i> check_login_ro check_login_epsv
14992272Smaxim%type	<u.i> struct_code mode_code type_code form_code
15075567Speter%type	<s> pathstring pathname password username
151102565Syar%type	<s> ALL NOTIMPL
1521592Srgrimes
1531592Srgrimes%start	cmd_list
1541592Srgrimes
1551592Srgrimes%%
1561592Srgrimes
1571592Srgrimescmd_list
1581592Srgrimes	: /* empty */
1591592Srgrimes	| cmd_list cmd
1601592Srgrimes		{
16188935Sdwmalone			if (fromname)
16288935Sdwmalone				free(fromname);
1631592Srgrimes			fromname = (char *) 0;
1641592Srgrimes			restart_point = (off_t) 0;
1651592Srgrimes		}
1661592Srgrimes	| cmd_list rcmd
1671592Srgrimes	;
1681592Srgrimes
1691592Srgrimescmd
1701592Srgrimes	: USER SP username CRLF
1711592Srgrimes		{
1721592Srgrimes			user($3);
1731592Srgrimes			free($3);
1741592Srgrimes		}
1751592Srgrimes	| PASS SP password CRLF
1761592Srgrimes		{
1771592Srgrimes			pass($3);
1781592Srgrimes			free($3);
1791592Srgrimes		}
18075556Sgreen	| PASS CRLF
18175556Sgreen		{
18275556Sgreen			pass("");
18375556Sgreen		}
18417433Spst	| PORT check_login SP host_port CRLF
1851592Srgrimes		{
18656668Sshin			if (epsvall) {
18756668Sshin				reply(501, "no PORT allowed after EPSV ALL");
18856668Sshin				goto port_done;
18956668Sshin			}
19056668Sshin			if (!$2)
19156668Sshin				goto port_done;
19256668Sshin			if (port_check("PORT") == 1)
19356668Sshin				goto port_done;
19456668Sshin#ifdef INET6
19556668Sshin			if ((his_addr.su_family != AF_INET6 ||
19656668Sshin			     !IN6_IS_ADDR_V4MAPPED(&his_addr.su_sin6.sin6_addr))) {
19756668Sshin				/* shoud never happen */
19856668Sshin				usedefault = 1;
19956668Sshin				reply(500, "Invalid address rejected.");
20056668Sshin				goto port_done;
20156668Sshin			}
20256668Sshin			port_check_v6("pcmd");
20356668Sshin#endif
20456668Sshin		port_done:
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:
22656668Sshin		}
22756668Sshin	| EPRT check_login SP STRING CRLF
22856668Sshin		{
22956668Sshin			char delim;
23056668Sshin			char *tmp = NULL;
23156668Sshin			char *p, *q;
23256668Sshin			char *result[3];
23356668Sshin			struct addrinfo hints;
23456668Sshin			struct addrinfo *res;
23556668Sshin			int i;
23656668Sshin
23756668Sshin			if (epsvall) {
23856668Sshin				reply(501, "no EPRT allowed after EPSV ALL");
23956668Sshin				goto eprt_done;
24056668Sshin			}
24156668Sshin			if (!$2)
24256668Sshin				goto eprt_done;
24356668Sshin
24456668Sshin			memset(&data_dest, 0, sizeof(data_dest));
24556668Sshin			tmp = strdup($4);
24676096Smarkm			if (ftpdebug)
24756668Sshin				syslog(LOG_DEBUG, "%s", tmp);
24856668Sshin			if (!tmp) {
24976096Smarkm				fatalerror("not enough core");
25056668Sshin				/*NOTREACHED*/
25156668Sshin			}
25256668Sshin			p = tmp;
25356668Sshin			delim = p[0];
25456668Sshin			p++;
25556668Sshin			memset(result, 0, sizeof(result));
25656668Sshin			for (i = 0; i < 3; i++) {
25756668Sshin				q = strchr(p, delim);
25856668Sshin				if (!q || *q != delim) {
25956668Sshin		parsefail:
26056668Sshin					reply(500,
26156668Sshin						"Invalid argument, rejected.");
26256668Sshin					if (tmp)
26356668Sshin						free(tmp);
26417433Spst					usedefault = 1;
26556668Sshin					goto eprt_done;
26617433Spst				}
26756668Sshin				*q++ = '\0';
26856668Sshin				result[i] = p;
26976096Smarkm				if (ftpdebug)
27056668Sshin					syslog(LOG_DEBUG, "%d: %s", i, p);
27156668Sshin				p = q;
2721592Srgrimes			}
27356668Sshin
27456668Sshin			/* some more sanity check */
27556668Sshin			p = result[0];
27656668Sshin			while (*p) {
27756668Sshin				if (!isdigit(*p))
27856668Sshin					goto parsefail;
27956668Sshin				p++;
28056668Sshin			}
28156668Sshin			p = result[2];
28256668Sshin			while (*p) {
28356668Sshin				if (!isdigit(*p))
28456668Sshin					goto parsefail;
28556668Sshin				p++;
28656668Sshin			}
28756668Sshin
28856668Sshin			/* grab address */
28956668Sshin			memset(&hints, 0, sizeof(hints));
29056668Sshin			if (atoi(result[0]) == 1)
29156668Sshin				hints.ai_family = PF_INET;
29256668Sshin#ifdef INET6
29356668Sshin			else if (atoi(result[0]) == 2)
29456668Sshin				hints.ai_family = PF_INET6;
29556668Sshin#endif
29656668Sshin			else
29756668Sshin				hints.ai_family = PF_UNSPEC;	/*XXX*/
29856668Sshin			hints.ai_socktype = SOCK_STREAM;
29956668Sshin			i = getaddrinfo(result[1], result[2], &hints, &res);
30056668Sshin			if (i)
30156668Sshin				goto parsefail;
30256668Sshin			memcpy(&data_dest, res->ai_addr, res->ai_addrlen);
30356668Sshin#ifdef INET6
30456668Sshin			if (his_addr.su_family == AF_INET6
30556668Sshin			    && data_dest.su_family == AF_INET6) {
30656668Sshin				/* XXX more sanity checks! */
30756668Sshin				data_dest.su_sin6.sin6_scope_id =
30856668Sshin					his_addr.su_sin6.sin6_scope_id;
30956668Sshin			}
31056668Sshin#endif
31156668Sshin			free(tmp);
31256668Sshin			tmp = NULL;
31356668Sshin
31456668Sshin			if (port_check("EPRT") == 1)
31556668Sshin				goto eprt_done;
31656668Sshin#ifdef INET6
31756668Sshin			if (his_addr.su_family != AF_INET6) {
31856668Sshin				usedefault = 1;
31956668Sshin				reply(500, "Invalid address rejected.");
32056668Sshin				goto eprt_done;
32156668Sshin			}
32256668Sshin			if (port_check_v6("EPRT") == 1)
32356668Sshin				goto eprt_done;
32456668Sshin#endif
32588935Sdwmalone		eprt_done:
32688935Sdwmalone			free($4);
3271592Srgrimes		}
32817433Spst	| PASV check_login CRLF
3291592Srgrimes		{
33056668Sshin			if (epsvall)
33156668Sshin				reply(501, "no PASV allowed after EPSV ALL");
33256668Sshin			else if ($2)
33317433Spst				passive();
3341592Srgrimes		}
33556668Sshin	| LPSV check_login CRLF
33656668Sshin		{
33756668Sshin			if (epsvall)
33856668Sshin				reply(501, "no LPSV allowed after EPSV ALL");
33956668Sshin			else if ($2)
34056668Sshin				long_passive("LPSV", PF_UNSPEC);
34156668Sshin		}
34270102Sphk	| EPSV check_login_epsv SP NUMBER CRLF
34356668Sshin		{
34456668Sshin			if ($2) {
34556668Sshin				int pf;
34692272Smaxim				switch ($4.i) {
34756668Sshin				case 1:
34856668Sshin					pf = PF_INET;
34956668Sshin					break;
35056668Sshin#ifdef INET6
35156668Sshin				case 2:
35256668Sshin					pf = PF_INET6;
35356668Sshin					break;
35456668Sshin#endif
35556668Sshin				default:
35656668Sshin					pf = -1;	/*junk value*/
35756668Sshin					break;
35856668Sshin				}
35956668Sshin				long_passive("EPSV", pf);
36056668Sshin			}
36156668Sshin		}
36270102Sphk	| EPSV check_login_epsv SP ALL CRLF
36356668Sshin		{
36456668Sshin			if ($2) {
36556668Sshin				reply(200,
36656668Sshin				      "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:
41871278Sjedgar					reply(200, "STRU F ok.");
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:
43271278Sjedgar					reply(200, "MODE S ok.");
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))
45582460Snik				reply(500, "RETR command is disabled");
45682460Snik			else if ($2 && $4 != NULL)
4571592Srgrimes				retrieve((char *) 0, $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);
52417433Spst					fromname = (char *) 0;
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		{
5521592Srgrimes			help(cmdtab, (char *) 0);
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
5651592Srgrimes					help(sitetab, (char *) 0);
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		{
6001592Srgrimes			help(sitetab, (char *) 0);
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);
6281592Srgrimes				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)) {
6371592Srgrimes					reply(501, "Bad UMASK value");
6381592Srgrimes				} else {
6391592Srgrimes					oldmask = umask($6);
6401592Srgrimes					reply(200,
6411592Srgrimes					    "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))
650101378Syar					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,
66371278Sjedgar			    	    "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,
67171278Sjedgar					    "Maximum IDLE time must be between 30 and %d seconds",
67271278Sjedgar					    maxtimeout);
67371278Sjedgar				} else {
67492272Smaxim					timeout = $6.i;
67571278Sjedgar					(void) alarm((unsigned) timeout);
67671278Sjedgar					reply(200,
67771278Sjedgar					    "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		{
69171278Sjedgar			if ($2)
6921592Srgrimes#ifdef unix
6931592Srgrimes#ifdef BSD
6941592Srgrimes			reply(215, "UNIX Type: L%d Version: BSD-%d",
695103949Smike				CHAR_BIT, BSD);
6961592Srgrimes#else /* BSD */
697103949Smike			reply(215, "UNIX Type: L%d", CHAR_BIT);
6981592Srgrimes#endif /* BSD */
6991592Srgrimes#else /* unix */
700103949Smike			reply(215, "UNKNOWN Type: L%d", CHAR_BIT);
7011592Srgrimes#endif /* unix */
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)
7331592Srgrimes					reply(550, "%s: %s",
7341592Srgrimes					    $4, strerror(errno));
7351592Srgrimes				else if (!S_ISREG(stbuf.st_mode)) {
7361592Srgrimes					reply(550, "%s: not a plain file.", $4);
7371592Srgrimes				} else {
7381592Srgrimes					struct tm *t;
7391592Srgrimes					t = gmtime(&stbuf.st_mtime);
7401592Srgrimes					reply(213,
74117435Spst					    "%04d%02d%02d%02d%02d%02d",
74217435Spst					    1900 + t->tm_year,
74317435Spst					    t->tm_mon+1, t->tm_mday,
7441592Srgrimes					    t->tm_hour, t->tm_min, t->tm_sec);
7451592Srgrimes				}
7461592Srgrimes			}
7471592Srgrimes			if ($4 != NULL)
7481592Srgrimes				free($4);
7491592Srgrimes		}
7501592Srgrimes	| QUIT CRLF
7511592Srgrimes		{
7521592Srgrimes			reply(221, "Goodbye.");
7531592Srgrimes			dologout(0);
7541592Srgrimes		}
755102565Syar	| NOTIMPL
756102565Syar		{
757102565Syar			nack($1);
758102565Syar		}
75989935Syar	| error
7601592Srgrimes		{
76189935Syar			yyclearin;		/* discard lookahead data */
76289935Syar			yyerrok;		/* clear error condition */
763102565Syar			state = CMD;		/* reset lexer state */
7641592Srgrimes		}
7651592Srgrimes	;
7661592Srgrimesrcmd
76770102Sphk	: RNFR check_login_ro SP pathname CRLF
7681592Srgrimes		{
7691592Srgrimes			restart_point = (off_t) 0;
7701592Srgrimes			if ($2 && $4) {
77188935Sdwmalone				if (fromname)
77288935Sdwmalone					free(fromname);
77388935Sdwmalone				fromname = (char *) 0;
77488935Sdwmalone				if (renamefrom($4))
77588935Sdwmalone					fromname = $4;
77688935Sdwmalone				else
7771592Srgrimes					free($4);
77888935Sdwmalone			} else if ($4) {
77988935Sdwmalone				free($4);
7801592Srgrimes			}
7811592Srgrimes		}
78292272Smaxim	| REST check_login SP NUMBER CRLF
7831592Srgrimes		{
78471278Sjedgar			if ($2) {
78588935Sdwmalone				if (fromname)
78688935Sdwmalone					free(fromname);
78771278Sjedgar				fromname = (char *) 0;
78892272Smaxim				restart_point = $4.o;
78992272Smaxim				reply(350, "Restarting at %llu. %s",
79071278Sjedgar				    restart_point,
79171278Sjedgar				    "Send STORE or RETRIEVE to initiate transfer.");
79271278Sjedgar			}
7931592Srgrimes		}
7941592Srgrimes	;
7951592Srgrimes
7961592Srgrimesusername
7971592Srgrimes	: STRING
7981592Srgrimes	;
7991592Srgrimes
8001592Srgrimespassword
8011592Srgrimes	: /* empty */
8021592Srgrimes		{
8031592Srgrimes			$$ = (char *)calloc(1, sizeof(char));
8041592Srgrimes		}
8051592Srgrimes	| STRING
8061592Srgrimes	;
8071592Srgrimes
8081592Srgrimesbyte_size
8091592Srgrimes	: NUMBER
81092272Smaxim		{
81192272Smaxim			$$ = $1.i;
81292272Smaxim		}
8131592Srgrimes	;
8141592Srgrimes
8151592Srgrimeshost_port
8161592Srgrimes	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
8171592Srgrimes		NUMBER COMMA NUMBER
8181592Srgrimes		{
8191592Srgrimes			char *a, *p;
8201592Srgrimes
82156668Sshin			data_dest.su_len = sizeof(struct sockaddr_in);
82256668Sshin			data_dest.su_family = AF_INET;
82356668Sshin			p = (char *)&data_dest.su_sin.sin_port;
82492272Smaxim			p[0] = $9.i; p[1] = $11.i;
82556668Sshin			a = (char *)&data_dest.su_sin.sin_addr;
82692272Smaxim			a[0] = $1.i; a[1] = $3.i; a[2] = $5.i; a[3] = $7.i;
8271592Srgrimes		}
8281592Srgrimes	;
8291592Srgrimes
83056668Sshinhost_long_port
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 COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
83656668Sshin		NUMBER
83756668Sshin		{
83856668Sshin			char *a, *p;
83956668Sshin
84056668Sshin			memset(&data_dest, 0, sizeof(data_dest));
84156668Sshin			data_dest.su_len = sizeof(struct sockaddr_in6);
84256668Sshin			data_dest.su_family = AF_INET6;
84356668Sshin			p = (char *)&data_dest.su_port;
84492272Smaxim			p[0] = $39.i; p[1] = $41.i;
84556668Sshin			a = (char *)&data_dest.su_sin6.sin6_addr;
84692272Smaxim			a[0] = $5.i; a[1] = $7.i; a[2] = $9.i; a[3] = $11.i;
84792272Smaxim			a[4] = $13.i; a[5] = $15.i; a[6] = $17.i; a[7] = $19.i;
84892272Smaxim			a[8] = $21.i; a[9] = $23.i; a[10] = $25.i; a[11] = $27.i;
84992272Smaxim			a[12] = $29.i; a[13] = $31.i; a[14] = $33.i; a[15] = $35.i;
85056668Sshin			if (his_addr.su_family == AF_INET6) {
85156668Sshin				/* XXX more sanity checks! */
85256668Sshin				data_dest.su_sin6.sin6_scope_id =
85356668Sshin					his_addr.su_sin6.sin6_scope_id;
85456668Sshin			}
85592272Smaxim			if ($1.i != 6 || $3.i != 16 || $37.i != 2)
85656668Sshin				memset(&data_dest, 0, sizeof(data_dest));
85756668Sshin		}
85856668Sshin	| NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
85956668Sshin		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
86056668Sshin		NUMBER
86156668Sshin		{
86256668Sshin			char *a, *p;
86356668Sshin
86456668Sshin			memset(&data_dest, 0, sizeof(data_dest));
86556668Sshin			data_dest.su_sin.sin_len = sizeof(struct sockaddr_in);
86656668Sshin			data_dest.su_family = AF_INET;
86756668Sshin			p = (char *)&data_dest.su_port;
86892272Smaxim			p[0] = $15.i; p[1] = $17.i;
86956668Sshin			a = (char *)&data_dest.su_sin.sin_addr;
87092272Smaxim			a[0] =  $5.i; a[1] = $7.i; a[2] = $9.i; a[3] = $11.i;
87192272Smaxim			if ($1.i != 4 || $3.i != 4 || $13.i != 2)
87256668Sshin				memset(&data_dest, 0, sizeof(data_dest));
87356668Sshin		}
87456668Sshin	;
87556668Sshin
8761592Srgrimesform_code
8771592Srgrimes	: N
8781592Srgrimes		{
8791592Srgrimes			$$ = FORM_N;
8801592Srgrimes		}
8811592Srgrimes	| T
8821592Srgrimes		{
8831592Srgrimes			$$ = FORM_T;
8841592Srgrimes		}
8851592Srgrimes	| C
8861592Srgrimes		{
8871592Srgrimes			$$ = FORM_C;
8881592Srgrimes		}
8891592Srgrimes	;
8901592Srgrimes
8911592Srgrimestype_code
8921592Srgrimes	: A
8931592Srgrimes		{
8941592Srgrimes			cmd_type = TYPE_A;
8951592Srgrimes			cmd_form = FORM_N;
8961592Srgrimes		}
8971592Srgrimes	| A SP form_code
8981592Srgrimes		{
8991592Srgrimes			cmd_type = TYPE_A;
9001592Srgrimes			cmd_form = $3;
9011592Srgrimes		}
9021592Srgrimes	| E
9031592Srgrimes		{
9041592Srgrimes			cmd_type = TYPE_E;
9051592Srgrimes			cmd_form = FORM_N;
9061592Srgrimes		}
9071592Srgrimes	| E SP form_code
9081592Srgrimes		{
9091592Srgrimes			cmd_type = TYPE_E;
9101592Srgrimes			cmd_form = $3;
9111592Srgrimes		}
9121592Srgrimes	| I
9131592Srgrimes		{
9141592Srgrimes			cmd_type = TYPE_I;
9151592Srgrimes		}
9161592Srgrimes	| L
9171592Srgrimes		{
9181592Srgrimes			cmd_type = TYPE_L;
919103949Smike			cmd_bytesz = CHAR_BIT;
9201592Srgrimes		}
9211592Srgrimes	| L SP byte_size
9221592Srgrimes		{
9231592Srgrimes			cmd_type = TYPE_L;
9241592Srgrimes			cmd_bytesz = $3;
9251592Srgrimes		}
9261592Srgrimes		/* this is for a bug in the BBN ftp */
9271592Srgrimes	| L byte_size
9281592Srgrimes		{
9291592Srgrimes			cmd_type = TYPE_L;
9301592Srgrimes			cmd_bytesz = $2;
9311592Srgrimes		}
9321592Srgrimes	;
9331592Srgrimes
9341592Srgrimesstruct_code
9351592Srgrimes	: F
9361592Srgrimes		{
9371592Srgrimes			$$ = STRU_F;
9381592Srgrimes		}
9391592Srgrimes	| R
9401592Srgrimes		{
9411592Srgrimes			$$ = STRU_R;
9421592Srgrimes		}
9431592Srgrimes	| P
9441592Srgrimes		{
9451592Srgrimes			$$ = STRU_P;
9461592Srgrimes		}
9471592Srgrimes	;
9481592Srgrimes
9491592Srgrimesmode_code
9501592Srgrimes	: S
9511592Srgrimes		{
9521592Srgrimes			$$ = MODE_S;
9531592Srgrimes		}
9541592Srgrimes	| B
9551592Srgrimes		{
9561592Srgrimes			$$ = MODE_B;
9571592Srgrimes		}
9581592Srgrimes	| C
9591592Srgrimes		{
9601592Srgrimes			$$ = MODE_C;
9611592Srgrimes		}
9621592Srgrimes	;
9631592Srgrimes
9641592Srgrimespathname
9651592Srgrimes	: pathstring
9661592Srgrimes		{
96775567Speter			if (logged_in && $1) {
968110340Syar				char *p;
9691592Srgrimes
970110340Syar				/*
971110340Syar				 * Expand ~user manually since glob(3)
972110340Syar				 * will return the unexpanded pathname
973110340Syar				 * if the corresponding file/directory
974110340Syar				 * doesn't exist yet.  Using sole glob(3)
975110340Syar				 * would break natural commands like
976110340Syar				 * MKD ~user/newdir
977110340Syar				 * or
978110340Syar				 * RNTO ~/newfile
979110340Syar				 */
980110340Syar				if ((p = exptilde($1)) != NULL) {
981110340Syar					$$ = expglob(p);
982110340Syar					free(p);
983110340Syar				} else
9841592Srgrimes					$$ = NULL;
9851592Srgrimes				free($1);
9861592Srgrimes			} else
9871592Srgrimes				$$ = $1;
9881592Srgrimes		}
9891592Srgrimes	;
9901592Srgrimes
9911592Srgrimespathstring
9921592Srgrimes	: STRING
9931592Srgrimes	;
9941592Srgrimes
9951592Srgrimesoctal_number
9961592Srgrimes	: NUMBER
9971592Srgrimes		{
9981592Srgrimes			int ret, dec, multby, digit;
9991592Srgrimes
10001592Srgrimes			/*
10011592Srgrimes			 * Convert a number that was read as decimal number
10021592Srgrimes			 * to what it would be if it had been read as octal.
10031592Srgrimes			 */
100492272Smaxim			dec = $1.i;
10051592Srgrimes			multby = 1;
10061592Srgrimes			ret = 0;
10071592Srgrimes			while (dec) {
10081592Srgrimes				digit = dec%10;
10091592Srgrimes				if (digit > 7) {
10101592Srgrimes					ret = -1;
10111592Srgrimes					break;
10121592Srgrimes				}
10131592Srgrimes				ret += digit * multby;
10141592Srgrimes				multby *= 8;
10151592Srgrimes				dec /= 10;
10161592Srgrimes			}
10171592Srgrimes			$$ = ret;
10181592Srgrimes		}
10191592Srgrimes	;
10201592Srgrimes
10211592Srgrimes
10221592Srgrimescheck_login
10231592Srgrimes	: /* empty */
10241592Srgrimes		{
102570102Sphk		$$ = check_login1();
10261592Srgrimes		}
10271592Srgrimes	;
10281592Srgrimes
102970102Sphkcheck_login_epsv
103070102Sphk	: /* empty */
103170102Sphk		{
103270102Sphk		if (noepsv) {
103370102Sphk			reply(500, "EPSV command disabled");
103470102Sphk			$$ = 0;
103570102Sphk		}
103670102Sphk		else
103770102Sphk			$$ = check_login1();
103870102Sphk		}
103970102Sphk	;
104070102Sphk
104170102Sphkcheck_login_ro
104270102Sphk	: /* empty */
104370102Sphk		{
104470102Sphk		if (readonly) {
104572710Sdes			reply(550, "Permission denied.");
104670102Sphk			$$ = 0;
104770102Sphk		}
104870102Sphk		else
104970102Sphk			$$ = check_login1();
105070102Sphk		}
105170102Sphk	;
105270102Sphk
10531592Srgrimes%%
10541592Srgrimes
10551592Srgrimes#define	CMD	0	/* beginning of command */
10561592Srgrimes#define	ARGS	1	/* expect miscellaneous arguments */
10571592Srgrimes#define	STR1	2	/* expect SP followed by STRING */
10581592Srgrimes#define	STR2	3	/* expect STRING */
10591592Srgrimes#define	OSTR	4	/* optional SP then STRING */
106075556Sgreen#define	ZSTR1	5	/* optional SP then optional STRING */
10611592Srgrimes#define	ZSTR2	6	/* optional STRING after SP */
10621592Srgrimes#define	SITECMD	7	/* SITE command */
10631592Srgrimes#define	NSTR	8	/* Number followed by a string */
10641592Srgrimes
106575560Sjedgar#define	MAXGLOBARGS	1000
106675560Sjedgar
1067101034Syar#define	MAXASIZE	10240	/* Deny ASCII SIZE on files larger than that */
1068101034Syar
10691592Srgrimesstruct tab {
10701592Srgrimes	char	*name;
10711592Srgrimes	short	token;
10721592Srgrimes	short	state;
10731592Srgrimes	short	implemented;	/* 1 if command is implemented */
10741592Srgrimes	char	*help;
10751592Srgrimes};
10761592Srgrimes
10771592Srgrimesstruct tab cmdtab[] = {		/* In order defined in RFC 765 */
10781592Srgrimes	{ "USER", USER, STR1, 1,	"<sp> username" },
107975556Sgreen	{ "PASS", PASS, ZSTR1, 1,	"[<sp> [password]]" },
10801592Srgrimes	{ "ACCT", ACCT, STR1, 0,	"(specify account)" },
10811592Srgrimes	{ "SMNT", SMNT, ARGS, 0,	"(structure mount)" },
10821592Srgrimes	{ "REIN", REIN, ARGS, 0,	"(reinitialize server state)" },
10831592Srgrimes	{ "QUIT", QUIT, ARGS, 1,	"(terminate service)", },
1084101806Syar	{ "PORT", PORT, ARGS, 1,	"<sp> b0, b1, b2, b3, b4, b5" },
108556668Sshin	{ "LPRT", LPRT, ARGS, 1,	"<sp> af, hal, h1, h2, h3,..., pal, p1, p2..." },
108656668Sshin	{ "EPRT", EPRT, STR1, 1,	"<sp> |af|addr|port|" },
10871592Srgrimes	{ "PASV", PASV, ARGS, 1,	"(set server in passive mode)" },
108856668Sshin	{ "LPSV", LPSV, ARGS, 1,	"(set server in passive mode)" },
108956668Sshin	{ "EPSV", EPSV, ARGS, 1,	"[<sp> af|ALL]" },
1090101806Syar	{ "TYPE", TYPE, ARGS, 1,	"<sp> { A | E | I | L }" },
10911592Srgrimes	{ "STRU", STRU, ARGS, 1,	"(specify file structure)" },
10921592Srgrimes	{ "MODE", MODE, ARGS, 1,	"(specify transfer mode)" },
10931592Srgrimes	{ "RETR", RETR, STR1, 1,	"<sp> file-name" },
10941592Srgrimes	{ "STOR", STOR, STR1, 1,	"<sp> file-name" },
10951592Srgrimes	{ "APPE", APPE, STR1, 1,	"<sp> file-name" },
10961592Srgrimes	{ "MLFL", MLFL, OSTR, 0,	"(mail file)" },
10971592Srgrimes	{ "MAIL", MAIL, OSTR, 0,	"(mail to user)" },
10981592Srgrimes	{ "MSND", MSND, OSTR, 0,	"(mail send to terminal)" },
10991592Srgrimes	{ "MSOM", MSOM, OSTR, 0,	"(mail send to terminal or mailbox)" },
11001592Srgrimes	{ "MSAM", MSAM, OSTR, 0,	"(mail send to terminal and mailbox)" },
11011592Srgrimes	{ "MRSQ", MRSQ, OSTR, 0,	"(mail recipient scheme question)" },
11021592Srgrimes	{ "MRCP", MRCP, STR1, 0,	"(mail recipient)" },
11031592Srgrimes	{ "ALLO", ALLO, ARGS, 1,	"allocate storage (vacuously)" },
11041592Srgrimes	{ "REST", REST, ARGS, 1,	"<sp> offset (restart command)" },
11051592Srgrimes	{ "RNFR", RNFR, STR1, 1,	"<sp> file-name" },
11061592Srgrimes	{ "RNTO", RNTO, STR1, 1,	"<sp> file-name" },
11071592Srgrimes	{ "ABOR", ABOR, ARGS, 1,	"(abort operation)" },
11081592Srgrimes	{ "DELE", DELE, STR1, 1,	"<sp> file-name" },
11091592Srgrimes	{ "CWD",  CWD,  OSTR, 1,	"[ <sp> directory-name ]" },
11101592Srgrimes	{ "XCWD", CWD,	OSTR, 1,	"[ <sp> directory-name ]" },
11111592Srgrimes	{ "LIST", LIST, OSTR, 1,	"[ <sp> path-name ]" },
11121592Srgrimes	{ "NLST", NLST, OSTR, 1,	"[ <sp> path-name ]" },
11131592Srgrimes	{ "SITE", SITE, SITECMD, 1,	"site-cmd [ <sp> arguments ]" },
11141592Srgrimes	{ "SYST", SYST, ARGS, 1,	"(get type of operating system)" },
11151592Srgrimes	{ "STAT", STAT, OSTR, 1,	"[ <sp> path-name ]" },
11161592Srgrimes	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
11171592Srgrimes	{ "NOOP", NOOP, ARGS, 1,	"" },
11181592Srgrimes	{ "MKD",  MKD,  STR1, 1,	"<sp> path-name" },
11191592Srgrimes	{ "XMKD", MKD,  STR1, 1,	"<sp> path-name" },
11201592Srgrimes	{ "RMD",  RMD,  STR1, 1,	"<sp> path-name" },
11211592Srgrimes	{ "XRMD", RMD,  STR1, 1,	"<sp> path-name" },
11221592Srgrimes	{ "PWD",  PWD,  ARGS, 1,	"(return current directory)" },
11231592Srgrimes	{ "XPWD", PWD,  ARGS, 1,	"(return current directory)" },
11241592Srgrimes	{ "CDUP", CDUP, ARGS, 1,	"(change to parent directory)" },
11251592Srgrimes	{ "XCUP", CDUP, ARGS, 1,	"(change to parent directory)" },
11261592Srgrimes	{ "STOU", STOU, STR1, 1,	"<sp> file-name" },
11271592Srgrimes	{ "SIZE", SIZE, OSTR, 1,	"<sp> path-name" },
11281592Srgrimes	{ "MDTM", MDTM, OSTR, 1,	"<sp> path-name" },
11291592Srgrimes	{ NULL,   0,    0,    0,	0 }
11301592Srgrimes};
11311592Srgrimes
11321592Srgrimesstruct tab sitetab[] = {
113375535Sphk	{ "MD5", MDFIVE, STR1, 1,	"[ <sp> file-name ]" },
11341592Srgrimes	{ "UMASK", UMASK, ARGS, 1,	"[ <sp> umask ]" },
11351592Srgrimes	{ "IDLE", IDLE, ARGS, 1,	"[ <sp> maximum-idle-time ]" },
11361592Srgrimes	{ "CHMOD", CHMOD, NSTR, 1,	"<sp> mode <sp> file-name" },
11371592Srgrimes	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
11381592Srgrimes	{ NULL,   0,    0,    0,	0 }
11391592Srgrimes};
11401592Srgrimes
114190148Simpstatic char	*copy(char *);
1142110340Syarstatic char	*expglob(char *);
1143110340Syarstatic char	*exptilde(char *);
114490148Simpstatic void	 help(struct tab *, char *);
11451592Srgrimesstatic struct tab *
114690148Simp		 lookup(struct tab *, char *);
114790148Simpstatic int	 port_check(const char *);
114890148Simpstatic int	 port_check_v6(const char *);
114990148Simpstatic void	 sizecmd(char *);
115090148Simpstatic void	 toolong(int);
115190148Simpstatic void	 v4map_data_dest(void);
115290148Simpstatic int	 yylex(void);
11531592Srgrimes
11541592Srgrimesstatic struct tab *
115590148Simplookup(struct tab *p, char *cmd)
11561592Srgrimes{
11571592Srgrimes
11581592Srgrimes	for (; p->name != NULL; p++)
11591592Srgrimes		if (strcmp(cmd, p->name) == 0)
11601592Srgrimes			return (p);
11611592Srgrimes	return (0);
11621592Srgrimes}
11631592Srgrimes
11641592Srgrimes#include <arpa/telnet.h>
11651592Srgrimes
11661592Srgrimes/*
11671592Srgrimes * getline - a hacked up version of fgets to ignore TELNET escape codes.
11681592Srgrimes */
11691592Srgrimeschar *
117090148Simpgetline(char *s, int n, FILE *iop)
11711592Srgrimes{
11721592Srgrimes	int c;
11731592Srgrimes	register char *cs;
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	}
11891592Srgrimes	while ((c = getc(iop)) != EOF) {
11901592Srgrimes		c &= 0377;
11911592Srgrimes		if (c == IAC) {
11921592Srgrimes		    if ((c = getc(iop)) != EOF) {
11931592Srgrimes			c &= 0377;
11941592Srgrimes			switch (c) {
11951592Srgrimes			case WILL:
11961592Srgrimes			case WONT:
11971592Srgrimes				c = getc(iop);
11981592Srgrimes				printf("%c%c%c", IAC, DONT, 0377&c);
11991592Srgrimes				(void) fflush(stdout);
12001592Srgrimes				continue;
12011592Srgrimes			case DO:
12021592Srgrimes			case DONT:
12031592Srgrimes				c = getc(iop);
12041592Srgrimes				printf("%c%c%c", IAC, WONT, 0377&c);
12051592Srgrimes				(void) fflush(stdout);
12061592Srgrimes				continue;
12071592Srgrimes			case IAC:
12081592Srgrimes				break;
12091592Srgrimes			default:
12101592Srgrimes				continue;	/* ignore command */
12111592Srgrimes			}
12121592Srgrimes		    }
12131592Srgrimes		}
12141592Srgrimes		*cs++ = c;
12151592Srgrimes		if (--n <= 0 || c == '\n')
12161592Srgrimes			break;
12171592Srgrimes	}
12181592Srgrimes	if (c == EOF && cs == s)
12191592Srgrimes		return (NULL);
12201592Srgrimes	*cs++ = '\0';
122176096Smarkm	if (ftpdebug) {
12221592Srgrimes		if (!guest && strncasecmp("pass ", s, 5) == 0) {
12231592Srgrimes			/* Don't syslog passwords */
12241592Srgrimes			syslog(LOG_DEBUG, "command: %.5s ???", s);
12251592Srgrimes		} else {
12261592Srgrimes			register char *cp;
12271592Srgrimes			register int len;
12281592Srgrimes
12291592Srgrimes			/* Don't syslog trailing CR-LF */
12301592Srgrimes			len = strlen(s);
12311592Srgrimes			cp = s + len - 1;
12321592Srgrimes			while (cp >= s && (*cp == '\n' || *cp == '\r')) {
12331592Srgrimes				--cp;
12341592Srgrimes				--len;
12351592Srgrimes			}
12361592Srgrimes			syslog(LOG_DEBUG, "command: %.*s", len, s);
12371592Srgrimes		}
12381592Srgrimes	}
12391592Srgrimes	return (s);
12401592Srgrimes}
12411592Srgrimes
12421592Srgrimesstatic void
124390148Simptoolong(int signo)
12441592Srgrimes{
12451592Srgrimes
12461592Srgrimes	reply(421,
12471592Srgrimes	    "Timeout (%d seconds): closing control connection.", timeout);
12481592Srgrimes	if (logging)
12491592Srgrimes		syslog(LOG_INFO, "User %s timed out after %d seconds",
12501592Srgrimes		    (pw ? pw -> pw_name : "unknown"), timeout);
12511592Srgrimes	dologout(1);
12521592Srgrimes}
12531592Srgrimes
12541592Srgrimesstatic int
125590148Simpyylex(void)
12561592Srgrimes{
125789935Syar	static int cpos;
12581592Srgrimes	char *cp, *cp2;
12591592Srgrimes	struct tab *p;
12601592Srgrimes	int n;
12611592Srgrimes	char c;
12621592Srgrimes
12631592Srgrimes	for (;;) {
12641592Srgrimes		switch (state) {
12651592Srgrimes
12661592Srgrimes		case CMD:
12671592Srgrimes			(void) signal(SIGALRM, toolong);
12681592Srgrimes			(void) alarm((unsigned) timeout);
12691592Srgrimes			if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
12701592Srgrimes				reply(221, "You could at least say goodbye.");
12711592Srgrimes				dologout(0);
12721592Srgrimes			}
12731592Srgrimes			(void) alarm(0);
12741592Srgrimes#ifdef SETPROCTITLE
127529574Sphk			if (strncasecmp(cbuf, "PASS", 4) != 0)
12761592Srgrimes				setproctitle("%s: %s", proctitle, cbuf);
12771592Srgrimes#endif /* SETPROCTITLE */
12781592Srgrimes			if ((cp = strchr(cbuf, '\r'))) {
12791592Srgrimes				*cp++ = '\n';
12801592Srgrimes				*cp = '\0';
12811592Srgrimes			}
12821592Srgrimes			if ((cp = strpbrk(cbuf, " \n")))
12831592Srgrimes				cpos = cp - cbuf;
12841592Srgrimes			if (cpos == 0)
12851592Srgrimes				cpos = 4;
12861592Srgrimes			c = cbuf[cpos];
12871592Srgrimes			cbuf[cpos] = '\0';
12881592Srgrimes			upper(cbuf);
12891592Srgrimes			p = lookup(cmdtab, cbuf);
12901592Srgrimes			cbuf[cpos] = c;
12913776Spst			if (p != 0) {
1292102565Syar				yylval.s = p->name;
1293102565Syar				if (!p->implemented)
1294102565Syar					return (NOTIMPL); /* state remains CMD */
12951592Srgrimes				state = p->state;
12961592Srgrimes				return (p->token);
12971592Srgrimes			}
12981592Srgrimes			break;
12991592Srgrimes
13001592Srgrimes		case SITECMD:
13011592Srgrimes			if (cbuf[cpos] == ' ') {
13021592Srgrimes				cpos++;
13031592Srgrimes				return (SP);
13041592Srgrimes			}
13051592Srgrimes			cp = &cbuf[cpos];
13061592Srgrimes			if ((cp2 = strpbrk(cp, " \n")))
13071592Srgrimes				cpos = cp2 - cbuf;
13081592Srgrimes			c = cbuf[cpos];
13091592Srgrimes			cbuf[cpos] = '\0';
13101592Srgrimes			upper(cp);
13111592Srgrimes			p = lookup(sitetab, cp);
13121592Srgrimes			cbuf[cpos] = c;
13133777Spst			if (guest == 0 && p != 0) {
1314102565Syar				yylval.s = p->name;
1315102565Syar				if (!p->implemented) {
13161592Srgrimes					state = CMD;
1317102565Syar					return (NOTIMPL);
13181592Srgrimes				}
13191592Srgrimes				state = p->state;
13201592Srgrimes				return (p->token);
13211592Srgrimes			}
13221592Srgrimes			state = CMD;
13231592Srgrimes			break;
13241592Srgrimes
132575556Sgreen		case ZSTR1:
13261592Srgrimes		case OSTR:
13271592Srgrimes			if (cbuf[cpos] == '\n') {
13281592Srgrimes				state = CMD;
13291592Srgrimes				return (CRLF);
13301592Srgrimes			}
13311592Srgrimes			/* FALLTHROUGH */
13321592Srgrimes
13331592Srgrimes		case STR1:
13341592Srgrimes		dostr1:
13351592Srgrimes			if (cbuf[cpos] == ' ') {
13361592Srgrimes				cpos++;
133751979Salfred				state = state == OSTR ? STR2 : state+1;
13381592Srgrimes				return (SP);
13391592Srgrimes			}
13401592Srgrimes			break;
13411592Srgrimes
13421592Srgrimes		case ZSTR2:
13431592Srgrimes			if (cbuf[cpos] == '\n') {
13441592Srgrimes				state = CMD;
13451592Srgrimes				return (CRLF);
13461592Srgrimes			}
13471592Srgrimes			/* FALLTHROUGH */
13481592Srgrimes
13491592Srgrimes		case STR2:
13501592Srgrimes			cp = &cbuf[cpos];
13511592Srgrimes			n = strlen(cp);
13521592Srgrimes			cpos += n - 1;
13531592Srgrimes			/*
13541592Srgrimes			 * Make sure the string is nonempty and \n terminated.
13551592Srgrimes			 */
13561592Srgrimes			if (n > 1 && cbuf[cpos] == '\n') {
13571592Srgrimes				cbuf[cpos] = '\0';
13581592Srgrimes				yylval.s = copy(cp);
13591592Srgrimes				cbuf[cpos] = '\n';
13601592Srgrimes				state = ARGS;
13611592Srgrimes				return (STRING);
13621592Srgrimes			}
13631592Srgrimes			break;
13641592Srgrimes
13651592Srgrimes		case NSTR:
13661592Srgrimes			if (cbuf[cpos] == ' ') {
13671592Srgrimes				cpos++;
13681592Srgrimes				return (SP);
13691592Srgrimes			}
13701592Srgrimes			if (isdigit(cbuf[cpos])) {
13711592Srgrimes				cp = &cbuf[cpos];
13721592Srgrimes				while (isdigit(cbuf[++cpos]))
13731592Srgrimes					;
13741592Srgrimes				c = cbuf[cpos];
13751592Srgrimes				cbuf[cpos] = '\0';
137692272Smaxim				yylval.u.i = atoi(cp);
13771592Srgrimes				cbuf[cpos] = c;
13781592Srgrimes				state = STR1;
13791592Srgrimes				return (NUMBER);
13801592Srgrimes			}
13811592Srgrimes			state = STR1;
13821592Srgrimes			goto dostr1;
13831592Srgrimes
13841592Srgrimes		case ARGS:
13851592Srgrimes			if (isdigit(cbuf[cpos])) {
13861592Srgrimes				cp = &cbuf[cpos];
13871592Srgrimes				while (isdigit(cbuf[++cpos]))
13881592Srgrimes					;
13891592Srgrimes				c = cbuf[cpos];
13901592Srgrimes				cbuf[cpos] = '\0';
139192272Smaxim				yylval.u.i = atoi(cp);
139292272Smaxim				yylval.u.o = strtoull(cp, (char **)NULL, 10);
13931592Srgrimes				cbuf[cpos] = c;
13941592Srgrimes				return (NUMBER);
13951592Srgrimes			}
139656668Sshin			if (strncasecmp(&cbuf[cpos], "ALL", 3) == 0
139756668Sshin			 && !isalnum(cbuf[cpos + 3])) {
139856668Sshin				cpos += 3;
139956668Sshin				return ALL;
140056668Sshin			}
14011592Srgrimes			switch (cbuf[cpos++]) {
14021592Srgrimes
14031592Srgrimes			case '\n':
14041592Srgrimes				state = CMD;
14051592Srgrimes				return (CRLF);
14061592Srgrimes
14071592Srgrimes			case ' ':
14081592Srgrimes				return (SP);
14091592Srgrimes
14101592Srgrimes			case ',':
14111592Srgrimes				return (COMMA);
14121592Srgrimes
14131592Srgrimes			case 'A':
14141592Srgrimes			case 'a':
14151592Srgrimes				return (A);
14161592Srgrimes
14171592Srgrimes			case 'B':
14181592Srgrimes			case 'b':
14191592Srgrimes				return (B);
14201592Srgrimes
14211592Srgrimes			case 'C':
14221592Srgrimes			case 'c':
14231592Srgrimes				return (C);
14241592Srgrimes
14251592Srgrimes			case 'E':
14261592Srgrimes			case 'e':
14271592Srgrimes				return (E);
14281592Srgrimes
14291592Srgrimes			case 'F':
14301592Srgrimes			case 'f':
14311592Srgrimes				return (F);
14321592Srgrimes
14331592Srgrimes			case 'I':
14341592Srgrimes			case 'i':
14351592Srgrimes				return (I);
14361592Srgrimes
14371592Srgrimes			case 'L':
14381592Srgrimes			case 'l':
14391592Srgrimes				return (L);
14401592Srgrimes
14411592Srgrimes			case 'N':
14421592Srgrimes			case 'n':
14431592Srgrimes				return (N);
14441592Srgrimes
14451592Srgrimes			case 'P':
14461592Srgrimes			case 'p':
14471592Srgrimes				return (P);
14481592Srgrimes
14491592Srgrimes			case 'R':
14501592Srgrimes			case 'r':
14511592Srgrimes				return (R);
14521592Srgrimes
14531592Srgrimes			case 'S':
14541592Srgrimes			case 's':
14551592Srgrimes				return (S);
14561592Srgrimes
14571592Srgrimes			case 'T':
14581592Srgrimes			case 't':
14591592Srgrimes				return (T);
14601592Srgrimes
14611592Srgrimes			}
14621592Srgrimes			break;
14631592Srgrimes
14641592Srgrimes		default:
146576096Smarkm			fatalerror("Unknown state in scanner.");
14661592Srgrimes		}
14671592Srgrimes		state = CMD;
146889935Syar		return (LEXERR);
14691592Srgrimes	}
14701592Srgrimes}
14711592Srgrimes
14721592Srgrimesvoid
147390148Simpupper(char *s)
14741592Srgrimes{
14751592Srgrimes	while (*s != '\0') {
14761592Srgrimes		if (islower(*s))
14771592Srgrimes			*s = toupper(*s);
14781592Srgrimes		s++;
14791592Srgrimes	}
14801592Srgrimes}
14811592Srgrimes
14821592Srgrimesstatic char *
148390148Simpcopy(char *s)
14841592Srgrimes{
14851592Srgrimes	char *p;
14861592Srgrimes
14871592Srgrimes	p = malloc((unsigned) strlen(s) + 1);
14881592Srgrimes	if (p == NULL)
148976096Smarkm		fatalerror("Ran out of memory.");
14901592Srgrimes	(void) strcpy(p, s);
14911592Srgrimes	return (p);
14921592Srgrimes}
14931592Srgrimes
14941592Srgrimesstatic void
149590148Simphelp(struct tab *ctab, char *s)
14961592Srgrimes{
14971592Srgrimes	struct tab *c;
14981592Srgrimes	int width, NCMDS;
14991592Srgrimes	char *type;
15001592Srgrimes
15011592Srgrimes	if (ctab == sitetab)
15021592Srgrimes		type = "SITE ";
15031592Srgrimes	else
15041592Srgrimes		type = "";
15051592Srgrimes	width = 0, NCMDS = 0;
15061592Srgrimes	for (c = ctab; c->name != NULL; c++) {
15071592Srgrimes		int len = strlen(c->name);
15081592Srgrimes
15091592Srgrimes		if (len > width)
15101592Srgrimes			width = len;
15111592Srgrimes		NCMDS++;
15121592Srgrimes	}
15131592Srgrimes	width = (width + 8) &~ 7;
15141592Srgrimes	if (s == 0) {
15151592Srgrimes		int i, j, w;
15161592Srgrimes		int columns, lines;
15171592Srgrimes
15181592Srgrimes		lreply(214, "The following %scommands are recognized %s.",
15191592Srgrimes		    type, "(* =>'s unimplemented)");
15201592Srgrimes		columns = 76 / width;
15211592Srgrimes		if (columns == 0)
15221592Srgrimes			columns = 1;
15231592Srgrimes		lines = (NCMDS + columns - 1) / columns;
15241592Srgrimes		for (i = 0; i < lines; i++) {
15251592Srgrimes			printf("   ");
15261592Srgrimes			for (j = 0; j < columns; j++) {
15271592Srgrimes				c = ctab + j * lines + i;
15281592Srgrimes				printf("%s%c", c->name,
15291592Srgrimes					c->implemented ? ' ' : '*');
15301592Srgrimes				if (c + lines >= &ctab[NCMDS])
15311592Srgrimes					break;
15321592Srgrimes				w = strlen(c->name) + 1;
15331592Srgrimes				while (w < width) {
15341592Srgrimes					putchar(' ');
15351592Srgrimes					w++;
15361592Srgrimes				}
15371592Srgrimes			}
15381592Srgrimes			printf("\r\n");
15391592Srgrimes		}
15401592Srgrimes		(void) fflush(stdout);
1541110037Syar		if (hostinfo)
1542110037Syar			reply(214, "Direct comments to ftp-bugs@%s.", hostname);
1543110037Syar		else
1544110037Syar			reply(214, "End.");
15451592Srgrimes		return;
15461592Srgrimes	}
15471592Srgrimes	upper(s);
15481592Srgrimes	c = lookup(ctab, s);
15491592Srgrimes	if (c == (struct tab *)0) {
15501592Srgrimes		reply(502, "Unknown command %s.", s);
15511592Srgrimes		return;
15521592Srgrimes	}
15531592Srgrimes	if (c->implemented)
15541592Srgrimes		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
15551592Srgrimes	else
15561592Srgrimes		reply(214, "%s%-*s\t%s; unimplemented.", type, width,
15571592Srgrimes		    c->name, c->help);
15581592Srgrimes}
15591592Srgrimes
15601592Srgrimesstatic void
156190148Simpsizecmd(char *filename)
15621592Srgrimes{
15631592Srgrimes	switch (type) {
15641592Srgrimes	case TYPE_L:
15651592Srgrimes	case TYPE_I: {
15661592Srgrimes		struct stat stbuf;
156763350Sdes		if (stat(filename, &stbuf) < 0)
156863350Sdes			perror_reply(550, filename);
156963350Sdes		else if (!S_ISREG(stbuf.st_mode))
15701592Srgrimes			reply(550, "%s: not a plain file.", filename);
15711592Srgrimes		else
15721592Srgrimes			reply(213, "%qu", stbuf.st_size);
15731592Srgrimes		break; }
15741592Srgrimes	case TYPE_A: {
15751592Srgrimes		FILE *fin;
15761592Srgrimes		int c;
15771592Srgrimes		off_t count;
15781592Srgrimes		struct stat stbuf;
15791592Srgrimes		fin = fopen(filename, "r");
15801592Srgrimes		if (fin == NULL) {
15811592Srgrimes			perror_reply(550, filename);
15821592Srgrimes			return;
15831592Srgrimes		}
158463350Sdes		if (fstat(fileno(fin), &stbuf) < 0) {
158563350Sdes			perror_reply(550, filename);
158663350Sdes			(void) fclose(fin);
158763350Sdes			return;
158863350Sdes		} else if (!S_ISREG(stbuf.st_mode)) {
15891592Srgrimes			reply(550, "%s: not a plain file.", filename);
15901592Srgrimes			(void) fclose(fin);
15911592Srgrimes			return;
1592101034Syar		} else if (stbuf.st_size > MAXASIZE) {
1593101034Syar			reply(550, "%s: too large for type A SIZE.", filename);
1594101034Syar			(void) fclose(fin);
1595101034Syar			return;
15961592Srgrimes		}
15971592Srgrimes
15981592Srgrimes		count = 0;
15991592Srgrimes		while((c=getc(fin)) != EOF) {
16001592Srgrimes			if (c == '\n')	/* will get expanded to \r\n */
16011592Srgrimes				count++;
16021592Srgrimes			count++;
16031592Srgrimes		}
16041592Srgrimes		(void) fclose(fin);
16051592Srgrimes
16061592Srgrimes		reply(213, "%qd", count);
16071592Srgrimes		break; }
16081592Srgrimes	default:
1609100684Syar		reply(504, "SIZE not implemented for type %s.",
1610100684Syar		           typenames[type]);
16111592Srgrimes	}
16121592Srgrimes}
161356668Sshin
161456668Sshin/* Return 1, if port check is done. Return 0, if not yet. */
161556668Sshinstatic int
161690148Simpport_check(const char *pcmd)
161756668Sshin{
161856668Sshin	if (his_addr.su_family == AF_INET) {
161956668Sshin		if (data_dest.su_family != AF_INET) {
162056668Sshin			usedefault = 1;
162156668Sshin			reply(500, "Invalid address rejected.");
162256668Sshin			return 1;
162356668Sshin		}
162456668Sshin		if (paranoid &&
162556668Sshin		    ((ntohs(data_dest.su_port) < IPPORT_RESERVED) ||
162656668Sshin		     memcmp(&data_dest.su_sin.sin_addr,
162756668Sshin			    &his_addr.su_sin.sin_addr,
162856668Sshin			    sizeof(data_dest.su_sin.sin_addr)))) {
162956668Sshin			usedefault = 1;
163056668Sshin			reply(500, "Illegal PORT range rejected.");
163156668Sshin		} else {
163256668Sshin			usedefault = 0;
163356668Sshin			if (pdata >= 0) {
163456668Sshin				(void) close(pdata);
163556668Sshin				pdata = -1;
163656668Sshin			}
163756668Sshin			reply(200, "%s command successful.", pcmd);
163856668Sshin		}
163956668Sshin		return 1;
164056668Sshin	}
164156668Sshin	return 0;
164256668Sshin}
164356668Sshin
164470102Sphkstatic int
164590148Simpcheck_login1(void)
164670102Sphk{
164770102Sphk	if (logged_in)
164870102Sphk		return 1;
164970102Sphk	else {
165070102Sphk		reply(530, "Please login with USER and PASS.");
165170102Sphk		return 0;
165270102Sphk	}
165370102Sphk}
165470102Sphk
1655110340Syar/*
1656110340Syar * Replace leading "~user" in a pathname by the user's login directory.
1657110340Syar * Returned string will be in a freshly malloced buffer unless it's NULL.
1658110340Syar */
1659110340Syarstatic char *
1660110340Syarexptilde(char *s)
1661110340Syar{
1662110340Syar	char *p, *q;
1663110340Syar	char *path, *user;
1664110340Syar	struct passwd *ppw;
1665110340Syar
1666110340Syar	if ((p = strdup(s)) == NULL)
1667110340Syar		return (NULL);
1668110340Syar	if (*p != '~')
1669110340Syar		return (p);
1670110340Syar
1671110340Syar	user = p + 1;	/* skip tilde */
1672110340Syar	if ((path = strchr(p, '/')) != NULL)
1673110340Syar		*(path++) = '\0'; /* separate ~user from the rest of path */
1674110378Syar	if (*user == '\0') /* no user specified, use the current user */
1675110378Syar		user = pw->pw_name;
1676110378Syar	/* read passwd even for the current user since we may be chrooted */
1677110378Syar	if ((ppw = getpwnam(user)) != NULL) {
1678110340Syar		/* user found, substitute login directory for ~user */
1679110340Syar		if (path)
1680110340Syar			asprintf(&q, "%s/%s", ppw->pw_dir, path);
1681110340Syar		else
1682110340Syar			q = strdup(ppw->pw_dir);
1683110340Syar		free(p);
1684110340Syar		p = q;
1685110340Syar	} else {
1686110340Syar		/* user not found, undo the damage */
1687110340Syar		if (path)
1688110340Syar			path[-1] = '/';
1689110340Syar	}
1690110340Syar	return (p);
1691110340Syar}
1692110340Syar
1693110340Syar/*
1694110340Syar * Expand glob(3) patterns possibly present in a pathname.
1695110340Syar * Avoid expanding to a pathname including '\r' or '\n' in order to
1696110340Syar * not disrupt the FTP protocol.
1697110340Syar * The expansion found must be unique.
1698110340Syar * Return the result as a malloced string, or NULL if an error occured.
1699110340Syar *
1700110340Syar * Problem: this production is used for all pathname
1701110340Syar * processing, but only gives a 550 error reply.
1702110340Syar * This is a valid reply in some cases but not in others.
1703110340Syar */
1704110340Syarstatic char *
1705110340Syarexpglob(char *s)
1706110340Syar{
1707110340Syar	char *p, **pp, *rval;
1708110340Syar	int flags = GLOB_BRACE | GLOB_NOCHECK;
1709110340Syar	int n;
1710110340Syar	glob_t gl;
1711110340Syar
1712110340Syar	memset(&gl, 0, sizeof(gl));
1713110340Syar	flags |= GLOB_LIMIT;
1714110340Syar	gl.gl_matchc = MAXGLOBARGS;
1715110340Syar	if (glob(s, flags, NULL, &gl) == 0 && gl.gl_pathc != 0) {
1716110340Syar		for (pp = gl.gl_pathv, p = NULL, n = 0; *pp; pp++)
1717110340Syar			if (*(*pp + strcspn(*pp, "\r\n")) == '\0') {
1718110340Syar				p = *pp;
1719110340Syar				n++;
1720110340Syar			}
1721110340Syar		if (n == 0)
1722110340Syar			rval = strdup(s);
1723110340Syar		else if (n == 1)
1724110340Syar			rval = strdup(p);
1725110340Syar		else {
1726110340Syar			reply(550, "ambiguous");
1727110340Syar			rval = NULL;
1728110340Syar		}
1729110340Syar	} else {
1730110340Syar		reply(550, "wildcard expansion error");
1731110340Syar		rval = NULL;
1732110340Syar	}
1733110340Syar	globfree(&gl);
1734110340Syar	return (rval);
1735110340Syar}
1736110340Syar
173756668Sshin#ifdef INET6
173856668Sshin/* Return 1, if port check is done. Return 0, if not yet. */
173956668Sshinstatic int
174090148Simpport_check_v6(const char *pcmd)
174156668Sshin{
174256668Sshin	if (his_addr.su_family == AF_INET6) {
174356668Sshin		if (IN6_IS_ADDR_V4MAPPED(&his_addr.su_sin6.sin6_addr))
174456668Sshin			/* Convert data_dest into v4 mapped sockaddr.*/
174556668Sshin			v4map_data_dest();
174656668Sshin		if (data_dest.su_family != AF_INET6) {
174756668Sshin			usedefault = 1;
174856668Sshin			reply(500, "Invalid address rejected.");
174956668Sshin			return 1;
175056668Sshin		}
175156668Sshin		if (paranoid &&
175256668Sshin		    ((ntohs(data_dest.su_port) < IPPORT_RESERVED) ||
175356668Sshin		     memcmp(&data_dest.su_sin6.sin6_addr,
175456668Sshin			    &his_addr.su_sin6.sin6_addr,
175556668Sshin			    sizeof(data_dest.su_sin6.sin6_addr)))) {
175656668Sshin			usedefault = 1;
175756668Sshin			reply(500, "Illegal PORT range rejected.");
175856668Sshin		} else {
175956668Sshin			usedefault = 0;
176056668Sshin			if (pdata >= 0) {
176156668Sshin				(void) close(pdata);
176256668Sshin				pdata = -1;
176356668Sshin			}
176456668Sshin			reply(200, "%s command successful.", pcmd);
176556668Sshin		}
176656668Sshin		return 1;
176756668Sshin	}
176856668Sshin	return 0;
176956668Sshin}
177056668Sshin
177156668Sshinstatic void
177290148Simpv4map_data_dest(void)
177356668Sshin{
177456668Sshin	struct in_addr savedaddr;
177556668Sshin	int savedport;
177656668Sshin
177756668Sshin	if (data_dest.su_family != AF_INET) {
177856668Sshin		usedefault = 1;
177956668Sshin		reply(500, "Invalid address rejected.");
178056668Sshin		return;
178156668Sshin	}
178256668Sshin
178356668Sshin	savedaddr = data_dest.su_sin.sin_addr;
178456668Sshin	savedport = data_dest.su_port;
178556668Sshin
178656668Sshin	memset(&data_dest, 0, sizeof(data_dest));
178756668Sshin	data_dest.su_sin6.sin6_len = sizeof(struct sockaddr_in6);
178856668Sshin	data_dest.su_sin6.sin6_family = AF_INET6;
178956668Sshin	data_dest.su_sin6.sin6_port = savedport;
179056668Sshin	memset((caddr_t)&data_dest.su_sin6.sin6_addr.s6_addr[10], 0xff, 2);
179156668Sshin	memcpy((caddr_t)&data_dest.su_sin6.sin6_addr.s6_addr[12],
179256668Sshin	       (caddr_t)&savedaddr, sizeof(savedaddr));
179356668Sshin}
179456668Sshin#endif
1795