#!/usr/bin/perl # # Brent Baccala 26 Sep 2000 # # This script is designed for use with openldap's shell backend. # In the slapd.conf file, it should be configured as the # external program for the operations "search", "add", "modify", # "modrdn", and "delete". It reads the requests from slapd on STDIN and # forwards them to another LDAP server, using various openldap client # programs. # # If you know nothing about LDAP, suffice it to say that we're dealing # with a directory of objects, each identified by a unique # Distinguished Name (DN) and each containing a list of attributes, # each attribute possibly missing, non-unique or duplicated. One of # the most important attributes is "objectclass", which is an ASCII # string defining the type of object. The server uses a schema # definition to define which attributes are possible and/or required # for each objectclass. # # It has some slight drawbacks, including an inability to handle multivalued # attributes in search results (it only returns a single value for each). # The LDAP URL to forward the requests to $URL = "ldap://localhost:2345/"; # Standard arguments to pass to all the child processes. # If you want to specify authentication, this is the place to do it. @stdargs = ( "-H", $URL ); # Trace file to see what this script is doing. # Comment the line out if you don't want a trace file. # $tracefile = "perl.out"; # The client programs # $clientpath can be left empty if the programs are in the path $clientpath = "clients/tools/"; $ldapadd = $clientpath . "ldapadd"; $ldapmodify = $clientpath . "ldapmodify"; $ldapdelete = $clientpath . "ldapdelete"; $ldapmodrdn = $clientpath . "ldapmodrdn"; $ldapsearch = $clientpath . "ldapsearch"; # Regular expression that matches attributes and pseudo-attributes that # should be filtered out from add/modify requests, either because they # aren't really attributes (msgid, suffix), or because they are generated # by the server and would cause it to barf if set explicitly. $ignores = "((msgid)|(suffix)|(creatorsName)|(createTimestamp)|(modifiersName)|(modifyTimestamp))"; # Regular expression that matches the possible changetypes that can # appear in a change request. See slapd.replog(5) $changetypes = "((add)|(replace)|(delete)|(modrdn))"; # In a search request, the server feeds us the scope and dereference fields # in the numeric form used by the protocol. ldapsearch(1) takes these # fields as arguments in symbolic form. These arrays convert between # the two representations. @scopes = ("base", "one", "sub"); @derefs = ("never", "always", "search", "find"); # The script proper. Initialize the result code to 1 (operationsError), # so we just fall through on any problems. The first line of input # should be an operation code. Call the right client program and pass # it any fields. The openldap clients are very nice about returning # LDAP response codes as their exit status if they fail. We take # advantage of this to set $result. $result = 1; $operation = <>; chop($operation); if (defined $tracefile) { open(TRACEFILE, ">>$tracefile"); print TRACEFILE "$operation\n"; } if ($operation eq "ADD") { while (<>) { next if ( /^$ignores:/o ); if (/^(.*): (.*)$/) { $Attributes{$1} = $2; } print TRACEFILE if defined $tracefile; } $dn = $Attributes{dn}; delete $Attributes{dn}; my $pid = open(PROGRAM, "|-"); if (defined $pid) { if (! $pid) { # child exec ($ldapadd, @stdargs); exit 1; } else { # parent print PROGRAM "dn: $dn\n"; foreach $attr (keys %$Attributes) { print PROGRAM "$attr: $$Attributes{$attr}\n"; } close(PROGRAM); $result = ($? >> 8); } } } elsif ($operation eq "MODIFY") { my $pid = open(PROGRAM, "|-"); if (defined $pid) { if (! $pid) { # child exec ($ldapmodify, @stdargs); exit 1; } else { # parent # We now read a series of change blocks from the server. # We pass them on to the child, with two modifications. # Some of the change blocks get droppped completely (those # matching the $ignores regex). We also insert a "-" line # after each change block, because this is what the client # expects, but not what the server sends. my $changeblockseen = 0; while (<>) { next if (/^-$/); next if (/^$ignores:/o || /^$changetypes: $ignores$/); if (/^$changetypes:/) { print PROGRAM "-\n" if ($changeblockseen); $changeblockseen=1; } print PROGRAM; print TRACEFILE if defined $tracefile; } print PROGRAM "-\n" if ($changeblockseen); close(PROGRAM); $result = ($? >> 8); } } } elsif ($operation eq "DELETE") { # This deletes an entire object, identified by its DN. If you only # want to delete an attribute within an object, use MODIFY with # a changetype of "delete" on the attribute in question. while (<>) { if (/^dn: (.*)$/) { $dn = $1; } } if (defined($dn)) { print TRACEFILE "$dn\n" if defined $tracefile; system ($ldapdelete, @stdargs, $dn); $result = ($? >> 8); } } elsif ($operation eq "MODRDN") { while (<>) { if (/^dn: (.*)$/) { $dn = $1; } elsif (/^newrdn: (.*)$/) { $rdn = $1; } elsif (/^deleteoldrdn: (.*)$/) { $deleteoldrdn = $1; } } if (defined($dn) and defined($rdn)) { print TRACEFILE "$dn -> $rdn\n" if defined $tracefile; system ($ldapmodrdn, @stdargs, $deleteoldrdn ? "-r" : (), $dn, $rdn); $result = ($? >> 8); } } elsif ($operation eq "SEARCH") { while (<>) { print TRACEFILE if defined $tracefile; if (/^base: (.*)$/) { $base = $1; } elsif (/^scope: (.*)$/) { $scope = $scopes[$1]; } elsif (/^deref: (.*)$/) { $deref = $derefs[$1]; } elsif (/^sizelimit: (.*)$/) { $sizelimit = $1; } elsif (/^timelimit: (.*)$/) { $timelimit = $1; } elsif (/^filter: (.*)$/) { $filter = $1; } elsif (/^attrsonly: (.*)$/) { $attrsonly = $1; } elsif (/^attrs: (.*)$/) { if ($1 eq "all") { @attrs = (); } else { @attrs = split / /, $1; } } } my $pid = open(PROGRAM, "-|"); if (defined $pid) { if (! $pid) { # child exec ($ldapsearch, @stdargs, "-LLL", "-b", $base, "-s", $scope, "-a", $deref, defined($timelimit) ? ("-l", $timelimit) : (), defined($sizelimit) ? ("-z", $sizelimit) : (), $attrsonly ? "-A" : (), $filter, @attrs); exit 1; } else { # parent @DNs = (); while () { if (/^dn: (.*)$/) { unshift @DNs, $1; } elsif (/^([^:]*): (.*)$/ and defined $DNs[0]) { $Attributes{lc($1)} = 1; if (not exists $AttributeValues{$DNs[0], lc($1)}) { $AttributeValues{$DNs[0], lc($1)} = []; } push @{$AttributeValues{$DNs[0], lc($1)}}, $2; } print TRACEFILE if defined $tracefile; } close(PROGRAM); $result = ($? >> 8); # At this point, @DNs contains a list (possibly empty) of # the returned objects' DNs, and %AttributeValues is a hash # containing their attributes, keyed on DN and attribute name. # @attrs is the list of attributes originally requested. # %Attributes is a hash containing 1 for each attribute seen. foreach $dn (@DNs) { print "dn: $dn\n"; foreach $attr (keys(%Attributes)) { if (exists $AttributeValues{$dn, lc($attr)}) { foreach $value (@{$AttributeValues{$dn, lc($attr)}}) { print "$attr: $value\n"; } } } print "\n"; } } } } done: print TRACEFILE "RESULT code $result\n\n" if defined $tracefile; print "RESULT\n"; print "code: $result\n";