3615c2
From 5b83cb4769a61068ad02cacc455c51fe3cc28b6f Mon Sep 17 00:00:00 2001
3615c2
From: Julien Rische <jrische@redhat.com>
3615c2
Date: Mon, 25 Mar 2024 18:25:52 +0200
3615c2
Subject: [PATCH] kdb: apply combinatorial logic for ticket flags
3615c2
3615c2
The initial design for ticket flags was implementing this logic:
3615c2
* If a ticket policy is defined for the principal entry, use flags from
3615c2
  this policy if they are set. Otherwise, use default ticket flags.
3615c2
* If no ticket policy is defined for the principal entry, but there is a
3615c2
  global one, use flags from the global ticket policy if they are set.
3615c2
  Otherwise, use default ticket flags.
3615c2
* If no policy (principal nor global) is defined, use default ticket
3615c2
  flags.
3615c2
3615c2
However, this logic was broken by a1165ffb which introduced creation of
3615c2
a principal-level ticket policy in case the ticket flag set is modified.
3615c2
This was typically the case for the -allow_tix flag, which was set
3615c2
virtually by the KDB driver when a user was locked until they initialize
3615c2
their password on first kinit pre-authentication.
3615c2
3615c2
This was causing multiple issues, which are mitigated by the new
3615c2
approach:
3615c2
3615c2
Now flags from each level are combined together. There flags like
3615c2
+requires_preauth which are set systematically by the KDB diver, as
3615c2
well as -allow_tix which is set based on the value of "nsAccountLock".
3615c2
This commit also adds the implicit -allow_svr ticket flag for user
3615c2
principals to protect users against Kerberoast-type attacks. None of
3615c2
these flags are stored in the LDAP database, they are hard-coded in the
3615c2
KDB driver.
3615c2
3615c2
In addition to these "virtual" ticket flags, flags from both global and
3615c2
principal ticket policies are applied (if these policies exist).
3615c2
3615c2
Principal ticket policies are not supported for hosts and services, but
3615c2
this is only an HTTP API limitation. The "krbTicketPolicyAux" object
3615c2
class is supported for all account types. This is required for ticket
3615c2
flags like +ok_to_auth_as_delegate. Such flags can be set using "ipa
3615c2
host-mod" and "ipa serivce-mod", or using kadmin's "modprinc".
3615c2
3615c2
It is possible to ignore flags from the global ticket policy or default
3615c2
flags like -allow_svr for a user principal by setting the
3615c2
"final_user_tkt_flags" string attribute to "true" in kadmin. In this
3615c2
case, any ticket flag can be configured in the principal ticket policy,
3615c2
except requires_preauth and allow_tix.
3615c2
3615c2
When in IPA setup mode (using the "ipa-setup-override-restrictions" KDB
3615c2
argument), all the system described above is disabled and ticket flags
3615c2
are written in the principal ticket policy as they are provided. This is
3615c2
required to initialize the Kerberos LDAP container during IPA server
3615c2
installation.
3615c2
3615c2
This fixes CVE-2024-3183
3615c2
3615c2
Signed-off-by: Julien Rische <jrische@redhat.com>
3615c2
---
3615c2
 daemons/ipa-kdb/ipa_kdb.h            |  43 ++++
3615c2
 daemons/ipa-kdb/ipa_kdb_principals.c | 352 +++++++++++++++++++++++----
3615c2
 util/ipa_krb5.c                      |  18 ++
3615c2
 util/ipa_krb5.h                      |   5 +
3615c2
 4 files changed, 365 insertions(+), 53 deletions(-)
3615c2
3615c2
diff --git a/daemons/ipa-kdb/ipa_kdb.h b/daemons/ipa-kdb/ipa_kdb.h
3615c2
index d187d969f..e7da25875 100644
3615c2
--- a/daemons/ipa-kdb/ipa_kdb.h
3615c2
+++ b/daemons/ipa-kdb/ipa_kdb.h
3615c2
@@ -82,6 +82,34 @@
3615c2
 #define IPA_KRB_AUTHZ_DATA_ATTR "ipaKrbAuthzData"
