view src/auth/auth-request.c @ 3257:92c16e82b806 HEAD

passdb can now change the username that was used to log in. This is mostly useful to support case-insensitive username lookups.
author Timo Sirainen <tss@iki.fi>
date Sun, 03 Apr 2005 01:00:49 +0300
parents 2ff790d5d9e2
children 36db3285f4a7
line wrap: on
line source

/* Copyright (C) 2002-2005 Timo Sirainen */

#include "common.h"
#include "ioloop.h"
#include "buffer.h"
#include "hash.h"
#include "str.h"
#include "safe-memset.h"
#include "str-sanitize.h"
#include "var-expand.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"

struct auth_request *
auth_request_new(struct auth *auth, 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->created = ioloop_time;

	request->auth = auth;
	request->mech = mech;
	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", 256);
	auth_request = p_new(pool, struct auth_request, 1);
	auth_request->pool = pool;

	auth_request->refcount = 1;
	auth_request->created = 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);

	request->state = AUTH_REQUEST_STATE_FINISHED;
	request->successful = TRUE;
	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->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++;
}

int auth_request_unref(struct auth_request *request)
{
	i_assert(request->refcount > 0);
	if (--request->refcount > 0)
		return TRUE;

	request->mech->auth_free(request);
	return FALSE;
}

void auth_request_export(struct auth_request *request, string_t *str)
{
	str_append(str, "user=");
	str_append(str, request->user);
	str_append(str, "\tservice=");
	str_append(str, request->service);
	str_append(str, "\tlip=");
	str_append(str, net_ip2addr(&request->local_ip));
	str_append(str, "\trip=");
	str_append(str, net_ip2addr(&request->remote_ip));
}

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->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;
	string_t *str;

	if (passdb_cache == NULL)
		return;

	if (passdb->cache_key == NULL)
		return;

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

	if (request->passdb_password == NULL) {
		/* no password given by passdb, cannot cache this */
		return;
	}

	/* save all except the currently given password in cache */
	str = t_str_new(32 + (request->extra_fields != NULL ? 
			      str_len(request->extra_fields) : 0));
	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, '}');
	}
	str_append(str, request->passdb_password);

	if (request->extra_fields != NULL) {
		str_append_c(str, '\t');
		str_append_str(str, request->extra_fields);
	}
	if (request->no_failure_delay) {
		str_append_c(str, '\t');
		str_append(str, "nodelay");
	}
	auth_cache_insert(passdb_cache, request, passdb->cache_key, str_c(str));
}

void auth_request_verify_plain_callback(enum passdb_result result,
					struct auth_request *request)
{
	const char *cache_key;

	i_assert(request->state == AUTH_REQUEST_STATE_PASSDB);

	request->state = AUTH_REQUEST_STATE_MECH_CONTINUE;

        auth_request_save_cache(request, result);

	cache_key = passdb_cache == NULL ? NULL :
		request->passdb->passdb->cache_key;
	if (result == PASSDB_RESULT_INTERNAL_FAILURE && cache_key != NULL) {
		/* lookup failed. if we're looking here only because the
		   request was expired in cache, fallback to using cached
		   expired record. */
		if (passdb_cache_verify_plain(request, cache_key,
					      request->mech_password,
					      &result, TRUE)) {
			request->private_callback.verify_plain(result, request);
			safe_memset(request->mech_password, 0,
				    strlen(request->mech_password));
			return;
		}
	}

	if (request->proxy) {
		/* we're proxying - send back the password that was
		   sent by user (not the password in passdb). */
		str_printfa(request->extra_fields, "\tpass=%s",
			    request->mech_password);
	}

	if (request->passdb_password != NULL) {
		safe_memset(request->passdb_password, 0,
			    strlen(request->passdb_password));
	}

	if (result != PASSDB_RESULT_OK &&
	    result != PASSDB_RESULT_INTERNAL_FAILURE &&
	    request->passdb->next != NULL) {
		/* try next passdb. */
		if (request->extra_fields != NULL)
			str_truncate(request->extra_fields, 0);

                request->state = AUTH_REQUEST_STATE_MECH_CONTINUE;
		request->passdb = request->passdb->next;
		auth_request_verify_plain(request, request->mech_password,
			request->private_callback.verify_plain);
		return;
	}

        safe_memset(request->mech_password, 0, strlen(request->mech_password));

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

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

	i_assert(request->state == AUTH_REQUEST_STATE_MECH_CONTINUE);

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

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

	request->state = AUTH_REQUEST_STATE_PASSDB;

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

void auth_request_lookup_credentials_callback(enum passdb_result result,
					      const char *credentials,
					      struct auth_request *request)
{
	const char *cache_key, *scheme;

	i_assert(request->state == AUTH_REQUEST_STATE_PASSDB);

	request->state = AUTH_REQUEST_STATE_MECH_CONTINUE;
        auth_request_save_cache(request, result);

	if (request->passdb_password != NULL) {
		safe_memset(request->passdb_password, 0,
			    strlen(request->passdb_password));
	}

