sdap_access.c revision 50b8a36b0932a510e825ed1ad8103f81ead2b7d8
/*
SSSD
Authors:
Stephen Gallagher <sgallagh@redhat.com>
Copyright (C) 2010 Red Hat
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <time.h>
#include <security/pam_modules.h>
#include <talloc.h>
#include <tevent.h>
#include <errno.h>
#include "util/strtonum.h"
#include "providers/ldap/ldap_common.h"
#include "providers/ldap/sdap_access.h"
#include "providers/ldap/sdap_async.h"
#include "providers/data_provider.h"
#include "providers/dp_backend.h"
#include "providers/ldap/ldap_auth.h"
#define PERMANENTLY_LOCKED_ACCOUNT "000001010000Z"
#define MALFORMED_FILTER "Malformed access control filter [%s]\n"
enum sdap_pwpolicy_mode {
};
struct sss_domain_info *domain,
struct sdap_options *opts);
const char *username,
const char *attr_name,
bool value);
const char *username,
const char **_basedn);
static struct tevent_req *
struct tevent_context *ev,
struct sss_domain_info *domain,
struct sdap_access_ctx *access_ctx,
struct sdap_id_conn_ctx *conn,
const char *username,
struct ldb_message *user_entry,
enum sdap_pwpolicy_mode pwpol_mod);
struct tevent_context *ev,
struct sss_domain_info *domain,
struct sdap_access_ctx *access_ctx,
struct sdap_id_conn_ctx *conn,
const char *username,
struct ldb_message *user_entry);
struct ldb_message *user_entry);
struct ldb_message *user_entry);
enum sdap_access_control_type {
};
struct sdap_access_req_ctx {
struct tevent_context *ev;
struct sdap_access_ctx *access_ctx;
struct sdap_id_conn_ctx *conn;
struct sss_domain_info *domain;
struct ldb_message *user_entry;
};
struct tevent_req *req);
struct tevent_req *
struct tevent_context *ev,
struct sss_domain_info *domain,
struct sdap_access_ctx *access_ctx,
struct sdap_id_conn_ctx *conn,
{
struct sdap_access_req_ctx *state;
struct tevent_req *req;
struct ldb_result *res;
return NULL;
}
state->current_rule = 0;
"No access rules defined, access denied.\n");
goto done;
}
/* Get original user DN, domain already points to the right (sub)domain */
/* If we can't find the user, return access denied */
goto done;
}
goto done;
}
else {
/* If we can't find the user, return access denied */
goto done;
}
"Invalid response from sysdb_get_user_attr\n");
goto done;
}
}
return req;
}
done:
} else {
}
return req;
}
struct tevent_req *req)
{
struct tevent_req *subreq;
case LDAP_ACCESS_EMPTY:
/* we are done with no errors */
return EOK;
case LDAP_ACCESS_LOCKOUT:
return ENOMEM;
}
return EAGAIN;
case LDAP_ACCESS_PPOLICY:
"sdap_access_ppolicy_send failed.\n");
return ENOMEM;
}
return EAGAIN;
case LDAP_ACCESS_FILTER:
state->user_entry);
return ENOMEM;
}
return EAGAIN;
case LDAP_ACCESS_EXPIRE:
break;
if (ret == ERR_PASSWORD_EXPIRED) {
}
break;
if (ret == ERR_PASSWORD_EXPIRED) {
}
break;
if (ret == ERR_PASSWORD_EXPIRED) {
}
break;
case LDAP_ACCESS_SERVICE:
break;
case LDAP_ACCESS_HOST:
break;
default:
"Unexpected access rule type. Access denied.\n");
}
state->current_rule++;
}
return ret;
}
{
struct tevent_req *req;
struct sdap_access_req_ctx *state;
/* process subrequest */
break;
break;
default:
break;
}
if (ret == ERR_ACCESS_DENIED) {
} else {
"Error retrieving access check result.\n");
}
return;
}
state->current_rule++;
switch (ret) {
case EAGAIN:
return;
case EOK:
return;
default:
return;
}
}
{
return EOK;
}
#define SHADOW_EXPIRE_MSG "Account expired according to shadow attributes"
struct ldb_message *user_entry)
{
int ret;
const char *val;
long sp_expire;
long today;
"Access will be granted.\n");
return EOK;
}
return ret;
}
sizeof(SHADOW_EXPIRE_MSG),
(const uint8_t *) SHADOW_EXPIRE_MSG);
}
return ERR_ACCOUNT_EXPIRED;
}
return EOK;
}
#define UAC_ACCOUNTDISABLE 0x00000002
#define AD_NEVER_EXP 0x7fffffffffffffffLL
#define AD_TO_UNIX_TIME_CONST 11644473600LL
#define AD_DISABLE_MESSAGE "The user account is disabled on the AD server"
#define AD_EXPIRED_MESSAGE "The user account is expired on the AD server"
{
int err;
return false;
}
return true;
}
/* NT timestamps start at 1601-01-01 and use a 100ns base */
if (nt_now > expiration_time) {
return true;
}
return false;
}
struct ldb_message *user_entry)
{
int ret;
0);
if (uac & UAC_ACCOUNTDISABLE) {
sizeof(AD_DISABLE_MESSAGE),
(const uint8_t *) AD_DISABLE_MESSAGE);
}
return ERR_ACCESS_DENIED;
} else if (ad_account_expired(expiration_time)) {
sizeof(AD_EXPIRED_MESSAGE),
(const uint8_t *) AD_EXPIRED_MESSAGE);
}
return ERR_ACCOUNT_EXPIRED;
}
return EOK;
}
#define RHDS_LOCK_MSG "The user account is locked on the server"
struct ldb_message *user_entry)
{
bool locked;
int ret;
if (locked) {
sizeof(RHDS_LOCK_MSG),
(const uint8_t *) RHDS_LOCK_MSG);
}
return ERR_ACCESS_DENIED;
}
return EOK;
}
#define NDS_DISABLE_MSG "The user account is disabled on the server"
#define NDS_EXPIRED_MSG "The user account is expired"
#define NDS_TIME_MAP_MSG "The user account is not allowed at this time"
bool nds_check_expired(const char *exp_time_str)
{
char *end;
if (exp_time_str == NULL) {
"ndsLoginExpirationTime is not set, access granted.\n");
return false;
}
"NDS expire date [%s] invalid.\n", exp_time_str);
return true;
}
if (*end != '\0') {
"NDS expire date [%s] contains extra characters.\n",
return true;
}
if (expire_time == -1) {
"mktime failed to convert [%s].\n", exp_time_str);
return true;
}
tzset();
expire_time -= timezone;
"Time info: tzname[0] [%s] tzname[1] [%s] timezone [%ld] "
"daylight [%d] now [%ld] expire_time [%ld].\n", tzname[0],
return true;
}
return false;
}
/* There is no real documentation of the byte string value of
* loginAllowedTimeMap, but some good example code in
*/
{
div_t q;
"loginAllowedTimeMap is missing, access granted.\n");
return false;
}
"Allowed time map has the wrong size, "
return true;
}
if (map_index > 335) {
"Unexpected index value [%zu] for time map.\n", map_index);
return true;
}
"Unexpected result of div(), [%zu][%d][%d].\n",
return true;
}
if (q.rem > 0) {
}
return false;
}
return true;
}
struct ldb_message *user_entry)
{
bool locked = true;
int ret;
const char *exp_time_str;
false);
if (locked) {
sizeof(NDS_DISABLE_MSG),
(const uint8_t *) NDS_DISABLE_MSG);
}
return ERR_ACCESS_DENIED;
} else {
NULL);
if (locked) {
sizeof(NDS_EXPIRED_MSG),
(const uint8_t *) NDS_EXPIRED_MSG);
}
return ERR_ACCESS_DENIED;
} else {
"Account for user [%s] is%s locked at this time.\n",
if (locked) {
sizeof(NDS_TIME_MAP_MSG),
(const uint8_t *) NDS_TIME_MAP_MSG);
}
return ERR_ACCESS_DENIED;
}
}
}
return EOK;
}
struct ldb_message *user_entry)
{
const char *expire;
int ret;
"Missing account expire policy. Access denied\n");
return ERR_ACCESS_DENIED;
} else {
if (ret == ERR_ACCOUNT_EXPIRED) {
"sdap_account_expired_shadow failed.\n");
}
}
if (ret == ERR_ACCESS_DENIED) {
"sdap_account_expired_rhds failed.\n");
}
if (ret == ERR_ACCESS_DENIED) {
"sdap_account_expired_nds failed.\n");
}
} else {
"Unsupported LDAP account expire policy [%s]. "
"Access denied.\n", expire);
}
}
return ret;
}
struct sss_domain_info *domain,
struct sdap_options *opts)
{
enum pwexpire pw_expire_type;
void *pw_expire_data;
char *dn;
goto done;
}
"check_pwexpire_policy returned %d:[%s].\n",
goto done;
}
done:
return ret;
}
struct sdap_access_filter_req_ctx {
const char *username;
const char *filter;
struct tevent_context *ev;
struct sdap_access_ctx *access_ctx;
struct sdap_options *opts;
struct sdap_id_conn_ctx *conn;
struct sdap_id_op *sdap_op;
struct sysdb_handle *handle;
struct sss_domain_info *domain;
/* cached result of access control checks */
bool cached_access;
const char *basedn;
};
struct tevent_context *ev,
struct sss_domain_info *domain,
struct sdap_access_ctx *access_ctx,
struct sdap_id_conn_ctx *conn,
const char *username,
struct ldb_message *user_entry)
{
struct sdap_access_filter_req_ctx *state;
struct tevent_req *req;
char *clean_username;
char *name;
return NULL;
}
/* If no filter is set, default to restrictive */
goto done;
}
"Performing access filter check for user [%s]\n", username);
false);
/* Ok, we have one result, check if we are online or offline */
if (be_is_offline(be_ctx)) {
/* Ok, we're offline. Return from the cache */
goto done;
}
goto done;
}
/* Construct the filter */
/* Subdomain users are identified by FQDN. We need to use just the username */
"Could not parse [%s] into name and "
"domain components, access might fail\n", username);
}
goto done;
}
"(&(%s=%s)(objectclass=%s)%s)",
goto done;
}
goto done;
}
goto done;
}
return req;
done:
} else {
}
return req;
}
/* Helper function,
* cached_ac => access granted
* !cached_ac => access denied
*/
{
if (cached_ac) {
return EOK;
} else {
return ERR_ACCESS_DENIED;
}
}
{
struct sdap_access_filter_req_ctx *state =
struct tevent_req *subreq;
int ret;
if (!subreq) {
return ret;
}
return EOK;
}
{
struct tevent_req);
struct sdap_access_filter_req_ctx *state =
if (dp_error == DP_ERR_OFFLINE) {
return;
}
}
return;
}
/* Connection to LDAP succeeded
* Send filter request
*/
NULL, 0,
false);
return;
}
}
{
bool found = false;
struct sysdb_attrs **results;
struct tevent_req *req =
struct sdap_access_filter_req_ctx *state =
&num_results, &results);
/* retry */
return;
}
} else if (dp_error == DP_ERR_OFFLINE) {
} else if (ret == ERR_INVALID_FILTER) {
} else {
"sdap_get_generic_send() returned error [%d][%s]\n",
}
goto done;
}
/* Check the number of responses we got
* If it's exactly 1, we passed the check
* If it's < 1, we failed the check
* Anything else is an error
*/
if (num_results < 1) {
"User [%s] was not found with the specified filter. "
found = false;
}
ret = ERR_INTERNAL;
goto done;
}
else if (num_results > 1) {
/* It should not be possible to get more than one reply
* here, since we're doing a base-scoped search
*/
ret = ERR_INTERNAL;
goto done;
}
else { /* Ok, we got a single reply */
found = true;
}
if (found) {
/* Save "allow" to the cache for future offline access checks. */
}
else {
/* Save "disallow" to the cache for future offline
* access checks.
*/
}
/* Failing to save to the cache is non-fatal.
* Just return the result.
*/
goto done;
}
done:
}
else {
}
}
{
return EOK;
}
#define AUTHR_SRV_MISSING_MSG "Authorized service attribute missing, " \
"access denied"
#define AUTHR_SRV_DENY_MSG "Access denied by authorized service attribute"
#define AUTHR_SRV_NO_MATCH_MSG "Authorized service attribute has " \
"no matching rule, access denied"
struct ldb_message *user_entry)
{
struct ldb_message_element *el;
unsigned int i;
char *service;
"Missing authorized services. Access denied\n");
sizeof(AUTHR_SRV_MISSING_MSG),
(const uint8_t *) AUTHR_SRV_MISSING_MSG);
}
return ERR_ACCESS_DENIED;
}
for (i = 0; i < el->num_values; i++) {
if (service[0] == '!' &&
/* This service is explicitly denied */
sizeof(AUTHR_SRV_DENY_MSG),
(const uint8_t *) AUTHR_SRV_DENY_MSG);
}
/* A denial trumps all. Break here */
return ERR_ACCESS_DENIED;
/* This service is explicitly allowed */
/* We still need to loop through to make sure
* that it's not also explicitly denied
*/
/* This user has access to all services */
/* We still need to loop through to make sure
* that it's not also explicitly denied
*/
}
}
sizeof(AUTHR_SRV_NO_MATCH_MSG),
(const uint8_t *) AUTHR_SRV_NO_MATCH_MSG);
}
}
return ret;
}
const char *username,
const char *attr_name,
bool value)
{
struct sysdb_attrs *attrs;
goto done;
}
goto done;
}
goto done;
}
done:
return ret;
}
{
struct ldb_message_element *el;
unsigned int i;
char *host;
return ERR_ACCESS_DENIED;
}
"Unable to get system hostname. Access denied\n");
return ERR_ACCESS_DENIED;
}
/* FIXME: PADL's pam_ldap also calls gethostbyname() on the hostname
* Not sure this is a good idea, but we might want to add it in
* order to be compatible...
*/
for (i = 0; i < el->num_values; i++) {
if (host[0] == '!' &&
/* This host is explicitly denied */
/* A denial trumps all. Break here */
return ERR_ACCESS_DENIED;
/* This host is explicitly allowed */
/* We still need to loop through to make sure
* that it's not also explicitly denied
*/
/* This user has access to all hosts */
/* We still need to loop through to make sure
* that it's not also explicitly denied
*/
}
}
}
return ret;
}
struct sdap_access_ppolicy_req_ctx {
const char *username;
const char *filter;
struct tevent_context *ev;
struct sdap_access_ctx *access_ctx;
struct sdap_options *opts;
struct sdap_id_conn_ctx *conn;
struct sdap_id_op *sdap_op;
struct sysdb_handle *handle;
struct sss_domain_info *domain;
/* cached results of access control checks */
bool cached_access;
const char *basedn;
/* default DNs to ppolicy */
const char **ppolicy_dns;
unsigned int ppolicy_dns_index;
enum sdap_pwpolicy_mode pwpol_mode;
};
static struct tevent_req *
struct tevent_context *ev,
struct sss_domain_info *domain,
struct sdap_access_ctx *access_ctx,
struct sdap_id_conn_ctx *conn,
const char *username,
struct ldb_message *user_entry,
enum sdap_pwpolicy_mode pwpol_mode)
{
struct sdap_access_ppolicy_req_ctx *state;
struct tevent_req *req;
&state, struct sdap_access_ppolicy_req_ctx);
return NULL;
}
state->ppolicy_dns_index = 0;
"Performing access ppolicy check for user [%s]\n", username);
user_entry, SYSDB_LDAP_ACCESS_CACHED_LOCKOUT, false);
/* Ok, we have one result, check if we are online or offline */
if (be_is_offline(be_ctx)) {
/* Ok, we're offline. Return from the cache */
goto done;
}
goto done;
}
goto done;
}
goto done;
}
return req;
done:
} else {
}
return req;
}
{
struct sdap_access_ppolicy_req_ctx *state;
struct tevent_req *subreq;
int ret;
if (!subreq) {
"sdap_id_op_connect_send failed: %d (%s)\n",
return ret;
}
return EOK;
}
static const char**
{
const char **ppolicy_dns;
int count = 0;
int i;
count++;
}
/* +1 to have space for final NULL */
for(i = 0; i < count; i++) {
}
return ppolicy_dns;
}
{
struct tevent_req *req;
struct sdap_access_ppolicy_req_ctx *state;
const char *ppolicy_dn;
if (dp_error == DP_ERR_OFFLINE) {
return;
}
}
return;
}
/* option was configured */
if (ppolicy_dn != NULL) {
return;
}
} else {
/* try to determine default value */
"ldap_pwdlockout_dn was not defined in configuration file.\n");
return;
}
}
/* Connection to LDAP succeeded
* Send 'pwdLockout' request
*/
"sdap_access_ppolicy_get_lockout_step failed: [%d][%s]\n",
return;
}
}
}
static errno_t
{
struct sdap_access_ppolicy_req_ctx *state;
struct tevent_req *subreq;
/* no more DNs to try */
goto done;
}
"Trying to find out if ppolicy is enabled using the DN: %s\n",
NULL, 0,
false);
goto done;
}
/* try next basedn */
done:
return ret;
}
{
bool pwdLockout = false;
struct sysdb_attrs **results;
struct tevent_req *req;
struct sdap_access_ppolicy_req_ctx *state;
goto done;
}
/* Check the number of responses we got
* If it's exactly 1, we passed the check
* If it's < 1, we failed the check
* Anything else is an error
*/
/* Didn't find ppolicy attribute */
if (num_results < 1) {
/* Try using next $search_base */
/* No more search bases to try */
"[%s] was not found. Granting access.\n",
} else {
"sdap_access_ppolicy_get_lockout_step failed: "
"[%d][%s]\n",
}
goto done;
}
ret = ERR_INTERNAL;
goto done;
} else if (num_results > 1) {
/* It should not be possible to get more than one reply
* here, since we're doing a base-scoped search
*/
ret = ERR_INTERNAL;
goto done;
} else { /* Ok, we got a single reply */
&pwdLockout);
"Error reading %s: [%s]\n", SYSDB_LDAP_ACCESS_LOCKOUT,
sss_strerror(ret));
ret = ERR_INTERNAL;
goto done;
}
}
if (pwdLockout) {
"Password policy is enabled on LDAP server.\n");
/* ppolicy is enabled => find out if account is locked */
"sdap_access_ppolicy_step failed: [%d][%s].\n",
}
goto done;
} else {
"Password policy is disabled on LDAP server "
"- storing 'access granted' in sysdb.\n");
true);
/* Failing to save to the cache is non-fatal.
* Just return the result.
*/
"Failed to set user locked attribute\n");
goto done;
}
goto done;
}
done:
/* release connection */
"sdap_get_generic_send() returned error [%d][%s]\n",
}
} else {
}
}
}
{
struct tevent_req *subreq;
struct sdap_access_ppolicy_req_ctx *state;
const char *attrs[] = { SYSDB_LDAP_ACCESS_LOCKED_TIME,
NULL };
NULL, 0,
false);
goto done;
}
done:
return ret;
}
static errno_t
is_account_locked(const char *pwdAccountLockedTime,
const char *pwdAccountLockedDurationTime,
enum sdap_pwpolicy_mode pwpol_mode,
const char *username,
bool *_locked)
{
bool locked;
/* Default action is to consider account to be locked. */
locked = true;
/* account is permanently locked */
PERMANENTLY_LOCKED_ACCOUNT) == 0) {
goto done;
}
switch(pwpol_mode) {
case PWP_LOCKOUT_ONLY:
/* We do *not* care about exact value of account locked time, we
* only *do* care if the value is equal to
* PERMANENTLY_LOCKED_ACCOUNT, which means that account is locked
* permanently.
*/
"Account of: %s is beeing blocked by password policy, "
"but value: [%s] value is ignored by SSSD.\n",
locked = false;
break;
case PWP_LOCKOUT_EXPIRE:
/* Account may be locked out from natural reasons (too many attempts,
* expired password). In this case, pwdAccountLockedTime is also set,
* to the time of lock out.
*/
&lock_time);
goto done;
}
/* Account was NOT locked in past. */
locked = false;
} else if (pwdAccountLockedDurationTime != NULL) {
errno = 0;
if (errno) {
goto done;
}
/* Lockout has expired */
locked = false;
}
}
break;
case PWP_SENTINEL:
default:
"Unexpected value of password policy mode: %d.\n", pwpol_mode);
goto done;
}
done:
}
return ret;
}
{
bool locked = false;
const char *pwdAccountLockedTime;
const char *pwdAccountLockedDurationTime;
struct sysdb_attrs **results;
struct tevent_req *req;
struct sdap_access_ppolicy_req_ctx *state;
/* retry */
return;
}
} else if (dp_error == DP_ERR_OFFLINE) {
} else {
"sdap_get_generic_send() returned error [%d][%s]\n",
}
goto done;
}
/* Check the number of responses we got
* If it's exactly 1, we passed the check
* If it's < 1, we failed the check
* Anything else is an error
*/
if (num_results < 1) {
"User [%s] was not found with the specified filter. "
ret = ERR_INTERNAL;
goto done;
} else if (num_results > 1) {
/* It should not be possible to get more than one reply
* here, since we're doing a base-scoped search
*/
ret = ERR_INTERNAL;
goto done;
} else { /* Ok, we got a single reply */
/* This attribute might not be set even if account is locked */
}
&locked);
if (ret == ERR_TIMESPEC_NOT_SUPPORTED) {
"timezone specifier in ppolicy is not supported\n");
} else {
"is_account_locked failed: %d:[%s].\n",
}
"Account will be considered to be locked.\n");
locked = true;
}
} else {
/* Attribute SYSDB_LDAP_ACCESS_LOCKED_TIME in not be present unless
* user's account is blocked by password policy.
*/
"Attribute %s failed to be obtained - [%d][%s].\n",
}
}
if (locked) {
"Access denied by online lookup - account is locked.\n");
} else {
"Access granted by online lookup - account is not locked.\n");
}
/* Save '!locked' to the cache for future offline access checks.
* Locked == true => access denied,
* Locked == false => access granted
*/
!locked);
/* Failing to save to the cache is non-fatal.
* Just return the result.
*/
goto done;
}
done:
} else {
}
}
{
return EOK;
}
const char *username,
const char **_basedn)
{
const char *basedn;
username);
goto done;
}
done:
return ret;
}