# # OpenLDAP Default Values overlay # Davor Ocelic, docelic@spinlocksolutions.com # Spinlock Solutions, http://www.spinlocksolutions.com/ # OpenLDAP, http://www.OpenLDAP.org/ # # # To patch OpenLDAP tree: # cvs -d :pserver:anonymous@cvs.openldap.org:2401/repo/OpenLDAP login # (password is OpenLDAP) # cvs -d :pserver:anonymous@cvs.openldap.org:2401/repo/OpenLDAP co openldap-src # cd openldap-src # patch -p1 < /tmp/openldap-defvals-11apr2008.patch # rm configure # autoconf # ./configure --enable-overlays=mod..... # diff --git a/configure.in b/configure.in index 1c55279..fa2b3e2 100644 --- a/configure.in +++ b/configure.in @@ -332,6 +332,7 @@ Overlays="accesslog \ auditlog \ constraint \ dds \ + defvals \ dyngroup \ dynlist \ memberof \ @@ -359,6 +360,8 @@ OL_ARG_ENABLE(constraint,[ --enable-constraint Attribute Constraint overlay no, [no yes mod], ol_enable_overlays) OL_ARG_ENABLE(dds,[ --enable-dds Dynamic Directory Services overlay], no, [no yes mod], ol_enable_overlays) +OL_ARG_ENABLE(defvals,[ --enable-defvals Default Values overlay], + no, [no yes mod], ol_enable_overlays) OL_ARG_ENABLE(dyngroup,[ --enable-dyngroup Dynamic Group overlay], no, [no yes mod], ol_enable_overlays) OL_ARG_ENABLE(dynlist,[ --enable-dynlist Dynamic List overlay], @@ -530,6 +533,7 @@ BUILD_ACCESSLOG=no BUILD_AUDITLOG=no BUILD_CONSTRAINT=no BUILD_DDS=no +BUILD_DEFVALS=no BUILD_DENYOP=no BUILD_DYNGROUP=no BUILD_DYNLIST=no @@ -2714,6 +2718,18 @@ if test "$ol_enable_dds" != no ; then AC_DEFINE_UNQUOTED(SLAPD_OVER_DDS,$MFLAG,[define for Dynamic Directory Services overlay]) fi +if test "$ol_enable_defvals" != no ; then + BUILD_DEFVALS=$ol_enable_defvals + if test "$ol_enable_defvals" = mod ; then + MFLAG=SLAPD_MOD_DYNAMIC + SLAPD_DYNAMIC_OVERLAYS="$SLAPD_DYNAMIC_OVERLAYS defvals.la" + else + MFLAG=SLAPD_MOD_STATIC + SLAPD_STATIC_OVERLAYS="$SLAPD_STATIC_OVERLAYS defvals.o" + fi + AC_DEFINE_UNQUOTED(SLAPD_OVER_DEFVALS,$MFLAG,[define for Default Values overlay]) +fi + if test "$ol_enable_dyngroup" != no ; then BUILD_DYNGROUP=$ol_enable_dyngroup if test "$ol_enable_dyngroup" = mod ; then @@ -2928,6 +2944,7 @@ dnl overlays AC_SUBST(BUILD_AUDITLOG) AC_SUBST(BUILD_CONSTRAINT) AC_SUBST(BUILD_DDS) + AC_SUBST(BUILD_DEFVALS) AC_SUBST(BUILD_DENYOP) AC_SUBST(BUILD_DYNGROUP) AC_SUBST(BUILD_DYNLIST) diff --git a/doc/man/man5/slapd.overlays.5 b/doc/man/man5/slapd.overlays.5 index ae3b662..71b0f5d 100644 --- a/doc/man/man5/slapd.overlays.5 +++ b/doc/man/man5/slapd.overlays.5 @@ -47,6 +47,12 @@ Dynamic Directory Services. This overlay supports dynamic objects, which have a limited life after which they expire and are automatically deleted. .TP +.B defvals +Default Values. +This overlay fills in missing attribute values for a requested entry from +other, "default" entries. The whole process is configurable and various +practical behaviors are possible. +.TP .B dyngroup Dynamic Group. This is a demo overlay which extends the Compare operation to detect @@ -128,6 +134,7 @@ default slapd configuration directory .BR slapo\-chain (5), .BR slapo\-constraint (5), .BR slapo\-dds (5), +.BR slapo\-defvals (5), .BR slapo\-dyngroup (5), .BR slapo\-dynlist (5), .BR slapo\-pcache (5), diff --git a/doc/man/man5/slapo-defvals.5 b/doc/man/man5/slapo-defvals.5 new file mode 100644 index 0000000..41c4327 --- /dev/null +++ b/doc/man/man5/slapo-defvals.5 @@ -0,0 +1,332 @@ +.TH SLAPO-DEFVALS 5 "RELEASEDATE" "OpenLDAP LDVERSION" +.\" Copyright 1998-2008 The OpenLDAP Foundation, All Rights Reserved. +.\" Copyright 2006-2008 Davor Ocelic, Spinlock Solutions +.\" Copying restrictions apply. See the COPYRIGHT file. +.\" $OpenLDAP: pkg/ldap/doc/man/man5/slapo-defvals.5,v 1.0 2006/12/08 16:25:11 docelic Exp $ +.SH NAME +slapo-defvals \- Default Values overlay +.SH SYNOPSIS +ETCDIR/slapd.conf +.SH DESCRIPTION + +The +.B defvals +overlay to +.BR slapd (8) +allows fallback to default values for empty attributes. + +Any time an entry is returned, the default values try to fill in or +expand the list of its attributes. There are a few specifics to this process: + +The "view" of the directory is unaltered for system administrators (those +who bind with credentials for which be_isroot() function returns TRUE); +you must bind with non-admin credentials to see this overlay in effect. + +The fallback only takes places if the requested entry is below the configured +starting point in the tree, and it either has attributes which point to the +default entries, or a fixed list of default entries was specified in the +config file. + +From the default entries, only non-operational and non-required attributes +are considered for merging into the original entry (and returning to the +client). + +The overlay is, through "wholesale" attribute inclusion, able to violate the +schema specification for a given entry. Sometimes, this might be desirable to +achieve interesting effects which might not primarily relate to default values. +Most of the time, however, you will want to have the control over the merging +in regard to schema conformance. Two options, +.B schemacheck +and +.B append-always +govern this process. The former can take values 0, 1 or 2, for +no schema checking, strict schema checking (attribute allowance + +single/multi value) and loose schema checking (only attribute +allowance), respectively. The latter can take values 0 or 1, for +default attribute merging only when no value is specified in the original +entry, or always. + + +.SH CONFIGURATION +This section describes the old-style, slapd.conf configuration. If you are +using new back-config style, it is expected that you know how to apply the +existing information to that configuration mode. + +The config directives that are specific to the +.B defvals +overlay must be prefixed by +.BR defvals\- , +to avoid potential conflicts with directives specific to the underlying +database or to other stacked overlays. + +.TP +.B overlay defvals +This directive adds the defvals overlay to the current database, +or to the frontend, if used before any database instantiation; see +.BR slapd.conf (5) +for details. + +.LP +The following +.B slapd.conf +configuration option are defined for the +.B defvals +overlay: +.TP +.B defvals-attr +The value +.B +is the starting point in the tree below which you want the defaults in action. + +The value +.B +is a tri-state (0/1/2) value which, when 0, does not enforce any schema +checks, allowing all default attributes to get merged into the original +entry and potentially break the entry's schema conformance. When 1, it +enables all schema checks in a way that attributes not allowed by the +schema are skipped, and attributes which can only hold a single value +(and do have one) are +.B not +appended with default values, regardless of the +.B append-always +boolean. When 2, it performs the schema check, but relaxes the +single-value restriction. A setting of "1" is the correct and safe bet. + +The value +.B +is a boolean which, when 0, signifies that the default attribute values +are to be added to the original entry only when they are undefined there. +When 1, it signifies that the default attributes are always to be merged +into the original entry, even if they already have a value there. +The setting of 1 partly depends on the +.B schemacheck +option, since if the attribute is defined as "SINGLE-VALUE" and is already +defined in the original entry, +.B schemacheck +must be set to 2 to allow append to have practical effect. Any setting +here is a safe bet, as long as +.B schemacheck +is set to "1". + +The value +.B +are the names of the attributes which are allowed to contain pointers +(DNs) to the default entries. + +.TP +.B defvals-dn +The value +.B +is the starting point in the tree below which you want the defaults in action. + +The value +.B +is a tri-state (0/1/2) value which, when 0, does not enforce any schema +checks, allowing all default attributes to get merged into the original +entry and potentially break the entry's schema conformance. When 1, it +enables all schema checks in a way that attributes not allowed by the +schema are skipped, and attributes which can only hold a single value +(and do have one) are +.B not +appended with default values, regardless of the +.B append-always +boolean. When 2, it performs the schema check, but relaxes the +single-value restriction. A setting of "1" is the correct and safe bet. + +The value +.B +is a boolean which, when 0, signifies that the default attribute values +are to be added to the original entry only when they are undefined there. +When 1, it signifies that the default attributes are always to be merged +into the original entry, even if they already have a value there. +The setting of 1 partly depends on the +.B schemacheck +option, since if the attribute is defined as "SINGLE-VALUE" and is already +defined in the original entry, +.B schemacheck +must be set to 2 to allow append to have practical effect. Any setting +here is a safe bet, as long as +.B schemacheck +is set to "1". + +The value +.B +are a fixed list of entries (DNs) you want to consult for the default values. + +.LP +The options may have multiple values and multiple occurrences, and they must appear after the +.B overlay defvals +directive. + +In the process of applying defaults, the first entry to supply the default wins. + +.SH EXAMPLE +This example looks up the value of the 'seeAlso' attribute within an entry +(which is a DN pointing to another entry) and fills in values for all the +attributes missing in the original entry: + +.LP +.nf + moduleload defvals.la + # ... + + database + # ... + + overlay defvals + defvals-attr dc=example,dc=com 1 0 seeAlso +.fi +.LP + +This example uses the specified DN unconditionally to +fill in values for all the attributes missing in the original entry: + +.LP +.nf + moduleload defvals.la + # ... + + database + # ... + + overlay defvals + defvals-dn dc=example,dc=com 1 0 \\ + uid=_defaults_,ou=People,dc=example,dc=com +.fi +.LP + +.SH COMPLETE EXAMPLE +Here are the standalone test LDIF file contents, slapd.conf definition and ldapsearch +invocation example: + +LDIF contents (sample.ldif): + +.nf + dn: dc=example,dc=com + objectClass: top + objectClass: dcObject + objectClass: organization + o: Example, Inc. + cn: Example, Inc. + dc: example + + dn: ou=People,dc=example,dc=com + ou: People + objectClass: top + objectClass: organizationalUnit + + dn: uid=joe,ou=People,dc=example,dc=com + objectClass: top + objectClass: account + uid: joe + seeAlso: uid=_defaults_,ou=People,dc=example,dc=com + description: THIS IS FROM THE ORIGINAL ENTRY + + dn: uid=_defaults_,ou=People,dc=example,dc=com + objectClass: top + objectClass: account + uid: _defaults_ + o: THIS IS FROM AN ENTRY POINTED TO BY seeAlso + + dn: uid=_defaults2_,ou=People,dc=example,dc=com + objectClass: top + objectClass: account + uid: _defaults2_ + l: THIS IS FROM AN ENTRY POINTED TO BY FIXED slapd.conf DIRECTIVE +.fi + +.LP +Optionally change every "dc=example,dc=com" to match your domain name, and load into LDAP with +.nf + "ldapadd -W -D cn=admin,dc=example,dc=com -x -c -f sample.ldif". +.fi + +(In order to successfully perform this operation, your OpenLDAP server needs +to be running, you must bind with proper administrator credentials, and your +schema must allow the 'account' objectClass. If it doesn't, it usually means +that not all required schema files have been included in slapd.conf; make +sure you include cosine.schema and nis.schema.) + +.LP +Load the overlay and the database s usual: + +.nf + moduleload defvals.la + # ... + + database + # ... +.fi + +.LP +Then add the following to slapd.conf (after the "database " stanza): + +.nf + overlay defvals + defvals-attr dc=example,dc=com 1 0 seeAlso + defvals-dn dc=example,dc=com 1 0 \\ + uid=_defaults2_,ou=People,dc=example,dc=com +.fi + +.LP +Finally, run ldapsearch: + +.nf + ldapsearch -x -b dc=example,dc=com uid=joe +.fi + +.LP +Where the output should look like: + +.nf + # joe, People, example.com + dn: uid=joe,ou=People,dc=example,dc=com + objectClass: top + objectClass: account + uid: joe + seeAlso: uid=_defaults_,ou=People,dc=example,dc=com + description: THIS IS FROM THE ORIGINAL ENTRY + o: THIS IS FROM AN ENTRY POINTED TO BY seeAlso + l: THIS IS FROM AN ENTRY POINTED TO BY FIXED slapd.conf DIRECTIVE +.fi + +.SH NOTES +.TP +The DNs pointed to by defvals-dn option are automatically normalized (that is, adjusted as necessary to match exact representation required by OpenLDAP functions). DNs that are part of entry attributes (those reached by following defvals-attr), however, are normalized automatically only if that's how they've been defined in the schema. So when using custom attributes for pointing to default values, make sure the schema normalizes their values. (An existing "seeAlso" attribute is a good schema template). +.TP +The default entry, regardless of how it was specified (through an entry attribute or directly in the config file), must be within the same backend as the original entry. +.TP +With the old slapd.conf configuration method, it is possible to specify multiple attributes or DNs on the same line, and practically have one config line result in multiple definitions being created internally. However, this must be avoided with the new back-config configuration method (to ensure that the dynamic rule deletion is working as expected). So when when back-config is used, limit yourself to one attribute or DN per configuration line. +.SH TODO +.TP +If attr is missing, so we go to add it, do we add only the first value or all values, if the attr is defined as single-value ? +.TP +Allow modify operation on the default attributes. LDAP clients will assume the entries are present and will issue modify operations which cannot work on missing entries. This should be solved by inserting a callback into the modify operation. For the moment, this produces an error, so don't try to change the values of the default attributes unless you are an administrator. You can also deny modification using OpenLDAP access rules. +.TP +Allow default entries to contain further references to other default entries themselves. Also add a limit on how deep this 'chaining' can go. Currently there is no any chaining, but you can achieve it by configuring multiple attributes and/or DNs to look up directly in the config file or the original entry attributes. +.TP +Allow explicit specification of attribute names for which you want the defaults in effect. Currently, all attributes from the default entry make their way into the original entry. It should be possible to specify names of the attributes. If that list is empty, then all attributes should be considered, honoring the setting of 'schemaheck' and 'append-always'. +.TP +Add tests. +.TP +Prevent user from saving invalid attribute name in olcDefValsAttr. The invalid name has the effect of not inserting anything in defvals_data* structure, but ldap still allows insertion, breaking order of elements in the linked list and LDAP config attributes, which should never be broken (or we can't rely on c->valx trick to figure out which entries to delete when user deletes some rule). We do return LDAP_NO_SUCH_ATTRIBUTE or something when there's an error, but still ldap goes on inserting the value.. +.TP +Make that the default entry does not have to be within the same backend. The simple solution is calling select_backend() before be_entry_get_rw(), and code that does that is for example seen in http://www.openldap.org/its/index.cgi/Contrib?id=4366;page=2. + +.SH FILES +.TP +/usr/local/etc/openldap/slapd.conf +default slapd configuration file +.SH SEE ALSO +.BR slapd.conf (5), +.BR slapd-config (5), +.BR slapd (8). + +.SH ACKNOWLEDGEMENTS +.P +This module was written in 2006 by Davor Ocelic for +Spinlock Solutions, http://www.spinlocksolutions.com/. +Thanks to Howard Chu, http://www.symas.com/, for +invaluable expert advice. + + diff --git a/include/portable.hin b/include/portable.hin index f309353..b793667 100644 --- a/include/portable.hin +++ b/include/portable.hin @@ -960,6 +960,9 @@ /* define for Dynamic Directory Services overlay */ #undef SLAPD_OVER_DDS +/* define for Default Values overlay */ +#undef SLAPD_OVER_DEFVALS + /* define for Dynamic Group overlay */ #undef SLAPD_OVER_DYNGROUP diff --git a/servers/slapd/overlays/Makefile.in b/servers/slapd/overlays/Makefile.in index 0737330..a7d3afb 100644 --- a/servers/slapd/overlays/Makefile.in +++ b/servers/slapd/overlays/Makefile.in @@ -18,6 +18,7 @@ SRCS = overlays.c \ auditlog.c \ constraint.c \ dds.c \ + defvals.c \ dyngroup.c \ dynlist.c \ memberof.c \ @@ -71,6 +72,9 @@ constraint.la : constraint.lo dds.la : dds.lo $(LTLINK_MOD) -module -o $@ dds.lo version.lo $(LINK_LIBS) +defvals.la : defvals.lo + $(LTLINK_MOD) -module -o $@ defvals.lo version.lo $(LINK_LIBS) + dyngroup.la : dyngroup.lo $(LTLINK_MOD) -module -o $@ dyngroup.lo version.lo $(LINK_LIBS) diff --git a/servers/slapd/overlays/defvals.c b/servers/slapd/overlays/defvals.c new file mode 100644 index 0000000..15d410c --- /dev/null +++ b/servers/slapd/overlays/defvals.c @@ -0,0 +1,802 @@ +/* defvals.c - Provide default values for attributes */ +/* $OpenLDAP: pkg/ldap/servers/slapd/overlays/defvals.c,v 1.5 2007/03/19 15:00:00 docelic Exp $ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 2006,2008 Davor Ocelic, http://www.spinlocksolutions.com/ + * Copyright 2006,2008 The OpenLDAP Foundation, http://www.openldap.org/ + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + */ + +#include "portable.h" + +/* This overlay provides default values for missing attributes. + * For complete documentation, see slapo-defvals(5) man page. */ + +#ifdef SLAPD_OVER_DEFVALS + +#include + +#include +#include + +#include "slap.h" +#include "config.h" + +/* Internal structure used to build a linked list, and hold our + * overlay configuration. In our case, and in the majority of other + * overlays too, each element in the linked list will hold a piece + * of configuration. So, each time you specify another defvals config + * line in slapd.conf (or value in slapd.d/cn=config (new-style options)), + * it will transform into internal representation as one struct + * defvals_data* element in the list. */ +typedef struct defvals_data { + struct defvals_data *di_next; + BerValue *di_suffix; /* Only work under this point in tree ... */ + AttributeDescription *di_ad; /* and either look for a specified attribute */ + BerVarray di_dn; /* or look at a fixed-location entry. */ + + int di_schemacheck; /* 0=no, 1=full, 2=tolerate break of + single-value schema rule for the attribute */ + int di_insert_policy; /* 0=when attribute is missing, 1=always */ +} defvals_data; + +/* ConfigDriver is needed for back-config (new-style configuration method), + * while the rest are, as you see, just function prototypes. */ +static ConfigDriver defvals_cf_gen; + +static int defvals_config( slap_overinst*, const char*, int, int, char**); +static int defvals_config_slapdconf( BackendDB*, const char*, int, int, char**); +static int defvals_config_slapdd( ConfigArgs *); + +static int argv2defvals_data(int, int, char**, char*, defvals_data*); +static int free_element( defvals_data *); + + +/*************************************************************************** + * * + * Configuration related code, until next mark ;) * + * * + ***************************************************************************/ + +/* This enum will be used later to determine which config attribute did the + * user change through back-config. You define the name in enum, then put + * ARG_type|DEFVALS_name in attr definition (just as you see below in + * defvals_cfats[]), and then later you test which option was it by doing + * if ( c->type == DEFVALS_name ) ... */ +enum { + DEFVALS_ATTR = 1, /* Identify olcDefValsAttr option in back-config */ + DEFVALS_DN /* Identify olcDefValsDN option in back-config */ +}; + +/* This is a table of accepted configuration attributes for our overlay + * ( cfats = config attributes ). + * This is only used with the new-style options where each config option + * is represented as a valid ldap attribute (has schema entry and all). + * So you can see that 8th attribute is the text that will be inserted + * in server's schema on each startup, to support this option. + * + * See the names and types of all arguments for ConfigTable in + * servers/slapd/config.h . + * + * Just one important thing is that, in our case here, as 7th argument + * we provide a function that will be called to process the data for + * the corresponding option. To be able to call the function, arg type + * (6th argument) must be set to ARG_MAGIC. Also, note that our handler + * function will receive the input split on whitespace - it will receive + * the familiar (argc, argv)-style options. This is the same behavior as + * with old config method - your function receives arguments that are + * already split and loaded into argv. */ +static ConfigTable defvals_cfats[] = { + { "defvals-attr", "suffix> op == SLAP_CONFIG_EMIT ) { + + /* If a new value is added to the config. We just call our function + * that will handle the work of adding an entry in our configuration. */ + } else if ( c->op == SLAP_CONFIG_ADD || c->op == LDAP_MOD_ADD ) { + rc = defvals_config_slapdd( c ); + + /* If a value is deleted from the config. NOTE: the above story about + * keeping internal structure and "text" in sync had a purpose :) + * In LDAP, when you define multiple values for an attribute (such as, + * multiple values for some of your config options), then in LDAP, + * it is still saved as just one attribute, although with multiple values. + * + * And exactly this property will we use to know which value was deleted + * in "text" and which value we then have to delete from our internal + * structure. + * + * Specifically, we will first find out which option we're talking about + * by testing if ( c->type == DEFVALS_optname ) - remember the enum story + * from above. Then, by using another hint from openldap (c->valx field) + * we will know which value (between possibly multiple values) to + * delete. This whole concept, as I understand it, depends on the "text" + * value indices matching our indices within our singly linked list + * that represents our internal config (defvals_data*). + * In case that the attribute has only one value, valx will be -1. + * In case of multiple values, it will be the index of the value, from 0. + * + * And finally, since that index will correspond to the "sequential number" + * within values belonging to the same config option, we must adjust our + * searching procedure - we can't just delete say, third item (index = 2), + * we can only delete the third element *of specific option*. Since options + * are all in the same list, we possibly need to go over more than 2 pointers + * to reach the 3rd element we're cutting out. + */ + } else if ( c->op == LDAP_MOD_DELETE ) { + slap_overinst *on; + defvals_data *di, *prev; + int i, limit; + + on = (slap_overinst *)c->bi; + di = on->on_bi.bi_private; + prev = NULL; + i = -1; + + /* if valx < 0, it means implicitly that this config option has + * only one value. So for our purposes, we say that limit = 0, which + * means that it should delete the first matching option it finds. + * (if there's only one of the kind in the first place, there's no + * room for error). */ + limit = c->valx; if ( limit < 0 ) limit = 0; + + /* Here, we use knowledge of struct defvals_data fields to determine the + * type of option. If di_ad field was specified, then we're talking + * about defvals-attr option, and if it wasn't, then that item + * represents defvals-dn option. This is kind of overlay-and-structure + * specific solution to the problem, but it's easy to adapt to your + * case. */ + while ( di ) { + if ( (c->type == DEFVALS_ATTR) && di->di_ad ) i++; + else if ( (c->type == DEFVALS_DN) && !di->di_ad ) i++; + + if ( i == limit ) break; /* found the item we're looking to delete */ + else { prev = di; di = di->di_next; } /* crawl on, not found yet */ + } + + /* We're positioned to the right element, so we remove it from the + * linked list and free the memory it was occupying. */ + if ( di ) { + Debug(LDAP_DEBUG_CONFIG, + "Defvals: Deleting rule from config: %s, %s, %s\n", + di->di_suffix, di->di_dn, di->di_ad->ad_cname.bv_val ); + if ( prev ) prev->di_next = di->di_next; + else on->on_bi.bi_private = di->di_next; + free_element( di ); + } + else { + Debug(LDAP_DEBUG_CONFIG, + "Defvals: Did not find matching rule to delete from config.\n",0,0,0); + } + } + + return rc; +} + +/* This function will be called for each configuration line found in + * slapd.conf that is intended for our overlay. This is just a small + * wrapper actually which gets access to the backend_info structure + * and then calls the real defvals_config() function. + * + * This is done so that similar wrapper could be called by cn=config + * (new config style), which also does its thing to get access to + * backend_info structure and then resumes with generic defvals_config() + * function which works for both slapd.conf and slapd.d style specification. */ +static int defvals_config_slapdconf ( + BackendDB *be, + const char *fname, + int lineno, + int argc, + char **argv + ) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + defvals_config(on, fname, lineno, argc, argv); +} + +/* Here's the config function called by slapd.d, similar to the above. + * Actually, this function is called from our _cf_gen handler, when + * new option is loaded into ldap. Makes sense, doesn't it? ;) */ +static int defvals_config_slapdd ( + ConfigArgs *c + ) +{ + slap_overinst *on = (slap_overinst *) c->bi; + defvals_config(on, c->fname, c->lineno, c->argc, c->argv); +} + + +/* And this is the generic config handler function. Apart from the small + * difference in accessing the backend_info* space (and through it, our + * config area) for which we've made two wrappers above, this function + * can work regardless of whether it was called through old or new-style + * config. + * + * As said, we have argc and argv. Argc is the usual arg count, while argv + * correspond to a config file line or a config value in ldap, split on + * whitespace. + * + * So the procedure as to what to do with it to turn it into internal + * config representation is the same in any case. */ +static int defvals_config( + slap_overinst *on, + const char *fname, + int lineno, + int argc, + char **argv + ) +{ + defvals_data *di_ptr; + int i, rc, skip; + + /* Side note: + * The on->on_bi.bi_private pointer can be used for + * anything that this instance of the overlay needs. + * It is a common approach that this be a pointer to + * a linked list of config data for the overlay. + * And exactly so do we do ourselves too ;) */ + di_ptr = (defvals_data *)on->on_bi.bi_private; + + if ( argc < 5 ) { + Debug( LDAP_DEBUG_CONFIG, + "Defvals: %s: line %d: not enough arguments.\n", fname, lineno, 0 ); + return( LDAP_INVALID_SYNTAX ); + } + + /* Note: since we need to support back-config, the attributes + * defvals-attr and -dn can also be called olcDefValsAttr and ...DN, + * when they are in back-config and not slapd.conf. + * To be able to use this function for both purposes, we need to + * adjust it to accept both as synonyms. + * + * The variable skip will have two purposes. First, if skip ends up + * being 0, it means an invalid option was passed. And if non-zero, + * it will signify how many bytes to skip in argv[0] to come to the + * "significant" part used to differentiate between options. + * ( I mean, if two options we support are olcDefValsAttr and + * olcDefValsDn, obviously skip is strlen("olcDefVals") ). + */ + skip = 0; + if ( strncasecmp( argv[0], "defvals-", strlen("defvals-") ) == 0 ) { + skip = strlen("defvals-"); + } + else if ( strncasecmp( argv[0], "olcDefVals", strlen("olcDefVals") ) == 0 ) { + skip = strlen("olcDefVals"); + } + else { + return SLAP_CONF_UNKNOWN; + } + + /* Now loop through all attribute descriptions (names), make sure they + * are valid, and load them to our configuration space. + * + * This happens to be the way it is, because in early days of creating + * this overlay, I envisioned that you could specify multiple attributes + * or DNs on a single line, to save typing in the slapd.conf file. + * (such as: defvals-attr suffix 1 0 ATTR1 ATTR2 ATTR3... ). + * + * So each ATTR would share the value of suffix, 1 and 0, but the example + * would internally expand to three defvals_data* items being added to the + * linked list. + * + * Later, I thought that this approach could break the mechanism I + * used for deleting elements from the config, when new-style method is + * used. (With old method - you never have to delete items since overlays + * do not support dynamic config in that mode). + * + * So when cn=config is used, we enforce that on each line, only one + * attribute or DN is specified, which has the effect that this for() + * loop below actually runs only once per config value - fourth argument + * provided is also the last. But that's cool, doesn't concern us. */ + for (i = 4; i < argc; i++) { + defvals_data *di = ch_malloc( sizeof( defvals_data )); + + /* our function which turns (argc,argv) into defvals_data* structure */ + rc = argv2defvals_data( skip, argc, argv, argv[i], di ); + Debug(LDAP_DEBUG_CONFIG, "Defvals: Going to add config rule: %s, %s\n", + argv[1], argv[4], 0); + + if ( rc != LDAP_SUCCESS ) return rc; + + /* Now find the last entry in our config list, and add new item to + * its tail. */ + if (! di_ptr) { + di_ptr = on->on_bi.bi_private = di; + } else { + while (di_ptr->di_next != NULL) di_ptr = di_ptr->di_next; + di_ptr->di_next = di; + } + } + + return LDAP_SUCCESS; +} + + +/*************************************************************************** + * * + * Response-related code (invoked when ldap is to return data to the * + * client, and the core of our work) * + * * + ***************************************************************************/ + +/* The _response function is invoked on every return from LDAP to the client. + * So for each entry, we check if the pointers to the default enties were given, + * and if they were, use their values to fill in missing attributes + * in the original entry. + * + * Since we've dealt with the config stuff and all, here we can finally focus + * on what was our actual task - default values ;) */ +static int +defvals_response( Operation *op, SlapReply *rs ) +{ + Entry *new; + Backend *o_bd_orig; + slap_overinst *on; + defvals_data *di; + + new = NULL; + + /* Do nothing if we are connected as admin; admin "view" + * is not altered in any way */ + if ( be_isroot( op )) return SLAP_CB_CONTINUE; + + /* Do nothing if not in the search operation. "Search" is in LDAP what + * you would usually call "retrieve" or something. + * TODO support for compare op */ + if ( rs->sr_type != REP_SEARCH ) return SLAP_CB_CONTINUE; + + /* The usual setup you'll see in any overlay's _response function */ + on = (slap_overinst *) op->o_bd->bd_info; + di = on->on_bi.bi_private; + op->o_bd->bd_info = (BackendInfo *)on->on_info; + + /* Now, we start the job by looping over all configured attributes or + * DNs. Then, for each of them in turn, we see whether it can fill in + * anything in the original entry. + * + * (It can be multiple attributes and/or DNs, not just one. + * If there are multiple specified, the first one to provide the value wins). + * Useful to know if you want to set up the seeAlso fields so that you + * have some kind of "chained defaults". + */ + for (; di; di = di->di_next ) { /* Looping over our config elements. */ + Attribute *a; /* Pointer to attr within the entry. */ + Entry *dfl; /* Temp pointer to default entry, */ + /* obtained either by looking up */ + /* attr within original entry, or by */ + /* looking up fixed DN. */ + BerVarray dn; + int i, rc; /* temp counter, and as usual rc - return code */ + + a = NULL; + dfl = NULL; + + /* Again, remember that the below code REPEATS for each configured + * default attribute or DN. + * + * To be more efficient and understandable with comments, I will assume + * that the admin configured the overlay as example: + * + * defvals-attr ou=People,dc=example,dc=com 1 0 seeAlso + * + * (in other words, "work under ou=People..., by using entry's + * seeAlso attribute, if it exists, to point to an entry from which + * to take default values". */ + + + /* See if entry is below the configured starting point in the tree. + * (ou=People,...) + * If it's not, then at least this config entry doesn't apply here + * and we have no work to do. */ + /* We can't use dnIsSuffix() because it's not quite the same behavior */ + /* if (!dnIsSuffix(&rs->sr_entry->e_nname, di->di_suffix)) continue; */ + rc = rs->sr_entry->e_nname.bv_len - di->di_suffix->bv_len; + if ( rc < 1 || + strcmp( rs->sr_entry->e_nname.bv_val + rc, di->di_suffix->bv_val )) + continue; + + + /* Now, if the default entry was configured as an attribute within an + * entry (so, using defvals-attr option and not defvals-dn) */ + if ( di->di_ad ) { + /* And in our example case it did, through seeAlso. + * Obtain pointer to seeAlso within original entry (if seeAlso exists). + * If not, again no work for us. */ + a = attr_find( rs->sr_entry->e_attrs, di->di_ad ); + if (! a ) continue; + + /* seeAlso can hold multiple values, it's not defined as SINGLE-VALUE + * in default schema. So user might have used it to specify multiple + * records to look up for default values. + * + * 'dn' will now be BerVarray (array of Ber Values). BER = Basic + * Encoding Rule, and in effect that means that every string won't + * be just "string", but wrapped in a BER structure like this: + * + * BerValue = { + * char *bv_val == string + * int bv_len == strlen(string) + * } + * + * You can read the value simply as BerValue.bv_val, but bv_len is + * provided because, as Howard Chu says, running strlen() any more + * times than necessary is a "collosal waste of processor power". + * + * The end of BerVarray is indicated by last element for which + * dn[i].bv_val == NULL. + * + * Also, a word on vals/nvals. Vals are attribute values as + * typed in by user. Nvals (normalized values) are "normalized" - + * possibily modified to conform to internal structure. For example, + * uid=docelic,ou=People,dc=example,dc=com is NOT normalized. + * (DNs must be all lowercase - and notice that "People" there). + * So normalization of that value would, for example, turn everything + * to lowercase. */ + dn = a->a_nvals; + + /* And if the default entry was directly specified using its DN, + * then we can just load that and move on.*/ + } else { + dn = di->di_dn; + } + + /* At this point, we've obtained the list of entries to consult + * for default values. The list is in BER array 'dn', and regardless + * of whether the entries were specified using defvals-dn option + * or indirectly through defvals-attr. + * + * The only note here is that defvals-dn specification will always + * result in dn having one entry, while a lookup into a seeAlso field + * my result in multiple values. So we need a for() loop in the + * next step. */ + + /* for-each dn from the BerVarray dn */ + for ( i=0; dn[i].bv_val != NULL; i++ ) { + Attribute *aa; /* attributes found in default entry */ + Attribute *aoc = NULL; /* objClasses of original entry */ + + /* We prevent infinite loops - don't go into retrieve if it's + * the same as the original entry. + */ + if (!( strcmp(rs->sr_entry->e_nname.bv_val, dn[i].bv_val))) continue; + + /* Now, for all practical work, we need to obtain pointer to + * objectClass attribute values of the original entry. + * We need it a bit later, but done here to avoid putting it in + * another for() loop. */ + aoc = attr_find(rs->sr_entry->e_attrs, slap_schema.si_ad_objectClass); + assert (aoc != NULL ); + + /* Retrieve default DN from the directory. The below code only works if + * the DN is within the same backend! (But it almost always is in + * common setups). */ + if ( be_entry_get_rw(op, &(dn[i]), NULL, NULL, 0, &dfl) ) { + Debug(LDAP_DEBUG_ANY, + "Defvals: error retrieving default entry '%s' for '%s'\n", + dn[i].bv_val, rs->sr_entry->e_nname.bv_val,0); + continue; + } + + /* Ok, now we have the default entry, at least the first one we + * will visit on our path. + * + * We get a pointer to its attributes, start going over them, and + * merging in all that are allowed to be merged, according to + * configured schemacheck and insert-policy values for that specific + * pass. */ + aa = dfl->e_attrs; + for (; aa; aa = aa->a_next ) { + ObjectClass *aa_oc; + + /* We're not interested in operational attributes, so we can + * rule those out right away. */ + if (is_at_operational(aa->a_desc->ad_type)) continue; + int i, skip, allowed; + + skip = 0; /* Here, skip signifies whether we consider attribute + * for inclusion or not. Not the same skip from above. */ + allowed = di->di_schemacheck ? 0 : 1; /* Allow if schemacheck is off */ + + /* Now loop over original entry's objectclasses (to which pointer we + * obtained above, remember), and for each objectClass value (OC), + * see if it would cause the candidate attribute at hand to be + * required. If it does, we skip it. + * We don't do anything for required attrs because you couldn't + * have created an entry without them in the first place. + * Another reason is that this is the only way I figured to + * exclude stuff like uid=, cn=, etc. (which obviously shouldn't + * be filled in with default values) from the picture. */ + for ( i=0; aoc->a_nvals[i].bv_val; i++ ) { + aa_oc = oc_bvfind( &(aoc->a_nvals[i]) ); + + if (at_find_in_list(aa->a_desc->ad_type, aa_oc->soc_required) >= 0) { + skip++; + break; + } + + /* We now have an attribute (from default entry) which we know + * is not operational, and also not required in our original entry. + * That makes it a good candidate to work on, but now user-specified + * rules come into play to define further restrictions. + */ + + /* See if schemacheck is enabled and/or allows the attribute. + * If schemacheck is off, all good - allow the attribute. If it's + * on and the attribute isn't allowed, we skip it. */ + if ( di->di_schemacheck && + (at_find_in_list(aa->a_desc->ad_type, aa_oc->soc_allowed ) >= 0) ) + allowed++; + } + if ( skip || !allowed ) continue; + + /* See about insert-policy. Should we insert only when there's no + * attribute in the original entry, or always ? (Setting of + * schemacheck=1 allows always-insert only if the option is not + * single-value. Setting of 2 relaxes this check, of course + * at expense of returning result that is not schema-conformant). */ + if ( attr_find( rs->sr_entry->e_attrs, aa->a_desc ) ) { + + /* With the above if(), this point in code here was reached only if + * the attribute already existed in the original entry, so + * if insert_policy is 0 ("Only insert when undefined"), then + * no shine, sunshine. */ + if (! di->di_insert_policy ) continue; + + /* Here, insert_policy = 1, and at least one value is already + * present in original entry. We won't merge if schemacheck is + * set to strict check (that is, not allowing single/multi + * value breakage) */ + if ( is_at_single_value(aa->a_desc->ad_type) && + di->di_schemacheck == 1 ) continue; + } + + /* Finally, merge it in if we reached this point in code */ + /* XXX Merge only one val if config says so? */ + if (! new) new = entry_dup( rs->sr_entry ); + attr_merge_normalize( new, aa->a_desc, aa->a_vals, NULL); + + } /* for ( aoc->a_nvals[i].bv_val ) */ + + be_entry_release_rw( op, dfl, 0 ); + } /* for (; aa; aa->a_next ) */ + } /* for ( dn[i].bv_val ) */ + + /* This is the new entry we've created on the fly to include extra + * stuff from default entries. Upon returning to the client, it will + * be dropped from memory and not persist anywhere. */ + if ( new ) { + rs->sr_entry = new; + rs->sr_flags |= REP_ENTRY_MUSTBEFREED; + } + + return SLAP_CB_CONTINUE; +} + +/* Function where we basically only have to free internal structures. */ +static int +defvals_destroy( + BackendDB *be + ) +{ + defvals_data *di; + slap_overinst *on = (slap_overinst *)be->bd_info; + + if ( on->on_bi.bi_private ) { + for ( di = on->on_bi.bi_private; di; di = on->on_bi.bi_private ) { + on->on_bi.bi_private = di->di_next; + free_element( di ); + } + } + + return LDAP_SUCCESS; +} + + +static slap_overinst defvals; + +/* The easily recognizable init function, similarly done in all projects + * providing dynamic modules functionality. + * + * You fill in hooks for functions you want to intercept with your + * own code. + */ +int defvals_initialize() { + int rc; + const char *text; + defvals_data *di; + AttributeDescription *ad = NULL; + + /* Register name and callbacks */ + defvals.on_bi.bi_type = "defvals"; + + defvals.on_bi.bi_db_destroy = defvals_destroy; + + defvals.on_bi.bi_db_config = defvals_config_slapdconf; + defvals.on_response = defvals_response; + + defvals.on_bi.bi_cf_ocs = defvals_cfocs; + + rc = config_register_schema( defvals_cfats, defvals_cfocs ); + if ( rc ) return rc; + + return overlay_register( &defvals ); +} + +#if SLAPD_OVER_DEFVALS == SLAPD_MOD_DYNAMIC && defined(PIC) +int init_module(int argc, char *argv[]) { + return defvals_initialize(); +} +#endif + +/* This is a helper function, defvals-specific, which reads a line + * from config (in (argc,argv)-style) and turns it into internal + * representation as defvals_data* element. */ +static int argv2defvals_data( + int skip, + int argc, + char** argv, + char* elem, + defvals_data *di + ) +{ + const char *text, *type; + int schemacheck, insert_policy; + BerValue *suffix, *tmpsuffix, *dn; + AttributeDescription *ad; + + type = argv[0] + skip; + ad = NULL; + dn = NULL; + + assert( di != NULL); + + /* Take out suffix, which is the first argument. Must also normalize it */ + tmpsuffix = ch_malloc(sizeof(BerValue)); + suffix = ch_malloc(sizeof(BerValue)); + ber_str2bv(argv[1],0,1,tmpsuffix); + dnNormalize(0, NULL, NULL, tmpsuffix, suffix, NULL); + ch_free(tmpsuffix); + + /* XXX Error checking? */ + schemacheck = strtol(argv[2], (char **)NULL, 10); + insert_policy = strtol(argv[3], (char **)NULL, 10); + + if (!strcmp(type, "attr") || !strcmp(type, "Attr")) { + if ( slap_str2ad( elem, &ad, &text ) ) { + Debug( LDAP_DEBUG_ANY, "Defvals: attribute '%s': %s.\n", elem, text,0 ); + return( ARG_BAD_CONF ); + } + + } else if (!strcmp(type, "dn") || !strcmp(type, "DN")) { + dn = ch_malloc(sizeof(BerValue)); + ber_str2bv(elem,0,1,dn); + + } else { + return SLAP_CONF_UNKNOWN; + } + + /* Create the entry for our defvals_data structure */ + di->di_ad = ad; + di->di_schemacheck = schemacheck; + di->di_insert_policy = insert_policy; + /* DN would be two lines (malloc + dnNormalize) but since DN + * is a BerVarray with no pointer 'next' or the number of elements + * recorded, the convention is to have last element bv_val = NULL. + * So we need to adjust our structure to that convention too. */ + if (dn) { + di->di_dn = ch_malloc( sizeof( BerValue ) * 2); + dnNormalize(0, NULL, NULL, dn, &(di->di_dn[0]), NULL); + di->di_dn[1].bv_len = 0; + di->di_dn[1].bv_val = NULL; + } else { + di->di_dn = ch_malloc( sizeof( BerValue )); + di->di_dn[0].bv_len = 0; + di->di_dn[0].bv_val = NULL; + } + di->di_suffix = suffix; + di->di_next = NULL; + + return LDAP_SUCCESS; +} + +/* Function that frees elements of type defvals_data* */ +static int free_element( + defvals_data *di + ) +{ + if ( di ) { + if (di->di_suffix) ch_free( di->di_suffix ); + if (di->di_dn) ch_free( di->di_dn ); + ch_free( di ); + } + + return LDAP_SUCCESS; +} + + +/* That's all, folks! */ + +#endif /* SLAPD_OVER_DEFVALS */