diff options
-rw-r--r-- | daemons/ipa-kdb/Makefile.am | 3 | ||||
-rw-r--r-- | daemons/ipa-kdb/ipa_kdb.h | 12 | ||||
-rw-r--r-- | daemons/ipa-kdb/ipa_kdb_passwords.c | 169 | ||||
-rw-r--r-- | daemons/ipa-kdb/ipa_kdb_principals.c | 171 |
4 files changed, 347 insertions, 8 deletions
diff --git a/daemons/ipa-kdb/Makefile.am b/daemons/ipa-kdb/Makefile.am index c3369593..036074f4 100644 --- a/daemons/ipa-kdb/Makefile.am +++ b/daemons/ipa-kdb/Makefile.am @@ -1,7 +1,8 @@ NULL = KRB5_UTIL_DIR = ../../util -KRB5_UTIL_SRCS = $(KRB5_UTIL_DIR)/ipa_krb5.c +KRB5_UTIL_SRCS = $(KRB5_UTIL_DIR)/ipa_krb5.c \ + $(KRB5_UTIL_DIR)/ipa_pwd.c INCLUDES = \ -I. \ diff --git a/daemons/ipa-kdb/ipa_kdb.h b/daemons/ipa-kdb/ipa_kdb.h index 7dc88052..78746918 100644 --- a/daemons/ipa-kdb/ipa_kdb.h +++ b/daemons/ipa-kdb/ipa_kdb.h @@ -41,6 +41,7 @@ #include <endian.h> #include "ipa_krb5.h" +#include "ipa_pwd.h" /* easier to copy the defines here than to mess with kadm5/admin.h * for now */ @@ -80,6 +81,17 @@ struct ipadb_context { int n_supp_encs; }; +#define IPA_E_DATA_MAGIC 0x0eda7a +struct ipadb_e_data { + int magic; + bool ipa_user; + char *passwd; + time_t last_pwd_change; + char *pw_policy_dn; + char **pw_history; + struct ipapwd_policy pol; +}; + struct ipadb_context *ipadb_get_context(krb5_context kcontext); int ipadb_get_connection(struct ipadb_context *ipactx); diff --git a/daemons/ipa-kdb/ipa_kdb_passwords.c b/daemons/ipa-kdb/ipa_kdb_passwords.c index 9c927c26..ab58057a 100644 --- a/daemons/ipa-kdb/ipa_kdb_passwords.c +++ b/daemons/ipa-kdb/ipa_kdb_passwords.c @@ -21,6 +21,147 @@ */ #include "ipa_kdb.h" +#include "ipa_pwd.h" +#include <kadm5/kadm_err.h> + +static char *pw_policy_attrs[] = { + "krbmaxpwdlife", + "krbminpwdlife", + "krbpwdmindiffchars", + "krbpwdminlength", + "krbpwdhistorylength", + + NULL +}; + +static krb5_error_code ipadb_get_pw_policy(struct ipadb_context *ipactx, + char *pw_policy_dn, + struct ipapwd_policy *pol) +{ + krb5_error_code kerr; + LDAPMessage *res = NULL; + LDAPMessage *lentry; + uint32_t result; + int ret; + + kerr = ipadb_simple_search(ipactx, pw_policy_dn, LDAP_SCOPE_BASE, + "(objectClass=*)", pw_policy_attrs, &res); + if (kerr) { + goto done; + } + + lentry = ldap_first_entry(ipactx->lcontext, res); + if (!lentry) { + kerr = KRB5_KDB_INTERNAL_ERROR; + goto done; + } + + ret = ipadb_ldap_attr_to_uint32(ipactx->lcontext, lentry, + "krbMinPwdLife", &result); + if (ret == 0) { + pol->min_pwd_life = result; + } + + ret = ipadb_ldap_attr_to_uint32(ipactx->lcontext, lentry, + "krbMaxPwdLife", &result); + if (ret == 0) { + pol->max_pwd_life = result; + } + + ret = ipadb_ldap_attr_to_uint32(ipactx->lcontext, lentry, + "krbPwdMinLength", &result); + if (ret == 0) { + pol->min_pwd_length = result; + } + + ret = ipadb_ldap_attr_to_uint32(ipactx->lcontext, lentry, + "krbPwdHistoryLength", &result); + if (ret == 0) { + pol->history_length = result; + } + + ret = ipadb_ldap_attr_to_uint32(ipactx->lcontext, lentry, + "krbPwdMinDiffChars", &result); + if (ret == 0) { + pol->min_complexity = result; + } + +done: + ldap_msgfree(res); + return kerr; +} + +static krb5_error_code ipapwd_error_to_kerr(krb5_context context, + enum ipapwd_error err) +{ + krb5_error_code kerr; + + switch(err) { + case IPAPWD_POLICY_OK: + kerr = 0; + break; + case IPAPWD_POLICY_ACCOUNT_EXPIRED: + kerr = KADM5_BAD_PRINCIPAL; + krb5_set_error_message(context, kerr, "Account expired"); + break; + case IPAPWD_POLICY_PWD_TOO_YOUNG: + kerr = KADM5_PASS_TOOSOON; + krb5_set_error_message(context, kerr, "Too soon to change password"); + break; + case IPAPWD_POLICY_PWD_TOO_SHORT: + kerr = KADM5_PASS_Q_TOOSHORT; + krb5_set_error_message(context, kerr, "Password is too short"); + break; + case IPAPWD_POLICY_PWD_IN_HISTORY: + kerr = KADM5_PASS_REUSE; + krb5_set_error_message(context, kerr, "Password reuse not permitted"); + break; + case IPAPWD_POLICY_PWD_COMPLEXITY: + kerr = KADM5_PASS_Q_CLASS; + krb5_set_error_message(context, kerr, "Password is too simple"); + break; + default: + kerr = KADM5_PASS_Q_GENERIC; + break; + } + + return kerr; +} + +static krb5_error_code ipadb_check_pw_policy(krb5_context context, + char *passwd, + krb5_db_entry *db_entry) +{ + krb5_error_code kerr; + struct ipadb_e_data *ied; + struct ipadb_context *ipactx; + int ret; + + ipactx = ipadb_get_context(context); + + ied = (struct ipadb_e_data *)db_entry->e_data; + if (ied->magic != IPA_E_DATA_MAGIC) { + return EINVAL; + } + + ied->passwd = strdup(passwd); + if (!ied->passwd) { + return ENOMEM; + } + + ied->pol.max_pwd_life = IPAPWD_DEFAULT_PWDLIFE; + ied->pol.min_pwd_length = IPAPWD_DEFAULT_MINLEN; + kerr = ipadb_get_pw_policy(ipactx, ied->pw_policy_dn, &ied->pol); + if (kerr != 0) { + return kerr; + } + ret = ipapwd_check_policy(&ied->pol, passwd, time(NULL), + db_entry->expiration, + db_entry->pw_expiration, + ied->last_pwd_change, + ied->pw_history); + return ipapwd_error_to_kerr(context, ret); +} krb5_error_code ipadb_change_pwd(krb5_context context, krb5_keyblock *master_key, @@ -32,6 +173,7 @@ krb5_error_code ipadb_change_pwd(krb5_context context, krb5_error_code kerr; krb5_data pwd; struct ipadb_context *ipactx; + struct ipadb_e_data *ied; krb5_key_salt_tuple *fks = NULL; int n_fks; krb5_key_data *keys = NULL; @@ -39,6 +181,7 @@ krb5_error_code ipadb_change_pwd(krb5_context context, krb5_key_data *tdata; int t_keys; int old_kvno; + int ret; int i; ipactx = ipadb_get_context(context); @@ -46,6 +189,32 @@ krb5_error_code ipadb_change_pwd(krb5_context context, return KRB5_KDB_DBNOTINITED; } + if (!db_entry->e_data) { + if (!ipactx->override_restrictions) { + return EINVAL; + } else { + /* kadmin is creating a new principal */ + ied = calloc(1, sizeof(struct ipadb_e_data)); + if (!ied) { + return ENOMEM; + } + ied->magic = IPA_E_DATA_MAGIC; + /* set the default policy on new entries */ + ret = asprintf(&ied->pw_policy_dn, + "cn=global_policy,%s", ipactx->realm_base); + if (ret == -1) { + return ENOMEM; + } + db_entry->e_data = (krb5_octet *)ied; + } + } + + /* check pwd policy before doing any other work */ + kerr = ipadb_check_pw_policy(context, passwd, db_entry); + if (kerr) { + return kerr; + } + old_kvno = krb5_db_get_key_data_kvno(context, db_entry->n_key_data, db_entry->key_data); if (old_kvno >= new_kvno) { diff --git a/daemons/ipa-kdb/ipa_kdb_principals.c b/daemons/ipa-kdb/ipa_kdb_principals.c index 993f2981..8e1d4218 100644 --- a/daemons/ipa-kdb/ipa_kdb_principals.c +++ b/daemons/ipa-kdb/ipa_kdb_principals.c @@ -49,7 +49,9 @@ static char *std_principal_attrs[] = { "krbMaxTicketLife", "krbMaxRenewableAge", + /* IPA SPECIFIC ATTRIBUTES */ "nsaccountlock", + "passwordHistory", NULL }; @@ -343,12 +345,15 @@ static krb5_error_code ipadb_parse_ldap_entry(krb5_context kcontext, krb5_db_entry **kentry, uint32_t *polmask) { + struct ipadb_context *ipactx; 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; time_t restime; bool resbool; @@ -363,7 +368,8 @@ static krb5_error_code ipadb_parse_ldap_entry(krb5_context kcontext, /* proceed to fill in attributes in the order they are defined in * krb5_db_entry in kdb.h */ - lcontext = (ipadb_get_context(kcontext))->lcontext; + ipactx = ipadb_get_context(kcontext); + lcontext = ipactx->lcontext; entry->magic = KRB5_KDB_MAGIC_NUMBER; entry->len = KRB5_KDB_V1_BASE_LENGTH; @@ -525,12 +531,12 @@ static krb5_error_code ipadb_parse_ldap_entry(krb5_context kcontext, } ret = ipadb_ldap_attr_to_time_t(lcontext, lentry, - "krbLastPwdChange", &restime); + "krbLastAdminUnlock", &restime); if (ret == 0) { krb5_int32 time32le = htole32((krb5_int32)restime); kerr = ipadb_set_tl_data(entry, - KRB5_TL_LAST_PWD_CHANGE, + KRB5_TL_LAST_ADMIN_UNLOCK, sizeof(time32le), (krb5_octet *)&time32le); if (kerr) { @@ -538,21 +544,70 @@ static krb5_error_code ipadb_parse_ldap_entry(krb5_context kcontext, } } + ied = calloc(1, sizeof(struct ipadb_e_data)); + if (!ied) { + kerr = ENOMEM; + goto done; + } + ied->magic = IPA_E_DATA_MAGIC; + + /* mark this as an ipa_user if it has the posixaccount objectclass */ + ret = ipadb_ldap_attr_has_value(lcontext, lentry, + "objectClass", "posixAccount"); + if (ret != 0 && ret != ENOENT) { + kerr = ret; + goto done; + } + if (ret == 0) { + ied->ipa_user = true; + } + + ret = ipadb_ldap_attr_to_str(lcontext, lentry, + "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; + + 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_time_t(lcontext, lentry, - "krbLastAdminUnlock", &restime); + "krbLastPwdChange", &restime); if (ret == 0) { krb5_int32 time32le = htole32((krb5_int32)restime); kerr = ipadb_set_tl_data(entry, - KRB5_TL_LAST_ADMIN_UNLOCK, + KRB5_TL_LAST_PWD_CHANGE, sizeof(time32le), (krb5_octet *)&time32le); if (kerr) { goto done; } + + ied->last_pwd_change = restime; } - /* FIXME: fetch and se policy via krbpwdpolicyreference or fallback */ + entry->e_data = (krb5_octet *)ied; kerr = 0; @@ -843,10 +898,11 @@ done: void ipadb_free_principal(krb5_context kcontext, krb5_db_entry *entry) { + struct ipadb_e_data *ied; krb5_tl_data *prev, *next; + int i; if (entry) { - free(entry->e_data); krb5_free_principal(kcontext, entry->princ); prev = entry->tl_data; while(prev) { @@ -856,6 +912,20 @@ void ipadb_free_principal(krb5_context kcontext, krb5_db_entry *entry) prev = next; } ipa_krb5_free_key_data(entry->key_data, entry->n_key_data); + + if (entry->e_data) { + ied = (struct ipadb_e_data *)entry->e_data; + if (ied->magic == IPA_E_DATA_MAGIC) { + free(ied->passwd); + 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); + free(ied); + } + } + free(entry); } } @@ -1249,6 +1319,49 @@ done: 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_entry_to_mods(struct ipadb_mods *imods, krb5_db_entry *entry, char *principal, @@ -1448,6 +1561,50 @@ static krb5_error_code ipadb_entry_to_mods(struct ipadb_mods *imods, /* KADM5_LOAD */ + /* Store saved password if any and password history */ + if (entry->e_data) { + struct ipadb_e_data *ied; + time_t now = time(NULL); + char **new_history; + int nh_len; + int ret; + + 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; + } + } + + if (ied->ipa_user && ied->passwd && 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); + if (kerr) { + goto done; + } + } + } + kerr = 0; done: |