Since November 2020, Active Directory KDC generates a new type of signature as part of the PAC. It is called "ticket signature", and is generated based on the encrypted part of the ticket. The presence of this signature is not mandatory in order for the PAC to be accepted for S4U requests. However, the behavior is different for MIT krb5. Support was added as part of the 1.20 release, and this signature is required in order to process S4U requests. Contrary to the PAC extended KDC signature, the code generating this signature cannot be isolated and backported to older krb5 versions because this version of the KDB API does not allow passing the content of the ticket's encrypted part to IPA. This is an issue in gradual upgrade scenarios where some IPA servers rely on 1.19 and older versions of MIT krb5, while others use version 1.20 or newer. A service ticket that was provided by 1.19- IPA KDC will be rejected when used by a service against a 1.20+ IPA KDC for S4U requests. On Fedora, CentOS 9 Stream, and RHEL 9, when the krb5 version is 1.20 or newer, it will include a downstream-only update adding the "optional_pac_tkt_chksum" KDB string attribute allowing to tolerate the absence of PAC ticket signatures, if necessary. This commit adds an extra step during the installation and update processes where it adds a "pacTktSignSupported" ipaConfigString attribute in "cn=KDC,cn=[server],cn=masters,cn=ipa,cn=etc,[basedn]" if the MIT krb5 version IPA what built with was 1.20 or newer. This commit also set "optional_pac_tkt_chksum" as a virtual KDB entry attribute. This means the value of the attribute is not actually stored in the database (to avoid race conditions), but its value is determined at the KDC starting time by search the "pacTktSignSupported" ipaConfigString in the server list. If this value is missing for at least of them is missing, enforcement of the PAC ticket signature is disabled by setting "optional_pac_tkt_chksum" to true for the local realm TGS KDB entry. For foreign realm TGS KDB entries, the "optional_pac_tkt_chksum" virtual string attribute is set to true systematically, because, at least for now, trusted AD domains can still have PAC ticket signature support disabled. Given the fact the "pacTktSignSupported" ipaConfigString for a single server is added when this server is updated, and that the value of "optional_pac_tkt_chksum" is determined at KDC starting time based on the ipaConfigString attributes of all the KDCs in the domain, this requires to restart all the KDCs in the domain after all IPA servers were updated in order for PAC ticket signature enforcement to actually take effect. Fixes: https://pagure.io/freeipa/issue/9371 Signed-off-by: Julien Rische <jrische@redhat.com> Reviewed-By: Rob Crittenden <rcritten@redhat.com> Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
3089 lines
88 KiB
C
3089 lines
88 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",
|
|
"ipaIdpConfigLink",
|
|
"krbAuthIndMaxTicketLife",
|
|
"krbAuthIndMaxRenewableAge",
|
|
"ipaNTSecurityIdentifier",
|
|
"ipaUniqueID",
|
|
"memberPrincipal",
|
|
|
|
"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"
|
|
|
|
#define OPT_PAC_TKT_CHKSUM_STR_ATTR_NAME "optional_pac_tkt_chksum"
|
|
|
|
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 bool
|
|
is_tgs_princ(krb5_context kcontext, krb5_const_principal princ)
|
|
{
|
|
krb5_data *primary;
|
|
size_t l_tgs_name;
|
|
|
|
if (2 != krb5_princ_size(kcontext, princ))
|
|
return false;
|
|
|
|
primary = krb5_princ_component(kcontext, princ, 0);
|
|
|
|
l_tgs_name = strlen(KRB5_TGS_NAME);
|
|
|
|
if (l_tgs_name != primary->length)
|
|
return false;
|
|
|
|
return 0 == memcmp(primary->data, KRB5_TGS_NAME, l_tgs_name);
|
|
}
|
|
|
|
static krb5_error_code
|
|
cmp_local_tgs_princ(krb5_context kcontext, const char *local_realm,
|
|
krb5_const_principal princ, bool *result)
|
|
{
|
|
krb5_principal local_tgs_princ;
|
|
size_t l_local_realm;
|
|
krb5_error_code kerr;
|
|
bool res;
|
|
|
|
l_local_realm = strlen(local_realm);
|
|
|
|
kerr = krb5_build_principal(kcontext, &local_tgs_princ,
|
|
l_local_realm, local_realm,
|
|
KRB5_TGS_NAME, local_realm, NULL);
|
|
if (kerr)
|
|
goto end;
|
|
|
|
res = (bool) krb5_principal_compare(kcontext, local_tgs_princ, princ);
|
|
|
|
if (result)
|
|
*result = res;
|
|
|
|
end:
|
|
krb5_free_principal(kcontext, local_tgs_princ);
|
|
return kerr;
|
|
}
|
|
|
|
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 {
|
|
/* OTP use implies presence of password in IPA LDAP,
|
|
* this is incompatible with RADIUS proxy case where
|
|
* a password in LDAP is not used anymore. */
|
|
*ua &= ~IPADB_USER_AUTH_OTP;
|
|
}
|
|
|
|
if (vals != NULL)
|
|
ldap_value_free_len(vals);
|
|
}
|
|
|
|
static void ipadb_validate_idp(struct ipadb_context *ipactx,
|
|
LDAPMessage *lentry,
|
|
enum ipadb_user_auth *ua)
|
|
{
|
|
struct berval **vals;
|
|
|
|
if (!(*ua & IPADB_USER_AUTH_IDP))
|
|
return;
|
|
|
|
/* Ensure that the user has a link to an IdP config. */
|
|
vals = ldap_get_values_len(ipactx->lcontext, lentry,
|
|
"ipaIdpConfigLink");
|
|
if (vals == NULL || vals[0] == NULL)
|
|
*ua &= ~IPADB_USER_AUTH_IDP;
|
|
|
|
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);
|
|
ipadb_validate_idp(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},
|
|
{"krbAuthIndMaxTicketLife;idp",
|
|
IPADB_USER_AUTH_IDP, IPADB_USER_AUTH_IDX_IDP},
|
|
{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},
|
|
{"krbAuthIndMaxRenewableAge;idp",
|
|
IPADB_USER_AUTH_IDP, IPADB_USER_AUTH_IDX_IDP},
|
|
{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++) {
|
|
/* Only change max_life/max_renewable_life per indicator
|
|
* if the value wasn't set yet. This function gets called twice:
|
|
* - for the principal entry
|
|
* - for the associated policy lookup */
|
|
if ((ua & life_authind_map[i].flag) &&
|
|
(ied->pol_limits[life_authind_map[i].idx].max_life == 0)) {
|
|
|
|
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\"]}]";
|
|
const krb5_octet idp_string[] = "idp\0[{\"type\":\"oauth2\",\"indicators\": [\"idp\"]}]";
|
|
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;
|
|
char **acl_list;
|
|
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 or hardened 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 | IPADB_USER_AUTH_HARDENED)) &&
|
|
(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;
|
|
} else if (ua & IPADB_USER_AUTH_IDP) {
|
|
kerr = ipadb_set_tl_data(entry, KRB5_TL_STRING_ATTRS,
|
|
sizeof(idp_string), idp_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;
|
|
}
|
|
|
|
/* check if it has the serviceDelegation objectclass */
|
|
ret = ipadb_ldap_attr_has_value(lcontext, lentry,
|
|
"objectClass", "resourceDelegation");
|
|
if (ret != 0 && ret != ENOENT) {
|
|
kerr = ret;
|
|
goto done;
|
|
}
|
|
if (ret == 0) {
|
|
ret = ipadb_ldap_attr_to_strlist(lcontext, lentry,
|
|
"memberPrincipal", &acl_list);
|
|
if (ret != 0 && ret != ENOENT) {
|
|
kerr = KRB5_KDB_INTERNAL_ERROR;
|
|
goto done;
|
|
}
|
|
if (ret == 0) {
|
|
kerr = ipadb_set_tl_data(entry, KRB5_TL_CONSTRAINED_DELEGATION_ACL,
|
|
sizeof(acl_list),
|
|
(const krb5_octet *) &acl_list);
|
|
if (kerr) {
|
|
goto done;
|
|
}
|
|
}
|
|
}
|
|
|
|
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 {
|
|
struct ipadb_e_data *ied;
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
ied = (struct ipadb_e_data *)entry->e_data;
|
|
if (ied && ied->ipa_user == true) {
|
|
/* Apply default policy to indicators, if any */
|
|
if (ied->user_auth & ~IPADB_USER_AUTH_NONE) {
|
|
ipadb_parse_authind_policies(kcontext, ipactx->lcontext,
|
|
first, entry, ied->user_auth);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
if (search_for == NULL) {
|
|
return FALSE;
|
|
}
|
|
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;
|
|
krb5_boolean check = FALSE;
|
|
|
|
|
|
#if defined(KRB5_KDB_FLAG_CLIENT)
|
|
check = flags & KRB5_KDB_FLAG_CLIENT;
|
|
#else
|
|
check = (flags & KRB5_KDB_FLAG_CLIENT_REFERRALS_ONLY) &&
|
|
(flags & KRB5_KDB_FLAG_CANONICALIZE);
|
|
#endif
|
|
|
|
if (check) {
|
|
/* 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;
|
|
krb5_boolean check = FALSE;
|
|
|
|
/* 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 defined(KRB5_KDB_FLAG_CLIENT)
|
|
check = ((flags & KRB5_KDB_FLAG_CLIENT) == 0) &&
|
|
((flags & KRB5_KDB_FLAG_REFERRAL_OK) == 0);
|
|
#else
|
|
check = ((flags & KRB5_KDB_FLAG_CLIENT_REFERRALS_ONLY) == 0) &&
|
|
((flags & KRB5_KDB_FLAG_CANONICALIZE) == 0);
|
|
#endif
|
|
if (check) {
|
|
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 defined(KRB5_KDB_FLAG_CLIENT)
|
|
check = (flags & KRB5_KDB_FLAG_CLIENT) && (flags & KRB5_KDB_FLAG_REFERRAL_OK);
|
|
#else
|
|
check = (flags & KRB5_KDB_FLAG_CLIENT_REFERRALS_ONLY) &&
|
|
((flags & KRB5_KDB_FLAG_CANONICALIZE) ||
|
|
search_for->type == KRB5_NT_ENTERPRISE_PRINCIPAL);
|
|
#endif
|
|
|
|
if (check) {
|
|
/* 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 defined(KRB5_KDB_FLAG_CLIENT)
|
|
check = flags & KRB5_KDB_FLAG_CLIENT;
|
|
#else
|
|
check = flags & KRB5_KDB_FLAG_INCLUDE_PAC;
|
|
#endif
|
|
if (check) {
|
|
/* 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;
|
|
bool is_local_tgs_princ;
|
|
const char *opt_pac_tkt_chksum_val;
|
|
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) {
|
|
kerr = dbget_alias(kcontext, ipactx, search_for, flags, entry);
|
|
}
|
|
if (kerr)
|
|
return kerr;
|
|
|
|
/* If TGS principal, some virtual attributes may be added */
|
|
if (is_tgs_princ(kcontext, (*entry)->princ)) {
|
|
kerr = cmp_local_tgs_princ(kcontext, ipactx->realm, (*entry)->princ,
|
|
&is_local_tgs_princ);
|
|
if (kerr)
|
|
return kerr;
|
|
|
|
/* PAC ticket signature should be optional for foreign realms, and local
|
|
* realm if not supported by all servers
|
|
*/
|
|
if (!is_local_tgs_princ || ipactx->optional_pac_tkt_chksum)
|
|
opt_pac_tkt_chksum_val = "true";
|
|
else
|
|
opt_pac_tkt_chksum_val = "false";
|
|
|
|
kerr = krb5_dbe_set_string(kcontext, *entry,
|
|
OPT_PAC_TKT_CHKSUM_STR_ATTR_NAME,
|
|
opt_pac_tkt_chksum_val);
|
|
}
|
|
|
|
return kerr;
|
|
}
|
|
|
|
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;
|
|
size_t i;
|
|
|
|
if (entry) {
|
|
krb5_free_principal(kcontext, entry->princ);
|
|
prev = entry->tl_data;
|
|
while(prev) {
|
|
next = prev->tl_data_next;
|
|
/* Handle RBCD ACL type */
|
|
if (prev->tl_data_type == KRB5_TL_CONSTRAINED_DELEGATION_ACL) {
|
|
char **acl_list = (char **) prev->tl_data_contents;
|
|
for (i = 0; (acl_list != NULL) && (acl_list[i] != NULL); i++) {
|
|
free(acl_list[i]);
|
|
}
|
|
}
|
|
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);
|
|
}
|
|
}
|
|
|
|
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 bool should_filter_out_attr(krb5_tl_data *data)
|
|
{
|
|
switch (data->tl_data_type) {
|
|
case KRB5_TL_DB_ARGS:
|
|
case KRB5_TL_KADM_DATA:
|
|
case KRB5_TL_LAST_ADMIN_UNLOCK:
|
|
case KRB5_TL_LAST_PWD_CHANGE:
|
|
case KRB5_TL_MKVNO:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
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 (should_filter_out_attr(data))
|
|
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 (should_filter_out_attr(data))
|
|
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;
|
|
}
|
|
|
|
static krb5_error_code
|
|
remove_virtual_str_attrs(krb5_context kcontext, krb5_db_entry *entry)
|
|
{
|
|
char *str_attr_val;
|
|
krb5_error_code kerr;
|
|
|
|
kerr = krb5_dbe_get_string(kcontext, entry,
|
|
OPT_PAC_TKT_CHKSUM_STR_ATTR_NAME,
|
|
&str_attr_val);
|
|
if (kerr)
|
|
return kerr;
|
|
|
|
if (str_attr_val)
|
|
kerr = krb5_dbe_set_string(kcontext, entry,
|
|
OPT_PAC_TKT_CHKSUM_STR_ATTR_NAME,
|
|
NULL);
|
|
|
|
krb5_dbe_free_string(kcontext, str_attr_val);
|
|
return kerr;
|
|
}
|
|
|
|
krb5_error_code ipadb_put_principal(krb5_context kcontext,
|
|
krb5_db_entry *entry,
|
|
char **db_args)
|
|
{
|
|
krb5_error_code kerr;
|
|
|
|
kerr = remove_virtual_str_attrs(kcontext, entry);
|
|
if (kerr)
|
|
return kerr;
|
|
|
|
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;
|
|
}
|
|
|