fr_ldap_directory_common_base_find resolves DNs to a covering naming context using a hash table built when the rootDSE is parsed, and fr_ldap_filter_afrom_dn_list builds the matching filter using the directory's dn_attr, which fr_ldap_directory_alloc now defaults to entryDN before discovery completes. fr_ldap_berval_to_string_list converts berval values to a NULL terminated string list carved from a single talloc pool.
fr_ldap_sync_type_t sync_type; //!< What kind of LDAP sync this directory supports.
char const *dn_attr; //!< Attribute to match an entry's DN in a search filter.
- ///< RFC 5020 entryDN where supported, distinguishedName
+ ///< Defaults to RFC 5020 entryDN, distinguishedName
///< on Active Directory and Samba.
char const **naming_contexts; //!< Databases served by this directory.
+ fr_hash_table_t *naming_contexts_ht; //!< For resolving DNs to the naming context
+ ///< containing them.
} fr_ldap_directory_t;
/** Connection configuration
"namingContexts", \
NULL }
+fr_ldap_directory_t *fr_ldap_directory_alloc(TALLOC_CTX *ctx);
+
+char const *fr_ldap_directory_common_base_find(fr_ldap_directory_t const *directory, char const * const *dn_list);
+
int fr_ldap_directory_result_parse(fr_ldap_directory_t *directory, LDAP *handle,
LDAPMessage *result, char const *name);
char *fr_ldap_berval_to_string(TALLOC_CTX *ctx, struct berval const *in);
+char const **fr_ldap_berval_to_string_list(TALLOC_CTX *ctx, struct berval **values, int count, size_t extra);
+
uint8_t *fr_ldap_berval_to_bin(TALLOC_CTX *ctx, struct berval const *in);
int fr_ldap_parse_url_extensions(LDAPControl **sss, size_t sss_len, char *extensions[]);
int fr_ldap_filter_box_escape(fr_value_box_t *vb, UNUSED void *uctx);
+char *fr_ldap_filter_afrom_dn_list(TALLOC_CTX *ctx, char const *dn_attr, char const *filter,
+ char const * const *dn_list);
+
int fr_ldap_filter_to_tmpl(TALLOC_CTX *ctx, tmpl_rules_t const *t_rules, char const **sub, size_t sublen,
tmpl_t **out) CC_HINT(nonnull());
* connection init callbacks run, populated asynchronously
* by the rootDSE query enqueued below.
*/
- MEM(new->directory = talloc_zero(new, fr_ldap_directory_t));
+ new->directory = fr_ldap_directory_alloc(new);
new->trunk = trunk_alloc(new, thread->el,
&(trunk_io_funcs_t){
};
static size_t fr_ldap_directory_type_table_len = NUM_ELEMENTS(fr_ldap_directory_type_table);
+/** Hash a naming context, case insensitively
+ *
+ */
+static uint32_t _naming_context_hash(void const *data)
+{
+ return fr_hash_case_string(data);
+}
+
+/** Compare two naming contexts, case insensitively
+ *
+ */
+static int8_t _naming_context_cmp(void const *one, void const *two)
+{
+ return CMP(strcasecmp(one, two), 0);
+}
+
int fr_ldap_directory_result_parse(fr_ldap_directory_t *directory, LDAP *handle,
LDAPMessage *result, char const *name)
{
break;
case FR_LDAP_DIRECTORY_EDIRECTORY:
- directory->dn_attr = "entryDN";
directory->cleartext_password = false;
break;
default:
- directory->dn_attr = "entryDN";
directory->cleartext_password = true;
break;
}
num = ldap_count_values_len(values);
MEM(directory->naming_contexts = talloc_array(directory, char const *, num));
+ MEM(directory->naming_contexts_ht = fr_hash_table_alloc(directory, _naming_context_hash,
+ _naming_context_cmp, NULL));
for (i = 0; i < num; i++) {
directory->naming_contexts[i] = fr_ldap_berval_to_string(directory, values[i]);
+ fr_hash_table_insert(directory->naming_contexts_ht, directory->naming_contexts[i]);
}
ldap_value_free_len(values);
return 0;
}
+/** Allocate a directory structure with defaults
+ *
+ * dn_attr defaults to the RFC 5020 entryDN attribute, overridden when
+ * parsing the rootDSE detects a directory which doesn't implement entryDN.
+ */
+fr_ldap_directory_t *fr_ldap_directory_alloc(TALLOC_CTX *ctx)
+{
+ fr_ldap_directory_t *directory;
+
+ MEM(directory = talloc_zero(ctx, fr_ldap_directory_t));
+ directory->dn_attr = "entryDN";
+
+ return directory;
+}
+
+/** Find the naming context which contains a set of DNs
+ *
+ * Looks up successively shorter suffixes of each DN in the hash table of
+ * naming contexts (database suffixes) built when the rootDSE was parsed,
+ * and returns the naming context containing every DN. A search with the
+ * returned base covers all the DNs.
+ *
+ * @param[in] directory Directory discovery results, providing the naming contexts.
+ * @param[in] dn_list NULL terminated list of DNs to cover, no empty strings.
+ * @return
+ * - The matching naming context.
+ * - NULL if the directory hasn't been discovered yet, or no single
+ * naming context contains every DN.
+ */
+char const *fr_ldap_directory_common_base_find(fr_ldap_directory_t const *directory, char const * const *dn_list)
+{
+ char const *common = NULL;
+ char const * const *dn_p;
+
+ if (!directory->naming_contexts_ht) return NULL;
+
+ for (dn_p = dn_list; *dn_p; dn_p++) {
+ char const *context = NULL;
+ char const *p = *dn_p;
+
+ /*
+ * Check successively shorter suffixes of the DN,
+ * starting after each RDN separator.
+ */
+ while (p) {
+ context = fr_hash_table_find(directory->naming_contexts_ht, p);
+ if (context) break;
+
+ p = strchr(p, ',');
+ if (p) p++;
+ }
+ if (!context) return NULL;
+
+ /*
+ * Lookups return the stored string, so pointer
+ * comparison is enough to check every DN resolved
+ * to the same naming context.
+ */
+ if (!common) {
+ common = context;
+ continue;
+ }
+ if (common != context) return NULL;
+ }
+
+ return common;
+}
+
/** Parse results of search on rootDSE to gather data on LDAP server
*
* @param[in] handle on which the query was run.
return 0;
}
-/** Async extract useful information from the rootDSE of the LDAP server
+/** Asynchronously extract useful information from the rootDSE of the LDAP server
*
* This version is for a single connection rather than a connection trunk
*
int msgid;
static char const *attrs[] = LDAP_DIRECTORY_ATTRS;
- ldap_conn->directory = talloc_zero(ldap_conn, fr_ldap_directory_t);
- if (!ldap_conn->directory) return -1;
+ ldap_conn->directory = fr_ldap_directory_alloc(ldap_conn);
if (fr_ldap_search_async(&msgid, NULL, ldap_conn, "", LDAP_SCOPE_BASE, "(objectclass=*)", attrs,
NULL, NULL) != LDAP_PROC_SUCCESS) return -1;
return (sss_end - sss_p);
}
+/** Convert a list of bervals to a NULL terminated list of talloced strings
+ *
+ * A list variant of fr_ldap_berval_to_string. Where talloc_pooled_object is
+ * available the pointer array and every string are allocated from a single
+ * talloc pool, so building the list costs one malloc. Zero length values
+ * are skipped.
+ *
+ * @param[in] ctx to parent the list.
+ * @param[in] values to copy.
+ * @param[in] count Number of values.
+ * @param[in] extra Leading pointer array entries to leave NULL, for the
+ * caller to fill with strings not copied into the pool.
+ * @return NULL terminated array of \0 terminated strings.
+ */
+char const **fr_ldap_berval_to_string_list(TALLOC_CTX *ctx, struct berval **values, int count, size_t extra)
+{
+ char const **list;
+ size_t num = extra;
+ int i;
+
+#ifdef HAVE_TALLOC_ZERO_POOLED_OBJECT
+ {
+ size_t strings_size = 0;
+
+ for (i = 0; i < count; i++) {
+ strings_size += values[i]->bv_len + 1;
+ }
+ MEM(list = _talloc_zero_pooled_object(ctx, sizeof(char const *) * (count + extra + 1),
+ "char const *[]", count, strings_size));
+ }
+#else
+ MEM(list = talloc_zero_array(ctx, char const *, count + extra + 1));
+#endif
+
+ for (i = 0; i < count; i++) {
+ if (values[i]->bv_len == 0) continue;
+ MEM(list[num++] = fr_ldap_berval_to_string(list, values[i]));
+ }
+
+ return list;
+}
+
/** Convert a berval to a talloced string
*
* The ldap_get_values function is deprecated, and ldap_get_values_len
return f_len - p_len;
}
+/** Build a filter matching a set of objects by DN
+ *
+ * Produces `(|(<dn_attr>=<dn>)...)`, ANDed with filter if one is given.
+ * DN values are escaped.
+ *
+ * @param[in] ctx to allocate the filter string in.
+ * @param[in] dn_attr Attribute which matches an object's own DN,
+ * e.g. entryDN or distinguishedName.
+ * @param[in] filter Optional filter to AND with the DN set, may be NULL.
+ * @param[in] dn_list NULL terminated list of DNs to match, no empty strings.
+ * @return The filter string.
+ */
+char *fr_ldap_filter_afrom_dn_list(TALLOC_CTX *ctx, char const *dn_attr, char const *filter,
+ char const * const *dn_list)
+{
+ char *out;
+ char const * const *dn_p;
+
+ MEM(out = talloc_typed_strdup(ctx, "(|"));
+ for (dn_p = dn_list; *dn_p; dn_p++) {
+ char *escaped;
+ size_t len;
+
+ len = (strlen(*dn_p) * 3) + 1;
+ MEM(escaped = talloc_array(ctx, char, len));
+ fr_ldap_filter_escape_func(NULL, escaped, len, *dn_p, NULL);
+ MEM(out = talloc_asprintf_append_buffer(out, "(%s=%s)", dn_attr, escaped));
+ talloc_free(escaped);
+ }
+ MEM(out = talloc_strdup_append_buffer(out, ")"));
+
+ if (filter && *filter) {
+ char *combined;
+
+ MEM(combined = talloc_typed_asprintf(ctx, "(&%s%s)", filter, out));
+ talloc_free(out);
+ return combined;
+ }
+
+ return out;
+}
+
/** Combine filters and tokenize to a tmpl
*
* @param ctx To allocate combined filter in
* Allocate the directory structure and send the query
*/
dir_ctx->msgid = fr_ldap_conn_directory_alloc_async(ldap_conn);
-
if (dir_ctx->msgid < 0) {
talloc_free(dir_ctx);
goto connection_failed;