From b858e371f4b875b42dcffe1584dab6363d622d3d Mon Sep 17 00:00:00 2001 From: Eygene Ryabinkin Date: Sat, 21 May 2011 14:07:57 +0400 Subject: [PATCH] Enables XCLIENT support for Exim Patch from Vsevolod Stakhov, http://cebka.pp.ru/blog/2009/01/14/%D0%BD%D0%BE%D0%B2%D0%B0%D1%8F-%D0%B2%D0%B5%D1%80%D1%81%D0%B8%D1%8F-%D0%BF%D0%B0%D1%82%D1%87%D0%B0-%D0%B4%D0%BB%D1%8F-%D0%BF%D0%BE%D0%B4%D0%B4%D0%B5%D1%80%D0%B6%D0%BA%D0%B8-exim-xclient/ but slightly reworked for Exim 4.76. Taken-from: http://cebka.pp.ru/blog/patch-exim-xclient Signed-off-by: Eygene Ryabinkin --- src/globals.c | 1 + src/globals.h | 1 + src/macros.h | 2 +- src/readconf.c | 3 +- src/smtp_in.c | 258 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 258 insertions(+), 7 deletions(-) diff --git a/src/globals.c b/src/globals.c index c7e6c20..9594ad7 100644 --- src/globals.c +++ src/globals.c @@ -634,6 +634,7 @@ uschar *helo_try_verify_hosts = NULL; BOOL helo_verified = FALSE; BOOL helo_verify_failed = FALSE; uschar *helo_verify_hosts = NULL; +uschar *xclient_allow_hosts = NULL; const uschar *hex_digits = CUS"0123456789abcdef"; uschar *hold_domains = NULL; BOOL host_checking = FALSE; diff --git a/src/globals.h b/src/globals.h index 3a1e537..e6f25f7 100644 --- src/globals.h +++ src/globals.h @@ -399,6 +399,7 @@ extern uschar *helo_try_verify_hosts; /* Soft check HELO argument for these */ extern BOOL helo_verified; /* True if HELO verified */ extern BOOL helo_verify_failed; /* True if attempt failed */ extern uschar *helo_verify_hosts; /* Hard check HELO argument for these */ +extern uschar *xclient_allow_hosts; /* Allow XCLIENT command for specified hosts */ extern const uschar *hex_digits; /* Used in several places */ extern uschar *hold_domains; /* Hold up deliveries to these */ extern BOOL host_find_failed_syntax;/* DNS syntax check failure */ diff --git a/src/macros.h b/src/macros.h index 610221f..a5a9066 100644 --- src/macros.h +++ src/macros.h @@ -718,7 +718,7 @@ is "empty". */ enum { SCH_NONE, SCH_AUTH, SCH_DATA, SCH_EHLO, SCH_ETRN, SCH_EXPN, SCH_HELO, SCH_HELP, SCH_MAIL, SCH_NOOP, SCH_QUIT, SCH_RCPT, SCH_RSET, SCH_STARTTLS, - SCH_VRFY }; + SCH_VRFY, SCH_XCLIENT }; /* Returns from host_find_by{name,dns}() */ diff --git a/src/readconf.c b/src/readconf.c index 7aa44cf..e0022e8 100644 --- src/readconf.c +++ src/readconf.c @@ -433,7 +433,8 @@ static optionlist optionlist_config[] = { { "uucp_from_pattern", opt_stringptr, &uucp_from_pattern }, { "uucp_from_sender", opt_stringptr, &uucp_from_sender }, { "warn_message_file", opt_stringptr, &warn_message_file }, - { "write_rejectlog", opt_bool, &write_rejectlog } + { "write_rejectlog", opt_bool, &write_rejectlog }, + { "xclient_allow_hosts", opt_stringptr, &xclient_allow_hosts }, }; static int optionlist_config_size = diff --git a/src/smtp_in.c b/src/smtp_in.c index 38c7afc..30649bd 100644 --- src/smtp_in.c +++ src/smtp_in.c @@ -64,10 +64,10 @@ enum { /* These commands are required to be synchronized, i.e. to be the last in a block of commands when pipelining. */ - HELO_CMD, EHLO_CMD, DATA_CMD, /* These are listed in the pipelining */ - VRFY_CMD, EXPN_CMD, NOOP_CMD, /* RFC as requiring synchronization */ - ETRN_CMD, /* This by analogy with TURN from the RFC */ - STARTTLS_CMD, /* Required by the STARTTLS RFC */ + HELO_CMD, EHLO_CMD, XCLIENT_CMD, DATA_CMD, /* These are listed in the pipelining */ + VRFY_CMD, EXPN_CMD, NOOP_CMD, /* RFC as requiring synchronization */ + ETRN_CMD, /* This by analogy with TURN from the RFC */ + STARTTLS_CMD, /* Required by the STARTTLS RFC */ /* This is a dummy to identify the non-sync commands when pipelining */ @@ -153,6 +153,7 @@ static smtp_cmd_list cmd_list[] = { { "rset", sizeof("rset")-1, RSET_CMD, FALSE, FALSE }, /* First */ { "helo", sizeof("helo")-1, HELO_CMD, TRUE, FALSE }, { "ehlo", sizeof("ehlo")-1, EHLO_CMD, TRUE, FALSE }, + { "xclient", sizeof("xclient")-1, XCLIENT_CMD, TRUE, FALSE }, { "auth", sizeof("auth")-1, AUTH_CMD, TRUE, TRUE }, #ifdef SUPPORT_TLS { "starttls", sizeof("starttls")-1, STARTTLS_CMD, FALSE, FALSE }, @@ -185,7 +186,7 @@ It must be kept in step with the SCH_xxx enumerations. */ static uschar *smtp_names[] = { - US"NONE", US"AUTH", US"DATA", US"EHLO", US"ETRN", US"EXPN", US"HELO", + US"NONE", US"AUTH", US"DATA", US"EHLO", US"ETRN", US"EXPN", US"HELO", US"XCLIENT", US"HELP", US"MAIL", US"NOOP", US"QUIT", US"RCPT", US"RSET", US"STARTTLS", US"VRFY" }; @@ -865,6 +866,205 @@ log_write(0, LOG_MAIN, "no MAIL in SMTP connection from %s D=%s%s", } +/************************************************* +* Check XCLIENT line and set sender_address * +*************************************************/ + +/* Check the format of a XCLIENT line. + * XCLIENT Command syntax + * + * An example client-server conversation is given at the end of this document. + * + * In SMTP server EHLO replies, the keyword associated with this extension is XCLIENT. It is followed by the names of the attributes that the XCLIENT implementation supports. + * + * The XCLIENT command may be sent at any time, except in the middle of a mail delivery transaction (i.e. between MAIL and DOT, or MAIL and RSET). + * The XCLIENT command may be pipelined when the server supports ESMTP command pipelining. + * To avoid triggering spamware detectors, the command should be sent at the end of a command group. + * + * The syntax of XCLIENT requests is described below. + * Upper case and quoted strings specify terminals, lowercase strings specify meta terminals, and SP is whitespace. + * Although command and attribute names are shown in upper case, they are in fact case insensitive. + * + * xclient-command = XCLIENT 1*( SP attribute-name"="attribute-value ) + * + * attribute-name = ( NAME | ADDR | PORT | HELO | PROTO | LOGIN) + * + * attribute-value = xtext + * + * Attribute values are xtext encoded as per RFC 1891. + * The NAME attribute specifies an SMTP client hostname (not an SMTP client address), [UNAVAILABLE] when client hostname lookup failed due to a permanent error, or [TEMPUNAVAIL] when the lookup error condition was transient. + * + * The ADDR attribute specifies an SMTP client numerical IPv4 network address, an IPv6 address prefixed with IPV6:, or [UNAVAILABLE] when the address information is unavailable. Address information is not enclosed with []. + * + * The PORT attribute specifies the SMTP client TCP port number as a decimal number, or [UNAVAILABLE] when the information is unavailable. + * The HELO attribute specifies an SMTP HELO parameter value, or the value [UNAVAILABLE] when the information is unavailable. + * The PROTO attribute specifies either SMTP or ESMTP. + * + * Note 1: syntactically valid NAME and HELO attribute-value elements can be up to 255 characters long. + * The client must not send XCLIENT commands that exceed the 512 character limit for SMTP commands. + * To avoid exceeding the limit the client should send the information in multiple XCLIENT commands; for example, send NAME and ADDR first, then HELO and PROTO. + * + * Note 2: [UNAVAILABLE], [TEMPUNAVAIL] and IPV6: may be specified in upper case, lower case or mixed case. +Argument: + s the data portion of the line (already past any white space) + +Returns: TRUE + FALSE +*/ + +/* XCLIENT MACROS */ +#define XCLIENT_UNAVAIL US"[UNAVAILABLE]" +#define XCLIENT_TEMPUNAVAIL US"[TEMPUNAVAIL]" + +static BOOL +smtp_handle_xclient(uschar *s) +{ + uschar *p, *end, *arg; + int len; + p = s; + end = s + Ustrlen(s); + + while (p <= end) { + /* Addr */ + if (strncmpic(p, US"ADDR=", 5) == 0) { + p += 5; + arg = p; + while (*p++ != ' ' && p <= end ); + len = p - arg; + /* Strip whitespace */ + if(*(p - 1) == ' ') { + len --; + } + if (len > 0) { + sender_host_address = string_copy_malloc(string_copyn(arg, len)); + } + else { + return FALSE; + } + } + /* Name */ + else if (strncmpic(p, US"NAME=", 5) == 0) { + p += 5; + arg = p; + while (*p++ != ' ' && p <= end ); + len = p - arg; + /* Strip whitespace */ + if(*(p - 1) == ' ') { + len --; + } + if (len > 0) { + if ((len == sizeof(XCLIENT_UNAVAIL) - 1 && strncmpic(arg, XCLIENT_UNAVAIL, sizeof (XCLIENT_UNAVAIL) -1) == 0) || + (len == sizeof(XCLIENT_TEMPUNAVAIL) - 1 && strncmpic(arg, XCLIENT_TEMPUNAVAIL, sizeof (XCLIENT_UNAVAIL) -1) == 0)) { + sender_host_name = NULL; + } + else { + sender_host_name = string_copy_malloc(string_copyn(arg, len)); + } + } + else { + return FALSE; + } + } + /* Helo */ + else if (strncmpic(p, US"HELO=", 5) == 0) { + p += 5; + arg = p; + while (*p++ != ' ' && p <= end ); + len = p - arg; + /* Strip whitespace */ + if(*(p - 1) == ' ') { + len --; + } + + if (len > 0) { + if ((len == sizeof(XCLIENT_UNAVAIL) - 1 && strncmpic(arg, XCLIENT_UNAVAIL, sizeof (XCLIENT_UNAVAIL) -1) == 0) || + (len == sizeof(XCLIENT_TEMPUNAVAIL) - 1 && strncmpic(arg, XCLIENT_TEMPUNAVAIL, sizeof (XCLIENT_UNAVAIL) -1) == 0)) { + sender_helo_name = NULL; + } + else { + sender_helo_name = string_copy_malloc(string_copyn(arg, len)); + } + } + else { + return FALSE; + } + } + /* Port */ + else if (strncmpic(p, US"PORT=", 5) == 0) { + p += 5; + arg = p; + while (*p++ != ' ' && p <= end); + len = p - arg; + if(*(p - 1) == ' ') { + len --; + } + if (len > 0) { + if ((len == sizeof(XCLIENT_UNAVAIL) - 1 && strncmpic(arg, XCLIENT_UNAVAIL, sizeof (XCLIENT_UNAVAIL) -1) == 0) || + (len == sizeof(XCLIENT_TEMPUNAVAIL) - 1 && strncmpic(arg, XCLIENT_TEMPUNAVAIL, sizeof (XCLIENT_UNAVAIL) -1) == 0)) { + sender_host_port = 0; + } + else { + sender_host_port = Uatoi(arg); + } + } + else { + return FALSE; + } + } + /* Login */ + else if (strncmpic(p, US"LOGIN=", 6) == 0) { + p += 6; + arg = p; + while (*p++ != ' ' && p <= end); + len = p - arg; + if(*(p - 1) == ' ') { + len --; + } + if (len > 0) { + if ((len == sizeof(XCLIENT_UNAVAIL) - 1 && strncmpic(arg, XCLIENT_UNAVAIL, sizeof (XCLIENT_UNAVAIL) -1) == 0) || + (len == sizeof(XCLIENT_TEMPUNAVAIL) - 1 && strncmpic(arg, XCLIENT_TEMPUNAVAIL, sizeof (XCLIENT_UNAVAIL) -1) == 0)) { + authenticated_id = NULL; + sender_host_authenticated = NULL; + } + else { + authenticated_id = string_copy_malloc(string_copyn(arg, len)); + sender_host_authenticated = "xclient"; + authentication_failed = FALSE; + } + } + else { + return FALSE; + } + } + /* Proto */ + else if (strncmpic(p, US"PROTO=", 6) == 0) { + p += 6; + arg = p; + while (*p++ != ' ' && p <= end); + len = p - arg; + if(*(p - 1) == ' ') { + len --; + } + if (len > 0) { + if (len == 4 && (strncmpic(arg, US"SMTP", 4) == 0)) { + esmtp = FALSE; + } + else if (len == 5 && (strncmpic(arg, US"ESMTP", 5) == 0)) { + esmtp = TRUE; + } + } + } + else { + return FALSE; + } + } + + host_build_sender_fullhost(); + return TRUE; +} + +#undef XCLIENT_UNAVAIL +#undef XCLIENT_TEMPUNAVAIL /************************************************* * Check HELO line and set sender_helo_name * @@ -1151,6 +1351,11 @@ while (done <= 0) bsmtp_transaction_linecount = receive_linecount; break; + /* Handle XCLIENT command */ + case XCLIENT_CMD: + smtp_handle_xclient(smtp_cmd_data); + break; + /* The MAIL FROM command requires an address as an operand. All we do here is to parse it for syntactic correctness. The form "<>" is @@ -3185,7 +3390,50 @@ while (done <= 0) toomany = FALSE; break; /* HELO/EHLO */ + case XCLIENT_CMD: + HAD(SCH_XCLIENT); + smtp_mailcmd_count++; + if (helo_required && !helo_seen) + { + smtp_printf("503 HELO or EHLO required\r\n"); + log_write(0, LOG_MAIN|LOG_REJECT, "rejected XCLIENT from %s: no " + "HELO/EHLO given", host_and_ident(FALSE)); + break; + } + + /* Check for an operand */ + if (smtp_cmd_data[0] == 0) + { + done = synprot_error(L_smtp_syntax_error, 501, NULL, + US"XCLIENT must have at least one operand"); + break; + } + if(xclient_allow_hosts != NULL) + { + if (match_isinlist (sender_host_address, &xclient_allow_hosts, ':', NULL, NULL, MCL_NOEXPAND, FALSE, NULL) != OK) + { + done = synprot_error(L_smtp_syntax_error, 550, NULL, + US"XCLIENT is not allowed"); + break; + } + } + else + { + done = synprot_error(L_smtp_syntax_error, 550, NULL, + US"XCLIENT is not allowed"); + break; + } + if(smtp_handle_xclient(smtp_cmd_data) == FALSE) + { + done = synprot_error(L_smtp_syntax_error, 501, NULL, + US"bad command parameter syntax"); + break; + } + smtp_code = US"220"; /* Default status code */ + + smtp_printf("%s XCLIENT success\r\n", smtp_code); + break; /* XCLIENT */ /* The MAIL command requires an address as an operand. All we do here is to parse it for syntactic correctness. The form "<>" is a special case which converts into an empty string. The start/end -- 1.7.0.6