/* Copyright (c) 2002-2010 Dovecot authors, see the included COPYING file */

#include "common.h"
#include "ioloop.h"
#include "buffer.h"
#include "hash.h"
#include "hex-binary.h"
#include "str.h"
#include "safe-memset.h"
#include "str-sanitize.h"
#include "strescape.h"
#include "var-expand.h"
#include "auth-cache.h"
#include "auth-request.h"
#include "auth-client-connection.h"
#include "auth-master-connection.h"
#include "passdb.h"
#include "passdb-blocking.h"
#include "userdb-blocking.h"
#include "passdb-cache.h"
#include "password-scheme.h"

#include <stdlib.h>
#include <sys/stat.h>

static void auth_request_userdb_reply_update_user(struct auth_request *request);

struct auth_request *
auth_request_new(struct auth *auth, const struct mech_module *mech,
		 mech_callback_t *callback, void *context)
{
	struct auth_request *request;

	request = mech->auth_new();
	request->state = AUTH_REQUEST_STATE_NEW;
	request->passdb = auth->passdbs;
	request->userdb = auth->userdbs;

	request->refcount = 1;
	request->last_access = ioloop_time;

	request->auth = auth;
	request->mech = mech;
	request->mech_name = mech == NULL ? NULL : mech->mech_name;
	request->callback = callback;
	request->context = context;
	return request;
}

struct auth_request *auth_request_new_dummy(struct auth *auth)
{
	struct auth_request *auth_request;
	pool_t pool;

	pool = pool_alloconly_create("auth_request", 1024);
	auth_request = p_new(pool, struct auth_request, 1);
	auth_request->pool = pool;

	auth_request->refcount = 1;
	auth_request->last_access = ioloop_time;
	auth_request->auth = auth;
	auth_request->passdb = auth->passdbs;
	auth_request->userdb = auth->userdbs;

	return auth_request;
}

void auth_request_success(struct auth_request *request,
			  const void *data, size_t data_size)
{
	i_assert(request->state == AUTH_REQUEST_STATE_MECH_CONTINUE);

	if (request->passdb_failure) {
		/* password was valid, but some other check failed. */
		auth_request_fail(request);
		return;
	}

	request->state = AUTH_REQUEST_STATE_FINISHED;
	request->successful = TRUE;
	request->last_access = ioloop_time;
	request->callback(request, AUTH_CLIENT_RESULT_SUCCESS,
			  data, data_size);
}

void auth_request_fail(struct auth_request *request)
{
	i_assert(request->state == AUTH_REQUEST_STATE_MECH_CONTINUE);

	request->state = AUTH_REQUEST_STATE_FINISHED;
	request->last_access = ioloop_time;
	request->callback(request, AUTH_CLIENT_RESULT_FAILURE, NULL, 0);
}

void auth_request_internal_failure(struct auth_request *request)
{
	request->internal_failure = TRUE;
	auth_request_fail(request);
}

void auth_request_ref(struct auth_request *request)
{
	request->refcount++;
}

void auth_request_unref(struct auth_request **_request)
{
	struct auth_request *request = *_request;

	*_request = NULL;
	i_assert(request->refcount > 0);
	if (--request->refcount > 0)
		return;

	if (request->mech != NULL)
		request->mech->auth_free(request);
	else
		pool_unref(&request->pool);
}

void auth_request_export(struct auth_request *request,
			 struct auth_stream_reply *reply)
{
	auth_stream_reply_add(reply, "user", request->user);
	auth_stream_reply_add(reply, "service", request->service);

        if (request->master_user != NULL) {
		auth_stream_reply_add(reply, "master_user",
				      request->master_user);
	}
	auth_stream_reply_add(reply, "original_username",
			      request->original_username);

	if (request->local_ip.family != 0) {
		auth_stream_reply_add(reply, "lip",
				      net_ip2addr(&request->local_ip));
	}
	if (request->remote_ip.family != 0) {
		auth_stream_reply_add(reply, "rip",
				      net_ip2addr(&request->remote_ip));
	}
	if (request->local_port != 0) {
		auth_stream_reply_add(reply, "lport",
				      dec2str(request->local_port));
	}
	if (request->remote_port != 0) {
		auth_stream_reply_add(reply, "rport",
				      dec2str(request->remote_port));
	}
	if (request->secured)
		auth_stream_reply_add(reply, "secured", "1");
	if (request->skip_password_check)
		auth_stream_reply_add(reply, "skip_password_check", "1");
	if (request->valid_client_cert)
		auth_stream_reply_add(reply, "valid-client-cert", "1");
	if (request->mech_name != NULL)
		auth_stream_reply_add(reply, "mech", request->mech_name);
}

bool auth_request_import(struct auth_request *request,
			 const char *key, const char *value)
{
	if (strcmp(key, "user") == 0)
		request->user = p_strdup(request->pool, value);
	else if (strcmp(key, "master_user") == 0)
		request->master_user = p_strdup(request->pool, value);
	else if (strcmp(key, "original_username") == 0)
		request->original_username = p_strdup(request->pool, value);
	else if (strcmp(key, "cert_username") == 0) {
		if (request->auth->ssl_username_from_cert) {
			/* get username from SSL certificate. it overrides
			   the username given by the auth mechanism. */
			request->user = p_strdup(request->pool, value);
			request->cert_username = TRUE;
		}
	} else if (strcmp(key, "service") == 0)
		request->service = p_strdup(request->pool, value);
	else if (strcmp(key, "lip") == 0)
		net_addr2ip(value, &request->local_ip);
	else if (strcmp(key, "rip") == 0)
		net_addr2ip(value, &request->remote_ip);
	else if (strcmp(key, "lport") == 0)
		request->local_port = atoi(value);
	else if (strcmp(key, "rport") == 0)
		request->remote_port = atoi(value);
	else if (strcmp(key, "secured") == 0)
		request->secured = TRUE;
	else if (strcmp(key, "nologin") == 0)
		request->no_login = TRUE;
	else if (strcmp(key, "valid-client-cert") == 0)
		request->valid_client_cert = TRUE;
	else if (strcmp(key, "skip_password_check") == 0) {
		i_assert(request->master_user !=  NULL);
		request->skip_password_check = TRUE;
	} else if (strcmp(key, "mech") == 0)
		request->mech_name = p_strdup(request->pool, value);
	else
		return FALSE;

	return TRUE;
}

