dovecot-2.2: ldap auth: Support field values containing DNs to o...

dovecot at dovecot.org dovecot at dovecot.org
Mon Mar 18 20:57:27 EET 2013


details:   http://hg.dovecot.org/dovecot-2.2/rev/07a52d0edd46
changeset: 16034:07a52d0edd46
user:      Timo Sirainen <tss at iki.fi>
date:      Mon Mar 18 20:51:33 2013 +0200
description:
ldap auth: Support field values containing DNs to other LDAP records and getting them.
For example:

user_attrs = \
  =user=%{ldap:uid}, \
  @mail=base(%{ldap:mailDN}), \
  =uid=%{ldap:uidNumber at mail}, \
  =gid=%{ldap:gidNumber at mail}, \
  =home=%{ldap:rootPath at mail}/%d/%n

This first does the regular lookup, and then does another lookup using
mailDN's value as the new lookup's base. The other lookup's filter is
currently hardcoded to "no filter".

diffstat:

 src/auth/db-ldap.c     |  322 +++++++++++++++++++++++++++++++++++++++++++-----
 src/auth/db-ldap.h     |   44 +++--
 src/auth/passdb-ldap.c |   14 +-
 src/auth/userdb-ldap.c |   16 +-
 4 files changed, 329 insertions(+), 67 deletions(-)

diffs (truncated from 659 to 300 lines):

diff -r d7d7cbcc2b67 -r 07a52d0edd46 src/auth/db-ldap.c
--- a/src/auth/db-ldap.c	Mon Mar 18 16:51:58 2013 +0200
+++ b/src/auth/db-ldap.c	Mon Mar 18 20:51:33 2013 +0200
@@ -66,6 +66,8 @@
 
 	const char *val_1_arr[2];
 	string_t *var, *debug;
