X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/b30275b8a70b539c195a3a12580f29ebdcc12d99..5013d912e961203f2ab2d5f64be90255cda81b80:/src/src/smtp_out.c diff --git a/src/src/smtp_out.c b/src/src/smtp_out.c index 530fcfec7..ba6153ea9 100644 --- a/src/src/smtp_out.c +++ b/src/src/smtp_out.c @@ -2,13 +2,14 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2009 */ +/* Copyright (c) University of Cambridge 1995 - 2016 */ /* See the file NOTICE for conditions of use and distribution. */ /* A number of functions for driving outgoing SMTP calls. */ #include "exim.h" +#include "transports/smtp.h" @@ -25,7 +26,6 @@ Arguments: which case the function does nothing host_af AF_INET or AF_INET6 for the outgoing IP address addr the mail address being handled (for setting errors) - changed if not NULL, set TRUE if expansion actually changed istring interface point this to the interface msg to add to any error message @@ -35,16 +35,15 @@ Returns: TRUE on success, FALSE on failure, with error message BOOL smtp_get_interface(uschar *istring, int host_af, address_item *addr, - BOOL *changed, uschar **interface, uschar *msg) + uschar **interface, uschar *msg) { -uschar *expint; +const uschar * expint; uschar *iface; int sep = 0; -if (istring == NULL) return TRUE; +if (!istring) return TRUE; -expint = expand_string(istring); -if (expint == NULL) +if (!(expint = expand_string(istring))) { if (expand_string_forcedfail) return TRUE; addr->transport_return = PANIC; @@ -53,13 +52,11 @@ if (expint == NULL) return FALSE; } -if (changed != NULL) *changed = expint != istring; - while (isspace(*expint)) expint++; if (*expint == 0) return TRUE; while ((iface = string_nextinlist(&expint, &sep, big_buffer, - big_buffer_size)) != NULL) + big_buffer_size))) { if (string_is_ip_address(iface, NULL) == 0) { @@ -74,7 +71,7 @@ while ((iface = string_nextinlist(&expint, &sep, big_buffer, break; } -if (iface != NULL) *interface = string_copy(iface); +if (iface) *interface = string_copy(iface); return TRUE; } @@ -143,80 +140,33 @@ return TRUE; -/************************************************* -* Connect to remote host * -*************************************************/ - -/* Create a socket, and connect it to a remote host. IPv6 addresses are -detected by checking for a colon in the address. AF_INET6 is defined even on -non-IPv6 systems, to enable the code to be less messy. However, on such systems -host->address will always be an IPv4 address. - -The port field in the host item is used if it is set (usually router from SRV -records or elsewhere). In other cases, the default passed as an argument is -used, and the host item is updated with its value. - -Arguments: - host host item containing name and address (and sometimes port) - host_af AF_INET or AF_INET6 - port default remote port to connect to, in host byte order, for those - hosts whose port setting is PORT_NONE - interface outgoing interface address or NULL - timeout timeout value or 0 - keepalive TRUE to use keepalive - dscp DSCP value to assign to socket - event event expansion - -Returns: connected socket number, or -1 with errno set -*/ - int -smtp_connect(host_item *host, int host_af, int port, uschar *interface, - int timeout, BOOL keepalive, const uschar *dscp -#ifdef EXPERIMENTAL_EVENT - , uschar * event -#endif - ) +smtp_sock_connect(host_item * host, int host_af, int port, uschar * interface, + transport_instance * tb, int timeout) { -int on = 1; -int save_errno = 0; +smtp_transport_options_block * ob = + (smtp_transport_options_block *)tb->options_block; +const uschar * dscp = ob->dscp; int dscp_value; int dscp_level; int dscp_option; int sock; +int save_errno = 0; +BOOL fastopen = FALSE; -if (host->port != PORT_NONE) - { - HDEBUG(D_transport|D_acl|D_v) - debug_printf("Transport port=%d replaced by host-specific port=%d\n", port, - host->port); - port = host->port; - } -else host->port = port; /* Set the port actually used */ - -HDEBUG(D_transport|D_acl|D_v) - { - if (interface == NULL) - debug_printf("Connecting to %s [%s]:%d ... ",host->name,host->address,port); - else - debug_printf("Connecting to %s [%s]:%d from %s ... ", host->name, - host->address, port, interface); - } - -#ifdef EXPERIMENTAL_EVENT - deliver_host_address = host->address; - deliver_host_port = port; - if (event_raise(event, US"tcp:connect", NULL)) return -1; - /* Logging? Debug? */ +#ifndef DISABLE_EVENT +deliver_host_address = host->address; +deliver_host_port = port; +if (event_raise(tb->event_action, US"tcp:connect", NULL)) return -1; #endif -/* Create the socket */ - if ((sock = ip_socket(SOCK_STREAM, host_af)) < 0) return -1; /* Set TCP_NODELAY; Exim does its own buffering. */ -setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (uschar *)(&on), sizeof(on)); +if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, US &on, sizeof(on))) + HDEBUG(D_transport|D_acl|D_v) + debug_printf_indent("failed to set NODELAY: %s ", strerror(errno)); /* Set DSCP value, if we can. For now, if we fail to set the value, we don't bomb out, just log it and continue in default traffic class. */ @@ -224,34 +174,36 @@ bomb out, just log it and continue in default traffic class. */ if (dscp && dscp_lookup(dscp, host_af, &dscp_level, &dscp_option, &dscp_value)) { HDEBUG(D_transport|D_acl|D_v) - debug_printf("DSCP \"%s\"=%x ", dscp, dscp_value); + debug_printf_indent("DSCP \"%s\"=%x ", dscp, dscp_value); if (setsockopt(sock, dscp_level, dscp_option, &dscp_value, sizeof(dscp_value)) < 0) HDEBUG(D_transport|D_acl|D_v) - debug_printf("failed to set DSCP: %s ", strerror(errno)); + debug_printf_indent("failed to set DSCP: %s ", strerror(errno)); /* If the kernel supports IPv4 and IPv6 on an IPv6 socket, we need to set the option for both; ignore failures here */ if (host_af == AF_INET6 && dscp_lookup(dscp, AF_INET, &dscp_level, &dscp_option, &dscp_value)) - { (void) setsockopt(sock, dscp_level, dscp_option, &dscp_value, sizeof(dscp_value)); - } } +#ifdef TCP_FASTOPEN +if (verify_check_given_host (&ob->hosts_try_fastopen, host) == OK) fastopen = TRUE; +#endif + /* Bind to a specific interface if requested. Caller must ensure the interface is the same type (IPv4 or IPv6) as the outgoing address. */ -if (interface != NULL && ip_bind(sock, host_af, interface, 0) < 0) +if (interface && ip_bind(sock, host_af, interface, 0) < 0) { save_errno = errno; HDEBUG(D_transport|D_acl|D_v) - debug_printf("unable to bind outgoing SMTP call to %s: %s", interface, + debug_printf_indent("unable to bind outgoing SMTP call to %s: %s", interface, strerror(errno)); } /* Connect to the remote host, and add keepalive to the socket before returning it, if requested. */ -else if (ip_connect(sock, host_af, host->address, port, timeout) < 0) +else if (ip_connect(sock, host_af, host->address, port, timeout, fastopen) < 0) save_errno = errno; /* Either bind() or connect() failed */ @@ -260,7 +212,7 @@ if (save_errno != 0) { HDEBUG(D_transport|D_acl|D_v) { - debug_printf("failed: %s", CUstrerror(save_errno)); + debug_printf_indent("failed: %s", CUstrerror(save_errno)); if (save_errno == ETIMEDOUT) debug_printf(" (timeout=%s)", readconf_printtime(timeout)); debug_printf("\n"); @@ -276,7 +228,7 @@ else { union sockaddr_46 interface_sock; EXIM_SOCKLEN_T size = sizeof(interface_sock); - HDEBUG(D_transport|D_acl|D_v) debug_printf("connected\n"); + HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("connected\n"); if (getsockname(sock, (struct sockaddr *)(&interface_sock), &size) == 0) sending_ip_address = host_ntoa(-1, &interface_sock, NULL, &sending_port); else @@ -286,11 +238,76 @@ else close(sock); return -1; } - if (keepalive) ip_keepalive(sock, host->address, TRUE); + if (ob->keepalive) ip_keepalive(sock, host->address, TRUE); return sock; } } +/************************************************* +* Connect to remote host * +*************************************************/ + +/* Create a socket, and connect it to a remote host. IPv6 addresses are +detected by checking for a colon in the address. AF_INET6 is defined even on +non-IPv6 systems, to enable the code to be less messy. However, on such systems +host->address will always be an IPv4 address. + +The port field in the host item is used if it is set (usually router from SRV +records or elsewhere). In other cases, the default passed as an argument is +used, and the host item is updated with its value. + +Arguments: + host host item containing name and address (and sometimes port) + host_af AF_INET or AF_INET6 + port default remote port to connect to, in host byte order, for those + hosts whose port setting is PORT_NONE + interface outgoing interface address or NULL + timeout timeout value or 0 + tb transport + +Returns: connected socket number, or -1 with errno set +*/ + +int +smtp_connect(host_item *host, int host_af, int port, uschar *interface, + int timeout, transport_instance * tb) +{ +#ifdef SUPPORT_SOCKS +smtp_transport_options_block * ob = + (smtp_transport_options_block *)tb->options_block; +#endif + +if (host->port != PORT_NONE) + { + HDEBUG(D_transport|D_acl|D_v) + debug_printf_indent("Transport port=%d replaced by host-specific port=%d\n", port, + host->port); + port = host->port; + } +else host->port = port; /* Set the port actually used */ + +callout_address = string_sprintf("[%s]:%d", host->address, port); + +HDEBUG(D_transport|D_acl|D_v) + { + uschar * s = US" "; + if (interface) s = string_sprintf(" from %s ", interface); +#ifdef SUPPORT_SOCKS + if (ob->socks_proxy) s = string_sprintf("%svia proxy ", s); +#endif + debug_printf_indent("Connecting to %s %s%s... ", host->name, callout_address, s); + } + +/* Create and connect the socket */ + +#ifdef SUPPORT_SOCKS +if (ob->socks_proxy) + return socks_sock_connect(host, host_af, port, interface, tb, timeout); +#endif + +return smtp_sock_connect(host, host_af, port, interface, tb, timeout); +} + /************************************************* * Flush outgoing command buffer * @@ -310,17 +327,19 @@ static BOOL flush_buffer(smtp_outblock *outblock) { int rc; +int n = outblock->ptr - outblock->buffer; +HDEBUG(D_transport|D_acl) debug_printf_indent("cmd buf flush %d bytes\n", n); #ifdef SUPPORT_TLS if (tls_out.active == outblock->sock) - rc = tls_write(FALSE, outblock->buffer, outblock->ptr - outblock->buffer); + rc = tls_write(FALSE, outblock->buffer, n); else #endif + rc = send(outblock->sock, outblock->buffer, n, 0); -rc = send(outblock->sock, outblock->buffer, outblock->ptr - outblock->buffer, 0); if (rc <= 0) { - HDEBUG(D_transport|D_acl) debug_printf("send failed: %s\n", strerror(errno)); + HDEBUG(D_transport|D_acl) debug_printf_indent("send failed: %s\n", strerror(errno)); return FALSE; } @@ -343,6 +362,7 @@ Arguments: noflush if TRUE, save the command in the output buffer, for pipelining format a format, starting with one of of HELO, MAIL FROM, RCPT TO, DATA, ".", or QUIT. + If NULL, flush pipeline buffer only. ... data for the format Returns: 0 if command added to pipelining buffer, with nothing transmitted @@ -357,48 +377,51 @@ int count; int rc = 0; va_list ap; -va_start(ap, format); -if (!string_vformat(big_buffer, big_buffer_size, CS format, ap)) - log_write(0, LOG_MAIN|LOG_PANIC_DIE, "overlong write_command in outgoing " - "SMTP"); -va_end(ap); -count = Ustrlen(big_buffer); - -if (count > outblock->buffersize) - log_write(0, LOG_MAIN|LOG_PANIC_DIE, "overlong write_command in outgoing " - "SMTP"); - -if (count > outblock->buffersize - (outblock->ptr - outblock->buffer)) +if (format) { - rc = outblock->cmd_count; /* flush resets */ - if (!flush_buffer(outblock)) return -1; - } + va_start(ap, format); + if (!string_vformat(big_buffer, big_buffer_size, CS format, ap)) + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "overlong write_command in outgoing " + "SMTP"); + va_end(ap); + count = Ustrlen(big_buffer); + + if (count > outblock->buffersize) + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "overlong write_command in outgoing " + "SMTP"); + + if (count > outblock->buffersize - (outblock->ptr - outblock->buffer)) + { + rc = outblock->cmd_count; /* flush resets */ + if (!flush_buffer(outblock)) return -1; + } -Ustrncpy(CS outblock->ptr, big_buffer, count); -outblock->ptr += count; -outblock->cmd_count++; -count -= 2; -big_buffer[count] = 0; /* remove \r\n for error message */ + Ustrncpy(CS outblock->ptr, big_buffer, count); + outblock->ptr += count; + outblock->cmd_count++; + count -= 2; + big_buffer[count] = 0; /* remove \r\n for error message */ -/* We want to hide the actual data sent in AUTH transactions from reflections -and logs. While authenticating, a flag is set in the outblock to enable this. -The AUTH command itself gets any data flattened. Other lines are flattened -completely. */ + /* We want to hide the actual data sent in AUTH transactions from reflections + and logs. While authenticating, a flag is set in the outblock to enable this. + The AUTH command itself gets any data flattened. Other lines are flattened + completely. */ -if (outblock->authenticating) - { - uschar *p = big_buffer; - if (Ustrncmp(big_buffer, "AUTH ", 5) == 0) + if (outblock->authenticating) { - p += 5; - while (isspace(*p)) p++; - while (!isspace(*p)) p++; - while (isspace(*p)) p++; + uschar *p = big_buffer; + if (Ustrncmp(big_buffer, "AUTH ", 5) == 0) + { + p += 5; + while (isspace(*p)) p++; + while (!isspace(*p)) p++; + while (isspace(*p)) p++; + } + while (*p != 0) *p++ = '*'; } - while (*p != 0) *p++ = '*'; - } -HDEBUG(D_transport|D_acl|D_v) debug_printf(" SMTP>> %s\n", big_buffer); + HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP>> %s\n", big_buffer); + } if (!noflush) { @@ -475,15 +498,19 @@ for (;;) /* Need to read a new input packet. */ - rc = ip_recv(sock, inblock->buffer, inblock->buffersize, timeout); - if (rc <= 0) break; + if((rc = ip_recv(sock, inblock->buffer, inblock->buffersize, timeout)) <= 0) + { + if (!errno) + DEBUG(D_deliver|D_transport|D_acl) debug_printf_indent(" SMTP(closed)<<\n"); + break; + } /* Another block of data has been successfully read. Set up the pointers and let the loop continue. */ ptrend = inblock->ptrend = inblock->buffer + rc; ptr = inblock->buffer; - DEBUG(D_transport|D_acl) debug_printf("read response data: size=%d\n", rc); + DEBUG(D_transport|D_acl) debug_printf_indent("read response data: size=%d\n", rc); } /* Get here if there has been some kind of recv() error; errno is set, but we @@ -513,7 +540,7 @@ Arguments: buffer where to put the response size the size of the buffer okdigit the expected first digit of the response - timeout the timeout to use + timeout the timeout to use, in seconds Returns: TRUE if a valid, non-error response was received; else FALSE */ @@ -527,7 +554,7 @@ int count; errno = 0; /* Ensure errno starts out zero */ -/* This is a loop to read and concatentate the lines that make up a multi-line +/* This is a loop to read and concatenate the lines that make up a multi-line response. */ for (;;) @@ -536,7 +563,7 @@ for (;;) return FALSE; HDEBUG(D_transport|D_acl|D_v) - debug_printf(" %s %s\n", (ptr == buffer)? "SMTP<<" : " ", ptr); + debug_printf_indent(" %s %s\n", (ptr == buffer)? "SMTP<<" : " ", ptr); /* Check the format of the response: it must start with three digits; if these are followed by a space or end of line, the response is complete. If @@ -581,3 +608,5 @@ return buffer[0] == okdigit; } /* End of smtp_out.c */ +/* vi: aw ai sw=2 +*/