void auth_request_initial(struct auth_request *request,
			  const unsigned char *data, size_t data_size)
{
	i_assert(request->state == AUTH_REQUEST_STATE_NEW);

	request->state = AUTH_REQUEST_STATE_MECH_CONTINUE;
	request->mech->auth_initial(request, data, data_size);
}

void auth_request_continue(struct auth_request *request,
			   const unsigned char *data, size_t data_size)
{
	i_assert(request->state == AUTH_REQUEST_STATE_MECH_CONTINUE);

	request->last_access = ioloop_time;
	request->mech->auth_continue(request, data, data_size);
}

static void auth_request_save_cache(struct auth_request *request,
				    enum passdb_result result)
{
	struct passdb_module *passdb = request->passdb->passdb;
	const char *extra_fields;
	string_t *str;

	switch (result) {
	case PASSDB_RESULT_USER_UNKNOWN:
	case PASSDB_RESULT_PASSWORD_MISMATCH:
	case PASSDB_RESULT_OK:
	case PASSDB_RESULT_SCHEME_NOT_AVAILABLE:
		/* can be cached */
		break;
	case PASSDB_RESULT_USER_DISABLED:
	case PASSDB_RESULT_PASS_EXPIRED:
		/* FIXME: we can't cache this now, or cache lookup would
		   return success. */
		return;
	case PASSDB_RESULT_INTERNAL_FAILURE:
		i_unreached();
	}

	extra_fields = request->extra_fields == NULL ? NULL :
		auth_stream_reply_export(request->extra_fields);

	if (passdb_cache == NULL || passdb->cache_key == NULL ||
	    request->master_user != NULL)
		return;

	if (result < 0) {
		/* lookup failed. */
		if (result == PASSDB_RESULT_USER_UNKNOWN) {
			auth_cache_insert(passdb_cache, request,
					  passdb->cache_key, "", FALSE);
		}
		return;
	}

	if (!request->no_password && request->passdb_password == NULL) {
		/* passdb didn't provide the correct password */
		if (result != PASSDB_RESULT_OK ||
		    request->mech_password == NULL)
			return;

		/* we can still cache valid password lookups though.
		   strdup() it so that mech_password doesn't get
		   cleared too early. */
		request->passdb_password =
			p_strconcat(request->pool, "{plain}",
				    request->mech_password, NULL);
	}

	/* save all except the currently given password in cache */
	str = t_str_new(256);
	if (request->passdb_password != NULL) {
		if (*request->passdb_password != '{') {
			/* cached passwords must have a known scheme */
			str_append_c(str, '{');
			str_append(str, passdb->default_pass_scheme);
			str_append_c(str, '}');
		}
		if (strchr(request->passdb_password, '\t') != NULL)
			i_panic("%s: Password contains TAB", request->user);
		if (strchr(request->passdb_password, '\n') != NULL)
			i_panic("%s: Password contains LF", request->user);
		str_append(str, request->passdb_password);
	}

	if (extra_fields != NULL && *extra_fields != '\0') {
		str_append_c(str, '\t');
		str_append(str, extra_fields);
	}
	if (request->extra_cache_fields != NULL) {
		extra_fields =
			auth_stream_reply_export(request->extra_cache_fields);
		if (*extra_fields != '\0') {
			str_append_c(str, '\t');
			str_append(str, extra_fields);
		}
	}
	auth_cache_insert(passdb_cache, request, passdb->cache_key, str_c(str),
			  result == PASSDB_RESULT_OK);
}

static bool auth_request_master_lookup_finish(struct auth_request *request)
{
	if (request->passdb_failure)
		return TRUE;

	/* master login successful. update user and master_user variables. */
	auth_request_log_info(request, "passdb", "Master user logging in as %s",
			      request->requested_login_user);

	request->master_user = request->user;
	request->user = request->requested_login_user;
	request->requested_login_user = NULL;

	request->skip_password_check = TRUE;
	request->passdb_password = NULL;

	if (!request->passdb->pass) {
		/* skip the passdb lookup, we're authenticated now. */
		return TRUE;
	}

	/* the authentication continues with passdb lookup for the
	   requested_login_user. */
	request->passdb = request->auth->passdbs;
	return FALSE;
}

static bool
auth_request_handle_passdb_callback(enum passdb_result *result,
				    struct auth_request *request)
{
	if (request->passdb_password != NULL) {
		safe_memset(request->passdb_password, 0,
			    strlen(request->passdb_password));
	}

	if (request->passdb->deny && *result != PASSDB_RESULT_USER_UNKNOWN) {
		/* deny passdb. we can get through this step only if the
		   lookup returned that user doesn't exist in it. internal
		   errors are fatal here. */
		if (*result != PASSDB_RESULT_INTERNAL_FAILURE) {
			auth_request_log_info(request, "passdb",
					      "User found from deny passdb");
			*result = PASSDB_RESULT_USER_DISABLED;
		}
	} else if (*result == PASSDB_RESULT_OK) {
		/* success */
		if (request->requested_login_user != NULL) {
			/* this was a master user lookup. */
			if (!auth_request_master_lookup_finish(request))
				return FALSE;
		} else {
			if (request->passdb->pass) {
				/* this wasn't the final passdb lookup,
				   continue to next passdb */
				request->passdb = request->passdb->next;
				request->passdb_password = NULL;
				return FALSE;
			}
		}
	} else if (*result == PASSDB_RESULT_PASS_EXPIRED) {
		if (request->extra_fields == NULL) {
			request->extra_fields =
				auth_stream_reply_init(request->pool);
		}
	        auth_stream_reply_add(request->extra_fields, "reason",
				      "Password expired");
	} else if (request->passdb->next != NULL &&
		   *result != PASSDB_RESULT_USER_DISABLED) {
		/* try next passdb. */
                request->passdb = request->passdb->next;
		request->passdb_password = NULL;

                if (*result == PASSDB_RESULT_INTERNAL_FAILURE) {
			/* remember that we have had an internal failure. at
			   the end return internal failure if we couldn't
			   successfully login. */
			request->passdb_internal_failure = TRUE;
		}
		if (request->extra_fields != NULL)
			auth_stream_reply_reset(request->extra_fields);

		return FALSE;
	} else if (request->passdb_internal_failure) {
		/* last passdb lookup returned internal failure. it may have
		   had the correct password, so return internal failure
		   instead of plain failure. */
		*result = PASSDB_RESULT_INTERNAL_FAILURE;
	}

