freeipa/daemons/ipa-kdb/ipa_kdb_principals.c
Alexander Bokovoy 6cfb9b7193 ipa-kdb: store SID in the principal entry
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>
2021-11-10 15:00:27 -05:00

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;
}