3615c2
 #define IPA_USER_AUTH_TYPE "ipaUserAuthType"
3615c2
 
3615c2
+/* Virtual managed ticket flags like "-allow_tix", are always controlled by the
3615c2
+ * "nsAccountLock" attribute, such flags should never be set in the database.
3615c2
+ * The following expression combine all of them, and is used to filter them
3615c2
+ * out. */
3615c2
+#define IPA_KDB_TKTFLAGS_VIRTUAL_MANAGED_ALL          (KRB5_KDB_DISALLOW_ALL_TIX)
3615c2
+
3615c2
+/* Virtual static ticket flags are hard-coded in the KDB driver. */
3615c2
+/*   Virtual static mandatory flags are set systematically and implicitly for all
3615c2
+ *   principals. They are filtered out from database ticket flags updates.
3615c2
+ *   (However, "KRB5_KDB_REQUIRES_PRE_AUTH" can still be unset by the
3615c2
+ *   "KDC:Disable Default Preauth for SPNs" global setting) */
3615c2
+#define IPA_KDB_TKTFLAGS_VIRTUAL_STATIC_MANDATORY     (KRB5_KDB_REQUIRES_PRE_AUTH)
3615c2
+/*   Virtual static default ticket flags are implicitly set for user and non-user
3615c2
+ *   (SPN) principals, and not stored in the database.
3615c2
+ *   (Except if the "IPA_KDB_STRATTR_FINAL_TKTFLAGS" string attribute is "true"
3615c2
+ *   the principal) */
3615c2
+/*     Virtual static default user ticket flags are set for users only. The
3615c2
+ *     "-allow_svr" flag is set to protect them from CVE-2024-3183. */
3615c2
+#define IPA_KDB_TKTFLAGS_VIRTUAL_STATIC_DEFAULTS_USER (KRB5_KDB_DISALLOW_SVR)
3615c2
+#define IPA_KDB_TKTFLAGS_VIRTUAL_STATIC_DEFAULTS_SPN  (0)
3615c2
+
3615c2
+/* If this string attribute is set to "true", then only the virtual managed and
3615c2
+ * virtual static mandatory ticket flags are applied and filtered out from
3615c2
+ * database read and write operations for the concerned user principal.
3615c2
+ * Configurable principal ticket flags are applied, but not the configurable
3615c2
+ * global ticket policy flags. */
3615c2
+#define IPA_KDB_STRATTR_FINAL_USER_TKTFLAGS "final_user_tkt_flags"
3615c2
+
3615c2
 struct ipadb_mspac;
3615c2
 
