1 /* adremap.c - Case-folding and DN-value remapping for AD proxies */
4 * Copyright 2015 Howard Chu <hyc@symas.com>.
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted only as authorized by the OpenLDAP
11 * A copy of this license is available in the file LICENSE in the
12 * top-level directory of the distribution or, alternatively, at
13 * <http://www.OpenLDAP.org/license.html>.
19 * This file implements an overlay that performs two remapping functions
20 * to allow older POSIX clients to use Microsoft AD:
21 * 1: downcase the values of a configurable list of attributes
22 * 2: dereference some DN-valued attributes and convert to their simple names
23 * e.g. generate memberUid based on member
26 #ifdef SLAPD_OVER_ADREMAP
33 #include <ac/string.h>
37 typedef struct adremap_dnv {
38 struct adremap_dnv *ad_next;
39 AttributeDescription *ad_dnattr; /* DN-valued attr to deref */
40 AttributeDescription *ad_deref; /* target attr's value to retrieve */
41 AttributeDescription *ad_newattr; /* New attr to collect new values */
42 ObjectClass *ad_group; /* group objectclass on target */
43 ObjectClass *ad_mapgrp; /* group objectclass to map */
44 ObjectClass *ad_refgrp; /* objectclass of target DN */
45 struct berval ad_refbase; /* base DN of target entries */
47 /* example: member uid memberUid */
49 typedef struct adremap_case {
50 struct adremap_case *ac_next;
51 AttributeDescription *ac_attr;
54 /* Per-instance configuration information */
55 typedef struct adremap_info {
56 adremap_case *ai_case; /* attrs to downcase */
57 adremap_dnv *ai_dnv; /* DN attrs to remap */
65 static ConfigDriver adremap_cf_case;
66 static ConfigDriver adremap_cf_dnv;
68 /* configuration attribute and objectclass */
69 static ConfigTable adremapcfg[] = {
70 { "adremap-downcase", "attrs", 2, 0, 0,
71 ARG_MAGIC|ADREMAP_CASE, adremap_cf_case,
73 "NAME 'olcADremapDowncase' "
74 "DESC 'List of attributes to casefold to lower case' "
75 "SYNTAX OMsDirectoryString )", NULL, NULL },
76 { "adremap-dnmap", "dnattr targetattr newattr remoteOC localOC targetOC baseDN", 8, 8, 0,
77 ARG_MAGIC|ADREMAP_DNV, adremap_cf_dnv,
79 "NAME 'olcADremapDNmap' "
80 "DESC 'DN attr to map, attr from target to use, attr to generate, objectclass of remote"
81 " group, objectclass mapped group, objectclass of target entry, base DN of target entry' "
82 "SYNTAX OMsDirectoryString )", NULL, NULL },
83 { NULL, NULL, 0, 0, 0, ARG_IGNORED }
86 static ConfigOCs adremapocs[] = {
88 "NAME 'olcADremapConfig' "
89 "DESC 'AD remap configuration' "
90 "SUP olcOverlayConfig "
91 "MAY ( olcADremapDowncase $ olcADremapDNmap ) )",
92 Cft_Overlay, adremapcfg, NULL, NULL },
97 adremap_cf_case(ConfigArgs *c)
99 BackendDB *be = (BackendDB *)c->be;
100 slap_overinst *on = (slap_overinst *)c->bi;
101 adremap_info *ai = on->on_bi.bi_private;
102 adremap_case *ac, **a2;
103 int rc = ARG_BAD_CONF;
106 case SLAP_CONFIG_EMIT:
107 for (ac = ai->ai_case; ac; ac=ac->ac_next) {
108 rc = value_add_one(&c->rvalue_vals, &ac->ac_attr->ad_cname);
112 case LDAP_MOD_DELETE:
114 for (ac = ai->ai_case; ac; ac=ai->ai_case) {
115 ai->ai_case = ac->ac_next;
120 for (i=0, a2 = &ai->ai_case; i<c->valx; i++, a2 = &(*a2)->ac_next);
131 rc = slap_str2ad(c->argv[1], &ad.ac_attr, &text);
133 for (a2 = &ai->ai_case; *a2; a2 = &(*a2)->ac_next);
134 ac = ch_malloc(sizeof(adremap_case));
136 ac->ac_attr = ad.ac_attr;
145 adremap_cf_dnv(ConfigArgs *c)
147 BackendDB *be = (BackendDB *)c->be;
148 slap_overinst *on = (slap_overinst *)c->bi;
149 adremap_info *ai = on->on_bi.bi_private;
150 adremap_dnv *ad, **a2;
151 int rc = ARG_BAD_CONF;
154 case SLAP_CONFIG_EMIT:
155 for (ad = ai->ai_dnv; ad; ad=ad->ad_next) {
158 bv.bv_len = ad->ad_dnattr->ad_cname.bv_len + ad->ad_deref->ad_cname.bv_len + ad->ad_newattr->ad_cname.bv_len + 2;
159 bv.bv_len += ad->ad_group->soc_cname.bv_len + ad->ad_mapgrp->soc_cname.bv_len + ad->ad_refgrp->soc_cname.bv_len + 3;
160 bv.bv_len += ad->ad_refbase.bv_len + 3;
161 bv.bv_val = ch_malloc(bv.bv_len + 1);
162 ptr = lutil_strcopy(bv.bv_val, ad->ad_dnattr->ad_cname.bv_val);
164 ptr = lutil_strcopy(ptr, ad->ad_deref->ad_cname.bv_val);
166 ptr = lutil_strcopy(ptr, ad->ad_newattr->ad_cname.bv_val);
168 ptr = lutil_strcopy(ptr, ad->ad_group->soc_cname.bv_val);
170 ptr = lutil_strcopy(ptr, ad->ad_mapgrp->soc_cname.bv_val);
172 ptr = lutil_strcopy(ptr, ad->ad_refgrp->soc_cname.bv_val);
175 ptr = lutil_strcopy(ptr, ad->ad_refbase.bv_val);
178 ber_bvarray_add(&c->rvalue_vals, &bv);
180 if (ai->ai_dnv) rc = 0;
182 case LDAP_MOD_DELETE:
184 for (ad = ai->ai_dnv; ad; ad=ai->ai_dnv) {
185 ai->ai_dnv = ad->ad_next;
190 for (i=0, a2 = &ai->ai_dnv; i<c->valx; i++, a2 = &(*a2)->ad_next);
199 adremap_dnv av = {0};
201 rc = slap_str2ad(c->argv[1], &av.ad_dnattr, &text);
203 if (av.ad_dnattr->ad_type->sat_syntax != slap_schema.si_syn_distinguishedName) {
205 snprintf(c->cr_msg, sizeof(c->cr_msg), "<%s> not a DN-valued attribute",
207 Debug(LDAP_DEBUG_ANY, "%s: %s(%s)\n", c->log, c->cr_msg, c->argv[1]);
210 rc = slap_str2ad(c->argv[2], &av.ad_deref, &text);
212 rc = slap_str2ad(c->argv[3], &av.ad_newattr, &text);
214 av.ad_group = oc_find(c->argv[4]);
219 av.ad_mapgrp = oc_find(c->argv[5]);
224 av.ad_refgrp = oc_find(c->argv[6]);
229 ber_str2bv(c->argv[7], 0, 0, &dn);
230 rc = dnNormalize(0, NULL, NULL, &dn, &av.ad_refbase, NULL);
233 for (a2 = &ai->ai_dnv; *a2; a2 = &(*a2)->ad_next);
234 ad = ch_malloc(sizeof(adremap_dnv));
236 ad->ad_dnattr = av.ad_dnattr;
237 ad->ad_deref = av.ad_deref;
238 ad->ad_newattr = av.ad_newattr;
239 ad->ad_group = av.ad_group;
240 ad->ad_mapgrp = av.ad_mapgrp;
241 ad->ad_refgrp = av.ad_refgrp;
242 ad->ad_refbase = av.ad_refbase;
250 typedef struct adremap_ctx {
253 AttributeDescription *ad;
263 adremap_ctx *ctx = op->o_callback->sc_private;
264 slap_overinst *on = ctx->on;
265 adremap_info *ai = on->on_bi.bi_private;
271 if (rs->sr_type != REP_SEARCH)
272 return SLAP_CB_CONTINUE;
274 /* we munged the attr list, restore it to original */
278 for (i=0; rs->sr_attrs[i].an_name.bv_val; i++) {
279 if (rs->sr_attrs[i].an_desc == ctx->ad) {
280 rs->sr_attrs[i] = ctx->an;
284 /* Usually rs->sr_attrs is just op->ors_attrs, but
285 * overlays like rwm may make a new copy. Fix both
288 if (op->ors_attrs != rs->sr_attrs) {
289 for (i=0; op->ors_attrs[i].an_name.bv_val; i++) {
290 if (op->ors_attrs[i].an_desc == ctx->ad) {
291 op->ors_attrs[i] = ctx->an;
298 for (ac = ai->ai_case; ac; ac = ac->ac_next) {
299 a = attr_find(e->e_attrs, ac->ac_attr);
302 if (!(rs->sr_flags & REP_ENTRY_MODIFIABLE)) {
304 rs_replace_entry(op, rs, on, e);
305 rs->sr_flags |= REP_ENTRY_MODIFIABLE|REP_ENTRY_MUSTBEFREED;
306 a = attr_find(e->e_attrs, ac->ac_attr);
308 for (i=0; i<a->a_numvals; i++) {
309 unsigned char *c = a->a_vals[i].bv_val;
310 for (j=0; j<a->a_vals[i].bv_len; j++)
312 c[j] = tolower(c[j]);
316 for (ad = ai->ai_dnv; ad; ad = ad->ad_next) {
317 a = attr_find(e->e_attrs, ad->ad_dnattr);
322 if (!(rs->sr_flags & REP_ENTRY_MODIFIABLE)) {
324 rs_replace_entry(op, rs, on, e);
325 rs->sr_flags |= REP_ENTRY_MODIFIABLE|REP_ENTRY_MUSTBEFREED;
326 a = attr_find(e->e_attrs, ad->ad_dnattr);
328 for (i=0; i<a->a_numvals; i++) {
330 dv = ad->ad_deref->ad_cname;
331 /* If the RDN uses the deref attr, just use it directly */
332 if (a->a_nvals[i].bv_val[dv.bv_len] == '=' &&
333 !memcmp(a->a_nvals[i].bv_val, dv.bv_val, dv.bv_len)) {
334 struct berval bv, nv;
338 bv.bv_val += dv.bv_len + 1;
339 ptr = strchr(bv.bv_val, ',');
341 bv.bv_len = ptr - bv.bv_val;
343 bv.bv_len -= dv.bv_len+1;
344 nv.bv_val += dv.bv_len + 1;
345 ptr = strchr(nv.bv_val, ',');
347 nv.bv_len = ptr - nv.bv_val;
349 nv.bv_len -= dv.bv_len+1;
350 attr_merge_one(e, ad->ad_newattr, &bv, &nv);
352 /* otherwise look up the deref attr */
354 rc = be_entry_get_rw(op, &a->a_nvals[i], NULL, ad->ad_deref, 0, &n);
356 dr = attr_find(n->e_attrs, ad->ad_deref);
358 attr_merge_one(e, ad->ad_newattr, dr->a_vals, dr->a_nvals);
359 be_entry_release_r(op, n);
365 return SLAP_CB_CONTINUE;
368 static int adremap_refsearch(
373 if (rs->sr_type == REP_SEARCH) {
374 slap_callback *sc = op->o_callback;
375 struct berval *dn = sc->sc_private;
376 ber_dupbv_x(dn, &rs->sr_entry->e_nname, op->o_tmpmemctx);
382 static adremap_dnv *adremap_filter(
387 slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
388 Filter *f = op->ors_filter, *fn = NULL;
389 adremap_dnv *ad = NULL;
393 /* Do we need to munge the filter? First see if it's of
394 * the form (objectClass=<mapgrp>)
395 * or form (&(objectClass=<mapgrp>)...)
396 * or form (&(&(objectClass=<mapgrp>)...)...)
398 if (f->f_choice == LDAP_FILTER_AND && f->f_and) {
403 if (f->f_choice == LDAP_FILTER_AND && f->f_and) {
407 if (f->f_choice == LDAP_FILTER_EQUALITY &&
408 f->f_av_desc == slap_schema.si_ad_objectClass) {
409 struct berval bv = f->f_av_value;
411 for (ad = ai->ai_dnv; ad; ad = ad->ad_next) {
412 if (!ber_bvstrcasecmp( &bv, &ad->ad_mapgrp->soc_cname )) {
413 /* Now check to see if next element is (<newattr>=foo) */
415 if (fn && fn->f_choice == LDAP_FILTER_EQUALITY &&
416 fn->f_av_desc == ad->ad_newattr) {
418 AttributeAssertion aa[2] = {0};
420 slap_callback cb = {0};
421 SlapReply rs = {REP_RESULT};
422 struct berval dn = BER_BVNULL;
424 /* It's a match, setup a search with filter
425 * (&(objectclass=<refgrp>)(<deref>=foo))
427 fr[0].f_choice = LDAP_FILTER_AND;
428 fr[0].f_and = &fr[1];
431 fr[1].f_choice = LDAP_FILTER_EQUALITY;
432 fr[1].f_ava = &aa[0];
433 fr[1].f_av_desc = slap_schema.si_ad_objectClass;
434 fr[1].f_av_value = ad->ad_refgrp->soc_cname;
435 fr[1].f_next = &fr[2];
437 fr[2].f_choice = LDAP_FILTER_EQUALITY;
438 fr[2].f_ava = &aa[1];
439 fr[2].f_av_desc = ad->ad_deref;
440 fr[2].f_av_value = fn->f_av_value;
443 /* Search with this filter to retrieve target DN */
445 op2.o_callback = &cb;
446 cb.sc_response = adremap_refsearch;
448 op2.o_req_dn = ad->ad_refbase;
449 op2.o_req_ndn = ad->ad_refbase;
451 filter2bv_x(op, fr, &op2.ors_filterstr);
452 op2.ors_deref = LDAP_DEREF_NEVER;
454 op2.ors_tlimit = SLAP_NO_LIMIT;
455 op2.ors_attrs = slap_anlist_no_attrs;
456 op2.ors_attrsonly = 1;
457 op2.o_no_schema_check = 1;
458 op2.o_bd->bd_info = (BackendInfo *)on->on_info;
459 op2.o_bd->be_search(&op2, &rs);
460 op2.o_bd->bd_info = (BackendInfo *)on;
461 op->o_tmpfree(op2.ors_filterstr.bv_val, op->o_tmpmemctx);
463 if (!dn.bv_len) { /* no match was found */
468 if (rs.sr_err) { /* sizelimit exceeded, etc.: invalid name */
469 op->o_tmpfree(dn.bv_val, op->o_tmpmemctx);
474 /* Build a new filter of form
475 * (&(objectclass=<group>)(<dnattr>=foo-DN)...)
477 f = op->o_tmpalloc(sizeof(Filter), op->o_tmpmemctx);
478 f->f_choice = LDAP_FILTER_AND;
482 f->f_and = op->o_tmpalloc(sizeof(Filter), op->o_tmpmemctx);
484 f->f_choice = LDAP_FILTER_EQUALITY;
485 f->f_ava = op->o_tmpcalloc(1, sizeof(AttributeAssertion), op->o_tmpmemctx);
486 f->f_av_desc = slap_schema.si_ad_objectClass;
487 ber_dupbv_x(&f->f_av_value, &ad->ad_group->soc_cname, op->o_tmpmemctx);
489 f->f_next = op->o_tmpalloc(sizeof(Filter), op->o_tmpmemctx);
491 f->f_choice = LDAP_FILTER_EQUALITY;
492 f->f_ava = op->o_tmpcalloc(1, sizeof(AttributeAssertion), op->o_tmpmemctx);
493 f->f_av_desc = ad->ad_dnattr;
496 f->f_next = fn->f_next;
499 /* Build a new filter of form
500 * (objectclass=<group>)
502 f->f_next = NULL; /* disconnect old chain */
504 f = op->o_tmpalloc(sizeof(Filter), op->o_tmpmemctx);
505 f->f_choice = LDAP_FILTER_EQUALITY;
506 f->f_ava = op->o_tmpcalloc(1, sizeof(AttributeAssertion), op->o_tmpmemctx);
507 f->f_av_desc = slap_schema.si_ad_objectClass;
508 ber_dupbv_x(&f->f_av_value, &ad->ad_group->soc_cname, op->o_tmpmemctx);
510 /* If there was a wrapping (&), attach it. */
512 fnew = op->o_tmpalloc(sizeof(Filter), op->o_tmpmemctx);
513 fnew->f_choice = LDAP_FILTER_AND;
523 f = op->o_tmpalloc(sizeof(Filter), op->o_tmpmemctx);
524 f->f_choice = LDAP_FILTER_AND;
525 f->f_and = fnew->f_and;
526 f->f_next = f->f_and->f_next;
527 f->f_and->f_next = op->ors_filter->f_and->f_and->f_next;
528 op->ors_filter->f_and->f_and->f_next = NULL;
531 filter_free_x(op, op->ors_filter, 1);
532 op->o_tmpfree(op->ors_filterstr.bv_val, op->o_tmpmemctx);
533 op->ors_filter = fnew;
534 filter2bv_x(op, op->ors_filter, &op->ors_filterstr);
548 slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
549 adremap_info *ai = (adremap_info *) on->on_bi.bi_private;
551 adremap_dnv *ad = NULL;
554 /* Is this our own internal search? Ignore it */
555 if (op->o_no_schema_check)
556 return SLAP_CB_CONTINUE;
559 /* check for filter match, fallthru if none */
560 ad = adremap_filter(op, ai);
562 cb = op->o_tmpcalloc(1, sizeof(slap_callback)+sizeof(adremap_ctx), op->o_tmpmemctx);
563 cb->sc_response = adremap_search_resp;
564 cb->sc_private = cb+1;
565 cb->sc_next = op->o_callback;
567 ctx = cb->sc_private;
569 if (ad && op->ors_attrs) { /* see if we need to remap a search attr */
571 for (i=0; op->ors_attrs[i].an_name.bv_val; i++) {
572 if (op->ors_attrs[i].an_desc == ad->ad_newattr) {
574 ctx->ad = ad->ad_dnattr;
575 ctx->an = op->ors_attrs[i];
576 op->ors_attrs[i].an_desc = ad->ad_dnattr;
577 op->ors_attrs[i].an_name = ad->ad_dnattr->ad_cname;
582 return SLAP_CB_CONTINUE;
591 slap_overinst *on = (slap_overinst *) be->bd_info;
593 /* initialize private structure to store configuration */
594 on->on_bi.bi_private = ch_calloc( 1, sizeof(adremap_info) );
605 slap_overinst *on = (slap_overinst *) be->bd_info;
606 adremap_info *ai = (adremap_info *) on->on_bi.bi_private;
611 for (ac = ai->ai_case; ac; ac = ai->ai_case) {
612 ai->ai_case = ac->ac_next;
615 for (ad = ai->ai_dnv; ad; ad = ai->ai_dnv) {
616 ai->ai_dnv = ad->ad_next;
624 static slap_overinst adremap;
626 int adremap_initialize()
630 adremap.on_bi.bi_type = "adremap";
631 adremap.on_bi.bi_db_init = adremap_db_init;
632 adremap.on_bi.bi_db_destroy = adremap_db_destroy;
633 adremap.on_bi.bi_op_search = adremap_search;
635 /* register configuration directives */
636 adremap.on_bi.bi_cf_ocs = adremapocs;
637 code = config_register_schema( adremapcfg, adremapocs );
638 if ( code ) return code;
640 return overlay_register( &adremap );
643 #if SLAPD_OVER_ADREMAP == SLAPD_MOD_DYNAMIC
644 int init_module(int argc, char *argv[]) {
645 return adremap_initialize();
649 #endif /* defined(SLAPD_OVER_ADREMAP) */