view src/lib-imap-urlauth/imap-urlauth-fetch.c @ 22325:e01bc3015b2f

lib-index: Check .log.2 rotation only when syncing Instead of also whenever appending transactions to .log file. This shouldn't change the behavior much, and it's needed for the following change to work correctly.
author Timo Sirainen <timo.sirainen@dovecot.fi>
date Tue, 11 Jul 2017 15:33:56 +0300
parents 2e2563132d5f
children cb108f786fb4
line wrap: on
line source

/* Copyright (c) 2013-2017 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;

		unsigned int succeeded:1;
		unsigned int binary_has_nuls:1;
	} pending_reply;

	unsigned int failed:1;
	unsigned int waiting_local:1;
	unsigned int 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);
	if (ufetch->pending_reply.input != NULL)
		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);
		if (mail_user->mail_debug)
			i_debug("%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);
		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) {
		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;
}