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