mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2026-11-02 20:30:26 -06:00
The initial design for ticket flags was implementing this logic:
* If a ticket policy is defined for the principal entry, use flags from
this policy if they are set. Otherwise, use default ticket flags.
* If no ticket policy is defined for the principal entry, but there is a
global one, use flags from the global ticket policy if they are set.
Otherwise, use default ticket flags.
* If no policy (principal nor global) is defined, use default ticket
flags.
However, this logic was broken by a1165ffb which introduced creation of
a principal-level ticket policy in case the ticket flag set is modified.
This was typically the case for the -allow_tix flag, which was set
virtually by the KDB driver when a user was locked until they initialize
their password on first kinit pre-authentication.
This was causing multiple issues, which are mitigated by the new
approach:
Now flags from each level are combined together. There flags like
+requires_preauth which are set systematically by the KDB diver, as
well as -allow_tix which is set based on the value of "nsAccountLock".
This commit also adds the implicit -allow_svr ticket flag for user
principals to protect users against Kerberoast-type attacks. None of
these flags are stored in the LDAP database, they are hard-coded in the
KDB driver.
In addition to these "virtual" ticket flags, flags from both global and
principal ticket policies are applied (if these policies exist).
Principal ticket policies are not supported for hosts and services, but
this is only an HTTP API limitation. The "krbTicketPolicyAux" object
class is supported for all account types. This is required for ticket
flags like +ok_to_auth_as_delegate. Such flags can be set using "ipa
host-mod" and "ipa serivce-mod", or using kadmin's "modprinc".
It is possible to ignore flags from the global ticket policy or default
flags like -allow_svr for a user principal by setting the
"final_user_tkt_flags" string attribute to "true" in kadmin. In this
case, any ticket flag can be configured in the principal ticket policy,
except requires_preauth and allow_tix.
When in IPA setup mode (using the "ipa-setup-override-restrictions" KDB
argument), all the system described above is disabled and ticket flags
are written in the principal ticket policy as they are provided. This is
required to initialize the Kerberos LDAP container during IPA server
installation.
This fixes CVE-2024-3183
Signed-off-by: Julien Rische <jrische@redhat.com>
3459 lines
100 KiB
C
3459 lines
100 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",
|
|
"ipaPassKey",
|
|
"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"
|
|
|
|
#ifndef KRB5_KDB_SK_OPTIONAL_PAC_TKT_CHKSUM
|
|
#define KRB5_KDB_SK_OPTIONAL_PAC_TKT_CHKSUM "optional_pac_tkt_chksum"
|
|
#endif
|
|
|
|
#ifndef KRB5_KDB_SK_PAC_PRIVSVR_ENCTYPE
|
|
#define KRB5_KDB_SK_PAC_PRIVSVR_ENCTYPE "pac_privsvr_enctype"
|
|
#endif
|
|
|
|
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 void ipadb_validate_passkey(struct ipadb_context *ipactx,
|
|
LDAPMessage *lentry,
|
|
enum ipadb_user_auth *ua)
|
|
{
|
|
struct berval **vals;
|
|
|
|
if (!(*ua & IPADB_USER_AUTH_PASSKEY))
|
|
return;
|
|
|
|
/* Ensure that the user has a link to an IdP config. */
|
|
vals = ldap_get_values_len(ipactx->lcontext, lentry,
|
|
"ipaPassKey");
|
|
if (vals == NULL || vals[0] == NULL)
|
|
*ua &= ~IPADB_USER_AUTH_PASSKEY;
|
|
|
|
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);
|
|
ipadb_validate_passkey(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},
|
|
{"krbAuthIndMaxTicketLife;passkey",
|
|
IPADB_USER_AUTH_PASSKEY, IPADB_USER_AUTH_IDX_PASSKEY},
|
|
{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},
|
|
{"krbAuthIndMaxRenewableAge;passkey",
|
|
IPADB_USER_AUTH_PASSKEY, IPADB_USER_AUTH_IDX_PASSKEY},
|
|
{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\"]}]";
|
|
const krb5_octet passkey_string[] = "passkey\0[{\"indicators\": [\"passkey\"]}]";
|
|
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;
|
|
}
|
|
/* Since principal, global policy, and virtual ticket flags are combined,
|
|
* they must always be resolved, except if we are in IPA setup mode (because
|
|
* ticket policies and virtual ticket flags are irrelevant in this case). */
|
|
if (!ipactx->override_restrictions)
|
|
*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) {
|
|
if (1 == krb5_princ_size(kcontext, entry->princ)) {
|
|
/* A principal must be a POSIX account AND have only one element to
|
|
* be considered a user (this is to filter out CIFS principals). */
|
|
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;
|
|
} else if (ua & IPADB_USER_AUTH_PASSKEY) {
|
|
kerr = ipadb_set_tl_data(entry, KRB5_TL_STRING_ATTRS,
|
|
sizeof(passkey_string), passkey_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_error_code
|
|
are_final_tktflags(struct ipadb_context *ipactx, krb5_db_entry *entry,
|
|
bool *final_tktflags)
|
|
{
|
|
krb5_error_code kerr;
|
|
struct ipadb_e_data *ied;
|
|
char *str = NULL;
|
|
bool in_final_tktflags = false;
|
|
|
|
kerr = ipadb_get_edata(entry, &ied);
|
|
if (kerr)
|
|
goto end;
|
|
|
|
if (!ied->ipa_user) {
|
|
kerr = 0;
|
|
goto end;
|
|
}
|
|
|
|
kerr = krb5_dbe_get_string(ipactx->kcontext, entry,
|
|
IPA_KDB_STRATTR_FINAL_USER_TKTFLAGS, &str);
|
|
if (kerr)
|
|
goto end;
|
|
|
|
in_final_tktflags = str && ipa_krb5_parse_bool(str);
|
|
|
|
end:
|
|
if (final_tktflags)
|
|
*final_tktflags = in_final_tktflags;
|
|
|
|
krb5_dbe_free_string(ipactx->kcontext, str);
|
|
return kerr;
|
|
}
|
|
|
|
static krb5_error_code
|
|
add_virtual_static_tktflags(struct ipadb_context *ipactx, krb5_db_entry *entry,
|
|
krb5_flags *tktflags)
|
|
{
|
|
krb5_error_code kerr;
|
|
krb5_flags vsflg;
|
|
bool final_tktflags;
|
|
const struct ipadb_global_config *gcfg;
|
|
struct ipadb_e_data *ied;
|
|
|
|
vsflg = IPA_KDB_TKTFLAGS_VIRTUAL_STATIC_MANDATORY;
|
|
|
|
kerr = ipadb_get_edata(entry, &ied);
|
|
if (kerr)
|
|
goto end;
|
|
|
|
kerr = are_final_tktflags(ipactx, entry, &final_tktflags);
|
|
if (kerr)
|
|
goto end;
|
|
|
|
/* In practice, principal ticket flags cannot be final for SPNs. */
|
|
if (!final_tktflags)
|
|
vsflg |= ied->ipa_user ? IPA_KDB_TKTFLAGS_VIRTUAL_STATIC_DEFAULTS_USER
|
|
: IPA_KDB_TKTFLAGS_VIRTUAL_STATIC_DEFAULTS_SPN;
|
|
|
|
if (!ied->ipa_user) {
|
|
gcfg = ipadb_get_global_config(ipactx);
|
|
if (gcfg && gcfg->disable_preauth_for_spns)
|
|
vsflg &= ~KRB5_KDB_REQUIRES_PRE_AUTH;
|
|
}
|
|
|
|
if (tktflags)
|
|
*tktflags |= vsflg;
|
|
|
|
end:
|
|
return kerr;
|
|
}
|
|
|
|
static krb5_error_code
|
|
get_virtual_static_tktflags_mask(struct ipadb_context *ipactx,
|
|
krb5_db_entry *entry, krb5_flags *mask)
|
|
{
|
|
krb5_error_code kerr;
|
|
krb5_flags flags = IPA_KDB_TKTFLAGS_VIRTUAL_MANAGED_ALL;
|
|
|
|
kerr = add_virtual_static_tktflags(ipactx, entry, &flags);
|
|
if (kerr)
|
|
goto end;
|
|
|
|
if (mask)
|
|
*mask = ~flags;
|
|
|
|
kerr = 0;
|
|
|
|
end:
|
|
return kerr;
|
|
}
|
|
|
|
/* Add ticket flags from the global ticket policy if it exists, otherwise
|
|
* succeed. If the global ticket policy is set, the "exists" parameter is set to
|
|
* true. */
|
|
static krb5_error_code
|
|
add_global_ticket_policy_flags(struct ipadb_context *ipactx,
|
|
bool *gtpol_exists, krb5_flags *tktflags)
|
|
{
|
|
krb5_error_code kerr;
|
|
char *policy_dn;
|
|
char *tktflags_attr[] = { "krbticketflags", NULL };
|
|
LDAPMessage *res = NULL, *first;
|
|
int ec, ldap_tktflags;
|
|
bool in_gtpol_exists = false;
|
|
|
|
ec = asprintf(&policy_dn, "cn=%s,cn=kerberos,%s", ipactx->realm,
|
|
ipactx->base);
|
|
if (-1 == ec) {
|
|
kerr = ENOMEM;
|
|
goto end;
|
|
}
|
|
|
|
kerr = ipadb_simple_search(ipactx, policy_dn, LDAP_SCOPE_BASE,
|
|
"(objectclass=krbticketpolicyaux)",
|
|
tktflags_attr, &res);
|
|
if (kerr) {
|
|
if (KRB5_KDB_NOENTRY == kerr)
|
|
kerr = 0;
|
|
goto end;
|
|
}
|
|
|
|
first = ldap_first_entry(ipactx->lcontext, res);
|
|
if (!first) {
|
|
kerr = 0;
|
|
goto end;
|
|
}
|
|
|
|
in_gtpol_exists = true;
|
|
|
|
ec = ipadb_ldap_attr_to_int(ipactx->lcontext, first, "krbticketflags",
|
|
&ldap_tktflags);
|
|
if (0 == ec && tktflags) {
|
|
*tktflags |= (krb5_flags)ldap_tktflags;
|
|
}
|
|
|
|
kerr = 0;
|
|
|
|
end:
|
|
if (gtpol_exists)
|
|
*gtpol_exists = in_gtpol_exists;
|
|
|
|
ldap_msgfree(res);
|
|
free(policy_dn);
|
|
return kerr;
|
|
}
|
|
|
|
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;
|
|
bool final_tktflags, has_local_tktpolicy = true;
|
|
int result;
|
|
int ret;
|
|
|
|
ipactx = ipadb_get_context(kcontext);
|
|
if (!ipactx) {
|
|
return KRB5_KDB_DBNOTINITED;
|
|
}
|
|
|
|
kerr = are_final_tktflags(ipactx, entry, &final_tktflags);
|
|
if (kerr)
|
|
goto done;
|
|
|
|
ret = ipadb_ldap_attr_to_str(ipactx->lcontext, lentry,
|
|
"krbticketpolicyreference", &policy_dn);
|
|
switch (ret) {
|
|
case 0:
|
|
break;
|
|
case ENOENT:
|
|
/* If no principal ticket policy, fallback to the global one. */
|
|
has_local_tktpolicy = false;
|
|
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) {
|
|
/* If global ticket policy is being applied, set flags only if
|
|
* user principal ticket flags are not final. */
|
|
if (has_local_tktpolicy || !final_tktflags) {
|
|
ret = ipadb_ldap_attr_to_int(ipactx->lcontext, first,
|
|
"krbticketflags", &result);
|
|
if (ret == 0)
|
|
entry->attributes |= result;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
kerr = 0;
|
|
}
|
|
|
|
if (polmask & TKTFLAGS_BIT) {
|
|
/* If the principal ticket flags were applied, then flags from the
|
|
* global ticket policy has to be applied atop of them if user principal
|
|
* ticket flags are not final. */
|
|
if (has_local_tktpolicy && !final_tktflags) {
|
|
kerr = add_global_ticket_policy_flags(ipactx, NULL,
|
|
&entry->attributes);
|
|
if (kerr)
|
|
goto done;
|
|
}
|
|
|
|
/* Virtual static ticket flags are set regardless of database content */
|
|
kerr = add_virtual_static_tktflags(ipactx, entry, &entry->attributes);
|
|
if (kerr)
|
|
goto done;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/* For krb5 1.19, there is no KRB5_KDB_FLAG_REFERRAL_OK, emulate it
|
|
* See krb5 commit a441fbe329ebbd7775eb5d4ccc4a05eef370f08b */
|
|
#ifndef KRB5_KDB_FLAG_REFERRAL_OK
|
|
#ifdef KRB5_KDB_FLAG_CANONICALIZE
|
|
#define KRB5_KDB_FLAG_REFERRAL_OK KRB5_KDB_FLAG_CANONICALIZE
|
|
#endif
|
|
#endif
|
|
|
|
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;
|
|
/* KRB5_NT_PRINCIPAL must be the last element */
|
|
krb5_int32 supported_types[] = {
|
|
[0] = KRB5_NT_ENTERPRISE_PRINCIPAL,
|
|
[1] = KRB5_NT_PRINCIPAL,
|
|
-1,
|
|
};
|
|
size_t i = 0;
|
|
const char *stmsg = NULL;
|
|
|
|
/* For TGS-REQ server principal lookup, KDC asks with KRB5_KDB_FLAG_REFERRAL_OK
|
|
* and client usually asks for an KRB5_NT_PRINCIPAL type principal. */
|
|
if ((flags & KRB5_KDB_FLAG_REFERRAL_OK) == 0) {
|
|
/* this is *not* TGS-REQ server principal search, remove
|
|
* KRB5_NT_PRINCIPAL from the supported principal types for this lookup */
|
|
supported_types[(sizeof(supported_types) / sizeof(supported_types[0])) - 2] = -1;
|
|
}
|
|
|
|
/* Enterprise principal name type is for potential aliases or principals
|
|
* from trusted realms. Except for the TGS-REQ server lookup, we only
|
|
* expect enterprise principals here */
|
|
for (i = 0; supported_types[i] != -1; i++) {
|
|
if (krb5_princ_type(kcontext, search_for) == supported_types[i]) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (supported_types[i] == -1) {
|
|
return KRB5_KDB_NOENTRY;
|
|
}
|
|
|
|
/* enterprise principal can only have single component in the name
|
|
* according to RFC6806 section 5. */
|
|
if ((krb5_princ_type(kcontext, search_for) == KRB5_NT_ENTERPRISE_PRINCIPAL) &&
|
|
(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);
|
|
}
|
|
|
|
/* if we haven't found the principal in our realm, it might still
|
|
* be a referral to a known realm. Otherwise, bail out with the result */
|
|
if ((kerr != KRB5_KDB_NOENTRY) &&
|
|
(flags & KRB5_KDB_FLAG_REFERRAL_OK) == 0) {
|
|
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, &stmsg);
|
|
if (kerr != 0) {
|
|
if (stmsg)
|
|
krb5_klog_syslog(LOG_WARNING, "MS-PAC generator: %s",
|
|
stmsg);
|
|
kerr = KRB5_KDB_NOENTRY;
|
|
goto done;
|
|
}
|
|
kerr = ipadb_is_princ_from_trusted_realm(kcontext,
|
|
realm->data,
|
|
realm->length,
|
|
&trusted_realm);
|
|
}
|
|
|
|
if (kerr == KRB5_KDB_NOENTRY) {
|
|
krb5_data *hstname = NULL;
|
|
int ncomponents = krb5_princ_size(kcontext, norm_princ);
|
|
|
|
/* We did not find any alias so far for non-server principal lookups */
|
|
if ((ncomponents < 2) && ((flags & KRB5_KDB_FLAG_REFERRAL_OK) == 0)) {
|
|
goto done;
|
|
}
|
|
|
|
/* At this point it is a server principal lookup that might be
|
|
* referencing a host name in a trusted domain. It might also
|
|
* have multiple service components so take the last one for the
|
|
* hostname or the domain name. See MS-ADTS 2.2.21 and MS-DRSR 2.2.4.2.
|
|
*/
|
|
hstname = krb5_princ_component(kcontext, norm_princ, ncomponents - 1);
|
|
|
|
kerr = ipadb_is_princ_from_trusted_realm(kcontext,
|
|
hstname->data,
|
|
hstname->length,
|
|
&trusted_realm);
|
|
}
|
|
|
|
if (kerr != 0) {
|
|
kerr = KRB5_KDB_NOENTRY;
|
|
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;
|
|
|
|
/* for trusted AD forests we currently must use SHA-1-based
|
|
* encryption types. For details, see
|
|
* https://github.com/krb5/krb5/commit/5af907156f8f502bbe268f0c62274f88a61261e4
|
|
*/
|
|
if (!is_local_tgs_princ) {
|
|
kerr = krb5_dbe_set_string(kcontext, *entry,
|
|
KRB5_KDB_SK_PAC_PRIVSVR_ENCTYPE,
|
|
"aes256-sha1");
|
|
if (kerr)
|
|
return kerr;
|
|
|
|
}
|
|
|
|
/* We should have been initialized at this point already */
|
|
if (ipactx->optional_pac_tkt_chksum == IPADB_TRISTATE_UNDEFINED) {
|
|
return KRB5_KDB_SERVER_INTERNAL_ERR;
|
|
}
|
|
/* 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,
|
|
KRB5_KDB_SK_OPTIONAL_PAC_TKT_CHKSUM,
|
|
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--;
|
|
}
|
|
|
|
/* Use LDAP REPLACE operation to remove an attribute.
|
|
* Contrary to the DELETE operation, it will not fail if the attribute does not
|
|
* exist. */
|
|
static krb5_error_code
|
|
ipadb_ldap_replace_remove(struct ipadb_mods *imods, char *attribute)
|
|
{
|
|
krb5_error_code kerr;
|
|
LDAPMod *m = NULL;
|
|
|
|
kerr = ipadb_mods_new(imods, &m);
|
|
if (kerr)
|
|
return kerr;
|
|
|
|
m->mod_op = LDAP_MOD_REPLACE;
|
|
m->mod_type = strdup(attribute);
|
|
if (!m->mod_type) {
|
|
kerr = ENOMEM;
|
|
goto end;
|
|
}
|
|
|
|
m->mod_values = NULL;
|
|
|
|
kerr = 0;
|
|
|
|
end:
|
|
if (kerr)
|
|
ipadb_mods_free_tip(imods);
|
|
return kerr;
|
|
}
|
|
|
|
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_CONSTRAINED_DELEGATION_ACL:
|
|
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
|
|
update_tktflags(krb5_context kcontext, struct ipadb_mods *imods,
|
|
krb5_db_entry *entry, int mod_op)
|
|
{
|
|
krb5_error_code kerr;
|
|
struct ipadb_context *ipactx;
|
|
struct ipadb_e_data *ied;
|
|
bool final_tktflags;
|
|
krb5_flags tktflags_mask;
|
|
int tktflags;
|
|
|
|
ipactx = ipadb_get_context(kcontext);
|
|
if (!ipactx) {
|
|
kerr = KRB5_KDB_DBNOTINITED;
|
|
goto end;
|
|
}
|
|
|
|
if (ipactx->override_restrictions) {
|
|
/* In IPA setup mode, IPA edata might not be available. In this mode,
|
|
* ticket flags are written as they are provided. */
|
|
tktflags = (int)entry->attributes;
|
|
} else {
|
|
kerr = ipadb_get_edata(entry, &ied);
|
|
if (kerr)
|
|
goto end;
|
|
|
|
kerr = get_virtual_static_tktflags_mask(ipactx, entry, &tktflags_mask);
|
|
if (kerr)
|
|
goto end;
|
|
|
|
kerr = are_final_tktflags(ipactx, entry, &final_tktflags);
|
|
if (kerr)
|
|
goto end;
|
|
|
|
/* Flags from the global ticket policy are filtered out only if the user
|
|
* principal flags are not final. */
|
|
if (!final_tktflags) {
|
|
krb5_flags gbl_tktflags = 0;
|
|
|
|
kerr = add_global_ticket_policy_flags(ipactx, NULL, &gbl_tktflags);
|
|
if (kerr)
|
|
goto end;
|
|
|
|
tktflags_mask &= ~gbl_tktflags;
|
|
}
|
|
|
|
tktflags = (int)(entry->attributes & tktflags_mask);
|
|
|
|
if (LDAP_MOD_REPLACE == mod_op && ied && !ied->has_tktpolaux) {
|
|
if (0 == tktflags) {
|
|
/* No point initializing principal ticket policy if there are no
|
|
* flags left after filtering out virtual and global ticket
|
|
* policy ones. */
|
|
kerr = 0;
|
|
goto end;
|
|
}
|
|
|
|
/* 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. */
|
|
kerr = ipadb_get_ldap_mod_str(imods, "objectclass",
|
|
"krbTicketPolicyAux", LDAP_MOD_ADD);
|
|
if (kerr)
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
if (tktflags != 0) {
|
|
kerr = ipadb_get_ldap_mod_int(imods, "krbTicketFlags", tktflags,
|
|
mod_op);
|
|
if (kerr)
|
|
goto end;
|
|
} else if (LDAP_MOD_REPLACE == mod_op) {
|
|
/* If the principal is not being created, and there are no custom ticket
|
|
* flags to be set, remove the "krbTicketFlags" attribute. */
|
|
kerr = ipadb_ldap_replace_remove(imods, "krbTicketFlags");
|
|
if (kerr)
|
|
goto end;
|
|
}
|
|
|
|
kerr = 0;
|
|
|
|
end:
|
|
return kerr;
|
|
}
|
|
|
|
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) {
|
|
kerr = update_tktflags(kcontext, imods, entry, 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;
|
|
const char *str_attrs[] = {
|
|
KRB5_KDB_SK_OPTIONAL_PAC_TKT_CHKSUM,
|
|
KRB5_KDB_SK_PAC_PRIVSVR_ENCTYPE,
|
|
NULL};
|
|
|
|
for(int i = 0; str_attrs[i] != NULL; i++) {
|
|
kerr = krb5_dbe_get_string(kcontext, entry,
|
|
str_attrs[i],
|
|
&str_attr_val);
|
|
if (kerr)
|
|
return kerr;
|
|
|
|
if (str_attr_val)
|
|
kerr = krb5_dbe_set_string(kcontext, entry,
|
|
str_attrs[i],
|
|
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;
|
|
}
|
|
|