From fb796d7ae45604c7c5842d6111f3a5204eeb53e5 Mon Sep 17 00:00:00 2001 From: Stef Walter Date: Fri, 26 Nov 2004 23:03:27 +0000 Subject: - Add XCLIENT support - Ignore XCLIENT/XFORWARD commands coming from clients. --- ChangeLog | 3 +- common/smtppass.c | 90 +++++++++++++++++++++++++++++++++++++++++++++++++--- common/smtppass.h | 1 + common/spio.c | 17 ++++++++++ common/sppriv.h | 1 + doc/clamsmtpd.conf | 3 ++ doc/clamsmtpd.conf.5 | 4 +++ 7 files changed, 114 insertions(+), 5 deletions(-) diff --git a/ChangeLog b/ChangeLog index 73febc4..b7d0e09 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,6 @@ 1.2 ???? - + - XCLIENT support + - Drop XCLIENT and XFORWARD commands coming in from clients for security. - Added the sample virus_action.sh script to the distribution - Documentation fixes [Olivier Beyssac] diff --git a/common/smtppass.c b/common/smtppass.c index 5d25719..59a4bf4 100644 --- a/common/smtppass.c +++ b/common/smtppass.c @@ -89,11 +89,13 @@ spthread_t; #define SMTP_DATAINTERMED "354 Start mail input; end with ." CRLF #define SMTP_FAILED "451 Local Error" CRLF #define SMTP_NOTSUPP "502 Command not implemented" CRLF +#define SMTP_NOTAUTH "554 Insufficient authorization" CRLF #define SMTP_OK "250 Ok" CRLF #define SMTP_REJPREFIX "550 Content Rejected; " #define SMTP_DATA "DATA" CRLF #define SMTP_NOOP "NOOP" CRLF +#define SMTP_XCLIENT "XCLIENT ADDR=%s" CRLF #define SMTP_BANNER "220 smtp.passthru" CRLF #define SMTP_HELO_RSP "250 smtp.passthru" CRLF #define SMTP_EHLO_RSP "250-smtp.passthru" CRLF @@ -106,6 +108,8 @@ spthread_t; #define ESMTP_CHUNK "CHUNKING" #define ESMTP_BINARY "BINARYMIME" #define ESMTP_CHECK "CHECKPOINT" +#define ESMTP_XFORWARD "XFORWARD" +#define ESMTP_XCLIENT "XCLIENT" #define HELO_CMD "HELO" #define EHLO_CMD "EHLO" @@ -115,6 +119,8 @@ spthread_t; #define RSET_CMD "RSET" #define STARTTLS_CMD "STARTTLS" #define BDAT_CMD "BDAT" +#define XFORWARD_CMD "XFORWARD" +#define XCLIENT_CMD "XCLIENT" #define DATA_END_SIG "." CRLF @@ -146,6 +152,7 @@ spthread_t; #define CFG_KEEPALIVES "KeepAlives" #define CFG_USER "User" #define CFG_PIDFILE "PidFile" +#define CFG_XCLIENT "XClient" /* ----------------------------------------------------------------------- * DEFAULT SETTINGS @@ -794,6 +801,10 @@ static int smtp_passthru(spctx_t* ctx) int first_rsp = 1; /* The first 220 response from server to be filtered */ int filter_host = 0; /* Next response is 250 hostname, which we change */ + /* XCLIENT is for use in access control */ + int xclient_sup = 0; /* Is XCLIENT supported? */ + int xclient_sent = 0; /* Have we sent an XCLIENT command? */ + ASSERT(spio_valid(&(ctx->client)) && spio_valid(&(ctx->server))); @@ -832,6 +843,26 @@ static int smtp_passthru(spctx_t* ctx) /* Only valid after EHLO or HELO commands */ filter_host = 0; + /* + * At this point we may want to send our XCLIENT. This is a per + * connection command, lasts until a RSET. + */ + if(xclient_sup && !xclient_sent && g_state.xclient) + { + sp_messagex(ctx, LOG_DEBUG, "sending XCLIENT"); + + if(spio_write_dataf(ctx, &(ctx->server), SMTP_XCLIENT, ctx->client.peername) == -1) + RETURN(-1); + + if(read_server_response(ctx) == -1) + RETURN(-1); + + if(!get_successful_rsp(S_LINE, NULL)) + sp_messagex(ctx, LOG_WARNING, "server didn't accept XCLIENT"); + + xclient_sent = 1; + } + /* Handle the DATA section via our AV checker */ if(is_first_word(C_LINE, DATA_CMD, KL(DATA_CMD))) { @@ -858,16 +889,29 @@ static int smtp_passthru(spctx_t* ctx) } /* - * We need our response to HELO to be modified in order + * We need our response to HELO and EHLO to be modified in order * to prevent complaints about mail loops */ - else if(is_first_word(C_LINE, EHLO_CMD, KL(EHLO_CMD)) || - is_first_word(C_LINE, HELO_CMD, KL(HELO_CMD))) + else if(is_first_word(C_LINE, EHLO_CMD, KL(EHLO_CMD))) { /* EHLO can have multline responses so we set a flag */ filter_host = 1; } + /* + * We always support XFORWARD on a HELO type connection. We do this + * for security reasons, so that a client can't get around filtering + * by backing up one on the protocol. + */ + else if(is_first_word(C_LINE, HELO_CMD, KL(HELO_CMD))) + { + sp_messagex(ctx, LOG_DEBUG, "XCLIENT support assumed"); + xclient_sup = 1; + + /* Filter host as with EHLO above */ + filter_host = 1; + } + /* * We don't like these commands. Filter them out. We should have * filtered out their service extensions earlier in the EHLO response. @@ -885,6 +929,25 @@ static int smtp_passthru(spctx_t* ctx) continue; } + /* + * For security reasons we're not about to forward any XCLIENTs + * or XFORWARDs from our client through. This could lead to a + * client using our privileged IP address to change an audit + * trail or relay etc... + */ + else if(is_first_word(C_LINE, XCLIENT_CMD, KL(XCLIENT_CMD)) || + is_first_word(C_LINE, XFORWARD_CMD, KL(XFORWARD_CMD))) + { + trim_end(C_LINE); + sp_messagex(ctx, LOG_WARNING, "client attempted use of privileged feature: %s", C_LINE); + + if(spio_write_data(ctx, &(ctx->client), SMTP_NOTAUTH) == -1) + RETURN(-1); + + /* Command handled */ + continue; + } + /* All other commands just get passed through to server */ if(spio_write_data(ctx, &(ctx->server), C_LINE) == -1) RETURN(-1); @@ -963,11 +1026,23 @@ static int smtp_passthru(spctx_t* ctx) */ if(is_first_word(C_LINE, EHLO_CMD, KL(EHLO_CMD))) { + /* + * On ESMTP connections we let the server tell us whether it + * wants XFORWARDs or not. (In contrast to old SMTP above). + */ + if(is_first_word(p, ESMTP_XCLIENT, KL(ESMTP_XCLIENT))) + { + sp_messagex(ctx, LOG_DEBUG, "XCLIENT supported"); + xclient_sup = 1; + } + if(is_first_word(p, ESMTP_PIPELINE, KL(ESMTP_PIPELINE)) || is_first_word(p, ESMTP_TLS, KL(ESMTP_TLS)) || is_first_word(p, ESMTP_CHUNK, KL(ESMTP_CHUNK)) || is_first_word(p, ESMTP_BINARY, KL(ESMTP_BINARY)) || - is_first_word(p, ESMTP_CHECK, KL(ESMTP_CHECK))) + is_first_word(p, ESMTP_CHECK, KL(ESMTP_CHECK)) || + is_first_word(p, ESMTP_XCLIENT, KL(ESMTP_XCLIENT)) || + is_first_word(p, ESMTP_XFORWARD, KL(ESMTP_XFORWARD))) { sp_messagex(ctx, LOG_DEBUG, "filtered ESMTP feature: %s", trim_space((char*)p)); @@ -1598,6 +1673,13 @@ int sp_parse_option(const char* name, const char* value) ret = 1; } + else if(strcasecmp(CFG_XCLIENT, name) == 0) + { + if((g_state.xclient = strtob(value)) == -1) + errx(2, "invalid value for " CFG_XCLIENT); + ret = 1; + } + else if(strcasecmp(CFG_OUTADDR, name) == 0) { if(sock_any_pton(value, &(g_state.outaddr), SANY_OPT_DEFPORT(25)) == -1) diff --git a/common/smtppass.h b/common/smtppass.h index 418686e..5522ca9 100644 --- a/common/smtppass.h +++ b/common/smtppass.h @@ -99,6 +99,7 @@ int spio_read_line(struct spctx* ctx, spio_t* io, int opts); /* Write data to socket (must supply line endings if needed). * Guaranteed to accept all data or fail. */ int spio_write_data(struct spctx* ctx, spio_t* io, const char* data); +int spio_write_dataf(struct spctx* ctx, spio_t* io, const char* fmt, ...); int spio_write_data_raw(struct spctx* ctx, spio_t* io, unsigned char* buf, int len); /* Empty the given socket */ diff --git a/common/spio.c b/common/spio.c index e8425bf..f43988a 100644 --- a/common/spio.c +++ b/common/spio.c @@ -510,6 +510,23 @@ int spio_write_data(spctx_t* ctx, spio_t* io, const char* data) return spio_write_data_raw(ctx, io, (unsigned char*)data, len); } +int spio_write_dataf(struct spctx* ctx, spio_t* io, const char* fmt, ...) +{ + char buf[SP_LINE_LENGTH]; + va_list ap; + ASSERT(ctx && io && fmt); + + buf[0] = 0; + + va_start(ap, fmt); + vsnprintf(buf, SP_LINE_LENGTH, fmt, ap); + va_end(ap); + + buf[SP_LINE_LENGTH - 1] = 0; + + return spio_write_data(ctx, io, buf); +} + int spio_write_data_raw(spctx_t* ctx, spio_t* io, unsigned char* buf, int len) { int r; diff --git a/common/sppriv.h b/common/sppriv.h index e978107..2ecf249 100644 --- a/common/sppriv.h +++ b/common/sppriv.h @@ -49,6 +49,7 @@ typedef struct spstate struct timeval timeout; /* Timeout for communication */ int keepalives; /* Send server keep alives at this interval */ int transparent; /* Transparent proxying */ + int xclient; /* Send XFORWARD info */ const char* directory; /* The temp directory */ const char* user; /* User to run as */ const char* pidfile; /* The pid file for daemon */ diff --git a/doc/clamsmtpd.conf b/doc/clamsmtpd.conf index 2033171..8ef4820 100644 --- a/doc/clamsmtpd.conf +++ b/doc/clamsmtpd.conf @@ -22,6 +22,9 @@ OutAddress: 10026 # Keep Alives (ie: NOOP's to server) #KeepAlives: 0 +# Send XCLIENT commands to receiving server +#XClient: off + # Address to listen on (defaults to all local addresses on port 10025) #Listen: 0.0.0.0:10025 diff --git a/doc/clamsmtpd.conf.5 b/doc/clamsmtpd.conf.5 index e9cac98..49cd582 100644 --- a/doc/clamsmtpd.conf.5 +++ b/doc/clamsmtpd.conf.5 @@ -149,6 +149,10 @@ in .Xr clamsmtpd 8 for a discussion of this option. [ Default: off ] +.It Ar XClient +Send an XCLIENT command to the receiving server. This is useful for forwarding +client addresses and connection info to servers that support this feature. +[ Default: off ] .El .Sh ADDRESSES Addresses can be specified in multiple formats: -- cgit v1.2.3