summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimo Sorce <ssorce@redhat.com>2011-07-01 13:33:28 -0400
committerSimo Sorce <ssorce@redhat.com>2011-08-26 08:24:50 -0400
commit0d048d7b49269853cd2a9c9b997c94717f05cf41 (patch)
treec6b011ee642dc8d83a7d1f55c4083be1396b6fd7
parent7ea0b5d56e2b157d7bcc15610d076e44f02beeda (diff)
downloadfreeipa-0d048d7b49269853cd2a9c9b997c94717f05cf41.tar.gz
freeipa-0d048d7b49269853cd2a9c9b997c94717f05cf41.tar.xz
freeipa-0d048d7b49269853cd2a9c9b997c94717f05cf41.zip
ipa-kdb: add password policy support
Use default policy for new principals created by kadmin
-rw-r--r--daemons/ipa-kdb/Makefile.am3
-rw-r--r--daemons/ipa-kdb/ipa_kdb.h12
-rw-r--r--daemons/ipa-kdb/ipa_kdb_passwords.c169
-rw-r--r--daemons/ipa-kdb/ipa_kdb_principals.c171
4 files changed, 347 insertions, 8 deletions
diff --git a/daemons/ipa-kdb/Makefile.am b/daemons/ipa-kdb/Makefile.am
index c33695933..036074f43 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 7dc880527..78746918a 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 9c927c265..ab58057a1 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 993f2981d..8e1d42185 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: