diff options
Diffstat (limited to '')
-rw-r--r-- | src/lib-imap-urlauth/imap-urlauth-fetch.c | 530 |
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; +} |