Exim had issues with error messages on mail rejection that weren't always very useful. This patch adds overkill messages that give a lot more info, as well as gives an explicit reject when the sending server is refusing empty envelope froms. This patch also adds the "sender_verify_callback_postmaster" option (disabled by default) which lets you make sure that the calling domains accepts mail for postmaster before you accept mail from them. This only works if you are already using SMTP callback to start with. I'm to blame for all of it :-) Marc MERLIN 2001/07/06 diff -urN exim-3.31-99.test.org/src/globals.c exim-3.31-99.test/src/globals.c --- exim-3.31-99.test.org/src/globals.c Fri Jul 6 14:45:46 2001 +++ exim-3.31-99.test/src/globals.c Tue Jul 3 17:36:23 2001 @@ -669,6 +669,7 @@ char *sender_verify_callback_domains = NULL; char *sender_verify_callback_error = NULL; BOOL sender_verify_callback_hdrfrm = TRUE; +BOOL sender_verify_callback_postmaster = FALSE; int sender_verify_callback_timeout = 30; BOOL sender_verify_fixup = FALSE; char *sender_verify_hosts = "*"; diff -urN exim-3.31-99.test.org/src/globals.h exim-3.31-99.test/src/globals.h --- exim-3.31-99.test.org/src/globals.h Fri Jul 6 14:45:46 2001 +++ exim-3.31-99.test/src/globals.h Tue Jul 3 17:36:58 2001 @@ -492,6 +492,7 @@ extern char *sender_verify_callback_domains; /* For SMTP callbacks */ extern char *sender_verify_callback_error; /* ditto - holds error */ extern BOOL sender_verify_callback_hdrfrm; /* callback on hdrfrm too */ +extern BOOL sender_verify_callback_postmaster; /* check for remote postmaster*/ extern int sender_verify_callback_timeout; /* ditto */ extern BOOL sender_verify_fixup; /* Fix broken senders from headers */ extern char *sender_verify_hosts; /* Verification hosts */ diff -urN exim-3.31-99.test.org/src/readconf.c exim-3.31-99.test/src/readconf.c --- exim-3.31-99.test.org/src/readconf.c Fri Jul 6 14:45:46 2001 +++ exim-3.31-99.test/src/readconf.c Tue Jul 3 17:37:38 2001 @@ -221,7 +221,8 @@ { "sender_verify", opt_bool, &sender_verify }, { "sender_verify_batch", opt_bool, &sender_verify_batch }, { "sender_verify_callback_domains", opt_stringptr, &sender_verify_callback_domains }, - { "sender_verify_callback_hdrfrm", opt_bool, &sender_verify_callback_hdrfrm }, + { "sender_verify_callback_hdrfrm", opt_bool, &sender_verify_callback_hdrfrm }, + { "sender_verify_callback_postmaster",opt_bool,&sender_verify_callback_postmaster }, { "sender_verify_callback_timeout", opt_time, &sender_verify_callback_timeout }, { "sender_verify_fixup", opt_bool, &sender_verify_fixup }, { "sender_verify_hosts", opt_stringptr, &sender_verify_hosts }, diff -urN exim-3.31-99.test.org/src/verify.c exim-3.31-99.test/src/verify.c --- exim-3.31-99.test.org/src/verify.c Fri Jul 6 14:45:46 2001 +++ exim-3.31-99.test/src/verify.c Fri Jul 6 13:56:49 2001 @@ -350,38 +350,117 @@ smtp_write_command(&outblock, FALSE, "HELO %s\r\n", primary_hostname) && smtp_read_response(&inblock, US big_buffer, big_buffer_size, '2', - sender_verify_callback_timeout) && + sender_verify_callback_timeout)) + { smtp_write_command(&outblock, FALSE, "MAIL FROM:<>\r\n") && smtp_read_response(&inblock, US big_buffer, big_buffer_size, '2', - sender_verify_callback_timeout) && - smtp_write_command(&outblock, FALSE, "RCPT TO:<%.1000s>\r\n", - addr->orig)) - { - if (smtp_read_response(&inblock, US big_buffer, big_buffer_size, '2', - sender_verify_callback_timeout)) - { - done = TRUE; /* Address accepted */ - } - else if (errno == 0) - { - sender_verify_callback_error = - string_sprintf("response from %s [%s] was %s", host->name, - host->address, big_buffer); - if (big_buffer[0] == '5') /* Address rejected */ + sender_verify_callback_timeout); + if (big_buffer[0] == '5') /* Bonehead server */ { + HDEBUG(1) debug_printf("MAIL FROM: <> rejected. Losers!\n"); + sender_verify_callback_error = + string_sprintf("response from %s [%s] after \"MAIL FROM: <>\" was \"%s\". This does not help fight spam effectively, breaks RFCs, and prevents you from getting bounces so we can't accept mail from you", + host->name, host->address, big_buffer); rc = FAIL; addr->message = string_sprintf("callback check with host %s failed: %s", host->name, big_buffer); done = TRUE; } + else + { + HDEBUG(1) debug_printf("MAIL FROM: <> accepted, trying RCPT TO: <%.1000s>\n",addr->orig); + if (smtp_write_command(&outblock, FALSE, "RCPT TO:<%.1000s>\r\n", + addr->orig)) + { + if (smtp_read_response(&inblock, US big_buffer, big_buffer_size, + '2', sender_verify_callback_timeout)) + { + HDEBUG(3) debug_printf("RCPT TO: <%.1000s> accepted \n",addr->orig); + if (sender_verify_callback_postmaster) + { + HDEBUG(1) debug_printf("Making sure that domain accepts mail for postmaster (sender_verify_callback_postmaster enabled)\n", addr->orig); + if (smtp_write_command(&outblock, FALSE, "RSET\r\n") && + smtp_read_response(&inblock, US big_buffer, + big_buffer_size, '2', sender_verify_callback_timeout) + && + smtp_write_command(&outblock, FALSE, + "MAIL FROM:<>\r\n") && + smtp_read_response(&inblock, US big_buffer, + big_buffer_size, '2', sender_verify_callback_timeout) + && + smtp_write_command(&outblock, FALSE, + "RCPT TO:\r\n", addr->domain)) + { + if (smtp_read_response(&inblock, US big_buffer, + big_buffer_size, '2', sender_verify_callback_timeout)) + { + done = TRUE; /* Address accepted */ + } + else if (errno == 0) + { + sender_verify_callback_error = + /* If you change this message, change it around line + * 1271 too */ + string_sprintf("Response from %s [%s] was \"%s\" when checking for existence of a postmaster mailbox. RFC 822 section 6.3 (which you have to abide by if you send and receive Email) states that you are required to have a postmaster mailbox that is routed to the person responsible for mail (http://www.rfc822.com/). We need to be able to contact you at the postmaster address to resolve potential problems if need be before we can accept your mail. Please create a postmaster account at your site", host->name, host->address, big_buffer); + if (big_buffer[0] == '5') /* Address rejected */ + { + rc = FAIL; + addr->message = + string_sprintf("callback check with host %s failed: %s", + host->name, big_buffer); + done = TRUE; + } + } + else + { + HDEBUG(1) debug_printf("Unknown codepath 2 in SMTP callback\n"); + } + } + else + { + HDEBUG(1) debug_printf("SMTP callback didn't complete after second RCPT TO:\n"); + } + } + else + { + done = TRUE; /* Address accepted */ + } + } + else if (errno == 0) + { + sender_verify_callback_error = + string_sprintf("response from %s [%s] was %s", host->name, + host->address, big_buffer); + if (big_buffer[0] == '5') /* Address rejected */ + { + rc = FAIL; + addr->message = + string_sprintf("callback check with host %s failed: %s", + host->name, big_buffer); + done = TRUE; + } + } + else + { + + HDEBUG(1) debug_printf("Unknown codepath 1 in SMTP callback\n"); + } + } + else + { + HDEBUG(1) debug_printf("SMTP callback didn't complete after first RCPT TO:\n"); + } + } } + else + { + HDEBUG(1) debug_printf("SMTP callback didn't complete\n"); + } + smtp_write_command(&outblock, FALSE, "QUIT\r\n"); + close(inblock.sock); } - smtp_write_command(&outblock, FALSE, "QUIT\r\n"); - close(inblock.sock); - } - /* Failure to connect to any host, or any response other than 2xx or 5xx is a temporary error. */ @@ -774,7 +853,7 @@ called without first calling the first one. The reason for this approach is that some SMTP mailers treat any error returned after the data has been transmitted as temporary (contrary to RFC821) and keep retrying, even after -they have been sent a 5xx error at the previous attempt. To get round this, +they have been sent a 5xx error at the previous attempt. To get around this, exim keeps a database of failed messages and their hosts, and if the same bad address is received from the same host soon afterwards, it is rejected at the preliminary stage (meaning after MAIL FROM for SMTP) in the hope that the far @@ -881,14 +960,14 @@ { if (rc == DEFER) { - *errmess = "warning: temporarily unable to resolve sender address: " + *errmess = "warning: temporarily unable to resolve envelope sender address: " "accepted unverified"; } else { *errmess = sender_is_local? - "warning: unknown local-part in sender address" : - "warning: cannot route to sender address"; + "warning: unknown local-part in envelope sender address" : + "warning: cannot route to envelope sender address"; } *errcode = 250; sender_ok = SENDER_OK_WARNING; @@ -962,9 +1041,9 @@ dbfn_close(dbm_file); DEBUG(2) debug_printf("%s verification deferred\n", sender_address); *errcode = 451; - *errmess = "temporarily unable to verify sender address (try again later)"; + *errmess = "temporarily unable to verify envelope sender address (try again later)"; if (sender_verify_callback_error != NULL) - *errmess = string_sprintf("%s: %s", *errmess, sender_verify_callback_error); + *errmess = string_sprintf("%s. An SMTP callback to your mail server yielded the following error: %s", *errmess, sender_verify_callback_error); return FALSE; } @@ -1004,7 +1083,7 @@ { *errcode = 550; *errmess = sender_is_local? - "unknown local-part in sender" : "cannot route to sender"; + "unknown local-part in sender" : "cannot route to envelope sender"; if (sender_verify_callback_error != NULL) *errmess = string_sprintf("%s: %s", *errmess, sender_verify_callback_error); reject->rejected_mail_from = TRUE; @@ -1106,7 +1185,7 @@ *errcode = 451; *errmess = string_sprintf( "can't currently verify any sender in the header lines " - "(envelope sender is <%.1024s>) - try later", sender_address); + "(envelope sender is <%.1024s>). Are you sure your domain in From: and/or Reply-To: resolves from the internet (host -t MX domain) and can be connected back to for delivery of replies? - Failure is temporary, you can try again later", sender_address); return FALSE; } @@ -1118,19 +1197,28 @@ if (headers_checks_fail) { *errcode = 550; - *errmess = (verify_address_parse_error == NULL)? - string_sprintf("there is no valid sender in any header line " - "(envelope sender is <%.1024s>)", sender_address) : - string_sprintf("%s (envelope sender is <%.1024s>)", - verify_address_parse_error, - sender_address); + if (verify_address_parse_error != NULL) + { + *errmess = string_sprintf("there is no valid sender in any header line: %s (envelope sender is <%.1024s>)", + verify_address_parse_error, sender_address); + } + else if (sender_verify_callback_error != NULL) + { + *errmess = string_sprintf("there is no valid sender in any header line" + " (envelope sender is <%.1024s>). Your mail server returned: %s.", + sender_address, sender_verify_callback_error); + } + else + { + *errmess = string_sprintf("there is no valid sender in any header line" + " (envelope sender is <%.1024s>). Are you sure your domain in From: and/or Reply-To: resolves from the internet (host -t MX domain) and can be connected back to for delivery of replies?", + sender_address); + } return FALSE; } - log_write(3, LOG_REJECT, "warning: from%s <%.1024s>: %s", - host_and_ident(" ", NULL), sender_address, - (verify_address_parse_error == NULL)? - "no valid sender in headers" : verify_address_parse_error); + log_write(3, LOG_REJECT, "warning: rejected from%s <%.1024s>: %s", + host_and_ident(" ", NULL), sender_address, errmess); break; } } @@ -1169,20 +1257,33 @@ { *errcode = 451; *errmess = string_sprintf( - "temporarily unable to verify sender address <%.1024s> (try later)", + "temporarily unable to verify envelope sender address <%.1024s> (There was a temporary failure while looking up the hostname in DNS, it was unreachable, or the mail server(s) for that hostname is/are currently not responding and your address couldn't be verified. You may be using an internal hostname that's not reachable from the internet and if so, bounces can't reach you as a result. You should setup an MX record for it, or masquerade your domain to something that does accept mail from the internet. This failure is temporary", sender_address); + /* Actually, I don't think this would ever get run, but just in case -- Marc */ + if (sender_verify_callback_error != NULL) + *errmess = string_sprintf("%s: %s", *errmess, sender_verify_callback_error); } else { *errcode = 550; - *errmess = string_sprintf(sender_is_local? - "unknown local part in sender <%.1024s>" : - "cannot route to sender <%.1024s>", - sender_address); + if (sender_verify_callback_error != NULL) + { + *errmess = string_sprintf(sender_is_local? + "unknown local part in envelope sender <%.1024s>" : + strstr(sender_verify_callback_error, "when checking for existence of a postmaster mailbox")? + "Cannot accept mail from <%.1024s> because your server doesn't have a postmaster account: %s" : + "Cannot route to envelope sender <%.1024s> (The envelope sender does not exist according to your mail server when it was asked): %s", + sender_address, sender_verify_callback_error); + } + else + { + *errmess = string_sprintf(sender_is_local? + "unknown local part in envelope sender <%.1024s>" : + "cannot route to envelope sender <%.1024s> (The most probable reason is that your domain doesn't resolve from the internet or the mail server(s) it resolves to isn't/aren't reachable. If you are using an internal domain, you should not expose it in your mail headers as it's not reachable from the internet and bounces can't reach you as a result. You should setup an MX record for it, or masquerade your domain to something that does accept mail from the internet)", + sender_address); + } } -if (sender_verify_callback_error != NULL) - *errmess = string_sprintf("%s: %s", *errmess, sender_verify_callback_error); /* Set up the key name and open the hints database. O_RDWR (rather than O_WRONLY) is needed by Berkeley native DB even when reading only. If the diff -urN exim-3.31-99.test.org/src/version.c exim-3.31-99.test/src/version.c --- exim-3.31-99.test.org/src/version.c Fri Jul 6 14:45:46 2001 +++ exim-3.31-99.test/src/version.c Tue Jul 3 11:43:27 2001 @@ -10,7 +10,7 @@ #include "exim.h" -#define THIS_VERSION "3.31-VA-mm1" +#define THIS_VERSION "3.31-VA-mm2" /* The header file cnumber.h contains a single line containing the