xref: /openbsd-src/libexec/ftpd/ftpd.c (revision 94c8de549078a156f9fd3b5860c3a954e0098074)
1*94c8de54Sflorian /*	$OpenBSD: ftpd.c,v 1.234 2024/05/09 08:35:03 florian Exp $	*/
2df930be7Sderaadt /*	$NetBSD: ftpd.c,v 1.15 1995/06/03 22:46:47 mycroft Exp $	*/
3df930be7Sderaadt 
4df930be7Sderaadt /*
5b1750805Sitojun  * Copyright (C) 1997 and 1998 WIDE Project.
6b1750805Sitojun  * All rights reserved.
7b1750805Sitojun  *
8b1750805Sitojun  * Redistribution and use in source and binary forms, with or without
9b1750805Sitojun  * modification, are permitted provided that the following conditions
10b1750805Sitojun  * are met:
11b1750805Sitojun  * 1. Redistributions of source code must retain the above copyright
12b1750805Sitojun  *    notice, this list of conditions and the following disclaimer.
13b1750805Sitojun  * 2. Redistributions in binary form must reproduce the above copyright
14b1750805Sitojun  *    notice, this list of conditions and the following disclaimer in the
15b1750805Sitojun  *    documentation and/or other materials provided with the distribution.
16b1750805Sitojun  * 3. Neither the name of the project nor the names of its contributors
17b1750805Sitojun  *    may be used to endorse or promote products derived from this software
18b1750805Sitojun  *    without specific prior written permission.
19b1750805Sitojun  *
20b1750805Sitojun  * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
21b1750805Sitojun  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22b1750805Sitojun  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23b1750805Sitojun  * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
24b1750805Sitojun  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25b1750805Sitojun  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26b1750805Sitojun  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27b1750805Sitojun  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28b1750805Sitojun  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29b1750805Sitojun  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30b1750805Sitojun  * SUCH DAMAGE.
31b1750805Sitojun  */
32b1750805Sitojun 
33b1750805Sitojun /*
34df930be7Sderaadt  * Copyright (c) 1985, 1988, 1990, 1992, 1993, 1994
35df930be7Sderaadt  *	The Regents of the University of California.  All rights reserved.
36df930be7Sderaadt  *
37df930be7Sderaadt  * Redistribution and use in source and binary forms, with or without
38df930be7Sderaadt  * modification, are permitted provided that the following conditions
39df930be7Sderaadt  * are met:
40df930be7Sderaadt  * 1. Redistributions of source code must retain the above copyright
41df930be7Sderaadt  *    notice, this list of conditions and the following disclaimer.
42df930be7Sderaadt  * 2. Redistributions in binary form must reproduce the above copyright
43df930be7Sderaadt  *    notice, this list of conditions and the following disclaimer in the
44df930be7Sderaadt  *    documentation and/or other materials provided with the distribution.
45e33d3bd3Smillert  * 3. Neither the name of the University nor the names of its contributors
46df930be7Sderaadt  *    may be used to endorse or promote products derived from this software
47df930be7Sderaadt  *    without specific prior written permission.
48df930be7Sderaadt  *
49df930be7Sderaadt  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
50df930be7Sderaadt  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
51df930be7Sderaadt  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
52df930be7Sderaadt  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
53df930be7Sderaadt  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
54df930be7Sderaadt  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
55df930be7Sderaadt  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
56df930be7Sderaadt  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
57df930be7Sderaadt  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
58df930be7Sderaadt  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
59df930be7Sderaadt  * SUCH DAMAGE.
60df930be7Sderaadt  */
61df930be7Sderaadt 
62df930be7Sderaadt /*
63df930be7Sderaadt  * FTP server.
64df930be7Sderaadt  */
65df930be7Sderaadt #include <sys/stat.h>
66df930be7Sderaadt #include <sys/ioctl.h>
67df930be7Sderaadt #include <sys/socket.h>
68df930be7Sderaadt #include <sys/wait.h>
693aba417aSdownsj #include <sys/mman.h>
70df930be7Sderaadt 
71df930be7Sderaadt #include <netinet/in.h>
72df930be7Sderaadt #include <netinet/ip.h>
73878f3b5aSdownsj #include <netinet/tcp.h>
74df930be7Sderaadt 
75df930be7Sderaadt #define	FTP_NAMES
76df930be7Sderaadt #include <arpa/ftp.h>
77df930be7Sderaadt #include <arpa/inet.h>
78df930be7Sderaadt #include <arpa/telnet.h>
79df930be7Sderaadt 
80b498a85fSmillert #include <bsd_auth.h>
81df930be7Sderaadt #include <ctype.h>
82df930be7Sderaadt #include <dirent.h>
83df930be7Sderaadt #include <errno.h>
84df930be7Sderaadt #include <fcntl.h>
85df930be7Sderaadt #include <glob.h>
86df930be7Sderaadt #include <limits.h>
87fed231abSmillert #include <login_cap.h>
88df930be7Sderaadt #include <netdb.h>
89df930be7Sderaadt #include <pwd.h>
90df930be7Sderaadt #include <signal.h>
91995182bbSmillert #include <stdarg.h>
92df930be7Sderaadt #include <stdio.h>
93df930be7Sderaadt #include <stdlib.h>
94df930be7Sderaadt #include <string.h>
95df930be7Sderaadt #include <syslog.h>
96df930be7Sderaadt #include <time.h>
978bc32b07Sderaadt #include <vis.h>
98df930be7Sderaadt #include <unistd.h>
99eb00e388Sdownsj #include <utmp.h>
1007683b0fbSitojun #include <poll.h>
101df930be7Sderaadt 
102df930be7Sderaadt #include "pathnames.h"
103b96c0bc5Shenning #include "monitor.h"
10492f68775Sragge #include "extern.h"
105df930be7Sderaadt 
106df930be7Sderaadt extern	off_t restart_point;
107df930be7Sderaadt extern	char cbuf[];
108df930be7Sderaadt 
109b1750805Sitojun union sockunion ctrl_addr;
110b1750805Sitojun union sockunion data_source;
111b1750805Sitojun union sockunion data_dest;
112b1750805Sitojun union sockunion his_addr;
113b1750805Sitojun union sockunion pasv_addr;
114df930be7Sderaadt 
1153608b787Smillert sigset_t allsigs;
1163608b787Smillert 
11727444405Sdownsj int	daemon_mode = 0;
118df930be7Sderaadt int	data;
119df930be7Sderaadt int	logged_in;
120df930be7Sderaadt struct	passwd *pw;
12127444405Sdownsj int	debug = 0;
122df930be7Sderaadt int	timeout = 900;    /* timeout after 15 minutes of inactivity */
123df930be7Sderaadt int	maxtimeout = 7200;/* don't allow idle time to be set beyond 2 hours */
124df930be7Sderaadt int	logging;
1257a6ccb19Sderaadt int	anon_ok = 1;
1260a04bae7Sderaadt int	anon_only = 0;
1276c941f8bSsthen unsigned int minuid = 1000;
128d8f69df0Sdownsj int	multihome = 0;
129df930be7Sderaadt int	guest;
13072fc7920Sdownsj int	stats;
13172fc7920Sdownsj int	statfd = -1;
132fc12f3acSderaadt int	portcheck = 1;
133df930be7Sderaadt int	dochroot;
134df930be7Sderaadt int	type;
135df930be7Sderaadt int	form;
136df930be7Sderaadt int	stru;			/* avoid C keyword */
137df930be7Sderaadt int	mode;
138eb00e388Sdownsj int	doutmp = 0;		/* update utmp file */
13905312c4aSajacoutot int	nowtmp = 0;		/* do not update wtmp file */
140df930be7Sderaadt int	usedefault = 1;		/* for data transfers */
141df930be7Sderaadt int	pdata = -1;		/* for passive mode */
1427683b0fbSitojun int	family = AF_UNSPEC;
143191622e9Sderaadt volatile sig_atomic_t transflag;
144df930be7Sderaadt off_t	file_size;
145df930be7Sderaadt off_t	byte_count;
146257cf6b4Sderaadt mode_t	defumask = S_IWGRP|S_IWOTH;		/* default umask value */
147b3f5c309Sderaadt int	umaskchange = 1;		/* allow user to change umask value. */
148df930be7Sderaadt char	tmpline[7];
149b9fc9a72Sderaadt char	hostname[HOST_NAME_MAX+1];
150b9fc9a72Sderaadt char	remotehost[HOST_NAME_MAX+1];
151b9fc9a72Sderaadt char	dhostname[HOST_NAME_MAX+1];
152d8f69df0Sdownsj char	*guestpw;
153f7818148Shenning char	ttyline[20];
154eb00e388Sdownsj static struct utmp utmp;	/* for utmp */
155fed231abSmillert static	login_cap_t *lc;
15641db49dbSmillert static	auth_session_t *as;
1572378d5feSmillert static	volatile sig_atomic_t recvurg;
158df930be7Sderaadt 
159b1750805Sitojun int epsvall = 0;
160b1750805Sitojun 
161df930be7Sderaadt /*
162df930be7Sderaadt  * Timeout intervals for retrying connections
163df930be7Sderaadt  * to hosts that don't accept PORT cmds.  This
164df930be7Sderaadt  * is a kludge, but given the problems with TCP...
165df930be7Sderaadt  */
166df930be7Sderaadt #define	SWAITMAX	90	/* wait at most 90 seconds */
167df930be7Sderaadt #define	SWAITINT	5	/* interval between retries */
168df930be7Sderaadt 
169df930be7Sderaadt int	swaitmax = SWAITMAX;
170df930be7Sderaadt int	swaitint = SWAITINT;
171df930be7Sderaadt 
172df930be7Sderaadt char	proctitle[BUFSIZ];	/* initial part of title */
173df930be7Sderaadt 
174df930be7Sderaadt #define LOGCMD(cmd, file) \
175df930be7Sderaadt 	if (logging > 1) \
176df930be7Sderaadt 	    syslog(LOG_INFO,"%s %s%s", cmd, \
177df930be7Sderaadt 		*(file) == '/' ? "" : curdir(), file);
178df930be7Sderaadt #define LOGCMD2(cmd, file1, file2) \
179df930be7Sderaadt 	 if (logging > 1) \
180df930be7Sderaadt 	    syslog(LOG_INFO,"%s %s%s %s%s", cmd, \
181df930be7Sderaadt 		*(file1) == '/' ? "" : curdir(), file1, \
182df930be7Sderaadt 		*(file2) == '/' ? "" : curdir(), file2);
183df930be7Sderaadt #define LOGBYTES(cmd, file, cnt) \
184df930be7Sderaadt 	if (logging > 1) { \
185a20a5177Sguenther 		if ((cnt) == -1) \
186df930be7Sderaadt 		    syslog(LOG_INFO,"%s %s%s", cmd, \
187df930be7Sderaadt 			*(file) == '/' ? "" : curdir(), file); \
188df930be7Sderaadt 		else \
189a20a5177Sguenther 		    syslog(LOG_INFO, "%s %s%s = %lld bytes", \
190a20a5177Sguenther 			cmd, (*(file) == '/') ? "" : curdir(), file, \
191a20a5177Sguenther 			(long long)(cnt)); \
192df930be7Sderaadt 	}
193df930be7Sderaadt 
1940c50dd5dSjan static void	 ack(const char *);
195c72b5b24Smillert static void	 sigurg(int);
196c72b5b24Smillert static void	 myoob(void);
1970c50dd5dSjan static int	 checkuser(char *, const char *);
1980c50dd5dSjan static FILE	*dataconn(const char *, off_t, char *);
199c72b5b24Smillert static void	 dolog(struct sockaddr *);
200c72b5b24Smillert static char	*copy_dir(char *, struct passwd *);
201c72b5b24Smillert static char	*curdir(void);
202c72b5b24Smillert static void	 end_login(void);
203c72b5b24Smillert static FILE	*getdatasock(char *);
2040c50dd5dSjan static int	 guniquefd(const char *, char **);
205c72b5b24Smillert static void	 lostconn(int);
206c72b5b24Smillert static void	 sigquit(int);
207c72b5b24Smillert static int	 receive_data(FILE *, FILE *);
208c72b5b24Smillert static void	 replydirname(const char *, const char *);
209c72b5b24Smillert static int	 send_data(FILE *, FILE *, off_t, off_t, int);
210df930be7Sderaadt static struct passwd *
2110c50dd5dSjan 		 sgetpwnam(const char *, struct passwd *);
212c72b5b24Smillert static void	 reapchild(int);
213c72b5b24Smillert static void	 usage(void);
21427444405Sdownsj 
2150c50dd5dSjan void	 logxfer(const char *, off_t, time_t);
216b96c0bc5Shenning void	 set_slave_signals(void);
217df930be7Sderaadt 
218df930be7Sderaadt static char *
curdir(void)2196bcb9e83Sderaadt curdir(void)
220df930be7Sderaadt {
221b9fc9a72Sderaadt 	static char path[PATH_MAX+1];	/* path + '/' */
222df930be7Sderaadt 
223640d6790Sderaadt 	if (getcwd(path, sizeof(path)-1) == NULL)
224df930be7Sderaadt 		return ("");
225df930be7Sderaadt 	if (path[1] != '\0')		/* special case for root dir. */
226178ab839Sderaadt 		strlcat(path, "/", sizeof path);
227df930be7Sderaadt 	/* For guest account, skip / since it's chrooted */
228df930be7Sderaadt 	return (guest ? path+1 : path);
229df930be7Sderaadt }
230df930be7Sderaadt 
231aa71bf93Ssthen char *argstr = "AdDhnlm:MSt:T:u:PUvW46";
232698d4f2cSderaadt 
233698d4f2cSderaadt static void
usage(void)2346bcb9e83Sderaadt usage(void)
235698d4f2cSderaadt {
236698d4f2cSderaadt 	syslog(LOG_ERR,
2376c941f8bSsthen 	    "usage: ftpd [-46ADdlMnPSUW] [-m minuid] [-T maxtimeout] "
2386c941f8bSsthen 	    "[-t timeout] [-u mask]");
239698d4f2cSderaadt 	exit(2);
240698d4f2cSderaadt }
241698d4f2cSderaadt 
242df930be7Sderaadt int
main(int argc,char * argv[])2436bcb9e83Sderaadt main(int argc, char *argv[])
244df930be7Sderaadt {
2450fe9133cSdanh 	socklen_t addrlen;
2460fe9133cSdanh 	int ch, on = 1, tos;
24761fc5ed6Sray 	char line[LINE_MAX];
248cad3e9c0Smillert 	FILE *fp;
249d8f69df0Sdownsj 	struct hostent *hp;
2502378d5feSmillert 	struct sigaction sa;
2511303e027Sniallo 	int error = 0;
25261fc5ed6Sray 	const char *errstr;
253df930be7Sderaadt 
25427444405Sdownsj 	tzset();		/* in case no timezone database in ~ftp */
2553608b787Smillert 	sigfillset(&allsigs);	/* used to block signals while root */
2562378d5feSmillert 	sigemptyset(&sa.sa_mask);
2572378d5feSmillert 	sa.sa_flags = SA_RESTART;
258df930be7Sderaadt 
25972799b18Smillert 	while ((ch = getopt(argc, argv, argstr)) != -1) {
260df930be7Sderaadt 		switch (ch) {
2610a04bae7Sderaadt 		case 'A':
2620a04bae7Sderaadt 			anon_only = 1;
2630a04bae7Sderaadt 			break;
2640a04bae7Sderaadt 
265df930be7Sderaadt 		case 'd':
266995182bbSmillert 		case 'v':		/* deprecated */
267df930be7Sderaadt 			debug = 1;
268df930be7Sderaadt 			break;
269df930be7Sderaadt 
27027444405Sdownsj 		case 'D':
27127444405Sdownsj 			daemon_mode = 1;
27227444405Sdownsj 			break;
27327444405Sdownsj 
274fc12f3acSderaadt 		case 'P':
275fc12f3acSderaadt 			portcheck = 0;
276fc12f3acSderaadt 			break;
277fc12f3acSderaadt 
2788ea584b2Sjakob 		case 'h':		/* deprecated */
279878f3b5aSdownsj 			break;
280878f3b5aSdownsj 
281df930be7Sderaadt 		case 'l':
282df930be7Sderaadt 			logging++;	/* > 1 == extra logging */
283df930be7Sderaadt 			break;
284df930be7Sderaadt 
2856c941f8bSsthen 		case 'm':
2866c941f8bSsthen 			minuid = strtonum(optarg, 0, UINT_MAX, &errstr);
2876c941f8bSsthen 			if (errstr) {
2886c941f8bSsthen 				syslog(LOG_ERR,
28974e6a2beSsthen 				    "%s is a bad value for -m, aborting",
2906c941f8bSsthen 				    optarg);
2916c941f8bSsthen 				exit(2);
2926c941f8bSsthen 			}
2936c941f8bSsthen 			break;
2946c941f8bSsthen 
295d8f69df0Sdownsj 		case 'M':
296d8f69df0Sdownsj 			multihome = 1;
297d8f69df0Sdownsj 			break;
298d8f69df0Sdownsj 
2997a6ccb19Sderaadt 		case 'n':
3007a6ccb19Sderaadt 			anon_ok = 0;
3017a6ccb19Sderaadt 			break;
3027a6ccb19Sderaadt 
30372fc7920Sdownsj 		case 'S':
30472fc7920Sdownsj 			stats = 1;
30572fc7920Sdownsj 			break;
30672fc7920Sdownsj 
307df930be7Sderaadt 		case 't':
30861fc5ed6Sray 			timeout = strtonum(optarg, 0, INT_MAX, &errstr);
30961fc5ed6Sray 			if (errstr) {
31061fc5ed6Sray 				syslog(LOG_ERR,
31161fc5ed6Sray 				    "%s is a bad value for -t, aborting",
31261fc5ed6Sray 				    optarg);
31361fc5ed6Sray 				exit(2);
31461fc5ed6Sray 			}
315df930be7Sderaadt 			if (maxtimeout < timeout)
316df930be7Sderaadt 				maxtimeout = timeout;
317df930be7Sderaadt 			break;
318df930be7Sderaadt 
319df930be7Sderaadt 		case 'T':
32061fc5ed6Sray 			maxtimeout = strtonum(optarg, 0, INT_MAX,
32161fc5ed6Sray 			    &errstr);
32261fc5ed6Sray 			if (errstr) {
32361fc5ed6Sray 				syslog(LOG_ERR,
32461fc5ed6Sray 				    "%s is a bad value for -T, aborting",
32561fc5ed6Sray 				    optarg);
32661fc5ed6Sray 				exit(2);
32761fc5ed6Sray 			}
328df930be7Sderaadt 			if (timeout > maxtimeout)
329df930be7Sderaadt 				timeout = maxtimeout;
330df930be7Sderaadt 			break;
331df930be7Sderaadt 
332df930be7Sderaadt 		case 'u':
333df930be7Sderaadt 		    {
334df930be7Sderaadt 			long val = 0;
33580297d10Sderaadt 			char *p;
336b3f5c309Sderaadt 			umaskchange = 0;
337df930be7Sderaadt 
33880297d10Sderaadt 			val = strtol(optarg, &p, 8);
33961fc5ed6Sray 			if (*optarg == '\0' || *p != '\0' || val < 0 ||
34061fc5ed6Sray 			    (val & ~ACCESSPERMS)) {
34180297d10Sderaadt 				syslog(LOG_ERR,
34261fc5ed6Sray 				    "%s is a bad value for -u, aborting",
34380297d10Sderaadt 				    optarg);
34480297d10Sderaadt 				exit(2);
34561fc5ed6Sray 			}
346df930be7Sderaadt 			defumask = val;
347df930be7Sderaadt 			break;
348df930be7Sderaadt 		    }
349df930be7Sderaadt 
350eb00e388Sdownsj 		case 'U':
351eb00e388Sdownsj 			doutmp = 1;
352eb00e388Sdownsj 			break;
353eb00e388Sdownsj 
35405312c4aSajacoutot 		case 'W':
35505312c4aSajacoutot 			nowtmp = 1;
35605312c4aSajacoutot 			break;
35705312c4aSajacoutot 
358b1750805Sitojun 		case '4':
359b1750805Sitojun 			family = AF_INET;
360b1750805Sitojun 			break;
361b1750805Sitojun 
362b1750805Sitojun 		case '6':
363b1750805Sitojun 			family = AF_INET6;
364b1750805Sitojun 			break;
365b1750805Sitojun 
366df930be7Sderaadt 		default:
367698d4f2cSderaadt 			usage();
368df930be7Sderaadt 			break;
369df930be7Sderaadt 		}
370df930be7Sderaadt 	}
37127444405Sdownsj 
37205312c4aSajacoutot 	if (nowtmp && doutmp) {
37305312c4aSajacoutot 		syslog(LOG_ERR, "options 'U' and 'W' are mutually exclusive");
37405312c4aSajacoutot 		exit(1);
37505312c4aSajacoutot 	}
37605312c4aSajacoutot 
377878f3b5aSdownsj 	(void) freopen(_PATH_DEVNULL, "w", stderr);
378878f3b5aSdownsj 
37927444405Sdownsj 	/*
38027444405Sdownsj 	 * LOG_NDELAY sets up the logging connection immediately,
38127444405Sdownsj 	 * necessary for anonymous ftp's that chroot and can't do it later.
38227444405Sdownsj 	 */
38327444405Sdownsj 	openlog("ftpd", LOG_PID | LOG_NDELAY, LOG_FTP);
38427444405Sdownsj 
385b96c0bc5Shenning 	if (getpwnam(FTPD_PRIVSEP_USER) == NULL) {
386b96c0bc5Shenning 		syslog(LOG_ERR, "privilege separation user %s not found",
387b96c0bc5Shenning 		    FTPD_PRIVSEP_USER);
388b96c0bc5Shenning 		exit(1);
389b96c0bc5Shenning 	}
390821c3d91Smoritz 	endpwent();
391b96c0bc5Shenning 
39227444405Sdownsj 	if (daemon_mode) {
39383b130e1Sbluhm 		int *fds, fd;
3947683b0fbSitojun 		struct pollfd *pfds;
3957683b0fbSitojun 		struct addrinfo hints, *res, *res0;
39683b130e1Sbluhm 		nfds_t n, i;
39727444405Sdownsj 
39827444405Sdownsj 		/*
39927444405Sdownsj 		 * Detach from parent.
40027444405Sdownsj 		 */
401df69c215Sderaadt 		if (daemon(1, 1) == -1) {
40227444405Sdownsj 			syslog(LOG_ERR, "failed to become a daemon");
40327444405Sdownsj 			exit(1);
40427444405Sdownsj 		}
4052378d5feSmillert 		sa.sa_handler = reapchild;
4062378d5feSmillert 		(void) sigaction(SIGCHLD, &sa, NULL);
4077683b0fbSitojun 
4087683b0fbSitojun 		memset(&hints, 0, sizeof(hints));
4097683b0fbSitojun 		hints.ai_family = family;
4107683b0fbSitojun 		hints.ai_socktype = SOCK_STREAM;
4117683b0fbSitojun 		hints.ai_protocol = IPPROTO_TCP;
4127683b0fbSitojun 		hints.ai_flags = AI_PASSIVE;
4137683b0fbSitojun 		error = getaddrinfo(NULL, "ftp", &hints, &res0);
4147683b0fbSitojun 		if (error) {
4157683b0fbSitojun 			syslog(LOG_ERR, "%s", gai_strerror(error));
41627444405Sdownsj 			exit(1);
41727444405Sdownsj 		}
4187683b0fbSitojun 
4197683b0fbSitojun 		n = 0;
4207683b0fbSitojun 		for (res = res0; res; res = res->ai_next)
4217683b0fbSitojun 			n++;
4227683b0fbSitojun 
4231ed98fdfSderaadt 		fds = calloc(n, sizeof(int));
4241ed98fdfSderaadt 		pfds = calloc(n, sizeof(struct pollfd));
4257683b0fbSitojun 		if (!fds || !pfds) {
4267683b0fbSitojun 			syslog(LOG_ERR, "%s", strerror(errno));
4277683b0fbSitojun 			exit(1);
4287683b0fbSitojun 		}
4297683b0fbSitojun 
43027444405Sdownsj 		/*
4317683b0fbSitojun 		 * Open sockets, bind it to the FTP port, and start
43227444405Sdownsj 		 * listening.
43327444405Sdownsj 		 */
4347683b0fbSitojun 		n = 0;
4357683b0fbSitojun 		for (res = res0; res; res = res->ai_next) {
4367683b0fbSitojun 			fds[n] = socket(res->ai_family, res->ai_socktype,
4377683b0fbSitojun 			    res->ai_protocol);
438df69c215Sderaadt 			if (fds[n] == -1)
4397683b0fbSitojun 				continue;
4407683b0fbSitojun 
44118ff6e9dSmillert 			if (setsockopt(fds[n], SOL_SOCKET, SO_KEEPALIVE,
442df69c215Sderaadt 			    &on, sizeof(on)) == -1) {
44318ff6e9dSmillert 				close(fds[n]);
44418ff6e9dSmillert 				fds[n] = -1;
44518ff6e9dSmillert 				continue;
44618ff6e9dSmillert 			}
44718ff6e9dSmillert 
4487683b0fbSitojun 			if (setsockopt(fds[n], SOL_SOCKET, SO_REUSEADDR,
449df69c215Sderaadt 			    &on, sizeof(on)) == -1) {
4507683b0fbSitojun 				close(fds[n]);
4517683b0fbSitojun 				fds[n] = -1;
4527683b0fbSitojun 				continue;
4537683b0fbSitojun 			}
4547683b0fbSitojun 
455df69c215Sderaadt 			if (bind(fds[n], res->ai_addr, res->ai_addrlen) == -1) {
4567683b0fbSitojun 				close(fds[n]);
4577683b0fbSitojun 				fds[n] = -1;
4587683b0fbSitojun 				continue;
4597683b0fbSitojun 			}
460df69c215Sderaadt 			if (listen(fds[n], 32) == -1) {
4617683b0fbSitojun 				close(fds[n]);
4627683b0fbSitojun 				fds[n] = -1;
4637683b0fbSitojun 				continue;
4647683b0fbSitojun 			}
4657683b0fbSitojun 
4667683b0fbSitojun 			pfds[n].fd = fds[n];
4677683b0fbSitojun 			pfds[n].events = POLLIN;
4687683b0fbSitojun 			n++;
4697683b0fbSitojun 		}
4707683b0fbSitojun 		freeaddrinfo(res0);
4717683b0fbSitojun 
4727683b0fbSitojun 		if (n == 0) {
4737683b0fbSitojun 			syslog(LOG_ERR, "could not open control socket");
47427444405Sdownsj 			exit(1);
47527444405Sdownsj 		}
4767683b0fbSitojun 
47727444405Sdownsj 		/*
47827444405Sdownsj 		 * Loop forever accepting connection requests and forking off
47927444405Sdownsj 		 * children to handle them.
48027444405Sdownsj 		 */
48127444405Sdownsj 		while (1) {
482df69c215Sderaadt 			if (poll(pfds, n, INFTIM) == -1) {
4837683b0fbSitojun 				if (errno == EINTR)
4847683b0fbSitojun 					continue;
4857e602e71Shenning 				syslog(LOG_ERR, "poll: %m");
4867e602e71Shenning 				exit(1);
48727444405Sdownsj 			}
4887683b0fbSitojun 			for (i = 0; i < n; i++)
4897683b0fbSitojun 				if (pfds[i].revents & POLLIN) {
4907683b0fbSitojun 					addrlen = sizeof(his_addr);
4917683b0fbSitojun 					fd = accept(pfds[i].fd,
4927683b0fbSitojun 					    (struct sockaddr *)&his_addr,
4937683b0fbSitojun 					    &addrlen);
4945be00cb5Sderaadt 					if (fd != -1) {
4957683b0fbSitojun 						if (fork() == 0)
4967683b0fbSitojun 							goto child;
49727444405Sdownsj 						close(fd);
49827444405Sdownsj 					}
4997683b0fbSitojun 				}
5005be00cb5Sderaadt 		}
501802787ddSdownsj 
5027683b0fbSitojun 	child:
5037683b0fbSitojun 		/* child */
5047683b0fbSitojun 		(void)dup2(fd, STDIN_FILENO);
5057683b0fbSitojun 		(void)dup2(fd, STDOUT_FILENO);
5067683b0fbSitojun 		for (i = 0; i < n; i++)
5077683b0fbSitojun 			close(fds[i]);
50827444405Sdownsj 	} else {
50927444405Sdownsj 		addrlen = sizeof(his_addr);
51027444405Sdownsj 		if (getpeername(0, (struct sockaddr *)&his_addr,
511df69c215Sderaadt 		    &addrlen) == -1) {
51205bbb7c9Sderaadt 			/* syslog(LOG_ERR, "getpeername (%s): %m", argv[0]); */
51327444405Sdownsj 			exit(1);
51427444405Sdownsj 		}
51527444405Sdownsj 	}
51627444405Sdownsj 
517d3176278Smillert 	/* set this here so klogin can use it... */
51813ee8a54Sderaadt 	(void)snprintf(ttyline, sizeof(ttyline), "ftp%ld", (long)getpid());
519d3176278Smillert 
520b96c0bc5Shenning 	set_slave_signals();
521acda61ddSdanh 
52227444405Sdownsj 	addrlen = sizeof(ctrl_addr);
523df69c215Sderaadt 	if (getsockname(0, (struct sockaddr *)&ctrl_addr, &addrlen) == -1) {
5242378d5feSmillert 		syslog(LOG_ERR, "getsockname: %m");
52527444405Sdownsj 		exit(1);
52627444405Sdownsj 	}
52795cc8c4aSderaadt 	if (his_addr.su_family == AF_INET6 &&
52895cc8c4aSderaadt 	    IN6_IS_ADDR_V4MAPPED(&his_addr.su_sin6.sin6_addr)) {
5290f94fb05Sjca 		syslog(LOG_WARNING,
530b1750805Sitojun 		    "Connection from IPv4 mapped address is not supported.");
5310f94fb05Sjca 		reply(530, "System not available.");
5320f94fb05Sjca 		exit(1);
533b1750805Sitojun 	}
534b1750805Sitojun 	tos = IPTOS_LOWDELAY;
5357ef737bdSjca 	switch (his_addr.su_family) {
5367ef737bdSjca 	case AF_INET:
53795cc8c4aSderaadt 		if (setsockopt(0, IPPROTO_IP, IP_TOS, &tos,
538df69c215Sderaadt 		    sizeof(int)) == -1)
539b1750805Sitojun 			syslog(LOG_WARNING, "setsockopt (IP_TOS): %m");
5407ef737bdSjca 		break;
5417ef737bdSjca 	case AF_INET6:
5427ef737bdSjca 		if (setsockopt(0, IPPROTO_IPV6, IPV6_TCLASS, &tos,
543df69c215Sderaadt 		    sizeof(int)) == -1)
5447ef737bdSjca 			syslog(LOG_WARNING, "setsockopt (IPV6_TCLASS): %m");
5457ef737bdSjca 		break;
546b1750805Sitojun 	}
547b1750805Sitojun 	data_source.su_port = htons(ntohs(ctrl_addr.su_port) - 1);
54827444405Sdownsj 
549df930be7Sderaadt 	/* Try to handle urgent data inline */
550df69c215Sderaadt 	if (setsockopt(0, SOL_SOCKET, SO_OOBINLINE, &on, sizeof(on)) == -1)
551df930be7Sderaadt 		syslog(LOG_ERR, "setsockopt: %m");
552df930be7Sderaadt 
553b1750805Sitojun 	dolog((struct sockaddr *)&his_addr);
55495cc8c4aSderaadt 
555df930be7Sderaadt 	/*
556df930be7Sderaadt 	 * Set up default state
557df930be7Sderaadt 	 */
558df930be7Sderaadt 	data = -1;
559df930be7Sderaadt 	type = TYPE_A;
560df930be7Sderaadt 	form = FORM_N;
561df930be7Sderaadt 	stru = STRU_F;
562df930be7Sderaadt 	mode = MODE_S;
563df930be7Sderaadt 	tmpline[0] = '\0';
564df930be7Sderaadt 
565df930be7Sderaadt 	/* If logins are disabled, print out the message. */
566cad3e9c0Smillert 	if ((fp = fopen(_PATH_NOLOGIN, "r")) != NULL) {
567cad3e9c0Smillert 		while (fgets(line, sizeof(line), fp) != NULL) {
56861fc5ed6Sray 			line[strcspn(line, "\n")] = '\0';
569df930be7Sderaadt 			lreply(530, "%s", line);
570df930be7Sderaadt 		}
571cad3e9c0Smillert 		(void) fclose(fp);
572df930be7Sderaadt 		reply(530, "System not available.");
573df930be7Sderaadt 		exit(0);
574df930be7Sderaadt 	}
575cad3e9c0Smillert 	if ((fp = fopen(_PATH_FTPWELCOME, "r")) != NULL) {
576cad3e9c0Smillert 		while (fgets(line, sizeof(line), fp) != NULL) {
57761fc5ed6Sray 			line[strcspn(line, "\n")] = '\0';
578df930be7Sderaadt 			lreply(220, "%s", line);
579df930be7Sderaadt 		}
580cad3e9c0Smillert 		(void) fclose(fp);
581df930be7Sderaadt 		/* reply(220,) must follow */
582df930be7Sderaadt 	}
583df930be7Sderaadt 	(void) gethostname(hostname, sizeof(hostname));
584d8f69df0Sdownsj 
585d8f69df0Sdownsj 	/* Make sure hostname is fully qualified. */
586d8f69df0Sdownsj 	hp = gethostbyname(hostname);
587d8f69df0Sdownsj 	if (hp != NULL)
588af715a80Sitojun 		strlcpy(hostname, hp->h_name, sizeof(hostname));
589d8f69df0Sdownsj 
590d8f69df0Sdownsj 	if (multihome) {
5911303e027Sniallo 		error = getnameinfo((struct sockaddr *)&ctrl_addr,
5921303e027Sniallo 		    ctrl_addr.su_len, dhostname, sizeof(dhostname), NULL, 0, 0);
593d8f69df0Sdownsj 	}
594d8f69df0Sdownsj 
5951303e027Sniallo 	if (error != 0)
596ddb8ccb6Scloder 		reply(220, "FTP server ready.");
5971303e027Sniallo 	else
598ddb8ccb6Scloder 		reply(220, "%s FTP server ready.",
599ddb8ccb6Scloder 		    (multihome ? dhostname : hostname));
600b96c0bc5Shenning 
601b96c0bc5Shenning 	monitor_init();
602b96c0bc5Shenning 
603df930be7Sderaadt 	for (;;)
604df930be7Sderaadt 		(void) yyparse();
605df930be7Sderaadt 	/* NOTREACHED */
606df930be7Sderaadt }
607df930be7Sderaadt 
60811f7798fSdownsj /*
60911f7798fSdownsj  * Signal handlers.
61011f7798fSdownsj  */
611df930be7Sderaadt static void
lostconn(int signo)6126bcb9e83Sderaadt lostconn(int signo)
613df930be7Sderaadt {
614abe43e41Sderaadt 	struct syslog_data sdata = SYSLOG_DATA_INIT;
6153608b787Smillert 
61676d1ef43Sotto 	sdata.log_fac = LOG_FTP;
617df930be7Sderaadt 	if (debug)
618abe43e41Sderaadt 		syslog_r(LOG_DEBUG, &sdata, "lost connection");
6192378d5feSmillert 	dologout(1);
620df930be7Sderaadt }
621df930be7Sderaadt 
62211f7798fSdownsj static void
sigquit(int signo)6236bcb9e83Sderaadt sigquit(int signo)
62411f7798fSdownsj {
625abe43e41Sderaadt 	struct syslog_data sdata = SYSLOG_DATA_INIT;
6263608b787Smillert 
62776d1ef43Sotto 	sdata.log_fac = LOG_FTP;
62876d1ef43Sotto 	syslog_r(LOG_DEBUG, &sdata, "got signal %s", sys_signame[signo]);
6292378d5feSmillert 	dologout(1);
63011f7798fSdownsj }
63111f7798fSdownsj 
632df930be7Sderaadt /*
633df930be7Sderaadt  * Save the result of a getpwnam.  Used for USER command, since
634df930be7Sderaadt  * the data returned must not be clobbered by any other command
635df930be7Sderaadt  * (e.g., globbing).
636df930be7Sderaadt  */
637df930be7Sderaadt static struct passwd *
sgetpwnam(const char * name,struct passwd * pw)6380c50dd5dSjan sgetpwnam(const char *name, struct passwd *pw)
639df930be7Sderaadt {
64041db49dbSmillert 	static struct passwd *save;
641642b79bdSmillert 	struct passwd *old;
642df930be7Sderaadt 
643642b79bdSmillert 	if (pw == NULL && (pw = getpwnam(name)) == NULL)
644874f6dd9Smillert 		return (NULL);
645642b79bdSmillert 	old = save;
64641db49dbSmillert 	save = pw_dup(pw);
64741db49dbSmillert 	if (save == NULL) {
64841db49dbSmillert 		perror_reply(421, "Local resource failure: malloc");
64941db49dbSmillert 		dologout(1);
65041db49dbSmillert 		/* NOTREACHED */
65141db49dbSmillert 	}
652642b79bdSmillert 	if (old) {
6538fbd7fcbSdoug 		explicit_bzero(old->pw_passwd, strlen(old->pw_passwd));
654642b79bdSmillert 		free(old);
655642b79bdSmillert 	}
65641db49dbSmillert 	return (save);
657df930be7Sderaadt }
658df930be7Sderaadt 
659df930be7Sderaadt static int login_attempts;	/* number of failed login attempts */
660df930be7Sderaadt static int askpasswd;		/* had user command, ask for passwd */
661b9fc9a72Sderaadt static char curname[LOGIN_NAME_MAX];	/* current USER name */
662df930be7Sderaadt 
663df930be7Sderaadt /*
664df930be7Sderaadt  * USER command.
665df930be7Sderaadt  * Sets global passwd pointer pw if named account exists and is acceptable;
666df930be7Sderaadt  * sets askpasswd if a PASS command is expected.  If logged in previously,
667df930be7Sderaadt  * need to reset state.  If name is "ftp" or "anonymous", the name is not in
668df930be7Sderaadt  * _PATH_FTPUSERS, and ftp account exists, set guest and pw, then just return.
669df930be7Sderaadt  * If account doesn't exist, ask for passwd anyway.  Otherwise, check user
670df930be7Sderaadt  * requesting login privileges.  Disallow anyone who does not have a standard
671df930be7Sderaadt  * shell as returned by getusershell().  Disallow anyone mentioned in the file
672df930be7Sderaadt  * _PATH_FTPUSERS to allow people such as root and uucp to be avoided.
673df930be7Sderaadt  */
674df930be7Sderaadt void
user(char * name)6756bcb9e83Sderaadt user(char *name)
676df930be7Sderaadt {
677874f6dd9Smillert 	char *cp, *shell, *style, *host;
67841db49dbSmillert 	char *class = NULL;
679fed231abSmillert 
680b96c0bc5Shenning 	if (logged_in) {
68176d1ef43Sotto 		kill_slave("user already logged in");
682874f6dd9Smillert 		end_login();
683b96c0bc5Shenning 	}
684874f6dd9Smillert 
685874f6dd9Smillert 	/* Close session from previous user if there was one. */
68641db49dbSmillert 	if (as) {
68741db49dbSmillert 		auth_close(as);
68841db49dbSmillert 		as = NULL;
68941db49dbSmillert 	}
690874f6dd9Smillert 	if (lc) {
691874f6dd9Smillert 		login_close(lc);
692874f6dd9Smillert 		lc = NULL;
693df930be7Sderaadt 	}
694df930be7Sderaadt 
69541db49dbSmillert 	if ((style = strchr(name, ':')) != NULL)
69641db49dbSmillert 		*style++ = 0;
69741db49dbSmillert 
698df930be7Sderaadt 	guest = 0;
69961ed3bcaStobias 	askpasswd = 0;
700874f6dd9Smillert 	host = multihome ? dhostname : hostname;
7017a6ccb19Sderaadt 	if (anon_ok &&
7027a6ccb19Sderaadt 	    (strcmp(name, "ftp") == 0 || strcmp(name, "anonymous") == 0)) {
703df930be7Sderaadt 		if (checkuser(_PATH_FTPUSERS, "ftp") ||
704df930be7Sderaadt 		    checkuser(_PATH_FTPUSERS, "anonymous"))
705df930be7Sderaadt 			reply(530, "User %s access denied.", name);
706642b79bdSmillert 		else if ((pw = sgetpwnam("ftp", NULL)) != NULL) {
707b73df284Sray 			if ((lc = login_getclass(pw->pw_class)) == NULL ||
708b73df284Sray 			    (as = auth_open()) == NULL ||
709b498a85fSmillert 			    auth_setpwd(as, pw) != 0 ||
710874f6dd9Smillert 			    auth_setoption(as, "FTPD_HOST", host) < 0) {
711874f6dd9Smillert 				if (as) {
712874f6dd9Smillert 					auth_close(as);
713874f6dd9Smillert 					as = NULL;
714874f6dd9Smillert 				}
715b73df284Sray 				if (lc) {
716874f6dd9Smillert 					login_close(lc);
717874f6dd9Smillert 					lc = NULL;
718b73df284Sray 				}
719874f6dd9Smillert 				reply(421, "Local resource failure");
720874f6dd9Smillert 				return;
721874f6dd9Smillert 			}
722b73df284Sray 			guest = 1;
723b73df284Sray 			askpasswd = 1;
724df930be7Sderaadt 			reply(331,
72541db49dbSmillert 			"Guest login ok, send your email address as password.");
726df930be7Sderaadt 		} else
727df930be7Sderaadt 			reply(530, "User %s unknown.", name);
728df930be7Sderaadt 		if (!askpasswd && logging)
729df930be7Sderaadt 			syslog(LOG_NOTICE,
730df930be7Sderaadt 			    "ANONYMOUS FTP LOGIN REFUSED FROM %s", remotehost);
731df930be7Sderaadt 		return;
732df930be7Sderaadt 	}
7330a04bae7Sderaadt 
734df930be7Sderaadt 	shell = _PATH_BSHELL;
735642b79bdSmillert 	if ((pw = sgetpwnam(name, NULL))) {
73641db49dbSmillert 		class = pw->pw_class;
73741db49dbSmillert 		if (pw->pw_shell != NULL && *pw->pw_shell != '\0')
73841db49dbSmillert 			shell = pw->pw_shell;
739df930be7Sderaadt 		while ((cp = getusershell()) != NULL)
740df930be7Sderaadt 			if (strcmp(cp, shell) == 0)
741df930be7Sderaadt 				break;
74241db49dbSmillert 		shell = cp;
743df930be7Sderaadt 		endusershell();
74441db49dbSmillert 	}
745df930be7Sderaadt 
74641db49dbSmillert 	/* Get login class; if invalid style treat like unknown user. */
74741db49dbSmillert 	lc = login_getclass(class);
74841db49dbSmillert 	if (lc && (style = login_getstyle(lc, style, "auth-ftp")) == NULL) {
74941db49dbSmillert 		login_close(lc);
75041db49dbSmillert 		lc = NULL;
75141db49dbSmillert 		pw = NULL;
75241db49dbSmillert 	}
75341db49dbSmillert 
75441db49dbSmillert 	/* Do pre-authentication setup. */
75541db49dbSmillert 	if (lc && ((as = auth_open()) == NULL ||
756b498a85fSmillert 	    (pw != NULL && auth_setpwd(as, pw) != 0) ||
75741db49dbSmillert 	    auth_setitem(as, AUTHV_STYLE, style) < 0 ||
75841db49dbSmillert 	    auth_setitem(as, AUTHV_NAME, name) < 0 ||
7594e04eba5Smillert 	    auth_setitem(as, AUTHV_CLASS, class) < 0 ||
76074bda7f2Shin 	    auth_setoption(as, "login", "yes") < 0 ||
761874f6dd9Smillert 	    auth_setoption(as, "notickets", "yes") < 0 ||
762874f6dd9Smillert 	    auth_setoption(as, "FTPD_HOST", host) < 0)) {
76341db49dbSmillert 		if (as) {
76441db49dbSmillert 			auth_close(as);
76541db49dbSmillert 			as = NULL;
76641db49dbSmillert 		}
76741db49dbSmillert 		login_close(lc);
76841db49dbSmillert 		lc = NULL;
76941db49dbSmillert 		reply(421, "Local resource failure");
77041db49dbSmillert 		return;
77141db49dbSmillert 	}
77241db49dbSmillert 	if (logging)
77341db49dbSmillert 		strlcpy(curname, name, sizeof(curname));
77441db49dbSmillert 
77541db49dbSmillert 	dochroot = (lc && login_getcapbool(lc, "ftp-chroot", 0)) ||
77641db49dbSmillert 	    checkuser(_PATH_FTPCHROOT, name);
77741db49dbSmillert 	if (anon_only && !dochroot) {
778f7fc00fbSmillert 		reply(530, "User %s access denied.", name);
77941db49dbSmillert 		return;
78041db49dbSmillert 	}
78141db49dbSmillert 	if (pw) {
7826c941f8bSsthen 		if (pw->pw_uid < minuid) {
7836c941f8bSsthen 			reply(530, "User %s access denied.", name);
7846c941f8bSsthen 			if (logging)
7856c941f8bSsthen 				syslog(LOG_NOTICE,
7866c941f8bSsthen 				    "FTP LOGIN REFUSED FROM %s, %s (UID))",
7876c941f8bSsthen 				    remotehost, name);
7886c941f8bSsthen 			return;
7896c941f8bSsthen 		}
79041db49dbSmillert 		if ((!shell && !dochroot) || checkuser(_PATH_FTPUSERS, name)) {
791df930be7Sderaadt 			reply(530, "User %s access denied.", name);
792df930be7Sderaadt 			if (logging)
793df930be7Sderaadt 				syslog(LOG_NOTICE,
794df930be7Sderaadt 				    "FTP LOGIN REFUSED FROM %s, %s",
795df930be7Sderaadt 				    remotehost, name);
79641db49dbSmillert 			pw = NULL;
797df930be7Sderaadt 			return;
798df930be7Sderaadt 		}
799df930be7Sderaadt 	}
800df930be7Sderaadt 
80141db49dbSmillert 	if (as != NULL && (cp = auth_challenge(as)) != NULL)
802be51aaf5Smillert 		reply(331, "%s", cp);
80341db49dbSmillert 	else
804df930be7Sderaadt 		reply(331, "Password required for %s.", name);
805df930be7Sderaadt 
806df930be7Sderaadt 	askpasswd = 1;
807df930be7Sderaadt 	/*
808df930be7Sderaadt 	 * Delay before reading passwd after first failed
809df930be7Sderaadt 	 * attempt to slow down passwd-guessing programs.
810df930be7Sderaadt 	 */
811df930be7Sderaadt 	if (login_attempts)
812df930be7Sderaadt 		sleep((unsigned) login_attempts);
813df930be7Sderaadt }
814df930be7Sderaadt 
815df930be7Sderaadt /*
816df930be7Sderaadt  * Check if a user is in the file "fname"
817df930be7Sderaadt  */
818df930be7Sderaadt static int
checkuser(char * fname,const char * name)8190c50dd5dSjan checkuser(char *fname, const char *name)
820df930be7Sderaadt {
821cad3e9c0Smillert 	FILE *fp;
822df930be7Sderaadt 	int found = 0;
823df930be7Sderaadt 	char *p, line[BUFSIZ];
824df930be7Sderaadt 
825cad3e9c0Smillert 	if ((fp = fopen(fname, "r")) != NULL) {
826cad3e9c0Smillert 		while (fgets(line, sizeof(line), fp) != NULL)
827df930be7Sderaadt 			if ((p = strchr(line, '\n')) != NULL) {
828df930be7Sderaadt 				*p = '\0';
829df930be7Sderaadt 				if (line[0] == '#')
830df930be7Sderaadt 					continue;
831df930be7Sderaadt 				if (strcmp(line, name) == 0) {
832df930be7Sderaadt 					found = 1;
833df930be7Sderaadt 					break;
834df930be7Sderaadt 				}
835df930be7Sderaadt 			}
836cad3e9c0Smillert 		(void) fclose(fp);
837df930be7Sderaadt 	}
838df930be7Sderaadt 	return (found);
839df930be7Sderaadt }
840df930be7Sderaadt 
841df930be7Sderaadt /*
842df930be7Sderaadt  * Terminate login as previous user, if any, resetting state;
843df930be7Sderaadt  * used when USER command is given or login fails.
844df930be7Sderaadt  */
845df930be7Sderaadt static void
end_login(void)8466bcb9e83Sderaadt end_login(void)
847df930be7Sderaadt {
8485b333653Sbitblt 	sigprocmask (SIG_BLOCK, &allsigs, NULL);
849eb00e388Sdownsj 	if (logged_in) {
85005312c4aSajacoutot 		if (!nowtmp)
8511d5555bfSderaadt 			ftpdlogwtmp(ttyline, "", "");
852eb00e388Sdownsj 		if (doutmp)
853b12aa87cSderaadt 			ftpd_logout(utmp.ut_line);
854eb00e388Sdownsj 	}
85598ee8727Smillert 	reply(530, "Please reconnect to work as another user");
856b96c0bc5Shenning 	_exit(0);
857df930be7Sderaadt }
858df930be7Sderaadt 
859b96c0bc5Shenning enum auth_ret
pass(char * passwd)8606bcb9e83Sderaadt pass(char *passwd)
861df930be7Sderaadt {
86295cc8c4aSderaadt 	int authok;
86395cc8c4aSderaadt 	unsigned int flags;
864cad3e9c0Smillert 	FILE *fp;
865b9fc9a72Sderaadt 	static char homedir[PATH_MAX];
866b9fc9a72Sderaadt 	char *motd, *dir, rootdir[PATH_MAX];
867642b79bdSmillert 	size_t sz_pw_dir;
868df930be7Sderaadt 
869df930be7Sderaadt 	if (logged_in || askpasswd == 0) {
870df930be7Sderaadt 		reply(503, "Login with USER first.");
871b96c0bc5Shenning 		return (AUTH_FAILED);
872df930be7Sderaadt 	}
873df930be7Sderaadt 	askpasswd = 0;
874df930be7Sderaadt 	if (!guest) {		/* "ftp" is only account allowed no password */
87541db49dbSmillert 		authok = 0;
876b498a85fSmillert 		if (pw == NULL || pw->pw_passwd[0] == '\0') {
877fb045e09Smillert 			useconds_t us;
878fb045e09Smillert 
879fb045e09Smillert 			/* Sleep between 1 and 3 seconds to emulate a crypt. */
88066ad965fSdjm 			us = arc4random_uniform(3000000);
881fb045e09Smillert 			usleep(us);
8821bfc2e59Sderaadt 			if (as != NULL) {
883b498a85fSmillert 				auth_close(as);
8841bfc2e59Sderaadt 				as = NULL;
8851bfc2e59Sderaadt 			}
88641db49dbSmillert 		} else {
88741db49dbSmillert 			authok = auth_userresponse(as, passwd, 0);
88841db49dbSmillert 			as = NULL;
889df930be7Sderaadt 		}
89041db49dbSmillert 		if (authok == 0) {
891df930be7Sderaadt 			reply(530, "Login incorrect.");
892df930be7Sderaadt 			if (logging)
893df930be7Sderaadt 				syslog(LOG_NOTICE,
894df930be7Sderaadt 				    "FTP LOGIN FAILED FROM %s, %s",
895df930be7Sderaadt 				    remotehost, curname);
896df930be7Sderaadt 			pw = NULL;
897df930be7Sderaadt 			if (login_attempts++ >= 5) {
898df930be7Sderaadt 				syslog(LOG_NOTICE,
899df930be7Sderaadt 				    "repeated login failures from %s",
900df930be7Sderaadt 				    remotehost);
90176d1ef43Sotto 				kill_slave("repeated login failures");
902b96c0bc5Shenning 				_exit(0);
903df930be7Sderaadt 			}
904b96c0bc5Shenning 			return (AUTH_FAILED);
905df930be7Sderaadt 		}
90641db49dbSmillert 	} else if (lc != NULL) {
907d8f69df0Sdownsj 		/* Save anonymous' password. */
908874f6dd9Smillert 		free(guestpw);
909d8f69df0Sdownsj 		guestpw = strdup(passwd);
910b96c0bc5Shenning 		if (guestpw == NULL) {
91176d1ef43Sotto 			kill_slave("out of mem");
912098b785aSmpech 			fatal("Out of memory.");
913b96c0bc5Shenning 		}
91441db49dbSmillert 
91541db49dbSmillert 		authok = auth_approval(as, lc, pw->pw_name, "ftp");
91641db49dbSmillert 		auth_close(as);
91741db49dbSmillert 		as = NULL;
91841db49dbSmillert 		if (authok == 0) {
91941db49dbSmillert 			syslog(LOG_INFO|LOG_AUTH,
92041db49dbSmillert 			    "FTP LOGIN FAILED (HOST) as %s: approval failure.",
92141db49dbSmillert 			    pw->pw_name);
922c418ff08Smpech 			reply(530, "Approval failure.");
92376d1ef43Sotto 			kill_slave("approval failure");
924b96c0bc5Shenning 			_exit(0);
92541db49dbSmillert 		}
92641db49dbSmillert 	} else {
92741db49dbSmillert 		syslog(LOG_INFO|LOG_AUTH,
92841db49dbSmillert 		    "FTP LOGIN CLASS %s MISSING for %s: approval failure.",
92941db49dbSmillert 		    pw->pw_class, pw->pw_name);
930c418ff08Smpech 		reply(530, "Permission denied.");
93176d1ef43Sotto 		kill_slave("permission denied");
932b96c0bc5Shenning 		_exit(0);
933df930be7Sderaadt 	}
934b96c0bc5Shenning 
935b96c0bc5Shenning 	if (monitor_post_auth() == 1) {
936b96c0bc5Shenning 		/* Post-auth monitor process */
937b96c0bc5Shenning 		logged_in = 1;
938b96c0bc5Shenning 		return (AUTH_MONITOR);
939b96c0bc5Shenning 	}
940b96c0bc5Shenning 
941df930be7Sderaadt 	login_attempts = 0;		/* this time successful */
942df8feab0Smillert 	/* set umask via setusercontext() unless -u flag was given. */
943df8feab0Smillert 	flags = LOGIN_SETGROUP|LOGIN_SETPRIORITY|LOGIN_SETRESOURCES;
944df8feab0Smillert 	if (umaskchange)
945df8feab0Smillert 		flags |= LOGIN_SETUMASK;
946df8feab0Smillert 	else
94745168a3fSmillert 		(void) umask(defumask);
948a20a5177Sguenther 	if (setusercontext(lc, pw, 0, flags) != 0) {
949ea0284a0Stobias 		perror_reply(421, "Local resource failure: setusercontext");
950cbf3115fSmillert 		syslog(LOG_NOTICE, "setusercontext: %m");
95151368fafSmillert 		dologout(1);
95251368fafSmillert 		/* NOTREACHED */
95351368fafSmillert 	}
954df930be7Sderaadt 
955df930be7Sderaadt 	/* open wtmp before chroot */
95605312c4aSajacoutot 	if (!nowtmp)
9571d5555bfSderaadt 		ftpdlogwtmp(ttyline, pw->pw_name, remotehost);
958eb00e388Sdownsj 
959eb00e388Sdownsj 	/* open utmp before chroot */
960eb00e388Sdownsj 	if (doutmp) {
961a20a5177Sguenther 		memset(&utmp, 0, sizeof(utmp));
962eb00e388Sdownsj 		(void)time(&utmp.ut_time);
9636a644c5bSpjanzen 		(void)strncpy(utmp.ut_name, pw->pw_name, sizeof(utmp.ut_name));
9646a644c5bSpjanzen 		(void)strncpy(utmp.ut_host, remotehost, sizeof(utmp.ut_host));
9656a644c5bSpjanzen 		(void)strncpy(utmp.ut_line, ttyline, sizeof(utmp.ut_line));
966b12aa87cSderaadt 		ftpd_login(&utmp);
967eb00e388Sdownsj 	}
968eb00e388Sdownsj 
96972fc7920Sdownsj 	/* open stats file before chroot */
97072fc7920Sdownsj 	if (guest && (stats == 1) && (statfd < 0))
971df69c215Sderaadt 		if ((statfd = open(_PATH_FTPDSTATFILE, O_WRONLY|O_APPEND)) == -1)
97272fc7920Sdownsj 			stats = 0;
97372fc7920Sdownsj 
974df930be7Sderaadt 	logged_in = 1;
975df930be7Sderaadt 
976fed231abSmillert 	if ((dir = login_getcapstr(lc, "ftp-dir", NULL, NULL))) {
9776f389908Smillert 		char *newdir;
9786f389908Smillert 
9796f389908Smillert 		newdir = copy_dir(dir, pw);
9806f389908Smillert 		if (newdir == NULL) {
9816f389908Smillert 			perror_reply(421, "Local resource failure: malloc");
9826f389908Smillert 			dologout(1);
9836f389908Smillert 			/* NOTREACHED */
9846f389908Smillert 		}
9856f389908Smillert 		pw->pw_dir = newdir;
986642b79bdSmillert 		pw = sgetpwnam(NULL, pw);
987642b79bdSmillert 		free(dir);
988642b79bdSmillert 		free(newdir);
989fed231abSmillert 	}
99060435dbcSmillert 
991383fcf70Sitojun 	/* make sure pw->pw_dir is big enough to hold "/" */
992642b79bdSmillert 	sz_pw_dir = strlen(pw->pw_dir) + 1;
993642b79bdSmillert 	if (sz_pw_dir < 2) {
994642b79bdSmillert 		pw->pw_dir = "/";
995642b79bdSmillert 		pw = sgetpwnam(NULL, pw);
996642b79bdSmillert 		sz_pw_dir = 2;
997383fcf70Sitojun 	}
998383fcf70Sitojun 
999d8f69df0Sdownsj 	if (guest || dochroot) {
100088629109Smickey 		if (multihome && guest) {
1001d8f69df0Sdownsj 			struct stat ts;
1002d8f69df0Sdownsj 
1003d8f69df0Sdownsj 			/* Compute root directory. */
1004d8f69df0Sdownsj 			snprintf(rootdir, sizeof(rootdir), "%s/%s",
1005d8f69df0Sdownsj 			    pw->pw_dir, dhostname);
1006df69c215Sderaadt 			if (stat(rootdir, &ts) == -1) {
1007d8f69df0Sdownsj 				snprintf(rootdir, sizeof(rootdir), "%s/%s",
1008d8f69df0Sdownsj 				    pw->pw_dir, hostname);
1009d8f69df0Sdownsj 			}
1010d8f69df0Sdownsj 		} else
1011af715a80Sitojun 			strlcpy(rootdir, pw->pw_dir, sizeof(rootdir));
1012d8f69df0Sdownsj 	}
1013df930be7Sderaadt 	if (guest) {
1014df930be7Sderaadt 		/*
1015df930be7Sderaadt 		 * We MUST do a chdir() after the chroot. Otherwise
1016df930be7Sderaadt 		 * the old current directory will be accessible as "."
1017df930be7Sderaadt 		 * outside the new root!
1018df930be7Sderaadt 		 */
1019df69c215Sderaadt 		if (chroot(rootdir) == -1 || chdir("/") == -1) {
1020df930be7Sderaadt 			reply(550, "Can't set guest privileges.");
1021df930be7Sderaadt 			goto bad;
1022df930be7Sderaadt 		}
1023642b79bdSmillert 		strlcpy(pw->pw_dir, "/", sz_pw_dir);
10242c20747dSderaadt 		if (setenv("HOME", "/", 1) == -1) {
10252c20747dSderaadt 			reply(550, "Can't setup environment.");
10262c20747dSderaadt 			goto bad;
10272c20747dSderaadt 		}
1028df930be7Sderaadt 	} else if (dochroot) {
1029df69c215Sderaadt 		if (chroot(rootdir) == -1 || chdir("/") == -1) {
1030df930be7Sderaadt 			reply(550, "Can't change root.");
1031df930be7Sderaadt 			goto bad;
1032df930be7Sderaadt 		}
1033642b79bdSmillert 		strlcpy(pw->pw_dir, "/", sz_pw_dir);
10342c20747dSderaadt 		if (setenv("HOME", "/", 1) == -1) {
10352c20747dSderaadt 			reply(550, "Can't setup environment.");
10362c20747dSderaadt 			goto bad;
10372c20747dSderaadt 		}
1038df69c215Sderaadt 	} else if (chdir(pw->pw_dir) == -1) {
1039df69c215Sderaadt 		if (chdir("/") == -1) {
1040df930be7Sderaadt 			reply(530, "User %s: can't change directory to %s.",
1041df930be7Sderaadt 			    pw->pw_name, pw->pw_dir);
1042df930be7Sderaadt 			goto bad;
1043df930be7Sderaadt 		} else
1044df930be7Sderaadt 			lreply(230, "No directory! Logging in with home=/");
1045df930be7Sderaadt 	}
1046df69c215Sderaadt 	if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) == -1) {
1047b96c0bc5Shenning 		reply(550, "Can't set gid.");
1048b96c0bc5Shenning 		goto bad;
1049b96c0bc5Shenning 	}
1050df69c215Sderaadt 	if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) == -1) {
1051df930be7Sderaadt 		reply(550, "Can't set uid.");
1052df930be7Sderaadt 		goto bad;
1053df930be7Sderaadt 	}
10545b333653Sbitblt 	sigprocmask(SIG_UNBLOCK, &allsigs, NULL);
1055256da855Sdownsj 
1056256da855Sdownsj 	/*
1057256da855Sdownsj 	 * Set home directory so that use of ~ (tilde) works correctly.
1058256da855Sdownsj 	 */
1059b9fc9a72Sderaadt 	if (getcwd(homedir, PATH_MAX) != NULL) {
10602c20747dSderaadt 		if (setenv("HOME", homedir, 1) == -1) {
10612c20747dSderaadt 			reply(550, "Can't setup environment.");
10622c20747dSderaadt 			goto bad;
10632c20747dSderaadt 		}
10642c20747dSderaadt 	}
1065256da855Sdownsj 
1066df930be7Sderaadt 	/*
1067df930be7Sderaadt 	 * Display a login message, if it exists.
1068df930be7Sderaadt 	 * N.B. reply(230,) must follow the message.
1069df930be7Sderaadt 	 */
107060435dbcSmillert 	motd = login_getcapstr(lc, "welcome", NULL, NULL);
107160435dbcSmillert 	if ((fp = fopen(motd ? motd : _PATH_FTPLOGINMESG, "r")) != NULL) {
107261fc5ed6Sray 		char line[LINE_MAX];
1073df930be7Sderaadt 
1074cad3e9c0Smillert 		while (fgets(line, sizeof(line), fp) != NULL) {
107561fc5ed6Sray 			line[strcspn(line, "\n")] = '\0';
1076df930be7Sderaadt 			lreply(230, "%s", line);
1077df930be7Sderaadt 		}
1078cad3e9c0Smillert 		(void) fclose(fp);
1079df930be7Sderaadt 	}
108060435dbcSmillert 	free(motd);
1081df930be7Sderaadt 	if (guest) {
1082df930be7Sderaadt 		reply(230, "Guest login ok, access restrictions apply.");
1083df930be7Sderaadt 		snprintf(proctitle, sizeof(proctitle),
1084df930be7Sderaadt 		    "%s: anonymous/%.*s", remotehost,
10851d5555bfSderaadt 		    (int)(sizeof(proctitle) - sizeof(remotehost) -
10861d5555bfSderaadt 		    sizeof(": anonymous/")), passwd);
10873cbaa3d7Sderaadt 		setproctitle("%s", proctitle);
1088df930be7Sderaadt 		if (logging)
1089df930be7Sderaadt 			syslog(LOG_INFO, "ANONYMOUS FTP LOGIN FROM %s, %s",
1090df930be7Sderaadt 			    remotehost, passwd);
1091df930be7Sderaadt 	} else {
1092df930be7Sderaadt 		reply(230, "User %s logged in.", pw->pw_name);
1093df930be7Sderaadt 		snprintf(proctitle, sizeof(proctitle),
1094df930be7Sderaadt 		    "%s: %s", remotehost, pw->pw_name);
10953cbaa3d7Sderaadt 		setproctitle("%s", proctitle);
1096df930be7Sderaadt 		if (logging)
1097df930be7Sderaadt 			syslog(LOG_INFO, "FTP LOGIN FROM %s as %s",
1098df930be7Sderaadt 			    remotehost, pw->pw_name);
1099df930be7Sderaadt 	}
1100fed231abSmillert 	login_close(lc);
1101fed231abSmillert 	lc = NULL;
1102b96c0bc5Shenning 	return (AUTH_SLAVE);
1103df930be7Sderaadt bad:
1104df930be7Sderaadt 	/* Forget all about it... */
1105fed231abSmillert 	login_close(lc);
1106fed231abSmillert 	lc = NULL;
1107df930be7Sderaadt 	end_login();
1108b96c0bc5Shenning 	return (AUTH_FAILED);
1109df930be7Sderaadt }
1110df930be7Sderaadt 
1111df930be7Sderaadt void
retrieve(enum ret_cmd cmd,const char * name)11120c50dd5dSjan retrieve(enum ret_cmd cmd, const char *name)
1113df930be7Sderaadt {
1114df930be7Sderaadt 	FILE *fin, *dout;
1115df930be7Sderaadt 	struct stat st;
11161b81cd4fStedu 	pid_t pid;
111772fc7920Sdownsj 	time_t start;
1118df930be7Sderaadt 
11197a7d71fbStedu 	if (cmd == RET_FILE) {
11201b81cd4fStedu 		fin = fopen(name, "r");
1121df930be7Sderaadt 		st.st_size = 0;
1122df930be7Sderaadt 	} else {
1123bca58876Sjan 		fin = ftpd_ls(name, &pid);
1124df930be7Sderaadt 		st.st_size = -1;
1125df930be7Sderaadt 		st.st_blksize = BUFSIZ;
1126df930be7Sderaadt 	}
1127df930be7Sderaadt 	if (fin == NULL) {
1128df930be7Sderaadt 		if (errno != 0) {
1129df930be7Sderaadt 			perror_reply(550, name);
11307a7d71fbStedu 			if (cmd == RET_FILE) {
1131df930be7Sderaadt 				LOGCMD("get", name);
1132df930be7Sderaadt 			}
1133df930be7Sderaadt 		}
1134df930be7Sderaadt 		return;
1135df930be7Sderaadt 	}
1136df930be7Sderaadt 	byte_count = -1;
11377a7d71fbStedu 	if (cmd == RET_FILE &&
1138df69c215Sderaadt 	    (fstat(fileno(fin), &st) == -1 || !S_ISREG(st.st_mode))) {
1139df930be7Sderaadt 		reply(550, "%s: not a plain file.", name);
1140df930be7Sderaadt 		goto done;
1141df930be7Sderaadt 	}
1142df930be7Sderaadt 	if (restart_point) {
1143df930be7Sderaadt 		if (type == TYPE_A) {
1144df930be7Sderaadt 			off_t i, n;
1145df930be7Sderaadt 			int c;
1146df930be7Sderaadt 
1147df930be7Sderaadt 			n = restart_point;
1148df930be7Sderaadt 			i = 0;
1149df930be7Sderaadt 			while (i++ < n) {
1150df930be7Sderaadt 				if ((c = getc(fin)) == EOF) {
1151bdbd0beaSmickey 					if (ferror(fin)) {
1152df930be7Sderaadt 						perror_reply(550, name);
1153df930be7Sderaadt 						goto done;
1154bdbd0beaSmickey 					} else
1155bdbd0beaSmickey 						break;
1156df930be7Sderaadt 				}
1157df930be7Sderaadt 				if (c == '\n')
1158df930be7Sderaadt 					i++;
1159df930be7Sderaadt 			}
1160df69c215Sderaadt 		} else if (lseek(fileno(fin), restart_point, SEEK_SET) == -1) {
1161df930be7Sderaadt 			perror_reply(550, name);
1162df930be7Sderaadt 			goto done;
1163df930be7Sderaadt 		}
1164df930be7Sderaadt 	}
1165df930be7Sderaadt 	dout = dataconn(name, st.st_size, "w");
1166df930be7Sderaadt 	if (dout == NULL)
1167df930be7Sderaadt 		goto done;
116872fc7920Sdownsj 	time(&start);
1169a20a5177Sguenther 	send_data(fin, dout, st.st_blksize, st.st_size,
11707a7d71fbStedu 	    (restart_point == 0 && cmd == RET_FILE && S_ISREG(st.st_mode)));
11717a7d71fbStedu 	if ((cmd == RET_FILE) && stats)
11724ae5d0cdSangelos 		logxfer(name, byte_count, start);
1173df930be7Sderaadt 	(void) fclose(dout);
1174df930be7Sderaadt 	data = -1;
1175df930be7Sderaadt done:
117629b238aaSderaadt 	if (pdata >= 0)
117729b238aaSderaadt 		(void) close(pdata);
117829b238aaSderaadt 	pdata = -1;
11797a7d71fbStedu 	if (cmd == RET_FILE) {
1180df930be7Sderaadt 		LOGBYTES("get", name, byte_count);
11811b81cd4fStedu 		fclose(fin);
11821b81cd4fStedu 	} else {
11831b81cd4fStedu 		ftpd_pclose(fin, pid);
11841b81cd4fStedu 	}
1185df930be7Sderaadt }
1186df930be7Sderaadt 
1187df930be7Sderaadt void
store(const char * name,const char * mode,int unique)11880c50dd5dSjan store(const char *name, const char *mode, int unique)
1189df930be7Sderaadt {
1190df930be7Sderaadt 	FILE *fout, *din;
1191c72b5b24Smillert 	int (*closefunc)(FILE *);
1192bee64f55Sderaadt 	struct stat st;
1193bee64f55Sderaadt 	int fd;
1194df930be7Sderaadt 
11950d4f5311Smillert 	if (restart_point && *mode != 'a')
1196ddbae1b3Smillert 		mode = "r+";
1197ddbae1b3Smillert 
1198bee64f55Sderaadt 	if (unique && stat(name, &st) == 0) {
1199bee64f55Sderaadt 		char *nam;
1200bee64f55Sderaadt 
1201bee64f55Sderaadt 		fd = guniquefd(name, &nam);
1202bee64f55Sderaadt 		if (fd == -1) {
1203df930be7Sderaadt 			LOGCMD(*mode == 'w' ? "put" : "append", name);
1204df930be7Sderaadt 			return;
1205df930be7Sderaadt 		}
1206bee64f55Sderaadt 		name = nam;
1207bee64f55Sderaadt 		fout = fdopen(fd, mode);
1208bee64f55Sderaadt 	} else
1209df930be7Sderaadt 		fout = fopen(name, mode);
1210bee64f55Sderaadt 
1211df930be7Sderaadt 	closefunc = fclose;
1212df930be7Sderaadt 	if (fout == NULL) {
1213df930be7Sderaadt 		perror_reply(553, name);
1214df930be7Sderaadt 		LOGCMD(*mode == 'w' ? "put" : "append", name);
1215df930be7Sderaadt 		return;
1216df930be7Sderaadt 	}
1217df930be7Sderaadt 	byte_count = -1;
1218df930be7Sderaadt 	if (restart_point) {
1219df930be7Sderaadt 		if (type == TYPE_A) {
1220df930be7Sderaadt 			off_t i, n;
1221df930be7Sderaadt 			int c;
1222df930be7Sderaadt 
1223df930be7Sderaadt 			n = restart_point;
1224df930be7Sderaadt 			i = 0;
1225df930be7Sderaadt 			while (i++ < n) {
1226df930be7Sderaadt 				if ((c = getc(fout)) == EOF) {
1227bdbd0beaSmickey 					if (ferror(fout)) {
1228df930be7Sderaadt 						perror_reply(550, name);
1229df930be7Sderaadt 						goto done;
1230bdbd0beaSmickey 					} else
1231bdbd0beaSmickey 						break;
1232df930be7Sderaadt 				}
1233df930be7Sderaadt 				if (c == '\n')
1234df930be7Sderaadt 					i++;
1235df930be7Sderaadt 			}
1236df930be7Sderaadt 			/*
1237df930be7Sderaadt 			 * We must do this seek to "current" position
1238df930be7Sderaadt 			 * because we are changing from reading to
1239df930be7Sderaadt 			 * writing.
1240df930be7Sderaadt 			 */
1241df69c215Sderaadt 			if (fseek(fout, 0, SEEK_CUR) == -1) {
1242df930be7Sderaadt 				perror_reply(550, name);
1243df930be7Sderaadt 				goto done;
1244df930be7Sderaadt 			}
1245df69c215Sderaadt 		} else if (lseek(fileno(fout), restart_point, SEEK_SET) == -1) {
1246df930be7Sderaadt 			perror_reply(550, name);
1247df930be7Sderaadt 			goto done;
1248df930be7Sderaadt 		}
1249df930be7Sderaadt 	}
1250a20a5177Sguenther 	din = dataconn(name, -1, "r");
1251df930be7Sderaadt 	if (din == NULL)
1252df930be7Sderaadt 		goto done;
1253df930be7Sderaadt 	if (receive_data(din, fout) == 0) {
1254df930be7Sderaadt 		if (unique)
1255df930be7Sderaadt 			reply(226, "Transfer complete (unique file name:%s).",
1256df930be7Sderaadt 			    name);
1257df930be7Sderaadt 		else
1258df930be7Sderaadt 			reply(226, "Transfer complete.");
1259df930be7Sderaadt 	}
1260df930be7Sderaadt 	(void) fclose(din);
1261df930be7Sderaadt 	data = -1;
1262df930be7Sderaadt 	pdata = -1;
1263df930be7Sderaadt done:
1264df930be7Sderaadt 	LOGBYTES(*mode == 'w' ? "put" : "append", name, byte_count);
1265df930be7Sderaadt 	(*closefunc)(fout);
1266df930be7Sderaadt }
1267df930be7Sderaadt 
1268df930be7Sderaadt static FILE *
getdatasock(char * mode)12696bcb9e83Sderaadt getdatasock(char *mode)
1270df930be7Sderaadt {
1271b68c787bSjca 	int opt, s, t, tries;
1272df930be7Sderaadt 
1273df930be7Sderaadt 	if (data >= 0)
1274df930be7Sderaadt 		return (fdopen(data, mode));
12755b333653Sbitblt 	sigprocmask (SIG_BLOCK, &allsigs, NULL);
1276efa4b9efSmoritz 	s = monitor_socket(ctrl_addr.su_family);
1277df930be7Sderaadt 	if (s < 0)
1278df930be7Sderaadt 		goto bad;
1279b68c787bSjca 	opt = 1;
1280df930be7Sderaadt 	if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
1281df69c215Sderaadt 	    &opt, sizeof(opt)) == -1)
1282df930be7Sderaadt 		goto bad;
1283df930be7Sderaadt 	/* anchor socket to avoid multi-homing problems */
1284b1750805Sitojun 	data_source = ctrl_addr;
1285b1750805Sitojun 	data_source.su_port = htons(20); /* ftp-data port */
1286df930be7Sderaadt 	for (tries = 1; ; tries++) {
1287b96c0bc5Shenning 		if (monitor_bind(s, (struct sockaddr *)&data_source,
1288b1750805Sitojun 		    data_source.su_len) >= 0)
1289df930be7Sderaadt 			break;
1290b96c0bc5Shenning 		if (errno != EADDRINUSE || tries > 10)
1291df930be7Sderaadt 			goto bad;
129295cc8c4aSderaadt 		sleep((unsigned int)tries);
1293df930be7Sderaadt 	}
12945b333653Sbitblt 	sigprocmask (SIG_UNBLOCK, &allsigs, NULL);
12955b333653Sbitblt 
1296b68c787bSjca 	opt = IPTOS_THROUGHPUT;
12977ef737bdSjca 	switch (ctrl_addr.su_family) {
12987ef737bdSjca 	case AF_INET:
1299b68c787bSjca 		if (setsockopt(s, IPPROTO_IP, IP_TOS, &opt,
1300df69c215Sderaadt 		    sizeof(opt)) == -1)
1301df930be7Sderaadt 			syslog(LOG_WARNING, "setsockopt (IP_TOS): %m");
13027ef737bdSjca 		break;
13037ef737bdSjca 	case AF_INET6:
1304b68c787bSjca 		if (setsockopt(s, IPPROTO_IPV6, IPV6_TCLASS, &opt,
1305df69c215Sderaadt 		    sizeof(opt)) == -1)
13067ef737bdSjca 			syslog(LOG_WARNING, "setsockopt (IPV6_TCLASS): %m");
13077ef737bdSjca 		break;
1308b1750805Sitojun 	}
1309878f3b5aSdownsj 	/*
1310878f3b5aSdownsj 	 * Turn off push flag to keep sender TCP from sending short packets
1311878f3b5aSdownsj 	 * at the boundaries of each write().  Should probably do a SO_SNDBUF
1312878f3b5aSdownsj 	 * to set the send buffer size as well, but that may not be desirable
1313878f3b5aSdownsj 	 * in heavy-load situations.
1314878f3b5aSdownsj 	 */
1315b68c787bSjca 	opt = 1;
1316df69c215Sderaadt 	if (setsockopt(s, IPPROTO_TCP, TCP_NOPUSH, &opt, sizeof(opt)) == -1)
1317878f3b5aSdownsj 		syslog(LOG_WARNING, "setsockopt (TCP_NOPUSH): %m");
1318b68c787bSjca 	opt = 65536;
1319df69c215Sderaadt 	if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, &opt, sizeof(opt)) == -1)
1320878f3b5aSdownsj 		syslog(LOG_WARNING, "setsockopt (SO_SNDBUF): %m");
1321878f3b5aSdownsj 
1322df930be7Sderaadt 	return (fdopen(s, mode));
1323df930be7Sderaadt bad:
1324df930be7Sderaadt 	/* Return the real value of errno (close may change it) */
1325df930be7Sderaadt 	t = errno;
13265b333653Sbitblt 	sigprocmask (SIG_UNBLOCK, &allsigs, NULL);
1327b96c0bc5Shenning 	if (s >= 0)
1328df930be7Sderaadt 		(void) close(s);
1329df930be7Sderaadt 	errno = t;
1330df930be7Sderaadt 	return (NULL);
1331df930be7Sderaadt }
1332df930be7Sderaadt 
1333df930be7Sderaadt static FILE *
dataconn(const char * name,off_t size,char * mode)13340c50dd5dSjan dataconn(const char *name, off_t size, char *mode)
1335df930be7Sderaadt {
1336df930be7Sderaadt 	char sizebuf[32];
133785df27d1Smillert 	FILE *file = NULL;
1338b1750805Sitojun 	int retry = 0;
1339b1750805Sitojun 	in_port_t *p;
13400fe9133cSdanh 	u_char *fa, *ha;
134195cc8c4aSderaadt 	size_t alen;
13421303e027Sniallo 	int error;
1343df930be7Sderaadt 
1344df930be7Sderaadt 	file_size = size;
1345df930be7Sderaadt 	byte_count = 0;
1346a20a5177Sguenther 	if (size != -1) {
1347a20a5177Sguenther 		(void) snprintf(sizebuf, sizeof(sizebuf), " (%lld bytes)",
1348a20a5177Sguenther 		    (long long)size);
13491e36a500Sdownsj 	} else
13501e36a500Sdownsj 		sizebuf[0] = '\0';
1351df930be7Sderaadt 	if (pdata >= 0) {
1352b1750805Sitojun 		union sockunion from;
13530fe9133cSdanh 		int s;
13540fe9133cSdanh 		socklen_t fromlen = sizeof(from);
1355df930be7Sderaadt 
1356c5d07883Sbitblt 		(void) alarm ((unsigned) timeout);
1357df930be7Sderaadt 		s = accept(pdata, (struct sockaddr *)&from, &fromlen);
1358c5d07883Sbitblt 		(void) alarm (0);
1359df69c215Sderaadt 		if (s == -1) {
1360df930be7Sderaadt 			reply(425, "Can't open data connection.");
1361df930be7Sderaadt 			(void) close(pdata);
1362df930be7Sderaadt 			pdata = -1;
1363df930be7Sderaadt 			return (NULL);
1364df930be7Sderaadt 		}
1365b1750805Sitojun 		switch (from.su_family) {
1366b1750805Sitojun 		case AF_INET:
1367b1750805Sitojun 			p = (in_port_t *)&from.su_sin.sin_port;
1368b1750805Sitojun 			fa = (u_char *)&from.su_sin.sin_addr;
1369b1750805Sitojun 			ha = (u_char *)&his_addr.su_sin.sin_addr;
1370353a5fa7Sderaadt 			alen = sizeof(struct in_addr);
1371b1750805Sitojun 			break;
1372b1750805Sitojun 		case AF_INET6:
1373b1750805Sitojun 			p = (in_port_t *)&from.su_sin6.sin6_port;
1374b1750805Sitojun 			fa = (u_char *)&from.su_sin6.sin6_addr;
1375b1750805Sitojun 			ha = (u_char *)&his_addr.su_sin6.sin6_addr;
1376353a5fa7Sderaadt 			alen = sizeof(struct in6_addr);
1377b1750805Sitojun 			break;
1378b1750805Sitojun 		default:
1379ba7d19adSray 			reply(425, "Can't build data connection: "
1380ba7d19adSray 			    "unknown address family");
138116a6ac13Sjoshd 			(void) close(pdata);
138216a6ac13Sjoshd 			(void) close(s);
138316a6ac13Sjoshd 			pdata = -1;
138416a6ac13Sjoshd 			return (NULL);
138516a6ac13Sjoshd 		}
1386353a5fa7Sderaadt 		if (from.su_family != his_addr.su_family ||
1387353a5fa7Sderaadt 		    ntohs(*p) < IPPORT_RESERVED) {
1388ba7d19adSray 			reply(425, "Can't build data connection: "
1389ba7d19adSray 			    "address family or port error");
1390b1750805Sitojun 			(void) close(pdata);
1391b1750805Sitojun 			(void) close(s);
1392b1750805Sitojun 			pdata = -1;
1393b1750805Sitojun 			return (NULL);
1394b1750805Sitojun 		}
13957bb9870fSangelos 		if (portcheck && memcmp(fa, ha, alen) != 0) {
1396ea0284a0Stobias 			reply(425, "Can't build data connection: "
1397ba7d19adSray 			    "illegal port number");
139816a6ac13Sjoshd 			(void) close(pdata);
139916a6ac13Sjoshd 			(void) close(s);
140016a6ac13Sjoshd 			pdata = -1;
140116a6ac13Sjoshd 			return (NULL);
140216a6ac13Sjoshd 		}
1403df930be7Sderaadt 		(void) close(pdata);
1404df930be7Sderaadt 		pdata = s;
1405df930be7Sderaadt 		reply(150, "Opening %s mode data connection for '%s'%s.",
1406df930be7Sderaadt 		    type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf);
1407df930be7Sderaadt 		return (fdopen(pdata, mode));
1408df930be7Sderaadt 	}
1409df930be7Sderaadt 	if (data >= 0) {
1410df930be7Sderaadt 		reply(125, "Using existing data connection for '%s'%s.",
1411df930be7Sderaadt 		    name, sizebuf);
1412df930be7Sderaadt 		usedefault = 1;
1413df930be7Sderaadt 		return (fdopen(data, mode));
1414df930be7Sderaadt 	}
1415df930be7Sderaadt 	if (usedefault)
1416df930be7Sderaadt 		data_dest = his_addr;
1417df930be7Sderaadt 	usedefault = 1;
141899ca0666Smillert 	do {
141985df27d1Smillert 		if (file != NULL)
142085df27d1Smillert 			(void) fclose(file);
1421df930be7Sderaadt 		file = getdatasock(mode);
1422df930be7Sderaadt 		if (file == NULL) {
1423b9fc9a72Sderaadt 			char hbuf[HOST_NAME_MAX+1], pbuf[10];
1424353a5fa7Sderaadt 
14251303e027Sniallo 			error = getnameinfo((struct sockaddr *)&data_source,
142699ca0666Smillert 			    data_source.su_len, hbuf, sizeof(hbuf), pbuf,
142799ca0666Smillert 			    sizeof(pbuf), NI_NUMERICHOST | NI_NUMERICSERV);
14281303e027Sniallo 			if (error != 0)
14291303e027Sniallo 				reply(425, "Can't create data socket: %s.",
14301303e027Sniallo 				    strerror(errno));
14311303e027Sniallo 			else
14321303e027Sniallo 				reply(425,
14331303e027Sniallo 				    "Can't create data socket (%s,%s): %s.",
1434b1750805Sitojun 				    hbuf, pbuf, strerror(errno));
1435df930be7Sderaadt 			return (NULL);
1436df930be7Sderaadt 		}
143716a6ac13Sjoshd 
143816a6ac13Sjoshd 		/*
143916a6ac13Sjoshd 		 * attempt to connect to reserved port on client machine;
144016a6ac13Sjoshd 		 * this looks like an attack
144116a6ac13Sjoshd 		 */
1442b1750805Sitojun 		switch (data_dest.su_family) {
1443b1750805Sitojun 		case AF_INET:
1444b1750805Sitojun 			p = (in_port_t *)&data_dest.su_sin.sin_port;
1445b1750805Sitojun 			fa = (u_char *)&data_dest.su_sin.sin_addr;
1446b1750805Sitojun 			ha = (u_char *)&his_addr.su_sin.sin_addr;
1447353a5fa7Sderaadt 			alen = sizeof(struct in_addr);
1448b1750805Sitojun 			break;
1449b1750805Sitojun 		case AF_INET6:
1450b1750805Sitojun 			p = (in_port_t *)&data_dest.su_sin6.sin6_port;
1451b1750805Sitojun 			fa = (u_char *)&data_dest.su_sin6.sin6_addr;
1452b1750805Sitojun 			ha = (u_char *)&his_addr.su_sin6.sin6_addr;
1453353a5fa7Sderaadt 			alen = sizeof(struct in6_addr);
1454b1750805Sitojun 			break;
1455b1750805Sitojun 		default:
1456ba7d19adSray 			reply(425, "Can't build data connection: "
1457ba7d19adSray 			    "unknown address family");
1458b1750805Sitojun 			(void) fclose(file);
1459b1750805Sitojun 			pdata = -1;
1460b1750805Sitojun 			return (NULL);
1461b1750805Sitojun 		}
1462353a5fa7Sderaadt 		if (data_dest.su_family != his_addr.su_family ||
1463353a5fa7Sderaadt 		    ntohs(*p) < IPPORT_RESERVED || ntohs(*p) == 2049) { /* XXX */
1464ba7d19adSray 			reply(425, "Can't build data connection: "
1465ba7d19adSray 			    "address family or port error");
146616a6ac13Sjoshd 			(void) fclose(file);
146716a6ac13Sjoshd 			return NULL;
146816a6ac13Sjoshd 		}
14697bb9870fSangelos 		if (portcheck && memcmp(fa, ha, alen) != 0) {
1470ba7d19adSray 			reply(435, "Can't build data connection: "
1471ba7d19adSray 			    "illegal port number");
147216a6ac13Sjoshd 			(void) fclose(file);
147316a6ac13Sjoshd 			return NULL;
147416a6ac13Sjoshd 		}
147599ca0666Smillert 
147699ca0666Smillert 		if (connect(fileno(file), (struct sockaddr *)&data_dest,
147799ca0666Smillert 		    data_dest.su_len) == 0) {
1478df930be7Sderaadt 			reply(150, "Opening %s mode data connection for '%s'%s.",
1479df930be7Sderaadt 			    type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf);
148099ca0666Smillert 			data = fileno(file);
1481df930be7Sderaadt 			return (file);
1482df930be7Sderaadt 		}
148399ca0666Smillert 		if (errno != EADDRINUSE)
148499ca0666Smillert 			break;
148599ca0666Smillert 		sleep((unsigned) swaitint);
148699ca0666Smillert 		retry += swaitint;
148799ca0666Smillert 	} while (retry <= swaitmax);
148899ca0666Smillert 	perror_reply(425, "Can't build data connection");
148999ca0666Smillert 	(void) fclose(file);
149099ca0666Smillert 	return (NULL);
149199ca0666Smillert }
1492df930be7Sderaadt 
1493df930be7Sderaadt /*
149484d3ec93Skrw  * Transfer the contents of "instr" to "outstr" peer using the appropriate
14953aba417aSdownsj  * encapsulation of the data subject to Mode, Structure, and Type.
1496df930be7Sderaadt  *
1497df930be7Sderaadt  * NB: Form isn't handled.
1498df930be7Sderaadt  */
14992378d5feSmillert static int
send_data(FILE * instr,FILE * outstr,off_t blksize,off_t filesize,int isreg)15006bcb9e83Sderaadt send_data(FILE *instr, FILE *outstr, off_t blksize, off_t filesize, int isreg)
1501df930be7Sderaadt {
1502df930be7Sderaadt 	int c, cnt, filefd, netfd;
15033aba417aSdownsj 	char *buf, *bp;
15043aba417aSdownsj 	size_t len;
1505df930be7Sderaadt 
1506df930be7Sderaadt 	transflag++;
1507df930be7Sderaadt 	switch (type) {
1508df930be7Sderaadt 
1509df930be7Sderaadt 	case TYPE_A:
1510df930be7Sderaadt 		while ((c = getc(instr)) != EOF) {
15112378d5feSmillert 			if (recvurg)
15122378d5feSmillert 				goto got_oob;
1513df930be7Sderaadt 			byte_count++;
1514df930be7Sderaadt 			if (c == '\n') {
1515df930be7Sderaadt 				if (ferror(outstr))
1516df930be7Sderaadt 					goto data_err;
1517df930be7Sderaadt 				(void) putc('\r', outstr);
1518df930be7Sderaadt 			}
1519df930be7Sderaadt 			(void) putc(c, outstr);
1520df930be7Sderaadt 		}
1521df930be7Sderaadt 		fflush(outstr);
1522df930be7Sderaadt 		transflag = 0;
1523df930be7Sderaadt 		if (ferror(instr))
1524df930be7Sderaadt 			goto file_err;
1525df930be7Sderaadt 		if (ferror(outstr))
1526df930be7Sderaadt 			goto data_err;
1527df930be7Sderaadt 		reply(226, "Transfer complete.");
15282378d5feSmillert 		return(0);
1529df930be7Sderaadt 
1530df930be7Sderaadt 	case TYPE_I:
1531df930be7Sderaadt 	case TYPE_L:
15323aba417aSdownsj 		/*
15333aba417aSdownsj 		 * isreg is only set if we are not doing restart and we
15343aba417aSdownsj 		 * are sending a regular file
15353aba417aSdownsj 		 */
15363aba417aSdownsj 		netfd = fileno(outstr);
15373aba417aSdownsj 		filefd = fileno(instr);
15383aba417aSdownsj 
1539a20a5177Sguenther 		if (isreg && filesize < 16 * 1024 * 1024) {
154095cc8c4aSderaadt 			size_t fsize = (size_t)filesize;
154195cc8c4aSderaadt 
1542aa8037beSjca 			if (fsize == 0) {
1543aa8037beSjca 				transflag = 0;
1544aa8037beSjca 				reply(226, "Transfer complete.");
1545aa8037beSjca 				return(0);
1546aa8037beSjca 			}
1547aa8037beSjca 
1548a20a5177Sguenther 			buf = mmap(0, fsize, PROT_READ, MAP_SHARED, filefd, 0);
1549aa0adb85Sart 			if (buf == MAP_FAILED) {
155095cc8c4aSderaadt 				syslog(LOG_WARNING, "mmap(%llu): %m",
155195cc8c4aSderaadt 				    (unsigned long long)fsize);
15523aba417aSdownsj 				goto oldway;
15533aba417aSdownsj 			}
15543aba417aSdownsj 			bp = buf;
155595cc8c4aSderaadt 			len = fsize;
15563aba417aSdownsj 			do {
15573aba417aSdownsj 				cnt = write(netfd, bp, len);
15582378d5feSmillert 				if (recvurg) {
155995cc8c4aSderaadt 					munmap(buf, fsize);
15602378d5feSmillert 					goto got_oob;
15612378d5feSmillert 				}
15623aba417aSdownsj 				len -= cnt;
15633aba417aSdownsj 				bp += cnt;
156420f6c32bSderaadt 				if (cnt > 0)
156520f6c32bSderaadt 					byte_count += cnt;
15663aba417aSdownsj 			} while(cnt > 0 && len > 0);
15673aba417aSdownsj 
15683aba417aSdownsj 			transflag = 0;
156995cc8c4aSderaadt 			munmap(buf, fsize);
15703aba417aSdownsj 			if (cnt < 0)
15713aba417aSdownsj 				goto data_err;
15723aba417aSdownsj 			reply(226, "Transfer complete.");
15732378d5feSmillert 			return(0);
15743aba417aSdownsj 		}
15753aba417aSdownsj 
15763aba417aSdownsj oldway:
157795cc8c4aSderaadt 		if ((buf = malloc((size_t)blksize)) == NULL) {
1578df930be7Sderaadt 			transflag = 0;
1579df930be7Sderaadt 			perror_reply(451, "Local resource failure: malloc");
15802378d5feSmillert 			return(-1);
1581df930be7Sderaadt 		}
15823aba417aSdownsj 
158395cc8c4aSderaadt 		while ((cnt = read(filefd, buf, (size_t)blksize)) > 0 &&
1584df930be7Sderaadt 		    write(netfd, buf, cnt) == cnt)
1585df930be7Sderaadt 			byte_count += cnt;
1586df930be7Sderaadt 		transflag = 0;
1587df930be7Sderaadt 		(void)free(buf);
1588df930be7Sderaadt 		if (cnt != 0) {
1589df69c215Sderaadt 			if (cnt == -1)
1590df930be7Sderaadt 				goto file_err;
1591df930be7Sderaadt 			goto data_err;
1592df930be7Sderaadt 		}
1593df930be7Sderaadt 		reply(226, "Transfer complete.");
15942378d5feSmillert 		return(0);
1595df930be7Sderaadt 	default:
1596df930be7Sderaadt 		transflag = 0;
1597df930be7Sderaadt 		reply(550, "Unimplemented TYPE %d in send_data", type);
15982378d5feSmillert 		return(-1);
1599df930be7Sderaadt 	}
1600df930be7Sderaadt 
1601df930be7Sderaadt data_err:
1602df930be7Sderaadt 	transflag = 0;
1603ba7d19adSray 	reply(426, "Data connection");
16042378d5feSmillert 	return(-1);
1605df930be7Sderaadt 
1606df930be7Sderaadt file_err:
1607df930be7Sderaadt 	transflag = 0;
1608ba7d19adSray 	reply(551, "Error on input file");
16092378d5feSmillert 	return(-1);
16102378d5feSmillert 
16112378d5feSmillert got_oob:
16122378d5feSmillert 	myoob();
16132378d5feSmillert 	recvurg = 0;
16142378d5feSmillert 	transflag = 0;
16152378d5feSmillert 	return(-1);
1616df930be7Sderaadt }
1617df930be7Sderaadt 
1618df930be7Sderaadt /*
1619df930be7Sderaadt  * Transfer data from peer to "outstr" using the appropriate encapulation of
1620df930be7Sderaadt  * the data subject to Mode, Structure, and Type.
1621df930be7Sderaadt  *
1622df930be7Sderaadt  * N.B.: Form isn't handled.
1623df930be7Sderaadt  */
1624df930be7Sderaadt static int
receive_data(FILE * instr,FILE * outstr)16256bcb9e83Sderaadt receive_data(FILE *instr, FILE *outstr)
1626df930be7Sderaadt {
1627df930be7Sderaadt 	int c;
1628a5553e71Sderaadt 	int cnt;
1629df930be7Sderaadt 	char buf[BUFSIZ];
16300fe9133cSdanh 	struct sigaction sa, sa_saved;
1631fdcdff86Smillert 	volatile int bare_lfs = 0;
1632df930be7Sderaadt 
1633df930be7Sderaadt 	transflag++;
1634df930be7Sderaadt 	switch (type) {
1635df930be7Sderaadt 
1636df930be7Sderaadt 	case TYPE_I:
1637df930be7Sderaadt 	case TYPE_L:
16380fe9133cSdanh 		memset(&sa, 0, sizeof(sa));
16390fe9133cSdanh 		sigfillset(&sa.sa_mask);
16400fe9133cSdanh 		sa.sa_flags = SA_RESTART;
16410fe9133cSdanh 		sa.sa_handler = lostconn;
16420fe9133cSdanh 		(void) sigaction(SIGALRM, &sa, &sa_saved);
164347dd8230Sdownsj 		do {
164447dd8230Sdownsj 			(void) alarm ((unsigned) timeout);
164547dd8230Sdownsj 			cnt = read(fileno(instr), buf, sizeof(buf));
164647dd8230Sdownsj 			(void) alarm (0);
16472378d5feSmillert 			if (recvurg)
16482378d5feSmillert 				goto got_oob;
164947dd8230Sdownsj 
165047dd8230Sdownsj 			if (cnt > 0) {
1651df930be7Sderaadt 				if (write(fileno(outstr), buf, cnt) != cnt)
1652df930be7Sderaadt 					goto file_err;
1653df930be7Sderaadt 				byte_count += cnt;
1654df930be7Sderaadt 			}
165547dd8230Sdownsj 		} while (cnt > 0);
16560fe9133cSdanh 		(void) sigaction(SIGALRM, &sa_saved, NULL);
1657df69c215Sderaadt 		if (cnt == -1)
1658df930be7Sderaadt 			goto data_err;
1659df930be7Sderaadt 		transflag = 0;
1660df930be7Sderaadt 		return (0);
1661df930be7Sderaadt 
1662df930be7Sderaadt 	case TYPE_E:
1663df930be7Sderaadt 		reply(553, "TYPE E not implemented.");
1664df930be7Sderaadt 		transflag = 0;
1665df930be7Sderaadt 		return (-1);
1666df930be7Sderaadt 
1667df930be7Sderaadt 	case TYPE_A:
1668df930be7Sderaadt 		while ((c = getc(instr)) != EOF) {
16692378d5feSmillert 			if (recvurg)
16702378d5feSmillert 				goto got_oob;
1671df930be7Sderaadt 			byte_count++;
1672df930be7Sderaadt 			if (c == '\n')
1673df930be7Sderaadt 				bare_lfs++;
1674df930be7Sderaadt 			while (c == '\r') {
1675df930be7Sderaadt 				if (ferror(outstr))
1676df930be7Sderaadt 					goto data_err;
1677df930be7Sderaadt 				if ((c = getc(instr)) != '\n') {
1678df930be7Sderaadt 					(void) putc ('\r', outstr);
1679df930be7Sderaadt 					if (c == '\0' || c == EOF)
1680df930be7Sderaadt 						goto contin2;
1681df930be7Sderaadt 				}
1682df930be7Sderaadt 			}
1683df930be7Sderaadt 			(void) putc(c, outstr);
1684df930be7Sderaadt 	contin2:	;
1685df930be7Sderaadt 		}
1686df930be7Sderaadt 		fflush(outstr);
1687df930be7Sderaadt 		if (ferror(instr))
1688df930be7Sderaadt 			goto data_err;
1689df930be7Sderaadt 		if (ferror(outstr))
1690df930be7Sderaadt 			goto file_err;
1691df930be7Sderaadt 		transflag = 0;
1692df930be7Sderaadt 		if (bare_lfs) {
1693df930be7Sderaadt 			lreply(226,
1694df930be7Sderaadt 			    "WARNING! %d bare linefeeds received in ASCII mode",
1695df930be7Sderaadt 			    bare_lfs);
1696353a5fa7Sderaadt 			printf("   File may not have transferred correctly.\r\n");
1697df930be7Sderaadt 		}
1698df930be7Sderaadt 		return (0);
1699df930be7Sderaadt 	default:
1700df930be7Sderaadt 		reply(550, "Unimplemented TYPE %d in receive_data", type);
1701df930be7Sderaadt 		transflag = 0;
1702df930be7Sderaadt 		return (-1);
1703df930be7Sderaadt 	}
1704df930be7Sderaadt 
1705df930be7Sderaadt data_err:
1706df930be7Sderaadt 	transflag = 0;
1707ba7d19adSray 	reply(426, "Data Connection");
1708df930be7Sderaadt 	return (-1);
1709df930be7Sderaadt 
1710df930be7Sderaadt file_err:
1711df930be7Sderaadt 	transflag = 0;
1712ba7d19adSray 	reply(452, "Error writing file");
1713df930be7Sderaadt 	return (-1);
17142378d5feSmillert 
17152378d5feSmillert got_oob:
17162378d5feSmillert 	myoob();
17172378d5feSmillert 	recvurg = 0;
17182378d5feSmillert 	transflag = 0;
17192378d5feSmillert 	return (-1);
1720df930be7Sderaadt }
1721df930be7Sderaadt 
1722df930be7Sderaadt void
statfilecmd(const char * filename)17230c50dd5dSjan statfilecmd(const char *filename)
1724df930be7Sderaadt {
1725df930be7Sderaadt 	FILE *fin;
1726df930be7Sderaadt 	int c;
17277effe281Sbitblt 	int atstart;
17281b81cd4fStedu 	pid_t pid;
1729bca58876Sjan 	fin = ftpd_ls(filename, &pid);
173083ddc0d4Stobias 	if (fin == NULL) {
173183ddc0d4Stobias 		reply(451, "Local resource failure");
173283ddc0d4Stobias 		return;
173383ddc0d4Stobias 	}
1734df930be7Sderaadt 	lreply(211, "status of %s:", filename);
17357effe281Sbitblt 	atstart = 1;
1736df930be7Sderaadt 	while ((c = getc(fin)) != EOF) {
1737df930be7Sderaadt 		if (c == '\n') {
1738df930be7Sderaadt 			if (ferror(stdout)){
1739df930be7Sderaadt 				perror_reply(421, "control connection");
17401b81cd4fStedu 				(void) ftpd_pclose(fin, pid);
1741df930be7Sderaadt 				dologout(1);
1742df930be7Sderaadt 				/* NOTREACHED */
1743df930be7Sderaadt 			}
1744df930be7Sderaadt 			if (ferror(fin)) {
1745df930be7Sderaadt 				perror_reply(551, filename);
17461b81cd4fStedu 				(void) ftpd_pclose(fin, pid);
1747df930be7Sderaadt 				return;
1748df930be7Sderaadt 			}
1749df930be7Sderaadt 			(void) putc('\r', stdout);
1750df930be7Sderaadt 		}
17517effe281Sbitblt 		if (atstart && isdigit(c))
17527effe281Sbitblt 			(void) putc(' ', stdout);
1753df930be7Sderaadt 		(void) putc(c, stdout);
17547effe281Sbitblt 		atstart = (c == '\n');
1755df930be7Sderaadt 	}
17561b81cd4fStedu 	(void) ftpd_pclose(fin, pid);
1757df930be7Sderaadt 	reply(211, "End of Status");
1758df930be7Sderaadt }
1759df930be7Sderaadt 
1760df930be7Sderaadt void
statcmd(void)17616bcb9e83Sderaadt statcmd(void)
1762df930be7Sderaadt {
1763b1750805Sitojun 	union sockunion *su;
1764df930be7Sderaadt 	u_char *a, *p;
1765b9fc9a72Sderaadt 	char hbuf[HOST_NAME_MAX+1];
1766b1750805Sitojun 	int ispassive;
17671303e027Sniallo 	int error;
1768df930be7Sderaadt 
176974200a40Sderaadt 	lreply(211, "%s FTP server status:", hostname);
17701303e027Sniallo 	error = getnameinfo((struct sockaddr *)&his_addr, his_addr.su_len,
1771b1750805Sitojun 	    hbuf, sizeof(hbuf), NULL, 0, NI_NUMERICHOST);
1772df930be7Sderaadt 	printf("     Connected to %s", remotehost);
17731303e027Sniallo 	if (error == 0 && strcmp(remotehost, hbuf) != 0)
1774b1750805Sitojun 		printf(" (%s)", hbuf);
1775df930be7Sderaadt 	printf("\r\n");
1776df930be7Sderaadt 	if (logged_in) {
1777df930be7Sderaadt 		if (guest)
1778df930be7Sderaadt 			printf("     Logged in anonymously\r\n");
1779df930be7Sderaadt 		else
1780df930be7Sderaadt 			printf("     Logged in as %s\r\n", pw->pw_name);
1781df930be7Sderaadt 	} else if (askpasswd)
1782df930be7Sderaadt 		printf("     Waiting for password\r\n");
1783df930be7Sderaadt 	else
1784df930be7Sderaadt 		printf("     Waiting for user name\r\n");
1785df930be7Sderaadt 	printf("     TYPE: %s", typenames[type]);
1786df930be7Sderaadt 	if (type == TYPE_A || type == TYPE_E)
1787df930be7Sderaadt 		printf(", FORM: %s", formnames[form]);
1788df930be7Sderaadt 	if (type == TYPE_L)
178908cb63c2Stedu 		printf(" 8");
1790df930be7Sderaadt 	printf("; STRUcture: %s; transfer MODE: %s\r\n",
1791df930be7Sderaadt 	    strunames[stru], modenames[mode]);
1792b1750805Sitojun 	ispassive = 0;
1793df930be7Sderaadt 	if (data != -1)
1794df930be7Sderaadt 		printf("     Data connection open\r\n");
1795df930be7Sderaadt 	else if (pdata != -1) {
1796a5952230Sitojun 		printf("     in Passive mode\r\n");
1797b1750805Sitojun 		su = (union sockunion *)&pasv_addr;
1798b1750805Sitojun 		ispassive++;
1799df930be7Sderaadt 		goto printaddr;
1800df930be7Sderaadt 	} else if (usedefault == 0) {
180183b130e1Sbluhm 		size_t alen, i;
180283b130e1Sbluhm 		int af;
1803b3a172ddSderaadt 
1804b1750805Sitojun 		su = (union sockunion *)&data_dest;
1805df930be7Sderaadt printaddr:
1806b1750805Sitojun 		/* PASV/PORT */
1807b1750805Sitojun 		if (su->su_family == AF_INET) {
1808b1750805Sitojun 			if (ispassive)
1809b1750805Sitojun 				printf("211- PASV ");
1810b1750805Sitojun 			else
1811b1750805Sitojun 				printf("211- PORT ");
1812b1750805Sitojun 			a = (u_char *)&su->su_sin.sin_addr;
1813b1750805Sitojun 			p = (u_char *)&su->su_sin.sin_port;
1814353a5fa7Sderaadt 			printf("(%u,%u,%u,%u,%u,%u)\r\n",
1815353a5fa7Sderaadt 			    a[0], a[1], a[2], a[3],
1816353a5fa7Sderaadt 			    p[0], p[1]);
1817b1750805Sitojun 		}
1818b1750805Sitojun 
1819b1750805Sitojun 		/* LPSV/LPRT */
1820b1750805Sitojun 		alen = 0;
1821b1750805Sitojun 		switch (su->su_family) {
1822b1750805Sitojun 		case AF_INET:
1823b1750805Sitojun 			a = (u_char *)&su->su_sin.sin_addr;
1824b1750805Sitojun 			p = (u_char *)&su->su_sin.sin_port;
1825b1750805Sitojun 			alen = sizeof(su->su_sin.sin_addr);
1826b1750805Sitojun 			af = 4;
1827b1750805Sitojun 			break;
1828b1750805Sitojun 		case AF_INET6:
1829b1750805Sitojun 			a = (u_char *)&su->su_sin6.sin6_addr;
1830b1750805Sitojun 			p = (u_char *)&su->su_sin6.sin6_port;
1831b1750805Sitojun 			alen = sizeof(su->su_sin6.sin6_addr);
1832b1750805Sitojun 			af = 6;
1833b1750805Sitojun 			break;
1834b1750805Sitojun 		default:
1835b1750805Sitojun 			af = 0;
1836b1750805Sitojun 			break;
1837b1750805Sitojun 		}
1838b1750805Sitojun 		if (af) {
1839b1750805Sitojun 			if (ispassive)
1840b1750805Sitojun 				printf("211- LPSV ");
1841b1750805Sitojun 			else
1842b1750805Sitojun 				printf("211- LPRT ");
184395cc8c4aSderaadt 			printf("(%u,%llu", af, (unsigned long long)alen);
1844b1750805Sitojun 			for (i = 0; i < alen; i++)
1845a5952230Sitojun 				printf(",%u", a[i]);
1846a5952230Sitojun 			printf(",%u,%u,%u)\r\n", 2, p[0], p[1]);
1847b1750805Sitojun 		}
1848b1750805Sitojun 
1849b1750805Sitojun 		/* EPRT/EPSV */
1850b1750805Sitojun 		switch (su->su_family) {
1851b1750805Sitojun 		case AF_INET:
1852b1750805Sitojun 			af = 1;
1853b1750805Sitojun 			break;
1854b1750805Sitojun 		case AF_INET6:
1855b1750805Sitojun 			af = 2;
1856b1750805Sitojun 			break;
1857b1750805Sitojun 		default:
1858b1750805Sitojun 			af = 0;
1859b1750805Sitojun 			break;
1860b1750805Sitojun 		}
1861b1750805Sitojun 		if (af) {
18627a2320c7Spvalchev 			char pbuf[10];
18632c9f1fc1Sitojun 			union sockunion tmp = *su;
18642c9f1fc1Sitojun 
18652c9f1fc1Sitojun 			if (tmp.su_family == AF_INET6)
18662c9f1fc1Sitojun 				tmp.su_sin6.sin6_scope_id = 0;
18672c9f1fc1Sitojun 			if (getnameinfo((struct sockaddr *)&tmp, tmp.su_len,
1868b1750805Sitojun 			    hbuf, sizeof(hbuf), pbuf, sizeof(pbuf),
18692c9f1fc1Sitojun 			    NI_NUMERICHOST | NI_NUMERICSERV) == 0) {
1870b1750805Sitojun 				if (ispassive)
1871b1750805Sitojun 					printf("211- EPSV ");
1872b1750805Sitojun 				else
1873b1750805Sitojun 					printf("211- EPRT ");
1874353a5fa7Sderaadt 				printf("(|%u|%s|%s|)\r\n",
1875b1750805Sitojun 				    af, hbuf, pbuf);
1876b1750805Sitojun 			}
1877b1750805Sitojun 		}
1878df930be7Sderaadt 	} else
1879df930be7Sderaadt 		printf("     No data connection\r\n");
1880df930be7Sderaadt 	reply(211, "End of status");
1881df930be7Sderaadt }
1882df930be7Sderaadt 
1883df930be7Sderaadt void
fatal(const char * s)18840c50dd5dSjan fatal(const char *s)
1885df930be7Sderaadt {
1886df930be7Sderaadt 
1887c418ff08Smpech 	reply(451, "Error in server: %s", s);
1888df930be7Sderaadt 	reply(221, "Closing connection due to server error.");
1889df930be7Sderaadt 	dologout(0);
1890df930be7Sderaadt 	/* NOTREACHED */
1891df930be7Sderaadt }
1892df930be7Sderaadt 
1893df930be7Sderaadt void
reply(int n,const char * fmt,...)1894df930be7Sderaadt reply(int n, const char *fmt, ...)
1895df930be7Sderaadt {
18963482ad5dSderaadt 	char *buf, *p, *next;
18973482ad5dSderaadt 	int rval;
1898df930be7Sderaadt 	va_list ap;
1899e7beb4a7Smillert 
1900df930be7Sderaadt 	va_start(ap, fmt);
19013482ad5dSderaadt 	rval = vasprintf(&buf, fmt, ap);
1902b4434528Smillert 	va_end(ap);
19033482ad5dSderaadt 	if (rval == -1 || buf == NULL) {
1904ea0284a0Stobias 		printf("421 Local resource failure: malloc\r\n");
19053482ad5dSderaadt 		fflush(stdout);
19063482ad5dSderaadt 		dologout(1);
19073482ad5dSderaadt 	}
19083482ad5dSderaadt 	next = buf;
190941db49dbSmillert 	while ((p = strsep(&next, "\n\r"))) {
1910521b4f7dSjsg 		printf("%d%s %s\r\n", n, (next != NULL) ? "-" : "", p);
19113482ad5dSderaadt 		if (debug)
19123482ad5dSderaadt 			syslog(LOG_DEBUG, "<--- %d%s %s", n,
1913521b4f7dSjsg 			    (next != NULL) ? "-" : "", p);
191441db49dbSmillert 	}
19153482ad5dSderaadt 	(void)fflush(stdout);
19163482ad5dSderaadt 	free(buf);
1917df930be7Sderaadt }
1918df930be7Sderaadt 
19198b6f1defSderaadt 
19208b6f1defSderaadt void
reply_r(int n,const char * fmt,...)19218b6f1defSderaadt reply_r(int n, const char *fmt, ...)
19228b6f1defSderaadt {
19238b6f1defSderaadt 	char *p, *next;
19248b6f1defSderaadt 	char msg[BUFSIZ];
19258b6f1defSderaadt 	char buf[BUFSIZ];
19268b6f1defSderaadt 	va_list ap;
19278b6f1defSderaadt 	struct syslog_data sdata = SYSLOG_DATA_INIT;
19288b6f1defSderaadt 
192976d1ef43Sotto 	sdata.log_fac = LOG_FTP;
19308b6f1defSderaadt 	va_start(ap, fmt);
19318b6f1defSderaadt 	vsnprintf(msg, sizeof(msg), fmt, ap);
19328b6f1defSderaadt 	va_end(ap);
19338b6f1defSderaadt 
19348b6f1defSderaadt 	next = msg;
19358b6f1defSderaadt 
19368b6f1defSderaadt 	while ((p = strsep(&next, "\n\r"))) {
19378b6f1defSderaadt 		snprintf(buf, sizeof(buf), "%d%s %s\r\n", n,
1938521b4f7dSjsg 		    (next != NULL) ? "-" : "", p);
19398b6f1defSderaadt 		write(STDOUT_FILENO, buf, strlen(buf));
19408b6f1defSderaadt 		if (debug) {
19418b6f1defSderaadt 			buf[strlen(buf) - 2] = '\0';
19428b6f1defSderaadt 			syslog_r(LOG_DEBUG, &sdata, "<--- %s", buf);
19438b6f1defSderaadt 		}
19448b6f1defSderaadt 	}
19458b6f1defSderaadt }
19468b6f1defSderaadt 
1947df930be7Sderaadt void
lreply(int n,const char * fmt,...)1948df930be7Sderaadt lreply(int n, const char *fmt, ...)
1949df930be7Sderaadt {
1950df930be7Sderaadt 	va_list ap;
1951e7beb4a7Smillert 
1952df930be7Sderaadt 	va_start(ap, fmt);
1953df930be7Sderaadt 	(void)printf("%d- ", n);
1954df930be7Sderaadt 	(void)vprintf(fmt, ap);
19550ddacdb8Smarkus 	va_end(ap);
1956df930be7Sderaadt 	(void)printf("\r\n");
1957df930be7Sderaadt 	(void)fflush(stdout);
1958df930be7Sderaadt 	if (debug) {
19590ddacdb8Smarkus 		va_start(ap, fmt);
1960df930be7Sderaadt 		syslog(LOG_DEBUG, "<--- %d- ", n);
1961df930be7Sderaadt 		vsyslog(LOG_DEBUG, fmt, ap);
19620ddacdb8Smarkus 		va_end(ap);
1963df930be7Sderaadt 	}
1964df930be7Sderaadt }
1965df930be7Sderaadt 
1966df930be7Sderaadt static void
ack(const char * s)19670c50dd5dSjan ack(const char *s)
1968df930be7Sderaadt {
1969df930be7Sderaadt 
1970df930be7Sderaadt 	reply(250, "%s command successful.", s);
1971df930be7Sderaadt }
1972df930be7Sderaadt 
1973df930be7Sderaadt void
nack(const char * s)19740c50dd5dSjan nack(const char *s)
1975df930be7Sderaadt {
1976df930be7Sderaadt 
1977df930be7Sderaadt 	reply(502, "%s command not implemented.", s);
1978df930be7Sderaadt }
1979df930be7Sderaadt 
1980df930be7Sderaadt void
yyerror(char * s)19816bcb9e83Sderaadt yyerror(char *s)
1982df930be7Sderaadt {
198361fc5ed6Sray 	cbuf[strcspn(cbuf, "\n")] = '\0';
19846e237da7Sderaadt 	reply(500, "'%s': command not understood.", cbuf);
1985df930be7Sderaadt }
1986df930be7Sderaadt 
1987df930be7Sderaadt void
delete(const char * name)19880c50dd5dSjan delete(const char *name)
1989df930be7Sderaadt {
1990df930be7Sderaadt 	struct stat st;
1991df930be7Sderaadt 
1992df930be7Sderaadt 	LOGCMD("delete", name);
1993df69c215Sderaadt 	if (stat(name, &st) == -1) {
1994df930be7Sderaadt 		perror_reply(550, name);
1995df930be7Sderaadt 		return;
1996df930be7Sderaadt 	}
1997df930be7Sderaadt 	if ((st.st_mode&S_IFMT) == S_IFDIR) {
1998df69c215Sderaadt 		if (rmdir(name) == -1) {
1999df930be7Sderaadt 			perror_reply(550, name);
2000df930be7Sderaadt 			return;
2001df930be7Sderaadt 		}
2002df930be7Sderaadt 		goto done;
2003df930be7Sderaadt 	}
2004df69c215Sderaadt 	if (unlink(name) == -1) {
2005df930be7Sderaadt 		perror_reply(550, name);
2006df930be7Sderaadt 		return;
2007df930be7Sderaadt 	}
2008df930be7Sderaadt done:
2009df930be7Sderaadt 	ack("DELE");
2010df930be7Sderaadt }
2011df930be7Sderaadt 
2012df930be7Sderaadt void
cwd(char * path)20136bcb9e83Sderaadt cwd(char *path)
2014df930be7Sderaadt {
2015d64f699bSmillert 	FILE *message;
2016df930be7Sderaadt 
2017df69c215Sderaadt 	if (chdir(path) == -1)
2018df930be7Sderaadt 		perror_reply(550, path);
2019d64f699bSmillert 	else {
2020d64f699bSmillert 		if ((message = fopen(_PATH_CWDMESG, "r")) != NULL) {
202161fc5ed6Sray 			char line[LINE_MAX];
2022d64f699bSmillert 
2023d64f699bSmillert 			while (fgets(line, sizeof(line), message) != NULL) {
202461fc5ed6Sray 				line[strcspn(line, "\n")] = '\0';
2025d64f699bSmillert 				lreply(250, "%s", line);
2026d64f699bSmillert 			}
2027d64f699bSmillert 			(void) fclose(message);
2028d64f699bSmillert 		}
2029df930be7Sderaadt 		ack("CWD");
2030df930be7Sderaadt 	}
2031d64f699bSmillert }
2032df930be7Sderaadt 
2033df930be7Sderaadt void
replydirname(const char * name,const char * message)20346bcb9e83Sderaadt replydirname(const char *name, const char *message)
20353702461fSderaadt {
203636a9ab6cSitojun 	char *p, *ep;
2037b9fc9a72Sderaadt 	char npath[PATH_MAX * 2];
20383702461fSderaadt 
203936a9ab6cSitojun 	p = npath;
204036a9ab6cSitojun 	ep = &npath[sizeof(npath) - 1];
204136a9ab6cSitojun 	while (*name) {
204214af2cdcSitojun 		if (*name == '"') {
204314af2cdcSitojun 			if (ep - p < 2)
204414af2cdcSitojun 				break;
204536a9ab6cSitojun 			*p++ = *name++;
204636a9ab6cSitojun 			*p++ = '"';
204714af2cdcSitojun 		} else {
204814af2cdcSitojun 			if (ep - p < 1)
204936a9ab6cSitojun 				break;
205014af2cdcSitojun 			*p++ = *name++;
205114af2cdcSitojun 		}
20523702461fSderaadt 	}
205336a9ab6cSitojun 	*p = '\0';
20543702461fSderaadt 	reply(257, "\"%s\" %s", npath, message);
20553702461fSderaadt }
20563702461fSderaadt 
20573702461fSderaadt void
makedir(const char * name)20580c50dd5dSjan makedir(const char *name)
2059df930be7Sderaadt {
2060df930be7Sderaadt 
2061df930be7Sderaadt 	LOGCMD("mkdir", name);
2062df69c215Sderaadt 	if (mkdir(name, 0777) == -1)
2063df930be7Sderaadt 		perror_reply(550, name);
2064df930be7Sderaadt 	else
20653702461fSderaadt 		replydirname(name, "directory created.");
2066df930be7Sderaadt }
2067df930be7Sderaadt 
2068df930be7Sderaadt void
removedir(const char * name)20690c50dd5dSjan removedir(const char *name)
2070df930be7Sderaadt {
2071df930be7Sderaadt 
2072df930be7Sderaadt 	LOGCMD("rmdir", name);
2073df69c215Sderaadt 	if (rmdir(name) == -1)
2074df930be7Sderaadt 		perror_reply(550, name);
2075df930be7Sderaadt 	else
2076df930be7Sderaadt 		ack("RMD");
2077df930be7Sderaadt }
2078df930be7Sderaadt 
2079df930be7Sderaadt void
pwd(void)20806bcb9e83Sderaadt pwd(void)
2081df930be7Sderaadt {
2082b9fc9a72Sderaadt 	char path[PATH_MAX];
2083df930be7Sderaadt 
208441db49dbSmillert 	if (getcwd(path, sizeof(path)) == NULL)
20854ee88263Smpech 		perror_reply(550, "Can't get current directory");
2086df930be7Sderaadt 	else
20873702461fSderaadt 		replydirname(path, "is current directory.");
2088df930be7Sderaadt }
2089df930be7Sderaadt 
2090df930be7Sderaadt char *
renamefrom(char * name)20916bcb9e83Sderaadt renamefrom(char *name)
2092df930be7Sderaadt {
2093df930be7Sderaadt 	struct stat st;
2094df930be7Sderaadt 
2095df69c215Sderaadt 	if (stat(name, &st) == -1) {
2096df930be7Sderaadt 		perror_reply(550, name);
2097c9899b11Skrw 		return (NULL);
2098df930be7Sderaadt 	}
2099df930be7Sderaadt 	reply(350, "File exists, ready for destination name");
2100df930be7Sderaadt 	return (name);
2101df930be7Sderaadt }
2102df930be7Sderaadt 
2103df930be7Sderaadt void
renamecmd(const char * from,const char * to)21040c50dd5dSjan renamecmd(const char *from, const char *to)
2105df930be7Sderaadt {
2106df930be7Sderaadt 
2107df930be7Sderaadt 	LOGCMD2("rename", from, to);
2108df69c215Sderaadt 	if (rename(from, to) == -1)
2109df930be7Sderaadt 		perror_reply(550, "rename");
2110df930be7Sderaadt 	else
2111df930be7Sderaadt 		ack("RNTO");
2112df930be7Sderaadt }
2113df930be7Sderaadt 
2114df930be7Sderaadt static void
dolog(struct sockaddr * sa)21156bcb9e83Sderaadt dolog(struct sockaddr *sa)
2116df930be7Sderaadt {
2117b1750805Sitojun 	char hbuf[sizeof(remotehost)];
2118df930be7Sderaadt 
21191303e027Sniallo 	if (getnameinfo(sa, sa->sa_len, hbuf, sizeof(hbuf), NULL, 0, 0) == 0)
21201d00cca4Spjanzen 		(void) strlcpy(remotehost, hbuf, sizeof(remotehost));
21211303e027Sniallo 	else
21221303e027Sniallo 		(void) strlcpy(remotehost, "unknown", sizeof(remotehost));
2123b1750805Sitojun 
2124df930be7Sderaadt 	snprintf(proctitle, sizeof(proctitle), "%s: connected", remotehost);
212557ba4edcSderaadt 	setproctitle("%s", proctitle);
2126df930be7Sderaadt 
212798a3d023Sschwarze 	if (logging) {
212898a3d023Sschwarze 		int error;
212998a3d023Sschwarze 		error = getnameinfo(sa, sa->sa_len, hbuf, sizeof(hbuf),
213098a3d023Sschwarze 		    NULL, 0, NI_NUMERICHOST);
213198a3d023Sschwarze 		syslog(LOG_INFO, "connection from %s [%s]", remotehost,
213298a3d023Sschwarze 		    error ? gai_strerror(error) : hbuf);
213398a3d023Sschwarze 	}
2134df930be7Sderaadt }
2135df930be7Sderaadt 
2136df930be7Sderaadt /*
21372378d5feSmillert  * Record logout in wtmp file and exit with supplied status.
21382378d5feSmillert  * NOTE: because this is called from signal handlers it cannot
21392378d5feSmillert  *       use stdio (or call other functions that use stdio).
2140df930be7Sderaadt  */
2141df930be7Sderaadt void
dologout(int status)21426bcb9e83Sderaadt dologout(int status)
2143df930be7Sderaadt {
21445b333653Sbitblt 
21456214adcaSderaadt 	transflag = 0;
2146df930be7Sderaadt 
2147df930be7Sderaadt 	if (logged_in) {
21485b333653Sbitblt 		sigprocmask(SIG_BLOCK, &allsigs, NULL);
214905312c4aSajacoutot 		if (!nowtmp)
21501d5555bfSderaadt 			ftpdlogwtmp(ttyline, "", "");
2151eb00e388Sdownsj 		if (doutmp)
2152b12aa87cSderaadt 			ftpd_logout(utmp.ut_line);
2153df930be7Sderaadt 	}
2154df930be7Sderaadt 	/* beware of flushing buffers after a SIGPIPE */
2155df930be7Sderaadt 	_exit(status);
2156df930be7Sderaadt }
2157df930be7Sderaadt 
2158df930be7Sderaadt static void
sigurg(int signo)21596bcb9e83Sderaadt sigurg(int signo)
2160df930be7Sderaadt {
2161df930be7Sderaadt 
21622378d5feSmillert 	recvurg = 1;
21632378d5feSmillert }
21642378d5feSmillert 
21652378d5feSmillert static void
myoob(void)21666bcb9e83Sderaadt myoob(void)
21672378d5feSmillert {
21682378d5feSmillert 	char *cp;
21690391227fSmoritz 	int ret;
2170abe43e41Sderaadt 
2171df930be7Sderaadt 	/* only process if transfer occurring */
2172df930be7Sderaadt 	if (!transflag)
2173df930be7Sderaadt 		return;
2174df930be7Sderaadt 	cp = tmpline;
2175330fc432Sjan 	ret = get_line(cp, sizeof(tmpline)-1);
21760391227fSmoritz 	if (ret == -1) {
2177df930be7Sderaadt 		reply(221, "You could at least say goodbye.");
2178df930be7Sderaadt 		dologout(0);
21790391227fSmoritz 	} else if (ret == -2) {
21800391227fSmoritz 		/* Ignore truncated command */
21810391227fSmoritz 		return;
2182df930be7Sderaadt 	}
2183df930be7Sderaadt 	upper(cp);
2184df930be7Sderaadt 	if (strcmp(cp, "ABOR\r\n") == 0) {
2185df930be7Sderaadt 		tmpline[0] = '\0';
2186df930be7Sderaadt 		reply(426, "Transfer aborted. Data connection closed.");
2187df930be7Sderaadt 		reply(226, "Abort successful");
2188df930be7Sderaadt 	}
2189df930be7Sderaadt 	if (strcmp(cp, "STAT\r\n") == 0) {
21902f263669Sderaadt 		tmpline[0] = '\0';
2191a20a5177Sguenther 		if (file_size != -1)
2192a20a5177Sguenther 			reply(213, "Status: %lld of %lld bytes transferred",
2193a20a5177Sguenther 			    (long long)byte_count, (long long)file_size);
2194df930be7Sderaadt 		else
2195a20a5177Sguenther 			reply(213, "Status: %lld bytes transferred",
2196a20a5177Sguenther 			    (long long)byte_count);
2197df930be7Sderaadt 	}
2198df930be7Sderaadt }
2199df930be7Sderaadt 
2200df930be7Sderaadt /*
2201df930be7Sderaadt  * Note: a response of 425 is not mentioned as a possible response to
2202df930be7Sderaadt  *	the PASV command in RFC959. However, it has been blessed as
2203df930be7Sderaadt  *	a legitimate response by Jon Postel in a telephone conversation
2204df930be7Sderaadt  *	with Rick Adams on 25 Jan 89.
2205df930be7Sderaadt  */
2206df930be7Sderaadt void
passive(void)22076bcb9e83Sderaadt passive(void)
2208df930be7Sderaadt {
22090fe9133cSdanh 	socklen_t len;
221018ff6e9dSmillert 	int on = 1;
2211353a5fa7Sderaadt 	u_char *p, *a;
2212df930be7Sderaadt 
2213878f3b5aSdownsj 	if (pw == NULL) {
2214878f3b5aSdownsj 		reply(530, "Please login with USER and PASS");
2215878f3b5aSdownsj 		return;
2216878f3b5aSdownsj 	}
2217555303e5Sderaadt 	if (pdata >= 0)
2218555303e5Sderaadt 		close(pdata);
2219353a5fa7Sderaadt 	/*
2220353a5fa7Sderaadt 	 * XXX
2221353a5fa7Sderaadt 	 * At this point, it would be nice to have an algorithm that
2222353a5fa7Sderaadt 	 * inserted a growing delay in an attack scenario.  Such a thing
2223353a5fa7Sderaadt 	 * would look like continual passive sockets being opened, but
2224353a5fa7Sderaadt 	 * nothing serious being done with them.  They're not used to
2225353a5fa7Sderaadt 	 * move data; the entire attempt is just to use tcp FIN_WAIT
2226353a5fa7Sderaadt 	 * resources.
2227353a5fa7Sderaadt 	 */
2228df930be7Sderaadt 	pdata = socket(AF_INET, SOCK_STREAM, 0);
2229df69c215Sderaadt 	if (pdata == -1) {
2230df930be7Sderaadt 		perror_reply(425, "Can't open passive connection");
2231df930be7Sderaadt 		return;
2232df930be7Sderaadt 	}
2233878f3b5aSdownsj 
223418ff6e9dSmillert 	if (setsockopt(pdata, SOL_SOCKET, SO_KEEPALIVE,
2235df69c215Sderaadt 	    &on, sizeof(on)) == -1)
223618ff6e9dSmillert 		goto pasv_error;
223718ff6e9dSmillert 
22388ea584b2Sjakob 	on = IP_PORTRANGE_HIGH;
2239878f3b5aSdownsj 	if (setsockopt(pdata, IPPROTO_IP, IP_PORTRANGE,
2240df69c215Sderaadt 	    &on, sizeof(on)) == -1)
2241878f3b5aSdownsj 		goto pasv_error;
2242878f3b5aSdownsj 
2243df930be7Sderaadt 	pasv_addr = ctrl_addr;
2244b1750805Sitojun 	pasv_addr.su_sin.sin_port = 0;
2245878f3b5aSdownsj 	if (bind(pdata, (struct sockaddr *)&pasv_addr,
2246df69c215Sderaadt 	    pasv_addr.su_len) == -1)
2247df930be7Sderaadt 		goto pasv_error;
2248878f3b5aSdownsj 
2249df930be7Sderaadt 	len = sizeof(pasv_addr);
2250df69c215Sderaadt 	if (getsockname(pdata, (struct sockaddr *)&pasv_addr, &len) == -1)
2251df930be7Sderaadt 		goto pasv_error;
2252df69c215Sderaadt 	if (listen(pdata, 1) == -1)
2253df930be7Sderaadt 		goto pasv_error;
2254353a5fa7Sderaadt 	a = (u_char *)&pasv_addr.su_sin.sin_addr;
2255353a5fa7Sderaadt 	p = (u_char *)&pasv_addr.su_sin.sin_port;
2256df930be7Sderaadt 
2257353a5fa7Sderaadt 	reply(227, "Entering Passive Mode (%u,%u,%u,%u,%u,%u)", a[0],
2258353a5fa7Sderaadt 	    a[1], a[2], a[3], p[0], p[1]);
2259df930be7Sderaadt 	return;
2260df930be7Sderaadt 
2261df930be7Sderaadt pasv_error:
2262ba7d19adSray 	perror_reply(425, "Can't open passive connection");
2263b1750805Sitojun 	(void) close(pdata);
2264b1750805Sitojun 	pdata = -1;
2265b1750805Sitojun 	return;
2266b1750805Sitojun }
2267b1750805Sitojun 
2268d94808dfSitojun int
epsvproto2af(int proto)2269d94808dfSitojun epsvproto2af(int proto)
2270d94808dfSitojun {
2271d94808dfSitojun 
2272d94808dfSitojun 	switch (proto) {
2273d94808dfSitojun 	case 1:	return AF_INET;
2274d94808dfSitojun 	case 2:	return AF_INET6;
2275d94808dfSitojun 	default: return -1;
2276d94808dfSitojun 	}
2277d94808dfSitojun }
2278d94808dfSitojun 
2279d94808dfSitojun int
af2epsvproto(int af)2280d94808dfSitojun af2epsvproto(int af)
2281d94808dfSitojun {
2282d94808dfSitojun 
2283d94808dfSitojun 	switch (af) {
2284d94808dfSitojun 	case AF_INET:	return 1;
2285d94808dfSitojun 	case AF_INET6:	return 2;
2286d94808dfSitojun 	default:	return -1;
2287d94808dfSitojun 	}
2288d94808dfSitojun }
2289d94808dfSitojun 
2290d94808dfSitojun /*
2291b1750805Sitojun  * 228 Entering Long Passive Mode (af, hal, h1, h2, h3,..., pal, p1, p2...)
2292b1750805Sitojun  * 229 Entering Extended Passive Mode (|||port|)
2293b1750805Sitojun  */
2294b1750805Sitojun void
long_passive(const char * cmd,int pf)22950c50dd5dSjan long_passive(const char *cmd, int pf)
2296b1750805Sitojun {
22970fe9133cSdanh 	socklen_t len;
229818ff6e9dSmillert 	int on = 1;
229950d2b651Smpech 	u_char *p, *a;
2300b1750805Sitojun 
2301b1750805Sitojun 	if (!logged_in) {
2302b1750805Sitojun 		syslog(LOG_NOTICE, "long passive but not logged in");
2303b1750805Sitojun 		reply(503, "Login with USER first.");
2304b1750805Sitojun 		return;
2305b1750805Sitojun 	}
2306b1750805Sitojun 
2307d94808dfSitojun 	if (pf != PF_UNSPEC && ctrl_addr.su_family != pf) {
2308b1750805Sitojun 		/*
2309b1750805Sitojun 		 * XXX
2310b1750805Sitojun 		 * only EPRT/EPSV ready clients will understand this
2311b1750805Sitojun 		 */
2312d94808dfSitojun 		if (strcmp(cmd, "EPSV") != 0)
2313b1750805Sitojun 			reply(501, "Network protocol mismatch"); /*XXX*/
2314d94808dfSitojun 		else
2315d94808dfSitojun 			epsv_protounsupp("Network protocol mismatch");
2316b1750805Sitojun 
2317b1750805Sitojun 		return;
2318b1750805Sitojun 	}
2319b1750805Sitojun 
2320181d0553Sitojun 	if (pdata >= 0)
2321181d0553Sitojun 		close(pdata);
2322353a5fa7Sderaadt 	/*
2323353a5fa7Sderaadt 	 * XXX
2324353a5fa7Sderaadt 	 * At this point, it would be nice to have an algorithm that
2325353a5fa7Sderaadt 	 * inserted a growing delay in an attack scenario.  Such a thing
2326353a5fa7Sderaadt 	 * would look like continual passive sockets being opened, but
2327353a5fa7Sderaadt 	 * nothing serious being done with them.  They not used to move
2328353a5fa7Sderaadt 	 * data; the entire attempt is just to use tcp FIN_WAIT
2329353a5fa7Sderaadt 	 * resources.
2330353a5fa7Sderaadt 	 */
2331b1750805Sitojun 	pdata = socket(ctrl_addr.su_family, SOCK_STREAM, 0);
2332df69c215Sderaadt 	if (pdata == -1) {
2333b1750805Sitojun 		perror_reply(425, "Can't open passive connection");
2334b1750805Sitojun 		return;
2335b1750805Sitojun 	}
233626b74261Sitojun 
233718ff6e9dSmillert 	if (setsockopt(pdata, SOL_SOCKET, SO_KEEPALIVE,
2338df69c215Sderaadt 	    &on, sizeof(on)) == -1)
233918ff6e9dSmillert 		goto pasv_error;
234018ff6e9dSmillert 
234126b74261Sitojun 	switch (ctrl_addr.su_family) {
234226b74261Sitojun 	case AF_INET:
23438ea584b2Sjakob 		on = IP_PORTRANGE_HIGH;
234426b74261Sitojun 		if (setsockopt(pdata, IPPROTO_IP, IP_PORTRANGE,
2345df69c215Sderaadt 		    &on, sizeof(on)) == -1)
234626b74261Sitojun 			goto pasv_error;
234726b74261Sitojun 		break;
234826b74261Sitojun 	case AF_INET6:
23498ea584b2Sjakob 		on = IPV6_PORTRANGE_HIGH;
235026b74261Sitojun 		if (setsockopt(pdata, IPPROTO_IPV6, IPV6_PORTRANGE,
2351df69c215Sderaadt 		    &on, sizeof(on)) == -1)
235226b74261Sitojun 			goto pasv_error;
235326b74261Sitojun 		break;
235426b74261Sitojun 	}
235526b74261Sitojun 
2356b1750805Sitojun 	pasv_addr = ctrl_addr;
2357b1750805Sitojun 	pasv_addr.su_port = 0;
2358df69c215Sderaadt 	if (bind(pdata, (struct sockaddr *)&pasv_addr, pasv_addr.su_len) == -1)
2359b1750805Sitojun 		goto pasv_error;
2360b1750805Sitojun 	len = pasv_addr.su_len;
2361df69c215Sderaadt 	if (getsockname(pdata, (struct sockaddr *)&pasv_addr, &len) == -1)
2362b1750805Sitojun 		goto pasv_error;
2363df69c215Sderaadt 	if (listen(pdata, 1) == -1)
2364b1750805Sitojun 		goto pasv_error;
2365353a5fa7Sderaadt 	p = (u_char *)&pasv_addr.su_port;
2366b1750805Sitojun 
2367b1750805Sitojun 	if (strcmp(cmd, "LPSV") == 0) {
2368b1750805Sitojun 		switch (pasv_addr.su_family) {
2369b1750805Sitojun 		case AF_INET:
2370353a5fa7Sderaadt 			a = (u_char *)&pasv_addr.su_sin.sin_addr;
2371353a5fa7Sderaadt 			reply(228,
2372353a5fa7Sderaadt 			    "Entering Long Passive Mode (%u,%u,%u,%u,%u,%u,%u,%u,%u)",
2373353a5fa7Sderaadt 			    4, 4, a[0], a[1], a[2], a[3], 2, p[0], p[1]);
2374b1750805Sitojun 			return;
2375b1750805Sitojun 		case AF_INET6:
23760fe9133cSdanh 			a = (u_char *)&pasv_addr.su_sin6.sin6_addr;
2377353a5fa7Sderaadt 			reply(228,
2378353a5fa7Sderaadt 			    "Entering Long Passive Mode (%u,%u,%u,%u,%u,%u,"
2379353a5fa7Sderaadt 			    "%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u)",
2380353a5fa7Sderaadt 				6, 16, a[0], a[1], a[2], a[3], a[4],
2381353a5fa7Sderaadt 				a[5], a[6], a[7], a[8], a[9], a[10],
23828e7aab87Sitojun 				a[11], a[12], a[13], a[14], a[15],
2383353a5fa7Sderaadt 				2, p[0], p[1]);
2384b1750805Sitojun 			return;
2385b1750805Sitojun 		}
2386b1750805Sitojun 	} else if (strcmp(cmd, "EPSV") == 0) {
2387b1750805Sitojun 		switch (pasv_addr.su_family) {
2388b1750805Sitojun 		case AF_INET:
2389b1750805Sitojun 		case AF_INET6:
2390353a5fa7Sderaadt 			reply(229, "Entering Extended Passive Mode (|||%u|)",
2391b1750805Sitojun 			    ntohs(pasv_addr.su_port));
2392b1750805Sitojun 			return;
2393b1750805Sitojun 		}
2394b1750805Sitojun 	} else {
2395b1750805Sitojun 		/* more proper error code? */
2396b1750805Sitojun 	}
2397b1750805Sitojun 
2398b1750805Sitojun   pasv_error:
2399ba7d19adSray 	perror_reply(425, "Can't open passive connection");
2400df930be7Sderaadt 	(void) close(pdata);
2401df930be7Sderaadt 	pdata = -1;
2402df930be7Sderaadt 	return;
2403df930be7Sderaadt }
2404df930be7Sderaadt 
2405df930be7Sderaadt /*
2406d94808dfSitojun  * EPRT |proto|addr|port|
2407d94808dfSitojun  */
2408d94808dfSitojun int
extended_port(const char * arg)2409d94808dfSitojun extended_port(const char *arg)
2410d94808dfSitojun {
2411d94808dfSitojun 	char *tmp = NULL;
2412d94808dfSitojun 	char *result[3];
2413d94808dfSitojun 	char *p, *q;
2414d94808dfSitojun 	char delim;
2415d94808dfSitojun 	struct addrinfo hints;
2416d94808dfSitojun 	struct addrinfo *res = NULL;
2417d94808dfSitojun 	int i;
2418d94808dfSitojun 	unsigned long proto;
2419d94808dfSitojun 
2420d94808dfSitojun 	if (epsvall) {
2421d94808dfSitojun 		reply(501, "EPRT disallowed after EPSV ALL");
2422d94808dfSitojun 		return -1;
2423d94808dfSitojun 	}
2424d94808dfSitojun 
2425d94808dfSitojun 	usedefault = 0;
2426d94808dfSitojun 	if (pdata >= 0) {
2427d94808dfSitojun 		(void) close(pdata);
2428d94808dfSitojun 		pdata = -1;
2429d94808dfSitojun 	}
2430d94808dfSitojun 
2431d94808dfSitojun 	tmp = strdup(arg);
2432d94808dfSitojun 	if (!tmp) {
2433d94808dfSitojun 		fatal("not enough core.");
2434d94808dfSitojun 		/*NOTREACHED*/
2435d94808dfSitojun 	}
2436d94808dfSitojun 	p = tmp;
2437d94808dfSitojun 	delim = p[0];
2438d94808dfSitojun 	p++;
2439d94808dfSitojun 	memset(result, 0, sizeof(result));
2440d94808dfSitojun 	for (i = 0; i < 3; i++) {
2441d94808dfSitojun 		q = strchr(p, delim);
2442d94808dfSitojun 		if (!q || *q != delim)
2443d94808dfSitojun 			goto parsefail;
2444d94808dfSitojun 		*q++ = '\0';
2445d94808dfSitojun 		result[i] = p;
2446d94808dfSitojun 		p = q;
2447d94808dfSitojun 	}
2448d94808dfSitojun 
2449d94808dfSitojun 	/* some more sanity check */
2450d94808dfSitojun 	p = NULL;
2451d94808dfSitojun 	(void)strtoul(result[2], &p, 10);
2452d94808dfSitojun 	if (!*result[2] || *p)
2453d94808dfSitojun 		goto protounsupp;
2454d94808dfSitojun 	p = NULL;
2455d94808dfSitojun 	proto = strtoul(result[0], &p, 10);
2456d94808dfSitojun 	if (!*result[0] || *p)
2457d94808dfSitojun 		goto protounsupp;
2458d94808dfSitojun 
2459d94808dfSitojun 	memset(&hints, 0, sizeof(hints));
2460d94808dfSitojun 	hints.ai_family = epsvproto2af((int)proto);
2461d94808dfSitojun 	if (hints.ai_family < 0)
2462d94808dfSitojun 		goto protounsupp;
2463d94808dfSitojun 	hints.ai_socktype = SOCK_STREAM;
2464d94808dfSitojun 	hints.ai_flags = AI_NUMERICHOST;	/*no DNS*/
2465d94808dfSitojun 	if (getaddrinfo(result[1], result[2], &hints, &res))
2466d94808dfSitojun 		goto parsefail;
2467d94808dfSitojun 	if (res->ai_next)
2468d94808dfSitojun 		goto parsefail;
2469d94808dfSitojun 	if (sizeof(data_dest) < res->ai_addrlen)
2470d94808dfSitojun 		goto parsefail;
2471d94808dfSitojun 	memcpy(&data_dest, res->ai_addr, res->ai_addrlen);
2472d94808dfSitojun 	if (his_addr.su_family == AF_INET6 &&
2473d94808dfSitojun 	    data_dest.su_family == AF_INET6) {
2474d94808dfSitojun 		/* XXX more sanity checks! */
2475d94808dfSitojun 		data_dest.su_sin6.sin6_scope_id =
2476d94808dfSitojun 		    his_addr.su_sin6.sin6_scope_id;
2477d94808dfSitojun 	}
2478d94808dfSitojun 	if (pdata >= 0) {
2479d94808dfSitojun 		(void) close(pdata);
2480d94808dfSitojun 		pdata = -1;
2481d94808dfSitojun 	}
2482d94808dfSitojun 	reply(200, "EPRT command successful.");
2483d94808dfSitojun 
2484d94808dfSitojun 	free(tmp);
2485d94808dfSitojun 	if (res)
2486d94808dfSitojun 		freeaddrinfo(res);
2487d94808dfSitojun 	return 0;
2488d94808dfSitojun 
2489d94808dfSitojun parsefail:
2490d94808dfSitojun 	reply(500, "Invalid argument, rejected.");
2491d94808dfSitojun 	usedefault = 1;
2492d94808dfSitojun 	free(tmp);
2493d94808dfSitojun 	if (res)
2494d94808dfSitojun 		freeaddrinfo(res);
2495d94808dfSitojun 	return -1;
2496d94808dfSitojun 
2497d94808dfSitojun protounsupp:
2498d94808dfSitojun 	epsv_protounsupp("Protocol not supported");
2499d94808dfSitojun 	usedefault = 1;
2500d94808dfSitojun 	free(tmp);
2501d94808dfSitojun 	if (res)
2502d94808dfSitojun 		freeaddrinfo(res);
2503d94808dfSitojun 	return -1;
2504d94808dfSitojun }
2505d94808dfSitojun 
2506d94808dfSitojun /*
2507d94808dfSitojun  * 522 Protocol not supported (proto,...)
2508d94808dfSitojun  * as we assume address family for control and data connections are the same,
2509d94808dfSitojun  * we do not return the list of address families we support - instead, we
2510d94808dfSitojun  * return the address family of the control connection.
2511d94808dfSitojun  */
2512d94808dfSitojun void
epsv_protounsupp(const char * message)2513d94808dfSitojun epsv_protounsupp(const char *message)
2514d94808dfSitojun {
2515d94808dfSitojun 	int proto;
2516d94808dfSitojun 
2517d94808dfSitojun 	proto = af2epsvproto(ctrl_addr.su_family);
2518d94808dfSitojun 	if (proto < 0)
2519d94808dfSitojun 		reply(501, "%s", message);	/*XXX*/
2520d94808dfSitojun 	else
2521d94808dfSitojun 		reply(522, "%s, use (%d)", message, proto);
2522d94808dfSitojun }
2523d94808dfSitojun 
2524d94808dfSitojun /*
2525df930be7Sderaadt  * Generate unique name for file with basename "local".
2526df930be7Sderaadt  * The file named "local" is already known to exist.
2527df930be7Sderaadt  * Generates failure reply on error.
2528df930be7Sderaadt  */
2529bee64f55Sderaadt static int
guniquefd(const char * local,char ** nam)25300c50dd5dSjan guniquefd(const char *local, char **nam)
2531df930be7Sderaadt {
2532b9fc9a72Sderaadt 	static char new[PATH_MAX];
2533df930be7Sderaadt 	struct stat st;
253483b130e1Sbluhm 	size_t len;
253583b130e1Sbluhm 	int count, fd;
2536df930be7Sderaadt 	char *cp;
2537df930be7Sderaadt 
2538df930be7Sderaadt 	cp = strrchr(local, '/');
2539df930be7Sderaadt 	if (cp)
2540df930be7Sderaadt 		*cp = '\0';
2541df69c215Sderaadt 	if (stat(cp ? local : ".", &st) == -1) {
2542df930be7Sderaadt 		perror_reply(553, cp ? local : ".");
2543bee64f55Sderaadt 		return (-1);
2544df930be7Sderaadt 	}
2545df930be7Sderaadt 	if (cp)
2546df930be7Sderaadt 		*cp = '/';
2547be864e19Smickey 	len = strlcpy(new, local, sizeof(new));
2548d8163e1dSderaadt 	if (len+2+1 >= sizeof(new)-1)
2549bee64f55Sderaadt 		return (-1);
2550d8163e1dSderaadt 	cp = new + len;
2551df930be7Sderaadt 	*cp++ = '.';
2552df930be7Sderaadt 	for (count = 1; count < 100; count++) {
25531e36a500Sdownsj 		(void)snprintf(cp, sizeof(new) - (cp - new), "%d", count);
2554e1cbcf7fSderaadt 		fd = open(new, O_RDWR|O_CREAT|O_EXCL, 0666);
2555e1cbcf7fSderaadt 		if (fd == -1)
2556e1cbcf7fSderaadt 			continue;
2557bee64f55Sderaadt 		if (nam)
2558bee64f55Sderaadt 			*nam = new;
2559bee64f55Sderaadt 		return (fd);
2560df930be7Sderaadt 	}
2561df930be7Sderaadt 	reply(452, "Unique file name cannot be created.");
2562bee64f55Sderaadt 	return (-1);
2563df930be7Sderaadt }
2564df930be7Sderaadt 
2565df930be7Sderaadt /*
2566df930be7Sderaadt  * Format and send reply containing system error number.
2567df930be7Sderaadt  */
2568df930be7Sderaadt void
perror_reply(int code,const char * string)25690c50dd5dSjan perror_reply(int code, const char *string)
2570df930be7Sderaadt {
2571df930be7Sderaadt 
2572df930be7Sderaadt 	reply(code, "%s: %s.", string, strerror(errno));
2573df930be7Sderaadt }
2574df930be7Sderaadt 
2575df930be7Sderaadt static char *onefile[] = {
2576df930be7Sderaadt 	"",
2577df930be7Sderaadt 	0
2578df930be7Sderaadt };
2579df930be7Sderaadt 
2580df930be7Sderaadt void
send_file_list(char * whichf)25816bcb9e83Sderaadt send_file_list(char *whichf)
2582df930be7Sderaadt {
2583df930be7Sderaadt 	struct stat st;
2584df930be7Sderaadt 	DIR *dirp = NULL;
2585df930be7Sderaadt 	struct dirent *dir;
2586df930be7Sderaadt 	FILE *dout = NULL;
2587a5553e71Sderaadt 	char **dirlist;
2588a5553e71Sderaadt 	char *dirname;
2589df930be7Sderaadt 	int simple = 0;
2590a5553e71Sderaadt 	volatile int freeglob = 0;
2591df930be7Sderaadt 	glob_t gl;
2592751337f4Sguenther 	size_t prefixlen;
2593df930be7Sderaadt 
2594df930be7Sderaadt 	if (strpbrk(whichf, "~{[*?") != NULL) {
2595df930be7Sderaadt 		memset(&gl, 0, sizeof(gl));
2596df930be7Sderaadt 		freeglob = 1;
25979dcba242Sderaadt 		if (glob(whichf,
25989dcba242Sderaadt 		    GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE|GLOB_LIMIT,
25999dcba242Sderaadt 		    0, &gl)) {
2600df930be7Sderaadt 			reply(550, "not found");
2601df930be7Sderaadt 			goto out;
2602df930be7Sderaadt 		} else if (gl.gl_pathc == 0) {
2603df930be7Sderaadt 			errno = ENOENT;
2604df930be7Sderaadt 			perror_reply(550, whichf);
2605df930be7Sderaadt 			goto out;
2606df930be7Sderaadt 		}
2607df930be7Sderaadt 		dirlist = gl.gl_pathv;
2608df930be7Sderaadt 	} else {
2609df930be7Sderaadt 		onefile[0] = whichf;
2610df930be7Sderaadt 		dirlist = onefile;
2611df930be7Sderaadt 		simple = 1;
2612df930be7Sderaadt 	}
2613df930be7Sderaadt 
26141d5555bfSderaadt 	while ((dirname = *dirlist++)) {
2615df69c215Sderaadt 		if (stat(dirname, &st) == -1) {
2616df930be7Sderaadt 			/*
2617df930be7Sderaadt 			 * If user typed "ls -l", etc, and the client
2618df930be7Sderaadt 			 * used NLST, do what the user meant.
2619df930be7Sderaadt 			 */
2620df930be7Sderaadt 			if (dirname[0] == '-' && *dirlist == NULL &&
2621df930be7Sderaadt 			    transflag == 0) {
26227a7d71fbStedu 				retrieve(RET_FILE, dirname);
2623df930be7Sderaadt 				goto out;
2624df930be7Sderaadt 			}
2625df930be7Sderaadt 			perror_reply(550, whichf);
2626df930be7Sderaadt 			if (dout != NULL) {
2627df930be7Sderaadt 				(void) fclose(dout);
2628df930be7Sderaadt 				transflag = 0;
2629df930be7Sderaadt 				data = -1;
2630df930be7Sderaadt 				pdata = -1;
2631df930be7Sderaadt 			}
2632df930be7Sderaadt 			goto out;
2633df930be7Sderaadt 		}
2634df930be7Sderaadt 
2635df930be7Sderaadt 		if (S_ISREG(st.st_mode)) {
2636df930be7Sderaadt 			if (dout == NULL) {
2637a20a5177Sguenther 				dout = dataconn("file list", -1, "w");
2638df930be7Sderaadt 				if (dout == NULL)
2639df930be7Sderaadt 					goto out;
2640df930be7Sderaadt 				transflag++;
2641df930be7Sderaadt 			}
2642df930be7Sderaadt 			fprintf(dout, "%s%s\n", dirname,
2643df930be7Sderaadt 				type == TYPE_A ? "\r" : "");
2644df930be7Sderaadt 			byte_count += strlen(dirname) + 1;
2645df930be7Sderaadt 			continue;
2646df930be7Sderaadt 		} else if (!S_ISDIR(st.st_mode))
2647df930be7Sderaadt 			continue;
2648df930be7Sderaadt 
2649df930be7Sderaadt 		if ((dirp = opendir(dirname)) == NULL)
2650df930be7Sderaadt 			continue;
2651df930be7Sderaadt 
2652751337f4Sguenther 		if (dirname[0] == '.' && dirname[1] == '\0')
2653751337f4Sguenther 			prefixlen = 0;
2654751337f4Sguenther 		else
2655751337f4Sguenther 			prefixlen = strlen(dirname) + 1;
2656df930be7Sderaadt 		while ((dir = readdir(dirp)) != NULL) {
26572378d5feSmillert 			if (recvurg) {
26582378d5feSmillert 				myoob();
26592378d5feSmillert 				recvurg = 0;
26602378d5feSmillert 				transflag = 0;
26612378d5feSmillert 				goto out;
26622378d5feSmillert 			}
26632378d5feSmillert 
2664df930be7Sderaadt 			if (dir->d_name[0] == '.' && dir->d_namlen == 1)
2665df930be7Sderaadt 				continue;
2666df930be7Sderaadt 			if (dir->d_name[0] == '.' && dir->d_name[1] == '.' &&
2667df930be7Sderaadt 			    dir->d_namlen == 2)
2668df930be7Sderaadt 				continue;
2669df930be7Sderaadt 
2670df930be7Sderaadt 			/*
2671df930be7Sderaadt 			 * We have to do a stat to insure it's
2672df930be7Sderaadt 			 * not a directory or special file.
2673df930be7Sderaadt 			 */
2674751337f4Sguenther 			if (simple ||
2675751337f4Sguenther 			    (fstatat(dirfd(dirp), dir->d_name, &st, 0) == 0 &&
2676df930be7Sderaadt 			    S_ISREG(st.st_mode))) {
2677df930be7Sderaadt 				if (dout == NULL) {
2678a20a5177Sguenther 					dout = dataconn("file list", -1, "w");
2679df930be7Sderaadt 					if (dout == NULL)
2680df930be7Sderaadt 						goto out;
2681df930be7Sderaadt 					transflag++;
2682df930be7Sderaadt 				}
2683751337f4Sguenther 
2684751337f4Sguenther 				if (prefixlen) {
2685751337f4Sguenther 					fprintf(dout, "%s/", dirname);
2686751337f4Sguenther 					byte_count += prefixlen;
2687751337f4Sguenther 				}
2688751337f4Sguenther 				fprintf(dout, "%s%s\n", dir->d_name,
2689df930be7Sderaadt 				    type == TYPE_A ? "\r" : "");
2690751337f4Sguenther 				byte_count += dir->d_namlen + 1;
2691df930be7Sderaadt 			}
2692df930be7Sderaadt 		}
2693df930be7Sderaadt 		(void) closedir(dirp);
2694df930be7Sderaadt 	}
2695df930be7Sderaadt 
2696df930be7Sderaadt 	if (dout == NULL)
2697df930be7Sderaadt 		reply(550, "No files found.");
2698df930be7Sderaadt 	else if (ferror(dout) != 0)
2699df930be7Sderaadt 		perror_reply(550, "Data connection");
2700df930be7Sderaadt 	else
2701df930be7Sderaadt 		reply(226, "Transfer complete.");
2702df930be7Sderaadt 
2703df930be7Sderaadt 	transflag = 0;
2704df930be7Sderaadt 	if (dout != NULL)
2705df930be7Sderaadt 		(void) fclose(dout);
270629b238aaSderaadt 	else {
270729b238aaSderaadt 		if (pdata >= 0)
270829b238aaSderaadt 			close(pdata);
270929b238aaSderaadt 	}
2710df930be7Sderaadt 	data = -1;
2711df930be7Sderaadt 	pdata = -1;
2712df930be7Sderaadt out:
2713df930be7Sderaadt 	if (freeglob) {
2714df930be7Sderaadt 		freeglob = 0;
2715df930be7Sderaadt 		globfree(&gl);
2716df930be7Sderaadt 	}
2717df930be7Sderaadt }
271872fc7920Sdownsj 
271927444405Sdownsj static void
reapchild(int signo)27206bcb9e83Sderaadt reapchild(int signo)
272127444405Sdownsj {
27226dccf94eSderaadt 	int save_errno = errno;
27232378d5feSmillert 	int rval;
27246dccf94eSderaadt 
27252378d5feSmillert 	do {
27262378d5feSmillert 		rval = waitpid(-1, NULL, WNOHANG);
27272378d5feSmillert 	} while (rval > 0 || (rval == -1 && errno == EINTR));
27286dccf94eSderaadt 	errno = save_errno;
272927444405Sdownsj }
273027444405Sdownsj 
273172fc7920Sdownsj void
logxfer(const char * name,off_t size,time_t start)27320c50dd5dSjan logxfer(const char *name, off_t size, time_t start)
273372fc7920Sdownsj {
2734b9fc9a72Sderaadt 	char buf[400 + (HOST_NAME_MAX+1)*4 + PATH_MAX*4];
2735b9fc9a72Sderaadt 	char dir[PATH_MAX], path[PATH_MAX], rpath[PATH_MAX];
2736b9fc9a72Sderaadt 	char vremotehost[(HOST_NAME_MAX+1)*4], vpath[PATH_MAX*4];
2737ab2a25eaSderaadt 	char *vpw;
273872fc7920Sdownsj 	time_t now;
2739be864e19Smickey 	int len;
274072fc7920Sdownsj 
2741ab2a25eaSderaadt 	if ((statfd >= 0) && (getcwd(dir, sizeof(dir)) != NULL)) {
2742*94c8de54Sflorian 		char *cnow;
2743*94c8de54Sflorian 
274472fc7920Sdownsj 		time(&now);
2745*94c8de54Sflorian 		cnow = ctime(&now);
2746d8f69df0Sdownsj 
2747be864e19Smickey 		vpw = malloc(strlen(guest ? guestpw : pw->pw_name) * 4 + 1);
2748ab2a25eaSderaadt 		if (vpw == NULL)
27498bc32b07Sderaadt 			return;
2750ab2a25eaSderaadt 
275141db49dbSmillert 		snprintf(path, sizeof(path), "%s/%s", dir, name);
27521d00cca4Spjanzen 		if (realpath(path, rpath) == NULL)
275341db49dbSmillert 			strlcpy(rpath, path, sizeof(rpath));
2754ab2a25eaSderaadt 		strvis(vpath, rpath, VIS_SAFE|VIS_NOSLASH);
27558bc32b07Sderaadt 
27568bc32b07Sderaadt 		strvis(vremotehost, remotehost, VIS_SAFE|VIS_NOSLASH);
2757be864e19Smickey 		strvis(vpw, guest? guestpw : pw->pw_name, VIS_SAFE|VIS_NOSLASH);
27588bc32b07Sderaadt 
2759e4023285Sderaadt 		len = snprintf(buf, sizeof(buf),
2760a20a5177Sguenther 		    "%.24s %lld %s %lld %s %c %s %c %c %s ftp %d %s %s\n",
2761*94c8de54Sflorian 		    cnow ? cnow : "?",
2762*94c8de54Sflorian 		    (long long)(now - start + (now == start)),
27631d5555bfSderaadt 		    vremotehost, (long long)size, vpath,
2764d8f69df0Sdownsj 		    ((type == TYPE_A) ? 'a' : 'b'), "*" /* none yet */,
2765d8f69df0Sdownsj 		    'o', ((guest) ? 'a' : 'r'),
27668bc32b07Sderaadt 		    vpw, 0 /* none yet */,
2767e4023285Sderaadt 		    ((guest) ? "*" : pw->pw_name), dhostname);
276811a3cd71Smoritz 		free(vpw);
2769e4023285Sderaadt 
2770515e489cSderaadt 		if (len < 0 || len >= sizeof(buf)) {
277160e5694aSmillert 			if ((len = strlen(buf)) == 0)
277260e5694aSmillert 				return;		/* should not happen */
277360e5694aSmillert 			buf[len - 1] = '\n';
2774be864e19Smickey 		}
2775be864e19Smickey 		write(statfd, buf, len);
277672fc7920Sdownsj 	}
277772fc7920Sdownsj }
2778802787ddSdownsj 
2779b96c0bc5Shenning void
set_slave_signals(void)2780b96c0bc5Shenning set_slave_signals(void)
2781b96c0bc5Shenning {
2782b96c0bc5Shenning 	struct sigaction sa;
2783b96c0bc5Shenning 
2784b96c0bc5Shenning 	sigemptyset(&sa.sa_mask);
2785b96c0bc5Shenning 	sa.sa_flags = SA_RESTART;
2786b96c0bc5Shenning 
2787b96c0bc5Shenning 	sa.sa_handler = SIG_DFL;
2788b96c0bc5Shenning 	(void) sigaction(SIGCHLD, &sa, NULL);
2789b96c0bc5Shenning 
2790b96c0bc5Shenning 	sa.sa_handler = sigurg;
2791b96c0bc5Shenning 	sa.sa_flags = 0;		/* don't restart syscalls for SIGURG */
2792b96c0bc5Shenning 	(void) sigaction(SIGURG, &sa, NULL);
2793b96c0bc5Shenning 
2794b96c0bc5Shenning 	sigfillset(&sa.sa_mask);	/* block all signals in handler */
2795b96c0bc5Shenning 	sa.sa_flags = SA_RESTART;
2796b96c0bc5Shenning 	sa.sa_handler = sigquit;
2797b96c0bc5Shenning 	(void) sigaction(SIGHUP, &sa, NULL);
2798b96c0bc5Shenning 	(void) sigaction(SIGINT, &sa, NULL);
2799b96c0bc5Shenning 	(void) sigaction(SIGQUIT, &sa, NULL);
2800b96c0bc5Shenning 	(void) sigaction(SIGTERM, &sa, NULL);
2801b96c0bc5Shenning 
2802b96c0bc5Shenning 	sa.sa_handler = lostconn;
2803b96c0bc5Shenning 	(void) sigaction(SIGPIPE, &sa, NULL);
2804b96c0bc5Shenning 
2805b96c0bc5Shenning 	sa.sa_handler = toolong;
2806b96c0bc5Shenning 	(void) sigaction(SIGALRM, &sa, NULL);
2807b96c0bc5Shenning 
2808b96c0bc5Shenning 	if (fcntl(fileno(stdin), F_SETOWN, getpid()) == -1)
2809b96c0bc5Shenning 		syslog(LOG_ERR, "fcntl F_SETOWN: %m");
2810b96c0bc5Shenning }
2811b96c0bc5Shenning 
28126f389908Smillert /*
28136f389908Smillert  * Allocate space and return a copy of the specified dir.
28146f389908Smillert  * If 'dir' begins with a tilde (~), expand it.
28156f389908Smillert  */
28166f389908Smillert char *
copy_dir(char * dir,struct passwd * pw)28176bcb9e83Sderaadt copy_dir(char *dir, struct passwd *pw)
28186f389908Smillert {
28196f389908Smillert 	char *cp;
28206f389908Smillert 	char *newdir;
28216f389908Smillert 	char *user = NULL;
28226f389908Smillert 
28236f389908Smillert 	/* Nothing to expand */
28246f389908Smillert 	if (dir[0] != '~')
28256f389908Smillert 		return (strdup(dir));
28266f389908Smillert 
28276f389908Smillert 	/* "dir" is of form ~user/some/dir, lookup user. */
28286f389908Smillert 	if (dir[1] != '/' && dir[1] != '\0') {
28296f389908Smillert 		if ((cp = strchr(dir + 1, '/')) == NULL)
28306f389908Smillert 			cp = dir + strlen(dir);
283195cc8c4aSderaadt 		if ((user = malloc((size_t)(cp - dir))) == NULL)
28326f389908Smillert 			return (NULL);
283395cc8c4aSderaadt 		strlcpy(user, dir + 1, (size_t)(cp - dir));
28346f389908Smillert 
28356f389908Smillert 		/* Only do lookup if it is a different user. */
28366f389908Smillert 		if (strcmp(user, pw->pw_name) != 0) {
28376f389908Smillert 			if ((pw = getpwnam(user)) == NULL) {
28386f389908Smillert 				/* No such user, interpret literally */
28396f389908Smillert 				free(user);
28406f389908Smillert 				return(strdup(dir));
28416f389908Smillert 			}
28426f389908Smillert 		}
2843ba7d19adSray 		free(user);
28446f389908Smillert 	}
28456f389908Smillert 
28466f389908Smillert 	/*
28476f389908Smillert 	 * If there is no directory separator (/) then it is just pw_dir.
28486f389908Smillert 	 * Otherwise, replace ~foo with pw_dir.
28496f389908Smillert 	 */
28506f389908Smillert 	if ((cp = strchr(dir + 1, '/')) == NULL) {
28516f389908Smillert 		newdir = strdup(pw->pw_dir);
28526f389908Smillert 	} else {
2853ea00c51aSray 		if (asprintf(&newdir, "%s%s", pw->pw_dir, cp) == -1)
28546f389908Smillert 			return (NULL);
28556f389908Smillert 	}
28566f389908Smillert 
28576f389908Smillert 	return(newdir);
28586f389908Smillert }
2859