auth-request.c revision 0f5dc4da3982053036be65190e44bf28a67b1ca2
/* Copyright (c) 2002-2016 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 "array.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-request-stats.h"
#include "auth-client-connection.h"
#include "auth-master-connection.h"
#include "auth-policy.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"
#define AUTH_SUBSYS_PROXY "proxy"
#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;
};
struct auth_policy_check_ctx {
enum {
} type;
struct auth_request *request;
};
const char auth_default_subsystems[2];
unsigned int auth_request_state_count[AUTH_REQUEST_STATE_MAX];
const char *subsystem);
static void
static
static
static
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;
}
{
/* NOTE: request->debug may already be TRUE here */
}
{
}
{
/* perform second policy lookup here */
}
static
{
struct auth_stats *stats;
/* password was valid, but some other check failed. */
return;
}
return;
}
/* we'll need one more SASL round, since client doesn't support
the final SASL response */
return;
}
}
{
struct auth_stats *stats;
}
{
}
{
}
{
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->local_name != 0) {
}
if (request->skip_password_check)
if (request->valid_client_cert)
if (request->no_penalty)
if (request->successful)
/* export any userdb fields */
const struct auth_field *field;
}
}
}
}
{
/* authentication and user lookups may set these */
if (request->real_local_port == 0)
if (request->real_remote_port == 0)
}
else
return FALSE;
/* NOTE: keep in sync with auth_request_export() */
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;
case PASSDB_RESULT_NEXT:
/* 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. */
"Master user logging in as %s",
}
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_userdb *userdb)
{
case AUTH_USERDB_SKIP_NEVER:
return FALSE;
case AUTH_USERDB_SKIP_FOUND:
return request->userdb_success;
return !request->userdb_success;
}
i_unreached();
}
static bool
struct auth_request *request)
{
struct auth_passdb *next_passdb;
enum auth_db_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;
}
/* The passdb didn't fail, but something inside it failed
(e.g. allow_nets mismatch). Make sure we'll fail this
lookup, but reset the failure so the next passdb can
succeed. */
if (*result == PASSDB_RESULT_OK)
}
/* 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;
case PASSDB_RESULT_NEXT:
"Not performing authentication (noauthenticate set)");
break;
default:
break;
}
switch (result_rule) {
case AUTH_DB_RULE_RETURN:
break;
case AUTH_DB_RULE_RETURN_OK:
break;
case AUTH_DB_RULE_RETURN_FAIL:
break;
case AUTH_DB_RULE_CONTINUE:
if (*result == PASSDB_RESULT_OK) {
/* password was successfully verified. don't bother
checking it again. */
}
break;
case AUTH_DB_RULE_CONTINUE_OK:
/* password was successfully verified. don't bother
checking it again. */
break;
break;
}
/* nopassword check is specific to a single passdb and shouldn't leak
to the next one. we already added it to cache. */
*result == PASSDB_RESULT_OK) {
/* if the passdb lookup continues, it continues with non-master
passdbs for the requested_login_user. */
} else {
}
while (next_passdb != NULL &&
/* this passdb lookup succeeded, preserve its extra fields */
} else {
/* this passdb lookup failed, remove any extra fields it set */
}
}
/* try next passdb. */
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 (*result == PASSDB_RESULT_NEXT) {
/* admin forgot to put proper passdb last */
"Last passdb had noauthenticate field, cannot authenticate user");
} else if (request->passdb_success) {
/* either this or a previous passdb lookup succeeded. */
} 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. */
}
return TRUE;
}
static void
struct auth_request *request)
{
const char *error;
"Failed to expand override_fields: %s", error);
}
/* try next passdb */
} else {
}
}
struct auth_request *request)
{
if (result == PASSDB_RESULT_OK &&
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;
}
static
void auth_request_policy_penalty_finish(void *context)
{
return;
return;
return;
default:
i_unreached();
}
}
static
{
if (result == -1) {
/* fail it right here and now */
} else if (ctx->type != AUTH_POLICY_CHECK_TYPE_SUCCESS && result > 0 && !ctx->request->no_penalty) {
context);
} else {
}
}
const char *password,
{
struct auth_policy_check_ctx *ctx;
else
if (request->policy_processed) {
} else {
}
}
static
struct auth_passdb *passdb;
enum passdb_result result;
return;
}
if (password_has_illegal_chars(password)) {
"Attempted login with password having illegal chars");
return;
}
return;
}
/* we're deinitializing and just want to get rid of this
request */
"Failed to expand default_fields: %s", error);
} else {
}
}
static void
const unsigned char *credentials,
struct auth_request *request)
{
const char *error;
"Failed to expand override_fields: %s", error);
}
/* try next passdb */
if (request->skip_password_check &&
/* passdb continue* rule after a successful lookup.
remember these credentials and use them later on. */
unsigned char *dup;
}
} else {
/* we did multiple passdb lookups, but the last one
didn't provide any credentials (e.g. just wanted to
add some extra fields). so use the first passdb's
credentials instead. */
}
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_OK &&
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,
{
struct auth_policy_check_ctx *ctx;
if (request->policy_processed)
else {
}
}
static
{
struct auth_passdb *passdb;
enum passdb_result result;
return;
}
request);
return;
}
}
/* this passdb doesn't support credentials */
"passdb doesn't support credential lookups");
uchar_empty_ptr, 0, request);
"Failed to expand default_fields: %s", error);
uchar_empty_ptr, 0, request);
} 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 {
/* username was changed by passdb or userdb */
}
/* no userdb fields. but we can't save an empty string,
since that means "user unknown". */
}
}
/* last_success has no meaning with userdb */
cache_value, FALSE);
}
const char *key,
enum userdb_result *result_r,
bool use_expired)
{
const char *value;
struct auth_cache_node *node;
bool expired, neg_expired;
&expired, &neg_expired);
"userdb cache expired");
return FALSE;
}
"userdb cache hit: %s", value);
if (*value == '\0') {
/* negative cache entry */
return TRUE;
}
/* We want to preserve any userdb fields set by the earlier passdb
lookup, so initialize userdb_reply only if it doesn't exist.
Don't use auth_request_init_userdb_reply(), because the entire
userdb part of the result comes from the cache so we don't want to
initialize it with default_fields. */
return TRUE;
}
struct auth_request *request)
{
struct auth_userdb *next_userdb;
enum auth_db_rule result_rule;
const char *error;
bool userdb_continue = FALSE;
switch (result) {
case USERDB_RESULT_OK:
break;
break;
default:
break;
}
switch (result_rule) {
case AUTH_DB_RULE_RETURN:
break;
case AUTH_DB_RULE_RETURN_OK:
break;
case AUTH_DB_RULE_RETURN_FAIL:
break;
case AUTH_DB_RULE_CONTINUE:
break;
case AUTH_DB_RULE_CONTINUE_OK:
break;
break;
}
while (next_userdb != NULL &&
/* try next userdb. */
if (result == USERDB_RESULT_INTERNAL_FAILURE)
if (result == USERDB_RESULT_OK) {
/* this userdb lookup succeeded, preserve its extra
fields */
"Failed to expand override_fields: %s", error);
return;
}
} else {
/* this userdb lookup failed, remove any extra fields
it set */
}
return;
}
if (request->userdb_success) {
"Failed to expand override_fields: %s", error);
} else {
}
} 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 (request->client_pid != 0) {
/* this was an actual login attempt, the user should
have been found. */
"user not found from userdb");
} else {
"user not found from any userdbs");
}
} else {
}
if (request->userdb_lookup_tempfailed) {
/* no caching */
} else if (result != USERDB_RESULT_INTERNAL_FAILURE) {
if (!request->userdb_result_from_cache)
/* 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");
}
}
}
{
else {
/* we still want to set default_fields. these override any
existing fields set by previous userdbs (because if that is
unwanted, ":protected" can be used). */
"Failed to expand default_fields: %s", error);
return;
}
}
/* (for now) auth_cache is shared between passdb and userdb */
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 char *error;
char *old_username;
"Failed to expand username_format=%s: %s",
}
}
if (user[0] == '\0') {
/* Some PAM plugins go nuts with empty usernames */
*error_r = "Empty username";
return NULL;
}
return user;
}
{
const char *p, *login_username = NULL;
/* check if the username contains a master user */
if (p != NULL) {
/* it does, set it. */
/* 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. */
}
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)
{
struct auth_passdb *master_passdb;
if (username[0] == '\0') {
*error_r = "Master user login attempted to use empty login username";
return FALSE;
}
/* The usernames are the same, we don't really wish to log
in as someone else */
return TRUE;
}
/* lookup request->user from masterdb first */
if (master_passdb == NULL) {
*error_r = "Master user login attempted without master passdbs";
return FALSE;
}
return FALSE;
"Master user lookup for login: %s",
return TRUE;
}
static void
{
const char *const *net;
unsigned int bits;
break;
}
break;
}
}
if (found)
;
"%s check failed: Remote IP not known and 'local' missing", name);
} else {
"%s check failed: IP %s not in allowed networks",
}
if (!found)
}
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;
if (new_value[0] == '\0') {
"username attempted to be changed to empty");
return TRUE;
}
"username changed %s -> %s",
}
return TRUE;
}
static void
const char *key_prefix, const char *default_scheme)
{
}
}
const char *default_scheme)
{
/* set this field only if it hasn't been set before */
return;
/* remove this field entirely */
return;
}
return;
}
return;
}
/* don't change the original value so it gets saved correctly
to cache. */
unsigned int extra_secs = 0;
const char *p;
if (p != NULL) {
if (str_to_uint(p, &extra_secs) < 0) {
"Invalid delay_until randomness number '%s'", p);
} else {
}
}
"Invalid delay_until timestamp '%s'", value);
} else if (timestamp <= ioloop_time) {
/* no more delays */
"delay_until timestamp %s is too much in the future, failing", value);
} else {
/* add randomness, but not too much of it */
timestamp += extra_secs;
}
/* for prefetch userdb */
/* we can't put the whole userdb_userdb_import
value to extra_cache_fields or it doesn't work
properly. so handle this explicitly. */
"userdb_", default_scheme);
return;
}
/* add "nopassword" also so that passdbs won't try to verify
the password. */
/* NULL password - anything goes */
(void)password_get_scheme(&password);
if (*password != '\0') {
"nopassword set but password is "
"non-empty");
return;
}
}
return;
return;
} else {
/* these fields are returned to client */
return;
}
/* add the field unconditionally to extra_fields. this is required if
a) auth cache is used, b) if we're a worker and we'll need to send
this to the main auth process that can store it in the cache,
c) for easily checking :protected fields' existence. */
}
{
/* make sure userdb prefetch is used even if all the fields
were returned as NULL. */
}
}
const char *field,
const char *default_scheme)
{
value = "";
} else {
value++;
}
}
const char *const *fields,
const char *default_scheme)
{
if (**fields == '\0')
continue;
}
}
{
const char *error;
"Failed to expand default_fields: %s", error);
}
}
const char *path_template)
{
const char *error;
} else {
}
}
static void
{
value = "";
} else {
value++;
}
}
}
{
/* set this field only if it hasn't been set before */
return;
/* remove this field entirely */
return;
}
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";
return;
}
}
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. give it access to lip and lport
so it can also perform proxy_maybe internally */
}
if (request->local_port != 0) {
}
} 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) {
}
}
}
static void
{
unsigned int max_len = 1024;
return;
if (p != NULL) {
i_unreached();
}
unsigned char sha1[SHA1_RESULTLEN];
max_len));
} else {
i_unreached();
}
}
const char *subsystem)
{
return;
}
}
const char *subsystem)
{
return;
}
if (request->userdb_lookup) {
} else {
}
}
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;
}
const char *subsystem)
{
#define MAX_LOG_USERNAME_LEN 64
if (subsystem == AUTH_SUBSYS_DB) {
if (!auth_request->userdb_lookup) {
} else {
}
} else if (subsystem == AUTH_SUBSYS_MECH) {
} else {
}
else {
}
if (ip[0] != '\0') {
}
}
static const char * ATTR_FORMAT(3, 0)
{
}
const char *subsystem,
const char *format, ...)
{
if (!auth_request->debug)
return;
T_BEGIN {
} T_END;
}
const char *subsystem,
const char *format, ...)
{
/* auth_debug=yes overrides auth_verbose settings */
} else {
const char *db_auth_verbose;
if (auth_request->userdb_lookup)
else
db_auth_verbose = "d";
switch (db_auth_verbose[0]) {
case 'y':
break;
case 'n':
return;
case 'd':
return;
break;
default:
i_unreached();
}
}
T_BEGIN {
} T_END;
}
const char *subsystem,
const char *format, ...)
{
T_BEGIN {
} T_END;
}
const char *subsystem,
const char *format, ...)
{
T_BEGIN {
} T_END;
}
{
}