3615c2
 enum ipadb_user_auth {
3615c2
@@ -141,6 +169,21 @@ struct ipadb_e_data {
3615c2
     bool has_tktpolaux;
3615c2
 };
3615c2
 
3615c2
+inline static krb5_error_code
3615c2
+ipadb_get_edata(krb5_db_entry *entry, struct ipadb_e_data **ied)
3615c2
+{
3615c2
+    struct ipadb_e_data *in_ied;
3615c2
+
3615c2
+    in_ied = (struct ipadb_e_data *)entry->e_data;
3615c2
+    if (!in_ied || in_ied->magic != IPA_E_DATA_MAGIC)
3615c2
+        return EINVAL;
3615c2
+
3615c2
+    if (ied)
3615c2
+        *ied = in_ied;
3615c2
+
3615c2
+    return 0;
3615c2
+}
3615c2
+
3615c2
 struct ipadb_context *ipadb_get_context(krb5_context kcontext);
3615c2
 int ipadb_get_connection(struct ipadb_context *ipactx);
3615c2
 
3615c2
diff --git a/daemons/ipa-kdb/ipa_kdb_principals.c b/daemons/ipa-kdb/ipa_kdb_principals.c
3615c2
index e1e86a610..2e522aa39 100644
3615c2
--- a/daemons/ipa-kdb/ipa_kdb_principals.c
3615c2
+++ b/daemons/ipa-kdb/ipa_kdb_principals.c
3615c2
@@ -592,9 +592,12 @@ static krb5_error_code ipadb_parse_ldap_entry(krb5_context kcontext,
3615c2
                                  "krbTicketFlags", &result);
3615c2
     if (ret == 0) {
3615c2
         entry->attributes = result;
3615c2
-    } else {
3615c2
-        *polmask |= TKTFLAGS_BIT;
3615c2
     }
3615c2
+    /* Since principal, global policy, and virtual ticket flags are combined,
3615c2
+     * they must always be resolved, except if we are in IPA setup mode (because
3615c2
+     * ticket policies and virtual ticket flags are irrelevant in this case). */
3615c2
+    if (!ipactx->override_restrictions)
3615c2
+        *polmask |= TKTFLAGS_BIT;
3615c2
 
3615c2
     ret = ipadb_ldap_attr_to_int(lcontext, lentry,
3615c2
                                  "krbMaxTicketLife", &result);
3615c2
@@ -788,7 +791,11 @@ static krb5_error_code ipadb_parse_ldap_entry(krb5_context kcontext,
3615c2
         goto done;
3615c2
     }
3615c2
     if (ret == 0) {
3615c2
-        ied->ipa_user = true;
3615c2
+        if (1 == krb5_princ_size(kcontext, entry->princ)) {
3615c2
+            /* A principal must be a POSIX account AND have only one element to
3615c2
+             * be considered a user (this is to filter out CIFS principals). */
3615c2
+            ied->ipa_user = true;
3615c2
+        }
3615c2
     }
3615c2
 
3615c2
     /* check if it has the krbTicketPolicyAux objectclass */
3615c2
@@ -1075,23 +1082,150 @@ krb5_error_code ipadb_find_principal(krb5_context kcontext,
3615c2
     return 0;
3615c2
 }
3615c2
 
3615c2
-static krb5_flags maybe_require_preauth(struct ipadb_context *ipactx,
3615c2
-                                        krb5_db_entry *entry)
3615c2
+static krb5_error_code
3615c2
+are_final_tktflags(struct ipadb_context *ipactx, krb5_db_entry *entry,
3615c2
+                   bool *final_tktflags)
3615c2
 {
3615c2
-    const struct ipadb_global_config *config;
3615c2
+    krb5_error_code kerr;
3615c2
     struct ipadb_e_data *ied;
3615c2
+    char *str = NULL;
3615c2
+    bool in_final_tktflags = false;
3615c2
 
3615c2
-    config = ipadb_get_global_config(ipactx);
3615c2
-    if (config && config->disable_preauth_for_spns) {
3615c2
-        ied = (struct ipadb_e_data *)entry->e_data;
3615c2
-        if (ied && ied->ipa_user != true) {
3615c2
-            /* not a user, assume SPN */
3615c2
-            return 0;
3615c2
-        }
3615c2
+    kerr = ipadb_get_edata(entry, &ied;;
3615c2
+    if (kerr)
3615c2
+        goto end;
3615c2
+
3615c2
+    if (!ied->ipa_user) {
3615c2
+        kerr = 0;
3615c2
+        goto end;
3615c2
+    }
3615c2
+
3615c2
+    kerr = krb5_dbe_get_string(ipactx->kcontext, entry,
3615c2
+                               IPA_KDB_STRATTR_FINAL_USER_TKTFLAGS, &str);
3615c2
+    if (kerr)
3615c2
+        goto end;
3615c2
+
3615c2
+    in_final_tktflags = str && ipa_krb5_parse_bool(str);
3615c2
+
3615c2
+end:
3615c2
+    if (final_tktflags)
3615c2
+        *final_tktflags = in_final_tktflags;
3615c2
+
3615c2
+    krb5_dbe_free_string(ipactx->kcontext, str);
3615c2
+    return kerr;
3615c2
+}
3615c2
+
3615c2
+static krb5_error_code
3615c2
+add_virtual_static_tktflags(struct ipadb_context *ipactx, krb5_db_entry *entry,
3615c2
+                            krb5_flags *tktflags)
3615c2
+{
3615c2
+    krb5_error_code kerr;
3615c2
+    krb5_flags vsflg;
3615c2
+    bool final_tktflags;
3615c2
+    const struct ipadb_global_config *gcfg;
3615c2
+    struct ipadb_e_data *ied;
3615c2
+
3615c2
+    vsflg = IPA_KDB_TKTFLAGS_VIRTUAL_STATIC_MANDATORY;
3615c2
+
3615c2
+    kerr = ipadb_get_edata(entry, &ied;;
3615c2
+    if (kerr)
3615c2
+        goto end;
3615c2
+
3615c2
+    kerr = are_final_tktflags(ipactx, entry, &final_tktflags);
3615c2
+    if (kerr)
3615c2
+        goto end;
3615c2
+
3615c2
+    /* In practice, principal ticket flags cannot be final for SPNs. */
3615c2
+    if (!final_tktflags)
3615c2
+        vsflg |= ied->ipa_user ? IPA_KDB_TKTFLAGS_VIRTUAL_STATIC_DEFAULTS_USER
3615c2
+                               : IPA_KDB_TKTFLAGS_VIRTUAL_STATIC_DEFAULTS_SPN;
3615c2
+
3615c2
+    if (!ied->ipa_user) {
3615c2
+        gcfg = ipadb_get_global_config(ipactx);
3615c2
+        if (gcfg && gcfg->disable_preauth_for_spns)
3615c2
+            vsflg &= ~KRB5_KDB_REQUIRES_PRE_AUTH;
3615c2
     }
3615c2
 
3615c2
-    /* By default require preauth for all principals */
3615c2
-    return KRB5_KDB_REQUIRES_PRE_AUTH;
3615c2
+    if (tktflags)
3615c2
+        *tktflags |= vsflg;
3615c2
+
3615c2
+end:
3615c2
+    return kerr;
3615c2
+}
3615c2
+
3615c2
+static krb5_error_code
3615c2
+get_virtual_static_tktflags_mask(struct ipadb_context *ipactx,
3615c2
+                                 krb5_db_entry *entry, krb5_flags *mask)
3615c2
+{
3615c2
+    krb5_error_code kerr;
3615c2
+    krb5_flags flags = IPA_KDB_TKTFLAGS_VIRTUAL_MANAGED_ALL;
3615c2
+
3615c2
+    kerr = add_virtual_static_tktflags(ipactx, entry, &flags);
3615c2
+    if (kerr)
3615c2
+        goto end;
3615c2
+
3615c2
+    if (mask)
3615c2
+        *mask = ~flags;
3615c2
+
3615c2
+    kerr = 0;
3615c2
+
3615c2
+end:
3615c2
+    return kerr;
3615c2
+}
3615c2
+
3615c2
+/* Add ticket flags from the global ticket policy if it exists, otherwise
3615c2
+ * succeed. If the global ticket policy is set, the "exists" parameter is set to
3615c2
+ * true. */
3615c2
+static krb5_error_code
3615c2
+add_global_ticket_policy_flags(struct ipadb_context *ipactx,
3615c2
+                               bool *gtpol_exists, krb5_flags *tktflags)
3615c2
+{
3615c2
+    krb5_error_code kerr;
3615c2
+    char *policy_dn;
3615c2
+    char *tktflags_attr[] = { "krbticketflags", NULL };
3615c2
+    LDAPMessage *res = NULL, *first;
3615c2
+    int ec, ldap_tktflags;
3615c2
+    bool in_gtpol_exists = false;
3615c2
+
3615c2
+    ec = asprintf(&policy_dn, "cn=%s,cn=kerberos,%s", ipactx->realm,
3615c2
+                  ipactx->base);
3615c2
+    if (-1 == ec) {
3615c2
+        kerr = ENOMEM;
3615c2
+        goto end;
3615c2
+    }
3615c2
+
3615c2
+    kerr = ipadb_simple_search(ipactx, policy_dn, LDAP_SCOPE_BASE,
3615c2
+                               "(objectclass=krbticketpolicyaux)",
3615c2
+                               tktflags_attr, &res;;
3615c2
+    if (kerr) {
3615c2
+        if (KRB5_KDB_NOENTRY == kerr)
3615c2
+            kerr = 0;
3615c2
+        goto end;
3615c2
+    }
3615c2
+
3615c2
+    first = ldap_first_entry(ipactx->lcontext, res);
3615c2
+    if (!first) {
3615c2
+        kerr = 0;
3615c2
+        goto end;
3615c2
+    }
3615c2
+
3615c2
+    in_gtpol_exists = true;
3615c2
+
3615c2
+    ec = ipadb_ldap_attr_to_int(ipactx->lcontext, first, "krbticketflags",
3615c2
+                                &ldap_tktflags);
3615c2
+    if (0 == ec && tktflags) {
3615c2
+        *tktflags |= (krb5_flags)ldap_tktflags;
3615c2
+    }
3615c2
+
3615c2
+    kerr = 0;
3615c2
+
3615c2
+end:
3615c2
+    if (gtpol_exists)
3615c2
+        *gtpol_exists = in_gtpol_exists;
3615c2
+
3615c2
+    ldap_msgfree(res);
3615c2
+    free(policy_dn);
3615c2
+    return kerr;
3615c2
 }
3615c2
 
3615c2
 static krb5_error_code ipadb_fetch_tktpolicy(krb5_context kcontext,
3615c2
@@ -1104,6 +1238,7 @@ static krb5_error_code ipadb_fetch_tktpolicy(krb5_context kcontext,
3615c2
     char *policy_dn = NULL;
3615c2
     LDAPMessage *res = NULL;
3615c2
     LDAPMessage *first;
3615c2
+    bool final_tktflags, has_local_tktpolicy = true;
3615c2
     int result;
3615c2
     int ret;
3615c2
 
3615c2
@@ -1112,12 +1247,18 @@ static krb5_error_code ipadb_fetch_tktpolicy(krb5_context kcontext,
3615c2
         return KRB5_KDB_DBNOTINITED;
3615c2
     }
3615c2
 
3615c2
+    kerr = are_final_tktflags(ipactx, entry, &final_tktflags);
3615c2
+    if (kerr)
3615c2
+        goto done;
3615c2
+
3615c2
     ret = ipadb_ldap_attr_to_str(ipactx->lcontext, lentry,
3615c2
                                  "krbticketpolicyreference", &policy_dn);
3615c2
     switch (ret) {
3615c2
     case 0:
3615c2
         break;
3615c2
     case ENOENT:
3615c2
+        /* If no principal ticket policy, fallback to the global one. */
3615c2
+        has_local_tktpolicy = false;
3615c2
         ret = asprintf(&policy_dn, "cn=%s,cn=kerberos,%s",
3615c2
                                    ipactx->realm, ipactx->base);
3615c2
         if (ret == -1) {
3615c2
@@ -1159,12 +1300,13 @@ static krb5_error_code ipadb_fetch_tktpolicy(krb5_context kcontext,
3615c2
                 }
3615c2
             }
3615c2
             if (polmask & TKTFLAGS_BIT) {
3615c2
-                ret = ipadb_ldap_attr_to_int(ipactx->lcontext, first,
3615c2
-                                             "krbticketflags", &result);
3615c2
-                if (ret == 0) {
3615c2
-                    entry->attributes |= result;
3615c2
-                } else {
3615c2
-                    entry->attributes |= maybe_require_preauth(ipactx, entry);
3615c2
+                /* If global ticket policy is being applied, set flags only if
3615c2
+                 * user principal ticket flags are not final. */
3615c2
+                if (has_local_tktpolicy || !final_tktflags) {
3615c2
+                    ret = ipadb_ldap_attr_to_int(ipactx->lcontext, first,
3615c2
+                                                 "krbticketflags", &result);
3615c2
+                    if (ret == 0)
3615c2
+                        entry->attributes |= result;
3615c2
                 }
3615c2
             }
3615c2
         }
3615c2
@@ -1179,13 +1321,27 @@ static krb5_error_code ipadb_fetch_tktpolicy(krb5_context kcontext,
3615c2
         if (polmask & MAXRENEWABLEAGE_BIT) {
3615c2
             entry->max_renewable_life = 604800;
3615c2
         }
3615c2
-        if (polmask & TKTFLAGS_BIT) {
3615c2
-            entry->attributes |= maybe_require_preauth(ipactx, entry);
3615c2
-        }
3615c2
 
3615c2
         kerr = 0;
3615c2
     }
3615c2
 
3615c2
+    if (polmask & TKTFLAGS_BIT) {
3615c2
+        /* If the principal ticket flags were applied, then flags from the
3615c2
+         * global ticket policy has to be applied atop of them if user principal
3615c2
+         * ticket flags are not final. */
3615c2
+        if (has_local_tktpolicy && !final_tktflags) {
3615c2
+            kerr = add_global_ticket_policy_flags(ipactx, NULL,
3615c2
+                                                  &entry->attributes);
3615c2
+            if (kerr)
3615c2
+            goto done;
3615c2
+        }
3615c2
+
3615c2
+        /* Virtual static ticket flags are set regardless of database content */
3615c2
+        kerr = add_virtual_static_tktflags(ipactx, entry, &entry->attributes);
3615c2
+        if (kerr)
3615c2
+            goto done;
3615c2
+    }
3615c2
+
3615c2
 done:
3615c2
     ldap_msgfree(res);
3615c2
     free(policy_dn);
3615c2
@@ -1512,6 +1668,36 @@ static void ipadb_mods_free_tip(struct ipadb_mods *imods)
3615c2
     imods->tip--;
3615c2
 }
3615c2
 
3615c2
+/* Use LDAP REPLACE operation to remove an attribute.
3615c2
+ * Contrary to the DELETE operation, it will not fail if the attribute does not
3615c2
+ * exist. */
3615c2
+static krb5_error_code
3615c2
+ipadb_ldap_replace_remove(struct ipadb_mods *imods, char *attribute)
3615c2
+{
3615c2
+    krb5_error_code kerr;
3615c2
+    LDAPMod *m = NULL;
3615c2
+
3615c2
+    kerr = ipadb_mods_new(imods, &m);
3615c2
+    if (kerr)
3615c2
+        return kerr;
3615c2
+
3615c2
+    m->mod_op = LDAP_MOD_REPLACE;
3615c2
+    m->mod_type = strdup(attribute);
3615c2
+    if (!m->mod_type) {
3615c2
+        kerr = ENOMEM;
3615c2
+        goto end;
3615c2
+    }
3615c2
+
3615c2
+    m->mod_values = NULL;
3615c2
+
3615c2
+    kerr = 0;
3615c2
+
3615c2
+end:
3615c2
+    if (kerr)
3615c2
+        ipadb_mods_free_tip(imods);
3615c2
+    return kerr;
3615c2
+}
3615c2
+
3615c2
 static krb5_error_code ipadb_get_ldap_mod_str(struct ipadb_mods *imods,
3615c2
                                               char *attribute, char *value,
3615c2
                                               int mod_op)
3615c2
@@ -1923,6 +2109,93 @@ static krb5_error_code ipadb_get_ldap_mod_auth_ind(krb5_context kcontext,
3615c2
     return ret;
3615c2
 }
3615c2
 
3615c2
+static krb5_error_code
3615c2
+update_tktflags(krb5_context kcontext, struct ipadb_mods *imods,
3615c2
+                krb5_db_entry *entry, int mod_op)
3615c2
+{
3615c2
+    krb5_error_code kerr;
3615c2
+    struct ipadb_context *ipactx;
3615c2
+    struct ipadb_e_data *ied;
3615c2
+    bool final_tktflags;
3615c2
+    krb5_flags tktflags_mask;
3615c2
+    int tktflags;
3615c2
+
3615c2
+    ipactx = ipadb_get_context(kcontext);
3615c2
+    if (!ipactx) {
3615c2
+        kerr = KRB5_KDB_DBNOTINITED;
3615c2
+        goto end;
3615c2
+    }
3615c2
+
3615c2
+    if (ipactx->override_restrictions) {
3615c2
+        /* In IPA setup mode, IPA edata might not be available. In this mode,
3615c2
+         * ticket flags are written as they are provided. */
3615c2
+        tktflags = (int)entry->attributes;
3615c2
+    } else {
3615c2
+        kerr = ipadb_get_edata(entry, &ied;;
3615c2
+        if (kerr)
3615c2
+            goto end;
3615c2
+
3615c2
+        kerr = get_virtual_static_tktflags_mask(ipactx, entry, &tktflags_mask);
3615c2
+        if (kerr)
3615c2
+            goto end;
3615c2
+
3615c2
+        kerr = are_final_tktflags(ipactx, entry, &final_tktflags);
3615c2
+        if (kerr)
3615c2
+            goto end;
3615c2
+
3615c2
+        /* Flags from the global ticket policy are filtered out only if the user
3615c2
+         * principal flags are not final. */
3615c2
+        if (!final_tktflags) {
3615c2
+            krb5_flags gbl_tktflags = 0;
3615c2
+
3615c2
+            kerr = add_global_ticket_policy_flags(ipactx, NULL, &gbl_tktflags);
3615c2
+            if (kerr)
3615c2
+                goto end;
3615c2
+
3615c2
+            tktflags_mask &= ~gbl_tktflags;
3615c2
+        }
3615c2
+
3615c2
+        tktflags = (int)(entry->attributes & tktflags_mask);
3615c2
+
3615c2
+        if (LDAP_MOD_REPLACE == mod_op && ied && !ied->has_tktpolaux) {
3615c2
+            if (0 == tktflags) {
3615c2
+                /* No point initializing principal ticket policy if there are no
3615c2
+                 * flags left after filtering out virtual and global ticket
3615c2
+                 * policy ones. */
3615c2
+                kerr = 0;
3615c2
+                goto end;
3615c2
+            }
3615c2
+
3615c2
+            /* if the object does not have the krbTicketPolicyAux class
3615c2
+             * we need to add it or this will fail, only for modifications.
3615c2
+             * We always add this objectclass by default when doing an add
3615c2
+             * from scratch. */
3615c2
+            kerr = ipadb_get_ldap_mod_str(imods, "objectclass",
3615c2
+                                          "krbTicketPolicyAux", LDAP_MOD_ADD);
3615c2
+            if (kerr)
3615c2
+                goto end;
3615c2
+        }
3615c2
+    }
3615c2
+
3615c2
+    if (tktflags != 0) {
3615c2
+        kerr = ipadb_get_ldap_mod_int(imods, "krbTicketFlags", tktflags,
3615c2
+                                      mod_op);
3615c2
+        if (kerr)
3615c2
+            goto end;
3615c2
+    } else if (LDAP_MOD_REPLACE == mod_op) {
3615c2
+        /* If the principal is not being created, and there are no custom ticket
3615c2
+         * flags to be set, remove the "krbTicketFlags" attribute. */
3615c2
+        kerr = ipadb_ldap_replace_remove(imods, "krbTicketFlags");
3615c2
+        if (kerr)
3615c2
+            goto end;
3615c2
+    }
3615c2
+
3615c2
+    kerr = 0;
3615c2
+
3615c2
+end:
3615c2
+    return kerr;
3615c2
+}
3615c2
+
3615c2
 static krb5_error_code ipadb_entry_to_mods(krb5_context kcontext,
3615c2
                                            struct ipadb_mods *imods,
3615c2
                                            krb5_db_entry *entry,
3615c2
@@ -1998,36 +2271,9 @@ static krb5_error_code ipadb_entry_to_mods(krb5_context kcontext,
3615c2
 
3615c2
     /* KADM5_ATTRIBUTES */
3615c2
     if (entry->mask & KMASK_ATTRIBUTES) {
3615c2
-        /* if the object does not have the krbTicketPolicyAux class
3615c2
-         * we need to add it or this will fail, only for modifications.
3615c2
-         * We always add this objectclass by default when doing an add
3615c2
-         * from scratch. */
3615c2
-        if ((mod_op == LDAP_MOD_REPLACE) && entry->e_data) {
3615c2
-            struct ipadb_e_data *ied;
3615c2
-
3615c2
-            ied = (struct ipadb_e_data *)entry->e_data;
3615c2
-            if (ied->magic != IPA_E_DATA_MAGIC) {
3615c2
-                kerr = EINVAL;
3615c2
-                goto done;
3615c2
-            }
3615c2
-
3615c2
-            if (!ied->has_tktpolaux) {
3615c2
-                kerr = ipadb_get_ldap_mod_str(imods, "objectclass",
3615c2
-                                              "krbTicketPolicyAux",
3615c2
-                                              LDAP_MOD_ADD);
3615c2
-                if (kerr) {
3615c2
-                    goto done;
3615c2
-                }
3615c2
-            }
3615c2
-        }
3615c2
-
3615c2
-        kerr = ipadb_get_ldap_mod_int(imods,
3615c2
-                                      "krbTicketFlags",
3615c2
-                                      (int)entry->attributes,
3615c2
-                                      mod_op);
3615c2
-        if (kerr) {
3615c2
+        kerr = update_tktflags(kcontext, imods, entry, mod_op);
3615c2
+        if (kerr)
3615c2
             goto done;
3615c2
-        }
3615c2
     }
3615c2
 
3615c2
     /* KADM5_MAX_LIFE */
3615c2
diff --git a/util/ipa_krb5.c b/util/ipa_krb5.c
3615c2
index 1ba6d25ee..2e663c506 100644
3615c2
--- a/util/ipa_krb5.c
3615c2
+++ b/util/ipa_krb5.c
3615c2
@@ -38,6 +38,12 @@ const char *ipapwd_password_max_len_errmsg = \
3615c2
     TOSTR(IPAPWD_PASSWORD_MAX_LEN) \
3615c2
     " chars)!";
3615c2
 
3615c2
+/* Case-insensitive string values to by parsed as boolean true */
3615c2
+static const char *const conf_yes[] = {
3615c2
+    "y", "yes", "true", "t", "1", "on",
3615c2
+    NULL,
3615c2
+};
3615c2
+
3615c2
 /* Salt types */
3615c2
 #define KRB5P_SALT_SIZE 16
3615c2
 
3615c2
@@ -1237,3 +1243,15 @@ done:
3615c2
     }
3615c2
     return ret;
3615c2
 }
3615c2
+
3615c2
+bool ipa_krb5_parse_bool(const char *str)
3615c2
+{
3615c2
+    const char *const *p;
3615c2
+
3615c2
+    for (p = conf_yes; *p; p++) {
3615c2
+        if (!strcasecmp(*p, str))
3615c2
+            return true;
3615c2
+    }
3615c2
+
3615c2
+    return false;
3615c2
+}
3615c2
diff --git a/util/ipa_krb5.h b/util/ipa_krb5.h
3615c2
index 8392a85b6..ba371449e 100644
3615c2
--- a/util/ipa_krb5.h
3615c2
+++ b/util/ipa_krb5.h
3615c2
@@ -4,6 +4,7 @@
3615c2
 #include <krb5/krb5.h>
3615c2
 #include <kdb.h>
3615c2
 #include <syslog.h>
3615c2
+#include <stdbool.h>
3615c2
 
3615c2
 struct krb_key_salt {
3615c2
     krb5_enctype enctype;
3615c2
@@ -86,3 +87,7 @@ int create_keys(krb5_context krbctx,
3615c2
                 char **err_msg);
3615c2
 
3615c2
 int ipa_kstuples_to_string(krb5_key_salt_tuple *kst, int n_kst, char **str);
3615c2
+
3615c2
+/* Implement boolean string parsing function from MIT krb5:
3615c2
+ * src/lib/krb5/krb/libdef_parse.c:_krb5_conf_boolean() */
3615c2
+bool ipa_krb5_parse_bool(const char *str);
3615c2
-- 
3615c2
2.44.0
3615c2