Mercurial > dovecot > core-2.2
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?..)
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, ¶ms)) + 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