ftpcmd.y revision 132925
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 132925 2004-07-31 14:03:14Z 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;
921592Srgrimesextern	char proctitle[];
931592Srgrimesextern	int usedefault;
941592Srgrimesextern  char tmpline[];
9570102Sphkextern	int readonly;
9670102Sphkextern	int noepsv;
9782460Snikextern	int noretr;
9882796Ssheldonhextern	int noguestretr;
99100684Syarextern	char *typenames[]; /* defined in <arpa/ftp.h> included from ftpd.c */
1001592Srgrimes
1011592Srgrimesoff_t	restart_point;
1021592Srgrimes
1031592Srgrimesstatic	int cmd_type;
1041592Srgrimesstatic	int cmd_form;
1051592Srgrimesstatic	int cmd_bytesz;
10689935Syarstatic	int state;
1071592Srgrimeschar	cbuf[512];
10888935Sdwmalonechar	*fromname = (char *) 0;
1091592Srgrimes
11056668Sshinextern int epsvall;
11156668Sshin
1121592Srgrimes%}
1131592Srgrimes
1141592Srgrimes%union {
11592272Smaxim	struct {
11692272Smaxim		off_t	o;
11792272Smaxim		int	i;
11892272Smaxim	} u;
1191592Srgrimes	char   *s;
1201592Srgrimes}
1211592Srgrimes
1221592Srgrimes%token
1231592Srgrimes	A	B	C	E	F	I
1241592Srgrimes	L	N	P	R	S	T
12556668Sshin	ALL
1261592Srgrimes
1271592Srgrimes	SP	CRLF	COMMA
1281592Srgrimes
1291592Srgrimes	USER	PASS	ACCT	REIN	QUIT	PORT
1301592Srgrimes	PASV	TYPE	STRU	MODE	RETR	STOR
1311592Srgrimes	APPE	MLFL	MAIL	MSND	MSOM	MSAM
1321592Srgrimes	MRSQ	MRCP	ALLO	REST	RNFR	RNTO
1331592Srgrimes	ABOR	DELE	CWD	LIST	NLST	SITE
1341592Srgrimes	STAT	HELP	NOOP	MKD	RMD	PWD
1351592Srgrimes	CDUP	STOU	SMNT	SYST	SIZE	MDTM
13656668Sshin	LPRT	LPSV	EPRT	EPSV
1371592Srgrimes
13875535Sphk	UMASK	IDLE	CHMOD	MDFIVE
1391592Srgrimes
140102565Syar	LEXERR	NOTIMPL
1411592Srgrimes
1421592Srgrimes%token	<s> STRING
14392272Smaxim%token	<u> NUMBER
1441592Srgrimes
14592272Smaxim%type	<u.i> check_login octal_number byte_size
14692272Smaxim%type	<u.i> check_login_ro check_login_epsv
14792272Smaxim%type	<u.i> struct_code mode_code type_code form_code
14875567Speter%type	<s> pathstring pathname password username
149102565Syar%type	<s> ALL NOTIMPL
1501592Srgrimes
1511592Srgrimes%start	cmd_list
1521592Srgrimes
1531592Srgrimes%%
1541592Srgrimes
1551592Srgrimescmd_list
1561592Srgrimes	: /* empty */
1571592Srgrimes	| cmd_list cmd
1581592Srgrimes		{
15988935Sdwmalone			if (fromname)
16088935Sdwmalone				free(fromname);
1611592Srgrimes			fromname = (char *) 0;
1621592Srgrimes			restart_point = (off_t) 0;
1631592Srgrimes		}
1641592Srgrimes	| cmd_list rcmd
1651592Srgrimes	;
1661592Srgrimes
1671592Srgrimescmd
1681592Srgrimes	: USER SP username CRLF
1691592Srgrimes		{
1701592Srgrimes			user($3);
1711592Srgrimes			free($3);
1721592Srgrimes		}
1731592Srgrimes	| PASS SP password CRLF
1741592Srgrimes		{
1751592Srgrimes			pass($3);
1761592Srgrimes			free($3);
1771592Srgrimes		}
17875556Sgreen	| PASS CRLF
17975556Sgreen		{
18075556Sgreen			pass("");
18175556Sgreen		}
18217433Spst	| PORT check_login SP host_port CRLF
1831592Srgrimes		{
18456668Sshin			if (epsvall) {
18556668Sshin				reply(501, "no PORT allowed after EPSV ALL");
18656668Sshin				goto port_done;
18756668Sshin			}
18856668Sshin			if (!$2)
18956668Sshin				goto port_done;
19056668Sshin			if (port_check("PORT") == 1)
19156668Sshin				goto port_done;
19256668Sshin#ifdef INET6
19356668Sshin			if ((his_addr.su_family != AF_INET6 ||
19456668Sshin			     !IN6_IS_ADDR_V4MAPPED(&his_addr.su_sin6.sin6_addr))) {
19556668Sshin				/* shoud never happen */
19656668Sshin				usedefault = 1;
19756668Sshin				reply(500, "Invalid address rejected.");
19856668Sshin				goto port_done;
19956668Sshin			}
20056668Sshin			port_check_v6("pcmd");
20156668Sshin#endif
20256668Sshin		port_done:
203132925Syar			;
20456668Sshin		}
20556668Sshin	| LPRT check_login SP host_long_port CRLF
20656668Sshin		{
20756668Sshin			if (epsvall) {
20856668Sshin				reply(501, "no LPRT allowed after EPSV ALL");
20956668Sshin				goto lprt_done;
21056668Sshin			}
21156668Sshin			if (!$2)
21256668Sshin				goto lprt_done;
21356668Sshin			if (port_check("LPRT") == 1)
21456668Sshin				goto lprt_done;
21556668Sshin#ifdef INET6
21656668Sshin			if (his_addr.su_family != AF_INET6) {
21756668Sshin				usedefault = 1;
21856668Sshin				reply(500, "Invalid address rejected.");
21956668Sshin				goto lprt_done;
22056668Sshin			}
22156668Sshin			if (port_check_v6("LPRT") == 1)
22256668Sshin				goto lprt_done;
22356668Sshin#endif
22456668Sshin		lprt_done:
225132925Syar			;
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		{
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)
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;
1174117352Syar	sigset_t sset, osset;
11751592Srgrimes
11761592Srgrimes	cs = s;
11771592Srgrimes/* tmpline may contain saved command from urgent mode interruption */
11781592Srgrimes	for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
11791592Srgrimes		*cs++ = tmpline[c];
11801592Srgrimes		if (tmpline[c] == '\n') {
11811592Srgrimes			*cs++ = '\0';
118276096Smarkm			if (ftpdebug)
11831592Srgrimes				syslog(LOG_DEBUG, "command: %s", s);
11841592Srgrimes			tmpline[0] = '\0';
11851592Srgrimes			return(s);
11861592Srgrimes		}
11871592Srgrimes		if (c == 0)
11881592Srgrimes			tmpline[0] = '\0';
11891592Srgrimes	}
1190117352Syar	/* SIGURG would interrupt stdio if not blocked during the read loop */
1191117352Syar	sigemptyset(&sset);
1192117352Syar	sigaddset(&sset, SIGURG);
1193117352Syar	sigprocmask(SIG_BLOCK, &sset, &osset);
11941592Srgrimes	while ((c = getc(iop)) != EOF) {
11951592Srgrimes		c &= 0377;
11961592Srgrimes		if (c == IAC) {
1197117351Syar			if ((c = getc(iop)) == EOF)
1198117351Syar				goto got_eof;
11991592Srgrimes			c &= 0377;
12001592Srgrimes			switch (c) {
12011592Srgrimes			case WILL:
12021592Srgrimes			case WONT:
1203117351Syar				if ((c = getc(iop)) == EOF)
1204117351Syar					goto got_eof;
12051592Srgrimes				printf("%c%c%c", IAC, DONT, 0377&c);
12061592Srgrimes				(void) fflush(stdout);
12071592Srgrimes				continue;
12081592Srgrimes			case DO:
12091592Srgrimes			case DONT:
1210117351Syar				if ((c = getc(iop)) == EOF)
1211117351Syar					goto got_eof;
12121592Srgrimes				printf("%c%c%c", IAC, WONT, 0377&c);
12131592Srgrimes				(void) fflush(stdout);
12141592Srgrimes				continue;
12151592Srgrimes			case IAC:
12161592Srgrimes				break;
12171592Srgrimes			default:
12181592Srgrimes				continue;	/* ignore command */
12191592Srgrimes			}
12201592Srgrimes		}
12211592Srgrimes		*cs++ = c;
12221592Srgrimes		if (--n <= 0 || c == '\n')
12231592Srgrimes			break;
12241592Srgrimes	}
1225117351Syargot_eof:
1226117352Syar	sigprocmask(SIG_SETMASK, &osset, NULL);
12271592Srgrimes	if (c == EOF && cs == s)
12281592Srgrimes		return (NULL);
12291592Srgrimes	*cs++ = '\0';
123076096Smarkm	if (ftpdebug) {
12311592Srgrimes		if (!guest && strncasecmp("pass ", s, 5) == 0) {
12321592Srgrimes			/* Don't syslog passwords */
12331592Srgrimes			syslog(LOG_DEBUG, "command: %.5s ???", s);
12341592Srgrimes		} else {
12351592Srgrimes			register char *cp;
12361592Srgrimes			register int len;
12371592Srgrimes
12381592Srgrimes			/* Don't syslog trailing CR-LF */
12391592Srgrimes			len = strlen(s);
12401592Srgrimes			cp = s + len - 1;
12411592Srgrimes			while (cp >= s && (*cp == '\n' || *cp == '\r')) {
12421592Srgrimes				--cp;
12431592Srgrimes				--len;
12441592Srgrimes			}
12451592Srgrimes			syslog(LOG_DEBUG, "command: %.*s", len, s);
12461592Srgrimes		}
12471592Srgrimes	}
12481592Srgrimes	return (s);
12491592Srgrimes}
12501592Srgrimes
12511592Srgrimesstatic void
125290148Simptoolong(int signo)
12531592Srgrimes{
12541592Srgrimes
12551592Srgrimes	reply(421,
12561592Srgrimes	    "Timeout (%d seconds): closing control connection.", timeout);
12571592Srgrimes	if (logging)
12581592Srgrimes		syslog(LOG_INFO, "User %s timed out after %d seconds",
12591592Srgrimes		    (pw ? pw -> pw_name : "unknown"), timeout);
12601592Srgrimes	dologout(1);
12611592Srgrimes}
12621592Srgrimes
12631592Srgrimesstatic int
126490148Simpyylex(void)
12651592Srgrimes{
126689935Syar	static int cpos;
12671592Srgrimes	char *cp, *cp2;
12681592Srgrimes	struct tab *p;
12691592Srgrimes	int n;
12701592Srgrimes	char c;
12711592Srgrimes
12721592Srgrimes	for (;;) {
12731592Srgrimes		switch (state) {
12741592Srgrimes
12751592Srgrimes		case CMD:
12761592Srgrimes			(void) signal(SIGALRM, toolong);
12771592Srgrimes			(void) alarm((unsigned) timeout);
12781592Srgrimes			if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
12791592Srgrimes				reply(221, "You could at least say goodbye.");
12801592Srgrimes				dologout(0);
12811592Srgrimes			}
12821592Srgrimes			(void) alarm(0);
12831592Srgrimes#ifdef SETPROCTITLE
128429574Sphk			if (strncasecmp(cbuf, "PASS", 4) != 0)
12851592Srgrimes				setproctitle("%s: %s", proctitle, cbuf);
12861592Srgrimes#endif /* SETPROCTITLE */
12871592Srgrimes			if ((cp = strchr(cbuf, '\r'))) {
12881592Srgrimes				*cp++ = '\n';
12891592Srgrimes				*cp = '\0';
12901592Srgrimes			}
12911592Srgrimes			if ((cp = strpbrk(cbuf, " \n")))
12921592Srgrimes				cpos = cp - cbuf;
12931592Srgrimes			if (cpos == 0)
12941592Srgrimes				cpos = 4;
12951592Srgrimes			c = cbuf[cpos];
12961592Srgrimes			cbuf[cpos] = '\0';
12971592Srgrimes			upper(cbuf);
12981592Srgrimes			p = lookup(cmdtab, cbuf);
12991592Srgrimes			cbuf[cpos] = c;
13003776Spst			if (p != 0) {
1301102565Syar				yylval.s = p->name;
1302102565Syar				if (!p->implemented)
1303102565Syar					return (NOTIMPL); /* state remains CMD */
13041592Srgrimes				state = p->state;
13051592Srgrimes				return (p->token);
13061592Srgrimes			}
13071592Srgrimes			break;
13081592Srgrimes
13091592Srgrimes		case SITECMD:
13101592Srgrimes			if (cbuf[cpos] == ' ') {
13111592Srgrimes				cpos++;
13121592Srgrimes				return (SP);
13131592Srgrimes			}
13141592Srgrimes			cp = &cbuf[cpos];
13151592Srgrimes			if ((cp2 = strpbrk(cp, " \n")))
13161592Srgrimes				cpos = cp2 - cbuf;
13171592Srgrimes			c = cbuf[cpos];
13181592Srgrimes			cbuf[cpos] = '\0';
13191592Srgrimes			upper(cp);
13201592Srgrimes			p = lookup(sitetab, cp);
13211592Srgrimes			cbuf[cpos] = c;
13223777Spst			if (guest == 0 && p != 0) {
1323102565Syar				yylval.s = p->name;
1324102565Syar				if (!p->implemented) {
13251592Srgrimes					state = CMD;
1326102565Syar					return (NOTIMPL);
13271592Srgrimes				}
13281592Srgrimes				state = p->state;
13291592Srgrimes				return (p->token);
13301592Srgrimes			}
13311592Srgrimes			state = CMD;
13321592Srgrimes			break;
13331592Srgrimes
133475556Sgreen		case ZSTR1:
13351592Srgrimes		case OSTR:
13361592Srgrimes			if (cbuf[cpos] == '\n') {
13371592Srgrimes				state = CMD;
13381592Srgrimes				return (CRLF);
13391592Srgrimes			}
13401592Srgrimes			/* FALLTHROUGH */
13411592Srgrimes
13421592Srgrimes		case STR1:
13431592Srgrimes		dostr1:
13441592Srgrimes			if (cbuf[cpos] == ' ') {
13451592Srgrimes				cpos++;
134651979Salfred				state = state == OSTR ? STR2 : state+1;
13471592Srgrimes				return (SP);
13481592Srgrimes			}
13491592Srgrimes			break;
13501592Srgrimes
13511592Srgrimes		case ZSTR2:
13521592Srgrimes			if (cbuf[cpos] == '\n') {
13531592Srgrimes				state = CMD;
13541592Srgrimes				return (CRLF);
13551592Srgrimes			}
13561592Srgrimes			/* FALLTHROUGH */
13571592Srgrimes
13581592Srgrimes		case STR2:
13591592Srgrimes			cp = &cbuf[cpos];
13601592Srgrimes			n = strlen(cp);
13611592Srgrimes			cpos += n - 1;
13621592Srgrimes			/*
13631592Srgrimes			 * Make sure the string is nonempty and \n terminated.
13641592Srgrimes			 */
13651592Srgrimes			if (n > 1 && cbuf[cpos] == '\n') {
13661592Srgrimes				cbuf[cpos] = '\0';
13671592Srgrimes				yylval.s = copy(cp);
13681592Srgrimes				cbuf[cpos] = '\n';
13691592Srgrimes				state = ARGS;
13701592Srgrimes				return (STRING);
13711592Srgrimes			}
13721592Srgrimes			break;
13731592Srgrimes
13741592Srgrimes		case NSTR:
13751592Srgrimes			if (cbuf[cpos] == ' ') {
13761592Srgrimes				cpos++;
13771592Srgrimes				return (SP);
13781592Srgrimes			}
13791592Srgrimes			if (isdigit(cbuf[cpos])) {
13801592Srgrimes				cp = &cbuf[cpos];
13811592Srgrimes				while (isdigit(cbuf[++cpos]))
13821592Srgrimes					;
13831592Srgrimes				c = cbuf[cpos];
13841592Srgrimes				cbuf[cpos] = '\0';
138592272Smaxim				yylval.u.i = atoi(cp);
13861592Srgrimes				cbuf[cpos] = c;
13871592Srgrimes				state = STR1;
13881592Srgrimes				return (NUMBER);
13891592Srgrimes			}
13901592Srgrimes			state = STR1;
13911592Srgrimes			goto dostr1;
13921592Srgrimes
13931592Srgrimes		case ARGS:
13941592Srgrimes			if (isdigit(cbuf[cpos])) {
13951592Srgrimes				cp = &cbuf[cpos];
13961592Srgrimes				while (isdigit(cbuf[++cpos]))
13971592Srgrimes					;
13981592Srgrimes				c = cbuf[cpos];
13991592Srgrimes				cbuf[cpos] = '\0';
140092272Smaxim				yylval.u.i = atoi(cp);
140192272Smaxim				yylval.u.o = strtoull(cp, (char **)NULL, 10);
14021592Srgrimes				cbuf[cpos] = c;
14031592Srgrimes				return (NUMBER);
14041592Srgrimes			}
140556668Sshin			if (strncasecmp(&cbuf[cpos], "ALL", 3) == 0
140656668Sshin			 && !isalnum(cbuf[cpos + 3])) {
140756668Sshin				cpos += 3;
140856668Sshin				return ALL;
140956668Sshin			}
14101592Srgrimes			switch (cbuf[cpos++]) {
14111592Srgrimes
14121592Srgrimes			case '\n':
14131592Srgrimes				state = CMD;
14141592Srgrimes				return (CRLF);
14151592Srgrimes
14161592Srgrimes			case ' ':
14171592Srgrimes				return (SP);
14181592Srgrimes
14191592Srgrimes			case ',':
14201592Srgrimes				return (COMMA);
14211592Srgrimes
14221592Srgrimes			case 'A':
14231592Srgrimes			case 'a':
14241592Srgrimes				return (A);
14251592Srgrimes
14261592Srgrimes			case 'B':
14271592Srgrimes			case 'b':
14281592Srgrimes				return (B);
14291592Srgrimes
14301592Srgrimes			case 'C':
14311592Srgrimes			case 'c':
14321592Srgrimes				return (C);
14331592Srgrimes
14341592Srgrimes			case 'E':
14351592Srgrimes			case 'e':
14361592Srgrimes				return (E);
14371592Srgrimes
14381592Srgrimes			case 'F':
14391592Srgrimes			case 'f':
14401592Srgrimes				return (F);
14411592Srgrimes
14421592Srgrimes			case 'I':
14431592Srgrimes			case 'i':
14441592Srgrimes				return (I);
14451592Srgrimes
14461592Srgrimes			case 'L':
14471592Srgrimes			case 'l':
14481592Srgrimes				return (L);
14491592Srgrimes
14501592Srgrimes			case 'N':
14511592Srgrimes			case 'n':
14521592Srgrimes				return (N);
14531592Srgrimes
14541592Srgrimes			case 'P':
14551592Srgrimes			case 'p':
14561592Srgrimes				return (P);
14571592Srgrimes
14581592Srgrimes			case 'R':
14591592Srgrimes			case 'r':
14601592Srgrimes				return (R);
14611592Srgrimes
14621592Srgrimes			case 'S':
14631592Srgrimes			case 's':
14641592Srgrimes				return (S);
14651592Srgrimes
14661592Srgrimes			case 'T':
14671592Srgrimes			case 't':
14681592Srgrimes				return (T);
14691592Srgrimes
14701592Srgrimes			}
14711592Srgrimes			break;
14721592Srgrimes
14731592Srgrimes		default:
147476096Smarkm			fatalerror("Unknown state in scanner.");
14751592Srgrimes		}
14761592Srgrimes		state = CMD;
147789935Syar		return (LEXERR);
14781592Srgrimes	}
14791592Srgrimes}
14801592Srgrimes
14811592Srgrimesvoid
148290148Simpupper(char *s)
14831592Srgrimes{
14841592Srgrimes	while (*s != '\0') {
14851592Srgrimes		if (islower(*s))
14861592Srgrimes			*s = toupper(*s);
14871592Srgrimes		s++;
14881592Srgrimes	}
14891592Srgrimes}
14901592Srgrimes
14911592Srgrimesstatic char *
149290148Simpcopy(char *s)
14931592Srgrimes{
14941592Srgrimes	char *p;
14951592Srgrimes
14961592Srgrimes	p = malloc((unsigned) strlen(s) + 1);
14971592Srgrimes	if (p == NULL)
149876096Smarkm		fatalerror("Ran out of memory.");
14991592Srgrimes	(void) strcpy(p, s);
15001592Srgrimes	return (p);
15011592Srgrimes}
15021592Srgrimes
15031592Srgrimesstatic void
150490148Simphelp(struct tab *ctab, char *s)
15051592Srgrimes{
15061592Srgrimes	struct tab *c;
15071592Srgrimes	int width, NCMDS;
15081592Srgrimes	char *type;
15091592Srgrimes
15101592Srgrimes	if (ctab == sitetab)
15111592Srgrimes		type = "SITE ";
15121592Srgrimes	else
15131592Srgrimes		type = "";
15141592Srgrimes	width = 0, NCMDS = 0;
15151592Srgrimes	for (c = ctab; c->name != NULL; c++) {
15161592Srgrimes		int len = strlen(c->name);
15171592Srgrimes
15181592Srgrimes		if (len > width)
15191592Srgrimes			width = len;
15201592Srgrimes		NCMDS++;
15211592Srgrimes	}
15221592Srgrimes	width = (width + 8) &~ 7;
15231592Srgrimes	if (s == 0) {
15241592Srgrimes		int i, j, w;
15251592Srgrimes		int columns, lines;
15261592Srgrimes
15271592Srgrimes		lreply(214, "The following %scommands are recognized %s.",
15281592Srgrimes		    type, "(* =>'s unimplemented)");
15291592Srgrimes		columns = 76 / width;
15301592Srgrimes		if (columns == 0)
15311592Srgrimes			columns = 1;
15321592Srgrimes		lines = (NCMDS + columns - 1) / columns;
15331592Srgrimes		for (i = 0; i < lines; i++) {
15341592Srgrimes			printf("   ");
15351592Srgrimes			for (j = 0; j < columns; j++) {
15361592Srgrimes				c = ctab + j * lines + i;
15371592Srgrimes				printf("%s%c", c->name,
15381592Srgrimes					c->implemented ? ' ' : '*');
15391592Srgrimes				if (c + lines >= &ctab[NCMDS])
15401592Srgrimes					break;
15411592Srgrimes				w = strlen(c->name) + 1;
15421592Srgrimes				while (w < width) {
15431592Srgrimes					putchar(' ');
15441592Srgrimes					w++;
15451592Srgrimes				}
15461592Srgrimes			}
15471592Srgrimes			printf("\r\n");
15481592Srgrimes		}
15491592Srgrimes		(void) fflush(stdout);
1550110037Syar		if (hostinfo)
1551110037Syar			reply(214, "Direct comments to ftp-bugs@%s.", hostname);
1552110037Syar		else
1553110037Syar			reply(214, "End.");
15541592Srgrimes		return;
15551592Srgrimes	}
15561592Srgrimes	upper(s);
15571592Srgrimes	c = lookup(ctab, s);
15581592Srgrimes	if (c == (struct tab *)0) {
15591592Srgrimes		reply(502, "Unknown command %s.", s);
15601592Srgrimes		return;
15611592Srgrimes	}
15621592Srgrimes	if (c->implemented)
15631592Srgrimes		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
15641592Srgrimes	else
15651592Srgrimes		reply(214, "%s%-*s\t%s; unimplemented.", type, width,
15661592Srgrimes		    c->name, c->help);
15671592Srgrimes}
15681592Srgrimes
15691592Srgrimesstatic void
157090148Simpsizecmd(char *filename)
15711592Srgrimes{
15721592Srgrimes	switch (type) {
15731592Srgrimes	case TYPE_L:
15741592Srgrimes	case TYPE_I: {
15751592Srgrimes		struct stat stbuf;
157663350Sdes		if (stat(filename, &stbuf) < 0)
157763350Sdes			perror_reply(550, filename);
157863350Sdes		else if (!S_ISREG(stbuf.st_mode))
15791592Srgrimes			reply(550, "%s: not a plain file.", filename);
15801592Srgrimes		else
15811592Srgrimes			reply(213, "%qu", stbuf.st_size);
15821592Srgrimes		break; }
15831592Srgrimes	case TYPE_A: {
15841592Srgrimes		FILE *fin;
15851592Srgrimes		int c;
15861592Srgrimes		off_t count;
15871592Srgrimes		struct stat stbuf;
15881592Srgrimes		fin = fopen(filename, "r");
15891592Srgrimes		if (fin == NULL) {
15901592Srgrimes			perror_reply(550, filename);
15911592Srgrimes			return;
15921592Srgrimes		}
159363350Sdes		if (fstat(fileno(fin), &stbuf) < 0) {
159463350Sdes			perror_reply(550, filename);
159563350Sdes			(void) fclose(fin);
159663350Sdes			return;
159763350Sdes		} else if (!S_ISREG(stbuf.st_mode)) {
15981592Srgrimes			reply(550, "%s: not a plain file.", filename);
15991592Srgrimes			(void) fclose(fin);
16001592Srgrimes			return;
1601101034Syar		} else if (stbuf.st_size > MAXASIZE) {
1602101034Syar			reply(550, "%s: too large for type A SIZE.", filename);
1603101034Syar			(void) fclose(fin);
1604101034Syar			return;
16051592Srgrimes		}
16061592Srgrimes
16071592Srgrimes		count = 0;
16081592Srgrimes		while((c=getc(fin)) != EOF) {
16091592Srgrimes			if (c == '\n')	/* will get expanded to \r\n */
16101592Srgrimes				count++;
16111592Srgrimes			count++;
16121592Srgrimes		}
16131592Srgrimes		(void) fclose(fin);
16141592Srgrimes
16151592Srgrimes		reply(213, "%qd", count);
16161592Srgrimes		break; }
16171592Srgrimes	default:
1618100684Syar		reply(504, "SIZE not implemented for type %s.",
1619100684Syar		           typenames[type]);
16201592Srgrimes	}
16211592Srgrimes}
162256668Sshin
162356668Sshin/* Return 1, if port check is done. Return 0, if not yet. */
162456668Sshinstatic int
162590148Simpport_check(const char *pcmd)
162656668Sshin{
162756668Sshin	if (his_addr.su_family == AF_INET) {
162856668Sshin		if (data_dest.su_family != AF_INET) {
162956668Sshin			usedefault = 1;
163056668Sshin			reply(500, "Invalid address rejected.");
163156668Sshin			return 1;
163256668Sshin		}
163356668Sshin		if (paranoid &&
163456668Sshin		    ((ntohs(data_dest.su_port) < IPPORT_RESERVED) ||
163556668Sshin		     memcmp(&data_dest.su_sin.sin_addr,
163656668Sshin			    &his_addr.su_sin.sin_addr,
163756668Sshin			    sizeof(data_dest.su_sin.sin_addr)))) {
163856668Sshin			usedefault = 1;
163956668Sshin			reply(500, "Illegal PORT range rejected.");
164056668Sshin		} else {
164156668Sshin			usedefault = 0;
164256668Sshin			if (pdata >= 0) {
164356668Sshin				(void) close(pdata);
164456668Sshin				pdata = -1;
164556668Sshin			}
164656668Sshin			reply(200, "%s command successful.", pcmd);
164756668Sshin		}
164856668Sshin		return 1;
164956668Sshin	}
165056668Sshin	return 0;
165156668Sshin}
165256668Sshin
165370102Sphkstatic int
165490148Simpcheck_login1(void)
165570102Sphk{
165670102Sphk	if (logged_in)
165770102Sphk		return 1;
165870102Sphk	else {
165970102Sphk		reply(530, "Please login with USER and PASS.");
166070102Sphk		return 0;
166170102Sphk	}
166270102Sphk}
166370102Sphk
1664110340Syar/*
1665110340Syar * Replace leading "~user" in a pathname by the user's login directory.
1666110340Syar * Returned string will be in a freshly malloced buffer unless it's NULL.
1667110340Syar */
1668110340Syarstatic char *
1669110340Syarexptilde(char *s)
1670110340Syar{
1671110340Syar	char *p, *q;
1672110340Syar	char *path, *user;
1673110340Syar	struct passwd *ppw;
1674110340Syar
1675110340Syar	if ((p = strdup(s)) == NULL)
1676110340Syar		return (NULL);
1677110340Syar	if (*p != '~')
1678110340Syar		return (p);
1679110340Syar
1680110340Syar	user = p + 1;	/* skip tilde */
1681110340Syar	if ((path = strchr(p, '/')) != NULL)
1682110340Syar		*(path++) = '\0'; /* separate ~user from the rest of path */
1683110378Syar	if (*user == '\0') /* no user specified, use the current user */
1684110378Syar		user = pw->pw_name;
1685110378Syar	/* read passwd even for the current user since we may be chrooted */
1686110378Syar	if ((ppw = getpwnam(user)) != NULL) {
1687110340Syar		/* user found, substitute login directory for ~user */
1688110340Syar		if (path)
1689110340Syar			asprintf(&q, "%s/%s", ppw->pw_dir, path);
1690110340Syar		else
1691110340Syar			q = strdup(ppw->pw_dir);
1692110340Syar		free(p);
1693110340Syar		p = q;
1694110340Syar	} else {
1695110340Syar		/* user not found, undo the damage */
1696110340Syar		if (path)
1697110340Syar			path[-1] = '/';
1698110340Syar	}
1699110340Syar	return (p);
1700110340Syar}
1701110340Syar
1702110340Syar/*
1703110340Syar * Expand glob(3) patterns possibly present in a pathname.
1704110340Syar * Avoid expanding to a pathname including '\r' or '\n' in order to
1705110340Syar * not disrupt the FTP protocol.
1706110340Syar * The expansion found must be unique.
1707110340Syar * Return the result as a malloced string, or NULL if an error occured.
1708110340Syar *
1709110340Syar * Problem: this production is used for all pathname
1710110340Syar * processing, but only gives a 550 error reply.
1711110340Syar * This is a valid reply in some cases but not in others.
1712110340Syar */
1713110340Syarstatic char *
1714110340Syarexpglob(char *s)
1715110340Syar{
1716110340Syar	char *p, **pp, *rval;
1717110340Syar	int flags = GLOB_BRACE | GLOB_NOCHECK;
1718110340Syar	int n;
1719110340Syar	glob_t gl;
1720110340Syar
1721110340Syar	memset(&gl, 0, sizeof(gl));
1722110340Syar	flags |= GLOB_LIMIT;
1723110340Syar	gl.gl_matchc = MAXGLOBARGS;
1724110340Syar	if (glob(s, flags, NULL, &gl) == 0 && gl.gl_pathc != 0) {
1725110340Syar		for (pp = gl.gl_pathv, p = NULL, n = 0; *pp; pp++)
1726110340Syar			if (*(*pp + strcspn(*pp, "\r\n")) == '\0') {
1727110340Syar				p = *pp;
1728110340Syar				n++;
1729110340Syar			}
1730110340Syar		if (n == 0)
1731110340Syar			rval = strdup(s);
1732110340Syar		else if (n == 1)
1733110340Syar			rval = strdup(p);
1734110340Syar		else {
1735110340Syar			reply(550, "ambiguous");
1736110340Syar			rval = NULL;
1737110340Syar		}
1738110340Syar	} else {
1739110340Syar		reply(550, "wildcard expansion error");
1740110340Syar		rval = NULL;
1741110340Syar	}
1742110340Syar	globfree(&gl);
1743110340Syar	return (rval);
1744110340Syar}
1745110340Syar
174656668Sshin#ifdef INET6
174756668Sshin/* Return 1, if port check is done. Return 0, if not yet. */
174856668Sshinstatic int
174990148Simpport_check_v6(const char *pcmd)
175056668Sshin{
175156668Sshin	if (his_addr.su_family == AF_INET6) {
175256668Sshin		if (IN6_IS_ADDR_V4MAPPED(&his_addr.su_sin6.sin6_addr))
175356668Sshin			/* Convert data_dest into v4 mapped sockaddr.*/
175456668Sshin			v4map_data_dest();
175556668Sshin		if (data_dest.su_family != AF_INET6) {
175656668Sshin			usedefault = 1;
175756668Sshin			reply(500, "Invalid address rejected.");
175856668Sshin			return 1;
175956668Sshin		}
176056668Sshin		if (paranoid &&
176156668Sshin		    ((ntohs(data_dest.su_port) < IPPORT_RESERVED) ||
176256668Sshin		     memcmp(&data_dest.su_sin6.sin6_addr,
176356668Sshin			    &his_addr.su_sin6.sin6_addr,
176456668Sshin			    sizeof(data_dest.su_sin6.sin6_addr)))) {
176556668Sshin			usedefault = 1;
176656668Sshin			reply(500, "Illegal PORT range rejected.");
176756668Sshin		} else {
176856668Sshin			usedefault = 0;
176956668Sshin			if (pdata >= 0) {
177056668Sshin				(void) close(pdata);
177156668Sshin				pdata = -1;
177256668Sshin			}
177356668Sshin			reply(200, "%s command successful.", pcmd);
177456668Sshin		}
177556668Sshin		return 1;
177656668Sshin	}
177756668Sshin	return 0;
177856668Sshin}
177956668Sshin
178056668Sshinstatic void
178190148Simpv4map_data_dest(void)
178256668Sshin{
178356668Sshin	struct in_addr savedaddr;
178456668Sshin	int savedport;
178556668Sshin
178656668Sshin	if (data_dest.su_family != AF_INET) {
178756668Sshin		usedefault = 1;
178856668Sshin		reply(500, "Invalid address rejected.");
178956668Sshin		return;
179056668Sshin	}
179156668Sshin
179256668Sshin	savedaddr = data_dest.su_sin.sin_addr;
179356668Sshin	savedport = data_dest.su_port;
179456668Sshin
179556668Sshin	memset(&data_dest, 0, sizeof(data_dest));
179656668Sshin	data_dest.su_sin6.sin6_len = sizeof(struct sockaddr_in6);
179756668Sshin	data_dest.su_sin6.sin6_family = AF_INET6;
179856668Sshin	data_dest.su_sin6.sin6_port = savedport;
179956668Sshin	memset((caddr_t)&data_dest.su_sin6.sin6_addr.s6_addr[10], 0xff, 2);
180056668Sshin	memcpy((caddr_t)&data_dest.su_sin6.sin6_addr.s6_addr[12],
180156668Sshin	       (caddr_t)&savedaddr, sizeof(savedaddr));
180256668Sshin}
180356668Sshin#endif
1804