]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-storage: Added initial support for pop3c backend.
authorTimo Sirainen <tss@iki.fi>
Mon, 2 Jan 2012 14:30:06 +0000 (16:30 +0200)
committerTimo Sirainen <tss@iki.fi>
Mon, 2 Jan 2012 14:30:06 +0000 (16:30 +0200)
12 files changed:
configure.in
src/lib-storage/index/Makefile.am
src/lib-storage/index/pop3c/Makefile.am [new file with mode: 0644]
src/lib-storage/index/pop3c/pop3c-client.c [new file with mode: 0644]
src/lib-storage/index/pop3c/pop3c-client.h [new file with mode: 0644]
src/lib-storage/index/pop3c/pop3c-mail.c [new file with mode: 0644]
src/lib-storage/index/pop3c/pop3c-settings.c [new file with mode: 0644]
src/lib-storage/index/pop3c/pop3c-settings.h [new file with mode: 0644]
src/lib-storage/index/pop3c/pop3c-storage.c [new file with mode: 0644]
src/lib-storage/index/pop3c/pop3c-storage.h [new file with mode: 0644]
src/lib-storage/index/pop3c/pop3c-sync.c [new file with mode: 0644]
src/lib-storage/index/pop3c/pop3c-sync.h [new file with mode: 0644]

index 0d0adbbf58565df69e0541c5c9fac1116c1a72b4..19d2a5954c8b35dc9fb6777c7130ffff3b6d8ad8 100644 (file)
@@ -242,12 +242,12 @@ AS_HELP_STRING([--with-gc], [Use Boehm garbage collector]),
   want_gc=no)
 
 AC_ARG_WITH(storages,
-AS_HELP_STRING([--with-storages], [Build with specified mail storage formats (mdbox sdbox maildir mbox cydir imapc)]), [
+AS_HELP_STRING([--with-storages], [Build with specified mail storage formats (mdbox sdbox maildir mbox cydir imapc pop3c)]), [
        if test "$withval" = "yes" || test "$withval" = "no"; then
                AC_MSG_ERROR([--with-storages needs storage list as parameter])
        fi
        mail_storages="shared `echo "$withval"|sed 's/,/ /g'`" ],
-       mail_storages="shared mdbox sdbox maildir mbox cydir imapc")
+       mail_storages="shared mdbox sdbox maildir mbox cydir imapc pop3c")
 AC_SUBST(mail_storages)
 mail_storages="$mail_storages raw"
 # drop duplicates
@@ -2448,7 +2448,8 @@ dbox_common_libs='$(top_builddir)/src/lib-storage/index/dbox-common/libstorage_d
 sdbox_libs='$(top_builddir)/src/lib-storage/index/dbox-single/libstorage_dbox_single.la'
 mdbox_libs='$(top_builddir)/src/lib-storage/index/dbox-multi/libstorage_dbox_multi.la'
 cydir_libs='$(top_builddir)/src/lib-storage/index/cydir/libstorage_cydir.la'
-imapc_libs='$(top_builddir)/src/lib-storage/index/imapc/libstorage_imapc.la $(top_builddir)/src/lib-imap-client/libimap_client.la $(top_builddir)/src/lib-ssl-iostream/libssl_iostream.la'
+imapc_libs='$(top_builddir)/src/lib-storage/index/imapc/libstorage_imapc.la $(top_builddir)/src/lib-imap-client/libimap_client.la'
+pop3c_libs='$(top_builddir)/src/lib-storage/index/pop3c/libstorage_pop3c.la'
 raw_libs='$(top_builddir)/src/lib-storage/index/raw/libstorage_raw.la'
 shared_libs='$(top_builddir)/src/lib-storage/index/shared/libstorage_shared.la'
 
@@ -2470,8 +2471,15 @@ for storage in $mail_storages; do
   fi
   if test $storage = imapc; then
     mailbox_list_drivers="$mailbox_list_drivers imapc"
+    want_ssl_libs=yes
+  fi
+  if test $storage = pop3c; then
+    want_ssl_libs=yes
   fi
 done
+if test "$want_ssl_libs" = yes; then
+  LINKED_STORAGE_LIBS="$LINKED_STORAGE_LIBS \$(top_builddir)/src/lib-ssl-iostream/libssl_iostream.la"
+fi
 AC_SUBST(LINKED_STORAGE_LIBS)
 AC_SUBST(mailbox_list_drivers)
 AC_DEFINE_UNQUOTED(MAIL_STORAGES, "$mail_storages", List of compiled in mail storages)
@@ -2737,6 +2745,7 @@ src/lib-storage/Makefile
 src/lib-storage/list/Makefile
 src/lib-storage/index/Makefile
 src/lib-storage/index/imapc/Makefile
+src/lib-storage/index/pop3c/Makefile
 src/lib-storage/index/maildir/Makefile
 src/lib-storage/index/mbox/Makefile
 src/lib-storage/index/dbox-common/Makefile
index 435ae8f097e5a395f6eef3c113647400b6ccea15..6e7d4773a090776beeca0f4219cc24da37fdee51 100644 (file)
@@ -1,4 +1,4 @@
-SUBDIRS = maildir mbox dbox-common dbox-multi dbox-single cydir imapc raw shared
+SUBDIRS = maildir mbox dbox-common dbox-multi dbox-single cydir imapc pop3c raw shared
 
 noinst_LTLIBRARIES = libstorage_index.la
 
