/* * MIT Kerberos KDC database backend for FreeIPA * * Authors: Simo Sorce * * 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 . */ #include "ipa_kdb.h" #include "ipa_pwd.h" #include 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; case IPAPWD_POLICY_PWD_CONSECUTIVE: kerr = KADM5_PASS_Q_GENERIC; krb5_set_error_message(context, kerr, "Password has repeating characters"); break; case IPAPWD_POLICY_PWD_SEQUENCE: kerr = KADM5_PASS_Q_GENERIC; krb5_set_error_message(context, kerr, "Password contains a monotonic sequence"); break; case IPAPWD_POLICY_PWD_PALINDROME: kerr = KADM5_PASS_Q_GENERIC; krb5_set_error_message(context, kerr, "Password is a palindrome"); break; case IPAPWD_POLICY_PWD_USER: kerr = KADM5_PASS_Q_GENERIC; krb5_set_error_message(context, kerr, "Password contains the user name"); break; case IPAPWD_POLICY_PWD_DICT_WORD: kerr = KADM5_PASS_Q_DICT; krb5_set_error_message(context, kerr, "Password contains dictionary words"); 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); if (!ipactx) { return KRB5_KDB_DBNOTINITED; } ied = (struct ipadb_e_data *)db_entry->e_data; if (ied->magic != IPA_E_DATA_MAGIC) { return EINVAL; } if (strlen(passwd) > IPAPWD_PASSWORD_MAX_LEN) { krb5_set_error_message(context, E2BIG, "%s", ipapwd_password_max_len_errmsg); return E2BIG; } ied->passwd = strdup(passwd); if (!ied->passwd) { return ENOMEM; } kerr = ipadb_get_ipapwd_policy(ipactx, ied->pw_policy_dn, &ied->pol); if (kerr != 0) { return kerr; } ret = ipapwd_check_policy(ied->pol, passwd, ied->user, 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, krb5_key_salt_tuple *ks_tuple, int ks_tuple_count, char *passwd, int new_kvno, krb5_boolean keepold, krb5_db_entry *db_entry) { 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; int n_keys; krb5_key_data *tdata; int t_keys; int old_kvno; int ret; int i; ipactx = ipadb_get_context(context); if (!ipactx) { 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) { free(ied); 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) { new_kvno = old_kvno + 1; } pwd.data = passwd; pwd.length = strlen(passwd); /* detect if kadmin is just passing along the default set */ if (ks_tuple_count == ipactx->n_supp_encs) { for (i = 0; i < ks_tuple_count; i++) { if (ks_tuple[i].ks_enctype != ipactx->supp_encs[i].ks_enctype) break; if (ks_tuple[i].ks_salttype != ipactx->supp_encs[i].ks_salttype) break; } if (i == ks_tuple_count) { /* we got passed the default supported enctypes, replace with * the actual default enctypes to use */ ks_tuple = ipactx->def_encs; ks_tuple_count = ipactx->n_def_encs; } } /* We further filter supported enctypes to restrict to the list * we have in ldap */ kerr = filter_key_salt_tuples(context, ks_tuple, ks_tuple_count, ipactx->supp_encs, ipactx->n_supp_encs, &fks, &n_fks); if (kerr) { return kerr; } kerr = ipa_krb5_generate_key_data(context, db_entry->princ, pwd, new_kvno, master_key, n_fks, fks, &n_keys, &keys); free(fks); if (kerr) { return kerr; } if (keepold) { /* need to add the new keys to the old list */ t_keys = db_entry->n_key_data; tdata = realloc(db_entry->key_data, sizeof(krb5_key_data) * (t_keys + n_keys)); if (!tdata) { ipa_krb5_free_key_data(keys, n_keys); return ENOMEM; } db_entry->key_data = tdata; db_entry->n_key_data = t_keys + n_keys; for (i = 0; i < n_keys; i++) { db_entry->key_data[t_keys + i] = keys[i]; } free(keys); } else { ipa_krb5_free_key_data(db_entry->key_data, db_entry->n_key_data); db_entry->key_data = keys; db_entry->n_key_data = n_keys; } return 0; } /* * Check who actually changed the password, if it is not 'self' then * we need to expire it if it is a user principal. */ krb5_error_code ipadb_get_pwd_expiration(krb5_context context, krb5_db_entry *entry, struct ipadb_e_data *ied, time_t *expire_time) { krb5_error_code kerr; krb5_timestamp mod_time = 0; krb5_principal mod_princ = NULL; krb5_boolean truexp = true; if (ied->ipa_user) { kerr = krb5_dbe_lookup_mod_princ_data(context, entry, &mod_time, &mod_princ); if (kerr) { goto done; } /* If the mod principal is kadmind then we have to assume an actual * password change for now. Apparently kadmind does not properly pass * the actual user principal down when said user is performing a * password change */ if (mod_princ->length == 1 && strcmp(mod_princ->data[0].data, "kadmind") != 0) { truexp = krb5_principal_compare(context, mod_princ, entry->princ); } } if (truexp) { if (ied->pol) { if (ied->pol->max_pwd_life) { *expire_time = krb5_ts2tt(krb5_ts_incr( mod_time, ied->pol->max_pwd_life)); } else { *expire_time = 0; } } else { *expire_time = krb5_ts2tt(krb5_ts_incr( mod_time, IPAPWD_DEFAULT_PWDLIFE)); } } else { /* not 'self', so reset */ *expire_time = krb5_ts2tt(mod_time); } kerr = 0; done: krb5_free_principal(context, mod_princ); return kerr; }