summaryrefslogtreecommitdiffstats
path: root/src/lib-imap-urlauth/imap-urlauth-fetch.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/lib-imap-urlauth/imap-urlauth-fetch.c530
1 files changed, 530 insertions, 0 deletions
diff --git a/src/lib-imap-urlauth/imap-urlauth-fetch.c b/src/lib-imap-urlauth/imap-urlauth-fetch.c
new file mode 100644
index 0000000..d5746f1
--- /dev/null
+++ b/src/lib-imap-urlauth/imap-urlauth-fetch.c
@@ -0,0 +1,530 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "llist.h"
+#include "array.h"
+#include "net.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 {
+ unsigned int refcount;
+ 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;
+
+ bool succeeded:1;
+ bool binary_has_nuls:1;
+ } pending_reply;
+
+ bool failed:1;
+ bool waiting_local:1;
+ bool waiting_service: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) {
+ ufetch->pending_requests--;
+ imap_msgpart_url_free(&ufetch->local_url);
+ }
+
+ i_free_and_null(ufetch->pending_reply.url);
+ i_free_and_null(ufetch->pending_reply.bodypartstruct);
+ i_free_and_null(ufetch->pending_reply.error);
+ i_stream_unref(&ufetch->pending_reply.input);
+
+ url = ufetch->local_urls_head;
+ for (; url != NULL; url = url_next) {
+ url_next = url->next;
+ i_free(url->url);
+ i_free(url);
+ ufetch->pending_requests--;
+ }
+ ufetch->local_urls_head = ufetch->local_urls_tail = NULL;
+}
+
+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->refcount = 1;
+ ufetch->uctx = uctx;
+ ufetch->callback = callback;
+ ufetch->context = context;
+ return ufetch;
+}
+
+static void imap_urlauth_fetch_ref(struct imap_urlauth_fetch *ufetch)
+{
+ i_assert(ufetch->refcount > 0);
+ ufetch->refcount++;
+}
+
+static void imap_urlauth_fetch_unref(struct imap_urlauth_fetch **_ufetch)
+{
+ struct imap_urlauth_fetch *ufetch = *_ufetch;
+
+ i_assert(ufetch->refcount > 0);
+
+ *_ufetch = NULL;
+ if (--ufetch->refcount > 0)
+ return;
+
+ ufetch->refcount++;
+ imap_urlauth_fetch_abort(ufetch);
+ ufetch->refcount--;
+ i_assert(ufetch->refcount == 0);
+
+ /* dont leave the connection in limbo; make sure continue is called */
+ if (ufetch->waiting_service)
+ imap_urlauth_connection_continue(ufetch->uctx->conn);
+ i_free(ufetch);
+}
+
+void imap_urlauth_fetch_deinit(struct imap_urlauth_fetch **_ufetch)
+{
+ imap_urlauth_fetch_unref(_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--;
+
+ i_zero(&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_local = TRUE;
+ ufetch->pending_requests++;
+ } 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 = NULL;
+ int ret;
+
+ 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) {
+ 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) {
+ ret = imap_msgpart_url_get_bodypartstructure(mpurl, &bpstruct, &error);
+ if (ret <= 0) {
+ if (ret == 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 */
+ i_zero(&mpresult);
+ if (success && ((url_flags & IMAP_URLAUTH_FETCH_FLAG_BODY) != 0 ||
+ (url_flags & IMAP_URLAUTH_FETCH_FLAG_BINARY) != 0)) {
+ ret = imap_msgpart_url_read_part(mpurl, &mpresult, &error);
+ if (ret <= 0) {
+ if (ret == 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 " : ""));
+ }
+ }
+
+ ufetch->pending_requests--;
+
+ if (!success && ret < 0) {
+ if (mpurl != NULL)
+ imap_msgpart_url_free(&mpurl);
+ (void)ufetch->callback(NULL, TRUE, ufetch->context);
+ imap_urlauth_fetch_fail(ufetch);
+ return;
+ }
+
+ i_zero(&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_local = TRUE;
+ ufetch->pending_requests++;
+ } 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_local && 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;
+ ufetch->waiting_service = TRUE;
+ return 0;
+ }
+
+ ufetch->waiting_local = FALSE;
+ ufetch->pending_requests--;
+
+ imap_urlauth_fetch_ref(ufetch);
+
+ 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;
+ } else if (ret == 0) {
+ ufetch->waiting_service = TRUE;
+ ufetch->pending_requests++;
+ }
+
+ imap_urlauth_fetch_unref(&ufetch);
+ return ret;
+}
+
+int imap_urlauth_fetch_url(struct imap_urlauth_fetch *ufetch, const char *url,
+ enum imap_urlauth_fetch_flags url_flags)
+{
+ struct imap_urlauth_context *uctx = ufetch->uctx;
+ enum imap_url_parse_flags url_parse_flags =
+ IMAP_URL_PARSE_ALLOW_URLAUTH;
+ struct mail_user *mail_user = uctx->user;
+ struct imap_url *imap_url;
+ const char *error, *errormsg;
+
+ /* 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);
+ e_debug(mail_user->event, "%s", errormsg);
+ ufetch->pending_requests++;
+ imap_urlauth_fetch_ref(ufetch);
+ imap_urlauth_fetch_error(ufetch, url, url_flags, errormsg);
+ imap_urlauth_fetch_unref(&ufetch);
+ return 1;
+ }
+
+ return imap_urlauth_fetch_url_parsed(ufetch, url, imap_url, url_flags);
+}
+
+int imap_urlauth_fetch_url_parsed(struct imap_urlauth_fetch *ufetch,
+ const char *url, struct imap_url *imap_url,
+ enum imap_urlauth_fetch_flags url_flags)
+{
+ struct imap_urlauth_context *uctx = ufetch->uctx;
+ struct mail_user *mail_user = uctx->user;
+ const char *error, *errormsg;
+ int ret = 0;
+
+ ufetch->failed = FALSE;
+ ufetch->pending_requests++;
+
+ imap_urlauth_fetch_ref(ufetch);
+
+ /* if access user and target user match, handle fetch request locally */
+ if (imap_url->userid != NULL &&
+ strcmp(mail_user->username, imap_url->userid) == 0) {
+
+ if (ufetch->waiting_local) {
+ 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);
+ e_debug(mail_user->event, "%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) {
+ i_assert(uctx->conn != NULL);
+ (void)imap_urlauth_request_new(uctx->conn, imap_url->userid,
+ url, url_flags,
+ imap_urlauth_fetch_request_callback, ufetch);
+ i_assert(uctx->conn != NULL);
+ if (imap_urlauth_connection_connect(uctx->conn) < 0)
+ ret = -1;
+ }
+ if (ret >= 0)
+ ret = (ufetch->pending_requests > 0 ? 0 : 1);
+
+ imap_urlauth_fetch_unref(&ufetch);
+ return ret;
+}
+
+static bool imap_urlauth_fetch_do_continue(struct imap_urlauth_fetch *ufetch)
+{
+ struct imap_urlauth_fetch_url *url, *url_next;
+ int ret;
+
+ if (ufetch->failed)
+ return FALSE;
+
+ if (!ufetch->waiting_local && !ufetch->waiting_service) {
+ /* not currently waiting for anything */
+ return ufetch->pending_requests > 0;
+ }
+
+ /* we finished a request */
+ ufetch->pending_requests--;
+
+ if (!ufetch->waiting_local) {
+ /* not waiting for local request handling */
+ ufetch->waiting_service = FALSE;
+ imap_urlauth_connection_continue(ufetch->uctx->conn);
+ return ufetch->pending_requests > 0;
+ }
+
+ /* finished local request */
+ if (ufetch->local_url != NULL) {
+ imap_msgpart_url_free(&ufetch->local_url);
+ }
+ ufetch->waiting_local = FALSE;
+
+ /* handle pending remote reply */
+ if (ufetch->pending_reply.url != NULL) {
+ struct imap_urlauth_fetch_reply reply;
+
+ ufetch->pending_requests--;
+
+ i_zero(&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;
+ }
+
+ if (ret == 0) {
+ ufetch->waiting_service = TRUE;
+ ufetch->pending_requests++;
+ return TRUE;
+ }
+
+ ufetch->waiting_service = FALSE;
+ imap_urlauth_connection_continue(ufetch->uctx->conn);
+ }
+
+ /* 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_local)
+ return TRUE;
+ url = url_next;
+ }
+
+ return ufetch->pending_requests > 0;
+}
+
+bool imap_urlauth_fetch_continue(struct imap_urlauth_fetch *ufetch)
+{
+ bool pending;
+
+ imap_urlauth_fetch_ref(ufetch);
+ pending = imap_urlauth_fetch_do_continue(ufetch);
+ imap_urlauth_fetch_unref(&ufetch);
+
+ return pending;
+}
+
+bool imap_urlauth_fetch_is_pending(struct imap_urlauth_fetch *ufetch)
+{
+ return ufetch->pending_requests > 0;
+}