changeset 15066:d419aac7ab31

Added support for IMAP URLAUTH and URLAUTH=BINARY extensions Extends imap service with URLAUTH and URLAUTH=BINARY support: - Adds new commands URLFETCH, GENURLAUTH and RESETKEY. Creates imap-urlauth service in src/imap-urlauth. Functionality common to both the imap and imap-urlauth services is located in src/lib-imap-urlauth. TODO: - use mailbox GUIDs instead of names - doveadm command to delete stale urlauth entries? - add delay when attempting to access nonexistent user - create urlauth-worker queue, similar to how indexer-worker works (could we share code?..)
author Stephan Bosch <stephan@rename-it.nl>
date Sat, 15 Sep 2012 23:57:08 +0300
parents 1b4ab06fa0e2
children 9bff478f81fd
files .hgignore README configure.in src/Makefile.am src/imap-urlauth/Makefile.am src/imap-urlauth/imap-urlauth-client.c src/imap-urlauth/imap-urlauth-client.h src/imap-urlauth/imap-urlauth-common.h src/imap-urlauth/imap-urlauth-login-settings.c src/imap-urlauth/imap-urlauth-login-settings.h src/imap-urlauth/imap-urlauth-login.c src/imap-urlauth/imap-urlauth-settings.c src/imap-urlauth/imap-urlauth-settings.h src/imap-urlauth/imap-urlauth-worker-settings.c src/imap-urlauth/imap-urlauth-worker-settings.h src/imap-urlauth/imap-urlauth-worker.c src/imap-urlauth/imap-urlauth.c src/imap/Makefile.am src/imap/cmd-genurlauth.c src/imap/cmd-resetkey.c src/imap/cmd-urlfetch.c src/imap/imap-client.c src/imap/imap-client.h src/imap/imap-commands.c src/imap/imap-commands.h src/imap/imap-settings.c src/imap/imap-settings.h src/imap/main.c src/lib-imap-urlauth/Makefile.am src/lib-imap-urlauth/imap-urlauth-backend.c src/lib-imap-urlauth/imap-urlauth-backend.h src/lib-imap-urlauth/imap-urlauth-connection.c src/lib-imap-urlauth/imap-urlauth-connection.h src/lib-imap-urlauth/imap-urlauth-fetch.c src/lib-imap-urlauth/imap-urlauth-fetch.h src/lib-imap-urlauth/imap-urlauth-private.h src/lib-imap-urlauth/imap-urlauth.c src/lib-imap-urlauth/imap-urlauth.h
diffstat 38 files changed, 5253 insertions(+), 5 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Sat Sep 15 21:00:54 2012 +0300
+++ b/.hgignore	Sat Sep 15 23:57:08 2012 +0300
@@ -67,6 +67,9 @@
 src/doveadm/doveadm
 src/doveadm/doveadm-server
 src/imap-login/imap-login
+src/imap-urlauth/imap-urlauth
+src/imap-urlauth/imap-urlauth-login
+src/imap-urlauth/imap-urlauth-worker
 src/imap/imap
 src/indexer/indexer
 src/indexer/indexer-worker
--- a/README	Sat Sep 15 21:00:54 2012 +0300
+++ b/README	Sat Sep 15 23:57:08 2012 +0300
@@ -40,6 +40,7 @@
  3691       - IMAP4 UNSELECT command
  4314       - IMAP4 Access Control List (ACL) Extension
  4315       - IMAP UIDPLUS extension
+ 4467       - IMAP URLAUTH Extension
  4469       - IMAP CATENATE Extension
  4551       - IMAP Extension for Conditional STORE Operation
               or Quick Flag Changes Resynchronization
@@ -55,6 +56,7 @@
  5256       - IMAP SORT and THREAD Extensions
  5258       - IMAP4 - LIST Command Extensions 
  5267       - Contexts for IMAP4
+ 5524       - Extended URLFETCH for Binary and Converted Parts
  5530       - IMAP Response Codes
  5819       - IMAP4 Extension for Returning STATUS Information in Extended LIST
  5957       - Display-Based Address Sorting for the IMAP4 SORT Extension
--- a/configure.in	Sat Sep 15 21:00:54 2012 +0300
+++ b/configure.in	Sat Sep 15 23:57:08 2012 +0300
@@ -2757,6 +2757,7 @@
 src/lib-imap/Makefile
 src/lib-imap-storage/Makefile
 src/lib-imap-client/Makefile
+src/lib-imap-urlauth/Makefile
 src/lib-index/Makefile
 src/lib-lda/Makefile
 src/lib-mail/Makefile
@@ -2796,6 +2797,7 @@
 src/ipc/Makefile
 src/imap/Makefile
 src/imap-login/Makefile
+src/imap-urlauth/Makefile
 src/login-common/Makefile
 src/master/Makefile
 src/pop3/Makefile
--- a/src/Makefile.am	Sat Sep 15 21:00:54 2012 +0300
+++ b/src/Makefile.am	Sat Sep 15 23:57:08 2012 +0300
@@ -16,6 +16,7 @@
 SUBDIRS = \
 	$(LIBDOVECOT_SUBDIRS) \
 	lib-imap-client \
+	lib-imap-urlauth \
 	lib-compression \
 	lib-dovecot \
 	lib-index \
@@ -34,6 +35,7 @@
 	login-common \
 	imap-login \
 	imap \
+	imap-urlauth \
 	pop3-login \
 	pop3 \
 	lda \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap-urlauth/Makefile.am	Sat Sep 15 23:57:08 2012 +0300
