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.
13262435Sbrueffer * 3. Neither the name of the University nor the names of its contributors
141592Srgrimes *    may be used to endorse or promote products derived from this software
151592Srgrimes *    without specific prior written permission.
161592Srgrimes *
171592Srgrimes * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
181592Srgrimes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
191592Srgrimes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
201592Srgrimes * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
211592Srgrimes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
221592Srgrimes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
231592Srgrimes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
241592Srgrimes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
251592Srgrimes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
261592Srgrimes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
271592Srgrimes * SUCH DAMAGE.
281592Srgrimes *
291592Srgrimes *	@(#)ftpcmd.y	8.3 (Berkeley) 4/6/94
301592Srgrimes */
311592Srgrimes
321592Srgrimes/*
331592Srgrimes * Grammar for FTP commands.
341592Srgrimes * See RFC 959.
351592Srgrimes */
361592Srgrimes
371592Srgrimes%{
381592Srgrimes
391592Srgrimes#ifndef lint
4031329Scharnier#if 0
411592Srgrimesstatic char sccsid[] = "@(#)ftpcmd.y	8.3 (Berkeley) 4/6/94";
4231329Scharnier#endif
431592Srgrimes#endif /* not lint */
441592Srgrimes
45137859Syar#include <sys/cdefs.h>
46137859Syar__FBSDID("$FreeBSD$");
47137859Syar
481592Srgrimes#include <sys/param.h>
491592Srgrimes#include <sys/socket.h>
501592Srgrimes#include <sys/stat.h>
511592Srgrimes
521592Srgrimes#include <netinet/in.h>
531592Srgrimes#include <arpa/ftp.h>
541592Srgrimes
551592Srgrimes#include <ctype.h>
561592Srgrimes#include <errno.h>
571592Srgrimes#include <glob.h>
5892090Smaxim#include <libutil.h>
5992272Smaxim#include <limits.h>
6092090Smaxim#include <md5.h>
6156668Sshin#include <netdb.h>
621592Srgrimes#include <pwd.h>
631592Srgrimes#include <signal.h>
64132929Syar#include <stdint.h>
651592Srgrimes#include <stdio.h>
661592Srgrimes#include <stdlib.h>
671592Srgrimes#include <string.h>
681592Srgrimes#include <syslog.h>
691592Srgrimes#include <time.h>
701592Srgrimes#include <unistd.h>
711592Srgrimes
721592Srgrimes#include "extern.h"
73109380Syar#include "pathnames.h"
741592Srgrimes
751592Srgrimesoff_t	restart_point;
761592Srgrimes
771592Srgrimesstatic	int cmd_type;
781592Srgrimesstatic	int cmd_form;
791592Srgrimesstatic	int cmd_bytesz;
8089935Syarstatic	int state;
811592Srgrimeschar	cbuf[512];
82132931Syarchar	*fromname = NULL;
831592Srgrimes
841592Srgrimes%}
851592Srgrimes
861592Srgrimes%union {
8792272Smaxim	struct {
8892272Smaxim		off_t	o;
8992272Smaxim		int	i;
9092272Smaxim	} u;
911592Srgrimes	char   *s;
921592Srgrimes}
931592Srgrimes
941592Srgrimes%token
951592Srgrimes	A	B	C	E	F	I
961592Srgrimes	L	N	P	R	S	T
9756668Sshin	ALL
981592Srgrimes
991592Srgrimes	SP	CRLF	COMMA
1001592Srgrimes
1011592Srgrimes	USER	PASS	ACCT	REIN	QUIT	PORT
1021592Srgrimes	PASV	TYPE	STRU	MODE	RETR	STOR
1031592Srgrimes	APPE	MLFL	MAIL	MSND	MSOM	MSAM
1041592Srgrimes	MRSQ	MRCP	ALLO	REST	RNFR	RNTO
1051592Srgrimes	ABOR	DELE	CWD	LIST	NLST	SITE
1061592Srgrimes	STAT	HELP	NOOP	MKD	RMD	PWD
1071592Srgrimes	CDUP	STOU	SMNT	SYST	SIZE	MDTM
108168849Syar	LPRT	LPSV	EPRT	EPSV	FEAT
1091592Srgrimes
11075535Sphk	UMASK	IDLE	CHMOD	MDFIVE
1111592Srgrimes
112102565Syar	LEXERR	NOTIMPL
1131592Srgrimes
1141592Srgrimes%token	<s> STRING
11592272Smaxim%token	<u> NUMBER
1161592Srgrimes
11792272Smaxim%type	<u.i> check_login octal_number byte_size
11892272Smaxim%type	<u.i> check_login_ro check_login_epsv
11992272Smaxim%type	<u.i> struct_code mode_code type_code form_code
12075567Speter%type	<s> pathstring pathname password username
121102565Syar%type	<s> ALL NOTIMPL
1221592Srgrimes
1231592Srgrimes%start	cmd_list
1241592Srgrimes
1251592Srgrimes%%
1261592Srgrimes
1271592Srgrimescmd_list
1281592Srgrimes	: /* empty */
1291592Srgrimes	| cmd_list cmd
1301592Srgrimes		{
13188935Sdwmalone			if (fromname)
13288935Sdwmalone				free(fromname);
133132931Syar			fromname = NULL;
134132930Syar			restart_point = 0;
1351592Srgrimes		}
1361592Srgrimes	| cmd_list rcmd
1371592Srgrimes	;
1381592Srgrimes
1391592Srgrimescmd
1401592Srgrimes	: USER SP username CRLF
1411592Srgrimes		{
1421592Srgrimes			user($3);
1431592Srgrimes			free($3);
1441592Srgrimes		}
1451592Srgrimes	| PASS SP password CRLF
1461592Srgrimes		{
1471592Srgrimes			pass($3);
1481592Srgrimes			free($3);
1491592Srgrimes		}
15075556Sgreen	| PASS CRLF
15175556Sgreen		{
15275556Sgreen			pass("");
15375556Sgreen		}
15417433Spst	| PORT check_login SP host_port CRLF
1551592Srgrimes		{
15656668Sshin			if (epsvall) {
157137852Syar				reply(501, "No PORT allowed after EPSV ALL.");
15856668Sshin				goto port_done;
15956668Sshin			}
16056668Sshin			if (!$2)
16156668Sshin				goto port_done;
16256668Sshin			if (port_check("PORT") == 1)
16356668Sshin				goto port_done;
16456668Sshin#ifdef INET6
16556668Sshin			if ((his_addr.su_family != AF_INET6 ||
16656668Sshin			     !IN6_IS_ADDR_V4MAPPED(&his_addr.su_sin6.sin6_addr))) {
16756668Sshin				/* shoud never happen */
16856668Sshin				usedefault = 1;
16956668Sshin				reply(500, "Invalid address rejected.");
17056668Sshin				goto port_done;
17156668Sshin			}
17256668Sshin			port_check_v6("pcmd");
17356668Sshin#endif
17456668Sshin		port_done:
175132925Syar			;
17656668Sshin		}
17756668Sshin	| LPRT check_login SP host_long_port CRLF
17856668Sshin		{
17956668Sshin			if (epsvall) {
180137852Syar				reply(501, "No LPRT allowed after EPSV ALL.");
18156668Sshin				goto lprt_done;
18256668Sshin			}
18356668Sshin			if (!$2)
18456668Sshin				goto lprt_done;
18556668Sshin			if (port_check("LPRT") == 1)
18656668Sshin				goto lprt_done;
18756668Sshin#ifdef INET6
18856668Sshin			if (his_addr.su_family != AF_INET6) {
18956668Sshin				usedefault = 1;
19056668Sshin				reply(500, "Invalid address rejected.");
19156668Sshin				goto lprt_done;
19256668Sshin			}
19356668Sshin			if (port_check_v6("LPRT") == 1)
19456668Sshin				goto lprt_done;
19556668Sshin#endif
19656668Sshin		lprt_done:
197132925Syar			;
19856668Sshin		}
19956668Sshin	| EPRT check_login SP STRING CRLF
20056668Sshin		{
20156668Sshin			char delim;
20256668Sshin			char *tmp = NULL;
20356668Sshin			char *p, *q;
20456668Sshin			char *result[3];
20556668Sshin			struct addrinfo hints;
20656668Sshin			struct addrinfo *res;
20756668Sshin			int i;
20856668Sshin
20956668Sshin			if (epsvall) {
210137852Syar				reply(501, "No EPRT allowed after EPSV ALL.");
21156668Sshin				goto eprt_done;
21256668Sshin			}
21356668Sshin			if (!$2)
21456668Sshin				goto eprt_done;
21556668Sshin
21656668Sshin			memset(&data_dest, 0, sizeof(data_dest));
21756668Sshin			tmp = strdup($4);
21876096Smarkm			if (ftpdebug)
21956668Sshin				syslog(LOG_DEBUG, "%s", tmp);
22056668Sshin			if (!tmp) {
22176096Smarkm				fatalerror("not enough core");
22256668Sshin				/*NOTREACHED*/
22356668Sshin			}
22456668Sshin			p = tmp;
22556668Sshin			delim = p[0];
22656668Sshin			p++;
22756668Sshin			memset(result, 0, sizeof(result));
22856668Sshin			for (i = 0; i < 3; i++) {
22956668Sshin				q = strchr(p, delim);
23056668Sshin				if (!q || *q != delim) {
23156668Sshin		parsefail:
23256668Sshin					reply(500,
23356668Sshin						"Invalid argument, rejected.");
23456668Sshin					if (tmp)
23556668Sshin						free(tmp);
23617433Spst					usedefault = 1;
23756668Sshin					goto eprt_done;
23817433Spst				}
23956668Sshin				*q++ = '\0';
24056668Sshin				result[i] = p;
24176096Smarkm				if (ftpdebug)
24256668Sshin					syslog(LOG_DEBUG, "%d: %s", i, p);
24356668Sshin				p = q;
2441592Srgrimes			}
24556668Sshin
24656668Sshin			/* some more sanity check */
24756668Sshin			p = result[0];
24856668Sshin			while (*p) {
24956668Sshin				if (!isdigit(*p))
25056668Sshin					goto parsefail;
25156668Sshin				p++;
25256668Sshin			}
25356668Sshin			p = result[2];
25456668Sshin			while (*p) {
25556668Sshin				if (!isdigit(*p))
25656668Sshin					goto parsefail;
25756668Sshin				p++;
25856668Sshin			}
25956668Sshin
26056668Sshin			/* grab address */
26156668Sshin			memset(&hints, 0, sizeof(hints));
26256668Sshin			if (atoi(result[0]) == 1)
26356668Sshin				hints.ai_family = PF_INET;
26456668Sshin#ifdef INET6
26556668Sshin			else if (atoi(result[0]) == 2)
26656668Sshin				hints.ai_family = PF_INET6;
26756668Sshin#endif
26856668Sshin			else
26956668Sshin				hints.ai_family = PF_UNSPEC;	/*XXX*/
27056668Sshin			hints.ai_socktype = SOCK_STREAM;
27156668Sshin			i = getaddrinfo(result[1], result[2], &hints, &res);
27256668Sshin			if (i)
27356668Sshin				goto parsefail;
27456668Sshin			memcpy(&data_dest, res->ai_addr, res->ai_addrlen);
27556668Sshin#ifdef INET6
27656668Sshin			if (his_addr.su_family == AF_INET6
27756668Sshin			    && data_dest.su_family == AF_INET6) {
27856668Sshin				/* XXX more sanity checks! */
27956668Sshin				data_dest.su_sin6.sin6_scope_id =
28056668Sshin					his_addr.su_sin6.sin6_scope_id;
28156668Sshin			}
28256668Sshin#endif
28356668Sshin			free(tmp);
28456668Sshin			tmp = NULL;
28556668Sshin
28656668Sshin			if (port_check("EPRT") == 1)
28756668Sshin				goto eprt_done;
28856668Sshin#ifdef INET6
28956668Sshin			if (his_addr.su_family != AF_INET6) {
29056668Sshin				usedefault = 1;
29156668Sshin				reply(500, "Invalid address rejected.");
29256668Sshin				goto eprt_done;
29356668Sshin			}
29456668Sshin			if (port_check_v6("EPRT") == 1)
29556668Sshin				goto eprt_done;
29656668Sshin#endif
29788935Sdwmalone		eprt_done:
29888935Sdwmalone			free($4);
2991592Srgrimes		}
30017433Spst	| PASV check_login CRLF
3011592Srgrimes		{
30256668Sshin			if (epsvall)
303137852Syar				reply(501, "No PASV allowed after EPSV ALL.");
30456668Sshin			else if ($2)
30517433Spst				passive();
3061592Srgrimes		}
30756668Sshin	| LPSV check_login CRLF
30856668Sshin		{
30956668Sshin			if (epsvall)
310137852Syar				reply(501, "No LPSV allowed after EPSV ALL.");
31156668Sshin			else if ($2)
31256668Sshin				long_passive("LPSV", PF_UNSPEC);
31356668Sshin		}
31470102Sphk	| EPSV check_login_epsv SP NUMBER CRLF
31556668Sshin		{
31656668Sshin			if ($2) {
31756668Sshin				int pf;
31892272Smaxim				switch ($4.i) {
31956668Sshin				case 1:
32056668Sshin					pf = PF_INET;
32156668Sshin					break;
32256668Sshin#ifdef INET6
32356668Sshin				case 2:
32456668Sshin					pf = PF_INET6;
32556668Sshin					break;
32656668Sshin#endif
32756668Sshin				default:
32856668Sshin					pf = -1;	/*junk value*/
32956668Sshin					break;
33056668Sshin				}
33156668Sshin				long_passive("EPSV", pf);
33256668Sshin			}
33356668Sshin		}
33470102Sphk	| EPSV check_login_epsv SP ALL CRLF
33556668Sshin		{
33656668Sshin			if ($2) {
337137852Syar				reply(200, "EPSV ALL command successful.");
33856668Sshin				epsvall++;
33956668Sshin			}
34056668Sshin		}
34170102Sphk	| EPSV check_login_epsv CRLF
34256668Sshin		{
34356668Sshin			if ($2)
34456668Sshin				long_passive("EPSV", PF_UNSPEC);
34556668Sshin		}
34671278Sjedgar	| TYPE check_login SP type_code CRLF
3471592Srgrimes		{
34871278Sjedgar			if ($2) {
34971278Sjedgar				switch (cmd_type) {
3501592Srgrimes
35171278Sjedgar				case TYPE_A:
35271278Sjedgar					if (cmd_form == FORM_N) {
35371278Sjedgar						reply(200, "Type set to A.");
35471278Sjedgar						type = cmd_type;
35571278Sjedgar						form = cmd_form;
35671278Sjedgar					} else
35771278Sjedgar						reply(504, "Form must be N.");
35871278Sjedgar					break;
3591592Srgrimes
36071278Sjedgar				case TYPE_E:
36171278Sjedgar					reply(504, "Type E not implemented.");
36271278Sjedgar					break;
3631592Srgrimes
36471278Sjedgar				case TYPE_I:
36571278Sjedgar					reply(200, "Type set to I.");
36671278Sjedgar					type = cmd_type;
36771278Sjedgar					break;
3681592Srgrimes
36971278Sjedgar				case TYPE_L:
370103949Smike#if CHAR_BIT == 8
37171278Sjedgar					if (cmd_bytesz == 8) {
37271278Sjedgar						reply(200,
37371278Sjedgar						    "Type set to L (byte size 8).");
37471278Sjedgar						type = cmd_type;
37571278Sjedgar					} else
37671278Sjedgar						reply(504, "Byte size must be 8.");
377103949Smike#else /* CHAR_BIT == 8 */
378103949Smike					UNIMPLEMENTED for CHAR_BIT != 8
379103949Smike#endif /* CHAR_BIT == 8 */
38071278Sjedgar				}
3811592Srgrimes			}
3821592Srgrimes		}
38371278Sjedgar	| STRU check_login SP struct_code CRLF
3841592Srgrimes		{
38571278Sjedgar			if ($2) {
38671278Sjedgar				switch ($4) {
3871592Srgrimes
38871278Sjedgar				case STRU_F:
389137852Syar					reply(200, "STRU F accepted.");
39071278Sjedgar					break;
3911592Srgrimes
39271278Sjedgar				default:
39371278Sjedgar					reply(504, "Unimplemented STRU type.");
39471278Sjedgar				}
3951592Srgrimes			}
3961592Srgrimes		}
39771278Sjedgar	| MODE check_login SP mode_code CRLF
3981592Srgrimes		{
39971278Sjedgar			if ($2) {
40071278Sjedgar				switch ($4) {
4011592Srgrimes
40271278Sjedgar				case MODE_S:
403137852Syar					reply(200, "MODE S accepted.");
40471278Sjedgar					break;
40571278Sjedgar
40671278Sjedgar				default:
40771278Sjedgar					reply(502, "Unimplemented MODE type.");
40871278Sjedgar				}
4091592Srgrimes			}
4101592Srgrimes		}
41171278Sjedgar	| ALLO check_login SP NUMBER CRLF
4121592Srgrimes		{
41371278Sjedgar			if ($2) {
41471278Sjedgar				reply(202, "ALLO command ignored.");
41571278Sjedgar			}
4161592Srgrimes		}
41771278Sjedgar	| ALLO check_login SP NUMBER SP R SP NUMBER CRLF
4181592Srgrimes		{
41971278Sjedgar			if ($2) {
42071278Sjedgar				reply(202, "ALLO command ignored.");
42171278Sjedgar			}
4221592Srgrimes		}
4231592Srgrimes	| RETR check_login SP pathname CRLF
4241592Srgrimes		{
42582796Ssheldonh			if (noretr || (guest && noguestretr))
426137852Syar				reply(500, "RETR command disabled.");
42782460Snik			else if ($2 && $4 != NULL)
428132931Syar				retrieve(NULL, $4);
42982460Snik
4301592Srgrimes			if ($4 != NULL)
4311592Srgrimes				free($4);
4321592Srgrimes		}
43370102Sphk	| STOR check_login_ro SP pathname CRLF
4341592Srgrimes		{
4351592Srgrimes			if ($2 && $4 != NULL)
4361592Srgrimes				store($4, "w", 0);
4371592Srgrimes			if ($4 != NULL)
4381592Srgrimes				free($4);
4391592Srgrimes		}
44070102Sphk	| APPE check_login_ro SP pathname CRLF
4411592Srgrimes		{
4421592Srgrimes			if ($2 && $4 != NULL)
4431592Srgrimes				store($4, "a", 0);
4441592Srgrimes			if ($4 != NULL)
4451592Srgrimes				free($4);
4461592Srgrimes		}
4471592Srgrimes	| NLST check_login CRLF
4481592Srgrimes		{
4491592Srgrimes			if ($2)
4501592Srgrimes				send_file_list(".");
4511592Srgrimes		}
452101395Syar	| NLST check_login SP pathstring CRLF
4531592Srgrimes		{
454101395Syar			if ($2)
4551592Srgrimes				send_file_list($4);
456101395Syar			free($4);
4571592Srgrimes		}
4581592Srgrimes	| LIST check_login CRLF
4591592Srgrimes		{
4601592Srgrimes			if ($2)
461109380Syar				retrieve(_PATH_LS " -lgA", "");
4621592Srgrimes		}
46375567Speter	| LIST check_login SP pathstring CRLF
4641592Srgrimes		{
465101395Syar			if ($2)
466109380Syar				retrieve(_PATH_LS " -lgA %s", $4);
467101395Syar			free($4);
4681592Srgrimes		}
4691592Srgrimes	| STAT check_login SP pathname CRLF
4701592Srgrimes		{
4711592Srgrimes			if ($2 && $4 != NULL)
4721592Srgrimes				statfilecmd($4);
4731592Srgrimes			if ($4 != NULL)
4741592Srgrimes				free($4);
4751592Srgrimes		}
47671278Sjedgar	| STAT check_login CRLF
4771592Srgrimes		{
47871278Sjedgar			if ($2) {
47971278Sjedgar				statcmd();
48071278Sjedgar			}
4811592Srgrimes		}
48270102Sphk	| DELE check_login_ro SP pathname CRLF
4831592Srgrimes		{
4841592Srgrimes			if ($2 && $4 != NULL)
4851592Srgrimes				delete($4);
4861592Srgrimes			if ($4 != NULL)
4871592Srgrimes				free($4);
4881592Srgrimes		}
48970102Sphk	| RNTO check_login_ro SP pathname CRLF
4901592Srgrimes		{
491101379Syar			if ($2 && $4 != NULL) {
49217433Spst				if (fromname) {
49317433Spst					renamecmd(fromname, $4);
49417433Spst					free(fromname);
495132931Syar					fromname = NULL;
49617433Spst				} else {
49717433Spst					reply(503, "Bad sequence of commands.");
49817433Spst				}
4991592Srgrimes			}
500101379Syar			if ($4 != NULL)
501101379Syar				free($4);
5021592Srgrimes		}
50371278Sjedgar	| ABOR check_login CRLF
5041592Srgrimes		{
50571278Sjedgar			if ($2)
50671278Sjedgar				reply(225, "ABOR command successful.");
5071592Srgrimes		}
5081592Srgrimes	| CWD check_login CRLF
5091592Srgrimes		{
51069234Sdanny			if ($2) {
511110036Syar				cwd(homedir);
51269234Sdanny			}
5131592Srgrimes		}
5141592Srgrimes	| CWD check_login SP pathname CRLF
5151592Srgrimes		{
5161592Srgrimes			if ($2 && $4 != NULL)
5171592Srgrimes				cwd($4);
5181592Srgrimes			if ($4 != NULL)
5191592Srgrimes				free($4);
5201592Srgrimes		}
5211592Srgrimes	| HELP CRLF
5221592Srgrimes		{
523132931Syar			help(cmdtab, NULL);
5241592Srgrimes		}
5251592Srgrimes	| HELP SP STRING CRLF
5261592Srgrimes		{
5271592Srgrimes			char *cp = $3;
5281592Srgrimes
5291592Srgrimes			if (strncasecmp(cp, "SITE", 4) == 0) {
5301592Srgrimes				cp = $3 + 4;
5311592Srgrimes				if (*cp == ' ')
5321592Srgrimes					cp++;
5331592Srgrimes				if (*cp)
5341592Srgrimes					help(sitetab, cp);
5351592Srgrimes				else
536132931Syar					help(sitetab, NULL);
5371592Srgrimes			} else
5381592Srgrimes				help(cmdtab, $3);
53988935Sdwmalone			free($3);
5401592Srgrimes		}
5411592Srgrimes	| NOOP CRLF
5421592Srgrimes		{
5431592Srgrimes			reply(200, "NOOP command successful.");
5441592Srgrimes		}
54570102Sphk	| MKD check_login_ro SP pathname CRLF
5461592Srgrimes		{
5471592Srgrimes			if ($2 && $4 != NULL)
5481592Srgrimes				makedir($4);
5491592Srgrimes			if ($4 != NULL)
5501592Srgrimes				free($4);
5511592Srgrimes		}
55270102Sphk	| RMD check_login_ro SP pathname CRLF
5531592Srgrimes		{
5541592Srgrimes			if ($2 && $4 != NULL)
5551592Srgrimes				removedir($4);
5561592Srgrimes			if ($4 != NULL)
5571592Srgrimes				free($4);
5581592Srgrimes		}
5591592Srgrimes	| PWD check_login CRLF
5601592Srgrimes		{
5611592Srgrimes			if ($2)
5621592Srgrimes				pwd();
5631592Srgrimes		}
5641592Srgrimes	| CDUP check_login CRLF
5651592Srgrimes		{
5661592Srgrimes			if ($2)
5671592Srgrimes				cwd("..");
5681592Srgrimes		}
5691592Srgrimes	| SITE SP HELP CRLF
5701592Srgrimes		{
571132931Syar			help(sitetab, NULL);
5721592Srgrimes		}
5731592Srgrimes	| SITE SP HELP SP STRING CRLF
5741592Srgrimes		{
5751592Srgrimes			help(sitetab, $5);
57688935Sdwmalone			free($5);
5771592Srgrimes		}
57875535Sphk	| SITE SP MDFIVE check_login SP pathname CRLF
57975535Sphk		{
58075535Sphk			char p[64], *q;
58175535Sphk
582101379Syar			if ($4 && $6) {
58375535Sphk				q = MD5File($6, p);
58475535Sphk				if (q != NULL)
58575535Sphk					reply(200, "MD5(%s) = %s", $6, p);
58675535Sphk				else
58775535Sphk					perror_reply(550, $6);
58875535Sphk			}
58988935Sdwmalone			if ($6)
59088935Sdwmalone				free($6);
59175535Sphk		}
5921592Srgrimes	| SITE SP UMASK check_login CRLF
5931592Srgrimes		{
5941592Srgrimes			int oldmask;
5951592Srgrimes
5961592Srgrimes			if ($4) {
5971592Srgrimes				oldmask = umask(0);
5981592Srgrimes				(void) umask(oldmask);
599137852Syar				reply(200, "Current UMASK is %03o.", oldmask);
6001592Srgrimes			}
6011592Srgrimes		}
6021592Srgrimes	| SITE SP UMASK check_login SP octal_number CRLF
6031592Srgrimes		{
6041592Srgrimes			int oldmask;
6051592Srgrimes
6061592Srgrimes			if ($4) {
6071592Srgrimes				if (($6 == -1) || ($6 > 0777)) {
608137852Syar					reply(501, "Bad UMASK value.");
6091592Srgrimes				} else {
6101592Srgrimes					oldmask = umask($6);
6111592Srgrimes					reply(200,
612137852Syar					    "UMASK set to %03o (was %03o).",
6131592Srgrimes					    $6, oldmask);
6141592Srgrimes				}
6151592Srgrimes			}
6161592Srgrimes		}
61770102Sphk	| SITE SP CHMOD check_login_ro SP octal_number SP pathname CRLF
6181592Srgrimes		{
6191592Srgrimes			if ($4 && ($8 != NULL)) {
620101378Syar				if (($6 == -1 ) || ($6 > 0777))
621137852Syar					reply(501, "Bad mode value.");
6221592Srgrimes				else if (chmod($8, $6) < 0)
6231592Srgrimes					perror_reply(550, $8);
6241592Srgrimes				else
6251592Srgrimes					reply(200, "CHMOD command successful.");
6261592Srgrimes			}
6271592Srgrimes			if ($8 != NULL)
6281592Srgrimes				free($8);
6291592Srgrimes		}
63071278Sjedgar	| SITE SP check_login IDLE CRLF
6311592Srgrimes		{
63271278Sjedgar			if ($3)
63371278Sjedgar				reply(200,
634137852Syar			    	    "Current IDLE time limit is %d seconds; max %d.",
63571278Sjedgar				    timeout, maxtimeout);
6361592Srgrimes		}
63771278Sjedgar	| SITE SP check_login IDLE SP NUMBER CRLF
6381592Srgrimes		{
63971278Sjedgar			if ($3) {
64092272Smaxim				if ($6.i < 30 || $6.i > maxtimeout) {
64171278Sjedgar					reply(501,
642137852Syar					    "Maximum IDLE time must be between 30 and %d seconds.",
64371278Sjedgar					    maxtimeout);
64471278Sjedgar				} else {
64592272Smaxim					timeout = $6.i;
646137659Syar					(void) alarm(timeout);
64771278Sjedgar					reply(200,
648137852Syar					    "Maximum IDLE time set to %d seconds.",
64971278Sjedgar					    timeout);
65071278Sjedgar				}
6511592Srgrimes			}
6521592Srgrimes		}
65370102Sphk	| STOU check_login_ro SP pathname CRLF
6541592Srgrimes		{
6551592Srgrimes			if ($2 && $4 != NULL)
6561592Srgrimes				store($4, "w", 1);
6571592Srgrimes			if ($4 != NULL)
6581592Srgrimes				free($4);
6591592Srgrimes		}
660168849Syar	| FEAT CRLF
661168849Syar		{
662168849Syar			lreply(211, "Extensions supported:");
663168849Syar#if 0
664168849Syar			/* XXX these two keywords are non-standard */
665168849Syar			printf(" EPRT\r\n");
666168849Syar			if (!noepsv)
667168849Syar				printf(" EPSV\r\n");
668168849Syar#endif
669168849Syar			printf(" MDTM\r\n");
670168849Syar			printf(" REST STREAM\r\n");
671168849Syar			printf(" SIZE\r\n");
672168849Syar			if (assumeutf8) {
673168849Syar				/* TVFS requires UTF8, see RFC 3659 */
674168849Syar				printf(" TVFS\r\n");
675168849Syar				printf(" UTF8\r\n");
676168849Syar			}
677168849Syar			reply(211, "End.");
678168849Syar		}
67971278Sjedgar	| SYST check_login CRLF
6801592Srgrimes		{
681116439Syar			if ($2) {
682116439Syar				if (hostinfo)
6831592Srgrimes#ifdef BSD
684116439Syar					reply(215, "UNIX Type: L%d Version: BSD-%d",
685116439Syar					      CHAR_BIT, BSD);
6861592Srgrimes#else /* BSD */
687116439Syar					reply(215, "UNIX Type: L%d", CHAR_BIT);
6881592Srgrimes#endif /* BSD */
689116439Syar				else
690116439Syar					reply(215, "UNKNOWN Type: L%d", CHAR_BIT);
691116439Syar			}
6921592Srgrimes		}
6931592Srgrimes
6941592Srgrimes		/*
6951592Srgrimes		 * SIZE is not in RFC959, but Postel has blessed it and
6961592Srgrimes		 * it will be in the updated RFC.
6971592Srgrimes		 *
6981592Srgrimes		 * Return size of file in a format suitable for
6991592Srgrimes		 * using with RESTART (we just count bytes).
7001592Srgrimes		 */
7011592Srgrimes	| SIZE check_login SP pathname CRLF
7021592Srgrimes		{
7031592Srgrimes			if ($2 && $4 != NULL)
7041592Srgrimes				sizecmd($4);
7051592Srgrimes			if ($4 != NULL)
7061592Srgrimes				free($4);
7071592Srgrimes		}
7081592Srgrimes
7091592Srgrimes		/*
7101592Srgrimes		 * MDTM is not in RFC959, but Postel has blessed it and
7111592Srgrimes		 * it will be in the updated RFC.
7121592Srgrimes		 *
7131592Srgrimes		 * Return modification time of file as an ISO 3307
7141592Srgrimes		 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
7151592Srgrimes		 * where xxx is the fractional second (of any precision,
7161592Srgrimes		 * not necessarily 3 digits)
7171592Srgrimes		 */
7181592Srgrimes	| MDTM check_login SP pathname CRLF
7191592Srgrimes		{
7201592Srgrimes			if ($2 && $4 != NULL) {
7211592Srgrimes				struct stat stbuf;
7221592Srgrimes				if (stat($4, &stbuf) < 0)
723137850Syar					perror_reply(550, $4);
7241592Srgrimes				else if (!S_ISREG(stbuf.st_mode)) {
7251592Srgrimes					reply(550, "%s: not a plain file.", $4);
7261592Srgrimes				} else {
7271592Srgrimes					struct tm *t;
7281592Srgrimes					t = gmtime(&stbuf.st_mtime);
7291592Srgrimes					reply(213,
73017435Spst					    "%04d%02d%02d%02d%02d%02d",
73117435Spst					    1900 + t->tm_year,
73217435Spst					    t->tm_mon+1, t->tm_mday,
7331592Srgrimes					    t->tm_hour, t->tm_min, t->tm_sec);
7341592Srgrimes				}
7351592Srgrimes			}
7361592Srgrimes			if ($4 != NULL)
7371592Srgrimes				free($4);
7381592Srgrimes		}
7391592Srgrimes	| QUIT CRLF
7401592Srgrimes		{
7411592Srgrimes			reply(221, "Goodbye.");
7421592Srgrimes			dologout(0);
7431592Srgrimes		}
744102565Syar	| NOTIMPL
745102565Syar		{
746102565Syar			nack($1);
747102565Syar		}
74889935Syar	| error
7491592Srgrimes		{
75089935Syar			yyclearin;		/* discard lookahead data */
75189935Syar			yyerrok;		/* clear error condition */
752102565Syar			state = CMD;		/* reset lexer state */
7531592Srgrimes		}
7541592Srgrimes	;
7551592Srgrimesrcmd
75670102Sphk	: RNFR check_login_ro SP pathname CRLF
7571592Srgrimes		{
758132930Syar			restart_point = 0;
7591592Srgrimes			if ($2 && $4) {
76088935Sdwmalone				if (fromname)
76188935Sdwmalone					free(fromname);
762132931Syar				fromname = NULL;
76388935Sdwmalone				if (renamefrom($4))
76488935Sdwmalone					fromname = $4;
76588935Sdwmalone				else
7661592Srgrimes					free($4);
76788935Sdwmalone			} else if ($4) {
76888935Sdwmalone				free($4);
7691592Srgrimes			}
7701592Srgrimes		}
77192272Smaxim	| REST check_login SP NUMBER CRLF
7721592Srgrimes		{
77371278Sjedgar			if ($2) {
77488935Sdwmalone				if (fromname)
77588935Sdwmalone					free(fromname);
776132931Syar				fromname = NULL;
77792272Smaxim				restart_point = $4.o;
778132929Syar				reply(350, "Restarting at %jd. %s",
779132929Syar				    (intmax_t)restart_point,
78071278Sjedgar				    "Send STORE or RETRIEVE to initiate transfer.");
78171278Sjedgar			}
7821592Srgrimes		}
7831592Srgrimes	;
7841592Srgrimes
7851592Srgrimesusername
7861592Srgrimes	: STRING
7871592Srgrimes	;
7881592Srgrimes
7891592Srgrimespassword
7901592Srgrimes	: /* empty */
7911592Srgrimes		{
7921592Srgrimes			$$ = (char *)calloc(1, sizeof(char));
7931592Srgrimes		}
7941592Srgrimes	| STRING
7951592Srgrimes	;
7961592Srgrimes
7971592Srgrimesbyte_size
7981592Srgrimes	: NUMBER
79992272Smaxim		{
80092272Smaxim			$$ = $1.i;
80192272Smaxim		}
8021592Srgrimes	;
8031592Srgrimes
8041592Srgrimeshost_port
8051592Srgrimes	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
8061592Srgrimes		NUMBER COMMA NUMBER
8071592Srgrimes		{
8081592Srgrimes			char *a, *p;
8091592Srgrimes
81056668Sshin			data_dest.su_len = sizeof(struct sockaddr_in);
81156668Sshin			data_dest.su_family = AF_INET;
81256668Sshin			p = (char *)&data_dest.su_sin.sin_port;
81392272Smaxim			p[0] = $9.i; p[1] = $11.i;
81456668Sshin			a = (char *)&data_dest.su_sin.sin_addr;
81592272Smaxim			a[0] = $1.i; a[1] = $3.i; a[2] = $5.i; a[3] = $7.i;
8161592Srgrimes		}
8171592Srgrimes	;
8181592Srgrimes
81956668Sshinhost_long_port
82056668Sshin	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
82156668Sshin		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
82256668Sshin		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
82356668Sshin		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
82456668Sshin		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
82556668Sshin		NUMBER
82656668Sshin		{
82756668Sshin			char *a, *p;
82856668Sshin
82956668Sshin			memset(&data_dest, 0, sizeof(data_dest));
83056668Sshin			data_dest.su_len = sizeof(struct sockaddr_in6);
83156668Sshin			data_dest.su_family = AF_INET6;
83256668Sshin			p = (char *)&data_dest.su_port;
83392272Smaxim			p[0] = $39.i; p[1] = $41.i;
83456668Sshin			a = (char *)&data_dest.su_sin6.sin6_addr;
83592272Smaxim			a[0] = $5.i; a[1] = $7.i; a[2] = $9.i; a[3] = $11.i;
83692272Smaxim			a[4] = $13.i; a[5] = $15.i; a[6] = $17.i; a[7] = $19.i;
83792272Smaxim			a[8] = $21.i; a[9] = $23.i; a[10] = $25.i; a[11] = $27.i;
83892272Smaxim			a[12] = $29.i; a[13] = $31.i; a[14] = $33.i; a[15] = $35.i;
83956668Sshin			if (his_addr.su_family == AF_INET6) {
84056668Sshin				/* XXX more sanity checks! */
84156668Sshin				data_dest.su_sin6.sin6_scope_id =
84256668Sshin					his_addr.su_sin6.sin6_scope_id;
84356668Sshin			}
84492272Smaxim			if ($1.i != 6 || $3.i != 16 || $37.i != 2)
84556668Sshin				memset(&data_dest, 0, sizeof(data_dest));
84656668Sshin		}
84756668Sshin	| NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
84856668Sshin		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
84956668Sshin		NUMBER
85056668Sshin		{
85156668Sshin			char *a, *p;
85256668Sshin
85356668Sshin			memset(&data_dest, 0, sizeof(data_dest));
85456668Sshin			data_dest.su_sin.sin_len = sizeof(struct sockaddr_in);
85556668Sshin			data_dest.su_family = AF_INET;
85656668Sshin			p = (char *)&data_dest.su_port;
85792272Smaxim			p[0] = $15.i; p[1] = $17.i;
85856668Sshin			a = (char *)&data_dest.su_sin.sin_addr;
85992272Smaxim			a[0] =  $5.i; a[1] = $7.i; a[2] = $9.i; a[3] = $11.i;
86092272Smaxim			if ($1.i != 4 || $3.i != 4 || $13.i != 2)
86156668Sshin				memset(&data_dest, 0, sizeof(data_dest));
86256668Sshin		}
86356668Sshin	;
86456668Sshin
8651592Srgrimesform_code
8661592Srgrimes	: N
8671592Srgrimes		{
8681592Srgrimes			$$ = FORM_N;
8691592Srgrimes		}
8701592Srgrimes	| T
8711592Srgrimes		{
8721592Srgrimes			$$ = FORM_T;
8731592Srgrimes		}
8741592Srgrimes	| C
8751592Srgrimes		{
8761592Srgrimes			$$ = FORM_C;
8771592Srgrimes		}
8781592Srgrimes	;
8791592Srgrimes
8801592Srgrimestype_code
8811592Srgrimes	: A
8821592Srgrimes		{
8831592Srgrimes			cmd_type = TYPE_A;
8841592Srgrimes			cmd_form = FORM_N;
8851592Srgrimes		}
8861592Srgrimes	| A SP form_code
8871592Srgrimes		{
8881592Srgrimes			cmd_type = TYPE_A;
8891592Srgrimes			cmd_form = $3;
8901592Srgrimes		}
8911592Srgrimes	| E
8921592Srgrimes		{
8931592Srgrimes			cmd_type = TYPE_E;
8941592Srgrimes			cmd_form = FORM_N;
8951592Srgrimes		}
8961592Srgrimes	| E SP form_code
8971592Srgrimes		{
8981592Srgrimes			cmd_type = TYPE_E;
8991592Srgrimes			cmd_form = $3;
9001592Srgrimes		}
9011592Srgrimes	| I
9021592Srgrimes		{
9031592Srgrimes			cmd_type = TYPE_I;
9041592Srgrimes		}
9051592Srgrimes	| L
9061592Srgrimes		{
9071592Srgrimes			cmd_type = TYPE_L;
908103949Smike			cmd_bytesz = CHAR_BIT;
9091592Srgrimes		}
9101592Srgrimes	| L SP byte_size
9111592Srgrimes		{
9121592Srgrimes			cmd_type = TYPE_L;
9131592Srgrimes			cmd_bytesz = $3;
9141592Srgrimes		}
9151592Srgrimes		/* this is for a bug in the BBN ftp */
9161592Srgrimes	| L byte_size
9171592Srgrimes		{
9181592Srgrimes			cmd_type = TYPE_L;
9191592Srgrimes			cmd_bytesz = $2;
9201592Srgrimes		}
9211592Srgrimes	;
9221592Srgrimes
9231592Srgrimesstruct_code
9241592Srgrimes	: F
9251592Srgrimes		{
9261592Srgrimes			$$ = STRU_F;
9271592Srgrimes		}
9281592Srgrimes	| R
9291592Srgrimes		{
9301592Srgrimes			$$ = STRU_R;
9311592Srgrimes		}
9321592Srgrimes	| P
9331592Srgrimes		{
9341592Srgrimes			$$ = STRU_P;
9351592Srgrimes		}
9361592Srgrimes	;
9371592Srgrimes
9381592Srgrimesmode_code
9391592Srgrimes	: S
9401592Srgrimes		{
9411592Srgrimes			$$ = MODE_S;
9421592Srgrimes		}
9431592Srgrimes	| B
9441592Srgrimes		{
9451592Srgrimes			$$ = MODE_B;
9461592Srgrimes		}
9471592Srgrimes	| C
9481592Srgrimes		{
9491592Srgrimes			$$ = MODE_C;
9501592Srgrimes		}
9511592Srgrimes	;
9521592Srgrimes
9531592Srgrimespathname
9541592Srgrimes	: pathstring
9551592Srgrimes		{
95675567Speter			if (logged_in && $1) {
957110340Syar				char *p;
9581592Srgrimes
959110340Syar				/*
960110340Syar				 * Expand ~user manually since glob(3)
961110340Syar				 * will return the unexpanded pathname
962110340Syar				 * if the corresponding file/directory
963110340Syar				 * doesn't exist yet.  Using sole glob(3)
964110340Syar				 * would break natural commands like
965110340Syar				 * MKD ~user/newdir
966110340Syar				 * or
967110340Syar				 * RNTO ~/newfile
968110340Syar				 */
969110340Syar				if ((p = exptilde($1)) != NULL) {
970110340Syar					$$ = expglob(p);
971110340Syar					free(p);
972110340Syar				} else
9731592Srgrimes					$$ = NULL;
9741592Srgrimes				free($1);
9751592Srgrimes			} else
9761592Srgrimes				$$ = $1;
9771592Srgrimes		}
9781592Srgrimes	;
9791592Srgrimes
9801592Srgrimespathstring
9811592Srgrimes	: STRING
9821592Srgrimes	;
9831592Srgrimes
9841592Srgrimesoctal_number
9851592Srgrimes	: NUMBER
9861592Srgrimes		{
9871592Srgrimes			int ret, dec, multby, digit;
9881592Srgrimes
9891592Srgrimes			/*
9901592Srgrimes			 * Convert a number that was read as decimal number
9911592Srgrimes			 * to what it would be if it had been read as octal.
9921592Srgrimes			 */
99392272Smaxim			dec = $1.i;
9941592Srgrimes			multby = 1;
9951592Srgrimes			ret = 0;
9961592Srgrimes			while (dec) {
9971592Srgrimes				digit = dec%10;
9981592Srgrimes				if (digit > 7) {
9991592Srgrimes					ret = -1;
10001592Srgrimes					break;
10011592Srgrimes				}
10021592Srgrimes				ret += digit * multby;
10031592Srgrimes				multby *= 8;
10041592Srgrimes				dec /= 10;
10051592Srgrimes			}
10061592Srgrimes			$$ = ret;
10071592Srgrimes		}
10081592Srgrimes	;
10091592Srgrimes
10101592Srgrimes
10111592Srgrimescheck_login
10121592Srgrimes	: /* empty */
10131592Srgrimes		{
101470102Sphk		$$ = check_login1();
10151592Srgrimes		}
10161592Srgrimes	;
10171592Srgrimes
101870102Sphkcheck_login_epsv
101970102Sphk	: /* empty */
102070102Sphk		{
102170102Sphk		if (noepsv) {
1022137852Syar			reply(500, "EPSV command disabled.");
102370102Sphk			$$ = 0;
102470102Sphk		}
102570102Sphk		else
102670102Sphk			$$ = check_login1();
102770102Sphk		}
102870102Sphk	;
102970102Sphk
103070102Sphkcheck_login_ro
103170102Sphk	: /* empty */
103270102Sphk		{
103370102Sphk		if (readonly) {
103472710Sdes			reply(550, "Permission denied.");
103570102Sphk			$$ = 0;
103670102Sphk		}
103770102Sphk		else
103870102Sphk			$$ = check_login1();
103970102Sphk		}
104070102Sphk	;
104170102Sphk
10421592Srgrimes%%
10431592Srgrimes
10441592Srgrimes#define	CMD	0	/* beginning of command */
10451592Srgrimes#define	ARGS	1	/* expect miscellaneous arguments */
10461592Srgrimes#define	STR1	2	/* expect SP followed by STRING */
10471592Srgrimes#define	STR2	3	/* expect STRING */
10481592Srgrimes#define	OSTR	4	/* optional SP then STRING */
104975556Sgreen#define	ZSTR1	5	/* optional SP then optional STRING */
10501592Srgrimes#define	ZSTR2	6	/* optional STRING after SP */
10511592Srgrimes#define	SITECMD	7	/* SITE command */
10521592Srgrimes#define	NSTR	8	/* Number followed by a string */
10531592Srgrimes
105475560Sjedgar#define	MAXGLOBARGS	1000
105575560Sjedgar
1056101034Syar#define	MAXASIZE	10240	/* Deny ASCII SIZE on files larger than that */
1057101034Syar
10581592Srgrimesstruct tab {
10591592Srgrimes	char	*name;
10601592Srgrimes	short	token;
10611592Srgrimes	short	state;
10621592Srgrimes	short	implemented;	/* 1 if command is implemented */
10631592Srgrimes	char	*help;
10641592Srgrimes};
10651592Srgrimes
10661592Srgrimesstruct tab cmdtab[] = {		/* In order defined in RFC 765 */
10671592Srgrimes	{ "USER", USER, STR1, 1,	"<sp> username" },
106875556Sgreen	{ "PASS", PASS, ZSTR1, 1,	"[<sp> [password]]" },
10691592Srgrimes	{ "ACCT", ACCT, STR1, 0,	"(specify account)" },
10701592Srgrimes	{ "SMNT", SMNT, ARGS, 0,	"(structure mount)" },
10711592Srgrimes	{ "REIN", REIN, ARGS, 0,	"(reinitialize server state)" },
10721592Srgrimes	{ "QUIT", QUIT, ARGS, 1,	"(terminate service)", },
1073101806Syar	{ "PORT", PORT, ARGS, 1,	"<sp> b0, b1, b2, b3, b4, b5" },
107456668Sshin	{ "LPRT", LPRT, ARGS, 1,	"<sp> af, hal, h1, h2, h3,..., pal, p1, p2..." },
107556668Sshin	{ "EPRT", EPRT, STR1, 1,	"<sp> |af|addr|port|" },
10761592Srgrimes	{ "PASV", PASV, ARGS, 1,	"(set server in passive mode)" },
107756668Sshin	{ "LPSV", LPSV, ARGS, 1,	"(set server in passive mode)" },
107856668Sshin	{ "EPSV", EPSV, ARGS, 1,	"[<sp> af|ALL]" },
1079101806Syar	{ "TYPE", TYPE, ARGS, 1,	"<sp> { A | E | I | L }" },
10801592Srgrimes	{ "STRU", STRU, ARGS, 1,	"(specify file structure)" },
10811592Srgrimes	{ "MODE", MODE, ARGS, 1,	"(specify transfer mode)" },
10821592Srgrimes	{ "RETR", RETR, STR1, 1,	"<sp> file-name" },
10831592Srgrimes	{ "STOR", STOR, STR1, 1,	"<sp> file-name" },
10841592Srgrimes	{ "APPE", APPE, STR1, 1,	"<sp> file-name" },
10851592Srgrimes	{ "MLFL", MLFL, OSTR, 0,	"(mail file)" },
10861592Srgrimes	{ "MAIL", MAIL, OSTR, 0,	"(mail to user)" },
10871592Srgrimes	{ "MSND", MSND, OSTR, 0,	"(mail send to terminal)" },
10881592Srgrimes	{ "MSOM", MSOM, OSTR, 0,	"(mail send to terminal or mailbox)" },
10891592Srgrimes	{ "MSAM", MSAM, OSTR, 0,	"(mail send to terminal and mailbox)" },
10901592Srgrimes	{ "MRSQ", MRSQ, OSTR, 0,	"(mail recipient scheme question)" },
10911592Srgrimes	{ "MRCP", MRCP, STR1, 0,	"(mail recipient)" },
10921592Srgrimes	{ "ALLO", ALLO, ARGS, 1,	"allocate storage (vacuously)" },
10931592Srgrimes	{ "REST", REST, ARGS, 1,	"<sp> offset (restart command)" },
10941592Srgrimes	{ "RNFR", RNFR, STR1, 1,	"<sp> file-name" },
10951592Srgrimes	{ "RNTO", RNTO, STR1, 1,	"<sp> file-name" },
10961592Srgrimes	{ "ABOR", ABOR, ARGS, 1,	"(abort operation)" },
10971592Srgrimes	{ "DELE", DELE, STR1, 1,	"<sp> file-name" },
10981592Srgrimes	{ "CWD",  CWD,  OSTR, 1,	"[ <sp> directory-name ]" },
10991592Srgrimes	{ "XCWD", CWD,	OSTR, 1,	"[ <sp> directory-name ]" },
11001592Srgrimes	{ "LIST", LIST, OSTR, 1,	"[ <sp> path-name ]" },
11011592Srgrimes	{ "NLST", NLST, OSTR, 1,	"[ <sp> path-name ]" },
11021592Srgrimes	{ "SITE", SITE, SITECMD, 1,	"site-cmd [ <sp> arguments ]" },
11031592Srgrimes	{ "SYST", SYST, ARGS, 1,	"(get type of operating system)" },
1104168849Syar	{ "FEAT", FEAT, ARGS, 1,	"(get extended features)" },
11051592Srgrimes	{ "STAT", STAT, OSTR, 1,	"[ <sp> path-name ]" },
11061592Srgrimes	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
11071592Srgrimes	{ "NOOP", NOOP, ARGS, 1,	"" },
11081592Srgrimes	{ "MKD",  MKD,  STR1, 1,	"<sp> path-name" },
11091592Srgrimes	{ "XMKD", MKD,  STR1, 1,	"<sp> path-name" },
11101592Srgrimes	{ "RMD",  RMD,  STR1, 1,	"<sp> path-name" },
11111592Srgrimes	{ "XRMD", RMD,  STR1, 1,	"<sp> path-name" },
11121592Srgrimes	{ "PWD",  PWD,  ARGS, 1,	"(return current directory)" },
11131592Srgrimes	{ "XPWD", PWD,  ARGS, 1,	"(return current directory)" },
11141592Srgrimes	{ "CDUP", CDUP, ARGS, 1,	"(change to parent directory)" },
11151592Srgrimes	{ "XCUP", CDUP, ARGS, 1,	"(change to parent directory)" },
11161592Srgrimes	{ "STOU", STOU, STR1, 1,	"<sp> file-name" },
11171592Srgrimes	{ "SIZE", SIZE, OSTR, 1,	"<sp> path-name" },
11181592Srgrimes	{ "MDTM", MDTM, OSTR, 1,	"<sp> path-name" },
11191592Srgrimes	{ NULL,   0,    0,    0,	0 }
11201592Srgrimes};
11211592Srgrimes
11221592Srgrimesstruct tab sitetab[] = {
112375535Sphk	{ "MD5", MDFIVE, STR1, 1,	"[ <sp> file-name ]" },
11241592Srgrimes	{ "UMASK", UMASK, ARGS, 1,	"[ <sp> umask ]" },
11251592Srgrimes	{ "IDLE", IDLE, ARGS, 1,	"[ <sp> maximum-idle-time ]" },
11261592Srgrimes	{ "CHMOD", CHMOD, NSTR, 1,	"<sp> mode <sp> file-name" },
11271592Srgrimes	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
11281592Srgrimes	{ NULL,   0,    0,    0,	0 }
11291592Srgrimes};
11301592Srgrimes
113190148Simpstatic char	*copy(char *);
1132110340Syarstatic char	*expglob(char *);
1133110340Syarstatic char	*exptilde(char *);
113490148Simpstatic void	 help(struct tab *, char *);
11351592Srgrimesstatic struct tab *
113690148Simp		 lookup(struct tab *, char *);
113790148Simpstatic int	 port_check(const char *);
1138159276Syar#ifdef INET6
113990148Simpstatic int	 port_check_v6(const char *);
1140159276Syar#endif
114190148Simpstatic void	 sizecmd(char *);
114290148Simpstatic void	 toolong(int);
1143159276Syar#ifdef INET6
114490148Simpstatic void	 v4map_data_dest(void);
1145159276Syar#endif
114690148Simpstatic int	 yylex(void);
11471592Srgrimes
11481592Srgrimesstatic struct tab *
114990148Simplookup(struct tab *p, char *cmd)
11501592Srgrimes{
11511592Srgrimes
11521592Srgrimes	for (; p->name != NULL; p++)
11531592Srgrimes		if (strcmp(cmd, p->name) == 0)
11541592Srgrimes			return (p);
11551592Srgrimes	return (0);
11561592Srgrimes}
11571592Srgrimes
11581592Srgrimes#include <arpa/telnet.h>
11591592Srgrimes
11601592Srgrimes/*
11611592Srgrimes * getline - a hacked up version of fgets to ignore TELNET escape codes.
11621592Srgrimes */
1163186405Scpercivaint
116490148Simpgetline(char *s, int n, FILE *iop)
11651592Srgrimes{
11661592Srgrimes	int c;
11671592Srgrimes	register char *cs;
1168117352Syar	sigset_t sset, osset;
11691592Srgrimes
11701592Srgrimes	cs = s;
11711592Srgrimes/* tmpline may contain saved command from urgent mode interruption */
11721592Srgrimes	for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
11731592Srgrimes		*cs++ = tmpline[c];
11741592Srgrimes		if (tmpline[c] == '\n') {
11751592Srgrimes			*cs++ = '\0';
117676096Smarkm			if (ftpdebug)
11771592Srgrimes				syslog(LOG_DEBUG, "command: %s", s);
11781592Srgrimes			tmpline[0] = '\0';
1179186405Scperciva			return(0);
11801592Srgrimes		}
11811592Srgrimes		if (c == 0)
11821592Srgrimes			tmpline[0] = '\0';
11831592Srgrimes	}
1184117352Syar	/* SIGURG would interrupt stdio if not blocked during the read loop */
1185117352Syar	sigemptyset(&sset);
1186117352Syar	sigaddset(&sset, SIGURG);
1187117352Syar	sigprocmask(SIG_BLOCK, &sset, &osset);
11881592Srgrimes	while ((c = getc(iop)) != EOF) {
11891592Srgrimes		c &= 0377;
11901592Srgrimes		if (c == IAC) {
1191117351Syar			if ((c = getc(iop)) == EOF)
1192117351Syar				goto got_eof;
11931592Srgrimes			c &= 0377;
11941592Srgrimes			switch (c) {
11951592Srgrimes			case WILL:
11961592Srgrimes			case WONT:
1197117351Syar				if ((c = getc(iop)) == EOF)
1198117351Syar					goto got_eof;
11991592Srgrimes				printf("%c%c%c", IAC, DONT, 0377&c);
12001592Srgrimes				(void) fflush(stdout);
12011592Srgrimes				continue;
12021592Srgrimes			case DO:
12031592Srgrimes			case DONT:
1204117351Syar				if ((c = getc(iop)) == EOF)
1205117351Syar					goto got_eof;
12061592Srgrimes				printf("%c%c%c", IAC, WONT, 0377&c);
12071592Srgrimes				(void) fflush(stdout);
12081592Srgrimes				continue;
12091592Srgrimes			case IAC:
12101592Srgrimes				break;
12111592Srgrimes			default:
12121592Srgrimes				continue;	/* ignore command */
12131592Srgrimes			}
12141592Srgrimes		}
12151592Srgrimes		*cs++ = c;
1216186405Scperciva		if (--n <= 0) {
1217186405Scperciva			/*
1218186405Scperciva			 * If command doesn't fit into buffer, discard the
1219186405Scperciva			 * rest of the command and indicate truncation.
1220186405Scperciva			 * This prevents the command to be split up into
1221186405Scperciva			 * multiple commands.
1222186405Scperciva			 */
1223186405Scperciva			while (c != '\n' && (c = getc(iop)) != EOF)
1224186405Scperciva				;
1225186405Scperciva			return (-2);
1226186405Scperciva		}
1227186405Scperciva		if (c == '\n')
12281592Srgrimes			break;
12291592Srgrimes	}
1230117351Syargot_eof:
1231117352Syar	sigprocmask(SIG_SETMASK, &osset, NULL);
12321592Srgrimes	if (c == EOF && cs == s)
1233186405Scperciva		return (-1);
12341592Srgrimes	*cs++ = '\0';
123576096Smarkm	if (ftpdebug) {
12361592Srgrimes		if (!guest && strncasecmp("pass ", s, 5) == 0) {
12371592Srgrimes			/* Don't syslog passwords */
12381592Srgrimes			syslog(LOG_DEBUG, "command: %.5s ???", s);
12391592Srgrimes		} else {
12401592Srgrimes			register char *cp;
12411592Srgrimes			register int len;
12421592Srgrimes
12431592Srgrimes			/* Don't syslog trailing CR-LF */
12441592Srgrimes			len = strlen(s);
12451592Srgrimes			cp = s + len - 1;
12461592Srgrimes			while (cp >= s && (*cp == '\n' || *cp == '\r')) {
12471592Srgrimes				--cp;
12481592Srgrimes				--len;
12491592Srgrimes			}
12501592Srgrimes			syslog(LOG_DEBUG, "command: %.*s", len, s);
12511592Srgrimes		}
12521592Srgrimes	}
1253186405Scperciva	return (0);
12541592Srgrimes}
12551592Srgrimes
12561592Srgrimesstatic void
125790148Simptoolong(int signo)
12581592Srgrimes{
12591592Srgrimes
12601592Srgrimes	reply(421,
12611592Srgrimes	    "Timeout (%d seconds): closing control connection.", timeout);
12621592Srgrimes	if (logging)
12631592Srgrimes		syslog(LOG_INFO, "User %s timed out after %d seconds",
12641592Srgrimes		    (pw ? pw -> pw_name : "unknown"), timeout);
12651592Srgrimes	dologout(1);
12661592Srgrimes}
12671592Srgrimes
12681592Srgrimesstatic int
126990148Simpyylex(void)
12701592Srgrimes{
127189935Syar	static int cpos;
12721592Srgrimes	char *cp, *cp2;
12731592Srgrimes	struct tab *p;
12741592Srgrimes	int n;
12751592Srgrimes	char c;
12761592Srgrimes
12771592Srgrimes	for (;;) {
12781592Srgrimes		switch (state) {
12791592Srgrimes
12801592Srgrimes		case CMD:
12811592Srgrimes			(void) signal(SIGALRM, toolong);
1282137659Syar			(void) alarm(timeout);
1283186405Scperciva			n = getline(cbuf, sizeof(cbuf)-1, stdin);
1284186405Scperciva			if (n == -1) {
12851592Srgrimes				reply(221, "You could at least say goodbye.");
12861592Srgrimes				dologout(0);
1287186405Scperciva			} else if (n == -2) {
1288186405Scperciva				reply(500, "Command too long.");
1289186405Scperciva				(void) alarm(0);
1290186405Scperciva				continue;
12911592Srgrimes			}
12921592Srgrimes			(void) alarm(0);
12931592Srgrimes#ifdef SETPROCTITLE
129429574Sphk			if (strncasecmp(cbuf, "PASS", 4) != 0)
12951592Srgrimes				setproctitle("%s: %s", proctitle, cbuf);
12961592Srgrimes#endif /* SETPROCTITLE */
12971592Srgrimes			if ((cp = strchr(cbuf, '\r'))) {
12981592Srgrimes				*cp++ = '\n';
12991592Srgrimes				*cp = '\0';
13001592Srgrimes			}
13011592Srgrimes			if ((cp = strpbrk(cbuf, " \n")))
13021592Srgrimes				cpos = cp - cbuf;
13031592Srgrimes			if (cpos == 0)
13041592Srgrimes				cpos = 4;
13051592Srgrimes			c = cbuf[cpos];
13061592Srgrimes			cbuf[cpos] = '\0';
13071592Srgrimes			upper(cbuf);
13081592Srgrimes			p = lookup(cmdtab, cbuf);
13091592Srgrimes			cbuf[cpos] = c;
13103776Spst			if (p != 0) {
1311102565Syar				yylval.s = p->name;
1312102565Syar				if (!p->implemented)
1313102565Syar					return (NOTIMPL); /* state remains CMD */
13141592Srgrimes				state = p->state;
13151592Srgrimes				return (p->token);
13161592Srgrimes			}
13171592Srgrimes			break;
13181592Srgrimes
13191592Srgrimes		case SITECMD:
13201592Srgrimes			if (cbuf[cpos] == ' ') {
13211592Srgrimes				cpos++;
13221592Srgrimes				return (SP);
13231592Srgrimes			}
13241592Srgrimes			cp = &cbuf[cpos];
13251592Srgrimes			if ((cp2 = strpbrk(cp, " \n")))
13261592Srgrimes				cpos = cp2 - cbuf;
13271592Srgrimes			c = cbuf[cpos];
13281592Srgrimes			cbuf[cpos] = '\0';
13291592Srgrimes			upper(cp);
13301592Srgrimes			p = lookup(sitetab, cp);
13311592Srgrimes			cbuf[cpos] = c;
13323777Spst			if (guest == 0 && p != 0) {
1333102565Syar				yylval.s = p->name;
1334102565Syar				if (!p->implemented) {
13351592Srgrimes					state = CMD;
1336102565Syar					return (NOTIMPL);
13371592Srgrimes				}
13381592Srgrimes				state = p->state;
13391592Srgrimes				return (p->token);
13401592Srgrimes			}
13411592Srgrimes			state = CMD;
13421592Srgrimes			break;
13431592Srgrimes
134475556Sgreen		case ZSTR1:
13451592Srgrimes		case OSTR:
13461592Srgrimes			if (cbuf[cpos] == '\n') {
13471592Srgrimes				state = CMD;
13481592Srgrimes				return (CRLF);
13491592Srgrimes			}
13501592Srgrimes			/* FALLTHROUGH */
13511592Srgrimes
13521592Srgrimes		case STR1:
13531592Srgrimes		dostr1:
13541592Srgrimes			if (cbuf[cpos] == ' ') {
13551592Srgrimes				cpos++;
135651979Salfred				state = state == OSTR ? STR2 : state+1;
13571592Srgrimes				return (SP);
13581592Srgrimes			}
13591592Srgrimes			break;
13601592Srgrimes
13611592Srgrimes		case ZSTR2:
13621592Srgrimes			if (cbuf[cpos] == '\n') {
13631592Srgrimes				state = CMD;
13641592Srgrimes				return (CRLF);
13651592Srgrimes			}
13661592Srgrimes			/* FALLTHROUGH */
13671592Srgrimes
13681592Srgrimes		case STR2:
13691592Srgrimes			cp = &cbuf[cpos];
13701592Srgrimes			n = strlen(cp);
13711592Srgrimes			cpos += n - 1;
13721592Srgrimes			/*
13731592Srgrimes			 * Make sure the string is nonempty and \n terminated.
13741592Srgrimes			 */
13751592Srgrimes			if (n > 1 && cbuf[cpos] == '\n') {
13761592Srgrimes				cbuf[cpos] = '\0';
13771592Srgrimes				yylval.s = copy(cp);
13781592Srgrimes				cbuf[cpos] = '\n';
13791592Srgrimes				state = ARGS;
13801592Srgrimes				return (STRING);
13811592Srgrimes			}
13821592Srgrimes			break;
13831592Srgrimes
13841592Srgrimes		case NSTR:
13851592Srgrimes			if (cbuf[cpos] == ' ') {
13861592Srgrimes				cpos++;
13871592Srgrimes				return (SP);
13881592Srgrimes			}
13891592Srgrimes			if (isdigit(cbuf[cpos])) {
13901592Srgrimes				cp = &cbuf[cpos];
13911592Srgrimes				while (isdigit(cbuf[++cpos]))
13921592Srgrimes					;
13931592Srgrimes				c = cbuf[cpos];
13941592Srgrimes				cbuf[cpos] = '\0';
139592272Smaxim				yylval.u.i = atoi(cp);
13961592Srgrimes				cbuf[cpos] = c;
13971592Srgrimes				state = STR1;
13981592Srgrimes				return (NUMBER);
13991592Srgrimes			}
14001592Srgrimes			state = STR1;
14011592Srgrimes			goto dostr1;
14021592Srgrimes
14031592Srgrimes		case ARGS:
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);
1411137811Syar				yylval.u.o = strtoull(cp, NULL, 10);
14121592Srgrimes				cbuf[cpos] = c;
14131592Srgrimes				return (NUMBER);
14141592Srgrimes			}
141556668Sshin			if (strncasecmp(&cbuf[cpos], "ALL", 3) == 0
141656668Sshin			 && !isalnum(cbuf[cpos + 3])) {
141756668Sshin				cpos += 3;
141856668Sshin				return ALL;
141956668Sshin			}
14201592Srgrimes			switch (cbuf[cpos++]) {
14211592Srgrimes
14221592Srgrimes			case '\n':
14231592Srgrimes				state = CMD;
14241592Srgrimes				return (CRLF);
14251592Srgrimes
14261592Srgrimes			case ' ':
14271592Srgrimes				return (SP);
14281592Srgrimes
14291592Srgrimes			case ',':
14301592Srgrimes				return (COMMA);
14311592Srgrimes
14321592Srgrimes			case 'A':
14331592Srgrimes			case 'a':
14341592Srgrimes				return (A);
14351592Srgrimes
14361592Srgrimes			case 'B':
14371592Srgrimes			case 'b':
14381592Srgrimes				return (B);
14391592Srgrimes
14401592Srgrimes			case 'C':
14411592Srgrimes			case 'c':
14421592Srgrimes				return (C);
14431592Srgrimes
14441592Srgrimes			case 'E':
14451592Srgrimes			case 'e':
14461592Srgrimes				return (E);
14471592Srgrimes
14481592Srgrimes			case 'F':
14491592Srgrimes			case 'f':
14501592Srgrimes				return (F);
14511592Srgrimes
14521592Srgrimes			case 'I':
14531592Srgrimes			case 'i':
14541592Srgrimes				return (I);
14551592Srgrimes
14561592Srgrimes			case 'L':
14571592Srgrimes			case 'l':
14581592Srgrimes				return (L);
14591592Srgrimes
14601592Srgrimes			case 'N':
14611592Srgrimes			case 'n':
14621592Srgrimes				return (N);
14631592Srgrimes
14641592Srgrimes			case 'P':
14651592Srgrimes			case 'p':
14661592Srgrimes				return (P);
14671592Srgrimes
14681592Srgrimes			case 'R':
14691592Srgrimes			case 'r':
14701592Srgrimes				return (R);
14711592Srgrimes
14721592Srgrimes			case 'S':
14731592Srgrimes			case 's':
14741592Srgrimes				return (S);
14751592Srgrimes
14761592Srgrimes			case 'T':
14771592Srgrimes			case 't':
14781592Srgrimes				return (T);
14791592Srgrimes
14801592Srgrimes			}
14811592Srgrimes			break;
14821592Srgrimes
14831592Srgrimes		default:
148476096Smarkm			fatalerror("Unknown state in scanner.");
14851592Srgrimes		}
14861592Srgrimes		state = CMD;
148789935Syar		return (LEXERR);
14881592Srgrimes	}
14891592Srgrimes}
14901592Srgrimes
14911592Srgrimesvoid
149290148Simpupper(char *s)
14931592Srgrimes{
14941592Srgrimes	while (*s != '\0') {
14951592Srgrimes		if (islower(*s))
14961592Srgrimes			*s = toupper(*s);
14971592Srgrimes		s++;
14981592Srgrimes	}
14991592Srgrimes}
15001592Srgrimes
15011592Srgrimesstatic char *
150290148Simpcopy(char *s)
15031592Srgrimes{
15041592Srgrimes	char *p;
15051592Srgrimes
1506137659Syar	p = malloc(strlen(s) + 1);
15071592Srgrimes	if (p == NULL)
150876096Smarkm		fatalerror("Ran out of memory.");
15091592Srgrimes	(void) strcpy(p, s);
15101592Srgrimes	return (p);
15111592Srgrimes}
15121592Srgrimes
15131592Srgrimesstatic void
151490148Simphelp(struct tab *ctab, char *s)
15151592Srgrimes{
15161592Srgrimes	struct tab *c;
15171592Srgrimes	int width, NCMDS;
15181592Srgrimes	char *type;
15191592Srgrimes
15201592Srgrimes	if (ctab == sitetab)
15211592Srgrimes		type = "SITE ";
15221592Srgrimes	else
15231592Srgrimes		type = "";
15241592Srgrimes	width = 0, NCMDS = 0;
15251592Srgrimes	for (c = ctab; c->name != NULL; c++) {
15261592Srgrimes		int len = strlen(c->name);
15271592Srgrimes
15281592Srgrimes		if (len > width)
15291592Srgrimes			width = len;
15301592Srgrimes		NCMDS++;
15311592Srgrimes	}
15321592Srgrimes	width = (width + 8) &~ 7;
15331592Srgrimes	if (s == 0) {
15341592Srgrimes		int i, j, w;
15351592Srgrimes		int columns, lines;
15361592Srgrimes
15371592Srgrimes		lreply(214, "The following %scommands are recognized %s.",
15381592Srgrimes		    type, "(* =>'s unimplemented)");
15391592Srgrimes		columns = 76 / width;
15401592Srgrimes		if (columns == 0)
15411592Srgrimes			columns = 1;
15421592Srgrimes		lines = (NCMDS + columns - 1) / columns;
15431592Srgrimes		for (i = 0; i < lines; i++) {
15441592Srgrimes			printf("   ");
15451592Srgrimes			for (j = 0; j < columns; j++) {
15461592Srgrimes				c = ctab + j * lines + i;
15471592Srgrimes				printf("%s%c", c->name,
15481592Srgrimes					c->implemented ? ' ' : '*');
15491592Srgrimes				if (c + lines >= &ctab[NCMDS])
15501592Srgrimes					break;
15511592Srgrimes				w = strlen(c->name) + 1;
15521592Srgrimes				while (w < width) {
15531592Srgrimes					putchar(' ');
15541592Srgrimes					w++;
15551592Srgrimes				}
15561592Srgrimes			}
15571592Srgrimes			printf("\r\n");
15581592Srgrimes		}
15591592Srgrimes		(void) fflush(stdout);
1560110037Syar		if (hostinfo)
1561110037Syar			reply(214, "Direct comments to ftp-bugs@%s.", hostname);
1562110037Syar		else
1563110037Syar			reply(214, "End.");
15641592Srgrimes		return;
15651592Srgrimes	}
15661592Srgrimes	upper(s);
15671592Srgrimes	c = lookup(ctab, s);
1568132931Syar	if (c == NULL) {
15691592Srgrimes		reply(502, "Unknown command %s.", s);
15701592Srgrimes		return;
15711592Srgrimes	}
15721592Srgrimes	if (c->implemented)
15731592Srgrimes		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
15741592Srgrimes	else
15751592Srgrimes		reply(214, "%s%-*s\t%s; unimplemented.", type, width,
15761592Srgrimes		    c->name, c->help);
15771592Srgrimes}
15781592Srgrimes
15791592Srgrimesstatic void
158090148Simpsizecmd(char *filename)
15811592Srgrimes{
15821592Srgrimes	switch (type) {
15831592Srgrimes	case TYPE_L:
15841592Srgrimes	case TYPE_I: {
15851592Srgrimes		struct stat stbuf;
158663350Sdes		if (stat(filename, &stbuf) < 0)
158763350Sdes			perror_reply(550, filename);
158863350Sdes		else if (!S_ISREG(stbuf.st_mode))
15891592Srgrimes			reply(550, "%s: not a plain file.", filename);
15901592Srgrimes		else
1591132929Syar			reply(213, "%jd", (intmax_t)stbuf.st_size);
15921592Srgrimes		break; }
15931592Srgrimes	case TYPE_A: {
15941592Srgrimes		FILE *fin;
15951592Srgrimes		int c;
15961592Srgrimes		off_t count;
15971592Srgrimes		struct stat stbuf;
15981592Srgrimes		fin = fopen(filename, "r");
15991592Srgrimes		if (fin == NULL) {
16001592Srgrimes			perror_reply(550, filename);
16011592Srgrimes			return;
16021592Srgrimes		}
160363350Sdes		if (fstat(fileno(fin), &stbuf) < 0) {
160463350Sdes			perror_reply(550, filename);
160563350Sdes			(void) fclose(fin);
160663350Sdes			return;
160763350Sdes		} else if (!S_ISREG(stbuf.st_mode)) {
16081592Srgrimes			reply(550, "%s: not a plain file.", filename);
16091592Srgrimes			(void) fclose(fin);
16101592Srgrimes			return;
1611101034Syar		} else if (stbuf.st_size > MAXASIZE) {
1612101034Syar			reply(550, "%s: too large for type A SIZE.", filename);
1613101034Syar			(void) fclose(fin);
1614101034Syar			return;
16151592Srgrimes		}
16161592Srgrimes
16171592Srgrimes		count = 0;
16181592Srgrimes		while((c=getc(fin)) != EOF) {
16191592Srgrimes			if (c == '\n')	/* will get expanded to \r\n */
16201592Srgrimes				count++;
16211592Srgrimes			count++;
16221592Srgrimes		}
16231592Srgrimes		(void) fclose(fin);
16241592Srgrimes
1625132929Syar		reply(213, "%jd", (intmax_t)count);
16261592Srgrimes		break; }
16271592Srgrimes	default:
1628100684Syar		reply(504, "SIZE not implemented for type %s.",
1629100684Syar		           typenames[type]);
16301592Srgrimes	}
16311592Srgrimes}
163256668Sshin
163356668Sshin/* Return 1, if port check is done. Return 0, if not yet. */
163456668Sshinstatic int
163590148Simpport_check(const char *pcmd)
163656668Sshin{
163756668Sshin	if (his_addr.su_family == AF_INET) {
163856668Sshin		if (data_dest.su_family != AF_INET) {
163956668Sshin			usedefault = 1;
164056668Sshin			reply(500, "Invalid address rejected.");
164156668Sshin			return 1;
164256668Sshin		}
164356668Sshin		if (paranoid &&
164456668Sshin		    ((ntohs(data_dest.su_port) < IPPORT_RESERVED) ||
164556668Sshin		     memcmp(&data_dest.su_sin.sin_addr,
164656668Sshin			    &his_addr.su_sin.sin_addr,
164756668Sshin			    sizeof(data_dest.su_sin.sin_addr)))) {
164856668Sshin			usedefault = 1;
164956668Sshin			reply(500, "Illegal PORT range rejected.");
165056668Sshin		} else {
165156668Sshin			usedefault = 0;
165256668Sshin			if (pdata >= 0) {
165356668Sshin				(void) close(pdata);
165456668Sshin				pdata = -1;
165556668Sshin			}
165656668Sshin			reply(200, "%s command successful.", pcmd);
165756668Sshin		}
165856668Sshin		return 1;
165956668Sshin	}
166056668Sshin	return 0;
166156668Sshin}
166256668Sshin
166370102Sphkstatic int
166490148Simpcheck_login1(void)
166570102Sphk{
166670102Sphk	if (logged_in)
166770102Sphk		return 1;
166870102Sphk	else {
166970102Sphk		reply(530, "Please login with USER and PASS.");
167070102Sphk		return 0;
167170102Sphk	}
167270102Sphk}
167370102Sphk
1674110340Syar/*
1675110340Syar * Replace leading "~user" in a pathname by the user's login directory.
1676110340Syar * Returned string will be in a freshly malloced buffer unless it's NULL.
1677110340Syar */
1678110340Syarstatic char *
1679110340Syarexptilde(char *s)
1680110340Syar{
1681110340Syar	char *p, *q;
1682110340Syar	char *path, *user;
1683110340Syar	struct passwd *ppw;
1684110340Syar
1685110340Syar	if ((p = strdup(s)) == NULL)
1686110340Syar		return (NULL);
1687110340Syar	if (*p != '~')
1688110340Syar		return (p);
1689110340Syar
1690110340Syar	user = p + 1;	/* skip tilde */
1691110340Syar	if ((path = strchr(p, '/')) != NULL)
1692110340Syar		*(path++) = '\0'; /* separate ~user from the rest of path */
1693110378Syar	if (*user == '\0') /* no user specified, use the current user */
1694110378Syar		user = pw->pw_name;
1695110378Syar	/* read passwd even for the current user since we may be chrooted */
1696110378Syar	if ((ppw = getpwnam(user)) != NULL) {
1697110340Syar		/* user found, substitute login directory for ~user */
1698110340Syar		if (path)
1699110340Syar			asprintf(&q, "%s/%s", ppw->pw_dir, path);
1700110340Syar		else
1701110340Syar			q = strdup(ppw->pw_dir);
1702110340Syar		free(p);
1703110340Syar		p = q;
1704110340Syar	} else {
1705110340Syar		/* user not found, undo the damage */
1706110340Syar		if (path)
1707110340Syar			path[-1] = '/';
1708110340Syar	}
1709110340Syar	return (p);
1710110340Syar}
1711110340Syar
1712110340Syar/*
1713110340Syar * Expand glob(3) patterns possibly present in a pathname.
1714110340Syar * Avoid expanding to a pathname including '\r' or '\n' in order to
1715110340Syar * not disrupt the FTP protocol.
1716110340Syar * The expansion found must be unique.
1717229780Suqs * Return the result as a malloced string, or NULL if an error occurred.
1718110340Syar *
1719110340Syar * Problem: this production is used for all pathname
1720110340Syar * processing, but only gives a 550 error reply.
1721110340Syar * This is a valid reply in some cases but not in others.
1722110340Syar */
1723110340Syarstatic char *
1724110340Syarexpglob(char *s)
1725110340Syar{
1726110340Syar	char *p, **pp, *rval;
1727110340Syar	int flags = GLOB_BRACE | GLOB_NOCHECK;
1728110340Syar	int n;
1729110340Syar	glob_t gl;
1730110340Syar
1731110340Syar	memset(&gl, 0, sizeof(gl));
1732110340Syar	flags |= GLOB_LIMIT;
1733110340Syar	gl.gl_matchc = MAXGLOBARGS;
1734110340Syar	if (glob(s, flags, NULL, &gl) == 0 && gl.gl_pathc != 0) {
1735110340Syar		for (pp = gl.gl_pathv, p = NULL, n = 0; *pp; pp++)
1736110340Syar			if (*(*pp + strcspn(*pp, "\r\n")) == '\0') {
1737110340Syar				p = *pp;
1738110340Syar				n++;
1739110340Syar			}
1740110340Syar		if (n == 0)
1741110340Syar			rval = strdup(s);
1742110340Syar		else if (n == 1)
1743110340Syar			rval = strdup(p);
1744110340Syar		else {
1745137852Syar			reply(550, "Wildcard is ambiguous.");
1746110340Syar			rval = NULL;
1747110340Syar		}
1748110340Syar	} else {
1749137852Syar		reply(550, "Wildcard expansion error.");
1750110340Syar		rval = NULL;
1751110340Syar	}
1752110340Syar	globfree(&gl);
1753110340Syar	return (rval);
1754110340Syar}
1755110340Syar
175656668Sshin#ifdef INET6
175756668Sshin/* Return 1, if port check is done. Return 0, if not yet. */
175856668Sshinstatic int
175990148Simpport_check_v6(const char *pcmd)
176056668Sshin{
176156668Sshin	if (his_addr.su_family == AF_INET6) {
176256668Sshin		if (IN6_IS_ADDR_V4MAPPED(&his_addr.su_sin6.sin6_addr))
176356668Sshin			/* Convert data_dest into v4 mapped sockaddr.*/
176456668Sshin			v4map_data_dest();
176556668Sshin		if (data_dest.su_family != AF_INET6) {
176656668Sshin			usedefault = 1;
176756668Sshin			reply(500, "Invalid address rejected.");
176856668Sshin			return 1;
176956668Sshin		}
177056668Sshin		if (paranoid &&
177156668Sshin		    ((ntohs(data_dest.su_port) < IPPORT_RESERVED) ||
177256668Sshin		     memcmp(&data_dest.su_sin6.sin6_addr,
177356668Sshin			    &his_addr.su_sin6.sin6_addr,
177456668Sshin			    sizeof(data_dest.su_sin6.sin6_addr)))) {
177556668Sshin			usedefault = 1;
177656668Sshin			reply(500, "Illegal PORT range rejected.");
177756668Sshin		} else {
177856668Sshin			usedefault = 0;
177956668Sshin			if (pdata >= 0) {
178056668Sshin				(void) close(pdata);
178156668Sshin				pdata = -1;
178256668Sshin			}
178356668Sshin			reply(200, "%s command successful.", pcmd);
178456668Sshin		}
178556668Sshin		return 1;
178656668Sshin	}
178756668Sshin	return 0;
178856668Sshin}
178956668Sshin
179056668Sshinstatic void
179190148Simpv4map_data_dest(void)
179256668Sshin{
179356668Sshin	struct in_addr savedaddr;
179456668Sshin	int savedport;
179556668Sshin
179656668Sshin	if (data_dest.su_family != AF_INET) {
179756668Sshin		usedefault = 1;
179856668Sshin		reply(500, "Invalid address rejected.");
179956668Sshin		return;
180056668Sshin	}
180156668Sshin
180256668Sshin	savedaddr = data_dest.su_sin.sin_addr;
180356668Sshin	savedport = data_dest.su_port;
180456668Sshin
180556668Sshin	memset(&data_dest, 0, sizeof(data_dest));
180656668Sshin	data_dest.su_sin6.sin6_len = sizeof(struct sockaddr_in6);
180756668Sshin	data_dest.su_sin6.sin6_family = AF_INET6;
180856668Sshin	data_dest.su_sin6.sin6_port = savedport;
180956668Sshin	memset((caddr_t)&data_dest.su_sin6.sin6_addr.s6_addr[10], 0xff, 2);
181056668Sshin	memcpy((caddr_t)&data_dest.su_sin6.sin6_addr.s6_addr[12],
181156668Sshin	       (caddr_t)&savedaddr, sizeof(savedaddr));
181256668Sshin}
181356668Sshin#endif
1814