/* Copyright (c) 2002-2018 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"
#include "wildcard-match.h"
struct auth_request_proxy_dns_lookup_ctx {
};
struct auth_policy_check_ctx {
enum {
} type;
};
const char *subsystem);
static void
static
static
static
struct auth_request *
{
return request;
}
{
return request;
}
enum auth_request_state state)
{
return;
}
{
/* NOTE: request->debug may already be TRUE here */
}
{
}
{
.success_data = &buf,
};
return;
}
/* perform second policy lookup here */
}
static
{
/* 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;
}
}
{
}
{
}
{
}
{
return;
}
else
}
static void
{
}
}
static void
const char *prefix)
{
}
}
}
{
}
}
}
}
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) {
}
case AUTH_REQUEST_SECURED_NONE: break;
default: break;
}
if (request->skip_password_check)
if (request->valid_client_cert)
if (request->no_penalty)
if (request->successful)
/* export passdb extra fields */
/* export any userdb fields */
}
{
/* authentication and user lookups may set these */
if (request->real_local_port == 0)
if (request->real_remote_port == 0)
}
"forward_", value, 0);
/* make sure the forward_ fields aren't deleted by
auth_fields_rollback() if the first passdb lookup fails. */
} else
return FALSE;
/* NOTE: keep in sync with auth_request_export() */
return TRUE;
}
{
return TRUE;
/* auth client may set these */
else
}
/* 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 */
/* just make passdb_handle_credentials() work identically in
auth-worker as it does in auth-master. the worker shouldn't
care about the actual contents of the credentials. */
} else
return FALSE;
return TRUE;
}
{
}
{
if (request->successful) {
return;
}
}
enum passdb_result result)
{
const char *encoded_password;
.rounds = 0 };
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
const struct mech_module *mech)
{
/* no filter specified, anything goes */
/* request has no mechanism, see if none is accepted */
/* check if request mechanism is accepted */
}
/**
Check if username is included in the filter. Logic is that if the username
is not excluded by anything, and is included by something, it will be accepted.
By default, all usernames are included, unless there is a inclusion item, when
username will be excluded if there is no inclusion for it.
Exclusions are denoted with a ! in front of the pattern.
*/
{
/* if filter has ! it means the pattern will be refused */
if (!exclude)
if (exclude) {
return FALSE;
} else {
matched_inc = TRUE;
}
}
}
return matched_inc || !have_includes;
}
static bool
struct auth_passdb *passdb)
{
/* if mechanism is not supported, skip */
const char *username;
: "none",
"skipping passdb: mechanism filtered");
return TRUE;
}
: "none",
"skipping passdb: username filtered");
return TRUE;
}
/* 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)
{
}
*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;
}
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");
return;
}
}
}
{
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
{
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,
{
else
} else {
}
}
static
return;
}
if (password_has_illegal_chars(password)) {
"Attempted login with password having illegal chars");
return;
}
"All password databases were skipped");
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)
{
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,
{
else {
}
}
static
{
return;
}
"All password databases were skipped");
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 {
}
}
{
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 {
if (request->user_changed_by_lookup) {
/* 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;
&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)
{
const char *error;
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 */
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;
}
{
/* 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)
{
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 *
{
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,
{
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;
}
{
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 {
"Invalid hostip in passdb: %s", hostip);
return -1;
}
/* asynchronous host lookup */
}
}
return 1;
}
{
/* drop all proxying fields */
}
const char *plain_password,
const char *crypted_password,
const char *scheme,
const struct password_generate_params *params,
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
{
return;
if (p != NULL) {
i_unreached();
}
max_len));
} else {
i_unreached();
}
}
const char *subsystem)
{
}
const char *subsystem)
{
}
const char *subsystem,
const char *message)
{
return;
}
if (request->userdb_lookup) {
} else {
}
}
const char *plain_password,
const char *crypted_password,
{
}
const char *plain_password,
const char *crypted_password,
bool log_password_mismatch)
{
const unsigned char *raw_password;
const char *error;
int ret;
.rounds = 0};
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)
{
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;
}
{
}