#!/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 Graham Barr's Net::LDAP # package. # # 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. use Net::LDAP qw(:all); use Net::LDAP::Entry; require MIME::Base64; # The LDAP host and port to forward the requests to $LDAPhost = "localhost"; $LDAPport = 2345; # Trace file to see what this script is doing. # Comment the line out if you don't want a trace file. # $tracefile = "perl.out"; # 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 main body of the script. We're reading from an openldap server # on STDIN, and it feeds us first an operation code, then various # fields, depending on the operation. sub process_request { my $ldap = Net::LDAP->new($LDAPhost, port => $LDAPport) or die; $ldap->bind or die; # The first line of input should be an operation code. my $operation = <>; chop($operation); print TRACEFILE "$operation\n" if defined $tracefile; if ($operation eq "ADD") { # This next code is lifted from Net::LDAP::LDIF # We can't use the LDIF routines directly because of the presence # of things like "msgid" that violate the LDIF standard. Basically, # we suck in the rest of the input as a single variable, take care # of any newline+space continuation sequences, split the input # into lines, and interpret each one as an attribute/value pair, # taking care to do the Base64 decode if it was binary encoded. $/ = ""; my $input = <>; $input =~ s/\n[ \t]//sg; chomp $input; my @lines; chomp(@lines = split(/^/, $input)); my $entry = Net::LDAP::Entry->new(); foreach my $line (@lines) { next if ( $line =~ /^$ignores:/o ); print TRACEFILE $line, "\n" if defined $tracefile; if ($line =~ s/^([-;\w]+):(:?)\s*//) { $line = MIME::Base64::decode($line) if ($2 eq ":"); if ($1 eq "dn") { $entry->dn($line); } else { $entry->add($1 => $line); } } } print TRACEFILE "Adding ", $entry->dn(), "\n" if defined $tracefile; my $result = $ldap->add($entry); print TRACEFILE "Result: ", $result->code, "\n" if defined $tracefile; return $result->code; } elsif ($operation eq "MODIFY") { # This is similar to the LDIF parsing routines, but not identical. # Each set of changes to an attribute is written as change block # that begins with a line of ": " and # ends with a line containing a single "-". my $entry = Net::LDAP::Entry->new(); $entry->changetype('modify'); $/ = ""; my $input = <>; $input =~ s/\n[ \t]//sg; chomp $input; my @lines; chomp(@lines = split(/^/, $input)); my $changetype; foreach my $line (@lines) { next if ( $line =~ /^$ignores:/o ); if ( $line eq "-") { undef $changetype; next; } # print TRACEFILE $line, "\n" if defined $tracefile; if ($line =~ s/^([-;\w]+):(:?)\s*//) { $line = MIME::Base64::decode($line) if ($2 eq ":"); if ($1 eq "dn") { $entry->dn($line); } elsif ($1 =~ $changetypes) { $changetype = $1; } elsif ($changetype eq "add") { print TRACEFILE "Add $1 $line\n" if defined $tracefile; $entry->add($1 => $line); } elsif ($changetype eq "replace") { print TRACEFILE "Replace $1 $line\n" if defined $tracefile; $entry->replace($1 => $line); } elsif ($changetype eq "delete") { print TRACEFILE "Delete $1 $line\n" if defined $tracefile; $entry->delete($1 => $line); } } } my $result = $entry->update($ldap); return $result->code; } 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. my $dn; while (<>) { if (/^dn: (.*)$/) { $dn = $1; } } if (defined($dn)) { print TRACEFILE "Deleting $dn\n" if defined $tracefile; my $result = $ldap->delete($dn); print TRACEFILE "Result: ", $result->code, "\n" if defined $tracefile; return $result->code; } } elsif ($operation eq "MODRDN") { my $dn; my $rdn; my $deleteoldrdn = 0; 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; my $result = $ldap->moddn($dn, newrdn => $rdn, deleteoldrdn => $deleteoldrdn); print TRACEFILE "Result: ", $result->code, "\n" if defined $tracefile; return $result->code; } } elsif ($operation eq "SEARCH") { my ($base, $scope, $deref, $sizelimit, $timelimit, $filter); my ($attrsonly, @attrs); 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 $result = $ldap->search(base => $base, scope => $scope, deref => $deref, sizelimit => $sizelimit, timelimit => $timelimit, typesonly => $attrsonly, filter => $filter, attrs => \@attrs); if ($result->code == 0) { foreach my $entry ($result->entries) { print "dn: ", $entry->dn, "\n"; print TRACEFILE "dn: ", $entry->dn, "\n" if defined $tracefile; foreach my $attr ($entry->attributes) { print TRACEFILE $attr, "\n" if defined $tracefile; foreach my $value ($entry->get_value($attr)) { print $attr, ": ", $value, "\n"; } } print "\n"; } } return $result->code; } } if (defined $tracefile) { open(TRACEFILE, ">>$tracefile"); print TRACEFILE "$operation\n"; } $resultCode = eval { &process_request; }; $resultCode = 1 if not defined $resultCode; $info = $@ if ($@); print TRACEFILE "RESULT code $resultCode\n\n" if defined $tracefile; print "RESULT\n"; print "code: $resultCode\n"; print "info: $info\n" if defined $info;