@@ -0,0 +1,82 @@
+pkglibexecdir = $(libexecdir)/dovecot
+
+# Refer to comment in imap-urlauth.c for info on what these binaries are for.
+pkglibexec_PROGRAMS = imap-urlauth-login imap-urlauth imap-urlauth-worker
+
+# imap-urlauth-login
+
+imap_urlauth_login_CPPFLAGS = \
+	-I$(top_srcdir)/src/lib \
+	-I$(top_srcdir)/src/lib-settings \
+	-I$(top_srcdir)/src/lib-auth \
+	-I$(top_srcdir)/src/lib-master \
+	-I$(top_srcdir)/src/login-common
+
+imap_urlauth_login_LDADD = \
+	$(LIBDOVECOT_LOGIN) \
+	$(LIBDOVECOT) \
+	$(SSL_LIBS)
+imap_urlauth_login_DEPENDENCIES = \
+	$(LIBDOVECOT_LOGIN) \
+	$(LIBDOVECOT_DEPS)
+
+imap_urlauth_login_SOURCES = \
+	imap-urlauth-login.c \
+	imap-urlauth-login-settings.c
+
+# imap-urlauth
+
+imap_urlauth_CPPFLAGS = \
+	-I$(top_srcdir)/src/lib \
+	-I$(top_srcdir)/src/lib-settings \
+	-I$(top_srcdir)/src/lib-auth \
+	-I$(top_srcdir)/src/lib-master \
+	-I$(top_srcdir)/src/lib-dict \
+	-DPKG_RUNDIR=\""$(rundir)"\"
+
+imap_urlauth_LDFLAGS = -export-dynamic
+
+imap_urlauth_LDADD = $(LIBDOVECOT) $(MODULE_LIBS)
+imap_urlauth_DEPENDENCIES = $(LIBDOVECOT_DEPS)
+
+imap_urlauth_SOURCES = \
+	imap-urlauth.c \
+	imap-urlauth-client.c \
+	imap-urlauth-settings.c
+
+# imap-urlauth-worker
+
+imap_urlauth_worker_CPPFLAGS = \
+	-I$(top_srcdir)/src/lib \
+	-I$(top_srcdir)/src/lib-auth \
+	-I$(top_srcdir)/src/lib-settings \
+	-I$(top_srcdir)/src/lib-master \
+	-I$(top_srcdir)/src/lib-dict \
+	-I$(top_srcdir)/src/imap \
+	-I$(top_srcdir)/src/lib-imap \
+	-I$(top_srcdir)/src/lib-imap-storage \
+	-I$(top_srcdir)/src/lib-imap-urlauth \
+	-I$(top_srcdir)/src/lib-mail \
+	-I$(top_srcdir)/src/lib-storage \
+	-I$(top_srcdir)/src/login-common
+
+imap_urlauth_worker_LDFLAGS = -export-dynamic
+
+urlauth_libs = \
+	$(LIBDOVECOT_STORAGE) \
+	$(top_builddir)/src/lib-imap-urlauth/libimap-urlauth.la
+
+imap_urlauth_worker_LDADD = $(urlauth_libs) $(LIBDOVECOT) $(MODULE_LIBS)
+imap_urlauth_worker_DEPENDENCIES = $(urlauth_libs) $(LIBDOVECOT_DEPS)
+
+imap_urlauth_worker_SOURCES = \
+	imap-urlauth-worker.c \
+	imap-urlauth-worker-settings.c
+
+noinst_HEADERS = \
+	imap-urlauth-client.h \
+	imap-urlauth-common.h \
+	imap-urlauth-settings.h \
+	imap-urlauth-login-settings.h \
+	imap-urlauth-worker-settings.h
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap-urlauth/imap-urlauth-client.c	Sat Sep 15 23:57:08 2012 +0300
@@ -0,0 +1,379 @@
+/* Copyright (c) 2012 Dovecot authors, see the included COPYING file */
+
+#include "imap-urlauth-common.h"
+#include "array.h"
+#include "ioloop.h"
+#include "network.h"
+#include "fdpass.h"
+#include "istream.h"
+#include "ostream.h"
+#include "str.h"
+#include "strescape.h"
+#include "eacces-error.h"
+#include "llist.h"
+#include "hostpid.h"
+#include "execv-const.h"
+#include "env-util.h"
+#include "var-expand.h"
+#include "restrict-access.h"
+#include "master-service.h"
+#include "master-interface.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/wait.h>
+
+#define IMAP_URLAUTH_PROTOCOL_MAJOR_VERSION 1
+#define IMAP_URLAUTH_PROTOCOL_MINOR_VERSION 0
+
+#define IMAP_URLAUTH_WORKER_SOCKET "imap-urlauth-worker"
+
+/* max. length of input lines (URLs) */
+#define MAX_INBUF_SIZE 2048
+
+/* Disconnect client after idling this many milliseconds */
+#define CLIENT_IDLE_TIMEOUT_MSECS (10*60*1000)
+
+#define USER_EXECUTABLE "imap-urlauth-worker"
+
+#define IS_STANDALONE() \
+        (getenv(MASTER_IS_PARENT_ENV) == NULL)
+
+struct client *imap_urlauth_clients;
+unsigned int imap_urlauth_client_count;
+
+static int client_worker_connect(struct client *client);
+static void client_worker_disconnect(struct client *client);
+static void client_worker_input(struct client *client);
+
+int client_create(const char *username, int fd_in, int fd_out,
+		  const struct imap_urlauth_settings *set,
+		  struct client **client_r)
+{
+	struct client *client;
+	const char *app;
+
+	/* always use nonblocking I/O */
+	net_set_nonblock(fd_in, TRUE);
+	net_set_nonblock(fd_out, TRUE);
+
+	client = i_new(struct client, 1);
+	client->fd_in = fd_in;
+	client->fd_out = fd_out;
+	client->fd_ctrl = -1;
+	client->set = set;
+
+	if (client_worker_connect(client) < 0) {
+		i_free(client);
+		return -1;
+	}
+
+	/* determine user's special privileges */
+	i_array_init(&client->access_apps, 4);
+	if (username != NULL) {
+		if (set->imap_urlauth_submit_user != NULL &&
+		    strcmp(set->imap_urlauth_submit_user, username) == 0) {
+			if (set->mail_debug)
+				i_debug("User %s has URLAUTH submit access", username);
+			app = "submit+";
+			array_append(&client->access_apps, &app, 1);
+		}
+		if (set->imap_urlauth_stream_user != NULL &&
+		    strcmp(set->imap_urlauth_stream_user, username) == 0) {
+			if (set->mail_debug)
+				i_debug("User %s has URLAUTH stream access", username);
+			app = "stream";
+			array_append(&client->access_apps, &app, 1);
+		}
+	}
+
+	if (username != NULL)
+		client->username = i_strdup(username);
+
+	client->output = o_stream_create_fd(fd_out, (size_t)-1, FALSE);
+
+	imap_urlauth_client_count++;
+	DLLIST_PREPEND(&imap_urlauth_clients, client);
+
+	imap_urlauth_refresh_proctitle();
+	*client_r = client;
+	return 0;
+}
+
+void client_send_line(struct client *client, const char *fmt, ...)
+{
+	va_list va;
+	ssize_t ret;
+
+	if (client->output->closed)
+		return;
+
+	va_start(va, fmt);
+
+	T_BEGIN {
+		string_t *str;
+
+		str = t_str_new(256);
+		str_vprintfa(str, fmt, va);
+		str_append(str, "\n");
+
+		ret = o_stream_send(client->output,
+				    str_data(str), str_len(str));
+		i_assert(ret < 0 || (size_t)ret == str_len(str));
+	} T_END;
+
+	va_end(va);
+}
+
+static int client_worker_connect(struct client *client)
+{
+	static const char handshake[] = "VERSION\timap-urlauth-worker\t1\t0\n";
+	const char *socket_path;
+	ssize_t ret;
+	unsigned char data;
+
+	socket_path = t_strconcat(client->set->base_dir,
+				  "/"IMAP_URLAUTH_WORKER_SOCKET, NULL);
+
+	if (client->set->mail_debug)
+		i_debug("Connecting to worker socket %s", socket_path);
+
+	client->fd_ctrl = net_connect_unix_with_retries(socket_path, 1000);
+	if (client->fd_ctrl < 0) {
+		if (errno == EACCES) {
+			i_error("imap-urlauth-client: %s",
+				eacces_error_get("net_connect_unix",
+						 socket_path));
+		} else {
+			i_error("imap-urlauth-client: net_connect_unix(%s) failed: %m",
+				socket_path);
+		}
+		return -1;
+	}
+
+	/* transfer one or two fds */
+	data = (client->fd_in == client->fd_out ? '0' : '1');
+	ret = fd_send(client->fd_ctrl, client->fd_in, &data, sizeof(data));
+	if (ret > 0 && client->fd_in != client->fd_out) {
+		data = '0';
+		ret = fd_send(client->fd_ctrl, client->fd_out,
+			      &data, sizeof(data));
+	}
+
+	if (ret <= 0) {
+		if (ret < 0) {
+			i_error("fd_send(%s, %d) failed: %m",
+				socket_path, client->fd_ctrl);
+		} else {
+			i_error("fd_send(%s, %d) failed to send byte",
+				socket_path, client->fd_ctrl);
+		}
+		client_worker_disconnect(client);
+		return -1;
+	}
+
+	client->ctrl_output =
+		o_stream_create_fd(client->fd_ctrl, (size_t)-1, FALSE);
+
+	/* send protocol version handshake */
+	if (o_stream_send_str(client->ctrl_output, handshake) < 0) {
+		i_error("Error sending handshake to imap-urlauth worker: %m");
+		client_worker_disconnect(client);
+		return -1;
+	}
+
+	client->ctrl_input =
+		i_stream_create_fd(client->fd_ctrl, MAX_INBUF_SIZE, FALSE);
+	client->ctrl_io =
+		io_add(client->fd_ctrl, IO_READ, client_worker_input, client);  
+	return 0;
+}
+
+void client_worker_disconnect(struct client *client)
+{
+	client->worker_state = IMAP_URLAUTH_WORKER_STATE_INACTIVE;
+
+	if (client->ctrl_io != NULL)
+		io_remove(&client->ctrl_io);
+	if (client->ctrl_output != NULL)
+		o_stream_destroy(&client->ctrl_output);
+	if (client->ctrl_input != NULL)
+		i_stream_destroy(&client->ctrl_input);
+	if (client->fd_ctrl >= 0) {
+		net_disconnect(client->fd_ctrl);
+		client->fd_ctrl = -1;
+	}
+}
+
+static int
+client_worker_input_line(struct client *client, const char *response)
+{
+	const char *const *apps;
+	unsigned int count, i;
+	bool restart;
+	string_t *str;
+	int ret;
+
+	switch (client->worker_state) {
+	case IMAP_URLAUTH_WORKER_STATE_INACTIVE:
+		if (strcasecmp(response, "OK") != 0) {
+			client_disconnect(client, "Worker handshake failed");
+			return -1;
+		}
+		client->worker_state = IMAP_URLAUTH_WORKER_STATE_CONNECTED;
+
+		str = t_str_new(256);
+		str_append(str, "ACCESS\t");
+		if (client->username != NULL)
+			str_tabescape_write(str, client->username);
+		if (client->set->mail_debug)
+			str_append(str, "\tdebug");
+		if (array_count(&client->access_apps) > 0) {
+			str_append(str, "\tapps=");
+			apps = array_get(&client->access_apps, &count);
+			str_append(str, apps[0]);
+			for (i = 1; i < count; i++) {
+				str_append_c(str, ',');
+				str_tabescape_write(str, apps[i]);
+			}
+		}
+		str_append(str, "\n");
+
+		ret = o_stream_send(client->ctrl_output,
+				    str_data(str), str_len(str));
+		i_assert(ret < 0 || (size_t)ret == str_len(str));
+		if (ret < 0) {
+			client_disconnect(client,
+				"Failed to send ACCESS control command to worker");
+			return -1;
+		}
+		break;
+
+	case IMAP_URLAUTH_WORKER_STATE_CONNECTED:
+		if (strcasecmp(response, "OK") != 0) {
+			client_disconnect(client,
+				"Failed to negotiate access parameters");
+			return -1;
+		}
+		client->worker_state = IMAP_URLAUTH_WORKER_STATE_ACTIVE;
+		break;
+
+	case IMAP_URLAUTH_WORKER_STATE_ACTIVE:
+		restart = TRUE;
+		if (strcasecmp(response, "DISCONNECTED") == 0) {
+			/* worker detected client disconnect */
+			restart = FALSE;
+		} else if (strcasecmp(response, "FINISHED") != 0) {
+			/* unknown response */
+			client_disconnect(client,
+				"Worker finished with unknown response");
+			return -1;
+		}
+
+		if (client->set->mail_debug)
+			i_debug("Worker finished successfully");
+
+		if (restart) {
+			/* connect to new worker for accessing different user */
+			client_worker_disconnect(client);
+			if (client_worker_connect(client) < 0) {
+				client_disconnect(client,
+					"Failed to connect to new worker");
+				return -1;
+			}
+
+			/* indicate success of "END" command */
+			client_send_line(client, "OK");
+		} else {
+			client_disconnect(client, "Client disconnected");
+		}
+		return -1;
+ 	default:
+		i_unreached();
+	}
+	return 0;
+}
+
+void client_worker_input(struct client *client)
+{
+	struct istream *input = client->ctrl_input;
+	const char *line;
+
+	if (input->closed) {
+		/* disconnected */
+		client_disconnect(client, "Worker disconnected unexpectedly");
+		return;
+	}
+
+	switch (i_stream_read(input)) {
+	case -1:
+		/* disconnected */
+		client_disconnect(client, "Worker disconnected unexpectedly");
+		return;
+	case -2:
+		/* input buffer full */
+		client_disconnect(client, "Worker sent too large input");
+		return;
+	}
+
+	while ((line = i_stream_next_line(input)) != NULL) {
+		if (client_worker_input_line(client, line) < 0)
+			return;
+	}
+}
+
+void client_destroy(struct client *client, const char *reason)
+{
+	i_set_failure_prefix(t_strdup_printf("%s: ",
+		master_service_get_name(master_service)));
+
+	if (!client->disconnected) {
+		if (reason == NULL)
+			reason = "Connection closed";
+		i_info("Disconnected: %s", reason);
+	}
+
+	imap_urlauth_client_count--;
+	DLLIST_REMOVE(&imap_urlauth_clients, client);
+
+	if (client->to_idle != NULL)
+		timeout_remove(&client->to_idle);
+
+	client_worker_disconnect(client);
+	
+	o_stream_destroy(&client->output);
+
+	net_disconnect(client->fd_in);
+	if (client->fd_in != client->fd_out)
+		net_disconnect(client->fd_out);
+
+	if (client->username != NULL)
+		i_free(client->username);
+	array_free(&client->access_apps);
+	i_free(client);
+
+	master_service_client_connection_destroyed(master_service);
+	imap_urlauth_refresh_proctitle();
+}
+
+static void client_destroy_timeout(struct client *client)
+{
+	client_destroy(client, NULL);
+}
+
+void client_disconnect(struct client *client, const char *reason)
+{
+	if (client->disconnected)
+		return;
+
+	client->disconnected = TRUE;
+	i_info("Disconnected: %s", reason);
+
+	client->to_idle = timeout_add(0, client_destroy_timeout, client);
+}
+
+void clients_destroy_all(void)
+{
+	while (imap_urlauth_clients != NULL)
+		client_destroy(imap_urlauth_clients, "Server shutting down.");
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap-urlauth/imap-urlauth-client.h	Sat Sep 15 23:57:08 2012 +0300
@@ -0,0 +1,48 @@
+#ifndef IMAP_URLAUTH_CLIENT_H
+#define IMAP_URLAUTH_CLIENT_H
+
+struct client;
+struct mail_storage;
+
+enum imap_urlauth_worker_state {
+	IMAP_URLAUTH_WORKER_STATE_INACTIVE = 0,
+	IMAP_URLAUTH_WORKER_STATE_CONNECTED,
+	IMAP_URLAUTH_WORKER_STATE_ACTIVE,
+};
+
+struct client {
+	struct client *prev, *next;
+
+	int fd_in, fd_out, fd_ctrl;
+	struct io *ctrl_io;
+	struct ostream *output, *ctrl_output;
+	struct istream *ctrl_input;
+	struct timeout *to_idle;
+
+	char *username;
+	ARRAY_TYPE(const_string) access_apps;
+
+	/* settings: */
+	const struct imap_urlauth_settings *set;
+
+	enum imap_urlauth_worker_state worker_state;
+
+	unsigned int disconnected:1;
+};
+
+extern struct client *imap_urlauth_clients;
+extern unsigned int imap_urlauth_client_count;
+
+int client_create(const char *username, int fd_in, int fd_out,
+		  const struct imap_urlauth_settings *set,
+		  struct client **client_r);
+void client_destroy(struct client *client, const char *reason);
+
+void client_send_line(struct client *client, const char *fmt, ...)
+	ATTR_FORMAT(2, 3);
+
+void client_disconnect(struct client *client, const char *reason);
+
+void clients_destroy_all(void);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap-urlauth/imap-urlauth-common.h	Sat Sep 15 23:57:08 2012 +0300
@@ -0,0 +1,13 @@
+#ifndef IMAP_URLAUTH_COMMON_H
+#define IMAP_URLAUTH_COMMON_H
+
+#include "lib.h"
+#include "imap-urlauth-client.h"
+#include "imap-urlauth-settings.h"
+
+extern bool verbose_proctitle;
+extern struct mail_storage_service_ctx *storage_service;
+
+void imap_urlauth_refresh_proctitle(void);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap-urlauth/imap-urlauth-login-settings.c	Sat Sep 15 23:57:08 2012 +0300
@@ -0,0 +1,75 @@
+/* Copyright (c) 2012 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "buffer.h"
+#include "settings-parser.h"
+#include "service-settings.h"
+#include "login-settings.h"
+#include "imap-urlauth-login-settings.h"
+
+#include <stddef.h>
+
+/* <settings checks> */
+static struct file_listener_settings
+imap_urlauth_login_unix_listeners_array[] = {
+	{ "imap-urlauth", 0666, "", "" }
+};
+static struct file_listener_settings *imap_urlauth_login_unix_listeners[] = {
+	&imap_urlauth_login_unix_listeners_array[0]
+};
+static buffer_t imap_urlauth_login_unix_listeners_buf = {
+	imap_urlauth_login_unix_listeners,
+		sizeof(imap_urlauth_login_unix_listeners), { 0, }
+};
+/* </settings checks> */
+
+struct service_settings imap_urlauth_login_service_settings = {
+	.name = "imap-urlauth-login",
+	.protocol = "imap",
+	.type = "login",
+	.executable = "imap-urlauth-login",
+	.user = "$default_login_user",
+	.group = "",
+	.privileged_group = "",
+	.extra_groups = "",
+	.chroot = "token-login",
+
+	.drop_priv_before_exec = FALSE,
+
+	.process_min_avail = 0,
+	.process_limit = 0,
+	.client_limit = 0,
+	.service_count = 1,
+	.idle_kill = 0,
+	.vsz_limit = (uoff_t)-1,
+
+	.unix_listeners = { { &imap_urlauth_login_unix_listeners_buf,
+			      sizeof(imap_urlauth_login_unix_listeners[0]) } },
+	.fifo_listeners = ARRAY_INIT,
+	.inet_listeners = ARRAY_INIT
+};
+
+static const struct setting_define imap_urlauth_login_setting_defines[] = {
+	SETTING_DEFINE_LIST_END
+};
+
+static const struct setting_parser_info *imap_urlauth_login_setting_dependencies[] = {
+	&login_setting_parser_info,
+	NULL
+};
+
+const struct setting_parser_info imap_urlauth_login_setting_parser_info = {
+	.module_name = "imap-urlauth-login",
+	.defines = imap_urlauth_login_setting_defines,
+
+	.type_offset = (size_t)-1,
+	.parent_offset = (size_t)-1,
+
+	.dependencies = imap_urlauth_login_setting_dependencies
+};
+
+const struct setting_parser_info *imap_urlauth_login_setting_roots[] = {
+	&login_setting_parser_info,
+	&imap_urlauth_login_setting_parser_info,
+	NULL
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap-urlauth/imap-urlauth-login-settings.h	Sat Sep 15 23:57:08 2012 +0300
@@ -0,0 +1,6 @@
+#ifndef IMAP_URLAUTH_LOGIN_SETTINGS_H
+#define IMAP_URLAUTH_LOGIN_SETTINGS_H
+
+extern const struct setting_parser_info *imap_urlauth_login_setting_roots[];
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap-urlauth/imap-urlauth-login.c	Sat Sep 15 23:57:08 2012 +0300
@@ -0,0 +1,192 @@
+/* Copyright (c) 2012 Dovecot authors, see the included COPYING file */
+
+#include "login-common.h"
+#include "str.h"
+#include "strescape.h"
+#include "base64.h"
+#include "network.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "ostream.h"
+#include "master-service.h"
+#include "auth-client.h"
+#include "client-common.h"
+#include "imap-urlauth-login-settings.h"
+
+#define IMAP_URLAUTH_PROTOCOL_MAJOR_VERSION 1
+#define IMAP_URLAUTH_PROTOCOL_MINOR_VERSION 0
+
+struct imap_urlauth_client {
+	struct client common;
+
+	const struct imap_urlauth_login_settings *set;
+
+	unsigned int version_received:1;
+};
+
+static void
+imap_urlauth_client_auth_result(struct client *client,
+				enum client_auth_result result,
+				const struct client_auth_reply *reply ATTR_UNUSED,
+				const char *text ATTR_UNUSED)
+{
+	if (result != CLIENT_AUTH_RESULT_SUCCESS) {
+		/* failed or otherwise invalid status */
+		client_send_raw(client, "FAILED\n");
+		client_destroy(client, "Disconnected: Authentication failed");
+	} else {
+		/* authentication succeeded */
+	}
+}
+
+static void imap_urlauth_client_handle_input(struct client *client)
+{
+#define AUTH_ARG_COUNT 5
+	struct imap_urlauth_client *uauth_client =
+		(struct imap_urlauth_client *)client;
+	struct net_unix_cred cred;
+	const char *line;
+	const char *const *args;
+	pid_t pid;
+
+	if (!uauth_client->version_received) {
+		if ((line = i_stream_next_line(client->input)) == NULL)
+			return;
+
+		if (!version_string_verify(line, "imap-urlauth",
+				IMAP_URLAUTH_PROTOCOL_MAJOR_VERSION)) {
+			i_error("IMAP URLAUTH client not compatible with this server "
+				"(mixed old and new binaries?) %s", line);
+			client_destroy(client, "Disconnected: Version mismatch");
+			return;
+		}
+		uauth_client->version_received = TRUE;
+	}
+
+	if ((line = i_stream_next_line(client->input)) == NULL)
+		return;
+
+	/* read authentication info from input;
+	   "AUTH"\t<session-pid>\t<auth-username>\t<session_id>\t<token> */
+	args = t_strsplit_tabescaped(line);
+	if (str_array_length(args) < AUTH_ARG_COUNT ||
+	    strcmp(args[0], "AUTH") != 0 || str_to_pid(args[1], &pid) < 0) {
+		i_error("IMAP URLAUTH client sent unexpected AUTH input: %s", line);
+		client_destroy(client, "Disconnected: Unexpected input");
+		return;
+	}
+
+	/* verify session pid if possible */
+	if (net_getunixcred(client->fd, &cred) == 0 &&
+	    cred.pid != (pid_t)-1 && pid != cred.pid) {
+		i_error("IMAP URLAUTH client sent invalid session pid %ld in AUTH request: "
+			"it did not match peer credentials (pid=%ld, uid=%ld)",
+			(long)pid, (long)cred.pid, (long)cred.uid);
+		client_destroy(client, "Disconnected: Invalid AUTH request");
+		return;
+	}
+
+	T_BEGIN {
+		string_t *auth_data = t_str_new(128);
+		string_t *init_resp;
+		unsigned int i;
+
+		str_append(auth_data, "imap");
+		for (i = 1; i < AUTH_ARG_COUNT; i++) {
+			str_append_c(auth_data, '\0');
+			str_append(auth_data, args[i]);
+		}
+		init_resp = t_str_new(256);
+		base64_encode(str_data(auth_data),
+			      str_len(auth_data), init_resp);
+
+		(void)client_auth_begin(client, "DOVECOT-TOKEN",
+					str_c(init_resp));
+	} T_END;
+}
+
+static void imap_urlauth_client_input(struct client *client)
+{
+	if (!client_read(client))
+		return;
+
+	client_ref(client);
+	o_stream_cork(client->output);
+	if (!auth_client_is_connected(auth_client)) {
+		/* we're not currently connected to auth process -
+		   don't allow any commands */
+		if (client->to_auth_waiting != NULL)
+			timeout_remove(&client->to_auth_waiting);
+		client->input_blocked = TRUE;
+	} else {
+		imap_urlauth_client_handle_input(client);
+	}
+	o_stream_uncork(client->output);
+	client_unref(&client);
+}
+
+static struct client *imap_urlauth_client_alloc(pool_t pool)
+{
+	struct imap_urlauth_client *uauth_client;
+
+	uauth_client = p_new(pool, struct imap_urlauth_client, 1);
+	return &uauth_client->common;
+}
+
+static void imap_urlauth_client_create
+(struct client *client, void **other_sets)
+{
+	struct imap_urlauth_client *uauth_client =
+		(struct imap_urlauth_client *)client;
+
+	uauth_client->set = other_sets[0];
+	client->io = io_add(client->fd, IO_READ, client_input, client);
+}
+
+static void imap_urlauth_login_preinit(void)
+{
+	login_set_roots = imap_urlauth_login_setting_roots;
+}
+
+static void imap_urlauth_login_init(void)
+{
+}
+
+static void imap_urlauth_login_deinit(void)
+{
+	clients_destroy_all();
+}
+
+static struct client_vfuncs imap_urlauth_vfuncs = {
+	imap_urlauth_client_alloc,
+	imap_urlauth_client_create,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	imap_urlauth_client_input,
+	NULL,
+	NULL,
+	imap_urlauth_client_auth_result,
+	NULL,
+	NULL,
+	NULL
+};
+
+static const struct login_binary imap_urlauth_login_binary = {
+	.protocol = "imap-urlauth",
+	.process_name = "imap-urlauth-login",
+	.default_login_socket = LOGIN_TOKEN_DEFAULT_SOCKET,
+
+	.client_vfuncs = &imap_urlauth_vfuncs,
+	.preinit = imap_urlauth_login_preinit,
+	.init = imap_urlauth_login_init,
+	.deinit = imap_urlauth_login_deinit,
+};
+
+int main(int argc, char *argv[])
+{
+	return login_binary_run(&imap_urlauth_login_binary, argc, argv);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap-urlauth/imap-urlauth-settings.c	Sat Sep 15 23:57:08 2012 +0300
@@ -0,0 +1,95 @@
+/* Copyright (c) 2012 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "buffer.h"
+#include "settings-parser.h"
+#include "service-settings.h"
+#include "imap-urlauth-settings.h"
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+/* <settings checks> */
+static struct file_listener_settings imap_urlauth_unix_listeners_array[] = {
+	{ "token-login/imap-urlauth", 0666, "", "" }
+};
+static struct file_listener_settings *imap_urlauth_unix_listeners[] = {
+	&imap_urlauth_unix_listeners_array[0]
+};
+static buffer_t imap_urlauth_unix_listeners_buf = {
+	imap_urlauth_unix_listeners, sizeof(imap_urlauth_unix_listeners), { 0, }
+};
+/* </settings checks> */
+
+struct service_settings imap_urlauth_service_settings = {
+	.name = "imap-urlauth",
+	.protocol = "imap",
+	.type = "",
+	.executable = "imap-urlauth",
+	.user = "$default_internal_user",
+	.group = "",
+	.privileged_group = "",
+	.extra_groups = "",
+	.chroot = "",
+
+	.drop_priv_before_exec = FALSE,
+
+	.process_min_avail = 0,
+	.process_limit = 1024,
+	.client_limit = 1,
+	.service_count = 1,
+	.idle_kill = 0,
+	.vsz_limit = (uoff_t)-1,
+
+	.unix_listeners = { { &imap_urlauth_unix_listeners_buf,
+			      sizeof(imap_urlauth_unix_listeners[0]) } },
+	.fifo_listeners = ARRAY_INIT,
+	.inet_listeners = ARRAY_INIT
+};
+
+#undef DEF
+#define DEF(type, name) \
+	{ type, #name, offsetof(struct imap_urlauth_settings, name), NULL }
+
+static const struct setting_define imap_urlauth_setting_defines[] = {
+	DEF(SET_STR, base_dir),
+
+	DEF(SET_BOOL, mail_debug),
+
+	DEF(SET_BOOL, verbose_proctitle),
+
+	DEF(SET_STR, imap_urlauth_logout_format),
+	DEF(SET_STR, imap_urlauth_submit_user),
+	DEF(SET_STR, imap_urlauth_stream_user),
+
+	SETTING_DEFINE_LIST_END
+};
+
+const struct imap_urlauth_settings imap_urlauth_default_settings = {
+	.base_dir = PKG_RUNDIR,
+  .mail_debug = FALSE,
+
+	.verbose_proctitle = FALSE,
+
+	.imap_urlauth_logout_format = "in=%i out=%o",
+	.imap_urlauth_submit_user = NULL,
+	.imap_urlauth_stream_user = NULL
+};
+
+static const struct setting_parser_info *imap_urlauth_setting_dependencies[] = {
+	NULL
+};
+
+const struct setting_parser_info imap_urlauth_setting_parser_info = {
+	.module_name = "imap-urlauth",
+	.defines = imap_urlauth_setting_defines,
+	.defaults = &imap_urlauth_default_settings,
+
+	.type_offset = (size_t)-1,
+	.struct_size = sizeof(struct imap_urlauth_settings),
+
+	.parent_offset = (size_t)-1,
+
+	.dependencies = imap_urlauth_setting_dependencies
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap-urlauth/imap-urlauth-settings.h	Sat Sep 15 23:57:08 2012 +0300
@@ -0,0 +1,24 @@
+#ifndef IMAP_URLAUTH_SETTINGS_H
+#define IMAP_URLAUTH_SETTINGS_H
+
+struct mail_user_settings;
+
+struct imap_urlauth_settings {
+	const char *base_dir;
+
+	bool mail_debug;
+
+	bool verbose_proctitle;
+
+	/* imap_urlauth: */
+	const char *imap_urlauth_logout_format;
+
+	const char *imap_urlauth_submit_user;
+	const char *imap_urlauth_stream_user;
+};
+
+extern const struct imap_urlauth_settings imap_urlauth_default_settings;
+
+extern const struct setting_parser_info imap_urlauth_setting_parser_info;
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap-urlauth/imap-urlauth-worker-settings.c	Sat Sep 15 23:57:08 2012 +0300
@@ -0,0 +1,90 @@
+/* Copyright (c) 2012 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "buffer.h"
+#include "settings-parser.h"
+#include "service-settings.h"
+#include "mail-storage-settings.h"
+#include "imap-urlauth-worker-settings.h"
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+/* <settings checks> */
+static struct file_listener_settings imap_urlauth_worker_unix_listeners_array[] = {
+	{ "imap-urlauth-worker", 0600, "$default_internal_user", "" }
+};
+static struct file_listener_settings *imap_urlauth_worker_unix_listeners[] = {
+	&imap_urlauth_worker_unix_listeners_array[0]
+};
+static buffer_t imap_urlauth_worker_unix_listeners_buf = {
+	imap_urlauth_worker_unix_listeners, sizeof(imap_urlauth_worker_unix_listeners), { 0, }
+};
+/* </settings checks> */
+
+struct service_settings imap_urlauth_worker_service_settings = {
+	.name = "imap-urlauth-worker",
+	.protocol = "imap",
+	.type = "",
+	.executable = "imap-urlauth-worker",
+	.user = "",
+	.group = "",
+	.privileged_group = "",
+	.extra_groups = "",
+	.chroot = "",
+
+	.drop_priv_before_exec = FALSE,
+
+	.process_min_avail = 0,
+	.process_limit = 1024,
+	.client_limit = 1,
+	.service_count = 1,
+	.idle_kill = 0,
+	.vsz_limit = (uoff_t)-1,
+
+	.unix_listeners = { { &imap_urlauth_worker_unix_listeners_buf,
+			      sizeof(imap_urlauth_worker_unix_listeners[0]) } },
+	.fifo_listeners = ARRAY_INIT,
+	.inet_listeners = ARRAY_INIT
+};
+
+#undef DEF
+#define DEF(type, name) \
+	{ type, #name, offsetof(struct imap_urlauth_worker_settings, name), NULL }
+
+static const struct setting_define imap_urlauth_worker_setting_defines[] = {
+	DEF(SET_BOOL, verbose_proctitle),
+
+	DEF(SET_STR_VARS, imap_urlauth_dict),
+	DEF(SET_STR, imap_urlauth_host),
+	DEF(SET_UINT, imap_urlauth_port),
+
+	SETTING_DEFINE_LIST_END
+};
+
+const struct imap_urlauth_worker_settings imap_urlauth_worker_default_settings = {
+	.verbose_proctitle = FALSE,
+
+	.imap_urlauth_dict = "",
+	.imap_urlauth_host = "",
+	.imap_urlauth_port = 143
+};
+
+static const struct setting_parser_info *imap_urlauth_worker_setting_dependencies[] = {
+	&mail_user_setting_parser_info,
+	NULL
+};
+
+const struct setting_parser_info imap_urlauth_worker_setting_parser_info = {
+	.module_name = "imap-urlauth-worker",
+	.defines = imap_urlauth_worker_setting_defines,
+	.defaults = &imap_urlauth_worker_default_settings,
+
+	.type_offset = (size_t)-1,
+	.struct_size = sizeof(struct imap_urlauth_worker_settings),
+
+	.parent_offset = (size_t)-1,
+
+	.dependencies = imap_urlauth_worker_setting_dependencies
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap-urlauth/imap-urlauth-worker-settings.h	Sat Sep 15 23:57:08 2012 +0300
@@ -0,0 +1,19 @@
+#ifndef IMAP_URLAUTH_SETTINGS_H
+#define IMAP_URLAUTH_SETTINGS_H
+
+struct mail_user_settings;
+
+struct imap_urlauth_worker_settings {
+	bool verbose_proctitle;
+
+	/* imap_urlauth: */
+	const char *imap_urlauth_dict;
+	const char *imap_urlauth_host;
+	unsigned int imap_urlauth_port;
+};
+
+extern const struct imap_urlauth_worker_settings imap_urlauth_worker_default_settings;
+
+extern const struct setting_parser_info imap_urlauth_worker_setting_parser_info;
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap-urlauth/imap-urlauth-worker.c	Sat Sep 15 23:57:08 2012 +0300
@@ -0,0 +1,1036 @@
+/* Copyright (c) 2012 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "ioloop.h"
+#include "network.h"
+#include "fdpass.h"
+#include "istream.h"
+#include "ostream.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "strescape.h"
+#include "llist.h"
+#include "hostpid.h"
+#include "var-expand.h"
+#include "process-title.h"
+#include "restrict-access.h"
+#include "settings-parser.h"
+#include "master-service.h"
+#include "master-interface.h"
+#include "mail-storage.h"
+#include "mail-storage-service.h"
+#include "mail-namespace.h"
+#include "imap-url.h"
+#include "imap-msgpart-url.h"
+#include "imap-urlauth.h"
+#include "imap-urlauth-fetch.h"
+#include "imap-urlauth-worker-settings.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <sysexits.h>
+
+#define MAX_CTRL_HANDSHAKE 255
+
+/* max. length of input lines (URLs) */
+#define MAX_INBUF_SIZE 2048
+
+/* Disconnect client after idling this many milliseconds */
+#define CLIENT_IDLE_TIMEOUT_MSECS (10*60*1000)
+
+#define IS_STANDALONE() \
+        (getenv(MASTER_IS_PARENT_ENV) == NULL)
+
+#define IMAP_URLAUTH_WORKER_PROTOCOL_MAJOR_VERSION 1
+#define IMAP_URLAUTH_WORKER_PROTOCOL_MINOR_VERSION 0
+
+struct client {
+	struct client *prev, *next;
+
+	int fd_in, fd_out, fd_ctrl;
+
+	struct io *io, *ctrl_io;
+	struct istream *input, *ctrl_input;
+	struct ostream *output, *ctrl_output;
+	struct timeout *to_idle, *to_delay;
+
+	char *access_user;
+	ARRAY_TYPE(string) access_apps;
+
+	struct mail_storage_service_user *service_user;
+	struct mail_user *mail_user;
+
+	struct imap_urlauth_context *urlauth_ctx;
+
+	struct imap_msgpart_url *url;
+	struct istream *msg_part_input;
+	uoff_t msg_part_size;
+
+	/* settings: */
+	const struct imap_urlauth_worker_settings *set;
+	const struct mail_storage_settings *mail_set;
+
+	unsigned int debug:1;
+	unsigned int finished:1;
+	unsigned int version_received:1;
+	unsigned int access_received:1;
+	unsigned int waiting_input:1;
+	unsigned int access_anonymous:1;
+};
+
+static bool verbose_proctitle = FALSE;
+static struct mail_storage_service_ctx *storage_service;
+
+struct client *imap_urlauth_worker_clients;
+unsigned int imap_urlauth_worker_client_count;
+
+static void client_destroy(struct client *client);
+static void client_abort(struct client *client, const char *reason);
+static void client_run_url(struct client *client);
+static void client_input(struct client *client);
+static bool client_handle_input(struct client *client);
+static int client_output(struct client *client);
+
+static void client_ctrl_input(struct client *client);
+
+static void imap_urlauth_worker_refresh_proctitle(void)
+{
+	struct client *client = imap_urlauth_worker_clients;
+	string_t *title;
+
+	if (!verbose_proctitle)
+		return;
+
+	title = t_str_new(128);
+	str_append_c(title, '[');
+	switch (imap_urlauth_worker_client_count) {
+	case 0:
+		str_append(title, "idling");
+		break;
+	case 1:
+		if (client->mail_user == NULL)
+			str_append(title, client->access_user);
+		else {
+			str_append(title, client->access_user);
+			str_append(title, "->");
+			str_append(title, client->mail_user->username);
+		}
+		break;
+	default:
+		str_printfa(title, "%u connections",
+			    imap_urlauth_worker_client_count);
+		break;
+	}
+	str_append_c(title, ']');
+	process_title_set(str_c(title));
+}
+
+static void client_idle_timeout(struct client *client)
+{
+	if (client->url != NULL) {
+		client_abort(client,
+			"Session closed for inactivity in reading our output");
+	} else {
+		client_destroy(client);
+	}
+}
+
+static struct client *client_create(int fd)
+{
+	struct client *client;
+
+	/* always use nonblocking I/O */
+	net_set_nonblock(fd, TRUE);
+
+	client = i_new(struct client, 1);
+	i_array_init(&client->access_apps, 16);
+	client->fd_in = -1;
+	client->fd_out = -1;
+	client->fd_ctrl = fd;
+	client->access_anonymous = TRUE; /* default until overridden */
+
+	client->ctrl_io = io_add(fd, IO_READ, client_ctrl_input, client);
+	client->to_idle = timeout_add(CLIENT_IDLE_TIMEOUT_MSECS,
+				      client_idle_timeout, client);
+
+	imap_urlauth_worker_client_count++;
+	DLLIST_PREPEND(&imap_urlauth_worker_clients, client);
+
+	imap_urlauth_worker_refresh_proctitle();
+	return client;
+}
+
+static struct client *
+client_create_standalone(const char *access_user,
+			 const char *const *access_applications,
+			 int fd_in, int fd_out, bool debug)
+{
+	struct client *client;
+
+	/* always use nonblocking I/O */
+	net_set_nonblock(fd_in, TRUE);
+	net_set_nonblock(fd_out, TRUE);
+
+	client = i_new(struct client, 1);
+	i_array_init(&client->access_apps, 16);
+	client->fd_in = fd_in;
+	client->fd_out = fd_out;
+	client->fd_ctrl = -1;
+
+	if (access_user != NULL && *access_user != '\0')
+		client->access_user = i_strdup(access_user);
+	else {
+		client->access_user = i_strdup("anonymous");
+		client->access_anonymous = TRUE;
+	}
+	if (access_applications != NULL) {
+		const char *const *apps = access_applications;
+		for (; *apps != NULL; apps++) {
+			char *app = i_strdup(*apps);
+			array_append(&client->access_apps, &app, 1);
+		}
+	}
+	client->debug = debug;
+
+	client->input = i_stream_create_fd(fd_in, MAX_INBUF_SIZE, FALSE);
+	client->output = o_stream_create_fd(fd_out, (size_t)-1, FALSE);
+	client->io = io_add(fd_in, IO_READ, client_input, client);
+	client->to_idle = timeout_add(CLIENT_IDLE_TIMEOUT_MSECS,
+				      client_idle_timeout, client);
+	o_stream_set_flush_callback(client->output, client_output, client);
+
+	imap_urlauth_worker_client_count++;
+	DLLIST_PREPEND(&imap_urlauth_worker_clients, client);
+
+	i_set_failure_prefix(t_strdup_printf("imap-urlauth[%s](%s): ", my_pid,
+					     client->access_user));
+
+	return client;
+}
+
+static void client_abort(struct client *client, const char *reason)
+{
+	i_error("%s", reason);
+	client_destroy(client);
+}
+
+static void client_destroy(struct client *client)
+{
+	char **app;
+
+	i_set_failure_prefix(t_strdup_printf("imap-urlauth[%s](%s): ",
+					     my_pid, client->access_user));
+
+	if (client->url != NULL) {
+		/* deinitialize url */
+		i_stream_close(client->input);
+		o_stream_close(client->output);
+		client_run_url(client);
+		i_assert(client->url == NULL);
+	}
+
+	imap_urlauth_worker_client_count--;
+	DLLIST_REMOVE(&imap_urlauth_worker_clients, client);
+
+	if (client->urlauth_ctx != NULL)
+		imap_urlauth_deinit(&client->urlauth_ctx);
+
+	if (client->mail_user != NULL)
+		mail_user_unref(&client->mail_user);
+
+	if (client->io != NULL)
+		io_remove(&client->io);
+	if (client->ctrl_io != NULL)
+		io_remove(&client->ctrl_io);
+	if (client->to_idle != NULL)
+		timeout_remove(&client->to_idle);
+	if (client->to_delay != NULL)
+		timeout_remove(&client->to_delay);
+
+	if (client->input != NULL)
+		i_stream_destroy(&client->input);
+	if (client->output != NULL)
+		o_stream_destroy(&client->output);
+
+	if (client->ctrl_input != NULL)
+		i_stream_destroy(&client->ctrl_input);
+	if (client->ctrl_output != NULL)
+		o_stream_destroy(&client->ctrl_output);
+
+	if (client->fd_in >= 0)
+		net_disconnect(client->fd_in);
+	if (client->fd_out >= 0 && client->fd_in != client->fd_out)
+		net_disconnect(client->fd_out);
+	if (client->fd_ctrl >= 0)
+		net_disconnect(client->fd_ctrl);
+
+	if (client->service_user != NULL)
+		mail_storage_service_user_free(&client->service_user);
+	i_free(client->access_user);
+	array_foreach_modifiable(&client->access_apps, app)
+		i_free(*app);
+	array_free(&client->access_apps);
+	i_free(client);
+
+	imap_urlauth_worker_refresh_proctitle();
+	master_service_client_connection_destroyed(master_service);
+}
+
+static void client_run_url(struct client *client)
+{
+	const unsigned char *data;
+	size_t size;
+	ssize_t ret = 0;
+
+	while (i_stream_read_data(client->msg_part_input, &data, &size, 0) > 0) {
+		if ((ret = o_stream_send(client->output, data, size)) < 0)
+			break;
+		i_stream_skip(client->msg_part_input, ret);
+
+		if (o_stream_get_buffer_used_size(client->output) >= 4096) {
+			if ((ret = o_stream_flush(client->output)) < 0)
+				break;
+			if (ret == 0)
+				return;
+		}
+	}
+
+	if (client->output->closed || ret < 0) {
+		imap_msgpart_url_free(&client->url);
+		client_destroy(client);
+		return;
+	}
+
+	if (client->msg_part_input->eof) {
+		o_stream_send(client->output, "\n", 1);
+		imap_msgpart_url_free(&client->url);
+	}
+}
+
+static void clients_destroy_all(void)
+{
+	while (imap_urlauth_worker_clients != NULL)
+		client_destroy(imap_urlauth_worker_clients);
+}
+
+static void ATTR_FORMAT(2, 3)
+client_send_line(struct client *client, const char *fmt, ...)
+{
+	va_list va;
+	ssize_t ret;
+
+	if (client->output->closed)
+		return;
+
+	va_start(va, fmt);
+
+	T_BEGIN {
+		string_t *str;
+
+		str = t_str_new(256);
+		str_vprintfa(str, fmt, va);
+		str_append(str, "\n");
+
+		ret = o_stream_send(client->output,
+				    str_data(str), str_len(str));
+		i_assert(ret < 0 || (size_t)ret == str_len(str));
+	} T_END;
+
+	va_end(va);
+}
+
+static int
+client_fetch_urlpart(struct client *client, const char *url,
+		     enum imap_urlauth_fetch_flags url_flags,
+		     const char **bpstruct_r, bool *binary_with_nuls_r,
+		     const char **errormsg_r)
+{
+	const char *error;
+	struct imap_msgpart_open_result mpresult;
+	enum mail_error error_code;
+	int ret;
+
+	*bpstruct_r = NULL;
+	*errormsg_r = NULL;
+	*binary_with_nuls_r = FALSE;
+
+	ret = imap_urlauth_fetch(client->urlauth_ctx, url,
+				 &client->url, &error_code, &error);
+	if (ret <= 0) {
+		if (ret < 0)
+			return -1;
+		error = t_strdup_printf("Failed to fetch URLAUTH \"%s\": %s",
+					url, error);
+		if (client->debug)
+			i_debug("%s", error);
+		/* don't leak info about existence/accessibility
+		   of mailboxes */
+		if (error_code == MAIL_ERROR_PARAMS)
+			*errormsg_r = error;
+		return 0;
+	}
+
+	if ((url_flags & IMAP_URLAUTH_FETCH_FLAG_BINARY) != 0)
+		imap_msgpart_url_set_decode_to_binary(client->url);
+	if ((url_flags & IMAP_URLAUTH_FETCH_FLAG_BODYPARTSTRUCTURE) != 0) {
+		if (imap_msgpart_url_get_bodypartstructure(client->url,
+							   bpstruct_r,
+							   &error) <= 0) {
+			*errormsg_r = t_strdup_printf(
+				"Failed to read URLAUTH \"%s\": %s", url, error);
+			if (client->debug)
+				i_debug("%s", *errormsg_r);
+			return 0;
+		}
+	}
+
+	/* if requested, read the message part the URL points to */
+	if ((url_flags & IMAP_URLAUTH_FETCH_FLAG_BODY) != 0 ||
+	    (url_flags & IMAP_URLAUTH_FETCH_FLAG_BINARY) != 0) {
+		if (imap_msgpart_url_read_part(client->url, &mpresult, &error) <= 0) {
+			*errormsg_r = t_strdup_printf(
+				"Failed to read URLAUTH \"%s\": %s", url, error);
+			if (client->debug)
+				i_debug("%s", *errormsg_r);
+			return 0;
+		}
+		client->msg_part_size = mpresult.size;
+		client->msg_part_input = mpresult.input;
+		*binary_with_nuls_r = mpresult.binary_decoded_input_has_nuls;
+	}
+	return 1;
+}
+
+static int client_fetch_url(struct client *client, const char *url,
+			    enum imap_urlauth_fetch_flags url_flags)
+{
+	string_t *response;
+	const char *bpstruct, *errormsg;
+	bool binary_with_nuls;
+	int ret;
+
+	i_assert(client->url == NULL);
+
+	client->msg_part_size = 0;
+	client->msg_part_input = NULL;
+
+	if (client->debug)
+		i_debug("Fetching URLAUTH %s", url);
+
+	/* fetch URL */
+	ret = client_fetch_urlpart(client, url, url_flags, &bpstruct,
+				   &binary_with_nuls, &errormsg);
+	if (ret <= 0) {
+		/* fetch failed */
+		if (client->url != NULL)
+			imap_msgpart_url_free(&client->url);
+		/* don't send error details to anonymous users: just to be sure
+		   that no information about the target user account is unduly
+		   leaked. */
+		if (client->access_anonymous || errormsg == NULL)
+			client_send_line(client, "NO");
+		else {
+			client_send_line(client, "NO\terror=%s",
+					 str_tabescape(errormsg));
+		}
+		if (ret < 0) {
+			/* fetch failed badly */
+			client_abort(client, "Session aborted: Fatal failure while fetching URL");
+		}
+		return 0;
+	}
+
+	response = t_str_new(256);
+	str_append(response, "OK");
+	if (binary_with_nuls)
+		str_append(response, "\thasnuls");
+	if (bpstruct != NULL) {
+		str_append(response, "\tbpstruct=");
+		str_append(response, str_tabescape(bpstruct));
+		if (client->debug) {
+			i_debug("Fetched URLAUTH yielded BODYPARTSTRUCTURE (%s)",
+				bpstruct);
+		}
+	}
+
+	/* return content */
+	o_stream_cork(client->output);
+	if (client->msg_part_size == 0 || client->msg_part_input == NULL) {
+		/* empty */
+		str_append(response, "\t0");
+		client_send_line(client, "%s", str_c(response));
+
+		imap_msgpart_url_free(&client->url);
+		client->url = NULL;
+		if (client->debug)
+			i_debug("Fetched URLAUTH yielded empty result");
+	} else {
+
+		/* actual content */
+		str_printfa(response, "\t%"PRIuUOFF_T, client->msg_part_size);
+		client_send_line(client, "%s", str_c(response));
+
+		if (client->debug) {
+			i_debug("Fetched URLAUTH yielded %"PRIuUOFF_T" bytes "
+				"of %smessage data", client->msg_part_size,
+				(binary_with_nuls ? "binary " : ""));
+		}
+		client_run_url(client);
+	}
+
+	if (client->url != NULL) {
+		/* URL not finished */
+		o_stream_set_flush_pending(client->output, TRUE);
+		client->waiting_input = TRUE;
+	}
+	o_stream_uncork(client->output);
+	return client->url != NULL ? 0 : 1;
+}
+
+static int
+client_handle_command(struct client *client, const char *cmd,
+		      const char *const *args, const char **error_r)
+{
+	int ret;
+
+	*error_r = NULL;
+
+	/* "URL"["\tbody"]["\tbinary"]["\tbpstruct"]"\t"<url>:
+	   fetch URL (meta)data */
+	if (strcmp(cmd, "URL") == 0) {
+		enum imap_urlauth_fetch_flags url_flags = 0;
+		const char *url;
+
+		if (*args == NULL) {
+			*error_r = "URL: Missing URL parameter";
+			return -1;
+		}
+
+		url = *args;
+	
+		args++;
+		while (*args != NULL) {
+			if (strcasecmp(*args, "body") == 0)
+				url_flags |= IMAP_URLAUTH_FETCH_FLAG_BODY;
+			else if (strcasecmp(*args, "binary") == 0)
+				url_flags |= IMAP_URLAUTH_FETCH_FLAG_BINARY;
+			else if (strcasecmp(*args, "bpstruct") == 0)
+				url_flags |= IMAP_URLAUTH_FETCH_FLAG_BODYPARTSTRUCTURE;
+
+			args++;
+		}
+
+		if (url_flags == 0)
+			url_flags = IMAP_URLAUTH_FETCH_FLAG_BODY;
+
+		T_BEGIN {
+			ret = client_fetch_url(client, url, url_flags);
+		} T_END;
+		return ret;
+	}
+
+	/* "END": unselect current user (closes worker) */
+	if (strcmp(cmd, "END") == 0) {
+		if (args[0] != NULL) {
+			*error_r = "END: Invalid number of parameters";
+			return -1;
+		}
+
+		client->finished = TRUE;
+		if (client->ctrl_output != NULL)
+			(void)o_stream_send_str(client->ctrl_output, "FINISHED\n");
+		client_destroy(client);
+		return 0;
+	}
+
+	*error_r = t_strconcat("Unknown or inappropriate command: ", cmd, NULL);
+	return -1;
+}
+
+static int
+client_handle_user_command(struct client *client, const char *cmd,
+			   const char *const *args, const char **error_r)
+{
+	struct mail_storage_service_input input;
+	struct imap_urlauth_worker_settings *set;
+	struct mail_storage_service_user *user;
+	struct imap_urlauth_config config;
+	struct mail_user *mail_user;
+	const char *error;
+	unsigned int count;
+	int ret;
+
+	/* "USER\t"<username> */
+	*error_r = NULL;
+
+	/* check command syntax */
+	if (strcmp(cmd, "USER") != 0) {
+		*error_r = t_strconcat("Unknown or inappropriate command: ",
+				       cmd, NULL);
+		return -1;
+	}
+
+	if (args[0] == NULL || args[1] != NULL) {
+		*error_r = "USER: Invalid number of parameters";
+		return -1;
+	}
+
+	/* lookup user */
+	memset(&input, 0, sizeof(input));
+	input.module = "imap-urlauth-worker";
+	input.service = "imap-urlauth-worker";
+	input.username = args[0];
+
+	if (client->debug)
+		i_debug("Looking up user %s", input.username);
+
+	ret = mail_storage_service_lookup_next(storage_service, &input,
+					       &user, &mail_user, &error);
+	if (ret < 0) {
+		i_error("Failed to lookup user %s: %s", input.username, error);
+		client_abort(client, "Session aborted: Failed to lookup user");
+		return 0;
+	} else if (ret == 0) {
+		if (client->debug)
+			i_debug("User %s doesn't exist", input.username);
+
+		client_send_line(client, "NO");
+		timeout_remove(&client->to_delay);
+		return 1;
+	}
+
+	client->debug = mail_user->mail_debug =
+		client->debug || mail_user->mail_debug;
+
+	/* drop privileges */
+	restrict_access_allow_coredumps(TRUE);
+
+	set = mail_storage_service_user_get_set(user)[1];
+	settings_var_expand(&imap_urlauth_worker_setting_parser_info, set,
+			    mail_user->pool,
+			    mail_user_var_expand_table(mail_user));
+
+	if (set->verbose_proctitle) {
+		verbose_proctitle = TRUE;
+		imap_urlauth_worker_refresh_proctitle();
+	}
+
+	client->service_user = user;
+	client->mail_user = mail_user;
+	client->set = set;
+
+	if (client->debug) {
+		i_debug("Found user account `%s' on behalf of user `%s'",
+			mail_user->username, client->access_user);
+	}
+
+	/* initialize urlauth context */
+	if (set->imap_urlauth_dict == NULL || *set->imap_urlauth_dict == '\0') {
+		i_error("imap_urlauth_dict setting is not configured for user %s",
+			mail_user->username);
+		client_send_line(client, "NO");
+		client_abort(client, "Session aborted: URLAUTH not configured");
+		return 0;
+	}
+
+	memset(&config, 0, sizeof(config));
+	config.dict_uri = set->imap_urlauth_dict;
+	config.url_host = set->imap_urlauth_host;
+	config.url_port = set->imap_urlauth_port;
+	config.access_user = client->access_user;
+	config.access_applications =
+		(const void *)array_get(&client->access_apps, &count);
+		
+	if (imap_urlauth_init(client->mail_user, &config, &client->urlauth_ctx) < 0) {
+		client_send_line(client, "NO");
+		client_abort(client,
+			"Session aborted: Failed to init URLAUTH context");
+		return 0;
+	}
+
+	if (client->debug) {
+		i_debug("Providing access to user account `%s' on behalf of `%s'",
+			mail_user->username, client->access_user);
+	}
+
+	i_set_failure_prefix(t_strdup_printf("imap-urlauth[%s](%s->%s): ",
+		my_pid, client->access_user, mail_user->username));
+
+	client_send_line(client, "OK");
+	return 1;
+}
+
+static bool client_handle_input(struct client *client)
+{
+	const char *line, *cmd, *error;
+	int ret;
+
+	if (client->url != NULL || client->to_delay != NULL) {
+		/* we're still processing a URL. wait until it's
+		   finished. */
+		io_remove(&client->io);
+		client->io = NULL;
+		client->waiting_input = TRUE;
+		return TRUE;
+	}
+
+	if (client->io == NULL) {
+		client->io = io_add(client->fd_in, IO_READ,
+				    client_input, client);
+	}
+	client->waiting_input = FALSE;
+	timeout_reset(client->to_idle);
+
+	switch (i_stream_read(client->input)) {
+	case -1:
+		/* disconnected */
+		if (client->ctrl_output != NULL)
+			(void)o_stream_send_str(client->ctrl_output, "DISCONNECTED\n");
+		client_destroy(client);
+		return FALSE;
+	case -2:
+		/* line too long, kill it */
+		client_abort(client, "Session aborted: Input line too long");
+		return FALSE;
+	}
+
+	while ((line = i_stream_next_line(client->input)) != NULL) {
+		const char *const *args = t_strsplit_tabescaped(line);
+
+		if (args[0] == NULL)
+			continue;
+		cmd = args[0]; args++;
+
+		if (client->mail_user == NULL)
+			ret = client_handle_user_command(client, cmd, args, &error);
+		else
+			ret = client_handle_command(client, cmd, args, &error);
+
+		if (ret <= 0) {
+			if (ret == 0)
+				break;
+			i_error("Client input error: %s", error);
+			client_abort(client, "Session aborted: Unexpected input");
+			return FALSE;
+		}
+	}
+	return TRUE;
+}
+
+static void client_input(struct client *client)
+{
+	(void)client_handle_input(client);
+}
+
+static int client_output(struct client *client)
+{
+	o_stream_cork(client->output);
+	if (o_stream_flush(client->output) < 0) {
+		if (client->ctrl_output != NULL)
+			(void)o_stream_send_str(client->ctrl_output, "DISCONNECTED\n");
+		client_destroy(client);
+		return 1;
+	}
+	timeout_reset(client->to_idle);
+
+	if (client->url != NULL) {
+		client_run_url(client);
+
+		if (client->url == NULL && client->waiting_input) {
+			if (!client_handle_input(client)) {
+				/* client got destroyed */
+				return 1;
+			}
+		}
+	}
+
+	o_stream_uncork(client->output);
+	if (client->url != NULL) {
+		/* url not finished yet */
+		return 0;
+	} else if (client->io == NULL) {
+		/* data still in output buffer, get back here to add IO */
+		return 0;
+	} else {
+		return 1;
+	}
+}
+
+static int
+client_ctrl_read_fds(struct client *client)
+{
+	unsigned char data;
+	ssize_t ret = 1;
+
+	if (client->fd_in == -1) {
+		ret = fd_read(client->fd_ctrl, &data,
+			      sizeof(data), &client->fd_in);
+		if (ret > 0 && data == '0')
+			client->fd_out = client->fd_in;
+	}
+	if (ret > 0 && client->fd_out == -1) {
+		ret = fd_read(client->fd_ctrl, &data,
+			      sizeof(data), &client->fd_out);
+	}
+
+	if (ret == 0) {
+		/* unexpectedly disconnected */
+		client_destroy(client);
+		return -1;
+	} else if (ret < 0) {
+		if (errno == EAGAIN)
+			return 0;
+		i_error("fd_read() failed: %m");
+		return -1;
+	} else if (data != '0') {
+		i_error("fd_read() returned invalid byte 0x%2x", data);
+		return -1;
+	}
+
+	if (client->fd_in == -1 || client->fd_out == -1) {
+		i_error("Handshake is missing a file descriptor");
+		return -1;
+	}
+
+	client->ctrl_input =
+		i_stream_create_fd(client->fd_ctrl, MAX_INBUF_SIZE, FALSE);
+	client->ctrl_output =
+		o_stream_create_fd(client->fd_ctrl, (size_t)-1, FALSE);
+	return 1;
+}
+
+static void client_ctrl_input(struct client *client)
+{
+	const char *const *args;
+	const char *line;
+	int ret;
+
+	timeout_reset(client->to_idle);
+
+	if (client->fd_in == -1 || client->fd_out == -1) {
+		if ((ret = client_ctrl_read_fds(client)) <= 0) {
+			if (ret < 0)
+				client_abort(client, "FD Transfer failed");
+			return;
+		}
+	}
+
+	switch (i_stream_read(client->ctrl_input)) {
+	case -1:
+		/* disconnected */
+		client_destroy(client);
+		return;
+	case -2:
+		/* line too long, kill it */
+		client_abort(client,
+			     "Control session aborted: Input line too long");
+		return;
+	}
+
+	if (!client->version_received) {
+		if ((line = i_stream_next_line(client->ctrl_input)) == NULL)
+			return;
+
+		if (!version_string_verify(line, "imap-urlauth-worker",
+				IMAP_URLAUTH_WORKER_PROTOCOL_MAJOR_VERSION)) {
+			i_error("imap-urlauth-worker client not compatible with this server "
+				"(mixed old and new binaries?) %s", line);
+			client_abort(client, "Control session aborted: Version mismatch");
+			return;
+		}
+
+		client->version_received = TRUE;
+		if (o_stream_send_str(client->ctrl_output, "OK\n") < 0) {
+			client_destroy(client);
+			return;
+		}
+	}
+
+	if (client->access_received) {
+		client_abort(client, "Control session aborted: Unexpected input");
+		return;
+	}
+
+	if ((line = i_stream_next_line(client->ctrl_input)) == NULL)
+		return;
+
+	args = t_strsplit_tabescaped(line);
+	if (*args == NULL || strcmp(*args, "ACCESS") != 0) {
+		i_error("Invalid control command: %s", str_sanitize(line, 80));
+		client_abort(client, "Control session aborted: Invalid command");
+		return;
+	}
+	args++;
+	if (*args == NULL) {
+		i_error("Invalid ACCESS command: %s", str_sanitize(line, 80));
+		client_abort(client, "Control session aborted: Invalid command");
+		return;
+	}
+
+	i_assert(client->access_user == NULL);
+	if (**args != '\0') {
+		client->access_user = i_strdup(*args);
+		client->access_anonymous = FALSE;
+	} else {
+		client->access_anonymous = TRUE;
+	}
+	i_set_failure_prefix(t_strdup_printf("imap-urlauth[%s](%s): ", my_pid,
+					     client->access_user));
+
+	args++;
+	while (*args != NULL) {
+		/* debug */
+		if (strcasecmp(*args, "debug") == 0) {
+			client->debug = TRUE;
+		/* apps=<access-application>[,<access-application,...] */
+		} else if (strncasecmp(*args, "apps=", 5) == 0 &&
+			   (*args)[5] != '\0') {
+			const char *const *apps = t_strsplit(*args+5, ",");
+
+			while (*apps != NULL) {
+				char *app = i_strdup(*apps);
+
+				array_append(&client->access_apps, &app, 1);
+				if (client->debug) {
+					i_debug("User %s has URLAUTH %s access",
+						client->access_user, app);
+				}
+				apps++;
+			}
+		} else {
+			i_error("Invalid ACCESS parameter: %s", str_sanitize(*args, 80));
+			client_abort(client, "Control session aborted: Invalid command");
+			return;
+		} 
+		args++;
+	}
+
+	client->access_received = TRUE;
+
+	if (o_stream_send_str(client->ctrl_output, "OK\n") < 0) {
+		client_destroy(client);
+		return;
+	}
+
+	client->input = i_stream_create_fd(client->fd_in, MAX_INBUF_SIZE, FALSE);
+	client->output = o_stream_create_fd(client->fd_out, (size_t)-1, FALSE); 
+	client->io = io_add(client->fd_in, IO_READ, client_input, client);
+	o_stream_set_flush_callback(client->output, client_output, client);
+
+	if (client->debug) {
+		i_debug("Worker activated for access by user %s",
+			client->access_user);
+	}
+}
+
+static void imap_urlauth_worker_die(void)
+{
+	/* do nothing */
+}
+
+static void main_stdio_run(const char *access_user,
+			   const char *const *access_applications)
+{
+	bool debug;
+
+	debug = getenv("DEBUG");
+	access_user = access_user != NULL ? access_user : getenv("USER");
+	if (access_user == NULL && IS_STANDALONE())
+		access_user = getlogin();
+	if (access_user == NULL)
+		i_fatal("USER environment missing");
+
+	(void)client_create_standalone(access_user, access_applications,
+				       STDIN_FILENO, STDOUT_FILENO, debug);
+}
+
+static void client_connected(struct master_service_connection *conn)
+{
+	master_service_client_connection_accept(conn);
+	(void)client_create(conn->fd);
+}
+
+int main(int argc, char *argv[])
+{
+	static const struct setting_parser_info *set_roots[] = {
+		&imap_urlauth_worker_setting_parser_info,
+		NULL
+	};
+	enum master_service_flags service_flags = 0;
+	enum mail_storage_service_flags storage_service_flags =
+		MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP |
+		MAIL_STORAGE_SERVICE_FLAG_NO_LOG_INIT;
+	ARRAY_TYPE (const_string) access_apps;
+	const char *access_user = NULL;
+	int c;
+
+	if (IS_STANDALONE()) {
+		service_flags |= MASTER_SERVICE_FLAG_STANDALONE |
+			MASTER_SERVICE_FLAG_STD_CLIENT;
+	} else {
+		service_flags |= MASTER_SERVICE_FLAG_KEEP_CONFIG_OPEN;
+		storage_service_flags |=
+			MAIL_STORAGE_SERVICE_FLAG_DISALLOW_ROOT;
+	}
+
+	master_service = master_service_init("imap-urlauth-worker", service_flags,
+					     &argc, &argv, "a:");
+
+	t_array_init(&access_apps, 4);
+	while ((c = master_getopt(master_service)) > 0) {
+		switch (c) {
+		case 'a': {
+			const char *app = t_strdup(optarg);
+
+			array_append(&access_apps, &app, 1);
+			break;
+		}
+		default:
+			return FATAL_DEFAULT;
+		}
+	}
+
+	if ( optind < argc ) {
+		access_user = argv[optind++];
+	}
+
+	if (optind != argc) {
+		i_fatal_status(EX_USAGE, "Unknown argument: %s", argv[optind]);
+	}
+
+	master_service_init_log(master_service,
+				t_strdup_printf("imap-urlauth[%s]: ", my_pid));
+
+	master_service_init_finish(master_service);
+	master_service_set_die_callback(master_service, imap_urlauth_worker_die);
+
+	storage_service =
+		mail_storage_service_init(master_service,
+					  set_roots, storage_service_flags);
+
+	/* fake that we're running, so we know if client was destroyed
+	   while handling its initial input */
+	io_loop_set_running(current_ioloop);
+
+	if (IS_STANDALONE()) {
+		T_BEGIN {
+			if (array_count(&access_apps) > 0) {
+				(void)array_append_space(&access_apps);
+				main_stdio_run(access_user, array_idx(&access_apps,0));
+			} else {
+				main_stdio_run(access_user, NULL);
+			}
+		} T_END;
+	} else {
+		io_loop_set_running(current_ioloop);
+	}
+
+	if (io_loop_is_running(current_ioloop))
+		master_service_run(master_service, client_connected);
+	clients_destroy_all();
+
+	mail_storage_service_deinit(&storage_service);
+	master_service_deinit(&master_service);
+	return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap-urlauth/imap-urlauth.c	Sat Sep 15 23:57:08 2012 +0300
@@ -0,0 +1,252 @@
+/* Copyright (c) 2012 Dovecot authors, see the included COPYING file */
+
+/*
+The imap-urlauth service provides URLAUTH access between different accounts. If
+user A has an URLAUTH that references a mail from user B, it makes a connection
+to the imap-urlauth service to access user B's mail store to retrieve the
+mail.
+
+The authentication and authorization of the URLAUTH is performed whithin
+this service. Because access to the mailbox and the associated mailbox keys is
+necessary to retrieve the message and for verification of the URLAUTH, the
+urlauth services need root privileges. To mitigate security concerns, the
+retrieval and verification of the URLs is performed in a worker service that
+drops root privileges and acts as target user B.
+
+The imap-urlauth service thus consists of three separate stages:
+
+- imap-urlauth-login:
+  This is the login service which operates identical to imap-login and
+  pop3-login equivalents, except for the fact that only token authentication is
+  allowed. It verifies that the connecting client is an IMAP service acting on
+  behaf of an authenticated user.
+
+- imap-urlauth:
+  Once the client is authenticated, the connection gets passed to the
+  imap-urlauth service (as implemented here). The goal of this stage is 
+  to prevent the need for re-authenticating to the imap-urlauth service when
+  the clients wants to switch to a different target user. It normally runs as
+  $default_internal_user and starts workers to perform the actual work. To start
+  a worker, the imap-urlauth service establishes a control connection to the
+  imap-urlauth-worker service. In the handshake phase of the control protocol,
+  the connection of the client is passed to the worker. Once the worker
+  finishes, a new worker is started and the client connection is tranfered to
+  it, unless the client is disconnected.
+
+- imap-urlauth-worker:
+  The worker handles the URLAUTH requests from the client, so this is where the
+  mail store of the target user is accessed. The worker starts as root. In the
+  protocol interaction the client first indicates what the target user is.
+  The worker then performs a userdb lookup and drops privileges. The client can
+  then submit URLAUTH requests, which are limited to that user. Once the client
+  wants to access a different user, the worker terminates and the imap-urlauth
+  service starts a new worker for the next target user.
+*/
+
+#include "imap-urlauth-common.h"
+#include "lib-signals.h"
+#include "ioloop.h"
+#include "buffer.h"
+#include "istream.h"
+#include "ostream.h"
+#include "abspath.h"
+#include "base64.h"
+#include "str.h"
+#include "process-title.h"
+#include "auth-master.h"
+#include "master-service.h"
+#include "master-service-settings.h"
+#include "master-login.h"
+#include "master-interface.h"
+#include "var-expand.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#define IS_STANDALONE() \
+        (getenv(MASTER_IS_PARENT_ENV) == NULL)
+
+bool verbose_proctitle = FALSE;
+static struct master_login *master_login = NULL;
+
+static const struct imap_urlauth_settings *imap_urlauth_settings;
+
+void imap_urlauth_refresh_proctitle(void)
+{
+	struct client *client;
+	string_t *title = t_str_new(128);
+
+	if (!verbose_proctitle)
+		return;
+
+	str_append_c(title, '[');
+	switch (imap_urlauth_client_count) {
+	case 0:
+		str_append(title, "idling");
+		break;
+	case 1:
+		client = imap_urlauth_clients;
+		str_append(title, client->username);
+		break;
+	default:
+		str_printfa(title, "%u connections", imap_urlauth_client_count);
+		break;
+	}
+	str_append_c(title, ']');
+	process_title_set(str_c(title));
+}
+
+static void imap_urlauth_die(void)
+{
+	/* do nothing. imap_urlauth connections typically die pretty quick anyway. */
+}
+
+static int
+client_create_from_input(const char *username, int fd_in, int fd_out)
+{
+	struct client *client;
+
+	if (client_create(username, fd_in, fd_out,
+			  imap_urlauth_settings, &client) < 0)
+		return -1;
+
+	if (!IS_STANDALONE())
+		client_send_line(client, "OK");
+	return 0;
+}
+
+static void main_stdio_run(const char *username)
+{
+	username = username != NULL ? username : getenv("USER");
+	if (username == NULL && IS_STANDALONE())
+		username = getlogin();
+	if (username == NULL)
+		i_fatal("USER environment missing");
+
+	(void)client_create_from_input(username, STDIN_FILENO, STDOUT_FILENO);
+}
+
+static void
+login_client_connected(const struct master_login_client *client,
+		       const char *username, const char *const *extra_fields)
+{
+	const char *msg = "NO\n";
+	struct auth_user_reply reply;
+	struct net_unix_cred cred;
+
+	auth_user_fields_parse(extra_fields, pool_datastack_create(), &reply);
+
+	/* check peer credentials if possible */
+	if (reply.uid != (uid_t)-1 && net_getunixcred(client->fd, &cred) == 0 &&
+		reply.uid != cred.uid) {
+		i_error("Peer's credentials (uid=%ld) do not match "
+			"the user that logged in (uid=%ld).",
+			(long)cred.uid, (long)reply.uid);
+		(void)write(client->fd, msg, strlen(msg));
+		net_disconnect(client->fd);
+		return;
+	}
+
+	if (reply.anonymous)
+		username = NULL;
+
+	if (client_create_from_input(username, client->fd, client->fd) < 0)
+		net_disconnect(client->fd);
+}
+
+static void login_client_failed(const struct master_login_client *client,
+				const char *errormsg ATTR_UNUSED)
+{
+	const char *msg = "NO\n";
+	(void)write(client->fd, msg, strlen(msg));
+}
+
+static void client_connected(struct master_service_connection *conn)
+{
+	/* when running standalone, we shouldn't even get here */
+	i_assert(master_login != NULL);
+
+	master_service_client_connection_accept(conn);
+	master_login_add(master_login, conn->fd);
+}
+
+int main(int argc, char *argv[])
+{
+	static const struct setting_parser_info *set_roots[] = {
+		&imap_urlauth_setting_parser_info,
+		NULL
+	};
+	struct master_login_settings login_set;
+	struct master_service_settings_input input;
+	struct master_service_settings_output output;
+	void **sets;
+	enum master_service_flags service_flags = 0;
+	const char *error = NULL, *username = NULL;
+
+	memset(&login_set, 0, sizeof(login_set));
+	login_set.postlogin_timeout_secs = MASTER_POSTLOGIN_TIMEOUT_DEFAULT;
+
+	if (IS_STANDALONE() && getuid() == 0 &&
+	    net_getpeername(1, NULL, NULL) == 0) {
+		printf("NO imap_urlauth binary must not be started from "
+		       "inetd, use imap-urlauth-login instead.\n");
+		return 1;
+	}
+
+	if (IS_STANDALONE()) {
+		service_flags |= MASTER_SERVICE_FLAG_STANDALONE |
+			MASTER_SERVICE_FLAG_STD_CLIENT;
+	} else {
+		service_flags |= MASTER_SERVICE_FLAG_KEEP_CONFIG_OPEN;
+	}
+
+	master_service = master_service_init("imap-urlauth", service_flags,
+					     &argc, &argv, "");
+	if (master_getopt(master_service) > 0)
+		return FATAL_DEFAULT;
+	master_service_init_log(master_service, "imap-urlauth: ");
+
+	memset(&input, 0, sizeof(input));
+	input.roots = set_roots;
+	input.module = "imap-urlauth";
+	input.service = "imap-urlauth";
+	if (master_service_settings_read(master_service, &input, &output,
+						&error) < 0)
+		i_fatal("Error reading configuration: %s", error);
+
+	sets = master_service_settings_get_others(master_service);
+	imap_urlauth_settings = sets[0];	
+
+	if (imap_urlauth_settings->verbose_proctitle)
+		verbose_proctitle = TRUE;
+
+	login_set.auth_socket_path = t_abspath("auth-master");
+	login_set.callback = login_client_connected;
+	login_set.failure_callback = login_client_failed;
+
+	master_service_init_finish(master_service);
+	master_service_set_die_callback(master_service, imap_urlauth_die);
+
+	/* fake that we're running, so we know if client was destroyed
+	   while handling its initial input */
+	io_loop_set_running(current_ioloop);
+
+	if (IS_STANDALONE()) {
+		T_BEGIN {
+			main_stdio_run(username);
+		} T_END;
+	} else {
+		master_login = master_login_init(master_service, &login_set);
+		io_loop_set_running(current_ioloop);
+	}
+
+	if (io_loop_is_running(current_ioloop))
+		master_service_run(master_service, client_connected);
+	clients_destroy_all();
+
+	if (master_login != NULL)
+		master_login_deinit(&master_login);
+	master_service_deinit(&master_service);
+	return 0;
+}
--- a/src/imap/Makefile.am	Sat Sep 15 21:00:54 2012 +0300
+++ b/src/imap/Makefile.am	Sat Sep 15 23:57:08 2012 +0300
@@ -9,6 +9,7 @@
 	-I$(top_srcdir)/src/lib-master \
 	-I$(top_srcdir)/src/lib-mail \
 	-I$(top_srcdir)/src/lib-imap \
+	-I$(top_srcdir)/src/lib-imap-urlauth \
 	-I$(top_srcdir)/src/lib-imap-storage \
 	-I$(top_srcdir)/src/lib-index \
 	-I$(top_srcdir)/src/lib-storage
@@ -16,10 +17,12 @@
 imap_LDFLAGS = -export-dynamic
 
 imap_LDADD = \
+	../lib-imap-urlauth/libimap-urlauth.la \
 	$(LIBDOVECOT_STORAGE) \
 	$(LIBDOVECOT) \
 	$(MODULE_LIBS)
 imap_DEPENDENCIES = \
+	../lib-imap-urlauth/libimap-urlauth.la \
 	$(LIBDOVECOT_STORAGE_DEPS) \
 	$(LIBDOVECOT_DEPS)
 
@@ -36,6 +39,7 @@
 	cmd-examine.c \
 	cmd-expunge.c \
 	cmd-fetch.c \
+	cmd-genurlauth.c \
 	cmd-id.c \
 	cmd-idle.c \
 	cmd-list.c \
@@ -45,6 +49,7 @@
 	cmd-noop.c \
 	cmd-notify.c \
 	cmd-rename.c \
+	cmd-resetkey.c \
 	cmd-search.c \
 	cmd-select.c \
 	cmd-sort.c \
@@ -55,6 +60,7 @@
 	cmd-uid.c \
 	cmd-unselect.c \
 	cmd-unsubscribe.c \
+	cmd-urlfetch.c \
 	cmd-x-cancel.c
 
 imap_SOURCES = \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap/cmd-genurlauth.c	Sat Sep 15 23:57:08 2012 +0300
@@ -0,0 +1,52 @@
+/* Copyright (c) 2012 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "str.h"
+#include "imap-commands.h"
+#include "imap-urlauth.h"
+
+bool cmd_genurlauth(struct client_command_context *cmd)
+{
+	const struct imap_arg *args;
+	string_t *response;
+	int ret;
+
+	if (cmd->client->urlauth_ctx == NULL) {
+		client_send_command_error(cmd, "URLAUTH disabled.");
+		return TRUE;
+	}
+
+	if (!client_read_args(cmd, 0, 0, &args))
+		return FALSE;
+
+	response = t_str_new(1024);
+	str_append(response, "* GENURLAUTH");
+	for (;;) {
+		const char *url_rump, *mechanism, *url, *error;
+
+		if (IMAP_ARG_IS_EOL(args))
+			break;
+		if (!imap_arg_get_astring(args++, &url_rump) ||
+		    !imap_arg_get_atom(args++, &mechanism)) {
+			client_send_command_error(cmd, "Invalid arguments.");
+			return FALSE;
+		}
+
+		ret = imap_urlauth_generate(cmd->client->urlauth_ctx,
+					    mechanism, url_rump, &url, &error);
+		if (ret <= 0) {
+			if (ret < 0)
+				client_send_internal_error(cmd);
+			else
+				client_send_command_error(cmd, error);
+			return TRUE;
+		}
+
+		str_append_c(response, ' ');
+		str_append(response, url);
+	}
+
+	client_send_line(cmd->client, str_c(response));
+	client_send_tagline(cmd, "OK GENURLAUTH completed.");
+	return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap/cmd-resetkey.c	Sat Sep 15 23:57:08 2012 +0300
@@ -0,0 +1,105 @@
+/* Copyright (c) 2012 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "imap-resp-code.h"
+#include "imap-commands.h"
+#include "imap-urlauth.h"
+
+static bool cmd_resetkey_all(struct client_command_context *cmd)
+{
+	if (imap_urlauth_reset_all_keys(cmd->client->urlauth_ctx) <= 0) {
+		client_send_internal_error(cmd);
+		return TRUE;
+	}
+
+	client_send_tagline(cmd, "OK All keys removed.");
+	return TRUE;
+}
+
+static bool
+cmd_resetkey_mailbox(struct client_command_context *cmd,
+		     const char *mailbox, const struct imap_arg *mech_args)
+{
+	enum mailbox_existence existence;
+	struct mail_namespace *ns;
+	enum mailbox_flags flags = MAILBOX_FLAG_READONLY;
+	struct mailbox *box;
+
+	/* check mechanism arguments (we support only INTERNAL mechanism) */
+	while (!IMAP_ARG_IS_EOL(mech_args)) {
+		const char *mechanism;
+
+		if (imap_arg_get_astring(mech_args, &mechanism)) {
+			if (strcasecmp(mechanism, "INTERNAL") != 0) {
+				client_send_tagline(cmd,
+					"NO Unsupported URLAUTH mechanism.");
+				return TRUE;
+			}
+		} else {
+			client_send_command_error(cmd, "Invalid arguments.");
+			return TRUE;
+		}
+
+		mech_args++;
+	}
+
+	/* find mailbox namespace */
+	ns = client_find_namespace(cmd, &mailbox);
+	if (ns == NULL)
+		return TRUE;
+
+	/* check mailbox */
+	box = mailbox_alloc(ns->list, mailbox, flags);
+	if (mailbox_exists(box, TRUE, &existence) < 0) {
+		client_send_internal_error(cmd);
+		mailbox_free(&box);
+		return TRUE;
+	}
+
+	if (existence == MAILBOX_EXISTENCE_NONE) {
+		client_send_tagline(cmd, t_strdup_printf(
+			"NO ["IMAP_RESP_CODE_NONEXISTENT"] "
+			MAIL_ERRSTR_MAILBOX_NOT_FOUND, mailbox));
+		mailbox_free(&box);
+		return TRUE;
+	}
+
+	/* check urlauth environment and reset requested key */
+	if (imap_urlauth_reset_mailbox_key(cmd->client->urlauth_ctx, box) < 0) {
+		client_send_internal_error(cmd);
+		mailbox_free(&box);
+		return TRUE;
+	}
+
+	/* confirm success */
+	/* FIXME: RFC Says: `Any current IMAP session logged in as the user
+	   that has the mailbox selected will receive an untagged OK response
+	   with the URLMECH status response code'. We currently don't do that
+	   at all. We could probably do it by communicating via mailbox list
+	   index. */
+	client_send_tagline(cmd, "OK [URLMECH INTERNAL] Key removed.");
+	mailbox_free(&box);
+	return TRUE;
+}
+
+bool cmd_resetkey(struct client_command_context *cmd)
+{
+	const struct imap_arg *args;
+	const char *mailbox;
+
+	if (cmd->client->urlauth_ctx == NULL) {
+		client_send_command_error(cmd, "URLAUTH disabled.");
+		return TRUE;
+	}
+
+	if (!client_read_args(cmd, 0, 0, &args))
+		return FALSE;
+
+	if (IMAP_ARG_IS_EOL(&args[0]))
+		return cmd_resetkey_all(cmd);
+	else if (imap_arg_get_astring(&args[0], &mailbox))
+		return cmd_resetkey_mailbox(cmd, mailbox, &args[1]);
+
+	client_send_command_error(cmd, "Invalid arguments.");
+	return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap/cmd-urlfetch.c	Sat Sep 15 23:57:08 2012 +0300
@@ -0,0 +1,387 @@
+/* Copyright (c) 2012 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "strfuncs.h"
+#include "str.h"
+#include "array.h"
+#include "network.h"
+#include "istream.h"
+#include "ostream.h"
+#include "imap-url.h"
+#include "imap-common.h"
+#include "imap-commands.h"
+#include "imap-urlauth.h"
+#include "imap-urlauth-fetch.h"
+
+struct cmd_urlfetch_context {
+	struct imap_urlauth_fetch *ufetch;
+	struct istream *input;
+
+	unsigned int failed:1;
+	unsigned int finished:1;
+	unsigned int extended:1;
+};
+
+struct cmd_urlfetch_url {
+	const char *url;
+	
+	enum imap_urlauth_fetch_flags flags;
+};
+
+static void cmd_urlfetch_finish
+(struct client_command_context *cmd)
+{
+	struct cmd_urlfetch_context *ctx =
+		(struct cmd_urlfetch_context *)cmd->context;
+
+	if (ctx->finished)
+		return;
+	ctx->finished = TRUE;
+
+	if (ctx->ufetch != NULL)
+		imap_urlauth_fetch_deinit(&ctx->ufetch);
+
+	if (ctx->failed) {
+		client_send_internal_error(cmd);
+		return;
+	}
+
+	client_send_tagline(cmd, "OK URLFETCH completed.");
+}
+
+static bool cmd_urlfetch_cancel(struct client_command_context *cmd)
+{
+	struct cmd_urlfetch_context *ctx =
+		(struct cmd_urlfetch_context *)cmd->context;
+
+	if (!cmd->cancel)
+		return FALSE;
+
+	if (ctx->ufetch != NULL) {
+		if (cmd->client->user->mail_debug) {
+			i_debug("URLFETCH: Canceling command; "
+				"aborting URLAUTH fetch requests prematurely");
+		}
+		imap_urlauth_fetch_deinit(&ctx->ufetch);
+	}
+	return TRUE;
+}
+
+static int cmd_urlfetch_transfer_literal(struct client_command_context *cmd)
+{
+	struct client *client = cmd->client;
+	struct cmd_urlfetch_context *ctx =
+		(struct cmd_urlfetch_context *)cmd->context;
+	const unsigned char *data;
+	size_t size;
+	int ret = 1;
+
+	/* are we in the middle of an urlfetch literal? */
+	if (ctx->input == NULL)
+		return 1;
+
+	/* flush output to client if buffer is filled above optimum */
+	if (o_stream_get_buffer_used_size(client->output) >=
+	    CLIENT_OUTPUT_OPTIMAL_SIZE) {
+		if ((ret = o_stream_flush(client->output)) <= 0)
+			return ret;
+	}
+
+	/* transfer literal to client */
+	o_stream_set_max_buffer_size(client->output, 4096);
+	while (i_stream_read_data(ctx->input, &data, &size, 0) > 0) {
+		if ((ret = o_stream_send(client->output, data, size)) < 0)
+			break;
+		i_stream_skip(ctx->input, ret);
+	}
+	o_stream_set_max_buffer_size(client->output, (size_t)-1);
+
+	if (ret < 0 || ctx->input->stream_errno != 0) {
+		/* fatal error */
+		return -1;
+	}
+
+	if (!ctx->input->eof)
+		return 0;
+
+	/* finished */
+	i_stream_unref(&ctx->input);
+	return 1;
+}
+
+static bool cmd_urlfetch_continue(struct client_command_context *cmd)
+{
+	struct client *client = cmd->client;
+	struct cmd_urlfetch_context *ctx =
+		(struct cmd_urlfetch_context *)cmd->context;
+	int ret = 1;
+
+	if (cmd->cancel)
+		return cmd_urlfetch_cancel(cmd);
+
+	i_assert(client->output_cmd_lock == NULL ||
+		 client->output_cmd_lock == cmd);
+
+	/* finish a pending literal transfer */
+	ret = cmd_urlfetch_transfer_literal(cmd);
+	if (ret < 0) {
+		ctx->failed = TRUE;
+		cmd_urlfetch_finish(cmd);
+		return TRUE;
+	}
+	if (ret == 0) {
+		/* not finished; apparently output blocked again */
+		return FALSE;
+	}
+
+	if (ctx->extended)
+		client_send_line(client, ")");
+	else
+		client_send_line(client, "");
+
+	if (imap_urlauth_fetch_continue(ctx->ufetch)) {
+		/* waiting for imap urlauth service */
+		cmd->state = CLIENT_COMMAND_STATE_WAIT_EXTERNAL;
+		cmd->func = cmd_urlfetch_cancel;
+		client->output_cmd_lock = NULL;
+
+		/* retrieve next url */
+		return FALSE;
+	}
+
+	/* finished */
+	cmd_urlfetch_finish(cmd);
+	return TRUE;
+}
+
+static int cmd_urlfetch_url_sucess(struct client_command_context *cmd,
+				   struct imap_urlauth_fetch_reply *reply)
+{
+	struct cmd_urlfetch_context *ctx = cmd->context;
+	int ret;
+
+	if ((reply->flags & IMAP_URLAUTH_FETCH_FLAG_EXTENDED) == 0) {
+		/* simple */
+		ctx->extended = FALSE;
+
+		client_send_line(cmd->client, t_strdup_printf(
+			"* URLFETCH %s {%"PRIuUOFF_T"}",
+			reply->url, reply->size));
+		i_assert(reply->size == 0 || reply->input != NULL);
+	} else {
+		string_t *response = t_str_new(256);
+		bool metadata = FALSE;
+
+		/* extended */
+		ctx->extended = TRUE;
+
+		str_append(response, "* URLFETCH ");
+		str_append(response, reply->url);
+		str_append(response, " (");
+		if ((reply->flags & IMAP_URLAUTH_FETCH_FLAG_BODYPARTSTRUCTURE) != 0 &&
+		    reply->bodypartstruct != NULL) {
+			str_append(response, "BODYPARTSTRUCTURE (");
+			str_append(response, reply->bodypartstruct);
+			str_append_c(response, ')');
+			metadata = TRUE;
+		}
+		if ((reply->flags & IMAP_URLAUTH_FETCH_FLAG_BODY) != 0 ||
+		    (reply->flags & IMAP_URLAUTH_FETCH_FLAG_BINARY) != 0) {
+			if (metadata)
+				str_append_c(response, ' ');
+			if ((reply->flags & IMAP_URLAUTH_FETCH_FLAG_BODY) != 0) {
+				str_append(response, "BODY ");
+			} else {
+				str_append(response, "BINARY ");
+				if (reply->binary_has_nuls)
+					str_append_c(response, '~');
+			}
+			str_printfa(response, "{%"PRIuUOFF_T"}", reply->size);
+			i_assert(reply->size == 0 || reply->input != NULL);
+		} else {
+			str_append_c(response, ')');
+			ctx->extended = FALSE;
+		}
+
+		client_send_line(cmd->client, str_c(response));
+	}
+
+	if (reply->input != NULL) {
+		ctx->input = reply->input;
+		i_stream_ref(reply->input);
+
+		ret = cmd_urlfetch_transfer_literal(cmd);
+		if (ret < 0) {
+			ctx->failed = TRUE;
+			cmd_urlfetch_finish(cmd);
+			return -1;
+		}
+		if (ret == 0) {
+			/* not finished; apparently output blocked */
+			cmd->state = CLIENT_COMMAND_STATE_WAIT_OUTPUT;
+			cmd->func = cmd_urlfetch_continue;
+			cmd->client->output_cmd_lock = cmd;
+			return 0;
+		}
+
+		if (ctx->extended)
+			client_send_line(cmd->client, ")");
+		else
+			client_send_line(cmd->client, "");
+	}
+	return 1;
+}
+
+static int
+cmd_urlfetch_url_callback(struct imap_urlauth_fetch_reply *reply,
+			  bool last, void *context)
+{
+	struct client_command_context *cmd = context;
+	struct cmd_urlfetch_context *ctx = cmd->context;
+	int ret;
+
+	if (cmd->cancel)
+		return 1;
+
+	if (reply == NULL) {
+		/* fatal failure */
+		last = TRUE;
+		ctx->failed = TRUE;
+	} else if (reply->succeeded) {
+		/* URL fetch succeeded */
+		if ((ret = cmd_urlfetch_url_sucess(cmd, reply)) <= 0)
+			return ret;
+	} else {
+		/* URL fetch failed */
+		client_send_line(cmd->client,
+			t_strdup_printf("* URLFETCH %s NIL", reply->url));
+		if (reply->error != NULL) {
+			client_send_line(cmd->client, t_strdup_printf(
+				"* NO %s.", reply->error));
+		}
+	}
+
+	if (last && cmd->state == CLIENT_COMMAND_STATE_WAIT_EXTERNAL) {
+		cmd_urlfetch_finish(cmd);
+		client_command_free(&cmd);
+	}
+	return 1;
+}
+
+static int
+cmd_urlfetch_parse_arg(struct client_command_context *cmd,
+		       const struct imap_arg *arg,
+		       struct cmd_urlfetch_url *ufurl_r)
+{
+	enum imap_urlauth_fetch_flags url_flags = 0;
+	const struct imap_arg *params;
+	const char *url_text;
+
+	if (imap_arg_get_list(arg, &params))
+		url_flags |= IMAP_URLAUTH_FETCH_FLAG_EXTENDED;
+	else
+		params = arg;
+
+	/* read url */
+	if (!imap_arg_get_astring(params++, &url_text)) {
+		client_send_command_error(cmd, "Invalid arguments.");
+		return -1;
+	}
+	ufurl_r->url = t_strdup(url_text);
+	if (url_flags == 0)
+		return 0;
+
+	while (!IMAP_ARG_IS_EOL(params)) {
+		const char *fetch_param;
+
+		if (!imap_arg_get_atom(params++, &fetch_param)) {
+			client_send_command_error(cmd,
+				"Invalid URL fetch parameter.");
+			return -1;
+		}
+
+		if (strcasecmp(fetch_param, "BODY") == 0)
+			url_flags |= IMAP_URLAUTH_FETCH_FLAG_BODY;
+		else if (strcasecmp(fetch_param, "BINARY") == 0)
+			url_flags |= IMAP_URLAUTH_FETCH_FLAG_BINARY;
+		else if (strcasecmp(fetch_param, "BODYPARTSTRUCTURE") == 0)
+			url_flags |= IMAP_URLAUTH_FETCH_FLAG_BODYPARTSTRUCTURE;
+		else {
+			client_send_command_error(cmd,
+				t_strdup_printf("Unknown URL fetch parameter: %s",
+						fetch_param));
+			return -1;
+		}
+	}
+
+	if ((url_flags & IMAP_URLAUTH_FETCH_FLAG_BODY) != 0 &&
+	    (url_flags & IMAP_URLAUTH_FETCH_FLAG_BINARY) != 0) {
+		client_send_command_error(cmd,
+			"URL cannot have both BODY and BINARY fetch parameters.");
+		return -1;
+	}
+
+	if (url_flags == IMAP_URLAUTH_FETCH_FLAG_EXTENDED)
+		url_flags |= IMAP_URLAUTH_FETCH_FLAG_BODY;
+	ufurl_r->flags = url_flags;
+	return 0;
+}
+
+bool cmd_urlfetch(struct client_command_context *cmd)
+{
+	struct client *client = cmd->client;
+	struct cmd_urlfetch_context *ctx;
+	ARRAY(struct cmd_urlfetch_url) urls;
+	const struct cmd_urlfetch_url *url;
+	const struct imap_arg *args;
+	struct cmd_urlfetch_url *ufurl;
+	int ret;
+
+	if (client->urlauth_ctx == NULL) {
+		client_send_command_error(cmd, "URLAUTH disabled.");
+		return TRUE;
+	}
+
+	if (!client_read_args(cmd, 0, 0, &args))
+		return FALSE;
+
+	if (IMAP_ARG_IS_EOL(args)) {
+		client_send_command_error(cmd, "Invalid arguments.");
+		return TRUE;
+	}
+	
+	t_array_init(&urls, 32);
+
+	/* parse url arguments and group them per userid */
+	for (; !IMAP_ARG_IS_EOL(args); args++) {
+		ufurl = array_append_space(&urls);
+		if (cmd_urlfetch_parse_arg(cmd, args, ufurl) < 0)
+			return TRUE;
+	}
+	cmd->context = ctx = p_new(cmd->pool, struct cmd_urlfetch_context, 1);
+	cmd->func = cmd_urlfetch_cancel;
+	cmd->state = CLIENT_COMMAND_STATE_WAIT_INPUT;
+
+	ctx->ufetch = imap_urlauth_fetch_init(client->urlauth_ctx,
+					      cmd_urlfetch_url_callback, cmd);
+
+	ret = 1;
+	array_foreach(&urls, url) {
+		ret = imap_urlauth_fetch_url(ctx->ufetch, url->url, url->flags);
+		if (ret < 0) {
+			/* fatal error */
+			ctx->failed = TRUE;
+			break;
+		}
+	}
+
+	if (ret != 0 && cmd->client->output_cmd_lock != cmd) {
+		/* finished */
+		cmd_urlfetch_finish(cmd);
+		return TRUE;
+	}
+
+	if (cmd->client->output_cmd_lock != cmd)
+		cmd->state = CLIENT_COMMAND_STATE_WAIT_EXTERNAL;
+	return FALSE;	
+}
--- a/src/imap/imap-client.c	Sat Sep 15 21:00:54 2012 +0300
+++ b/src/imap/imap-client.c	Sat Sep 15 23:57:08 2012 +0300
@@ -8,10 +8,13 @@
 #include "network.h"
 #include "istream.h"
 #include "ostream.h"
+#include "time-util.h"
 #include "var-expand.h"
 #include "master-service.h"
 #include "imap-resp-code.h"
 #include "imap-util.h"
+#include "imap-urlauth.h"
+#include "mail-error.h"
 #include "mail-namespace.h"
 #include "mail-storage-service.h"
 #include "imap-search.h"
@@ -36,6 +39,23 @@
 	client_destroy(client, "Disconnected for inactivity");
 }
 
+static int client_init_urlauth(struct client *client)
+{
+	struct imap_urlauth_config config;
+
+	memset(&config, 0, sizeof(config));
+	config.dict_uri = client->set->imap_urlauth_dict;
+	config.url_host = client->set->imap_urlauth_host;
+	config.url_port = client->set->imap_urlauth_port;
+	config.socket_path = t_strconcat(client->user->set->base_dir,
+					 "/"IMAP_URLAUTH_SOCKET_NAME, NULL);
+	config.session_id = client->session_id;
+	config.access_user = client->user->anonymous ? NULL :
+		client->user->username;
+
+	return imap_urlauth_init(client->user, &config, &client->urlauth_ctx);
+}
+
 struct client *client_create(int fd_in, int fd_out, const char *session_id,
 			     struct mail_user *user,
 			     struct mail_storage_service_user *service_user,
@@ -109,6 +129,15 @@
 		str_append(client->capability_string, " NOTIFY");
 	}
 
+	if (set->imap_urlauth_dict != NULL && *set->imap_urlauth_dict != '\0') {
+		if (client_init_urlauth(client) == 0 &&
+		    !explicit_capability) {
+			/* Enable URLAUTH capability only when dict is
+			   configured correctly */
+			str_append(client->capability_string, " URLAUTH URLAUTH=BINARY");
+		}
+	}
+
 	ident = mail_user_get_anvil_userip_ident(client->user);
 	if (ident != NULL) {
 		master_service_anvil_send(master_service, t_strconcat(
@@ -138,6 +167,7 @@
 		if (cmd->context == NULL)
 			break;
 		/* fall through */
+	case CLIENT_COMMAND_STATE_WAIT_EXTERNAL:
 	case CLIENT_COMMAND_STATE_WAIT_OUTPUT:
 		cmd->cancel = TRUE;
 		break;
@@ -236,7 +266,8 @@
 	}
 	if (client->notify_ctx != NULL)
 		imap_notify_deinit(&client->notify_ctx);
-
+	if (client->urlauth_ctx != NULL)
+		imap_urlauth_deinit(&client->urlauth_ctx);
 	if (client->anvil_sent) {
 		master_service_anvil_send(master_service, t_strconcat(
 			"DISCONNECT\t", my_pid, "\timap/",
@@ -251,6 +282,8 @@
 		io_remove(&client->io);
 	if (client->to_idle_output != NULL)
 		timeout_remove(&client->to_idle_output);
+	if (client->to_delayed_input != NULL)
+		timeout_remove(&client->to_delayed_input);
 	timeout_remove(&client->to_idle);
 
 	i_stream_destroy(&client->input);
@@ -393,6 +426,12 @@
 	cmd->state = CLIENT_COMMAND_STATE_DONE;
 }
 
+void client_send_internal_error(struct client_command_context *cmd)
+{
+	client_send_tagline(cmd,
+		t_strflocaltime("NO "MAIL_ERRSTR_CRITICAL_MSG_STAMP, ioloop_time));
+}
+
 bool client_read_args(struct client_command_context *cmd, unsigned int count,
 		      unsigned int flags, const struct imap_arg **args_r)
 {
@@ -574,6 +613,7 @@
 {
 	struct client_command_context *cmd = *_cmd;
 	struct client *client = cmd->client;
+	enum client_command_state state = cmd->state;
 
 	*_cmd = NULL;
 
@@ -616,6 +656,13 @@
 	}
 	imap_client_notify_command_freed(client);
 	imap_refresh_proctitle();
+
+	/* if command finished from external event, check input for more
+	   unhandled commands since we may not be executing from client_input
+	   or client_output. */
+	if (state == CLIENT_COMMAND_STATE_WAIT_EXTERNAL &&
+	    !client->disconnected && client->to_delayed_input == NULL)
+		client->to_delayed_input = timeout_add(0, client_input, client);
 }
 
 static void client_add_missing_io(struct client *client)
@@ -850,6 +897,9 @@
 	client->last_input = ioloop_time;
 	timeout_reset(client->to_idle);
 
+	if (client->to_delayed_input != NULL)
+		timeout_remove(&client->to_delayed_input);
+
 	bytes = i_stream_read(client->input);
 	if (bytes == -1) {
 		/* disconnected */
--- a/src/imap/imap-client.h	Sat Sep 15 21:00:54 2012 +0300
+++ b/src/imap/imap-client.h	Sat Sep 15 23:57:08 2012 +0300
@@ -14,6 +14,7 @@
 struct mail_storage;
 struct imap_parser;
 struct imap_arg;
+struct imap_urlauth_context;
 
 struct mailbox_keywords {
 	/* All keyword names. The array itself exists in mail_index.
@@ -39,6 +40,8 @@
 	CLIENT_COMMAND_STATE_WAIT_INPUT,
 	/* Waiting to be able to send more output */
 	CLIENT_COMMAND_STATE_WAIT_OUTPUT,
+	/* Waiting for external interaction */
+	CLIENT_COMMAND_STATE_WAIT_EXTERNAL,
 	/* Wait for other commands to finish execution */
 	CLIENT_COMMAND_STATE_WAIT_UNAMBIGUITY,
 	/* Waiting for other commands to finish so we can sync */
@@ -96,7 +99,7 @@
 	struct io *io;
 	struct istream *input;
 	struct ostream *output;
-	struct timeout *to_idle, *to_idle_output;
+	struct timeout *to_idle, *to_idle_output, *to_delayed_input;
 
 	pool_t pool;
 	struct mail_storage_service_user *service_user;
@@ -138,6 +141,9 @@
 	/* command changing the mailbox */
 	struct client_command_context *mailbox_change_lock;
 
+	/* IMAP URLAUTH context (RFC4467) */
+	struct imap_urlauth_context *urlauth_ctx;	
+
 	/* Module-specific contexts. */
 	ARRAY(union imap_module_context *) module_contexts;
 
@@ -202,6 +208,10 @@
 void client_send_command_error(struct client_command_context *cmd,
 			       const char *msg);
 
+/* Send a NO command reply with the default internal error message to client
+   via client_send_tagline(). */
+void client_send_internal_error(struct client_command_context *cmd);
+
 /* Read a number of arguments. Returns TRUE if everything was read or
    FALSE if either needs more data or error occurred. */
 bool client_read_args(struct client_command_context *cmd, unsigned int count,
--- a/src/imap/imap-commands.c	Sat Sep 15 21:00:54 2012 +0300
+++ b/src/imap/imap-commands.c	Sat Sep 15 23:57:08 2012 +0300
@@ -47,6 +47,7 @@
 #define IMAP4REV1_COMMANDS_COUNT N_ELEMENTS(imap4rev1_commands)
 
 static const struct command imap_ext_commands[] = {
+	/* IMAP extensions: */
 	{ "CANCELUPDATE",	cmd_cancelupdate,0 },
 	{ "ENABLE",		cmd_enable,      0 },
 	{ "ID",			cmd_id,          0 },
@@ -64,7 +65,11 @@
 	{ "UID THREAD",		cmd_thread,      COMMAND_FLAG_BREAKS_SEQS },
 	{ "UNSELECT",		cmd_unselect,    COMMAND_FLAG_BREAKS_MAILBOX },
 	{ "X-CANCEL",		cmd_x_cancel,    0 },
-	{ "XLIST",		cmd_list,        0 }
+	{ "XLIST",		cmd_list,        0 },
+	/* IMAP URLAUTH (RFC4467): */
+	{ "GENURLAUTH",		cmd_genurlauth,  0 },
+	{ "RESETKEY",		cmd_resetkey,    0 },
+	{ "URLFETCH",		cmd_urlfetch,    0 }
 };
 #define IMAP_EXT_COMMANDS_COUNT N_ELEMENTS(imap_ext_commands)
 
--- a/src/imap/imap-commands.h	Sat Sep 15 21:00:54 2012 +0300
+++ b/src/imap/imap-commands.h	Sat Sep 15 23:57:08 2012 +0300
@@ -115,6 +115,11 @@
 bool cmd_unselect(struct client_command_context *cmd);
 bool cmd_x_cancel(struct client_command_context *cmd);
 
+/* IMAP URLAUTH (RFC4467): */
+bool cmd_genurlauth(struct client_command_context *cmd);
+bool cmd_resetkey(struct client_command_context *cmd);
+bool cmd_urlfetch(struct client_command_context *cmd);
+
 /* private: */
 bool cmd_list_full(struct client_command_context *cmd, bool lsub);
 bool cmd_select_full(struct client_command_context *cmd, bool readonly);
--- a/src/imap/imap-settings.c	Sat Sep 15 21:00:54 2012 +0300
+++ b/src/imap/imap-settings.c	Sat Sep 15 23:57:08 2012 +0300
@@ -70,6 +70,10 @@
 	DEF(SET_STR, imap_id_send),
 	DEF(SET_STR, imap_id_log),
 
+	DEF(SET_STR_VARS, imap_urlauth_dict),
+	DEF(SET_STR, imap_urlauth_host),
+	DEF(SET_UINT, imap_urlauth_port),
+
 	SETTING_DEFINE_LIST_END
 };
 
@@ -85,7 +89,11 @@
 	.imap_client_workarounds = "",
 	.imap_logout_format = "in=%i out=%o",
 	.imap_id_send = "",
-	.imap_id_log = ""
+	.imap_id_log = "",
+
+	.imap_urlauth_dict = "",
+	.imap_urlauth_host = "",
+	.imap_urlauth_port = 143
 };
 
 static const struct setting_parser_info *imap_setting_dependencies[] = {
--- a/src/imap/imap-settings.h	Sat Sep 15 21:00:54 2012 +0300
+++ b/src/imap/imap-settings.h	Sat Sep 15 23:57:08 2012 +0300
@@ -23,6 +23,11 @@
 	const char *imap_id_send;
 	const char *imap_id_log;
 
+	/* imap urlauth: */
+	const char *imap_urlauth_dict;
+	const char *imap_urlauth_host;
+	unsigned int imap_urlauth_port;
+
 	enum imap_client_workarounds parsed_workarounds;
 };
 
--- a/src/imap/main.c	Sat Sep 15 21:00:54 2012 +0300
+++ b/src/imap/main.c	Sat Sep 15 23:57:08 2012 +0300
@@ -10,6 +10,7 @@
 #include "process-title.h"
 #include "restrict-access.h"
 #include "fd-close-on-exec.h"
+#include "settings-parser.h"
 #include "master-interface.h"
 #include "master-service.h"
 #include "master-login.h"
@@ -196,7 +197,7 @@
 	struct mail_storage_service_user *user;
 	struct mail_user *mail_user;
 	struct client *client;
-	const struct imap_settings *set;
+	struct imap_settings *set;
 	enum mail_auth_request_flags flags;
 
 	if (mail_storage_service_lookup_next(storage_service, input,
@@ -208,6 +209,9 @@
 	if (set->verbose_proctitle)
 		verbose_proctitle = TRUE;
 
+	settings_var_expand(&imap_setting_parser_info, set, mail_user->pool,
+			    mail_user_var_expand_table(mail_user));
+
 	client = client_create(fd_in, fd_out, input->session_id,
 			       mail_user, user, set);
 	T_BEGIN {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-imap-urlauth/Makefile.am	Sat Sep 15 23:57:08 2012 +0300
@@ -0,0 +1,28 @@
+noinst_LTLIBRARIES = libimap-urlauth.la
+
+AM_CPPFLAGS = \
+	-I$(top_srcdir)/src/lib \
+	-I$(top_srcdir)/src/lib-test \
+	-I$(top_srcdir)/src/lib-charset \
+	-I$(top_srcdir)/src/lib-dict \
+	-I$(top_srcdir)/src/lib-mail \
+	-I$(top_srcdir)/src/lib-storage \
+	-I$(top_srcdir)/src/lib-imap \
+	-I$(top_srcdir)/src/lib-imap-storage
+
+libimap_urlauth_la_SOURCES = \
+	imap-urlauth.c \
+	imap-urlauth-fetch.c \
+	imap-urlauth-backend.c \
+	imap-urlauth-connection.c
+
+headers = \
+	imap-urlauth.h \
+	imap-urlauth-private.h \
+	imap-urlauth-fetch.h \
+	imap-urlauth-backend.h \
+	imap-urlauth-connection.h
+
+pkginc_libdir=$(pkgincludedir)
+pkginc_lib_HEADERS = $(headers)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-imap-urlauth/imap-urlauth-backend.c	Sat Sep 15 23:57:08 2012 +0300
@@ -0,0 +1,164 @@
+/* Copyright (c) 2012 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "buffer.h"
+#include "hex-binary.h"
+#include "randgen.h"
+#include "dict.h"
+#include "mail-user.h"
+#include "mail-storage.h"
+#include "imap-urlauth-private.h"
+#include "imap-urlauth-backend.h"
+
+#define IMAP_URLAUTH_PATH DICT_PATH_PRIVATE"imap-urlauth/"
+
+struct imap_urlauth_backend {
+	struct mail_user *user;
+	struct dict *dict;
+};
+
+int imap_urlauth_backend_create(struct mail_user *user, const char *dict_uri,
+				struct imap_urlauth_backend **backend_r)
+{
+	struct imap_urlauth_backend *backend;
+	struct dict *dict;
+
+	if (user->mail_debug)
+		i_debug("imap-urlauth backend: opening backend dict URI %s", dict_uri);
+
+	dict = dict_init(dict_uri, DICT_DATA_TYPE_STRING,
+			 user->username, user->set->base_dir);
+	if (dict == NULL)
+		return -1;
+
+	backend = i_new(struct imap_urlauth_backend, 1);
+	backend->user = user;
+	backend->dict = dict;
+
+	random_init();
+	*backend_r = backend;
+	return 0;
+}
+
+void imap_urlauth_backend_destroy(struct imap_urlauth_backend **_backend)
+{
+	struct imap_urlauth_backend *backend = *_backend;
+
+	*_backend = NULL;
+
+	if (backend->dict != NULL) {
+		(void)dict_wait(backend->dict);
+		dict_deinit(&backend->dict);
+	}
+	i_free(backend);
+	random_deinit();
+}
+
+static int
+imap_urlauth_backend_set_key(struct imap_urlauth_backend *backend,
+			     const char *path, const char *mailbox_key)
+{
+	struct dict_transaction_context *dtrans;
+
+	dtrans = dict_transaction_begin(backend->dict);
+	dict_set(dtrans, path, mailbox_key);
+	return dict_transaction_commit(&dtrans) < 0 ? -1 : 1;
+}
+
+static int
+imap_urlauth_backend_reset_key(struct imap_urlauth_backend *backend,
+			       const char *path)
+{
+	struct dict_transaction_context *dtrans;
+
+	dtrans = dict_transaction_begin(backend->dict);
+	dict_unset(dtrans, path);
+	return dict_transaction_commit(&dtrans) < 0 ? -1 : 1;
+}
+
+static int
+imap_urlauth_backend_get_key(struct imap_urlauth_backend *backend,
+			     const char *path, const char **mailbox_key_r)
+{
+	return dict_lookup(backend->dict, pool_datastack_create(), path,
+			   mailbox_key_r);
+}
+
+int imap_urlauth_backend_get_mailbox_key(struct imap_urlauth_backend *backend,
+					 struct mailbox *box, bool create,
+					 unsigned char mailbox_key[IMAP_URLAUTH_KEY_LEN])
+{
+	const char *path, *mailbox_key_hex = NULL;
+	const char *mailbox = mailbox_get_vname(box);
+	buffer_t key_buf;
+	int ret;
+
+	path = t_strconcat(IMAP_URLAUTH_PATH, dict_escape_string(mailbox), NULL);
+	if ((ret = imap_urlauth_backend_get_key(backend, path,
+						&mailbox_key_hex)) < 0)
+		return -1;
+
+	if (backend->user->mail_debug) {
+		i_debug("imap-urlauth backend: %skey found for mailbox %s at %s",
+			(ret > 0 ? "" : "no "), mailbox, path);
+	}
+
+	if (ret == 0) {
+		if (!create)
+			return 0;
+
+		/* create new key */
+		random_fill(mailbox_key, IMAP_URLAUTH_KEY_LEN);
+		mailbox_key_hex = binary_to_hex(mailbox_key,
+						IMAP_URLAUTH_KEY_LEN);
+		if ((ret = imap_urlauth_backend_set_key(backend, path,
+							mailbox_key_hex)) < 0)
+			return -1;
+		if (backend->user->mail_debug) {
+			i_debug("imap-urlauth backend: created key for mailbox %s at %s",
+				mailbox, path);
+		}
+	} else {
+		/* read existing key */
+		buffer_create_from_data(&key_buf, mailbox_key,
+					IMAP_URLAUTH_KEY_LEN);
+		if (strlen(mailbox_key_hex) != 2*IMAP_URLAUTH_KEY_LEN ||
+		    hex_to_binary(mailbox_key_hex, &key_buf) < 0 ||
+		    key_buf.used != IMAP_URLAUTH_KEY_LEN) {
+			i_error("imap-urlauth backend: key found for mailbox %s at %s is invalid",
+				mailbox, path);
+			return -1;
+		}
+		memcpy(mailbox_key, key_buf.data, IMAP_URLAUTH_KEY_LEN);
+	}
+	return 1;
+}
+
+int imap_urlauth_backend_reset_mailbox_key(struct imap_urlauth_backend *backend,
+					   struct mailbox *box)
+{
+	const char *path, *mailbox = mailbox_get_vname(box);
+
+	path = t_strconcat(IMAP_URLAUTH_PATH, dict_escape_string(mailbox), NULL);
+	return imap_urlauth_backend_reset_key(backend, path) < 0 ? -1 : 1;
+}
+
+int imap_urlauth_backend_reset_all_keys(struct imap_urlauth_backend *backend)
+{ 
+	struct dict_transaction_context *dtrans;
+	struct dict_iterate_context *diter;
+	const char *path, *value;
+	int ret = 1;
+
+	dtrans = dict_transaction_begin(backend->dict);
+	diter = dict_iterate_init(backend->dict, IMAP_URLAUTH_PATH,
+				  DICT_ITERATE_FLAG_RECURSE);
+	while (dict_iterate(diter, &path, &value))
+		dict_unset(dtrans, path);
+	
+	if (dict_iterate_deinit(&diter) < 0)
+		ret = -1;
+	if (dict_transaction_commit(&dtrans) < 0)
+		ret = -1;
+	return ret;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-imap-urlauth/imap-urlauth-backend.h	Sat Sep 15 23:57:08 2012 +0300
@@ -0,0 +1,20 @@
+#ifndef IMAP_URLAUTH_BACKEND_H
+#define IMAP_URLAUTH_BACKEND_H
+
+#define IMAP_URLAUTH_KEY_LEN 64
+
+struct imap_urlauth_backend;
+
+int imap_urlauth_backend_create(struct mail_user *user, const char *dict_uri,
+				struct imap_urlauth_backend **backend_r);
+void imap_urlauth_backend_destroy(struct imap_urlauth_backend **backend);
+
+int imap_urlauth_backend_get_mailbox_key(struct imap_urlauth_backend *backend,
+					 struct mailbox *box, bool create,
+					 unsigned char mailbox_key[IMAP_URLAUTH_KEY_LEN]);
+int imap_urlauth_backend_reset_mailbox_key(struct imap_urlauth_backend *backend,
+					   struct mailbox *box);
+int imap_urlauth_backend_reset_all_keys(struct imap_urlauth_backend *backend);
+
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-imap-urlauth/imap-urlauth-connection.c	Sat Sep 15 23:57:08 2012 +0300
@@ -0,0 +1,1016 @@
+/* Copyright (c) 2012 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "llist.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "strescape.h"
+#include "ioloop.h"
+#include "safe-mkstemp.h"
+#include "hostpid.h"
+#include "network.h"
+#include "istream.h"
+#include "ostream.h"
+#include "write-full.h"
+#include "array.h"
+#include "aqueue.h"
+#include "mail-user.h"
+#include "imap-urlauth-fetch.h"
+
+#include "imap-urlauth-connection.h"
+
+enum imap_urlauth_state {
+	IMAP_URLAUTH_STATE_DISCONNECTED = 0,
+	IMAP_URLAUTH_STATE_AUTHENTICATING,
+	IMAP_URLAUTH_STATE_AUTHENTICATED,
+	IMAP_URLAUTH_STATE_SELECTING_TARGET,
+	IMAP_URLAUTH_STATE_UNSELECTING_TARGET,
+	IMAP_URLAUTH_STATE_READY,
+	IMAP_URLAUTH_STATE_REQUEST_PENDING,
+	IMAP_URLAUTH_STATE_REQUEST_WAIT,
+};
+
+struct imap_urlauth_request {
+	struct imap_urlauth_target *target;
+	struct imap_urlauth_request *prev, *next;
+	
+	char *url;
+	enum imap_urlauth_fetch_flags flags;
+
+	char *bodypartstruct;
+
+	imap_urlauth_request_callback_t *callback;
+	void *context;
+
+	unsigned int binary_has_nuls;
+};
+
+struct imap_urlauth_target {
+	struct imap_urlauth_target *prev, *next;
+
+	char *userid;
+
+	struct imap_urlauth_request *requests_head, *requests_tail;
+};
+
+struct imap_urlauth_connection {
+	int refcount;
+
+	char *path, *session_id;
+	struct mail_user *user;
+
+	int fd;
+	struct istream *input;
+	struct ostream *output;
+	struct io *io;
+
+	struct timeout *to_reconnect, *to_idle, *to_response;
+	time_t last_reconnect;
+	unsigned int reconnect_attempts;
+	unsigned int idle_timeout_msecs;
+
+	char *literal_temp_path;
+	int literal_fd;
+	buffer_t *literal_buf;
+	uoff_t literal_size, literal_bytes_left;
+
+	enum imap_urlauth_state state;
+
+	/* userid => target struct */
+	struct imap_urlauth_target *targets_head, *targets_tail;
+
+	unsigned int reading_literal:1;
+};
+
+#define IMAP_URLAUTH_RECONNECT_MIN_SECS 2
+#define IMAP_URLAUTH_RECONNECT_MAX_ATTEMPTS 3
+
+#define IMAP_URLAUTH_RESPONSE_TIMEOUT_MSECS 2*60*1000
+
+#define IMAP_URLAUTH_HANDSHAKE "VERSION\timap-urlauth\t1\t0\n"
+
+#define IMAP_URLAUTH_MAX_INLINE_LITERAL_SIZE (1024*32)
+
+static void imap_urlauth_connection_disconnect
+	(struct imap_urlauth_connection *conn, const char *reason);
+static void imap_urlauth_connection_abort
+	(struct imap_urlauth_connection *conn, const char *reason);
+static void imap_urlauth_connection_reconnect
+	(struct imap_urlauth_connection *conn);
+static void imap_urlauth_connection_idle_disconnect
+	(struct imap_urlauth_connection *conn);
+static void imap_urlauth_connection_timeout_abort
+	(struct imap_urlauth_connection *conn);
+static void imap_urlauth_connection_fail
+	(struct imap_urlauth_connection *conn);
+
+struct imap_urlauth_connection *
+imap_urlauth_connection_init(const char *path, struct mail_user *user,
+			     const char *session_id,
+			     unsigned int idle_timeout_msecs)
+{
+	struct imap_urlauth_connection *conn;
+
+	conn = i_new(struct imap_urlauth_connection, 1);
+	conn->refcount = 1;
+	conn->path = i_strdup(path);
+	if (session_id != NULL)
+		conn->session_id = i_strdup(session_id);
+	conn->user = user;
+	conn->fd = -1;
+	conn->literal_fd = -1;
+	conn->idle_timeout_msecs = idle_timeout_msecs;
+	return conn;
+}
+
+void imap_urlauth_connection_deinit(struct imap_urlauth_connection **_conn)
+{
+	struct imap_urlauth_connection *conn = *_conn;
+
+	*_conn = NULL;
+
+	imap_urlauth_connection_abort(conn, NULL);
+
+	i_free(conn->path);
+	if (conn->session_id != NULL)
+		i_free(conn->session_id);
+
+	i_assert(conn->to_idle == NULL);
+	i_assert(conn->to_reconnect == NULL);
+	i_assert(conn->to_response == NULL);
+
+	i_free(conn);
+}
+
+static void
+imap_urlauth_stop_response_timeout(struct imap_urlauth_connection *conn)
+{
+	if (conn->to_response != NULL)
+		timeout_remove(&conn->to_response);
+}
+
+static void
+imap_urlauth_start_response_timeout(struct imap_urlauth_connection *conn)
+{
+	imap_urlauth_stop_response_timeout(conn);
+	conn->to_response = timeout_add(IMAP_URLAUTH_RESPONSE_TIMEOUT_MSECS,
+		imap_urlauth_connection_timeout_abort, conn);
+}
+
+static struct imap_urlauth_target *
+imap_urlauth_connection_get_target(struct imap_urlauth_connection *conn,
+				   const char *target_user)
+{
+	struct imap_urlauth_target *target = conn->targets_head;
+
+	while (target != NULL) {
+		if (strcmp(target->userid, target_user) == 0)
+			return target;
+		target = target->next;
+	}
+
+	target = i_new(struct imap_urlauth_target, 1);
+	target->userid = i_strdup(target_user);
+	DLLIST2_APPEND(&conn->targets_head, &conn->targets_tail, target);
+	return target;
+}
+
+static void
+imap_urlauth_target_free(struct imap_urlauth_connection *conn,
+			 struct imap_urlauth_target *target)
+{
+	DLLIST2_REMOVE(&conn->targets_head, &conn->targets_tail, target);
+	i_free(target->userid);
+	i_free(target);
+}
+
+static void
+imap_urlauth_connection_select_target(struct imap_urlauth_connection *conn)
+{
+	struct imap_urlauth_target *target = conn->targets_head;
+	const char *cmd;
+
+	if (target == NULL || conn->state != IMAP_URLAUTH_STATE_AUTHENTICATED)
+		return;
+
+	if (conn->user->mail_debug)
+		i_debug("imap-urlauth: Selecting target user `%s'", target->userid);
+
+	conn->state = IMAP_URLAUTH_STATE_SELECTING_TARGET;
+	cmd = t_strdup_printf("USER\t%s\n", str_tabescape(target->userid));
+	if (o_stream_send_str(conn->output, cmd) < 0) {
+		i_warning("Error sending USER request to imap-urlauth server: %m");
+		imap_urlauth_connection_fail(conn);
+	}
+
+	imap_urlauth_start_response_timeout(conn);
+}
+
+static void
+imap_urlauth_connection_send_request(struct imap_urlauth_connection *conn)
+{
+	struct imap_urlauth_request *urlreq;
+	string_t *cmd;
+
+	if (conn->targets_head == NULL ||
+	    (conn->targets_head->requests_head == NULL &&
+	     conn->targets_head->next == NULL &&
+	     conn->state == IMAP_URLAUTH_STATE_READY)) {
+		if (conn->user->mail_debug)
+			i_debug("imap-urlauth: No more requests pending; scheduling disconnect");
+		if (conn->to_idle != NULL)
+			timeout_remove(&conn->to_idle);
+		if (conn->idle_timeout_msecs > 0) {
+			conn->to_idle =	timeout_add(conn->idle_timeout_msecs,
+				imap_urlauth_connection_idle_disconnect, conn);
+		}
+		return;
+	}
+
+	if (conn->state == IMAP_URLAUTH_STATE_AUTHENTICATED) {
+		imap_urlauth_connection_select_target(conn);
+		return;
+	}
+
+	if (conn->state != IMAP_URLAUTH_STATE_READY)
+		return;
+
+	urlreq = conn->targets_head->requests_head;
+	if (urlreq == NULL) {
+		if (conn->targets_head->next == NULL)
+			return;
+		
+		conn->state = IMAP_URLAUTH_STATE_UNSELECTING_TARGET;
+		imap_urlauth_target_free(conn, conn->targets_head);
+
+		if (o_stream_send_str(conn->output, "END\n") < 0) {
+			i_warning("Error sending END request to imap-urlauth server: %m");
+			imap_urlauth_connection_fail(conn);
+		}
+		imap_urlauth_start_response_timeout(conn);
+		return;
+	}	
+
+	if (conn->user->mail_debug)
+		i_debug("imap-urlauth: Fetching URL `%s'", urlreq->url);
+
+	cmd = t_str_new(128);
+	str_append(cmd, "URL\t");
+	str_tabescape_write(cmd, urlreq->url);
+	if ((urlreq->flags & IMAP_URLAUTH_FETCH_FLAG_BODYPARTSTRUCTURE) != 0)
+		str_append(cmd, "\tbpstruct");
+	if ((urlreq->flags & IMAP_URLAUTH_FETCH_FLAG_BINARY) != 0)
+		str_append(cmd, "\tbinary");
+	else if ((urlreq->flags & IMAP_URLAUTH_FETCH_FLAG_BODY) != 0)
+		str_append(cmd, "\tbody");
+	str_append_c(cmd, '\n');
+
+	conn->state = IMAP_URLAUTH_STATE_REQUEST_PENDING;
+	if (o_stream_send(conn->output, str_data(cmd), str_len(cmd)) < 0) {
+		i_warning("Error sending URL request to imap-urlauth server: %m");
+		imap_urlauth_connection_fail(conn);
+	}
+
+	imap_urlauth_start_response_timeout(conn);
+}
+
+struct imap_urlauth_request *
+imap_urlauth_request_new(struct imap_urlauth_connection *conn,
+			 const char *target_user, const char *url,
+			 enum imap_urlauth_fetch_flags flags,
+			 imap_urlauth_request_callback_t *callback,
+			 void *context)
+{
+	struct imap_urlauth_request *urlreq;
+	struct imap_urlauth_target *target;
+
+	target = imap_urlauth_connection_get_target(conn, target_user);
+	
+	urlreq = i_new(struct imap_urlauth_request, 1);
+	urlreq->url = i_strdup(url);
+	urlreq->flags = flags;
+	urlreq->target = target;
+	urlreq->callback = callback;
+	urlreq->context = context;
+
+	DLLIST2_APPEND(&target->requests_head, &target->requests_tail, urlreq);
+
+	if (conn->to_idle != NULL)
+		timeout_remove(&conn->to_idle);
+	
+	if (conn->user->mail_debug) {
+		i_debug("imap-urlauth: Added request for URL `%s' from user `%s'",
+			url, target_user);
+	}
+
+	imap_urlauth_connection_send_request(conn);
+	return urlreq;
+}
+
+static void imap_urlauth_request_free(struct imap_urlauth_request *urlreq)
+{
+	struct imap_urlauth_target *target = urlreq->target;
+
+	DLLIST2_REMOVE(&target->requests_head, &target->requests_tail, urlreq);
+	i_free(urlreq->url);
+	i_free(urlreq->bodypartstruct);
+	i_free(urlreq);
+}
+
+void imap_urlauth_request_abort(struct imap_urlauth_connection *conn,
+				struct imap_urlauth_request *urlreq)
+{
+	if (urlreq->callback != NULL) {
+		T_BEGIN {
+			urlreq->callback(NULL, urlreq->context);
+		} T_END;
+	}
+
+	if (conn->state == IMAP_URLAUTH_STATE_REQUEST_PENDING &&
+	    conn->targets_head != NULL &&
+	    conn->targets_head->requests_head == urlreq) {
+		/* cannot just drop pending request without breaking
+		   protocol state */
+		urlreq->callback = NULL;
+		return;
+	}
+	imap_urlauth_request_free(urlreq);
+}
+
+static void
+imap_urlauth_request_fail(struct imap_urlauth_connection *conn,
+			  struct imap_urlauth_request *urlreq,
+			  const char *error)
+{
+	struct imap_urlauth_fetch_reply reply;
+
+	memset(&reply, 0, sizeof(reply));
+	reply.url = urlreq->url;
+	reply.flags = urlreq->flags;
+	reply.succeeded = FALSE;
+	reply.error = error;
+
+	if (urlreq->callback != NULL) T_BEGIN {
+		(void)urlreq->callback(&reply, urlreq->context);
+	} T_END;
+
+	if (conn->state == IMAP_URLAUTH_STATE_REQUEST_PENDING &&
+	    conn->targets_head != NULL &&
+	    conn->targets_head->requests_head == urlreq) {
+		/* cannot just drop pending request without breaking protocol state */
+		urlreq->callback = NULL;
+		return;
+	}
+
+	imap_urlauth_request_free(urlreq);
+}
+
+static void
+imap_urlauth_target_abort(struct imap_urlauth_connection *conn,
+			  struct imap_urlauth_target *target)
+{
+	struct imap_urlauth_request *urlreq, *next;
+
+	urlreq = target->requests_head;
+	while (urlreq != NULL) {
+		next = urlreq->next;
+		imap_urlauth_request_abort(conn, urlreq);
+		urlreq = next;
+	}
+
+	imap_urlauth_target_free(conn, target);
+}
+
+static void
+imap_urlauth_target_fail(struct imap_urlauth_connection *conn,
+			 struct imap_urlauth_target *target, const char *error)
+{
+	struct imap_urlauth_request *urlreq, *next;
+
+	urlreq = target->requests_head;
+	while (urlreq != NULL) {
+		next = urlreq->next;
+		imap_urlauth_request_fail(conn, urlreq, error);
+		urlreq = next;
+	}
+
+	imap_urlauth_target_free(conn, target);
+}
+
+static void
+imap_urlauth_target_abort_by_context(struct imap_urlauth_connection *conn,
+				     struct imap_urlauth_target *target,
+				     void *context)
+{
+	struct imap_urlauth_request *urlreq, *next;
+
+	/* abort all matching requests */
+	urlreq = target->requests_head;
+	while (urlreq != NULL) {
+		next = urlreq->next;
+		if (urlreq->context == context)
+			imap_urlauth_request_abort(conn, urlreq);
+		urlreq = next;
+	}
+
+	if (target->requests_head == NULL)
+		imap_urlauth_target_free(conn, target);
+}
+
+static void
+imap_urlauth_connection_abort(struct imap_urlauth_connection *conn,
+			      const char *reason)
+{
+	struct imap_urlauth_target *target, *next;
+	
+	if (reason == NULL)
+		reason = "Aborting due to error";
+	imap_urlauth_connection_disconnect(conn, reason);
+
+	/* abort all requests */
+	target = conn->targets_head;
+	while (target != NULL) {
+		next = target->next;
+		imap_urlauth_target_abort(conn, target);
+		target = next;
+	}
+}
+
+void imap_urlauth_request_abort_by_context(struct imap_urlauth_connection *conn,
+					   void *context)
+{
+	struct imap_urlauth_target *target, *next;
+
+	/* abort all matching requests */
+	target = conn->targets_head;
+	while (target != NULL) {
+		next = target->next;
+		imap_urlauth_target_abort_by_context(conn, target, context);
+		target = next;
+	}
+}
+
+static void imap_urlauth_connection_fail(struct imap_urlauth_connection *conn)
+{
+	if (conn->reconnect_attempts > IMAP_URLAUTH_RECONNECT_MAX_ATTEMPTS) {
+		imap_urlauth_connection_abort(conn,
+			"Connection failed and connection attempts exhausted");
+	} else {
+		imap_urlauth_connection_reconnect(conn);
+	}
+}
+
+static int
+imap_urlauth_connection_create_temp_fd(struct imap_urlauth_connection *conn,
+				       const char **path_r)
+{
+	string_t *path;
+	int fd;
+
+	path = t_str_new(128);
+	mail_user_set_get_temp_prefix(path, conn->user->set);
+	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));
+		i_close_fd(&fd);
+		return -1;
+	}
+
+	*path_r = str_c(path);
+	return fd;
+}
+
+static int
+imap_urlauth_connection_read_literal_init(struct imap_urlauth_connection *conn,
+					  uoff_t size)
+{
+	const char *path;
+
+	i_assert(conn->literal_fd == -1 && conn->literal_buf == NULL);
+
+	if (size <= IMAP_URLAUTH_MAX_INLINE_LITERAL_SIZE) {
+		/* read the literal directly */
+		if (size > 0) {
+			conn->literal_buf =
+				buffer_create_dynamic(default_pool, size);
+		}
+	} else {
+		/* read it into a file */
+		conn->literal_fd =
+			imap_urlauth_connection_create_temp_fd(conn, &path);
+		if (conn->literal_fd == -1)
+			return -1;
+		conn->literal_temp_path = i_strdup(path);
+	}
+
+	conn->literal_size = size;
+	conn->literal_bytes_left = size;
+	conn->reading_literal = TRUE;
+	return 1;
+}
+
+void imap_urlauth_connection_continue(struct imap_urlauth_connection *conn)
+{
+	i_assert(conn->targets_head != NULL);
+	i_assert(conn->targets_head->requests_head != NULL);
+
+	if (conn->state != IMAP_URLAUTH_STATE_REQUEST_WAIT)
+		return;
+
+	conn->state = IMAP_URLAUTH_STATE_READY;
+	imap_urlauth_request_free(conn->targets_head->requests_head);
+
+	imap_urlauth_connection_send_request(conn);
+}
+
+static int
+imap_urlauth_connection_read_literal_data(struct imap_urlauth_connection *conn)
+{
+	const unsigned char *data;
+	size_t size;
+
+	/* read data */
+	data = i_stream_get_data(conn->input, &size);
+	if (size > conn->literal_bytes_left)
+		size = conn->literal_bytes_left;
+
+	/* write to buffer or file */
+	if (size > 0) {
+		if (conn->literal_fd >= 0) {
+			if (write_full(conn->literal_fd, data, size) < 0) {
+				i_error("imap-urlauth: write(%s) failed: %m",
+					conn->literal_temp_path);
+				return -1;
+			}
+		} else {
+			i_assert(conn->literal_buf != NULL);
+			buffer_append(conn->literal_buf, data, size);
+		}
+		i_stream_skip(conn->input, size);
+		conn->literal_bytes_left -= size;
+	}
+
+	/* exit if not finished */
+	if (conn->literal_bytes_left > 0)
+		return 0;
+
+	/* read LF guard */
+	data = i_stream_get_data(conn->input, &size);
+	if (size < 1)
+		return 0;
+
+	/* check LF guard */
+	if (data[0] != '\n') {
+		i_error("imap-urlauth: no LF at end of literal (found 0x%x)",
+			data[0]);
+		return -1;
+	}
+	i_stream_skip(conn->input, 1);
+	return 1;
+}
+
+static void literal_stream_destroy(buffer_t *buffer)
+{
+	buffer_free(&buffer);
+}
+
+static int
+imap_urlauth_fetch_reply_set_literal_stream(struct imap_urlauth_connection *conn,
+					    struct imap_urlauth_fetch_reply *reply)
+{
+	const unsigned char *data;
+	size_t size;
+	uoff_t fd_size;
+
+	if (conn->literal_fd != -1) {
+		reply->input = i_stream_create_fd(conn->literal_fd,
+						  (size_t)-1, TRUE);
+		if (i_stream_get_size(reply->input, TRUE, &fd_size) < 1 ||
+		    fd_size != conn->literal_size) {
+			i_stream_unref(&reply->input);
+			i_error("imap-urlauth: Failed to obtain proper size from literal stream");
+			imap_urlauth_connection_abort(conn,
+				"Failed during literal transfer");
+			return -1;
+		}
+	} else {
+		data = buffer_get_data(conn->literal_buf, &size);
+		i_assert(size == conn->literal_size);
+		reply->input = i_stream_create_from_data(data, size);
+		i_stream_set_destroy_callback(reply->input,
+					      literal_stream_destroy,
+					      conn->literal_buf);
+	}
+	reply->size = conn->literal_size;
+	return 0;
+}
+
+static int
+imap_urlauth_connection_read_literal(struct imap_urlauth_connection *conn)
+{
+	struct imap_urlauth_request *urlreq = conn->targets_head->requests_head;
+	struct imap_urlauth_fetch_reply reply;
+	int ret;
+
+	i_assert(conn->reading_literal);
+	i_assert(urlreq != NULL);
+
+	if (conn->literal_size > 0) {
+		ret = imap_urlauth_connection_read_literal_data(conn);
+		if (ret <= 0)
+			return ret;
+	}
+	i_assert(conn->literal_bytes_left == 0);
+
+	/* reply */
+	memset(&reply, 0, sizeof(reply));
+	reply.url = urlreq->url;
+	reply.flags = urlreq->flags;
+	reply.bodypartstruct = urlreq->bodypartstruct;
+	reply.binary_has_nuls = urlreq->binary_has_nuls;
+
+	if (conn->literal_size > 0) {
+		if (imap_urlauth_fetch_reply_set_literal_stream(conn, &reply) < 0)
+			return -1;
+	}
+	reply.succeeded = TRUE;
+
+	ret = 1;
+	if (urlreq->callback != NULL) T_BEGIN {
+		ret = urlreq->callback(&reply, urlreq->context);
+	} T_END;
+
+	if (reply.input != NULL)
+		i_stream_unref(&reply.input);
+
+	if (ret < 0) {
+		/* Drop any related requests upon error */
+		imap_urlauth_request_abort_by_context(conn, urlreq->context);
+	}
+
+	conn->state = IMAP_URLAUTH_STATE_REQUEST_WAIT;
+	if (ret != 0)
+		imap_urlauth_connection_continue(conn);
+
+	/* finished */
+	i_free_and_null(conn->literal_temp_path);
+	conn->literal_fd = -1;
+	conn->literal_buf = NULL;
+	conn->reading_literal = FALSE;
+	return 1;
+}
+
+static int imap_urlauth_input_pending(struct imap_urlauth_connection *conn)
+{
+	struct imap_urlauth_request *urlreq;
+	const char *response, *const *args, *bpstruct = NULL;
+	uoff_t literal_size;
+
+	i_assert(conn->targets_head != NULL);
+	i_assert(conn->targets_head->requests_head != NULL);
+	urlreq = conn->targets_head->requests_head;
+
+	if (conn->reading_literal) {
+		/* Read pending literal; may callback */
+		return imap_urlauth_connection_read_literal(conn);
+	}
+
+	/* "OK"[<metadata-items>]"\t"<literal-size>"\n" or
+	   "NO"["\terror="<error>]"\n" */
+	if ((response = i_stream_next_line(conn->input)) == NULL)
+		return 0;
+	imap_urlauth_stop_response_timeout(conn);
+
+	args = t_strsplit_tabescaped(response);
+	if (args[0] == NULL) {
+		i_error("imap-urlauth: Empty URL response: %s",
+			str_sanitize(response, 80));
+		return -1;
+	}
+
+	if (strcmp(args[0], "OK") != 0 || args[1] == NULL) {
+		if (strcmp(args[0], "NO") == 0) {
+			const char *param = args[1], *error = NULL;
+
+			if (param != NULL &&
+			    strncasecmp(param, "error=", 6) == 0 &&
+			    param[6] != '\0') {
+				error = param+6;
+			}
+			imap_urlauth_request_fail(conn,
+				conn->targets_head->requests_head, error);
+			return 1;
+		}
+		i_error("imap-urlauth: Unexpected URL response: %s",
+			str_sanitize(response, 80));
+		return -1;
+	}
+
+	/* read metadata */
+	args++;
+	for (; args[1] != NULL; args++) {
+		const char *param = args[0];
+
+		if (strcasecmp(param, "hasnuls") == 0) {
+			urlreq->binary_has_nuls = TRUE;
+		} else if (strncasecmp(param, "bpstruct=", 9) == 0 &&
+			   param[9] != '\0') {
+			bpstruct = param+9;
+		}
+	}
+
+	/* read literal size */
+	if (str_to_uoff(args[0], &literal_size) < 0) {
+		i_error("imap-urlauth: "
+			"Overflowing unsigned integer value for literal size: %s",
+			args[1]);
+		return -1;
+	}
+
+	/* read literal */
+	if (imap_urlauth_connection_read_literal_init(conn, literal_size) < 0)
+		return -1;
+
+	urlreq->bodypartstruct = i_strdup(bpstruct);
+	return imap_urlauth_connection_read_literal(conn);
+}
+
+static int imap_urlauth_input_next(struct imap_urlauth_connection *conn)
+{
+	const char *response;
+	int ret;
+
+	switch (conn->state) {
+	case IMAP_URLAUTH_STATE_AUTHENTICATING:
+	case IMAP_URLAUTH_STATE_UNSELECTING_TARGET:
+		if ((response = i_stream_next_line(conn->input)) == NULL)
+			return 0;
+		imap_urlauth_stop_response_timeout(conn);
+
+		if (strcasecmp(response, "OK") != 0) {
+			if (conn->state == IMAP_URLAUTH_STATE_AUTHENTICATING)
+				i_error("imap-urlauth: Failed to authenticate to service: "
+					"Got unexpected response: %s", str_sanitize(response, 80));
+			else
+				i_error("imap-urlauth: Failed to unselect target user: "
+					"Got unexpected response: %s", str_sanitize(response, 80));
+			imap_urlauth_connection_abort(conn, NULL);
+			return -1;
+		}
+
+		if (conn->user->mail_debug) {
+			if (conn->state == IMAP_URLAUTH_STATE_AUTHENTICATING)
+				i_debug("imap-urlauth: Successfully authenticated to service");
+			else
+				i_debug("imap-urlauth: Successfully unselected target user");
+		}
+
+		conn->state = IMAP_URLAUTH_STATE_AUTHENTICATED;
+		imap_urlauth_connection_select_target(conn);
+		return 0;
+	case IMAP_URLAUTH_STATE_SELECTING_TARGET:
+		if ((response = i_stream_next_line(conn->input)) == NULL)
+			return 0;
+		imap_urlauth_stop_response_timeout(conn);
+
+		i_assert(conn->targets_head != NULL);
+
+		if (strcasecmp(response, "NO") == 0) {
+			if (conn->user->mail_debug) {
+				i_debug("imap-urlauth: Failed to select target user %s",
+					conn->targets_head->userid);
+			}
+			imap_urlauth_target_fail(conn, conn->targets_head, NULL);
+
+			conn->state = IMAP_URLAUTH_STATE_AUTHENTICATED;
+			imap_urlauth_connection_select_target(conn);
+			return 0;
+		}
+		if (strcasecmp(response, "OK") != 0) {
+			i_error("imap-urlauth: Failed to select target user %s: "
+				"Got unexpected response: %s", conn->targets_head->userid,
+				str_sanitize(response, 80));
+			imap_urlauth_connection_abort(conn, NULL);
+			return -1;
+		}
+
+		if (conn->user->mail_debug) {
+			i_debug("imap-urlauth: Successfully selected target user %s",
+				conn->targets_head->userid);
+		}
+		conn->state = IMAP_URLAUTH_STATE_READY;
+		imap_urlauth_connection_send_request(conn);
+		return 0;
+	case IMAP_URLAUTH_STATE_AUTHENTICATED:
+	case IMAP_URLAUTH_STATE_READY:
+	case IMAP_URLAUTH_STATE_REQUEST_WAIT:
+		if ((response = i_stream_next_line(conn->input)) == NULL)
+			return 0;
+
+		i_error("imap-urlauth: Received input while no requests were pending");
+		imap_urlauth_connection_abort(conn, NULL);
+		return -1;
+	case IMAP_URLAUTH_STATE_REQUEST_PENDING:
+		if ((ret = imap_urlauth_input_pending(conn)) < 0)
+			imap_urlauth_connection_fail(conn);
+		return ret;
+	case IMAP_URLAUTH_STATE_DISCONNECTED:
+		break;
+	}
+	i_unreached();
+}
+
+static void imap_urlauth_input(struct imap_urlauth_connection *conn)
+{
+	int ret;
+
+	i_assert(conn->state != IMAP_URLAUTH_STATE_DISCONNECTED);
+
+	if (conn->input->closed) {
+		/* disconnected */
+		i_error("imap-urlauth: Service disconnected unexpectedly");
+		imap_urlauth_connection_fail(conn);
+		return;
+	}
+
+	switch (i_stream_read(conn->input)) {
+	case -1:
+		/* disconnected */
+		i_error("imap-urlauth: Service disconnected unexpectedly");
+		imap_urlauth_connection_fail(conn);
+		return;
+	case -2:
+		/* input buffer full */
+		i_error("imap-urlauth: Service sent too large input");
+		imap_urlauth_connection_abort(conn, NULL);
+		return;
+	}
+
+	while (!conn->input->closed) {
+		if ((ret = imap_urlauth_input_next(conn)) <= 0)
+			break;
+	}
+}
+
+static int
+imap_urlauth_connection_do_connect(struct imap_urlauth_connection *conn)
+{
+	string_t *str;
+	int fd;
+
+	if (conn->state != IMAP_URLAUTH_STATE_DISCONNECTED) {
+		imap_urlauth_connection_send_request(conn);
+		return 1;
+	}
+
+	if (conn->user->auth_token == NULL) {
+		i_error("imap-urlauth: cannot authenticate because no auth token "
+			"is available for this session (standalone IMAP?).");
+		return -1;
+	}
+
+	if (conn->user->mail_debug)
+		i_debug("imap-urlauth: Connecting to service at %s", conn->path);
+
+	i_assert(conn->fd == -1);
+	fd = net_connect_unix(conn->path);
+	if (fd == -1) {
+		i_error("imap-urlauth: net_connect_unix(%s) failed: %m",
+			conn->path);
+		return -1;
+	}
+
+	if (conn->to_reconnect != NULL)
+		timeout_remove(&conn->to_reconnect);
+
+	conn->fd = fd;
+	conn->input = i_stream_create_fd(fd, (size_t)-1, FALSE);
+	conn->output = o_stream_create_fd(fd, (size_t)-1, FALSE);
+	conn->io = io_add(fd, IO_READ, imap_urlauth_input, conn);
+	conn->state = IMAP_URLAUTH_STATE_AUTHENTICATING;
+
+	str = t_str_new(128);
+	str_printfa(str, IMAP_URLAUTH_HANDSHAKE"AUTH\t%s\t", my_pid);
+	str_tabescape_write(str, conn->user->username);
+	str_append_c(str, '\t');
+	if (conn->session_id != NULL)
+		str_tabescape_write(str, conn->session_id);
+	str_append_c(str, '\t');
+	str_tabescape_write(str, conn->user->auth_token);
+	str_append_c(str, '\n');
+	if (o_stream_send(conn->output, str_data(str), str_len(str)) < 0) {
+		i_warning("Error sending handshake to imap-urlauth server: %m");
+		imap_urlauth_connection_abort(conn, NULL);
+		return -1;
+	}
+
+	imap_urlauth_start_response_timeout(conn);
+	return 0;
+}
+
+int imap_urlauth_connection_connect(struct imap_urlauth_connection *conn)
+{
+	conn->reconnect_attempts = 0;
+
+	if (conn->to_reconnect == NULL)
+		return imap_urlauth_connection_do_connect(conn);
+	return 0;
+}
+
+static void imap_urlauth_connection_disconnect
+(struct imap_urlauth_connection *conn, const char *reason)
+{
+	conn->state = IMAP_URLAUTH_STATE_DISCONNECTED;
+
+	if (conn->fd != -1) {
+		if (conn->user->mail_debug) {
+			if (reason == NULL)
+				i_debug("imap-urlauth: Disconnecting from service");
+			else
+				i_debug("imap-urlauth: Disconnected: %s", reason);
+		}
+
+		io_remove(&conn->io);
+		i_stream_destroy(&conn->input);
+		o_stream_destroy(&conn->output);
+		net_disconnect(conn->fd);
+		conn->fd = -1;
+	}
+	conn->reading_literal = FALSE;
+
+	if (conn->literal_fd != -1) {
+		if (close(conn->literal_fd) < 0)
+			i_error("imap-urlauth: close(%s) failed: %m", conn->literal_temp_path);
+
+		i_free_and_null(conn->literal_temp_path);
+		conn->literal_fd = -1;
+	}
+
+	if (conn->literal_buf != NULL)
+		buffer_free(&conn->literal_buf);
+	if (conn->to_reconnect != NULL)
+		timeout_remove(&conn->to_reconnect);
+	if (conn->to_idle != NULL)
+		timeout_remove(&conn->to_idle);
+	imap_urlauth_stop_response_timeout(conn);
+}
+
+static void
+imap_urlauth_connection_do_reconnect(struct imap_urlauth_connection *conn)
+{
+	if (conn->reconnect_attempts >= IMAP_URLAUTH_RECONNECT_MAX_ATTEMPTS) {
+		imap_urlauth_connection_abort(conn,
+			"Connection failed and connection attempts exhausted");
+		return;
+	}
+
+	if (ioloop_time - conn->last_reconnect < IMAP_URLAUTH_RECONNECT_MIN_SECS) {
+		if (conn->user->mail_debug)
+			i_debug("imap-urlauth: Scheduling reconnect");
+		if (conn->to_reconnect != NULL)
+			timeout_remove(&conn->to_reconnect);
+		conn->to_reconnect =
+			timeout_add(IMAP_URLAUTH_RECONNECT_MIN_SECS*1000,
+				imap_urlauth_connection_do_reconnect, conn);
+	} else {
+		conn->reconnect_attempts++;
+		conn->last_reconnect = ioloop_time;
+		(void)imap_urlauth_connection_do_connect(conn);
+	}
+}
+
+static void
+imap_urlauth_connection_reconnect(struct imap_urlauth_connection *conn)
+{
+	imap_urlauth_connection_disconnect(conn, NULL);
+
+	/* don't reconnect if there are no requests */
+	if (conn->targets_head == NULL)
+		return;
+
+	imap_urlauth_connection_do_reconnect(conn);
+}
+
+static void
+imap_urlauth_connection_idle_disconnect(struct imap_urlauth_connection *conn)
+{
+	imap_urlauth_connection_disconnect(conn, "Idle timeout");
+}
+
+static void
+imap_urlauth_connection_timeout_abort(struct imap_urlauth_connection *conn)
+{
+	imap_urlauth_connection_abort(conn, "Service is not responding");
+}
+
+bool imap_urlauth_connection_is_connected(struct imap_urlauth_connection *conn)
+{
+	return conn->fd != -1 && conn->state != IMAP_URLAUTH_STATE_DISCONNECTED;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-imap-urlauth/imap-urlauth-connection.h	Sat Sep 15 23:57:08 2012 +0300
@@ -0,0 +1,42 @@
+#ifndef IMAP_URLAUTH_CONNECTION_H
+#define IMAP_URLAUTH_CONNECTION_H
+
+struct imap_urlauth_request;
+struct imap_urlauth_fetch_reply;
+
+typedef int
+imap_urlauth_request_callback_t(struct imap_urlauth_fetch_reply *reply,
+				void *context);
+
+/* If reconnect_callback is specified, it's called when connection is lost.
+   If the callback returns FALSE, reconnection isn't attempted. */
+struct imap_urlauth_connection *
+imap_urlauth_connection_init(const char *path, struct mail_user *user,
+			     const char *session_id,
+			     unsigned int idle_timeout_msecs);
+void imap_urlauth_connection_deinit(struct imap_urlauth_connection **conn);
+
+/* Connect to imap-urlauth (even if failed for previous requests). */
+int imap_urlauth_connection_connect(struct imap_urlauth_connection *conn);
+
+/* Continue after request callback returned 0 */
+void imap_urlauth_connection_continue(struct imap_urlauth_connection *conn);
+
+/* Create a new URL fetch request */
+struct imap_urlauth_request *
+imap_urlauth_request_new(struct imap_urlauth_connection *conn,
+			 const char *target_user, const char *url,
+			 enum imap_urlauth_fetch_flags flags,
+			 imap_urlauth_request_callback_t *callback,
+			 void *context);
+/* Abort request */
+void imap_urlauth_request_abort(struct imap_urlauth_connection *conn,
+				struct imap_urlauth_request *urlreq);
+/* Abort all requests with matching context value */
+void imap_urlauth_request_abort_by_context(struct imap_urlauth_connection *conn,
+					   void *context);
+
+/* Returns TRUE if currently connected imap-urlauth service. */
+bool imap_urlauth_connection_is_connected(struct imap_urlauth_connection *conn);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-imap-urlauth/imap-urlauth-fetch.c	Sat Sep 15 23:57:08 2012 +0300
@@ -0,0 +1,437 @@
+/* Copyright (c) 2012 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "llist.h"
+#include "array.h"
+#include "network.h"
+#include "istream.h"
+#include "mail-user.h"
+#include "mail-error.h"
+#include "mail-storage.h"
+#include "imap-url.h"
+#include "imap-msgpart-url.h"
+#include "imap-urlauth-private.h"
+#include "imap-urlauth-fetch.h"
+#include "imap-urlauth-connection.h"
+
+struct imap_urlauth_fetch_url {
+	struct imap_urlauth_fetch_url *prev, *next;
+
+	char *url;
+	enum imap_urlauth_fetch_flags flags;
+};
+
+struct imap_urlauth_fetch {
+	struct imap_urlauth_context *uctx;
+
+	imap_urlauth_fetch_callback_t *callback;
+	void *context;
+
+	/* local urls */
+	struct imap_urlauth_fetch_url *local_urls_head, *local_urls_tail;
+	struct imap_msgpart_url *local_url;
+
+	unsigned int pending_requests;
+
+	struct {
+		char *url;
+		enum imap_urlauth_fetch_flags flags;
+
+		struct istream *input;
+		uoff_t size;
+
+		char *bodypartstruct;
+		char *error;
+
+		unsigned int succeeded:1;
+		unsigned int binary_has_nuls:1;
+	} pending_reply;
+
+	unsigned int failed:1;
+	unsigned int waiting:1;
+};
+
+static void imap_urlauth_fetch_abort_local(struct imap_urlauth_fetch *ufetch)
+{
+	struct imap_urlauth_fetch_url *url, *url_next;
+
+	if (ufetch->local_url != NULL)
+		imap_msgpart_url_free(&ufetch->local_url);
+
+	if (ufetch->pending_reply.url != NULL)
+		i_free(ufetch->pending_reply.url);
+	if (ufetch->pending_reply.input != NULL)
+		i_stream_unref(&ufetch->pending_reply.input);
+	if (ufetch->pending_reply.bodypartstruct != NULL)
+		i_free(ufetch->pending_reply.bodypartstruct);
+	if (ufetch->pending_reply.error != NULL)
+		i_free(ufetch->pending_reply.error);
+
+	url = ufetch->local_urls_head;
+	while (url != NULL) {
+		url_next = url->next;
+		i_free(url->url);
+		i_free(url);
+		ufetch->pending_requests--;
+		url = url_next;
+	}
+}
+
+static void imap_urlauth_fetch_abort(struct imap_urlauth_fetch *ufetch)
+{
+	if (ufetch->pending_requests > 0)
+		imap_urlauth_request_abort_by_context(ufetch->uctx->conn, ufetch);
+
+	imap_urlauth_fetch_abort_local(ufetch);
+
+	i_assert(ufetch->pending_requests == 0);
+}
+
+static void imap_urlauth_fetch_fail(struct imap_urlauth_fetch *ufetch)
+{
+	imap_urlauth_fetch_abort(ufetch);
+	ufetch->failed = TRUE;
+}
+
+struct imap_urlauth_fetch *
+imap_urlauth_fetch_init(struct imap_urlauth_context *uctx,
+			imap_urlauth_fetch_callback_t *callback, void *context)
+{
+	struct imap_urlauth_fetch *ufetch;
+
+	ufetch = i_new(struct imap_urlauth_fetch, 1);
+	ufetch->uctx = uctx;
+	ufetch->callback = callback;
+	ufetch->context = context;
+	return ufetch;
+}
+
+void imap_urlauth_fetch_deinit(struct imap_urlauth_fetch **_ufetch)
+{
+	struct imap_urlauth_fetch *ufetch = *_ufetch;
+
+	*_ufetch = NULL;
+
+	imap_urlauth_fetch_abort(ufetch);
+	i_free(ufetch);
+}
+
+static void
+imap_urlauth_fetch_error(struct imap_urlauth_fetch *ufetch, const char *url,
+			 enum imap_urlauth_fetch_flags url_flags,
+			 const char *error)
+{
+	struct imap_urlauth_fetch_reply reply;
+	int ret;
+
+	ufetch->pending_requests--;
+
+	memset(&reply, 0, sizeof(reply));
+	reply.url = url;
+	reply.flags = url_flags;
+	reply.succeeded = FALSE;
+	reply.error = error;
+	
+	T_BEGIN {
+		ret = ufetch->callback(&reply, ufetch->pending_requests == 0,
+				       ufetch->context);
+	} T_END;
+
+	if (ret == 0)
+		ufetch->waiting = TRUE;
+	else if (ret < 0)
+		imap_urlauth_fetch_fail(ufetch);
+}
+
+static void
+imap_urlauth_fetch_local(struct imap_urlauth_fetch *ufetch, const char *url,
+			 enum imap_urlauth_fetch_flags url_flags,
+			 struct imap_url *imap_url)
+{
+	struct imap_urlauth_fetch_reply reply;
+	struct imap_msgpart_open_result mpresult;
+	const char *error, *errormsg = NULL, *bpstruct = NULL;
+	bool debug = ufetch->uctx->user->mail_debug, success;
+	enum mail_error error_code;
+	struct imap_msgpart_url *mpurl;
+	int ret;
+
+	ufetch->pending_requests--;
+	success = TRUE;
+
+	if (debug)
+		i_debug("Fetching local URLAUTH %s", url);
+
+	if (url_flags == 0)
+		url_flags = IMAP_URLAUTH_FETCH_FLAG_BODY;
+
+	/* fetch URL */
+	if (imap_url == NULL) {
+		ret = imap_urlauth_fetch(ufetch->uctx, url,
+					 &mpurl, &error_code, &error);
+	} else {
+		ret = imap_urlauth_fetch_parsed(ufetch->uctx, imap_url,
+						&mpurl, &error_code, &error);
+	}
+	if (ret < 0) {
+		(void)ufetch->callback(NULL, TRUE, ufetch->context);
+		imap_urlauth_fetch_fail(ufetch);
+		return;
+	}
+	if (ret == 0) {
+		errormsg = t_strdup_printf("Failed to fetch URLAUTH \"%s\": %s",
+					   url, error);
+		if (debug)
+			i_debug("%s", errormsg);
+		success = FALSE;
+	}
+
+	/* fetch metadata */
+	if (success && (url_flags & IMAP_URLAUTH_FETCH_FLAG_BINARY) != 0)
+		imap_msgpart_url_set_decode_to_binary(mpurl);
+	if (success &&
+	    (url_flags & IMAP_URLAUTH_FETCH_FLAG_BODYPARTSTRUCTURE) != 0) {
+		if (imap_msgpart_url_get_bodypartstructure
+		    (mpurl, &bpstruct, &error) <= 0) {
+			errormsg = t_strdup_printf
+				("Failed to read URLAUTH \"%s\": %s",	url, error);
+			if (debug)
+				i_debug("%s", errormsg);
+			success = FALSE;
+		}
+	}
+
+	/* if requested, read the message part the URL points to */
+	mpresult.size = 0;
+	mpresult.input = NULL;
+	if (success && ((url_flags & IMAP_URLAUTH_FETCH_FLAG_BODY) != 0 ||
+			(url_flags & IMAP_URLAUTH_FETCH_FLAG_BINARY) != 0)) {
+		if (imap_msgpart_url_read_part(mpurl, &mpresult, &error) <= 0) {
+			errormsg = t_strdup_printf
+				("Failed to read URLAUTH \"%s\": %s",	url, error);
+			if (debug)
+				i_debug("%s", errormsg);
+			success = FALSE;
+		}
+	}
+
+	if (debug && success) {
+		if (bpstruct != NULL)
+			i_debug("Fetched URLAUTH yielded BODYPARTSTRUCTURE (%s)", bpstruct);
+		if (mpresult.size == 0 || mpresult.input == NULL)
+			i_debug("Fetched URLAUTH yielded empty result");
+		else {
+			i_debug("Fetched URLAUTH yielded %"PRIuUOFF_T" bytes "
+				"of %smessage data", mpresult.size,
+				(mpresult.binary_decoded_input_has_nuls ? "binary " : ""));
+		}
+	}
+
+	memset(&reply, 0, sizeof(reply));
+	reply.url = url;
+	reply.flags = url_flags;
+	reply.error = errormsg;
+	reply.succeeded = success;
+
+	reply.bodypartstruct = bpstruct;
+	reply.binary_has_nuls = mpresult.binary_decoded_input_has_nuls;
+	reply.size = mpresult.size;
+	reply.input = mpresult.input;
+
+	ret = ufetch->callback(&reply, ufetch->pending_requests == 0,
+			       ufetch->context);
+	if (ret == 0) {
+		ufetch->local_url = mpurl;
+		ufetch->waiting = TRUE;
+	} else {
+		if (mpurl != NULL)
+			imap_msgpart_url_free(&mpurl);
+		if (ret < 0)
+			imap_urlauth_fetch_fail(ufetch);
+	}
+}
+
+static int
+imap_urlauth_fetch_request_callback(struct imap_urlauth_fetch_reply *reply,
+				    void *context)
+{
+	struct imap_urlauth_fetch *ufetch =
+		(struct imap_urlauth_fetch *)context;
+	int ret = 1;
+
+	if (ufetch->waiting && reply != NULL) {
+		i_assert(ufetch->pending_reply.url == NULL);
+		ufetch->pending_reply.url = i_strdup(reply->url);
+		ufetch->pending_reply.flags = reply->flags;
+		ufetch->pending_reply.bodypartstruct =
+			i_strdup(reply->bodypartstruct);
+		ufetch->pending_reply.error = i_strdup(reply->error);
+		if (reply->input != NULL) {
+			ufetch->pending_reply.input = reply->input;
+			i_stream_ref(ufetch->pending_reply.input);
+		}
+		ufetch->pending_reply.size = reply->size;
+		ufetch->pending_reply.succeeded = reply->succeeded;
+		ufetch->pending_reply.binary_has_nuls = reply->binary_has_nuls;
+		return 0;
+	}
+
+	ufetch->waiting = FALSE;
+	ufetch->pending_requests--;
+
+	if (!ufetch->failed) {
+		bool last = ufetch->pending_requests == 0 || reply == NULL;
+		ret = ufetch->callback(reply, last, ufetch->context);
+	}
+
+	/* report failure only once */
+	if (ret < 0 || reply == NULL) {
+		if (!ufetch->failed)
+			imap_urlauth_fetch_abort_local(ufetch);
+		ufetch->failed = TRUE;
+	}
+	return ret;
+}
+
+int imap_urlauth_fetch_url(struct imap_urlauth_fetch *ufetch, const char *url,
+			   enum imap_urlauth_fetch_flags url_flags)
+{
+	enum imap_url_parse_flags url_parse_flags =
+		IMAP_URL_PARSE_ALLOW_URLAUTH;
+	struct imap_urlauth_context *uctx = ufetch->uctx;
+	struct mail_user *mail_user = uctx->user;
+	struct imap_url *imap_url = NULL;
+	const char *error, *errormsg;
+
+	ufetch->failed = FALSE;
+	ufetch->pending_requests++;
+
+	/* parse the url */
+	if (imap_url_parse(url, NULL, url_parse_flags, &imap_url, &error) < 0) {
+		errormsg = t_strdup_printf(
+			"Failed to fetch URLAUTH \"%s\": %s", url, error);
+		if (mail_user->mail_debug)
+			i_debug("%s", errormsg);
+		imap_urlauth_fetch_error(ufetch, url, url_flags, errormsg);
+	
+	/* if access user and target user match, handle fetch request locally */
+	} else if (strcmp(mail_user->username, imap_url->userid) == 0) {
+
+		if (ufetch->waiting) {
+			struct imap_urlauth_fetch_url *url_local;
+
+			url_local = i_new(struct imap_urlauth_fetch_url, 1);
+			url_local->url = i_strdup(url);
+			url_local->flags = url_flags;
+
+			DLLIST2_APPEND(&ufetch->local_urls_head,
+				       &ufetch->local_urls_tail, url_local);
+		} else T_BEGIN {
+			imap_urlauth_fetch_local(ufetch, url,
+						 url_flags, imap_url);
+		} T_END;
+		imap_url = NULL;
+	/* don't try to fetch remote URLs that are already known to fail access */
+	} else if (!imap_urlauth_check(uctx, imap_url, TRUE, &error)) {
+		errormsg = t_strdup_printf(
+			"Failed to fetch URLAUTH \"%s\": %s", url, error);
+		if (mail_user->mail_debug)
+			i_debug("%s", errormsg);
+		imap_urlauth_fetch_error(ufetch, url, url_flags, errormsg);
+		imap_url = NULL;
+	}
+
+	/* create request for url */
+	if (imap_url != NULL && imap_url->userid != NULL) {
+		(void)imap_urlauth_request_new(uctx->conn, imap_url->userid,
+				url, url_flags,
+				imap_urlauth_fetch_request_callback, ufetch);
+	}
+
+	if (ufetch->pending_requests > 0) {
+		i_assert(uctx->conn != NULL);
+		if (imap_urlauth_connection_connect(uctx->conn) < 0)
+			return -1;
+		return 0;
+	}
+	return 1;
+}
+
+bool imap_urlauth_fetch_continue(struct imap_urlauth_fetch *ufetch)
+{
+	struct imap_urlauth_fetch_url *url, *url_next;
+	int ret;
+
+	if (ufetch->failed)
+		return FALSE;
+
+	if (!ufetch->waiting) {
+		/* not waiting for local request handling */
+		imap_urlauth_connection_continue(ufetch->uctx->conn);
+		return ufetch->pending_requests > 0;
+	} 
+
+	if (ufetch->local_url != NULL)
+		imap_msgpart_url_free(&ufetch->local_url);
+	ufetch->waiting = FALSE;
+
+	/* handle pending remote reply */
+	if (ufetch->pending_reply.url != NULL) {
+		struct imap_urlauth_fetch_reply reply;
+
+		ufetch->pending_requests--;
+
+		memset(&reply, 0, sizeof(reply));
+		reply.url = ufetch->pending_reply.url;
+		reply.flags = ufetch->pending_reply.flags;
+		reply.bodypartstruct = ufetch->pending_reply.bodypartstruct;
+		reply.error = ufetch->pending_reply.error;
+		reply.input = ufetch->pending_reply.input;
+		reply.size = ufetch->pending_reply.size;
+		reply.succeeded = ufetch->pending_reply.succeeded;
+		reply.binary_has_nuls = ufetch->pending_reply.binary_has_nuls;
+
+		ret = ufetch->callback(&reply, ufetch->pending_requests == 0,
+				       ufetch->context);
+
+		if (ufetch->pending_reply.url != NULL)
+			i_free(ufetch->pending_reply.url);
+		if (ufetch->pending_reply.input != NULL)
+			i_stream_unref(&ufetch->pending_reply.input);
+		if (ufetch->pending_reply.bodypartstruct != NULL)
+			i_free(ufetch->pending_reply.bodypartstruct);
+		if (ufetch->pending_reply.error != NULL)
+			i_free(ufetch->pending_reply.error);
+
+		if (ret < 0) {
+			imap_urlauth_fetch_fail(ufetch);
+			return FALSE;
+		} 
+		
+		imap_urlauth_connection_continue(ufetch->uctx->conn);
+
+		if (ret == 0) {
+			ufetch->waiting = TRUE;
+			return TRUE;
+		}
+	}
+
+	/* handle pending local urls */
+	url = ufetch->local_urls_head;
+	while (url != NULL) {
+		url_next = url->next;
+		T_BEGIN {
+			imap_urlauth_fetch_local(ufetch, url->url,
+						 url->flags, NULL);
+		} T_END;
+		DLLIST2_REMOVE(&ufetch->local_urls_head,
+			       &ufetch->local_urls_tail, url);
+		i_free(url->url);
+		i_free(url);
+		if (ufetch->waiting) 
+			return TRUE;
+		url = url_next;
+	}
+
+	return ufetch->pending_requests > 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-imap-urlauth/imap-urlauth-fetch.h	Sat Sep 15 23:57:08 2012 +0300
@@ -0,0 +1,50 @@
+#ifndef IMAP_URLAUTH_FETCH_H
+#define IMAP_URLAUTH_FETCH_H
+
+struct imap_urlauth_context;
+struct imap_urlauth_fetch;
+
+enum imap_urlauth_fetch_flags {
+	/* Indicates that this is an extended request */
+	IMAP_URLAUTH_FETCH_FLAG_EXTENDED		= 0x01,
+	/* Fetch body part unmodified */
+	IMAP_URLAUTH_FETCH_FLAG_BODY			= 0x02,
+	/* Fetch body part as binary, i.e. without content encoding */
+	IMAP_URLAUTH_FETCH_FLAG_BINARY			= 0x04,
+	/* Fetch IMAP bodypartstructure */
+	IMAP_URLAUTH_FETCH_FLAG_BODYPARTSTRUCTURE	= 0x08,
+};
+
+struct imap_urlauth_fetch_reply {
+	const char *url;
+	enum imap_urlauth_fetch_flags flags;
+
+	struct istream *input;
+	uoff_t size;
+
+	const char *bodypartstruct;
+	const char *error;
+
+	unsigned int succeeded:1;
+	unsigned int binary_has_nuls:1;
+};
+
+/* Callback to handle fetch reply. Returns 1 if handled completely and ready
+   for next reply, 0 if not all data was processed, and -1 for error. If a
+   callback returns 0, imap_urlauth_connection_continue() must be called once
+   new replies may be processed. If this is the last request to yield a reply,
+   argument last is TRUE */
+typedef int
+imap_urlauth_fetch_callback_t(struct imap_urlauth_fetch_reply *reply,
+			      bool last, void *context);
+
+struct imap_urlauth_fetch *
+imap_urlauth_fetch_init(struct imap_urlauth_context *uctx,
+			imap_urlauth_fetch_callback_t *callback, void *context);
+void imap_urlauth_fetch_deinit(struct imap_urlauth_fetch **_ufetch);
+
+int imap_urlauth_fetch_url(struct imap_urlauth_fetch *ufetch, const char *url,
+			   enum imap_urlauth_fetch_flags flags);
+bool imap_urlauth_fetch_continue(struct imap_urlauth_fetch *ufetch);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-imap-urlauth/imap-urlauth-private.h	Sat Sep 15 23:57:08 2012 +0300
@@ -0,0 +1,18 @@
+#ifndef IMAP_URLAUTH_PRIVATE_H
+#define IMAP_URLAUTH_PRIVATE_H
+
+#include "imap-urlauth.h"
+
+struct imap_urlauth_context {
+	struct mail_user *user;
+	struct imap_urlauth_connection *conn;
+	struct imap_urlauth_backend *backend;
+
+	char *url_host;
+	unsigned int url_port;
+
+	char *access_user;
+	const char **access_applications;
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-imap-urlauth/imap-urlauth.c	Sat Sep 15 23:57:08 2012 +0300
@@ -0,0 +1,467 @@
+/* Copyright (c) 2012 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "hostpid.h"
+#include "var-expand.h"
+#include "hmac-sha1.h"
+#include "randgen.h"
+#include "safe-memset.h"
+#include "mail-storage.h"
+#include "mail-storage-service.h"
+#include "mail-namespace.h"
+#include "mail-user.h"
+#include "imap-url.h"
+#include "imap-msgpart-url.h"
+#include "imap-urlauth-backend.h"
+#include "imap-urlauth-fetch.h"
+#include "imap-urlauth-connection.h"
+
+#include "imap-urlauth-private.h"
+
+#include <time.h>
+
+#define IMAP_URLAUTH_MECH_INTERNAL_VERSION    0x01
+
+#define IMAP_URLAUTH_NORMAL_TIMEOUT_MSECS     5*1000
+#define IMAP_URLAUTH_SPECIAL_TIMEOUT_MSECS    3*60*1000
+
+int imap_urlauth_init(struct mail_user *user,
+		      const struct imap_urlauth_config *config,
+		      struct imap_urlauth_context **ctx_r)
+{
+	struct imap_urlauth_backend *backend;
+	struct imap_urlauth_context *uctx;
+	unsigned int timeout;
+
+	if (imap_urlauth_backend_create(user, config->dict_uri, &backend) < 0)
+		return -1;
+
+	uctx = i_new(struct imap_urlauth_context, 1);
+	uctx->user = user;
+	uctx->backend = backend;
+	if (config->url_host != NULL && *config->url_host != '\0')
+		uctx->url_host = i_strdup(config->url_host);
+	else
+		uctx->url_host = i_strdup(my_hostdomain());
+	uctx->url_port = config->url_port;
+
+	if (config->access_user != NULL && *config->access_user != '\0')
+		uctx->access_user = i_strdup(config->access_user);
+	if (config->access_applications != NULL &&
+	    *config->access_applications != NULL) {
+		uctx->access_applications =
+			p_strarray_dup(default_pool,
+				       config->access_applications);
+		timeout = IMAP_URLAUTH_SPECIAL_TIMEOUT_MSECS;
+	} else {
+		timeout = IMAP_URLAUTH_NORMAL_TIMEOUT_MSECS;
+	}
+
+	if (config->socket_path != NULL) {
+		uctx->conn = imap_urlauth_connection_init(config->socket_path,
+					user, config->session_id, timeout);
+	}
+	*ctx_r = uctx;
+	return 0;
+}
+
+void imap_urlauth_deinit(struct imap_urlauth_context **_uctx)
+{
+	struct imap_urlauth_context *uctx = *_uctx;
+
+	if (uctx->backend != NULL)
+		imap_urlauth_backend_destroy(&uctx->backend);
+	if (uctx->conn != NULL)
+		imap_urlauth_connection_deinit(&uctx->conn);
+	i_free(uctx->url_host);
+	i_free(uctx->access_user);
+	i_free(uctx->access_applications);
+	i_free(uctx);
+	*_uctx = uctx;
+}
+
+static const unsigned char *
+imap_urlauth_internal_generate(const char *rumpurl,
+			       const unsigned char mailbox_key[IMAP_URLAUTH_KEY_LEN],
+			       size_t *token_len_r)
+{
+	struct hmac_sha1_context hmac;
+	unsigned char *token;
+
+	token = t_new(unsigned char, SHA1_RESULTLEN + 1);
+	token[0] = IMAP_URLAUTH_MECH_INTERNAL_VERSION;
+
+	hmac_sha1_init(&hmac, mailbox_key, IMAP_URLAUTH_KEY_LEN);
+	hmac_sha1_update(&hmac, rumpurl, strlen(rumpurl));
+	hmac_sha1_final(&hmac, token+1);
+
+	*token_len_r = SHA1_RESULTLEN + 1;
+	return token;
+}
+
+static bool
+imap_urlauth_internal_verify(const char *rumpurl,
+			     const unsigned char mailbox_key[IMAP_URLAUTH_KEY_LEN],
+			     const unsigned char *token, size_t token_len)
+{
+	const unsigned char *valtoken;
+	size_t valtoken_len;
+
+	if (rumpurl == NULL || token == NULL)
+		return FALSE;
+
+	valtoken = imap_urlauth_internal_generate(rumpurl, mailbox_key,
+						  &valtoken_len);
+	if (token_len != valtoken_len)
+		return FALSE;
+
+	return memcmp(token, valtoken, valtoken_len) == 0;
+}
+
+static bool
+access_applications_have_access(struct imap_url *url,
+				const char *const *access_applications)
+{
+	const char *const *application;
+
+	if (access_applications == NULL)
+		return FALSE;
+
+	application = access_applications;
+	for (; *application != NULL; application++) {
+		const char *app = *application;
+		bool have_userid = FALSE;
+		size_t len = strlen(app);
+
+		if (app[len-1] == '+') {
+			have_userid = TRUE;
+			app = t_strndup(app, len-1);
+		}
+
+		if (strcasecmp(url->uauth_access_application, app) == 0) {
+			if (have_userid)
+				return url->uauth_access_user != NULL;
+			else
+				return url->uauth_access_user == NULL;
+		}
+	}
+	return FALSE;
+}
+
+static bool
+imap_urlauth_check_access(struct imap_urlauth_context *uctx,
+			  struct imap_url *url, bool ignore_unknown,
+			  const char **error_r)
+{
+	if (url->uauth_access_application == NULL) {
+		*error_r = "URL is missing URLAUTH";
+		return FALSE;
+	}
+
+	if (strcasecmp(url->uauth_access_application, "user") == 0) {
+		/* user+<access_user> */
+		if (uctx->access_user == NULL ||
+		    strcasecmp(url->uauth_access_user, uctx->access_user) != 0)  {
+			if (uctx->access_user == NULL) {
+				*error_r = t_strdup_printf(
+					"No 'user+%s' access allowed for anonymous user",
+					url->uauth_access_user);
+			} else {
+				*error_r = t_strdup_printf("No 'user+%s' access allowed for user %s",
+					url->uauth_access_user, uctx->access_user);
+			}
+			return FALSE;
+		}
+	} else if (strcasecmp(url->uauth_access_application, "authuser") == 0) {
+		/* authuser */
+		if (uctx->access_user == NULL) {
+			*error_r = "No 'authuser' access allowed for anonymous user";
+			return FALSE;
+		}
+	} else if (strcasecmp(url->uauth_access_application, "anonymous") == 0) {
+		/* anonymous */
+	} else if (!ignore_unknown &&
+		   !access_applications_have_access(url, uctx->access_applications)) {
+		const char *userid = url->uauth_access_user == NULL ? "" :
+			t_strdup_printf("+%s", url->uauth_access_user);
+
+		if (uctx->access_user == NULL) {
+			*error_r = t_strdup_printf(
+				"No '%s%s' access allowed for anonymous user",
+				url->uauth_access_application, userid);
+		} else {
+			*error_r = t_strdup_printf(
+				"No '%s%s' access allowed for user %s",
+				url->uauth_access_application, userid, uctx->access_user);
+		}
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static bool
+imap_urlauth_check_hostport(struct imap_urlauth_context *uctx,
+			    struct imap_url *url, const char **error_r)
+{
+	/* validate host */
+	/* FIXME: allow host ip/ip6 as well? */
+	if (strcmp(url->host_name, uctx->url_host) != 0) {
+		*error_r = "Invalid URL: Inappropriate host name";
+		return FALSE;
+	}
+
+	/* validate port */
+	if ((!url->have_port && uctx->url_port != 143) ||
+	    (url->have_port && uctx->url_port != url->port)) {
+		*error_r = "Invalid URL: Inappropriate server port";
+		return FALSE;
+	}
+	return TRUE;
+}
+
+int imap_urlauth_generate(struct imap_urlauth_context *uctx,
+			  const char *mechanism, const char *rumpurl,
+			  const char **urlauth_r, const char **error_r)
+{
+	struct mail_user *user = uctx->user;
+	enum imap_url_parse_flags url_flags =
+		IMAP_URL_PARSE_ALLOW_URLAUTH;
+	struct imap_url *url;
+	struct imap_msgpart_url *mpurl;
+	struct mailbox *box;
+	const char *error;
+	unsigned char mailbox_key[IMAP_URLAUTH_KEY_LEN];
+	const unsigned char *token;
+	size_t token_len;
+	int ret;
+
+	/* validate mechanism */
+	if (strcasecmp(mechanism, "INTERNAL") != 0) {
+		*error_r = t_strdup_printf("Unsupported URLAUTH mechanism: %s", mechanism);
+		return 0;
+	}
+
+	/* validate URL */
+	if (imap_url_parse(rumpurl, NULL, url_flags, &url, &error) < 0) {
+		*error_r = t_strdup_printf("Invalid URL: %s", error);
+		return 0;
+	}
+
+	if (url->mailbox == NULL || url->uid == 0 || url->search_program != NULL ||
+		url->uauth_rumpurl == NULL || url->uauth_mechanism != NULL) {
+		*error_r = "Invalid URL: Must be an URLAUTH rump URL";
+		return 0;
+	}
+
+	/* validate expiry time */
+	if (url->uauth_expire != (time_t)-1) {
+		time_t now = time(NULL);
+
+		if (now > url->uauth_expire) {
+			*error_r = t_strdup_printf("URLAUTH has already expired");
+			return 0;
+		}
+	}
+
+	/* validate user */
+	if (url->userid == NULL) {
+		*error_r = "Invalid URL: Missing user name";
+		return 0;
+	}
+	if (strcmp(url->userid, user->username) != 0) {
+		*error_r = t_strdup_printf(
+			"Not permitted to generate URLAUTH for user %s",
+			url->userid);
+		return 0;
+	}
+
+	/* validate host:port */
+	if (!imap_urlauth_check_hostport(uctx, url, error_r))
+		return 0;
+
+	/* validate mailbox */
+	if ((ret = imap_msgpart_url_create(user, url, &mpurl, &error)) < 0 ||
+	    imap_msgpart_url_verify(mpurl, &error) <= 0) {
+		*error_r = t_strdup_printf("Invalid URL: %s", error);
+		imap_msgpart_url_free(&mpurl);
+		return ret;
+	}
+	box = imap_msgpart_url_get_mailbox(mpurl);
+
+	/* obtain mailbox key */
+	ret = imap_urlauth_backend_get_mailbox_key(uctx->backend, box, TRUE,
+						   mailbox_key);
+	if (ret < 0) {
+		*error_r = "Internal server error";
+		imap_msgpart_url_free(&mpurl);
+		return -1;
+	}
+
+	token = imap_urlauth_internal_generate(rumpurl, mailbox_key, &token_len);
+	imap_msgpart_url_free(&mpurl);
+
+	*urlauth_r = imap_url_add_urlauth(rumpurl, mechanism, token, token_len);
+	return 1;
+}
+
+bool imap_urlauth_check(struct imap_urlauth_context *uctx,
+			struct imap_url *url, bool ignore_unknown_access,
+			const char **error_r)
+{
+	/* validate URL fields */
+	if (url->mailbox == NULL || url->uid == 0 ||
+	    url->search_program != NULL || url->uauth_rumpurl == NULL ||
+	    url->uauth_mechanism == NULL) {
+		*error_r = "Invalid URL: Must be a full URLAUTH URL";
+		return FALSE;
+	}
+
+	/* check presence of userid */
+	if (url->userid == NULL) {
+		*error_r = "Invalid URLAUTH: Missing user name";
+		return FALSE;
+	}
+
+	/* validate mechanism */
+	if (strcasecmp(url->uauth_mechanism, "INTERNAL") != 0) {
+		*error_r = t_strdup_printf("Unsupported URLAUTH mechanism: %s",
+					   url->uauth_mechanism);
+		return FALSE;
+	}
+
+	/* validate expiry time */
+	if (url->uauth_expire != (time_t)-1) {
+		time_t now = time(NULL);
+
+		if (now > url->uauth_expire) {
+			*error_r = t_strdup_printf("URLAUTH has expired");
+			return FALSE;
+		}
+	}
+
+	/* validate access */
+	if (!imap_urlauth_check_access(uctx, url, ignore_unknown_access,
+				       error_r))
+		return FALSE;
+	/* validate host:port */
+	if (!imap_urlauth_check_hostport(uctx, url, error_r))
+		return FALSE;
+	return TRUE;
+}
+
+int imap_urlauth_fetch_parsed(struct imap_urlauth_context *uctx,
+			      struct imap_url *url,
+			      struct imap_msgpart_url **mpurl_r,
+			      enum mail_error *error_code_r,
+			      const char **error_r)
+{
+	struct mail_user *user = uctx->user;
+	struct imap_msgpart_url *mpurl;
+	struct mailbox *box;
+	const char *error;
+	unsigned char mailbox_key[IMAP_URLAUTH_KEY_LEN];
+	int ret;
+
+	*error_r = NULL;
+	*error_code_r = MAIL_ERROR_NONE;
+
+	/* check urlauth mechanism, access, userid and authority */
+	if (!imap_urlauth_check(uctx, url, FALSE, error_r)) {
+		*error_code_r = MAIL_ERROR_PARAMS;
+		return 0;
+	}
+
+	/* validate target user */
+	if (strcmp(url->userid, user->username) != 0) {
+		*error_r = t_strdup_printf("Not permitted to fetch URLAUTH for user %s",
+					   url->userid);
+		*error_code_r = MAIL_ERROR_PARAMS;
+		return 0;
+	}
+
+	/* validate mailbox */
+	if ((ret = imap_msgpart_url_create(user, url, &mpurl, &error)) < 0) {
+		*error_r = t_strdup_printf("Invalid URLAUTH: %s", error);
+		*error_code_r = MAIL_ERROR_PARAMS;
+		return ret;
+	}
+
+	if ((ret = imap_msgpart_url_open_mailbox(mpurl, &box, error_code_r,
+						 &error)) < 0) {
+		*error_r = "Internal server error";
+		imap_msgpart_url_free(&mpurl);
+		return -1;
+	}
+
+	if (ret == 0) {
+		/* RFC says: `If the mailbox cannot be identified, an
+		   authorization token is calculated on the rump URL, using
+		   random "plausible" keys (selected by the server) as needed,
+		   before returning a validation failure. This prevents timing
+		   attacks aimed at identifying mailbox names.' */
+		random_fill_weak(mailbox_key, sizeof(mailbox_key));
+		(void)imap_urlauth_internal_verify(url->uauth_rumpurl,
+			mailbox_key, url->uauth_token, url->uauth_token_size);
+
+		*error_r = t_strdup_printf("Invalid URLAUTH: %s", error);
+		imap_msgpart_url_free(&mpurl);
+		return 0;
+	}
+
+	/* obtain mailbox key */
+	ret = imap_urlauth_backend_get_mailbox_key(uctx->backend, box, FALSE,
+						   mailbox_key);
+	if (ret < 0) {
+		*error_r = "Internal server error";
+		*error_code_r = MAIL_ERROR_TEMP;
+		imap_msgpart_url_free(&mpurl);
+		return -1;
+	}
+
+	if (ret == 0 ||
+	    !imap_urlauth_internal_verify(url->uauth_rumpurl, mailbox_key,
+					  url->uauth_token,
+					  url->uauth_token_size)) {
+		*error_r = "URLAUTH verification failed";
+		*error_code_r = MAIL_ERROR_PERM;
+		imap_msgpart_url_free(&mpurl);
+		ret = 0;
+	} else {
+		ret = 1;
+	}
+
+	safe_memset(mailbox_key, 0, sizeof(mailbox_key));
+	*mpurl_r = mpurl;
+	return ret;
+}
+
+int imap_urlauth_fetch(struct imap_urlauth_context *uctx,
+		       const char *urlauth, struct imap_msgpart_url **mpurl_r,
+		       enum mail_error *error_code_r, const char **error_r)
+{
+	struct imap_url *url;
+	enum imap_url_parse_flags url_flags = IMAP_URL_PARSE_ALLOW_URLAUTH;
+	const char *error;
+
+	/* validate URL */
+	if (imap_url_parse(urlauth, NULL, url_flags, &url, &error) < 0) {
+		*error_r = t_strdup_printf("Invalid URLAUTH: %s", error);
+		*error_code_r = MAIL_ERROR_PARAMS;
+		return 0;
+	}
+
+	return imap_urlauth_fetch_parsed(uctx, url, mpurl_r,
+					 error_code_r, error_r);
+}
+
+int imap_urlauth_reset_mailbox_key(struct imap_urlauth_context *uctx,
+				   struct mailbox *box)
+{
+	return imap_urlauth_backend_reset_mailbox_key(uctx->backend, box);
+}
+
+int imap_urlauth_reset_all_keys(struct imap_urlauth_context *uctx)
+{
+	return imap_urlauth_backend_reset_all_keys(uctx->backend);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-imap-urlauth/imap-urlauth.h	Sat Sep 15 23:57:08 2012 +0300
@@ -0,0 +1,49 @@
+#ifndef IMAP_URLAUTH_H
+#define IMAP_URLAUTH_H
+
+#define IMAP_URLAUTH_SOCKET_NAME "imap-urlauth"
+
+struct imap_url;
+struct imap_msgpart_url;
+struct imap_urlauth_context;
+
+struct imap_urlauth_config {
+	const char *dict_uri;
+
+	const char *url_host;
+	unsigned int url_port;
+
+	const char *socket_path;
+	const char *session_id;
+
+	const char *access_user;
+	const char *const *access_applications;
+};
+
+int imap_urlauth_init(struct mail_user *user,
+		      const struct imap_urlauth_config *config,
+		      struct imap_urlauth_context **ctx_r);
+void imap_urlauth_deinit(struct imap_urlauth_context **_uctx);
+
+int imap_urlauth_generate(struct imap_urlauth_context *uctx,
+			  const char *mechanism, const char *rumpurl,
+			  const char **urlauth_r, const char **error_r);
+
+bool imap_urlauth_check(struct imap_urlauth_context *uctx,
+			struct imap_url *url, bool ignore_unknown_access,
+			const char **error_r);
+
+int imap_urlauth_fetch_parsed(struct imap_urlauth_context *uctx,
+			      struct imap_url *url,
+			      struct imap_msgpart_url **mpurl_r,
+			      enum mail_error *error_code_r,
+			      const char **error_r);
+int imap_urlauth_fetch(struct imap_urlauth_context *uctx,
+		       const char *urlauth, struct imap_msgpart_url **mpurl_r,
+		       enum mail_error *error_code_r, const char **error_r);
+
+int imap_urlauth_reset_mailbox_key(struct imap_urlauth_context *uctx,
+				   struct mailbox *box);
+int imap_urlauth_reset_all_keys(struct imap_urlauth_context *uctx);
+
+#endif