From e9e426354f28ca01586b1b5ef7130b19b392232b Mon Sep 17 00:00:00 2001 From: Simo Sorce Date: Wed, 1 Jun 2011 17:58:21 -0400 Subject: ipa-kdb: add functions to change principals --- daemons/ipa-kdb/Makefile.am | 5 + daemons/ipa-kdb/ipa_kdb.h | 36 ++ daemons/ipa-kdb/ipa_kdb_principals.c | 764 ++++++++++++++++++++++++++++++++++- 3 files changed, 804 insertions(+), 1 deletion(-) (limited to 'daemons') diff --git a/daemons/ipa-kdb/Makefile.am b/daemons/ipa-kdb/Makefile.am index 2b97a93d3..a632bb01c 100644 --- a/daemons/ipa-kdb/Makefile.am +++ b/daemons/ipa-kdb/Makefile.am @@ -1,8 +1,12 @@ NULL = +KRB5_UTIL_DIR = ../../util +KRB5_UTIL_SRCS = $(KRB5_UTIL_DIR)/ipa_krb5.c + INCLUDES = \ -I. \ -I$(srcdir) \ + -I$(KRB5_UTIL_DIR) \ -DPREFIX=\""$(prefix)"\" \ -DBINDIR=\""$(bindir)"\" \ -DLIBDIR=\""$(libdir)"\" \ @@ -25,6 +29,7 @@ ipadb_la_SOURCES = \ ipa_kdb.c \ ipa_kdb_common.c \ ipa_kdb_principals.c \ + $(KRB5_UTIL_SRCS) \ $(NULL) ipadb_la_LDFLAGS = \ diff --git a/daemons/ipa-kdb/ipa_kdb.h b/daemons/ipa-kdb/ipa_kdb.h index 7e20f98ab..26c6a3df6 100644 --- a/daemons/ipa-kdb/ipa_kdb.h +++ b/daemons/ipa-kdb/ipa_kdb.h @@ -24,6 +24,12 @@ #define _GNU_SOURCE 1 #endif +/* although we have nothing to do with SECURID yet, there are a + * couple of TL_DATA Ids that need it to be available. + * We need them to be avilable even if SECURID is not used for + * filtering purposes */ +#define SECURID 1 + #include #include #include @@ -34,6 +40,36 @@ #include #include +#include "ipa_krb5.h" + +/* easier to copy the defines here than to mess with kadm5/admin.h + * for now */ +#define KMASK_PRINCIPAL 0x000001 +#define KMASK_PRINC_EXPIRE_TIME 0x000002 +#define KMASK_PW_EXPIRATION 0x000004 +#define KMASK_LAST_PWD_CHANGE 0x000008 +#define KMASK_ATTRIBUTES 0x000010 +#define KMASK_MAX_LIFE 0x000020 +#define KMASK_MOD_TIME 0x000040 +#define KMASK_MOD_NAME 0x000080 +#define KMASK_KVNO 0x000100 +#define KMASK_MKVNO 0x000200 +#define KMASK_AUX_ATTRIBUTES 0x000400 +#define KMASK_POLICY 0x000800 +#define KMASK_POLICY_CLR 0x001000 +/* version 2 masks */ +#define KMASK_MAX_RLIFE 0x002000 +#define KMASK_LAST_SUCCESS 0x004000 +#define KMASK_LAST_FAILED 0x008000 +#define KMASK_FAIL_AUTH_COUNT 0x010000 +#define KMASK_KEY_DATA 0x020000 +#define KMASK_TL_DATA 0x040000 +#define KMASK_LOAD 0x200000 + +/* MIT Kerberos sanctioned hack to carry private data around. + * In krb5 1.10 this should be superceeded by a better mechanism */ +#define KDB_TL_USER_INFO 0x7ffe + struct ipadb_context { char *uri; char *base; diff --git a/daemons/ipa-kdb/ipa_kdb_principals.c b/daemons/ipa-kdb/ipa_kdb_principals.c index d6a816f96..bd84f5b3b 100644 --- a/daemons/ipa-kdb/ipa_kdb_principals.c +++ b/daemons/ipa-kdb/ipa_kdb_principals.c @@ -66,6 +66,16 @@ static char *std_tktpolicy_attrs[] = { #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) + static int ipadb_ldap_attr_to_tl_data(LDAP *lcontext, LDAPMessage *le, char *attrname, krb5_tl_data **result, int *num) @@ -855,11 +865,763 @@ void ipadb_free_principal(krb5_context kcontext, krb5_db_entry *entry) } } +static 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--; +} + +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 = (time_t)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 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 (data->tl_data_type == KRB5_TL_LAST_PWD_CHANGE || + data->tl_data_type == KRB5_TL_KADM_DATA || + data->tl_data_type == KRB5_TL_DB_ARGS || + data->tl_data_type == KRB5_TL_MKVNO || + data->tl_data_type == KRB5_TL_LAST_ADMIN_UNLOCK || + data->tl_data_type == KDB_TL_USER_INFO) { + 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 (data->tl_data_type == KRB5_TL_LAST_PWD_CHANGE || + data->tl_data_type == KRB5_TL_KADM_DATA || + data->tl_data_type == KRB5_TL_DB_ARGS || + data->tl_data_type == KRB5_TL_MKVNO || + data->tl_data_type == KRB5_TL_LAST_ADMIN_UNLOCK || + data->tl_data_type == KDB_TL_USER_INFO) { + 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; + int ret; + + 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_entry_to_mods(struct ipadb_mods *imods, + krb5_db_entry *entry, + char *principal, + int mod_op) +{ + krb5_error_code kerr; + krb5_int32 time32le; + int mkvno; + + /* check each mask flag in order */ + + /* KADM5_PRINCIPAL */ + if (entry->mask & KMASK_PRINCIPAL) { + kerr = ipadb_get_ldap_mod_str(imods, "krbPrincipalName", + principal, mod_op); + if (kerr) { + goto done; + } + } + + /* 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 (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 always check for it regardless. */ +#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) { +#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 = ipadb_get_ldap_mod_int(imods, + "krbTicketFlags", + (int)entry->attributes, + 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; + } + } + + /* 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 = ipadb_get_ldap_mod_extra_data(imods, + entry->tl_data, + mod_op); + if (kerr && kerr != ENOENT) { + goto done; + } + } + + /* KADM5_LOAD */ + + kerr = 0; + +done: + 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; + int 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; + } + + 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_to_mods(imods, entry, principal, LDAP_MOD_ADD); + if (kerr != 0) { + goto done; + } + + kerr = ipadb_entry_default_attrs(imods); + 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; + + ipactx = ipadb_get_context(kcontext); + if (!ipactx) { + return KRB5_KDB_DBNOTINITED; + } + + kerr = krb5_unparse_name(kcontext, entry->princ, &principal); + if (kerr != 0) { + goto done; + } + + kerr = ipadb_fetch_principals(ipactx, 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 = new_ipadb_mods(&imods); + if (kerr) { + goto done; + } + + kerr = ipadb_entry_to_mods(imods, entry, principal, LDAP_MOD_REPLACE); + if (kerr != 0) { + goto done; + } + + kerr = ipadb_simple_modify(ipactx, dn, imods->mods); + +done: + ipadb_mods_free(imods); + ldap_msgfree(res); + krb5_free_unparsed_name(kcontext, principal); + ldap_memfree(dn); + return kerr; +} + krb5_error_code ipadb_put_principal(krb5_context kcontext, krb5_db_entry *entry, char **db_args) { - return KRB5_PLUGIN_OP_NOTSUPP; + 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, -- cgit