/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
#include "lib.h"
#include "llist.h"
#include "array.h"
#include "safe-memset.h"
#include "ioloop.h"
#include "net.h"
#include "base64.h"
#include "istream.h"
#include "ostream.h"
#include "ostream-dot.h"
#include "iostream-rawlog.h"
#include "iostream-ssl.h"
#include "str.h"
#include "dsasl-client.h"
#include "dns-lookup.h"
#include "smtp-syntax.h"
#include "smtp-reply-parser.h"
#include "smtp-client-private.h"
#include <ctype.h>
const char *const smtp_client_connection_state_names[] = {
"disconnected",
"connecting",
"handshaking",
"authenticating",
"ready",
"transaction"
};
static int
const char **error_r);
static void
static void
static void
/*
* Capabilities
*/
enum smtp_capability
{
return conn->capabilities;
}
struct smtp_client_connection *conn)
{
}
/*
* Logging
*/
const char *
{
}
}
const char *format, ...)
{
i_debug("%s-client: conn %s: %s",
}
}
const char *format, ...)
{
i_warning("%s-client: conn %s: %s",
}
const char *format, ...)
{
i_error("%s-client: conn %s: %s",
}
/*
*
*/
static void
{
}
static void
const struct smtp_reply *reply)
{
}
static void
{
}
static void
const struct smtp_reply *reply)
{
return;
}
static void
{
}
static void
{
}
{
}
{
return;
}
}
}
{
}
static void
{
"Command timed out, disconnecting");
"Command timed out");
}
struct smtp_client_connection *conn)
{
/* pre-login uses connect timeout */
return;
}
if (msecs == 0) {
/* no timeout configured */
return;
}
/* no commands pending */
return;
}
}
}
struct smtp_client_connection *conn)
{
/* pre-login uses connect timeout */
return;
}
if (msecs == 0) {
/* no timeout configured */
return;
}
"No commands pending; stop timeout");
}
"Reset timeout");
} else {
}
}
static void
const struct smtp_reply *reply)
{
trans = trans_next;
}
}
{
}
struct smtp_client_connection *conn)
{
"Connection lost: write(%s) failed: %s",
"Lost connection to remote server: "
"Write failure");
} else {
"Connection lost: Remote disconnected");
"Lost connection to remote server: "
"Remote closed connection unexpectedly");
}
}
const struct smtp_reply *reply)
{
}
static void
{
return;
return;
}
static void
struct smtp_client_connection *conn)
{
const unsigned char *sasl_output;
const char *error;
"Authentication failed: "
"Server returned multi-line reply: %s",
"Authentication protocol error");
return;
}
if (base64_decode
"Authentication failed: "
"Server sent non-base64 input for AUTH: %s",
reply->text_lines[0]);
"Authentication failed: %s", error);
"Authentication failed: %s", error);
} else {
return;
}
"Authentication failed");
return;
}
"Authentication failed: %s",
return;
}
"Authenticated successfully");
}
}
static int
const struct dsasl_client_mech **mech_r,
const char **error_r)
{
const char *const *mechanisms;
"Server doesn't support `%s' SASL mechanism",
mech);
return -1;
}
return 0;
}
return 0;
}
/* find one of the specified SASL mechanisms */
*mechanisms)) {
return 0;
"Support for SASL mechanism `%s' is missing",
*mechanisms);
return -1;
}
}
"Server doesn't support any of "
"the requested SASL mechanisms: %s",
return -1;
}
static bool
{
const unsigned char *sasl_output;
if (conn->authenticated)
return TRUE;
return TRUE;
"Authentication not supported");
return FALSE;
}
"Authenticating as %s for user %s",
"Authenticating");
} else {
}
}
"Authentication failed: %s", error);
"Server authentication mechanisms incompatible");
return FALSE;
}
else {
}
&sasl_output_len, &error) < 0) {
"Failed to create initial %s SASL reply: %s",
"Internal authentication failure");
return FALSE;
}
/* RFC 4954, Section 4:
If the client is transmitting an initial response of zero
length, it MUST instead transmit the response as a single
equals sign ("="). This indicates that the response is
present, but contains no data. */
return FALSE;
}
static void
struct smtp_client_connection *conn)
{
"Received XCLIENT handshake reply: %s",
}
return;
}
return;
}
void
struct smtp_proxy_data *xclient)
{
unsigned int empty_len;
return;
return;
/* Older versions of Dovecot LMTP don't quite follow Postfix'
specification of the XCLIENT command regarding IPv6
addresses: the "IPV6:" prefix is omitted. For now, we
maintain this deviation for LMTP. Newer versions of Dovecot
LMTP can work with or without the prefix. */
else
}
if (xclient->source_port != 0 &&
}
case SMTP_PROXY_PROTOCOL_SMTP:
break;
break;
case SMTP_PROXY_PROTOCOL_LMTP:
break;
default:
break;
}
}
}
if (xclient->ttl_plus_1 > 0 &&
if (xclient->timeout_secs > 0 &&
"Sending XCLIENT handshake");
}
}
static bool
{
if (!conn->initial_xclient_sent) {
}
return smtp_client_connection_authenticate(conn);
}
static void
struct smtp_client_connection *conn)
{
const char *error;
"Received STARTTLS reply: %s",
}
return;
}
} else {
}
}
static bool
{
"Requested STARTTLS, "
"but server doesn't support it");
"STARTTLS not supported");
return FALSE;
}
return FALSE;
}
return smtp_client_connection_init_xclient(conn);
}
static void
struct smtp_client_connection *conn)
{
const char *const *lines;
unsigned int j;
/* check reply status */
/* RFC 5321, Section 3.2:
For a particular connection attempt, if the server returns a
"command not recognized" response to EHLO, the client SHOULD
be able to fall back and send HELO. */
/* try HELO */
return;
}
/* failed */
return;
}
/* reset capabilities */
conn->capabilities = 0;
"Invalid handshake reply");
return;
}
/* greeting line */
lines++;
/* capability lines */
const char *const *params;
if (smtp_ehlo_line_parse(*lines,
"Received invalid EHLO response line: %s",
error);
lines++;
continue;
}
cap = &smtp_capability_names[j];
break;
}
switch (cap->capability) {
case SMTP_CAPABILITY_AUTH:
break;
case SMTP_CAPABILITY_SIZE:
break;
"Received invalid SIZE capability "
"in EHLO response line");
}
break;
case SMTP_CAPABILITY_XCLIENT:
break;
default:
break;
}
}
lines++;
}
if (smtp_client_connection_starttls(conn)) {
}
}
static void
{
const char *command;
case SMTP_PROTOCOL_SMTP:
break;
case SMTP_PROTOCOL_LMTP:
command = "LHLO";
break;
default:
i_unreached();
}
"Sending %s handshake", command);
}
static int
const struct smtp_reply *reply)
{
int ret;
/* initial greeting? */
"Received greeting from server: %s",
}
if (smtp_reply_is_success(reply)) {
"Received inappropriate greeting");
} else {
}
return -1;
}
return 1;
}
/* unexpected reply? */
} else {
"Got unexpected reply");
}
return -1;
}
/* command reply */
return -1;
return ret;
}
{
(struct smtp_client_connection *)_conn;
int ret;
/* finish SSL negotiation by reading from input stream */
break;
}
if (ret < 0) {
/* failed somehow */
"SSL handshaking with %s failed: "
return;
}
/* not finished */
return;
}
/* ready for SMTP handshake */
}
for (;;) {
break;
} else {
break;
}
T_BEGIN {
} T_END;
if (ret < 0) {
return;
}
}
"Command reply line too long");
"read(%s) failed: %s",
"Lost connection to remote server "
"(read failure)");
"Lost connection to remote server "
"(disconnected in input)");
} else {
t_strdup_printf("Invalid command reply: %s",
error));
}
}
}
}
{
int ret;
if (ret < 0)
return ret;
}
if (smtp_client_command_send_more(conn) < 0)
ret = -1;
}
return ret;
}
{
}
{
(struct smtp_client_connection *)_conn;
const char *error;
switch (_conn->disconnect_reason) {
break;
"Connection deinit");
break;
"connect(%s) failed: Connection timed out",
"Connect timed out");
break;
default:
if (conn->connect_failed)
break;
t_strdup_printf("Connection lost: %s",
break;
}
}
static void
{
/* set flush callback */
}
static int
{
"SSL handshake successful");
"SSL handshake successful, "
"ignoring invalid certificate: %s", error);
} else {
return -1;
}
return 0;
}
static void
{
}
} else {
}
}
static int
const char **error_r)
{
const char *error;
return 0;
return -1;
return 0;
}
*error_r = "Requested SSL connection, but no SSL settings given";
return -1;
}
error);
return -1;
}
return 0;
}
static int
const char **error_r)
{
const char *error;
"Failed to initialize SSL: %s", error);
return -1;
}
/* recreate rawlog after STARTTLS */
}
"Couldn't initialize SSL client for %s: %s",
return -1;
}
return -1;
}
} else {
/* wait for handshake to complete; connection input handler
does the rest by reading from the input stream */
}
return 0;
}
static void
struct smtp_client_connection *conn)
{
"Failed to connect to remote server");
}
static void
{
(struct smtp_client_connection *)_conn;
const char *error;
if (!success) {
return;
}
if (set->socket_send_buffer_size > 0) {
set->socket_send_buffer_size) < 0)
}
if (set->socket_recv_buffer_size > 0) {
set->socket_recv_buffer_size) < 0)
}
conn);
}
} else {
}
}
static void
{
"connect(%s) failed: "
"Connection timed out after %u seconds",
"Connect timed out");
break;
"SMTP handshake timed out after %u seconds",
"Handshake timed out");
break;
"Authentication timed out after %u seconds",
"Authentication timed out");
break;
default:
i_unreached();
}
}
static void
{
unsigned int msecs;
return;
}
/* don't use connection.h timeout because we want this timeout
to include also the SSL handshake */
if (msecs == 0)
if (msecs > 0) {
}
}
static void
{
"Connecting to %s:%u (from %s)",
} else {
"Connecting to %s:%u",
}
}
static void
struct smtp_client_connection *conn)
{
"Delayed host lookup failure");
"Failed to lookup remote server");
}
static void
struct smtp_client_connection *conn)
{
"dns_lookup(%s) failed: %s",
return;
}
"DNS lookup successful; got %d IPs",
}
{
unsigned int ips_count;
int ret;
return;
}
/* IP address */
"Performing asynchronous DNS lookup");
&conn->dns_lookup);
"Performing asynchronous DNS lookup");
&conn->dns_lookup);
} else {
/* no dns-conn, use blocking lookup */
if (ret != 0) {
"net_gethostbyname(%s) failed: %s",
return;
}
"DNS lookup successful; got %d IPs", ips_count);
}
return;
/* always work asynchronously */
}
};
};
struct connection_list *
{
return connection_list_init
}
{
return;
!conn->sending_command) {
/* Close the connection gracefully if possible */
}
"Disconnected from server");
"Disconnected from server");
}
struct smtp_client_connection *
const struct smtp_client_settings *set)
{
static unsigned int id = 0;
}
}
if (set->command_timeout_msecs > 0)
if (set->connect_timeout_msecs > 0)
if (set->max_reply_size > 0)
if (set->max_data_chunk_size > 0)
if (set->max_data_chunk_pipeline > 0)
if (set->socket_send_buffer_size > 0)
if (set->socket_recv_buffer_size > 0)
}
}
("smtp client connection capabilities", 128);
return conn;
}
{
}
{
return;
if (conn->destroying)
return;
/* could have been created while already disconnected */
}
{
return;
/* could have been created while already disconnected */
}
{
}
}
static void
{
/* nothing */
}
static void
{
"Submitting RSET command");
}
static void
{
return;
return;
if (conn->reset_needed)
}
struct smtp_client_connection *conn,
struct smtp_client_transaction *trans)
{
}
}
struct smtp_client_connection *conn,
struct smtp_client_transaction *trans)
{
if (!was_first)
return;
return;
/* transaction messed up; protocol state needs to be reset for
next transaction */
}
struct smtp_client_connection *conn,
struct smtp_client_transaction *trans)
{
return;
}