	return TRUE;
}

static void
auth_request_verify_plain_callback_finish(enum passdb_result result,
					  struct auth_request *request)
{
	if (!auth_request_handle_passdb_callback(&result, request)) {
		/* try next passdb */
		auth_request_verify_plain(request, request->mech_password,
			request->private_callback.verify_plain);
	} else {
		auth_request_ref(request);
		request->private_callback.verify_plain(result, request);
		safe_memset(request->mech_password, 0,
			    strlen(request->mech_password));
		auth_request_unref(&request);
	}
}

void auth_request_verify_plain_callback(enum passdb_result result,
					struct auth_request *request)
{
	i_assert(request->state == AUTH_REQUEST_STATE_PASSDB);

	request->state = AUTH_REQUEST_STATE_MECH_CONTINUE;

	if (result != PASSDB_RESULT_INTERNAL_FAILURE)
		auth_request_save_cache(request, result);
	else {
		/* lookup failed. if we're looking here only because the
		   request was expired in cache, fallback to using cached
		   expired record. */
		const char *cache_key = request->passdb->passdb->cache_key;

		if (passdb_cache_verify_plain(request, cache_key,
					      request->mech_password,
					      &result, TRUE)) {
			auth_request_log_info(request, "passdb",
				"Fallbacking to expired data from cache");
		}
	}

	auth_request_verify_plain_callback_finish(result, request);
}

static bool password_has_illegal_chars(const char *password)
{
	for (; *password != '\0'; password++) {
		switch (*password) {
		case '\001':
		case '\t':
		case '\r':
		case '\n':
			/* these characters have a special meaning in internal
			   protocols, make sure the password doesn't
			   accidentally get there unescaped. */
			return TRUE;
		}
	}
	return FALSE;
}

void auth_request_verify_plain(struct auth_request *request,
			       const char *password,
			       verify_plain_callback_t *callback)
{
	struct passdb_module *passdb;
	enum passdb_result result;
	const char *cache_key;

	i_assert(request->state == AUTH_REQUEST_STATE_MECH_CONTINUE);

        if (request->passdb == NULL) {
                /* no masterdbs, master logins not supported */
                i_assert(request->requested_login_user != NULL);
		auth_request_log_info(request, "passdb",
			"Attempted master login with no master passdbs "
			"(trying to log in as user: %s)",
			request->requested_login_user);
		callback(PASSDB_RESULT_USER_UNKNOWN, request);
		return;
	}

	if (password_has_illegal_chars(password)) {
		auth_request_log_info(request, "passdb",
			"Attempted login with password having illegal chars");
		callback(PASSDB_RESULT_USER_UNKNOWN, request);
		return;
	}

        passdb = request->passdb->passdb;
	if (request->mech_password == NULL)
		request->mech_password = p_strdup(request->pool, password);
	else
		i_assert(request->mech_password == password);
	request->private_callback.verify_plain = callback;

	cache_key = passdb_cache == NULL ? NULL : passdb->cache_key;
	if (passdb_cache_verify_plain(request, cache_key, password,
				      &result, FALSE)) {
		auth_request_verify_plain_callback_finish(result, request);
		return;
	}

	request->state = AUTH_REQUEST_STATE_PASSDB;
	request->credentials_scheme = NULL;

	if (passdb->blocking)
		passdb_blocking_verify_plain(request);
	else {
		passdb->iface.verify_plain(request, password,
					   auth_request_verify_plain_callback);
	}
}

static void
auth_request_lookup_credentials_finish(enum passdb_result result,
				       const unsigned char *credentials,
				       size_t size,
				       struct auth_request *request)
{
	if (!auth_request_handle_passdb_callback(&result, request)) {
		/* try next passdb */
		auth_request_lookup_credentials(request,
			request->credentials_scheme,
                	request->private_callback.lookup_credentials);
	} else {
		if (request->auth->verbose_debug_passwords &&
		    result == PASSDB_RESULT_OK) {
			auth_request_log_debug(request, "password",
				"Credentials: %s",
				binary_to_hex(credentials, size));
		}
		request->private_callback.
			lookup_credentials(result, credentials, size, request);
	}
}

void auth_request_lookup_credentials_callback(enum passdb_result result,
					      const unsigned char *credentials,
					      size_t size,
					      struct auth_request *request)
{
	const char *cache_cred, *cache_scheme;

	i_assert(request->state == AUTH_REQUEST_STATE_PASSDB);

	request->state = AUTH_REQUEST_STATE_MECH_CONTINUE;

	if (result != PASSDB_RESULT_INTERNAL_FAILURE)
		auth_request_save_cache(request, result);
	else {
		/* lookup failed. if we're looking here only because the
		   request was expired in cache, fallback to using cached
		   expired record. */
		const char *cache_key = request->passdb->passdb->cache_key;

		if (passdb_cache_lookup_credentials(request, cache_key,
						    &cache_cred, &cache_scheme,
						    &result, TRUE)) {
			auth_request_log_info(request, "passdb",
				"Fallbacking to expired data from cache");
			passdb_handle_credentials(
				result, cache_cred, cache_scheme,
				auth_request_lookup_credentials_finish,
				request);
			return;
		}
	}

