auth-request.c revision 325d17cdbb7a338f7c413788f5e8e42d2e80a7f8
/* Copyright (c) 2002-2013 Dovecot authors, see the included COPYING file */
#include "auth-common.h"
#include "ioloop.h"
#include "buffer.h"
#include "hash.h"
#include "sha1.h"
#include "hex-binary.h"
#include "str.h"
#include "safe-memset.h"
#include "str-sanitize.h"
#include "strescape.h"
#include "var-expand.h"
#include "dns-lookup.h"
#include "auth-cache.h"
#include "auth-request.h"
#include "auth-request-handler.h"
#include "auth-client-connection.h"
#include "auth-master-connection.h"
#include "passdb.h"
#include "passdb-blocking.h"
#include "passdb-cache.h"
#include "passdb-template.h"
#include "userdb-blocking.h"
#include "userdb-template.h"
#include "password-scheme.h"
#include <stdlib.h>
#define AUTH_DNS_SOCKET_PATH "dns-client"
#define AUTH_DNS_WARN_MSECS 500
#define CACHED_PASSWORD_SCHEME "SHA1"
struct auth_request_proxy_dns_lookup_ctx {
struct auth_request *request;
struct dns_lookup *dns_lookup;
};
unsigned int auth_request_state_count[AUTH_REQUEST_STATE_MAX];
const char *subsystem);
struct auth_request *
{
struct auth_request *request;
return request;
}
struct auth_request *auth_request_new_dummy(void)
{
struct auth_request *request;
return request;
}
enum auth_request_state state)
{
return;
}
{
}
{
}
{
/* password was valid, but some other check failed. */
return;
}
/* we'll need one more SASL round, since client doesn't support
the final SASL response */
return;
}
}
{
}
{
}
{
}
{
return;
}
else
}
static void
{
}
}
{
}
}
}
}
if (request->local_port != 0)
if (request->remote_port != 0)
}
}
if (request->real_local_port != 0)
if (request->real_remote_port != 0)
if (request->skip_password_check)
if (request->valid_client_cert)
if (request->no_penalty)
if (request->successful)
}
{
/* authentication and user lookups may set these */
if (request->real_local_port == 0)
if (request->real_remote_port == 0)
}
else
return FALSE;
return TRUE;
}
{
return TRUE;
/* auth client may set these */
/* get username from SSL certificate. it overrides
the username given by the auth mechanism. */
}
} else {
return FALSE;
}
return TRUE;
}
{
/* master request lookups may set these */
} else
return FALSE;
return TRUE;
}
{
return TRUE;
/* for communication between auth master and worker processes */
else
return FALSE;
return TRUE;
}
{
}
{
if (request->successful) {
return;
}
}
enum passdb_result result)
{
const char *encoded_password;
switch (result) {
case PASSDB_RESULT_OK:
/* can be cached */
break;
/* FIXME: we can't cache this now, or cache lookup would
return success. */
return;
i_unreached();
}
return;
if (result < 0) {
/* lookup failed. */
if (result == PASSDB_RESULT_USER_UNKNOWN) {
}
return;
}
/* passdb didn't provide the correct password */
if (result != PASSDB_RESULT_OK ||
return;
/* we can still cache valid password lookups though.
strdup() it so that mech_password doesn't get
cleared too early. */
i_unreached();
}
/* save all except the currently given password in cache */
/* cached passwords must have a known scheme */
}
}
/* add only those extra fields to cache that were set by this
passdb lookup. the CHANGED flag does this, because we
snapshotted the extra_fields before the current passdb
lookup. */
}
result == PASSDB_RESULT_OK);
}
{
return;
/* master login successful. update user and master_user variables. */
}
static bool
struct auth_passdb *passdb)
{
/* skip_password_check basically specifies if authentication is
finished */
case AUTH_PASSDB_SKIP_NEVER:
return FALSE;
return authenticated;
return !authenticated;
}
i_unreached();
}
static bool
struct auth_request *request)
{
struct auth_passdb *next_passdb;
enum auth_passdb_rule result_rule;
bool passdb_continue = FALSE;
}
*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) {
"User found from deny passdb");
}
return TRUE;
}
/* users that exist but can't log in are special. we don't try to match
switch (*result) {
return TRUE;
"Password expired", NULL);
return TRUE;
case PASSDB_RESULT_OK:
break;
break;
default:
break;
}
switch (result_rule) {
case AUTH_PASSDB_RULE_RETURN:
break;
break;
break;
break;
break;
break;
}
/* password was successfully verified. don't bother
checking it again. */
}
/* if the passdb lookup continues, it continues with non-master
passdbs for the requested_login_user. */
} else {
}
while (next_passdb != NULL &&
/* try next passdb. */
if (*result == PASSDB_RESULT_OK) {
/* this passdb lookup succeeded, preserve its extra
fields */
} else {
/* this passdb lookup failed, remove any extra fields
it set */
}
if (*result == PASSDB_RESULT_USER_UNKNOWN) {
/* remember that we did at least one successful
passdb lookup */
} else 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. */
}
return FALSE;
} else if (request->passdbs_seen_internal_failure) {
/* last passdb lookup returned internal failure. it may have
had the correct password, so return internal failure
instead of plain failure. */
} else if (request->passdb_success) {
/* either this or a previous passdb lookup succeeded. */
}
return TRUE;
}
static void
struct auth_request *request)
{
/* try next passdb */
} else {
}
}
struct auth_request *request)
{
if (result != PASSDB_RESULT_INTERNAL_FAILURE) {
} else {
/* lookup failed. if we're looking here only because the
request was expired in cache, fallback to using cached
expired record. */
"Falling back to expired data from cache");
}
}
}
static bool password_has_illegal_chars(const char *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;
}
{
return FALSE;
/* no masterdbs, master logins not supported */
"Attempted master login with no master passdbs "
"(trying to log in as user: %s)",
return TRUE;
}
const char *password,
{
struct passdb_module *passdb;
enum passdb_result result;
const char *cache_key;
return;
}
if (password_has_illegal_chars(password)) {
"Attempted login with password having illegal chars");
return;
}
else
return;
}
/* we're deinitializing and just want to get rid of this
request */
} else {
}
}
static void
const unsigned char *credentials,
struct auth_request *request)
{
/* try next passdb */
} else {
result == PASSDB_RESULT_OK) {
"Credentials: %s",
}
if (result == PASSDB_RESULT_SCHEME_NOT_AVAILABLE &&
/* one of the passdbs accepted the scheme,
but the user was unknown there */
}
}
}
const unsigned char *credentials,
struct auth_request *request)
{
const char *cache_cred, *cache_scheme;
if (result != PASSDB_RESULT_INTERNAL_FAILURE) {
} else {
/* lookup failed. if we're looking here only because the
request was expired in cache, fallback to using cached
expired record. */
"Falling back to expired data from cache");
request);
return;
}
}
request);
}
const char *scheme,
{
enum passdb_result result;
return;
}
request);
return;
}
}
/* this passdb doesn't support credentials */
"passdb doesn't support credential lookups");
} else {
}
}
{
const char *cache_key, *new_credentials;
callback);
} else {
/* this passdb doesn't support credentials update */
}
}
enum userdb_result result)
{
const char *cache_value;
return;
if (result == USERDB_RESULT_USER_UNKNOWN)
cache_value = "";
else {
}
/* last_success has no meaning with userdb */
cache_value, FALSE);
}
const char *key,
struct auth_fields **reply_r,
enum userdb_result *result_r,
bool use_expired)
{
const char *value;
struct auth_cache_node *node;
bool expired, neg_expired;
return FALSE;
&expired, &neg_expired);
return FALSE;
}
if (*value == '\0') {
/* negative cache entry */
return TRUE;
}
return TRUE;
}
struct auth_request *request)
{
/* try next userdb. */
if (result == USERDB_RESULT_INTERNAL_FAILURE)
return;
}
if (result == USERDB_RESULT_OK)
else if (request->userdbs_seen_internal_failure) {
/* one of the userdb lookups failed. the user might have been
in there, so this is an 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. */
"user not found from userdb %s",
} else {
"user not found from any userdbs");
}
}
if (request->userdb_lookup_failed) {
/* no caching */
} else if (result != USERDB_RESULT_INTERNAL_FAILURE)
/* lookup failed. if we're looking here only because the
request was expired in cache, fallback to using cached
expired record. */
struct auth_fields *reply;
"Falling back to expired data from cache");
}
}
}
{
const char *cache_key;
/* (for now) auth_cache is shared between passdb and userdb */
struct auth_fields *reply;
enum userdb_result result;
return;
}
}
/* we are deinitializing */
request);
else
}
static char *
const char **error_r)
{
unsigned char *p;
char *user;
} else {
}
for (p = (unsigned char *)user; *p != '\0'; p++) {
"Username character disallowed by auth_username_chars: "
"0x%02x (username: %s)", *p,
return 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;
}
return user;
}
{
const char *p, *login_username = NULL;
/* check if the username contains a master user */
if (p != NULL) {
/* it does, set it. */
if (*login_username == '\0') {
*error_r = "Empty login username";
return FALSE;
}
/* username is the master user */
username = p + 1;
}
}
/* the username may change later, but we need to use this
username when verifying at least DIGEST-MD5 password. */
}
if (request->cert_username) {
/* cert_username overrides the username given by
authentication mechanism. but still do checks and
translations to it. */
}
if (*username == '\0') {
/* Some PAM plugins go nuts with empty usernames */
*error_r = "Empty username";
return FALSE;
}
return FALSE;
/* similar to original_username, but after translations */
}
if (login_username != NULL) {
error_r))
return FALSE;
}
return TRUE;
}
const char *username,
const char **error_r)
{
/* The usernames are the same, we don't really wish to log
in as someone else */
return TRUE;
}
/* lookup request->user from masterdb first */
return FALSE;
"Master user lookup for login: %s",
return TRUE;
}
const char *networks)
{
const char *const *net;
unsigned int bits;
/* IP not known */
"allow_nets check failed: Remote IP not known");
return;
}
"allow_nets: Matching for network %s", *net);
"allow_nets: Invalid network '%s'", *net);
}
break;
}
}
if (!found) {
"allow_nets check failed: IP not in allowed networks");
}
}
static void
const char *default_scheme, bool noscheme)
{
"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. */
else {
}
}
static const char *
get_updated_username(const char *old_username,
{
const char *p;
/* replace the whole username */
return value;
}
return value;
/* preserve the current @domain */
}
if (p == NULL) {
/* add the domain */
} else {
/* replace the existing domain */
}
}
return NULL;
}
static bool
{
const char *new_value;
return FALSE;
"username changed %s -> %s",
}
return TRUE;
}
const char *default_scheme)
{
return;
}
return;
}
/* don't change the original value so it gets saved correctly
to cache. */
/* don't delay replying to client of the failure */
/* NULL password - anything goes */
(void)password_get_scheme(&password);
if (*password != '\0') {
"nopassword set but password is "
"non-empty");
return;
}
}
/* for prefetch userdb */
} else {
/* these fields are returned to client */
return;
}
if ((passdb_cache != NULL &&
/* 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. */
}
}
const char *field,
const char *default_scheme)
{
value = "";
} else {
value++;
}
}
const char *const *fields,
const char *default_scheme)
{
if (**fields == '\0')
continue;
}
}
{
}
const char *path_template)
{
} else {
}
}
{
return;
}
return;
}
return;
return;
return;
return;
/* FIXME: the system_user is for backwards compatibility */
if (!warned) {
i_warning("userdb: Replace system_user with system_groups_user");
}
name = "system_groups_user";
}
}
const char *name,
const char *const *values)
{
return;
/* convert gids to comma separated list */
return;
}
}
} else {
/* add only one */
"Multiple values found for '%s', "
}
}
}
{
/* check if the port is the same */
return FALSE;
/* don't check destuser. in some systems destuser is intentionally
changed to proxied connections, but that shouldn't affect the
proxying decision.
it's unlikely any systems would actually want to proxy a connection
to itself only to change the username, since it can already be done
without proxying by changing the "user" field. */
return TRUE;
}
static bool
{
unsigned int i;
return TRUE;
return TRUE;
}
return FALSE;
}
static void
bool proxy_host_is_self)
{
/* proxying */
} else if (!proxy_host_is_self ||
/* proxy destination isn't ourself - proxy */
} else {
/* proxying to ourself - log in without proxying by dropping
all the proxying fields. */
"proxy_always");
if (proxy_always) {
/* setup where "self" refers to the local director
cluster, while "non-self" refers to remote clusters.
we've matched self here, so add proxy field and
let director fill the host. */
"proxy", NULL, 0);
}
}
}
static void
struct auth_request_proxy_dns_lookup_ctx *ctx)
{
const char *host;
unsigned int i;
bool proxy_host_is_self;
} else {
"DNS lookup for %s took %u.%03u s",
}
break;
}
}
}
}
const char *host,
{
struct auth_request_proxy_dns_lookup_ctx *ctx;
struct dns_lookup_settings dns_set;
const char *value;
unsigned int secs;
/* need to do dns lookup for the host */
"Invalid proxy_timeout value: %s", value);
} else {
}
}
&ctx->dns_lookup) < 0) {
/* failed early */
return -1;
}
return 0;
}
{
const char *host;
bool proxy_host_is_self;
return 1;
return 1;
/* director can set the host */
} else {
/* asynchronous host lookup */
}
return 1;
}
{
/* drop all proxying fields */
}
const char *plain_password,
const char *crypted_password,
const char *subsystem)
{
const char *working_scheme;
if (!scheme_ok) {
/* perhaps the scheme is wrong - see if we can find
a working one */
if (working_scheme != NULL) {
}
}
}
const char *subsystem)
{
return;
}
unsigned char sha1[SHA1_RESULTLEN];
} else {
i_unreached();
}
}
const char *plain_password,
const char *crypted_password,
{
const unsigned char *raw_password;
const char *error;
int ret;
if (request->skip_password_check) {
/* passdb continue* rule after a successful authentication */
return 1;
}
/* this is a deny database, we don't care about the password */
return 0;
}
"Allowing any password");
return 1;
}
if (ret <= 0) {
if (ret < 0) {
"Password data is not valid for scheme %s: %s",
} else {
"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. */
if (ret < 0) {
"Invalid password%s in passdb: %s",
} else if (ret == 0) {
}
} T_END;
return ret;
}
static const char *
escape_none(const char *string,
{
return string;
}
const char *
auth_request_str_escape(const char *string,
{
return str_escape(string);
}
const struct var_expand_table auth_request_var_expand_static_tab[] = {
};
struct var_expand_table *
unsigned int *count)
{
const unsigned int auth_count =
if (escape_func == NULL)
/* keep the extra fields at the beginning. the last static_tab field
contains the ending NULL-fields. */
*count += auth_count;
auth_count * sizeof(*tab));
/* tab[4] = we have no home dir */
}
if (auth_request->userdb_lookup) {
} else {
}
}
}
return ret_tab;
}
const struct var_expand_table *
{
unsigned int count = 0;
&count);
}
const char *subsystem)
{
#define MAX_LOG_USERNAME_LEN 64
const char *ip;
else {
}
}
}
static const char * ATTR_FORMAT(3, 0)
{
}
const char *subsystem,
const char *format, ...)
{
return;
T_BEGIN {
} T_END;
}
const char *subsystem,
const char *format, ...)
{
return;
T_BEGIN {
} T_END;
}
const char *subsystem,
const char *format, ...)
{
T_BEGIN {
} T_END;
}
const char *subsystem,
const char *format, ...)
{
T_BEGIN {
} T_END;
}
{
}