1/*
2 * Copyright (c) 1985, 1988, 1993, 1994
3 *	The Regents of the University of California.  All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 * 3. All advertising materials mentioning features or use of this software
14 *    must display the following acknowledgement:
15 *	This product includes software developed by the University of
16 *	California, Berkeley and its contributors.
17 * 4. Neither the name of the University nor the names of its contributors
18 *    may be used to endorse or promote products derived from this software
19 *    without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 *
33 *	@(#)ftpcmd.y	8.3 (Berkeley) 4/6/94
34 */
35
36/*
37 * Grammar for FTP commands.
38 * See RFC 959.
39 */
40
41%{
42
43#ifndef lint
44#if 0
45static char sccsid[] = "@(#)ftpcmd.y	8.3 (Berkeley) 4/6/94";
46#endif
47#endif /* not lint */
48
49#include <sys/cdefs.h>
50__FBSDID("$FreeBSD: src/libexec/ftpd/ftpcmd.y,v 1.66 2007/04/18 22:43:39 yar Exp $");
51
52#include <sys/param.h>
53#include <sys/socket.h>
54#include <sys/stat.h>
55
56#include <netinet/in.h>
57#include <arpa/ftp.h>
58
59#include <ctype.h>
60#include <errno.h>
61#include <glob.h>
62#include <libutil.h>
63#include <limits.h>
64#include <md5.h>
65#include <netdb.h>
66#include <pwd.h>
67#include <signal.h>
68#include <stdint.h>
69#include <stdio.h>
70#include <stdlib.h>
71#include <string.h>
72#include <syslog.h>
73#include <time.h>
74#include <unistd.h>
75
76#include "extern.h"
77#include "pathnames.h"
78
79extern	union sockunion data_dest, his_addr;
80extern	int hostinfo;
81extern	int logged_in;
82extern	struct passwd *pw;
83extern	int guest;
84extern	char *homedir;
85extern 	int paranoid;
86extern	int logging;
87extern	int type;
88extern	int form;
89extern	int ftpdebug;
90extern	int timeout;
91extern	int maxtimeout;
92extern  int pdata;
93extern	char *hostname;
94extern	char proctitle[];
95extern	int usedefault;
96extern  char tmpline[];
97extern	int readonly;
98extern	int assumeutf8;
99extern	int noepsv;
100extern	int noretr;
101extern	int noguestretr;
102extern	char *typenames[]; /* defined in <arpa/ftp.h> included from ftpd.c */
103
104off_t	restart_point;
105
106static	int cmd_type;
107static	int cmd_form;
108static	int cmd_bytesz;
109static	int state;
110char	cbuf[512];
111char	*fromname = NULL;
112
113extern int epsvall;
114
115#define	CMD	0	/* beginning of command */
116#define	ARGS	1	/* expect miscellaneous arguments */
117#define	STR1	2	/* expect SP followed by STRING */
118#define	STR2	3	/* expect STRING */
119#define	OSTR	4	/* optional SP then STRING */
120#define	ZSTR1	5	/* optional SP then optional STRING */
121#define	ZSTR2	6	/* optional STRING after SP */
122#define	SITECMD	7	/* SITE command */
123#define	NSTR	8	/* Number followed by a string */
124
125#define	MAXGLOBARGS	1000
126
127#define	MAXASIZE	10240	/* Deny ASCII SIZE on files larger than that */
128
129%}
130
131%union {
132	struct {
133		off_t	o;
134		int	i;
135	} u;
136	char   *s;
137}
138
139%token
140	A	B	C	E	F	I
141	L	N	P	R	S	T
142	ALL
143
144	SP	CRLF	COMMA
145
146	USER	PASS	ACCT	REIN	QUIT	PORT
147	PASV	TYPE	STRU	MODE	RETR	STOR
148	APPE	MLFL	MAIL	MSND	MSOM	MSAM
149	MRSQ	MRCP	ALLO	REST	RNFR	RNTO
150	ABOR	DELE	CWD	LIST	NLST	SITE
151	STAT	HELP	NOOP	MKD	RMD	PWD
152	CDUP	STOU	SMNT	SYST	SIZE	MDTM
153	LPRT	LPSV	EPRT	EPSV	FEAT
154
155	UMASK	IDLE	CHMOD	MDFIVE
156
157	LEXERR	NOTIMPL
158
159%token	<s> STRING
160%token	<u> NUMBER
161
162%type	<u.i> check_login octal_number byte_size
163%type	<u.i> check_login_ro check_login_epsv
164%type	<u.i> struct_code mode_code type_code form_code
165%type	<s> pathstring pathname password username
166%type	<s> ALL NOTIMPL
167
168%start	cmd_list
169
170%{
171
172struct tab {
173	char	*name;
174	short	token;
175	short	state;
176	short	implemented;	/* 1 if command is implemented */
177	char	*help;
178};
179
180struct tab cmdtab[] = {		/* In order defined in RFC 765 */
181	{ "USER", USER, STR1, 1,	"<sp> username" },
182	{ "PASS", PASS, ZSTR1, 1,	"[<sp> [password]]" },
183	{ "ACCT", ACCT, STR1, 0,	"(specify account)" },
184	{ "SMNT", SMNT, ARGS, 0,	"(structure mount)" },
185	{ "REIN", REIN, ARGS, 0,	"(reinitialize server state)" },
186	{ "QUIT", QUIT, ARGS, 1,	"(terminate service)", },
187	{ "PORT", PORT, ARGS, 1,	"<sp> b0, b1, b2, b3, b4, b5" },
188	{ "LPRT", LPRT, ARGS, 1,	"<sp> af, hal, h1, h2, h3,..., pal, p1, p2..." },
189	{ "EPRT", EPRT, STR1, 1,	"<sp> |af|addr|port|" },
190	{ "PASV", PASV, ARGS, 1,	"(set server in passive mode)" },
191	{ "LPSV", LPSV, ARGS, 1,	"(set server in passive mode)" },
192	{ "EPSV", EPSV, ARGS, 1,	"[<sp> af|ALL]" },
193	{ "TYPE", TYPE, ARGS, 1,	"<sp> { A | E | I | L }" },
194	{ "STRU", STRU, ARGS, 1,	"(specify file structure)" },
195	{ "MODE", MODE, ARGS, 1,	"(specify transfer mode)" },
196	{ "RETR", RETR, STR1, 1,	"<sp> file-name" },
197	{ "STOR", STOR, STR1, 1,	"<sp> file-name" },
198	{ "APPE", APPE, STR1, 1,	"<sp> file-name" },
199	{ "MLFL", MLFL, OSTR, 0,	"(mail file)" },
200	{ "MAIL", MAIL, OSTR, 0,	"(mail to user)" },
201	{ "MSND", MSND, OSTR, 0,	"(mail send to terminal)" },
202	{ "MSOM", MSOM, OSTR, 0,	"(mail send to terminal or mailbox)" },
203	{ "MSAM", MSAM, OSTR, 0,	"(mail send to terminal and mailbox)" },
204	{ "MRSQ", MRSQ, OSTR, 0,	"(mail recipient scheme question)" },
205	{ "MRCP", MRCP, STR1, 0,	"(mail recipient)" },
206	{ "ALLO", ALLO, ARGS, 1,	"allocate storage (vacuously)" },
207	{ "REST", REST, ARGS, 1,	"<sp> offset (restart command)" },
208	{ "RNFR", RNFR, STR1, 1,	"<sp> file-name" },
209	{ "RNTO", RNTO, STR1, 1,	"<sp> file-name" },
210	{ "ABOR", ABOR, ARGS, 1,	"(abort operation)" },
211	{ "DELE", DELE, STR1, 1,	"<sp> file-name" },
212	{ "CWD",  CWD,  OSTR, 1,	"[ <sp> directory-name ]" },
213	{ "XCWD", CWD,	OSTR, 1,	"[ <sp> directory-name ]" },
214	{ "LIST", LIST, OSTR, 1,	"[ <sp> path-name ]" },
215	{ "NLST", NLST, OSTR, 1,	"[ <sp> path-name ]" },
216	{ "SITE", SITE, SITECMD, 1,	"site-cmd [ <sp> arguments ]" },
217	{ "SYST", SYST, ARGS, 1,	"(get type of operating system)" },
218	{ "FEAT", FEAT, ARGS, 1,	"(get extended features)" },
219	{ "STAT", STAT, OSTR, 1,	"[ <sp> path-name ]" },
220	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
221	{ "NOOP", NOOP, ARGS, 1,	"" },
222	{ "MKD",  MKD,  STR1, 1,	"<sp> path-name" },
223	{ "XMKD", MKD,  STR1, 1,	"<sp> path-name" },
224	{ "RMD",  RMD,  STR1, 1,	"<sp> path-name" },
225	{ "XRMD", RMD,  STR1, 1,	"<sp> path-name" },
226	{ "PWD",  PWD,  ARGS, 1,	"(return current directory)" },
227	{ "XPWD", PWD,  ARGS, 1,	"(return current directory)" },
228	{ "CDUP", CDUP, ARGS, 1,	"(change to parent directory)" },
229	{ "XCUP", CDUP, ARGS, 1,	"(change to parent directory)" },
230	{ "STOU", STOU, STR1, 1,	"<sp> file-name" },
231	{ "SIZE", SIZE, OSTR, 1,	"<sp> path-name" },
232	{ "MDTM", MDTM, OSTR, 1,	"<sp> path-name" },
233	{ NULL,   0,    0,    0,	0 }
234};
235
236struct tab sitetab[] = {
237	{ "MD5", MDFIVE, STR1, 1,	"[ <sp> file-name ]" },
238	{ "UMASK", UMASK, ARGS, 1,	"[ <sp> umask ]" },
239	{ "IDLE", IDLE, ARGS, 1,	"[ <sp> maximum-idle-time ]" },
240	{ "CHMOD", CHMOD, NSTR, 1,	"<sp> mode <sp> file-name" },
241	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
242	{ NULL,   0,    0,    0,	0 }
243};
244
245static char	*copy(char *);
246static char	*expglob(char *);
247static char	*exptilde(char *);
248static void	 help(struct tab *, char *);
249static struct tab *
250		 lookup(struct tab *, char *);
251static int	 port_check(const char *);
252#ifdef INET6
253static int	 port_check_v6(const char *);
254#endif
255static int	 check_login1(void);
256static void	 sizecmd(char *);
257static void	 toolong(int);
258#ifdef INET6
259static void	 v4map_data_dest(void);
260#endif
261static int	 yylex(void);
262
263%}
264
265%%
266
267cmd_list
268	: /* empty */
269	| cmd_list cmd
270		{
271			if (fromname)
272				free(fromname);
273			fromname = NULL;
274			restart_point = 0;
275		}
276	| cmd_list rcmd
277	;
278
279cmd
280	: USER SP username CRLF
281		{
282			user($3);
283			free($3);
284		}
285	| PASS SP password CRLF
286		{
287			pass($3);
288			free($3);
289		}
290	| PASS CRLF
291		{
292			pass("");
293		}
294	| PORT check_login SP host_port CRLF
295		{
296			if (epsvall) {
297				reply(501, "No PORT allowed after EPSV ALL.");
298				goto port_done;
299			}
300			if (!$2)
301				goto port_done;
302			if (port_check("PORT") == 1)
303				goto port_done;
304#ifdef INET6
305			if ((his_addr.su_family != AF_INET6 ||
306			     !IN6_IS_ADDR_V4MAPPED(&his_addr.su_sin6.sin6_addr))) {
307				/* shoud never happen */
308				usedefault = 1;
309				reply(500, "Invalid address rejected.");
310				goto port_done;
311			}
312			port_check_v6("pcmd");
313#endif
314		port_done:
315			;
316		}
317	| LPRT check_login SP host_long_port CRLF
318		{
319			if (epsvall) {
320				reply(501, "No LPRT allowed after EPSV ALL.");
321				goto lprt_done;
322			}
323			if (!$2)
324				goto lprt_done;
325			if (port_check("LPRT") == 1)
326				goto lprt_done;
327#ifdef INET6
328			if (his_addr.su_family != AF_INET6) {
329				usedefault = 1;
330				reply(500, "Invalid address rejected.");
331				goto lprt_done;
332			}
333			if (port_check_v6("LPRT") == 1)
334				goto lprt_done;
335#endif
336		lprt_done:
337			;
338		}
339	| EPRT check_login SP STRING CRLF
340		{
341			char delim;
342			char *tmp = NULL;
343			char *p, *q;
344			char *result[3];
345			struct addrinfo hints;
346			struct addrinfo *res;
347			int i;
348
349			if (epsvall) {
350				reply(501, "No EPRT allowed after EPSV ALL.");
351				goto eprt_done;
352			}
353			if (!$2)
354				goto eprt_done;
355
356			memset(&data_dest, 0, sizeof(data_dest));
357			tmp = strdup($4);
358			if (ftpdebug)
359				syslog(LOG_DEBUG, "%s", tmp);
360			if (!tmp) {
361				fatalerror("not enough core");
362				/*NOTREACHED*/
363			}
364			p = tmp;
365			delim = p[0];
366			p++;
367			memset(result, 0, sizeof(result));
368			for (i = 0; i < 3; i++) {
369				q = strchr(p, delim);
370				if (!q || *q != delim) {
371		parsefail:
372					reply(500,
373						"Invalid argument, rejected.");
374					if (tmp)
375						free(tmp);
376					usedefault = 1;
377					goto eprt_done;
378				}
379				*q++ = '\0';
380				result[i] = p;
381				if (ftpdebug)
382					syslog(LOG_DEBUG, "%d: %s", i, p);
383				p = q;
384			}
385
386			/* some more sanity check */
387			p = result[0];
388			while (*p) {
389				if (!isdigit(*p))
390					goto parsefail;
391				p++;
392			}
393			p = result[2];
394			while (*p) {
395				if (!isdigit(*p))
396					goto parsefail;
397				p++;
398			}
399
400			/* grab address */
401			memset(&hints, 0, sizeof(hints));
402			if (atoi(result[0]) == 1)
403				hints.ai_family = PF_INET;
404#ifdef INET6
405			else if (atoi(result[0]) == 2)
406				hints.ai_family = PF_INET6;
407#endif
408			else
409				hints.ai_family = PF_UNSPEC;	/*XXX*/
410			hints.ai_socktype = SOCK_STREAM;
411			i = getaddrinfo(result[1], result[2], &hints, &res);
412			if (i)
413				goto parsefail;
414			memcpy(&data_dest, res->ai_addr, res->ai_addrlen);
415#ifdef INET6
416			if (his_addr.su_family == AF_INET6
417			    && data_dest.su_family == AF_INET6) {
418				/* XXX more sanity checks! */
419				data_dest.su_sin6.sin6_scope_id =
420					his_addr.su_sin6.sin6_scope_id;
421			}
422#endif
423			free(tmp);
424			tmp = NULL;
425
426			if (port_check("EPRT") == 1)
427				goto eprt_done;
428#ifdef INET6
429			if (his_addr.su_family != AF_INET6) {
430				usedefault = 1;
431				reply(500, "Invalid address rejected.");
432				goto eprt_done;
433			}
434			if (port_check_v6("EPRT") == 1)
435				goto eprt_done;
436#endif
437		eprt_done:
438			free($4);
439		}
440	| PASV check_login CRLF
441		{
442			if (epsvall)
443				reply(501, "No PASV allowed after EPSV ALL.");
444			else if ($2)
445				passive();
446		}
447	| LPSV check_login CRLF
448		{
449			if (epsvall)
450				reply(501, "No LPSV allowed after EPSV ALL.");
451			else if ($2)
452				long_passive("LPSV", PF_UNSPEC);
453		}
454	| EPSV check_login_epsv SP NUMBER CRLF
455		{
456			if ($2) {
457				int pf;
458				switch ($4.i) {
459				case 1:
460					pf = PF_INET;
461					break;
462#ifdef INET6
463				case 2:
464					pf = PF_INET6;
465					break;
466#endif
467				default:
468					pf = -1;	/*junk value*/
469					break;
470				}
471				long_passive("EPSV", pf);
472			}
473		}
474	| EPSV check_login_epsv SP ALL CRLF
475		{
476			if ($2) {
477				reply(200, "EPSV ALL command successful.");
478				epsvall++;
479			}
480		}
481	| EPSV check_login_epsv CRLF
482		{
483			if ($2)
484				long_passive("EPSV", PF_UNSPEC);
485		}
486	| TYPE check_login SP type_code CRLF
487		{
488			if ($2) {
489				switch (cmd_type) {
490
491				case TYPE_A:
492					if (cmd_form == FORM_N) {
493						reply(200, "Type set to A.");
494						type = cmd_type;
495						form = cmd_form;
496					} else
497						reply(504, "Form must be N.");
498					break;
499
500				case TYPE_E:
501					reply(504, "Type E not implemented.");
502					break;
503
504				case TYPE_I:
505					reply(200, "Type set to I.");
506					type = cmd_type;
507					break;
508
509				case TYPE_L:
510#if CHAR_BIT == 8
511					if (cmd_bytesz == 8) {
512						reply(200,
513						    "Type set to L (byte size 8).");
514						type = cmd_type;
515					} else
516						reply(504, "Byte size must be 8.");
517#else /* CHAR_BIT == 8 */
518					UNIMPLEMENTED for CHAR_BIT != 8
519#endif /* CHAR_BIT == 8 */
520				}
521			}
522		}
523	| STRU check_login SP struct_code CRLF
524		{
525			if ($2) {
526				switch ($4) {
527
528				case STRU_F:
529					reply(200, "STRU F accepted.");
530					break;
531
532				default:
533					reply(504, "Unimplemented STRU type.");
534				}
535			}
536		}
537	| MODE check_login SP mode_code CRLF
538		{
539			if ($2) {
540				switch ($4) {
541
542				case MODE_S:
543					reply(200, "MODE S accepted.");
544					break;
545
546				default:
547					reply(502, "Unimplemented MODE type.");
548				}
549			}
550		}
551	| ALLO check_login SP NUMBER CRLF
552		{
553			if ($2) {
554				reply(202, "ALLO command ignored.");
555			}
556		}
557	| ALLO check_login SP NUMBER SP R SP NUMBER CRLF
558		{
559			if ($2) {
560				reply(202, "ALLO command ignored.");
561			}
562		}
563	| RETR check_login SP pathname CRLF
564		{
565			if (noretr || (guest && noguestretr))
566				reply(500, "RETR command disabled.");
567			else if ($2 && $4 != NULL)
568				retrieve(NULL, $4);
569
570			if ($4 != NULL)
571				free($4);
572		}
573	| STOR check_login_ro SP pathname CRLF
574		{
575			if ($2 && $4 != NULL)
576				store($4, "w", 0);
577			if ($4 != NULL)
578				free($4);
579		}
580	| APPE check_login_ro SP pathname CRLF
581		{
582			if ($2 && $4 != NULL)
583				store($4, "a", 0);
584			if ($4 != NULL)
585				free($4);
586		}
587	| NLST check_login CRLF
588		{
589			if ($2)
590				send_file_list(".");
591		}
592	| NLST check_login SP pathstring CRLF
593		{
594			if ($2)
595				send_file_list($4);
596			free($4);
597		}
598	| LIST check_login CRLF
599		{
600			if ($2)
601				retrieve(_PATH_LS " -lgA", "");
602		}
603	| LIST check_login SP pathstring CRLF
604		{
605			if ($2)
606				retrieve(_PATH_LS " -lgA %s", $4);
607			free($4);
608		}
609	| STAT check_login SP pathname CRLF
610		{
611			if ($2 && $4 != NULL)
612				statfilecmd($4);
613			if ($4 != NULL)
614				free($4);
615		}
616	| STAT check_login CRLF
617		{
618			if ($2) {
619				statcmd();
620			}
621		}
622	| DELE check_login_ro SP pathname CRLF
623		{
624			if ($2 && $4 != NULL)
625				delete($4);
626			if ($4 != NULL)
627				free($4);
628		}
629	| RNTO check_login_ro SP pathname CRLF
630		{
631			if ($2 && $4 != NULL) {
632				if (fromname) {
633					renamecmd(fromname, $4);
634					free(fromname);
635					fromname = NULL;
636				} else {
637					reply(503, "Bad sequence of commands.");
638				}
639			}
640			if ($4 != NULL)
641				free($4);
642		}
643	| ABOR check_login CRLF
644		{
645			if ($2)
646				reply(225, "ABOR command successful.");
647		}
648	| CWD check_login CRLF
649		{
650			if ($2) {
651				cwd(homedir);
652			}
653		}
654	| CWD check_login SP pathname CRLF
655		{
656			if ($2 && $4 != NULL)
657				cwd($4);
658			if ($4 != NULL)
659				free($4);
660		}
661	| HELP CRLF
662		{
663			help(cmdtab, NULL);
664		}
665	| HELP SP STRING CRLF
666		{
667			char *cp = $3;
668
669			if (strncasecmp(cp, "SITE", 4) == 0) {
670				cp = $3 + 4;
671				if (*cp == ' ')
672					cp++;
673				if (*cp)
674					help(sitetab, cp);
675				else
676					help(sitetab, NULL);
677			} else
678				help(cmdtab, $3);
679			free($3);
680		}
681	| NOOP CRLF
682		{
683			reply(200, "NOOP command successful.");
684		}
685	| MKD check_login_ro SP pathname CRLF
686		{
687			if ($2 && $4 != NULL)
688				makedir($4);
689			if ($4 != NULL)
690				free($4);
691		}
692	| RMD check_login_ro SP pathname CRLF
693		{
694			if ($2 && $4 != NULL)
695				removedir($4);
696			if ($4 != NULL)
697				free($4);
698		}
699	| PWD check_login CRLF
700		{
701			if ($2)
702				pwd();
703		}
704	| CDUP check_login CRLF
705		{
706			if ($2)
707				cwd("..");
708		}
709	| SITE SP HELP CRLF
710		{
711			help(sitetab, NULL);
712		}
713	| SITE SP HELP SP STRING CRLF
714		{
715			help(sitetab, $5);
716			free($5);
717		}
718	| SITE SP MDFIVE check_login SP pathname CRLF
719		{
720			char p[64], *q;
721
722			if ($4 && $6) {
723				q = MD5File($6, p);
724				if (q != NULL)
725					reply(200, "MD5(%s) = %s", $6, p);
726				else
727					perror_reply(550, $6);
728			}
729			if ($6)
730				free($6);
731		}
732	| SITE SP UMASK check_login CRLF
733		{
734			int oldmask;
735
736			if ($4) {
737				oldmask = umask(0);
738				(void) umask(oldmask);
739				reply(200, "Current UMASK is %03o.", oldmask);
740			}
741		}
742	| SITE SP UMASK check_login SP octal_number CRLF
743		{
744			int oldmask;
745
746			if ($4) {
747				if (($6 == -1) || ($6 > 0777)) {
748					reply(501, "Bad UMASK value.");
749				} else {
750					oldmask = umask($6);
751					reply(200,
752					    "UMASK set to %03o (was %03o).",
753					    $6, oldmask);
754				}
755			}
756		}
757	| SITE SP CHMOD check_login_ro SP octal_number SP pathname CRLF
758		{
759			if ($4 && ($8 != NULL)) {
760				if (($6 == -1 ) || ($6 > 0777))
761					reply(501, "Bad mode value.");
762				else if (chmod($8, $6) < 0)
763					perror_reply(550, $8);
764				else
765					reply(200, "CHMOD command successful.");
766			}
767			if ($8 != NULL)
768				free($8);
769		}
770	| SITE SP check_login IDLE CRLF
771		{
772			if ($3)
773				reply(200,
774			    	    "Current IDLE time limit is %d seconds; max %d.",
775				    timeout, maxtimeout);
776		}
777	| SITE SP check_login IDLE SP NUMBER CRLF
778		{
779			if ($3) {
780				if ($6.i < 30 || $6.i > maxtimeout) {
781					reply(501,
782					    "Maximum IDLE time must be between 30 and %d seconds.",
783					    maxtimeout);
784				} else {
785					timeout = $6.i;
786					(void) alarm(timeout);
787					reply(200,
788					    "Maximum IDLE time set to %d seconds.",
789					    timeout);
790				}
791			}
792		}
793	| STOU check_login_ro SP pathname CRLF
794		{
795			if ($2 && $4 != NULL)
796				store($4, "w", 1);
797			if ($4 != NULL)
798				free($4);
799		}
800	| FEAT CRLF
801		{
802			lreply(211, "Extensions supported:");
803#if 0
804			/* XXX these two keywords are non-standard */
805			printf(" EPRT\r\n");
806			if (!noepsv)
807				printf(" EPSV\r\n");
808#endif
809			printf(" MDTM\r\n");
810			printf(" REST STREAM\r\n");
811			printf(" SIZE\r\n");
812			if (assumeutf8) {
813				/* TVFS requires UTF8, see RFC 3659 */
814				printf(" TVFS\r\n");
815				printf(" UTF8\r\n");
816			}
817			reply(211, "End.");
818		}
819	| SYST check_login CRLF
820		{
821			if ($2) {
822				if (hostinfo)
823#ifdef BSD
824					reply(215, "UNIX Type: L%d Version: BSD-%d",
825					      CHAR_BIT, BSD);
826#else /* BSD */
827					reply(215, "UNIX Type: L%d", CHAR_BIT);
828#endif /* BSD */
829				else
830					reply(215, "UNKNOWN Type: L%d", CHAR_BIT);
831			}
832		}
833
834		/*
835		 * SIZE is not in RFC959, but Postel has blessed it and
836		 * it will be in the updated RFC.
837		 *
838		 * Return size of file in a format suitable for
839		 * using with RESTART (we just count bytes).
840		 */
841	| SIZE check_login SP pathname CRLF
842		{
843			if ($2 && $4 != NULL)
844				sizecmd($4);
845			if ($4 != NULL)
846				free($4);
847		}
848
849		/*
850		 * MDTM is not in RFC959, but Postel has blessed it and
851		 * it will be in the updated RFC.
852		 *
853		 * Return modification time of file as an ISO 3307
854		 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
855		 * where xxx is the fractional second (of any precision,
856		 * not necessarily 3 digits)
857		 */
858	| MDTM check_login SP pathname CRLF
859		{
860			if ($2 && $4 != NULL) {
861				struct stat stbuf;
862				if (stat($4, &stbuf) < 0)
863					perror_reply(550, $4);
864				else if (!S_ISREG(stbuf.st_mode)) {
865					reply(550, "%s: not a plain file.", $4);
866				} else {
867					struct tm *t;
868					t = gmtime(&stbuf.st_mtime);
869					reply(213,
870					    "%04d%02d%02d%02d%02d%02d",
871					    1900 + t->tm_year,
872					    t->tm_mon+1, t->tm_mday,
873					    t->tm_hour, t->tm_min, t->tm_sec);
874				}
875			}
876			if ($4 != NULL)
877				free($4);
878		}
879	| QUIT CRLF
880		{
881			reply(221, "Goodbye.");
882			dologout(0);
883		}
884	| NOTIMPL
885		{
886			nack($1);
887		}
888	| error
889		{
890			yyclearin;		/* discard lookahead data */
891			yyerrok;		/* clear error condition */
892			state = CMD;		/* reset lexer state */
893		}
894	;
895rcmd
896	: RNFR check_login_ro SP pathname CRLF
897		{
898			restart_point = 0;
899			if ($2 && $4) {
900				if (fromname)
901					free(fromname);
902				fromname = NULL;
903				if (renamefrom($4))
904					fromname = $4;
905				else
906					free($4);
907			} else if ($4) {
908				free($4);
909			}
910		}
911	| REST check_login SP NUMBER CRLF
912		{
913			if ($2) {
914				if (fromname)
915					free(fromname);
916				fromname = NULL;
917				restart_point = $4.o;
918				reply(350, "Restarting at %lld. %s",
919				    (intmax_t)restart_point,
920				    "Send STORE or RETRIEVE to initiate transfer.");
921			}
922		}
923	;
924
925username
926	: STRING
927	;
928
929password
930	: /* empty */
931		{
932			$$ = (char *)calloc(1, sizeof(char));
933		}
934	| STRING
935	;
936
937byte_size
938	: NUMBER
939		{
940			$$ = $1.i;
941		}
942	;
943
944host_port
945	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
946		NUMBER COMMA NUMBER
947		{
948			char *a, *p;
949
950			data_dest.su_len = sizeof(struct sockaddr_in);
951			data_dest.su_family = AF_INET;
952			p = (char *)&data_dest.su_sin.sin_port;
953			p[0] = $9.i; p[1] = $11.i;
954			a = (char *)&data_dest.su_sin.sin_addr;
955			a[0] = $1.i; a[1] = $3.i; a[2] = $5.i; a[3] = $7.i;
956		}
957	;
958
959host_long_port
960	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
961		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
962		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
963		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
964		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
965		NUMBER
966		{
967			char *a, *p;
968
969			memset(&data_dest, 0, sizeof(data_dest));
970			data_dest.su_len = sizeof(struct sockaddr_in6);
971			data_dest.su_family = AF_INET6;
972			p = (char *)&data_dest.su_port;
973			p[0] = $39.i; p[1] = $41.i;
974			a = (char *)&data_dest.su_sin6.sin6_addr;
975			a[0] = $5.i; a[1] = $7.i; a[2] = $9.i; a[3] = $11.i;
976			a[4] = $13.i; a[5] = $15.i; a[6] = $17.i; a[7] = $19.i;
977			a[8] = $21.i; a[9] = $23.i; a[10] = $25.i; a[11] = $27.i;
978			a[12] = $29.i; a[13] = $31.i; a[14] = $33.i; a[15] = $35.i;
979			if (his_addr.su_family == AF_INET6) {
980				/* XXX more sanity checks! */
981				data_dest.su_sin6.sin6_scope_id =
982					his_addr.su_sin6.sin6_scope_id;
983			}
984			if ($1.i != 6 || $3.i != 16 || $37.i != 2)
985				memset(&data_dest, 0, sizeof(data_dest));
986		}
987	| NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
988		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
989		NUMBER
990		{
991			char *a, *p;
992
993			memset(&data_dest, 0, sizeof(data_dest));
994			data_dest.su_sin.sin_len = sizeof(struct sockaddr_in);
995			data_dest.su_family = AF_INET;
996			p = (char *)&data_dest.su_port;
997			p[0] = $15.i; p[1] = $17.i;
998			a = (char *)&data_dest.su_sin.sin_addr;
999			a[0] =  $5.i; a[1] = $7.i; a[2] = $9.i; a[3] = $11.i;
1000			if ($1.i != 4 || $3.i != 4 || $13.i != 2)
1001				memset(&data_dest, 0, sizeof(data_dest));
1002		}
1003	;
1004
1005form_code
1006	: N
1007		{
1008			$$ = FORM_N;
1009		}
1010	| T
1011		{
1012			$$ = FORM_T;
1013		}
1014	| C
1015		{
1016			$$ = FORM_C;
1017		}
1018	;
1019
1020type_code
1021	: A
1022		{
1023			cmd_type = TYPE_A;
1024			cmd_form = FORM_N;
1025		}
1026	| A SP form_code
1027		{
1028			cmd_type = TYPE_A;
1029			cmd_form = $3;
1030		}
1031	| E
1032		{
1033			cmd_type = TYPE_E;
1034			cmd_form = FORM_N;
1035		}
1036	| E SP form_code
1037		{
1038			cmd_type = TYPE_E;
1039			cmd_form = $3;
1040		}
1041	| I
1042		{
1043			cmd_type = TYPE_I;
1044		}
1045	| L
1046		{
1047			cmd_type = TYPE_L;
1048			cmd_bytesz = CHAR_BIT;
1049		}
1050	| L SP byte_size
1051		{
1052			cmd_type = TYPE_L;
1053			cmd_bytesz = $3;
1054		}
1055		/* this is for a bug in the BBN ftp */
1056	| L byte_size
1057		{
1058			cmd_type = TYPE_L;
1059			cmd_bytesz = $2;
1060		}
1061	;
1062
1063struct_code
1064	: F
1065		{
1066			$$ = STRU_F;
1067		}
1068	| R
1069		{
1070			$$ = STRU_R;
1071		}
1072	| P
1073		{
1074			$$ = STRU_P;
1075		}
1076	;
1077
1078mode_code
1079	: S
1080		{
1081			$$ = MODE_S;
1082		}
1083	| B
1084		{
1085			$$ = MODE_B;
1086		}
1087	| C
1088		{
1089			$$ = MODE_C;
1090		}
1091	;
1092
1093pathname
1094	: pathstring
1095		{
1096			if (logged_in && $1) {
1097				char *p;
1098
1099				/*
1100				 * Expand ~user manually since glob(3)
1101				 * will return the unexpanded pathname
1102				 * if the corresponding file/directory
1103				 * doesn't exist yet.  Using sole glob(3)
1104				 * would break natural commands like
1105				 * MKD ~user/newdir
1106				 * or
1107				 * RNTO ~/newfile
1108				 */
1109				if ((p = exptilde($1)) != NULL) {
1110					$$ = expglob(p);
1111					free(p);
1112				} else
1113					$$ = NULL;
1114				free($1);
1115			} else
1116				$$ = $1;
1117		}
1118	;
1119
1120pathstring
1121	: STRING
1122	;
1123
1124octal_number
1125	: NUMBER
1126		{
1127			int ret, dec, multby, digit;
1128
1129			/*
1130			 * Convert a number that was read as decimal number
1131			 * to what it would be if it had been read as octal.
1132			 */
1133			dec = $1.i;
1134			multby = 1;
1135			ret = 0;
1136			while (dec) {
1137				digit = dec%10;
1138				if (digit > 7) {
1139					ret = -1;
1140					break;
1141				}
1142				ret += digit * multby;
1143				multby *= 8;
1144				dec /= 10;
1145			}
1146			$$ = ret;
1147		}
1148	;
1149
1150
1151check_login
1152	: /* empty */
1153		{
1154		$$ = check_login1();
1155		}
1156	;
1157
1158check_login_epsv
1159	: /* empty */
1160		{
1161		if (noepsv) {
1162			reply(500, "EPSV command disabled.");
1163			$$ = 0;
1164		}
1165		else
1166			$$ = check_login1();
1167		}
1168	;
1169
1170check_login_ro
1171	: /* empty */
1172		{
1173		if (readonly) {
1174			reply(550, "Permission denied.");
1175			$$ = 0;
1176		}
1177		else
1178			$$ = check_login1();
1179		}
1180	;
1181
1182%%
1183
1184static struct tab *
1185lookup(struct tab *p, char *cmd)
1186{
1187
1188	for (; p->name != NULL; p++)
1189		if (strcmp(cmd, p->name) == 0)
1190			return (p);
1191	return (0);
1192}
1193
1194#include <arpa/telnet.h>
1195
1196/*
1197 * ftpd_getline - a hacked up version of fgets to ignore TELNET escape codes.
1198 */
1199int *
1200ftpd_getline(char *s, int n, FILE *iop)
1201{
1202	int c;
1203	register char *cs;
1204	sigset_t sset, osset;
1205
1206	cs = s;
1207/* tmpline may contain saved command from urgent mode interruption */
1208	for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
1209		*cs++ = tmpline[c];
1210		if (tmpline[c] == '\n') {
1211			*cs++ = '\0';
1212			if (ftpdebug)
1213				syslog(LOG_DEBUG, "command: %s", s);
1214			tmpline[0] = '\0';
1215			return(0);
1216		}
1217		if (c == 0)
1218			tmpline[0] = '\0';
1219	}
1220	/* SIGURG would interrupt stdio if not blocked during the read loop */
1221	sigemptyset(&sset);
1222	sigaddset(&sset, SIGURG);
1223	sigprocmask(SIG_BLOCK, &sset, &osset);
1224	while ((c = getc(iop)) != EOF) {
1225		c &= 0377;
1226		if (c == IAC) {
1227			if ((c = getc(iop)) == EOF)
1228				goto got_eof;
1229			c &= 0377;
1230			switch (c) {
1231			case WILL:
1232			case WONT:
1233				if ((c = getc(iop)) == EOF)
1234					goto got_eof;
1235				printf("%c%c%c", IAC, DONT, 0377&c);
1236				(void) fflush(stdout);
1237				continue;
1238			case DO:
1239			case DONT:
1240				if ((c = getc(iop)) == EOF)
1241					goto got_eof;
1242				printf("%c%c%c", IAC, WONT, 0377&c);
1243				(void) fflush(stdout);
1244				continue;
1245			case IAC:
1246				break;
1247			default:
1248				continue;	/* ignore command */
1249			}
1250		}
1251		*cs++ = c;
1252		if (--n <= 0) {
1253			/*
1254			 * If command doesn't fit into buffer, discard the
1255			 * rest of the command and indicate truncation.
1256			 * This prevents the command to be split up into
1257			 * multiple commands.
1258			 */
1259			while (c != '\n' && (c = getc(iop)) != EOF)
1260				;
1261			return (-2);
1262		}
1263		if (c == '\n')
1264			break;
1265	}
1266got_eof:
1267	sigprocmask(SIG_SETMASK, &osset, NULL);
1268	if (c == EOF && cs == s)
1269		return (-1);
1270	*cs++ = '\0';
1271	if (ftpdebug) {
1272		if (!guest && strncasecmp("pass ", s, 5) == 0) {
1273			/* Don't syslog passwords */
1274			syslog(LOG_DEBUG, "command: %.5s ???", s);
1275		} else {
1276			register char *cp;
1277			register int len;
1278
1279			/* Don't syslog trailing CR-LF */
1280			len = strlen(s);
1281			cp = s + len - 1;
1282			while (cp >= s && (*cp == '\n' || *cp == '\r')) {
1283				--cp;
1284				--len;
1285			}
1286			syslog(LOG_DEBUG, "command: %.*s", len, s);
1287		}
1288	}
1289	return (0);
1290}
1291
1292static void
1293toolong(int signo)
1294{
1295
1296	reply(421,
1297	    "Timeout (%d seconds): closing control connection.", timeout);
1298	if (logging)
1299		syslog(LOG_INFO, "User %s timed out after %d seconds",
1300		    (pw ? pw -> pw_name : "unknown"), timeout);
1301	dologout(1);
1302}
1303
1304static int
1305yylex(void)
1306{
1307	static int cpos;
1308	char *cp, *cp2;
1309	struct tab *p;
1310	int n;
1311	char c;
1312
1313	for (;;) {
1314		switch (state) {
1315
1316		case CMD:
1317			(void) signal(SIGALRM, toolong);
1318			(void) alarm(timeout);
1319			n = ftpd_getline(cbuf, sizeof(cbuf)-1, stdin);
1320			if (n == -1) {
1321				reply(221, "You could at least say goodbye.");
1322				dologout(0);
1323			} else if (n == -2) {
1324				reply(500, "Command too long.");
1325				(void) alarm(0);
1326				continue;
1327			}
1328			(void) alarm(0);
1329#ifdef SETPROCTITLE
1330			if (strncasecmp(cbuf, "PASS", 4) != 0)
1331				setproctitle("%s: %s", proctitle, cbuf);
1332#endif /* SETPROCTITLE */
1333			if ((cp = strchr(cbuf, '\r'))) {
1334				*cp++ = '\n';
1335				*cp = '\0';
1336			}
1337			if ((cp = strpbrk(cbuf, " \n")))
1338				cpos = cp - cbuf;
1339			if (cpos == 0)
1340				cpos = 4;
1341			c = cbuf[cpos];
1342			cbuf[cpos] = '\0';
1343			upper(cbuf);
1344			p = lookup(cmdtab, cbuf);
1345			cbuf[cpos] = c;
1346			if (p != 0) {
1347				yylval.s = p->name;
1348				if (!p->implemented)
1349					return (NOTIMPL); /* state remains CMD */
1350				state = p->state;
1351				return (p->token);
1352			}
1353			break;
1354
1355		case SITECMD:
1356			if (cbuf[cpos] == ' ') {
1357				cpos++;
1358				return (SP);
1359			}
1360			cp = &cbuf[cpos];
1361			if ((cp2 = strpbrk(cp, " \n")))
1362				cpos = cp2 - cbuf;
1363			c = cbuf[cpos];
1364			cbuf[cpos] = '\0';
1365			upper(cp);
1366			p = lookup(sitetab, cp);
1367			cbuf[cpos] = c;
1368			if (guest == 0 && p != 0) {
1369				yylval.s = p->name;
1370				if (!p->implemented) {
1371					state = CMD;
1372					return (NOTIMPL);
1373				}
1374				state = p->state;
1375				return (p->token);
1376			}
1377			state = CMD;
1378			break;
1379
1380		case ZSTR1:
1381		case OSTR:
1382			if (cbuf[cpos] == '\n') {
1383				state = CMD;
1384				return (CRLF);
1385			}
1386			/* FALLTHROUGH */
1387
1388		case STR1:
1389		dostr1:
1390			if (cbuf[cpos] == ' ') {
1391				cpos++;
1392				state = state == OSTR ? STR2 : state+1;
1393				return (SP);
1394			}
1395			break;
1396
1397		case ZSTR2:
1398			if (cbuf[cpos] == '\n') {
1399				state = CMD;
1400				return (CRLF);
1401			}
1402			/* FALLTHROUGH */
1403
1404		case STR2:
1405			cp = &cbuf[cpos];
1406			n = strlen(cp);
1407			cpos += n - 1;
1408			/*
1409			 * Make sure the string is nonempty and \n terminated.
1410			 */
1411			if (n > 1 && cbuf[cpos] == '\n') {
1412				cbuf[cpos] = '\0';
1413				yylval.s = copy(cp);
1414				cbuf[cpos] = '\n';
1415				state = ARGS;
1416				return (STRING);
1417			}
1418			break;
1419
1420		case NSTR:
1421			if (cbuf[cpos] == ' ') {
1422				cpos++;
1423				return (SP);
1424			}
1425			if (isdigit(cbuf[cpos])) {
1426				cp = &cbuf[cpos];
1427				while (isdigit(cbuf[++cpos]))
1428					;
1429				c = cbuf[cpos];
1430				cbuf[cpos] = '\0';
1431				yylval.u.i = atoi(cp);
1432				cbuf[cpos] = c;
1433				state = STR1;
1434				return (NUMBER);
1435			}
1436			state = STR1;
1437			goto dostr1;
1438
1439		case ARGS:
1440			if (isdigit(cbuf[cpos])) {
1441				cp = &cbuf[cpos];
1442				while (isdigit(cbuf[++cpos]))
1443					;
1444				c = cbuf[cpos];
1445				cbuf[cpos] = '\0';
1446				yylval.u.i = atoi(cp);
1447				yylval.u.o = strtoull(cp, NULL, 10);
1448				cbuf[cpos] = c;
1449				return (NUMBER);
1450			}
1451			if (strncasecmp(&cbuf[cpos], "ALL", 3) == 0
1452			 && !isalnum(cbuf[cpos + 3])) {
1453				cpos += 3;
1454				return ALL;
1455			}
1456			switch (cbuf[cpos++]) {
1457
1458			case '\n':
1459				state = CMD;
1460				return (CRLF);
1461
1462			case ' ':
1463				return (SP);
1464
1465			case ',':
1466				return (COMMA);
1467
1468			case 'A':
1469			case 'a':
1470				return (A);
1471
1472			case 'B':
1473			case 'b':
1474				return (B);
1475
1476			case 'C':
1477			case 'c':
1478				return (C);
1479
1480			case 'E':
1481			case 'e':
1482				return (E);
1483
1484			case 'F':
1485			case 'f':
1486				return (F);
1487
1488			case 'I':
1489			case 'i':
1490				return (I);
1491
1492			case 'L':
1493			case 'l':
1494				return (L);
1495
1496			case 'N':
1497			case 'n':
1498				return (N);
1499
1500			case 'P':
1501			case 'p':
1502				return (P);
1503
1504			case 'R':
1505			case 'r':
1506				return (R);
1507
1508			case 'S':
1509			case 's':
1510				return (S);
1511
1512			case 'T':
1513			case 't':
1514				return (T);
1515
1516			}
1517			break;
1518
1519		default:
1520			fatalerror("Unknown state in scanner.");
1521		}
1522		state = CMD;
1523		return (LEXERR);
1524	}
1525}
1526
1527void
1528upper(char *s)
1529{
1530	while (*s != '\0') {
1531		if (islower(*s))
1532			*s = toupper(*s);
1533		s++;
1534	}
1535}
1536
1537static char *
1538copy(char *s)
1539{
1540	char *p;
1541
1542	p = malloc(strlen(s) + 1);
1543	if (p == NULL)
1544		fatalerror("Ran out of memory.");
1545	(void) strcpy(p, s);
1546	return (p);
1547}
1548
1549static void
1550help(struct tab *ctab, char *s)
1551{
1552	struct tab *c;
1553	int width, NCMDS;
1554	char *type;
1555
1556	if (ctab == sitetab)
1557		type = "SITE ";
1558	else
1559		type = "";
1560	width = 0, NCMDS = 0;
1561	for (c = ctab; c->name != NULL; c++) {
1562		int len = strlen(c->name);
1563
1564		if (len > width)
1565			width = len;
1566		NCMDS++;
1567	}
1568	width = (width + 8) &~ 7;
1569	if (s == 0) {
1570		int i, j, w;
1571		int columns, lines;
1572
1573		lreply(214, "The following %scommands are recognized %s.",
1574		    type, "(* =>'s unimplemented)");
1575		columns = 76 / width;
1576		if (columns == 0)
1577			columns = 1;
1578		lines = (NCMDS + columns - 1) / columns;
1579		for (i = 0; i < lines; i++) {
1580			printf("   ");
1581			for (j = 0; j < columns; j++) {
1582				c = ctab + j * lines + i;
1583				printf("%s%c", c->name,
1584					c->implemented ? ' ' : '*');
1585				if (c + lines >= &ctab[NCMDS])
1586					break;
1587				w = strlen(c->name) + 1;
1588				while (w < width) {
1589					putchar(' ');
1590					w++;
1591				}
1592			}
1593			printf("\r\n");
1594		}
1595		(void) fflush(stdout);
1596		if (hostinfo)
1597			reply(214, "Direct comments to ftp-bugs@%s.", hostname);
1598		else
1599			reply(214, "End.");
1600		return;
1601	}
1602	upper(s);
1603	c = lookup(ctab, s);
1604	if (c == NULL) {
1605		reply(502, "Unknown command %s.", s);
1606		return;
1607	}
1608	if (c->implemented)
1609		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
1610	else
1611		reply(214, "%s%-*s\t%s; unimplemented.", type, width,
1612		    c->name, c->help);
1613}
1614
1615static void
1616sizecmd(char *filename)
1617{
1618	switch (type) {
1619	case TYPE_L:
1620	case TYPE_I: {
1621		struct stat stbuf;
1622		if (stat(filename, &stbuf) < 0)
1623			perror_reply(550, filename);
1624		else if (!S_ISREG(stbuf.st_mode))
1625			reply(550, "%s: not a plain file.", filename);
1626		else
1627			reply(213, "%lld", (intmax_t)stbuf.st_size);
1628		break; }
1629	case TYPE_A: {
1630		FILE *fin;
1631		int c;
1632		off_t count;
1633		struct stat stbuf;
1634		fin = fopen(filename, "r");
1635		if (fin == NULL) {
1636			perror_reply(550, filename);
1637			return;
1638		}
1639		if (fstat(fileno(fin), &stbuf) < 0) {
1640			perror_reply(550, filename);
1641			(void) fclose(fin);
1642			return;
1643		} else if (!S_ISREG(stbuf.st_mode)) {
1644			reply(550, "%s: not a plain file.", filename);
1645			(void) fclose(fin);
1646			return;
1647		} else if (stbuf.st_size > MAXASIZE) {
1648			reply(550, "%s: too large for type A SIZE.", filename);
1649			(void) fclose(fin);
1650			return;
1651		}
1652
1653		count = 0;
1654		while((c=getc(fin)) != EOF) {
1655			if (c == '\n')	/* will get expanded to \r\n */
1656				count++;
1657			count++;
1658		}
1659		(void) fclose(fin);
1660
1661		reply(213, "%lld", (intmax_t)count);
1662		break; }
1663	default:
1664		reply(504, "SIZE not implemented for type %s.",
1665		           typenames[type]);
1666	}
1667}
1668
1669/* Return 1, if port check is done. Return 0, if not yet. */
1670static int
1671port_check(const char *pcmd)
1672{
1673	if (his_addr.su_family == AF_INET) {
1674		if (data_dest.su_family != AF_INET) {
1675			usedefault = 1;
1676			reply(500, "Invalid address rejected.");
1677			return 1;
1678		}
1679		if (paranoid &&
1680		    ((ntohs(data_dest.su_port) < IPPORT_RESERVED) ||
1681		     memcmp(&data_dest.su_sin.sin_addr,
1682			    &his_addr.su_sin.sin_addr,
1683			    sizeof(data_dest.su_sin.sin_addr)))) {
1684			usedefault = 1;
1685			reply(500, "Illegal PORT range rejected.");
1686		} else {
1687			usedefault = 0;
1688			if (pdata >= 0) {
1689				(void) close(pdata);
1690				pdata = -1;
1691			}
1692			reply(200, "%s command successful.", pcmd);
1693		}
1694		return 1;
1695	}
1696	return 0;
1697}
1698
1699static int
1700check_login1(void)
1701{
1702	if (logged_in)
1703		return 1;
1704	else {
1705		reply(530, "Please login with USER and PASS.");
1706		return 0;
1707	}
1708}
1709
1710/*
1711 * Replace leading "~user" in a pathname by the user's login directory.
1712 * Returned string will be in a freshly malloced buffer unless it's NULL.
1713 */
1714static char *
1715exptilde(char *s)
1716{
1717	char *p, *q;
1718	char *path, *user;
1719	struct passwd *ppw;
1720
1721	if ((p = strdup(s)) == NULL)
1722		return (NULL);
1723	if (*p != '~')
1724		return (p);
1725
1726	user = p + 1;	/* skip tilde */
1727	if ((path = strchr(p, '/')) != NULL)
1728		*(path++) = '\0'; /* separate ~user from the rest of path */
1729	if (*user == '\0') /* no user specified, use the current user */
1730		user = pw->pw_name;
1731	/* read passwd even for the current user since we may be chrooted */
1732	if ((ppw = getpwnam(user)) != NULL) {
1733		/* user found, substitute login directory for ~user */
1734		if (path)
1735			asprintf(&q, "%s/%s", ppw->pw_dir, path);
1736		else
1737			q = strdup(ppw->pw_dir);
1738		free(p);
1739		p = q;
1740	} else {
1741		/* user not found, undo the damage */
1742		if (path)
1743			path[-1] = '/';
1744	}
1745	return (p);
1746}
1747
1748/*
1749 * Expand glob(3) patterns possibly present in a pathname.
1750 * Avoid expanding to a pathname including '\r' or '\n' in order to
1751 * not disrupt the FTP protocol.
1752 * The expansion found must be unique.
1753 * Return the result as a malloced string, or NULL if an error occured.
1754 *
1755 * Problem: this production is used for all pathname
1756 * processing, but only gives a 550 error reply.
1757 * This is a valid reply in some cases but not in others.
1758 */
1759static char *
1760expglob(char *s)
1761{
1762	char *p, **pp, *rval;
1763	int flags = GLOB_BRACE | GLOB_NOCHECK;
1764	int n;
1765	glob_t gl;
1766
1767	memset(&gl, 0, sizeof(gl));
1768	flags |= GLOB_LIMIT;
1769	gl.gl_matchc = MAXGLOBARGS;
1770	if (glob(s, flags, NULL, &gl) == 0 && gl.gl_pathc != 0) {
1771		for (pp = gl.gl_pathv, p = NULL, n = 0; *pp; pp++)
1772			if (*(*pp + strcspn(*pp, "\r\n")) == '\0') {
1773				p = *pp;
1774				n++;
1775			}
1776		if (n == 0)
1777			rval = strdup(s);
1778		else if (n == 1)
1779			rval = strdup(p);
1780		else {
1781			reply(550, "Wildcard is ambiguous.");
1782			rval = NULL;
1783		}
1784	} else {
1785		reply(550, "Wildcard expansion error.");
1786		rval = NULL;
1787	}
1788	globfree(&gl);
1789	return (rval);
1790}
1791
1792#ifdef INET6
1793/* Return 1, if port check is done. Return 0, if not yet. */
1794static int
1795port_check_v6(const char *pcmd)
1796{
1797	if (his_addr.su_family == AF_INET6) {
1798		if (IN6_IS_ADDR_V4MAPPED(&his_addr.su_sin6.sin6_addr))
1799			/* Convert data_dest into v4 mapped sockaddr.*/
1800			v4map_data_dest();
1801		if (data_dest.su_family != AF_INET6) {
1802			usedefault = 1;
1803			reply(500, "Invalid address rejected.");
1804			return 1;
1805		}
1806		if (paranoid &&
1807		    ((ntohs(data_dest.su_port) < IPPORT_RESERVED) ||
1808		     memcmp(&data_dest.su_sin6.sin6_addr,
1809			    &his_addr.su_sin6.sin6_addr,
1810			    sizeof(data_dest.su_sin6.sin6_addr)))) {
1811			usedefault = 1;
1812			reply(500, "Illegal PORT range rejected.");
1813		} else {
1814			usedefault = 0;
1815			if (pdata >= 0) {
1816				(void) close(pdata);
1817				pdata = -1;
1818			}
1819			reply(200, "%s command successful.", pcmd);
1820		}
1821		return 1;
1822	}
1823	return 0;
1824}
1825
1826static void
1827v4map_data_dest(void)
1828{
1829	struct in_addr savedaddr;
1830	int savedport;
1831
1832	if (data_dest.su_family != AF_INET) {
1833		usedefault = 1;
1834		reply(500, "Invalid address rejected.");
1835		return;
1836	}
1837
1838	savedaddr = data_dest.su_sin.sin_addr;
1839	savedport = data_dest.su_port;
1840
1841	memset(&data_dest, 0, sizeof(data_dest));
1842	data_dest.su_sin6.sin6_len = sizeof(struct sockaddr_in6);
1843	data_dest.su_sin6.sin6_family = AF_INET6;
1844	data_dest.su_sin6.sin6_port = savedport;
1845	memset((caddr_t)&data_dest.su_sin6.sin6_addr.s6_addr[10], 0xff, 2);
1846	memcpy((caddr_t)&data_dest.su_sin6.sin6_addr.s6_addr[12],
1847	       (caddr_t)&savedaddr, sizeof(savedaddr));
1848}
1849#endif
1850