	auth_request_lookup_credentials_finish(result, credentials, size,
					       request);
}

void auth_request_lookup_credentials(struct auth_request *request,
				     const char *scheme,
				     lookup_credentials_callback_t *callback)
{
	struct passdb_module *passdb = request->passdb->passdb;
	const char *cache_key, *cache_cred, *cache_scheme;
	enum passdb_result result;

	i_assert(request->state == AUTH_REQUEST_STATE_MECH_CONTINUE);

	request->credentials_scheme = p_strdup(request->pool, scheme);
	request->private_callback.lookup_credentials = callback;

	cache_key = passdb_cache == NULL ? NULL : passdb->cache_key;
	if (cache_key != NULL) {
		if (passdb_cache_lookup_credentials(request, cache_key,
						    &cache_cred, &cache_scheme,
						    &result, FALSE)) {
			passdb_handle_credentials(
				result, cache_cred, cache_scheme,
				auth_request_lookup_credentials_finish,
				request);
			return;
		}
	}

	request->state = AUTH_REQUEST_STATE_PASSDB;

	if (passdb->iface.lookup_credentials == NULL) {
		/* this passdb doesn't support credentials */
		auth_request_log_debug(request, "password",
			"passdb doesn't support credential lookups");
		auth_request_lookup_credentials_callback(
			PASSDB_RESULT_SCHEME_NOT_AVAILABLE, NULL, 0, request);
	} else if (passdb->blocking) {
		passdb_blocking_lookup_credentials(request);
	} else {
		passdb->iface.lookup_credentials(request,
			auth_request_lookup_credentials_callback);
	}
}

void auth_request_set_credentials(struct auth_request *request,
				  const char *scheme, const char *data,
				  set_credentials_callback_t *callback)
{
	struct passdb_module *passdb = request->passdb->passdb;
	const char *cache_key, *new_credentials;

	cache_key = passdb_cache == NULL ? NULL : passdb->cache_key;
	if (cache_key != NULL)
		auth_cache_remove(passdb_cache, request, cache_key);

	request->private_callback.set_credentials = callback;

	new_credentials = t_strdup_printf("{%s}%s", scheme, data);
	if (passdb->blocking)
		passdb_blocking_set_credentials(request, new_credentials);
	else if (passdb->iface.set_credentials != NULL) {
		passdb->iface.set_credentials(request, new_credentials,
					      callback);
	} else {
		/* this passdb doesn't support credentials update */
		callback(PASSDB_RESULT_INTERNAL_FAILURE, request);
	}
}

static void auth_request_userdb_save_cache(struct auth_request *request,
					   enum userdb_result result)
{
	struct userdb_module *userdb = request->userdb->userdb;
	const char *str;

	if (passdb_cache == NULL || userdb->cache_key == NULL ||
	    request->master_user != NULL)
		return;

	str = result == USERDB_RESULT_USER_UNKNOWN ? "" :
		auth_stream_reply_export(request->userdb_reply);
	/* last_success has no meaning with userdb */
	auth_cache_insert(passdb_cache, request, userdb->cache_key, str, FALSE);
}

static bool auth_request_lookup_user_cache(struct auth_request *request,
					   const char *key,
					   struct auth_stream_reply **reply_r,
					   enum userdb_result *result_r,
					   bool use_expired)
{
	const char *value;
	struct auth_cache_node *node;
	bool expired, neg_expired;

	if (request->master_user != NULL)
		return FALSE;

	value = auth_cache_lookup(passdb_cache, request, key, &node,
				  &expired, &neg_expired);
	if (value == NULL || (expired && !use_expired))
		return FALSE;

	if (*value == '\0') {
		/* negative cache entry */
		*result_r = USERDB_RESULT_USER_UNKNOWN;
		*reply_r = auth_stream_reply_init(request->pool);
		return TRUE;
	}

	*result_r = USERDB_RESULT_OK;
	*reply_r = auth_stream_reply_init(request->pool);
	auth_stream_reply_import(*reply_r, value);
	return TRUE;
}

void auth_request_userdb_callback(enum userdb_result result,
				  struct auth_request *request)
{
	struct userdb_module *userdb = request->userdb->userdb;

	if (result != USERDB_RESULT_OK && request->userdb->next != NULL) {
		/* try next userdb. */
		if (result == USERDB_RESULT_INTERNAL_FAILURE)
			request->userdb_internal_failure = TRUE;

		request->userdb = request->userdb->next;
		auth_request_lookup_user(request,
					 request->private_callback.userdb);
		return;
	}

	if (request->userdb_internal_failure && result != USERDB_RESULT_OK) {
		/* one of the userdb lookups failed. the user might have been
		   in there, so this is an internal failure */
		result = USERDB_RESULT_INTERNAL_FAILURE;
	} else if (result == USERDB_RESULT_USER_UNKNOWN &&
		   request->client_pid != 0) {
		/* this was an actual login attempt, the user should
		   have been found. */
		if (request->auth->userdbs->next == NULL) {
			auth_request_log_error(request, "userdb",
				"user not found from userdb %s",
				request->auth->userdbs->userdb->iface->name);
		} else {
			auth_request_log_error(request, "userdb",
				"user not found from any userdbs");
		}
	}

	if (result != USERDB_RESULT_INTERNAL_FAILURE)
		auth_request_userdb_save_cache(request, result);
	else if (passdb_cache != NULL && userdb->cache_key != NULL) {
		/* lookup failed. if we're looking here only because the
		   request was expired in cache, fallback to using cached
		   expired record. */
		const char *cache_key = userdb->cache_key;
		struct auth_stream_reply *reply;

		if (auth_request_lookup_user_cache(request, cache_key, &reply,
						   &result, TRUE)) {
			request->userdb_reply = reply;
			auth_request_log_info(request, "userdb",
				"Fallbacking to expired data from cache");
		}
	}

        request->private_callback.userdb(result, request);
}