diff --git a/src/lib-storage/index/pop3c/Makefile.am b/src/lib-storage/index/pop3c/Makefile.am
new file mode 100644 (file)
index 0000000..868dc1e
--- /dev/null
@@ -0,0 +1,28 @@
+noinst_LTLIBRARIES = libstorage_pop3c.la
+
+AM_CPPFLAGS = \
+       -I$(top_srcdir)/src/lib \
+       -I$(top_srcdir)/src/lib-settings \
+       -I$(top_srcdir)/src/lib-dns \
+       -I$(top_srcdir)/src/lib-ssl-iostream \
+       -I$(top_srcdir)/src/lib-mail \
+       -I$(top_srcdir)/src/lib-imap \
+       -I$(top_srcdir)/src/lib-index \
+       -I$(top_srcdir)/src/lib-storage \
+       -I$(top_srcdir)/src/lib-storage/index
+
+libstorage_pop3c_la_SOURCES = \
+       pop3c-client.c \
+       pop3c-mail.c \
+       pop3c-settings.c \
+       pop3c-storage.c \
+       pop3c-sync.c
+
+headers = \
+       pop3c-client.h \
+       pop3c-settings.h \
+       pop3c-storage.h \
+       pop3c-sync.h
+
+pkginc_libdir=$(pkgincludedir)
+pkginc_lib_HEADERS = $(headers)
diff --git a/src/lib-storage/index/pop3c/pop3c-client.c b/src/lib-storage/index/pop3c/pop3c-client.c
new file mode 100644 (file)
index 0000000..714b980
--- /dev/null
@@ -0,0 +1,781 @@
+/* Copyright (c) 2011 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "network.h"
+#include "istream.h"
+#include "istream-dot.h"
+#include "istream-seekable.h"
+#include "ostream.h"
+#include "iostream-rawlog.h"
+#include "iostream-ssl.h"
+#include "close-keep-errno.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*30)
+
+enum pop3c_client_state {
+       /* No connection */
+       POP3C_CLIENT_STATE_DISCONNECTED = 0,
+       /* Trying to connect */
+       POP3C_CLIENT_STATE_CONNECTING,
+       /* 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 {
+       pool_t pool;
+       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;
+
+       enum pop3c_client_state state;
+       enum pop3c_capability capabilities;
+
+       pop3c_login_callback_t *login_callback;
+       void *login_context;
+
+       unsigned int async_commands;
+       const char *input_line;
+       struct istream *dot_input;
+
+       unsigned int handshake_failed:1;
+       unsigned int running:1;
+};
+
+static void
+pop3c_dns_callback(const struct dns_lookup_result *result, void *context);
+
+struct pop3c_client *
+pop3c_client_init(const struct pop3c_client_settings *set)
+{
+       struct pop3c_client *client;
+       struct ssl_iostream_settings ssl_set;
+       const char *source;
+       pool_t pool;
+
+       pool = pool_alloconly_create("pop3c client", 1024);
+       client = p_new(pool, struct pop3c_client, 1);
+       client->pool = pool;
+       client->fd = -1;
+
+       client->set.debug = set->debug;
+       client->set.host = p_strdup(pool, set->host);
+       client->set.port = set->port;
+       client->set.master_user = p_strdup(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);
+
+       if (set->ssl_mode != POP3C_CLIENT_SSL_MODE_NONE) {
+               client->set.ssl_mode = set->ssl_mode;
+               client->set.ssl_ca_dir = p_strdup(pool, set->ssl_ca_dir);
+               client->set.ssl_verify = set->ssl_verify;
+
+               memset(&ssl_set, 0, sizeof(ssl_set));
+               ssl_set.ca_dir = set->ssl_ca_dir;
+               ssl_set.verify_remote_cert = set->ssl_verify;
+               ssl_set.crypto_device = set->ssl_crypto_device;
+
+               source = t_strdup_printf("%s:%u", set->host, set->port);
+               if (ssl_iostream_context_init_client(source, &ssl_set,
+                                                    &client->ssl_ctx) < 0) {
+                       i_error("pop3c(%s): Couldn't initialize SSL context",
+                               source);
+               }
+       }
+       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_disconnect(struct pop3c_client *client)
+{
+       client->state = POP3C_CLIENT_STATE_DISCONNECTED;
+       client->async_commands = 0;
+
+       if (client->running)
+               io_loop_stop(current_ioloop);
+
+       if (client->to != NULL)
+               timeout_remove(&client->to);
+       if (client->io != NULL)
+               io_remove(&client->io);
+       if (client->input != NULL)
+               i_stream_destroy(&client->input);
+       if (client->output != NULL)
+               o_stream_destroy(&client->output);
+       if (client->ssl_iostream != NULL)
+               ssl_iostream_unref(&client->ssl_iostream);
+       if (client->fd != -1) {
+               if (close(client->fd) < 0)
+                       i_error("close(pop3c) failed: %m");
+               client->fd = -1;
+       }
+       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_deinit(&client->ssl_ctx);
+       pool_unref(&client->pool);
+}
+
+static void pop3c_client_ioloop_changed(struct pop3c_client *client)
+{
+       if (client->to != NULL)
+               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);
+}
+
+void pop3c_client_run(struct pop3c_client *client)
+{
+       struct ioloop *ioloop, *prev_ioloop = current_ioloop;
+       bool timeout_added = FALSE;
+
+       i_assert(client->fd != -1 ||
+                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 */
+               struct dns_lookup_settings dns_set;
+
+               i_assert(client->state == POP3C_CLIENT_STATE_CONNECTING);
+               memset(&dns_set, 0, sizeof(dns_set));
+               dns_set.dns_client_socket_path =
+                       client->set.dns_client_socket_path;
+               dns_set.timeout_msecs = POP3C_DNS_LOOKUP_TIMEOUT_MSECS;
+               (void)dns_lookup(client->set.host, &dns_set,
+                                pop3c_dns_callback, client);
+       } else if (client->to == NULL) {
+               client->to = timeout_add(POP3C_COMMAND_TIMEOUT_MSECS,
+                                        pop3c_client_timeout, client);
+               timeout_added = TRUE;
+       }
+
+       client->running = TRUE;
+       io_loop_run(ioloop);
+       client->running = FALSE;
+
+       if (timeout_added && client->to != NULL)
+               timeout_remove(&client->to);
+
+       current_ioloop = prev_ioloop;
+       pop3c_client_ioloop_changed(client);
+       current_ioloop = ioloop;
+       io_loop_destroy(&ioloop);
+}
+
+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",
+                               client->set.host, set->username);
+               } else {
+                       i_debug("pop3c(%s): Authenticating as %s for user %s",
+                               client->set.host, set->master_user,
+                               set->username);
+               }
+       }
+
+       if (set->master_user == NULL) {
+               o_stream_send_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_send_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);
+       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;
+               }
+               pop3c_client_authenticate1(client);
+               break;
+       case POP3C_CLIENT_STATE_USER:
+               if (!success) {
+                       i_error("pop3c(%s): USER failed: %s",
+                               client->set.host, line);
+                       return -1;
+               }
+               o_stream_send_str(client->output,
+                       t_strdup_printf("PASS %s\r\n", client->set.password));
+               client->state = POP3C_CLIENT_STATE_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_send_str(client->output,
+                       pop3c_client_get_sasl_plain_request(client));
+               client->state = POP3C_CLIENT_STATE_PASS;
+               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 failed: %s",
+                               client->set.host, line);
+               }
+               if (!success)
+                       return -1;
+
+               o_stream_send_str(client->output, "CAPA\r\n");
+               client->state = POP3C_CLIENT_STATE_CAPA;
+               break;
+       case POP3C_CLIENT_STATE_CAPA:
+               if (strncasecmp(line, "-ERR", 4) == 0 ||
+                   strcmp(line, ".") == 0) {
+                       pop3c_client_login_finished(client);
+                       break;
+               }
+               if (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 if (!client->handshake_failed) {
+                       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(void *context)
+{
+       struct pop3c_client *client = context;
+
+       if (!client->set.ssl_verify) {
+               /* skip certificate checks */
+               return 0;
+       } else if (!ssl_iostream_has_valid_client_cert(client->ssl_iostream)) {
+               if (!ssl_iostream_has_broken_client_cert(client->ssl_iostream)) {
+                       i_error("pop3c(%s): SSL certificate not received",
+                               client->set.host);
+               } else {
+                       i_error("pop3c(%s): Received invalid SSL certificate",
+                               client->set.host);
+               }
+       } else if (ssl_iostream_cert_match_name(client->ssl_iostream,
+                                               client->set.host) < 0) {
+               i_error("pop3c(%s): SSL certificate doesn't match host name",
+                       client->set.host);
+       } else {
+               if (client->set.debug) {
+                       i_debug("pop3c(%s): SSL handshake successful",
+                               client->set.host);
+               }
+               return 0;
+       }
+       client->handshake_failed = TRUE;
+       i_stream_close(client->input);
+       return -1;
+}
+
+static int pop3c_client_ssl_init(struct pop3c_client *client)
+{
+       struct ssl_iostream_settings ssl_set;
+       struct stat st;
+       const char *source;
+
+       if (client->ssl_ctx == NULL) {
+               i_error("pop3c(%s): No SSL context", client->set.host);
+               return -1;
+       }
+
+       memset(&ssl_set, 0, sizeof(ssl_set));
+       if (client->set.ssl_verify) {
+               ssl_set.verbose_invalid_cert = TRUE;
+               ssl_set.verify_remote_cert = TRUE;
+               ssl_set.require_valid_cert = TRUE;
+       }
+
+       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;
+       }
+
+       source = t_strdup_printf("pop3c(%s): ", client->set.host);
+       if (io_stream_create_ssl(client->ssl_ctx, source, &ssl_set,
+                                &client->input, &client->output,
+                                &client->ssl_iostream) < 0) {
+               i_error("pop3c(%s): Couldn't initialize SSL client",
+                       client->set.host);
+               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' &&
+           stat(client->set.rawlog_dir, &st) == 0) {
+               (void)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)
+{
+       struct stat st;
+
+       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, FALSE);
+       client->output = client->raw_output =
+               o_stream_create_fd(client->fd, (size_t)-1, FALSE);
+
+       if (*client->set.rawlog_dir != '\0' &&
+           client->set.ssl_mode != POP3C_CLIENT_SSL_MODE_IMMEDIATE &&
+           stat(client->set.rawlog_dir, &st) == 0) {
+               (void)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, void *context)
+{
+       struct pop3c_client *client = context;
+
+       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 void pop3c_client_input_reply(struct pop3c_client *client)
+{
+       i_assert(client->state == POP3C_CLIENT_STATE_DONE);
+
+       if (client->to != NULL)
+               timeout_reset(client->to);
+       client->input_line = i_stream_read_next_line(client->input);
+       if (client->input_line != NULL)
+               io_loop_stop(current_ioloop);
+       else if (client->input->closed || client->input->eof ||
+                client->input->stream_errno != 0) {
+               /* disconnected */
+               i_error("pop3c(%s): Server disconnected unexpectedly",
+                       client->set.host);
+               pop3c_client_disconnect(client);
+               io_loop_stop(current_ioloop);
+       }
+}
+
+static int
+pop3c_client_read_line(struct pop3c_client *client,
+                      const char **line_r, const char **error_r)
+{
+       i_assert(client->io == NULL);
+       i_assert(client->input_line == NULL);
+
+       client->io = io_add(client->fd, IO_READ,
+                           pop3c_client_input_reply, client);
+       pop3c_client_input_reply(client);
+       if (client->input_line == NULL && client->input != NULL)
+               pop3c_client_run(client);
+
+       if (client->input_line == NULL) {
+               i_assert(client->io == NULL);
+               *error_r = "Disconnected";
+               return -1;
+       }
+
+       io_remove(&client->io);
+       *line_r = t_strdup(client->input_line);
+       client->input_line = NULL;
+       return 0;
+}
+
+static int
+pop3c_client_flush_asyncs(struct pop3c_client *client, const char **error_r)
+{
+       const char *line;
+
+       if (client->state != POP3C_CLIENT_STATE_DONE) {
+               i_assert(client->state == POP3C_CLIENT_STATE_DISCONNECTED);
+               *error_r = "Disconnected";
+               return -1;
+       }
+
+       while (client->async_commands > 0) {
+               if (pop3c_client_read_line(client, &line, error_r) < 0)
+                       return -1;
+               client->async_commands--;
+       }
+       return 0;
+}
+
+int pop3c_client_cmd_line(struct pop3c_client *client, const char *cmd,
+                         const char **reply_r)
+{
+       const char *line;
+       int ret;
+
+       if (pop3c_client_flush_asyncs(client, reply_r) < 0)
+               return -1;
+       o_stream_send_str(client->output, cmd);
+       if (pop3c_client_read_line(client, &line, reply_r) < 0)
+               return -1;
+       if (strncasecmp(line, "+OK", 3) == 0) {
+               *reply_r = line + 3;
+               ret = 0;
+       } else if (strncasecmp(line, "-ERR", 4) == 0) {
+               *reply_r = line + 3;
+               ret = -1;
+       } else {
+               *reply_r = line;
+               ret = -1;
+       }
+       if (**reply_r == ' ')
+               *reply_r += 1;
+       return ret;
+}
+
+void pop3c_client_cmd_line_async(struct pop3c_client *client, const char *cmd)
+{
+       const char *error;
+
+       if (client->state != POP3C_CLIENT_STATE_DONE) {
+               i_assert(client->state == POP3C_CLIENT_STATE_DISCONNECTED);
+               return;
+       }
+
+       if ((client->capabilities & POP3C_CAPABILITY_PIPELINING) == 0) {
+               if (pop3c_client_flush_asyncs(client, &error) < 0)
+                       return;
+       }
+       o_stream_send_str(client->output, cmd);
+       client->async_commands++;
+}
+
+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 (unlink(str_c(path)) < 0) {
+               /* shouldn't happen.. */
+               i_error("unlink(%s) failed: %m", str_c(path));
+               close_keep_errno(fd);
+               return -1;
+       }
+
+       *path_r = str_c(path);
+       return fd;
+}
+
+static void pop3c_client_dot_input(struct pop3c_client *client)
+{
+       ssize_t ret;
+       size_t size;
+
+       if (client->to != NULL)
+               timeout_reset(client->to);
+       while ((ret = i_stream_read(client->dot_input)) > 0 || ret == -2) {
+               (void)i_stream_get_data(client->dot_input, &size);
+               i_stream_skip(client->dot_input, size);
+       }
+       (void)i_stream_get_data(client->dot_input, &size);
+       if (ret != 0) {
+               i_assert(ret == -1);
+               if (client->dot_input->stream_errno != 0) {
+                       i_error("pop3c(%s): Server disconnected unexpectedly",
+                               client->set.host);
+                       pop3c_client_disconnect(client);
+               }
+               if (client->running)
+                       io_loop_stop(current_ioloop);
+       }
+}
+
+int pop3c_client_cmd_stream(struct pop3c_client *client, const char *cmd,
+                           struct istream **input_r, const char **error_r)
+{
+       struct istream *inputs[2];
+
+       *input_r = NULL;
+
+       /* read the +OK / -ERR */
+       if (pop3c_client_cmd_line(client, cmd, error_r) < 0)
+               return -1;
+       /* read the stream */
+       inputs[0] = i_stream_create_dot(client->input, FALSE);
+       inputs[1] = NULL;
+       client->dot_input =
+               i_stream_create_seekable(inputs, POP3C_MAX_INBUF_SIZE,
+                                        seekable_fd_callback, client);
+
+       i_assert(client->io == NULL);
+       client->io = io_add(client->fd, IO_READ,
+                           pop3c_client_dot_input, client);
+       /* read any pending data from the stream */
+       pop3c_client_dot_input(client);
+       if (!client->dot_input->eof)
+               pop3c_client_run(client);
+
+       if (client->input == NULL) {
+               i_assert(client->io == NULL);
+               i_stream_destroy(&client->dot_input);
+               *error_r = "Disconnected";
+               return -1;
+       }
+       io_remove(&client->io);
+       i_stream_seek(client->dot_input, 0);
+
+       *input_r = client->dot_input;
+       client->dot_input = NULL;
+       return 0;
+}
diff --git a/src/lib-storage/index/pop3c/pop3c-client.h b/src/lib-storage/index/pop3c/pop3c-client.h
new file mode 100644 (file)
index 0000000..b455167
--- /dev/null
@@ -0,0 +1,69 @@
+#ifndef POP3C_CLIENT_H
+#define POP3C_CLIENT_H
+
+enum pop3c_capability {
+       POP3C_CAPABILITY_PIPELINING     = 0x01,
+       POP3C_CAPABILITY_TOP            = 0x02,
+       POP3C_CAPABILITY_UIDL           = 0x04
+};
+
+enum pop3c_command_state {
+       POP3C_COMMAND_STATE_OK,
+       POP3C_COMMAND_STATE_ERR,
+       POP3C_COMMAND_STATE_DISCONNECTED
+};
+
+enum pop3c_client_ssl_mode {
+       POP3C_CLIENT_SSL_MODE_NONE,
+       POP3C_CLIENT_SSL_MODE_IMMEDIATE,
+       POP3C_CLIENT_SSL_MODE_STARTTLS
+};
+
+struct pop3c_client_settings {
+       const char *host;
+       unsigned int port;
+
+       const char *master_user;
+       const char *username;
+       const char *password;
+
+       const char *dns_client_socket_path;
+       const char *temp_path_prefix;
+
+       enum pop3c_client_ssl_mode ssl_mode;
+       const char *ssl_ca_dir;
+       bool ssl_verify;
+
+       const char *rawlog_dir;
+       const char *ssl_crypto_device;
+       bool debug;
+};
+
+typedef void pop3c_login_callback_t(enum pop3c_command_state state,
+                                   const char *reply, void *context);
+
+struct pop3c_client *
+pop3c_client_init(const struct pop3c_client_settings *set);
+void pop3c_client_deinit(struct pop3c_client **client);
+
+void pop3c_client_run(struct pop3c_client *client);
+
+void pop3c_client_login(struct pop3c_client *client,
+                       pop3c_login_callback_t *callback, void *context);
+
+bool pop3c_client_is_connected(struct pop3c_client *client);
+enum pop3c_capability
+pop3c_client_get_capabilities(struct pop3c_client *client);
+
+/* Returns 0 if received +OK reply, reply contains the text without the +OK.
+   Returns -1 if received -ERR reply or disconnected. */
+int pop3c_client_cmd_line(struct pop3c_client *client, const char *cmd,
+                         const char **reply_r);
+/* Send a command, don't care if it succeeds or not. */
+void pop3c_client_cmd_line_async(struct pop3c_client *client, const char *cmd);
+/* Returns 0 and stream if succeeded, -1 and error if received -ERR reply or
+   disconnected. */
+int pop3c_client_cmd_stream(struct pop3c_client *client, const char *cmd,
+                           struct istream **input_r, const char **error_r);
+
+#endif
diff --git a/src/lib-storage/index/pop3c/pop3c-mail.c b/src/lib-storage/index/pop3c/pop3c-mail.c
new file mode 100644 (file)
index 0000000..3d8e8a1
--- /dev/null
@@ -0,0 +1,184 @@
+/* Copyright (c) 2011 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "istream.h"
+#include "index-mail.h"
+#include "pop3c-client.h"
+#include "pop3c-sync.h"
+#include "pop3c-storage.h"
+
+static int pop3c_mail_get_received_date(struct mail *_mail, time_t *date_r)
+{
+       mail_storage_set_error(_mail->box->storage, MAIL_ERROR_NOTPOSSIBLE,
+                              "POP3 has no received date");
+       *date_r = (time_t)-1;
+       return -1;
+}
+
+static int pop3c_mail_get_save_date(struct mail *_mail, time_t *date_r)
+{
+       mail_storage_set_error(_mail->box->storage, MAIL_ERROR_NOTPOSSIBLE,
+                              "POP3 has no save date");
+       *date_r = (time_t)-1;
+       return -1;
+}
+
+static int pop3c_mail_get_physical_size(struct mail *_mail, uoff_t *size_r)
+{
+       struct index_mail *mail = (struct index_mail *)_mail;
+       struct pop3c_mailbox *mbox = (struct pop3c_mailbox *)_mail->box;
+       struct message_size hdr_size, body_size;
+       struct istream *input;
+
+       if (mail->data.virtual_size != 0) {
+               /* virtual size is already known. it's the same as our
+                  (correct) physical size */
+               *size_r = mail->data.virtual_size;
+               return 0;
+       }
+       if (index_mail_get_physical_size(_mail, size_r) == 0) {
+               *size_r = mail->data.physical_size;
+               return 0;
+       }
+
+       if (_mail->lookup_abort == MAIL_LOOKUP_ABORT_READ_MAIL &&
+           (_mail->box->flags & MAILBOX_FLAG_POP3_SESSION) != 0) {
+               /* kludge: we want output for POP3 LIST with
+                  pop3_fast_size_lookups=yes. use the remote's LIST values
+                  regardless of their correctness */
+               if (mbox->msg_sizes == NULL) {
+                       if (pop3c_sync_get_sizes(mbox) < 0)
+                               return -1;
+               }
+               i_assert(_mail->seq <= mbox->msg_count);
+               *size_r = mbox->msg_sizes[_mail->seq-1];
+               return 0;
+       }
+
+       /* slow way: get the whole message body */
+       if (mail_get_stream(_mail, &hdr_size, &body_size, &input) < 0)
+               return -1;
+
+       i_assert(mail->data.physical_size != (uoff_t)-1);
+       *size_r = mail->data.physical_size;
+       return 0;
+}
+
+static void pop3c_mail_cache_size(struct index_mail *mail)
+{
+       struct mail *_mail = &mail->mail.mail;
+       uoff_t size;
+       unsigned int cache_idx;
+
+       if (i_stream_get_size(mail->data.stream, TRUE, &size) <= 0)
+               return;
+       mail->data.virtual_size = size;
+
+       cache_idx = mail->ibox->cache_fields[MAIL_CACHE_VIRTUAL_FULL_SIZE].idx;
+       if (mail_cache_field_exists(_mail->transaction->cache_view,
+                                   _mail->seq, cache_idx) == 0) {
+               index_mail_cache_add_idx(mail, cache_idx, &size, sizeof(size));
+               /* make sure it's not cached twice */
+               mail->data.dont_cache_fetch_fields |=
+                       MAIL_CACHE_VIRTUAL_FULL_SIZE;
+       }
+}
+
+static int
+pop3c_mail_get_stream(struct mail *_mail, bool get_body,
+                     struct message_size *hdr_size,
+                     struct message_size *body_size, struct istream **stream_r)
+{
+       struct index_mail *mail = (struct index_mail *)_mail;
+       struct pop3c_mailbox *mbox = (struct pop3c_mailbox *)_mail->box;
+       enum pop3c_capability capa;
+       const char *name, *cmd, *error;
+       struct istream *input;
+
+       if (get_body && mail->data.stream != NULL) {
+               name = i_stream_get_name(mail->data.stream);
+               if (strncmp(name, "RETR", 4) == 0) {
+                       /* we've fetched the body */
+               } else if (strncmp(name, "TOP", 3) == 0) {
+                       /* we've fetched the header, but we need the body
+                          now too */
+                       index_mail_close_streams(mail);
+               } else {
+                       i_panic("Unexpected POP3 stream name: %s", name);
+               }
+       }
+
+       if (mail->data.stream == NULL) {
+               capa = pop3c_client_get_capabilities(mbox->client);
+               if (get_body || (capa & POP3C_CAPABILITY_TOP) == 0)
+                       cmd = t_strdup_printf("RETR %u\r\n", _mail->seq);
+               else
+                       cmd = t_strdup_printf("TOP %u\r\n", _mail->seq);
+               if (pop3c_client_cmd_stream(mbox->client, cmd,
+                                           &input, &error) < 0) {
+                       mail_storage_set_error(mbox->box.storage,
+                               !pop3c_client_is_connected(mbox->client) ?
+                               MAIL_ERROR_TEMP : MAIL_ERROR_EXPUNGED, error);
+                       return -1;
+               }
+               i_stream_set_name(input, t_strcut(cmd, '\r'));
+               mail->data.stream = input;
+               pop3c_mail_cache_size(mail);
+       }
+       return index_mail_init_stream(mail, hdr_size, body_size, stream_r);
+}
+
+static int
+pop3c_mail_get_special(struct mail *_mail, enum mail_fetch_field field,
+                      const char **value_r)
+{
+       struct pop3c_mailbox *mbox = (struct pop3c_mailbox *)_mail->box;
+
+       switch (field) {
+       case MAIL_FETCH_UIDL_BACKEND:
+               if (mbox->msg_uidls == NULL) {
+                       if (pop3c_sync_get_uidls(mbox) < 0)
+                               return -1;
+               }
+               i_assert(_mail->seq <= mbox->msg_count);
+               *value_r = mbox->msg_uidls[_mail->seq-1];
+               return 0;
+       default:
+               return index_mail_get_special(_mail, field, value_r);
+       }
+}
+
+struct mail_vfuncs pop3c_mail_vfuncs = {
+       index_mail_close,
+       index_mail_free,
+       index_mail_set_seq,
+       index_mail_set_uid,
+       index_mail_set_uid_cache_updates,
+       index_mail_prefetch,
+       index_mail_precache,
+       index_mail_add_temp_wanted_fields,
+
+       index_mail_get_flags,
+       index_mail_get_keywords,
+       index_mail_get_keyword_indexes,
+       index_mail_get_modseq,
+       index_mail_get_parts,
+       index_mail_get_date,
+       pop3c_mail_get_received_date,
+       pop3c_mail_get_save_date,
+       index_mail_get_virtual_size,
+       pop3c_mail_get_physical_size,
+       index_mail_get_first_header,
+       index_mail_get_headers,
+       index_mail_get_header_stream,
+       pop3c_mail_get_stream,
+       pop3c_mail_get_special,
+       index_mail_get_real_mail,
+       index_mail_update_flags,
+       index_mail_update_keywords,
+       index_mail_update_modseq,
+       NULL,
+       index_mail_expunge,
+       index_mail_set_cache_corrupted,
+       index_mail_opened
+};
diff --git a/src/lib-storage/index/pop3c/pop3c-settings.c b/src/lib-storage/index/pop3c/pop3c-settings.c
new file mode 100644 (file)
index 0000000..eb49dfe
--- /dev/null
@@ -0,0 +1,87 @@
+/* Copyright (c) 2011 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "settings-parser.h"
+#include "mail-storage-settings.h"
+#include "pop3c-settings.h"
+
+#include <stddef.h>
+
+#undef DEF
+#define DEF(type, name) \
+       { type, #name, offsetof(struct pop3c_settings, name), NULL }
+
+static bool pop3c_settings_check(void *_set, pool_t pool, const char **error_r);
+
+static const struct setting_define pop3c_setting_defines[] = {
+       DEF(SET_STR, pop3c_host),
+       DEF(SET_UINT, pop3c_port),
+
+       DEF(SET_STR_VARS, pop3c_user),
+       DEF(SET_STR, pop3c_password),
+
+       DEF(SET_ENUM, pop3c_ssl),
+       DEF(SET_STR, pop3c_ssl_ca_dir),
+       DEF(SET_BOOL, pop3c_ssl_verify),
+
+       DEF(SET_STR, pop3c_rawlog_dir),
+       DEF(SET_STR, ssl_crypto_device),
+
+       SETTING_DEFINE_LIST_END
+};
+
+static const struct pop3c_settings pop3c_default_settings = {
+       .pop3c_host = "",
+       .pop3c_port = 143,
+
+       .pop3c_user = "%u",
+       .pop3c_password = "",
+
+       .pop3c_ssl = "no:pop3s:starttls",
+       .pop3c_ssl_ca_dir = "",
+       .pop3c_ssl_verify = TRUE,
+
+       .pop3c_rawlog_dir = "",
+       .ssl_crypto_device = ""
+};
+
+static const struct setting_parser_info pop3c_setting_parser_info = {
+       .module_name = "pop3c",
+       .defines = pop3c_setting_defines,
+       .defaults = &pop3c_default_settings,
+
+       .type_offset = (size_t)-1,
+       .struct_size = sizeof(struct pop3c_settings),
+
+       .parent_offset = (size_t)-1,
+       .parent = &mail_user_setting_parser_info,
+
+       .check_func = pop3c_settings_check
+};
+
+const struct setting_parser_info *pop3c_get_setting_parser_info(void)
+{
+       return &pop3c_setting_parser_info;
+}
+
+/* <settings checks> */
+static bool pop3c_settings_check(void *_set, pool_t pool ATTR_UNUSED,
+                                const char **error_r)
+{
+       struct pop3c_settings *set = _set;
+
+       if (set->pop3c_port == 0 || set->pop3c_port > 65535) {
+               *error_r = "invalid pop3c_port";
+               return FALSE;
+       }
+#ifndef CONFIG_BINARY
+       if (*set->pop3c_ssl_ca_dir != '\0' &&
+           access(set->pop3c_ssl_ca_dir, X_OK) < 0) {
+               *error_r = t_strdup_printf(
+                       "pop3c_ssl_ca_dir: access(%s) failed: %m",
+                       set->pop3c_ssl_ca_dir);
+               return FALSE;
+       }
+#endif
+       return TRUE;
+}
diff --git a/src/lib-storage/index/pop3c/pop3c-settings.h b/src/lib-storage/index/pop3c/pop3c-settings.h
new file mode 100644 (file)
index 0000000..5d8ce09
--- /dev/null
@@ -0,0 +1,21 @@
+#ifndef POP3C_SETTINGS_H
+#define POP3C_SETTINGS_H
+
+struct pop3c_settings {
+       const char *pop3c_host;
+       unsigned int pop3c_port;
+
+       const char *pop3c_user;
+       const char *pop3c_password;
+
+       const char *pop3c_ssl;
+       const char *pop3c_ssl_ca_dir;
+       bool pop3c_ssl_verify;
+
+       const char *pop3c_rawlog_dir;
+       const char *ssl_crypto_device;
+};
+
+const struct setting_parser_info *pop3c_get_setting_parser_info(void);
+
+#endif
diff --git a/src/lib-storage/index/pop3c/pop3c-storage.c b/src/lib-storage/index/pop3c/pop3c-storage.c
new file mode 100644 (file)
index 0000000..6b84398
--- /dev/null
@@ -0,0 +1,282 @@
+/* Copyright (c) 2011 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "str.h"
+#include "mail-copy.h"
+#include "mail-user.h"
+#include "mailbox-list-private.h"
+#include "index-mail.h"
+#include "pop3c-client.h"
+#include "pop3c-settings.h"
+#include "pop3c-sync.h"
+#include "pop3c-storage.h"
+
+#define DNS_CLIENT_SOCKET_NAME "dns-client"
+
+extern struct mail_storage pop3c_storage;
+extern struct mailbox pop3c_mailbox;
+
+static struct mail_storage *pop3c_storage_alloc(void)
+{
+       struct pop3c_storage *storage;
+       pool_t pool;
+
+       pool = pool_alloconly_create("pop3c storage", 512+256);
+       storage = p_new(pool, struct pop3c_storage, 1);
+       storage->storage = pop3c_storage;
+       storage->storage.pool = pool;
+       return &storage->storage;
+}
+
+static int
+pop3c_storage_create(struct mail_storage *_storage,
+                    struct mail_namespace *ns ATTR_UNUSED,
+                    const char **error_r)
+{
+       struct pop3c_storage *storage = (struct pop3c_storage *)_storage;
+
+       storage->set = mail_storage_get_driver_settings(_storage);
+       if (storage->set->pop3c_host[0] == '\0') {
+               *error_r = "missing pop3c_host";
+               return -1;
+       }
+       if (storage->set->pop3c_password == '\0') {
+               *error_r = "missing pop3c_password";
+               return -1;
+       }
+
+       return 0;
+}
+
+static struct pop3c_client *
+pop3c_client_create_from_set(struct mail_user *user,
+                            const struct pop3c_settings *set)
+{
+       struct pop3c_client_settings client_set;
+       string_t *str;
+
+       memset(&client_set, 0, sizeof(client_set));
+       client_set.host = set->pop3c_host;
+       client_set.port = set->pop3c_port;
+       client_set.username = set->pop3c_user;
+       client_set.password = set->pop3c_password;
+       client_set.dns_client_socket_path =
+               t_strconcat(user->set->base_dir, "/",
+                           DNS_CLIENT_SOCKET_NAME, NULL);
+       str = t_str_new(128);
+       mail_user_set_get_temp_prefix(str, user->set);
+       client_set.temp_path_prefix = str_c(str);
+
+       client_set.debug = user->mail_debug;
+       client_set.rawlog_dir =
+               mail_user_home_expand(user, set->pop3c_rawlog_dir);
+
+       client_set.ssl_ca_dir = set->pop3c_ssl_ca_dir;
+       client_set.ssl_verify = set->pop3c_ssl_verify;
+       if (strcmp(set->pop3c_ssl, "pop3s") == 0)
+               client_set.ssl_mode = POP3C_CLIENT_SSL_MODE_IMMEDIATE;
+       else if (strcmp(set->pop3c_ssl, "starttls") == 0)
+               client_set.ssl_mode = POP3C_CLIENT_SSL_MODE_STARTTLS;
+       else
+               client_set.ssl_mode = POP3C_CLIENT_SSL_MODE_NONE;
+       client_set.ssl_crypto_device = set->ssl_crypto_device;
+       return pop3c_client_init(&client_set);
+}
+
+static void
+pop3c_storage_get_list_settings(const struct mail_namespace *ns,
+                               struct mailbox_list_settings *set)
+{
+       set->layout = MAILBOX_LIST_NAME_FS;
+       if (*set->root_dir != '\0' && set->index_dir == NULL) {
+               /* we don't really care about root_dir, but we
+                  just need to get index_dir autocreated.
+                  it happens when index_dir differs from root_dir. */
+               set->index_dir = set->root_dir;
+               set->root_dir = p_strconcat(ns->user->pool,
+                                           set->root_dir, "/.", NULL);
+       }
+}
+
+static struct mailbox *
+pop3c_mailbox_alloc(struct mail_storage *storage, struct mailbox_list *list,
+                   const char *vname, enum mailbox_flags flags)
+{
+       struct pop3c_mailbox *mbox;
+       pool_t pool;
+
+       pool = pool_alloconly_create("pop3c mailbox", 1024*3);
+       mbox = p_new(pool, struct pop3c_mailbox, 1);
+       mbox->box = pop3c_mailbox;
+       mbox->box.pool = pool;
+       mbox->box.storage = storage;
+       mbox->box.list = list;
+       mbox->box.mail_vfuncs = &pop3c_mail_vfuncs;
+       mbox->storage = (struct pop3c_storage *)storage;
+
+       index_storage_mailbox_alloc(&mbox->box, vname, flags,
+                                   POP3C_INDEX_PREFIX);
+       return &mbox->box;
+}
+
+static int
+pop3c_mailbox_exists(struct mailbox *box, bool auto_boxes ATTR_UNUSED,
+                    enum mailbox_existence *existence_r)
+{
+       if (box->inbox_any)
+               *existence_r = MAILBOX_EXISTENCE_SELECT;
+       else
+               *existence_r = MAILBOX_EXISTENCE_NONE;
+       return 0;
+}
+
+static void pop3c_login_callback(enum pop3c_command_state state,
+                                const char *reply, void *context)
+{
+       struct pop3c_mailbox *mbox = context;
+
+       switch (state) {
+       case POP3C_COMMAND_STATE_OK:
+               mbox->logged_in = TRUE;
+               break;
+       case POP3C_COMMAND_STATE_ERR:
+               if (strncmp(reply, "[IN-USE] ", 9) == 0) {
+                       mail_storage_set_error(mbox->box.storage,
+                                              MAIL_ERROR_INUSE, reply + 9);
+               } else {
+                       /* authentication failure probably */
+                       mail_storage_set_error(mbox->box.storage,
+                                              MAIL_ERROR_PARAMS, reply);
+               }
+               break;
+       case POP3C_COMMAND_STATE_DISCONNECTED:
+               mail_storage_set_critical(mbox->box.storage,
+                       "pop3c: Disconnected from remote server");
+               break;
+       }
+}
+
+static int pop3c_mailbox_open(struct mailbox *box)
+{
+       struct pop3c_mailbox *mbox = (struct pop3c_mailbox *)box;
+
+       if (!box->inbox_any) {
+               mail_storage_set_error(box->storage, MAIL_ERROR_NOTFOUND,
+                                      T_MAIL_ERR_MAILBOX_NOT_FOUND(box->name));
+               return -1;
+       }
+
+       if (index_storage_mailbox_open(box, FALSE) < 0)
+               return -1;
+
+       mbox->client = pop3c_client_create_from_set(box->storage->user,
+                                                   mbox->storage->set);
+       pop3c_client_login(mbox->client, pop3c_login_callback, mbox);
+       pop3c_client_run(mbox->client);
+       return mbox->logged_in ? 0 : -1;
+}
+
+static void pop3c_mailbox_close(struct mailbox *box)
+{
+       struct pop3c_mailbox *mbox = (struct pop3c_mailbox *)box;
+
+       if (mbox->uidl_pool != NULL)
+               pool_unref(&mbox->uidl_pool);
+       i_free(mbox->msg_sizes);
+       pop3c_client_deinit(&mbox->client);
+       index_storage_mailbox_close(box);
+}
+
+static int
+pop3c_mailbox_create(struct mailbox *box,
+                    const struct mailbox_update *update ATTR_UNUSED,
+                    bool directory ATTR_UNUSED)
+{
+       mail_storage_set_error(box->storage, MAIL_ERROR_NOTPOSSIBLE,
+                              "POP3 mailbox creation isn't supported");
+       return -1;
+}
+
+static int
+pop3c_mailbox_update(struct mailbox *box,
+                    const struct mailbox_update *update ATTR_UNUSED)
+{
+       mail_storage_set_error(box->storage, MAIL_ERROR_NOTPOSSIBLE,
+                              "POP3 mailbox update isn't supported");
+       return -1;
+}
+
+static void pop3c_notify_changes(struct mailbox *box ATTR_UNUSED)
+{
+}
+
+static bool pop3c_storage_is_inconsistent(struct mailbox *box)
+{
+       struct pop3c_mailbox *mbox = (struct pop3c_mailbox *)box;
+
+       return index_storage_is_inconsistent(box) ||
+               !pop3c_client_is_connected(mbox->client);
+}
+
+struct mail_storage pop3c_storage = {
+       .name = POP3C_STORAGE_NAME,
+       .class_flags = MAIL_STORAGE_CLASS_FLAG_MAILBOX_IS_FILE |
+               MAIL_STORAGE_CLASS_FLAG_OPEN_STREAMS,
+
+       .v = {
+               pop3c_get_setting_parser_info,
+               pop3c_storage_alloc,
+               pop3c_storage_create,
+               NULL,
+               NULL,
+               pop3c_storage_get_list_settings,
+               NULL,
+               pop3c_mailbox_alloc,
+               NULL
+       }
+};
+
+struct mailbox pop3c_mailbox = {
+       .v = {
+               index_storage_is_readonly,
+               index_storage_mailbox_enable,
+               pop3c_mailbox_exists,
+               pop3c_mailbox_open,
+               pop3c_mailbox_close,
+               index_storage_mailbox_free,
+               pop3c_mailbox_create,
+               pop3c_mailbox_update,
+               index_storage_mailbox_delete,
+               index_storage_mailbox_rename,
+               index_storage_get_status,
+               index_mailbox_get_metadata,
+               index_storage_set_subscribed,
+               index_storage_list_index_has_changed,
+               index_storage_list_index_update_sync,
+               pop3c_storage_sync_init,
+               index_mailbox_sync_next,
+               index_mailbox_sync_deinit,
+               NULL,
+               pop3c_notify_changes,
+               index_transaction_begin,
+               index_transaction_commit,
+               index_transaction_rollback,
+               NULL,
+               index_mail_alloc,
+               index_storage_search_init,
+               index_storage_search_deinit,
+               index_storage_search_next_nonblock,
+               index_storage_search_next_update_seq,
+               NULL,
+               NULL,
+               NULL,
+               NULL,
+               NULL,
+               mail_storage_copy,
+               NULL,
+               NULL,
+               NULL,
+               pop3c_storage_is_inconsistent
+       }
+};
diff --git a/src/lib-storage/index/pop3c/pop3c-storage.h b/src/lib-storage/index/pop3c/pop3c-storage.h
new file mode 100644 (file)
index 0000000..7f72090
--- /dev/null
@@ -0,0 +1,35 @@
+#ifndef POP3C_STORAGE_H
+#define POP3C_STORAGE_H
+
+#include "index-storage.h"
+
+#define POP3C_STORAGE_NAME "pop3c"
+#define POP3C_INDEX_PREFIX "dovecot.index"
+
+struct pop3c_storage {
+       struct mail_storage storage;
+       const struct pop3c_settings *set;
+};
+
+struct pop3c_mailbox {
+       struct mailbox box;
+       struct pop3c_storage *storage;
+
+       struct pop3c_client *client;
+
+       pool_t uidl_pool;
+       unsigned int msg_count;
+       /* LIST sizes */
+       uoff_t *msg_sizes;
+       /* UIDL strings */
+       const char *const *msg_uidls;
+       /* index UIDs for each message in this session.
+          the UID may not exist for the entire session */
+       uint32_t *msg_uids;
+
+       unsigned int logged_in:1;
+};
+
+extern struct mail_vfuncs pop3c_mail_vfuncs;
+
+#endif
diff --git a/src/lib-storage/index/pop3c/pop3c-sync.c b/src/lib-storage/index/pop3c/pop3c-sync.c
new file mode 100644 (file)
index 0000000..c5d7d1d
--- /dev/null
@@ -0,0 +1,298 @@
+/* Copyright (c) 2011 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "bsearch-insert-pos.h"
+#include "str.h"
+#include "strnum.h"
+#include "index-mail.h"
+#include "pop3c-client.h"
+#include "pop3c-storage.h"
+#include "pop3c-sync.h"
+
+int pop3c_sync_get_uidls(struct pop3c_mailbox *mbox)
+{
+       ARRAY_TYPE(const_string) uidls;
+       struct istream *input;
+       const char *error, *cline;
+       char *line, *p;
+       unsigned int seq, line_seq;
+
+       if (mbox->msg_uidls != NULL)
+               return 0;
+       if ((pop3c_client_get_capabilities(mbox->client) &
+            POP3C_CAPABILITY_UIDL) == 0) {
+               mail_storage_set_error(mbox->box.storage,
+                                      MAIL_ERROR_NOTPOSSIBLE,
+                                      "UIDLs not supported by server");
+               return -1;
+       }
+
+       if (pop3c_client_cmd_stream(mbox->client, "UIDL\r\n",
+                                   &input, &error) < 0) {
+               mail_storage_set_critical(mbox->box.storage,
+                                         "UIDL failed: %s", error);
+               return -1;
+       }
+
+       mbox->uidl_pool = pool_alloconly_create("POP3 UIDLs", 1024*32);
+       p_array_init(&uidls, mbox->uidl_pool, 64); seq = 0;
+       while ((line = i_stream_read_next_line(input)) != NULL) {
+               seq++;
+               p = strchr(line, ' ');
+               if (p == NULL) {
+                       mail_storage_set_critical(mbox->box.storage,
+                               "Invalid UIDL line: %s", line);
+                       break;
+               }
+               *p++ = '\0';
+               if (str_to_uint(line, &line_seq) < 0 || line_seq != seq) {
+                       mail_storage_set_critical(mbox->box.storage,
+                               "Unexpected UIDL seq: %s != %u", line, seq);
+                       break;
+               }
+
+               cline = p_strdup(mbox->uidl_pool, p);
+               array_append(&uidls, &cline, 1);
+       }
+       i_stream_destroy(&input);
+       if (line != NULL) {
+               pool_unref(&mbox->uidl_pool);
+               return -1;
+       }
+       if (seq == 0) {
+               /* make msg_uidls non-NULL */
+               (void)array_append_space(&uidls);
+       }
+       mbox->msg_uidls = array_idx(&uidls, 0);
+       mbox->msg_count = seq;
+       return 0;
+}
+
+int pop3c_sync_get_sizes(struct pop3c_mailbox *mbox)
+{
+       struct istream *input;
+       const char *error;
+       char *line, *p;
+       unsigned int seq, line_seq;
+
+       i_assert(mbox->msg_sizes == NULL);
+
+       if (mbox->msg_uidls == NULL) {
+               if (pop3c_sync_get_uidls(mbox) < 0)
+                       return -1;
+       }
+       if (mbox->msg_count == 0) {
+               mbox->msg_sizes = i_new(uoff_t, 1);
+               return 0;
+       }
+
+       if (pop3c_client_cmd_stream(mbox->client, "LIST\r\n",
+                                   &input, &error) < 0) {
+               mail_storage_set_critical(mbox->box.storage,
+                                         "LIST failed: %s", error);
+               return -1;
+       }
+
+       mbox->msg_sizes = i_new(uoff_t, mbox->msg_count); seq = 0;
+       while ((line = i_stream_read_next_line(input)) != NULL) {
+               if (++seq > mbox->msg_count) {
+                       mail_storage_set_critical(mbox->box.storage,
+                               "Too much data in LIST: %s", line);
+                       break;
+               }
+               p = strchr(line, ' ');
+               if (p == NULL) {
+                       mail_storage_set_critical(mbox->box.storage,
+                               "Invalid LIST line: %s", line);
+                       break;
+               }
+               *p++ = '\0';
+               if (str_to_uint(line, &line_seq) < 0 || line_seq != seq) {
+                       mail_storage_set_critical(mbox->box.storage,
+                               "Unexpected LIST seq: %s != %u", line, seq);
+                       break;
+               }
+               if (str_to_uoff(p, &mbox->msg_sizes[seq-1]) < 0) {
+                       mail_storage_set_critical(mbox->box.storage,
+                               "Invalid LIST size: %s", p);
+                       break;
+               }
+       }
+       i_stream_destroy(&input);
+       if (line != NULL) {
+               i_free_and_null(mbox->msg_sizes);
+               return -1;
+       }
+       return 0;
+}
+
+static void
+pop3c_sync_messages(struct pop3c_mailbox *mbox,
+                   struct mail_index_view *sync_view,
+                   struct mail_index_transaction *sync_trans,
+                   struct mail_cache_view *cache_view)
+{
+       struct index_mailbox_context *ibox =
+               INDEX_STORAGE_CONTEXT(&mbox->box);
+       const struct mail_index_header *hdr;
+       struct mail_cache_transaction_ctx *cache_trans;
+       string_t *str;
+       uint32_t seq, seq1, seq2, iseq, uid;
+       unsigned int cache_idx = ibox->cache_fields[MAIL_CACHE_POP3_UIDL].idx;
+
+       i_assert(mbox->msg_uids == NULL);
+
+       /* set our uidvalidity */
+       hdr = mail_index_get_header(sync_view);
+       if (hdr->uid_validity == 0) {
+               uint32_t uid_validity = ioloop_time;
+               mail_index_update_header(sync_trans,
+                       offsetof(struct mail_index_header, uid_validity),
+                       &uid_validity, sizeof(uid_validity), TRUE);
+       }
+
+       /* skip over existing messages with matching UIDLs */
+       mbox->msg_uids = i_new(uint32_t, mbox->msg_count + 1);
+       str = t_str_new(128);
+       for (seq = 1; seq <= hdr->messages_count && seq <= mbox->msg_count; seq++) {
+               str_truncate(str, 0);
+               if (mail_cache_lookup_field(cache_view, str, seq,
+                                           cache_idx) > 0 &&
+                   strcmp(str_c(str), mbox->msg_uidls[seq-1]) == 0) {
+                       /* UIDL matched */
+                       mail_index_lookup_uid(sync_view, seq,
+                                             &mbox->msg_uids[seq-1]);
+               } else {
+                       break;
+               }
+       }
+       seq2 = seq;
+       /* remove the rest of the messages from index */
+       for (; seq <= hdr->messages_count; seq++)
+               mail_index_expunge(sync_trans, seq);
+       /* add the rest of the messages in POP3 mailbox to index */
+       cache_trans = mail_cache_get_transaction(cache_view, sync_trans);
+       uid = hdr->next_uid;
+       for (seq = seq2; seq <= mbox->msg_count; seq++) {
+               mbox->msg_uids[seq-1] = uid;
+               mail_index_append(sync_trans, uid++, &iseq);
+               mail_cache_add(cache_trans, iseq, cache_idx,
+                              mbox->msg_uidls[seq-1],
+                              strlen(mbox->msg_uidls[seq-1])+1);
+       }
+
+       /* mark the newly seen messages as recent */
+       if (mail_index_lookup_seq_range(sync_view, hdr->first_recent_uid,
+                                       hdr->next_uid, &seq1, &seq2))
+               index_mailbox_set_recent_seq(&mbox->box, sync_view, seq1, seq2);
+}
+
+static int uint32_cmp(const void *p1, const void *p2)
+{
+       const uint32_t *u1 = p1, *u2 = p2;
+
+       return *u1 < *u2 ? -1 :
+               (*u1 > *u2 ? 1 : 0);
+}
+
+int pop3c_sync(struct pop3c_mailbox *mbox)
+{
+        struct mail_index_sync_ctx *index_sync_ctx;
+       struct mail_index_view *sync_view, *trans_view;
+       struct mail_index_transaction *sync_trans;
+       struct mail_index_sync_rec sync_rec;
+       struct mail_cache_view *cache_view = NULL;
+       enum mail_index_sync_flags sync_flags;
+       unsigned int idx;
+       string_t *str;
+       const char *reply;
+       int ret;
+       bool deletions = FALSE;
+
+       if (pop3c_sync_get_uidls(mbox) < 0)
+               return -1;
+
+       sync_flags = index_storage_get_sync_flags(&mbox->box) |
+               MAIL_INDEX_SYNC_FLAG_FLUSH_DIRTY;
+
+       ret = mail_index_sync_begin(mbox->box.index, &index_sync_ctx,
+                                   &sync_view, &sync_trans, sync_flags);
+       if (ret <= 0) {
+               if (ret < 0)
+                       mail_storage_set_index_error(&mbox->box);
+               return ret;
+       }
+
+       if (mbox->msg_uids == NULL) {
+               trans_view = mail_index_transaction_open_updated_view(sync_trans);
+               cache_view = mail_cache_view_open(mbox->box.cache, trans_view);
+               pop3c_sync_messages(mbox, sync_view, sync_trans, cache_view);
+       }
+
+       /* mark expunges messages as deleted in this pop3 session,
+          if those exist */
+       str = t_str_new(32);
+       while (mail_index_sync_next(index_sync_ctx, &sync_rec)) {
+               if (sync_rec.type != MAIL_INDEX_SYNC_TYPE_EXPUNGE)
+                       continue;
+
+               if (!bsearch_insert_pos(&sync_rec.uid1, mbox->msg_uids,
+                                       mbox->msg_count, sizeof(uint32_t),
+                                       uint32_cmp, &idx)) {
+                       /* no such messages in this session */
+                       continue;
+               }
+               for (; idx < mbox->msg_count; idx++) {
+                       i_assert(mbox->msg_uids[idx] >= sync_rec.uid1);
+                       if (mbox->msg_uids[idx] > sync_rec.uid2)
+                               break;
+
+                       str_truncate(str, 0);
+                       str_printfa(str, "DELE %u\r\n", idx+1);
+                       pop3c_client_cmd_line_async(mbox->client, str_c(str));
+                       deletions = TRUE;
+               }
+       }
+
+       if (mail_index_sync_commit(&index_sync_ctx) < 0) {
+               mail_storage_set_index_error(&mbox->box);
+               return -1;
+       }
+       if (cache_view != NULL) {
+               mail_cache_view_close(&cache_view);
+               mail_index_view_close(&trans_view);
+       }
+       if (deletions) {
+               if (pop3c_client_cmd_line(mbox->client, "QUIT\r\n",
+                                         &reply) < 0) {
+                       mail_storage_set_error(mbox->box.storage,
+                                              MAIL_ERROR_TEMP, reply);
+                       return -1;
+               }
+       }
+       return 0;
+}
+
+struct mailbox_sync_context *
+pop3c_storage_sync_init(struct mailbox *box, enum mailbox_sync_flags flags)
+{
+       struct pop3c_mailbox *mbox = (struct pop3c_mailbox *)box;
+       int ret = 0;
+
+       if (!box->opened) {
+               if (mailbox_open(box) < 0)
+                       ret = -1;
+       } else {
+               if ((flags & MAILBOX_SYNC_FLAG_FULL_READ) != 0 &&
+                   mbox->msg_uidls == NULL) {
+                       /* FIXME: reconnect */
+               }
+       }
+
+       if (ret == 0)
+               ret = pop3c_sync(mbox);
+
+       return index_mailbox_sync_init(box, flags, ret < 0);
+}
diff --git a/src/lib-storage/index/pop3c/pop3c-sync.h b/src/lib-storage/index/pop3c/pop3c-sync.h
new file mode 100644 (file)
index 0000000..bf3c802
--- /dev/null
@@ -0,0 +1,14 @@
+#ifndef POP3C_SYNC_H
+#define POP3C_SYNC_H
+
+struct mailbox;
+struct pop3c_mailbox;
+
+struct mailbox_sync_context *
+pop3c_storage_sync_init(struct mailbox *box, enum mailbox_sync_flags flags);
+int pop3c_sync(struct pop3c_mailbox *mbox);
+
+int pop3c_sync_get_sizes(struct pop3c_mailbox *mbox);
+int pop3c_sync_get_uidls(struct pop3c_mailbox *mbox);
+
+#endif