	cache_key = passdb_cache == NULL ? NULL :
		request->passdb->passdb->cache_key;
	if (result == PASSDB_RESULT_INTERNAL_FAILURE && cache_key != NULL) {
		/* lookup failed. if we're looking here only because the
		   request was expired in cache, fallback to using cached
		   expired record. */
		if (passdb_cache_lookup_credentials(request, cache_key,
						    &credentials, &scheme,
						    TRUE)) {
			passdb_handle_credentials(credentials != NULL ?
				PASSDB_RESULT_OK : PASSDB_RESULT_USER_UNKNOWN,
				request->credentials, credentials, scheme,
				request->private_callback.lookup_credentials,
				request);
			return;
		}
	}

	request->private_callback.lookup_credentials(result, credentials,
						     request);
}

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

	i_assert(request->state == AUTH_REQUEST_STATE_MECH_CONTINUE);

	cache_key = passdb_cache == NULL ? NULL : passdb->cache_key;
	if (cache_key != NULL) {
		if (passdb_cache_lookup_credentials(request, cache_key,
						    &result, &scheme, FALSE)) {
			passdb_handle_credentials(result != NULL ?
						  PASSDB_RESULT_OK :
						  PASSDB_RESULT_USER_UNKNOWN,
						  credentials, result, scheme,
						  callback, request);
			return;
		}
	}

	request->state = AUTH_REQUEST_STATE_PASSDB;
	request->credentials = credentials;
	request->private_callback.lookup_credentials = callback;

	if (passdb->blocking)
		passdb_blocking_lookup_credentials(request);
	else {
		passdb->lookup_credentials(request, credentials,
			auth_request_lookup_credentials_callback);
	}
}

void auth_request_userdb_callback(const char *result,
				  struct auth_request *request)
{
	if (result == NULL && request->userdb->next != NULL) {
		/* try next userdb. */
		if (request->extra_fields != NULL)
			str_truncate(request->extra_fields, 0);

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

	if (result == NULL && request->client_pid != 0) {
		/* this was actual login attempt */
		auth_request_log_error(request, "userdb",
				       "user not found from userdb");
	}

        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;

	request->private_callback.userdb = callback;

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

int auth_request_set_username(struct auth_request *request,
			      const char *username, const char **error_r)
{
	unsigned char *p;

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

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

	for (p = (unsigned char *)request->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 = "Username contains disallowed characters";
			return FALSE;
		}
	}

	return TRUE;
}

void auth_request_set_field(struct auth_request *request,
			    const char *name, const char *value)
{
	string_t *str;

	i_assert(value != NULL);

	if (strcmp(name, "password") == 0) {
		if (request->passdb_password == NULL) {
			request->passdb_password =
				p_strdup(request->pool, value);
		} else {
			auth_request_log_error(request, "auth",
				"Multiple password values not supported");
		}
		return;
	}

	if (strcmp(name, "user") == 0) {
		/* update username to be exactly as it's in database */
		request->user = p_strdup(request->pool, value);
		return;
	}

	if (strcmp(name, "nodelay") == 0) {
		/* don't delay replying to client of the failure */
		request->no_failure_delay = TRUE;
		return;
	}

	str = request->extra_fields;
	if (str == NULL)
		request->extra_fields = str = str_new(request->pool, 64);

	if (strcmp(name, "nologin") == 0) {
		/* user can't actually login - don't keep this
		   reply for master */
		request->no_login = TRUE;
		if (str_len(str) > 0)
			str_append_c(str, '\t');
		str_append(str, name);
	} else if (strcmp(name, "proxy") == 0) {
		/* we're proxying authentication for this user. send
		   password back if using plaintext authentication. */
		request->proxy = TRUE;
		if (str_len(str) > 0)
			str_append_c(str, '\t');
		str_append(str, name);
	} else {
		if (str_len(str) > 0)
			str_append_c(str, '\t');
		str_printfa(str, "%s=%s", name, value);
	}
}

static const char *escape_none(const char *str)
{
	return str;
}

const struct var_expand_table *
auth_request_get_var_expand_table(const struct auth_request *auth_request,
				  const char *(*escape_func)(const char *))
{
	static struct var_expand_table static_tab[] = {
		{ 'u', NULL },
		{ 'n', NULL },
		{ 'd', NULL },
		{ 's', NULL },
		{ 'h', NULL },
		{ 'l', NULL },
		{ 'r', NULL },
		{ 'p', NULL },
		{ '\0', 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);
	tab[1].value = escape_func(t_strcut(auth_request->user, '@'));
	tab[2].value = strchr(auth_request->user, '@');
	if (tab[2].value != NULL)
		tab[2].value = escape_func(tab[2].value+1);
	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);
	return tab;
}

static const char *
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);
	}
	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_push();
	i_info("%s", get_log_str(auth_request, subsystem, format, va));
	t_pop();
	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_push();
	i_info("%s", get_log_str(auth_request, subsystem, format, va));
	t_pop();
	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_push();
	i_error("%s", get_log_str(auth_request, subsystem, format, va));
	t_pop();
	va_end(va);
}