void auth_request_lookup_user(struct auth_request *request,
			      userdb_callback_t *callback)
{
	struct userdb_module *userdb = request->userdb->userdb;
	const char *cache_key;

	request->private_callback.userdb = callback;
	request->userdb_lookup = TRUE;

	/* (for now) auth_cache is shared between passdb and userdb */
	cache_key = passdb_cache == NULL ? NULL : userdb->cache_key;
	if (cache_key != NULL) {
		struct auth_stream_reply *reply;
		enum userdb_result result;

		if (auth_request_lookup_user_cache(request, cache_key, &reply,
						   &result, FALSE)) {
			request->userdb_reply = reply;
			request->private_callback.userdb(result, request);
			return;
		}
	}

	if (userdb->blocking)
		userdb_blocking_lookup(request);
	else
		userdb->iface->lookup(request, auth_request_userdb_callback);
}

static char *
auth_request_fix_username(struct auth_request *request, const char *username,
                          const char **error_r)
{
	unsigned char *p;
        char *user;

	if (strchr(username, '@') == NULL &&
	    request->auth->default_realm != NULL) {
		user = p_strconcat(request->pool, username, "@",
                                   request->auth->default_realm, NULL);
	} else {
		user = p_strdup(request->pool, username);
	}

        for (p = (unsigned char *)user; *p != '\0'; p++) {
		if (request->auth->username_translation[*p & 0xff] != 0)
			*p = request->auth->username_translation[*p & 0xff];
		if (request->auth->username_chars[*p & 0xff] == 0) {
			*error_r = t_strdup_printf(
				"Username contains disallowed character: "
				"0x%02x", *p);
			return NULL;
		}
	}

	if (request->auth->username_format != NULL) {
		/* username format given, put it through variable expansion.
		   we'll have to temporarily replace request->user to get
		   %u to be the wanted username */
		const struct var_expand_table *table;
		char *old_username;
		string_t *dest;

		old_username = request->user;
		request->user = user;

		dest = t_str_new(256);
		table = auth_request_get_var_expand_table(request, NULL);
		var_expand(dest, request->auth->username_format, table);
		user = p_strdup(request->pool, str_c(dest));

		request->user = old_username;
	}

        return user;
}

bool auth_request_set_username(struct auth_request *request,
			       const char *username, const char **error_r)
{
	const char *p, *login_username = NULL;

	if (request->auth->master_user_separator != '\0' &&
	    !request->userdb_lookup) {
		/* check if the username contains a master user */
		p = strchr(username, request->auth->master_user_separator);
		if (p != NULL) {
			/* it does, set it. */
			login_username = t_strdup_until(username, p);

			if (*login_username == '\0') {
				*error_r = "Empty login username";
				return FALSE;
			}

			/* username is the master user */
			username = p + 1;
		}
	}

	if (request->original_username == NULL) {
		/* the username may change later, but we need to use this
		   username when verifying at least DIGEST-MD5 password. */
		request->original_username = p_strdup(request->pool, username);
	}
	if (request->cert_username) {
		/* cert_username overrides the username given by
		   authentication mechanism. */
		return TRUE;
	}

	if (*username == '\0') {
		/* Some PAM plugins go nuts with empty usernames */
		*error_r = "Empty username";
		return FALSE;
	}

        request->user = auth_request_fix_username(request, username, error_r);
	if (request->user == NULL) {
		auth_request_log_debug(request, "auth",
			"Invalid username: %s", str_sanitize(username, 128));
		return FALSE;
	}
	if (request->translated_username == NULL) {
		/* similar to original_username, but after translations */
		request->translated_username = request->user;
	}

	if (login_username != NULL) {
		if (!auth_request_set_login_username(request,
						     login_username,
						     error_r))
			return FALSE;
	}
	return TRUE;
}

bool auth_request_set_login_username(struct auth_request *request,
                                     const char *username,
                                     const char **error_r)
{
        i_assert(*username != '\0');

	if (strcmp(username, request->user) == 0) {
		/* The usernames are the same, we don't really wish to log
		   in as someone else */
		return TRUE;
	}

        /* lookup request->user from masterdb first */
        request->passdb = request->auth->masterdbs;

        request->requested_login_user =
                auth_request_fix_username(request, username, error_r);
	return request->requested_login_user != NULL;
}

static void auth_request_validate_networks(struct auth_request *request,
					   const char *networks)
{
	const char *const *net;
	struct ip_addr net_ip;
	unsigned int bits;
	bool found = FALSE;

	if (request->remote_ip.family == 0) {
		/* IP not known */
		auth_request_log_info(request, "passdb",
			"allow_nets check failed: Remote IP not known");
		request->passdb_failure = TRUE;
		return;
	}

	for (net = t_strsplit_spaces(networks, ", "); *net != NULL; net++) {
		auth_request_log_debug(request, "auth",
			"allow_nets: Matching for network %s", *net);

		if (net_parse_range(*net, &net_ip, &bits) < 0) {
			auth_request_log_info(request, "passdb",
				"allow_nets: Invalid network '%s'", *net);
		}

		if (net_is_in_network(&request->remote_ip, &net_ip, bits)) {
			found = TRUE;
			break;
		}
	}

	if (!found) {
		auth_request_log_info(request, "passdb",
			"allow_nets check failed: IP not in allowed networks");
	}
	request->passdb_failure = !found;
}

static void
auth_request_set_password(struct auth_request *request, const char *value,
			  const char *default_scheme, bool noscheme)
{
	if (request->passdb_password != NULL) {
		auth_request_log_error(request,
			request->passdb->passdb->iface.name,
			"Multiple password values not supported");
		return;
	}

	/* if the password starts with '{' it most likely contains
	   also '}'. check it anyway to make sure, because we
	   assert-crash later if it doesn't exist. this could happen
	   if plaintext passwords are used. */
	if (*value == '{' && !noscheme && strchr(value, '}') != NULL)
		request->passdb_password = p_strdup(request->pool, value);
	else {
		i_assert(default_scheme != NULL);
		request->passdb_password =
			p_strdup_printf(request->pool, "{%s}%s",
					default_scheme, value);
	}
}

