summaryrefslogtreecommitdiffstats
path: root/src/lib-storage/index/pop3c/pop3c-client.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib-storage/index/pop3c/pop3c-client.c')
-rw-r--r--src/lib-storage/index/pop3c/pop3c-client.c902
1 files changed, 902 insertions, 0 deletions
diff --git a/src/lib-storage/index/pop3c/pop3c-client.c b/src/lib-storage/index/pop3c/pop3c-client.c
new file mode 100644
index 0000000..44544a3
--- /dev/null
+++ b/src/lib-storage/index/pop3c/pop3c-client.c
@@ -0,0 +1,902 @@
+/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "ioloop.h"
+#include "net.h"
+#include "istream.h"
+#include "istream-chain.h"
+#include "istream-dot.h"
+#include "istream-seekable.h"
+#include "ostream.h"
+#include "iostream-rawlog.h"
+#include "iostream-ssl.h"
+#include "safe-mkstemp.h"
+#include "base64.h"
+#include "str.h"
+#include "dns-lookup.h"
+#include "pop3c-client.h"
+
+#include <unistd.h>
+
+#define POP3C_MAX_INBUF_SIZE (1024*32)
+#define POP3C_DNS_LOOKUP_TIMEOUT_MSECS (1000*30)
+#define POP3C_CONNECT_TIMEOUT_MSECS (1000*30)
+#define POP3C_COMMAND_TIMEOUT_MSECS (1000*60*5)
+
+enum pop3c_client_state {
+ /* No connection */
+ POP3C_CLIENT_STATE_DISCONNECTED = 0,
+ /* Trying to connect */
+ POP3C_CLIENT_STATE_CONNECTING,
+ POP3C_CLIENT_STATE_STARTTLS,
+ /* Connected, trying to authenticate */
+ POP3C_CLIENT_STATE_USER,
+ POP3C_CLIENT_STATE_AUTH,
+ POP3C_CLIENT_STATE_PASS,
+ /* Post-authentication, asking for capabilities */
+ POP3C_CLIENT_STATE_CAPA,
+ /* Authenticated, ready to accept commands */
+ POP3C_CLIENT_STATE_DONE
+};
+
+struct pop3c_client_sync_cmd_ctx {
+ enum pop3c_command_state state;
+ char *reply;
+};
+
+struct pop3c_client_cmd {
+ struct istream *input;
+ struct istream_chain *chain;
+ bool reading_dot;
+
+ pop3c_cmd_callback_t *callback;
+ void *context;
+};
+
+struct pop3c_client {
+ pool_t pool;
+ struct event *event;
+ struct pop3c_client_settings set;
+ struct ssl_iostream_context *ssl_ctx;
+ struct ip_addr ip;
+
+ int fd;
+ struct io *io;
+ struct istream *input, *raw_input;
+ struct ostream *output, *raw_output;
+ struct ssl_iostream *ssl_iostream;
+ struct timeout *to;
+ struct dns_lookup *dns_lookup;
+
+ enum pop3c_client_state state;
+ enum pop3c_capability capabilities;
+ const char *auth_mech;
+
+ pop3c_login_callback_t *login_callback;
+ void *login_context;
+
+ ARRAY(struct pop3c_client_cmd) commands;
+ const char *input_line;
+ struct istream *dot_input;
+
+ bool running:1;
+};
+
+static void
+pop3c_dns_callback(const struct dns_lookup_result *result,
+ struct pop3c_client *client);
+static void pop3c_client_connect_ip(struct pop3c_client *client);
+static int pop3c_client_ssl_init(struct pop3c_client *client);
+static void pop3c_client_input(struct pop3c_client *client);
+
+struct pop3c_client *
+pop3c_client_init(const struct pop3c_client_settings *set,
+ struct event *event_parent)
+{
+ struct pop3c_client *client;
+ const char *error;
+ pool_t pool;
+
+ pool = pool_alloconly_create("pop3c client", 1024);
+ client = p_new(pool, struct pop3c_client, 1);
+ client->pool = pool;
+ client->event = event_create(event_parent);
+ client->fd = -1;
+ p_array_init(&client->commands, pool, 16);
+
+ client->set.debug = set->debug;
+ client->set.host = p_strdup(pool, set->host);
+ client->set.port = set->port;
+ client->set.master_user = p_strdup_empty(pool, set->master_user);
+ client->set.username = p_strdup(pool, set->username);
+ client->set.password = p_strdup(pool, set->password);
+ client->set.dns_client_socket_path =
+ p_strdup(pool, set->dns_client_socket_path);
+ client->set.temp_path_prefix = p_strdup(pool, set->temp_path_prefix);
+ client->set.rawlog_dir = p_strdup(pool, set->rawlog_dir);
+ client->set.ssl_mode = set->ssl_mode;
+
+ if (set->ssl_mode != POP3C_CLIENT_SSL_MODE_NONE) {
+ ssl_iostream_settings_init_from(client->pool, &client->set.ssl_set, &set->ssl_set);
+ client->set.ssl_set.verbose_invalid_cert = !client->set.ssl_set.allow_invalid_cert;
+ if (ssl_iostream_client_context_cache_get(&set->ssl_set,
+ &client->ssl_ctx,
+ &error) < 0) {
+ i_error("pop3c(%s:%u): Couldn't initialize SSL context: %s",
+ set->host, set->port, error);
+ }
+ }
+ return client;
+}
+
+static void
+client_login_callback(struct pop3c_client *client,
+ enum pop3c_command_state state, const char *reason)
+{
+ pop3c_login_callback_t *callback = client->login_callback;
+ void *context = client->login_context;
+
+ if (client->login_callback != NULL) {
+ client->login_callback = NULL;
+ client->login_context = NULL;
+ callback(state, reason, context);
+ }
+}
+
+static void
+pop3c_client_async_callback(struct pop3c_client *client,
+ enum pop3c_command_state state, const char *reply)
+{
+ struct pop3c_client_cmd *cmd, cmd_copy;
+ bool running = client->running;
+
+ i_assert(reply != NULL);
+ i_assert(array_count(&client->commands) > 0);
+
+ cmd = array_front_modifiable(&client->commands);
+ if (cmd->input != NULL && state == POP3C_COMMAND_STATE_OK &&
+ !cmd->reading_dot) {
+ /* read the full input into seekable-istream before calling
+ the callback */
+ i_assert(client->dot_input == NULL);
+ i_stream_chain_append(cmd->chain, client->input);
+ client->dot_input = cmd->input;
+ cmd->reading_dot = TRUE;
+ return;
+ }
+ cmd_copy = *cmd;
+ array_pop_front(&client->commands);
+
+ if (cmd_copy.input != NULL) {
+ i_stream_seek(cmd_copy.input, 0);
+ i_stream_unref(&cmd_copy.input);
+ }
+ if (cmd_copy.callback != NULL)
+ cmd_copy.callback(state, reply, cmd_copy.context);
+ if (running)
+ io_loop_stop(current_ioloop);
+}
+
+static void
+pop3c_client_async_callback_disconnected(struct pop3c_client *client)
+{
+ pop3c_client_async_callback(client, POP3C_COMMAND_STATE_DISCONNECTED,
+ "Disconnected");
+}
+
+static void pop3c_client_disconnect(struct pop3c_client *client)
+{
+ client->state = POP3C_CLIENT_STATE_DISCONNECTED;
+
+ if (client->running)
+ io_loop_stop(current_ioloop);
+
+ if (client->dns_lookup != NULL)
+ dns_lookup_abort(&client->dns_lookup);
+ timeout_remove(&client->to);
+ io_remove(&client->io);
+ i_stream_destroy(&client->input);
+ o_stream_destroy(&client->output);
+ ssl_iostream_destroy(&client->ssl_iostream);
+ i_close_fd(&client->fd);
+ while (array_count(&client->commands) > 0)
+ pop3c_client_async_callback_disconnected(client);
+ client_login_callback(client, POP3C_COMMAND_STATE_DISCONNECTED,
+ "Disconnected");
+}
+
+void pop3c_client_deinit(struct pop3c_client **_client)
+{
+ struct pop3c_client *client = *_client;
+
+ pop3c_client_disconnect(client);
+ if (client->ssl_ctx != NULL)
+ ssl_iostream_context_unref(&client->ssl_ctx);
+ event_unref(&client->event);
+ pool_unref(&client->pool);
+}
+
+static void pop3c_client_ioloop_changed(struct pop3c_client *client)
+{
+ if (client->to != NULL)
+ client->to = io_loop_move_timeout(&client->to);
+ if (client->io != NULL)
+ client->io = io_loop_move_io(&client->io);
+ if (client->output != NULL)
+ o_stream_switch_ioloop(client->output);
+}
+
+static void pop3c_client_timeout(struct pop3c_client *client)
+{
+ switch (client->state) {
+ case POP3C_CLIENT_STATE_CONNECTING:
+ i_error("pop3c(%s): connect(%s, %u) timed out after %u seconds",
+ client->set.host, net_ip2addr(&client->ip),
+ client->set.port, POP3C_CONNECT_TIMEOUT_MSECS/1000);
+ break;
+ case POP3C_CLIENT_STATE_DONE:
+ i_error("pop3c(%s): Command timed out after %u seconds",
+ client->set.host, POP3C_COMMAND_TIMEOUT_MSECS/1000);
+ break;
+ default:
+ i_error("pop3c(%s): Authentication timed out after %u seconds",
+ client->set.host, POP3C_CONNECT_TIMEOUT_MSECS/1000);
+ break;
+ }
+ pop3c_client_disconnect(client);
+}
+
+static int pop3c_client_dns_lookup(struct pop3c_client *client)
+{
+ struct dns_lookup_settings dns_set;
+
+ i_assert(client->state == POP3C_CLIENT_STATE_CONNECTING);
+
+ if (client->set.dns_client_socket_path[0] == '\0') {
+ struct ip_addr *ips;
+ unsigned int ips_count;
+ int ret;
+
+ ret = net_gethostbyname(client->set.host, &ips, &ips_count);
+ if (ret != 0) {
+ i_error("pop3c(%s): net_gethostbyname() failed: %s",
+ client->set.host, net_gethosterror(ret));
+ return -1;
+ }
+ i_assert(ips_count > 0);
+ client->ip = ips[0];
+ pop3c_client_connect_ip(client);
+ } else {
+ i_zero(&dns_set);
+ dns_set.dns_client_socket_path =
+ client->set.dns_client_socket_path;
+ dns_set.timeout_msecs = POP3C_DNS_LOOKUP_TIMEOUT_MSECS;
+ dns_set.event_parent = client->event;
+ if (dns_lookup(client->set.host, &dns_set,
+ pop3c_dns_callback, client,
+ &client->dns_lookup) < 0)
+ return -1;
+ }
+ return 0;
+}
+
+void pop3c_client_wait_one(struct pop3c_client *client)
+{
+ struct ioloop *ioloop, *prev_ioloop = current_ioloop;
+ bool timeout_added = FALSE, failed = FALSE;
+
+ if (client->state == POP3C_CLIENT_STATE_DISCONNECTED &&
+ array_count(&client->commands) > 0) {
+ while (array_count(&client->commands) > 0)
+ pop3c_client_async_callback_disconnected(client);
+ return;
+ }
+
+ i_assert(client->fd != -1 ||
+ client->state == POP3C_CLIENT_STATE_CONNECTING);
+ i_assert(array_count(&client->commands) > 0 ||
+ client->state == POP3C_CLIENT_STATE_CONNECTING);
+
+ ioloop = io_loop_create();
+ pop3c_client_ioloop_changed(client);
+
+ if (client->ip.family == 0) {
+ /* we're connecting, start DNS lookup after our ioloop
+ is created */
+ if (pop3c_client_dns_lookup(client) < 0)
+ failed = TRUE;
+ } else if (client->to == NULL) {
+ client->to = timeout_add(POP3C_COMMAND_TIMEOUT_MSECS,
+ pop3c_client_timeout, client);
+ timeout_added = TRUE;
+ }
+
+ if (!failed) {
+ client->running = TRUE;
+ io_loop_run(ioloop);
+ client->running = FALSE;
+ }
+
+ if (timeout_added && client->to != NULL)
+ timeout_remove(&client->to);
+
+ io_loop_set_current(prev_ioloop);
+ pop3c_client_ioloop_changed(client);
+ io_loop_set_current(ioloop);
+ io_loop_destroy(&ioloop);
+}
+
+static void pop3c_client_starttls(struct pop3c_client *client)
+{
+ o_stream_nsend_str(client->output, "STLS\r\n");
+ client->state = POP3C_CLIENT_STATE_STARTTLS;
+}
+
+static void pop3c_client_authenticate1(struct pop3c_client *client)
+{
+ const struct pop3c_client_settings *set = &client->set;
+
+ if (client->set.debug) {
+ if (set->master_user == NULL) {
+ i_debug("pop3c(%s): Authenticating as '%s' (with USER+PASS)",
+ client->set.host, set->username);
+ } else {
+ i_debug("pop3c(%s): Authenticating as master user '%s' for user '%s' (with SASL PLAIN)",
+ client->set.host, set->master_user,
+ set->username);
+ }
+ }
+
+ if (set->master_user == NULL) {
+ o_stream_nsend_str(client->output,
+ t_strdup_printf("USER %s\r\n", set->username));
+ client->state = POP3C_CLIENT_STATE_USER;
+ } else {
+ client->state = POP3C_CLIENT_STATE_AUTH;
+ o_stream_nsend_str(client->output, "AUTH PLAIN\r\n");
+ }
+}
+
+static const char *
+pop3c_client_get_sasl_plain_request(struct pop3c_client *client)
+{
+ const struct pop3c_client_settings *set = &client->set;
+ string_t *in, *out;
+
+ in = t_str_new(128);
+ if (set->master_user != NULL) {
+ str_append(in, set->username);
+ str_append_c(in, '\0');
+ str_append(in, set->master_user);
+ } else {
+ str_append_c(in, '\0');
+ str_append(in, set->username);
+ }
+ str_append_c(in, '\0');
+ str_append(in, set->password);
+
+ out = t_str_new(128);
+ base64_encode(str_data(in), str_len(in), out);
+ str_append(out, "\r\n");
+ return str_c(out);
+}
+
+static void pop3c_client_login_finished(struct pop3c_client *client)
+{
+ io_remove(&client->io);
+ client->io = io_add(client->fd, IO_READ, pop3c_client_input, client);
+
+ timeout_remove(&client->to);
+ client->state = POP3C_CLIENT_STATE_DONE;
+
+ if (client->running)
+ io_loop_stop(current_ioloop);
+}
+
+static int
+pop3c_client_prelogin_input_line(struct pop3c_client *client, const char *line)
+{
+ bool success = line[0] == '+';
+ const char *reply;
+
+ switch (client->state) {
+ case POP3C_CLIENT_STATE_CONNECTING:
+ if (!success) {
+ i_error("pop3c(%s): Server sent invalid banner: %s",
+ client->set.host, line);
+ return -1;
+ }
+ if (client->set.ssl_mode == POP3C_CLIENT_SSL_MODE_STARTTLS)
+ pop3c_client_starttls(client);
+ else
+ pop3c_client_authenticate1(client);
+ break;
+ case POP3C_CLIENT_STATE_STARTTLS:
+ if (!success) {
+ i_error("pop3c(%s): STLS failed: %s",
+ client->set.host, line);
+ return -1;
+ }
+ if (pop3c_client_ssl_init(client) < 0)
+ pop3c_client_disconnect(client);
+ break;
+ case POP3C_CLIENT_STATE_USER:
+ if (!success) {
+ i_error("pop3c(%s): USER failed: %s",
+ client->set.host, line);
+ return -1;
+ }
+
+ /* the PASS reply can take a long time.
+ switch to command timeout. */
+ timeout_remove(&client->to);
+ client->to = timeout_add(POP3C_COMMAND_TIMEOUT_MSECS,
+ pop3c_client_timeout, client);
+
+ o_stream_nsend_str(client->output,
+ t_strdup_printf("PASS %s\r\n", client->set.password));
+ client->state = POP3C_CLIENT_STATE_PASS;
+ client->auth_mech = "USER+PASS";
+ break;
+ case POP3C_CLIENT_STATE_AUTH:
+ if (line[0] != '+') {
+ i_error("pop3c(%s): AUTH PLAIN failed: %s",
+ client->set.host, line);
+ return -1;
+ }
+ o_stream_nsend_str(client->output,
+ pop3c_client_get_sasl_plain_request(client));
+ client->state = POP3C_CLIENT_STATE_PASS;
+ client->auth_mech = "AUTH PLAIN";
+ break;
+ case POP3C_CLIENT_STATE_PASS:
+ if (client->login_callback != NULL) {
+ reply = strncasecmp(line, "+OK ", 4) == 0 ? line + 4 :
+ strncasecmp(line, "-ERR ", 5) == 0 ? line + 5 :
+ line;
+ client_login_callback(client, success ?
+ POP3C_COMMAND_STATE_OK :
+ POP3C_COMMAND_STATE_ERR, reply);
+ } else if (!success) {
+ i_error("pop3c(%s): Authentication via %s failed: %s",
+ client->set.host, client->auth_mech, line);
+ }
+ if (!success)
+ return -1;
+
+ o_stream_nsend_str(client->output, "CAPA\r\n");
+ client->state = POP3C_CLIENT_STATE_CAPA;
+ break;
+ case POP3C_CLIENT_STATE_CAPA:
+ if (strncasecmp(line, "-ERR", 4) == 0) {
+ /* CAPA command not supported. some commands still
+ support UIDL though. */
+ client->capabilities |= POP3C_CAPABILITY_UIDL;
+ pop3c_client_login_finished(client);
+ break;
+ } else if (strcmp(line, ".") == 0) {
+ pop3c_client_login_finished(client);
+ break;
+ }
+ if ((client->set.parsed_features & POP3C_FEATURE_NO_PIPELINING) == 0 &&
+ strcasecmp(line, "PIPELINING") == 0)
+ client->capabilities |= POP3C_CAPABILITY_PIPELINING;
+ else if (strcasecmp(line, "TOP") == 0)
+ client->capabilities |= POP3C_CAPABILITY_TOP;
+ else if (strcasecmp(line, "UIDL") == 0)
+ client->capabilities |= POP3C_CAPABILITY_UIDL;
+ break;
+ case POP3C_CLIENT_STATE_DISCONNECTED:
+ case POP3C_CLIENT_STATE_DONE:
+ i_unreached();
+ }
+ return 0;
+}
+
+static void pop3c_client_prelogin_input(struct pop3c_client *client)
+{
+ const char *line, *errstr;
+
+ i_assert(client->state != POP3C_CLIENT_STATE_DONE);
+
+ /* we need to read as much as we can with SSL streams to avoid
+ hanging */
+ while ((line = i_stream_read_next_line(client->input)) != NULL) {
+ if (pop3c_client_prelogin_input_line(client, line) < 0) {
+ pop3c_client_disconnect(client);
+ return;
+ }
+ }
+
+ if (client->input->closed || client->input->eof ||
+ client->input->stream_errno != 0) {
+ /* disconnected */
+ if (client->ssl_iostream == NULL) {
+ i_error("pop3c(%s): Server disconnected unexpectedly",
+ client->set.host);
+ } else {
+ errstr = ssl_iostream_get_last_error(client->ssl_iostream);
+ if (errstr == NULL) {
+ errstr = client->input->stream_errno == 0 ? "EOF" :
+ strerror(client->input->stream_errno);
+ }
+ i_error("pop3c(%s): Server disconnected: %s",
+ client->set.host, errstr);
+ }
+ pop3c_client_disconnect(client);
+ }
+}
+
+static int pop3c_client_ssl_handshaked(const char **error_r, void *context)
+{
+ struct pop3c_client *client = context;
+ const char *error;
+
+ if (ssl_iostream_check_cert_validity(client->ssl_iostream,
+ client->set.host, &error) == 0) {
+ if (client->set.debug) {
+ i_debug("pop3c(%s): SSL handshake successful",
+ client->set.host);
+ }
+ return 0;
+ } else if (client->set.ssl_set.allow_invalid_cert) {
+ if (client->set.debug) {
+ i_debug("pop3c(%s): SSL handshake successful, "
+ "ignoring invalid certificate: %s",
+ client->set.host, error);
+ }
+ return 0;
+ } else {
+ *error_r = error;
+ return -1;
+ }
+}
+
+static int pop3c_client_ssl_init(struct pop3c_client *client)
+{
+ const char *error;
+
+ if (client->ssl_ctx == NULL) {
+ i_error("pop3c(%s): No SSL context", client->set.host);
+ return -1;
+ }
+
+ if (client->set.debug)
+ i_debug("pop3c(%s): Starting SSL handshake", client->set.host);
+
+ if (client->raw_input != client->input) {
+ /* recreate rawlog after STARTTLS */
+ i_stream_ref(client->raw_input);
+ o_stream_ref(client->raw_output);
+ i_stream_destroy(&client->input);
+ o_stream_destroy(&client->output);
+ client->input = client->raw_input;
+ client->output = client->raw_output;
+ }
+
+ if (io_stream_create_ssl_client(client->ssl_ctx, client->set.host,
+ &client->set.ssl_set, &client->input,
+ &client->output, &client->ssl_iostream, &error) < 0) {
+ i_error("pop3c(%s): Couldn't initialize SSL client: %s",
+ client->set.host, error);
+ return -1;
+ }
+ ssl_iostream_set_handshake_callback(client->ssl_iostream,
+ pop3c_client_ssl_handshaked,
+ client);
+ if (ssl_iostream_handshake(client->ssl_iostream) < 0) {
+ i_error("pop3c(%s): SSL handshake failed: %s", client->set.host,
+ ssl_iostream_get_last_error(client->ssl_iostream));
+ return -1;
+ }
+
+ if (*client->set.rawlog_dir != '\0') {
+ iostream_rawlog_create(client->set.rawlog_dir,
+ &client->input, &client->output);
+ }
+ return 0;
+}
+
+static void pop3c_client_connected(struct pop3c_client *client)
+{
+ int err;
+
+ err = net_geterror(client->fd);
+ if (err != 0) {
+ i_error("pop3c(%s): connect(%s, %u) failed: %s",
+ client->set.host, net_ip2addr(&client->ip),
+ client->set.port, strerror(err));
+ pop3c_client_disconnect(client);
+ return;
+ }
+ io_remove(&client->io);
+ client->io = io_add(client->fd, IO_READ,
+ pop3c_client_prelogin_input, client);
+
+ if (client->set.ssl_mode == POP3C_CLIENT_SSL_MODE_IMMEDIATE) {
+ if (pop3c_client_ssl_init(client) < 0)
+ pop3c_client_disconnect(client);
+ }
+}
+
+static void pop3c_client_connect_ip(struct pop3c_client *client)
+{
+ client->fd = net_connect_ip(&client->ip, client->set.port, NULL);
+ if (client->fd == -1) {
+ pop3c_client_disconnect(client);
+ return;
+ }
+
+ client->input = client->raw_input =
+ i_stream_create_fd(client->fd, POP3C_MAX_INBUF_SIZE);
+ client->output = client->raw_output =
+ o_stream_create_fd(client->fd, SIZE_MAX);
+ o_stream_set_no_error_handling(client->output, TRUE);
+
+ if (*client->set.rawlog_dir != '\0' &&
+ client->set.ssl_mode != POP3C_CLIENT_SSL_MODE_IMMEDIATE) {
+ iostream_rawlog_create(client->set.rawlog_dir,
+ &client->input, &client->output);
+ }
+ client->io = io_add(client->fd, IO_WRITE,
+ pop3c_client_connected, client);
+ client->to = timeout_add(POP3C_CONNECT_TIMEOUT_MSECS,
+ pop3c_client_timeout, client);
+ if (client->set.debug) {
+ i_debug("pop3c(%s): Connecting to %s:%u", client->set.host,
+ net_ip2addr(&client->ip), client->set.port);
+ }
+}
+
+static void
+pop3c_dns_callback(const struct dns_lookup_result *result,
+ struct pop3c_client *client)
+{
+ client->dns_lookup = NULL;
+
+ if (result->ret != 0) {
+ i_error("pop3c(%s): dns_lookup() failed: %s",
+ client->set.host, result->error);
+ pop3c_client_disconnect(client);
+ return;
+ }
+
+ i_assert(result->ips_count > 0);
+ client->ip = result->ips[0];
+ pop3c_client_connect_ip(client);
+}
+
+void pop3c_client_login(struct pop3c_client *client,
+ pop3c_login_callback_t *callback, void *context)
+{
+ if (client->fd != -1) {
+ i_assert(callback == NULL);
+ return;
+ }
+ i_assert(client->login_callback == NULL);
+ client->login_callback = callback;
+ client->login_context = context;
+ client->state = POP3C_CLIENT_STATE_CONNECTING;
+
+ if (client->set.debug)
+ i_debug("pop3c(%s): Looking up IP address", client->set.host);
+}
+
+bool pop3c_client_is_connected(struct pop3c_client *client)
+{
+ return client->fd != -1;
+}
+
+enum pop3c_capability
+pop3c_client_get_capabilities(struct pop3c_client *client)
+{
+ return client->capabilities;
+}
+
+static int pop3c_client_dot_input(struct pop3c_client *client)
+{
+ ssize_t ret;
+
+ while ((ret = i_stream_read(client->dot_input)) > 0 || ret == -2) {
+ i_stream_skip(client->dot_input,
+ i_stream_get_data_size(client->dot_input));
+ }
+ if (ret == 0)
+ return 0;
+ i_assert(ret == -1);
+
+ if (client->dot_input->stream_errno == 0)
+ ret = 1;
+ client->dot_input = NULL;
+
+ if (ret > 0) {
+ /* currently we don't actually care about preserving the
+ +OK reply line for multi-line replies, so just return
+ it as empty */
+ pop3c_client_async_callback(client, POP3C_COMMAND_STATE_OK, "");
+ return 1;
+ } else {
+ pop3c_client_async_callback_disconnected(client);
+ return -1;
+ }
+}
+
+static int
+pop3c_client_input_next_reply(struct pop3c_client *client)
+{
+ const char *line;
+ enum pop3c_command_state state;
+
+ line = i_stream_read_next_line(client->input);
+ if (line == NULL)
+ return client->input->eof ? -1 : 0;
+
+ if (strncasecmp(line, "+OK", 3) == 0) {
+ line += 3;
+ state = POP3C_COMMAND_STATE_OK;
+ } else if (strncasecmp(line, "-ERR", 4) == 0) {
+ line += 4;
+ state = POP3C_COMMAND_STATE_ERR;
+ } else {
+ i_error("pop3c(%s): Server sent unrecognized line: %s",
+ client->set.host, line);
+ state = POP3C_COMMAND_STATE_ERR;
+ }
+ if (line[0] == ' ')
+ line++;
+ if (array_count(&client->commands) == 0) {
+ i_error("pop3c(%s): Server sent line when no command was running: %s",
+ client->set.host, line);
+ } else {
+ pop3c_client_async_callback(client, state, line);
+ }
+ return 1;
+}
+
+static void pop3c_client_input(struct pop3c_client *client)
+{
+ int ret;
+
+ if (client->to != NULL)
+ timeout_reset(client->to);
+ do {
+ if (client->dot_input != NULL) {
+ /* continue reading the current multiline reply */
+ if ((ret = pop3c_client_dot_input(client)) == 0)
+ return;
+ } else {
+ ret = pop3c_client_input_next_reply(client);
+ }
+ } while (ret > 0);
+
+ if (ret < 0) {
+ i_error("pop3c(%s): Server disconnected unexpectedly",
+ client->set.host);
+ pop3c_client_disconnect(client);
+ }
+}
+
+static void pop3c_client_cmd_reply(enum pop3c_command_state state,
+ const char *reply, void *context)
+{
+ struct pop3c_client_sync_cmd_ctx *ctx = context;
+
+ i_assert(ctx->reply == NULL);
+
+ ctx->state = state;
+ ctx->reply = i_strdup(reply);
+}
+
+int pop3c_client_cmd_line(struct pop3c_client *client, const char *cmdline,
+ const char **reply_r)
+{
+ struct pop3c_client_sync_cmd_ctx ctx;
+
+ i_zero(&ctx);
+ pop3c_client_cmd_line_async(client, cmdline, pop3c_client_cmd_reply, &ctx);
+ while (ctx.reply == NULL)
+ pop3c_client_wait_one(client);
+ *reply_r = t_strdup(ctx.reply);
+ i_free(ctx.reply);
+ return ctx.state == POP3C_COMMAND_STATE_OK ? 0 : -1;
+}
+
+struct pop3c_client_cmd *
+pop3c_client_cmd_line_async(struct pop3c_client *client, const char *cmdline,
+ pop3c_cmd_callback_t *callback, void *context)
+{
+ struct pop3c_client_cmd *cmd;
+
+ if ((client->capabilities & POP3C_CAPABILITY_PIPELINING) == 0) {
+ while (array_count(&client->commands) > 0)
+ pop3c_client_wait_one(client);
+ }
+ i_assert(client->state == POP3C_CLIENT_STATE_DISCONNECTED ||
+ client->state == POP3C_CLIENT_STATE_DONE);
+ if (client->state == POP3C_CLIENT_STATE_DONE)
+ o_stream_nsend_str(client->output, cmdline);
+
+ cmd = array_append_space(&client->commands);
+ cmd->callback = callback;
+ cmd->context = context;
+ return cmd;
+}
+
+void pop3c_client_cmd_line_async_nocb(struct pop3c_client *client,
+ const char *cmdline)
+{
+ pop3c_client_cmd_line_async(client, cmdline, NULL, NULL);
+}
+
+static int seekable_fd_callback(const char **path_r, void *context)
+{
+ struct pop3c_client *client = context;
+ string_t *path;
+ int fd;
+
+ path = t_str_new(128);
+ str_append(path, client->set.temp_path_prefix);
+ fd = safe_mkstemp(path, 0600, (uid_t)-1, (gid_t)-1);
+ if (fd == -1) {
+ i_error("safe_mkstemp(%s) failed: %m", str_c(path));
+ return -1;
+ }
+
+ /* we just want the fd, unlink it */
+ if (i_unlink(str_c(path)) < 0) {
+ /* shouldn't happen.. */
+ i_close_fd(&fd);
+ return -1;
+ }
+
+ *path_r = str_c(path);
+ return fd;
+}
+
+int pop3c_client_cmd_stream(struct pop3c_client *client, const char *cmdline,
+ struct istream **input_r, const char **error_r)
+{
+ struct pop3c_client_sync_cmd_ctx ctx;
+ const char *reply;
+
+ if (client->state == POP3C_CLIENT_STATE_DISCONNECTED) {
+ *error_r = "Disconnected from server";
+ return -1;
+ }
+
+ i_zero(&ctx);
+ *input_r = pop3c_client_cmd_stream_async(client, cmdline,
+ pop3c_client_cmd_reply, &ctx);
+ while (ctx.reply == NULL)
+ pop3c_client_wait_one(client);
+ reply = t_strdup(ctx.reply);
+ i_free(ctx.reply);
+
+ if (ctx.state == POP3C_COMMAND_STATE_OK)
+ return 0;
+ i_stream_unref(input_r);
+ *error_r = reply;
+ return -1;
+}
+
+struct istream *
+pop3c_client_cmd_stream_async(struct pop3c_client *client, const char *cmdline,
+ pop3c_cmd_callback_t *callback, void *context)
+{
+ struct istream *input, *inputs[2];
+ struct pop3c_client_cmd *cmd;
+
+ cmd = pop3c_client_cmd_line_async(client, cmdline, callback, context);
+
+ input = i_stream_create_chain(&cmd->chain, POP3C_MAX_INBUF_SIZE);
+ inputs[0] = i_stream_create_dot(input, TRUE);
+ inputs[1] = NULL;
+ cmd->input = i_stream_create_seekable(inputs, POP3C_MAX_INBUF_SIZE,
+ seekable_fd_callback, client);
+ i_stream_unref(&input);
+ i_stream_unref(&inputs[0]);
+
+ i_stream_ref(cmd->input);
+ return cmd->input;
+}