diff options
author | Nalin Dahyabhai <nalin.dahyabhai@pobox.com> | 2009-06-17 16:40:49 -0400 |
---|---|---|
committer | Nalin Dahyabhai <nalin.dahyabhai@pobox.com> | 2009-06-17 16:40:49 -0400 |
commit | 5245e4a23bb8fbd03d7f4c7fda9142accce244ca (patch) | |
tree | 7c6e4eb81b99fde3adab5b0957190a7baa194b6b /doc/devel | |
parent | 85279ca2a378d6484ab83ad1ac3bc87a0cac8409 (diff) | |
download | slapi-nis-5245e4a23bb8fbd03d7f4c7fda9142accce244ca.tar.gz slapi-nis-5245e4a23bb8fbd03d7f4c7fda9142accce244ca.tar.xz slapi-nis-5245e4a23bb8fbd03d7f4c7fda9142accce244ca.zip |
- move some documentation files around
Diffstat (limited to 'doc/devel')
-rw-r--r-- | doc/devel/index-memberof.ldif | 21 | ||||
-rwxr-xr-x | doc/devel/migrate-nis.sh | 387 | ||||
-rw-r--r-- | doc/devel/nis-compatibility.txt | 77 | ||||
-rw-r--r-- | doc/devel/nis-design.txt | 289 | ||||
-rw-r--r-- | doc/devel/sch-design.txt | 127 | ||||
-rw-r--r-- | doc/devel/todo.txt | 8 |
6 files changed, 909 insertions, 0 deletions
diff --git a/doc/devel/index-memberof.ldif b/doc/devel/index-memberof.ldif new file mode 100644 index 0000000..b82f7fd --- /dev/null +++ b/doc/devel/index-memberof.ldif @@ -0,0 +1,21 @@ +# +# Modify the default for a 389-based directory so that newly-created +# databases will index the 'memberOf' attribute by default. Older +# versions did not do so automatically, which causes a dramatic speed +# hit if you try to use these plugins, which frequently perform internal +# searches using memberOf in the filter. +# +dn: cn=memberOf,cn=default indexes, cn=config,cn=ldbm database,cn=plugins,cn=config +objectClass: top +objectClass: nsIndex +cn: memberOf +nsSystemIndex: false +nsIndexType: eq + +dn: cn=memberOf, cn=index, cn=userRoot, cn=ldbm database, cn=plugins, cn=config +objectClass: top +objectClass: nsIndex +cn: memberOf +nsSystemIndex: false +nsIndexType: eq + diff --git a/doc/devel/migrate-nis.sh b/doc/devel/migrate-nis.sh new file mode 100755 index 0000000..d8dcb63 --- /dev/null +++ b/doc/devel/migrate-nis.sh @@ -0,0 +1,387 @@ +#!/bin/sh + +domain=`domainname` +server=`ypwhich -d $domain` +suffix=dc=example,dc=com +people=cn=Users +groups=cn=Group +ipa=false +realm=`echo "$domain" | tr '[a-z]' '[A-Z]'` +rfc2307bis=false +mergegroups=true +maps= +automap=false +help=false +email=false +containers=false +entries=true + +object_from_attr() +{ + case "$1" in + cn) + containerobject=nsContainer + ;; + dc) + containerobject=domain + ;; + ou) + containerobject=organizationalUnit + ;; + *) + containerobject=extensibleObject + ;; + esac + echo $containerobject +} + +migrate_passwd() { + if $containers ; then + nameattr=`echo "$people" | cut -f1 -d=` + nameval=`echo "$people" | cut -f2- -d=` + containerclass=`object_from_attr "$nameattr"` + grep -v '^$' <<- EOF + dn: $people,$suffix + ${nameattr}: ${nameval} + objectClass: $containerclass + EOF + echo + fi + while read key value ; do + if ! $entries ; then + continue + fi + uid=`echo "$value" | cut -d: -f1` + userpassword=`echo "$value" | cut -d: -f2` + uidnumber=`echo "$value" | cut -d: -f3` + gidnumber=`echo "$value" | cut -d: -f4` + gecos=`echo "$value" | cut -d: -f5` + homedirectory=`echo "$value" | cut -d: -f6` + loginshell=`echo "$value" | cut -d: -f7` + cn=`echo "$gecos" | cut -d, -f1` + givenname=`echo "$gecos" | awk '{print $1}'` + sn=`echo "$gecos" | awk '{print $NF}'` + grep -v '^$' <<- EOF + dn: uid=$uid,$people,$suffix + objectClass: posixAccount + uid: $uid + uidNumber: $uidnumber + gidNumber: $gidnumber + homeDirectory: $homedirectory + ${userpassword:+userPassword: "{CRYPT}"$userpassword} + ${loginshell:+loginShell: $loginshell} + EOF + if $rfc2307bis || $ipa || $email ; then + grep -v '^$' <<- EOF + objectClass: inetOrgPerson + objectClass: inetUser + objectClass: organizationalPerson + objectClass: person + cn: ${cn:-$uid} + sn: ${sn:-$uid} + givenName: ${givenname:-$uid} + mail: ${uid}@${domain} + EOF + fi + if $ipa ; then + grep -v '^$' <<- EOF + objectClass: krbprincipalaux + krbPrincipalName: $uid@$realm + EOF + fi + echo + done +} + +migrate_group() { + if $containers ; then + nameattr=`echo "$groups" | cut -f1 -d=` + nameval=`echo "$groups" | cut -f2- -d=` + containerclass=`object_from_attr "$nameattr"` + grep -v '^$' <<- EOF + dn: $groups,$suffix + ${nameattr}: ${nameval} + objectClass: $containerclass + EOF + echo + fi + while read key value ; do + if ! $entries ; then + continue + fi + gid=`echo "$value" | cut -d: -f1` + userpassword=`echo "$value" | cut -d: -f2` + gidnumber=`echo "$value" | cut -d: -f3` + members=`echo "$value" | cut -d: -f4` + grep -v '^$' <<- EOF + dn: cn=$gid,$groups,$suffix + objectClass: posixGroup + cn: $gid + gidNumber: $gidnumber + ${userpassword:+userPassword: "{CRYPT}"$userpassword} + EOF + if $rfc2307bis || $ipa ; then + grep -v '^$' <<- EOF + objectClass: groupOfNames + EOF + for member in `echo "$members" | sed 's:,: :g'` ; do + echo member: uid=$member,$people,$suffix + done + else + for member in `echo "$members" | sed 's:,: :g'` ; do + echo memberUid: $member + done + fi + echo + done +} + +migrate_automount() { + if $containers ; then + grep -v '^$' <<- EOF + dn: automountMapName=$1,$suffix + objectClass: automountMap + automountMapName: $1 + EOF + echo + fi + while read key value ; do + if ! $entries ; then + continue + fi + grep -v '^$' <<- EOF + dn: automountKey=$key,automountMap=$1,$suffix + objectClass: automount + automountKey: $key + automountInformation: $value + EOF + echo + done +} + +migrate_nis() { + if $containers ; then + grep -v '^$' <<- EOF + dn: nisMapName=$1,$suffix + objectClass: nisMap + automountMapName: $1 + EOF + echo + fi + while read key value ; do + if ! $entries ; then + continue + fi + grep -v '^$' <<- EOF + dn: cn=$key,automountMap=$1,$suffix + objectClass: nisObject + nisMapName: $1 + cn: $key + nisEntry: $value + EOF + echo + done +} + +mergegroups() { + if $mergegroups ; then + awk -F: ' + BEGIN { OFS=":" } + { + if ((length(NAMES[$3]) == 0) || + (length(NAMES[$3]) > length($1))) { + NAMES[$3] = $1 + } + GIDS[$3] = $3 + PASS[$3] = $2 + if (length(MEMBERS[$3]) > 0) { + MEMBERS[$3] = MEMBERS[$3] "," $4 + } else { + MEMBERS[$3] = $4 + } + } + END { + for (GID in GIDS) { + print NAMES[GID],PASS[GID],GID,MEMBERS[GID] + } + }' + else + cat + fi +} + +get_map() { + case "$1" in + passwd*) + ypcat -k ${server:+-h $server} ${domain:+-d $domain} passwd.byname | sort + ;; + group*) + ypcat -k ${server:+-h $server} ${domain:+-d $domain} group.byname | mergegroups | sort + ;; + *) + ypcat -k ${server:+-h $server} ${domain:+-d $domain} "$1" | sort + ;; + esac +} + +migrate_map() { + case "$1" in + passwd*) + (get_map "$1" || echo) | migrate_passwd + ;; + group*) + (get_map "$1" || echo) | migrate_group + ;; + auto.*|auto_*) + (get_map "$1" || echo) | migrate_automount "$1" + ;; + *) + (get_map "$1" || echo) | migrate_nis "$1" + ;; + esac +} + +while test $# -gt 0 ; do + case "$1" in + --domain=*) + domain=`echo "$1" | cut -f2- -d=` + automap=false + ;; + --domain) + shift + domain="$1" + automap=false + ;; + --server=*) + server=`echo "$1" | cut -f2- -d=` + automap=false + ;; + --server) + shift + server="$1" + automap=false + ;; + --suffix=*) + suffix=`echo "$1" | cut -f2- -d=` + ;; + --suffix) + shift + suffix="$1" + ;; + --people=*) + people=`echo "$1" | cut -f2- -d=` + ;; + --people) + shift + people="$1" + ;; + --groups=*) + groups=`echo "$1" | cut -f2- -d=` + ;; + --groups) + shift + groups="$1" + ;; + --nomergegroups) + mergegroups=false + ;; + --rfc2307bis) + rfc2307bis=true + ;; + --ipa) + ipa=true + ;; + --email) + email=true + ;; + --realm=*) + realm=`echo "$1" | cut -f2- -d= | tr '[a-z]' '[A-Z]'` + automap=false + ;; + --realm) + shift + realm=`echo "$1" | tr '[a-z]' '[A-Z]'` + automap=false + ;; + -a|--all) + automap=true + ;; + --containers) + containers=true + ;; + --just-containers) + containers=true + entries=false + ;; + -*|-h|--help) + help=true + ;; + *) + maps="${maps:+$maps }$1" + ;; + esac + shift +done + +if $automap && test -z "$maps" ; then + maps=`./ypmaplist.py` +fi +if $help || test -z "$maps" ; then + echo `basename $0`: create LDIF from NIS maps + echo Usage: `basename $0` "[options] [mapname [...]]" + cat <<- EOF + Options: + -h --help Print this text. + --domain Query maps for a non-default domain (default is + "$domain"). + --server Query a non-default server (default is + "$server"). + --suffix Store entries under a non-default suffix (default is + "$suffix"). + --people Store account entries under a non-default container + under the suffix (default is "$people"). + --groups Store group entries under a non-default container + under the suffix (default is "$groups"). + --nomergegroups Don't merge group entries which have the same GID. + --rfc2307bis Use groupOfNames groups, create user account + entries which are also inetOrgPerson entries. + --ipa Use groupOfNames groups, create user account + entries which are also inetOrgPerson and Kerberos + user entries. + --realm Use a non-default Kerberos realm name (default is + "$realm"). + --email Add email addresses by default (default domain for + mail addresses is "$domain"). + -a --all Attempt to migrate all maps in the local domain. + (Can not be used with either the --server or + the --domain options.) + --containers Create containers for maps in addition to entries. + --just-containers Create containers for maps, but not for entries. + EOF +else + seen_passwd=false + seen_group=false + for map in $maps ; do + seen_before=false + case "$map" in + *.by*) + base=`echo "$map" | sed 's,\.by.*,,g'` + case $base in + passwd) + if $seen_passwd ; then + seen_before=true + fi + seen_passwd=true + ;; + group) + if $seen_group ; then + seen_before=true + fi + seen_group=true + ;; + esac + ;; + esac + $seen_before || migrate_map "$map" + done +fi diff --git a/doc/devel/nis-compatibility.txt b/doc/devel/nis-compatibility.txt new file mode 100644 index 0000000..c600450 --- /dev/null +++ b/doc/devel/nis-compatibility.txt @@ -0,0 +1,77 @@ +NIS itself consists of approximately a dozen RPC functions. In RPC +parlance, the yppasswd protocol, which provides a means of remotely +changing passwords and GECOS information, is a wholly different +protocol, and no attempt has been made to implement any part of it in +this plugin. The rationale for this is that browser-based self-service +solutions for this problem are assumed to be plentiful, and as the +yppasswd protocol doesn't encrypt passwords when they're sent over the +network, it's best avoided if possible. + +Getting back to NIS, the protocol consists of several remote procedure +call interfaces which a server is expected to provide, and compatibility +can most simply be described by noting differences between this +implementation and typical NIS server implementations. + +YPPROC_NULL + This function doesn't actually do anything. Fully implemented. + +YPPROC_DOMAIN +YPPROC_DOMAIN_NONACK + These functions are used by a client's ypbind daemon to verify that a + particular server contains maps for a given domain. Fully implemented. + +YPPROC_MATCH + This function searches for one entry in a map, and is typically what + the "ypmatch" command-line utility uses. Fully implemented. + +YPPROC_FIRST + This function retrieves the first entry in a map. Fully implemented. + +YPPROC_NEXT + This function retrieves the entry in a map which immediately follows + another, specified, entry in the map. The combination of YPPROC_FIRST + and YPPROC_NEXT provides one mechanism for clients to walk the entire + set of entries in a map. If a single key occurs more than once in a + map, however, a client will either "skip over" entries which are + between instances of that key or may become stuck endlessly traversing + that same set of entries. + + While typical NIS servers have a defined order for entries in maps + (that order usually being the same as the ordering used in the files + which were used to generate the maps), a directory server does not + define such an order, so while this function can still be used to + retrieve all of the entries in a map, no guarantee can be made with + respect to the order in which those entries will be found. + +YPPROC_ALL + This function retrieves all of the entries in a map, and is typically + what the "ypcat" command-line utility uses. Fully implemented. + +YPPROC_MASTER + This function retrieves the name of the NIS server which is the master + (in replication scenarios) for a specified map. Because replication + of data is handled at the directory server level, this function is a + no-op. + +YPPROC_CLEAR + This function typically instructs a NIS server to close and reopen the + file from which it is reading the contents of a map. Because the + plugin updates its maps at the same time the data on which those maps + depend is modified, this function is a no-op. + +YPPROC_XFR + This function typically instructs a NIS server to re-fetch a map from + the server which is the master for the map. Because replication of + data is handled at the directory server level, this function is a + no-op. + +YPPROC_ORDER + This function retrieves the time that a specified map was last + modified, typically used by a replica NIS server to determine when it + needs to fetch an updated set of map entries. Fully implemented. + +YPPROC_MAPLIST + This function retrieves the list of maps defined for the specified + domain. While there is no typical command-line utility which fetches + this information, the source tree includes a python script which calls + this function and prints its results. Fully implemented. diff --git a/doc/devel/nis-design.txt b/doc/devel/nis-design.txt new file mode 100644 index 0000000..f98bf57 --- /dev/null +++ b/doc/devel/nis-design.txt @@ -0,0 +1,289 @@ += Design Overview = + +The NIS Server plugin's aim is to serve up data from the directory +server using the NIS protocols. It does this by doing what any gateway +would do: it queries the directory server for entries which would +correspond to the contents of maps, reads the contents of various +attributes from those entries, and uses that data to synthesize entries +for maps which it serves to clients. + +In broad strokes, one design might look like this: + + ┌──────────┐ NIS ┌───────────┐ LDAP ┌────────────────────┐ + │ Client │─────────│ Gateway │──────────│ Directory Server │ + └──────────┘ └───────────┘ └────────────────────┘ + +The links in this diagram represent network traffic. The client uses +the NIS protocol to communicate with the gateway, and the gateway uses +the LDAP protocol to communicate with the directory server. + +This implementation requires that the gateway be robust against +variations in directory server availability, be flexible enough to use +any of a number of methods of authenticating to the directory server, +and may additionally require the presence of specific extensions on the +server in order to be able to be even reasonably certain of consistency +with the directory's contents. + +In order to sidestep these requirements, and the complexity they add to +an implementation, we decided to implement the gateway as a plugin. As +a plugin, the gateway starts and stops with the directory server, it +does not need to authenticate as a normal client would, and it can be +expected to work with a server which can use it. + +Taking just the gateway and directory server portions of the above +diagram, and breaking them down further, we can come to this: + + ┌──────────────┐ ┌─────────┐ ┌────────────────────────────┐ + │ NIS Protocol │───│ Mapping │───│ Directory Server Back Ends │ + └──────────────┘ └─────────┘ └────────────────────────────┘ + +The links in this diagram are now API calls. We've relegated the work +of reading a query (parsed from the NIS client by the NIS Protocol +handler), converting that query to a directory server search operation, +and marshalling the results of that search into a format suitable for +transmission as a NIS response, all to the Mapping module. The +directory server back ends are exposed by SLAPI, of course. + +This approach does have its problems, though. + +NIS, as a protocol, requires that the server be able to supply a few +bits of information which can't readily (or shouldn't) be retrieved this +way, information which is not normally kept in a directory server. + +NIS requires that a server be able to report a revision number for a +map, which is used as an indicator of the time when the map was last +modified. A slave server can use this information to poll for changes +in map contents on the master, possibly beginning a full map enumeration +to read those new contents in order to serve its clients. + +A directory server, if it stores revision information at all, stores +it on a per-entry basis. So when a gateway designed as we diagrammed +above is asked for this information, it has at least these options: + a) always use the current time + - This causes frequent map updates on clients when they don't need + them, and completely unnecessary network traffic. + b) always use the same value + - This keeps clients from ever noticing that a map has changed. + c) return the latest revision of any of the results which formed the + contents of the map + - This could severely load a directory server if the information + needs to be generated by reading the last-modified timestamps + from many directory server entries. + +NIS also requires that a server be able to answer whether or not it +services a specified domain, and which maps it serves for a domain that +it serves. While the mapping module could search the directory's +configuration space whenever it is asked these questions, the first +question is asked regularly by each running copy of ypbind, which could +also bog servers down (though admittedly, less than the previous case). + +If we break the mapping portion up further, we can introduce a map +cache. In this module we can maintain a cache of the NIS server's data +set, taking care to construct it at startup-time, updating it as the +contents of the directory server change, and always serving clients +using data from the cache. + + ┌──────────────┐ ┌───────────┐ ┌──────────────┐ ┌──────┐ + │ NIS Protocol │──│ Map Cache │──│ Map Back End │──│ Data │ + └──────────────┘ └───────────┘ └──────────────┘ └──────┘ + +Which takes us to the current design. The NIS protocol handler reads +data from the map cache, and the map back end uses SLAPI to obtain data +which is used to populate the map cache at startup-time, as well as to +watch for changes in the directory's contents which would need to be +reflected in the map cache. + += Components = + +== Protocol Handler == + +This NIS protocol handler module takes the opportunity to set up +listening sockets (listening on the port specified in the plugin +configuration entry's "nsslapd-pluginarg0" attribute, or an unused port +if none is specified) and register with the local portmapper at module +initialization time. The plugin then starts a listening thread to +service its clients. + +The plugin listens for datagram queries from clients, processing them as +they come in, as well as accepting connections from clients. Because +connected clients may not always transmit an entire request at once, and +because the server may find itself unable to transmit an entire response +at once, it buffers traffic for connected clients, multiplexing the work +it does for all of its clients from inside of its thread. The actual +protocol datagram parsing is performed by libnsl, which is provided as a +part of the C library. + +Datagram responses which exceed the "nis-max-dgram-size" threshold (by +default, 1024 bytes, or 1 kilobyte) are simply dropped. Response +records larger than "nis-max-value-size" (by default, 256 kilobytes) are +also ignored, even for connected clients. + +Client access is limited by the local tcp_wrappers configuration on the +directory server, with a tcp_wrappers service name as dictated by the +"nis-tcp-wrappers-name" attribute (by default, "nis-plugin") in the +plugin's configuration. If the tcp_wrappers configuration denies access +for the client, a connected client's connection will be closed, and a +datagram client's request will be discarded. + +Client requests are also limited based on a client's address using +"securenet"-style settings in the module's configuration entry's +"nis-securenet" attribute. If no values are specified, access is +allowed to all clients. If the securenet configuration denies access +for the client, a connected client's connection will be closed, and a +datagram client's request will be discarded. + +Client requests are further classed as "secure" or not, based on the +query's originating port. This information is used elsewhere for +additional access control. + +== NIS Layer == + +The NIS layer processes complete requests, whether they come in from +connected or datagram clients, fetches the requested information from +the map cache, and uses callbacks provided by the protocol handler to +respond. Before doing so, if a value is being retrieved from a map, it +checks if the map's contents are restricted to "secure" clients. If +they are, but the client is not a "secure" client, the NIS layer will +respond as if no data were present in the map. + +== Map Cache == + +The map cache keeps a dynamically-constructed set of maps in memory, +grouped by domain, and for each map maintains information regarding the +last time its contents were modified (to answer client requests for a +map's order) and whether or not the map's contents should be restricted +to "secure" clients. The map cache can quickly answer whether or not a +domain is being served by checking whether or not any maps are defined +for it. The definitions of which maps are served for which domains is +configurable via internal APIs -- the map cache itself has no forehand +knowledge of domain names, map names, or formats, as it merely models +data in the way that a conventional NIS server might. + +Forcing queries to use the cache provides a couple of benefits over an +alternate approach of performing an LDAP query for each NIS query: +* While the directory server is generally only case-preserving, the NIS + server can be case-sensitive, which is preferred by NIS clients and + a requirement for some customers. +* Because the query used is never used to construct an LDAP filter or + query, we don't have to worry about escaping text to avoid string + injection attacks. + +=== Internal Representation === + +At the topmost level, the map cache is a table. Each entry in the table +is the name of a domain and a table of maps. + +Each entry in a domain's table of maps contains the map's name, the time +the map was last modified, a note indicating whether or not the map is a +"secure" map, a linked list of map entries, and a set of indexes into +the list. Each map can also hold a data pointer on behalf of the +backend. + +Each item in the map's list of entries contains an array of NIS keys, an +array of corresponding values, a unique identifier (which, currently, +stores the NDN of the directory server entry which was used to create +this list item) and a data pointer which is kept on behalf of the +backend. + +The map indexes its entry list using an entry's unique identifier, and +each of its keys. + +== Back End == + +The backend interface module sets up, populates, and maintains the map +cache. At startup time, it configures the map cache with the list of +domains and maps, and populates the maps with initial data. Using +postoperation plugin hooks, the backend interface also notes when +entries are added, modified, renamed (modrdn'd), or deleted from the +directory server. It uses this information to create or destroy maps in +the map cache, and to add, remove, or update entries in the map cache's +maps, thereby ensuring that the map cache always reflects the current +contents of the directory server. + +The backend interface reads the configuration it should use for the map +cache from its configuration area in the directory server. Beneath the +plugin's entry, the backend checks for entries with these attributes: +* nis-domain +* nis-map +* nis-secure +* nis-base +* nis-filter +* nis-key-format +* nis-keys-format +* nis-value-format +* nis-values-format +The backend then instructs the map cache to prepare to hold a map in the +given domain (or domains) with the given map name (or names), and then +performs a subtree search under the specified base (or bases, if there's +more than one ''nis-base'' value) for entries which match the provided +filter. Each entry found is then "added" to the map, using the format +specifiers stored in the ''nis-key-format'' and ''nis-keys-format'' +attributes to construct the keys for the entry in the map, with the +corresponding value in the map being constructed using the format +specifiers stored in the ''nis-value-format'' and ''nis-values-format'' +attributes. The map is also marked as a "secure" map according to the +''nis-secure'' attribute, if so set. + +For each ''nis-key-format'' value, exactly one entry will be created in a +NIS map. (If a ''nis-key-format'' does not yield a single value, the +directory server entry will not appear in the NIS map.) For each +''nis-keys-format'' value, any number of entries will be created in a NIS +map. The method by which these attributes (and the ''nis-value-format'' +and ''nis-value-formats'') are interpreted is described below. + +Should one of the directory server entries which was used to construct +one or more NIS map entries be modified or removed, the corresponding +entries in every applicable NIS map are updated or removed. Likewise, +if an entry is added to the directory server which would correspond to +an entry in a NIS map, entries are created in the corresponding NIS +maps. + +== Formatting Data for NIS == + +The ''nis-key-format'' and ''nis-value-format'' specifiers resemble an RPM +format specifier, and can include the values of multiple attributes in +any part of the specifier. The backend composes the string using the +attribute values stored in the directory server entry, using the format +specifier as a guide. In this way, the NIS map's contents can be +constructed to almost any specification, and can make use of data stored +using any schema. + +An example specification for the ''nis-value-format'' for a user's entry +could look something like this: + %{uid}:%{userPassword:-*}:%{uidNumber}:%{gidNumber}:%{gecos:-%{cn:-}}:%{homeDirectory}:%{loginShell:-/bin/sh} +The syntax borrows from RPM's syntax, which in turn borrows from shell +syntax, to allow the specification of alternate values to be used when +the directory server entry doesn't include a ''userPassword'' or ''gecos'' +attribute. Additional operators include "#", "##", "%", "%%", "/", +"//", which operate in ways similar to their shell counterparts (with +one notable exception being that patterns for the "/" operator can not +currently be anchored to the beginning or end of the string). + +A format specifier can actually be interpreted in two ways: it can be +interpreted as a single value (when given as ''nis-key-format'' or +''nis-value-format''), or it can be interpreted as providing a list of +values (''nis-keys-format'' or ''nis-values-format''). When the format +specifier is being interpreted as a single value, any reference to an +attribute value which does not also specify an alternate value will +cause the directory server entry to be ignored if the referenced +attribute has no value defined for that entry, or contains multiple +values. In the above example, the entry would be ignored if the ''uid'', +''uidNumber'', ''gidNumber'', or ''homeDirectory'' attributes of the entry did +not each contain exactly one value. + +The ''nis-filter'', ''nis-key-format'', and ''nis-value-format'' settings have +sensible defaults for the maps which we expect to be commonly used -- +this is important because it's easy to subtly construct malformed result +specifiers which could trigger undefined behavior on clients -- for +example by leaving the user's numeric UID empty in a passwd entry, which +may be treated as "0" by inattentive clients. + +The format specifier syntax further defines "functions" which can be +used to concatenate lists of multiple values into a single result, for +example for groups: + %{cn}:%{userPassword:-*}:%{gidNumber}:%merge(",","%{memberUid}") +This filter takes advantage of a built-in ''merge'' function, which +processes zero or more single or list values and concatenates them +together with a "," separator, to generate the list of the group's +members. The available functions are described more fully in +"format-specifiers.txt". diff --git a/doc/devel/sch-design.txt b/doc/devel/sch-design.txt new file mode 100644 index 0000000..ecaf14c --- /dev/null +++ b/doc/devel/sch-design.txt @@ -0,0 +1,127 @@ += Design Overview = + +The Schema Compatibility plugin's aim is to synthesize a modified view +of the contents of one part of the directory and to make that view +visible in another part of the directory. It does this by precomputing +the contents of its entries at startup-time and refreshing its entries +as needed. + +== Map Cache == + +The map cache keeps a dynamically-constructed group of sets of entries +in memory, grouped by the name of their top-level container, and for +each set of entries maintains a copy of the set's releative +distinguished name. The map cache can be used to quickly answer whether +or not a particular search request crosses into the portion of the DIT +which is "served" by the data in the cache. + +=== Internal Representation === + +At the topmost level, the map cache is a table. Each entry in the table +contains the name of a top-level container entry and a table of sets. + +Each set in a container's table of sets contains the relative +distinguished name (RDN) of the set, a linked list of map entries, and a +set of indexes into the list. Each set can also hold a data pointer on +behalf of the backend. + +Each item in a set's list of entries contains the entry's relative +distinguished name (relative to the set, which is itself relative to the +group-of-sets container), the entry's whole distinguished name, a unique +identifier (which, currently, stores the NDN of the directory server +entry which was used to create this entry) and a data pointer which is +kept on behalf of the backend (which, currently, stores a Slapi_DN for +the directory server entry which was used to create this entry and the +full Slapi_Entry for the generated entry). + +The set indexes its entry list using an entry's unique identifier and +its relative distinguished name. + +== Back End == + +The backend interface module sets up, populates, and maintains the map +cache. At startup time, it configures the map cache with the list of +groups of sets of entries, and populates the sets with initial data. +Using postoperation plugin hooks, the backend interface also notes when +entries are added, modified, renamed (modrdn'd), or deleted from the +directory server. It uses this information to create or destroy sets in +the map cache, and to add, remove, or update entries in the map cache's +sets, thereby ensuring that the synthetic data in the map cache is +always up to date with respect to the current contents of the directory +server. + +The backend interface reads the configuration it should use for the map +cache from its configuration area in the directory server. Beneath the +plugin's entry, the backend checks for entries with these attributes: +* schema-compat-search-base +* schema-compat-search-filter +* schema-compat-container-group +* schema-compat-container-rdn +* schema-compat-entry-rdn +* schema-compat-entry-attribute +* schema-compat-check-access + +The backend then instructs the map cache to prepare to hold a set of +entries in the given container group (or container groups) with the +given subcontainer RDN name (or names), and then performs a subtree +search under the specified base (or bases, if there's more than one +''schema-compat-search-base'' value) for entries which match the provided +filter (''schema-compat-search-filter''). + +For each entry found, a new entry is generated and "added" to the +subcontainer, using the format specifier stored in the +''schema-compat-entry-rdn'' and ''schema-compat-entry-attribute'' +attributes to construct the RDN and attribute values for the entry in +the set. + +Should one of the directory server entries which was used to construct +one or more entries be modified or removed, the corresponding entries in +every applicable container are updated or removed. Likewise, if an +entry is added to the directory server which would correspond to an +entry in a container, entries are created in the corresponding +container. + +== Specifying Entry Contents == + +The ''schema-compat-entry-rdn'' specifier resembles an RPM format +specifier, and can include the values of multiple attributes in any part +of the specifier. The backend composes the string using the attribute +values stored in the directory server entry, using the format specifier +as a guide, and names the resulting entry using the subcontainer's name +and the generated RDN. Attributes specified using values of the +''schema-compat-entry-attribute'' attribute are then added. If the +resulting entry fails schema checks, it is automatically given the +''extensibleObject'' object class. + +An example specification for the ''schema-compat-entry-rdn'' for a user's +entry could look something like this: + uid=%{uid} +The syntax borrows from RPM's syntax, which in turn borrows from shell +syntax, to allow the specification of alternate values to be used when +the directory server entry doesn't include a "uid" attribute. +Additional operators include "#", "##", "%", "%%", "/", "//", which +operate in ways similar to their shell counterparts (with one notable +exception being that patterns for the "/" operator can not currently be +anchored to the beginning or end of the string). + +After the RDN is determined, attributes can be added. Most often, this +will be done either by specifying a specific value (typically for the +entry's object classes) or an existing attribute, like so: + objectclass=posixAccount + cn=%{cn} + +A format specifier can actually be interpreted in two ways: it can be +interpreted as a single value (as it is for ''schema-compat-entry-rdn'' +values), or it can be interpreted as providing a list of values (as it +is for ''schema-compat-entry-attribute'' values). When the format +specifier is being interpreted as a single value, any reference to an +attribute value which does not also specify an alternate value will +cause the directory server entry to be ignored if the referenced +attribute has no value defined for that entry, or contains multiple +values. + +The format specifier syntax further defines "functions" which can be +used to manipulate values for attributes in the entry being examined, +follow references to other entries and retrieve values from them, and to +manipulate those values into whatever format is required. The available +functions are described more fully in "format-specifiers.txt". diff --git a/doc/devel/todo.txt b/doc/devel/todo.txt new file mode 100644 index 0000000..c8cdb8a --- /dev/null +++ b/doc/devel/todo.txt @@ -0,0 +1,8 @@ +* Finish figuring out sane default key and value formats for the set of maps + for which we're trying to provide default settings. (NIS) +* More tests. +* Stop using an internal function to send the LDAP result for failed searches. +* Reintroduce / and // operator support in attribute references. +* Look at moving the schema compatibility plugin to using a private database, + so that it doesn't have to store everything it has in memory, we get working + indexing, and so on and so forth. |