static void auth_request_set_reply_field(struct auth_request *request,
					 const char *name, const char *value)
{
	if (strcmp(name, "nologin") == 0) {
		/* user can't actually login - don't keep this
		   reply for master */
		request->no_login = TRUE;
		value = NULL;
	} else if (strcmp(name, "proxy") == 0) {
		/* we're proxying authentication for this user. send
		   password back if using plaintext authentication. */
		request->proxy = TRUE;
		value = NULL;
	} else if (strcmp(name, "proxy_maybe") == 0) {
		/* like "proxy", but log in normally if we're proxying to
		   ourself */
		request->proxy = TRUE;
		request->proxy_maybe = TRUE;
		value = NULL;
	}

	if (request->extra_fields == NULL)
		request->extra_fields = auth_stream_reply_init(request->pool);
	auth_stream_reply_add(request->extra_fields, name, value);
}

void auth_request_set_field(struct auth_request *request,
			    const char *name, const char *value,
			    const char *default_scheme)
{
	const char *p, *orig_value;

	i_assert(*name != '\0');
	i_assert(value != NULL);

	if (strcmp(name, "password") == 0) {
		auth_request_set_password(request, value,
					  default_scheme, FALSE);
		return;
	}
	if (strcmp(name, "password_noscheme") == 0) {
		auth_request_set_password(request, value, default_scheme, TRUE);
		return;
	}

	if (strcmp(name, "user") == 0 ||
	    strcmp(name, "username") == 0 || strcmp(name, "domain") == 0) {
		/* update username */
		orig_value = value;
		if (strcmp(name, "username") == 0 &&
		    strchr(value, '@') == NULL &&
		    (p = strchr(request->user, '@')) != NULL) {
			/* preserve the current @domain */
			value = t_strconcat(value, p, NULL);
		} else if (strcmp(name, "domain") == 0) {
			p = strchr(request->user, '@');
			if (p == NULL) {
				/* add the domain */
				value = t_strconcat(request->user, "@",
						    value, NULL);
			} else {
				/* replace the existing domain */
				p = t_strdup_until(request->user, p + 1);
				value = t_strconcat(p, value, NULL);
			}
		}

		if (strcmp(request->user, value) != 0) {
			auth_request_log_debug(request, "auth",
				"username changed %s -> %s",
				request->user, value);
			request->user = p_strdup(request->pool, value);
		}

		if (request->userdb_reply != NULL)
			auth_request_userdb_reply_update_user(request);

		/* restore the original value so it gets saved correctly to
		   cache. */
		value = orig_value;
	} else if (strcmp(name, "nodelay") == 0) {
		/* don't delay replying to client of the failure */
		request->no_failure_delay = TRUE;
	} else if (strcmp(name, "nopassword") == 0) {
		/* NULL password - anything goes */
		const char *password = request->passdb_password;

		if (password != NULL) {
			(void)password_get_scheme(&password);
			if (*password != '\0') {
				auth_request_log_error(request,
					request->passdb->passdb->iface.name,
					"nopassword set but password is "
					"non-empty");
				return;
			}
		}
		request->no_password = TRUE;
		request->passdb_password = NULL;
	} else if (strcmp(name, "allow_nets") == 0) {
		auth_request_validate_networks(request, value);
	} else if (strncmp(name, "userdb_", 7) == 0) {
		/* for prefetch userdb */
		if (request->userdb_reply == NULL)
			auth_request_init_userdb_reply(request);
		auth_request_set_userdb_field(request, name + 7, value);
	} else {
		/* these fields are returned to client */
		auth_request_set_reply_field(request, name, value);
		return;
	}

	if ((passdb_cache != NULL &&
	     request->passdb->passdb->cache_key != NULL) || worker) {
		/* we'll need to get this field stored into cache,
		   or we're a worker and we'll need to send this to the main
		   auth process that can store it in the cache. */
		if (request->extra_cache_fields == NULL) {
			request->extra_cache_fields =
				auth_stream_reply_init(request->pool);
		}
		auth_stream_reply_add(request->extra_cache_fields, name, value);
	}
}

void auth_request_set_fields(struct auth_request *request,
			     const char *const *fields,
			     const char *default_scheme)
{
	const char *key, *value;

	for (; *fields != NULL; fields++) {
		if (**fields == '\0')
			continue;

		value = strchr(*fields, '=');
		if (value == NULL) {
			key = *fields;
			value = "";
		} else {
			key = t_strdup_until(*fields, value);
			value++;
		}
		auth_request_set_field(request, key, value, default_scheme);
	}
}

void auth_request_init_userdb_reply(struct auth_request *request)
{
	request->userdb_reply = auth_stream_reply_init(request->pool);
	auth_stream_reply_add(request->userdb_reply, NULL, request->user);
}

static void auth_request_userdb_reply_update_user(struct auth_request *request)
{
	const char *str, *p;

	str = t_strdup(auth_stream_reply_export(request->userdb_reply));

	auth_stream_reply_reset(request->userdb_reply);
	auth_stream_reply_add(request->userdb_reply, NULL, request->user);

	p = strchr(str, '\t');
	if (p != NULL)
		auth_stream_reply_import(request->userdb_reply, p + 1);
}

static void
auth_request_change_userdb_user(struct auth_request *request, const char *user)
{
	const char *str;

	/* replace the username in userdb_reply if it changed */
	if (strcmp(user, request->user) == 0)
		return;

	str = t_strdup(auth_stream_reply_export(request->userdb_reply));

	/* reset the reply and add the new username */
	auth_request_set_field(request, "user", user, NULL);
	auth_stream_reply_reset(request->userdb_reply);
	auth_stream_reply_add(request->userdb_reply,
			      NULL, request->user);

	/* add the rest */
	str = strchr(str, '\t');
	if (str != NULL)
		auth_stream_reply_import(request->userdb_reply, str + 1);
}