+
+	bool iter_dn_values;
 };
 
 struct db_ldap_sasl_bind_context {
@@ -153,6 +155,10 @@
 
 static int db_ldap_bind(struct ldap_connection *conn);
 static void db_ldap_conn_close(struct ldap_connection *conn);
+struct db_ldap_result_iterate_context *
+db_ldap_result_iterate_init_full(struct ldap_connection *conn,
+				 struct ldap_request_search *ldap_request,
+				 bool iter_dn_values);
 
 static int deref2str(const char *str)
 {
@@ -540,26 +546,178 @@
 	return NULL;
 }
 
-static void
-db_ldap_handle_result(struct ldap_connection *conn, LDAPMessage *res)
+static int db_ldap_dn_parse(struct auth_request *auth_request,
+			    struct ldap_request_named_result *named_res,
+			    const char *name, const char *value)
 {
-	struct ldap_request *request;
-	unsigned int idx;
-	int msgid, ret;
+	unsigned int len = strlen(value);
 
-	msgid = ldap_msgid(res);
-	if (msgid == conn->default_bind_msgid) {
-		db_ldap_default_bind_finished(conn, res);
-		return;
+	/* in future we could also have a filter() in here and maybe some
+	   options.. of course, in future the whole ldap configuration will
+	   probably be redesigned to become simpler.. */
+	if (strncmp(value, "base(", 5) != 0 || value[len-1] != ')') {
+		auth_request_log_error(auth_request, "ldap",
+			"Invalid @%s value '%s'", name, value);
+		return -1;
 	}
+	named_res->dn = p_strndup(auth_request->pool, value+5, len-5-1);
+	return 0;
+}
 
-	request = db_ldap_find_request(conn, msgid, &idx);
-	if (request == NULL) {
-		i_error("LDAP: Reply with unknown msgid %d", msgid);
-		return;
+static int db_ldap_fields_get_dn(struct ldap_connection *conn,
+				 struct ldap_request_search *request)
+{
+	struct auth_request *auth_request = request->request.auth_request;
+	struct ldap_request_named_result *named_res;
+	struct db_ldap_result_iterate_context *ldap_iter;
+	const char *name, *const *values;
+
+	ldap_iter = db_ldap_result_iterate_init_full(conn, request, TRUE);
+	while (db_ldap_result_iterate_next(ldap_iter, &name, &values)) {
+		if (values[1] != NULL) {
+			auth_request_log_warning(auth_request, "ldap",
+				"Multiple values found for '%s', "
+				"using value '%s'", name, values[0]);
+		}
+		array_foreach_modifiable(&request->named_results, named_res) {
+			if (strcmp(named_res->field->name, name) != 0)
+				continue;
+			if (db_ldap_dn_parse(auth_request, named_res,
+					     name, values[0]) < 0)
+				return -1;
+		}
 	}
+	db_ldap_result_iterate_deinit(&ldap_iter);
+	return 0;
+}
+
+struct ldap_field_find_subquery_context {
+	ARRAY_TYPE(string) attr_names;
+	const char *name;
+};
+
+static const char *
+db_ldap_field_subquery_find(const char *data, void *context)
+{
+	struct ldap_field_find_subquery_context *ctx = context;
+	char *ldap_attr;
+	const char *p;
+
+	if (*data != '\0') {
+		data = t_strcut(data, ':');
+		p = strchr(data, '@');
+		if (p != NULL && strcmp(p+1, ctx->name) == 0) {
+			ldap_attr = p_strdup_until(unsafe_data_stack_pool,
+						   data, p);
+			array_append(&ctx->attr_names, &ldap_attr, 1);
+		}
+	}
+	return NULL;
+}
+
+
+static int
+ldap_request_send_subquery(struct ldap_connection *conn,
+			   struct ldap_request_search *request,
+			   struct ldap_request_named_result *named_res)
+{
+	static struct var_expand_func_table var_funcs_table[] = {
+		{ "ldap", db_ldap_field_subquery_find },
+		{ NULL, NULL }
+	};
+	const struct ldap_field *field;
+	const char *p;
+	char *name;
+	struct ldap_field_find_subquery_context ctx;
+	string_t *tmp_str = t_str_new(64);
+
+	memset(&ctx, 0, sizeof(ctx));
+	t_array_init(&ctx.attr_names, 8);
+	ctx.name = named_res->field->name;
+
+	/* get the attributes names into array (ldapAttr at name -> ldapAttr) */
+	array_foreach(request->attr_map, field) {
+		if (field->ldap_attr_name[0] == '\0') {
+			str_truncate(tmp_str, 0);
+			var_expand_with_funcs(tmp_str, field->value, NULL,
+					      var_funcs_table, &ctx);
+		} else {
+			p = strchr(field->ldap_attr_name, '@');
+			if (p != NULL &&
+			    strcmp(p+1, named_res->field->name) == 0) {
+				name = p_strdup_until(unsafe_data_stack_pool,
+						      field->ldap_attr_name, p);
+				array_append(&ctx.attr_names, &name, 1);
+			}
+		}
+	}
+	array_append_zero(&ctx.attr_names);
+
+	request->request.msgid =
+		ldap_search(conn->ld, named_res->dn, LDAP_SCOPE_BASE,
+			    NULL, array_idx_modifiable(&ctx.attr_names, 0), 0);
+	if (request->request.msgid == -1) {
+		auth_request_log_error(request->request.auth_request, "ldap",
+				       "ldap_search(dn=%s) failed: %s",
+				       named_res->dn, ldap_get_error(conn));
+		return -1;
+	}
+	return 0;
+}
+
+static int db_ldap_search_next_subsearch(struct ldap_connection *conn,
+					 struct ldap_request *_request,
+					 LDAPMessage *res)
+{
+	struct ldap_request_search *request =
+		(struct ldap_request_search *)_request;
+	struct ldap_request_named_result *named_res;
+	const struct ldap_field *field;
+
+	if (request->result == NULL) {
+		request->result = res;
+		/* see if we need to do more LDAP queries */
+		p_array_init(&request->named_results,
+			     _request->auth_request->pool, 2);
+		array_foreach(request->attr_map, field) {
+			if (!field->value_is_dn)
+				continue;
+			named_res = array_append_space(&request->named_results);
+			named_res->field = field;
+		}
+		if (db_ldap_fields_get_dn(conn, request) < 0)
+			return -1;
+	} else {
+		named_res = array_idx_modifiable(&request->named_results,
+						 request->name_idx++);
+		named_res->result = res;
+	}
+	while (request->name_idx < array_count(&request->named_results)) {
+		/* send the next LDAP query */
+		named_res = array_idx_modifiable(&request->named_results,
+						 request->name_idx);
+		if (named_res->dn != NULL) {
+			if (ldap_request_send_subquery(conn, request,
+						       named_res) < 0)
+				return -1;
+			return 1;
+		}
+		/* dn field wasn't returned, skip this */
+		request->name_idx++;
+	}
+	return 0;
+}
+
+static bool
+db_ldap_handle_request_result(struct ldap_connection *conn,
+			      struct ldap_request *request, unsigned int idx,
+			      LDAPMessage *res)
+{
+	int ret;
+	bool final_result;
 
 	i_assert(conn->pending_count > 0);
+
 	if (request->type == LDAP_REQUEST_TYPE_BIND) {
 		i_assert(conn->conn_state == LDAP_CONN_STATE_BINDING);
 		i_assert(conn->pending_count == 1);
@@ -571,18 +729,18 @@
 			break;
 		case LDAP_RES_SEARCH_REFERENCE:
 			/* we're going to ignore this */
-			return;
+			return FALSE;
 		default:
 			i_error("LDAP: Reply with unexpected type %d",
 				ldap_msgtype(res));
-			return;
+			return TRUE;
 		}
 	}
-	if (ldap_msgtype(res) == LDAP_RES_SEARCH_ENTRY)
+	if (ldap_msgtype(res) == LDAP_RES_SEARCH_ENTRY) {
 		ret = LDAP_SUCCESS;
-	else {
-		conn->pending_count--;
-		aqueue_delete(conn->request_queue, idx);
+		final_result = FALSE;
+	} else {
+		final_result = TRUE;
 		ret = ldap_result2error(conn->ld, res, 0);
 	}
 	if (ret != LDAP_SUCCESS && request->type == LDAP_REQUEST_TYPE_SEARCH) {
@@ -596,6 +754,20 @@
 			ldap_err2string(ret));
 		res = NULL;
 	}
+	if (ret == LDAP_SUCCESS && request->type == LDAP_REQUEST_TYPE_SEARCH) {
+		/* expand any @results */
+		ret = db_ldap_search_next_subsearch(conn, request, res);
+		if (ret > 0) {
+			/* wait for finish, don't free the result yet */
+			return FALSE;
+		}
+		if (ret < 0)
+			res = NULL;
+	}
+	if (final_result) {
+		conn->pending_count--;
+		aqueue_delete(conn->request_queue, idx);
+	}
 
 	T_BEGIN {
 		request->callback(conn, request, res);
@@ -607,6 +779,54 @@
 				       DB_LDAP_REQUEST_LOST_TIMEOUT_SECS,
 				       TRUE, "Request lost");
 	}
+	return TRUE;
+}
+
+static void db_ldap_request_free(struct ldap_request *request, LDAPMessage *res)
+{
+	if (request->type == LDAP_REQUEST_TYPE_SEARCH) {
+		struct ldap_request_search *srequest =
+			(struct ldap_request_search *)request;
+		const struct ldap_request_named_result *named_res;
+
+		if (srequest->result == res)
+			res = NULL;
+		if (srequest->result != NULL)
+			ldap_msgfree(srequest->result);
+
+		array_foreach(&srequest->named_results, named_res) {
+			if (named_res->result == res)
+				res = NULL;
+			if (named_res->result != NULL)
+				ldap_msgfree(named_res->result);
+		}
+	}
+	if (res != NULL)
+		ldap_msgfree(res);
+}
+
+static void
+db_ldap_handle_result(struct ldap_connection *conn, LDAPMessage *res)
+{
+	struct ldap_request *request;
+	unsigned int idx;
+	int msgid;
+
+	msgid = ldap_msgid(res);
+	if (msgid == conn->default_bind_msgid) {


More information about the dovecot-cvs mailing list