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