static void auth_request_set_uidgid_file(struct auth_request *request,
					 const char *path_template)
{
	string_t *path;
	struct stat st;

	path = t_str_new(256);
	var_expand(path, path_template,
		   auth_request_get_var_expand_table(request, NULL));
	if (stat(str_c(path), &st) < 0) {
		auth_request_log_error(request, "uidgid_file",
				       "stat(%s) failed: %m", str_c(path));
	} else {
		auth_stream_reply_add(request->userdb_reply,
				      "uid", dec2str(st.st_uid));
		auth_stream_reply_add(request->userdb_reply,
				      "gid", dec2str(st.st_gid));
	}
}

void auth_request_set_userdb_field(struct auth_request *request,
				   const char *name, const char *value)
{
	uid_t uid;
	gid_t gid;

	if (strcmp(name, "uid") == 0) {
		uid = userdb_parse_uid(request, value);
		if (uid == (uid_t)-1) {
			request->userdb_lookup_failed = TRUE;
			return;
		}
		value = dec2str(uid);
	} else if (strcmp(name, "gid") == 0) {
		gid = userdb_parse_gid(request, value);
		if (gid == (gid_t)-1) {
			request->userdb_lookup_failed = TRUE;
			return;
		}
		value = dec2str(gid);
	} else if (strcmp(name, "user") == 0) {
		auth_request_change_userdb_user(request, value);
		return;
	} else if (strcmp(name, "uidgid_file") == 0) {
		auth_request_set_uidgid_file(request, value);
		return;
	} else if (strcmp(name, "userdb_import") == 0) {
		auth_stream_reply_import(request->userdb_reply, value);
		return;
	} else if (strcmp(name, "system_user") == 0) {
		/* FIXME: the system_user is for backwards compatibility */
		name = "system_groups_user";
	}

	auth_stream_reply_add(request->userdb_reply, name, value);
}

void auth_request_set_userdb_field_values(struct auth_request *request,
					  const char *name,
					  const char *const *values)
{
	if (*values == NULL)
		return;

	if (strcmp(name, "uid") == 0) {
		/* there can be only one. use the first one. */
		auth_request_set_userdb_field(request, name, *values);
	} else if (strcmp(name, "gid") == 0) {
		/* convert gids to comma separated list */
		string_t *value;
		gid_t gid;

		value = t_str_new(128);
		for (; *values != NULL; values++) {
			gid = userdb_parse_gid(request, *values);
			if (gid == (gid_t)-1) {
				request->userdb_lookup_failed = TRUE;
				return;
			}

			if (str_len(value) > 0)
				str_append_c(value, ',');
			str_append(value, dec2str(gid));
		}
		auth_stream_reply_add(request->userdb_reply, name,
				      str_c(value));
	} else {
		/* add only one */
		auth_request_set_userdb_field(request, name, *values);
	}
}

static bool auth_request_proxy_is_self(struct auth_request *request)
{
	const char *const *tmp, *host = NULL, *port = NULL, *destuser = NULL;
	struct ip_addr ip;

	tmp = auth_stream_split(request->extra_fields);
	for (; *tmp != NULL; tmp++) {
		if (strncmp(*tmp, "host=", 5) == 0)
			host = *tmp + 5;
		else if (strncmp(*tmp, "port=", 5) == 0)
			port = *tmp + 5;
		if (strncmp(*tmp, "destuser=", 9) == 0)
			destuser = *tmp + 9;
	}

	if (host == NULL || net_addr2ip(host, &ip) < 0) {
		/* broken setup */
		return FALSE;
	}
	if (!net_ip_compare(&ip, &request->local_ip))
		return FALSE;

	if (port != NULL && (unsigned int)atoi(port) != request->local_port)
		return FALSE;
	return destuser == NULL ||
		strcmp(destuser, request->original_username) == 0;
}

void auth_request_proxy_finish(struct auth_request *request, bool success)
{
	if (!request->proxy || request->no_login)
		return;

	if (!success) {
		/* drop all proxy fields */
	} else if (!request->proxy_maybe) {
		/* proxying */
		request->no_login = TRUE;
		return;
	} else if (!auth_request_proxy_is_self(request)) {
		/* proxy destination isn't ourself - proxy */
		auth_stream_reply_remove(request->extra_fields, "proxy_maybe");
		auth_stream_reply_add(request->extra_fields, "proxy", NULL);
		request->no_login = TRUE;
		return;
	} else {
		/* proxying to ourself - log in without proxying by dropping
		   all the proxying fields. */
	}
	auth_stream_reply_remove(request->extra_fields, "proxy");
	auth_stream_reply_remove(request->extra_fields, "proxy_maybe");
	auth_stream_reply_remove(request->extra_fields, "host");
	auth_stream_reply_remove(request->extra_fields, "port");
	auth_stream_reply_remove(request->extra_fields, "destuser");
}

static void log_password_failure(struct auth_request *request,
				 const char *plain_password,
				 const char *crypted_password,
				 const char *scheme, const char *user,
				 const char *subsystem)
{
	static bool scheme_ok = FALSE;
	string_t *str = t_str_new(256);
	const char *working_scheme;

	str_printfa(str, "%s(%s) != '%s'", scheme,
		    plain_password, crypted_password);

	if (!scheme_ok) {
		/* perhaps the scheme is wrong - see if we can find
		   a working one */
		working_scheme = password_scheme_detect(plain_password,
							crypted_password, user);
		if (working_scheme != NULL) {
			str_printfa(str, ", try %s scheme instead",
				    working_scheme);
		}
	}

	auth_request_log_debug(request, subsystem, "%s", str_c(str));
}

