/****************************************************************************** * * Copyright (C) 2000 Pierangelo Masarati, * All rights reserved. * * Permission is granted to anyone to use this software for any purpose * on any computer system, and to alter it and redistribute it, subject * to the following restrictions: * * 1. The author is not responsible for the consequences of use of this * software, no matter how awful, even if they arise from flaws in it. * * 2. The origin of this software must not be misrepresented, either by * explicit claim or by omission. Since few users ever read sources, * credits should appear in the documentation. * * 3. Altered versions must be plainly marked as such, and must not be * misrepresented as being the original software. Since few users * ever read sources, credits should appear in the documentation. * * 4. This notice may not be removed or altered. * ******************************************************************************/ /* * Description * * A string is rewritten according to a set of rules, called * a `rewrite context'. * The rules are based on Regular Expressions (POSIX regex) with * substring matching; extensions are planned to allow basic variable * substitution and map resolution of substrings. * The behavior of pattern matching/substitution can be altered by a * set of flags. * * The underlying concept is to build a lightweight rewrite module * for the slapd server (initially dedicated to the back-ldap module). * * * Passes * * An incoming string is matched agains a set of rules. Rules are made * of a match pattern, a substitution pattern and a set of actions. * In case of match a string rewriting is performed according to the * substitution pattern that allows to refer to substrings matched * in the incoming string. The actions, if any, are finally performed. * The substitution pattern allows map resolution of substrings. * A map is a generic object that maps a substitution pattern to a * value. * * * Pattern Matching Flags * * 'C' honors case in matching (default is case insensitive) * 'R' use POSIX Basic Regular Expressions (default is Extended) * * * Action Flags * * ':' apply the rule once only (default is recursive) * '@' stop applying rules in case of match. * '#' stop current operation if the rule matches, and issue an * `unwilling to perform' error. * 'G{n}' jump n rules back and forth (watch for loops!). Note that * 'G{1}' is implicit in every rule. * 'I' ignores errors in rule; this means, in case of error, e.g. * issued by a map, the error is treated as a missed match. * The 'unwilling to perform' is not overridden. * * the ordering of the flags is significant. For instance: * * 'IG{2}' means ignore errors and jump two lines ahead both in case * of match and in case of error, while * 'G{2}I' means ignore errors, but jump thwo lines ahead only in case * of match. * * More flags (mainly Action Flags) will be added as needed. * * * Pattern matching: * * see regex(7) * * * String Substitution: * * the string substitution happens according to a substitution pattern. * - susbtring substitution is allowed with the syntax '\d' * where 'd' is a digit ranging 0-9 (0 is the full match). * I see that 0-9 digit expansion is a widely accepted * practise; however there is no technical reason to use * such a strict limit. A syntax of the form '\{ddd}' * should be fine if there is any need to use a higher * number of possible submatches. * - variable substitution will be allowed (at least when I * figure out which kind of variable could be proficiently * substituted) * - map lookup will be allowed (map lookup of substring matches * in gdbm, ldap(!), math(?) and so on maps 'a la sendmail'. * - subroutine invocation will make it possible to rewrite a * submatch in terms of the output of another rewriteContext * * Old syntax: * * '\' {0-9} [ '{' [ '(' ')' ] '}' ] * * where is the name of a built-in map, and * are optional arguments to the map, if * the map requires them. * The following experimental maps have been implemented: * * \n{xpasswd} * maps the n-th substring match as uid to * the gecos field in /etc/passwd; * * \n{xfile(/absolute/path)} * maps the n-th substring match * to a 'key value' style plain text file. * * \n{xldap(ldap://url/with?%0?in?filter) * maps the n-th substring match to an * attribute retrieved by means of an LDAP * url with substitution of %0 in the filter * (NOT IMPL.) * * New scheme: * * - everything starting with '\' requires substitution; * - the only obvious exception is '\\', which is left as is; * - the basic substitution is '\d', where 'd' is a digit; * 0 means the whole string, while 1-9 is a submatch; * - in the outdated schema, the digit may be optionally * followed by a '{', which means pipe the submatch into * the map described by the string up to the following '}'; * - the output of the map is used instead of the submatch; * - in the new schema, a '\' followed by a '{' invokes an * advanced substitution scheme. The pattern is: * * '\' '{' [{ }] '(' ')' '}' * * where must be a legal name for the map, i.e. * * ::= [a-z][a-z0-9]* (case insensitive) * ::= '>' '|' '&' '&&' '*' '**' '$' * * and must be a legal substitution * schema, with no limits on the nesting level. * The operators are: * > sub context invocation; must be a legal, * already defined rewrite context name * | external command invocation; must refer * to a legal, already defined command name (NOT IMPL.) * & variable assignment; defines a variable * in the running operation structure which can be * dereferenced later (NOT IMPL.) * * variable dereferencing; must refer to a * variable that is defined and assigned for the * running operation (NOT IMPL.) * $ parameter dereferencing; must refer to * an existing parameter; the idea is to make * some run-time parameters set by the system * available to the rewrite engine, as the client * host name, the bind dn if any, constant * parameters initialized at config time, and so * on (NOT IMPL.) * * Note: as the slapd parsing routines escape backslashes ('\'), * a double backslash is required inside substitution patterns. * To overcome the resulting heavy notation, the substitution escaping * has been delegated to the '%' symbol, which should be used * instead of '\' in string substitution patterns. The symbol can * be altered at will by redefining the related macro in "rewrite-int.h". * In the current snapshot, all the '\' on the left side of each rule * (the regex pattern) must be converted in '\\'; all the '\' on the * right side of the rule (the substitution pattern) must be turned * into '%'. In the following examples, the original (more readable) * syntax is used; however, in the servers/slapd/back-ldap/slapd.conf * example file, the working syntax is used. * * * * Rewrite context: * * a rewrite context is a set of rules which are applied in sequence. * The basic idea is to have an application initialize a rewrite * engine (think of Apache's mod_rewrite ...) with a set of rewrite * contexts; when string rewriting is required, one invokes the * appropriate rewrite context with the input string and obtains the * newly rewritten one if no errors occur. * * An interesting application, in back-ldap or in slapd itself, * could associate each basic server operation to a rewrite context * (most of them possibly aliasing the default one). Then, DN rewriting + could take place at any invocation of a backend operation. * * client -> server: * default if defined and no specific context is available * bindDn bind * searchBase search * searchFilter search * compareDn compare * addDn add * modifyDn modify * modrDn modrdn * newSuperiorDn modrdn * deleteDn delete * * server -> client: * searchResult search (only if defined; no default) * * * Configuration syntax: * * Basics: * * rewriteEngine { on | off } * * rewriteContext [ alias ] * * rewriteRule [ ] * * * Additional: * * rewriteMap [ ] * * rewriteParam * * rewriteMaxPasses * * * * rewriteEngine: * * if 'on', the requested rewriting is performed; if 'off', no * rewriting takes place (an easy way to stop rewriting without * altering too much the configuration file) * * rewriteContext: * * is the name that identifies the context, i.e. * the name used by the application to refer to the set of rules * it contains. It is used also to reference sub contexts in * string rewriting. A context may aliase another one. In this * case the alias context contains no rule, and any reference to * it will result in accessing the aliased one. * * rewriteRule: * * determines how a tring can be rewritten if a pattern is matched. * Examples are reported below. * * rewriteMap: * * allows to define a map that transforms substring rewriting into * something else. The map is referenced inside the substitution * pattern of a rule. * * rewriteParam: * * sets a value with global scope, that can be dereferenced by the * command '\{$paramName}'. * * rewriteMaxPasses: * * sets the maximum number of total rewriting passes taht can be * performed in a signle rewriting operation (to avoid loops). * * * Configuration examples: * * # set to 'off' to disable rewriting * * rewriteEngine on * * * # everything defined here goes into the 'default' context * # this rule changes the naming context of anything sent to * # 'dc=home,dc=net' to 'dc=OpenLDAP, dc=org' * * rewriteRule "(.*)dc=home,[ ]?dc=net" "\1dc=OpenLDAP, dc=org" ":" * * * # start a new context (ends input of the previous one) * # this rule adds blancs between dn parts if not present. * * rewriteContext addBlancs * rewriteRule "(.*),([^ ].*)" "\1, \2" * * * # this one eats blancs * * rewriteContext eatBlancs * rewriteRule "(.*),[ ](.*)" "\1,\2" * * * # here control goes back to the default rewrite context; rules are * # appended to the existing ones. * # anything that gets here is piped into rule 'addBlancs' * * rewriteContext default * rewriteRule ".*" "\{>addBlancs(\0)}" ":" * * * # anything with 'uid=username' gets looked up in /etc/passwd for * # gecos (I know it's nearly useless, but it is there just to * # test something fancy!). Note the 'I' flag that leaves * # 'uid=username' in place if 'username' does not have a valid * # account. Note also the ':' that forces the rule to be processed * # exactly once. * * rewriteContext uid2Gecos * rewriteRule "(.*)uid=([a-z0-9]+),(.+)" "\1cn=\2{xpasswd},\3" "I:" * * * # finally, in case of bind, if one uses a 'uid=username' dn, * # it is rewritten in 'cn=name surname' if possible. * * rewriteContext bindDn * rewriteRule ".*" "\{>addBlancs(\{>uid2Gecos(\0)})}" ":" * * * # the search base is rewritten according to 'default' rules * * rewriteContext searchBase alias default * * * # search results with OpenLDAP dn are rewritten back with * # 'dc=home,dc=net' naming context, with spaces eaten. * * rewriteContext searchResult * rewriteRule "(.*[^ ]?)[ ]?dc=OpenLDAP,[ ]?dc=org" * "\{>eatBlancs(\1)}dc=home,dc=net" ":" * * # bind with email instead of full dn: we first need an ldap map * # that turns attributes into a dn (the filter is provided by the * # substitution string): * * rewriteMap ldap attr2dn "ldap://host/dc=my,dc=org?dn?sub" * * # then we need to detect emails; note that the rule in case of match * # stops rewriting; in case of error, it is ignored. * # In case we are mapping virtual to real naming contexts, we also * # need to rewrite regular dns, because the definition of a bindDn * # rewrite context overrides the default definition. * * rewriteContext bindDn * rewriteRule "(mail=[^,]+@[^,]+)" "\{attr2dn(\1)}" "@I" * * # This is a rather sophisticate example. It massages a search filter * # in case who performs the search has administrative privileges. * # First we need to keep track of the bind dn of the incoming request: * * rewriteContext bindDn * rewriteRule ".+" "\{**&binddn(\0)}" ":" * * # a search filter containing 'uid=' is rewritten only if an * # appropriate dn is bound. * # to do this, in the first rule the bound dn is dereferenced, while * # the filter is decomposed in a prefix, the argument of the 'uid=', * # and in a suffix. A tag '<>' is appended to the dn. If the dn * # refers to an entry in the 'ou=admin' subtree, the filter is * # rewritten OR-ing the 'uid=' with 'cn='; otherwise * # it is left as is. This could be useful, for instance, to allow * # apache's auth_ldap-1.4 module to authenticate users with both * # 'uid' and 'cn', but only if the request comes from a possible * # 'dn: cn=Web auth, ou=admin, dc=home, dc=net' user. * * rewriteContext searchFilter * rewriteRule "(.*\()uid=([a-z0-9_]+)(\).*)" * "\{**binddn}<>\{&prefix(\1)}\{&arg(\2)}\{&suffix(\3)}" ":I" * rewriteRule "[^,]+,[ ]?ou=admin,[ ]?dc=home,[ ]?dc=net" * "\{*prefix}|(uid=\{*arg})(cn=\{*arg})\{*suffix}" "@I" * rewriteRule ".*<>" "\{*prefix}uid=\{*arg}\{*suffix}" * * * LDAP Proxy resolution (a possible evolution of the back-ldap): * * in case the rewritten dn is an LDAP URL, the operation is initiated * towards the host[:port] indicated in the url, if it does not refer * to the local server. * * e.g.: * * rewriteRule '^cn=root,.*' '\0' 'G{3}' * rewriteRule '^cn=[a-l].*' 'ldap://ldap1.my.org/\0' '@' * rewriteRule '^cn=[m-z].*' 'ldap://ldap2.my.org/\0' '@' * rewriteRule '.*' 'ldap://ldap3.my.org/\0' '@' * * (rule 1 is simply there to illustrate the 'G{n}' action; it could * have been written: * * rewriteRule '^cn=root,.*' 'ldap://ldap3.my.org/\0' '@' * * with the advantage of saving one rewrite pass ...) */