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