From d7940eef5df42f23ff72f20430849e7a91cd292c Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek@redhat.com>
Date: Thu, 7 Apr 2011 11:44:40 +0200
Subject: [PATCH 4/9] OpenLDAP dereference searches

This dereference method is supported at least by OpenLDAP and
389DS/RHDS

For more details, see:
http://tools.ietf.org/html/draft-masarati-ldap-deref-00
---
 src/providers/ldap/sdap.c       |  159 +++++++++++++++++++++++++++++
 src/providers/ldap/sdap.h       |    7 ++
 src/providers/ldap/sdap_async.c |  210 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 376 insertions(+), 0 deletions(-)

diff --git a/src/providers/ldap/sdap.c b/src/providers/ldap/sdap.c
index e827209..61044bb 100644
--- a/src/providers/ldap/sdap.c
+++ b/src/providers/ldap/sdap.c
@@ -277,6 +277,165 @@ int sdap_parse_group(TALLOC_CTX *memctx, struct sdap_options *opts,
                             SDAP_OPTS_GROUP, _attrs, _dn);
 }
 
+/* Parses an LDAPDerefRes into sdap_deref_attrs structure */
+errno_t sdap_parse_deref(TALLOC_CTX *mem_ctx,
+                         struct sdap_attr_map_info *minfo,
+                         size_t num_maps,
+                         struct sdap_handle *sh,
+                         LDAPDerefRes *dref,
+                         struct sdap_deref_attrs ***_res)
+{
+    TALLOC_CTX *tmp_ctx;
+    LDAPDerefVal *dval;
+    const char *orig_dn;
+    const char **ocs;
+    struct sdap_attr_map *map;
+    int num_attrs;
+    struct ldb_val v;
+    int ret, i, a, mi;
+    const char *name;
+    size_t len;
+    struct sdap_deref_attrs **res;
+
+    if (!dref || !minfo) return EINVAL;
+
+    tmp_ctx = talloc_new(NULL);
+    if (!tmp_ctx) return ENOMEM;
+
+    res = talloc_array(tmp_ctx, struct sdap_deref_attrs *, num_maps);
+    if (!res) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    for (i=0; i < num_maps; i++) {
+        res[i] = talloc_zero(res, struct sdap_deref_attrs);
+        if (!res[i]) {
+            ret = ENOMEM;
+            goto done;
+        }
+
+        res[i]->map = minfo[i].map;
+    }
+
+    if (!dref->derefVal.bv_val) {
+        DEBUG(2, ("Entry has no DN?\n"));
+        ret = EINVAL;
+        goto done;
+    }
+
+    if (!dref->attrVals) {
+        DEBUG(2, ("Dereferenced entry has no attributes\n"));
+        ret = EINVAL;
+        goto done;
+    }
+
+    orig_dn = dref->derefVal.bv_val;
+    DEBUG(7, ("Dereferenced DN: %s\n", orig_dn));
+
+    ocs = NULL;
+    for (dval = dref->attrVals; dval != NULL; dval = dval->next) {
+        if (strcasecmp("objectClass", dval->type) == 0) {
+            if (dval->vals == NULL) {
+                DEBUG(4, ("No value for objectClass, skipping\n"));
+                continue;
+            }
+
+            for(len=0; dval->vals[len].bv_val; len++);
+
+            ocs = talloc_array(tmp_ctx, const char *, len+1);
+            if (!ocs) {
+                ret = ENOMEM;
+                goto done;
+            }
+
+            for (i=0; i<len; i++) {
+                DEBUG(9, ("Dereferenced objectClass value: %s\n",
+                          dval->vals[i].bv_val));
+                ocs[i] = talloc_strdup(ocs, dval->vals[i].bv_val);
+                if (!ocs[i]) {
+                    ret = ENOMEM;
+                    goto done;
+                }
+            }
+            ocs[i] = NULL;
+            break;
+        }
+    }
+    if (!ocs) {
+        DEBUG(1, ("Unknown entry type, no objectClasses found!\n"));
+        ret = EINVAL;
+        goto done;
+    }
+
+    for (mi = 0; mi < num_maps; mi++) {
+        map = NULL;
+
+        for (i=0; ocs[i]; i++) {
+            /* the objectclass is always the first name in the map */
+            if (strcasecmp(minfo[mi].map[0].name, ocs[i]) == 0) {
+                DEBUG(9, ("Found map for objectclass '%s'\n", ocs[i]));
+                map = minfo[mi].map;
+                num_attrs = minfo[mi].num_attrs;
+                break;
+            }
+        }
+        if (!map) continue;
+
+        res[mi]->attrs = sysdb_new_attrs(res[mi]);
+        if (!res[mi]->attrs) {
+            ret = ENOMEM;
+            goto done;
+        }
+
+        ret = sysdb_attrs_add_string(res[mi]->attrs, SYSDB_ORIG_DN,
+                                     orig_dn);
+        if (ret) {
+            goto done;
+        }
+
+        for (dval = dref->attrVals; dval != NULL; dval = dval->next) {
+            DEBUG(8, ("Dereferenced attribute: %s\n", dval->type));
+
+            for (a = 1; a < num_attrs; a++) {
+                /* check if this attr is valid with the chosen schema */
+                if (!map[a].name) continue;
+                /* check if it is an attr we are interested in */
+                if (strcasecmp(dval->type, map[a].name) == 0) break;
+            }
+
+            /* interesting attr */
+            if (a < num_attrs) {
+                name = map[a].sys_name;
+            } else {
+                continue;
+            }
+
+            if (dval->vals == NULL) {
+                DEBUG(4, ("No value for attribute %s, skipping\n", name));
+                continue;
+            }
+
+            for (i=0; dval->vals[i].bv_val; i++) {
+                DEBUG(9, ("Dereferenced attribute value: %s\n",
+                          dval->vals[i].bv_val));
+                v.data = (uint8_t *) dval->vals[i].bv_val;
+                v.length = dval->vals[i].bv_len;
+
+                ret = sysdb_attrs_add_val(res[mi]->attrs, name, &v);
+                if (ret) goto done;
+            }
+        }
+    }
+
+
+    *_res = talloc_steal(mem_ctx, res);
+    ret = EOK;
+done:
+    talloc_zfree(tmp_ctx);
+    return ret;
+}
+
 /* =Get-DN-from-message=================================================== */
 
 int sdap_get_msg_dn(TALLOC_CTX *memctx, struct sdap_handle *sh,
diff --git a/src/providers/ldap/sdap.h b/src/providers/ldap/sdap.h
index cef2d23..e98b27b 100644
--- a/src/providers/ldap/sdap.h
+++ b/src/providers/ldap/sdap.h
@@ -335,6 +335,13 @@ int sdap_parse_group(TALLOC_CTX *memctx, struct sdap_options *opts,
                      struct sdap_handle *sh, struct sdap_msg *sm,
                      struct sysdb_attrs **_attrs, char **_dn);
 
+errno_t sdap_parse_deref(TALLOC_CTX *mem_ctx,
+                         struct sdap_attr_map_info *minfo,
+                         size_t num_maps,
+                         struct sdap_handle *sh,
+                         LDAPDerefRes *dref,
+                         struct sdap_deref_attrs ***_res);
+
 int sdap_get_msg_dn(TALLOC_CTX *memctx, struct sdap_handle *sh,
                     struct sdap_msg *sm, char **_dn);
 
diff --git a/src/providers/ldap/sdap_async.c b/src/providers/ldap/sdap_async.c
index bcc1813..9d319bf 100644
--- a/src/providers/ldap/sdap_async.c
+++ b/src/providers/ldap/sdap_async.c
@@ -1285,6 +1285,216 @@ int sdap_get_generic_recv(struct tevent_req *req,
     return EOK;
 }
 
+/* ==OpenLDAP deref search============================================== */
+static int sdap_x_deref_create_control(struct sdap_handle *sh,
+                                       const char *deref_attr,
+                                       const char **attrs,
+                                       LDAPControl **ctrl);
+
+static void sdap_x_deref_search_done(struct tevent_req *subreq);
+
+static errno_t sdap_x_deref_parse_entry(struct sdap_handle *sh,
+                                        struct sdap_msg *msg,
+                                        void *pvt);
+struct sdap_x_deref_search_state {
+    struct sdap_handle *sh;
+    struct sdap_op *op;
+    struct sdap_attr_map_info *maps;
+
+    struct sdap_deref_reply dreply;
+    int num_maps;
+};
+
+static struct tevent_req *
+sdap_x_deref_search_send(TALLOC_CTX *memctx, struct tevent_context *ev,
+                         struct sdap_options *opts, struct sdap_handle *sh,
+                         const char *base_dn, const char *deref_attr,
+                         const char **attrs, struct sdap_attr_map_info *maps,
+                         int num_maps, int timeout)
+{
+    struct tevent_req *req = NULL;
+    struct tevent_req *subreq = NULL;
+    struct sdap_x_deref_search_state *state;
+    LDAPControl *ctrls[2] = { NULL, NULL };
+    int ret;
+
+    req = tevent_req_create(memctx, &state, struct sdap_x_deref_search_state);
+    if (!req) return NULL;
+
+    state->sh = sh;
+    state->maps = maps;
+    state->op = NULL;
+    state->num_maps = num_maps;
+
+    ret = sdap_x_deref_create_control(sh, deref_attr, attrs, &ctrls[0]);
+    if (ret != EOK) {
+        DEBUG(1, ("Could not create OpenLDAP deref control\n"));
+        talloc_zfree(req);
+        return NULL;
+    }
+
+    DEBUG(6, ("Dereferencing entry [%s] using OpenLDAP deref\n", base_dn));
+    subreq = sdap_get_generic_ext_send(state, ev, opts, sh, base_dn,
+                                       LDAP_SCOPE_BASE, NULL, attrs,
+                                       false, ctrls, NULL, 0, timeout,
+                                       sdap_x_deref_parse_entry,
+                                       state);
+    ldap_control_free(ctrls[0]);
+    if (!subreq) {
+        talloc_zfree(req);
+        return NULL;
+    }
+    tevent_req_set_callback(subreq, sdap_x_deref_search_done, req);
+
+    return req;
+}
+
+static int sdap_x_deref_create_control(struct sdap_handle *sh,
+                                       const char *deref_attr,
+                                       const char **attrs,
+                                       LDAPControl **ctrl)
+{
+    struct berval derefval;
+    int ret;
+    static LDAPDerefSpec ds;
+
+    ds.derefAttr = discard_const(deref_attr);
+    ds.attributes = discard_const(attrs);
+
+    ret = ldap_create_deref_control_value(sh->ldap, &ds, &derefval);
+    if (ret != LDAP_SUCCESS) {
+        DEBUG(1, ("sss_ldap_control_create failed: %s\n",
+                  ldap_err2string(ret)));
+        return ret;
+    }
+
+    ret = sdap_control_create(sh, LDAP_CONTROL_X_DEREF,
+                              1, &derefval, 1, ctrl);
+    ldap_memfree(derefval.bv_val);
+    if (ret != EOK) {
+        DEBUG(1, ("sss_ldap_control_create failed\n"));
+        return ret;
+    }
+
+    return EOK;
+}
+
+static errno_t sdap_x_deref_parse_entry(struct sdap_handle *sh,
+                                        struct sdap_msg *msg,
+                                        void *pvt)
+{
+    errno_t ret;
+    LDAPControl **ctrls = NULL;
+    LDAPControl **next = NULL;
+    LDAPControl **start = NULL;
+    LDAPControl *derefctrl = NULL;
+    LDAPDerefRes    *deref_res;
+    LDAPDerefRes    *dref;
+    struct sdap_deref_attrs **res;
+    TALLOC_CTX *tmp_ctx;
+
+    struct sdap_x_deref_search_state *state = talloc_get_type(pvt,
+                                            struct sdap_x_deref_search_state);
+
+    tmp_ctx = talloc_new(NULL);
+    if (!tmp_ctx) return ENOMEM;
+
+    ret = ldap_get_entry_controls(state->sh->ldap, msg->msg,
+                                  &ctrls);
+    if (ret != LDAP_SUCCESS) {
+        DEBUG(1, ("ldap_parse_result failed\n"));
+        goto done;
+    }
+
+    if (!ctrls) {
+        DEBUG(4, ("No controls found for entry\n"));
+        ret = ENOENT;
+        goto done;
+    }
+
+    start = ctrls;
+    res = NULL;
+    do {
+        derefctrl = ldap_control_find(LDAP_CONTROL_X_DEREF, start, &next);
+        if (!derefctrl) break;
+
+        DEBUG(9, ("Got deref control\n"));
+        start = next;
+
+        ret = ldap_parse_derefresponse_control(state->sh->ldap,
+                                               derefctrl,
+                                               &deref_res);
+        if (ret != LDAP_SUCCESS) {
+            DEBUG(1, ("ldap_parse_derefresponse_control failed: %s\n",
+                      ldap_err2string(ret)));
+            goto done;
+        }
+
+        for (dref = deref_res; dref; dref=dref->next) {
+            ret = sdap_parse_deref(tmp_ctx, state->maps, state->num_maps,
+                                   state->sh, dref, &res);
+            if (ret) {
+                DEBUG(1, ("sdap_parse_deref failed [%d]: %s\n",
+                          ret, strerror(ret)));
+                goto done;
+            }
+
+            ret = add_to_deref_reply(state, state->num_maps,
+                                     &state->dreply, res);
+            if (ret != EOK) {
+                DEBUG(1, ("add_to_deref_reply failed.\n"));
+                goto done;
+            }
+        }
+
+        DEBUG(9, ("All deref results from a single control parsed\n"));
+        ldap_derefresponse_free(deref_res);
+        deref_res = NULL;
+    } while (derefctrl);
+
+    ret = EOK;
+done:
+    talloc_zfree(tmp_ctx);
+    ldap_controls_free(ctrls);
+    ldap_derefresponse_free(deref_res);
+    return ret;
+}
+
+static void sdap_x_deref_search_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    int ret;
+
+    ret = sdap_get_generic_ext_recv(subreq);
+    talloc_zfree(subreq);
+    if (ret) {
+        DEBUG(4, ("sdap_get_generic_ext_recv failed [%d]: %s\n",
+                  ret, strerror(ret)));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    tevent_req_done(req);
+}
+
+static int
+sdap_x_deref_search_recv(struct tevent_req *req,
+                         TALLOC_CTX *mem_ctx,
+                         size_t *reply_count,
+                         struct sdap_deref_attrs ***reply)
+{
+    struct sdap_x_deref_search_state *state = tevent_req_data(req,
+                                            struct sdap_x_deref_search_state);
+
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+
+    *reply_count = state->dreply.reply_count;
+    *reply = talloc_steal(mem_ctx, state->dreply.reply);
+
+    return EOK;
+}
+
 /* ==Attribute scoped search============================================ */
 struct sdap_asq_search_state {
     struct sdap_attr_map_info *maps;
-- 
1.7.4.4

