From fdafaaeec8b05ab1e62457ecb6202ced98caa202 Mon Sep 17 00:00:00 2001 From: Stef Walter Date: Sat, 4 Sep 2004 00:05:08 +0000 Subject: Transparent proxy support. --- src/clamsmtpd.c | 129 ++++++++++++++++++++++++++++++++++++++++---------------- src/clamsmtpd.h | 1 + src/clio.c | 2 +- src/clstate.c | 24 ++++++++--- src/sock_any.c | 30 +++++++++++++ src/sock_any.h | 4 +- 6 files changed, 144 insertions(+), 46 deletions(-) (limited to 'src') diff --git a/src/clamsmtpd.c b/src/clamsmtpd.c index e4c69b9..baed725 100644 --- a/src/clamsmtpd.c +++ b/src/clamsmtpd.c @@ -52,6 +52,11 @@ #include #include "usuals.h" + +#ifdef LINUX_TRANSPARENT_PROXY +#include +#endif + #include "compat.h" #include "sock_any.h" #include "clamsmtpd.h" @@ -142,6 +147,7 @@ static void pid_file(const char* pidfile, int write); static void connection_loop(int sock); static void* thread_main(void* arg); static int smtp_passthru(clamsmtp_context_t* ctx); +static int connect_out(clamsmtp_context_t* ctx); static int connect_clam(clamsmtp_context_t* ctx); static int disconnect_clam(clamsmtp_context_t* ctx); static void add_to_logline(char* logline, char* prefix, char* line); @@ -542,10 +548,6 @@ static void connection_loop(int sock) static void* thread_main(void* arg) { clamsmtp_thread_t* thread = (clamsmtp_thread_t*)arg; - struct sockaddr_any addr; - struct sockaddr_any* outaddr; - const char* outname; - char buf[MAXPATHLEN]; clamsmtp_context_t* ctx = NULL; int processing = 0; int ret = 0; @@ -590,45 +592,14 @@ static void* thread_main(void* arg) ASSERT(ctx->client.fd != -1); messagex(ctx, LOG_DEBUG, "processing %d on thread %x", ctx->client.fd, (int)pthread_self()); - memset(&addr, 0, sizeof(addr)); - SANY_LEN(addr) = sizeof(addr); - - /* Get the peer name */ - if(getpeername(ctx->client.fd, &SANY_ADDR(addr), &SANY_LEN(addr)) == -1 || - sock_any_ntop(&addr, buf, MAXPATHLEN, SANY_OPT_NOPORT) == -1) - message(ctx, LOG_WARNING, "couldn't get peer address"); - else - messagex(ctx, LOG_INFO, "accepted connection from: %s", buf); - - - /* Create the server connection address */ - outaddr = &(g_state.outaddr); - outname = g_state.outname; - - if(SANY_TYPE(*outaddr) == AF_INET && - outaddr->s.in.sin_addr.s_addr == 0) - { - /* Use the incoming IP as the default */ - in_addr_t in = addr.s.in.sin_addr.s_addr; - memcpy(&addr, &(g_state.outaddr), sizeof(addr)); - addr.s.in.sin_addr.s_addr = in; - - outaddr = &addr; - - if(sock_any_ntop(outaddr, buf, MAXPATHLEN, 0) != -1) - outname = buf; - } - - - /* Connect to the server */ - if(clio_connect(ctx, &(ctx->server), outaddr, outname) == -1) + /* Connect to the outgoing server ... */ + if(connect_out(ctx) == -1) RETURN(-1); /* ... and to the AV daemon */ if(connect_clam(ctx) == -1) RETURN(-1); - /* call the processor */ processing = 1; ret = smtp_passthru(ctx); @@ -655,6 +626,90 @@ cleanup: return (void*)(ret == 0 ? 0 : 1); } +static int connect_out(clamsmtp_context_t* ctx) +{ + struct sockaddr_any peeraddr; + struct sockaddr_any addr; + struct sockaddr_any* outaddr; + char buf[MAXPATHLEN]; + const char* outname; + + memset(&peeraddr, 0, sizeof(peeraddr)); + SANY_LEN(peeraddr) = sizeof(peeraddr); + + /* Get the peer name */ + if(getpeername(ctx->client.fd, &SANY_ADDR(peeraddr), &SANY_LEN(peeraddr)) == -1 || + sock_any_ntop(&peeraddr, buf, MAXPATHLEN, SANY_OPT_NOPORT) == -1) + message(ctx, LOG_WARNING, "couldn't get peer address"); + else + messagex(ctx, LOG_INFO, "accepted connection from: %s", buf); + + /* Create the server connection address */ + outaddr = &(g_state.outaddr); + outname = g_state.outname; + + /* For transparent proxying we have to discover the address to connect to */ + if(g_state.transparent) + { + memset(&addr, 0, sizeof(addr)); + SANY_LEN(addr) = sizeof(addr); + +#ifdef LINUX_TRANSPARENT_PROXY + if(getsockopt(ctx->client.fd, SOL_IP, SO_ORIGINAL_DST, &SANY_ADDR(addr), &SANY_LEN(addr)) == -1) +#else + if(getsockname(ctx->client.fd, &SANY_ADDR(addr1), &SANY_LEN(addr1)) == -1) +#endif + { + message(ctx, LOG_ERR, "couldn't get source address for transparent proxying"); + return -1; + } + + /* Check address types */ + if(sock_any_cmp(&addr, &peeraddr, SANY_OPT_NOPORT) == 0) + { + messagex(ctx, LOG_ERR, "loop detected in transparent proxying"); + return -1; + } + + outaddr = &addr; + } + + /* No transparent proxy but check for loopback option */ + else + { + if(SANY_TYPE(*outaddr) == AF_INET && + outaddr->s.in.sin_addr.s_addr == 0) + { + /* Use the incoming IP as the default */ + memcpy(&addr, &(g_state.outaddr), sizeof(addr)); + memcpy(&(addr.s.in.sin_addr), &(peeraddr.s.in.sin_addr), sizeof(addr.s.in.sin_addr)); + outaddr = &addr; + } +#ifdef HAVE_INET6 + else if(SANY_TYPE(*outaddr) == AF_INET6 && + outaddr->s.in.in6.sin_addr.s_addr == 0) + { + /* Use the incoming IP as the default */ + memcpy(&addr, &(g_state.outaddr), sizeof(addr)); + memcpy(&(addr.s.in.sin6_addr), &(peeraddr.s.in.sin6_addr), sizeof(addr.s.in.sin6_addr)); + outaddr = &addr; + } +#endif + } + + /* Reparse name if possible */ + if(outaddr != &(g_state.outaddr)) + { + if(sock_any_ntop(outaddr, buf, MAXPATHLEN, 0) != -1) + outname = "unknown"; + } + + /* Connect to the server */ + if(clio_connect(ctx, &(ctx->server), outaddr, outname) == -1) + return -1; + + return 0; +} /* ---------------------------------------------------------------------------------- * SMTP HANDLING diff --git a/src/clamsmtpd.h b/src/clamsmtpd.h index 2fc6f9e..eb9378e 100644 --- a/src/clamsmtpd.h +++ b/src/clamsmtpd.h @@ -122,6 +122,7 @@ typedef struct clstate int bounce; /* Send back a reject line */ int quarantine; /* Leave virus files in temp dir */ int debug_files; /* Leave all files in temp dir */ + int transparent; /* Transparent proxying */ /* State --------------------------------- */ int daemonized; /* Whether process is daemonized or not */ diff --git a/src/clio.c b/src/clio.c index a2694bb..b4c9cb8 100644 --- a/src/clio.c +++ b/src/clio.c @@ -90,7 +90,7 @@ static void log_io_data(clamsmtp_context_t* ctx, clio_t* io, const char* data, i memcpy(buf, data, len); buf[len] = 0; - messagex(0, LOG_DEBUG, "%s%s%s", GET_IO_NAME(io), + messagex(ctx, LOG_DEBUG, "%s%s%s", GET_IO_NAME(io), read ? " < " : " > ", buf); data += pos; diff --git a/src/clstate.c b/src/clstate.c index 0a166f9..2c74e11 100644 --- a/src/clstate.c +++ b/src/clstate.c @@ -86,6 +86,7 @@ #define CFG_BOUNCE "Bounce" #define CFG_QUARANTINE "Quarantine" #define CFG_DEBUGFILES "DebugFiles" +#define CFG_TRANSPARENT "TransparentProxy" /* The set of delimiters that can be present between config and value */ #define CFG_DELIMS ": \t" @@ -230,7 +231,6 @@ int clstate_parse_config(clstate_t* state, const char* configfile) else if(PARSE(CFG_DIRECTORY)) state->directory = VAL; - else if(PARSE(CFG_BOUNCE)) state->bounce = strtob(VAL); @@ -240,6 +240,9 @@ int clstate_parse_config(clstate_t* state, const char* configfile) else if(PARSE(CFG_DEBUGFILES)) state->debug_files = strtob(VAL); + else if(PARSE(CFG_TRANSPARENT)) + state->transparent = strtob(VAL); + /* Unrecognized option */ else errx(2, "invalid config line: %s", p); @@ -264,10 +267,6 @@ void clstate_validate(clstate_t* state) if(state->timeout.tv_sec <= 0) errx(2, "invalid setting: " CFG_TIMEOUT); - /* This option has no default, but is required */ - if(state->outname == NULL) - errx(2, "no " CFG_OUTADDR " specified."); - if(state->bounce == -1) errx(2, "invalid value for " CFG_BOUNCE); if(state->quarantine == -1) @@ -275,11 +274,22 @@ void clstate_validate(clstate_t* state) if(state->debug_files == -1) errx(2, "invalid value for " CFG_DEBUGFILES); + /* This option has no default, but is required */ + if(state->outname == NULL && !state->transparent) + errx(2, "no " CFG_OUTADDR " specified."); + /* Parse all the addresses */ + if(state->outname != NULL) + { + if(state->transparent) + warnx("the " CFG_OUTADDR " option will be ignored when " CFG_TRANSPARENT " is enabled"); + else + if(sock_any_pton(state->outname, &(state->outaddr), SANY_OPT_DEFPORT(25)) == -1) + errx(2, "invalid " CFG_OUTADDR " socket name or ip: %s", state->outname); + } + if(sock_any_pton(state->listenname, &(state->listenaddr), SANY_OPT_DEFANY | SANY_OPT_DEFPORT(DEFAULT_PORT)) == -1) errx(2, "invalid " CFG_LISTENADDR " socket name or ip: %s", state->listenname); - if(sock_any_pton(state->outname, &(state->outaddr), SANY_OPT_DEFPORT(25)) == -1) - errx(2, "invalid " CFG_OUTADDR " socket name or ip: %s", state->outname); if(sock_any_pton(state->clamname, &(state->clamaddr), SANY_OPT_DEFLOCAL) == -1) errx(2, "invalid " CFG_CLAMADDR " socket name: %s", state->clamname); diff --git a/src/sock_any.c b/src/sock_any.c index 03621ff..e5f24c0 100644 --- a/src/sock_any.c +++ b/src/sock_any.c @@ -353,3 +353,33 @@ int sock_any_ntop(struct sockaddr_any* any, char* addr, size_t addrlen, int opts return 0; } + +int sock_any_cmp(struct sockaddr_any* a1, struct sockaddr_any* a2, int opts) +{ + if(a1->s.a.sa_family != a2->s.a.sa_family) + return -1; + + switch(a1->s.a.sa_family) + { + case AF_UNIX: + return strcmp(a1->s.un.sun_path, a2->s.un.sun_path); + + case AF_INET: + if(memcmp(&(a1->s.in.sin_addr), &(a2->s.in.sin_addr), sizeof(a2->s.in.sin_addr)) != 0) + return -1; + if(!(opts && SANY_OPT_NOPORT) && a1->s.in.sin_port != a2->s.in.sin_port) + return -1; + return 0; +#ifdef HAVE_INET6 + case AF_INET6: + if(memcmp(&(a1->s.in6.sin6_addr), &(a2->s.in6.sin6_addr), sizeof(a2->s.in6.sin6_addr)) != 0) + return -1; + if(!(opts && SANY_OPT_NOPORT) && a1->s.in6.sin6_port != a2->s.in6.sin6_port) + return -1; + return 0; +#endif + default: + errno = EAFNOSUPPORT; + return -1; + } +} diff --git a/src/sock_any.h b/src/sock_any.h index 0b492b4..9fcb128 100644 --- a/src/sock_any.h +++ b/src/sock_any.h @@ -82,7 +82,9 @@ int sock_any_pton(const char* addr, struct sockaddr_any* any, int opts); int sock_any_ntop(struct sockaddr_any* any, char* addr, size_t addrlen, int opts); -/* Don't print the port */ +/* Don't print or compare the port */ #define SANY_OPT_NOPORT 0x01000000 +int sock_any_cmp(struct sockaddr_any* a1, struct sockaddr_any* a2, int opts); + #endif /* __SOCK_ANY_H__ */ -- cgit v1.2.3