-/* $Cambridge: exim/src/src/verify.c,v 1.46 2007/01/17 11:17:58 ph10 Exp $ */
-
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2007 */
+/* Copyright (c) University of Cambridge 1995 - 2009 */
/* See the file NOTICE for conditions of use and distribution. */
/* Functions concerned with verifying things. The original code for callout
#include "exim.h"
+#define CUTTHROUGH_CMD_TIMEOUT 30 /* timeout for cutthrough-routing calls */
+#define CUTTHROUGH_DATA_TIMEOUT 60 /* timeout for cutthrough-routing calls */
+address_item cutthrough_addr;
/* Structure for caching DNSBL lookups */
if (callout_connect < 0) callout_connect = callout;
callout_start_time = time(NULL);
+/* Before doing a real callout, if this is an SMTP connection, flush the SMTP
+output because a callout might take some time. When PIPELINING is active and
+there are many recipients, the total time for doing lots of callouts can add up
+and cause the client to time out. So in this case we forgo the PIPELINING
+optimization. */
+
+if (smtp_out != NULL && !disable_callout_flush) mac_smtp_fflush();
+
/* Now make connections to the hosts and do real callouts. The list of hosts
is passed in as an argument. */
log_write(0, LOG_MAIN|LOG_PANIC, "<%s>: %s", addr->address,
addr->message);
- /* Expand the helo_data string to find the host name to use. */
-
- if (tf->helo_data != NULL)
- {
- uschar *s = expand_string(tf->helo_data);
- if (s == NULL)
- log_write(0, LOG_MAIN|LOG_PANIC, "<%s>: failed to expand transport's "
- "helo_data value for callout: %s", addr->address,
- expand_string_message);
- else active_hostname = s;
- }
-
- deliver_host = deliver_host_address = NULL;
- deliver_domain = save_deliver_domain;
-
/* Set HELO string according to the protocol */
if (Ustrcmp(tf->protocol, "lmtp") == 0) helo = US"LHLO";
set the error for the last one. Use the callout_connect timeout. */
inblock.sock = outblock.sock =
- smtp_connect(host, host_af, port, interface, callout_connect, TRUE);
+ smtp_connect(host, host_af, port, interface, callout_connect, TRUE, NULL);
+ /* reconsider DSCP here */
if (inblock.sock < 0)
{
addr->message = string_sprintf("could not connect to %s [%s]: %s",
host->name, host->address, strerror(errno));
+ deliver_host = deliver_host_address = NULL;
+ deliver_domain = save_deliver_domain;
continue;
}
+ /* Expand the helo_data string to find the host name to use. */
+
+ if (tf->helo_data != NULL)
+ {
+ uschar *s = expand_string(tf->helo_data);
+ if (s == NULL)
+ log_write(0, LOG_MAIN|LOG_PANIC, "<%s>: failed to expand transport's "
+ "helo_data value for callout: %s", addr->address,
+ expand_string_message);
+ else active_hostname = s;
+ }
+
+ deliver_host = deliver_host_address = NULL;
+ deliver_domain = save_deliver_domain;
+
/* Wait for initial response, and send HELO. The smtp_write_command()
function leaves its command in big_buffer. This is used in error responses.
Initialize it in case the connection is rejected. */
if (done && pm_mailfrom != NULL)
{
+ /*XXX not suitable for cutthrough - sequencing problems */
+ cutthrough_delivery= FALSE;
+
done =
smtp_write_command(&outblock, FALSE, "RSET\r\n") >= 0 &&
smtp_read_response(&inblock, responsebuffer,
/* End the SMTP conversation and close the connection. */
- if (send_quit) (void)smtp_write_command(&outblock, FALSE, "QUIT\r\n");
- (void)close(inblock.sock);
+ /*XXX cutthrough - if "done"
+ and "yeild" is OK
+ and we have no cutthrough conn so far
+ here is where we want to leave the conn open */
+ /* and leave some form of marker for it */
+ /*XXX in fact for simplicity we should abandon cutthrough as soon as more than one address
+ comes into play */
+/*XXX what about TLS? */
+ if ( cutthrough_delivery
+ && done
+ && yield == OK
+ && cutthrough_fd < 0
+ && (options & (vopt_callout_recipsender|vopt_callout_recippmaster)) == vopt_callout_recipsender
+ && !random_local_part
+ && !pm_mailfrom
+ )
+ {
+ cutthrough_fd= outblock.sock; /* We assume no buffer in use in the outblock */
+ cutthrough_addr= *addr; /* Save the address_item for later logging */
+ }
+ else
+ {
+ if (send_quit) (void)smtp_write_command(&outblock, FALSE, "QUIT\r\n");
+ (void)close(inblock.sock);
+ }
+
} /* Loop through all hosts, while !done */
/* If we get here with done == TRUE, a successful callout happened, and yield
+void
+open_cutthrough_connection( address_item * addr )
+{
+address_item addr2;
+
+/* Use a recipient-verify-callout to set up the cutthrough connection. */
+/* We must use a copy of the address for verification, because it might
+get rewritten. */
+
+addr2 = *addr;
+HDEBUG(D_acl) debug_printf("----------- start cutthrough setup ------------\n");
+(void) verify_address(&addr2, NULL,
+ vopt_is_recipient | vopt_callout_recipsender | vopt_callout_no_cache,
+ CUTTHROUGH_CMD_TIMEOUT, -1, -1,
+ NULL, NULL, NULL);
+HDEBUG(D_acl) debug_printf("----------- end cutthrough setup ------------\n");
+return;
+}
+
+
+static smtp_outblock ctblock;
+uschar ctbuffer[8192];
+
+
+void
+cancel_cutthrough_connection( void )
+{
+ctblock.ptr = ctbuffer;
+cutthrough_delivery= FALSE;
+if(cutthrough_fd >= 0) /*XXX get that initialised, also at RSET */
+ {
+ int rc;
+
+ /* We could be sending this after a bunch of data, but that is ok as
+ the only way to cancel the transfer in dataphase is to drop the tcp
+ conn before the final dot.
+ */
+ HDEBUG(D_transport|D_acl|D_v) debug_printf(" SMTP>> QUIT\n");
+ rc= send(cutthrough_fd, "QUIT\r\n", 6, 0);
+ /*XXX error handling? TLS? See flush_buffer() in smtp_out.c */
+
+ (void)close(cutthrough_fd);
+ cutthrough_fd= -1;
+ HDEBUG(D_acl) debug_printf("----------- cutthrough shutdown ------------\n");
+ }
+}
+
+
+
+/* Buffered output counted data block. Return boolean success */
+BOOL
+cutthrough_puts(uschar * cp, int n)
+{
+if(cutthrough_fd >= 0)
+ while(n--)
+ {
+ /*XXX TLS? See flush_buffer() in smtp_out.c */
+
+ if(ctblock.ptr >= ctblock.buffer+ctblock.buffersize)
+ {
+ if(send(cutthrough_fd, ctblock.buffer, ctblock.buffersize, 0) < 0)
+ goto bad;
+ transport_count += ctblock.buffersize;
+ ctblock.ptr= ctblock.buffer;
+ }
+
+ *ctblock.ptr++ = *cp++;
+ }
+return TRUE;
+
+bad:
+cancel_cutthrough_connection();
+return FALSE;
+}
+
+BOOL
+cutthrough_flush_send( void )
+{
+if(cutthrough_fd >= 0)
+ {
+ if(send(cutthrough_fd, ctblock.buffer, ctblock.ptr-ctblock.buffer, 0) < 0)
+ goto bad;
+ transport_count += ctblock.ptr-ctblock.buffer;
+ ctblock.ptr= ctblock.buffer;
+ }
+return TRUE;
+
+bad:
+cancel_cutthrough_connection();
+return FALSE;
+}
+
+
+BOOL
+cutthrough_put_nl( void )
+{
+return cutthrough_puts(US"\r\n", 2);
+}
+
+
+/* Get and check response from cutthrough target */
+static uschar
+cutthrough_response(char expect, uschar ** copy)
+{
+smtp_inblock inblock;
+uschar inbuffer[4096];
+uschar responsebuffer[4096];
+
+inblock.buffer = inbuffer;
+inblock.buffersize = sizeof(inbuffer);
+inblock.ptr = inbuffer;
+inblock.ptrend = inbuffer;
+inblock.sock = cutthrough_fd;
+if(!smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), expect, CUTTHROUGH_DATA_TIMEOUT))
+ cancel_cutthrough_connection();
+
+if(copy != NULL)
+ {
+ uschar * cp;
+ *copy= cp= string_copy(responsebuffer);
+ /* Trim the trailing end of line */
+ cp += Ustrlen(responsebuffer);
+ if(cp > *copy && cp[-1] == '\n') *--cp = '\0';
+ if(cp > *copy && cp[-1] == '\r') *--cp = '\0';
+ }
+
+return responsebuffer[0];
+}
+
+
+/* Negotiate dataphase with the cutthrough target, returning success boolean */
+BOOL
+cutthrough_predata( void )
+{
+int rc;
+
+if(cutthrough_fd < 0)
+ return FALSE;
+
+HDEBUG(D_transport|D_acl|D_v) debug_printf(" SMTP>> DATA\n");
+rc= send(cutthrough_fd, "DATA\r\n", 6, 0);
+if (rc <= 0)
+ {
+ HDEBUG(D_transport|D_acl) debug_printf("send failed: %s\n", strerror(errno));
+ cancel_cutthrough_connection();
+ return FALSE;
+ }
+/*XXX error handling? TLS? See flush_buffer() in smtp_out.c */
+
+/* Assume nothing buffered. If it was it gets ignored. */
+return cutthrough_response('3', NULL) == '3';
+}
+
+
+/* Buffered send of headers. Return success boolean. */
+/* Also sends header-terminating blank line. */
+/* Sets up the "ctblock" buffer as a side-effect. */
+BOOL
+cutthrough_headers_send( void )
+{
+header_line * h;
+
+if(cutthrough_fd < 0)
+ return FALSE;
+
+ctblock.buffer = ctbuffer;
+ctblock.buffersize = sizeof(ctbuffer);
+ctblock.ptr = ctbuffer;
+/* ctblock.cmd_count = 0; ctblock.authenticating = FALSE; */
+ctblock.sock = cutthrough_fd;
+
+for(h= header_list; h != NULL; h= h->next)
+ if(h->type != htype_old && h->text != NULL)
+ if(!cutthrough_puts(h->text, h->slen))
+ return FALSE;
+
+if(!cutthrough_put_nl())
+ return TRUE;
+}
+
+
+/* Have senders final-dot. Send one to cutthrough target, and grab the response.
+ Log an OK response as a transmission.
+ Return smtp response-class digit.
+ XXX where do fail responses from target get logged?
+*/
+uschar *
+cutthrough_finaldot( void )
+{
+HDEBUG(D_transport|D_acl|D_v) debug_printf(" SMTP>> .\n");
+
+/* Assume data finshed with new-line */
+if(!cutthrough_puts(US".", 1) || !cutthrough_put_nl()
+ || !cutthrough_flush_send()
+ || cutthrough_response('2', &cutthrough_addr.message) != '2')
+ return cutthrough_addr.message;
+
+(void)close(cutthrough_fd);
+cutthrough_fd= -1;
+HDEBUG(D_acl) debug_printf("----------- cutthrough close ------------\n");
+
+delivery_log(&cutthrough_addr, (int)'>');
+/* C= ok */
+/* QT ok */
+/* DT always 0? */
+/* delivery S= zero! (transport_count) */
+/* not TLS yet hence no X, CV, DN */
+
+return cutthrough_addr.message;
+}
+
+
/*************************************************
* Copy error to toplevel address *
*************************************************/
vaddr->basic_errno = addr->basic_errno;
vaddr->more_errno = addr->more_errno;
vaddr->p.address_data = addr->p.address_data;
+ copyflag(vaddr, addr, af_pass_message);
}
return yield;
}
+/**************************************************
+* printf that automatically handles TLS if needed *
+***************************************************/
+
+/* This function is used by verify_address() as a substitute for all fprintf()
+calls; a direct fprintf() will not produce output in a TLS SMTP session, such
+as a response to an EXPN command. smtp_in.c makes smtp_printf available but
+that assumes that we always use the smtp_out FILE* when not using TLS or the
+ssl buffer when we are. Instead we take a FILE* parameter and check to see if
+that is smtp_out; if so, smtp_printf() with TLS support, otherwise regular
+fprintf().
+
+Arguments:
+ f the candidate FILE* to write to
+ format format string
+ ... optional arguments
+
+Returns:
+ nothing
+*/
+
+static void PRINTF_FUNCTION(2,3)
+respond_printf(FILE *f, const char *format, ...)
+{
+va_list ap;
+
+va_start(ap, format);
+if (smtp_out && (f == smtp_out))
+ smtp_vprintf(format, ap);
+else
+ vfprintf(f, format, ap);
+va_end(ap);
+}
+
+
+
/*************************************************
* Verify an email address *
*************************************************/
if ((options & vopt_qualify) == 0)
{
if (f != NULL)
- fprintf(f, "%sA domain is required for \"%s\"%s\n", ko_prefix, address,
- cr);
+ respond_printf(f, "%sA domain is required for \"%s\"%s\n",
+ ko_prefix, address, cr);
*failure_ptr = US"qualify";
return FAIL;
}
{
address_item *p = addr->parent;
- fprintf(f, "%s%s %s", ko_prefix, full_info? addr->address : address,
+ respond_printf(f, "%s%s %s", ko_prefix,
+ full_info? addr->address : address,
address_test_mode? "is undeliverable" : "failed to verify");
if (!expn && admin_user)
{
if (addr->basic_errno > 0)
- fprintf(f, ": %s", strerror(addr->basic_errno));
+ respond_printf(f, ": %s", strerror(addr->basic_errno));
if (addr->message != NULL)
- fprintf(f, ": %s", addr->message);
+ respond_printf(f, ": %s", addr->message);
}
/* Show parents iff doing full info */
if (full_info) while (p != NULL)
{
- fprintf(f, "%s\n <-- %s", cr, p->address);
+ respond_printf(f, "%s\n <-- %s", cr, p->address);
p = p->parent;
}
- fprintf(f, "%s\n", cr);
+ respond_printf(f, "%s\n", cr);
}
+ cancel_cutthrough_connection();
if (!full_info) return copy_error(vaddr, addr, FAIL);
else yield = FAIL;
if (f != NULL)
{
address_item *p = addr->parent;
- fprintf(f, "%s%s cannot be resolved at this time", ko_prefix,
+ respond_printf(f, "%s%s cannot be resolved at this time", ko_prefix,
full_info? addr->address : address);
if (!expn && admin_user)
{
if (addr->basic_errno > 0)
- fprintf(f, ": %s", strerror(addr->basic_errno));
+ respond_printf(f, ": %s", strerror(addr->basic_errno));
if (addr->message != NULL)
- fprintf(f, ": %s", addr->message);
+ respond_printf(f, ": %s", addr->message);
else if (addr->basic_errno <= 0)
- fprintf(f, ": unknown error");
+ respond_printf(f, ": unknown error");
}
/* Show parents iff doing full info */
if (full_info) while (p != NULL)
{
- fprintf(f, "%s\n <-- %s", cr, p->address);
+ respond_printf(f, "%s\n <-- %s", cr, p->address);
p = p->parent;
}
- fprintf(f, "%s\n", cr);
+ respond_printf(f, "%s\n", cr);
}
+ cancel_cutthrough_connection();
if (!full_info) return copy_error(vaddr, addr, DEFER);
else if (yield == OK) yield = DEFER;
if (addr_new == NULL)
{
if (addr_local == NULL && addr_remote == NULL)
- fprintf(f, "250 mail to <%s> is discarded\r\n", address);
+ respond_printf(f, "250 mail to <%s> is discarded\r\n", address);
else
- fprintf(f, "250 <%s>\r\n", address);
+ respond_printf(f, "250 <%s>\r\n", address);
}
else while (addr_new != NULL)
{
address_item *addr2 = addr_new;
addr_new = addr2->next;
if (addr_new == NULL) ok_prefix = US"250 ";
- fprintf(f, "%s<%s>\r\n", ok_prefix, addr2->address);
+ respond_printf(f, "%s<%s>\r\n", ok_prefix, addr2->address);
}
return OK;
}
name, we have to fish the file off the start of the query. For a single-key
lookup, the key is the current IP address, masked appropriately, and
reconverted to text form, with the mask appended. For IPv6 addresses, specify
- dot separators instead of colons. */
+ dot separators instead of colons, except when the lookup type is "iplsearch".
+ */
if (mac_islookup(search_type, lookup_absfilequery))
{
filename = NULL;
key = semicolon + 1;
}
- else
+ else /* Single-key style */
{
+ int sep = (Ustrcmp(lookup_list[search_type]->name, "iplsearch") == 0)?
+ ':' : '.';
insize = host_aton(cb->host_address, incoming);
host_mask(insize, incoming, mlen);
- (void)host_nmtoa(insize, incoming, mlen, buffer, '.');
+ (void)host_nmtoa(insize, incoming, mlen, buffer, sep);
key = buffer;
filename = semicolon + 1;
}
/*************************************************
-* Invert an IP address for a DNS black list *
+* Invert an IP address *
*************************************************/
-/*
+/* Originally just used for DNS xBL lists, now also used for the
+reverse_ip expansion operator.
+
Arguments:
buffer where to put the answer
address the address to invert
*/
-static void
+void
invert_address(uschar *buffer, uschar *address)
{
int bin[4];
for (s = domain; *s != 0; s++)
{
- if (!isalnum(*s) && *s != '-' && *s != '.')
+ if (!isalnum(*s) && *s != '-' && *s != '.' && *s != '_')
{
log_write(0, LOG_MAIN, "dnslists domain \"%s\" contains "
"strange characters - is this right?", domain);
if (domain_txt != domain) for (s = domain_txt; *s != 0; s++)
{
- if (!isalnum(*s) && *s != '-' && *s != '.')
+ if (!isalnum(*s) && *s != '-' && *s != '.' && *s != '_')
{
log_write(0, LOG_MAIN, "dnslists domain \"%s\" contains "
"strange characters - is this right?", domain_txt);
if (rc == OK)
{
dnslist_domain = string_copy(domain_txt);
+ dnslist_matched = string_copy(sender_host_address);
HDEBUG(D_dnsbl) debug_printf("=> that means %s is listed at %s\n",
sender_host_address, dnslist_domain);
}
if (rc == OK)
{
dnslist_domain = string_copy(domain_txt);
+ dnslist_matched = string_copy(keydomain);
HDEBUG(D_dnsbl) debug_printf("=> that means %s is listed at %s\n",
keydomain, dnslist_domain);
return OK;