ftpcmd.y revision 168849
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
471592Srgrimes#endif /* not lint */
481592Srgrimes
49137859Syar#include <sys/cdefs.h>
50137859Syar__FBSDID("$FreeBSD: head/libexec/ftpd/ftpcmd.y 168849 2007-04-18 22:43:39Z yar $");
51137859Syar
521592Srgrimes#include <sys/param.h>
531592Srgrimes#include <sys/socket.h>
541592Srgrimes#include <sys/stat.h>
551592Srgrimes
561592Srgrimes#include <netinet/in.h>
571592Srgrimes#include <arpa/ftp.h>
581592Srgrimes
591592Srgrimes#include <ctype.h>
601592Srgrimes#include <errno.h>
611592Srgrimes#include <glob.h>
6292090Smaxim#include <libutil.h>
6392272Smaxim#include <limits.h>
6492090Smaxim#include <md5.h>
6556668Sshin#include <netdb.h>
661592Srgrimes#include <pwd.h>
671592Srgrimes#include <signal.h>
68132929Syar#include <stdint.h>
691592Srgrimes#include <stdio.h>
701592Srgrimes#include <stdlib.h>
711592Srgrimes#include <string.h>
721592Srgrimes#include <syslog.h>
731592Srgrimes#include <time.h>
741592Srgrimes#include <unistd.h>
751592Srgrimes
761592Srgrimes#include "extern.h"
77109380Syar#include "pathnames.h"
781592Srgrimes
7956668Sshinextern	union sockunion data_dest, his_addr;
80110037Syarextern	int hostinfo;
811592Srgrimesextern	int logged_in;
821592Srgrimesextern	struct passwd *pw;
831592Srgrimesextern	int guest;
84110036Syarextern	char *homedir;
8517435Spstextern 	int paranoid;
861592Srgrimesextern	int logging;
871592Srgrimesextern	int type;
881592Srgrimesextern	int form;
8976096Smarkmextern	int ftpdebug;
901592Srgrimesextern	int timeout;
911592Srgrimesextern	int maxtimeout;
921592Srgrimesextern  int pdata;
9327650Sdavidnextern	char *hostname;
941592Srgrimesextern	char proctitle[];
951592Srgrimesextern	int usedefault;
961592Srgrimesextern  char tmpline[];
9770102Sphkextern	int readonly;
98168849Syarextern	int assumeutf8;
9970102Sphkextern	int noepsv;
10082460Snikextern	int noretr;
10182796Ssheldonhextern	int noguestretr;
102100684Syarextern	char *typenames[]; /* defined in <arpa/ftp.h> included from ftpd.c */
1031592Srgrimes
1041592Srgrimesoff_t	restart_point;
1051592Srgrimes
1061592Srgrimesstatic	int cmd_type;
1071592Srgrimesstatic	int cmd_form;
1081592Srgrimesstatic	int cmd_bytesz;
10989935Syarstatic	int state;
1101592Srgrimeschar	cbuf[512];
111132931Syarchar	*fromname = NULL;
1121592Srgrimes
11356668Sshinextern int epsvall;
11456668Sshin
1151592Srgrimes%}
1161592Srgrimes
1171592Srgrimes%union {
11892272Smaxim	struct {
11992272Smaxim		off_t	o;
12092272Smaxim		int	i;
12192272Smaxim	} u;
1221592Srgrimes	char   *s;
1231592Srgrimes}
1241592Srgrimes
1251592Srgrimes%token
1261592Srgrimes	A	B	C	E	F	I
1271592Srgrimes	L	N	P	R	S	T
12856668Sshin	ALL
1291592Srgrimes
1301592Srgrimes	SP	CRLF	COMMA
1311592Srgrimes
1321592Srgrimes	USER	PASS	ACCT	REIN	QUIT	PORT
1331592Srgrimes	PASV	TYPE	STRU	MODE	RETR	STOR
1341592Srgrimes	APPE	MLFL	MAIL	MSND	MSOM	MSAM
1351592Srgrimes	MRSQ	MRCP	ALLO	REST	RNFR	RNTO
1361592Srgrimes	ABOR	DELE	CWD	LIST	NLST	SITE
1371592Srgrimes	STAT	HELP	NOOP	MKD	RMD	PWD
1381592Srgrimes	CDUP	STOU	SMNT	SYST	SIZE	MDTM
139168849Syar	LPRT	LPSV	EPRT	EPSV	FEAT
1401592Srgrimes
14175535Sphk	UMASK	IDLE	CHMOD	MDFIVE
1421592Srgrimes
143102565Syar	LEXERR	NOTIMPL
1441592Srgrimes
1451592Srgrimes%token	<s> STRING
14692272Smaxim%token	<u> NUMBER
1471592Srgrimes
14892272Smaxim%type	<u.i> check_login octal_number byte_size
14992272Smaxim%type	<u.i> check_login_ro check_login_epsv
15092272Smaxim%type	<u.i> struct_code mode_code type_code form_code
15175567Speter%type	<s> pathstring pathname password username
152102565Syar%type	<s> ALL NOTIMPL
1531592Srgrimes
1541592Srgrimes%start	cmd_list
1551592Srgrimes
1561592Srgrimes%%
1571592Srgrimes
1581592Srgrimescmd_list
1591592Srgrimes	: /* empty */
1601592Srgrimes	| cmd_list cmd
1611592Srgrimes		{
16288935Sdwmalone			if (fromname)
16388935Sdwmalone				free(fromname);
164132931Syar			fromname = NULL;
165132930Syar			restart_point = 0;
1661592Srgrimes		}
1671592Srgrimes	| cmd_list rcmd
1681592Srgrimes	;
1691592Srgrimes
1701592Srgrimescmd
1711592Srgrimes	: USER SP username CRLF
1721592Srgrimes		{
1731592Srgrimes			user($3);
1741592Srgrimes			free($3);
1751592Srgrimes		}
1761592Srgrimes	| PASS SP password CRLF
1771592Srgrimes		{
1781592Srgrimes			pass($3);
1791592Srgrimes			free($3);
1801592Srgrimes		}
18175556Sgreen	| PASS CRLF
18275556Sgreen		{
18375556Sgreen			pass("");
18475556Sgreen		}
18517433Spst	| PORT check_login SP host_port CRLF
1861592Srgrimes		{
18756668Sshin			if (epsvall) {
188137852Syar				reply(501, "No PORT allowed after EPSV ALL.");
18956668Sshin				goto port_done;
19056668Sshin			}
19156668Sshin			if (!$2)
19256668Sshin				goto port_done;
19356668Sshin			if (port_check("PORT") == 1)
19456668Sshin				goto port_done;
19556668Sshin#ifdef INET6
19656668Sshin			if ((his_addr.su_family != AF_INET6 ||
19756668Sshin			     !IN6_IS_ADDR_V4MAPPED(&his_addr.su_sin6.sin6_addr))) {
19856668Sshin				/* shoud never happen */
19956668Sshin				usedefault = 1;
20056668Sshin				reply(500, "Invalid address rejected.");
20156668Sshin				goto port_done;
20256668Sshin			}
20356668Sshin			port_check_v6("pcmd");
20456668Sshin#endif
20556668Sshin		port_done:
206132925Syar			;
20756668Sshin		}
20856668Sshin	| LPRT check_login SP host_long_port CRLF
20956668Sshin		{
21056668Sshin			if (epsvall) {
211137852Syar				reply(501, "No LPRT allowed after EPSV ALL.");
21256668Sshin				goto lprt_done;
21356668Sshin			}
21456668Sshin			if (!$2)
21556668Sshin				goto lprt_done;
21656668Sshin			if (port_check("LPRT") == 1)
21756668Sshin				goto lprt_done;
21856668Sshin#ifdef INET6
21956668Sshin			if (his_addr.su_family != AF_INET6) {
22056668Sshin				usedefault = 1;
22156668Sshin				reply(500, "Invalid address rejected.");
22256668Sshin				goto lprt_done;
22356668Sshin			}
22456668Sshin			if (port_check_v6("LPRT") == 1)
22556668Sshin				goto lprt_done;
22656668Sshin#endif
22756668Sshin		lprt_done:
228132925Syar			;
22956668Sshin		}
23056668Sshin	| EPRT check_login SP STRING CRLF
23156668Sshin		{
23256668Sshin			char delim;
23356668Sshin			char *tmp = NULL;
23456668Sshin			char *p, *q;
23556668Sshin			char *result[3];
23656668Sshin			struct addrinfo hints;
23756668Sshin			struct addrinfo *res;
23856668Sshin			int i;
23956668Sshin
24056668Sshin			if (epsvall) {
241137852Syar				reply(501, "No EPRT allowed after EPSV ALL.");
24256668Sshin				goto eprt_done;
24356668Sshin			}
24456668Sshin			if (!$2)
24556668Sshin				goto eprt_done;
24656668Sshin
24756668Sshin			memset(&data_dest, 0, sizeof(data_dest));
24856668Sshin			tmp = strdup($4);
24976096Smarkm			if (ftpdebug)
25056668Sshin				syslog(LOG_DEBUG, "%s", tmp);
25156668Sshin			if (!tmp) {
25276096Smarkm				fatalerror("not enough core");
25356668Sshin				/*NOTREACHED*/
25456668Sshin			}
25556668Sshin			p = tmp;
25656668Sshin			delim = p[0];
25756668Sshin			p++;
25856668Sshin			memset(result, 0, sizeof(result));
25956668Sshin			for (i = 0; i < 3; i++) {
26056668Sshin				q = strchr(p, delim);
26156668Sshin				if (!q || *q != delim) {
26256668Sshin		parsefail:
26356668Sshin					reply(500,
26456668Sshin						"Invalid argument, rejected.");
26556668Sshin					if (tmp)
26656668Sshin						free(tmp);
26717433Spst					usedefault = 1;
26856668Sshin					goto eprt_done;
26917433Spst				}
27056668Sshin				*q++ = '\0';
27156668Sshin				result[i] = p;
27276096Smarkm				if (ftpdebug)
27356668Sshin					syslog(LOG_DEBUG, "%d: %s", i, p);
27456668Sshin				p = q;
2751592Srgrimes			}
27656668Sshin
27756668Sshin			/* some more sanity check */
27856668Sshin			p = result[0];
27956668Sshin			while (*p) {
28056668Sshin				if (!isdigit(*p))
28156668Sshin					goto parsefail;
28256668Sshin				p++;
28356668Sshin			}
28456668Sshin			p = result[2];
28556668Sshin			while (*p) {
28656668Sshin				if (!isdigit(*p))
28756668Sshin					goto parsefail;
28856668Sshin				p++;
28956668Sshin			}
29056668Sshin
29156668Sshin			/* grab address */
29256668Sshin			memset(&hints, 0, sizeof(hints));
29356668Sshin			if (atoi(result[0]) == 1)
29456668Sshin				hints.ai_family = PF_INET;
29556668Sshin#ifdef INET6
29656668Sshin			else if (atoi(result[0]) == 2)
29756668Sshin				hints.ai_family = PF_INET6;
29856668Sshin#endif
29956668Sshin			else
30056668Sshin				hints.ai_family = PF_UNSPEC;	/*XXX*/
30156668Sshin			hints.ai_socktype = SOCK_STREAM;
30256668Sshin			i = getaddrinfo(result[1], result[2], &hints, &res);
30356668Sshin			if (i)
30456668Sshin				goto parsefail;
30556668Sshin			memcpy(&data_dest, res->ai_addr, res->ai_addrlen);
30656668Sshin#ifdef INET6
30756668Sshin			if (his_addr.su_family == AF_INET6
30856668Sshin			    && data_dest.su_family == AF_INET6) {
30956668Sshin				/* XXX more sanity checks! */
31056668Sshin				data_dest.su_sin6.sin6_scope_id =
31156668Sshin					his_addr.su_sin6.sin6_scope_id;
31256668Sshin			}
31356668Sshin#endif
31456668Sshin			free(tmp);
31556668Sshin			tmp = NULL;
31656668Sshin
31756668Sshin			if (port_check("EPRT") == 1)
31856668Sshin				goto eprt_done;
31956668Sshin#ifdef INET6
32056668Sshin			if (his_addr.su_family != AF_INET6) {
32156668Sshin				usedefault = 1;
32256668Sshin				reply(500, "Invalid address rejected.");
32356668Sshin				goto eprt_done;
32456668Sshin			}
32556668Sshin			if (port_check_v6("EPRT") == 1)
32656668Sshin				goto eprt_done;
32756668Sshin#endif
32888935Sdwmalone		eprt_done:
32988935Sdwmalone			free($4);
3301592Srgrimes		}
33117433Spst	| PASV check_login CRLF
3321592Srgrimes		{
33356668Sshin			if (epsvall)
334137852Syar				reply(501, "No PASV allowed after EPSV ALL.");
33556668Sshin			else if ($2)
33617433Spst				passive();
3371592Srgrimes		}
33856668Sshin	| LPSV check_login CRLF
33956668Sshin		{
34056668Sshin			if (epsvall)
341137852Syar				reply(501, "No LPSV allowed after EPSV ALL.");
34256668Sshin			else if ($2)
34356668Sshin				long_passive("LPSV", PF_UNSPEC);
34456668Sshin		}
34570102Sphk	| EPSV check_login_epsv SP NUMBER CRLF
34656668Sshin		{
34756668Sshin			if ($2) {
34856668Sshin				int pf;
34992272Smaxim				switch ($4.i) {
35056668Sshin				case 1:
35156668Sshin					pf = PF_INET;
35256668Sshin					break;
35356668Sshin#ifdef INET6
35456668Sshin				case 2:
35556668Sshin					pf = PF_INET6;
35656668Sshin					break;
35756668Sshin#endif
35856668Sshin				default:
35956668Sshin					pf = -1;	/*junk value*/
36056668Sshin					break;
36156668Sshin				}
36256668Sshin				long_passive("EPSV", pf);
36356668Sshin			}
36456668Sshin		}
36570102Sphk	| EPSV check_login_epsv SP ALL CRLF
36656668Sshin		{
36756668Sshin			if ($2) {
368137852Syar				reply(200, "EPSV ALL command successful.");
36956668Sshin				epsvall++;
37056668Sshin			}
37156668Sshin		}
37270102Sphk	| EPSV check_login_epsv CRLF
37356668Sshin		{
37456668Sshin			if ($2)
37556668Sshin				long_passive("EPSV", PF_UNSPEC);
37656668Sshin		}
37771278Sjedgar	| TYPE check_login SP type_code CRLF
3781592Srgrimes		{
37971278Sjedgar			if ($2) {
38071278Sjedgar				switch (cmd_type) {
3811592Srgrimes
38271278Sjedgar				case TYPE_A:
38371278Sjedgar					if (cmd_form == FORM_N) {
38471278Sjedgar						reply(200, "Type set to A.");
38571278Sjedgar						type = cmd_type;
38671278Sjedgar						form = cmd_form;
38771278Sjedgar					} else
38871278Sjedgar						reply(504, "Form must be N.");
38971278Sjedgar					break;
3901592Srgrimes
39171278Sjedgar				case TYPE_E:
39271278Sjedgar					reply(504, "Type E not implemented.");
39371278Sjedgar					break;
3941592Srgrimes
39571278Sjedgar				case TYPE_I:
39671278Sjedgar					reply(200, "Type set to I.");
39771278Sjedgar					type = cmd_type;
39871278Sjedgar					break;
3991592Srgrimes
40071278Sjedgar				case TYPE_L:
401103949Smike#if CHAR_BIT == 8
40271278Sjedgar					if (cmd_bytesz == 8) {
40371278Sjedgar						reply(200,
40471278Sjedgar						    "Type set to L (byte size 8).");
40571278Sjedgar						type = cmd_type;
40671278Sjedgar					} else
40771278Sjedgar						reply(504, "Byte size must be 8.");
408103949Smike#else /* CHAR_BIT == 8 */
409103949Smike					UNIMPLEMENTED for CHAR_BIT != 8
410103949Smike#endif /* CHAR_BIT == 8 */
41171278Sjedgar				}
4121592Srgrimes			}
4131592Srgrimes		}
41471278Sjedgar	| STRU check_login SP struct_code CRLF
4151592Srgrimes		{
41671278Sjedgar			if ($2) {
41771278Sjedgar				switch ($4) {
4181592Srgrimes
41971278Sjedgar				case STRU_F:
420137852Syar					reply(200, "STRU F accepted.");
42171278Sjedgar					break;
4221592Srgrimes
42371278Sjedgar				default:
42471278Sjedgar					reply(504, "Unimplemented STRU type.");
42571278Sjedgar				}
4261592Srgrimes			}
4271592Srgrimes		}
42871278Sjedgar	| MODE check_login SP mode_code CRLF
4291592Srgrimes		{
43071278Sjedgar			if ($2) {
43171278Sjedgar				switch ($4) {
4321592Srgrimes
43371278Sjedgar				case MODE_S:
434137852Syar					reply(200, "MODE S accepted.");
43571278Sjedgar					break;
43671278Sjedgar
43771278Sjedgar				default:
43871278Sjedgar					reply(502, "Unimplemented MODE type.");
43971278Sjedgar				}
4401592Srgrimes			}
4411592Srgrimes		}
44271278Sjedgar	| ALLO check_login SP NUMBER CRLF
4431592Srgrimes		{
44471278Sjedgar			if ($2) {
44571278Sjedgar				reply(202, "ALLO command ignored.");
44671278Sjedgar			}
4471592Srgrimes		}
44871278Sjedgar	| ALLO check_login SP NUMBER SP R SP NUMBER CRLF
4491592Srgrimes		{
45071278Sjedgar			if ($2) {
45171278Sjedgar				reply(202, "ALLO command ignored.");
45271278Sjedgar			}
4531592Srgrimes		}
4541592Srgrimes	| RETR check_login SP pathname CRLF
4551592Srgrimes		{
45682796Ssheldonh			if (noretr || (guest && noguestretr))
457137852Syar				reply(500, "RETR command disabled.");
45882460Snik			else if ($2 && $4 != NULL)
459132931Syar				retrieve(NULL, $4);
46082460Snik
4611592Srgrimes			if ($4 != NULL)
4621592Srgrimes				free($4);
4631592Srgrimes		}
46470102Sphk	| STOR check_login_ro SP pathname CRLF
4651592Srgrimes		{
4661592Srgrimes			if ($2 && $4 != NULL)
4671592Srgrimes				store($4, "w", 0);
4681592Srgrimes			if ($4 != NULL)
4691592Srgrimes				free($4);
4701592Srgrimes		}
47170102Sphk	| APPE check_login_ro SP pathname CRLF
4721592Srgrimes		{
4731592Srgrimes			if ($2 && $4 != NULL)
4741592Srgrimes				store($4, "a", 0);
4751592Srgrimes			if ($4 != NULL)
4761592Srgrimes				free($4);
4771592Srgrimes		}
4781592Srgrimes	| NLST check_login CRLF
4791592Srgrimes		{
4801592Srgrimes			if ($2)
4811592Srgrimes				send_file_list(".");
4821592Srgrimes		}
483101395Syar	| NLST check_login SP pathstring CRLF
4841592Srgrimes		{
485101395Syar			if ($2)
4861592Srgrimes				send_file_list($4);
487101395Syar			free($4);
4881592Srgrimes		}
4891592Srgrimes	| LIST check_login CRLF
4901592Srgrimes		{
4911592Srgrimes			if ($2)
492109380Syar				retrieve(_PATH_LS " -lgA", "");
4931592Srgrimes		}
49475567Speter	| LIST check_login SP pathstring CRLF
4951592Srgrimes		{
496101395Syar			if ($2)
497109380Syar				retrieve(_PATH_LS " -lgA %s", $4);
498101395Syar			free($4);
4991592Srgrimes		}
5001592Srgrimes	| STAT check_login SP pathname CRLF
5011592Srgrimes		{
5021592Srgrimes			if ($2 && $4 != NULL)
5031592Srgrimes				statfilecmd($4);
5041592Srgrimes			if ($4 != NULL)
5051592Srgrimes				free($4);
5061592Srgrimes		}
50771278Sjedgar	| STAT check_login CRLF
5081592Srgrimes		{
50971278Sjedgar			if ($2) {
51071278Sjedgar				statcmd();
51171278Sjedgar			}
5121592Srgrimes		}
51370102Sphk	| DELE check_login_ro SP pathname CRLF
5141592Srgrimes		{
5151592Srgrimes			if ($2 && $4 != NULL)
5161592Srgrimes				delete($4);
5171592Srgrimes			if ($4 != NULL)
5181592Srgrimes				free($4);
5191592Srgrimes		}
52070102Sphk	| RNTO check_login_ro SP pathname CRLF
5211592Srgrimes		{
522101379Syar			if ($2 && $4 != NULL) {
52317433Spst				if (fromname) {
52417433Spst					renamecmd(fromname, $4);
52517433Spst					free(fromname);
526132931Syar					fromname = NULL;
52717433Spst				} else {
52817433Spst					reply(503, "Bad sequence of commands.");
52917433Spst				}
5301592Srgrimes			}
531101379Syar			if ($4 != NULL)
532101379Syar				free($4);
5331592Srgrimes		}
53471278Sjedgar	| ABOR check_login CRLF
5351592Srgrimes		{
53671278Sjedgar			if ($2)
53771278Sjedgar				reply(225, "ABOR command successful.");
5381592Srgrimes		}
5391592Srgrimes	| CWD check_login CRLF
5401592Srgrimes		{
54169234Sdanny			if ($2) {
542110036Syar				cwd(homedir);
54369234Sdanny			}
5441592Srgrimes		}
5451592Srgrimes	| CWD check_login SP pathname CRLF
5461592Srgrimes		{
5471592Srgrimes			if ($2 && $4 != NULL)
5481592Srgrimes				cwd($4);
5491592Srgrimes			if ($4 != NULL)
5501592Srgrimes				free($4);
5511592Srgrimes		}
5521592Srgrimes	| HELP CRLF
5531592Srgrimes		{
554132931Syar			help(cmdtab, NULL);
5551592Srgrimes		}
5561592Srgrimes	| HELP SP STRING CRLF
5571592Srgrimes		{
5581592Srgrimes			char *cp = $3;
5591592Srgrimes
5601592Srgrimes			if (strncasecmp(cp, "SITE", 4) == 0) {
5611592Srgrimes				cp = $3 + 4;
5621592Srgrimes				if (*cp == ' ')
5631592Srgrimes					cp++;
5641592Srgrimes				if (*cp)
5651592Srgrimes					help(sitetab, cp);
5661592Srgrimes				else
567132931Syar					help(sitetab, NULL);
5681592Srgrimes			} else
5691592Srgrimes				help(cmdtab, $3);
57088935Sdwmalone			free($3);
5711592Srgrimes		}
5721592Srgrimes	| NOOP CRLF
5731592Srgrimes		{
5741592Srgrimes			reply(200, "NOOP command successful.");
5751592Srgrimes		}
57670102Sphk	| MKD check_login_ro SP pathname CRLF
5771592Srgrimes		{
5781592Srgrimes			if ($2 && $4 != NULL)
5791592Srgrimes				makedir($4);
5801592Srgrimes			if ($4 != NULL)
5811592Srgrimes				free($4);
5821592Srgrimes		}
58370102Sphk	| RMD check_login_ro SP pathname CRLF
5841592Srgrimes		{
5851592Srgrimes			if ($2 && $4 != NULL)
5861592Srgrimes				removedir($4);
5871592Srgrimes			if ($4 != NULL)
5881592Srgrimes				free($4);
5891592Srgrimes		}
5901592Srgrimes	| PWD check_login CRLF
5911592Srgrimes		{
5921592Srgrimes			if ($2)
5931592Srgrimes				pwd();
5941592Srgrimes		}
5951592Srgrimes	| CDUP check_login CRLF
5961592Srgrimes		{
5971592Srgrimes			if ($2)
5981592Srgrimes				cwd("..");
5991592Srgrimes		}
6001592Srgrimes	| SITE SP HELP CRLF
6011592Srgrimes		{
602132931Syar			help(sitetab, NULL);
6031592Srgrimes		}
6041592Srgrimes	| SITE SP HELP SP STRING CRLF
6051592Srgrimes		{
6061592Srgrimes			help(sitetab, $5);
60788935Sdwmalone			free($5);
6081592Srgrimes		}
60975535Sphk	| SITE SP MDFIVE check_login SP pathname CRLF
61075535Sphk		{
61175535Sphk			char p[64], *q;
61275535Sphk
613101379Syar			if ($4 && $6) {
61475535Sphk				q = MD5File($6, p);
61575535Sphk				if (q != NULL)
61675535Sphk					reply(200, "MD5(%s) = %s", $6, p);
61775535Sphk				else
61875535Sphk					perror_reply(550, $6);
61975535Sphk			}
62088935Sdwmalone			if ($6)
62188935Sdwmalone				free($6);
62275535Sphk		}
6231592Srgrimes	| SITE SP UMASK check_login CRLF
6241592Srgrimes		{
6251592Srgrimes			int oldmask;
6261592Srgrimes
6271592Srgrimes			if ($4) {
6281592Srgrimes				oldmask = umask(0);
6291592Srgrimes				(void) umask(oldmask);
630137852Syar				reply(200, "Current UMASK is %03o.", oldmask);
6311592Srgrimes			}
6321592Srgrimes		}
6331592Srgrimes	| SITE SP UMASK check_login SP octal_number CRLF
6341592Srgrimes		{
6351592Srgrimes			int oldmask;
6361592Srgrimes
6371592Srgrimes			if ($4) {
6381592Srgrimes				if (($6 == -1) || ($6 > 0777)) {
639137852Syar					reply(501, "Bad UMASK value.");
6401592Srgrimes				} else {
6411592Srgrimes					oldmask = umask($6);
6421592Srgrimes					reply(200,
643137852Syar					    "UMASK set to %03o (was %03o).",
6441592Srgrimes					    $6, oldmask);
6451592Srgrimes				}
6461592Srgrimes			}
6471592Srgrimes		}
64870102Sphk	| SITE SP CHMOD check_login_ro SP octal_number SP pathname CRLF
6491592Srgrimes		{
6501592Srgrimes			if ($4 && ($8 != NULL)) {
651101378Syar				if (($6 == -1 ) || ($6 > 0777))
652137852Syar					reply(501, "Bad mode value.");
6531592Srgrimes				else if (chmod($8, $6) < 0)
6541592Srgrimes					perror_reply(550, $8);
6551592Srgrimes				else
6561592Srgrimes					reply(200, "CHMOD command successful.");
6571592Srgrimes			}
6581592Srgrimes			if ($8 != NULL)
6591592Srgrimes				free($8);
6601592Srgrimes		}
66171278Sjedgar	| SITE SP check_login IDLE CRLF
6621592Srgrimes		{
66371278Sjedgar			if ($3)
66471278Sjedgar				reply(200,
665137852Syar			    	    "Current IDLE time limit is %d seconds; max %d.",
66671278Sjedgar				    timeout, maxtimeout);
6671592Srgrimes		}
66871278Sjedgar	| SITE SP check_login IDLE SP NUMBER CRLF
6691592Srgrimes		{
67071278Sjedgar			if ($3) {
67192272Smaxim				if ($6.i < 30 || $6.i > maxtimeout) {
67271278Sjedgar					reply(501,
673137852Syar					    "Maximum IDLE time must be between 30 and %d seconds.",
67471278Sjedgar					    maxtimeout);
67571278Sjedgar				} else {
67692272Smaxim					timeout = $6.i;
677137659Syar					(void) alarm(timeout);
67871278Sjedgar					reply(200,
679137852Syar					    "Maximum IDLE time set to %d seconds.",
68071278Sjedgar					    timeout);
68171278Sjedgar				}
6821592Srgrimes			}
6831592Srgrimes		}
68470102Sphk	| STOU check_login_ro SP pathname CRLF
6851592Srgrimes		{
6861592Srgrimes			if ($2 && $4 != NULL)
6871592Srgrimes				store($4, "w", 1);
6881592Srgrimes			if ($4 != NULL)
6891592Srgrimes				free($4);
6901592Srgrimes		}
691168849Syar	| FEAT CRLF
692168849Syar		{
693168849Syar			lreply(211, "Extensions supported:");
694168849Syar#if 0
695168849Syar			/* XXX these two keywords are non-standard */
696168849Syar			printf(" EPRT\r\n");
697168849Syar			if (!noepsv)
698168849Syar				printf(" EPSV\r\n");
699168849Syar#endif
700168849Syar			printf(" MDTM\r\n");
701168849Syar			printf(" REST STREAM\r\n");
702168849Syar			printf(" SIZE\r\n");
703168849Syar			if (assumeutf8) {
704168849Syar				/* TVFS requires UTF8, see RFC 3659 */
705168849Syar				printf(" TVFS\r\n");
706168849Syar				printf(" UTF8\r\n");
707168849Syar			}
708168849Syar			reply(211, "End.");
709168849Syar		}
71071278Sjedgar	| SYST check_login CRLF
7111592Srgrimes		{
712116439Syar			if ($2) {
713116439Syar				if (hostinfo)
7141592Srgrimes#ifdef BSD
715116439Syar					reply(215, "UNIX Type: L%d Version: BSD-%d",
716116439Syar					      CHAR_BIT, BSD);
7171592Srgrimes#else /* BSD */
718116439Syar					reply(215, "UNIX Type: L%d", CHAR_BIT);
7191592Srgrimes#endif /* BSD */
720116439Syar				else
721116439Syar					reply(215, "UNKNOWN Type: L%d", CHAR_BIT);
722116439Syar			}
7231592Srgrimes		}
7241592Srgrimes
7251592Srgrimes		/*
7261592Srgrimes		 * SIZE is not in RFC959, but Postel has blessed it and
7271592Srgrimes		 * it will be in the updated RFC.
7281592Srgrimes		 *
7291592Srgrimes		 * Return size of file in a format suitable for
7301592Srgrimes		 * using with RESTART (we just count bytes).
7311592Srgrimes		 */
7321592Srgrimes	| SIZE check_login SP pathname CRLF
7331592Srgrimes		{
7341592Srgrimes			if ($2 && $4 != NULL)
7351592Srgrimes				sizecmd($4);
7361592Srgrimes			if ($4 != NULL)
7371592Srgrimes				free($4);
7381592Srgrimes		}
7391592Srgrimes
7401592Srgrimes		/*
7411592Srgrimes		 * MDTM is not in RFC959, but Postel has blessed it and
7421592Srgrimes		 * it will be in the updated RFC.
7431592Srgrimes		 *
7441592Srgrimes		 * Return modification time of file as an ISO 3307
7451592Srgrimes		 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
7461592Srgrimes		 * where xxx is the fractional second (of any precision,
7471592Srgrimes		 * not necessarily 3 digits)
7481592Srgrimes		 */
7491592Srgrimes	| MDTM check_login SP pathname CRLF
7501592Srgrimes		{
7511592Srgrimes			if ($2 && $4 != NULL) {
7521592Srgrimes				struct stat stbuf;
7531592Srgrimes				if (stat($4, &stbuf) < 0)
754137850Syar					perror_reply(550, $4);
7551592Srgrimes				else if (!S_ISREG(stbuf.st_mode)) {
7561592Srgrimes					reply(550, "%s: not a plain file.", $4);
7571592Srgrimes				} else {
7581592Srgrimes					struct tm *t;
7591592Srgrimes					t = gmtime(&stbuf.st_mtime);
7601592Srgrimes					reply(213,
76117435Spst					    "%04d%02d%02d%02d%02d%02d",
76217435Spst					    1900 + t->tm_year,
76317435Spst					    t->tm_mon+1, t->tm_mday,
7641592Srgrimes					    t->tm_hour, t->tm_min, t->tm_sec);
7651592Srgrimes				}
7661592Srgrimes			}
7671592Srgrimes			if ($4 != NULL)
7681592Srgrimes				free($4);
7691592Srgrimes		}
7701592Srgrimes	| QUIT CRLF
7711592Srgrimes		{
7721592Srgrimes			reply(221, "Goodbye.");
7731592Srgrimes			dologout(0);
7741592Srgrimes		}
775102565Syar	| NOTIMPL
776102565Syar		{
777102565Syar			nack($1);
778102565Syar		}
77989935Syar	| error
7801592Srgrimes		{
78189935Syar			yyclearin;		/* discard lookahead data */
78289935Syar			yyerrok;		/* clear error condition */
783102565Syar			state = CMD;		/* reset lexer state */
7841592Srgrimes		}
7851592Srgrimes	;
7861592Srgrimesrcmd
78770102Sphk	: RNFR check_login_ro SP pathname CRLF
7881592Srgrimes		{
789132930Syar			restart_point = 0;
7901592Srgrimes			if ($2 && $4) {
79188935Sdwmalone				if (fromname)
79288935Sdwmalone					free(fromname);
793132931Syar				fromname = NULL;
79488935Sdwmalone				if (renamefrom($4))
79588935Sdwmalone					fromname = $4;
79688935Sdwmalone				else
7971592Srgrimes					free($4);
79888935Sdwmalone			} else if ($4) {
79988935Sdwmalone				free($4);
8001592Srgrimes			}
8011592Srgrimes		}
80292272Smaxim	| REST check_login SP NUMBER CRLF
8031592Srgrimes		{
80471278Sjedgar			if ($2) {
80588935Sdwmalone				if (fromname)
80688935Sdwmalone					free(fromname);
807132931Syar				fromname = NULL;
80892272Smaxim				restart_point = $4.o;
809132929Syar				reply(350, "Restarting at %jd. %s",
810132929Syar				    (intmax_t)restart_point,
81171278Sjedgar				    "Send STORE or RETRIEVE to initiate transfer.");
81271278Sjedgar			}
8131592Srgrimes		}
8141592Srgrimes	;
8151592Srgrimes
8161592Srgrimesusername
8171592Srgrimes	: STRING
8181592Srgrimes	;
8191592Srgrimes
8201592Srgrimespassword
8211592Srgrimes	: /* empty */
8221592Srgrimes		{
8231592Srgrimes			$$ = (char *)calloc(1, sizeof(char));
8241592Srgrimes		}
8251592Srgrimes	| STRING
8261592Srgrimes	;
8271592Srgrimes
8281592Srgrimesbyte_size
8291592Srgrimes	: NUMBER
83092272Smaxim		{
83192272Smaxim			$$ = $1.i;
83292272Smaxim		}
8331592Srgrimes	;
8341592Srgrimes
8351592Srgrimeshost_port
8361592Srgrimes	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
8371592Srgrimes		NUMBER COMMA NUMBER
8381592Srgrimes		{
8391592Srgrimes			char *a, *p;
8401592Srgrimes
84156668Sshin			data_dest.su_len = sizeof(struct sockaddr_in);
84256668Sshin			data_dest.su_family = AF_INET;
84356668Sshin			p = (char *)&data_dest.su_sin.sin_port;
84492272Smaxim			p[0] = $9.i; p[1] = $11.i;
84556668Sshin			a = (char *)&data_dest.su_sin.sin_addr;
84692272Smaxim			a[0] = $1.i; a[1] = $3.i; a[2] = $5.i; a[3] = $7.i;
8471592Srgrimes		}
8481592Srgrimes	;
8491592Srgrimes
85056668Sshinhost_long_port
85156668Sshin	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
85256668Sshin		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
85356668Sshin		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
85456668Sshin		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
85556668Sshin		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
85656668Sshin		NUMBER
85756668Sshin		{
85856668Sshin			char *a, *p;
85956668Sshin
86056668Sshin			memset(&data_dest, 0, sizeof(data_dest));
86156668Sshin			data_dest.su_len = sizeof(struct sockaddr_in6);
86256668Sshin			data_dest.su_family = AF_INET6;
86356668Sshin			p = (char *)&data_dest.su_port;
86492272Smaxim			p[0] = $39.i; p[1] = $41.i;
86556668Sshin			a = (char *)&data_dest.su_sin6.sin6_addr;
86692272Smaxim			a[0] = $5.i; a[1] = $7.i; a[2] = $9.i; a[3] = $11.i;
86792272Smaxim			a[4] = $13.i; a[5] = $15.i; a[6] = $17.i; a[7] = $19.i;
86892272Smaxim			a[8] = $21.i; a[9] = $23.i; a[10] = $25.i; a[11] = $27.i;
86992272Smaxim			a[12] = $29.i; a[13] = $31.i; a[14] = $33.i; a[15] = $35.i;
87056668Sshin			if (his_addr.su_family == AF_INET6) {
87156668Sshin				/* XXX more sanity checks! */
87256668Sshin				data_dest.su_sin6.sin6_scope_id =
87356668Sshin					his_addr.su_sin6.sin6_scope_id;
87456668Sshin			}
87592272Smaxim			if ($1.i != 6 || $3.i != 16 || $37.i != 2)
87656668Sshin				memset(&data_dest, 0, sizeof(data_dest));
87756668Sshin		}
87856668Sshin	| NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
87956668Sshin		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
88056668Sshin		NUMBER
88156668Sshin		{
88256668Sshin			char *a, *p;
88356668Sshin
88456668Sshin			memset(&data_dest, 0, sizeof(data_dest));
88556668Sshin			data_dest.su_sin.sin_len = sizeof(struct sockaddr_in);
88656668Sshin			data_dest.su_family = AF_INET;
88756668Sshin			p = (char *)&data_dest.su_port;
88892272Smaxim			p[0] = $15.i; p[1] = $17.i;
88956668Sshin			a = (char *)&data_dest.su_sin.sin_addr;
89092272Smaxim			a[0] =  $5.i; a[1] = $7.i; a[2] = $9.i; a[3] = $11.i;
89192272Smaxim			if ($1.i != 4 || $3.i != 4 || $13.i != 2)
89256668Sshin				memset(&data_dest, 0, sizeof(data_dest));
89356668Sshin		}
89456668Sshin	;
89556668Sshin
8961592Srgrimesform_code
8971592Srgrimes	: N
8981592Srgrimes		{
8991592Srgrimes			$$ = FORM_N;
9001592Srgrimes		}
9011592Srgrimes	| T
9021592Srgrimes		{
9031592Srgrimes			$$ = FORM_T;
9041592Srgrimes		}
9051592Srgrimes	| C
9061592Srgrimes		{
9071592Srgrimes			$$ = FORM_C;
9081592Srgrimes		}
9091592Srgrimes	;
9101592Srgrimes
9111592Srgrimestype_code
9121592Srgrimes	: A
9131592Srgrimes		{
9141592Srgrimes			cmd_type = TYPE_A;
9151592Srgrimes			cmd_form = FORM_N;
9161592Srgrimes		}
9171592Srgrimes	| A SP form_code
9181592Srgrimes		{
9191592Srgrimes			cmd_type = TYPE_A;
9201592Srgrimes			cmd_form = $3;
9211592Srgrimes		}
9221592Srgrimes	| E
9231592Srgrimes		{
9241592Srgrimes			cmd_type = TYPE_E;
9251592Srgrimes			cmd_form = FORM_N;
9261592Srgrimes		}
9271592Srgrimes	| E SP form_code
9281592Srgrimes		{
9291592Srgrimes			cmd_type = TYPE_E;
9301592Srgrimes			cmd_form = $3;
9311592Srgrimes		}
9321592Srgrimes	| I
9331592Srgrimes		{
9341592Srgrimes			cmd_type = TYPE_I;
9351592Srgrimes		}
9361592Srgrimes	| L
9371592Srgrimes		{
9381592Srgrimes			cmd_type = TYPE_L;
939103949Smike			cmd_bytesz = CHAR_BIT;
9401592Srgrimes		}
9411592Srgrimes	| L SP byte_size
9421592Srgrimes		{
9431592Srgrimes			cmd_type = TYPE_L;
9441592Srgrimes			cmd_bytesz = $3;
9451592Srgrimes		}
9461592Srgrimes		/* this is for a bug in the BBN ftp */
9471592Srgrimes	| L byte_size
9481592Srgrimes		{
9491592Srgrimes			cmd_type = TYPE_L;
9501592Srgrimes			cmd_bytesz = $2;
9511592Srgrimes		}
9521592Srgrimes	;
9531592Srgrimes
9541592Srgrimesstruct_code
9551592Srgrimes	: F
9561592Srgrimes		{
9571592Srgrimes			$$ = STRU_F;
9581592Srgrimes		}
9591592Srgrimes	| R
9601592Srgrimes		{
9611592Srgrimes			$$ = STRU_R;
9621592Srgrimes		}
9631592Srgrimes	| P
9641592Srgrimes		{
9651592Srgrimes			$$ = STRU_P;
9661592Srgrimes		}
9671592Srgrimes	;
9681592Srgrimes
9691592Srgrimesmode_code
9701592Srgrimes	: S
9711592Srgrimes		{
9721592Srgrimes			$$ = MODE_S;
9731592Srgrimes		}
9741592Srgrimes	| B
9751592Srgrimes		{
9761592Srgrimes			$$ = MODE_B;
9771592Srgrimes		}
9781592Srgrimes	| C
9791592Srgrimes		{
9801592Srgrimes			$$ = MODE_C;
9811592Srgrimes		}
9821592Srgrimes	;
9831592Srgrimes
9841592Srgrimespathname
9851592Srgrimes	: pathstring
9861592Srgrimes		{
98775567Speter			if (logged_in && $1) {
988110340Syar				char *p;
9891592Srgrimes
990110340Syar				/*
991110340Syar				 * Expand ~user manually since glob(3)
992110340Syar				 * will return the unexpanded pathname
993110340Syar				 * if the corresponding file/directory
994110340Syar				 * doesn't exist yet.  Using sole glob(3)
995110340Syar				 * would break natural commands like
996110340Syar				 * MKD ~user/newdir
997110340Syar				 * or
998110340Syar				 * RNTO ~/newfile
999110340Syar				 */
1000110340Syar				if ((p = exptilde($1)) != NULL) {
1001110340Syar					$$ = expglob(p);
1002110340Syar					free(p);
1003110340Syar				} else
10041592Srgrimes					$$ = NULL;
10051592Srgrimes				free($1);
10061592Srgrimes			} else
10071592Srgrimes				$$ = $1;
10081592Srgrimes		}
10091592Srgrimes	;
10101592Srgrimes
10111592Srgrimespathstring
10121592Srgrimes	: STRING
10131592Srgrimes	;
10141592Srgrimes
10151592Srgrimesoctal_number
10161592Srgrimes	: NUMBER
10171592Srgrimes		{
10181592Srgrimes			int ret, dec, multby, digit;
10191592Srgrimes
10201592Srgrimes			/*
10211592Srgrimes			 * Convert a number that was read as decimal number
10221592Srgrimes			 * to what it would be if it had been read as octal.
10231592Srgrimes			 */
102492272Smaxim			dec = $1.i;
10251592Srgrimes			multby = 1;
10261592Srgrimes			ret = 0;
10271592Srgrimes			while (dec) {
10281592Srgrimes				digit = dec%10;
10291592Srgrimes				if (digit > 7) {
10301592Srgrimes					ret = -1;
10311592Srgrimes					break;
10321592Srgrimes				}
10331592Srgrimes				ret += digit * multby;
10341592Srgrimes				multby *= 8;
10351592Srgrimes				dec /= 10;
10361592Srgrimes			}
10371592Srgrimes			$$ = ret;
10381592Srgrimes		}
10391592Srgrimes	;
10401592Srgrimes
10411592Srgrimes
10421592Srgrimescheck_login
10431592Srgrimes	: /* empty */
10441592Srgrimes		{
104570102Sphk		$$ = check_login1();
10461592Srgrimes		}
10471592Srgrimes	;
10481592Srgrimes
104970102Sphkcheck_login_epsv
105070102Sphk	: /* empty */
105170102Sphk		{
105270102Sphk		if (noepsv) {
1053137852Syar			reply(500, "EPSV command disabled.");
105470102Sphk			$$ = 0;
105570102Sphk		}
105670102Sphk		else
105770102Sphk			$$ = check_login1();
105870102Sphk		}
105970102Sphk	;
106070102Sphk
106170102Sphkcheck_login_ro
106270102Sphk	: /* empty */
106370102Sphk		{
106470102Sphk		if (readonly) {
106572710Sdes			reply(550, "Permission denied.");
106670102Sphk			$$ = 0;
106770102Sphk		}
106870102Sphk		else
106970102Sphk			$$ = check_login1();
107070102Sphk		}
107170102Sphk	;
107270102Sphk
10731592Srgrimes%%
10741592Srgrimes
10751592Srgrimes#define	CMD	0	/* beginning of command */
10761592Srgrimes#define	ARGS	1	/* expect miscellaneous arguments */
10771592Srgrimes#define	STR1	2	/* expect SP followed by STRING */
10781592Srgrimes#define	STR2	3	/* expect STRING */
10791592Srgrimes#define	OSTR	4	/* optional SP then STRING */
108075556Sgreen#define	ZSTR1	5	/* optional SP then optional STRING */
10811592Srgrimes#define	ZSTR2	6	/* optional STRING after SP */
10821592Srgrimes#define	SITECMD	7	/* SITE command */
10831592Srgrimes#define	NSTR	8	/* Number followed by a string */
10841592Srgrimes
108575560Sjedgar#define	MAXGLOBARGS	1000
108675560Sjedgar
1087101034Syar#define	MAXASIZE	10240	/* Deny ASCII SIZE on files larger than that */
1088101034Syar
10891592Srgrimesstruct tab {
10901592Srgrimes	char	*name;
10911592Srgrimes	short	token;
10921592Srgrimes	short	state;
10931592Srgrimes	short	implemented;	/* 1 if command is implemented */
10941592Srgrimes	char	*help;
10951592Srgrimes};
10961592Srgrimes
10971592Srgrimesstruct tab cmdtab[] = {		/* In order defined in RFC 765 */
10981592Srgrimes	{ "USER", USER, STR1, 1,	"<sp> username" },
109975556Sgreen	{ "PASS", PASS, ZSTR1, 1,	"[<sp> [password]]" },
11001592Srgrimes	{ "ACCT", ACCT, STR1, 0,	"(specify account)" },
11011592Srgrimes	{ "SMNT", SMNT, ARGS, 0,	"(structure mount)" },
11021592Srgrimes	{ "REIN", REIN, ARGS, 0,	"(reinitialize server state)" },
11031592Srgrimes	{ "QUIT", QUIT, ARGS, 1,	"(terminate service)", },
1104101806Syar	{ "PORT", PORT, ARGS, 1,	"<sp> b0, b1, b2, b3, b4, b5" },
110556668Sshin	{ "LPRT", LPRT, ARGS, 1,	"<sp> af, hal, h1, h2, h3,..., pal, p1, p2..." },
110656668Sshin	{ "EPRT", EPRT, STR1, 1,	"<sp> |af|addr|port|" },
11071592Srgrimes	{ "PASV", PASV, ARGS, 1,	"(set server in passive mode)" },
110856668Sshin	{ "LPSV", LPSV, ARGS, 1,	"(set server in passive mode)" },
110956668Sshin	{ "EPSV", EPSV, ARGS, 1,	"[<sp> af|ALL]" },
1110101806Syar	{ "TYPE", TYPE, ARGS, 1,	"<sp> { A | E | I | L }" },
11111592Srgrimes	{ "STRU", STRU, ARGS, 1,	"(specify file structure)" },
11121592Srgrimes	{ "MODE", MODE, ARGS, 1,	"(specify transfer mode)" },
11131592Srgrimes	{ "RETR", RETR, STR1, 1,	"<sp> file-name" },
11141592Srgrimes	{ "STOR", STOR, STR1, 1,	"<sp> file-name" },
11151592Srgrimes	{ "APPE", APPE, STR1, 1,	"<sp> file-name" },
11161592Srgrimes	{ "MLFL", MLFL, OSTR, 0,	"(mail file)" },
11171592Srgrimes	{ "MAIL", MAIL, OSTR, 0,	"(mail to user)" },
11181592Srgrimes	{ "MSND", MSND, OSTR, 0,	"(mail send to terminal)" },
11191592Srgrimes	{ "MSOM", MSOM, OSTR, 0,	"(mail send to terminal or mailbox)" },
11201592Srgrimes	{ "MSAM", MSAM, OSTR, 0,	"(mail send to terminal and mailbox)" },
11211592Srgrimes	{ "MRSQ", MRSQ, OSTR, 0,	"(mail recipient scheme question)" },
11221592Srgrimes	{ "MRCP", MRCP, STR1, 0,	"(mail recipient)" },
11231592Srgrimes	{ "ALLO", ALLO, ARGS, 1,	"allocate storage (vacuously)" },
11241592Srgrimes	{ "REST", REST, ARGS, 1,	"<sp> offset (restart command)" },
11251592Srgrimes	{ "RNFR", RNFR, STR1, 1,	"<sp> file-name" },
11261592Srgrimes	{ "RNTO", RNTO, STR1, 1,	"<sp> file-name" },
11271592Srgrimes	{ "ABOR", ABOR, ARGS, 1,	"(abort operation)" },
11281592Srgrimes	{ "DELE", DELE, STR1, 1,	"<sp> file-name" },
11291592Srgrimes	{ "CWD",  CWD,  OSTR, 1,	"[ <sp> directory-name ]" },
11301592Srgrimes	{ "XCWD", CWD,	OSTR, 1,	"[ <sp> directory-name ]" },
11311592Srgrimes	{ "LIST", LIST, OSTR, 1,	"[ <sp> path-name ]" },
11321592Srgrimes	{ "NLST", NLST, OSTR, 1,	"[ <sp> path-name ]" },
11331592Srgrimes	{ "SITE", SITE, SITECMD, 1,	"site-cmd [ <sp> arguments ]" },
11341592Srgrimes	{ "SYST", SYST, ARGS, 1,	"(get type of operating system)" },
1135168849Syar	{ "FEAT", FEAT, ARGS, 1,	"(get extended features)" },
11361592Srgrimes	{ "STAT", STAT, OSTR, 1,	"[ <sp> path-name ]" },
11371592Srgrimes	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
11381592Srgrimes	{ "NOOP", NOOP, ARGS, 1,	"" },
11391592Srgrimes	{ "MKD",  MKD,  STR1, 1,	"<sp> path-name" },
11401592Srgrimes	{ "XMKD", MKD,  STR1, 1,	"<sp> path-name" },
11411592Srgrimes	{ "RMD",  RMD,  STR1, 1,	"<sp> path-name" },
11421592Srgrimes	{ "XRMD", RMD,  STR1, 1,	"<sp> path-name" },
11431592Srgrimes	{ "PWD",  PWD,  ARGS, 1,	"(return current directory)" },
11441592Srgrimes	{ "XPWD", PWD,  ARGS, 1,	"(return current directory)" },
11451592Srgrimes	{ "CDUP", CDUP, ARGS, 1,	"(change to parent directory)" },
11461592Srgrimes	{ "XCUP", CDUP, ARGS, 1,	"(change to parent directory)" },
11471592Srgrimes	{ "STOU", STOU, STR1, 1,	"<sp> file-name" },
11481592Srgrimes	{ "SIZE", SIZE, OSTR, 1,	"<sp> path-name" },
11491592Srgrimes	{ "MDTM", MDTM, OSTR, 1,	"<sp> path-name" },
11501592Srgrimes	{ NULL,   0,    0,    0,	0 }
11511592Srgrimes};
11521592Srgrimes
11531592Srgrimesstruct tab sitetab[] = {
115475535Sphk	{ "MD5", MDFIVE, STR1, 1,	"[ <sp> file-name ]" },
11551592Srgrimes	{ "UMASK", UMASK, ARGS, 1,	"[ <sp> umask ]" },
11561592Srgrimes	{ "IDLE", IDLE, ARGS, 1,	"[ <sp> maximum-idle-time ]" },
11571592Srgrimes	{ "CHMOD", CHMOD, NSTR, 1,	"<sp> mode <sp> file-name" },
11581592Srgrimes	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
11591592Srgrimes	{ NULL,   0,    0,    0,	0 }
11601592Srgrimes};
11611592Srgrimes
116290148Simpstatic char	*copy(char *);
1163110340Syarstatic char	*expglob(char *);
1164110340Syarstatic char	*exptilde(char *);
116590148Simpstatic void	 help(struct tab *, char *);
11661592Srgrimesstatic struct tab *
116790148Simp		 lookup(struct tab *, char *);
116890148Simpstatic int	 port_check(const char *);
1169159276Syar#ifdef INET6
117090148Simpstatic int	 port_check_v6(const char *);
1171159276Syar#endif
117290148Simpstatic void	 sizecmd(char *);
117390148Simpstatic void	 toolong(int);
1174159276Syar#ifdef INET6
117590148Simpstatic void	 v4map_data_dest(void);
1176159276Syar#endif
117790148Simpstatic int	 yylex(void);
11781592Srgrimes
11791592Srgrimesstatic struct tab *
118090148Simplookup(struct tab *p, char *cmd)
11811592Srgrimes{
11821592Srgrimes
11831592Srgrimes	for (; p->name != NULL; p++)
11841592Srgrimes		if (strcmp(cmd, p->name) == 0)
11851592Srgrimes			return (p);
11861592Srgrimes	return (0);
11871592Srgrimes}
11881592Srgrimes
11891592Srgrimes#include <arpa/telnet.h>
11901592Srgrimes
11911592Srgrimes/*
11921592Srgrimes * getline - a hacked up version of fgets to ignore TELNET escape codes.
11931592Srgrimes */
11941592Srgrimeschar *
119590148Simpgetline(char *s, int n, FILE *iop)
11961592Srgrimes{
11971592Srgrimes	int c;
11981592Srgrimes	register char *cs;
1199117352Syar	sigset_t sset, osset;
12001592Srgrimes
12011592Srgrimes	cs = s;
12021592Srgrimes/* tmpline may contain saved command from urgent mode interruption */
12031592Srgrimes	for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
12041592Srgrimes		*cs++ = tmpline[c];
12051592Srgrimes		if (tmpline[c] == '\n') {
12061592Srgrimes			*cs++ = '\0';
120776096Smarkm			if (ftpdebug)
12081592Srgrimes				syslog(LOG_DEBUG, "command: %s", s);
12091592Srgrimes			tmpline[0] = '\0';
12101592Srgrimes			return(s);
12111592Srgrimes		}
12121592Srgrimes		if (c == 0)
12131592Srgrimes			tmpline[0] = '\0';
12141592Srgrimes	}
1215117352Syar	/* SIGURG would interrupt stdio if not blocked during the read loop */
1216117352Syar	sigemptyset(&sset);
1217117352Syar	sigaddset(&sset, SIGURG);
1218117352Syar	sigprocmask(SIG_BLOCK, &sset, &osset);
12191592Srgrimes	while ((c = getc(iop)) != EOF) {
12201592Srgrimes		c &= 0377;
12211592Srgrimes		if (c == IAC) {
1222117351Syar			if ((c = getc(iop)) == EOF)
1223117351Syar				goto got_eof;
12241592Srgrimes			c &= 0377;
12251592Srgrimes			switch (c) {
12261592Srgrimes			case WILL:
12271592Srgrimes			case WONT:
1228117351Syar				if ((c = getc(iop)) == EOF)
1229117351Syar					goto got_eof;
12301592Srgrimes				printf("%c%c%c", IAC, DONT, 0377&c);
12311592Srgrimes				(void) fflush(stdout);
12321592Srgrimes				continue;
12331592Srgrimes			case DO:
12341592Srgrimes			case DONT:
1235117351Syar				if ((c = getc(iop)) == EOF)
1236117351Syar					goto got_eof;
12371592Srgrimes				printf("%c%c%c", IAC, WONT, 0377&c);
12381592Srgrimes				(void) fflush(stdout);
12391592Srgrimes				continue;
12401592Srgrimes			case IAC:
12411592Srgrimes				break;
12421592Srgrimes			default:
12431592Srgrimes				continue;	/* ignore command */
12441592Srgrimes			}
12451592Srgrimes		}
12461592Srgrimes		*cs++ = c;
12471592Srgrimes		if (--n <= 0 || c == '\n')
12481592Srgrimes			break;
12491592Srgrimes	}
1250117351Syargot_eof:
1251117352Syar	sigprocmask(SIG_SETMASK, &osset, NULL);
12521592Srgrimes	if (c == EOF && cs == s)
12531592Srgrimes		return (NULL);
12541592Srgrimes	*cs++ = '\0';
125576096Smarkm	if (ftpdebug) {
12561592Srgrimes		if (!guest && strncasecmp("pass ", s, 5) == 0) {
12571592Srgrimes			/* Don't syslog passwords */
12581592Srgrimes			syslog(LOG_DEBUG, "command: %.5s ???", s);
12591592Srgrimes		} else {
12601592Srgrimes			register char *cp;
12611592Srgrimes			register int len;
12621592Srgrimes
12631592Srgrimes			/* Don't syslog trailing CR-LF */
12641592Srgrimes			len = strlen(s);
12651592Srgrimes			cp = s + len - 1;
12661592Srgrimes			while (cp >= s && (*cp == '\n' || *cp == '\r')) {
12671592Srgrimes				--cp;
12681592Srgrimes				--len;
12691592Srgrimes			}
12701592Srgrimes			syslog(LOG_DEBUG, "command: %.*s", len, s);
12711592Srgrimes		}
12721592Srgrimes	}
12731592Srgrimes	return (s);
12741592Srgrimes}
12751592Srgrimes
12761592Srgrimesstatic void
127790148Simptoolong(int signo)
12781592Srgrimes{
12791592Srgrimes
12801592Srgrimes	reply(421,
12811592Srgrimes	    "Timeout (%d seconds): closing control connection.", timeout);
12821592Srgrimes	if (logging)
12831592Srgrimes		syslog(LOG_INFO, "User %s timed out after %d seconds",
12841592Srgrimes		    (pw ? pw -> pw_name : "unknown"), timeout);
12851592Srgrimes	dologout(1);
12861592Srgrimes}
12871592Srgrimes
12881592Srgrimesstatic int
128990148Simpyylex(void)
12901592Srgrimes{
129189935Syar	static int cpos;
12921592Srgrimes	char *cp, *cp2;
12931592Srgrimes	struct tab *p;
12941592Srgrimes	int n;
12951592Srgrimes	char c;
12961592Srgrimes
12971592Srgrimes	for (;;) {
12981592Srgrimes		switch (state) {
12991592Srgrimes
13001592Srgrimes		case CMD:
13011592Srgrimes			(void) signal(SIGALRM, toolong);
1302137659Syar			(void) alarm(timeout);
13031592Srgrimes			if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
13041592Srgrimes				reply(221, "You could at least say goodbye.");
13051592Srgrimes				dologout(0);
13061592Srgrimes			}
13071592Srgrimes			(void) alarm(0);
13081592Srgrimes#ifdef SETPROCTITLE
130929574Sphk			if (strncasecmp(cbuf, "PASS", 4) != 0)
13101592Srgrimes				setproctitle("%s: %s", proctitle, cbuf);
13111592Srgrimes#endif /* SETPROCTITLE */
13121592Srgrimes			if ((cp = strchr(cbuf, '\r'))) {
13131592Srgrimes				*cp++ = '\n';
13141592Srgrimes				*cp = '\0';
13151592Srgrimes			}
13161592Srgrimes			if ((cp = strpbrk(cbuf, " \n")))
13171592Srgrimes				cpos = cp - cbuf;
13181592Srgrimes			if (cpos == 0)
13191592Srgrimes				cpos = 4;
13201592Srgrimes			c = cbuf[cpos];
13211592Srgrimes			cbuf[cpos] = '\0';
13221592Srgrimes			upper(cbuf);
13231592Srgrimes			p = lookup(cmdtab, cbuf);
13241592Srgrimes			cbuf[cpos] = c;
13253776Spst			if (p != 0) {
1326102565Syar				yylval.s = p->name;
1327102565Syar				if (!p->implemented)
1328102565Syar					return (NOTIMPL); /* state remains CMD */
13291592Srgrimes				state = p->state;
13301592Srgrimes				return (p->token);
13311592Srgrimes			}
13321592Srgrimes			break;
13331592Srgrimes
13341592Srgrimes		case SITECMD:
13351592Srgrimes			if (cbuf[cpos] == ' ') {
13361592Srgrimes				cpos++;
13371592Srgrimes				return (SP);
13381592Srgrimes			}
13391592Srgrimes			cp = &cbuf[cpos];
13401592Srgrimes			if ((cp2 = strpbrk(cp, " \n")))
13411592Srgrimes				cpos = cp2 - cbuf;
13421592Srgrimes			c = cbuf[cpos];
13431592Srgrimes			cbuf[cpos] = '\0';
13441592Srgrimes			upper(cp);
13451592Srgrimes			p = lookup(sitetab, cp);
13461592Srgrimes			cbuf[cpos] = c;
13473777Spst			if (guest == 0 && p != 0) {
1348102565Syar				yylval.s = p->name;
1349102565Syar				if (!p->implemented) {
13501592Srgrimes					state = CMD;
1351102565Syar					return (NOTIMPL);
13521592Srgrimes				}
13531592Srgrimes				state = p->state;
13541592Srgrimes				return (p->token);
13551592Srgrimes			}
13561592Srgrimes			state = CMD;
13571592Srgrimes			break;
13581592Srgrimes
135975556Sgreen		case ZSTR1:
13601592Srgrimes		case OSTR:
13611592Srgrimes			if (cbuf[cpos] == '\n') {
13621592Srgrimes				state = CMD;
13631592Srgrimes				return (CRLF);
13641592Srgrimes			}
13651592Srgrimes			/* FALLTHROUGH */
13661592Srgrimes
13671592Srgrimes		case STR1:
13681592Srgrimes		dostr1:
13691592Srgrimes			if (cbuf[cpos] == ' ') {
13701592Srgrimes				cpos++;
137151979Salfred				state = state == OSTR ? STR2 : state+1;
13721592Srgrimes				return (SP);
13731592Srgrimes			}
13741592Srgrimes			break;
13751592Srgrimes
13761592Srgrimes		case ZSTR2:
13771592Srgrimes			if (cbuf[cpos] == '\n') {
13781592Srgrimes				state = CMD;
13791592Srgrimes				return (CRLF);
13801592Srgrimes			}
13811592Srgrimes			/* FALLTHROUGH */
13821592Srgrimes
13831592Srgrimes		case STR2:
13841592Srgrimes			cp = &cbuf[cpos];
13851592Srgrimes			n = strlen(cp);
13861592Srgrimes			cpos += n - 1;
13871592Srgrimes			/*
13881592Srgrimes			 * Make sure the string is nonempty and \n terminated.
13891592Srgrimes			 */
13901592Srgrimes			if (n > 1 && cbuf[cpos] == '\n') {
13911592Srgrimes				cbuf[cpos] = '\0';
13921592Srgrimes				yylval.s = copy(cp);
13931592Srgrimes				cbuf[cpos] = '\n';
13941592Srgrimes				state = ARGS;
13951592Srgrimes				return (STRING);
13961592Srgrimes			}
13971592Srgrimes			break;
13981592Srgrimes
13991592Srgrimes		case NSTR:
14001592Srgrimes			if (cbuf[cpos] == ' ') {
14011592Srgrimes				cpos++;
14021592Srgrimes				return (SP);
14031592Srgrimes			}
14041592Srgrimes			if (isdigit(cbuf[cpos])) {
14051592Srgrimes				cp = &cbuf[cpos];
14061592Srgrimes				while (isdigit(cbuf[++cpos]))
14071592Srgrimes					;
14081592Srgrimes				c = cbuf[cpos];
14091592Srgrimes				cbuf[cpos] = '\0';
141092272Smaxim				yylval.u.i = atoi(cp);
14111592Srgrimes				cbuf[cpos] = c;
14121592Srgrimes				state = STR1;
14131592Srgrimes				return (NUMBER);
14141592Srgrimes			}
14151592Srgrimes			state = STR1;
14161592Srgrimes			goto dostr1;
14171592Srgrimes
14181592Srgrimes		case ARGS:
14191592Srgrimes			if (isdigit(cbuf[cpos])) {
14201592Srgrimes				cp = &cbuf[cpos];
14211592Srgrimes				while (isdigit(cbuf[++cpos]))
14221592Srgrimes					;
14231592Srgrimes				c = cbuf[cpos];
14241592Srgrimes				cbuf[cpos] = '\0';
142592272Smaxim				yylval.u.i = atoi(cp);
1426137811Syar				yylval.u.o = strtoull(cp, NULL, 10);
14271592Srgrimes				cbuf[cpos] = c;
14281592Srgrimes				return (NUMBER);
14291592Srgrimes			}
143056668Sshin			if (strncasecmp(&cbuf[cpos], "ALL", 3) == 0
143156668Sshin			 && !isalnum(cbuf[cpos + 3])) {
143256668Sshin				cpos += 3;
143356668Sshin				return ALL;
143456668Sshin			}
14351592Srgrimes			switch (cbuf[cpos++]) {
14361592Srgrimes
14371592Srgrimes			case '\n':
14381592Srgrimes				state = CMD;
14391592Srgrimes				return (CRLF);
14401592Srgrimes
14411592Srgrimes			case ' ':
14421592Srgrimes				return (SP);
14431592Srgrimes
14441592Srgrimes			case ',':
14451592Srgrimes				return (COMMA);
14461592Srgrimes
14471592Srgrimes			case 'A':
14481592Srgrimes			case 'a':
14491592Srgrimes				return (A);
14501592Srgrimes
14511592Srgrimes			case 'B':
14521592Srgrimes			case 'b':
14531592Srgrimes				return (B);
14541592Srgrimes
14551592Srgrimes			case 'C':
14561592Srgrimes			case 'c':
14571592Srgrimes				return (C);
14581592Srgrimes
14591592Srgrimes			case 'E':
14601592Srgrimes			case 'e':
14611592Srgrimes				return (E);
14621592Srgrimes
14631592Srgrimes			case 'F':
14641592Srgrimes			case 'f':
14651592Srgrimes				return (F);
14661592Srgrimes
14671592Srgrimes			case 'I':
14681592Srgrimes			case 'i':
14691592Srgrimes				return (I);
14701592Srgrimes
14711592Srgrimes			case 'L':
14721592Srgrimes			case 'l':
14731592Srgrimes				return (L);
14741592Srgrimes
14751592Srgrimes			case 'N':
14761592Srgrimes			case 'n':
14771592Srgrimes				return (N);
14781592Srgrimes
14791592Srgrimes			case 'P':
14801592Srgrimes			case 'p':
14811592Srgrimes				return (P);
14821592Srgrimes
14831592Srgrimes			case 'R':
14841592Srgrimes			case 'r':
14851592Srgrimes				return (R);
14861592Srgrimes
14871592Srgrimes			case 'S':
14881592Srgrimes			case 's':
14891592Srgrimes				return (S);
14901592Srgrimes
14911592Srgrimes			case 'T':
14921592Srgrimes			case 't':
14931592Srgrimes				return (T);
14941592Srgrimes
14951592Srgrimes			}
14961592Srgrimes			break;
14971592Srgrimes
14981592Srgrimes		default:
149976096Smarkm			fatalerror("Unknown state in scanner.");
15001592Srgrimes		}
15011592Srgrimes		state = CMD;
150289935Syar		return (LEXERR);
15031592Srgrimes	}
15041592Srgrimes}
15051592Srgrimes
15061592Srgrimesvoid
150790148Simpupper(char *s)
15081592Srgrimes{
15091592Srgrimes	while (*s != '\0') {
15101592Srgrimes		if (islower(*s))
15111592Srgrimes			*s = toupper(*s);
15121592Srgrimes		s++;
15131592Srgrimes	}
15141592Srgrimes}
15151592Srgrimes
15161592Srgrimesstatic char *
151790148Simpcopy(char *s)
15181592Srgrimes{
15191592Srgrimes	char *p;
15201592Srgrimes
1521137659Syar	p = malloc(strlen(s) + 1);
15221592Srgrimes	if (p == NULL)
152376096Smarkm		fatalerror("Ran out of memory.");
15241592Srgrimes	(void) strcpy(p, s);
15251592Srgrimes	return (p);
15261592Srgrimes}
15271592Srgrimes
15281592Srgrimesstatic void
152990148Simphelp(struct tab *ctab, char *s)
15301592Srgrimes{
15311592Srgrimes	struct tab *c;
15321592Srgrimes	int width, NCMDS;
15331592Srgrimes	char *type;
15341592Srgrimes
15351592Srgrimes	if (ctab == sitetab)
15361592Srgrimes		type = "SITE ";
15371592Srgrimes	else
15381592Srgrimes		type = "";
15391592Srgrimes	width = 0, NCMDS = 0;
15401592Srgrimes	for (c = ctab; c->name != NULL; c++) {
15411592Srgrimes		int len = strlen(c->name);
15421592Srgrimes
15431592Srgrimes		if (len > width)
15441592Srgrimes			width = len;
15451592Srgrimes		NCMDS++;
15461592Srgrimes	}
15471592Srgrimes	width = (width + 8) &~ 7;
15481592Srgrimes	if (s == 0) {
15491592Srgrimes		int i, j, w;
15501592Srgrimes		int columns, lines;
15511592Srgrimes
15521592Srgrimes		lreply(214, "The following %scommands are recognized %s.",
15531592Srgrimes		    type, "(* =>'s unimplemented)");
15541592Srgrimes		columns = 76 / width;
15551592Srgrimes		if (columns == 0)
15561592Srgrimes			columns = 1;
15571592Srgrimes		lines = (NCMDS + columns - 1) / columns;
15581592Srgrimes		for (i = 0; i < lines; i++) {
15591592Srgrimes			printf("   ");
15601592Srgrimes			for (j = 0; j < columns; j++) {
15611592Srgrimes				c = ctab + j * lines + i;
15621592Srgrimes				printf("%s%c", c->name,
15631592Srgrimes					c->implemented ? ' ' : '*');
15641592Srgrimes				if (c + lines >= &ctab[NCMDS])
15651592Srgrimes					break;
15661592Srgrimes				w = strlen(c->name) + 1;
15671592Srgrimes				while (w < width) {
15681592Srgrimes					putchar(' ');
15691592Srgrimes					w++;
15701592Srgrimes				}
15711592Srgrimes			}
15721592Srgrimes			printf("\r\n");
15731592Srgrimes		}
15741592Srgrimes		(void) fflush(stdout);
1575110037Syar		if (hostinfo)
1576110037Syar			reply(214, "Direct comments to ftp-bugs@%s.", hostname);
1577110037Syar		else
1578110037Syar			reply(214, "End.");
15791592Srgrimes		return;
15801592Srgrimes	}
15811592Srgrimes	upper(s);
15821592Srgrimes	c = lookup(ctab, s);
1583132931Syar	if (c == NULL) {
15841592Srgrimes		reply(502, "Unknown command %s.", s);
15851592Srgrimes		return;
15861592Srgrimes	}
15871592Srgrimes	if (c->implemented)
15881592Srgrimes		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
15891592Srgrimes	else
15901592Srgrimes		reply(214, "%s%-*s\t%s; unimplemented.", type, width,
15911592Srgrimes		    c->name, c->help);
15921592Srgrimes}
15931592Srgrimes
15941592Srgrimesstatic void
159590148Simpsizecmd(char *filename)
15961592Srgrimes{
15971592Srgrimes	switch (type) {
15981592Srgrimes	case TYPE_L:
15991592Srgrimes	case TYPE_I: {
16001592Srgrimes		struct stat stbuf;
160163350Sdes		if (stat(filename, &stbuf) < 0)
160263350Sdes			perror_reply(550, filename);
160363350Sdes		else if (!S_ISREG(stbuf.st_mode))
16041592Srgrimes			reply(550, "%s: not a plain file.", filename);
16051592Srgrimes		else
1606132929Syar			reply(213, "%jd", (intmax_t)stbuf.st_size);
16071592Srgrimes		break; }
16081592Srgrimes	case TYPE_A: {
16091592Srgrimes		FILE *fin;
16101592Srgrimes		int c;
16111592Srgrimes		off_t count;
16121592Srgrimes		struct stat stbuf;
16131592Srgrimes		fin = fopen(filename, "r");
16141592Srgrimes		if (fin == NULL) {
16151592Srgrimes			perror_reply(550, filename);
16161592Srgrimes			return;
16171592Srgrimes		}
161863350Sdes		if (fstat(fileno(fin), &stbuf) < 0) {
161963350Sdes			perror_reply(550, filename);
162063350Sdes			(void) fclose(fin);
162163350Sdes			return;
162263350Sdes		} else if (!S_ISREG(stbuf.st_mode)) {
16231592Srgrimes			reply(550, "%s: not a plain file.", filename);
16241592Srgrimes			(void) fclose(fin);
16251592Srgrimes			return;
1626101034Syar		} else if (stbuf.st_size > MAXASIZE) {
1627101034Syar			reply(550, "%s: too large for type A SIZE.", filename);
1628101034Syar			(void) fclose(fin);
1629101034Syar			return;
16301592Srgrimes		}
16311592Srgrimes
16321592Srgrimes		count = 0;
16331592Srgrimes		while((c=getc(fin)) != EOF) {
16341592Srgrimes			if (c == '\n')	/* will get expanded to \r\n */
16351592Srgrimes				count++;
16361592Srgrimes			count++;
16371592Srgrimes		}
16381592Srgrimes		(void) fclose(fin);
16391592Srgrimes
1640132929Syar		reply(213, "%jd", (intmax_t)count);
16411592Srgrimes		break; }
16421592Srgrimes	default:
1643100684Syar		reply(504, "SIZE not implemented for type %s.",
1644100684Syar		           typenames[type]);
16451592Srgrimes	}
16461592Srgrimes}
164756668Sshin
164856668Sshin/* Return 1, if port check is done. Return 0, if not yet. */
164956668Sshinstatic int
165090148Simpport_check(const char *pcmd)
165156668Sshin{
165256668Sshin	if (his_addr.su_family == AF_INET) {
165356668Sshin		if (data_dest.su_family != AF_INET) {
165456668Sshin			usedefault = 1;
165556668Sshin			reply(500, "Invalid address rejected.");
165656668Sshin			return 1;
165756668Sshin		}
165856668Sshin		if (paranoid &&
165956668Sshin		    ((ntohs(data_dest.su_port) < IPPORT_RESERVED) ||
166056668Sshin		     memcmp(&data_dest.su_sin.sin_addr,
166156668Sshin			    &his_addr.su_sin.sin_addr,
166256668Sshin			    sizeof(data_dest.su_sin.sin_addr)))) {
166356668Sshin			usedefault = 1;
166456668Sshin			reply(500, "Illegal PORT range rejected.");
166556668Sshin		} else {
166656668Sshin			usedefault = 0;
166756668Sshin			if (pdata >= 0) {
166856668Sshin				(void) close(pdata);
166956668Sshin				pdata = -1;
167056668Sshin			}
167156668Sshin			reply(200, "%s command successful.", pcmd);
167256668Sshin		}
167356668Sshin		return 1;
167456668Sshin	}
167556668Sshin	return 0;
167656668Sshin}
167756668Sshin
167870102Sphkstatic int
167990148Simpcheck_login1(void)
168070102Sphk{
168170102Sphk	if (logged_in)
168270102Sphk		return 1;
168370102Sphk	else {
168470102Sphk		reply(530, "Please login with USER and PASS.");
168570102Sphk		return 0;
168670102Sphk	}
168770102Sphk}
168870102Sphk
1689110340Syar/*
1690110340Syar * Replace leading "~user" in a pathname by the user's login directory.
1691110340Syar * Returned string will be in a freshly malloced buffer unless it's NULL.
1692110340Syar */
1693110340Syarstatic char *
1694110340Syarexptilde(char *s)
1695110340Syar{
1696110340Syar	char *p, *q;
1697110340Syar	char *path, *user;
1698110340Syar	struct passwd *ppw;
1699110340Syar
1700110340Syar	if ((p = strdup(s)) == NULL)
1701110340Syar		return (NULL);
1702110340Syar	if (*p != '~')
1703110340Syar		return (p);
1704110340Syar
1705110340Syar	user = p + 1;	/* skip tilde */
1706110340Syar	if ((path = strchr(p, '/')) != NULL)
1707110340Syar		*(path++) = '\0'; /* separate ~user from the rest of path */
1708110378Syar	if (*user == '\0') /* no user specified, use the current user */
1709110378Syar		user = pw->pw_name;
1710110378Syar	/* read passwd even for the current user since we may be chrooted */
1711110378Syar	if ((ppw = getpwnam(user)) != NULL) {
1712110340Syar		/* user found, substitute login directory for ~user */
1713110340Syar		if (path)
1714110340Syar			asprintf(&q, "%s/%s", ppw->pw_dir, path);
1715110340Syar		else
1716110340Syar			q = strdup(ppw->pw_dir);
1717110340Syar		free(p);
1718110340Syar		p = q;
1719110340Syar	} else {
1720110340Syar		/* user not found, undo the damage */
1721110340Syar		if (path)
1722110340Syar			path[-1] = '/';
1723110340Syar	}
1724110340Syar	return (p);
1725110340Syar}
1726110340Syar
1727110340Syar/*
1728110340Syar * Expand glob(3) patterns possibly present in a pathname.
1729110340Syar * Avoid expanding to a pathname including '\r' or '\n' in order to
1730110340Syar * not disrupt the FTP protocol.
1731110340Syar * The expansion found must be unique.
1732110340Syar * Return the result as a malloced string, or NULL if an error occured.
1733110340Syar *
1734110340Syar * Problem: this production is used for all pathname
1735110340Syar * processing, but only gives a 550 error reply.
1736110340Syar * This is a valid reply in some cases but not in others.
1737110340Syar */
1738110340Syarstatic char *
1739110340Syarexpglob(char *s)
1740110340Syar{
1741110340Syar	char *p, **pp, *rval;
1742110340Syar	int flags = GLOB_BRACE | GLOB_NOCHECK;
1743110340Syar	int n;
1744110340Syar	glob_t gl;
1745110340Syar
1746110340Syar	memset(&gl, 0, sizeof(gl));
1747110340Syar	flags |= GLOB_LIMIT;
1748110340Syar	gl.gl_matchc = MAXGLOBARGS;
1749110340Syar	if (glob(s, flags, NULL, &gl) == 0 && gl.gl_pathc != 0) {
1750110340Syar		for (pp = gl.gl_pathv, p = NULL, n = 0; *pp; pp++)
1751110340Syar			if (*(*pp + strcspn(*pp, "\r\n")) == '\0') {
1752110340Syar				p = *pp;
1753110340Syar				n++;
1754110340Syar			}
1755110340Syar		if (n == 0)
1756110340Syar			rval = strdup(s);
1757110340Syar		else if (n == 1)
1758110340Syar			rval = strdup(p);
1759110340Syar		else {
1760137852Syar			reply(550, "Wildcard is ambiguous.");
1761110340Syar			rval = NULL;
1762110340Syar		}
1763110340Syar	} else {
1764137852Syar		reply(550, "Wildcard expansion error.");
1765110340Syar		rval = NULL;
1766110340Syar	}
1767110340Syar	globfree(&gl);
1768110340Syar	return (rval);
1769110340Syar}
1770110340Syar
177156668Sshin#ifdef INET6
177256668Sshin/* Return 1, if port check is done. Return 0, if not yet. */
177356668Sshinstatic int
177490148Simpport_check_v6(const char *pcmd)
177556668Sshin{
177656668Sshin	if (his_addr.su_family == AF_INET6) {
177756668Sshin		if (IN6_IS_ADDR_V4MAPPED(&his_addr.su_sin6.sin6_addr))
177856668Sshin			/* Convert data_dest into v4 mapped sockaddr.*/
177956668Sshin			v4map_data_dest();
178056668Sshin		if (data_dest.su_family != AF_INET6) {
178156668Sshin			usedefault = 1;
178256668Sshin			reply(500, "Invalid address rejected.");
178356668Sshin			return 1;
178456668Sshin		}
178556668Sshin		if (paranoid &&
178656668Sshin		    ((ntohs(data_dest.su_port) < IPPORT_RESERVED) ||
178756668Sshin		     memcmp(&data_dest.su_sin6.sin6_addr,
178856668Sshin			    &his_addr.su_sin6.sin6_addr,
178956668Sshin			    sizeof(data_dest.su_sin6.sin6_addr)))) {
179056668Sshin			usedefault = 1;
179156668Sshin			reply(500, "Illegal PORT range rejected.");
179256668Sshin		} else {
179356668Sshin			usedefault = 0;
179456668Sshin			if (pdata >= 0) {
179556668Sshin				(void) close(pdata);
179656668Sshin				pdata = -1;
179756668Sshin			}
179856668Sshin			reply(200, "%s command successful.", pcmd);
179956668Sshin		}
180056668Sshin		return 1;
180156668Sshin	}
180256668Sshin	return 0;
180356668Sshin}
180456668Sshin
180556668Sshinstatic void
180690148Simpv4map_data_dest(void)
180756668Sshin{
180856668Sshin	struct in_addr savedaddr;
180956668Sshin	int savedport;
181056668Sshin
181156668Sshin	if (data_dest.su_family != AF_INET) {
181256668Sshin		usedefault = 1;
181356668Sshin		reply(500, "Invalid address rejected.");
181456668Sshin		return;
181556668Sshin	}
181656668Sshin
181756668Sshin	savedaddr = data_dest.su_sin.sin_addr;
181856668Sshin	savedport = data_dest.su_port;
181956668Sshin
182056668Sshin	memset(&data_dest, 0, sizeof(data_dest));
182156668Sshin	data_dest.su_sin6.sin6_len = sizeof(struct sockaddr_in6);
182256668Sshin	data_dest.su_sin6.sin6_family = AF_INET6;
182356668Sshin	data_dest.su_sin6.sin6_port = savedport;
182456668Sshin	memset((caddr_t)&data_dest.su_sin6.sin6_addr.s6_addr[10], 0xff, 2);
182556668Sshin	memcpy((caddr_t)&data_dest.su_sin6.sin6_addr.s6_addr[12],
182656668Sshin	       (caddr_t)&savedaddr, sizeof(savedaddr));
182756668Sshin}
182856668Sshin#endif
1829