mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2024-12-26 08:51:50 -06:00
6cfb9b7193
If the principal entry in LDAP has SID associated with it, store it to be able to quickly assess the SID when processing PAC. Also rename string_to_sid to IPA-specific version as it uses different prototype than Samba version. Fixes: https://pagure.io/freeipa/issue/9031 Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com> Reviewed-by: Andreas Schneider <asn@samba.org> Reviewed-by: Robert Crittenden <rcritten@redhat.com> Reviewed-By: Rob Crittenden <rcritten@redhat.com>
2870 lines
82 KiB
C
2870 lines
82 KiB
C
/*
|
|
* MIT Kerberos KDC database backend for FreeIPA
|
|
*
|
|
* Authors: Simo Sorce <ssorce@redhat.com>
|
|
*
|
|
* Copyright (C) 2011 Simo Sorce, Red Hat
|
|
* see file 'COPYING' for use and warranty information
|
|
*
|
|
* This program is free software you can redistribute it and/or modify
|
|
* 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 "ipa_kdb.h"
|
|
#include "ipa_krb5.h"
|
|
#include <unicase.h>
|
|
|
|
/*
|
|
* During TGS request search by ipaKrbPrincipalName (case-insensitive)
|
|
* and krbPrincipalName (case-sensitive)
|
|
*/
|
|
#define PRINC_TGS_SEARCH_FILTER_EXTRA "(&(|(objectclass=krbprincipalaux)" \
|
|
"(objectclass=krbprincipal)" \
|
|
"(objectclass=ipakrbprincipal))" \
|
|
"(|(ipakrbprincipalalias=%s)" \
|
|
"(krbprincipalname:caseIgnoreIA5Match:=%s))" \
|
|
"%s)"
|
|
|
|
#define PRINC_SEARCH_FILTER_EXTRA "(&(|(objectclass=krbprincipalaux)" \
|
|
"(objectclass=krbprincipal))" \
|
|
"(krbprincipalname=%s)" \
|
|
"%s)"
|
|
|
|
#define PRINC_TGS_SEARCH_FILTER_WILD_EXTRA "(&(|(objectclass=krbprincipalaux)" \
|
|
"(objectclass=krbprincipal)" \
|
|
"(objectclass=ipakrbprincipal))" \
|
|
"(|(ipakrbprincipalalias=*)" \
|
|
"(krbprincipalname=*))" \
|
|
"%s)"
|
|
static char *std_principal_attrs[] = {
|
|
"krbPrincipalName",
|
|
"krbCanonicalName",
|
|
"krbUPEnabled",
|
|
"krbPrincipalKey",
|
|
"krbTicketPolicyReference",
|
|
"krbPrincipalExpiration",
|
|
"krbPasswordExpiration",
|
|
"krbPwdPolicyReference",
|
|
"krbPrincipalType",
|
|
"krbPwdHistory",
|
|
"krbLastPwdChange",
|
|
"krbPrincipalAliases",
|
|
"krbLastSuccessfulAuth",
|
|
"krbLastFailedAuth",
|
|
"krbLoginFailedCount",
|
|
"krbPrincipalAuthInd",
|
|
"krbExtraData",
|
|
"krbLastAdminUnlock",
|
|
"krbObjectReferences",
|
|
"krbTicketFlags",
|
|
"krbMaxTicketLife",
|
|
"krbMaxRenewableAge",
|
|
|
|
/* IPA SPECIFIC ATTRIBUTES */
|
|
"uid",
|
|
"nsaccountlock",
|
|
"passwordHistory",
|
|
IPA_KRB_AUTHZ_DATA_ATTR,
|
|
IPA_USER_AUTH_TYPE,
|
|
"ipatokenRadiusConfigLink",
|
|
"krbAuthIndMaxTicketLife",
|
|
"krbAuthIndMaxRenewableAge",
|
|
"ipaNTSecurityIdentifier",
|
|
"ipaUniqueID",
|
|
|
|
"objectClass",
|
|
NULL
|
|
};
|
|
|
|
static char *std_tktpolicy_attrs[] = {
|
|
"krbmaxticketlife",
|
|
"krbmaxrenewableage",
|
|
"krbticketflags",
|
|
"krbauthindmaxticketlife",
|
|
"krbauthindmaxrenewableage",
|
|
|
|
NULL
|
|
};
|
|
|
|
#define TKTFLAGS_BIT 0x01
|
|
#define MAXTKTLIFE_BIT 0x02
|
|
#define MAXRENEWABLEAGE_BIT 0x04
|
|
|
|
static char *std_principal_obj_classes[] = {
|
|
"krbprincipal",
|
|
"krbprincipalaux",
|
|
"krbTicketPolicyAux",
|
|
|
|
NULL
|
|
};
|
|
|
|
#define STD_PRINCIPAL_OBJ_CLASSES_SIZE (sizeof(std_principal_obj_classes) / sizeof(char *) - 1)
|
|
|
|
#define DEFAULT_TL_DATA_CONTENT "\x00\x00\x00\x00principal@UNINITIALIZED"
|
|
|
|
static int ipadb_ldap_attr_to_tl_data(LDAP *lcontext, LDAPMessage *le,
|
|
char *attrname,
|
|
krb5_tl_data **result, int *num)
|
|
{
|
|
struct berval **vals;
|
|
krb5_tl_data *prev, *next;
|
|
krb5_int16 be_type;
|
|
int i;
|
|
int ret = ENOENT;
|
|
|
|
*result = NULL;
|
|
prev = NULL;
|
|
next = NULL;
|
|
vals = ldap_get_values_len(lcontext, le, attrname);
|
|
if (vals) {
|
|
for (i = 0; vals[i]; i++) {
|
|
next = calloc(1, sizeof(krb5_tl_data));
|
|
if (!next) {
|
|
ret = ENOMEM;
|
|
goto done;
|
|
}
|
|
|
|
/* fill tl_data struct with the data */
|
|
memcpy(&be_type, vals[i]->bv_val, 2);
|
|
next->tl_data_type = ntohs(be_type);
|
|
next->tl_data_length = vals[i]->bv_len - 2;
|
|
next->tl_data_contents = malloc(next->tl_data_length);
|
|
if (!next->tl_data_contents) {
|
|
ret = ENOMEM;
|
|
goto done;
|
|
}
|
|
memcpy(next->tl_data_contents,
|
|
vals[i]->bv_val + 2,
|
|
next->tl_data_length);
|
|
|
|
if (prev) {
|
|
prev->tl_data_next = next;
|
|
} else {
|
|
*result = next;
|
|
}
|
|
prev = next;
|
|
}
|
|
*num = i;
|
|
ret = 0;
|
|
|
|
ldap_value_free_len(vals);
|
|
}
|
|
|
|
done:
|
|
if (ret) {
|
|
free(next);
|
|
if (*result) {
|
|
prev = *result;
|
|
while (prev) {
|
|
next = prev->tl_data_next;
|
|
free(prev);
|
|
prev = next;
|
|
}
|
|
}
|
|
*result = NULL;
|
|
*num = 0;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static krb5_error_code ipadb_set_tl_data(krb5_db_entry *entry,
|
|
krb5_int16 type,
|
|
krb5_ui_2 length,
|
|
const krb5_octet *data)
|
|
{
|
|
krb5_error_code kerr;
|
|
krb5_tl_data *new_td = NULL;
|
|
krb5_tl_data *td;
|
|
|
|
for (td = entry->tl_data; td; td = td->tl_data_next) {
|
|
if (td->tl_data_type == type) {
|
|
break;
|
|
}
|
|
}
|
|
if (!td) {
|
|
/* an existing entry was not found, make new */
|
|
new_td = malloc(sizeof(krb5_tl_data));
|
|
if (!new_td) {
|
|
kerr = ENOMEM;
|
|
goto done;
|
|
}
|
|
td = new_td;
|
|
td->tl_data_next = entry->tl_data;
|
|
td->tl_data_type = type;
|
|
entry->tl_data = td;
|
|
entry->n_tl_data++;
|
|
}
|
|
td->tl_data_length = length;
|
|
td->tl_data_contents = malloc(td->tl_data_length);
|
|
if (!td->tl_data_contents) {
|
|
kerr = ENOMEM;
|
|
goto done;
|
|
}
|
|
memcpy(td->tl_data_contents, data, td->tl_data_length);
|
|
|
|
new_td = NULL;
|
|
kerr = 0;
|
|
|
|
done:
|
|
free(new_td);
|
|
return kerr;
|
|
}
|
|
|
|
static int ipadb_ldap_attr_to_key_data(LDAP *lcontext, LDAPMessage *le,
|
|
char *attrname,
|
|
krb5_key_data **result, int *num,
|
|
krb5_kvno *res_mkvno)
|
|
{
|
|
struct berval **vals;
|
|
int mkvno;
|
|
int ret;
|
|
|
|
vals = ldap_get_values_len(lcontext, le, attrname);
|
|
if (!vals) {
|
|
return ENOENT;
|
|
}
|
|
|
|
ret = ber_decode_krb5_key_data(vals[0], &mkvno, num, result);
|
|
ldap_value_free_len(vals);
|
|
if (ret == 0) {
|
|
*res_mkvno = mkvno;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static void ipadb_validate_otp(struct ipadb_context *ipactx,
|
|
LDAPMessage *lentry,
|
|
enum ipadb_user_auth *ua)
|
|
{
|
|
static const char *attrs[] = { "dn", NULL };
|
|
static const char *dttmpl = "%Y%m%d%H%M%SZ";
|
|
static const char *ftmpl = "(&"
|
|
"(objectClass=ipaToken)(ipatokenOwner=%s)"
|
|
"(|(ipatokenNotBefore<=%s)(!(ipatokenNotBefore=*)))"
|
|
"(|(ipatokenNotAfter>=%s)(!(ipatokenNotAfter=*)))"
|
|
"(|(ipatokenDisabled=FALSE)(!(ipatokenDisabled=*)))"
|
|
")";
|
|
krb5_error_code kerr = 0;
|
|
LDAPMessage *res = NULL;
|
|
char datetime[16] = {};
|
|
char *filter = NULL;
|
|
struct tm tm = {};
|
|
char *dn = NULL;
|
|
time_t now = 0;
|
|
int count = 0;
|
|
|
|
if (!(*ua & IPADB_USER_AUTH_OTP))
|
|
return;
|
|
|
|
/* Get the current time. */
|
|
if (time(&now) == (time_t) -1)
|
|
return;
|
|
if (gmtime_r(&now, &tm) == NULL)
|
|
return;
|
|
|
|
/* Make the current time string. */
|
|
if (strftime(datetime, sizeof(datetime), dttmpl, &tm) == 0)
|
|
return;
|
|
|
|
/* Make the filter. */
|
|
dn = ldap_get_dn(ipactx->lcontext, lentry);
|
|
if (dn == NULL)
|
|
return;
|
|
count = asprintf(&filter, ftmpl, dn, datetime, datetime);
|
|
ldap_memfree(dn);
|
|
if (count < 0)
|
|
return;
|
|
|
|
/* Fetch the active token list. */
|
|
kerr = ipadb_simple_search(ipactx, ipactx->base, LDAP_SCOPE_SUBTREE,
|
|
filter, (char**) attrs, &res);
|
|
free(filter);
|
|
if (kerr != 0 || res == NULL)
|
|
return;
|
|
|
|
/* Count the number of active tokens. */
|
|
count = ldap_count_entries(ipactx->lcontext, res);
|
|
ldap_msgfree(res);
|
|
|
|
/* If the user is configured for OTP, but has no active tokens, remove
|
|
* OTP from the list since the user obviously can't log in this way. */
|
|
if (count == 0)
|
|
*ua &= ~IPADB_USER_AUTH_OTP;
|
|
}
|
|
|
|
static void ipadb_validate_radius(struct ipadb_context *ipactx,
|
|
LDAPMessage *lentry,
|
|
enum ipadb_user_auth *ua)
|
|
{
|
|
struct berval **vals;
|
|
|
|
if (!(*ua & IPADB_USER_AUTH_RADIUS))
|
|
return;
|
|
|
|
/* Ensure that the user has a link to a RADIUS config. */
|
|
vals = ldap_get_values_len(ipactx->lcontext, lentry,
|
|
"ipatokenRadiusConfigLink");
|
|
if (vals == NULL || vals[0] == NULL)
|
|
*ua &= ~IPADB_USER_AUTH_RADIUS;
|
|
else
|
|
*ua = IPADB_USER_AUTH_RADIUS;
|
|
|
|
if (vals != NULL)
|
|
ldap_value_free_len(vals);
|
|
}
|
|
|
|
static enum ipadb_user_auth ipadb_get_user_auth(struct ipadb_context *ipactx,
|
|
LDAPMessage *lentry)
|
|
{
|
|
enum ipadb_user_auth gua = IPADB_USER_AUTH_NONE;
|
|
enum ipadb_user_auth ua = IPADB_USER_AUTH_NONE;
|
|
const struct ipadb_global_config *gcfg = NULL;
|
|
|
|
/* Get the global user_auth settings. */
|
|
gcfg = ipadb_get_global_config(ipactx);
|
|
if (gcfg != NULL)
|
|
gua = gcfg->user_auth;
|
|
|
|
/* lcontext == NULL means ipadb_get_global_config() failed to load
|
|
* global config and cleared the ipactx */
|
|
if (ipactx->lcontext == NULL)
|
|
return IPADB_USER_AUTH_NONE;
|
|
|
|
/* Get the user's user_auth settings if not disabled. */
|
|
if ((gua & IPADB_USER_AUTH_DISABLED) == 0)
|
|
ipadb_parse_user_auth(ipactx->lcontext, lentry, &ua);
|
|
|
|
/* Filter out the disabled flag. */
|
|
gua &= ~IPADB_USER_AUTH_DISABLED;
|
|
ua &= ~IPADB_USER_AUTH_DISABLED;
|
|
|
|
/* Determine which user_auth policy is active: user or global. */
|
|
if (ua == IPADB_USER_AUTH_NONE)
|
|
ua = gua;
|
|
|
|
/* Perform flag validation. */
|
|
ipadb_validate_otp(ipactx, lentry, &ua);
|
|
ipadb_validate_radius(ipactx, lentry, &ua);
|
|
|
|
return ua;
|
|
}
|
|
|
|
#define OSA_ADB_PRINC_VERSION_1 0x12345C01
|
|
/* The XDR encoding of OSA_PRINC_ENC is as follows:
|
|
version: int (signed 32 bit integer)
|
|
name: nullstring (null terminated variable string)
|
|
aux_attributes: long (signed 32 bit integer)
|
|
old_key_next: u_int (unsigned 32 bit integer)
|
|
adm_hist_kvno: u_char (unisgned char)
|
|
old_keys: array of keys, we do not care so alway u_int of 0
|
|
*/
|
|
#define OSA_PRINC_ENC_BASE_SIZE 20
|
|
|
|
static krb5_error_code ipadb_policydn_to_kdam_tl_data(const char *policydn,
|
|
krb5_db_entry *entry)
|
|
{
|
|
krb5_error_code kerr;
|
|
uint32_t tmp;
|
|
char *policy_name = NULL;
|
|
char *p;
|
|
uint8_t *buf = NULL;
|
|
size_t buf_len;
|
|
int slen;
|
|
int plen;
|
|
int cur;
|
|
|
|
/* policy objects must use cn as the RDN */
|
|
if (strncmp(policydn, "cn=", 3) != 0) {
|
|
return KRB5_KDB_INTERNAL_ERROR;
|
|
}
|
|
|
|
/* Should we try to consider the case where a ',' is part of the polict
|
|
* name ? */
|
|
policy_name = strdup(&policydn[3]);
|
|
if (!policy_name) {
|
|
kerr = ENOMEM;
|
|
goto done;
|
|
}
|
|
p = strchr(policy_name, ',');
|
|
if (p) *p = '\0';
|
|
|
|
/* Now we open code a basic KRB5_TL_KADM_DATA which is a XDR encoded
|
|
* structure in MIT code */
|
|
|
|
slen = strlen(policy_name) + 1;
|
|
/* A xdr varstring is preceeded by a 32bit len field and is always 32
|
|
* bit aligned */
|
|
plen = slen + 4;
|
|
plen = (((plen + 3) / 4) * 4);
|
|
|
|
buf_len = OSA_PRINC_ENC_BASE_SIZE + plen;
|
|
buf = calloc(1, buf_len);
|
|
if (!buf) {
|
|
kerr = ENOMEM;
|
|
goto done;
|
|
}
|
|
|
|
/* version */
|
|
cur = 0;
|
|
tmp = htobe32(OSA_ADB_PRINC_VERSION_1);
|
|
memcpy(&buf[cur], &tmp, 4);
|
|
cur += 4;
|
|
|
|
/* name */
|
|
tmp = htobe32(slen);
|
|
memcpy(&buf[cur], &tmp, 4);
|
|
memcpy(&buf[cur + 4], policy_name, slen);
|
|
cur += plen;
|
|
|
|
/* All the other fileds are left empty */
|
|
|
|
kerr = ipadb_set_tl_data(entry, KRB5_TL_KADM_DATA, buf_len, buf);
|
|
|
|
done:
|
|
free(policy_name);
|
|
free(buf);
|
|
return kerr;
|
|
}
|
|
|
|
static void strv_free(char **strv)
|
|
{
|
|
int i;
|
|
|
|
if (strv == NULL) {
|
|
return;
|
|
}
|
|
|
|
for (i = 0; strv[i] != NULL; i++) {
|
|
free(strv[i]);
|
|
}
|
|
|
|
free(strv);
|
|
}
|
|
|
|
static krb5_error_code ipadb_get_ldap_auth_ind(krb5_context kcontext,
|
|
LDAP *lcontext,
|
|
LDAPMessage *lentry,
|
|
krb5_db_entry *entry)
|
|
{
|
|
krb5_error_code ret = 0;
|
|
char **authinds = NULL;
|
|
char *aistr = NULL;
|
|
char *ap = NULL;
|
|
size_t len = 0;
|
|
size_t l = 0;
|
|
int count = 0;
|
|
int i = 0;
|
|
|
|
ret = ipadb_ldap_attr_to_strlist(lcontext, lentry, "krbPrincipalAuthInd",
|
|
&authinds);
|
|
switch (ret) {
|
|
case 0:
|
|
break;
|
|
case ENOENT:
|
|
return 0;
|
|
default:
|
|
return ret;
|
|
}
|
|
|
|
for (count = 0; authinds != NULL && authinds[count] != NULL; count++) {
|
|
len += strlen(authinds[count]) + 1;
|
|
}
|
|
|
|
if (len == 0) {
|
|
strv_free(authinds);
|
|
return 0;
|
|
}
|
|
|
|
aistr = malloc(len);
|
|
if (aistr == NULL) {
|
|
ret = errno;
|
|
goto cleanup;
|
|
}
|
|
|
|
/* Create a space-separated string of authinds. */
|
|
ap = aistr;
|
|
l = len;
|
|
for (i = 0; i < count; i++) {
|
|
ret = snprintf(ap, l, "%s ", authinds[i]);
|
|
if (ret <= 0 || ret > (int) l) {
|
|
ret = ENOMEM;
|
|
goto cleanup;
|
|
}
|
|
ap += ret;
|
|
l -= ret;
|
|
}
|
|
aistr[len - 1] = '\0';
|
|
|
|
ret = krb5_dbe_set_string(kcontext, entry, "require_auth",
|
|
aistr);
|
|
|
|
cleanup:
|
|
strv_free(authinds);
|
|
free(aistr);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void ipadb_parse_authind_policies(krb5_context kcontext,
|
|
LDAP *lcontext,
|
|
LDAPMessage *lentry,
|
|
krb5_db_entry *entry,
|
|
enum ipadb_user_auth ua)
|
|
{
|
|
int result;
|
|
int ret;
|
|
struct ipadb_e_data *ied;
|
|
const struct {
|
|
char *attribute;
|
|
enum ipadb_user_auth flag;
|
|
enum ipadb_user_auth_idx idx;
|
|
} life_authind_map[] = {
|
|
{"krbAuthIndMaxTicketLife;otp",
|
|
IPADB_USER_AUTH_OTP, IPADB_USER_AUTH_IDX_OTP},
|
|
{"krbAuthIndMaxTicketLife;radius",
|
|
IPADB_USER_AUTH_RADIUS, IPADB_USER_AUTH_IDX_RADIUS},
|
|
{"krbAuthIndMaxTicketLife;pkinit",
|
|
IPADB_USER_AUTH_PKINIT, IPADB_USER_AUTH_IDX_PKINIT},
|
|
{"krbAuthIndMaxTicketLife;hardened",
|
|
IPADB_USER_AUTH_HARDENED, IPADB_USER_AUTH_IDX_HARDENED},
|
|
{NULL, IPADB_USER_AUTH_NONE, IPADB_USER_AUTH_IDX_MAX},
|
|
}, age_authind_map[] = {
|
|
{"krbAuthIndMaxRenewableAge;otp",
|
|
IPADB_USER_AUTH_OTP, IPADB_USER_AUTH_IDX_OTP},
|
|
{"krbAuthIndMaxRenewableAge;radius",
|
|
IPADB_USER_AUTH_RADIUS, IPADB_USER_AUTH_IDX_RADIUS},
|
|
{"krbAuthIndMaxRenewableAge;pkinit",
|
|
IPADB_USER_AUTH_PKINIT, IPADB_USER_AUTH_IDX_PKINIT},
|
|
{"krbAuthIndMaxRenewableAge;hardened",
|
|
IPADB_USER_AUTH_HARDENED, IPADB_USER_AUTH_IDX_HARDENED},
|
|
{NULL, IPADB_USER_AUTH_NONE, IPADB_USER_AUTH_IDX_MAX},
|
|
};
|
|
|
|
ied = (struct ipadb_e_data *)entry->e_data;
|
|
if (ied == NULL) {
|
|
return;
|
|
}
|
|
|
|
for (size_t i = 0; life_authind_map[i].attribute != NULL; i++) {
|
|
if (ua & life_authind_map[i].flag) {
|
|
ret = ipadb_ldap_attr_to_int(lcontext, lentry,
|
|
life_authind_map[i].attribute,
|
|
&result);
|
|
if (ret == 0) {
|
|
ied->pol_limits[life_authind_map[i].idx].max_life = result;
|
|
}
|
|
|
|
ret = ipadb_ldap_attr_to_int(lcontext, lentry,
|
|
age_authind_map[i].attribute,
|
|
&result);
|
|
if (ret == 0) {
|
|
ied->pol_limits[age_authind_map[i].idx].max_renewable_life = result;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static krb5_error_code ipadb_parse_ldap_entry(krb5_context kcontext,
|
|
char *principal,
|
|
LDAPMessage *lentry,
|
|
krb5_db_entry **kentry,
|
|
uint32_t *polmask)
|
|
{
|
|
const krb5_octet rad_string[] = "otp\0[{\"indicators\": [\"radius\"]}]";
|
|
const krb5_octet otp_string[] = "otp\0[{\"indicators\": [\"otp\"]}]";
|
|
struct ipadb_context *ipactx;
|
|
enum ipadb_user_auth ua;
|
|
LDAP *lcontext;
|
|
krb5_db_entry *entry;
|
|
struct ipadb_e_data *ied;
|
|
krb5_error_code kerr;
|
|
krb5_tl_data *res_tl_data;
|
|
krb5_key_data *res_key_data;
|
|
krb5_kvno mkvno = 0;
|
|
char **restrlist;
|
|
char *restring;
|
|
char *uidstring;
|
|
char **authz_data_list;
|
|
char *princ_sid;
|
|
krb5_timestamp restime;
|
|
bool resbool;
|
|
int result;
|
|
int ret;
|
|
|
|
*polmask = 0;
|
|
entry = calloc(1, sizeof(krb5_db_entry));
|
|
if (!entry) {
|
|
return ENOMEM;
|
|
}
|
|
|
|
/* proceed to fill in attributes in the order they are defined in
|
|
* krb5_db_entry in kdb.h */
|
|
ipactx = ipadb_get_context(kcontext);
|
|
if (!ipactx) {
|
|
free(entry);
|
|
return KRB5_KDB_DBNOTINITED;
|
|
}
|
|
|
|
entry->magic = KRB5_KDB_MAGIC_NUMBER;
|
|
entry->len = KRB5_KDB_V1_BASE_LENGTH;
|
|
|
|
/* Get User Auth configuration. */
|
|
ua = ipadb_get_user_auth(ipactx, lentry);
|
|
|
|
/* ipadb_get_user_auth() calls into ipadb_get_global_config()
|
|
* and that might fail, causing lcontext to become NULL */
|
|
if (!ipactx->lcontext) {
|
|
krb5_klog_syslog(LOG_INFO,
|
|
"No LDAP connection in ipadb_parse_ldap_entry(); retrying...\n");
|
|
ret = ipadb_get_connection(ipactx);
|
|
if (ret != 0) {
|
|
krb5_klog_syslog(LOG_ERR,
|
|
"No LDAP connection on retry in ipadb_parse_ldap_entry()!\n");
|
|
kerr = KRB5_KDB_INTERNAL_ERROR;
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
/* If any code below would result in invalidating ipactx->lcontext,
|
|
* lcontext must be updated with the new ipactx->lcontext value.
|
|
* We rely on the fact that none of LDAP-parsing helpers does it. */
|
|
lcontext = ipactx->lcontext;
|
|
|
|
/* ignore mask for now */
|
|
|
|
ret = ipadb_ldap_attr_to_int(lcontext, lentry,
|
|
"krbTicketFlags", &result);
|
|
if (ret == 0) {
|
|
entry->attributes = result;
|
|
} else {
|
|
*polmask |= TKTFLAGS_BIT;
|
|
}
|
|
|
|
ret = ipadb_ldap_attr_to_int(lcontext, lentry,
|
|
"krbMaxTicketLife", &result);
|
|
if (ret == 0) {
|
|
entry->max_life = result;
|
|
} else {
|
|
*polmask |= MAXTKTLIFE_BIT;
|
|
}
|
|
|
|
ret = ipadb_ldap_attr_to_int(lcontext, lentry,
|
|
"krbMaxRenewableAge", &result);
|
|
if (ret == 0) {
|
|
entry->max_renewable_life = result;
|
|
} else {
|
|
*polmask |= MAXRENEWABLEAGE_BIT;
|
|
}
|
|
|
|
ret = ipadb_ldap_attr_to_krb5_timestamp(lcontext, lentry,
|
|
"krbPrincipalexpiration", &restime);
|
|
switch (ret) {
|
|
case 0:
|
|
entry->expiration = restime;
|
|
case ENOENT:
|
|
break;
|
|
default:
|
|
kerr = KRB5_KDB_INTERNAL_ERROR;
|
|
goto done;
|
|
}
|
|
|
|
ret = ipadb_ldap_attr_to_krb5_timestamp(lcontext, lentry,
|
|
"krbPasswordExpiration", &restime);
|
|
switch (ret) {
|
|
case 0:
|
|
entry->pw_expiration = restime;
|
|
|
|
/* If we are using only RADIUS, we don't know expiration. */
|
|
if (ua == IPADB_USER_AUTH_RADIUS)
|
|
entry->pw_expiration = 0;
|
|
case ENOENT:
|
|
break;
|
|
default:
|
|
kerr = KRB5_KDB_INTERNAL_ERROR;
|
|
goto done;
|
|
}
|
|
|
|
ret = ipadb_ldap_attr_to_krb5_timestamp(lcontext, lentry,
|
|
"krbLastSuccessfulAuth", &restime);
|
|
switch (ret) {
|
|
case 0:
|
|
entry->last_success = restime;
|
|
case ENOENT:
|
|
break;
|
|
default:
|
|
kerr = KRB5_KDB_INTERNAL_ERROR;
|
|
goto done;
|
|
}
|
|
|
|
ret = ipadb_ldap_attr_to_krb5_timestamp(lcontext, lentry,
|
|
"krbLastFailedAuth", &restime);
|
|
switch (ret) {
|
|
case 0:
|
|
entry->last_failed = restime;
|
|
case ENOENT:
|
|
break;
|
|
default:
|
|
kerr = KRB5_KDB_INTERNAL_ERROR;
|
|
goto done;
|
|
}
|
|
|
|
ret = ipadb_ldap_attr_to_int(lcontext, lentry,
|
|
"krbLoginFailedCount", &result);
|
|
if (ret == 0) {
|
|
entry->fail_auth_count = result;
|
|
}
|
|
|
|
/* TODO: e_length, e_data */
|
|
|
|
if (principal) {
|
|
kerr = krb5_parse_name(kcontext, principal, &entry->princ);
|
|
if (kerr != 0) {
|
|
goto done;
|
|
}
|
|
} else {
|
|
/* see if canonical name is available */
|
|
ret = ipadb_ldap_attr_to_str(lcontext, lentry,
|
|
"krbCanonicalName", &restring);
|
|
switch (ret) {
|
|
case ENOENT:
|
|
/* if not pick the first principal name in the entry */
|
|
ret = ipadb_ldap_attr_to_str(lcontext, lentry,
|
|
"krbPrincipalName", &restring);
|
|
if (ret != 0) {
|
|
kerr = KRB5_KDB_INTERNAL_ERROR;
|
|
goto done;
|
|
}
|
|
case 0:
|
|
break;
|
|
default:
|
|
kerr = KRB5_KDB_INTERNAL_ERROR;
|
|
goto done;
|
|
}
|
|
kerr = krb5_parse_name(kcontext, restring, &entry->princ);
|
|
free(restring);
|
|
if (kerr != 0) {
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
ret = ipadb_ldap_attr_to_tl_data(lcontext, lentry,
|
|
"krbExtraData", &res_tl_data, &result);
|
|
switch (ret) {
|
|
case 0:
|
|
entry->tl_data = res_tl_data;
|
|
entry->n_tl_data = result;
|
|
break;
|
|
case ENOENT:
|
|
/* The kadmin utility expects always at least KRB5_TL_MOD_PRINC tl_data
|
|
* to be available. So if krbExtraData is missing (may happen when a
|
|
* user is created but no password has been set yet) then add a default
|
|
* one. */
|
|
kerr = ipadb_set_tl_data(entry, KRB5_TL_MOD_PRINC,
|
|
sizeof(DEFAULT_TL_DATA_CONTENT),
|
|
(const krb5_octet *)DEFAULT_TL_DATA_CONTENT);
|
|
break;
|
|
default:
|
|
kerr = KRB5_KDB_INTERNAL_ERROR;
|
|
goto done;
|
|
}
|
|
|
|
ret = ipadb_get_ldap_auth_ind(kcontext, lcontext, lentry, entry);
|
|
if (ret)
|
|
goto done;
|
|
|
|
ret = ipadb_ldap_attr_to_key_data(lcontext, lentry,
|
|
"krbPrincipalKey",
|
|
&res_key_data, &result, &mkvno);
|
|
switch (ret) {
|
|
case 0:
|
|
/* Only set a principal's key if password auth can be used. Otherwise
|
|
* the KDC would add pre-authentication methods to the NEEDED_PREAUTH
|
|
* reply for AS-REQs which indicate the password authentication is
|
|
* available. This might confuse applications like e.g. SSSD which try
|
|
* to determine suitable authentication methods and corresponding
|
|
* prompts with the help of MIT Kerberos' responder interface which
|
|
* acts on the returned pre-authentication methods. A typical example
|
|
* is enforced OTP authentication where of course keys are available
|
|
* for the first factor but password authentication should not be
|
|
* advertised by the KDC. */
|
|
if (!(ua & IPADB_USER_AUTH_PASSWORD) && (ua != IPADB_USER_AUTH_NONE)) {
|
|
/* This is the same behavior as ENOENT below. */
|
|
ipa_krb5_free_key_data(res_key_data, result);
|
|
break;
|
|
}
|
|
|
|
entry->key_data = res_key_data;
|
|
entry->n_key_data = result;
|
|
if (mkvno) {
|
|
krb5_int16 kvno16le = htole16((krb5_int16)mkvno);
|
|
|
|
kerr = ipadb_set_tl_data(entry, KRB5_TL_MKVNO,
|
|
sizeof(kvno16le),
|
|
(krb5_octet *)&kvno16le);
|
|
if (kerr) {
|
|
goto done;
|
|
}
|
|
}
|
|
case ENOENT:
|
|
break;
|
|
default:
|
|
kerr = KRB5_KDB_INTERNAL_ERROR;
|
|
goto done;
|
|
}
|
|
|
|
ret = ipadb_ldap_attr_to_bool(lcontext, lentry,
|
|
"nsAccountLock", &resbool);
|
|
if ((ret == 0 && resbool == true) || (ret != 0 && ret != ENOENT)) {
|
|
entry->attributes |= KRB5_KDB_DISALLOW_ALL_TIX;
|
|
}
|
|
|
|
ied = calloc(1, sizeof(struct ipadb_e_data));
|
|
if (!ied) {
|
|
kerr = ENOMEM;
|
|
goto done;
|
|
}
|
|
ied->magic = IPA_E_DATA_MAGIC;
|
|
|
|
entry->e_data = (krb5_octet *)ied;
|
|
|
|
ied->entry_dn = ldap_get_dn(lcontext, lentry);
|
|
if (!ied->entry_dn) {
|
|
kerr = ENOMEM;
|
|
goto done;
|
|
}
|
|
|
|
/* mark this as an ipa_user if it has the posixaccount objectclass */
|
|
ret = ipadb_ldap_attr_has_value(lcontext, lentry,
|
|
"objectClass", "posixAccount");
|
|
if (ret != 0 && ret != ENOENT) {
|
|
kerr = ret;
|
|
goto done;
|
|
}
|
|
if (ret == 0) {
|
|
ied->ipa_user = true;
|
|
ret = ipadb_ldap_attr_to_str(lcontext, lentry,
|
|
"uid", &uidstring);
|
|
if (ret != 0 && ret != ENOENT) {
|
|
kerr = ret;
|
|
goto done;
|
|
}
|
|
ied->user = uidstring;
|
|
}
|
|
|
|
/* check if it has the krbTicketPolicyAux objectclass */
|
|
ret = ipadb_ldap_attr_has_value(lcontext, lentry,
|
|
"objectClass", "krbTicketPolicyAux");
|
|
if (ret != 0 && ret != ENOENT) {
|
|
kerr = ret;
|
|
goto done;
|
|
}
|
|
if (ret == 0) {
|
|
ied->has_tktpolaux = true;
|
|
}
|
|
|
|
ret = ipadb_ldap_attr_to_str(lcontext, lentry,
|
|
"krbPwdPolicyReference", &restring);
|
|
switch (ret) {
|
|
case ENOENT:
|
|
/* use the default policy if ref. is not available */
|
|
ret = asprintf(&restring,
|
|
"cn=global_policy,%s", ipactx->realm_base);
|
|
if (ret == -1) {
|
|
kerr = ENOMEM;
|
|
goto done;
|
|
}
|
|
case 0:
|
|
break;
|
|
default:
|
|
kerr = KRB5_KDB_INTERNAL_ERROR;
|
|
goto done;
|
|
}
|
|
ied->pw_policy_dn = restring;
|
|
|
|
kerr = ipadb_policydn_to_kdam_tl_data(restring, entry);
|
|
if (kerr) goto done;
|
|
|
|
ret = ipadb_ldap_attr_to_strlist(lcontext, lentry,
|
|
"passwordHistory", &restrlist);
|
|
if (ret != 0 && ret != ENOENT) {
|
|
kerr = KRB5_KDB_INTERNAL_ERROR;
|
|
goto done;
|
|
}
|
|
if (ret == 0) {
|
|
ied->pw_history = restrlist;
|
|
}
|
|
|
|
ret = ipadb_ldap_attr_to_krb5_timestamp(lcontext, lentry,
|
|
"krbLastPwdChange", &restime);
|
|
if (ret == 0) {
|
|
krb5_int32 time32le = htole32((krb5_int32)restime);
|
|
|
|
kerr = ipadb_set_tl_data(entry,
|
|
KRB5_TL_LAST_PWD_CHANGE,
|
|
sizeof(time32le),
|
|
(krb5_octet *)&time32le);
|
|
if (kerr) {
|
|
goto done;
|
|
}
|
|
|
|
ied->last_pwd_change = krb5_ts2tt(restime);
|
|
}
|
|
|
|
ret = ipadb_ldap_attr_to_krb5_timestamp(lcontext, lentry,
|
|
"krbLastAdminUnlock", &restime);
|
|
if (ret == 0) {
|
|
krb5_int32 time32le = htole32((krb5_int32)restime);
|
|
|
|
kerr = ipadb_set_tl_data(entry,
|
|
KRB5_TL_LAST_ADMIN_UNLOCK,
|
|
sizeof(time32le),
|
|
(krb5_octet *)&time32le);
|
|
if (kerr) {
|
|
goto done;
|
|
}
|
|
|
|
ied->last_admin_unlock = krb5_ts2tt(restime);
|
|
}
|
|
|
|
ret = ipadb_ldap_attr_to_strlist(lcontext, lentry,
|
|
IPA_KRB_AUTHZ_DATA_ATTR, &authz_data_list);
|
|
if (ret != 0 && ret != ENOENT) {
|
|
kerr = KRB5_KDB_INTERNAL_ERROR;
|
|
goto done;
|
|
}
|
|
if (ret == 0) {
|
|
ied->authz_data = authz_data_list;
|
|
}
|
|
|
|
ied->user_auth = ua;
|
|
|
|
/* If enabled, set the otp user string, enabling otp. */
|
|
if (ua & IPADB_USER_AUTH_OTP) {
|
|
kerr = ipadb_set_tl_data(entry, KRB5_TL_STRING_ATTRS,
|
|
sizeof(otp_string), otp_string);
|
|
if (kerr)
|
|
goto done;
|
|
} else if (ua & IPADB_USER_AUTH_RADIUS) {
|
|
kerr = ipadb_set_tl_data(entry, KRB5_TL_STRING_ATTRS,
|
|
sizeof(rad_string), rad_string);
|
|
if (kerr)
|
|
goto done;
|
|
}
|
|
|
|
if (ua & ~IPADB_USER_AUTH_NONE) {
|
|
ipadb_parse_authind_policies(kcontext, lcontext, lentry, entry, ua);
|
|
}
|
|
|
|
/* Add SID if it is associated with the principal account */
|
|
ied->has_sid = false;
|
|
ied->sid = NULL;
|
|
ret = ipadb_ldap_attr_to_str(ipactx->lcontext, lentry,
|
|
"ipaNTSecurityIdentifier", &princ_sid);
|
|
if (ret == 0 && princ_sid != NULL) {
|
|
alloc_sid(&ied->sid);
|
|
if (ied->sid == NULL) {
|
|
kerr = KRB5_KDB_INTERNAL_ERROR;
|
|
free(princ_sid);
|
|
goto done;
|
|
}
|
|
ret = ipadb_string_to_sid(princ_sid, ied->sid);
|
|
free(princ_sid);
|
|
if (ret != 0) {
|
|
kerr = ret;
|
|
goto done;
|
|
}
|
|
ied->has_sid = true;
|
|
}
|
|
|
|
kerr = 0;
|
|
|
|
done:
|
|
if (kerr) {
|
|
ipadb_free_principal(kcontext, entry);
|
|
entry = NULL;
|
|
}
|
|
*kentry = entry;
|
|
return kerr;
|
|
}
|
|
|
|
krb5_error_code
|
|
ipadb_fetch_principals_with_extra_filter(struct ipadb_context *ipactx,
|
|
unsigned int flags,
|
|
const char *principal,
|
|
const char *filter,
|
|
LDAPMessage **result)
|
|
{
|
|
krb5_error_code kerr;
|
|
char *src_filter = NULL, *esc_original_princ = NULL;
|
|
int ret;
|
|
int len = 0;
|
|
|
|
if (!ipactx->lcontext) {
|
|
ret = ipadb_get_connection(ipactx);
|
|
if (ret != 0) {
|
|
kerr = KRB5_KDB_SERVER_INTERNAL_ERR;
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
/* Escape filter but do not touch '*' as this function accepts
|
|
* wildcards in names. */
|
|
esc_original_princ = ipadb_filter_escape(principal, false);
|
|
if (!esc_original_princ) {
|
|
kerr = KRB5_KDB_INTERNAL_ERROR;
|
|
goto done;
|
|
}
|
|
|
|
len = strlen(esc_original_princ);
|
|
|
|
/* Starting in DAL 8.0, aliases are always okay. */
|
|
#ifdef KRB5_KDB_FLAG_ALIAS_OK
|
|
if (!(flags & KRB5_KDB_FLAG_ALIAS_OK)) {
|
|
ret = asprintf(&src_filter, PRINC_SEARCH_FILTER_EXTRA,
|
|
esc_original_princ,
|
|
filter ? filter : "");
|
|
} else
|
|
#endif
|
|
{
|
|
/* In case we've got a principal name as '*', we don't need to specify
|
|
* the principal itself, use pre-defined filter for a wild-card search.
|
|
*/
|
|
if ((len == 1) && (esc_original_princ[0] == '*')) {
|
|
ret = asprintf(&src_filter, PRINC_TGS_SEARCH_FILTER_WILD_EXTRA,
|
|
filter ? filter : "");
|
|
} else {
|
|
ret = asprintf(&src_filter, PRINC_TGS_SEARCH_FILTER_EXTRA,
|
|
esc_original_princ, esc_original_princ,
|
|
filter ? filter : "");
|
|
}
|
|
}
|
|
|
|
if (ret == -1) {
|
|
kerr = KRB5_KDB_INTERNAL_ERROR;
|
|
goto done;
|
|
}
|
|
|
|
kerr = ipadb_simple_search(ipactx, ipactx->base, LDAP_SCOPE_SUBTREE,
|
|
src_filter, std_principal_attrs, result);
|
|
done:
|
|
free(src_filter);
|
|
free(esc_original_princ);
|
|
return kerr;
|
|
}
|
|
|
|
static krb5_error_code ipadb_fetch_principals(struct ipadb_context *ipactx,
|
|
unsigned int flags,
|
|
char *principal,
|
|
LDAPMessage **result)
|
|
{
|
|
return ipadb_fetch_principals_with_extra_filter(ipactx, flags, principal,
|
|
NULL, result);
|
|
}
|
|
|
|
krb5_error_code ipadb_find_principal(krb5_context kcontext,
|
|
unsigned int flags,
|
|
LDAPMessage *res,
|
|
char **principal,
|
|
LDAPMessage **entry)
|
|
{
|
|
struct ipadb_context *ipactx;
|
|
bool found = false;
|
|
LDAPMessage *le = NULL;
|
|
struct berval **vals = NULL;
|
|
int result;
|
|
krb5_error_code ret;
|
|
size_t princ_len = 0;
|
|
|
|
ipactx = ipadb_get_context(kcontext);
|
|
if (!ipactx) {
|
|
ret = KRB5_KDB_DBNOTINITED;
|
|
goto done;
|
|
}
|
|
|
|
princ_len = strlen(*principal);
|
|
for (le = ldap_first_entry(ipactx->lcontext, res); le != NULL;
|
|
le = ldap_next_entry(ipactx->lcontext, le)) {
|
|
vals = ldap_get_values_len(ipactx->lcontext, le, "krbprincipalname");
|
|
if (vals == NULL)
|
|
continue;
|
|
|
|
/* We need to check for a strict match as a '*' in the name may have
|
|
* caused the ldap server to return multiple entries. */
|
|
for (int i = 0; vals[i]; i++) {
|
|
#ifdef KRB5_KDB_FLAG_ALIAS_OK
|
|
if ((flags & KRB5_KDB_FLAG_ALIAS_OK) == 0) {
|
|
found = ((vals[i]->bv_len == princ_len) &&
|
|
strncmp(vals[i]->bv_val, *principal, vals[i]->bv_len) == 0);
|
|
if (found)
|
|
break;
|
|
|
|
continue;
|
|
}
|
|
#endif
|
|
|
|
/* The KDC will accept aliases when doing TGT lookup
|
|
* (ref_tgt_again in do_tgs_req.c), so use case-insensitive
|
|
* comparison. */
|
|
if (ulc_casecmp(vals[i]->bv_val, vals[i]->bv_len, *principal,
|
|
princ_len, NULL, NULL, &result) != 0) {
|
|
ret = KRB5_KDB_INTERNAL_ERROR;
|
|
goto done;
|
|
}
|
|
if (result != 0)
|
|
continue;
|
|
|
|
/* Fix case on the incoming principal to ensure that a valid
|
|
* name/alias is returned even if krbCanonicalName is not
|
|
* present. */
|
|
free(*principal);
|
|
*principal = strndup(vals[i]->bv_val, vals[i]->bv_len);
|
|
if (!*principal) {
|
|
ret = KRB5_KDB_INTERNAL_ERROR;
|
|
goto done;
|
|
}
|
|
princ_len = strlen(*principal);
|
|
found = true;
|
|
break;
|
|
}
|
|
|
|
ldap_value_free_len(vals);
|
|
vals = NULL;
|
|
if (!found) {
|
|
continue;
|
|
}
|
|
|
|
/* We need to check if this is the canonical name. */
|
|
vals = ldap_get_values_len(ipactx->lcontext, le, "krbcanonicalname");
|
|
if (vals == NULL)
|
|
break;
|
|
|
|
#ifdef KRB5_KDB_FLAG_ALIAS_OK
|
|
/* If aliases aren't accepted by the KDC, use case-sensitive
|
|
* comparison. */
|
|
if ((flags & KRB5_KDB_FLAG_ALIAS_OK) == 0) {
|
|
found = ((vals[0]->bv_len == strlen(*principal)) &&
|
|
strncmp(vals[0]->bv_val, *principal, vals[0]->bv_len) == 0);
|
|
if (!found) {
|
|
ldap_value_free_len(vals);
|
|
vals = NULL;
|
|
continue;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
free(*principal);
|
|
*principal = strndup(vals[0]->bv_val, vals[0]->bv_len);
|
|
if (!*principal) {
|
|
ret = KRB5_KDB_INTERNAL_ERROR;
|
|
goto done;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (!found || !le) {
|
|
ret = KRB5_KDB_NOENTRY;
|
|
goto done;
|
|
}
|
|
|
|
ret = 0;
|
|
*entry = le;
|
|
done:
|
|
if (vals)
|
|
ldap_value_free_len(vals);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static krb5_flags maybe_require_preauth(struct ipadb_context *ipactx,
|
|
krb5_db_entry *entry)
|
|
{
|
|
const struct ipadb_global_config *config;
|
|
struct ipadb_e_data *ied;
|
|
|
|
config = ipadb_get_global_config(ipactx);
|
|
if (config && config->disable_preauth_for_spns) {
|
|
ied = (struct ipadb_e_data *)entry->e_data;
|
|
if (ied && ied->ipa_user != true) {
|
|
/* not a user, assume SPN */
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* By default require preauth for all principals */
|
|
return KRB5_KDB_REQUIRES_PRE_AUTH;
|
|
}
|
|
|
|
static krb5_error_code ipadb_fetch_tktpolicy(krb5_context kcontext,
|
|
LDAPMessage *lentry,
|
|
krb5_db_entry *entry,
|
|
uint32_t polmask)
|
|
{
|
|
struct ipadb_context *ipactx;
|
|
krb5_error_code kerr;
|
|
char *policy_dn = NULL;
|
|
LDAPMessage *res = NULL;
|
|
LDAPMessage *first;
|
|
int result;
|
|
int ret;
|
|
|
|
ipactx = ipadb_get_context(kcontext);
|
|
if (!ipactx) {
|
|
return KRB5_KDB_DBNOTINITED;
|
|
}
|
|
|
|
ret = ipadb_ldap_attr_to_str(ipactx->lcontext, lentry,
|
|
"krbticketpolicyreference", &policy_dn);
|
|
switch (ret) {
|
|
case 0:
|
|
break;
|
|
case ENOENT:
|
|
ret = asprintf(&policy_dn, "cn=%s,cn=kerberos,%s",
|
|
ipactx->realm, ipactx->base);
|
|
if (ret == -1) {
|
|
kerr = ENOMEM;
|
|
goto done;
|
|
}
|
|
break;
|
|
default:
|
|
kerr = ret;
|
|
goto done;
|
|
}
|
|
|
|
kerr = ipadb_simple_search(ipactx,
|
|
policy_dn, LDAP_SCOPE_BASE,
|
|
"(objectclass=krbticketpolicyaux)",
|
|
std_tktpolicy_attrs,
|
|
&res);
|
|
if (kerr == 0) {
|
|
first = ldap_first_entry(ipactx->lcontext, res);
|
|
if (!first) {
|
|
kerr = KRB5_KDB_NOENTRY;
|
|
} else {
|
|
if (polmask & MAXTKTLIFE_BIT) {
|
|
ret = ipadb_ldap_attr_to_int(ipactx->lcontext, first,
|
|
"krbmaxticketlife", &result);
|
|
if (ret == 0) {
|
|
entry->max_life = result;
|
|
} else {
|
|
entry->max_life = 86400;
|
|
}
|
|
}
|
|
if (polmask & MAXRENEWABLEAGE_BIT) {
|
|
ret = ipadb_ldap_attr_to_int(ipactx->lcontext, first,
|
|
"krbmaxrenewableage", &result);
|
|
if (ret == 0) {
|
|
entry->max_renewable_life = result;
|
|
} else {
|
|
entry->max_renewable_life = 604800;
|
|
}
|
|
}
|
|
if (polmask & TKTFLAGS_BIT) {
|
|
ret = ipadb_ldap_attr_to_int(ipactx->lcontext, first,
|
|
"krbticketflags", &result);
|
|
if (ret == 0) {
|
|
entry->attributes |= result;
|
|
} else {
|
|
entry->attributes |= maybe_require_preauth(ipactx, entry);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (kerr == KRB5_KDB_NOENTRY) {
|
|
/* No policy at all ??
|
|
* set hardcoded default policy for now */
|
|
if (polmask & MAXTKTLIFE_BIT) {
|
|
entry->max_life = 86400;
|
|
}
|
|
if (polmask & MAXRENEWABLEAGE_BIT) {
|
|
entry->max_renewable_life = 604800;
|
|
}
|
|
if (polmask & TKTFLAGS_BIT) {
|
|
entry->attributes |= maybe_require_preauth(ipactx, entry);
|
|
}
|
|
|
|
kerr = 0;
|
|
}
|
|
|
|
done:
|
|
ldap_msgfree(res);
|
|
free(policy_dn);
|
|
return kerr;
|
|
}
|
|
|
|
static krb5_boolean is_request_for_us(krb5_context kcontext,
|
|
krb5_principal local_tgs,
|
|
krb5_const_principal search_for)
|
|
{
|
|
krb5_boolean for_us;
|
|
|
|
for_us = krb5_realm_compare(kcontext, local_tgs, search_for) ||
|
|
krb5_principal_compare_any_realm(kcontext,
|
|
local_tgs, search_for);
|
|
return for_us;
|
|
}
|
|
|
|
static krb5_error_code dbget_princ(krb5_context kcontext,
|
|
struct ipadb_context *ipactx,
|
|
krb5_const_principal search_for,
|
|
unsigned int flags,
|
|
krb5_db_entry **entry)
|
|
{
|
|
krb5_error_code kerr;
|
|
char *principal = NULL;
|
|
LDAPMessage *res = NULL;
|
|
LDAPMessage *lentry;
|
|
uint32_t pol;
|
|
|
|
|
|
if ((flags & KRB5_KDB_FLAG_CLIENT_REFERRALS_ONLY) != 0 &&
|
|
(flags & KRB5_KDB_FLAG_CANONICALIZE) != 0) {
|
|
|
|
/* AS_REQ with canonicalization*/
|
|
krb5_principal norm_princ = NULL;
|
|
|
|
/* unparse the Kerberos principal without (our) outer realm. */
|
|
kerr = krb5_unparse_name_flags(kcontext, search_for,
|
|
KRB5_PRINCIPAL_UNPARSE_NO_REALM |
|
|
KRB5_PRINCIPAL_UNPARSE_DISPLAY,
|
|
&principal);
|
|
if (kerr != 0) {
|
|
goto done;
|
|
}
|
|
|
|
/* Re-parse the principal to normalize it. Innner realm becomes
|
|
* the realm if present. If no inner realm, our default realm
|
|
* will be used instead (as it was before). */
|
|
kerr = krb5_parse_name(kcontext, principal, &norm_princ);
|
|
if (kerr != 0) {
|
|
goto done;
|
|
}
|
|
/* Unparse without escaping '@' and '/' because we are going to use them
|
|
* in LDAP filters where escaping character '\' will be escaped and the
|
|
* result will never match. */
|
|
kerr = krb5_unparse_name_flags(kcontext, norm_princ,
|
|
KRB5_PRINCIPAL_UNPARSE_DISPLAY, &principal);
|
|
krb5_free_principal(kcontext, norm_princ);
|
|
} else {
|
|
/* Unparse without escaping '@' and '/' because we are going to use them
|
|
* in LDAP filters where escaping character '\' will be escaped and the
|
|
* result will never match. */
|
|
kerr = krb5_unparse_name_flags(kcontext, search_for,
|
|
KRB5_PRINCIPAL_UNPARSE_DISPLAY, &principal);
|
|
}
|
|
|
|
if (kerr != 0) {
|
|
goto done;
|
|
}
|
|
|
|
kerr = ipadb_fetch_principals(ipactx, flags, principal, &res);
|
|
if (kerr != 0) {
|
|
goto done;
|
|
}
|
|
|
|
kerr = ipadb_find_principal(kcontext, flags, res, &principal, &lentry);
|
|
if (kerr != 0) {
|
|
goto done;
|
|
}
|
|
|
|
kerr = ipadb_parse_ldap_entry(kcontext, principal, lentry, entry, &pol);
|
|
if (kerr != 0) {
|
|
goto done;
|
|
}
|
|
|
|
if (pol) {
|
|
kerr = ipadb_fetch_tktpolicy(kcontext, lentry, *entry, pol);
|
|
if (kerr != 0) {
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
done:
|
|
ldap_msgfree(res);
|
|
krb5_free_unparsed_name(kcontext, principal);
|
|
|
|
return kerr;
|
|
}
|
|
|
|
static krb5_error_code dbget_alias(krb5_context kcontext,
|
|
struct ipadb_context *ipactx,
|
|
krb5_const_principal search_for,
|
|
unsigned int flags,
|
|
krb5_db_entry **entry)
|
|
{
|
|
krb5_error_code kerr = 0;
|
|
char *principal = NULL;
|
|
krb5_principal norm_princ = NULL;
|
|
char *trusted_realm = NULL;
|
|
krb5_db_entry *kentry = NULL;
|
|
krb5_data *realm;
|
|
|
|
/* TODO: also support hostbased aliases */
|
|
|
|
/* Enterprise principal name type is for potential aliases or principals
|
|
* from trusted realms. The logic below only applies to this type */
|
|
if (krb5_princ_type(kcontext, search_for) != KRB5_NT_ENTERPRISE_PRINCIPAL) {
|
|
return KRB5_KDB_NOENTRY;
|
|
}
|
|
|
|
/* enterprise principal can only have single component in the name
|
|
* according to RFC6806 section 5. */
|
|
if (krb5_princ_size(kcontext, search_for) != 1) {
|
|
return KRB5_KDB_NOENTRY;
|
|
}
|
|
|
|
/* unparse the Kerberos principal without (our) outer realm. */
|
|
kerr = krb5_unparse_name_flags(kcontext, search_for,
|
|
KRB5_PRINCIPAL_UNPARSE_NO_REALM |
|
|
KRB5_PRINCIPAL_UNPARSE_DISPLAY,
|
|
&principal);
|
|
if (kerr != 0) {
|
|
goto done;
|
|
}
|
|
|
|
/* Re-parse the principal to normalize it. Innner realm becomes
|
|
* the realm if present. If no inner realm, our default realm
|
|
* will be used instead (as it was before). */
|
|
kerr = krb5_parse_name(kcontext, principal, &norm_princ);
|
|
if (kerr != 0) {
|
|
goto done;
|
|
}
|
|
|
|
if (krb5_realm_compare(kcontext, ipactx->local_tgs, norm_princ)) {
|
|
/* In realm alias, try to retrieve it and let the caller handle it. */
|
|
kerr = dbget_princ(kcontext, ipactx, norm_princ, flags, entry);
|
|
goto done;
|
|
}
|
|
|
|
/* The request is out of realm starting from here */
|
|
|
|
/*
|
|
* Per RFC6806 section 7 and 8, the canonicalize flag is required for
|
|
* both client and server referrals. But it is more useful to ignore it
|
|
* like Windows KDC does for client referrals.
|
|
*/
|
|
if (((flags & KRB5_KDB_FLAG_CANONICALIZE) == 0) &&
|
|
((flags & KRB5_KDB_FLAG_CLIENT_REFERRALS_ONLY) == 0)) {
|
|
kerr = KRB5_KDB_NOENTRY;
|
|
goto done;
|
|
}
|
|
|
|
/* Determine the trusted realm to refer to. We don't need the principal
|
|
* itself, only its realm */
|
|
realm = krb5_princ_realm(kcontext, norm_princ);
|
|
kerr = ipadb_is_princ_from_trusted_realm(kcontext,
|
|
realm->data,
|
|
realm->length,
|
|
&trusted_realm);
|
|
if (kerr == KRB5_KDB_NOENTRY) {
|
|
/* If no trusted realm found, refresh trusted domain data and try again
|
|
* because it might be a freshly added trust to AD */
|
|
kerr = ipadb_reinit_mspac(ipactx, false);
|
|
if (kerr != 0) {
|
|
kerr = KRB5_KDB_NOENTRY;
|
|
goto done;
|
|
}
|
|
kerr = ipadb_is_princ_from_trusted_realm(kcontext,
|
|
realm->data,
|
|
realm->length,
|
|
&trusted_realm);
|
|
}
|
|
|
|
if (kerr != 0) {
|
|
goto done;
|
|
}
|
|
|
|
/* This is a known trusted realm. Issue a referral depending on whether this
|
|
* is client or server referral request */
|
|
if (flags & KRB5_KDB_FLAG_CLIENT_REFERRALS_ONLY) {
|
|
/* client referral out of realm, set next realm. */
|
|
kerr = krb5_set_principal_realm(kcontext, norm_princ, trusted_realm);
|
|
if (kerr != 0) {
|
|
goto done;
|
|
}
|
|
kentry = calloc(1, sizeof(krb5_db_entry));
|
|
if (!kentry) {
|
|
kerr = ENOMEM;
|
|
goto done;
|
|
}
|
|
|
|
kentry->princ = norm_princ;
|
|
norm_princ = NULL;
|
|
*entry = kentry;
|
|
|
|
goto done;
|
|
}
|
|
|
|
if (flags & KRB5_KDB_FLAG_INCLUDE_PAC) {
|
|
/* TGS request where KDC wants to generate PAC
|
|
* but the principal is out of our realm */
|
|
kerr = KRB5_KDB_NOENTRY;
|
|
goto done;
|
|
}
|
|
|
|
/* server referrals: lookup krbtgt/next_realm@our_realm */
|
|
|
|
krb5_free_principal(kcontext, norm_princ);
|
|
norm_princ = NULL;
|
|
kerr = krb5_build_principal_ext(kcontext, &norm_princ,
|
|
strlen(ipactx->realm),
|
|
ipactx->realm,
|
|
KRB5_TGS_NAME_SIZE,
|
|
KRB5_TGS_NAME,
|
|
strlen(trusted_realm),
|
|
trusted_realm, 0);
|
|
if (kerr != 0) {
|
|
goto done;
|
|
}
|
|
|
|
kerr = dbget_princ(kcontext, ipactx, norm_princ, flags, entry);
|
|
|
|
done:
|
|
free(trusted_realm);
|
|
krb5_free_principal(kcontext, norm_princ);
|
|
krb5_free_unparsed_name(kcontext, principal);
|
|
|
|
return kerr;
|
|
}
|
|
|
|
/* TODO: handle case where main object and krbprincipal data are not
|
|
* the same object but linked objects ?
|
|
* (by way of krbprincipalaux being in a separate object from krbprincipal).
|
|
* Currently we only support objcts with both objectclasses present at the
|
|
* same time. */
|
|
|
|
krb5_error_code ipadb_get_principal(krb5_context kcontext,
|
|
krb5_const_principal search_for,
|
|
unsigned int flags,
|
|
krb5_db_entry **entry)
|
|
{
|
|
struct ipadb_context *ipactx;
|
|
krb5_error_code kerr;
|
|
|
|
*entry = NULL;
|
|
|
|
ipactx = ipadb_get_context(kcontext);
|
|
if (!ipactx) {
|
|
return KRB5_KDB_DBNOTINITED;
|
|
}
|
|
|
|
if (!is_request_for_us(kcontext, ipactx->local_tgs, search_for)) {
|
|
return KRB5_KDB_NOENTRY;
|
|
}
|
|
|
|
/* Lookup local names and aliases first. */
|
|
kerr = dbget_princ(kcontext, ipactx, search_for, flags, entry);
|
|
if (kerr != KRB5_KDB_NOENTRY) {
|
|
return kerr;
|
|
}
|
|
|
|
return dbget_alias(kcontext, ipactx, search_for, flags, entry);
|
|
}
|
|
|
|
void ipadb_free_principal_e_data(krb5_context kcontext, krb5_octet *e_data)
|
|
{
|
|
struct ipadb_e_data *ied;
|
|
int i;
|
|
|
|
ied = (struct ipadb_e_data *)e_data;
|
|
if (ied->magic == IPA_E_DATA_MAGIC) {
|
|
ldap_memfree(ied->entry_dn);
|
|
free(ied->passwd);
|
|
free(ied->user);
|
|
free(ied->pw_policy_dn);
|
|
for (i = 0; ied->pw_history && ied->pw_history[i]; i++) {
|
|
free(ied->pw_history[i]);
|
|
}
|
|
free(ied->pw_history);
|
|
for (i = 0; ied->authz_data && ied->authz_data[i]; i++) {
|
|
free(ied->authz_data[i]);
|
|
}
|
|
free(ied->authz_data);
|
|
free(ied->pol);
|
|
free_sid(&ied->sid);
|
|
free(ied);
|
|
}
|
|
}
|
|
|
|
void ipadb_free_principal(krb5_context kcontext, krb5_db_entry *entry)
|
|
{
|
|
krb5_tl_data *prev, *next;
|
|
|
|
if (entry) {
|
|
krb5_free_principal(kcontext, entry->princ);
|
|
prev = entry->tl_data;
|
|
while(prev) {
|
|
next = prev->tl_data_next;
|
|
free(prev->tl_data_contents);
|
|
free(prev);
|
|
prev = next;
|
|
}
|
|
ipa_krb5_free_key_data(entry->key_data, entry->n_key_data);
|
|
|
|
if (entry->e_data) {
|
|
ipadb_free_principal_e_data(kcontext, entry->e_data);
|
|
}
|
|
|
|
free(entry);
|
|
}
|
|
}
|
|
|
|
static krb5_error_code ipadb_get_tl_data(krb5_db_entry *entry,
|
|
krb5_int16 type,
|
|
krb5_ui_2 length,
|
|
krb5_octet *data)
|
|
{
|
|
krb5_tl_data *td;
|
|
|
|
for (td = entry->tl_data; td; td = td->tl_data_next) {
|
|
if (td->tl_data_type == type) {
|
|
break;
|
|
}
|
|
}
|
|
if (!td) {
|
|
return ENOENT;
|
|
}
|
|
|
|
if (td->tl_data_length != length) {
|
|
return EINVAL;
|
|
}
|
|
|
|
memcpy(data, td->tl_data_contents, length);
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct ipadb_mods {
|
|
LDAPMod **mods;
|
|
int alloc_size;
|
|
int tip;
|
|
};
|
|
|
|
static int new_ipadb_mods(struct ipadb_mods **imods)
|
|
{
|
|
struct ipadb_mods *r;
|
|
|
|
r = malloc(sizeof(struct ipadb_mods));
|
|
if (!r) {
|
|
return ENOMEM;
|
|
}
|
|
|
|
/* alloc the average space for a full change of all ldap attrinbutes */
|
|
r->alloc_size = 15;
|
|
r->mods = calloc(r->alloc_size, sizeof(LDAPMod *));
|
|
if (!r->mods) {
|
|
free(r);
|
|
return ENOMEM;
|
|
}
|
|
r->tip = 0;
|
|
|
|
*imods = r;
|
|
return 0;
|
|
}
|
|
|
|
static void ipadb_mods_free(struct ipadb_mods *imods)
|
|
{
|
|
if (imods == NULL) {
|
|
return;
|
|
}
|
|
|
|
ldap_mods_free(imods->mods, 1);
|
|
free(imods);
|
|
}
|
|
|
|
static krb5_error_code ipadb_mods_new(struct ipadb_mods *imods,
|
|
LDAPMod **slot)
|
|
{
|
|
LDAPMod **lmods = NULL;
|
|
LDAPMod *m;
|
|
int n;
|
|
|
|
lmods = imods->mods;
|
|
for (n = imods->tip; n < imods->alloc_size && lmods[n] != NULL; n++) {
|
|
/* find empty slot */ ;
|
|
}
|
|
|
|
if (n + 1 > imods->alloc_size) {
|
|
/* need to increase size */
|
|
lmods = realloc(imods->mods, (n * 2) * sizeof(LDAPMod *));
|
|
if (!lmods) {
|
|
return ENOMEM;
|
|
}
|
|
imods->mods = lmods;
|
|
imods->alloc_size = n * 2;
|
|
memset(&lmods[n + 1], 0,
|
|
(imods->alloc_size - n - 1) * sizeof(LDAPMod *));
|
|
}
|
|
|
|
m = calloc(1, sizeof(LDAPMod));
|
|
if (!m) {
|
|
return ENOMEM;
|
|
}
|
|
imods->tip = n;
|
|
*slot = imods->mods[n] = m;
|
|
return 0;
|
|
}
|
|
|
|
static void ipadb_mods_free_tip(struct ipadb_mods *imods)
|
|
{
|
|
LDAPMod *m;
|
|
int i;
|
|
|
|
if (imods->alloc_size == 0) {
|
|
return;
|
|
}
|
|
|
|
m = imods->mods[imods->tip];
|
|
|
|
if (!m) {
|
|
return;
|
|
}
|
|
|
|
free(m->mod_type);
|
|
if (m->mod_values) {
|
|
for (i = 0; m->mod_values[i]; i++) {
|
|
free(m->mod_values[i]);
|
|
}
|
|
}
|
|
free(m->mod_values);
|
|
free(m);
|
|
|
|
imods->mods[imods->tip] = NULL;
|
|
imods->tip--;
|
|
}
|
|
|
|
static krb5_error_code ipadb_get_ldap_mod_str(struct ipadb_mods *imods,
|
|
char *attribute, char *value,
|
|
int mod_op)
|
|
{
|
|
krb5_error_code kerr;
|
|
LDAPMod *m = NULL;
|
|
|
|
kerr = ipadb_mods_new(imods, &m);
|
|
if (kerr) {
|
|
return kerr;
|
|
}
|
|
|
|
m->mod_op = mod_op;
|
|
m->mod_type = strdup(attribute);
|
|
if (!m->mod_type) {
|
|
kerr = ENOMEM;
|
|
goto done;
|
|
}
|
|
m->mod_values = calloc(2, sizeof(char *));
|
|
if (!m->mod_values) {
|
|
kerr = ENOMEM;
|
|
goto done;
|
|
}
|
|
m->mod_values[0] = strdup(value);
|
|
if (!m->mod_values[0]) {
|
|
kerr = ENOMEM;
|
|
goto done;
|
|
}
|
|
|
|
kerr = 0;
|
|
|
|
done:
|
|
if (kerr) {
|
|
ipadb_mods_free_tip(imods);
|
|
}
|
|
return kerr;
|
|
}
|
|
|
|
static krb5_error_code ipadb_get_ldap_mod_int(struct ipadb_mods *imods,
|
|
char *attribute, int value,
|
|
int mod_op)
|
|
{
|
|
krb5_error_code kerr;
|
|
char *v = NULL;
|
|
int ret;
|
|
|
|
ret = asprintf(&v, "%d", value);
|
|
if (ret == -1) {
|
|
kerr = KRB5_KDB_INTERNAL_ERROR;
|
|
goto done;
|
|
}
|
|
|
|
kerr = ipadb_get_ldap_mod_str(imods, attribute, v, mod_op);
|
|
|
|
done:
|
|
free(v);
|
|
return kerr;
|
|
}
|
|
|
|
static krb5_error_code ipadb_get_ldap_mod_time(struct ipadb_mods *imods,
|
|
char *attribute,
|
|
krb5_timestamp value,
|
|
int mod_op)
|
|
{
|
|
struct tm date, *t;
|
|
time_t timeval;
|
|
char v[20];
|
|
|
|
timeval = krb5_ts2tt(value);
|
|
t = gmtime_r(&timeval, &date);
|
|
if (t == NULL) {
|
|
return EINVAL;
|
|
}
|
|
|
|
strftime(v, 20, "%Y%m%d%H%M%SZ", &date);
|
|
|
|
return ipadb_get_ldap_mod_str(imods, attribute, v, mod_op);
|
|
}
|
|
|
|
static krb5_error_code ipadb_get_ldap_mod_bvalues(struct ipadb_mods *imods,
|
|
char *attribute,
|
|
struct berval **values,
|
|
int num_values,
|
|
int mod_op)
|
|
{
|
|
krb5_error_code kerr;
|
|
LDAPMod *m = NULL;
|
|
int i;
|
|
|
|
if (values == NULL || values[0] == NULL || num_values <= 0) {
|
|
return EINVAL;
|
|
}
|
|
|
|
kerr = ipadb_mods_new(imods, &m);
|
|
if (kerr) {
|
|
return kerr;
|
|
}
|
|
|
|
m->mod_op = mod_op | LDAP_MOD_BVALUES;
|
|
m->mod_type = strdup(attribute);
|
|
if (!m->mod_type) {
|
|
kerr = ENOMEM;
|
|
goto done;
|
|
}
|
|
m->mod_bvalues = calloc(num_values + 1, sizeof(struct berval *));
|
|
if (!m->mod_bvalues) {
|
|
kerr = ENOMEM;
|
|
goto done;
|
|
}
|
|
|
|
for (i = 0; i < num_values; i++) {
|
|
m->mod_bvalues[i] = values[i];
|
|
}
|
|
|
|
kerr = 0;
|
|
|
|
done:
|
|
if (kerr) {
|
|
/* we need to free bvalues manually here otherwise
|
|
* ipadb_mods_free_tip will free contents which we
|
|
* did not allocate here */
|
|
free(m->mod_bvalues);
|
|
m->mod_bvalues = NULL;
|
|
ipadb_mods_free_tip(imods);
|
|
}
|
|
return kerr;
|
|
}
|
|
|
|
static krb5_error_code ipadb_get_ldap_mod_extra_data(struct ipadb_mods *imods,
|
|
krb5_tl_data *tl_data,
|
|
int mod_op)
|
|
{
|
|
krb5_error_code kerr;
|
|
krb5_tl_data *data;
|
|
struct berval **bvs = NULL;
|
|
krb5_int16 be_type;
|
|
int n, i;
|
|
|
|
for (n = 0, data = tl_data; data; data = data->tl_data_next) {
|
|
if (data->tl_data_type == KRB5_TL_LAST_PWD_CHANGE ||
|
|
data->tl_data_type == KRB5_TL_KADM_DATA ||
|
|
data->tl_data_type == KRB5_TL_DB_ARGS ||
|
|
data->tl_data_type == KRB5_TL_MKVNO ||
|
|
data->tl_data_type == KRB5_TL_LAST_ADMIN_UNLOCK) {
|
|
continue;
|
|
}
|
|
n++;
|
|
}
|
|
|
|
if (n == 0) {
|
|
return ENOENT;
|
|
}
|
|
|
|
bvs = calloc(n + 1, sizeof(struct berval *));
|
|
if (!bvs) {
|
|
kerr = ENOMEM;
|
|
goto done;
|
|
}
|
|
|
|
for (i = 0, data = tl_data; data; data = data->tl_data_next) {
|
|
|
|
if (data->tl_data_type == KRB5_TL_LAST_PWD_CHANGE ||
|
|
data->tl_data_type == KRB5_TL_KADM_DATA ||
|
|
data->tl_data_type == KRB5_TL_DB_ARGS ||
|
|
data->tl_data_type == KRB5_TL_MKVNO ||
|
|
data->tl_data_type == KRB5_TL_LAST_ADMIN_UNLOCK) {
|
|
continue;
|
|
}
|
|
|
|
be_type = htons(data->tl_data_type);
|
|
|
|
bvs[i] = calloc(1, sizeof(struct berval));
|
|
if (!bvs[i]) {
|
|
kerr = ENOMEM;
|
|
goto done;
|
|
}
|
|
|
|
bvs[i]->bv_len = data->tl_data_length + 2;
|
|
bvs[i]->bv_val = malloc(bvs[i]->bv_len);
|
|
if (!bvs[i]->bv_val) {
|
|
kerr = ENOMEM;
|
|
goto done;
|
|
}
|
|
memcpy(bvs[i]->bv_val, &be_type, 2);
|
|
memcpy(&(bvs[i]->bv_val[2]), data->tl_data_contents, data->tl_data_length);
|
|
|
|
i++;
|
|
|
|
if (i > n) {
|
|
kerr = KRB5_KDB_INTERNAL_ERROR;
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
kerr = ipadb_get_ldap_mod_bvalues(imods, "krbExtraData", bvs, i, mod_op);
|
|
|
|
done:
|
|
if (kerr) {
|
|
for (i = 0; bvs && bvs[i]; i++) {
|
|
free(bvs[i]->bv_val);
|
|
free(bvs[i]);
|
|
}
|
|
}
|
|
free(bvs);
|
|
return kerr;
|
|
}
|
|
|
|
static krb5_error_code ipadb_get_mkvno_from_tl_data(krb5_tl_data *tl_data,
|
|
int *mkvno)
|
|
{
|
|
krb5_tl_data *data;
|
|
int master_kvno = 0;
|
|
krb5_int16 tmp;
|
|
|
|
for (data = tl_data; data; data = data->tl_data_next) {
|
|
|
|
if (data->tl_data_type != KRB5_TL_MKVNO) {
|
|
continue;
|
|
}
|
|
|
|
if (data->tl_data_length != 2) {
|
|
return KRB5_KDB_TRUNCATED_RECORD;
|
|
}
|
|
|
|
memcpy(&tmp, data->tl_data_contents, 2);
|
|
master_kvno = le16toh(tmp);
|
|
|
|
break;
|
|
}
|
|
|
|
if (master_kvno == 0) {
|
|
/* fall back to std mkvno of 1 */
|
|
*mkvno = 1;
|
|
} else {
|
|
*mkvno = master_kvno;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static krb5_error_code ipadb_get_ldap_mod_key_data(struct ipadb_mods *imods,
|
|
krb5_key_data *key_data,
|
|
int n_key_data, int mkvno,
|
|
int mod_op)
|
|
{
|
|
krb5_error_code kerr;
|
|
struct berval *bval = NULL;
|
|
LDAPMod *mod;
|
|
int ret;
|
|
|
|
/* If the key data is empty, remove all keys. */
|
|
if (n_key_data == 0 || key_data == NULL) {
|
|
kerr = ipadb_mods_new(imods, &mod);
|
|
if (kerr != 0)
|
|
return kerr;
|
|
|
|
mod->mod_op = LDAP_MOD_DELETE;
|
|
mod->mod_bvalues = NULL;
|
|
mod->mod_type = strdup("krbPrincipalKey");
|
|
if (mod->mod_type == NULL) {
|
|
ipadb_mods_free_tip(imods);
|
|
return ENOMEM;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
ret = ber_encode_krb5_key_data(key_data, n_key_data, mkvno, &bval);
|
|
if (ret != 0) {
|
|
kerr = ret;
|
|
goto done;
|
|
}
|
|
|
|
kerr = ipadb_get_ldap_mod_bvalues(imods, "krbPrincipalKey",
|
|
&bval, 1, mod_op);
|
|
|
|
done:
|
|
if (kerr) {
|
|
ber_bvfree(bval);
|
|
}
|
|
return kerr;
|
|
}
|
|
|
|
static krb5_error_code ipadb_get_ldap_mod_str_list(struct ipadb_mods *imods,
|
|
char *attrname,
|
|
char **strlist, int len,
|
|
int mod_op)
|
|
{
|
|
krb5_error_code kerr;
|
|
struct berval **bvs = NULL;
|
|
int i;
|
|
|
|
bvs = calloc(len + 1, sizeof(struct berval *));
|
|
if (!bvs) {
|
|
kerr = ENOMEM;
|
|
goto done;
|
|
}
|
|
|
|
for (i = 0; i < len; i++) {
|
|
bvs[i] = calloc(1, sizeof(struct berval));
|
|
if (!bvs[i]) {
|
|
kerr = ENOMEM;
|
|
goto done;
|
|
}
|
|
|
|
bvs[i]->bv_val = strdup(strlist[i]);
|
|
if (!bvs[i]->bv_val) {
|
|
kerr = ENOMEM;
|
|
goto done;
|
|
}
|
|
bvs[i]->bv_len = strlen(strlist[i]) + 1;
|
|
}
|
|
|
|
kerr = ipadb_get_ldap_mod_bvalues(imods, attrname, bvs, len, mod_op);
|
|
|
|
done:
|
|
if (kerr) {
|
|
for (i = 0; bvs && bvs[i]; i++) {
|
|
free(bvs[i]->bv_val);
|
|
free(bvs[i]);
|
|
}
|
|
}
|
|
free(bvs);
|
|
return kerr;
|
|
}
|
|
|
|
static krb5_error_code ipadb_principal_to_mods(krb5_context kcontext,
|
|
struct ipadb_mods *imods,
|
|
char *principal,
|
|
int mod_op)
|
|
{
|
|
krb5_error_code kerr;
|
|
|
|
if (principal == NULL) {
|
|
kerr = EINVAL;
|
|
goto done;
|
|
}
|
|
|
|
kerr = ipadb_get_ldap_mod_str(imods, "krbPrincipalName",
|
|
principal, mod_op);
|
|
if (kerr) {
|
|
goto done;
|
|
}
|
|
kerr = ipadb_get_ldap_mod_str(imods, "krbCanonicalName",
|
|
principal, mod_op);
|
|
if (kerr) {
|
|
goto done;
|
|
}
|
|
|
|
kerr = 0;
|
|
|
|
done:
|
|
return kerr;
|
|
}
|
|
|
|
static krb5_error_code ipadb_get_ldap_mod_auth_ind(krb5_context kcontext,
|
|
struct ipadb_mods *imods,
|
|
krb5_db_entry *entry,
|
|
int mod_op)
|
|
{
|
|
krb5_error_code ret = 0;
|
|
char **strlist = NULL;
|
|
char *ais = NULL;
|
|
char *ai = NULL;
|
|
char *s = NULL;
|
|
size_t ai_size = 0;
|
|
int cnt = 0;
|
|
size_t i = 0;
|
|
|
|
ret = krb5_dbe_get_string(kcontext, entry, "require_auth", &ais);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
if (ais == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
ai_size = strlen(ais) + 1;
|
|
|
|
for (i = 0; i < ai_size; i++) {
|
|
if (ais[i] != ' ') {
|
|
continue;
|
|
}
|
|
if (i > 0 && ais[i - 1] != ' ') {
|
|
cnt++;
|
|
}
|
|
}
|
|
|
|
strlist = calloc(cnt + 2, sizeof(*strlist));
|
|
if (strlist == NULL) {
|
|
free(ais);
|
|
return errno;
|
|
}
|
|
|
|
cnt = 0;
|
|
ai = strtok_r(ais, " ", &s);
|
|
while (ai != NULL) {
|
|
if (ai[0] != '\0') {
|
|
strlist[cnt++] = ai;
|
|
}
|
|
ai = strtok_r(NULL, " ", &s);
|
|
}
|
|
|
|
ret = ipadb_get_ldap_mod_str_list(imods, "krbPrincipalAuthInd",
|
|
strlist, cnt, mod_op);
|
|
|
|
free(ais);
|
|
free(strlist);
|
|
return ret;
|
|
}
|
|
|
|
static krb5_error_code ipadb_entry_to_mods(krb5_context kcontext,
|
|
struct ipadb_mods *imods,
|
|
krb5_db_entry *entry,
|
|
int mod_op)
|
|
{
|
|
krb5_error_code kerr;
|
|
krb5_int32 time32le;
|
|
int mkvno;
|
|
char *req_auth_str = NULL;
|
|
|
|
/* check each mask flag in order */
|
|
|
|
/* KADM5_PRINC_EXPIRE_TIME */
|
|
if (entry->mask & KMASK_PRINC_EXPIRE_TIME) {
|
|
kerr = ipadb_get_ldap_mod_time(imods,
|
|
"krbPrincipalExpiration",
|
|
entry->expiration,
|
|
mod_op);
|
|
if (kerr) {
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
/* KADM5_PW_EXPIRATION */
|
|
if (entry->mask & KMASK_PW_EXPIRATION) {
|
|
kerr = ipadb_get_ldap_mod_time(imods,
|
|
"krbPasswordExpiration",
|
|
entry->pw_expiration,
|
|
mod_op);
|
|
if (entry->pw_expiration == 0) {
|
|
kerr = ipadb_get_ldap_mod_time(imods,
|
|
"krbPasswordExpiration",
|
|
entry->pw_expiration, LDAP_MOD_DELETE);
|
|
}
|
|
if (kerr) {
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
/* KADM5_LAST_PWD_CHANGE */
|
|
/* apparently, at least some versions of kadmin fail to set this flag
|
|
* when they do include a pwd change timestamp in TL_DATA.
|
|
* So for now check if KADM5_KEY_DATA has been set, which kadm5
|
|
* always does on password changes */
|
|
#if KADM5_ACTUALLY_SETS_LAST_PWD_CHANGE
|
|
if (entry->mask & KMASK_LAST_PWD_CHANGE) {
|
|
if (!entry->n_tl_data) {
|
|
kerr = EINVAL;
|
|
goto done;
|
|
}
|
|
|
|
#else
|
|
if (entry->n_tl_data &&
|
|
entry->mask & KMASK_KEY_DATA) {
|
|
#endif
|
|
kerr = ipadb_get_tl_data(entry,
|
|
KRB5_TL_LAST_PWD_CHANGE,
|
|
sizeof(time32le),
|
|
(krb5_octet *)&time32le);
|
|
if (kerr && kerr != ENOENT) {
|
|
goto done;
|
|
}
|
|
if (kerr == 0) {
|
|
kerr = ipadb_get_ldap_mod_time(imods,
|
|
"krbLastPwdChange",
|
|
le32toh(time32le),
|
|
mod_op);
|
|
if (kerr) {
|
|
goto done;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* KADM5_ATTRIBUTES */
|
|
if (entry->mask & KMASK_ATTRIBUTES) {
|
|
/* if the object does not have the krbTicketPolicyAux class
|
|
* we need to add it or this will fail, only for modifications.
|
|
* We always add this objectclass by default when doing an add
|
|
* from scratch. */
|
|
if ((mod_op == LDAP_MOD_REPLACE) && entry->e_data) {
|
|
struct ipadb_e_data *ied;
|
|
|
|
ied = (struct ipadb_e_data *)entry->e_data;
|
|
if (ied->magic != IPA_E_DATA_MAGIC) {
|
|
kerr = EINVAL;
|
|
goto done;
|
|
}
|
|
|
|
if (!ied->has_tktpolaux) {
|
|
kerr = ipadb_get_ldap_mod_str(imods, "objectclass",
|
|
"krbTicketPolicyAux",
|
|
LDAP_MOD_ADD);
|
|
if (kerr) {
|
|
goto done;
|
|
}
|
|
}
|
|
}
|
|
|
|
kerr = ipadb_get_ldap_mod_int(imods,
|
|
"krbTicketFlags",
|
|
(int)entry->attributes,
|
|
mod_op);
|
|
if (kerr) {
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
/* KADM5_MAX_LIFE */
|
|
if (entry->mask & KMASK_MAX_LIFE) {
|
|
kerr = ipadb_get_ldap_mod_int(imods,
|
|
"krbMaxTicketLife",
|
|
(int)entry->max_life,
|
|
mod_op);
|
|
if (kerr) {
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
/* KADM5_MOD_TIME */
|
|
/* KADM5_MOD_NAME */
|
|
/* KADM5_KVNO */
|
|
/* KADM5_MKVNO */
|
|
/* KADM5_AUX_ATTRIBUTES */
|
|
/* KADM5_POLICY */
|
|
/* KADM5_POLICY_CLR */
|
|
|
|
/* version 2 masks */
|
|
/* KADM5_MAX_RLIFE */
|
|
if (entry->mask & KMASK_MAX_RLIFE) {
|
|
kerr = ipadb_get_ldap_mod_int(imods,
|
|
"krbMaxRenewableAge",
|
|
(int)entry->max_renewable_life,
|
|
mod_op);
|
|
if (kerr) {
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
/* KADM5_LAST_SUCCESS */
|
|
if (entry->mask & KMASK_LAST_SUCCESS) {
|
|
kerr = ipadb_get_ldap_mod_time(imods,
|
|
"krbLastSuccessfulAuth",
|
|
entry->last_success,
|
|
mod_op);
|
|
if (kerr) {
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
/* KADM5_LAST_FAILED */
|
|
if (entry->mask & KMASK_LAST_FAILED) {
|
|
kerr = ipadb_get_ldap_mod_time(imods,
|
|
"krbLastFailedAuth",
|
|
entry->last_failed,
|
|
mod_op);
|
|
if (kerr) {
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
/* KADM5_FAIL_AUTH_COUNT */
|
|
if (entry->mask & KMASK_FAIL_AUTH_COUNT) {
|
|
kerr = ipadb_get_ldap_mod_int(imods,
|
|
"krbLoginFailedCount",
|
|
(int)entry->fail_auth_count,
|
|
mod_op);
|
|
if (kerr) {
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
/* KADM5_KEY_DATA */
|
|
if (entry->mask & KMASK_KEY_DATA) {
|
|
/* TODO: password changes should go via change_pwd
|
|
* then we can get clear text and set all needed
|
|
* LDAP attributes */
|
|
|
|
kerr = ipadb_get_mkvno_from_tl_data(entry->tl_data, &mkvno);
|
|
if (kerr) {
|
|
goto done;
|
|
}
|
|
|
|
kerr = ipadb_get_ldap_mod_key_data(imods,
|
|
entry->key_data,
|
|
entry->n_key_data,
|
|
mkvno,
|
|
mod_op);
|
|
if (kerr) {
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
kerr = ipadb_get_ldap_mod_auth_ind(kcontext, imods, entry, mod_op);
|
|
if (kerr)
|
|
goto done;
|
|
|
|
/* KADM5_TL_DATA */
|
|
if (entry->mask & KMASK_TL_DATA) {
|
|
kerr = ipadb_get_tl_data(entry,
|
|
KRB5_TL_LAST_ADMIN_UNLOCK,
|
|
sizeof(time32le),
|
|
(krb5_octet *)&time32le);
|
|
if (kerr && kerr != ENOENT) {
|
|
goto done;
|
|
}
|
|
if (kerr == 0) {
|
|
kerr = ipadb_get_ldap_mod_time(imods,
|
|
"krbLastAdminUnlock",
|
|
le32toh(time32le),
|
|
mod_op);
|
|
if (kerr) {
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
kerr = krb5_dbe_get_string(kcontext, entry, "require_auth",
|
|
&req_auth_str);
|
|
if (kerr) {
|
|
goto done;
|
|
}
|
|
|
|
/* Do not store auth indicators from the string attribute in
|
|
* krbExtraData. Remove require_auth value from the entry temporarily. */
|
|
if (req_auth_str != NULL) {
|
|
kerr = krb5_dbe_set_string(kcontext, entry, "require_auth", NULL);
|
|
if (kerr) {
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
kerr = ipadb_get_ldap_mod_extra_data(imods,
|
|
entry->tl_data,
|
|
mod_op);
|
|
if (kerr && kerr != ENOENT) {
|
|
goto done;
|
|
}
|
|
|
|
/* Restore require_auth value */
|
|
if (req_auth_str != NULL) {
|
|
kerr = krb5_dbe_set_string(kcontext, entry, "require_auth",
|
|
req_auth_str);
|
|
if (kerr) {
|
|
goto done;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* KADM5_LOAD */
|
|
|
|
/* Handle password change related operations. */
|
|
if (entry->e_data) {
|
|
struct ipadb_e_data *ied;
|
|
time_t now = time(NULL);
|
|
time_t expire_time;
|
|
char **new_history;
|
|
int nh_len;
|
|
int ret;
|
|
int i;
|
|
|
|
ied = (struct ipadb_e_data *)entry->e_data;
|
|
if (ied->magic != IPA_E_DATA_MAGIC) {
|
|
kerr = EINVAL;
|
|
goto done;
|
|
}
|
|
|
|
/*
|
|
* We need to set userPassword and history only if this is
|
|
* a IPA User, we don't do that for simple service principals
|
|
*/
|
|
if (ied->ipa_user && ied->passwd) {
|
|
kerr = ipadb_get_ldap_mod_str(imods, "userPassword",
|
|
ied->passwd, mod_op);
|
|
if (kerr) {
|
|
goto done;
|
|
}
|
|
|
|
/* Also set new password expiration time.
|
|
* Have to do it here because kadmin doesn't know policies and
|
|
* resets entry->mask after we have gone through the password
|
|
* change code. */
|
|
kerr = ipadb_get_pwd_expiration(kcontext, entry,
|
|
ied, &expire_time);
|
|
if (kerr) {
|
|
goto done;
|
|
}
|
|
|
|
kerr = ipadb_get_ldap_mod_time(imods,
|
|
"krbPasswordExpiration",
|
|
expire_time, mod_op);
|
|
if (expire_time == 0) {
|
|
kerr = ipadb_get_ldap_mod_time(imods,
|
|
"krbPasswordExpiration",
|
|
expire_time, LDAP_MOD_DELETE);
|
|
}
|
|
|
|
if (kerr) {
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
if (ied->ipa_user && ied->passwd &&
|
|
ied->pol && ied->pol->history_length) {
|
|
ret = ipapwd_generate_new_history(ied->passwd, now,
|
|
ied->pol->history_length,
|
|
ied->pw_history,
|
|
&new_history, &nh_len);
|
|
if (ret) {
|
|
kerr = ret;
|
|
goto done;
|
|
}
|
|
|
|
kerr = ipadb_get_ldap_mod_str_list(imods, "passwordHistory",
|
|
new_history, nh_len, mod_op);
|
|
|
|
for (i = 0; i < nh_len; i++) {
|
|
free(new_history[i]);
|
|
}
|
|
free(new_history);
|
|
|
|
if (kerr) {
|
|
goto done;
|
|
}
|
|
}
|
|
}
|
|
|
|
kerr = 0;
|
|
|
|
done:
|
|
free(req_auth_str);
|
|
return kerr;
|
|
}
|
|
|
|
/* adds default objectclasses and attributes */
|
|
static krb5_error_code ipadb_entry_default_attrs(struct ipadb_mods *imods)
|
|
{
|
|
krb5_error_code kerr;
|
|
LDAPMod *m = NULL;
|
|
size_t i;
|
|
|
|
kerr = ipadb_mods_new(imods, &m);
|
|
if (kerr) {
|
|
return kerr;
|
|
}
|
|
|
|
m->mod_op = LDAP_MOD_ADD;
|
|
m->mod_type = strdup("objectClass");
|
|
if (!m->mod_type) {
|
|
kerr = ENOMEM;
|
|
goto done;
|
|
}
|
|
m->mod_values = calloc(STD_PRINCIPAL_OBJ_CLASSES_SIZE + 1, sizeof(char *));
|
|
if (!m->mod_values) {
|
|
kerr = ENOMEM;
|
|
goto done;
|
|
}
|
|
for (i = 0; i < STD_PRINCIPAL_OBJ_CLASSES_SIZE; i++) {
|
|
m->mod_values[i] = strdup(std_principal_obj_classes[i]);
|
|
if (!m->mod_values[i]) {
|
|
kerr = ENOMEM;
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
kerr = 0;
|
|
|
|
done:
|
|
if (kerr) {
|
|
ipadb_mods_free_tip(imods);
|
|
}
|
|
return kerr;
|
|
}
|
|
|
|
static krb5_error_code ipadb_add_principal(krb5_context kcontext,
|
|
krb5_db_entry *entry)
|
|
{
|
|
struct ipadb_context *ipactx;
|
|
krb5_error_code kerr;
|
|
char *principal = NULL;
|
|
struct ipadb_mods *imods = NULL;
|
|
char *dn = NULL;
|
|
int ret;
|
|
|
|
ipactx = ipadb_get_context(kcontext);
|
|
if (!ipactx) {
|
|
kerr = KRB5_KDB_DBNOTINITED;
|
|
goto done;
|
|
}
|
|
|
|
if (!ipactx->override_restrictions) {
|
|
return KRB5_KDB_CONSTRAINT_VIOLATION;
|
|
}
|
|
|
|
kerr = krb5_unparse_name(kcontext, entry->princ, &principal);
|
|
if (kerr != 0) {
|
|
goto done;
|
|
}
|
|
|
|
ret = asprintf(&dn, "krbPrincipalName=%s,cn=%s,cn=kerberos,%s",
|
|
principal, ipactx->realm, ipactx->base);
|
|
if (ret == -1) {
|
|
kerr = ENOMEM;
|
|
goto done;
|
|
}
|
|
|
|
ret = new_ipadb_mods(&imods);
|
|
if (ret != 0) {
|
|
kerr = ret;
|
|
goto done;
|
|
}
|
|
|
|
kerr = ipadb_entry_default_attrs(imods);
|
|
if (kerr != 0) {
|
|
goto done;
|
|
}
|
|
|
|
kerr = ipadb_principal_to_mods(kcontext, imods, principal, LDAP_MOD_ADD);
|
|
if (kerr != 0) {
|
|
goto done;
|
|
}
|
|
|
|
kerr = ipadb_entry_to_mods(kcontext, imods, entry, LDAP_MOD_ADD);
|
|
if (kerr != 0) {
|
|
goto done;
|
|
}
|
|
|
|
kerr = ipadb_simple_add(ipactx, dn, imods->mods);
|
|
|
|
done:
|
|
ipadb_mods_free(imods);
|
|
krb5_free_unparsed_name(kcontext, principal);
|
|
ldap_memfree(dn);
|
|
return kerr;
|
|
}
|
|
|
|
static krb5_error_code ipadb_modify_principal(krb5_context kcontext,
|
|
krb5_db_entry *entry)
|
|
{
|
|
struct ipadb_context *ipactx;
|
|
krb5_error_code kerr;
|
|
char *principal = NULL;
|
|
LDAPMessage *res = NULL;
|
|
LDAPMessage *lentry;
|
|
struct ipadb_mods *imods = NULL;
|
|
char *dn = NULL;
|
|
struct ipadb_e_data *ied;
|
|
|
|
ipactx = ipadb_get_context(kcontext);
|
|
if (!ipactx) {
|
|
return KRB5_KDB_DBNOTINITED;
|
|
}
|
|
|
|
kerr = new_ipadb_mods(&imods);
|
|
if (kerr) {
|
|
goto done;
|
|
}
|
|
|
|
ied = (struct ipadb_e_data *)entry->e_data;
|
|
if (!ied || !ied->entry_dn) {
|
|
kerr = krb5_unparse_name(kcontext, entry->princ, &principal);
|
|
if (kerr != 0) {
|
|
goto done;
|
|
}
|
|
|
|
kerr = ipadb_fetch_principals(ipactx, 0, principal, &res);
|
|
if (kerr != 0) {
|
|
goto done;
|
|
}
|
|
|
|
/* FIXME: no alias allowed for now, should we allow modifies
|
|
* by alias name ? */
|
|
kerr = ipadb_find_principal(kcontext, 0, res, &principal, &lentry);
|
|
if (kerr != 0) {
|
|
goto done;
|
|
}
|
|
|
|
dn = ldap_get_dn(ipactx->lcontext, lentry);
|
|
if (!dn) {
|
|
kerr = KRB5_KDB_INTERNAL_ERROR;
|
|
goto done;
|
|
}
|
|
|
|
kerr = ipadb_principal_to_mods(kcontext, imods, principal,
|
|
LDAP_MOD_REPLACE);
|
|
if (kerr != 0) {
|
|
goto done;
|
|
}
|
|
|
|
}
|
|
|
|
kerr = ipadb_entry_to_mods(kcontext, imods, entry, LDAP_MOD_REPLACE);
|
|
if (kerr != 0) {
|
|
goto done;
|
|
}
|
|
|
|
if (!ied || !ied->entry_dn) {
|
|
kerr = ipadb_simple_modify(ipactx, dn, imods->mods);
|
|
} else {
|
|
kerr = ipadb_simple_modify(ipactx, ied->entry_dn, imods->mods);
|
|
}
|
|
|
|
done:
|
|
ipadb_mods_free(imods);
|
|
ldap_msgfree(res);
|
|
krb5_free_unparsed_name(kcontext, principal);
|
|
ldap_memfree(dn);
|
|
return kerr;
|
|
}
|
|
|
|
krb5_error_code ipadb_put_principal(krb5_context kcontext,
|
|
krb5_db_entry *entry,
|
|
char **db_args)
|
|
{
|
|
if (entry->mask & KMASK_PRINCIPAL) {
|
|
return ipadb_add_principal(kcontext, entry);
|
|
} else {
|
|
return ipadb_modify_principal(kcontext, entry);
|
|
}
|
|
}
|
|
|
|
static krb5_error_code ipadb_delete_entry(krb5_context kcontext,
|
|
LDAPMessage *lentry)
|
|
{
|
|
struct ipadb_context *ipactx;
|
|
krb5_error_code kerr;
|
|
char *dn = NULL;
|
|
int ret;
|
|
|
|
ipactx = ipadb_get_context(kcontext);
|
|
if (!ipactx) {
|
|
kerr = KRB5_KDB_DBNOTINITED;
|
|
goto done;
|
|
}
|
|
|
|
if (!ipactx->lcontext) {
|
|
ret = ipadb_get_connection(ipactx);
|
|
if (ret != 0) {
|
|
kerr = KRB5_KDB_SERVER_INTERNAL_ERR;
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
dn = ldap_get_dn(ipactx->lcontext, lentry);
|
|
if (!dn) {
|
|
kerr = KRB5_KDB_INTERNAL_ERROR;
|
|
goto done;
|
|
}
|
|
|
|
kerr = ipadb_simple_delete(ipactx, dn);
|
|
|
|
done:
|
|
ldap_memfree(dn);
|
|
return kerr;
|
|
}
|
|
|
|
static krb5_error_code ipadb_delete_alias(krb5_context kcontext,
|
|
LDAPMessage *lentry,
|
|
char *principal)
|
|
{
|
|
struct ipadb_context *ipactx;
|
|
krb5_error_code kerr;
|
|
char *dn = NULL;
|
|
int ret;
|
|
|
|
ipactx = ipadb_get_context(kcontext);
|
|
if (!ipactx) {
|
|
kerr = KRB5_KDB_DBNOTINITED;
|
|
goto done;
|
|
}
|
|
|
|
if (!ipactx->lcontext) {
|
|
ret = ipadb_get_connection(ipactx);
|
|
if (ret != 0) {
|
|
kerr = KRB5_KDB_SERVER_INTERNAL_ERR;
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
dn = ldap_get_dn(ipactx->lcontext, lentry);
|
|
if (!dn) {
|
|
kerr = KRB5_KDB_INTERNAL_ERROR;
|
|
goto done;
|
|
}
|
|
|
|
kerr = ipadb_simple_delete_val(ipactx, dn, "krbprincipalname", principal);
|
|
|
|
done:
|
|
ldap_memfree(dn);
|
|
return kerr;
|
|
}
|
|
|
|
krb5_error_code ipadb_delete_principal(krb5_context kcontext,
|
|
krb5_const_principal search_for)
|
|
{
|
|
struct ipadb_context *ipactx;
|
|
krb5_error_code kerr;
|
|
char *principal = NULL;
|
|
char *canonicalized = NULL;
|
|
LDAPMessage *res = NULL;
|
|
LDAPMessage *lentry;
|
|
unsigned int flags = 0;
|
|
|
|
ipactx = ipadb_get_context(kcontext);
|
|
if (!ipactx) {
|
|
return KRB5_KDB_DBNOTINITED;
|
|
}
|
|
|
|
if (!ipactx->override_restrictions) {
|
|
return KRB5_KDB_CONSTRAINT_VIOLATION;
|
|
}
|
|
|
|
kerr = krb5_unparse_name(kcontext, search_for, &principal);
|
|
if (kerr != 0) {
|
|
goto done;
|
|
}
|
|
|
|
kerr = ipadb_fetch_principals(ipactx, 0, principal, &res);
|
|
if (kerr != 0) {
|
|
goto done;
|
|
}
|
|
|
|
canonicalized = strdup(principal);
|
|
if (!canonicalized) {
|
|
kerr = ENOMEM;
|
|
goto done;
|
|
}
|
|
|
|
#ifdef KRB5_KDB_FLAG_ALIAS_OK
|
|
flags = KRB5_KDB_FLAG_ALIAS_OK;
|
|
#endif
|
|
kerr = ipadb_find_principal(kcontext, flags, res, &canonicalized, &lentry);
|
|
if (kerr != 0) {
|
|
goto done;
|
|
}
|
|
|
|
/* check if this is an alias (remove it) or if we should remove the whole
|
|
* ldap record */
|
|
|
|
/* TODO: should we use case insensitive matching here ? */
|
|
if (strcmp(canonicalized, principal) == 0) {
|
|
kerr = ipadb_delete_entry(kcontext, lentry);
|
|
} else {
|
|
kerr = ipadb_delete_alias(kcontext, lentry, principal);
|
|
}
|
|
|
|
done:
|
|
ldap_msgfree(res);
|
|
free(canonicalized);
|
|
krb5_free_unparsed_name(kcontext, principal);
|
|
return kerr;
|
|
}
|
|
|
|
#if KRB5_KDB_API_VERSION < 8
|
|
krb5_error_code ipadb_iterate(krb5_context kcontext,
|
|
char *match_entry,
|
|
int (*func)(krb5_pointer, krb5_db_entry *),
|
|
krb5_pointer func_arg)
|
|
#else
|
|
krb5_error_code ipadb_iterate(krb5_context kcontext,
|
|
char *match_entry,
|
|
int (*func)(krb5_pointer, krb5_db_entry *),
|
|
krb5_pointer func_arg, krb5_flags iterflags)
|
|
#endif
|
|
{
|
|
struct ipadb_context *ipactx;
|
|
krb5_error_code kerr;
|
|
LDAPMessage *res = NULL;
|
|
LDAPMessage *lentry;
|
|
krb5_db_entry *kentry;
|
|
uint32_t pol;
|
|
|
|
ipactx = ipadb_get_context(kcontext);
|
|
if (!ipactx) {
|
|
return KRB5_KDB_DBNOTINITED;
|
|
}
|
|
|
|
/* If no match_entry is given iterate through all krb princs like the db2
|
|
* or ldap plugin */
|
|
if (match_entry == NULL) {
|
|
match_entry = "*";
|
|
}
|
|
|
|
/* fetch list of principal matching filter */
|
|
kerr = ipadb_fetch_principals(ipactx, 0, match_entry, &res);
|
|
if (kerr != 0) {
|
|
goto done;
|
|
}
|
|
|
|
lentry = ldap_first_entry(ipactx->lcontext, res);
|
|
|
|
while (lentry) {
|
|
|
|
kentry = NULL;
|
|
kerr = ipadb_parse_ldap_entry(kcontext, NULL, lentry, &kentry, &pol);
|
|
if (kerr == 0 && pol != 0) {
|
|
kerr = ipadb_fetch_tktpolicy(kcontext, lentry, kentry, pol);
|
|
}
|
|
if (kerr == 0) {
|
|
/* Now call the callback with the entry */
|
|
func(func_arg, kentry);
|
|
}
|
|
ipadb_free_principal(kcontext, kentry);
|
|
|
|
lentry = ldap_next_entry(ipactx->lcontext, lentry);
|
|
}
|
|
|
|
kerr = 0;
|
|
|
|
done:
|
|
ldap_msgfree(res);
|
|
return kerr;
|
|
}
|
|
|