int auth_request_password_verify(struct auth_request *request,
				 const char *plain_password,
				 const char *crypted_password,
				 const char *scheme, const char *subsystem)
{
	const unsigned char *raw_password;
	size_t raw_password_size;
	int ret;

	if (request->skip_password_check) {
		/* currently this can happen only with master logins */
		i_assert(request->master_user != NULL);
		return 1;
	}

	if (request->passdb->deny) {
		/* this is a deny database, we don't care about the password */
		return 0;
	}

	if (request->no_password) {
		auth_request_log_info(request, subsystem, "No password");
		return 1;
	}

	ret = password_decode(crypted_password, scheme,
			      &raw_password, &raw_password_size);
	if (ret <= 0) {
		if (ret < 0) {
			auth_request_log_error(request, subsystem,
				"Password in passdb is not in expected scheme %s",
				scheme);
		} else {
			auth_request_log_error(request, subsystem,
					       "Unknown scheme %s", scheme);
		}
		return -1;
	}

	/* Use original_username since it may be important for some
	   password schemes (eg. digest-md5). Otherwise the username is used
	   only for logging purposes. */
	ret = password_verify(plain_password, request->original_username,
			      scheme, raw_password, raw_password_size);
	i_assert(ret >= 0);
	if (ret == 0) {
		auth_request_log_info(request, subsystem,
				      "Password mismatch");
		if (request->auth->verbose_debug_passwords) T_BEGIN {
			log_password_failure(request, plain_password,
					     crypted_password, scheme,
					     request->original_username,
					     subsystem);
		} T_END;
	}
	return ret;
}

static const char *
escape_none(const char *string,
	    const struct auth_request *request ATTR_UNUSED)
{
	return string;
}

const char *
auth_request_str_escape(const char *string,
			const struct auth_request *request ATTR_UNUSED)
{
	return str_escape(string);
}

const struct var_expand_table *
auth_request_get_var_expand_table(const struct auth_request *auth_request,
				  auth_request_escape_func_t *escape_func)
{
	static struct var_expand_table static_tab[] = {
		{ 'u', NULL, "user" },
		{ 'n', NULL, "username" },
		{ 'd', NULL, "domain" },
		{ 's', NULL, "service" },
		{ 'h', NULL, "home" },
		{ 'l', NULL, "lip" },
		{ 'r', NULL, "rip" },
		{ 'p', NULL, "pid" },
		{ 'w', NULL, "password" },
		{ '!', NULL, NULL },
		{ 'm', NULL, "mech" },
		{ 'c', NULL, "secured" },
		{ 'a', NULL, "lport" },
		{ 'b', NULL, "rport" },
		{ 'k', NULL, "cert" },
		{ '\0', NULL, NULL }
	};
	struct var_expand_table *tab;

	if (escape_func == NULL)
		escape_func = escape_none;

	tab = t_malloc(sizeof(static_tab));
	memcpy(tab, static_tab, sizeof(static_tab));

	tab[0].value = escape_func(auth_request->user, auth_request);
	tab[1].value = escape_func(t_strcut(auth_request->user, '@'),
				   auth_request);
	tab[2].value = strchr(auth_request->user, '@');
	if (tab[2].value != NULL)
		tab[2].value = escape_func(tab[2].value+1, auth_request);
	tab[3].value = auth_request->service;
	/* tab[4] = we have no home dir */
	if (auth_request->local_ip.family != 0)
		tab[5].value = net_ip2addr(&auth_request->local_ip);
	if (auth_request->remote_ip.family != 0)
		tab[6].value = net_ip2addr(&auth_request->remote_ip);
	tab[7].value = dec2str(auth_request->client_pid);
	if (auth_request->mech_password != NULL) {
		tab[8].value = escape_func(auth_request->mech_password,
					   auth_request);
	}
	if (auth_request->userdb_lookup) {
		tab[9].value = auth_request->userdb == NULL ? "" :
			dec2str(auth_request->userdb->num);
	} else {
		tab[9].value = auth_request->passdb == NULL ? "" :
			dec2str(auth_request->passdb->id);
	}
	tab[10].value = auth_request->mech_name == NULL ? "" :
		auth_request->mech_name;
	tab[11].value = auth_request->secured ? "secured" : "";
	tab[12].value = dec2str(auth_request->local_port);
	tab[13].value = dec2str(auth_request->remote_port);
	tab[14].value = auth_request->valid_client_cert ? "valid" : "";
	return tab;
}

static const char * ATTR_FORMAT(3, 0)
get_log_str(struct auth_request *auth_request, const char *subsystem,
	    const char *format, va_list va)
{
#define MAX_LOG_USERNAME_LEN 64
	const char *ip;
	string_t *str;

	str = t_str_new(128);
	str_append(str, subsystem);
	str_append_c(str, '(');

	if (auth_request->user == NULL)
		str_append(str, "?");
	else {
		str_sanitize_append(str, auth_request->user,
				    MAX_LOG_USERNAME_LEN);
	}

	ip = net_ip2addr(&auth_request->remote_ip);
	if (ip != NULL) {
		str_append_c(str, ',');
		str_append(str, ip);
	}
	if (auth_request->requested_login_user != NULL)
		str_append(str, ",master");
	str_append(str, "): ");
	str_vprintfa(str, format, va);
	return str_c(str);
}

void auth_request_log_debug(struct auth_request *auth_request,
			    const char *subsystem,
			    const char *format, ...)
{
	va_list va;

	if (!auth_request->auth->verbose_debug)
		return;

	va_start(va, format);
	T_BEGIN {
		i_info("%s", get_log_str(auth_request, subsystem, format, va));
	} T_END;
	va_end(va);
}

void auth_request_log_info(struct auth_request *auth_request,
			   const char *subsystem,
			   const char *format, ...)
{
	va_list va;

	if (!auth_request->auth->verbose)
		return;

	va_start(va, format);
	T_BEGIN {
		i_info("%s", get_log_str(auth_request, subsystem, format, va));
	} T_END;
	va_end(va);
}

void auth_request_log_error(struct auth_request *auth_request,
			    const char *subsystem,
			    const char *format, ...)
{
	va_list va;

	va_start(va, format);
	T_BEGIN {
		i_error("%s", get_log_str(auth_request, subsystem, format, va));
	} T_END;
	va_end(va);
}
