xref: /freebsd-src/contrib/pf/ftp-proxy/ftp-proxy.c (revision 13b9f61009dba106a59671273645868ab9d8b39c)
1*13b9f610SMax Laier /*	$OpenBSD: ftp-proxy.c,v 1.33 2003/08/22 21:50:34 david Exp $ */
2*13b9f610SMax Laier 
3*13b9f610SMax Laier /*
4*13b9f610SMax Laier  * Copyright (c) 1996-2001
5*13b9f610SMax Laier  *	Obtuse Systems Corporation.  All rights reserved.
6*13b9f610SMax Laier  *
7*13b9f610SMax Laier  * Redistribution and use in source and binary forms, with or without
8*13b9f610SMax Laier  * modification, are permitted provided that the following conditions
9*13b9f610SMax Laier  * are met:
10*13b9f610SMax Laier  * 1. Redistributions of source code must retain the above copyright
11*13b9f610SMax Laier  *    notice, this list of conditions and the following disclaimer.
12*13b9f610SMax Laier  * 2. Redistributions in binary form must reproduce the above copyright
13*13b9f610SMax Laier  *    notice, this list of conditions and the following disclaimer in the
14*13b9f610SMax Laier  *    documentation and/or other materials provided with the distribution.
15*13b9f610SMax Laier  * 3. Neither the name of the Obtuse Systems nor the names of its contributors
16*13b9f610SMax Laier  *    may be used to endorse or promote products derived from this software
17*13b9f610SMax Laier  *    without specific prior written permission.
18*13b9f610SMax Laier  *
19*13b9f610SMax Laier  * THIS SOFTWARE IS PROVIDED BY OBTUSE SYSTEMS AND CONTRIBUTORS ``AS IS'' AND
20*13b9f610SMax Laier  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21*13b9f610SMax Laier  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22*13b9f610SMax Laier  * ARE DISCLAIMED.  IN NO EVENT SHALL OBTUSE SYSTEMS CORPORATION OR
23*13b9f610SMax Laier  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
24*13b9f610SMax Laier  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
25*13b9f610SMax Laier  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
26*13b9f610SMax Laier  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
27*13b9f610SMax Laier  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
28*13b9f610SMax Laier  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
29*13b9f610SMax Laier  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30*13b9f610SMax Laier  *
31*13b9f610SMax Laier  */
32*13b9f610SMax Laier 
33*13b9f610SMax Laier /*
34*13b9f610SMax Laier  * ftp proxy, Originally based on juniper_ftp_proxy from the Obtuse
35*13b9f610SMax Laier  * Systems juniper firewall, written by Dan Boulet <danny@obtuse.com>
36*13b9f610SMax Laier  * and Bob Beck <beck@obtuse.com>
37*13b9f610SMax Laier  *
38*13b9f610SMax Laier  * This version basically passes everything through unchanged except
39*13b9f610SMax Laier  * for the PORT and the * "227 Entering Passive Mode" reply.
40*13b9f610SMax Laier  *
41*13b9f610SMax Laier  * A PORT command is handled by noting the IP address and port number
42*13b9f610SMax Laier  * specified and then configuring a listen port on some very high port
43*13b9f610SMax Laier  * number and telling the server about it using a PORT message.
44*13b9f610SMax Laier  * We then watch for an in-bound connection on the port from the server
45*13b9f610SMax Laier  * and connect to the client's port when it happens.
46*13b9f610SMax Laier  *
47*13b9f610SMax Laier  * A "227 Entering Passive Mode" reply is handled by noting the IP address
48*13b9f610SMax Laier  * and port number specified and then configuring a listen port on some
49*13b9f610SMax Laier  * very high port number and telling the client about it using a
50*13b9f610SMax Laier  * "227 Entering Passive Mode" reply.
51*13b9f610SMax Laier  * We then watch for an in-bound connection on the port from the client
52*13b9f610SMax Laier  * and connect to the server's port when it happens.
53*13b9f610SMax Laier  *
54*13b9f610SMax Laier  * supports tcp wrapper lookups/access control with the -w flag using
55*13b9f610SMax Laier  * the real destination address - the tcp wrapper stuff is done after
56*13b9f610SMax Laier  * the real destination address is retrieved from pf
57*13b9f610SMax Laier  *
58*13b9f610SMax Laier  */
59*13b9f610SMax Laier 
60*13b9f610SMax Laier /*
61*13b9f610SMax Laier  * TODO:
62*13b9f610SMax Laier  * Plenty, this is very basic, with the idea to get it in clean first.
63*13b9f610SMax Laier  *
64*13b9f610SMax Laier  * - IPv6 and EPASV support
65*13b9f610SMax Laier  * - Content filter support
66*13b9f610SMax Laier  * - filename filter support
67*13b9f610SMax Laier  * - per-user rules perhaps.
68*13b9f610SMax Laier  */
69*13b9f610SMax Laier 
70*13b9f610SMax Laier #include <sys/types.h>
71*13b9f610SMax Laier #include <sys/time.h>
72*13b9f610SMax Laier #include <sys/socket.h>
73*13b9f610SMax Laier 
74*13b9f610SMax Laier #include <net/if.h>
75*13b9f610SMax Laier #include <netinet/in.h>
76*13b9f610SMax Laier 
77*13b9f610SMax Laier #include <arpa/inet.h>
78*13b9f610SMax Laier 
79*13b9f610SMax Laier #include <ctype.h>
80*13b9f610SMax Laier #include <errno.h>
81*13b9f610SMax Laier #include <grp.h>
82*13b9f610SMax Laier #include <netdb.h>
83*13b9f610SMax Laier #include <pwd.h>
84*13b9f610SMax Laier #include <signal.h>
85*13b9f610SMax Laier #include <stdarg.h>
86*13b9f610SMax Laier #include <stdio.h>
87*13b9f610SMax Laier #include <stdlib.h>
88*13b9f610SMax Laier #include <string.h>
89*13b9f610SMax Laier #include <sysexits.h>
90*13b9f610SMax Laier #include <syslog.h>
91*13b9f610SMax Laier #include <unistd.h>
92*13b9f610SMax Laier 
93*13b9f610SMax Laier #include "util.h"
94*13b9f610SMax Laier 
95*13b9f610SMax Laier #ifdef LIBWRAP
96*13b9f610SMax Laier #include <tcpd.h>
97*13b9f610SMax Laier int allow_severity = LOG_INFO;
98*13b9f610SMax Laier int deny_severity = LOG_NOTICE;
99*13b9f610SMax Laier #endif /* LIBWRAP */
100*13b9f610SMax Laier 
101*13b9f610SMax Laier int min_port = IPPORT_HIFIRSTAUTO;
102*13b9f610SMax Laier int max_port = IPPORT_HILASTAUTO;
103*13b9f610SMax Laier 
104*13b9f610SMax Laier #define STARTBUFSIZE  1024	/* Must be at least 3 */
105*13b9f610SMax Laier 
106*13b9f610SMax Laier /*
107*13b9f610SMax Laier  * Variables used to support PORT mode connections.
108*13b9f610SMax Laier  *
109*13b9f610SMax Laier  * This gets a bit complicated.
110*13b9f610SMax Laier  *
111*13b9f610SMax Laier  * If PORT mode is on then client_listen_sa describes the socket that
112*13b9f610SMax Laier  * the real client is listening on and server_listen_sa describes the
113*13b9f610SMax Laier  * socket that we are listening on (waiting for the real server to connect
114*13b9f610SMax Laier  * with us).
115*13b9f610SMax Laier  *
116*13b9f610SMax Laier  * If PASV mode is on then client_listen_sa describes the socket that
117*13b9f610SMax Laier  * we are listening on (waiting for the real client to connect to us on)
118*13b9f610SMax Laier  * and server_listen_sa describes the socket that the real server is
119*13b9f610SMax Laier  * listening on.
120*13b9f610SMax Laier  *
121*13b9f610SMax Laier  * If the socket we are listening on gets a connection then we connect
122*13b9f610SMax Laier  * to the other side's socket.  Similarly, if a connected socket is
123*13b9f610SMax Laier  * shutdown then we shutdown the other side's socket.
124*13b9f610SMax Laier  */
125*13b9f610SMax Laier 
126*13b9f610SMax Laier double xfer_start_time;
127*13b9f610SMax Laier 
128*13b9f610SMax Laier struct sockaddr_in real_server_sa;
129*13b9f610SMax Laier struct sockaddr_in client_listen_sa;
130*13b9f610SMax Laier struct sockaddr_in server_listen_sa;
131*13b9f610SMax Laier 
132*13b9f610SMax Laier int client_listen_socket = -1;	/* Only used in PASV mode */
133*13b9f610SMax Laier int client_data_socket = -1;	/* Connected socket to real client */
134*13b9f610SMax Laier int server_listen_socket = -1;	/* Only used in PORT mode */
135*13b9f610SMax Laier int server_data_socket = -1;	/* Connected socket to real server */
136*13b9f610SMax Laier int client_data_bytes, server_data_bytes;
137*13b9f610SMax Laier 
138*13b9f610SMax Laier int AnonFtpOnly;
139*13b9f610SMax Laier int Verbose;
140*13b9f610SMax Laier int NatMode;
141*13b9f610SMax Laier 
142*13b9f610SMax Laier char ClientName[NI_MAXHOST];
143*13b9f610SMax Laier char RealServerName[NI_MAXHOST];
144*13b9f610SMax Laier char OurName[NI_MAXHOST];
145*13b9f610SMax Laier 
146*13b9f610SMax Laier char *User = "proxy";
147*13b9f610SMax Laier char *Group;
148*13b9f610SMax Laier 
149*13b9f610SMax Laier extern int Debug_Level;
150*13b9f610SMax Laier extern int Use_Rdns;
151*13b9f610SMax Laier extern char *__progname;
152*13b9f610SMax Laier 
153*13b9f610SMax Laier typedef enum {
154*13b9f610SMax Laier 	UNKNOWN_MODE,
155*13b9f610SMax Laier 	PORT_MODE,
156*13b9f610SMax Laier 	PASV_MODE,
157*13b9f610SMax Laier 	EPRT_MODE,
158*13b9f610SMax Laier 	EPSV_MODE
159*13b9f610SMax Laier } connection_mode_t;
160*13b9f610SMax Laier 
161*13b9f610SMax Laier connection_mode_t connection_mode;
162*13b9f610SMax Laier 
163*13b9f610SMax Laier extern void	debuglog(int debug_level, const char *fmt, ...);
164*13b9f610SMax Laier double		wallclock_time(void);
165*13b9f610SMax Laier void		show_xfer_stats(void);
166*13b9f610SMax Laier void		log_control_command (char *cmd, int client);
167*13b9f610SMax Laier int		new_dataconn(int server);
168*13b9f610SMax Laier void		do_client_cmd(struct csiob *client, struct csiob *server);
169*13b9f610SMax Laier void		do_server_reply(struct csiob *server, struct csiob *client);
170*13b9f610SMax Laier static void
171*13b9f610SMax Laier usage(void)
172*13b9f610SMax Laier {
173*13b9f610SMax Laier 	syslog(LOG_NOTICE,
174*13b9f610SMax Laier 	    "usage: %s [-AnrVw] [-D debuglevel] [-g group] %s %s",
175*13b9f610SMax Laier 	    __progname, "[-m minport] [-M maxport] [-t timeout]",
176*13b9f610SMax Laier 	    "[-u user]");
177*13b9f610SMax Laier 	exit(EX_USAGE);
178*13b9f610SMax Laier }
179*13b9f610SMax Laier 
180*13b9f610SMax Laier static void
181*13b9f610SMax Laier close_client_data(void)
182*13b9f610SMax Laier {
183*13b9f610SMax Laier 	if (client_data_socket >= 0) {
184*13b9f610SMax Laier 		shutdown(client_data_socket, 2);
185*13b9f610SMax Laier 		close(client_data_socket);
186*13b9f610SMax Laier 		client_data_socket = -1;
187*13b9f610SMax Laier 	}
188*13b9f610SMax Laier }
189*13b9f610SMax Laier 
190*13b9f610SMax Laier static void
191*13b9f610SMax Laier close_server_data(void)
192*13b9f610SMax Laier {
193*13b9f610SMax Laier 	if (server_data_socket >= 0)  {
194*13b9f610SMax Laier 		shutdown(server_data_socket, 2);
195*13b9f610SMax Laier 		close(server_data_socket);
196*13b9f610SMax Laier 		server_data_socket = -1;
197*13b9f610SMax Laier 	}
198*13b9f610SMax Laier }
199*13b9f610SMax Laier 
200*13b9f610SMax Laier static void
201*13b9f610SMax Laier drop_privs(void)
202*13b9f610SMax Laier {
203*13b9f610SMax Laier 	struct passwd *pw;
204*13b9f610SMax Laier 	struct group *gr;
205*13b9f610SMax Laier 	uid_t uid = 0;
206*13b9f610SMax Laier 	gid_t gid = 0;
207*13b9f610SMax Laier 
208*13b9f610SMax Laier 	if (User != NULL) {
209*13b9f610SMax Laier 		pw = getpwnam(User);
210*13b9f610SMax Laier 		if (pw == NULL) {
211*13b9f610SMax Laier 			syslog(LOG_ERR, "cannot find user %s", User);
212*13b9f610SMax Laier 			exit(EX_USAGE);
213*13b9f610SMax Laier 		}
214*13b9f610SMax Laier 		uid = pw->pw_uid;
215*13b9f610SMax Laier 		gid = pw->pw_gid;
216*13b9f610SMax Laier 	}
217*13b9f610SMax Laier 
218*13b9f610SMax Laier 	if (Group != NULL) {
219*13b9f610SMax Laier 		gr = getgrnam(Group);
220*13b9f610SMax Laier 		if (gr == NULL) {
221*13b9f610SMax Laier 			syslog(LOG_ERR, "cannot find group %s", Group);
222*13b9f610SMax Laier 			exit(EX_USAGE);
223*13b9f610SMax Laier 		}
224*13b9f610SMax Laier 		gid = gr->gr_gid;
225*13b9f610SMax Laier 	}
226*13b9f610SMax Laier 
227*13b9f610SMax Laier 	if (gid != 0 && (setegid(gid) == -1 || setgid(gid) == -1)) {
228*13b9f610SMax Laier 		syslog(LOG_ERR, "cannot drop group privs (%m)");
229*13b9f610SMax Laier 		exit(EX_CONFIG);
230*13b9f610SMax Laier 	}
231*13b9f610SMax Laier 
232*13b9f610SMax Laier 	if (uid != 0 && (seteuid(uid) == -1 || setuid(uid) == -1)) {
233*13b9f610SMax Laier 		syslog(LOG_ERR, "cannot drop root privs (%m)");
234*13b9f610SMax Laier 		exit(EX_CONFIG);
235*13b9f610SMax Laier 	}
236*13b9f610SMax Laier }
237*13b9f610SMax Laier 
238*13b9f610SMax Laier #ifdef LIBWRAP
239*13b9f610SMax Laier /*
240*13b9f610SMax Laier  * Check a connection against the tcpwrapper, log if we're going to
241*13b9f610SMax Laier  * reject it, returns: 0 -> reject, 1 -> accept. We add in hostnames
242*13b9f610SMax Laier  * if we are set to do reverse DNS, otherwise no.
243*13b9f610SMax Laier  */
244*13b9f610SMax Laier static int
245*13b9f610SMax Laier check_host(struct sockaddr_in *client_sin, struct sockaddr_in *server_sin)
246*13b9f610SMax Laier {
247*13b9f610SMax Laier 	char cname[NI_MAXHOST];
248*13b9f610SMax Laier 	char sname[NI_MAXHOST];
249*13b9f610SMax Laier 	struct request_info request;
250*13b9f610SMax Laier 	int i;
251*13b9f610SMax Laier 
252*13b9f610SMax Laier 	request_init(&request, RQ_DAEMON, __progname, RQ_CLIENT_SIN,
253*13b9f610SMax Laier 	    client_sin, RQ_SERVER_SIN, server_sin, RQ_CLIENT_ADDR,
254*13b9f610SMax Laier 	    inet_ntoa(client_sin->sin_addr), 0);
255*13b9f610SMax Laier 
256*13b9f610SMax Laier 	if (Use_Rdns)  {
257*13b9f610SMax Laier 		/*
258*13b9f610SMax Laier 		 * We already looked these up, but we have to do it again
259*13b9f610SMax Laier 		 * for tcp wrapper, to ensure that we get the DNS name, since
260*13b9f610SMax Laier 		 * the tcp wrapper cares about these things, and we don't
261*13b9f610SMax Laier 		 * want to pass in a printed address as a name.
262*13b9f610SMax Laier 		 */
263*13b9f610SMax Laier 		i = getnameinfo((struct sockaddr *) &client_sin->sin_addr,
264*13b9f610SMax Laier 		    sizeof(&client_sin->sin_addr), cname, sizeof(cname),
265*13b9f610SMax Laier 		    NULL, 0, NI_NAMEREQD);
266*13b9f610SMax Laier 
267*13b9f610SMax Laier 		if (i != 0 && i != EAI_NONAME && i != EAI_AGAIN)
268*13b9f610SMax Laier 			strlcpy(cname, STRING_UNKNOWN, sizeof(cname));
269*13b9f610SMax Laier 
270*13b9f610SMax Laier 		i = getnameinfo((struct sockaddr *)&server_sin->sin_addr,
271*13b9f610SMax Laier 		    sizeof(&server_sin->sin_addr), sname, sizeof(sname),
272*13b9f610SMax Laier 		    NULL, 0, NI_NAMEREQD);
273*13b9f610SMax Laier 
274*13b9f610SMax Laier 		if (i != 0 && i != EAI_NONAME && i != EAI_AGAIN)
275*13b9f610SMax Laier 			strlcpy(sname, STRING_UNKNOWN, sizeof(sname));
276*13b9f610SMax Laier 	} else {
277*13b9f610SMax Laier 		/*
278*13b9f610SMax Laier 		 * ensure the TCP wrapper doesn't start doing
279*13b9f610SMax Laier 		 * reverse DNS lookups if we aren't supposed to.
280*13b9f610SMax Laier 		 */
281*13b9f610SMax Laier 		strlcpy(cname, STRING_UNKNOWN, sizeof(cname));
282*13b9f610SMax Laier 		strlcpy(sname, STRING_UNKNOWN, sizeof(sname));
283*13b9f610SMax Laier 	}
284*13b9f610SMax Laier 
285*13b9f610SMax Laier 	request_set(&request, RQ_SERVER_ADDR, inet_ntoa(server_sin->sin_addr),
286*13b9f610SMax Laier 	    0);
287*13b9f610SMax Laier 	request_set(&request, RQ_CLIENT_NAME, cname, RQ_SERVER_NAME, sname, 0);
288*13b9f610SMax Laier 
289*13b9f610SMax Laier 	if (!hosts_access(&request)) {
290*13b9f610SMax Laier 		syslog(LOG_NOTICE, "tcpwrappers rejected: %s -> %s",
291*13b9f610SMax Laier 		    ClientName, RealServerName);
292*13b9f610SMax Laier 		return(0);
293*13b9f610SMax Laier 	}
294*13b9f610SMax Laier 	return(1);
295*13b9f610SMax Laier }
296*13b9f610SMax Laier #endif /* LIBWRAP */
297*13b9f610SMax Laier 
298*13b9f610SMax Laier double
299*13b9f610SMax Laier wallclock_time(void)
300*13b9f610SMax Laier {
301*13b9f610SMax Laier 	struct timeval tv;
302*13b9f610SMax Laier 
303*13b9f610SMax Laier 	gettimeofday(&tv, NULL);
304*13b9f610SMax Laier 	return(tv.tv_sec + tv.tv_usec / 1e6);
305*13b9f610SMax Laier }
306*13b9f610SMax Laier 
307*13b9f610SMax Laier /*
308*13b9f610SMax Laier  * Show the stats for this data transfer
309*13b9f610SMax Laier  */
310*13b9f610SMax Laier void
311*13b9f610SMax Laier show_xfer_stats(void)
312*13b9f610SMax Laier {
313*13b9f610SMax Laier 	char tbuf[1000];
314*13b9f610SMax Laier 	double delta;
315*13b9f610SMax Laier 	size_t len;
316*13b9f610SMax Laier 	int i;
317*13b9f610SMax Laier 
318*13b9f610SMax Laier 	if (!Verbose)
319*13b9f610SMax Laier 		return;
320*13b9f610SMax Laier 
321*13b9f610SMax Laier 	delta = wallclock_time() - xfer_start_time;
322*13b9f610SMax Laier 
323*13b9f610SMax Laier 	if (delta < 0.001)
324*13b9f610SMax Laier 		delta = 0.001;
325*13b9f610SMax Laier 
326*13b9f610SMax Laier 	if (client_data_bytes == 0 && server_data_bytes == 0) {
327*13b9f610SMax Laier 		syslog(LOG_INFO,
328*13b9f610SMax Laier 		  "data transfer complete (no bytes transferred)");
329*13b9f610SMax Laier 		return;
330*13b9f610SMax Laier 	}
331*13b9f610SMax Laier 
332*13b9f610SMax Laier 	len = sizeof(tbuf);
333*13b9f610SMax Laier 
334*13b9f610SMax Laier 	if (delta >= 60) {
335*13b9f610SMax Laier 		int idelta;
336*13b9f610SMax Laier 
337*13b9f610SMax Laier 		idelta = delta + 0.5;
338*13b9f610SMax Laier 		if (idelta >= 60*60) {
339*13b9f610SMax Laier 			i = snprintf(tbuf, len,
340*13b9f610SMax Laier 			    "data transfer complete (%dh %dm %ds",
341*13b9f610SMax Laier 			    idelta / (60*60), (idelta % (60*60)) / 60,
342*13b9f610SMax Laier 			    idelta % 60);
343*13b9f610SMax Laier 			if (i >= len)
344*13b9f610SMax Laier 				goto logit;
345*13b9f610SMax Laier 			len -= i;
346*13b9f610SMax Laier 		} else {
347*13b9f610SMax Laier 			i = snprintf(tbuf, len,
348*13b9f610SMax Laier 			    "data transfer complete (%dm %ds", idelta / 60,
349*13b9f610SMax Laier 			    idelta % 60);
350*13b9f610SMax Laier 			if (i >= len)
351*13b9f610SMax Laier 				goto logit;
352*13b9f610SMax Laier 			len -= i;
353*13b9f610SMax Laier 		}
354*13b9f610SMax Laier 	} else {
355*13b9f610SMax Laier 		i = snprintf(tbuf, len, "data transfer complete (%.1fs",
356*13b9f610SMax Laier 		    delta);
357*13b9f610SMax Laier 		if (i >= len)
358*13b9f610SMax Laier 			goto logit;
359*13b9f610SMax Laier 		len -= i;
360*13b9f610SMax Laier 	}
361*13b9f610SMax Laier 
362*13b9f610SMax Laier 	if (client_data_bytes > 0) {
363*13b9f610SMax Laier 		i = snprintf(&tbuf[strlen(tbuf)], len,
364*13b9f610SMax Laier 		    ", %d bytes to server) (%.1fKB/s", client_data_bytes,
365*13b9f610SMax Laier 		    (client_data_bytes / delta) / (double)1024);
366*13b9f610SMax Laier 		if (i >= len)
367*13b9f610SMax Laier 			goto logit;
368*13b9f610SMax Laier 		len -= i;
369*13b9f610SMax Laier 	}
370*13b9f610SMax Laier 	if (server_data_bytes > 0) {
371*13b9f610SMax Laier 		i = snprintf(&tbuf[strlen(tbuf)], len,
372*13b9f610SMax Laier 		    ", %d bytes to client) (%.1fKB/s", server_data_bytes,
373*13b9f610SMax Laier 		    (server_data_bytes / delta) / (double)1024);
374*13b9f610SMax Laier 		if (i >= len)
375*13b9f610SMax Laier 			goto logit;
376*13b9f610SMax Laier 		len -= i;
377*13b9f610SMax Laier 	}
378*13b9f610SMax Laier 	strlcat(tbuf, ")", sizeof(tbuf));
379*13b9f610SMax Laier  logit:
380*13b9f610SMax Laier 	syslog(LOG_INFO, "%s", tbuf);
381*13b9f610SMax Laier }
382*13b9f610SMax Laier 
383*13b9f610SMax Laier void
384*13b9f610SMax Laier log_control_command (char *cmd, int client)
385*13b9f610SMax Laier {
386*13b9f610SMax Laier 	/* log an ftp control command or reply */
387*13b9f610SMax Laier 	char *logstring;
388*13b9f610SMax Laier 	int level = LOG_DEBUG;
389*13b9f610SMax Laier 
390*13b9f610SMax Laier 	if (!Verbose)
391*13b9f610SMax Laier 		return;
392*13b9f610SMax Laier 
393*13b9f610SMax Laier 	/* don't log passwords */
394*13b9f610SMax Laier 	if (strncasecmp(cmd, "pass ", 5) == 0)
395*13b9f610SMax Laier 		logstring = "PASS XXXX";
396*13b9f610SMax Laier 	else
397*13b9f610SMax Laier 		logstring = cmd;
398*13b9f610SMax Laier 	if (client) {
399*13b9f610SMax Laier 		/* log interesting stuff at LOG_INFO, rest at LOG_DEBUG */
400*13b9f610SMax Laier 		if ((strncasecmp(cmd, "user ", 5) == 0) ||
401*13b9f610SMax Laier 		    (strncasecmp(cmd, "retr ", 5) == 0) ||
402*13b9f610SMax Laier 		    (strncasecmp(cmd, "cwd ", 4) == 0) ||
403*13b9f610SMax Laier 		    (strncasecmp(cmd, "stor " ,5) == 0))
404*13b9f610SMax Laier 			level = LOG_INFO;
405*13b9f610SMax Laier 	}
406*13b9f610SMax Laier 	syslog(level, "%s %s", client ? "client:" : " server:",
407*13b9f610SMax Laier 	    logstring);
408*13b9f610SMax Laier }
409*13b9f610SMax Laier 
410*13b9f610SMax Laier /*
411*13b9f610SMax Laier  * set ourselves up for a new data connection. Direction is toward client if
412*13b9f610SMax Laier  * "server" is 0, towards server otherwise.
413*13b9f610SMax Laier  */
414*13b9f610SMax Laier int
415*13b9f610SMax Laier new_dataconn(int server)
416*13b9f610SMax Laier {
417*13b9f610SMax Laier 	/*
418*13b9f610SMax Laier 	 * Close existing data conn.
419*13b9f610SMax Laier 	 */
420*13b9f610SMax Laier 
421*13b9f610SMax Laier 	if (client_listen_socket != -1) {
422*13b9f610SMax Laier 		close(client_listen_socket);
423*13b9f610SMax Laier 		client_listen_socket = -1;
424*13b9f610SMax Laier 	}
425*13b9f610SMax Laier 	close_client_data();
426*13b9f610SMax Laier 
427*13b9f610SMax Laier 	if (server_listen_socket != -1) {
428*13b9f610SMax Laier 		close(server_listen_socket);
429*13b9f610SMax Laier 		server_listen_socket = -1;
430*13b9f610SMax Laier 	}
431*13b9f610SMax Laier 	close_server_data();
432*13b9f610SMax Laier 
433*13b9f610SMax Laier 	if (server) {
434*13b9f610SMax Laier 		bzero(&server_listen_sa, sizeof(server_listen_sa));
435*13b9f610SMax Laier 		server_listen_socket = get_backchannel_socket(SOCK_STREAM,
436*13b9f610SMax Laier 		    min_port, max_port, -1, 1, &server_listen_sa);
437*13b9f610SMax Laier 
438*13b9f610SMax Laier 		if (server_listen_socket == -1) {
439*13b9f610SMax Laier 			syslog(LOG_INFO, "server socket bind() failed (%m)");
440*13b9f610SMax Laier 			exit(EX_OSERR);
441*13b9f610SMax Laier 		}
442*13b9f610SMax Laier 		if (listen(server_listen_socket, 5) != 0) {
443*13b9f610SMax Laier 			syslog(LOG_INFO, "server socket listen() failed (%m)");
444*13b9f610SMax Laier 			exit(EX_OSERR);
445*13b9f610SMax Laier 		}
446*13b9f610SMax Laier 	} else {
447*13b9f610SMax Laier 		bzero(&client_listen_sa, sizeof(client_listen_sa));
448*13b9f610SMax Laier 		client_listen_socket = get_backchannel_socket(SOCK_STREAM,
449*13b9f610SMax Laier 		    min_port, max_port, -1, 1, &client_listen_sa);
450*13b9f610SMax Laier 
451*13b9f610SMax Laier 		if (client_listen_socket == -1) {
452*13b9f610SMax Laier 			syslog(LOG_NOTICE,
453*13b9f610SMax Laier 			    "cannot get client listen socket (%m)");
454*13b9f610SMax Laier 			exit(EX_OSERR);
455*13b9f610SMax Laier 		}
456*13b9f610SMax Laier 		if (listen(client_listen_socket, 5) != 0) {
457*13b9f610SMax Laier 			syslog(LOG_NOTICE,
458*13b9f610SMax Laier 			    "cannot listen on client socket (%m)");
459*13b9f610SMax Laier 			exit(EX_OSERR);
460*13b9f610SMax Laier 		}
461*13b9f610SMax Laier 	}
462*13b9f610SMax Laier 	return(0);
463*13b9f610SMax Laier }
464*13b9f610SMax Laier 
465*13b9f610SMax Laier static void
466*13b9f610SMax Laier connect_pasv_backchannel(void)
467*13b9f610SMax Laier {
468*13b9f610SMax Laier 	struct sockaddr_in listen_sa;
469*13b9f610SMax Laier 	socklen_t salen;
470*13b9f610SMax Laier 
471*13b9f610SMax Laier 	/*
472*13b9f610SMax Laier 	 * We are about to accept a connection from the client.
473*13b9f610SMax Laier 	 * This is a PASV data connection.
474*13b9f610SMax Laier 	 */
475*13b9f610SMax Laier 	debuglog(2, "client listen socket ready");
476*13b9f610SMax Laier 
477*13b9f610SMax Laier 	close_server_data();
478*13b9f610SMax Laier 	close_client_data();
479*13b9f610SMax Laier 
480*13b9f610SMax Laier 	salen = sizeof(listen_sa);
481*13b9f610SMax Laier 	client_data_socket = accept(client_listen_socket,
482*13b9f610SMax Laier 	    (struct sockaddr *)&listen_sa, &salen);
483*13b9f610SMax Laier 
484*13b9f610SMax Laier 	if (client_data_socket < 0) {
485*13b9f610SMax Laier 		syslog(LOG_NOTICE, "accept() failed (%m)");
486*13b9f610SMax Laier 		exit(EX_OSERR);
487*13b9f610SMax Laier 	}
488*13b9f610SMax Laier 	close(client_listen_socket);
489*13b9f610SMax Laier 	client_listen_socket = -1;
490*13b9f610SMax Laier 	memset(&listen_sa, 0, sizeof(listen_sa));
491*13b9f610SMax Laier 
492*13b9f610SMax Laier 	server_data_socket = get_backchannel_socket(SOCK_STREAM, min_port,
493*13b9f610SMax Laier 	    max_port, -1, 1, &listen_sa);
494*13b9f610SMax Laier 	if (server_data_socket < 0) {
495*13b9f610SMax Laier 		syslog(LOG_NOTICE, "get_backchannel_socket() failed (%m)");
496*13b9f610SMax Laier 		exit(EX_OSERR);
497*13b9f610SMax Laier 	}
498*13b9f610SMax Laier 	if (connect(server_data_socket, (struct sockaddr *) &server_listen_sa,
499*13b9f610SMax Laier 	    sizeof(server_listen_sa)) != 0) {
500*13b9f610SMax Laier 		syslog(LOG_NOTICE, "connect() failed (%m)");
501*13b9f610SMax Laier 		exit(EX_NOHOST);
502*13b9f610SMax Laier 	}
503*13b9f610SMax Laier 	client_data_bytes = 0;
504*13b9f610SMax Laier 	server_data_bytes = 0;
505*13b9f610SMax Laier 	xfer_start_time = wallclock_time();
506*13b9f610SMax Laier }
507*13b9f610SMax Laier 
508*13b9f610SMax Laier static void
509*13b9f610SMax Laier connect_port_backchannel(void)
510*13b9f610SMax Laier {
511*13b9f610SMax Laier 	struct sockaddr_in listen_sa;
512*13b9f610SMax Laier 	socklen_t salen;
513*13b9f610SMax Laier 
514*13b9f610SMax Laier 	/*
515*13b9f610SMax Laier 	 * We are about to accept a connection from the server.
516*13b9f610SMax Laier 	 * This is a PORT or EPRT data connection.
517*13b9f610SMax Laier 	 */
518*13b9f610SMax Laier 	debuglog(2, "server listen socket ready");
519*13b9f610SMax Laier 
520*13b9f610SMax Laier 	close_server_data();
521*13b9f610SMax Laier 	close_client_data();
522*13b9f610SMax Laier 
523*13b9f610SMax Laier 	salen = sizeof(listen_sa);
524*13b9f610SMax Laier 	server_data_socket = accept(server_listen_socket,
525*13b9f610SMax Laier 	    (struct sockaddr *)&listen_sa, &salen);
526*13b9f610SMax Laier 	if (server_data_socket < 0) {
527*13b9f610SMax Laier 		syslog(LOG_NOTICE, "accept() failed (%m)");
528*13b9f610SMax Laier 		exit(EX_OSERR);
529*13b9f610SMax Laier 	}
530*13b9f610SMax Laier 	close(server_listen_socket);
531*13b9f610SMax Laier 	server_listen_socket = -1;
532*13b9f610SMax Laier 
533*13b9f610SMax Laier 	if (getuid() != 0)  {
534*13b9f610SMax Laier 		/*
535*13b9f610SMax Laier 		 * We're not running as root, so we get a backchannel
536*13b9f610SMax Laier 		 * socket bound in our designated range, instead of
537*13b9f610SMax Laier 		 * getting one bound to port 20 - This is deliberately
538*13b9f610SMax Laier 		 * not RFC compliant.
539*13b9f610SMax Laier 		 */
540*13b9f610SMax Laier 		bzero(&listen_sa.sin_addr, sizeof(struct in_addr));
541*13b9f610SMax Laier 		client_data_socket =  get_backchannel_socket(SOCK_STREAM,
542*13b9f610SMax Laier 		    min_port, max_port, -1, 1, &listen_sa);
543*13b9f610SMax Laier 		if (client_data_socket < 0) {
544*13b9f610SMax Laier 			syslog(LOG_NOTICE,  "get_backchannel_socket() failed (%m)");
545*13b9f610SMax Laier 			exit(EX_OSERR);
546*13b9f610SMax Laier 		}
547*13b9f610SMax Laier 
548*13b9f610SMax Laier 	} else {
549*13b9f610SMax Laier 
550*13b9f610SMax Laier 		/*
551*13b9f610SMax Laier 		 * We're root, get our backchannel socket bound to port
552*13b9f610SMax Laier 		 * 20 here, so we're fully RFC compliant.
553*13b9f610SMax Laier 		 */
554*13b9f610SMax Laier 		client_data_socket = socket(AF_INET, SOCK_STREAM, 0);
555*13b9f610SMax Laier 
556*13b9f610SMax Laier 		salen = 1;
557*13b9f610SMax Laier 		listen_sa.sin_family = AF_INET;
558*13b9f610SMax Laier 		bzero(&listen_sa.sin_addr, sizeof(struct in_addr));
559*13b9f610SMax Laier 		listen_sa.sin_port = htons(20);
560*13b9f610SMax Laier 
561*13b9f610SMax Laier 		if (setsockopt(client_data_socket, SOL_SOCKET, SO_REUSEADDR,
562*13b9f610SMax Laier 		    &salen, sizeof(salen)) == -1) {
563*13b9f610SMax Laier 			syslog(LOG_NOTICE, "setsockopt() failed (%m)");
564*13b9f610SMax Laier 			exit(EX_OSERR);
565*13b9f610SMax Laier 		}
566*13b9f610SMax Laier 
567*13b9f610SMax Laier 		if (bind(client_data_socket, (struct sockaddr *)&listen_sa,
568*13b9f610SMax Laier 		    sizeof(listen_sa)) == - 1) {
569*13b9f610SMax Laier 			syslog(LOG_NOTICE, "data channel bind() failed (%m)");
570*13b9f610SMax Laier 			exit(EX_OSERR);
571*13b9f610SMax Laier 		}
572*13b9f610SMax Laier 	}
573*13b9f610SMax Laier 
574*13b9f610SMax Laier 	if (connect(client_data_socket, (struct sockaddr *) &client_listen_sa,
575*13b9f610SMax Laier 	    sizeof(client_listen_sa)) != 0) {
576*13b9f610SMax Laier 		syslog(LOG_INFO, "cannot connect data channel (%m)");
577*13b9f610SMax Laier 		exit(EX_NOHOST);
578*13b9f610SMax Laier 	}
579*13b9f610SMax Laier 
580*13b9f610SMax Laier 	client_data_bytes = 0;
581*13b9f610SMax Laier 	server_data_bytes = 0;
582*13b9f610SMax Laier 	xfer_start_time = wallclock_time();
583*13b9f610SMax Laier }
584*13b9f610SMax Laier 
585*13b9f610SMax Laier void
586*13b9f610SMax Laier do_client_cmd(struct csiob *client, struct csiob *server)
587*13b9f610SMax Laier {
588*13b9f610SMax Laier 	int i, j, rv;
589*13b9f610SMax Laier 	char tbuf[100];
590*13b9f610SMax Laier 	char *sendbuf = NULL;
591*13b9f610SMax Laier 
592*13b9f610SMax Laier 	log_control_command((char *)client->line_buffer, 1);
593*13b9f610SMax Laier 
594*13b9f610SMax Laier 	/* client->line_buffer is an ftp control command.
595*13b9f610SMax Laier 	 * There is no reason for these to be very long.
596*13b9f610SMax Laier 	 * In the interest of limiting buffer overrun attempts,
597*13b9f610SMax Laier 	 * we catch them here.
598*13b9f610SMax Laier 	 */
599*13b9f610SMax Laier 	if (strlen((char *)client->line_buffer) > 512) {
600*13b9f610SMax Laier 		syslog(LOG_NOTICE, "excessively long control command");
601*13b9f610SMax Laier 		exit(EX_DATAERR);
602*13b9f610SMax Laier 	}
603*13b9f610SMax Laier 
604*13b9f610SMax Laier 	/*
605*13b9f610SMax Laier 	 * Check the client user provided if needed
606*13b9f610SMax Laier 	 */
607*13b9f610SMax Laier 	if (AnonFtpOnly && strncasecmp((char *)client->line_buffer, "user ",
608*13b9f610SMax Laier 	    strlen("user ")) == 0) {
609*13b9f610SMax Laier 		char *cp;
610*13b9f610SMax Laier 
611*13b9f610SMax Laier 		cp = (char *) client->line_buffer + strlen("user ");
612*13b9f610SMax Laier 		if ((strcasecmp(cp, "ftp\r\n") != 0) &&
613*13b9f610SMax Laier 		    (strcasecmp(cp, "anonymous\r\n") != 0)) {
614*13b9f610SMax Laier 			/*
615*13b9f610SMax Laier 			 * this isn't anonymous - give the client an
616*13b9f610SMax Laier 			 * error before they send a password
617*13b9f610SMax Laier 			 */
618*13b9f610SMax Laier 			snprintf(tbuf, sizeof(tbuf),
619*13b9f610SMax Laier 			    "500 Only anonymous FTP is allowed\r\n");
620*13b9f610SMax Laier 			j = 0;
621*13b9f610SMax Laier 			i = strlen(tbuf);
622*13b9f610SMax Laier 			do {
623*13b9f610SMax Laier 				rv = send(client->fd, tbuf + j, i - j, 0);
624*13b9f610SMax Laier 				if (rv == -1 && errno != EAGAIN &&
625*13b9f610SMax Laier 				    errno != EINTR)
626*13b9f610SMax Laier 					break;
627*13b9f610SMax Laier 				else if (rv != -1)
628*13b9f610SMax Laier 					j += rv;
629*13b9f610SMax Laier 			} while (j >= 0 && j < i);
630*13b9f610SMax Laier 			sendbuf = NULL;
631*13b9f610SMax Laier 		} else
632*13b9f610SMax Laier 			sendbuf = (char *)client->line_buffer;
633*13b9f610SMax Laier 	} else if ((strncasecmp((char *)client->line_buffer, "eprt ",
634*13b9f610SMax Laier 	    strlen("eprt ")) == 0)) {
635*13b9f610SMax Laier 
636*13b9f610SMax Laier 		/* Watch out for EPRT commands */
637*13b9f610SMax Laier 		char *line = NULL,  *q, *p, *result[3], delim;
638*13b9f610SMax Laier 		struct addrinfo hints, *res = NULL;
639*13b9f610SMax Laier 		unsigned long proto;
640*13b9f610SMax Laier 
641*13b9f610SMax Laier 		j = 0;
642*13b9f610SMax Laier 		line = strdup((char *)client->line_buffer+strlen("eprt "));
643*13b9f610SMax Laier 		if (line == NULL) {
644*13b9f610SMax Laier 			syslog(LOG_ERR, "insufficient memory");
645*13b9f610SMax Laier 			exit(EX_UNAVAILABLE);
646*13b9f610SMax Laier 		}
647*13b9f610SMax Laier 		p = line;
648*13b9f610SMax Laier 		delim = p[0];
649*13b9f610SMax Laier 		p++;
650*13b9f610SMax Laier 
651*13b9f610SMax Laier 		memset(result,0, sizeof(result));
652*13b9f610SMax Laier 		for (i = 0; i < 3; i++) {
653*13b9f610SMax Laier 			q = strchr(p, delim);
654*13b9f610SMax Laier 			if (!q || *q != delim)
655*13b9f610SMax Laier 				goto parsefail;
656*13b9f610SMax Laier 			*q++ = '\0';
657*13b9f610SMax Laier 			result[i] = p;
658*13b9f610SMax Laier 			p = q;
659*13b9f610SMax Laier 		}
660*13b9f610SMax Laier 
661*13b9f610SMax Laier 		proto = strtoul(result[0], &p, 10);
662*13b9f610SMax Laier 		if (!*result[0] || *p)
663*13b9f610SMax Laier 			goto protounsupp;
664*13b9f610SMax Laier 
665*13b9f610SMax Laier 		memset(&hints, 0, sizeof(hints));
666*13b9f610SMax Laier 		if (proto != 1) /* 1 == AF_INET - all we support for now */
667*13b9f610SMax Laier 			goto protounsupp;
668*13b9f610SMax Laier 		hints.ai_family = AF_INET;
669*13b9f610SMax Laier 		hints.ai_socktype = SOCK_STREAM;
670*13b9f610SMax Laier 		hints.ai_flags = AI_NUMERICHOST;	/*no DNS*/
671*13b9f610SMax Laier 		if (getaddrinfo(result[1], result[2], &hints, &res))
672*13b9f610SMax Laier 			goto parsefail;
673*13b9f610SMax Laier 		if (res->ai_next)
674*13b9f610SMax Laier 			goto parsefail;
675*13b9f610SMax Laier 		if (sizeof(client_listen_sa) < res->ai_addrlen)
676*13b9f610SMax Laier 			goto parsefail;
677*13b9f610SMax Laier 		memcpy(&client_listen_sa, res->ai_addr, res->ai_addrlen);
678*13b9f610SMax Laier 
679*13b9f610SMax Laier 		debuglog(1, "client wants us to use %s:%u",
680*13b9f610SMax Laier 		    inet_ntoa(client_listen_sa.sin_addr),
681*13b9f610SMax Laier 		    htons(client_listen_sa.sin_port));
682*13b9f610SMax Laier 
683*13b9f610SMax Laier 		/*
684*13b9f610SMax Laier 		 * Configure our own listen socket and tell the server about it
685*13b9f610SMax Laier 		 */
686*13b9f610SMax Laier 		new_dataconn(1);
687*13b9f610SMax Laier 		connection_mode = EPRT_MODE;
688*13b9f610SMax Laier 
689*13b9f610SMax Laier 		debuglog(1, "we want server to use %s:%u",
690*13b9f610SMax Laier 		    inet_ntoa(server->sa.sin_addr),
691*13b9f610SMax Laier 		    ntohs(server_listen_sa.sin_port));
692*13b9f610SMax Laier 
693*13b9f610SMax Laier 		snprintf(tbuf, sizeof(tbuf), "EPRT |%d|%s|%u|\r\n", 1,
694*13b9f610SMax Laier 		    inet_ntoa(server->sa.sin_addr),
695*13b9f610SMax Laier 		    ntohs(server_listen_sa.sin_port));
696*13b9f610SMax Laier 		debuglog(1, "to server (modified): %s", tbuf);
697*13b9f610SMax Laier 		sendbuf = tbuf;
698*13b9f610SMax Laier 		goto out;
699*13b9f610SMax Laier parsefail:
700*13b9f610SMax Laier 		snprintf(tbuf, sizeof(tbuf),
701*13b9f610SMax Laier 		    "500 Invalid argument; rejected\r\n");
702*13b9f610SMax Laier 		sendbuf = NULL;
703*13b9f610SMax Laier 		goto out;
704*13b9f610SMax Laier protounsupp:
705*13b9f610SMax Laier 		/* we only support AF_INET for now */
706*13b9f610SMax Laier 		if (proto == 2)
707*13b9f610SMax Laier 			snprintf(tbuf, sizeof(tbuf),
708*13b9f610SMax Laier 			    "522 Protocol not supported, use (1)\r\n");
709*13b9f610SMax Laier 		else
710*13b9f610SMax Laier 			snprintf(tbuf, sizeof(tbuf),
711*13b9f610SMax Laier 			    "501 Protocol not supported\r\n");
712*13b9f610SMax Laier 		sendbuf = NULL;
713*13b9f610SMax Laier out:
714*13b9f610SMax Laier 		if (line)
715*13b9f610SMax Laier 			free(line);
716*13b9f610SMax Laier 		if (res)
717*13b9f610SMax Laier 			freeaddrinfo(res);
718*13b9f610SMax Laier 		if (sendbuf == NULL) {
719*13b9f610SMax Laier 			debuglog(1, "to client (modified): %s", tbuf);
720*13b9f610SMax Laier 			i = strlen(tbuf);
721*13b9f610SMax Laier 			do {
722*13b9f610SMax Laier 				rv = send(client->fd, tbuf + j, i - j, 0);
723*13b9f610SMax Laier 				if (rv == -1 && errno != EAGAIN &&
724*13b9f610SMax Laier 				    errno != EINTR)
725*13b9f610SMax Laier 					break;
726*13b9f610SMax Laier 				else if (rv != -1)
727*13b9f610SMax Laier 					j += rv;
728*13b9f610SMax Laier 			} while (j >= 0 && j < i);
729*13b9f610SMax Laier 		}
730*13b9f610SMax Laier 	} else if (!NatMode && (strncasecmp((char *)client->line_buffer,
731*13b9f610SMax Laier 	    "epsv", strlen("epsv")) == 0)) {
732*13b9f610SMax Laier 
733*13b9f610SMax Laier 		/*
734*13b9f610SMax Laier 		 * If we aren't in NAT mode, deal with EPSV.
735*13b9f610SMax Laier 		 * EPSV is a problem - Unlike PASV, the reply from the
736*13b9f610SMax Laier 		 * server contains *only* a port, we can't modify the reply
737*13b9f610SMax Laier 		 * to the client and get the client to connect to us without
738*13b9f610SMax Laier 		 * resorting to using a dynamic rdr rule we have to add in
739*13b9f610SMax Laier 		 * for the reply to this connection, and take away afterwards.
740*13b9f610SMax Laier 		 * so this will wait until we have the right solution for rule
741*13b9f610SMax Laier 		 * additions/deletions in pf.
742*13b9f610SMax Laier 		 *
743*13b9f610SMax Laier 		 * in the meantime we just tell the client we don't do it,
744*13b9f610SMax Laier 		 * and most clients should fall back to using PASV.
745*13b9f610SMax Laier 		 */
746*13b9f610SMax Laier 
747*13b9f610SMax Laier 		snprintf(tbuf, sizeof(tbuf),
748*13b9f610SMax Laier 		    "500 EPSV command not understood\r\n");
749*13b9f610SMax Laier 		debuglog(1, "to client (modified): %s", tbuf);
750*13b9f610SMax Laier 		j = 0;
751*13b9f610SMax Laier 		i = strlen(tbuf);
752*13b9f610SMax Laier 		do {
753*13b9f610SMax Laier 			rv = send(client->fd, tbuf + j, i - j, 0);
754*13b9f610SMax Laier 			if (rv == -1 && errno != EAGAIN && errno != EINTR)
755*13b9f610SMax Laier 				break;
756*13b9f610SMax Laier 			else if (rv != -1)
757*13b9f610SMax Laier 				j += rv;
758*13b9f610SMax Laier 		} while (j >= 0 && j < i);
759*13b9f610SMax Laier 		sendbuf = NULL;
760*13b9f610SMax Laier 	} else if (strncasecmp((char *)client->line_buffer, "port ",
761*13b9f610SMax Laier 	    strlen("port ")) == 0) {
762*13b9f610SMax Laier 		unsigned int values[6];
763*13b9f610SMax Laier 		char *tailptr;
764*13b9f610SMax Laier 
765*13b9f610SMax Laier 		debuglog(1, "Got a PORT command");
766*13b9f610SMax Laier 
767*13b9f610SMax Laier 		tailptr = (char *)&client->line_buffer[strlen("port ")];
768*13b9f610SMax Laier 		values[0] = 0;
769*13b9f610SMax Laier 
770*13b9f610SMax Laier 		i = sscanf(tailptr, "%u,%u,%u,%u,%u,%u", &values[0],
771*13b9f610SMax Laier 		    &values[1], &values[2], &values[3], &values[4],
772*13b9f610SMax Laier 		    &values[5]);
773*13b9f610SMax Laier 		if (i != 6) {
774*13b9f610SMax Laier 			syslog(LOG_INFO, "malformed PORT command (%s)",
775*13b9f610SMax Laier 			    client->line_buffer);
776*13b9f610SMax Laier 			exit(EX_DATAERR);
777*13b9f610SMax Laier 		}
778*13b9f610SMax Laier 
779*13b9f610SMax Laier 		for (i = 0; i<6; i++) {
780*13b9f610SMax Laier 			if (values[i] > 255) {
781*13b9f610SMax Laier 				syslog(LOG_INFO,
782*13b9f610SMax Laier 				    "malformed PORT command (%s)",
783*13b9f610SMax Laier 				    client->line_buffer);
784*13b9f610SMax Laier 				exit(EX_DATAERR);
785*13b9f610SMax Laier 			}
786*13b9f610SMax Laier 		}
787*13b9f610SMax Laier 
788*13b9f610SMax Laier 		client_listen_sa.sin_family = AF_INET;
789*13b9f610SMax Laier 		client_listen_sa.sin_addr.s_addr = htonl((values[0] << 24) |
790*13b9f610SMax Laier 		    (values[1] << 16) | (values[2] <<  8) |
791*13b9f610SMax Laier 		    (values[3] <<  0));
792*13b9f610SMax Laier 
793*13b9f610SMax Laier 		client_listen_sa.sin_port = htons((values[4] << 8) |
794*13b9f610SMax Laier 		    values[5]);
795*13b9f610SMax Laier 		debuglog(1, "client wants us to use %u.%u.%u.%u:%u",
796*13b9f610SMax Laier 		    values[0], values[1], values[2], values[3],
797*13b9f610SMax Laier 		    (values[4] << 8) | values[5]);
798*13b9f610SMax Laier 
799*13b9f610SMax Laier 		/*
800*13b9f610SMax Laier 		 * Configure our own listen socket and tell the server about it
801*13b9f610SMax Laier 		 */
802*13b9f610SMax Laier 		new_dataconn(1);
803*13b9f610SMax Laier 		connection_mode = PORT_MODE;
804*13b9f610SMax Laier 
805*13b9f610SMax Laier 		debuglog(1, "we want server to use %s:%u",
806*13b9f610SMax Laier 		    inet_ntoa(server->sa.sin_addr),
807*13b9f610SMax Laier 		    ntohs(server_listen_sa.sin_port));
808*13b9f610SMax Laier 
809*13b9f610SMax Laier 		snprintf(tbuf, sizeof(tbuf), "PORT %u,%u,%u,%u,%u,%u\r\n",
810*13b9f610SMax Laier 		    ((u_char *)&server->sa.sin_addr.s_addr)[0],
811*13b9f610SMax Laier 		    ((u_char *)&server->sa.sin_addr.s_addr)[1],
812*13b9f610SMax Laier 		    ((u_char *)&server->sa.sin_addr.s_addr)[2],
813*13b9f610SMax Laier 		    ((u_char *)&server->sa.sin_addr.s_addr)[3],
814*13b9f610SMax Laier 		    ((u_char *)&server_listen_sa.sin_port)[0],
815*13b9f610SMax Laier 		    ((u_char *)&server_listen_sa.sin_port)[1]);
816*13b9f610SMax Laier 
817*13b9f610SMax Laier 		debuglog(1, "to server (modified): %s", tbuf);
818*13b9f610SMax Laier 
819*13b9f610SMax Laier 		sendbuf = tbuf;
820*13b9f610SMax Laier 	} else
821*13b9f610SMax Laier 		sendbuf = (char *)client->line_buffer;
822*13b9f610SMax Laier 
823*13b9f610SMax Laier 	/*
824*13b9f610SMax Laier 	 *send our (possibly modified) control command in sendbuf
825*13b9f610SMax Laier 	 * on it's way to the server
826*13b9f610SMax Laier 	 */
827*13b9f610SMax Laier 	if (sendbuf != NULL) {
828*13b9f610SMax Laier 		j = 0;
829*13b9f610SMax Laier 		i = strlen(sendbuf);
830*13b9f610SMax Laier 		do {
831*13b9f610SMax Laier 			rv = send(server->fd, sendbuf + j, i - j, 0);
832*13b9f610SMax Laier 			if (rv == -1 && errno != EAGAIN && errno != EINTR)
833*13b9f610SMax Laier 				break;
834*13b9f610SMax Laier 			else if (rv != -1)
835*13b9f610SMax Laier 				j += rv;
836*13b9f610SMax Laier 		} while (j >= 0 && j < i);
837*13b9f610SMax Laier 	}
838*13b9f610SMax Laier }
839*13b9f610SMax Laier 
840*13b9f610SMax Laier void
841*13b9f610SMax Laier do_server_reply(struct csiob *server, struct csiob *client)
842*13b9f610SMax Laier {
843*13b9f610SMax Laier 	int code, i, j, rv;
844*13b9f610SMax Laier 	struct in_addr *iap;
845*13b9f610SMax Laier 	static int continuing = 0;
846*13b9f610SMax Laier 	char tbuf[100], *sendbuf, *p;
847*13b9f610SMax Laier 
848*13b9f610SMax Laier 	log_control_command((char *)server->line_buffer, 0);
849*13b9f610SMax Laier 
850*13b9f610SMax Laier 	if (strlen((char *)server->line_buffer) > 512) {
851*13b9f610SMax Laier 		/*
852*13b9f610SMax Laier 		 * someone's playing games. Have a cow in the syslogs and
853*13b9f610SMax Laier 		 * exit - we don't pass this on for fear of hurting
854*13b9f610SMax Laier 		 * our other end, which might be poorly implemented.
855*13b9f610SMax Laier 		 */
856*13b9f610SMax Laier 		syslog(LOG_NOTICE, "long FTP control reply");
857*13b9f610SMax Laier 		exit(EX_DATAERR);
858*13b9f610SMax Laier 	}
859*13b9f610SMax Laier 
860*13b9f610SMax Laier 	/*
861*13b9f610SMax Laier 	 * Watch out for "227 Entering Passive Mode ..." replies
862*13b9f610SMax Laier 	 */
863*13b9f610SMax Laier 	code = strtol((char *)server->line_buffer, &p, 10);
864*13b9f610SMax Laier 	if (isspace(server->line_buffer[0]))
865*13b9f610SMax Laier 		code = 0;
866*13b9f610SMax Laier 	if (!*(server->line_buffer) || (*p != ' ' && *p != '-')) {
867*13b9f610SMax Laier 		if (continuing)
868*13b9f610SMax Laier 			goto sendit;
869*13b9f610SMax Laier 		syslog(LOG_INFO, "malformed control reply");
870*13b9f610SMax Laier 		exit(EX_DATAERR);
871*13b9f610SMax Laier 	}
872*13b9f610SMax Laier 	if (code <= 0 || code > 999) {
873*13b9f610SMax Laier 		if (continuing)
874*13b9f610SMax Laier 			goto sendit;
875*13b9f610SMax Laier 		syslog(LOG_INFO, "invalid server reply code %d", code);
876*13b9f610SMax Laier 		exit(EX_DATAERR);
877*13b9f610SMax Laier 	}
878*13b9f610SMax Laier 	if (*p == '-')
879*13b9f610SMax Laier 		continuing = 1;
880*13b9f610SMax Laier 	else
881*13b9f610SMax Laier 		continuing = 0;
882*13b9f610SMax Laier 	if (code == 227 && !NatMode) {
883*13b9f610SMax Laier 		unsigned int values[6];
884*13b9f610SMax Laier 		char *tailptr;
885*13b9f610SMax Laier 
886*13b9f610SMax Laier 		debuglog(1, "Got a PASV reply");
887*13b9f610SMax Laier 		debuglog(1, "{%s}", (char *)server->line_buffer);
888*13b9f610SMax Laier 
889*13b9f610SMax Laier 		tailptr = (char *)strchr((char *)server->line_buffer, '(');
890*13b9f610SMax Laier 		if (tailptr == NULL) {
891*13b9f610SMax Laier 			tailptr = strrchr((char *)server->line_buffer, ' ');
892*13b9f610SMax Laier 			if (tailptr == NULL) {
893*13b9f610SMax Laier 				syslog(LOG_NOTICE, "malformed 227 reply");
894*13b9f610SMax Laier 				exit(EX_DATAERR);
895*13b9f610SMax Laier 			}
896*13b9f610SMax Laier 		}
897*13b9f610SMax Laier 		tailptr++; /* skip past space or ( */
898*13b9f610SMax Laier 
899*13b9f610SMax Laier 		values[0] = 0;
900*13b9f610SMax Laier 
901*13b9f610SMax Laier 		i = sscanf(tailptr, "%u,%u,%u,%u,%u,%u", &values[0],
902*13b9f610SMax Laier 		    &values[1], &values[2], &values[3], &values[4],
903*13b9f610SMax Laier 		    &values[5]);
904*13b9f610SMax Laier 		if (i != 6) {
905*13b9f610SMax Laier 			syslog(LOG_INFO, "malformed PASV reply (%s)",
906*13b9f610SMax Laier 			    client->line_buffer);
907*13b9f610SMax Laier 			exit(EX_DATAERR);
908*13b9f610SMax Laier 		}
909*13b9f610SMax Laier 		for (i = 0; i<6; i++)
910*13b9f610SMax Laier 			if (values[i] > 255) {
911*13b9f610SMax Laier 				syslog(LOG_INFO, "malformed PASV reply(%s)",
912*13b9f610SMax Laier 				    client->line_buffer);
913*13b9f610SMax Laier 				exit(EX_DATAERR);
914*13b9f610SMax Laier 			}
915*13b9f610SMax Laier 
916*13b9f610SMax Laier 		server_listen_sa.sin_family = AF_INET;
917*13b9f610SMax Laier 		server_listen_sa.sin_addr.s_addr = htonl((values[0] << 24) |
918*13b9f610SMax Laier 		    (values[1] << 16) | (values[2] <<  8) | (values[3] <<  0));
919*13b9f610SMax Laier 		server_listen_sa.sin_port = htons((values[4] << 8) |
920*13b9f610SMax Laier 		    values[5]);
921*13b9f610SMax Laier 
922*13b9f610SMax Laier 		debuglog(1, "server wants us to use %s:%u",
923*13b9f610SMax Laier 		    inet_ntoa(server_listen_sa.sin_addr), (values[4] << 8) |
924*13b9f610SMax Laier 		    values[5]);
925*13b9f610SMax Laier 
926*13b9f610SMax Laier 		new_dataconn(0);
927*13b9f610SMax Laier 		connection_mode = PASV_MODE;
928*13b9f610SMax Laier 		iap = &(server->sa.sin_addr);
929*13b9f610SMax Laier 
930*13b9f610SMax Laier 		debuglog(1, "we want client to use %s:%u", inet_ntoa(*iap),
931*13b9f610SMax Laier 		    htons(client_listen_sa.sin_port));
932*13b9f610SMax Laier 
933*13b9f610SMax Laier 		snprintf(tbuf, sizeof(tbuf),
934*13b9f610SMax Laier 		    "227 Entering Passive Mode (%u,%u,%u,%u,%u,%u)\r\n",
935*13b9f610SMax Laier 		    ((u_char *)iap)[0], ((u_char *)iap)[1],
936*13b9f610SMax Laier 		    ((u_char *)iap)[2], ((u_char *)iap)[3],
937*13b9f610SMax Laier 		    ((u_char *)&client_listen_sa.sin_port)[0],
938*13b9f610SMax Laier 		    ((u_char *)&client_listen_sa.sin_port)[1]);
939*13b9f610SMax Laier 		debuglog(1, "to client (modified): %s", tbuf);
940*13b9f610SMax Laier 		sendbuf = tbuf;
941*13b9f610SMax Laier 	} else {
942*13b9f610SMax Laier  sendit:
943*13b9f610SMax Laier 		sendbuf = (char *)server->line_buffer;
944*13b9f610SMax Laier 	}
945*13b9f610SMax Laier 
946*13b9f610SMax Laier 	/*
947*13b9f610SMax Laier 	 * send our (possibly modified) control command in sendbuf
948*13b9f610SMax Laier 	 * on it's way to the client
949*13b9f610SMax Laier 	 */
950*13b9f610SMax Laier 	j = 0;
951*13b9f610SMax Laier 	i = strlen(sendbuf);
952*13b9f610SMax Laier 	do {
953*13b9f610SMax Laier 		rv = send(client->fd, sendbuf + j, i - j, 0);
954*13b9f610SMax Laier 		if (rv == -1 && errno != EAGAIN && errno != EINTR)
955*13b9f610SMax Laier 			break;
956*13b9f610SMax Laier 		else if (rv != -1)
957*13b9f610SMax Laier 			j += rv;
958*13b9f610SMax Laier 	} while (j >= 0 && j < i);
959*13b9f610SMax Laier 
960*13b9f610SMax Laier }
961*13b9f610SMax Laier 
962*13b9f610SMax Laier int
963*13b9f610SMax Laier main(int argc, char *argv[])
964*13b9f610SMax Laier {
965*13b9f610SMax Laier 	struct csiob client_iob, server_iob;
966*13b9f610SMax Laier 	struct sigaction new_sa, old_sa;
967*13b9f610SMax Laier 	int sval, ch, flags, i;
968*13b9f610SMax Laier 	socklen_t salen;
969*13b9f610SMax Laier 	int one = 1;
970*13b9f610SMax Laier 	long timeout_seconds = 0;
971*13b9f610SMax Laier 	struct timeval tv;
972*13b9f610SMax Laier #ifdef LIBWRAP
973*13b9f610SMax Laier 	int use_tcpwrapper = 0;
974*13b9f610SMax Laier #endif /* LIBWRAP */
975*13b9f610SMax Laier 
976*13b9f610SMax Laier 	while ((ch = getopt(argc, argv, "D:g:m:M:t:u:AnVwr")) != -1) {
977*13b9f610SMax Laier 		char *p;
978*13b9f610SMax Laier 		switch (ch) {
979*13b9f610SMax Laier 		case 'A':
980*13b9f610SMax Laier 			AnonFtpOnly = 1; /* restrict to anon usernames only */
981*13b9f610SMax Laier 			break;
982*13b9f610SMax Laier 		case 'D':
983*13b9f610SMax Laier 			Debug_Level = strtol(optarg, &p, 10);
984*13b9f610SMax Laier 			if (!*optarg || *p)
985*13b9f610SMax Laier 				usage();
986*13b9f610SMax Laier 			break;
987*13b9f610SMax Laier 		case 'g':
988*13b9f610SMax Laier 			Group = optarg;
989*13b9f610SMax Laier 			break;
990*13b9f610SMax Laier 		case 'm':
991*13b9f610SMax Laier 			min_port = strtol(optarg, &p, 10);
992*13b9f610SMax Laier 			if (!*optarg || *p)
993*13b9f610SMax Laier 				usage();
994*13b9f610SMax Laier 			if (min_port < 0 || min_port > USHRT_MAX)
995*13b9f610SMax Laier 				usage();
996*13b9f610SMax Laier 			break;
997*13b9f610SMax Laier 		case 'M':
998*13b9f610SMax Laier 			max_port = strtol(optarg, &p, 10);
999*13b9f610SMax Laier 			if (!*optarg || *p)
1000*13b9f610SMax Laier 				usage();
1001*13b9f610SMax Laier 			if (max_port < 0 || max_port > USHRT_MAX)
1002*13b9f610SMax Laier 				usage();
1003*13b9f610SMax Laier 			break;
1004*13b9f610SMax Laier 		case 'n':
1005*13b9f610SMax Laier 			NatMode = 1; /* pass all passives, we're using NAT */
1006*13b9f610SMax Laier 			break;
1007*13b9f610SMax Laier 		case 'r':
1008*13b9f610SMax Laier 			Use_Rdns = 1; /* look up hostnames */
1009*13b9f610SMax Laier 			break;
1010*13b9f610SMax Laier 		case 't':
1011*13b9f610SMax Laier 			timeout_seconds = strtol(optarg, &p, 10);
1012*13b9f610SMax Laier 			if (!*optarg || *p)
1013*13b9f610SMax Laier 				usage();
1014*13b9f610SMax Laier 			break;
1015*13b9f610SMax Laier 		case 'u':
1016*13b9f610SMax Laier 			User = optarg;
1017*13b9f610SMax Laier 			break;
1018*13b9f610SMax Laier 		case 'V':
1019*13b9f610SMax Laier 			Verbose = 1;
1020*13b9f610SMax Laier 			break;
1021*13b9f610SMax Laier #ifdef LIBWRAP
1022*13b9f610SMax Laier 		case 'w':
1023*13b9f610SMax Laier 			use_tcpwrapper = 1; /* do the libwrap thing */
1024*13b9f610SMax Laier 			break;
1025*13b9f610SMax Laier #endif /* LIBWRAP */
1026*13b9f610SMax Laier 		default:
1027*13b9f610SMax Laier 			usage();
1028*13b9f610SMax Laier 			/* NOTREACHED */
1029*13b9f610SMax Laier 		}
1030*13b9f610SMax Laier 	}
1031*13b9f610SMax Laier 	argc -= optind;
1032*13b9f610SMax Laier 	argv += optind;
1033*13b9f610SMax Laier 
1034*13b9f610SMax Laier 	if (max_port < min_port)
1035*13b9f610SMax Laier 		usage();
1036*13b9f610SMax Laier 
1037*13b9f610SMax Laier 	openlog(__progname, LOG_NDELAY|LOG_PID, LOG_DAEMON);
1038*13b9f610SMax Laier 
1039*13b9f610SMax Laier 	setlinebuf(stdout);
1040*13b9f610SMax Laier 	setlinebuf(stderr);
1041*13b9f610SMax Laier 
1042*13b9f610SMax Laier 	memset(&client_iob, 0, sizeof(client_iob));
1043*13b9f610SMax Laier 	memset(&server_iob, 0, sizeof(server_iob));
1044*13b9f610SMax Laier 
1045*13b9f610SMax Laier 	if (get_proxy_env(0, &real_server_sa, &client_iob.sa) == -1)
1046*13b9f610SMax Laier 		exit(EX_PROTOCOL);
1047*13b9f610SMax Laier 
1048*13b9f610SMax Laier 	/*
1049*13b9f610SMax Laier 	 * We may now drop root privs, as we have done our ioctl for
1050*13b9f610SMax Laier 	 * pf. If we do drop root, we can't make backchannel connections
1051*13b9f610SMax Laier 	 * for PORT and EPRT come from port 20, which is not strictly
1052*13b9f610SMax Laier 	 * RFC compliant. This shouldn't cause problems for all but
1053*13b9f610SMax Laier 	 * the stupidest ftp clients and the stupidest packet filters.
1054*13b9f610SMax Laier 	 */
1055*13b9f610SMax Laier 	drop_privs();
1056*13b9f610SMax Laier 
1057*13b9f610SMax Laier 	/*
1058*13b9f610SMax Laier 	 * We check_host after get_proxy_env so that checks are done
1059*13b9f610SMax Laier 	 * against the original destination endpoint, not the endpoint
1060*13b9f610SMax Laier 	 * of our side of the rdr. This allows the use of tcpwrapper
1061*13b9f610SMax Laier 	 * rules to restrict destinations as well as sources of connections
1062*13b9f610SMax Laier 	 * for ftp.
1063*13b9f610SMax Laier 	 */
1064*13b9f610SMax Laier 	if (Use_Rdns)
1065*13b9f610SMax Laier 		flags = 0;
1066*13b9f610SMax Laier 	else
1067*13b9f610SMax Laier 		flags = NI_NUMERICHOST | NI_NUMERICSERV;
1068*13b9f610SMax Laier 
1069*13b9f610SMax Laier 	i = getnameinfo((struct sockaddr *)&client_iob.sa,
1070*13b9f610SMax Laier 	    sizeof(client_iob.sa), ClientName, sizeof(ClientName), NULL, 0,
1071*13b9f610SMax Laier 	    flags);
1072*13b9f610SMax Laier 
1073*13b9f610SMax Laier 	if (i != 0 && i != EAI_NONAME && i != EAI_AGAIN) {
1074*13b9f610SMax Laier 		debuglog(2, "name resolution failure (client)");
1075*13b9f610SMax Laier 		exit(EX_OSERR);
1076*13b9f610SMax Laier 	}
1077*13b9f610SMax Laier 
1078*13b9f610SMax Laier 	i = getnameinfo((struct sockaddr *)&real_server_sa,
1079*13b9f610SMax Laier 	    sizeof(real_server_sa), RealServerName, sizeof(RealServerName),
1080*13b9f610SMax Laier 	    NULL, 0, flags);
1081*13b9f610SMax Laier 
1082*13b9f610SMax Laier 	if (i != 0 && i != EAI_NONAME && i != EAI_AGAIN) {
1083*13b9f610SMax Laier 		debuglog(2, "name resolution failure (server)");
1084*13b9f610SMax Laier 		exit(EX_OSERR);
1085*13b9f610SMax Laier 	}
1086*13b9f610SMax Laier 
1087*13b9f610SMax Laier #ifdef LIBWRAP
1088*13b9f610SMax Laier 	if (use_tcpwrapper && !check_host(&client_iob.sa, &real_server_sa))
1089*13b9f610SMax Laier 		exit(EX_NOPERM);
1090*13b9f610SMax Laier #endif
1091*13b9f610SMax Laier 
1092*13b9f610SMax Laier 	client_iob.fd = 0;
1093*13b9f610SMax Laier 
1094*13b9f610SMax Laier 	syslog(LOG_INFO, "accepted connection from %s:%u to %s:%u", ClientName,
1095*13b9f610SMax Laier 		ntohs(client_iob.sa.sin_port), RealServerName,
1096*13b9f610SMax Laier 		ntohs(real_server_sa.sin_port));
1097*13b9f610SMax Laier 
1098*13b9f610SMax Laier 	server_iob.fd = get_backchannel_socket(SOCK_STREAM, min_port, max_port,
1099*13b9f610SMax Laier 	    -1,	1, &server_iob.sa);
1100*13b9f610SMax Laier 
1101*13b9f610SMax Laier 	if (connect(server_iob.fd, (struct sockaddr *)&real_server_sa,
1102*13b9f610SMax Laier 	    sizeof(real_server_sa)) != 0) {
1103*13b9f610SMax Laier 		syslog(LOG_INFO, "cannot connect to %s:%u (%m)", RealServerName,
1104*13b9f610SMax Laier 		    ntohs(real_server_sa.sin_port));
1105*13b9f610SMax Laier 		exit(EX_NOHOST);
1106*13b9f610SMax Laier 	}
1107*13b9f610SMax Laier 
1108*13b9f610SMax Laier 	/*
1109*13b9f610SMax Laier 	 * Now that we are connected to the real server, get the name
1110*13b9f610SMax Laier 	 * of our end of the server socket so we know our IP address
1111*13b9f610SMax Laier 	 * from the real server's perspective.
1112*13b9f610SMax Laier 	 */
1113*13b9f610SMax Laier 	salen = sizeof(server_iob.sa);
1114*13b9f610SMax Laier 	getsockname(server_iob.fd, (struct sockaddr *)&server_iob.sa, &salen);
1115*13b9f610SMax Laier 
1116*13b9f610SMax Laier 	i = getnameinfo((struct sockaddr *)&server_iob.sa,
1117*13b9f610SMax Laier 	    sizeof(server_iob.sa), OurName, sizeof(OurName), NULL, 0, flags);
1118*13b9f610SMax Laier 
1119*13b9f610SMax Laier 	if (i != 0 && i != EAI_NONAME && i != EAI_AGAIN) {
1120*13b9f610SMax Laier 		debuglog(2, "name resolution failure (local)");
1121*13b9f610SMax Laier 		exit(EX_OSERR);
1122*13b9f610SMax Laier 	}
1123*13b9f610SMax Laier 
1124*13b9f610SMax Laier 	debuglog(1, "local socket is %s:%u", OurName,
1125*13b9f610SMax Laier 	    ntohs(server_iob.sa.sin_port));
1126*13b9f610SMax Laier 
1127*13b9f610SMax Laier 	/* ignore SIGPIPE */
1128*13b9f610SMax Laier 	bzero(&new_sa, sizeof(new_sa));
1129*13b9f610SMax Laier 	new_sa.sa_handler = SIG_IGN;
1130*13b9f610SMax Laier 	(void)sigemptyset(&new_sa.sa_mask);
1131*13b9f610SMax Laier 	new_sa.sa_flags = SA_RESTART;
1132*13b9f610SMax Laier 	if (sigaction(SIGPIPE, &new_sa, &old_sa) != 0) {
1133*13b9f610SMax Laier 		syslog(LOG_ERR, "sigaction() failed (%m)");
1134*13b9f610SMax Laier 		exit(EX_OSERR);
1135*13b9f610SMax Laier 	}
1136*13b9f610SMax Laier 
1137*13b9f610SMax Laier 	if (setsockopt(client_iob.fd, SOL_SOCKET, SO_OOBINLINE, (char *)&one,
1138*13b9f610SMax Laier 	    sizeof(one)) == -1) {
1139*13b9f610SMax Laier 		syslog(LOG_NOTICE, "cannot set SO_OOBINLINE (%m)");
1140*13b9f610SMax Laier 		exit(EX_OSERR);
1141*13b9f610SMax Laier 	}
1142*13b9f610SMax Laier 
1143*13b9f610SMax Laier 	client_iob.line_buffer_size = STARTBUFSIZE;
1144*13b9f610SMax Laier 	client_iob.line_buffer = malloc(client_iob.line_buffer_size);
1145*13b9f610SMax Laier 	client_iob.io_buffer_size = STARTBUFSIZE;
1146*13b9f610SMax Laier 	client_iob.io_buffer = malloc(client_iob.io_buffer_size);
1147*13b9f610SMax Laier 	client_iob.next_byte = 0;
1148*13b9f610SMax Laier 	client_iob.io_buffer_len = 0;
1149*13b9f610SMax Laier 	client_iob.alive = 1;
1150*13b9f610SMax Laier 	client_iob.who = "client";
1151*13b9f610SMax Laier 	client_iob.send_oob_flags = 0;
1152*13b9f610SMax Laier 	client_iob.real_sa = client_iob.sa;
1153*13b9f610SMax Laier 
1154*13b9f610SMax Laier 	server_iob.line_buffer_size = STARTBUFSIZE;
1155*13b9f610SMax Laier 	server_iob.line_buffer = malloc(server_iob.line_buffer_size);
1156*13b9f610SMax Laier 	server_iob.io_buffer_size = STARTBUFSIZE;
1157*13b9f610SMax Laier 	server_iob.io_buffer = malloc(server_iob.io_buffer_size);
1158*13b9f610SMax Laier 	server_iob.next_byte = 0;
1159*13b9f610SMax Laier 	server_iob.io_buffer_len = 0;
1160*13b9f610SMax Laier 	server_iob.alive = 1;
1161*13b9f610SMax Laier 	server_iob.who = "server";
1162*13b9f610SMax Laier 	server_iob.send_oob_flags = MSG_OOB;
1163*13b9f610SMax Laier 	server_iob.real_sa = real_server_sa;
1164*13b9f610SMax Laier 
1165*13b9f610SMax Laier 	if (client_iob.line_buffer == NULL || client_iob.io_buffer == NULL ||
1166*13b9f610SMax Laier 	    server_iob.line_buffer == NULL || server_iob.io_buffer == NULL) {
1167*13b9f610SMax Laier 		syslog (LOG_NOTICE, "insufficient memory");
1168*13b9f610SMax Laier 		exit(EX_UNAVAILABLE);
1169*13b9f610SMax Laier 	}
1170*13b9f610SMax Laier 
1171*13b9f610SMax Laier 	while (client_iob.alive || server_iob.alive) {
1172*13b9f610SMax Laier 		int maxfd = 0;
1173*13b9f610SMax Laier 		fd_set *fdsp;
1174*13b9f610SMax Laier 
1175*13b9f610SMax Laier 		if (client_iob.fd > maxfd)
1176*13b9f610SMax Laier 			maxfd = client_iob.fd;
1177*13b9f610SMax Laier 		if (client_listen_socket > maxfd)
1178*13b9f610SMax Laier 			maxfd = client_listen_socket;
1179*13b9f610SMax Laier 		if (client_data_socket > maxfd)
1180*13b9f610SMax Laier 			maxfd = client_data_socket;
1181*13b9f610SMax Laier 		if (server_iob.fd > maxfd)
1182*13b9f610SMax Laier 			maxfd = server_iob.fd;
1183*13b9f610SMax Laier 		if (server_listen_socket > maxfd)
1184*13b9f610SMax Laier 			maxfd = server_listen_socket;
1185*13b9f610SMax Laier 		if (server_data_socket > maxfd)
1186*13b9f610SMax Laier 			maxfd = server_data_socket;
1187*13b9f610SMax Laier 
1188*13b9f610SMax Laier 		debuglog(3, "client is %s; server is %s",
1189*13b9f610SMax Laier 		    client_iob.alive ? "alive" : "dead",
1190*13b9f610SMax Laier 		    server_iob.alive ? "alive" : "dead");
1191*13b9f610SMax Laier 
1192*13b9f610SMax Laier 		fdsp = (fd_set *)calloc(howmany(maxfd + 1, NFDBITS),
1193*13b9f610SMax Laier 		    sizeof(fd_mask));
1194*13b9f610SMax Laier 		if (fdsp == NULL) {
1195*13b9f610SMax Laier 			syslog(LOG_NOTICE, "insufficient memory");
1196*13b9f610SMax Laier 			exit(EX_UNAVAILABLE);
1197*13b9f610SMax Laier 		}
1198*13b9f610SMax Laier 
1199*13b9f610SMax Laier 		if (client_iob.alive && telnet_getline(&client_iob,
1200*13b9f610SMax Laier 		    &server_iob)) {
1201*13b9f610SMax Laier 			debuglog(3, "client line buffer is \"%s\"",
1202*13b9f610SMax Laier 			    (char *)client_iob.line_buffer);
1203*13b9f610SMax Laier 			if (client_iob.line_buffer[0] != '\0')
1204*13b9f610SMax Laier 				do_client_cmd(&client_iob, &server_iob);
1205*13b9f610SMax Laier 		} else if (server_iob.alive && telnet_getline(&server_iob,
1206*13b9f610SMax Laier 		    &client_iob)) {
1207*13b9f610SMax Laier 			debuglog(3, "server line buffer is \"%s\"",
1208*13b9f610SMax Laier 			    (char *)server_iob.line_buffer);
1209*13b9f610SMax Laier 			if (server_iob.line_buffer[0] != '\0')
1210*13b9f610SMax Laier 				do_server_reply(&server_iob, &client_iob);
1211*13b9f610SMax Laier 		} else {
1212*13b9f610SMax Laier 			if (client_iob.alive) {
1213*13b9f610SMax Laier 				FD_SET(client_iob.fd, fdsp);
1214*13b9f610SMax Laier 				if (client_listen_socket >= 0)
1215*13b9f610SMax Laier 					FD_SET(client_listen_socket, fdsp);
1216*13b9f610SMax Laier 				if (client_data_socket >= 0)
1217*13b9f610SMax Laier 					FD_SET(client_data_socket, fdsp);
1218*13b9f610SMax Laier 			}
1219*13b9f610SMax Laier 			if (server_iob.alive) {
1220*13b9f610SMax Laier 				FD_SET(server_iob.fd, fdsp);
1221*13b9f610SMax Laier 				if (server_listen_socket >= 0)
1222*13b9f610SMax Laier 					FD_SET(server_listen_socket, fdsp);
1223*13b9f610SMax Laier 				if (server_data_socket >= 0)
1224*13b9f610SMax Laier 					FD_SET(server_data_socket, fdsp);
1225*13b9f610SMax Laier 			}
1226*13b9f610SMax Laier 			tv.tv_sec = timeout_seconds;
1227*13b9f610SMax Laier 			tv.tv_usec = 0;
1228*13b9f610SMax Laier 
1229*13b9f610SMax Laier 		doselect:
1230*13b9f610SMax Laier 			sval = select(maxfd + 1, fdsp, NULL, NULL,
1231*13b9f610SMax Laier 			    (tv.tv_sec == 0) ? NULL : &tv);
1232*13b9f610SMax Laier 			if (sval == 0) {
1233*13b9f610SMax Laier 				/*
1234*13b9f610SMax Laier 				 * This proxy has timed out. Expire it
1235*13b9f610SMax Laier 				 * quietly with an obituary in the syslogs
1236*13b9f610SMax Laier 				 * for any passing mourners.
1237*13b9f610SMax Laier 				 */
1238*13b9f610SMax Laier 				syslog(LOG_INFO,
1239*13b9f610SMax Laier 				    "timeout: no data for %ld seconds",
1240*13b9f610SMax Laier 				    timeout_seconds);
1241*13b9f610SMax Laier 				exit(EX_OK);
1242*13b9f610SMax Laier 			}
1243*13b9f610SMax Laier 			if (sval == -1) {
1244*13b9f610SMax Laier 				if (errno == EINTR || errno == EAGAIN)
1245*13b9f610SMax Laier 					goto doselect;
1246*13b9f610SMax Laier 				syslog(LOG_NOTICE,
1247*13b9f610SMax Laier 				    "select() failed (%m)");
1248*13b9f610SMax Laier 				exit(EX_OSERR);
1249*13b9f610SMax Laier 			}
1250*13b9f610SMax Laier 			if (client_data_socket >= 0 &&
1251*13b9f610SMax Laier 			    FD_ISSET(client_data_socket, fdsp)) {
1252*13b9f610SMax Laier 				int rval;
1253*13b9f610SMax Laier 
1254*13b9f610SMax Laier 				debuglog(3, "transfer: client to server");
1255*13b9f610SMax Laier 				rval = xfer_data("client to server",
1256*13b9f610SMax Laier 				    client_data_socket,
1257*13b9f610SMax Laier 				    server_data_socket,
1258*13b9f610SMax Laier 				    client_iob.sa.sin_addr,
1259*13b9f610SMax Laier 				    real_server_sa.sin_addr);
1260*13b9f610SMax Laier 				if (rval <= 0) {
1261*13b9f610SMax Laier 					close_client_data();
1262*13b9f610SMax Laier 					close_server_data();
1263*13b9f610SMax Laier 					show_xfer_stats();
1264*13b9f610SMax Laier 				} else
1265*13b9f610SMax Laier 					client_data_bytes += rval;
1266*13b9f610SMax Laier 			}
1267*13b9f610SMax Laier 			if (server_data_socket >= 0 &&
1268*13b9f610SMax Laier 			    FD_ISSET(server_data_socket, fdsp)) {
1269*13b9f610SMax Laier 				int rval;
1270*13b9f610SMax Laier 
1271*13b9f610SMax Laier 				debuglog(3, "transfer: server to client");
1272*13b9f610SMax Laier 				rval = xfer_data("server to client",
1273*13b9f610SMax Laier 				    server_data_socket,
1274*13b9f610SMax Laier 				    client_data_socket,
1275*13b9f610SMax Laier 				    real_server_sa.sin_addr,
1276*13b9f610SMax Laier 				    client_iob.sa.sin_addr);
1277*13b9f610SMax Laier 				if (rval <= 0) {
1278*13b9f610SMax Laier 					close_client_data();
1279*13b9f610SMax Laier 					close_server_data();
1280*13b9f610SMax Laier 					show_xfer_stats();
1281*13b9f610SMax Laier 				} else
1282*13b9f610SMax Laier 					server_data_bytes += rval;
1283*13b9f610SMax Laier 			}
1284*13b9f610SMax Laier 			if (server_listen_socket >= 0 &&
1285*13b9f610SMax Laier 			    FD_ISSET(server_listen_socket, fdsp)) {
1286*13b9f610SMax Laier 				connect_port_backchannel();
1287*13b9f610SMax Laier 			}
1288*13b9f610SMax Laier 			if (client_listen_socket >= 0 &&
1289*13b9f610SMax Laier 			    FD_ISSET(client_listen_socket, fdsp)) {
1290*13b9f610SMax Laier 				connect_pasv_backchannel();
1291*13b9f610SMax Laier 			}
1292*13b9f610SMax Laier 			if (client_iob.alive &&
1293*13b9f610SMax Laier 			    FD_ISSET(client_iob.fd, fdsp)) {
1294*13b9f610SMax Laier 				client_iob.data_available = 1;
1295*13b9f610SMax Laier 			}
1296*13b9f610SMax Laier 			if (server_iob.alive &&
1297*13b9f610SMax Laier 			    FD_ISSET(server_iob.fd, fdsp)) {
1298*13b9f610SMax Laier 				server_iob.data_available = 1;
1299*13b9f610SMax Laier 			}
1300*13b9f610SMax Laier 		}
1301*13b9f610SMax Laier 		free(fdsp);
1302*13b9f610SMax Laier 		if (client_iob.got_eof) {
1303*13b9f610SMax Laier 			shutdown(server_iob.fd, 1);
1304*13b9f610SMax Laier 			shutdown(client_iob.fd, 0);
1305*13b9f610SMax Laier 			client_iob.got_eof = 0;
1306*13b9f610SMax Laier 			client_iob.alive = 0;
1307*13b9f610SMax Laier 		}
1308*13b9f610SMax Laier 		if (server_iob.got_eof) {
1309*13b9f610SMax Laier 			shutdown(client_iob.fd, 1);
1310*13b9f610SMax Laier 			shutdown(server_iob.fd, 0);
1311*13b9f610SMax Laier 			server_iob.got_eof = 0;
1312*13b9f610SMax Laier 			server_iob.alive = 0;
1313*13b9f610SMax Laier 		}
1314*13b9f610SMax Laier 	}
1315*13b9f610SMax Laier 
1316*13b9f610SMax Laier 	if (Verbose)
1317*13b9f610SMax Laier 		syslog(LOG_INFO, "session ended");
1318*13b9f610SMax Laier 
1319*13b9f610SMax Laier 	exit(EX_OK);
1320*13b9f610SMax Laier }
1321