summaryrefslogtreecommitdiffstats
path: root/src/lib-imap-urlauth/imap-urlauth-connection.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/lib-imap-urlauth/imap-urlauth-connection.c1027
1 files changed, 1027 insertions, 0 deletions
diff --git a/src/lib-imap-urlauth/imap-urlauth-connection.c b/src/lib-imap-urlauth/imap-urlauth-connection.c
new file mode 100644
index 0000000..5fce6f7
--- /dev/null
+++ b/src/lib-imap-urlauth/imap-urlauth-connection.c
@@ -0,0 +1,1027 @@
+/* Copyright (c) 2013-2018 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 "net.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;
+
+ bool 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, *service, *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;
+
+ bool 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\t2\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, const char *service,
+ 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->service = i_strdup(service);
+ 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);
+ i_free(conn->service);
+ 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)
+{
+ 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;
+
+ e_debug(conn->user->event,
+ "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)) {
+ e_debug(conn->user->event,
+ "imap-urlauth: No more requests pending; scheduling disconnect");
+ 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;
+ }
+
+ e_debug(conn->user->event,
+ "imap-urlauth: Fetching URL `%s'", urlreq->url);
+
+ cmd = t_str_new(128);
+ str_append(cmd, "URL\t");
+ str_append_tabescaped(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);
+
+ timeout_remove(&conn->to_idle);
+
+ e_debug(conn->user->event,
+ "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);
+}
+
+static void imap_urlauth_request_drop(struct imap_urlauth_connection *conn,
+ struct imap_urlauth_request *urlreq)
+{
+ if ((conn->state == IMAP_URLAUTH_STATE_REQUEST_PENDING ||
+ conn->state == IMAP_URLAUTH_STATE_REQUEST_WAIT) &&
+ conn->targets_head != NULL &&
+ conn->targets_head->requests_head == urlreq) {
+ /* cannot just drop pending request without breaking
+ protocol state */
+ return;
+ }
+ imap_urlauth_request_free(urlreq);
+
+}
+
+void imap_urlauth_request_abort(struct imap_urlauth_connection *conn,
+ struct imap_urlauth_request *urlreq)
+{
+ imap_urlauth_request_callback_t *callback;
+
+ callback = urlreq->callback;
+ urlreq->callback = NULL;
+ if (callback != NULL) {
+ T_BEGIN {
+ callback(NULL, urlreq->context);
+ } T_END;
+ }
+
+ imap_urlauth_request_drop(conn, 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;
+ imap_urlauth_request_callback_t *callback;
+ int ret = 1;
+
+ callback = urlreq->callback;
+ urlreq->callback = NULL;
+ if (callback != NULL) {
+ i_zero(&reply);
+ reply.url = urlreq->url;
+ reply.flags = urlreq->flags;
+ reply.succeeded = FALSE;
+ reply.error = error;
+
+ T_BEGIN {
+ ret = callback(&reply, urlreq->context);
+ } T_END;
+ }
+
+ void *urlreq_context = urlreq->context;
+ imap_urlauth_request_drop(conn, urlreq);
+
+ if (ret < 0) {
+ /* Drop any related requests upon error */
+ imap_urlauth_request_abort_by_context(conn, urlreq_context);
+ }
+
+ if (ret != 0)
+ imap_urlauth_connection_continue(conn);
+}
+
+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 (i_unlink(str_c(path)) < 0) {
+ /* shouldn't happen.. */
+ 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_autoclose(&conn->literal_fd,
+ SIZE_MAX);
+ 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_add_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;
+ imap_urlauth_request_callback_t *callback;
+ 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 */
+ i_zero(&reply);
+ reply.url = urlreq->url;
+ reply.flags = urlreq->flags;
+ reply.bodypartstruct = urlreq->bodypartstruct;
+ reply.binary_has_nuls = urlreq->binary_has_nuls ? 1 : 0;
+
+ if (conn->literal_size > 0) {
+ if (imap_urlauth_fetch_reply_set_literal_stream(conn, &reply) < 0)
+ return -1;
+ }
+ reply.succeeded = TRUE;
+
+ ret = 1;
+ callback = urlreq->callback;
+ urlreq->callback = NULL;
+ if (callback != NULL) T_BEGIN {
+ ret = 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;
+ }
+ conn->state = IMAP_URLAUTH_STATE_REQUEST_WAIT;
+ 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->state == IMAP_URLAUTH_STATE_AUTHENTICATING) {
+ e_debug(conn->user->event,
+ "imap-urlauth: Successfully authenticated to service");
+ } else {
+ e_debug(conn->user->event,
+ "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) {
+ e_debug(conn->user->event,
+ "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;
+ }
+
+ e_debug(conn->user->event,
+ "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: %s",
+ str_sanitize(response, 80));
+ 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)
+{
+ 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 (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 (running standalone?).");
+ imap_urlauth_connection_abort(conn, NULL);
+ return -1;
+ }
+
+ e_debug(conn->user->event, "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);
+ imap_urlauth_connection_abort(conn, NULL);
+ return -1;
+ }
+
+ timeout_remove(&conn->to_reconnect);
+
+ conn->fd = fd;
+ conn->input = i_stream_create_fd(fd, SIZE_MAX);
+ conn->output = o_stream_create_fd(fd, SIZE_MAX);
+ 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%s\t",
+ conn->service, my_pid);
+ str_append_tabescaped(str, conn->user->username);
+ str_append_c(str, '\t');
+ if (conn->session_id != NULL)
+ str_append_tabescaped(str, conn->session_id);
+ str_append_c(str, '\t');
+ str_append_tabescaped(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 (reason == NULL)
+ e_debug(conn->user->event, "imap-urlauth: Disconnecting from service");
+ else
+ e_debug(conn->user->event, "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;
+ }
+
+ buffer_free(&conn->literal_buf);
+ timeout_remove(&conn->to_reconnect);
+ 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) {
+ e_debug(conn->user->event, "imap-urlauth: Scheduling reconnect");
+ 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;
+}