ftpcmd.y revision 117352
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 117352 2003-07-09 13:54:33Z 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:
20356668Sshin		}
20456668Sshin	| LPRT check_login SP host_long_port CRLF
20556668Sshin		{
20656668Sshin			if (epsvall) {
20756668Sshin				reply(501, "no LPRT allowed after EPSV ALL");
20856668Sshin				goto lprt_done;
20956668Sshin			}
21056668Sshin			if (!$2)
21156668Sshin				goto lprt_done;
21256668Sshin			if (port_check("LPRT") == 1)
21356668Sshin				goto lprt_done;
21456668Sshin#ifdef INET6
21556668Sshin			if (his_addr.su_family != AF_INET6) {
21656668Sshin				usedefault = 1;
21756668Sshin				reply(500, "Invalid address rejected.");
21856668Sshin				goto lprt_done;
21956668Sshin			}
22056668Sshin			if (port_check_v6("LPRT") == 1)
22156668Sshin				goto lprt_done;
22256668Sshin#endif
22356668Sshin		lprt_done:
22456668Sshin		}
22556668Sshin	| EPRT check_login SP STRING CRLF
22656668Sshin		{
22756668Sshin			char delim;
22856668Sshin			char *tmp = NULL;
22956668Sshin			char *p, *q;
23056668Sshin			char *result[3];
23156668Sshin			struct addrinfo hints;
23256668Sshin			struct addrinfo *res;
23356668Sshin			int i;
23456668Sshin
23556668Sshin			if (epsvall) {
23656668Sshin				reply(501, "no EPRT allowed after EPSV ALL");
23756668Sshin				goto eprt_done;
23856668Sshin			}
23956668Sshin			if (!$2)
24056668Sshin				goto eprt_done;
24156668Sshin
24256668Sshin			memset(&data_dest, 0, sizeof(data_dest));
24356668Sshin			tmp = strdup($4);
24476096Smarkm			if (ftpdebug)
24556668Sshin				syslog(LOG_DEBUG, "%s", tmp);
24656668Sshin			if (!tmp) {
24776096Smarkm				fatalerror("not enough core");
24856668Sshin				/*NOTREACHED*/
24956668Sshin			}
25056668Sshin			p = tmp;
25156668Sshin			delim = p[0];
25256668Sshin			p++;
25356668Sshin			memset(result, 0, sizeof(result));
25456668Sshin			for (i = 0; i < 3; i++) {
25556668Sshin				q = strchr(p, delim);
25656668Sshin				if (!q || *q != delim) {
25756668Sshin		parsefail:
25856668Sshin					reply(500,
25956668Sshin						"Invalid argument, rejected.");
26056668Sshin					if (tmp)
26156668Sshin						free(tmp);
26217433Spst					usedefault = 1;
26356668Sshin					goto eprt_done;
26417433Spst				}
26556668Sshin				*q++ = '\0';
26656668Sshin				result[i] = p;
26776096Smarkm				if (ftpdebug)
26856668Sshin					syslog(LOG_DEBUG, "%d: %s", i, p);
26956668Sshin				p = q;
2701592Srgrimes			}
27156668Sshin
27256668Sshin			/* some more sanity check */
27356668Sshin			p = result[0];
27456668Sshin			while (*p) {
27556668Sshin				if (!isdigit(*p))
27656668Sshin					goto parsefail;
27756668Sshin				p++;
27856668Sshin			}
27956668Sshin			p = result[2];
28056668Sshin			while (*p) {
28156668Sshin				if (!isdigit(*p))
28256668Sshin					goto parsefail;
28356668Sshin				p++;
28456668Sshin			}
28556668Sshin
28656668Sshin			/* grab address */
28756668Sshin			memset(&hints, 0, sizeof(hints));
28856668Sshin			if (atoi(result[0]) == 1)
28956668Sshin				hints.ai_family = PF_INET;
29056668Sshin#ifdef INET6
29156668Sshin			else if (atoi(result[0]) == 2)
29256668Sshin				hints.ai_family = PF_INET6;
29356668Sshin#endif
29456668Sshin			else
29556668Sshin				hints.ai_family = PF_UNSPEC;	/*XXX*/
29656668Sshin			hints.ai_socktype = SOCK_STREAM;
29756668Sshin			i = getaddrinfo(result[1], result[2], &hints, &res);
29856668Sshin			if (i)
29956668Sshin				goto parsefail;
30056668Sshin			memcpy(&data_dest, res->ai_addr, res->ai_addrlen);
30156668Sshin#ifdef INET6
30256668Sshin			if (his_addr.su_family == AF_INET6
30356668Sshin			    && data_dest.su_family == AF_INET6) {
30456668Sshin				/* XXX more sanity checks! */
30556668Sshin				data_dest.su_sin6.sin6_scope_id =
30656668Sshin					his_addr.su_sin6.sin6_scope_id;
30756668Sshin			}
30856668Sshin#endif
30956668Sshin			free(tmp);
31056668Sshin			tmp = NULL;
31156668Sshin
31256668Sshin			if (port_check("EPRT") == 1)
31356668Sshin				goto eprt_done;
31456668Sshin#ifdef INET6
31556668Sshin			if (his_addr.su_family != AF_INET6) {
31656668Sshin				usedefault = 1;
31756668Sshin				reply(500, "Invalid address rejected.");
31856668Sshin				goto eprt_done;
31956668Sshin			}
32056668Sshin			if (port_check_v6("EPRT") == 1)
32156668Sshin				goto eprt_done;
32256668Sshin#endif
32388935Sdwmalone		eprt_done:
32488935Sdwmalone			free($4);
3251592Srgrimes		}
32617433Spst	| PASV check_login CRLF
3271592Srgrimes		{
32856668Sshin			if (epsvall)
32956668Sshin				reply(501, "no PASV allowed after EPSV ALL");
33056668Sshin			else if ($2)
33117433Spst				passive();
3321592Srgrimes		}
33356668Sshin	| LPSV check_login CRLF
33456668Sshin		{
33556668Sshin			if (epsvall)
33656668Sshin				reply(501, "no LPSV allowed after EPSV ALL");
33756668Sshin			else if ($2)
33856668Sshin				long_passive("LPSV", PF_UNSPEC);
33956668Sshin		}
34070102Sphk	| EPSV check_login_epsv SP NUMBER CRLF
34156668Sshin		{
34256668Sshin			if ($2) {
34356668Sshin				int pf;
34492272Smaxim				switch ($4.i) {
34556668Sshin				case 1:
34656668Sshin					pf = PF_INET;
34756668Sshin					break;
34856668Sshin#ifdef INET6
34956668Sshin				case 2:
35056668Sshin					pf = PF_INET6;
35156668Sshin					break;
35256668Sshin#endif
35356668Sshin				default:
35456668Sshin					pf = -1;	/*junk value*/
35556668Sshin					break;
35656668Sshin				}
35756668Sshin				long_passive("EPSV", pf);
35856668Sshin			}
35956668Sshin		}
36070102Sphk	| EPSV check_login_epsv SP ALL CRLF
36156668Sshin		{
36256668Sshin			if ($2) {
36356668Sshin				reply(200,
36456668Sshin				      "EPSV ALL command successful.");
36556668Sshin				epsvall++;
36656668Sshin			}
36756668Sshin		}
36870102Sphk	| EPSV check_login_epsv CRLF
36956668Sshin		{
37056668Sshin			if ($2)
37156668Sshin				long_passive("EPSV", PF_UNSPEC);
37256668Sshin		}
37371278Sjedgar	| TYPE check_login SP type_code CRLF
3741592Srgrimes		{
37571278Sjedgar			if ($2) {
37671278Sjedgar				switch (cmd_type) {
3771592Srgrimes
37871278Sjedgar				case TYPE_A:
37971278Sjedgar					if (cmd_form == FORM_N) {
38071278Sjedgar						reply(200, "Type set to A.");
38171278Sjedgar						type = cmd_type;
38271278Sjedgar						form = cmd_form;
38371278Sjedgar					} else
38471278Sjedgar						reply(504, "Form must be N.");
38571278Sjedgar					break;
3861592Srgrimes
38771278Sjedgar				case TYPE_E:
38871278Sjedgar					reply(504, "Type E not implemented.");
38971278Sjedgar					break;
3901592Srgrimes
39171278Sjedgar				case TYPE_I:
39271278Sjedgar					reply(200, "Type set to I.");
39371278Sjedgar					type = cmd_type;
39471278Sjedgar					break;
3951592Srgrimes
39671278Sjedgar				case TYPE_L:
397103949Smike#if CHAR_BIT == 8
39871278Sjedgar					if (cmd_bytesz == 8) {
39971278Sjedgar						reply(200,
40071278Sjedgar						    "Type set to L (byte size 8).");
40171278Sjedgar						type = cmd_type;
40271278Sjedgar					} else
40371278Sjedgar						reply(504, "Byte size must be 8.");
404103949Smike#else /* CHAR_BIT == 8 */
405103949Smike					UNIMPLEMENTED for CHAR_BIT != 8
406103949Smike#endif /* CHAR_BIT == 8 */
40771278Sjedgar				}
4081592Srgrimes			}
4091592Srgrimes		}
41071278Sjedgar	| STRU check_login SP struct_code CRLF
4111592Srgrimes		{
41271278Sjedgar			if ($2) {
41371278Sjedgar				switch ($4) {
4141592Srgrimes
41571278Sjedgar				case STRU_F:
41671278Sjedgar					reply(200, "STRU F ok.");
41771278Sjedgar					break;
4181592Srgrimes
41971278Sjedgar				default:
42071278Sjedgar					reply(504, "Unimplemented STRU type.");
42171278Sjedgar				}
4221592Srgrimes			}
4231592Srgrimes		}
42471278Sjedgar	| MODE check_login SP mode_code CRLF
4251592Srgrimes		{
42671278Sjedgar			if ($2) {
42771278Sjedgar				switch ($4) {
4281592Srgrimes
42971278Sjedgar				case MODE_S:
43071278Sjedgar					reply(200, "MODE S ok.");
43171278Sjedgar					break;
43271278Sjedgar
43371278Sjedgar				default:
43471278Sjedgar					reply(502, "Unimplemented MODE type.");
43571278Sjedgar				}
4361592Srgrimes			}
4371592Srgrimes		}
43871278Sjedgar	| ALLO check_login SP NUMBER CRLF
4391592Srgrimes		{
44071278Sjedgar			if ($2) {
44171278Sjedgar				reply(202, "ALLO command ignored.");
44271278Sjedgar			}
4431592Srgrimes		}
44471278Sjedgar	| ALLO check_login SP NUMBER SP R SP NUMBER CRLF
4451592Srgrimes		{
44671278Sjedgar			if ($2) {
44771278Sjedgar				reply(202, "ALLO command ignored.");
44871278Sjedgar			}
4491592Srgrimes		}
4501592Srgrimes	| RETR check_login SP pathname CRLF
4511592Srgrimes		{
45282796Ssheldonh			if (noretr || (guest && noguestretr))
45382460Snik				reply(500, "RETR command is disabled");
45482460Snik			else if ($2 && $4 != NULL)
4551592Srgrimes				retrieve((char *) 0, $4);
45682460Snik
4571592Srgrimes			if ($4 != NULL)
4581592Srgrimes				free($4);
4591592Srgrimes		}
46070102Sphk	| STOR check_login_ro SP pathname CRLF
4611592Srgrimes		{
4621592Srgrimes			if ($2 && $4 != NULL)
4631592Srgrimes				store($4, "w", 0);
4641592Srgrimes			if ($4 != NULL)
4651592Srgrimes				free($4);
4661592Srgrimes		}
46770102Sphk	| APPE check_login_ro SP pathname CRLF
4681592Srgrimes		{
4691592Srgrimes			if ($2 && $4 != NULL)
4701592Srgrimes				store($4, "a", 0);
4711592Srgrimes			if ($4 != NULL)
4721592Srgrimes				free($4);
4731592Srgrimes		}
4741592Srgrimes	| NLST check_login CRLF
4751592Srgrimes		{
4761592Srgrimes			if ($2)
4771592Srgrimes				send_file_list(".");
4781592Srgrimes		}
479101395Syar	| NLST check_login SP pathstring CRLF
4801592Srgrimes		{
481101395Syar			if ($2)
4821592Srgrimes				send_file_list($4);
483101395Syar			free($4);
4841592Srgrimes		}
4851592Srgrimes	| LIST check_login CRLF
4861592Srgrimes		{
4871592Srgrimes			if ($2)
488109380Syar				retrieve(_PATH_LS " -lgA", "");
4891592Srgrimes		}
49075567Speter	| LIST check_login SP pathstring CRLF
4911592Srgrimes		{
492101395Syar			if ($2)
493109380Syar				retrieve(_PATH_LS " -lgA %s", $4);
494101395Syar			free($4);
4951592Srgrimes		}
4961592Srgrimes	| STAT check_login SP pathname CRLF
4971592Srgrimes		{
4981592Srgrimes			if ($2 && $4 != NULL)
4991592Srgrimes				statfilecmd($4);
5001592Srgrimes			if ($4 != NULL)
5011592Srgrimes				free($4);
5021592Srgrimes		}
50371278Sjedgar	| STAT check_login CRLF
5041592Srgrimes		{
50571278Sjedgar			if ($2) {
50671278Sjedgar				statcmd();
50771278Sjedgar			}
5081592Srgrimes		}
50970102Sphk	| DELE check_login_ro SP pathname CRLF
5101592Srgrimes		{
5111592Srgrimes			if ($2 && $4 != NULL)
5121592Srgrimes				delete($4);
5131592Srgrimes			if ($4 != NULL)
5141592Srgrimes				free($4);
5151592Srgrimes		}
51670102Sphk	| RNTO check_login_ro SP pathname CRLF
5171592Srgrimes		{
518101379Syar			if ($2 && $4 != NULL) {
51917433Spst				if (fromname) {
52017433Spst					renamecmd(fromname, $4);
52117433Spst					free(fromname);
52217433Spst					fromname = (char *) 0;
52317433Spst				} else {
52417433Spst					reply(503, "Bad sequence of commands.");
52517433Spst				}
5261592Srgrimes			}
527101379Syar			if ($4 != NULL)
528101379Syar				free($4);
5291592Srgrimes		}
53071278Sjedgar	| ABOR check_login CRLF
5311592Srgrimes		{
53271278Sjedgar			if ($2)
53371278Sjedgar				reply(225, "ABOR command successful.");
5341592Srgrimes		}
5351592Srgrimes	| CWD check_login CRLF
5361592Srgrimes		{
53769234Sdanny			if ($2) {
538110036Syar				cwd(homedir);
53969234Sdanny			}
5401592Srgrimes		}
5411592Srgrimes	| CWD check_login SP pathname CRLF
5421592Srgrimes		{
5431592Srgrimes			if ($2 && $4 != NULL)
5441592Srgrimes				cwd($4);
5451592Srgrimes			if ($4 != NULL)
5461592Srgrimes				free($4);
5471592Srgrimes		}
5481592Srgrimes	| HELP CRLF
5491592Srgrimes		{
5501592Srgrimes			help(cmdtab, (char *) 0);
5511592Srgrimes		}
5521592Srgrimes	| HELP SP STRING CRLF
5531592Srgrimes		{
5541592Srgrimes			char *cp = $3;
5551592Srgrimes
5561592Srgrimes			if (strncasecmp(cp, "SITE", 4) == 0) {
5571592Srgrimes				cp = $3 + 4;
5581592Srgrimes				if (*cp == ' ')
5591592Srgrimes					cp++;
5601592Srgrimes				if (*cp)
5611592Srgrimes					help(sitetab, cp);
5621592Srgrimes				else
5631592Srgrimes					help(sitetab, (char *) 0);
5641592Srgrimes			} else
5651592Srgrimes				help(cmdtab, $3);
56688935Sdwmalone			free($3);
5671592Srgrimes		}
5681592Srgrimes	| NOOP CRLF
5691592Srgrimes		{
5701592Srgrimes			reply(200, "NOOP command successful.");
5711592Srgrimes		}
57270102Sphk	| MKD check_login_ro SP pathname CRLF
5731592Srgrimes		{
5741592Srgrimes			if ($2 && $4 != NULL)
5751592Srgrimes				makedir($4);
5761592Srgrimes			if ($4 != NULL)
5771592Srgrimes				free($4);
5781592Srgrimes		}
57970102Sphk	| RMD check_login_ro SP pathname CRLF
5801592Srgrimes		{
5811592Srgrimes			if ($2 && $4 != NULL)
5821592Srgrimes				removedir($4);
5831592Srgrimes			if ($4 != NULL)
5841592Srgrimes				free($4);
5851592Srgrimes		}
5861592Srgrimes	| PWD check_login CRLF
5871592Srgrimes		{
5881592Srgrimes			if ($2)
5891592Srgrimes				pwd();
5901592Srgrimes		}
5911592Srgrimes	| CDUP check_login CRLF
5921592Srgrimes		{
5931592Srgrimes			if ($2)
5941592Srgrimes				cwd("..");
5951592Srgrimes		}
5961592Srgrimes	| SITE SP HELP CRLF
5971592Srgrimes		{
5981592Srgrimes			help(sitetab, (char *) 0);
5991592Srgrimes		}
6001592Srgrimes	| SITE SP HELP SP STRING CRLF
6011592Srgrimes		{
6021592Srgrimes			help(sitetab, $5);
60388935Sdwmalone			free($5);
6041592Srgrimes		}
60575535Sphk	| SITE SP MDFIVE check_login SP pathname CRLF
60675535Sphk		{
60775535Sphk			char p[64], *q;
60875535Sphk
609101379Syar			if ($4 && $6) {
61075535Sphk				q = MD5File($6, p);
61175535Sphk				if (q != NULL)
61275535Sphk					reply(200, "MD5(%s) = %s", $6, p);
61375535Sphk				else
61475535Sphk					perror_reply(550, $6);
61575535Sphk			}
61688935Sdwmalone			if ($6)
61788935Sdwmalone				free($6);
61875535Sphk		}
6191592Srgrimes	| SITE SP UMASK check_login CRLF
6201592Srgrimes		{
6211592Srgrimes			int oldmask;
6221592Srgrimes
6231592Srgrimes			if ($4) {
6241592Srgrimes				oldmask = umask(0);
6251592Srgrimes				(void) umask(oldmask);
6261592Srgrimes				reply(200, "Current UMASK is %03o", oldmask);
6271592Srgrimes			}
6281592Srgrimes		}
6291592Srgrimes	| SITE SP UMASK check_login SP octal_number CRLF
6301592Srgrimes		{
6311592Srgrimes			int oldmask;
6321592Srgrimes
6331592Srgrimes			if ($4) {
6341592Srgrimes				if (($6 == -1) || ($6 > 0777)) {
6351592Srgrimes					reply(501, "Bad UMASK value");
6361592Srgrimes				} else {
6371592Srgrimes					oldmask = umask($6);
6381592Srgrimes					reply(200,
6391592Srgrimes					    "UMASK set to %03o (was %03o)",
6401592Srgrimes					    $6, oldmask);
6411592Srgrimes				}
6421592Srgrimes			}
6431592Srgrimes		}
64470102Sphk	| SITE SP CHMOD check_login_ro SP octal_number SP pathname CRLF
6451592Srgrimes		{
6461592Srgrimes			if ($4 && ($8 != NULL)) {
647101378Syar				if (($6 == -1 ) || ($6 > 0777))
648101378Syar					reply(501, "Bad mode value");
6491592Srgrimes				else if (chmod($8, $6) < 0)
6501592Srgrimes					perror_reply(550, $8);
6511592Srgrimes				else
6521592Srgrimes					reply(200, "CHMOD command successful.");
6531592Srgrimes			}
6541592Srgrimes			if ($8 != NULL)
6551592Srgrimes				free($8);
6561592Srgrimes		}
65771278Sjedgar	| SITE SP check_login IDLE CRLF
6581592Srgrimes		{
65971278Sjedgar			if ($3)
66071278Sjedgar				reply(200,
66171278Sjedgar			    	    "Current IDLE time limit is %d seconds; max %d",
66271278Sjedgar				    timeout, maxtimeout);
6631592Srgrimes		}
66471278Sjedgar	| SITE SP check_login IDLE SP NUMBER CRLF
6651592Srgrimes		{
66671278Sjedgar			if ($3) {
66792272Smaxim				if ($6.i < 30 || $6.i > maxtimeout) {
66871278Sjedgar					reply(501,
66971278Sjedgar					    "Maximum IDLE time must be between 30 and %d seconds",
67071278Sjedgar					    maxtimeout);
67171278Sjedgar				} else {
67292272Smaxim					timeout = $6.i;
67371278Sjedgar					(void) alarm((unsigned) timeout);
67471278Sjedgar					reply(200,
67571278Sjedgar					    "Maximum IDLE time set to %d seconds",
67671278Sjedgar					    timeout);
67771278Sjedgar				}
6781592Srgrimes			}
6791592Srgrimes		}
68070102Sphk	| STOU check_login_ro SP pathname CRLF
6811592Srgrimes		{
6821592Srgrimes			if ($2 && $4 != NULL)
6831592Srgrimes				store($4, "w", 1);
6841592Srgrimes			if ($4 != NULL)
6851592Srgrimes				free($4);
6861592Srgrimes		}
68771278Sjedgar	| SYST check_login CRLF
6881592Srgrimes		{
689116439Syar			if ($2) {
690116439Syar				if (hostinfo)
6911592Srgrimes#ifdef BSD
692116439Syar					reply(215, "UNIX Type: L%d Version: BSD-%d",
693116439Syar					      CHAR_BIT, BSD);
6941592Srgrimes#else /* BSD */
695116439Syar					reply(215, "UNIX Type: L%d", CHAR_BIT);
6961592Srgrimes#endif /* BSD */
697116439Syar				else
698116439Syar					reply(215, "UNKNOWN Type: L%d", CHAR_BIT);
699116439Syar			}
7001592Srgrimes		}
7011592Srgrimes
7021592Srgrimes		/*
7031592Srgrimes		 * SIZE is not in RFC959, but Postel has blessed it and
7041592Srgrimes		 * it will be in the updated RFC.
7051592Srgrimes		 *
7061592Srgrimes		 * Return size of file in a format suitable for
7071592Srgrimes		 * using with RESTART (we just count bytes).
7081592Srgrimes		 */
7091592Srgrimes	| SIZE check_login SP pathname CRLF
7101592Srgrimes		{
7111592Srgrimes			if ($2 && $4 != NULL)
7121592Srgrimes				sizecmd($4);
7131592Srgrimes			if ($4 != NULL)
7141592Srgrimes				free($4);
7151592Srgrimes		}
7161592Srgrimes
7171592Srgrimes		/*
7181592Srgrimes		 * MDTM is not in RFC959, but Postel has blessed it and
7191592Srgrimes		 * it will be in the updated RFC.
7201592Srgrimes		 *
7211592Srgrimes		 * Return modification time of file as an ISO 3307
7221592Srgrimes		 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
7231592Srgrimes		 * where xxx is the fractional second (of any precision,
7241592Srgrimes		 * not necessarily 3 digits)
7251592Srgrimes		 */
7261592Srgrimes	| MDTM check_login SP pathname CRLF
7271592Srgrimes		{
7281592Srgrimes			if ($2 && $4 != NULL) {
7291592Srgrimes				struct stat stbuf;
7301592Srgrimes				if (stat($4, &stbuf) < 0)
7311592Srgrimes					reply(550, "%s: %s",
7321592Srgrimes					    $4, strerror(errno));
7331592Srgrimes				else if (!S_ISREG(stbuf.st_mode)) {
7341592Srgrimes					reply(550, "%s: not a plain file.", $4);
7351592Srgrimes				} else {
7361592Srgrimes					struct tm *t;
7371592Srgrimes					t = gmtime(&stbuf.st_mtime);
7381592Srgrimes					reply(213,
73917435Spst					    "%04d%02d%02d%02d%02d%02d",
74017435Spst					    1900 + t->tm_year,
74117435Spst					    t->tm_mon+1, t->tm_mday,
7421592Srgrimes					    t->tm_hour, t->tm_min, t->tm_sec);
7431592Srgrimes				}
7441592Srgrimes			}
7451592Srgrimes			if ($4 != NULL)
7461592Srgrimes				free($4);
7471592Srgrimes		}
7481592Srgrimes	| QUIT CRLF
7491592Srgrimes		{
7501592Srgrimes			reply(221, "Goodbye.");
7511592Srgrimes			dologout(0);
7521592Srgrimes		}
753102565Syar	| NOTIMPL
754102565Syar		{
755102565Syar			nack($1);
756102565Syar		}
75789935Syar	| error
7581592Srgrimes		{
75989935Syar			yyclearin;		/* discard lookahead data */
76089935Syar			yyerrok;		/* clear error condition */
761102565Syar			state = CMD;		/* reset lexer state */
7621592Srgrimes		}
7631592Srgrimes	;
7641592Srgrimesrcmd
76570102Sphk	: RNFR check_login_ro SP pathname CRLF
7661592Srgrimes		{
7671592Srgrimes			restart_point = (off_t) 0;
7681592Srgrimes			if ($2 && $4) {
76988935Sdwmalone				if (fromname)
77088935Sdwmalone					free(fromname);
77188935Sdwmalone				fromname = (char *) 0;
77288935Sdwmalone				if (renamefrom($4))
77388935Sdwmalone					fromname = $4;
77488935Sdwmalone				else
7751592Srgrimes					free($4);
77688935Sdwmalone			} else if ($4) {
77788935Sdwmalone				free($4);
7781592Srgrimes			}
7791592Srgrimes		}
78092272Smaxim	| REST check_login SP NUMBER CRLF
7811592Srgrimes		{
78271278Sjedgar			if ($2) {
78388935Sdwmalone				if (fromname)
78488935Sdwmalone					free(fromname);
78571278Sjedgar				fromname = (char *) 0;
78692272Smaxim				restart_point = $4.o;
78792272Smaxim				reply(350, "Restarting at %llu. %s",
78871278Sjedgar				    restart_point,
78971278Sjedgar				    "Send STORE or RETRIEVE to initiate transfer.");
79071278Sjedgar			}
7911592Srgrimes		}
7921592Srgrimes	;
7931592Srgrimes
7941592Srgrimesusername
7951592Srgrimes	: STRING
7961592Srgrimes	;
7971592Srgrimes
7981592Srgrimespassword
7991592Srgrimes	: /* empty */
8001592Srgrimes		{
8011592Srgrimes			$$ = (char *)calloc(1, sizeof(char));
8021592Srgrimes		}
8031592Srgrimes	| STRING
8041592Srgrimes	;
8051592Srgrimes
8061592Srgrimesbyte_size
8071592Srgrimes	: NUMBER
80892272Smaxim		{
80992272Smaxim			$$ = $1.i;
81092272Smaxim		}
8111592Srgrimes	;
8121592Srgrimes
8131592Srgrimeshost_port
8141592Srgrimes	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
8151592Srgrimes		NUMBER COMMA NUMBER
8161592Srgrimes		{
8171592Srgrimes			char *a, *p;
8181592Srgrimes
81956668Sshin			data_dest.su_len = sizeof(struct sockaddr_in);
82056668Sshin			data_dest.su_family = AF_INET;
82156668Sshin			p = (char *)&data_dest.su_sin.sin_port;
82292272Smaxim			p[0] = $9.i; p[1] = $11.i;
82356668Sshin			a = (char *)&data_dest.su_sin.sin_addr;
82492272Smaxim			a[0] = $1.i; a[1] = $3.i; a[2] = $5.i; a[3] = $7.i;
8251592Srgrimes		}
8261592Srgrimes	;
8271592Srgrimes
82856668Sshinhost_long_port
82956668Sshin	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
83056668Sshin		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
83156668Sshin		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
83256668Sshin		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
83356668Sshin		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
83456668Sshin		NUMBER
83556668Sshin		{
83656668Sshin			char *a, *p;
83756668Sshin
83856668Sshin			memset(&data_dest, 0, sizeof(data_dest));
83956668Sshin			data_dest.su_len = sizeof(struct sockaddr_in6);
84056668Sshin			data_dest.su_family = AF_INET6;
84156668Sshin			p = (char *)&data_dest.su_port;
84292272Smaxim			p[0] = $39.i; p[1] = $41.i;
84356668Sshin			a = (char *)&data_dest.su_sin6.sin6_addr;
84492272Smaxim			a[0] = $5.i; a[1] = $7.i; a[2] = $9.i; a[3] = $11.i;
84592272Smaxim			a[4] = $13.i; a[5] = $15.i; a[6] = $17.i; a[7] = $19.i;
84692272Smaxim			a[8] = $21.i; a[9] = $23.i; a[10] = $25.i; a[11] = $27.i;
84792272Smaxim			a[12] = $29.i; a[13] = $31.i; a[14] = $33.i; a[15] = $35.i;
84856668Sshin			if (his_addr.su_family == AF_INET6) {
84956668Sshin				/* XXX more sanity checks! */
85056668Sshin				data_dest.su_sin6.sin6_scope_id =
85156668Sshin					his_addr.su_sin6.sin6_scope_id;
85256668Sshin			}
85392272Smaxim			if ($1.i != 6 || $3.i != 16 || $37.i != 2)
85456668Sshin				memset(&data_dest, 0, sizeof(data_dest));
85556668Sshin		}
85656668Sshin	| NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
85756668Sshin		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
85856668Sshin		NUMBER
85956668Sshin		{
86056668Sshin			char *a, *p;
86156668Sshin
86256668Sshin			memset(&data_dest, 0, sizeof(data_dest));
86356668Sshin			data_dest.su_sin.sin_len = sizeof(struct sockaddr_in);
86456668Sshin			data_dest.su_family = AF_INET;
86556668Sshin			p = (char *)&data_dest.su_port;
86692272Smaxim			p[0] = $15.i; p[1] = $17.i;
86756668Sshin			a = (char *)&data_dest.su_sin.sin_addr;
86892272Smaxim			a[0] =  $5.i; a[1] = $7.i; a[2] = $9.i; a[3] = $11.i;
86992272Smaxim			if ($1.i != 4 || $3.i != 4 || $13.i != 2)
87056668Sshin				memset(&data_dest, 0, sizeof(data_dest));
87156668Sshin		}
87256668Sshin	;
87356668Sshin
8741592Srgrimesform_code
8751592Srgrimes	: N
8761592Srgrimes		{
8771592Srgrimes			$$ = FORM_N;
8781592Srgrimes		}
8791592Srgrimes	| T
8801592Srgrimes		{
8811592Srgrimes			$$ = FORM_T;
8821592Srgrimes		}
8831592Srgrimes	| C
8841592Srgrimes		{
8851592Srgrimes			$$ = FORM_C;
8861592Srgrimes		}
8871592Srgrimes	;
8881592Srgrimes
8891592Srgrimestype_code
8901592Srgrimes	: A
8911592Srgrimes		{
8921592Srgrimes			cmd_type = TYPE_A;
8931592Srgrimes			cmd_form = FORM_N;
8941592Srgrimes		}
8951592Srgrimes	| A SP form_code
8961592Srgrimes		{
8971592Srgrimes			cmd_type = TYPE_A;
8981592Srgrimes			cmd_form = $3;
8991592Srgrimes		}
9001592Srgrimes	| E
9011592Srgrimes		{
9021592Srgrimes			cmd_type = TYPE_E;
9031592Srgrimes			cmd_form = FORM_N;
9041592Srgrimes		}
9051592Srgrimes	| E SP form_code
9061592Srgrimes		{
9071592Srgrimes			cmd_type = TYPE_E;
9081592Srgrimes			cmd_form = $3;
9091592Srgrimes		}
9101592Srgrimes	| I
9111592Srgrimes		{
9121592Srgrimes			cmd_type = TYPE_I;
9131592Srgrimes		}
9141592Srgrimes	| L
9151592Srgrimes		{
9161592Srgrimes			cmd_type = TYPE_L;
917103949Smike			cmd_bytesz = CHAR_BIT;
9181592Srgrimes		}
9191592Srgrimes	| L SP byte_size
9201592Srgrimes		{
9211592Srgrimes			cmd_type = TYPE_L;
9221592Srgrimes			cmd_bytesz = $3;
9231592Srgrimes		}
9241592Srgrimes		/* this is for a bug in the BBN ftp */
9251592Srgrimes	| L byte_size
9261592Srgrimes		{
9271592Srgrimes			cmd_type = TYPE_L;
9281592Srgrimes			cmd_bytesz = $2;
9291592Srgrimes		}
9301592Srgrimes	;
9311592Srgrimes
9321592Srgrimesstruct_code
9331592Srgrimes	: F
9341592Srgrimes		{
9351592Srgrimes			$$ = STRU_F;
9361592Srgrimes		}
9371592Srgrimes	| R
9381592Srgrimes		{
9391592Srgrimes			$$ = STRU_R;
9401592Srgrimes		}
9411592Srgrimes	| P
9421592Srgrimes		{
9431592Srgrimes			$$ = STRU_P;
9441592Srgrimes		}
9451592Srgrimes	;
9461592Srgrimes
9471592Srgrimesmode_code
9481592Srgrimes	: S
9491592Srgrimes		{
9501592Srgrimes			$$ = MODE_S;
9511592Srgrimes		}
9521592Srgrimes	| B
9531592Srgrimes		{
9541592Srgrimes			$$ = MODE_B;
9551592Srgrimes		}
9561592Srgrimes	| C
9571592Srgrimes		{
9581592Srgrimes			$$ = MODE_C;
9591592Srgrimes		}
9601592Srgrimes	;
9611592Srgrimes
9621592Srgrimespathname
9631592Srgrimes	: pathstring
9641592Srgrimes		{
96575567Speter			if (logged_in && $1) {
966110340Syar				char *p;
9671592Srgrimes
968110340Syar				/*
969110340Syar				 * Expand ~user manually since glob(3)
970110340Syar				 * will return the unexpanded pathname
971110340Syar				 * if the corresponding file/directory
972110340Syar				 * doesn't exist yet.  Using sole glob(3)
973110340Syar				 * would break natural commands like
974110340Syar				 * MKD ~user/newdir
975110340Syar				 * or
976110340Syar				 * RNTO ~/newfile
977110340Syar				 */
978110340Syar				if ((p = exptilde($1)) != NULL) {
979110340Syar					$$ = expglob(p);
980110340Syar					free(p);
981110340Syar				} else
9821592Srgrimes					$$ = NULL;
9831592Srgrimes				free($1);
9841592Srgrimes			} else
9851592Srgrimes				$$ = $1;
9861592Srgrimes		}
9871592Srgrimes	;
9881592Srgrimes
9891592Srgrimespathstring
9901592Srgrimes	: STRING
9911592Srgrimes	;
9921592Srgrimes
9931592Srgrimesoctal_number
9941592Srgrimes	: NUMBER
9951592Srgrimes		{
9961592Srgrimes			int ret, dec, multby, digit;
9971592Srgrimes
9981592Srgrimes			/*
9991592Srgrimes			 * Convert a number that was read as decimal number
10001592Srgrimes			 * to what it would be if it had been read as octal.
10011592Srgrimes			 */
100292272Smaxim			dec = $1.i;
10031592Srgrimes			multby = 1;
10041592Srgrimes			ret = 0;
10051592Srgrimes			while (dec) {
10061592Srgrimes				digit = dec%10;
10071592Srgrimes				if (digit > 7) {
10081592Srgrimes					ret = -1;
10091592Srgrimes					break;
10101592Srgrimes				}
10111592Srgrimes				ret += digit * multby;
10121592Srgrimes				multby *= 8;
10131592Srgrimes				dec /= 10;
10141592Srgrimes			}
10151592Srgrimes			$$ = ret;
10161592Srgrimes		}
10171592Srgrimes	;
10181592Srgrimes
10191592Srgrimes
10201592Srgrimescheck_login
10211592Srgrimes	: /* empty */
10221592Srgrimes		{
102370102Sphk		$$ = check_login1();
10241592Srgrimes		}
10251592Srgrimes	;
10261592Srgrimes
102770102Sphkcheck_login_epsv
102870102Sphk	: /* empty */
102970102Sphk		{
103070102Sphk		if (noepsv) {
103170102Sphk			reply(500, "EPSV command disabled");
103270102Sphk			$$ = 0;
103370102Sphk		}
103470102Sphk		else
103570102Sphk			$$ = check_login1();
103670102Sphk		}
103770102Sphk	;
103870102Sphk
103970102Sphkcheck_login_ro
104070102Sphk	: /* empty */
104170102Sphk		{
104270102Sphk		if (readonly) {
104372710Sdes			reply(550, "Permission denied.");
104470102Sphk			$$ = 0;
104570102Sphk		}
104670102Sphk		else
104770102Sphk			$$ = check_login1();
104870102Sphk		}
104970102Sphk	;
105070102Sphk
10511592Srgrimes%%
10521592Srgrimes
10531592Srgrimes#define	CMD	0	/* beginning of command */
10541592Srgrimes#define	ARGS	1	/* expect miscellaneous arguments */
10551592Srgrimes#define	STR1	2	/* expect SP followed by STRING */
10561592Srgrimes#define	STR2	3	/* expect STRING */
10571592Srgrimes#define	OSTR	4	/* optional SP then STRING */
105875556Sgreen#define	ZSTR1	5	/* optional SP then optional STRING */
10591592Srgrimes#define	ZSTR2	6	/* optional STRING after SP */
10601592Srgrimes#define	SITECMD	7	/* SITE command */
10611592Srgrimes#define	NSTR	8	/* Number followed by a string */
10621592Srgrimes
106375560Sjedgar#define	MAXGLOBARGS	1000
106475560Sjedgar
1065101034Syar#define	MAXASIZE	10240	/* Deny ASCII SIZE on files larger than that */
1066101034Syar
10671592Srgrimesstruct tab {
10681592Srgrimes	char	*name;
10691592Srgrimes	short	token;
10701592Srgrimes	short	state;
10711592Srgrimes	short	implemented;	/* 1 if command is implemented */
10721592Srgrimes	char	*help;
10731592Srgrimes};
10741592Srgrimes
10751592Srgrimesstruct tab cmdtab[] = {		/* In order defined in RFC 765 */
10761592Srgrimes	{ "USER", USER, STR1, 1,	"<sp> username" },
107775556Sgreen	{ "PASS", PASS, ZSTR1, 1,	"[<sp> [password]]" },
10781592Srgrimes	{ "ACCT", ACCT, STR1, 0,	"(specify account)" },
10791592Srgrimes	{ "SMNT", SMNT, ARGS, 0,	"(structure mount)" },
10801592Srgrimes	{ "REIN", REIN, ARGS, 0,	"(reinitialize server state)" },
10811592Srgrimes	{ "QUIT", QUIT, ARGS, 1,	"(terminate service)", },
1082101806Syar	{ "PORT", PORT, ARGS, 1,	"<sp> b0, b1, b2, b3, b4, b5" },
108356668Sshin	{ "LPRT", LPRT, ARGS, 1,	"<sp> af, hal, h1, h2, h3,..., pal, p1, p2..." },
108456668Sshin	{ "EPRT", EPRT, STR1, 1,	"<sp> |af|addr|port|" },
10851592Srgrimes	{ "PASV", PASV, ARGS, 1,	"(set server in passive mode)" },
108656668Sshin	{ "LPSV", LPSV, ARGS, 1,	"(set server in passive mode)" },
108756668Sshin	{ "EPSV", EPSV, ARGS, 1,	"[<sp> af|ALL]" },
1088101806Syar	{ "TYPE", TYPE, ARGS, 1,	"<sp> { A | E | I | L }" },
10891592Srgrimes	{ "STRU", STRU, ARGS, 1,	"(specify file structure)" },
10901592Srgrimes	{ "MODE", MODE, ARGS, 1,	"(specify transfer mode)" },
10911592Srgrimes	{ "RETR", RETR, STR1, 1,	"<sp> file-name" },
10921592Srgrimes	{ "STOR", STOR, STR1, 1,	"<sp> file-name" },
10931592Srgrimes	{ "APPE", APPE, STR1, 1,	"<sp> file-name" },
10941592Srgrimes	{ "MLFL", MLFL, OSTR, 0,	"(mail file)" },
10951592Srgrimes	{ "MAIL", MAIL, OSTR, 0,	"(mail to user)" },
10961592Srgrimes	{ "MSND", MSND, OSTR, 0,	"(mail send to terminal)" },
10971592Srgrimes	{ "MSOM", MSOM, OSTR, 0,	"(mail send to terminal or mailbox)" },
10981592Srgrimes	{ "MSAM", MSAM, OSTR, 0,	"(mail send to terminal and mailbox)" },
10991592Srgrimes	{ "MRSQ", MRSQ, OSTR, 0,	"(mail recipient scheme question)" },
11001592Srgrimes	{ "MRCP", MRCP, STR1, 0,	"(mail recipient)" },
11011592Srgrimes	{ "ALLO", ALLO, ARGS, 1,	"allocate storage (vacuously)" },
11021592Srgrimes	{ "REST", REST, ARGS, 1,	"<sp> offset (restart command)" },
11031592Srgrimes	{ "RNFR", RNFR, STR1, 1,	"<sp> file-name" },
11041592Srgrimes	{ "RNTO", RNTO, STR1, 1,	"<sp> file-name" },
11051592Srgrimes	{ "ABOR", ABOR, ARGS, 1,	"(abort operation)" },
11061592Srgrimes	{ "DELE", DELE, STR1, 1,	"<sp> file-name" },
11071592Srgrimes	{ "CWD",  CWD,  OSTR, 1,	"[ <sp> directory-name ]" },
11081592Srgrimes	{ "XCWD", CWD,	OSTR, 1,	"[ <sp> directory-name ]" },
11091592Srgrimes	{ "LIST", LIST, OSTR, 1,	"[ <sp> path-name ]" },
11101592Srgrimes	{ "NLST", NLST, OSTR, 1,	"[ <sp> path-name ]" },
11111592Srgrimes	{ "SITE", SITE, SITECMD, 1,	"site-cmd [ <sp> arguments ]" },
11121592Srgrimes	{ "SYST", SYST, ARGS, 1,	"(get type of operating system)" },
11131592Srgrimes	{ "STAT", STAT, OSTR, 1,	"[ <sp> path-name ]" },
11141592Srgrimes	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
11151592Srgrimes	{ "NOOP", NOOP, ARGS, 1,	"" },
11161592Srgrimes	{ "MKD",  MKD,  STR1, 1,	"<sp> path-name" },
11171592Srgrimes	{ "XMKD", MKD,  STR1, 1,	"<sp> path-name" },
11181592Srgrimes	{ "RMD",  RMD,  STR1, 1,	"<sp> path-name" },
11191592Srgrimes	{ "XRMD", RMD,  STR1, 1,	"<sp> path-name" },
11201592Srgrimes	{ "PWD",  PWD,  ARGS, 1,	"(return current directory)" },
11211592Srgrimes	{ "XPWD", PWD,  ARGS, 1,	"(return current directory)" },
11221592Srgrimes	{ "CDUP", CDUP, ARGS, 1,	"(change to parent directory)" },
11231592Srgrimes	{ "XCUP", CDUP, ARGS, 1,	"(change to parent directory)" },
11241592Srgrimes	{ "STOU", STOU, STR1, 1,	"<sp> file-name" },
11251592Srgrimes	{ "SIZE", SIZE, OSTR, 1,	"<sp> path-name" },
11261592Srgrimes	{ "MDTM", MDTM, OSTR, 1,	"<sp> path-name" },
11271592Srgrimes	{ NULL,   0,    0,    0,	0 }
11281592Srgrimes};
11291592Srgrimes
11301592Srgrimesstruct tab sitetab[] = {
113175535Sphk	{ "MD5", MDFIVE, STR1, 1,	"[ <sp> file-name ]" },
11321592Srgrimes	{ "UMASK", UMASK, ARGS, 1,	"[ <sp> umask ]" },
11331592Srgrimes	{ "IDLE", IDLE, ARGS, 1,	"[ <sp> maximum-idle-time ]" },
11341592Srgrimes	{ "CHMOD", CHMOD, NSTR, 1,	"<sp> mode <sp> file-name" },
11351592Srgrimes	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
11361592Srgrimes	{ NULL,   0,    0,    0,	0 }
11371592Srgrimes};
11381592Srgrimes
113990148Simpstatic char	*copy(char *);
1140110340Syarstatic char	*expglob(char *);
1141110340Syarstatic char	*exptilde(char *);
114290148Simpstatic void	 help(struct tab *, char *);
11431592Srgrimesstatic struct tab *
114490148Simp		 lookup(struct tab *, char *);
114590148Simpstatic int	 port_check(const char *);
114690148Simpstatic int	 port_check_v6(const char *);
114790148Simpstatic void	 sizecmd(char *);
114890148Simpstatic void	 toolong(int);
114990148Simpstatic void	 v4map_data_dest(void);
115090148Simpstatic int	 yylex(void);
11511592Srgrimes
11521592Srgrimesstatic struct tab *
115390148Simplookup(struct tab *p, char *cmd)
11541592Srgrimes{
11551592Srgrimes
11561592Srgrimes	for (; p->name != NULL; p++)
11571592Srgrimes		if (strcmp(cmd, p->name) == 0)
11581592Srgrimes			return (p);
11591592Srgrimes	return (0);
11601592Srgrimes}
11611592Srgrimes
11621592Srgrimes#include <arpa/telnet.h>
11631592Srgrimes
11641592Srgrimes/*
11651592Srgrimes * getline - a hacked up version of fgets to ignore TELNET escape codes.
11661592Srgrimes */
11671592Srgrimeschar *
116890148Simpgetline(char *s, int n, FILE *iop)
11691592Srgrimes{
11701592Srgrimes	int c;
11711592Srgrimes	register char *cs;
1172117352Syar	sigset_t sset, osset;
11731592Srgrimes
11741592Srgrimes	cs = s;
11751592Srgrimes/* tmpline may contain saved command from urgent mode interruption */
11761592Srgrimes	for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
11771592Srgrimes		*cs++ = tmpline[c];
11781592Srgrimes		if (tmpline[c] == '\n') {
11791592Srgrimes			*cs++ = '\0';
118076096Smarkm			if (ftpdebug)
11811592Srgrimes				syslog(LOG_DEBUG, "command: %s", s);
11821592Srgrimes			tmpline[0] = '\0';
11831592Srgrimes			return(s);
11841592Srgrimes		}
11851592Srgrimes		if (c == 0)
11861592Srgrimes			tmpline[0] = '\0';
11871592Srgrimes	}
1188117352Syar	/* SIGURG would interrupt stdio if not blocked during the read loop */
1189117352Syar	sigemptyset(&sset);
1190117352Syar	sigaddset(&sset, SIGURG);
1191117352Syar	sigprocmask(SIG_BLOCK, &sset, &osset);
11921592Srgrimes	while ((c = getc(iop)) != EOF) {
11931592Srgrimes		c &= 0377;
11941592Srgrimes		if (c == IAC) {
1195117351Syar			if ((c = getc(iop)) == EOF)
1196117351Syar				goto got_eof;
11971592Srgrimes			c &= 0377;
11981592Srgrimes			switch (c) {
11991592Srgrimes			case WILL:
12001592Srgrimes			case WONT:
1201117351Syar				if ((c = getc(iop)) == EOF)
1202117351Syar					goto got_eof;
12031592Srgrimes				printf("%c%c%c", IAC, DONT, 0377&c);
12041592Srgrimes				(void) fflush(stdout);
12051592Srgrimes				continue;
12061592Srgrimes			case DO:
12071592Srgrimes			case DONT:
1208117351Syar				if ((c = getc(iop)) == EOF)
1209117351Syar					goto got_eof;
12101592Srgrimes				printf("%c%c%c", IAC, WONT, 0377&c);
12111592Srgrimes				(void) fflush(stdout);
12121592Srgrimes				continue;
12131592Srgrimes			case IAC:
12141592Srgrimes				break;
12151592Srgrimes			default:
12161592Srgrimes				continue;	/* ignore command */
12171592Srgrimes			}
12181592Srgrimes		}
12191592Srgrimes		*cs++ = c;
12201592Srgrimes		if (--n <= 0 || c == '\n')
12211592Srgrimes			break;
12221592Srgrimes	}
1223117351Syargot_eof:
1224117352Syar	sigprocmask(SIG_SETMASK, &osset, NULL);
12251592Srgrimes	if (c == EOF && cs == s)
12261592Srgrimes		return (NULL);
12271592Srgrimes	*cs++ = '\0';
122876096Smarkm	if (ftpdebug) {
12291592Srgrimes		if (!guest && strncasecmp("pass ", s, 5) == 0) {
12301592Srgrimes			/* Don't syslog passwords */
12311592Srgrimes			syslog(LOG_DEBUG, "command: %.5s ???", s);
12321592Srgrimes		} else {
12331592Srgrimes			register char *cp;
12341592Srgrimes			register int len;
12351592Srgrimes
12361592Srgrimes			/* Don't syslog trailing CR-LF */
12371592Srgrimes			len = strlen(s);
12381592Srgrimes			cp = s + len - 1;
12391592Srgrimes			while (cp >= s && (*cp == '\n' || *cp == '\r')) {
12401592Srgrimes				--cp;
12411592Srgrimes				--len;
12421592Srgrimes			}
12431592Srgrimes			syslog(LOG_DEBUG, "command: %.*s", len, s);
12441592Srgrimes		}
12451592Srgrimes	}
12461592Srgrimes	return (s);
12471592Srgrimes}
12481592Srgrimes
12491592Srgrimesstatic void
125090148Simptoolong(int signo)
12511592Srgrimes{
12521592Srgrimes
12531592Srgrimes	reply(421,
12541592Srgrimes	    "Timeout (%d seconds): closing control connection.", timeout);
12551592Srgrimes	if (logging)
12561592Srgrimes		syslog(LOG_INFO, "User %s timed out after %d seconds",
12571592Srgrimes		    (pw ? pw -> pw_name : "unknown"), timeout);
12581592Srgrimes	dologout(1);
12591592Srgrimes}
12601592Srgrimes
12611592Srgrimesstatic int
126290148Simpyylex(void)
12631592Srgrimes{
126489935Syar	static int cpos;
12651592Srgrimes	char *cp, *cp2;
12661592Srgrimes	struct tab *p;
12671592Srgrimes	int n;
12681592Srgrimes	char c;
12691592Srgrimes
12701592Srgrimes	for (;;) {
12711592Srgrimes		switch (state) {
12721592Srgrimes
12731592Srgrimes		case CMD:
12741592Srgrimes			(void) signal(SIGALRM, toolong);
12751592Srgrimes			(void) alarm((unsigned) timeout);
12761592Srgrimes			if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
12771592Srgrimes				reply(221, "You could at least say goodbye.");
12781592Srgrimes				dologout(0);
12791592Srgrimes			}
12801592Srgrimes			(void) alarm(0);
12811592Srgrimes#ifdef SETPROCTITLE
128229574Sphk			if (strncasecmp(cbuf, "PASS", 4) != 0)
12831592Srgrimes				setproctitle("%s: %s", proctitle, cbuf);
12841592Srgrimes#endif /* SETPROCTITLE */
12851592Srgrimes			if ((cp = strchr(cbuf, '\r'))) {
12861592Srgrimes				*cp++ = '\n';
12871592Srgrimes				*cp = '\0';
12881592Srgrimes			}
12891592Srgrimes			if ((cp = strpbrk(cbuf, " \n")))
12901592Srgrimes				cpos = cp - cbuf;
12911592Srgrimes			if (cpos == 0)
12921592Srgrimes				cpos = 4;
12931592Srgrimes			c = cbuf[cpos];
12941592Srgrimes			cbuf[cpos] = '\0';
12951592Srgrimes			upper(cbuf);
12961592Srgrimes			p = lookup(cmdtab, cbuf);
12971592Srgrimes			cbuf[cpos] = c;
12983776Spst			if (p != 0) {
1299102565Syar				yylval.s = p->name;
1300102565Syar				if (!p->implemented)
1301102565Syar					return (NOTIMPL); /* state remains CMD */
13021592Srgrimes				state = p->state;
13031592Srgrimes				return (p->token);
13041592Srgrimes			}
13051592Srgrimes			break;
13061592Srgrimes
13071592Srgrimes		case SITECMD:
13081592Srgrimes			if (cbuf[cpos] == ' ') {
13091592Srgrimes				cpos++;
13101592Srgrimes				return (SP);
13111592Srgrimes			}
13121592Srgrimes			cp = &cbuf[cpos];
13131592Srgrimes			if ((cp2 = strpbrk(cp, " \n")))
13141592Srgrimes				cpos = cp2 - cbuf;
13151592Srgrimes			c = cbuf[cpos];
13161592Srgrimes			cbuf[cpos] = '\0';
13171592Srgrimes			upper(cp);
13181592Srgrimes			p = lookup(sitetab, cp);
13191592Srgrimes			cbuf[cpos] = c;
13203777Spst			if (guest == 0 && p != 0) {
1321102565Syar				yylval.s = p->name;
1322102565Syar				if (!p->implemented) {
13231592Srgrimes					state = CMD;
1324102565Syar					return (NOTIMPL);
13251592Srgrimes				}
13261592Srgrimes				state = p->state;
13271592Srgrimes				return (p->token);
13281592Srgrimes			}
13291592Srgrimes			state = CMD;
13301592Srgrimes			break;
13311592Srgrimes
133275556Sgreen		case ZSTR1:
13331592Srgrimes		case OSTR:
13341592Srgrimes			if (cbuf[cpos] == '\n') {
13351592Srgrimes				state = CMD;
13361592Srgrimes				return (CRLF);
13371592Srgrimes			}
13381592Srgrimes			/* FALLTHROUGH */
13391592Srgrimes
13401592Srgrimes		case STR1:
13411592Srgrimes		dostr1:
13421592Srgrimes			if (cbuf[cpos] == ' ') {
13431592Srgrimes				cpos++;
134451979Salfred				state = state == OSTR ? STR2 : state+1;
13451592Srgrimes				return (SP);
13461592Srgrimes			}
13471592Srgrimes			break;
13481592Srgrimes
13491592Srgrimes		case ZSTR2:
13501592Srgrimes			if (cbuf[cpos] == '\n') {
13511592Srgrimes				state = CMD;
13521592Srgrimes				return (CRLF);
13531592Srgrimes			}
13541592Srgrimes			/* FALLTHROUGH */
13551592Srgrimes
13561592Srgrimes		case STR2:
13571592Srgrimes			cp = &cbuf[cpos];
13581592Srgrimes			n = strlen(cp);
13591592Srgrimes			cpos += n - 1;
13601592Srgrimes			/*
13611592Srgrimes			 * Make sure the string is nonempty and \n terminated.
13621592Srgrimes			 */
13631592Srgrimes			if (n > 1 && cbuf[cpos] == '\n') {
13641592Srgrimes				cbuf[cpos] = '\0';
13651592Srgrimes				yylval.s = copy(cp);
13661592Srgrimes				cbuf[cpos] = '\n';
13671592Srgrimes				state = ARGS;
13681592Srgrimes				return (STRING);
13691592Srgrimes			}
13701592Srgrimes			break;
13711592Srgrimes
13721592Srgrimes		case NSTR:
13731592Srgrimes			if (cbuf[cpos] == ' ') {
13741592Srgrimes				cpos++;
13751592Srgrimes				return (SP);
13761592Srgrimes			}
13771592Srgrimes			if (isdigit(cbuf[cpos])) {
13781592Srgrimes				cp = &cbuf[cpos];
13791592Srgrimes				while (isdigit(cbuf[++cpos]))
13801592Srgrimes					;
13811592Srgrimes				c = cbuf[cpos];
13821592Srgrimes				cbuf[cpos] = '\0';
138392272Smaxim				yylval.u.i = atoi(cp);
13841592Srgrimes				cbuf[cpos] = c;
13851592Srgrimes				state = STR1;
13861592Srgrimes				return (NUMBER);
13871592Srgrimes			}
13881592Srgrimes			state = STR1;
13891592Srgrimes			goto dostr1;
13901592Srgrimes
13911592Srgrimes		case ARGS:
13921592Srgrimes			if (isdigit(cbuf[cpos])) {
13931592Srgrimes				cp = &cbuf[cpos];
13941592Srgrimes				while (isdigit(cbuf[++cpos]))
13951592Srgrimes					;
13961592Srgrimes				c = cbuf[cpos];
13971592Srgrimes				cbuf[cpos] = '\0';
139892272Smaxim				yylval.u.i = atoi(cp);
139992272Smaxim				yylval.u.o = strtoull(cp, (char **)NULL, 10);
14001592Srgrimes				cbuf[cpos] = c;
14011592Srgrimes				return (NUMBER);
14021592Srgrimes			}
140356668Sshin			if (strncasecmp(&cbuf[cpos], "ALL", 3) == 0
140456668Sshin			 && !isalnum(cbuf[cpos + 3])) {
140556668Sshin				cpos += 3;
140656668Sshin				return ALL;
140756668Sshin			}
14081592Srgrimes			switch (cbuf[cpos++]) {
14091592Srgrimes
14101592Srgrimes			case '\n':
14111592Srgrimes				state = CMD;
14121592Srgrimes				return (CRLF);
14131592Srgrimes
14141592Srgrimes			case ' ':
14151592Srgrimes				return (SP);
14161592Srgrimes
14171592Srgrimes			case ',':
14181592Srgrimes				return (COMMA);
14191592Srgrimes
14201592Srgrimes			case 'A':
14211592Srgrimes			case 'a':
14221592Srgrimes				return (A);
14231592Srgrimes
14241592Srgrimes			case 'B':
14251592Srgrimes			case 'b':
14261592Srgrimes				return (B);
14271592Srgrimes
14281592Srgrimes			case 'C':
14291592Srgrimes			case 'c':
14301592Srgrimes				return (C);
14311592Srgrimes
14321592Srgrimes			case 'E':
14331592Srgrimes			case 'e':
14341592Srgrimes				return (E);
14351592Srgrimes
14361592Srgrimes			case 'F':
14371592Srgrimes			case 'f':
14381592Srgrimes				return (F);
14391592Srgrimes
14401592Srgrimes			case 'I':
14411592Srgrimes			case 'i':
14421592Srgrimes				return (I);
14431592Srgrimes
14441592Srgrimes			case 'L':
14451592Srgrimes			case 'l':
14461592Srgrimes				return (L);
14471592Srgrimes
14481592Srgrimes			case 'N':
14491592Srgrimes			case 'n':
14501592Srgrimes				return (N);
14511592Srgrimes
14521592Srgrimes			case 'P':
14531592Srgrimes			case 'p':
14541592Srgrimes				return (P);
14551592Srgrimes
14561592Srgrimes			case 'R':
14571592Srgrimes			case 'r':
14581592Srgrimes				return (R);
14591592Srgrimes
14601592Srgrimes			case 'S':
14611592Srgrimes			case 's':
14621592Srgrimes				return (S);
14631592Srgrimes
14641592Srgrimes			case 'T':
14651592Srgrimes			case 't':
14661592Srgrimes				return (T);
14671592Srgrimes
14681592Srgrimes			}
14691592Srgrimes			break;
14701592Srgrimes
14711592Srgrimes		default:
147276096Smarkm			fatalerror("Unknown state in scanner.");
14731592Srgrimes		}
14741592Srgrimes		state = CMD;
147589935Syar		return (LEXERR);
14761592Srgrimes	}
14771592Srgrimes}
14781592Srgrimes
14791592Srgrimesvoid
148090148Simpupper(char *s)
14811592Srgrimes{
14821592Srgrimes	while (*s != '\0') {
14831592Srgrimes		if (islower(*s))
14841592Srgrimes			*s = toupper(*s);
14851592Srgrimes		s++;
14861592Srgrimes	}
14871592Srgrimes}
14881592Srgrimes
14891592Srgrimesstatic char *
149090148Simpcopy(char *s)
14911592Srgrimes{
14921592Srgrimes	char *p;
14931592Srgrimes
14941592Srgrimes	p = malloc((unsigned) strlen(s) + 1);
14951592Srgrimes	if (p == NULL)
149676096Smarkm		fatalerror("Ran out of memory.");
14971592Srgrimes	(void) strcpy(p, s);
14981592Srgrimes	return (p);
14991592Srgrimes}
15001592Srgrimes
15011592Srgrimesstatic void
150290148Simphelp(struct tab *ctab, char *s)
15031592Srgrimes{
15041592Srgrimes	struct tab *c;
15051592Srgrimes	int width, NCMDS;
15061592Srgrimes	char *type;
15071592Srgrimes
15081592Srgrimes	if (ctab == sitetab)
15091592Srgrimes		type = "SITE ";
15101592Srgrimes	else
15111592Srgrimes		type = "";
15121592Srgrimes	width = 0, NCMDS = 0;
15131592Srgrimes	for (c = ctab; c->name != NULL; c++) {
15141592Srgrimes		int len = strlen(c->name);
15151592Srgrimes
15161592Srgrimes		if (len > width)
15171592Srgrimes			width = len;
15181592Srgrimes		NCMDS++;
15191592Srgrimes	}
15201592Srgrimes	width = (width + 8) &~ 7;
15211592Srgrimes	if (s == 0) {
15221592Srgrimes		int i, j, w;
15231592Srgrimes		int columns, lines;
15241592Srgrimes
15251592Srgrimes		lreply(214, "The following %scommands are recognized %s.",
15261592Srgrimes		    type, "(* =>'s unimplemented)");
15271592Srgrimes		columns = 76 / width;
15281592Srgrimes		if (columns == 0)
15291592Srgrimes			columns = 1;
15301592Srgrimes		lines = (NCMDS + columns - 1) / columns;
15311592Srgrimes		for (i = 0; i < lines; i++) {
15321592Srgrimes			printf("   ");
15331592Srgrimes			for (j = 0; j < columns; j++) {
15341592Srgrimes				c = ctab + j * lines + i;
15351592Srgrimes				printf("%s%c", c->name,
15361592Srgrimes					c->implemented ? ' ' : '*');
15371592Srgrimes				if (c + lines >= &ctab[NCMDS])
15381592Srgrimes					break;
15391592Srgrimes				w = strlen(c->name) + 1;
15401592Srgrimes				while (w < width) {
15411592Srgrimes					putchar(' ');
15421592Srgrimes					w++;
15431592Srgrimes				}
15441592Srgrimes			}
15451592Srgrimes			printf("\r\n");
15461592Srgrimes		}
15471592Srgrimes		(void) fflush(stdout);
1548110037Syar		if (hostinfo)
1549110037Syar			reply(214, "Direct comments to ftp-bugs@%s.", hostname);
1550110037Syar		else
1551110037Syar			reply(214, "End.");
15521592Srgrimes		return;
15531592Srgrimes	}
15541592Srgrimes	upper(s);
15551592Srgrimes	c = lookup(ctab, s);
15561592Srgrimes	if (c == (struct tab *)0) {
15571592Srgrimes		reply(502, "Unknown command %s.", s);
15581592Srgrimes		return;
15591592Srgrimes	}
15601592Srgrimes	if (c->implemented)
15611592Srgrimes		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
15621592Srgrimes	else
15631592Srgrimes		reply(214, "%s%-*s\t%s; unimplemented.", type, width,
15641592Srgrimes		    c->name, c->help);
15651592Srgrimes}
15661592Srgrimes
15671592Srgrimesstatic void
156890148Simpsizecmd(char *filename)
15691592Srgrimes{
15701592Srgrimes	switch (type) {
15711592Srgrimes	case TYPE_L:
15721592Srgrimes	case TYPE_I: {
15731592Srgrimes		struct stat stbuf;
157463350Sdes		if (stat(filename, &stbuf) < 0)
157563350Sdes			perror_reply(550, filename);
157663350Sdes		else if (!S_ISREG(stbuf.st_mode))
15771592Srgrimes			reply(550, "%s: not a plain file.", filename);
15781592Srgrimes		else
15791592Srgrimes			reply(213, "%qu", stbuf.st_size);
15801592Srgrimes		break; }
15811592Srgrimes	case TYPE_A: {
15821592Srgrimes		FILE *fin;
15831592Srgrimes		int c;
15841592Srgrimes		off_t count;
15851592Srgrimes		struct stat stbuf;
15861592Srgrimes		fin = fopen(filename, "r");
15871592Srgrimes		if (fin == NULL) {
15881592Srgrimes			perror_reply(550, filename);
15891592Srgrimes			return;
15901592Srgrimes		}
159163350Sdes		if (fstat(fileno(fin), &stbuf) < 0) {
159263350Sdes			perror_reply(550, filename);
159363350Sdes			(void) fclose(fin);
159463350Sdes			return;
159563350Sdes		} else if (!S_ISREG(stbuf.st_mode)) {
15961592Srgrimes			reply(550, "%s: not a plain file.", filename);
15971592Srgrimes			(void) fclose(fin);
15981592Srgrimes			return;
1599101034Syar		} else if (stbuf.st_size > MAXASIZE) {
1600101034Syar			reply(550, "%s: too large for type A SIZE.", filename);
1601101034Syar			(void) fclose(fin);
1602101034Syar			return;
16031592Srgrimes		}
16041592Srgrimes
16051592Srgrimes		count = 0;
16061592Srgrimes		while((c=getc(fin)) != EOF) {
16071592Srgrimes			if (c == '\n')	/* will get expanded to \r\n */
16081592Srgrimes				count++;
16091592Srgrimes			count++;
16101592Srgrimes		}
16111592Srgrimes		(void) fclose(fin);
16121592Srgrimes
16131592Srgrimes		reply(213, "%qd", count);
16141592Srgrimes		break; }
16151592Srgrimes	default:
1616100684Syar		reply(504, "SIZE not implemented for type %s.",
1617100684Syar		           typenames[type]);
16181592Srgrimes	}
16191592Srgrimes}
162056668Sshin
162156668Sshin/* Return 1, if port check is done. Return 0, if not yet. */
162256668Sshinstatic int
162390148Simpport_check(const char *pcmd)
162456668Sshin{
162556668Sshin	if (his_addr.su_family == AF_INET) {
162656668Sshin		if (data_dest.su_family != AF_INET) {
162756668Sshin			usedefault = 1;
162856668Sshin			reply(500, "Invalid address rejected.");
162956668Sshin			return 1;
163056668Sshin		}
163156668Sshin		if (paranoid &&
163256668Sshin		    ((ntohs(data_dest.su_port) < IPPORT_RESERVED) ||
163356668Sshin		     memcmp(&data_dest.su_sin.sin_addr,
163456668Sshin			    &his_addr.su_sin.sin_addr,
163556668Sshin			    sizeof(data_dest.su_sin.sin_addr)))) {
163656668Sshin			usedefault = 1;
163756668Sshin			reply(500, "Illegal PORT range rejected.");
163856668Sshin		} else {
163956668Sshin			usedefault = 0;
164056668Sshin			if (pdata >= 0) {
164156668Sshin				(void) close(pdata);
164256668Sshin				pdata = -1;
164356668Sshin			}
164456668Sshin			reply(200, "%s command successful.", pcmd);
164556668Sshin		}
164656668Sshin		return 1;
164756668Sshin	}
164856668Sshin	return 0;
164956668Sshin}
165056668Sshin
165170102Sphkstatic int
165290148Simpcheck_login1(void)
165370102Sphk{
165470102Sphk	if (logged_in)
165570102Sphk		return 1;
165670102Sphk	else {
165770102Sphk		reply(530, "Please login with USER and PASS.");
165870102Sphk		return 0;
165970102Sphk	}
166070102Sphk}
166170102Sphk
1662110340Syar/*
1663110340Syar * Replace leading "~user" in a pathname by the user's login directory.
1664110340Syar * Returned string will be in a freshly malloced buffer unless it's NULL.
1665110340Syar */
1666110340Syarstatic char *
1667110340Syarexptilde(char *s)
1668110340Syar{
1669110340Syar	char *p, *q;
1670110340Syar	char *path, *user;
1671110340Syar	struct passwd *ppw;
1672110340Syar
1673110340Syar	if ((p = strdup(s)) == NULL)
1674110340Syar		return (NULL);
1675110340Syar	if (*p != '~')
1676110340Syar		return (p);
1677110340Syar
1678110340Syar	user = p + 1;	/* skip tilde */
1679110340Syar	if ((path = strchr(p, '/')) != NULL)
1680110340Syar		*(path++) = '\0'; /* separate ~user from the rest of path */
1681110378Syar	if (*user == '\0') /* no user specified, use the current user */
1682110378Syar		user = pw->pw_name;
1683110378Syar	/* read passwd even for the current user since we may be chrooted */
1684110378Syar	if ((ppw = getpwnam(user)) != NULL) {
1685110340Syar		/* user found, substitute login directory for ~user */
1686110340Syar		if (path)
1687110340Syar			asprintf(&q, "%s/%s", ppw->pw_dir, path);
1688110340Syar		else
1689110340Syar			q = strdup(ppw->pw_dir);
1690110340Syar		free(p);
1691110340Syar		p = q;
1692110340Syar	} else {
1693110340Syar		/* user not found, undo the damage */
1694110340Syar		if (path)
1695110340Syar			path[-1] = '/';
1696110340Syar	}
1697110340Syar	return (p);
1698110340Syar}
1699110340Syar
1700110340Syar/*
1701110340Syar * Expand glob(3) patterns possibly present in a pathname.
1702110340Syar * Avoid expanding to a pathname including '\r' or '\n' in order to
1703110340Syar * not disrupt the FTP protocol.
1704110340Syar * The expansion found must be unique.
1705110340Syar * Return the result as a malloced string, or NULL if an error occured.
1706110340Syar *
1707110340Syar * Problem: this production is used for all pathname
1708110340Syar * processing, but only gives a 550 error reply.
1709110340Syar * This is a valid reply in some cases but not in others.
1710110340Syar */
1711110340Syarstatic char *
1712110340Syarexpglob(char *s)
1713110340Syar{
1714110340Syar	char *p, **pp, *rval;
1715110340Syar	int flags = GLOB_BRACE | GLOB_NOCHECK;
1716110340Syar	int n;
1717110340Syar	glob_t gl;
1718110340Syar
1719110340Syar	memset(&gl, 0, sizeof(gl));
1720110340Syar	flags |= GLOB_LIMIT;
1721110340Syar	gl.gl_matchc = MAXGLOBARGS;
1722110340Syar	if (glob(s, flags, NULL, &gl) == 0 && gl.gl_pathc != 0) {
1723110340Syar		for (pp = gl.gl_pathv, p = NULL, n = 0; *pp; pp++)
1724110340Syar			if (*(*pp + strcspn(*pp, "\r\n")) == '\0') {
1725110340Syar				p = *pp;
1726110340Syar				n++;
1727110340Syar			}
1728110340Syar		if (n == 0)
1729110340Syar			rval = strdup(s);
1730110340Syar		else if (n == 1)
1731110340Syar			rval = strdup(p);
1732110340Syar		else {
1733110340Syar			reply(550, "ambiguous");
1734110340Syar			rval = NULL;
1735110340Syar		}
1736110340Syar	} else {
1737110340Syar		reply(550, "wildcard expansion error");
1738110340Syar		rval = NULL;
1739110340Syar	}
1740110340Syar	globfree(&gl);
1741110340Syar	return (rval);
1742110340Syar}
1743110340Syar
174456668Sshin#ifdef INET6
174556668Sshin/* Return 1, if port check is done. Return 0, if not yet. */
174656668Sshinstatic int
174790148Simpport_check_v6(const char *pcmd)
174856668Sshin{
174956668Sshin	if (his_addr.su_family == AF_INET6) {
175056668Sshin		if (IN6_IS_ADDR_V4MAPPED(&his_addr.su_sin6.sin6_addr))
175156668Sshin			/* Convert data_dest into v4 mapped sockaddr.*/
175256668Sshin			v4map_data_dest();
175356668Sshin		if (data_dest.su_family != AF_INET6) {
175456668Sshin			usedefault = 1;
175556668Sshin			reply(500, "Invalid address rejected.");
175656668Sshin			return 1;
175756668Sshin		}
175856668Sshin		if (paranoid &&
175956668Sshin		    ((ntohs(data_dest.su_port) < IPPORT_RESERVED) ||
176056668Sshin		     memcmp(&data_dest.su_sin6.sin6_addr,
176156668Sshin			    &his_addr.su_sin6.sin6_addr,
176256668Sshin			    sizeof(data_dest.su_sin6.sin6_addr)))) {
176356668Sshin			usedefault = 1;
176456668Sshin			reply(500, "Illegal PORT range rejected.");
176556668Sshin		} else {
176656668Sshin			usedefault = 0;
176756668Sshin			if (pdata >= 0) {
176856668Sshin				(void) close(pdata);
176956668Sshin				pdata = -1;
177056668Sshin			}
177156668Sshin			reply(200, "%s command successful.", pcmd);
177256668Sshin		}
177356668Sshin		return 1;
177456668Sshin	}
177556668Sshin	return 0;
177656668Sshin}
177756668Sshin
177856668Sshinstatic void
177990148Simpv4map_data_dest(void)
178056668Sshin{
178156668Sshin	struct in_addr savedaddr;
178256668Sshin	int savedport;
178356668Sshin
178456668Sshin	if (data_dest.su_family != AF_INET) {
178556668Sshin		usedefault = 1;
178656668Sshin		reply(500, "Invalid address rejected.");
178756668Sshin		return;
178856668Sshin	}
178956668Sshin
179056668Sshin	savedaddr = data_dest.su_sin.sin_addr;
179156668Sshin	savedport = data_dest.su_port;
179256668Sshin
179356668Sshin	memset(&data_dest, 0, sizeof(data_dest));
179456668Sshin	data_dest.su_sin6.sin6_len = sizeof(struct sockaddr_in6);
179556668Sshin	data_dest.su_sin6.sin6_family = AF_INET6;
179656668Sshin	data_dest.su_sin6.sin6_port = savedport;
179756668Sshin	memset((caddr_t)&data_dest.su_sin6.sin6_addr.s6_addr[10], 0xff, 2);
179856668Sshin	memcpy((caddr_t)&data_dest.su_sin6.sin6_addr.s6_addr[12],
179956668Sshin	       (caddr_t)&savedaddr, sizeof(savedaddr));
180056668Sshin}
180156668Sshin#endif
1802