1 |
|
|
/* $OpenBSD: ftp-proxy.c,v 1.36 2016/09/26 17:15:19 jca Exp $ */ |
2 |
|
|
|
3 |
|
|
/* |
4 |
|
|
* Copyright (c) 2004, 2005 Camiel Dobbelaar, <cd@sentia.nl> |
5 |
|
|
* |
6 |
|
|
* Permission to use, copy, modify, and distribute this software for any |
7 |
|
|
* purpose with or without fee is hereby granted, provided that the above |
8 |
|
|
* copyright notice and this permission notice appear in all copies. |
9 |
|
|
* |
10 |
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
11 |
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
12 |
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
13 |
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
14 |
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
15 |
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
16 |
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
17 |
|
|
*/ |
18 |
|
|
|
19 |
|
|
#include <sys/queue.h> |
20 |
|
|
#include <sys/types.h> |
21 |
|
|
#include <sys/time.h> |
22 |
|
|
#include <sys/resource.h> |
23 |
|
|
#include <sys/socket.h> |
24 |
|
|
|
25 |
|
|
#include <netinet/in.h> |
26 |
|
|
#include <arpa/inet.h> |
27 |
|
|
#include <net/if.h> |
28 |
|
|
#include <net/pfvar.h> |
29 |
|
|
|
30 |
|
|
#include <err.h> |
31 |
|
|
#include <errno.h> |
32 |
|
|
#include <event.h> |
33 |
|
|
#include <fcntl.h> |
34 |
|
|
#include <netdb.h> |
35 |
|
|
#include <paths.h> |
36 |
|
|
#include <pwd.h> |
37 |
|
|
#include <signal.h> |
38 |
|
|
#include <stdarg.h> |
39 |
|
|
#include <stdio.h> |
40 |
|
|
#include <stdlib.h> |
41 |
|
|
#include <string.h> |
42 |
|
|
#include <syslog.h> |
43 |
|
|
#include <unistd.h> |
44 |
|
|
#include <vis.h> |
45 |
|
|
|
46 |
|
|
#include "filter.h" |
47 |
|
|
|
48 |
|
|
#define CONNECT_TIMEOUT 30 |
49 |
|
|
#define MIN_PORT 1024 |
50 |
|
|
#define MAX_LINE 500 |
51 |
|
|
#define MAX_LOGLINE 300 |
52 |
|
|
#define NTOP_BUFS 3 |
53 |
|
|
#define TCP_BACKLOG 10 |
54 |
|
|
|
55 |
|
|
#define CHROOT_DIR "/var/empty" |
56 |
|
|
#define NOPRIV_USER "_ftp_proxy" |
57 |
|
|
|
58 |
|
|
/* pfctl standard NAT range. */ |
59 |
|
|
#define PF_NAT_PROXY_PORT_LOW 50001 |
60 |
|
|
#define PF_NAT_PROXY_PORT_HIGH 65535 |
61 |
|
|
|
62 |
|
|
#define sstosa(ss) ((struct sockaddr *)(ss)) |
63 |
|
|
|
64 |
|
|
enum { CMD_NONE = 0, CMD_PORT, CMD_EPRT, CMD_PASV, CMD_EPSV }; |
65 |
|
|
|
66 |
|
|
struct session { |
67 |
|
|
u_int32_t id; |
68 |
|
|
struct sockaddr_storage client_ss; |
69 |
|
|
struct sockaddr_storage proxy_ss; |
70 |
|
|
struct sockaddr_storage server_ss; |
71 |
|
|
struct sockaddr_storage orig_server_ss; |
72 |
|
|
struct bufferevent *client_bufev; |
73 |
|
|
struct bufferevent *server_bufev; |
74 |
|
|
int client_fd; |
75 |
|
|
int server_fd; |
76 |
|
|
char cbuf[MAX_LINE]; |
77 |
|
|
size_t cbuf_valid; |
78 |
|
|
char sbuf[MAX_LINE]; |
79 |
|
|
size_t sbuf_valid; |
80 |
|
|
int cmd; |
81 |
|
|
int client_rd; |
82 |
|
|
u_int16_t port; |
83 |
|
|
u_int16_t proxy_port; |
84 |
|
|
LIST_ENTRY(session) entry; |
85 |
|
|
}; |
86 |
|
|
|
87 |
|
|
LIST_HEAD(, session) sessions = LIST_HEAD_INITIALIZER(sessions); |
88 |
|
|
|
89 |
|
|
void client_error(struct bufferevent *, short, void *); |
90 |
|
|
int client_parse(struct session *s); |
91 |
|
|
int client_parse_anon(struct session *s); |
92 |
|
|
int client_parse_cmd(struct session *s); |
93 |
|
|
void client_read(struct bufferevent *, void *); |
94 |
|
|
int drop_privs(void); |
95 |
|
|
void end_session(struct session *); |
96 |
|
|
void exit_daemon(void); |
97 |
|
|
int get_line(char *, size_t *); |
98 |
|
|
void handle_connection(const int, short, void *); |
99 |
|
|
void handle_signal(int, short, void *); |
100 |
|
|
struct session * init_session(void); |
101 |
|
|
void logmsg(int, const char *, ...); |
102 |
|
|
u_int16_t parse_port(int); |
103 |
|
|
u_int16_t pick_proxy_port(void); |
104 |
|
|
void proxy_reply(int, struct sockaddr *, u_int16_t); |
105 |
|
|
int rdaemon(int); |
106 |
|
|
void server_error(struct bufferevent *, short, void *); |
107 |
|
|
int server_parse(struct session *s); |
108 |
|
|
int allow_data_connection(struct session *s); |
109 |
|
|
void server_read(struct bufferevent *, void *); |
110 |
|
|
const char *sock_ntop(struct sockaddr *); |
111 |
|
|
void usage(void); |
112 |
|
|
|
113 |
|
|
char linebuf[MAX_LINE + 1]; |
114 |
|
|
size_t linelen; |
115 |
|
|
|
116 |
|
|
char ntop_buf[NTOP_BUFS][INET6_ADDRSTRLEN]; |
117 |
|
|
|
118 |
|
|
struct event listen_ev, pause_accept_ev; |
119 |
|
|
struct sockaddr_storage fixed_server_ss, fixed_proxy_ss; |
120 |
|
|
char *fixed_server, *fixed_server_port, *fixed_proxy, *listen_ip, *listen_port, |
121 |
|
|
*qname, *tagname; |
122 |
|
|
int anonymous_only, daemonize, id_count, ipv6_mode, loglevel, max_sessions, |
123 |
|
|
rfc_mode, session_count, timeout, verbose; |
124 |
|
|
extern char *__progname; |
125 |
|
|
|
126 |
|
|
void |
127 |
|
|
client_error(struct bufferevent *bufev, short what, void *arg) |
128 |
|
|
{ |
129 |
|
|
struct session *s = arg; |
130 |
|
|
|
131 |
|
|
if (what & EVBUFFER_EOF) |
132 |
|
|
logmsg(LOG_INFO, "#%d client close", s->id); |
133 |
|
|
else if (what == (EVBUFFER_ERROR | EVBUFFER_READ)) |
134 |
|
|
logmsg(LOG_ERR, "#%d client reset connection", s->id); |
135 |
|
|
else if (what & EVBUFFER_TIMEOUT) |
136 |
|
|
logmsg(LOG_ERR, "#%d client timeout", s->id); |
137 |
|
|
else if (what & EVBUFFER_WRITE) |
138 |
|
|
logmsg(LOG_ERR, "#%d client write error: %d", s->id, what); |
139 |
|
|
else |
140 |
|
|
logmsg(LOG_ERR, "#%d abnormal client error: %d", s->id, what); |
141 |
|
|
|
142 |
|
|
end_session(s); |
143 |
|
|
} |
144 |
|
|
|
145 |
|
|
int |
146 |
|
|
client_parse(struct session *s) |
147 |
|
|
{ |
148 |
|
|
/* Reset any previous command. */ |
149 |
|
|
s->cmd = CMD_NONE; |
150 |
|
|
s->port = 0; |
151 |
|
|
|
152 |
|
|
/* Commands we are looking for are at least 4 chars long. */ |
153 |
|
|
if (linelen < 4) |
154 |
|
|
return (1); |
155 |
|
|
|
156 |
|
|
if (linebuf[0] == 'P' || linebuf[0] == 'p' || |
157 |
|
|
linebuf[0] == 'E' || linebuf[0] == 'e') { |
158 |
|
|
if (!client_parse_cmd(s)) |
159 |
|
|
return (0); |
160 |
|
|
|
161 |
|
|
/* |
162 |
|
|
* Allow active mode connections immediately, instead of |
163 |
|
|
* waiting for a positive reply from the server. Some |
164 |
|
|
* rare servers/proxies try to probe or setup the data |
165 |
|
|
* connection before an actual transfer request. |
166 |
|
|
*/ |
167 |
|
|
if (s->cmd == CMD_PORT || s->cmd == CMD_EPRT) |
168 |
|
|
return (allow_data_connection(s)); |
169 |
|
|
} |
170 |
|
|
|
171 |
|
|
if (anonymous_only && (linebuf[0] == 'U' || linebuf[0] == 'u')) |
172 |
|
|
return (client_parse_anon(s)); |
173 |
|
|
|
174 |
|
|
return (1); |
175 |
|
|
} |
176 |
|
|
|
177 |
|
|
int |
178 |
|
|
client_parse_anon(struct session *s) |
179 |
|
|
{ |
180 |
|
|
if (strcasecmp("USER ftp\r\n", linebuf) != 0 && |
181 |
|
|
strcasecmp("USER anonymous\r\n", linebuf) != 0) { |
182 |
|
|
snprintf(linebuf, sizeof linebuf, |
183 |
|
|
"500 Only anonymous FTP allowed\r\n"); |
184 |
|
|
logmsg(LOG_DEBUG, "#%d proxy: %s", s->id, linebuf); |
185 |
|
|
|
186 |
|
|
/* Talk back to the client ourself. */ |
187 |
|
|
linelen = strlen(linebuf); |
188 |
|
|
bufferevent_write(s->client_bufev, linebuf, linelen); |
189 |
|
|
|
190 |
|
|
/* Clear buffer so it's not sent to the server. */ |
191 |
|
|
linebuf[0] = '\0'; |
192 |
|
|
linelen = 0; |
193 |
|
|
} |
194 |
|
|
|
195 |
|
|
return (1); |
196 |
|
|
} |
197 |
|
|
|
198 |
|
|
int |
199 |
|
|
client_parse_cmd(struct session *s) |
200 |
|
|
{ |
201 |
|
|
if (strncasecmp("PASV", linebuf, 4) == 0) |
202 |
|
|
s->cmd = CMD_PASV; |
203 |
|
|
else if (strncasecmp("PORT ", linebuf, 5) == 0) |
204 |
|
|
s->cmd = CMD_PORT; |
205 |
|
|
else if (strncasecmp("EPSV", linebuf, 4) == 0) |
206 |
|
|
s->cmd = CMD_EPSV; |
207 |
|
|
else if (strncasecmp("EPRT ", linebuf, 5) == 0) |
208 |
|
|
s->cmd = CMD_EPRT; |
209 |
|
|
else |
210 |
|
|
return (1); |
211 |
|
|
|
212 |
|
|
if (ipv6_mode && (s->cmd == CMD_PASV || s->cmd == CMD_PORT)) { |
213 |
|
|
logmsg(LOG_CRIT, "PASV and PORT not allowed with IPv6"); |
214 |
|
|
return (0); |
215 |
|
|
} |
216 |
|
|
|
217 |
|
|
if (s->cmd == CMD_PORT || s->cmd == CMD_EPRT) { |
218 |
|
|
s->port = parse_port(s->cmd); |
219 |
|
|
if (s->port < MIN_PORT) { |
220 |
|
|
logmsg(LOG_CRIT, "#%d bad port in '%s'", s->id, |
221 |
|
|
linebuf); |
222 |
|
|
return (0); |
223 |
|
|
} |
224 |
|
|
s->proxy_port = pick_proxy_port(); |
225 |
|
|
proxy_reply(s->cmd, sstosa(&s->proxy_ss), s->proxy_port); |
226 |
|
|
logmsg(LOG_DEBUG, "#%d proxy: %s", s->id, linebuf); |
227 |
|
|
} |
228 |
|
|
|
229 |
|
|
return (1); |
230 |
|
|
} |
231 |
|
|
|
232 |
|
|
void |
233 |
|
|
client_read(struct bufferevent *bufev, void *arg) |
234 |
|
|
{ |
235 |
|
|
struct session *s = arg; |
236 |
|
|
size_t buf_avail, read; |
237 |
|
|
int n; |
238 |
|
|
|
239 |
|
|
do { |
240 |
|
|
buf_avail = sizeof s->cbuf - s->cbuf_valid; |
241 |
|
|
read = bufferevent_read(bufev, s->cbuf + s->cbuf_valid, |
242 |
|
|
buf_avail); |
243 |
|
|
s->cbuf_valid += read; |
244 |
|
|
|
245 |
|
|
while ((n = get_line(s->cbuf, &s->cbuf_valid)) > 0) { |
246 |
|
|
logmsg(LOG_DEBUG, "#%d client: %s", s->id, linebuf); |
247 |
|
|
if (!client_parse(s)) { |
248 |
|
|
end_session(s); |
249 |
|
|
return; |
250 |
|
|
} |
251 |
|
|
bufferevent_write(s->server_bufev, linebuf, linelen); |
252 |
|
|
} |
253 |
|
|
|
254 |
|
|
if (n == -1) { |
255 |
|
|
logmsg(LOG_ERR, "#%d client command too long or not" |
256 |
|
|
" clean", s->id); |
257 |
|
|
end_session(s); |
258 |
|
|
return; |
259 |
|
|
} |
260 |
|
|
} while (read == buf_avail); |
261 |
|
|
} |
262 |
|
|
|
263 |
|
|
int |
264 |
|
|
drop_privs(void) |
265 |
|
|
{ |
266 |
|
|
struct passwd *pw; |
267 |
|
|
|
268 |
|
|
pw = getpwnam(NOPRIV_USER); |
269 |
|
|
if (pw == NULL) |
270 |
|
|
return (0); |
271 |
|
|
|
272 |
|
|
tzset(); |
273 |
|
|
if (chroot(CHROOT_DIR) != 0 || chdir("/") != 0 || |
274 |
|
|
setgroups(1, &pw->pw_gid) != 0 || |
275 |
|
|
setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) != 0 || |
276 |
|
|
setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) != 0) |
277 |
|
|
return (0); |
278 |
|
|
|
279 |
|
|
return (1); |
280 |
|
|
} |
281 |
|
|
|
282 |
|
|
void |
283 |
|
|
end_session(struct session *s) |
284 |
|
|
{ |
285 |
|
|
int err; |
286 |
|
|
|
287 |
|
|
logmsg(LOG_INFO, "#%d ending session", s->id); |
288 |
|
|
|
289 |
|
|
/* Flush output buffers. */ |
290 |
|
|
if (s->client_bufev && s->client_fd != -1) |
291 |
|
|
evbuffer_write(s->client_bufev->output, s->client_fd); |
292 |
|
|
if (s->server_bufev && s->server_fd != -1) |
293 |
|
|
evbuffer_write(s->server_bufev->output, s->server_fd); |
294 |
|
|
|
295 |
|
|
if (s->client_fd != -1) |
296 |
|
|
close(s->client_fd); |
297 |
|
|
if (s->server_fd != -1) |
298 |
|
|
close(s->server_fd); |
299 |
|
|
|
300 |
|
|
if (s->client_bufev) |
301 |
|
|
bufferevent_free(s->client_bufev); |
302 |
|
|
if (s->server_bufev) |
303 |
|
|
bufferevent_free(s->server_bufev); |
304 |
|
|
|
305 |
|
|
/* Remove rulesets by committing empty ones. */ |
306 |
|
|
err = 0; |
307 |
|
|
if (prepare_commit(s->id) == -1) |
308 |
|
|
err = errno; |
309 |
|
|
else if (do_commit() == -1) { |
310 |
|
|
err = errno; |
311 |
|
|
do_rollback(); |
312 |
|
|
} |
313 |
|
|
if (err) |
314 |
|
|
logmsg(LOG_ERR, "#%d pf rule removal failed: %s", s->id, |
315 |
|
|
strerror(err)); |
316 |
|
|
|
317 |
|
|
LIST_REMOVE(s, entry); |
318 |
|
|
free(s); |
319 |
|
|
session_count--; |
320 |
|
|
} |
321 |
|
|
|
322 |
|
|
void |
323 |
|
|
exit_daemon(void) |
324 |
|
|
{ |
325 |
|
|
struct session *s, *next; |
326 |
|
|
|
327 |
|
|
for (s = LIST_FIRST(&sessions); s != NULL; s = next) { |
328 |
|
|
next = LIST_NEXT(s, entry); |
329 |
|
|
end_session(s); |
330 |
|
|
} |
331 |
|
|
|
332 |
|
|
if (daemonize) |
333 |
|
|
closelog(); |
334 |
|
|
|
335 |
|
|
exit(0); |
336 |
|
|
} |
337 |
|
|
|
338 |
|
|
int |
339 |
|
|
get_line(char *buf, size_t *valid) |
340 |
|
|
{ |
341 |
|
|
size_t i; |
342 |
|
|
|
343 |
|
|
if (*valid > MAX_LINE) |
344 |
|
|
return (-1); |
345 |
|
|
|
346 |
|
|
/* Copy to linebuf while searching for a newline. */ |
347 |
|
|
for (i = 0; i < *valid; i++) { |
348 |
|
|
linebuf[i] = buf[i]; |
349 |
|
|
if (buf[i] == '\0') |
350 |
|
|
return (-1); |
351 |
|
|
if (buf[i] == '\n') |
352 |
|
|
break; |
353 |
|
|
} |
354 |
|
|
|
355 |
|
|
if (i == *valid) { |
356 |
|
|
/* No newline found. */ |
357 |
|
|
linebuf[0] = '\0'; |
358 |
|
|
linelen = 0; |
359 |
|
|
if (i < MAX_LINE) |
360 |
|
|
return (0); |
361 |
|
|
return (-1); |
362 |
|
|
} |
363 |
|
|
|
364 |
|
|
linelen = i + 1; |
365 |
|
|
linebuf[linelen] = '\0'; |
366 |
|
|
*valid -= linelen; |
367 |
|
|
|
368 |
|
|
/* Move leftovers to the start. */ |
369 |
|
|
if (*valid != 0) |
370 |
|
|
bcopy(buf + linelen, buf, *valid); |
371 |
|
|
|
372 |
|
|
return ((int)linelen); |
373 |
|
|
} |
374 |
|
|
|
375 |
|
|
void |
376 |
|
|
handle_connection(const int listen_fd, short event, void *arg) |
377 |
|
|
{ |
378 |
|
|
struct sockaddr_storage tmp_ss; |
379 |
|
|
struct sockaddr *client_sa, *server_sa, *fixed_server_sa; |
380 |
|
|
struct sockaddr *proxy_to_server_sa; |
381 |
|
|
struct session *s; |
382 |
|
|
socklen_t len; |
383 |
|
|
int client_fd, fc, on; |
384 |
|
|
|
385 |
|
|
event_add(&listen_ev, NULL); |
386 |
|
|
|
387 |
|
|
if ((event & EV_TIMEOUT)) |
388 |
|
|
/* accept() is no longer paused. */ |
389 |
|
|
return; |
390 |
|
|
|
391 |
|
|
/* |
392 |
|
|
* We _must_ accept the connection, otherwise libevent will keep |
393 |
|
|
* coming back, and we will chew up all CPU. |
394 |
|
|
*/ |
395 |
|
|
client_sa = sstosa(&tmp_ss); |
396 |
|
|
len = sizeof(struct sockaddr_storage); |
397 |
|
|
if ((client_fd = accept(listen_fd, client_sa, &len)) < 0) { |
398 |
|
|
logmsg(LOG_CRIT, "accept() failed: %s", strerror(errno)); |
399 |
|
|
|
400 |
|
|
/* |
401 |
|
|
* Pause accept if we are out of file descriptors, or |
402 |
|
|
* libevent will haunt us here too. |
403 |
|
|
*/ |
404 |
|
|
if (errno == ENFILE || errno == EMFILE) { |
405 |
|
|
struct timeval pause = { 1, 0 }; |
406 |
|
|
|
407 |
|
|
event_del(&listen_ev); |
408 |
|
|
evtimer_add(&pause_accept_ev, &pause); |
409 |
|
|
} else if (errno != EWOULDBLOCK && errno != EINTR && |
410 |
|
|
errno != ECONNABORTED) |
411 |
|
|
logmsg(LOG_CRIT, "accept() failed: %s", strerror(errno)); |
412 |
|
|
return; |
413 |
|
|
} |
414 |
|
|
|
415 |
|
|
/* Refuse connection if the maximum is reached. */ |
416 |
|
|
if (session_count >= max_sessions) { |
417 |
|
|
logmsg(LOG_ERR, "client limit (%d) reached, refusing " |
418 |
|
|
"connection from %s", max_sessions, sock_ntop(client_sa)); |
419 |
|
|
close(client_fd); |
420 |
|
|
return; |
421 |
|
|
} |
422 |
|
|
|
423 |
|
|
/* Allocate session and copy back the info from the accept(). */ |
424 |
|
|
s = init_session(); |
425 |
|
|
if (s == NULL) { |
426 |
|
|
logmsg(LOG_CRIT, "init_session failed"); |
427 |
|
|
close(client_fd); |
428 |
|
|
return; |
429 |
|
|
} |
430 |
|
|
s->client_fd = client_fd; |
431 |
|
|
memcpy(sstosa(&s->client_ss), client_sa, client_sa->sa_len); |
432 |
|
|
|
433 |
|
|
/* Cast it once, and be done with it. */ |
434 |
|
|
client_sa = sstosa(&s->client_ss); |
435 |
|
|
server_sa = sstosa(&s->server_ss); |
436 |
|
|
proxy_to_server_sa = sstosa(&s->proxy_ss); |
437 |
|
|
fixed_server_sa = sstosa(&fixed_server_ss); |
438 |
|
|
|
439 |
|
|
/* Log id/client early to ease debugging. */ |
440 |
|
|
logmsg(LOG_DEBUG, "#%d accepted connection from %s", s->id, |
441 |
|
|
sock_ntop(client_sa)); |
442 |
|
|
|
443 |
|
|
/* |
444 |
|
|
* Find out the real server and port that the client wanted. |
445 |
|
|
*/ |
446 |
|
|
len = sizeof(struct sockaddr_storage); |
447 |
|
|
if (getsockname(s->client_fd, server_sa, &len) < 0) { |
448 |
|
|
logmsg(LOG_CRIT, "#%d getsockname failed: %s", s->id, |
449 |
|
|
strerror(errno)); |
450 |
|
|
goto fail; |
451 |
|
|
} |
452 |
|
|
len = sizeof(s->client_rd); |
453 |
|
|
if (getsockopt(s->client_fd, SOL_SOCKET, SO_RTABLE, &s->client_rd, |
454 |
|
|
&len) && errno != ENOPROTOOPT) { |
455 |
|
|
logmsg(LOG_CRIT, "#%d getsockopt failed: %s", s->id, |
456 |
|
|
strerror(errno)); |
457 |
|
|
goto fail; |
458 |
|
|
} |
459 |
|
|
if (fixed_server) { |
460 |
|
|
memcpy(sstosa(&s->orig_server_ss), server_sa, |
461 |
|
|
server_sa->sa_len); |
462 |
|
|
memcpy(server_sa, fixed_server_sa, fixed_server_sa->sa_len); |
463 |
|
|
} |
464 |
|
|
|
465 |
|
|
/* XXX: check we are not connecting to ourself. */ |
466 |
|
|
|
467 |
|
|
/* |
468 |
|
|
* Setup socket and connect to server. |
469 |
|
|
*/ |
470 |
|
|
if ((s->server_fd = socket(server_sa->sa_family, SOCK_STREAM, |
471 |
|
|
IPPROTO_TCP)) < 0) { |
472 |
|
|
logmsg(LOG_CRIT, "#%d server socket failed: %s", s->id, |
473 |
|
|
strerror(errno)); |
474 |
|
|
goto fail; |
475 |
|
|
} |
476 |
|
|
if (fixed_proxy && bind(s->server_fd, sstosa(&fixed_proxy_ss), |
477 |
|
|
fixed_proxy_ss.ss_len) != 0) { |
478 |
|
|
logmsg(LOG_CRIT, "#%d cannot bind fixed proxy address: %s", |
479 |
|
|
s->id, strerror(errno)); |
480 |
|
|
goto fail; |
481 |
|
|
} |
482 |
|
|
|
483 |
|
|
/* Use non-blocking connect(), see CONNECT_TIMEOUT below. */ |
484 |
|
|
if ((fc = fcntl(s->server_fd, F_GETFL)) == -1 || |
485 |
|
|
fcntl(s->server_fd, F_SETFL, fc | O_NONBLOCK) == -1) { |
486 |
|
|
logmsg(LOG_CRIT, "#%d cannot mark socket non-blocking: %s", |
487 |
|
|
s->id, strerror(errno)); |
488 |
|
|
goto fail; |
489 |
|
|
} |
490 |
|
|
if (connect(s->server_fd, server_sa, server_sa->sa_len) < 0 && |
491 |
|
|
errno != EINPROGRESS) { |
492 |
|
|
logmsg(LOG_CRIT, "#%d proxy cannot connect to server %s: %s", |
493 |
|
|
s->id, sock_ntop(server_sa), strerror(errno)); |
494 |
|
|
goto fail; |
495 |
|
|
} |
496 |
|
|
|
497 |
|
|
len = sizeof(struct sockaddr_storage); |
498 |
|
|
if ((getsockname(s->server_fd, proxy_to_server_sa, &len)) < 0) { |
499 |
|
|
logmsg(LOG_CRIT, "#%d getsockname failed: %s", s->id, |
500 |
|
|
strerror(errno)); |
501 |
|
|
goto fail; |
502 |
|
|
} |
503 |
|
|
|
504 |
|
|
logmsg(LOG_INFO, "#%d FTP session %d/%d started: client %s to server " |
505 |
|
|
"%s via proxy %s", s->id, session_count, max_sessions, |
506 |
|
|
sock_ntop(client_sa), sock_ntop(server_sa), |
507 |
|
|
sock_ntop(proxy_to_server_sa)); |
508 |
|
|
|
509 |
|
|
/* Keepalive is nice, but don't care if it fails. */ |
510 |
|
|
on = 1; |
511 |
|
|
setsockopt(s->client_fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, |
512 |
|
|
sizeof on); |
513 |
|
|
setsockopt(s->server_fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, |
514 |
|
|
sizeof on); |
515 |
|
|
|
516 |
|
|
/* |
517 |
|
|
* Setup buffered events. |
518 |
|
|
*/ |
519 |
|
|
s->client_bufev = bufferevent_new(s->client_fd, &client_read, NULL, |
520 |
|
|
&client_error, s); |
521 |
|
|
if (s->client_bufev == NULL) { |
522 |
|
|
logmsg(LOG_CRIT, "#%d bufferevent_new client failed", s->id); |
523 |
|
|
goto fail; |
524 |
|
|
} |
525 |
|
|
bufferevent_settimeout(s->client_bufev, timeout, 0); |
526 |
|
|
bufferevent_enable(s->client_bufev, EV_READ | EV_TIMEOUT); |
527 |
|
|
|
528 |
|
|
s->server_bufev = bufferevent_new(s->server_fd, &server_read, NULL, |
529 |
|
|
&server_error, s); |
530 |
|
|
if (s->server_bufev == NULL) { |
531 |
|
|
logmsg(LOG_CRIT, "#%d bufferevent_new server failed", s->id); |
532 |
|
|
goto fail; |
533 |
|
|
} |
534 |
|
|
bufferevent_settimeout(s->server_bufev, CONNECT_TIMEOUT, 0); |
535 |
|
|
bufferevent_enable(s->server_bufev, EV_READ | EV_TIMEOUT); |
536 |
|
|
|
537 |
|
|
return; |
538 |
|
|
|
539 |
|
|
fail: |
540 |
|
|
end_session(s); |
541 |
|
|
} |
542 |
|
|
|
543 |
|
|
void |
544 |
|
|
handle_signal(int sig, short event, void *arg) |
545 |
|
|
{ |
546 |
|
|
/* |
547 |
|
|
* Signal handler rules don't apply, libevent decouples for us. |
548 |
|
|
*/ |
549 |
|
|
|
550 |
|
|
logmsg(LOG_ERR, "exiting on signal %d", sig); |
551 |
|
|
|
552 |
|
|
exit_daemon(); |
553 |
|
|
} |
554 |
|
|
|
555 |
|
|
|
556 |
|
|
struct session * |
557 |
|
|
init_session(void) |
558 |
|
|
{ |
559 |
|
|
struct session *s; |
560 |
|
|
|
561 |
|
|
s = calloc(1, sizeof(struct session)); |
562 |
|
|
if (s == NULL) |
563 |
|
|
return (NULL); |
564 |
|
|
|
565 |
|
|
s->id = id_count++; |
566 |
|
|
s->client_fd = -1; |
567 |
|
|
s->server_fd = -1; |
568 |
|
|
s->cbuf[0] = '\0'; |
569 |
|
|
s->cbuf_valid = 0; |
570 |
|
|
s->sbuf[0] = '\0'; |
571 |
|
|
s->sbuf_valid = 0; |
572 |
|
|
s->client_bufev = NULL; |
573 |
|
|
s->server_bufev = NULL; |
574 |
|
|
s->cmd = CMD_NONE; |
575 |
|
|
s->port = 0; |
576 |
|
|
|
577 |
|
|
LIST_INSERT_HEAD(&sessions, s, entry); |
578 |
|
|
session_count++; |
579 |
|
|
|
580 |
|
|
return (s); |
581 |
|
|
} |
582 |
|
|
|
583 |
|
|
void |
584 |
|
|
logmsg(int pri, const char *message, ...) |
585 |
|
|
{ |
586 |
|
|
va_list ap; |
587 |
|
|
|
588 |
|
|
if (pri > loglevel) |
589 |
|
|
return; |
590 |
|
|
|
591 |
|
|
va_start(ap, message); |
592 |
|
|
|
593 |
|
|
if (daemonize) |
594 |
|
|
/* syslog does its own vissing. */ |
595 |
|
|
vsyslog(pri, message, ap); |
596 |
|
|
else { |
597 |
|
|
char buf[MAX_LOGLINE]; |
598 |
|
|
char visbuf[2 * MAX_LOGLINE]; |
599 |
|
|
|
600 |
|
|
/* We don't care about truncation. */ |
601 |
|
|
vsnprintf(buf, sizeof buf, message, ap); |
602 |
|
|
strnvis(visbuf, buf, sizeof visbuf, VIS_CSTYLE | VIS_NL); |
603 |
|
|
fprintf(stderr, "%s\n", visbuf); |
604 |
|
|
} |
605 |
|
|
|
606 |
|
|
va_end(ap); |
607 |
|
|
} |
608 |
|
|
|
609 |
|
|
int |
610 |
|
|
main(int argc, char *argv[]) |
611 |
|
|
{ |
612 |
|
|
struct rlimit rlp; |
613 |
|
|
struct addrinfo hints, *res; |
614 |
|
|
struct event ev_sighup, ev_sigint, ev_sigterm; |
615 |
|
|
int ch, devnull, error, listenfd, on; |
616 |
|
|
const char *errstr; |
617 |
|
|
|
618 |
|
|
/* Defaults. */ |
619 |
|
|
anonymous_only = 0; |
620 |
|
|
daemonize = 1; |
621 |
|
|
fixed_proxy = NULL; |
622 |
|
|
fixed_server = NULL; |
623 |
|
|
fixed_server_port = "21"; |
624 |
|
|
ipv6_mode = 0; |
625 |
|
|
listen_ip = NULL; |
626 |
|
|
listen_port = "8021"; |
627 |
|
|
loglevel = LOG_NOTICE; |
628 |
|
|
max_sessions = 100; |
629 |
|
|
qname = NULL; |
630 |
|
|
rfc_mode = 0; |
631 |
|
|
tagname = NULL; |
632 |
|
|
timeout = 24 * 3600; |
633 |
|
|
verbose = 0; |
634 |
|
|
|
635 |
|
|
/* Other initialization. */ |
636 |
|
|
devnull = -1; |
637 |
|
|
id_count = 1; |
638 |
|
|
session_count = 0; |
639 |
|
|
|
640 |
|
|
while ((ch = getopt(argc, argv, "6Aa:b:D:dm:P:p:q:R:rT:t:v")) != -1) { |
641 |
|
|
switch (ch) { |
642 |
|
|
case '6': |
643 |
|
|
ipv6_mode = 1; |
644 |
|
|
break; |
645 |
|
|
case 'A': |
646 |
|
|
anonymous_only = 1; |
647 |
|
|
break; |
648 |
|
|
case 'a': |
649 |
|
|
fixed_proxy = optarg; |
650 |
|
|
break; |
651 |
|
|
case 'b': |
652 |
|
|
listen_ip = optarg; |
653 |
|
|
break; |
654 |
|
|
case 'D': |
655 |
|
|
loglevel = strtonum(optarg, LOG_EMERG, LOG_DEBUG, |
656 |
|
|
&errstr); |
657 |
|
|
if (errstr) |
658 |
|
|
errx(1, "loglevel %s", errstr); |
659 |
|
|
break; |
660 |
|
|
case 'd': |
661 |
|
|
daemonize = 0; |
662 |
|
|
break; |
663 |
|
|
case 'm': |
664 |
|
|
max_sessions = strtonum(optarg, 1, 500, &errstr); |
665 |
|
|
if (errstr) |
666 |
|
|
errx(1, "max sessions %s", errstr); |
667 |
|
|
break; |
668 |
|
|
case 'P': |
669 |
|
|
fixed_server_port = optarg; |
670 |
|
|
break; |
671 |
|
|
case 'p': |
672 |
|
|
listen_port = optarg; |
673 |
|
|
break; |
674 |
|
|
case 'q': |
675 |
|
|
if (strlen(optarg) >= PF_QNAME_SIZE) |
676 |
|
|
errx(1, "queuename too long"); |
677 |
|
|
qname = optarg; |
678 |
|
|
break; |
679 |
|
|
case 'R': |
680 |
|
|
fixed_server = optarg; |
681 |
|
|
break; |
682 |
|
|
case 'r': |
683 |
|
|
rfc_mode = 1; |
684 |
|
|
break; |
685 |
|
|
case 'T': |
686 |
|
|
if (strlen(optarg) >= PF_TAG_NAME_SIZE) |
687 |
|
|
errx(1, "tagname too long"); |
688 |
|
|
tagname = optarg; |
689 |
|
|
break; |
690 |
|
|
case 't': |
691 |
|
|
timeout = strtonum(optarg, 0, 86400, &errstr); |
692 |
|
|
if (errstr) |
693 |
|
|
errx(1, "timeout %s", errstr); |
694 |
|
|
break; |
695 |
|
|
case 'v': |
696 |
|
|
verbose++; |
697 |
|
|
if (verbose > 2) |
698 |
|
|
usage(); |
699 |
|
|
break; |
700 |
|
|
default: |
701 |
|
|
usage(); |
702 |
|
|
} |
703 |
|
|
} |
704 |
|
|
|
705 |
|
|
if (listen_ip == NULL) |
706 |
|
|
listen_ip = ipv6_mode ? "::1" : "127.0.0.1"; |
707 |
|
|
|
708 |
|
|
/* Check for root to save the user from cryptic failure messages. */ |
709 |
|
|
if (getuid() != 0) |
710 |
|
|
errx(1, "needs to start as root"); |
711 |
|
|
|
712 |
|
|
if (getpwnam(NOPRIV_USER) == NULL) |
713 |
|
|
errx(1, "unknown user %s", NOPRIV_USER); |
714 |
|
|
|
715 |
|
|
/* Raise max. open files limit to satisfy max. sessions. */ |
716 |
|
|
rlp.rlim_cur = rlp.rlim_max = (2 * max_sessions) + 10; |
717 |
|
|
if (setrlimit(RLIMIT_NOFILE, &rlp) == -1) |
718 |
|
|
err(1, "setrlimit"); |
719 |
|
|
|
720 |
|
|
if (fixed_proxy) { |
721 |
|
|
memset(&hints, 0, sizeof hints); |
722 |
|
|
hints.ai_flags = AI_NUMERICHOST; |
723 |
|
|
hints.ai_family = ipv6_mode ? AF_INET6 : AF_INET; |
724 |
|
|
hints.ai_socktype = SOCK_STREAM; |
725 |
|
|
error = getaddrinfo(fixed_proxy, NULL, &hints, &res); |
726 |
|
|
if (error) |
727 |
|
|
errx(1, "getaddrinfo fixed proxy address failed: %s", |
728 |
|
|
gai_strerror(error)); |
729 |
|
|
memcpy(&fixed_proxy_ss, res->ai_addr, res->ai_addrlen); |
730 |
|
|
logmsg(LOG_INFO, "using %s to connect to servers", |
731 |
|
|
sock_ntop(sstosa(&fixed_proxy_ss))); |
732 |
|
|
freeaddrinfo(res); |
733 |
|
|
} |
734 |
|
|
|
735 |
|
|
if (fixed_server) { |
736 |
|
|
memset(&hints, 0, sizeof hints); |
737 |
|
|
hints.ai_family = ipv6_mode ? AF_INET6 : AF_INET; |
738 |
|
|
hints.ai_socktype = SOCK_STREAM; |
739 |
|
|
error = getaddrinfo(fixed_server, fixed_server_port, &hints, |
740 |
|
|
&res); |
741 |
|
|
if (error) |
742 |
|
|
errx(1, "getaddrinfo fixed server address failed: %s", |
743 |
|
|
gai_strerror(error)); |
744 |
|
|
memcpy(&fixed_server_ss, res->ai_addr, res->ai_addrlen); |
745 |
|
|
logmsg(LOG_INFO, "using fixed server %s", |
746 |
|
|
sock_ntop(sstosa(&fixed_server_ss))); |
747 |
|
|
freeaddrinfo(res); |
748 |
|
|
} |
749 |
|
|
|
750 |
|
|
/* Setup listener. */ |
751 |
|
|
memset(&hints, 0, sizeof hints); |
752 |
|
|
hints.ai_flags = AI_NUMERICHOST | AI_PASSIVE; |
753 |
|
|
hints.ai_family = ipv6_mode ? AF_INET6 : AF_INET; |
754 |
|
|
hints.ai_socktype = SOCK_STREAM; |
755 |
|
|
error = getaddrinfo(listen_ip, listen_port, &hints, &res); |
756 |
|
|
if (error) |
757 |
|
|
errx(1, "getaddrinfo listen address failed: %s", |
758 |
|
|
gai_strerror(error)); |
759 |
|
|
if ((listenfd = socket(res->ai_family, SOCK_STREAM, IPPROTO_TCP)) == -1) |
760 |
|
|
errx(1, "socket failed"); |
761 |
|
|
on = 1; |
762 |
|
|
if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (void *)&on, |
763 |
|
|
sizeof on) != 0) |
764 |
|
|
err(1, "setsockopt failed"); |
765 |
|
|
if (bind(listenfd, (struct sockaddr *)res->ai_addr, |
766 |
|
|
(socklen_t)res->ai_addrlen) != 0) |
767 |
|
|
err(1, "bind failed"); |
768 |
|
|
if (listen(listenfd, TCP_BACKLOG) != 0) |
769 |
|
|
err(1, "listen failed"); |
770 |
|
|
freeaddrinfo(res); |
771 |
|
|
|
772 |
|
|
/* Initialize pf. */ |
773 |
|
|
init_filter(qname, tagname, verbose); |
774 |
|
|
|
775 |
|
|
if (daemonize) { |
776 |
|
|
devnull = open(_PATH_DEVNULL, O_RDWR, 0); |
777 |
|
|
if (devnull == -1) |
778 |
|
|
err(1, "open(%s)", _PATH_DEVNULL); |
779 |
|
|
} |
780 |
|
|
|
781 |
|
|
if (!drop_privs()) |
782 |
|
|
err(1, "cannot drop privileges"); |
783 |
|
|
|
784 |
|
|
if (daemonize) { |
785 |
|
|
if (rdaemon(devnull) == -1) |
786 |
|
|
err(1, "cannot daemonize"); |
787 |
|
|
openlog(__progname, LOG_PID | LOG_NDELAY, LOG_DAEMON); |
788 |
|
|
} |
789 |
|
|
|
790 |
|
|
/* Use logmsg for output from here on. */ |
791 |
|
|
|
792 |
|
|
event_init(); |
793 |
|
|
|
794 |
|
|
/* Setup signal handler. */ |
795 |
|
|
signal(SIGPIPE, SIG_IGN); |
796 |
|
|
signal_set(&ev_sighup, SIGHUP, handle_signal, NULL); |
797 |
|
|
signal_set(&ev_sigint, SIGINT, handle_signal, NULL); |
798 |
|
|
signal_set(&ev_sigterm, SIGTERM, handle_signal, NULL); |
799 |
|
|
signal_add(&ev_sighup, NULL); |
800 |
|
|
signal_add(&ev_sigint, NULL); |
801 |
|
|
signal_add(&ev_sigterm, NULL); |
802 |
|
|
|
803 |
|
|
event_set(&listen_ev, listenfd, EV_READ, handle_connection, NULL); |
804 |
|
|
event_add(&listen_ev, NULL); |
805 |
|
|
evtimer_set(&pause_accept_ev, handle_connection, NULL); |
806 |
|
|
|
807 |
|
|
logmsg(LOG_NOTICE, "listening on %s port %s", listen_ip, listen_port); |
808 |
|
|
|
809 |
|
|
/* Vroom, vroom. */ |
810 |
|
|
event_dispatch(); |
811 |
|
|
|
812 |
|
|
logmsg(LOG_ERR, "event_dispatch error: %s", strerror(errno)); |
813 |
|
|
exit_daemon(); |
814 |
|
|
|
815 |
|
|
/* NOTREACHED */ |
816 |
|
|
return (1); |
817 |
|
|
} |
818 |
|
|
|
819 |
|
|
u_int16_t |
820 |
|
|
parse_port(int mode) |
821 |
|
|
{ |
822 |
|
|
unsigned int port, v[6]; |
823 |
|
|
int n; |
824 |
|
|
char *p; |
825 |
|
|
|
826 |
|
|
/* Find the last space or left-parenthesis. */ |
827 |
|
|
for (p = linebuf + linelen; p > linebuf; p--) |
828 |
|
|
if (*p == ' ' || *p == '(') |
829 |
|
|
break; |
830 |
|
|
if (p == linebuf) |
831 |
|
|
return (0); |
832 |
|
|
|
833 |
|
|
switch (mode) { |
834 |
|
|
case CMD_PORT: |
835 |
|
|
n = sscanf(p, " %u,%u,%u,%u,%u,%u", &v[0], &v[1], &v[2], |
836 |
|
|
&v[3], &v[4], &v[5]); |
837 |
|
|
if (n == 6 && v[0] < 256 && v[1] < 256 && v[2] < 256 && |
838 |
|
|
v[3] < 256 && v[4] < 256 && v[5] < 256) |
839 |
|
|
return ((v[4] << 8) | v[5]); |
840 |
|
|
break; |
841 |
|
|
case CMD_PASV: |
842 |
|
|
n = sscanf(p, "(%u,%u,%u,%u,%u,%u)", &v[0], &v[1], &v[2], |
843 |
|
|
&v[3], &v[4], &v[5]); |
844 |
|
|
if (n == 6 && v[0] < 256 && v[1] < 256 && v[2] < 256 && |
845 |
|
|
v[3] < 256 && v[4] < 256 && v[5] < 256) |
846 |
|
|
return ((v[4] << 8) | v[5]); |
847 |
|
|
break; |
848 |
|
|
case CMD_EPSV: |
849 |
|
|
n = sscanf(p, "(|||%u|)", &port); |
850 |
|
|
if (n == 1 && port < 65536) |
851 |
|
|
return (port); |
852 |
|
|
break; |
853 |
|
|
case CMD_EPRT: |
854 |
|
|
n = sscanf(p, " |1|%u.%u.%u.%u|%u|", &v[0], &v[1], &v[2], |
855 |
|
|
&v[3], &port); |
856 |
|
|
if (n == 5 && v[0] < 256 && v[1] < 256 && v[2] < 256 && |
857 |
|
|
v[3] < 256 && port < 65536) |
858 |
|
|
return (port); |
859 |
|
|
n = sscanf(p, " |2|%*[a-fA-F0-9:]|%u|", &port); |
860 |
|
|
if (n == 1 && port < 65536) |
861 |
|
|
return (port); |
862 |
|
|
break; |
863 |
|
|
default: |
864 |
|
|
return (0); |
865 |
|
|
} |
866 |
|
|
|
867 |
|
|
return (0); |
868 |
|
|
} |
869 |
|
|
|
870 |
|
|
u_int16_t |
871 |
|
|
pick_proxy_port(void) |
872 |
|
|
{ |
873 |
|
|
/* Random should be good enough for avoiding port collisions. */ |
874 |
|
|
return (IPPORT_HIFIRSTAUTO + |
875 |
|
|
arc4random_uniform(IPPORT_HILASTAUTO - IPPORT_HIFIRSTAUTO)); |
876 |
|
|
} |
877 |
|
|
|
878 |
|
|
void |
879 |
|
|
proxy_reply(int cmd, struct sockaddr *sa, u_int16_t port) |
880 |
|
|
{ |
881 |
|
|
int i, r; |
882 |
|
|
|
883 |
|
|
switch (cmd) { |
884 |
|
|
case CMD_PORT: |
885 |
|
|
r = snprintf(linebuf, sizeof linebuf, |
886 |
|
|
"PORT %s,%u,%u\r\n", sock_ntop(sa), port / 256, |
887 |
|
|
port % 256); |
888 |
|
|
break; |
889 |
|
|
case CMD_PASV: |
890 |
|
|
r = snprintf(linebuf, sizeof linebuf, |
891 |
|
|
"227 Entering Passive Mode (%s,%u,%u)\r\n", sock_ntop(sa), |
892 |
|
|
port / 256, port % 256); |
893 |
|
|
break; |
894 |
|
|
case CMD_EPRT: |
895 |
|
|
if (sa->sa_family == AF_INET) |
896 |
|
|
r = snprintf(linebuf, sizeof linebuf, |
897 |
|
|
"EPRT |1|%s|%u|\r\n", sock_ntop(sa), port); |
898 |
|
|
else if (sa->sa_family == AF_INET6) |
899 |
|
|
r = snprintf(linebuf, sizeof linebuf, |
900 |
|
|
"EPRT |2|%s|%u|\r\n", sock_ntop(sa), port); |
901 |
|
|
break; |
902 |
|
|
case CMD_EPSV: |
903 |
|
|
r = snprintf(linebuf, sizeof linebuf, |
904 |
|
|
"229 Entering Extended Passive Mode (|||%u|)\r\n", port); |
905 |
|
|
break; |
906 |
|
|
} |
907 |
|
|
|
908 |
|
|
if (r < 0 || r >= sizeof linebuf) { |
909 |
|
|
logmsg(LOG_ERR, "proxy_reply failed: %d", r); |
910 |
|
|
linebuf[0] = '\0'; |
911 |
|
|
linelen = 0; |
912 |
|
|
return; |
913 |
|
|
} |
914 |
|
|
linelen = (size_t)r; |
915 |
|
|
|
916 |
|
|
if (cmd == CMD_PORT || cmd == CMD_PASV) { |
917 |
|
|
/* Replace dots in IP address with commas. */ |
918 |
|
|
for (i = 0; i < linelen; i++) |
919 |
|
|
if (linebuf[i] == '.') |
920 |
|
|
linebuf[i] = ','; |
921 |
|
|
} |
922 |
|
|
} |
923 |
|
|
|
924 |
|
|
void |
925 |
|
|
server_error(struct bufferevent *bufev, short what, void *arg) |
926 |
|
|
{ |
927 |
|
|
struct session *s = arg; |
928 |
|
|
|
929 |
|
|
if (what & EVBUFFER_EOF) |
930 |
|
|
logmsg(LOG_INFO, "#%d server close", s->id); |
931 |
|
|
else if (what == (EVBUFFER_ERROR | EVBUFFER_READ)) |
932 |
|
|
logmsg(LOG_ERR, "#%d server refused connection", s->id); |
933 |
|
|
else if (what & EVBUFFER_WRITE) |
934 |
|
|
logmsg(LOG_ERR, "#%d server write error: %d", s->id, what); |
935 |
|
|
else if (what & EVBUFFER_TIMEOUT) |
936 |
|
|
logmsg(LOG_NOTICE, "#%d server timeout", s->id); |
937 |
|
|
else |
938 |
|
|
logmsg(LOG_ERR, "#%d abnormal server error: %d", s->id, what); |
939 |
|
|
|
940 |
|
|
end_session(s); |
941 |
|
|
} |
942 |
|
|
|
943 |
|
|
int |
944 |
|
|
server_parse(struct session *s) |
945 |
|
|
{ |
946 |
|
|
if (s->cmd == CMD_NONE || linelen < 4 || linebuf[0] != '2') |
947 |
|
|
goto out; |
948 |
|
|
|
949 |
|
|
if ((s->cmd == CMD_PASV && strncmp("227 ", linebuf, 4) == 0) || |
950 |
|
|
(s->cmd == CMD_EPSV && strncmp("229 ", linebuf, 4) == 0)) |
951 |
|
|
return (allow_data_connection(s)); |
952 |
|
|
|
953 |
|
|
out: |
954 |
|
|
s->cmd = CMD_NONE; |
955 |
|
|
s->port = 0; |
956 |
|
|
|
957 |
|
|
return (1); |
958 |
|
|
} |
959 |
|
|
|
960 |
|
|
int |
961 |
|
|
allow_data_connection(struct session *s) |
962 |
|
|
{ |
963 |
|
|
struct sockaddr *client_sa, *orig_sa, *proxy_sa, *server_sa; |
964 |
|
|
int prepared = 0; |
965 |
|
|
|
966 |
|
|
/* |
967 |
|
|
* The pf rules below do quite some NAT rewriting, to keep up |
968 |
|
|
* appearances. Points to keep in mind: |
969 |
|
|
* 1) The client must think it's talking to the real server, |
970 |
|
|
* for both control and data connections. Transparently. |
971 |
|
|
* 2) The server must think that the proxy is the client. |
972 |
|
|
* 3) Source and destination ports are rewritten to minimize |
973 |
|
|
* port collisions, to aid security (some systems pick weak |
974 |
|
|
* ports) or to satisfy RFC requirements (source port 20). |
975 |
|
|
*/ |
976 |
|
|
|
977 |
|
|
/* Cast this once, to make code below it more readable. */ |
978 |
|
|
client_sa = sstosa(&s->client_ss); |
979 |
|
|
server_sa = sstosa(&s->server_ss); |
980 |
|
|
proxy_sa = sstosa(&s->proxy_ss); |
981 |
|
|
if (fixed_server) |
982 |
|
|
/* Fixed server: data connections must appear to come |
983 |
|
|
from / go to the original server, not the fixed one. */ |
984 |
|
|
orig_sa = sstosa(&s->orig_server_ss); |
985 |
|
|
else |
986 |
|
|
/* Server not fixed: orig_server == server. */ |
987 |
|
|
orig_sa = sstosa(&s->server_ss); |
988 |
|
|
|
989 |
|
|
/* Passive modes. */ |
990 |
|
|
if (s->cmd == CMD_PASV || s->cmd == CMD_EPSV) { |
991 |
|
|
s->port = parse_port(s->cmd); |
992 |
|
|
if (s->port < MIN_PORT) { |
993 |
|
|
logmsg(LOG_CRIT, "#%d bad port in '%s'", s->id, |
994 |
|
|
linebuf); |
995 |
|
|
return (0); |
996 |
|
|
} |
997 |
|
|
s->proxy_port = pick_proxy_port(); |
998 |
|
|
logmsg(LOG_INFO, "#%d passive: client to server port %d" |
999 |
|
|
" via port %d", s->id, s->port, s->proxy_port); |
1000 |
|
|
|
1001 |
|
|
if (prepare_commit(s->id) == -1) |
1002 |
|
|
goto fail; |
1003 |
|
|
prepared = 1; |
1004 |
|
|
|
1005 |
|
|
proxy_reply(s->cmd, orig_sa, s->proxy_port); |
1006 |
|
|
logmsg(LOG_DEBUG, "#%d proxy: %s", s->id, linebuf); |
1007 |
|
|
|
1008 |
|
|
/* pass in from $client to $orig_server port $proxy_port |
1009 |
|
|
rdr-to $server port $port */ |
1010 |
|
|
if (add_rdr(s->id, client_sa, s->client_rd, orig_sa, |
1011 |
|
|
s->proxy_port, server_sa, s->port, getrtable()) == -1) |
1012 |
|
|
goto fail; |
1013 |
|
|
|
1014 |
|
|
/* pass out from $client to $server port $port nat-to $proxy */ |
1015 |
|
|
if (add_nat(s->id, client_sa, getrtable(), server_sa, |
1016 |
|
|
s->port, proxy_sa, PF_NAT_PROXY_PORT_LOW, |
1017 |
|
|
PF_NAT_PROXY_PORT_HIGH) == -1) |
1018 |
|
|
goto fail; |
1019 |
|
|
} |
1020 |
|
|
|
1021 |
|
|
/* Active modes. */ |
1022 |
|
|
if (s->cmd == CMD_PORT || s->cmd == CMD_EPRT) { |
1023 |
|
|
logmsg(LOG_INFO, "#%d active: server to client port %d" |
1024 |
|
|
" via port %d", s->id, s->port, s->proxy_port); |
1025 |
|
|
|
1026 |
|
|
if (prepare_commit(s->id) == -1) |
1027 |
|
|
goto fail; |
1028 |
|
|
prepared = 1; |
1029 |
|
|
|
1030 |
|
|
/* pass in from $server to $proxy port $proxy_port |
1031 |
|
|
rdr-to $client port $port */ |
1032 |
|
|
if (add_rdr(s->id, server_sa, getrtable(), proxy_sa, |
1033 |
|
|
s->proxy_port, client_sa, s->port, s->client_rd) == -1) |
1034 |
|
|
goto fail; |
1035 |
|
|
|
1036 |
|
|
/* pass out from $server to $client port $port |
1037 |
|
|
nat-to $orig_server port $natport */ |
1038 |
|
|
if (rfc_mode && s->cmd == CMD_PORT) { |
1039 |
|
|
/* Rewrite sourceport to RFC mandated 20. */ |
1040 |
|
|
if (add_nat(s->id, server_sa, s->client_rd, client_sa, |
1041 |
|
|
s->port, orig_sa, 20, 20) == -1) |
1042 |
|
|
goto fail; |
1043 |
|
|
} else { |
1044 |
|
|
/* Let pf pick a source port from the standard range. */ |
1045 |
|
|
if (add_nat(s->id, server_sa, s->client_rd, client_sa, |
1046 |
|
|
s->port, orig_sa, PF_NAT_PROXY_PORT_LOW, |
1047 |
|
|
PF_NAT_PROXY_PORT_HIGH) == -1) |
1048 |
|
|
goto fail; |
1049 |
|
|
} |
1050 |
|
|
} |
1051 |
|
|
|
1052 |
|
|
/* Commit rules if they were prepared. */ |
1053 |
|
|
if (prepared && (do_commit() == -1)) { |
1054 |
|
|
if (errno != EBUSY) |
1055 |
|
|
goto fail; |
1056 |
|
|
/* One more try if busy. */ |
1057 |
|
|
usleep(5000); |
1058 |
|
|
if (do_commit() == -1) |
1059 |
|
|
goto fail; |
1060 |
|
|
} |
1061 |
|
|
|
1062 |
|
|
s->cmd = CMD_NONE; |
1063 |
|
|
s->port = 0; |
1064 |
|
|
|
1065 |
|
|
return (1); |
1066 |
|
|
|
1067 |
|
|
fail: |
1068 |
|
|
logmsg(LOG_CRIT, "#%d pf operation failed: %s", s->id, strerror(errno)); |
1069 |
|
|
if (prepared) |
1070 |
|
|
do_rollback(); |
1071 |
|
|
return (0); |
1072 |
|
|
} |
1073 |
|
|
|
1074 |
|
|
void |
1075 |
|
|
server_read(struct bufferevent *bufev, void *arg) |
1076 |
|
|
{ |
1077 |
|
|
struct session *s = arg; |
1078 |
|
|
size_t buf_avail, read; |
1079 |
|
|
int n; |
1080 |
|
|
|
1081 |
|
|
bufferevent_settimeout(bufev, timeout, 0); |
1082 |
|
|
|
1083 |
|
|
do { |
1084 |
|
|
buf_avail = sizeof s->sbuf - s->sbuf_valid; |
1085 |
|
|
read = bufferevent_read(bufev, s->sbuf + s->sbuf_valid, |
1086 |
|
|
buf_avail); |
1087 |
|
|
s->sbuf_valid += read; |
1088 |
|
|
|
1089 |
|
|
while ((n = get_line(s->sbuf, &s->sbuf_valid)) > 0) { |
1090 |
|
|
logmsg(LOG_DEBUG, "#%d server: %s", s->id, linebuf); |
1091 |
|
|
if (!server_parse(s)) { |
1092 |
|
|
end_session(s); |
1093 |
|
|
return; |
1094 |
|
|
} |
1095 |
|
|
bufferevent_write(s->client_bufev, linebuf, linelen); |
1096 |
|
|
} |
1097 |
|
|
|
1098 |
|
|
if (n == -1) { |
1099 |
|
|
logmsg(LOG_ERR, "#%d server reply too long or not" |
1100 |
|
|
" clean", s->id); |
1101 |
|
|
end_session(s); |
1102 |
|
|
return; |
1103 |
|
|
} |
1104 |
|
|
} while (read == buf_avail); |
1105 |
|
|
} |
1106 |
|
|
|
1107 |
|
|
const char * |
1108 |
|
|
sock_ntop(struct sockaddr *sa) |
1109 |
|
|
{ |
1110 |
|
|
static int n = 0; |
1111 |
|
|
|
1112 |
|
|
/* Cycle to next buffer. */ |
1113 |
|
|
n = (n + 1) % NTOP_BUFS; |
1114 |
|
|
ntop_buf[n][0] = '\0'; |
1115 |
|
|
|
1116 |
|
|
if (sa->sa_family == AF_INET) { |
1117 |
|
|
struct sockaddr_in *sin = (struct sockaddr_in *)sa; |
1118 |
|
|
|
1119 |
|
|
return (inet_ntop(AF_INET, &sin->sin_addr, ntop_buf[n], |
1120 |
|
|
sizeof ntop_buf[0])); |
1121 |
|
|
} |
1122 |
|
|
|
1123 |
|
|
if (sa->sa_family == AF_INET6) { |
1124 |
|
|
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa; |
1125 |
|
|
|
1126 |
|
|
return (inet_ntop(AF_INET6, &sin6->sin6_addr, ntop_buf[n], |
1127 |
|
|
sizeof ntop_buf[0])); |
1128 |
|
|
} |
1129 |
|
|
|
1130 |
|
|
return (NULL); |
1131 |
|
|
} |
1132 |
|
|
|
1133 |
|
|
void |
1134 |
|
|
usage(void) |
1135 |
|
|
{ |
1136 |
|
|
fprintf(stderr, "usage: %s [-6Adrv] [-a address] [-b address]" |
1137 |
|
|
" [-D level] [-m maxsessions]\n [-P port]" |
1138 |
|
|
" [-p port] [-q queue] [-R address] [-T tag]\n" |
1139 |
|
|
" [-t timeout]\n", __progname); |
1140 |
|
|
exit(1); |
1141 |
|
|
} |
1142 |
|
|
|
1143 |
|
|
int |
1144 |
|
|
rdaemon(int devnull) |
1145 |
|
|
{ |
1146 |
|
|
if (devnull == -1) { |
1147 |
|
|
errno = EBADF; |
1148 |
|
|
return (-1); |
1149 |
|
|
} |
1150 |
|
|
if (fcntl(devnull, F_GETFL) == -1) |
1151 |
|
|
return (-1); |
1152 |
|
|
|
1153 |
|
|
switch (fork()) { |
1154 |
|
|
case -1: |
1155 |
|
|
return (-1); |
1156 |
|
|
case 0: |
1157 |
|
|
break; |
1158 |
|
|
default: |
1159 |
|
|
_exit(0); |
1160 |
|
|
} |
1161 |
|
|
|
1162 |
|
|
if (setsid() == -1) |
1163 |
|
|
return (-1); |
1164 |
|
|
|
1165 |
|
|
(void)dup2(devnull, STDIN_FILENO); |
1166 |
|
|
(void)dup2(devnull, STDOUT_FILENO); |
1167 |
|
|
(void)dup2(devnull, STDERR_FILENO); |
1168 |
|
|
if (devnull > 2) |
1169 |
|
|
(void)close(devnull); |
1170 |
|
|
|
1171 |
|
|
return (0); |
1172 |
|
|
} |