summaryrefslogtreecommitdiff
path: root/src/plugins/kdb/ldap/libkdb_ldap/ldap_misc.c
diff options
context:
space:
mode:
authorCy Schubert <cy@FreeBSD.org>2017-07-07 17:03:42 +0000
committerCy Schubert <cy@FreeBSD.org>2017-07-07 17:03:42 +0000
commit33a9b234e7087f573ef08cd7318c6497ba08b439 (patch)
treed0ea40ad3bf5463a3c55795977c71bcb7d781b4b /src/plugins/kdb/ldap/libkdb_ldap/ldap_misc.c
Diffstat (limited to 'src/plugins/kdb/ldap/libkdb_ldap/ldap_misc.c')
-rw-r--r--src/plugins/kdb/ldap/libkdb_ldap/ldap_misc.c1745
1 files changed, 1745 insertions, 0 deletions
diff --git a/src/plugins/kdb/ldap/libkdb_ldap/ldap_misc.c b/src/plugins/kdb/ldap/libkdb_ldap/ldap_misc.c
new file mode 100644
index 000000000000..32efc4f54ad0
--- /dev/null
+++ b/src/plugins/kdb/ldap/libkdb_ldap/ldap_misc.c
@@ -0,0 +1,1745 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* plugins/kdb/ldap/libkdb_ldap/ldap_misc.c */
+/*
+ * Copyright (c) 2004-2005, Novell, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * The copyright holder's name is not used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/*
+ * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+#include "kdb_ldap.h"
+#include "ldap_misc.h"
+#include "ldap_handle.h"
+#include "ldap_err.h"
+#include "ldap_principal.h"
+#include "princ_xdr.h"
+#include "ldap_pwd_policy.h"
+#include <time.h>
+#include <ctype.h>
+#include <kadm5/admin.h>
+
+#ifdef NEED_STRPTIME_PROTO
+extern char *strptime(const char *, const char *, struct tm *);
+#endif
+
+static void remove_overlapping_subtrees(char **listin, int *subtcount,
+ int sscope);
+
+/* Set an extended error message about being unable to read name. */
+static krb5_error_code
+attr_read_error(krb5_context ctx, krb5_error_code code, const char *name)
+{
+ k5_setmsg(ctx, code, _("Error reading '%s' attribute: %s"), name,
+ error_message(code));
+ return code;
+}
+
+/* Get integer or string values from the config section, falling back to the
+ * default section, then to hard-coded values. */
+static krb5_error_code
+prof_get_integer_def(krb5_context ctx, const char *conf_section,
+ const char *name, int dfl, krb5_ui_4 *out)
+{
+ krb5_error_code ret;
+ int val;
+
+ ret = profile_get_integer(ctx->profile, KDB_MODULE_SECTION, conf_section,
+ name, 0, &val);
+ if (ret)
+ return attr_read_error(ctx, ret, name);
+ if (val != 0) {
+ *out = val;
+ return 0;
+ }
+ ret = profile_get_integer(ctx->profile, KDB_MODULE_DEF_SECTION, name, NULL,
+ dfl, &val);
+ if (ret)
+ return attr_read_error(ctx, ret, name);
+ *out = val;
+ return 0;
+}
+
+/* Get integer or string values from the config section, falling back to the
+ * default section, then to hard-coded values. */
+static krb5_error_code
+prof_get_boolean_def(krb5_context ctx, const char *conf_section,
+ const char *name, krb5_boolean dfl, krb5_boolean *out)
+{
+ krb5_error_code ret;
+ int val = 0;
+
+ ret = profile_get_boolean(ctx->profile, KDB_MODULE_SECTION, conf_section,
+ name, -1, &val);
+ if (ret)
+ return attr_read_error(ctx, ret, name);
+ if (val != -1) {
+ *out = val;
+ return 0;
+ }
+ ret = profile_get_boolean(ctx->profile, KDB_MODULE_DEF_SECTION, name, NULL,
+ dfl, &val);
+ if (ret)
+ return attr_read_error(ctx, ret, name);
+ *out = val;
+ return 0;
+}
+
+/* We don't have non-null defaults in any of our calls, so don't bother with
+ * the extra argument. */
+static krb5_error_code
+prof_get_string_def(krb5_context ctx, const char *conf_section,
+ const char *name, char **out)
+{
+ krb5_error_code ret;
+
+ ret = profile_get_string(ctx->profile, KDB_MODULE_SECTION, conf_section,
+ name, NULL, out);
+ if (ret)
+ return attr_read_error(ctx, ret, name);
+ if (*out != NULL)
+ return 0;
+ ret = profile_get_string(ctx->profile, KDB_MODULE_DEF_SECTION, name, NULL,
+ NULL, out);
+ if (ret)
+ return attr_read_error(ctx, ret, name);
+ return 0;
+}
+
+static krb5_error_code
+get_db_opt(const char *input, char **opt_out, char **val_out)
+{
+ krb5_error_code ret;
+ const char *pos;
+ char *opt, *val = NULL;
+ size_t len;
+
+ *opt_out = *val_out = NULL;
+ pos = strchr(input, '=');
+ if (pos == NULL) {
+ opt = strdup(input);
+ if (opt == NULL)
+ return ENOMEM;
+ } else {
+ len = pos - input;
+ /* Ignore trailing spaces. */
+ while (len > 0 && isspace((unsigned char)input[len - 1]))
+ len--;
+ opt = k5memdup0(input, len, &ret);
+ if (opt == NULL)
+ return ret;
+
+ pos++; /* Move past '='. */
+ while (isspace(*pos)) /* Ignore leading spaces. */
+ pos++;
+ if (*pos != '\0') {
+ val = strdup(pos);
+ if (val == NULL) {
+ free(opt);
+ return ENOMEM;
+ }
+ }
+ }
+ *opt_out = opt;
+ *val_out = val;
+ return 0;
+}
+
+static krb5_error_code
+add_server_entry(krb5_context context, const char *name)
+{
+ krb5_ldap_context *ctx = context->dal_handle->db_context;
+ krb5_ldap_server_info **sp, **list, *server;
+ size_t count = 0;
+
+ /* Allocate list space for the new entry and null terminator. */
+ for (sp = ctx->server_info_list; sp != NULL && *sp != NULL; sp++)
+ count++;
+ list = realloc(ctx->server_info_list, (count + 2) * sizeof(*list));
+ if (list == NULL)
+ return ENOMEM;
+ ctx->server_info_list = list;
+
+ server = calloc(1, sizeof(krb5_ldap_server_info));
+ if (server == NULL)
+ return ENOMEM;
+ server->server_status = NOTSET;
+ server->server_name = strdup(name);
+ if (server->server_name == NULL) {
+ free(server);
+ return ENOMEM;
+ }
+ list[count] = server;
+ list[count + 1] = NULL;
+ return 0;
+}
+
+krb5_error_code
+krb5_ldap_parse_db_params(krb5_context context, char **db_args)
+{
+ char *opt = NULL, *val = NULL;
+ krb5_error_code ret = 0;
+ krb5_ldap_context *ctx = context->dal_handle->db_context;
+
+ if (db_args == NULL)
+ return 0;
+ for (; *db_args != NULL; db_args++) {
+ ret = get_db_opt(*db_args, &opt, &val);
+ if (ret)
+ goto cleanup;
+
+ /* Check for options which don't require values. */
+ if (!strcmp(opt, "temporary")) {
+ /* "temporary" is passed by kdb5_util load without -update,
+ * which we don't support. */
+ ret = EINVAL;
+ k5_setmsg(context, ret, _("KDB module requires -update argument"));
+ goto cleanup;
+ }
+
+ if (val == NULL) {
+ ret = EINVAL;
+ k5_setmsg(context, ret, _("'%s' value missing"), opt);
+ goto cleanup;
+ }
+
+ /* Check for options which do require arguments. */
+ if (!strcmp(opt, "binddn")) {
+ free(ctx->bind_dn);
+ ctx->bind_dn = strdup(val);
+ if (ctx->bind_dn == NULL) {
+ ret = ENOMEM;
+ goto cleanup;
+ }
+ } else if (!strcmp(opt, "nconns")) {
+ ctx->max_server_conns = atoi(val) ? atoi(val) :
+ DEFAULT_CONNS_PER_SERVER;
+ } else if (!strcmp(opt, "bindpwd")) {
+ free(ctx->bind_pwd);
+ ctx->bind_pwd = strdup(val);
+ if (ctx->bind_pwd == NULL) {
+ ret = ENOMEM;
+ goto cleanup;
+ }
+ } else if (!strcmp(opt, "sasl_mech")) {
+ free(ctx->sasl_mech);
+ ctx->sasl_mech = strdup(val);
+ if (ctx->sasl_mech == NULL) {
+ ret = ENOMEM;
+ goto cleanup;
+ }
+ } else if (!strcmp(opt, "sasl_authcid")) {
+ free(ctx->sasl_authcid);
+ ctx->sasl_authcid = strdup(val);
+ if (ctx->sasl_authcid == NULL) {
+ ret = ENOMEM;
+ goto cleanup;
+ }
+ } else if (!strcmp(opt, "sasl_authzid")) {
+ free(ctx->sasl_authzid);
+ ctx->sasl_authzid = strdup(val);
+ if (ctx->sasl_authzid == NULL) {
+ ret = ENOMEM;
+ goto cleanup;
+ }
+ } else if (!strcmp(opt, "sasl_realm")) {
+ free(ctx->sasl_realm);
+ ctx->sasl_realm = strdup(val);
+ if (ctx->sasl_realm == NULL) {
+ ret = ENOMEM;
+ goto cleanup;
+ }
+ } else if (!strcmp(opt, "host")) {
+ ret = add_server_entry(context, val);
+ if (ret)
+ goto cleanup;
+ } else if (!strcmp(opt, "debug")) {
+ ctx->ldap_debug = atoi(val);
+ } else {
+ ret = EINVAL;
+ k5_setmsg(context, ret, _("unknown option '%s'"), opt);
+ goto cleanup;
+ }
+
+ free(opt);
+ free(val);
+ opt = val = NULL;
+ }
+
+cleanup:
+ free(opt);
+ free(val);
+ return ret;
+}
+
+/* Pick kdc_var or kadmind_var depending on the server type. */
+static inline const char *
+choose_var(int srv_type, const char *kdc_var, const char *kadmind_var)
+{
+ return (srv_type == KRB5_KDB_SRV_TYPE_KDC) ? kdc_var : kadmind_var;
+}
+
+/*
+ * This function reads the parameters from the krb5.conf file. The
+ * parameters read here are DAL-LDAP specific attributes. Some of
+ * these are ldap_server ....
+ */
+krb5_error_code
+krb5_ldap_read_server_params(krb5_context context, char *conf_section,
+ int srv_type)
+{
+ char *servers, *save_ptr, *item;
+ const char *delims = "\t\n\f\v\r ,", *name;
+ krb5_error_code ret = 0;
+ kdb5_dal_handle *dal_handle = context->dal_handle;
+ krb5_ldap_context *ldap_context = dal_handle->db_context;
+
+ /* copy the conf_section into ldap_context for later use */
+ if (conf_section != NULL) {
+ ldap_context->conf_section = strdup(conf_section);
+ if (ldap_context->conf_section == NULL)
+ return ENOMEM;
+ }
+
+ /* This mutex is used in the LDAP connection pool. */
+ if (k5_mutex_init(&(ldap_context->hndl_lock)) != 0)
+ return KRB5_KDB_SERVER_INTERNAL_ERR;
+
+ /* Read the maximum number of LDAP connections per server. */
+ if (ldap_context->max_server_conns == 0) {
+ ret = prof_get_integer_def(context, conf_section,
+ KRB5_CONF_LDAP_CONNS_PER_SERVER,
+ DEFAULT_CONNS_PER_SERVER,
+ &ldap_context->max_server_conns);
+ if (ret)
+ return ret;
+ }
+
+ if (ldap_context->max_server_conns < 2) {
+ k5_setmsg(context, EINVAL,
+ _("Minimum connections required per server is 2"));
+ return EINVAL;
+ }
+
+ /* Read the DN used to connect to the LDAP server. */
+ if (ldap_context->bind_dn == NULL) {
+ name = choose_var(srv_type, KRB5_CONF_LDAP_KDC_DN,
+ KRB5_CONF_LDAP_KADMIND_DN);
+ ret = prof_get_string_def(context, conf_section, name,
+ &ldap_context->bind_dn);
+ if (ret)
+ return ret;
+ }
+
+ /* Read the filename containing stashed DN passwords. */
+ if (ldap_context->service_password_file == NULL) {
+ ret = prof_get_string_def(context, conf_section,
+ KRB5_CONF_LDAP_SERVICE_PASSWORD_FILE,
+ &ldap_context->service_password_file);
+ if (ret)
+ return ret;
+ }
+
+ if (ldap_context->sasl_mech == NULL) {
+ name = choose_var(srv_type, KRB5_CONF_LDAP_KDC_SASL_MECH,
+ KRB5_CONF_LDAP_KADMIND_SASL_MECH);
+ ret = prof_get_string_def(context, conf_section, name,
+ &ldap_context->sasl_mech);
+ if (ret)
+ return ret;
+ }
+
+ if (ldap_context->sasl_authcid == NULL) {
+ name = choose_var(srv_type, KRB5_CONF_LDAP_KDC_SASL_AUTHCID,
+ KRB5_CONF_LDAP_KADMIND_SASL_AUTHCID);
+ ret = prof_get_string_def(context, conf_section, name,
+ &ldap_context->sasl_authcid);
+ if (ret)
+ return ret;
+ }
+
+ if (ldap_context->sasl_authzid == NULL) {
+ name = choose_var(srv_type, KRB5_CONF_LDAP_KDC_SASL_AUTHZID,
+ KRB5_CONF_LDAP_KADMIND_SASL_AUTHZID);
+ ret = prof_get_string_def(context, conf_section, name,
+ &ldap_context->sasl_authzid);
+ if (ret)
+ return ret;
+ }
+
+ if (ldap_context->sasl_realm == NULL) {
+ name = choose_var(srv_type, KRB5_CONF_LDAP_KDC_SASL_REALM,
+ KRB5_CONF_LDAP_KADMIND_SASL_REALM);
+ ret = prof_get_string_def(context, conf_section, name,
+ &ldap_context->sasl_realm);
+ if (ret)
+ return ret;
+ }
+
+ /* Read the LDAP server URL list. */
+ if (ldap_context->server_info_list == NULL) {
+ ret = profile_get_string(context->profile, KDB_MODULE_SECTION,
+ conf_section, KRB5_CONF_LDAP_SERVERS, NULL,
+ &servers);
+ if (ret)
+ return attr_read_error(context, ret, KRB5_CONF_LDAP_SERVERS);
+
+ if (servers == NULL) {
+ ret = add_server_entry(context, "ldapi://");
+ if (ret)
+ return ret;
+ } else {
+ item = strtok_r(servers, delims, &save_ptr);
+ while (item != NULL) {
+ ret = add_server_entry(context, item);
+ if (ret) {
+ profile_release_string(servers);
+ return ret;
+ }
+ item = strtok_r(NULL, delims, &save_ptr);
+ }
+ profile_release_string(servers);
+ }
+ }
+
+ ret = prof_get_boolean_def(context, conf_section,
+ KRB5_CONF_DISABLE_LAST_SUCCESS, FALSE,
+ &ldap_context->disable_last_success);
+ if (ret)
+ return ret;
+
+ return prof_get_boolean_def(context, conf_section,
+ KRB5_CONF_DISABLE_LOCKOUT, FALSE,
+ &ldap_context->disable_lockout);
+}
+
+void
+krb5_ldap_free_server_context_params(krb5_ldap_context *ctx)
+{
+ int i;
+ krb5_ldap_server_info **list;
+ krb5_ldap_server_handle *h, *next;
+
+ if (ctx == NULL)
+ return;
+
+ list = ctx->server_info_list;
+ for (i = 0; list != NULL && list[i] != NULL; i++) {
+ free(list[i]->server_name);
+ for (h = list[i]->ldap_server_handles; h != NULL; h = next) {
+ next = h->next;
+ ldap_unbind_ext_s(h->ldap_handle, NULL, NULL);
+ free(h);
+ }
+ free(list[i]);
+ }
+ free(list);
+ ctx->server_info_list = NULL;
+
+ free(ctx->sasl_mech);
+ free(ctx->sasl_authcid);
+ free(ctx->sasl_authzid);
+ free(ctx->sasl_realm);
+ free(ctx->conf_section);
+ free(ctx->bind_dn);
+ zapfreestr(ctx->bind_pwd);
+ free(ctx->service_password_file);
+ ctx->conf_section = ctx->bind_dn = ctx->bind_pwd = NULL;
+ ctx->service_password_file = NULL;
+}
+
+void
+krb5_ldap_free_server_params(krb5_ldap_context *ctx)
+{
+ if (ctx == NULL)
+ return;
+ krb5_ldap_free_server_context_params(ctx);
+ k5_mutex_destroy(&ctx->hndl_lock);
+ free(ctx);
+}
+
+/* Return true if princ is in the default realm of ldap_context or is a
+ * cross-realm TGS principal for that realm. */
+krb5_boolean
+is_principal_in_realm(krb5_ldap_context *ldap_context,
+ krb5_const_principal princ)
+{
+ const char *realm = ldap_context->lrparams->realm_name;
+
+ if (princ->length == 2 &&
+ data_eq_string(princ->data[0], "krbtgt") &&
+ data_eq_string(princ->data[1], realm))
+ return TRUE;
+ return data_eq_string(princ->realm, realm);
+}
+
+/*
+ * Deduce the subtree information from the context. A realm can have
+ * multiple subtrees.
+ * 1. the Realm container
+ * 2. the actual subtrees associated with the Realm
+ *
+ * However, there are some conditions to be considered to deduce the
+ * actual subtree/s associated with the realm. The conditions are as
+ * follows:
+ * 1. If the subtree information of the Realm is [Root] or NULL (that
+ * is internal a [Root]) then the realm has only one subtree
+ * i.e [Root], i.e. whole of the tree.
+ * 2. If the subtree information of the Realm is missing/absent, then the
+ * realm has only one, i.e., the Realm container. NOTE: In all cases
+ * Realm container SHOULD be the one among the subtrees or the only
+ * one subtree.
+ * 3. The subtree information of the realm is overlapping the realm
+ * container of the realm, then the realm has only one subtree and
+ * it is the subtree information associated with the realm.
+ */
+krb5_error_code
+krb5_get_subtree_info(krb5_ldap_context *ldap_context, char ***subtreearr,
+ unsigned int *ntree)
+{
+ krb5_error_code ret;
+ int subtreecount, count = 0, search_scope;
+ char **subtree, *realm_cont_dn, *containerref;
+ char **subtarr = NULL;
+
+ containerref = ldap_context->lrparams->containerref;
+ subtree = ldap_context->lrparams->subtree;
+ realm_cont_dn = ldap_context->lrparams->realmdn;
+ subtreecount = ldap_context->lrparams->subtreecount;
+ search_scope = ldap_context->lrparams->search_scope;
+
+ /* Leave space for realm DN, containerref, and null terminator. */
+ subtarr = k5calloc(subtreecount + 3, sizeof(char *), &ret);
+ if (subtarr == NULL)
+ goto cleanup;
+
+ /* Get the complete subtree list. */
+ while (count < subtreecount && subtree[count] != NULL) {
+ subtarr[count] = strdup(subtree[count]);
+ if (subtarr[count++] == NULL) {
+ ret = ENOMEM;
+ goto cleanup;
+ }
+ }
+
+ subtarr[count] = strdup(realm_cont_dn);
+ if (subtarr[count++] == NULL) {
+ ret = ENOMEM;
+ goto cleanup;
+ }
+
+ if (containerref != NULL) {
+ subtarr[count] = strdup(containerref);
+ if (subtarr[count++] == NULL) {
+ ret = ENOMEM;
+ goto cleanup;
+ }
+ }
+
+ remove_overlapping_subtrees(subtarr, &count, search_scope);
+ *ntree = count;
+ *subtreearr = subtarr;
+ subtarr = NULL;
+ count = 0;
+
+cleanup:
+ while (count > 0)
+ free(subtarr[--count]);
+ free(subtarr);
+ return ret;
+}
+
+/* Reallocate tl and return a pointer to the new space, or NULL on failure. */
+static unsigned char *
+expand_tl_data(krb5_tl_data *tl, uint16_t len)
+{
+ unsigned char *newptr;
+
+ if (len > UINT16_MAX - tl->tl_data_length)
+ return NULL;
+ newptr = realloc(tl->tl_data_contents, tl->tl_data_length + len);
+ if (newptr == NULL)
+ return NULL;
+ tl->tl_data_contents = newptr;
+ tl->tl_data_length += len;
+ return tl->tl_data_contents + tl->tl_data_length - len;
+}
+
+/* Append a one-byte type, a two-byte length, and a value to a KDB_TL_USER_INFO
+ * tl_data item. The length is inferred from type and value. */
+krb5_error_code
+store_tl_data(krb5_tl_data *tl, int type, void *value)
+{
+ unsigned char *ptr;
+ int ival;
+ char *str;
+ size_t len;
+
+ tl->tl_data_type = KDB_TL_USER_INFO;
+ switch (type) {
+ case KDB_TL_PRINCCOUNT:
+ case KDB_TL_PRINCTYPE:
+ case KDB_TL_MASK:
+ ival = *(int *)value;
+ if (ival > UINT16_MAX)
+ return EINVAL;
+ ptr = expand_tl_data(tl, 5);
+ if (ptr == NULL)
+ return ENOMEM;
+ *ptr = type;
+ store_16_be(2, ptr + 1);
+ store_16_be(ival, ptr + 3);
+ break;
+
+ case KDB_TL_USERDN:
+ case KDB_TL_LINKDN:
+ str = value;
+ len = strlen(str);
+ if (len > UINT16_MAX - 3)
+ return ENOMEM;
+ ptr = expand_tl_data(tl, 3 + len);
+ if (ptr == NULL)
+ return ENOMEM;
+ *ptr = type;
+ store_16_be(len, ptr + 1);
+ memcpy(ptr + 3, str, len);
+ break;
+
+ default:
+ return EINVAL;
+ }
+ return 0;
+}
+
+/* Scan tl for a value of the given type and return it in allocated memory.
+ * For KDB_TL_LINKDN, return a list of all values found. */
+static krb5_error_code
+decode_tl_data(krb5_tl_data *tl, int type, void **data_out)
+{
+ krb5_error_code ret;
+ const unsigned char *ptr, *end;
+ uint16_t len;
+ size_t linkcount = 0, i;
+ char **dnlist = NULL, **newlist;
+ int *intptr;
+
+ *data_out = NULL;
+
+ /* Find the first matching subfield or return ENOENT. For KDB_TL_LINKDN,
+ * keep iterating after finding a match as it may be repeated. */
+ ptr = tl->tl_data_contents;
+ end = ptr + tl->tl_data_length;
+ for (;;) {
+ if (end - ptr < 3)
+ break;
+ len = load_16_be(ptr + 1);
+ if (len > (end - ptr) - 3)
+ break;
+ if (*ptr != type) {
+ ptr += 3 + len;
+ continue;
+ }
+ ptr += 3;
+
+ switch (type) {
+ case KDB_TL_PRINCCOUNT:
+ case KDB_TL_PRINCTYPE:
+ case KDB_TL_MASK:
+ if (len != 2)
+ return EINVAL;
+ intptr = malloc(sizeof(int));
+ if (intptr == NULL)
+ return ENOMEM;
+ *intptr = load_16_be(ptr);
+ *data_out = intptr;
+ return 0;
+
+ case KDB_TL_USERDN:
+ *data_out = k5memdup0(ptr, len, &ret);
+ return ret;
+
+ case KDB_TL_LINKDN:
+ newlist = realloc(dnlist, (linkcount + 2) * sizeof(char *));
+ if (newlist == NULL)
+ goto oom;
+ dnlist = newlist;
+ dnlist[linkcount] = k5memdup0(ptr, len, &ret);
+ if (dnlist[linkcount] == NULL)
+ goto oom;
+ dnlist[linkcount + 1] = NULL;
+ linkcount++;
+ break;
+ }
+
+ ptr += len;
+ }
+
+ if (type != KDB_TL_LINKDN || dnlist == NULL)
+ return ENOENT;
+ *data_out = dnlist;
+ return 0;
+
+oom:
+ for (i = 0; i < linkcount; i++)
+ free(dnlist[i]);
+ free(dnlist);
+ return ENOMEM;
+}
+
+/*
+ * wrapper routines for decode_tl_data
+ */
+static krb5_error_code
+get_int_from_tl_data(krb5_context context, krb5_db_entry *entry, int type,
+ int *intval)
+{
+ krb5_error_code ret;
+ krb5_tl_data tl_data;
+ void *ptr;
+ int *intptr;
+
+ tl_data.tl_data_type = KDB_TL_USER_INFO;
+ ret = krb5_dbe_lookup_tl_data(context, entry, &tl_data);
+ if (ret || tl_data.tl_data_length == 0)
+ return ret;
+
+ if (decode_tl_data(&tl_data, type, &ptr) == 0) {
+ intptr = ptr;
+ *intval = *intptr;
+ free(intptr);
+ }
+
+ return 0;
+}
+
+/*
+ * Get the mask representing the attributes set on the directory
+ * object (user, policy ...).
+ */
+krb5_error_code
+krb5_get_attributes_mask(krb5_context context, krb5_db_entry *entry,
+ int *mask)
+{
+ return get_int_from_tl_data(context, entry, KDB_TL_MASK, mask);
+}
+
+krb5_error_code
+krb5_get_princ_type(krb5_context context, krb5_db_entry *entry, int *ptype)
+{
+ return get_int_from_tl_data(context, entry, KDB_TL_PRINCTYPE, ptype);
+}
+
+krb5_error_code
+krb5_get_princ_count(krb5_context context, krb5_db_entry *entry, int *pcount)
+{
+ return get_int_from_tl_data(context, entry, KDB_TL_PRINCCOUNT, pcount);
+}
+
+krb5_error_code
+krb5_get_linkdn(krb5_context context, krb5_db_entry *entry, char ***link_dn)
+{
+ krb5_error_code ret;
+ krb5_tl_data tl_data;
+ void *ptr;
+
+ *link_dn = NULL;
+ tl_data.tl_data_type = KDB_TL_USER_INFO;
+ ret = krb5_dbe_lookup_tl_data(context, entry, &tl_data);
+ if (ret || tl_data.tl_data_length == 0)
+ return ret;
+
+ if (decode_tl_data(&tl_data, KDB_TL_LINKDN, &ptr) == 0)
+ *link_dn = ptr;
+
+ return 0;
+}
+
+static krb5_error_code
+get_str_from_tl_data(krb5_context context, krb5_db_entry *entry, int type,
+ char **strval)
+{
+ krb5_error_code ret;
+ krb5_tl_data tl_data;
+ void *ptr;
+
+ if (type != KDB_TL_USERDN)
+ return EINVAL;
+
+ tl_data.tl_data_type = KDB_TL_USER_INFO;
+ ret = krb5_dbe_lookup_tl_data(context, entry, &tl_data);
+ if (ret || tl_data.tl_data_length == 0)
+ return ret;
+
+ if (decode_tl_data(&tl_data, type, &ptr) == 0)
+ *strval = ptr;
+
+ return 0;
+}
+
+/*
+ * Replace the relative DN component of dn with newrdn.
+ */
+krb5_error_code
+replace_rdn(krb5_context context, const char *dn, const char *newrdn,
+ char **newdn_out)
+{
+ krb5_error_code ret;
+ LDAPDN ldn = NULL;
+ LDAPRDN lrdn = NULL;
+ char *next;
+
+ *newdn_out = NULL;
+
+ ret = ldap_str2dn(dn, &ldn, LDAP_DN_FORMAT_LDAPV3);
+ if (ret != LDAP_SUCCESS || ldn[0] == NULL) {
+ ret = EINVAL;
+ goto cleanup;
+ }
+
+ ret = ldap_str2rdn(newrdn, &lrdn, &next, LDAP_DN_FORMAT_LDAPV3);
+ if (ret != LDAP_SUCCESS) {
+ ret = EINVAL;
+ goto cleanup;
+ }
+
+ ldap_rdnfree(ldn[0]);
+ ldn[0] = lrdn;
+ lrdn = NULL;
+
+ ret = ldap_dn2str(ldn, newdn_out, LDAP_DN_FORMAT_LDAPV3);
+ if (ret != LDAP_SUCCESS)
+ ret = KRB5_KDB_SERVER_INTERNAL_ERR;
+
+cleanup:
+ if (ldn != NULL)
+ ldap_dnfree(ldn);
+ if (lrdn != NULL)
+ ldap_rdnfree(lrdn);
+ return ret;
+}
+
+krb5_error_code
+krb5_get_userdn(krb5_context context, krb5_db_entry *entry, char **userdn)
+{
+ *userdn = NULL;
+ return get_str_from_tl_data(context, entry, KDB_TL_USERDN, userdn);
+}
+
+/*
+ * If attribute or attrvalues is NULL, just check for the existence of dn.
+ * Otherwise, read values for attribute from dn; then set the bit 1<<n in mask
+ * for each attrvalues[n] which is present in the values read.
+ */
+krb5_error_code
+checkattributevalue(LDAP *ld, char *dn, char *attribute, char **attrvalues,
+ int *mask)
+{
+ krb5_error_code ret;
+ int one = 1, i, j;
+ char **values = NULL, *attributes[2] = { NULL };
+ LDAPMessage *result = NULL, *entry;
+
+ if (strlen(dn) == 0)
+ return set_ldap_error(0, LDAP_NO_SUCH_OBJECT, OP_SEARCH);
+
+ attributes[0] = attribute;
+
+ /* Read values for attribute from the dn, or check for its existence. */
+ ret = ldap_search_ext_s(ld, dn, LDAP_SCOPE_BASE, 0, attributes, 0, NULL,
+ NULL, &timelimit, LDAP_NO_LIMIT, &result);
+ if (ret != LDAP_SUCCESS) {
+ ldap_msgfree(result);
+ return set_ldap_error(0, ret, OP_SEARCH);
+ }
+
+ /* Don't touch *mask if we are only checking for existence. */
+ if (attribute == NULL || attrvalues == NULL)
+ goto done;
+
+ *mask = 0;
+
+ entry = ldap_first_entry(ld, result);
+ if (entry == NULL)
+ goto done;
+ values = ldap_get_values(ld, entry, attribute);
+ if (values == NULL)
+ goto done;
+
+ /* Set bits in mask for each matching value we read. */
+ for (i = 0; attrvalues[i]; i++) {
+ for (j = 0; values[j]; j++) {
+ if (strcasecmp(attrvalues[i], values[j]) == 0) {
+ *mask |= (one << i);
+ break;
+ }
+ }
+ }
+
+done:
+ ldap_msgfree(result);
+ ldap_value_free(values);
+ return 0;
+}
+
+static krb5_error_code
+getepochtime(char *strtime, krb5_timestamp *epochtime)
+{
+ struct tm tme;
+
+ memset(&tme, 0, sizeof(tme));
+ if (strptime(strtime,"%Y%m%d%H%M%SZ", &tme) == NULL) {
+ *epochtime = 0;
+ return EINVAL;
+ }
+ *epochtime = krb5int_gmt_mktime(&tme);
+ return 0;
+}
+
+/* Get the integer value of attribute from int. If it is not found, return
+ * ENOENT and set *val_out to 0. */
+krb5_error_code
+krb5_ldap_get_value(LDAP *ld, LDAPMessage *ent, char *attribute, int *val_out)
+{
+ char **values;
+
+ *val_out = 0;
+ values = ldap_get_values(ld, ent, attribute);
+ if (values == NULL)
+ return ENOENT;
+ if (values[0] != NULL)
+ *val_out = atoi(values[0]);
+ ldap_value_free(values);
+ return 0;
+}
+
+/* Return the first string value of attribute in ent. */
+krb5_error_code
+krb5_ldap_get_string(LDAP *ld, LDAPMessage *ent, char *attribute,
+ char **str_out, krb5_boolean *attr_present)
+{
+ char **values;
+ krb5_error_code ret = 0;
+
+ *str_out = NULL;
+ if (attr_present != NULL)
+ *attr_present = FALSE;
+
+ values = ldap_get_values(ld, ent, attribute);
+ if (values == NULL)
+ return 0;
+ if (values[0] != NULL) {
+ if (attr_present != NULL)
+ *attr_present = TRUE;
+ *str_out = strdup(values[0]);
+ if (*str_out == NULL)
+ ret = ENOMEM;
+ }
+ ldap_value_free(values);
+ return ret;
+}
+
+static krb5_error_code
+get_time(LDAP *ld, LDAPMessage *ent, char *attribute, krb5_timestamp *time_out,
+ krb5_boolean *attr_present)
+{
+ char **values = NULL;
+ krb5_error_code ret = 0;
+
+ *time_out = 0;
+ *attr_present = FALSE;
+
+ values = ldap_get_values(ld, ent, attribute);
+ if (values == NULL)
+ return 0;
+ if (values[0] != NULL) {
+ *attr_present = TRUE;
+ ret = getepochtime(values[0], time_out);
+ }
+ ldap_value_free(values);
+ return ret;
+}
+
+/* Add an entry to *list_inout and return it in *mod_out. */
+static krb5_error_code
+alloc_mod(LDAPMod ***list_inout, LDAPMod **mod_out)
+{
+ size_t count;
+ LDAPMod **mods = *list_inout;
+
+ *mod_out = NULL;
+
+ for (count = 0; mods != NULL && mods[count] != NULL; count++);
+ mods = realloc(mods, (count + 2) * sizeof(*mods));
+ if (mods == NULL)
+ return ENOMEM;
+ *list_inout = mods;
+
+ mods[count] = calloc(1, sizeof(LDAPMod));
+ if (mods[count] == NULL)
+ return ENOMEM;
+ mods[count + 1] = NULL;
+ *mod_out = mods[count];
+ return 0;
+}
+
+krb5_error_code
+krb5_add_str_mem_ldap_mod(LDAPMod ***list, char *attribute, int op,
+ char **values)
+{
+ krb5_error_code ret;
+ LDAPMod *mod;
+ size_t count, i;
+
+ ret = alloc_mod(list, &mod);
+ if (ret)
+ return ret;
+
+ mod->mod_type = strdup(attribute);
+ if (mod->mod_type == NULL)
+ return ENOMEM;
+ mod->mod_op = op;
+
+ mod->mod_values = NULL;
+ if (values == NULL)
+ return 0;
+
+ for (count = 0; values[count] != NULL; count++);
+ mod->mod_values = calloc(count + 1, sizeof(char *));
+ if (mod->mod_values == NULL)
+ return ENOMEM;
+
+ for (i = 0; i < count; i++) {
+ mod->mod_values[i] = strdup(values[i]);
+ if (mod->mod_values[i] == NULL)
+ return ENOMEM;
+ }
+ mod->mod_values[i] = NULL;
+ return 0;
+}
+
+krb5_error_code
+krb5_add_ber_mem_ldap_mod(LDAPMod ***list, char *attribute, int op,
+ struct berval **ber_values)
+{
+ krb5_error_code ret;
+ LDAPMod *mod;
+ size_t count, i;
+
+ ret = alloc_mod(list, &mod);
+ if (ret)
+ return ret;
+
+ mod->mod_type = strdup(attribute);
+ if (mod->mod_type == NULL)
+ return ENOMEM;
+ mod->mod_op = op;
+
+ for (count = 0; ber_values[count] != NULL; count++);
+ mod->mod_bvalues = calloc(count + 1, sizeof(struct berval *));
+ if (mod->mod_bvalues == NULL)
+ return ENOMEM;
+
+ for (i = 0; i < count; i++) {
+ mod->mod_bvalues[i] = calloc(1, sizeof(struct berval));
+ if (mod->mod_bvalues[i] == NULL)
+ return ENOMEM;
+
+ mod->mod_bvalues[i]->bv_len = ber_values[i]->bv_len;
+ mod->mod_bvalues[i]->bv_val = k5memdup(ber_values[i]->bv_val,
+ ber_values[i]->bv_len, &ret);
+ if (mod->mod_bvalues[i]->bv_val == NULL)
+ return ret;
+ }
+ mod->mod_bvalues[i] = NULL;
+ return 0;
+}
+
+static inline char *
+format_d(int val)
+{
+ char tmpbuf[3 * sizeof(val) + 2];
+
+ snprintf(tmpbuf, sizeof(tmpbuf), "%d", val);
+ return strdup(tmpbuf);
+}
+
+krb5_error_code
+krb5_add_int_mem_ldap_mod(LDAPMod ***list, char *attribute, int op, int value)
+{
+ krb5_error_code ret;
+ LDAPMod *mod;
+
+ ret = alloc_mod(list, &mod);
+ if (ret)
+ return ret;
+
+ mod->mod_type = strdup(attribute);
+ if (mod->mod_type == NULL)
+ return ENOMEM;
+
+ mod->mod_op = op;
+ mod->mod_values = calloc(2, sizeof(char *));
+ if (mod->mod_values == NULL)
+ return ENOMEM;
+ mod->mod_values[0] = format_d(value);
+ if (mod->mod_values[0] == NULL)
+ return ENOMEM;
+ return 0;
+}
+
+krb5_error_code
+krb5_ldap_modify_ext(krb5_context context, LDAP *ld, const char *dn,
+ LDAPMod **mods, int op)
+{
+ int ret;
+
+ ret = ldap_modify_ext_s(ld, dn, mods, NULL, NULL);
+ return (ret == LDAP_SUCCESS) ? 0 : set_ldap_error(context, ret, op);
+}
+
+krb5_error_code
+krb5_ldap_lock(krb5_context kcontext, int mode)
+{
+ krb5_error_code status = KRB5_PLUGIN_OP_NOTSUPP;
+
+ k5_setmsg(kcontext, status, "LDAP %s", error_message(status));
+ return status;
+}
+
+krb5_error_code
+krb5_ldap_unlock(krb5_context kcontext)
+{
+ krb5_error_code status = KRB5_PLUGIN_OP_NOTSUPP;
+
+ k5_setmsg(kcontext, status, "LDAP %s", error_message(status));
+ return status;
+}
+
+
+/*
+ * Get the number of times an object has been referred to in a realm. This is
+ * needed to find out if deleting the attribute will cause dangling links.
+ *
+ * An LDAP handle may be optionally specified to prevent race condition - there
+ * are a limited number of LDAP handles.
+ */
+krb5_error_code
+krb5_ldap_get_reference_count(krb5_context context, char *dn, char *refattr,
+ int *count, LDAP *ld)
+{
+ int n, st, tempst, gothandle = 0;
+ unsigned int i, ntrees = 0;
+ char *refcntattr[2];
+ char *filter = NULL, *corrected = NULL, **subtree = NULL;
+ kdb5_dal_handle *dal_handle = NULL;
+ krb5_ldap_context *ldap_context = NULL;
+ krb5_ldap_server_handle *ldap_server_handle = NULL;
+ LDAPMessage *result = NULL;
+
+ if (dn == NULL || refattr == NULL) {
+ st = EINVAL;
+ goto cleanup;
+ }
+
+ SETUP_CONTEXT();
+ if (ld == NULL) {
+ GET_HANDLE();
+ gothandle = 1;
+ }
+
+ refcntattr[0] = refattr;
+ refcntattr[1] = NULL;
+
+ corrected = ldap_filter_correct(dn);
+ if (corrected == NULL) {
+ st = ENOMEM;
+ goto cleanup;
+ }
+
+ if (asprintf(&filter, "%s=%s", refattr, corrected) < 0) {
+ filter = NULL;
+ st = ENOMEM;
+ goto cleanup;
+ }
+
+ st = krb5_get_subtree_info(ldap_context, &subtree, &ntrees);
+ if (st)
+ goto cleanup;
+
+ for (i = 0, *count = 0; i < ntrees; i++) {
+ LDAP_SEARCH(subtree[i], LDAP_SCOPE_SUBTREE, filter, refcntattr);
+ n = ldap_count_entries(ld, result);
+ if (n == -1) {
+ int ret, errcode = 0;
+ ret = ldap_parse_result(ld, result, &errcode, NULL, NULL, NULL,
+ NULL, 0);
+ if (ret != LDAP_SUCCESS)
+ errcode = ret;
+ st = translate_ldap_error(errcode, OP_SEARCH);
+ goto cleanup;
+ }
+
+ ldap_msgfree(result);
+ result = NULL;
+
+ *count += n;
+ }
+
+cleanup:
+ free(filter);
+ ldap_msgfree(result);
+ for (i = 0; i < ntrees; i++)
+ free(subtree[i]);
+ free(subtree);
+ free(corrected);
+ if (gothandle)
+ krb5_ldap_put_handle_to_pool(ldap_context, ldap_server_handle);
+ return st;
+}
+
+/* Extract a name from policy_dn, which must be directly under the realm
+ * container. */
+krb5_error_code
+krb5_ldap_policydn_to_name(krb5_context context, const char *policy_dn,
+ char **name_out)
+{
+ size_t len1, len2, plen;
+ krb5_error_code ret;
+ kdb5_dal_handle *dal_handle;
+ krb5_ldap_context *ldap_context;
+ const char *realmdn;
+
+ *name_out = NULL;
+ SETUP_CONTEXT();
+
+ realmdn = ldap_context->lrparams->realmdn;
+ if (realmdn == NULL)
+ return EINVAL;
+
+ /* policyn_dn should be "cn=<policyname>,<realmdn>". */
+ len1 = strlen(realmdn);
+ len2 = strlen(policy_dn);
+ if (len1 == 0 || len2 == 0 || len1 + 1 >= len2)
+ return EINVAL;
+ plen = len2 - len1 - 1;
+ if (policy_dn[plen] != ',' || strcmp(realmdn, policy_dn + plen + 1) != 0)
+ return EINVAL;
+
+#if defined HAVE_LDAP_STR2DN
+ {
+ char *rdn;
+ LDAPDN dn;
+
+ rdn = k5memdup0(policy_dn, plen, &ret);
+ if (rdn == NULL)
+ return ret;
+ ret = ldap_str2dn(rdn, &dn, LDAP_DN_FORMAT_LDAPV3 | LDAP_DN_PEDANTIC);
+ free(rdn);
+ if (ret)
+ return EINVAL;
+ if (dn[0] == NULL || dn[1] != NULL ||
+ dn[0][0]->la_attr.bv_len != 2 ||
+ strncasecmp(dn[0][0]->la_attr.bv_val, "cn", 2) != 0) {
+ ret = EINVAL;
+ } else {
+ *name_out = k5memdup0(dn[0][0]->la_value.bv_val,
+ dn[0][0]->la_value.bv_len, &ret);
+ }
+ ldap_dnfree(dn);
+ return ret;
+ }
+#elif defined HAVE_LDAP_EXPLODE_DN
+ {
+ char **parsed_dn;
+
+ /* 1 = return DN components without type prefix */
+ parsed_dn = ldap_explode_dn(policy_dn, 1);
+ if (parsed_dn == NULL)
+ return EINVAL;
+ *name_out = strdup(parsed_dn[0]);
+ if (*name_out == NULL)
+ return ENOMEM;
+ ldap_value_free(parsed_dn);
+ return 0;
+ }
+#else
+ return EINVAL;
+#endif
+}
+
+/* Compute the policy DN for the given policy name. */
+krb5_error_code
+krb5_ldap_name_to_policydn(krb5_context context, char *name, char **policy_dn)
+{
+ int st;
+ char *corrected;
+ kdb5_dal_handle *dal_handle;
+ krb5_ldap_context *ldap_context;
+
+ *policy_dn = NULL;
+
+ /* Used for removing policy reference from an object */
+ if (name[0] == '\0') {
+ *policy_dn = strdup("");
+ return (*policy_dn == NULL) ? ENOMEM : 0;
+ }
+
+ SETUP_CONTEXT();
+
+ if (ldap_context->lrparams->realmdn == NULL)
+ return EINVAL;
+
+ corrected = ldap_filter_correct(name);
+ if (corrected == NULL)
+ return ENOMEM;
+
+ st = asprintf(policy_dn, "cn=%s,%s", corrected,
+ ldap_context->lrparams->realmdn);
+ free(corrected);
+ if (st == -1) {
+ *policy_dn = NULL;
+ return ENOMEM;
+ }
+ return 0;
+}
+
+/* Return true if dn1 is a subtree of dn2. */
+static inline krb5_boolean
+is_subtree(const char *dn1, size_t len1, const char *dn2, size_t len2)
+{
+ return len1 > len2 && dn1[len1 - len2 - 1] == ',' &&
+ strcasecmp(dn1 + (len1 - len2), dn2) == 0;
+}
+
+/* Remove overlapping and repeated subtree entries from the list of subtrees.
+ * If sscope is not 2 (sub), only remove repeated entries. */
+static void
+remove_overlapping_subtrees(char **list, int *subtcount, int sscope)
+{
+ size_t ilen, jlen;
+ int i, j;
+ int count = *subtcount;
+
+ for (i = 0; i < count && list[i] != NULL; i++) {
+ ilen = strlen(list[i]);
+ for (j = i + 1; j < count && list[j] != NULL; j++) {
+ jlen = strlen(list[j]);
+ /* Remove list[j] if it is identical to list[i] or a subtree of it.
+ * Remove list[i] if it is a subtree of list[j]. */
+ if ((ilen == jlen && strcasecmp(list[j], list[i]) == 0) ||
+ (sscope == 2 && is_subtree(list[j], jlen, list[i], ilen))) {
+ free(list[j]);
+ list[j--] = list[count - 1];
+ list[--count] = NULL;
+ } else if (sscope == 2 &&
+ is_subtree(list[i], ilen, list[j], jlen)) {
+ free(list[i]);
+ list[i--] = list[count - 1];
+ list[--count] = NULL;
+ break;
+ }
+ }
+ }
+ *subtcount = count;
+}
+
+static void
+free_princ_ent_contents(osa_princ_ent_t princ_ent)
+{
+ unsigned int i;
+
+ for (i = 0; i < princ_ent->old_key_len; i++) {
+ k5_free_key_data(princ_ent->old_keys[i].n_key_data,
+ princ_ent->old_keys[i].key_data);
+ princ_ent->old_keys[i].n_key_data = 0;
+ princ_ent->old_keys[i].key_data = NULL;
+ }
+ free(princ_ent->old_keys);
+ princ_ent->old_keys = NULL;
+ princ_ent->old_key_len = 0;
+}
+
+/* Get any auth indicator values from LDAP and update the "require_auth"
+ * string. */
+static krb5_error_code
+get_ldap_auth_ind(krb5_context context, LDAP *ld, LDAPMessage *ldap_ent,
+ krb5_db_entry *entry, unsigned int *mask)
+{
+ krb5_error_code ret;
+ int i;
+ char **auth_inds = NULL;
+ struct k5buf buf = EMPTY_K5BUF;
+
+ auth_inds = ldap_get_values(ld, ldap_ent, "krbPrincipalAuthInd");
+ if (auth_inds == NULL)
+ return 0;
+
+ k5_buf_init_dynamic(&buf);
+
+ /* Make a space seperated list of indicators. */
+ for (i = 0; auth_inds[i] != NULL; i++) {
+ k5_buf_add(&buf, auth_inds[i]);
+ if (auth_inds[i + 1] != NULL)
+ k5_buf_add(&buf, " ");
+ }
+
+ ret = k5_buf_status(&buf);
+ if (ret)
+ goto cleanup;
+
+ ret = krb5_dbe_set_string(context, entry, KRB5_KDB_SK_REQUIRE_AUTH,
+ buf.data);
+ if (!ret)
+ *mask |= KDB_AUTH_IND_ATTR;
+
+cleanup:
+ k5_buf_free(&buf);
+ ldap_value_free(auth_inds);
+ return ret;
+}
+
+/*
+ * Fill out a krb5_db_entry princ entry struct given a LDAP message containing
+ * the results of a principal search of the directory.
+ */
+krb5_error_code
+populate_krb5_db_entry(krb5_context context, krb5_ldap_context *ldap_context,
+ LDAP *ld, LDAPMessage *ent, krb5_const_principal princ,
+ krb5_db_entry *entry)
+{
+ krb5_error_code ret;
+ unsigned int mask = 0;
+ int val, i, pcount, objtype;
+ krb5_boolean attr_present;
+ krb5_kvno mkvno = 0;
+ krb5_timestamp lastpwdchange, unlock_time;
+ char *policydn = NULL, *pwdpolicydn = NULL, *polname = NULL, *user = NULL;
+ char *tktpolname = NULL, *dn = NULL, **link_references = NULL;
+ char **pnvalues = NULL, **ocvalues = NULL, **a2d2 = NULL;
+ struct berval **ber_key_data = NULL, **ber_tl_data = NULL;
+ krb5_tl_data userinfo_tl_data = { NULL }, **endp, *tl;
+ osa_princ_ent_rec princ_ent;
+
+ memset(&princ_ent, 0, sizeof(princ_ent));
+
+ ret = krb5_copy_principal(context, princ, &entry->princ);
+ if (ret)
+ goto cleanup;
+
+ /* get the associated directory user information */
+ pnvalues = ldap_get_values(ld, ent, "krbprincipalname");
+ if (pnvalues != NULL) {
+ ret = krb5_unparse_name(context, princ, &user);
+ if (ret)
+ goto cleanup;
+
+ pcount = 0;
+ for (i = 0; pnvalues[i] != NULL; i++) {
+ if (strcasecmp(pnvalues[i], user) == 0) {
+ pcount = ldap_count_values(pnvalues);
+ break;
+ }
+ }
+
+ dn = ldap_get_dn(ld, ent);
+ if (dn == NULL) {
+ ldap_get_option(ld, LDAP_OPT_RESULT_CODE, &ret);
+ ret = set_ldap_error(context, ret, 0);
+ goto cleanup;
+ }
+
+ ocvalues = ldap_get_values(ld, ent, "objectclass");
+ if (ocvalues != NULL) {
+ for (i = 0; ocvalues[i] != NULL; i++) {
+ if (strcasecmp(ocvalues[i], "krbprincipal") == 0) {
+ objtype = KDB_STANDALONE_PRINCIPAL_OBJECT;
+ ret = store_tl_data(&userinfo_tl_data, KDB_TL_PRINCTYPE,
+ &objtype);
+ if (ret)
+ goto cleanup;
+ break;
+ }
+ }
+ }
+
+ /* Add principalcount, DN and principaltype user information to
+ * tl_data */
+ ret = store_tl_data(&userinfo_tl_data, KDB_TL_PRINCCOUNT, &pcount);
+ if (ret)
+ goto cleanup;
+ ret = store_tl_data(&userinfo_tl_data, KDB_TL_USERDN, dn);
+ if (ret)
+ goto cleanup;
+ }
+
+ ret = get_time(ld, ent, "krbLastSuccessfulAuth", &entry->last_success,
+ &attr_present);
+ if (ret)
+ goto cleanup;
+ if (attr_present)
+ mask |= KDB_LAST_SUCCESS_ATTR;
+
+ ret = get_time(ld, ent, "krbLastFailedAuth", &entry->last_failed,
+ &attr_present);
+ if (ret)
+ goto cleanup;
+ if (attr_present)
+ mask |= KDB_LAST_FAILED_ATTR;
+
+ if (krb5_ldap_get_value(ld, ent, "krbLoginFailedCount", &val) == 0) {
+ entry->fail_auth_count = val;
+ mask |= KDB_FAIL_AUTH_COUNT_ATTR;
+ }
+ if (krb5_ldap_get_value(ld, ent, "krbmaxticketlife", &val) == 0) {
+ entry->max_life = val;
+ mask |= KDB_MAX_LIFE_ATTR;
+ }
+ if (krb5_ldap_get_value(ld, ent, "krbmaxrenewableage", &val) == 0) {
+ entry->max_renewable_life = val;
+ mask |= KDB_MAX_RLIFE_ATTR;
+ }
+ if (krb5_ldap_get_value(ld, ent, "krbticketflags", &val) == 0) {
+ entry->attributes = val;
+ mask |= KDB_TKT_FLAGS_ATTR;
+ }
+ ret = get_time(ld, ent, "krbprincipalexpiration", &entry->expiration,
+ &attr_present);
+ if (ret)
+ goto cleanup;
+ if (attr_present)
+ mask |= KDB_PRINC_EXPIRE_TIME_ATTR;
+
+ ret = get_time(ld, ent, "krbpasswordexpiration", &entry->pw_expiration,
+ &attr_present);
+ if (ret)
+ goto cleanup;
+ if (attr_present)
+ mask |= KDB_PWD_EXPIRE_TIME_ATTR;
+
+ ret = krb5_ldap_get_string(ld, ent, "krbticketpolicyreference", &policydn,
+ &attr_present);
+ if (ret)
+ goto cleanup;
+ if (attr_present) {
+ mask |= KDB_POL_REF_ATTR;
+ /* Ensure that the policy is inside the realm container. */
+ ret = krb5_ldap_policydn_to_name(context, policydn, &tktpolname);
+ if (ret)
+ goto cleanup;
+ }
+
+ ret = krb5_ldap_get_string(ld, ent, "krbpwdpolicyreference", &pwdpolicydn,
+ &attr_present);
+ if (ret)
+ goto cleanup;
+ if (attr_present) {
+ mask |= KDB_PWD_POL_REF_ATTR;
+
+ /* Ensure that the policy is inside the realm container. */
+ ret = krb5_ldap_policydn_to_name(context, pwdpolicydn, &polname);
+ if (ret)
+ goto cleanup;
+ princ_ent.policy = polname;
+ princ_ent.aux_attributes |= KADM5_POLICY;
+ }
+
+ ber_key_data = ldap_get_values_len(ld, ent, "krbpwdhistory");
+ if (ber_key_data != NULL) {
+ mask |= KDB_PWD_HISTORY_ATTR;
+ ret = krb5_decode_histkey(context, ber_key_data, &princ_ent);
+ if (ret)
+ goto cleanup;
+ ldap_value_free_len(ber_key_data);
+ }
+
+ if (princ_ent.aux_attributes) {
+ ret = krb5_update_tl_kadm_data(context, entry, &princ_ent);
+ if (ret)
+ goto cleanup;
+ }
+
+ ber_key_data = ldap_get_values_len(ld, ent, "krbprincipalkey");
+ if (ber_key_data != NULL) {
+ mask |= KDB_SECRET_KEY_ATTR;
+ ret = krb5_decode_krbsecretkey(context, entry, ber_key_data, &mkvno);
+ if (ret)
+ goto cleanup;
+ if (mkvno != 0) {
+ ret = krb5_dbe_update_mkvno(context, entry, mkvno);
+ if (ret)
+ goto cleanup;
+ }
+ }
+
+ ret = get_time(ld, ent, "krbLastPwdChange", &lastpwdchange, &attr_present);
+ if (ret)
+ goto cleanup;
+ if (attr_present) {
+ ret = krb5_dbe_update_last_pwd_change(context, entry, lastpwdchange);
+ if (ret)
+ goto cleanup;
+ mask |= KDB_LAST_PWD_CHANGE_ATTR;
+ }
+
+ ret = get_time(ld, ent, "krbLastAdminUnlock", &unlock_time, &attr_present);
+ if (ret)
+ goto cleanup;
+ if (attr_present) {
+ ret = krb5_dbe_update_last_admin_unlock(context, entry, unlock_time);
+ if (ret)
+ goto cleanup;
+ mask |= KDB_LAST_ADMIN_UNLOCK_ATTR;
+ }
+
+ a2d2 = ldap_get_values(ld, ent, "krbAllowedToDelegateTo");
+ if (a2d2 != NULL) {
+ for (endp = &entry->tl_data; *endp; endp = &(*endp)->tl_data_next);
+ for (i = 0; a2d2[i] != NULL; i++) {
+ tl = k5alloc(sizeof(*tl), &ret);
+ if (tl == NULL)
+ goto cleanup;
+ tl->tl_data_type = KRB5_TL_CONSTRAINED_DELEGATION_ACL;
+ tl->tl_data_length = strlen(a2d2[i]);
+ tl->tl_data_contents = (unsigned char *)strdup(a2d2[i]);
+ if (tl->tl_data_contents == NULL) {
+ ret = ENOMEM;
+ free(tl);
+ goto cleanup;
+ }
+ tl->tl_data_next = NULL;
+ *endp = tl;
+ endp = &tl->tl_data_next;
+ }
+ }
+
+ link_references = ldap_get_values(ld, ent, "krbobjectreferences");
+ if (link_references != NULL) {
+ for (i = 0; link_references[i] != NULL; i++) {
+ ret = store_tl_data(&userinfo_tl_data, KDB_TL_LINKDN,
+ link_references[i]);
+ if (ret)
+ goto cleanup;
+ }
+ }
+
+ ber_tl_data = ldap_get_values_len(ld, ent, "krbExtraData");
+ if (ber_tl_data != NULL) {
+ for (i = 0; ber_tl_data[i] != NULL; i++) {
+ ret = berval2tl_data(ber_tl_data[i], &tl);
+ if (ret)
+ goto cleanup;
+ ret = krb5_dbe_update_tl_data(context, entry, tl);
+ free(tl->tl_data_contents);
+ free(tl);
+ if (ret)
+ goto cleanup;
+ }
+ mask |= KDB_EXTRA_DATA_ATTR;
+ }
+
+ /* Auth indicators from krbPrincipalAuthInd will replace those from
+ * krbExtraData. */
+ ret = get_ldap_auth_ind(context, ld, ent, entry, &mask);
+ if (ret)
+ goto cleanup;
+
+ /* Update the mask of attributes present on the directory object to the
+ * tl_data. */
+ ret = store_tl_data(&userinfo_tl_data, KDB_TL_MASK, &mask);
+ if (ret)
+ goto cleanup;
+ ret = krb5_dbe_update_tl_data(context, entry, &userinfo_tl_data);
+ if (ret)
+ goto cleanup;
+
+ ret = krb5_read_tkt_policy(context, ldap_context, entry, tktpolname);
+ if (ret)
+ goto cleanup;
+
+ /* For compatibility with DB2 principals. */
+ entry->len = KRB5_KDB_V1_BASE_LENGTH;
+
+cleanup:
+ ldap_memfree(dn);
+ ldap_value_free_len(ber_key_data);
+ ldap_value_free_len(ber_tl_data);
+ ldap_value_free(pnvalues);
+ ldap_value_free(ocvalues);
+ ldap_value_free(link_references);
+ ldap_value_free(a2d2);
+ free(userinfo_tl_data.tl_data_contents);
+ free(pwdpolicydn);
+ free(polname);
+ free(tktpolname);
+ free(policydn);
+ krb5_free_unparsed_name(context, user);
+ free_princ_ent_contents(&princ_ent);
+ return ret;
+}
+
+/* Solaris libldap does not provide the following functions which are in
+ * OpenLDAP. */
+#ifndef HAVE_LDAP_INITIALIZE
+int
+ldap_initialize(LDAP **ldp, char *url)
+{
+ int rc = 0;
+ LDAP *ld = NULL;
+ LDAPURLDesc *ludp = NULL;
+
+ /*
+ * For now, we don't use any DN that may be provided. And on Solaris
+ * (based on Mozilla's LDAP client code), we need the _nodn form to parse
+ * "ldap://host" without a trailing slash.
+ *
+ * Also, this version won't handle an input string which contains multiple
+ * URLs, unlike the OpenLDAP ldap_initialize. See
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=353336#c1 .
+ */
+#ifdef HAVE_LDAP_URL_PARSE_NODN
+ rc = ldap_url_parse_nodn(url, &ludp);
+#else
+ rc = ldap_url_parse(url, &ludp);
+#endif
+ if (rc == 0) {
+ ld = ldap_init(ludp->lud_host, ludp->lud_port);
+ if (ld != NULL)
+ *ldp = ld;
+ else
+ rc = KRB5_KDB_ACCESS_ERROR;
+ ldap_free_urldesc(ludp);
+ }
+ return rc;
+}
+#endif /* HAVE_LDAP_INITIALIZE */
+
+#ifndef HAVE_LDAP_UNBIND_EXT_S
+int
+ldap_unbind_ext_s(LDAP *ld, LDAPControl **sctrls, LDAPControl **cctrls)
+{
+ return ldap_unbind_ext(ld, sctrls, cctrls);
+}
+#endif /* HAVE_LDAP_UNBIND_EXT_S */