1# Connector::Proxy::Net::LDAP
2#
3# Proxy class for accessing LDAP directories
4#
5# Written by Scott Hardin,  Martin Bartosch and Oliver Welter for the OpenXPKI project 2012
6#
7package Connector::Proxy::Net::LDAP;
8
9use strict;
10use warnings;
11use English;
12use Net::LDAP;
13use Template;
14use Data::Dumper;
15
16use Moose;
17extends 'Connector::Proxy';
18
19has base => (
20    is  => 'rw',
21    isa => 'Str',
22    required => 1,
23);
24
25has binddn => (
26    is  => 'rw',
27    isa => 'Str',
28);
29
30has password => (
31    is  => 'rw',
32    isa => 'Str',
33);
34
35has filter => (
36    is  => 'rw',
37    # TODO: this does not work (currently); NB: do we need that?
38#    isa => 'Str|Net::LDAP::Filter',
39    isa => 'Str',
40    required => 1,
41);
42
43has attrs => (
44    is  => 'rw',
45    isa => 'ArrayRef|Str',
46    trigger => \&_convert_attrs
47);
48
49has scope => (
50    is  => 'rw',
51    isa => 'Str',
52);
53
54has timeout => (
55    is  => 'rw',
56    isa => 'Int',
57);
58
59has keepalive => (
60    is  => 'rw',
61    isa => 'Int',
62);
63
64has timelimit => (
65    is  => 'rw',
66    isa => 'Int',
67);
68
69has sizelimit => (
70    is  => 'rw',
71    isa => 'Int',
72);
73
74has multihomed => (
75    is => 'rw',
76    isa => 'Int',
77);
78
79has localaddr => (
80    is  => 'rw',
81    isa => 'Str',
82);
83
84has raw => (
85    is  => 'rw',
86    isa => 'RegexpRef|Undef',
87    default => sub { return qr/(?i:;binary)/; },
88);
89
90has debug => (
91    is  => 'rw',
92    isa => 'Int',
93    default => 0,
94);
95
96# ssl certificate options in SSLUserAgent format
97has certificate_file => (
98    is => 'rw',
99    isa => 'Str',
100);
101
102has certificate_key_file => (
103    is => 'rw',
104    isa => 'Str',
105);
106
107has ca_certificate_path => (
108    is => 'rw',
109    isa => 'Str',
110);
111
112has ca_certificate_file => (
113    is => 'rw',
114    isa => 'Str',
115);
116
117has ssl_ignore_mode => (
118    is => 'rw',
119    isa => 'Bool',
120    default => 0,
121);
122
123# ssl options in Net::LDAP format
124has verify => (
125    is  => 'rw',
126    isa => 'Str|Undef',
127    lazy => 1,
128    default => sub { shift->ssl_ignore_mode() ? 'none' : undef }
129);
130
131has capath => (
132    is  => 'rw',
133    isa => 'Str|Undef',
134    lazy => 1,
135    default => sub { shift->ca_certificate_path(); }
136);
137
138has cafile => (
139    is  => 'rw',
140    isa => 'Str|Undef',
141    lazy => 1,
142    default => sub { shift->ca_certificate_file(); }
143);
144
145has sslversion => (
146    is => 'rw',
147    isa => 'Str',
148);
149
150has ciphers => (
151    is  => 'rw',
152    isa => 'Str',
153);
154
155has clientcert => (
156    is  => 'rw',
157    isa => 'Str|Undef',
158    lazy => 1,
159    default => sub { shift->certificate_file(); }
160);
161
162has clientkey => (
163    is  => 'rw',
164    isa => 'Str|Undef',
165    lazy => 1,
166    default => sub { shift->certificate_key_file(); }
167);
168
169has checkcrl => (
170    is  => 'rw',
171    isa => 'Int',
172);
173
174has bind => (
175    is  => 'ro',
176    isa => 'Net::LDAP',
177    reader => '_bind',
178    builder => '_init_bind',
179    clearer   => '_purge_bind',
180    lazy => 1,
181);
182
183has action => (
184    is  => 'rw',
185    isa => 'Str',
186    lazy => 1,
187    default => 'replace',
188);
189
190has create => (
191    is  => 'ro',
192    isa => 'HashRef',
193    default => sub { return {} },
194);
195
196has schema => (
197    is  => 'ro',
198    isa => 'HashRef',
199);
200
201sub _build_config {
202    my $self = shift;
203
204}
205
206sub _build_options {
207    my $self = shift;
208
209    my %options;
210    foreach my $key (@_) {
211        if (defined $self->$key()) {
212            $options{$key} = $self->$key();
213        }
214    }
215    return %options;
216}
217
218sub _build_new_options {
219    my $self = shift;
220    return $self->_build_options(qw(
221        timeout verify  keepalive debug raw multihomed localaddr
222        verify sslversion ciphers capath cafile capath clientcert clientkey checkcrl
223    ));
224}
225
226sub _build_bind_options {
227    my $self = shift;
228    return $self->_build_options(qw( password ));
229}
230
231# the argument passed to this method will be used as template parameters
232# in the expansion of the filter attribute
233sub _build_search_options {
234    my $self = shift;
235    my $arg = shift;
236    my $params = shift;
237
238    my %options = $self->_build_options(qw( base scope sizelimit timelimit ));
239
240    my $filter = $self->filter();
241
242    # template expansion is performed on filter strings only, not
243    # on Net::LDAP::Filter objects
244    my $value;
245    if (ref $filter eq '') {
246        Template->new()->process(\$filter, $arg, \$value) || $self->_log_and_die("Error processing argument template.");
247        $options{filter} = $value;
248    } else {
249        $options{filter} = $filter;
250    }
251
252
253    # Add the attributes to the query to return only the ones we are asked for
254    # Will not work if we allow Filters
255    $options{attrs} = $self->attrs unless( $params->{noattrs} );
256
257    $self->log()->debug('LDAP Search options ' . Dumper %options);
258
259    return %options;
260}
261
262# If the attr property is set using a string (necessary atm for Config::Std)
263# its converted to an arrayref. Might be removed if Config::* improves
264# This might create indefinite loops if something goes wrong on the conversion!
265sub _convert_attrs {
266    my ( $self, $new, $old ) = @_;
267
268    # Test if the given value is a non empty scalar
269    if ($new && !ref $new && (!$old || $new ne $old)) {
270        my @attrs = split(" ", $new);
271        $self->attrs( \@attrs )
272    }
273
274}
275
276sub _init_bind {
277
278    my $self = shift;
279
280    $self->log()->debug('Open bind to to ' . $self->LOCATION());
281
282    my $ldap = Net::LDAP->new(
283        $self->LOCATION(),
284        $self->_build_new_options(),
285    );
286
287    if (! $ldap) {
288       $self->_log_and_die("Could not instantiate ldap object ($@)");
289    }
290
291    my $mesg;
292    if (defined $self->binddn()) {
293        $self->log()->debug('Binding with ' . $self->binddn());
294        my %options = $self->_build_bind_options();
295        $self->log()->warn('Binding with DN but without password') if (!defined $options{password});
296        $mesg = $ldap->bind(
297            $self->binddn(),
298            %options,
299        );
300    } else {
301        # anonymous bind
302        $self->log()->debug('Binding anonymously');
303        $mesg = $ldap->bind(
304            $self->_build_bind_options(),
305        );
306    }
307
308    if ($mesg->is_error()) {
309        $self->_log_and_die(sprintf("LDAP bind failed with error code %s (error: %s)",  $mesg->code(), $mesg->error_desc()));
310    }
311    return $ldap;
312}
313
314sub ldap {
315    # ToDo - check if still bound
316    my $self = shift;
317    return $self->_bind;
318}
319
320# It looks like a half closed connection (server gone / load balancer etc)
321# causes an operational error and not a connection error
322# so the list of codes to use this reconnect foo is somewhat experimental
323sub _is_transient_error {
324    my $self = shift;
325    my $mesg = shift;
326    if ($mesg->is_error() && (grep { $_ == $mesg->code() } (1,81,82))) {
327        $self->log()->debug('Connection lost - try rebind and rerun query');
328        $self->_purge_bind();
329        return 1;
330    }
331    return 0;
332}
333
334sub _getbyDN {
335
336    my $self = shift;
337    my $dn = shift;
338    my $params = shift;
339
340    my $ldap = $self->ldap();
341
342    my %options = $self->_build_options(qw( sizelimit timelimit ));
343    $options{attrs} = $params->{attrs} if ($params->{attrs});
344
345    my $mesg = $ldap->search( base => $dn, scope  => 'base', filter => '(objectclass=*)', %options );
346
347    if ($self->_is_transient_error($mesg)) {
348        $mesg = $ldap->search( base => $dn, scope  => 'base', filter => '(objectclass=*)', %options );
349    }
350
351    if ($mesg->is_error()) {
352        $self->log()->debug("LDAP getByDN failed with error code " . $mesg->code() . " (error: " . $mesg->error_desc() .")" );
353        return;
354    }
355
356    if ( $mesg->count() == 1) {
357
358        my $entry = $mesg->entry(0);
359        # For testing
360        if (lc($entry->dn()) ne lc($dn)) {
361            $self->log()->warn('Search by DN with result looks fishy. Request: '.$dn.' - Entry: '.$entry->dn());
362        }
363        return $entry;
364    }
365
366    # Query is ambigous - can this happen ?
367    if ( $mesg->count() > 1) {
368        $self->_log_and_die("There is more than one matching node.",
369            sprintf('Search by DN got multiple (%01d) results (%s)', $mesg->count(), $dn));
370    }
371
372    # If autocreation is not requested, return undef
373    if (!$params->{create}) {
374        return;
375    }
376
377    # No match, so split up the DN and walk upwards
378    my $base_dn = $self->base;
379
380    # we cant do much if the base dn does not exists
381    if ($dn eq $base_dn) {
382        $self->_log_and_die('Request to auto-create the base dn');
383    }
384
385    # Strip the basedn from the dn and tokenize the rest
386    my $path = $dn;
387    $path =~ s/$base_dn\z//;
388
389    if (!$path) {
390        $self->_log_and_die('Request to auto-create empty path');
391    }
392
393    my @dn_attributes = $self->_splitDN( $path );
394
395    my $currentPath = $base_dn;
396    my @nextComponent;
397    my $i;
398    for ($i = scalar(@dn_attributes)-1; $i >= 0; $i--) {
399
400        # For the moment we just implement single value components
401        my $nextComponentKey = $dn_attributes[$i][0];
402        my $nextComponentValue = $dn_attributes[$i][1];
403
404        my $nextComponent = $nextComponentKey.'='.$nextComponentValue;
405
406        # Search for the next node
407        #print "Probe $currentPath - $nextComponent: ";
408        $mesg = $ldap->search( base => $currentPath, scope  => 'one', filter => '('.$nextComponent.')' );
409
410        # found, push to path and test next
411        if ( $mesg->count() == 1) {
412            #print "Found\n";
413            $currentPath = $nextComponent.','.$currentPath;
414            next;
415        }
416
417        #print Dumper( $mesg );
418        #print "not Found - i: $i\n\n";
419
420        # Reuse counter and list to build the missing nodes
421        while ($i >= 0) {
422            $nextComponentKey = $dn_attributes[$i][0];
423            $nextComponentValue = $dn_attributes[$i][1];
424            $currentPath = $self->_createPathItem($currentPath, $nextComponentKey, $nextComponentValue);
425            $i--;
426        }
427    }
428
429    return $self->_getbyDN( $dn );
430}
431
432sub _createPathItem {
433
434    my $self = shift;
435    $self->log()->trace("Create Path called with " . Dumper \@_) if ($self->log()->is_trace);
436
437    my $currentPath = shift;
438    my $nextComponentKey = shift;
439    my $nextComponentValue;
440
441    my $values = {};
442    if (ref $nextComponentKey) {
443        $values = $nextComponentKey;
444    } else {
445        $nextComponentValue = shift;
446        $values = { lc($nextComponentKey) => $nextComponentValue };
447    }
448    my $attributes = shift;
449
450    my $rdnkey = lc(join("+", sort keys %{$values}));
451
452    my $newDN = join( "+", map { sprintf("%s=%s", $_, $values->{$_}) } sort keys %{$values});
453    $newDN .= ','.$currentPath;
454
455    my $schema = $self->schema();
456    $self->_log_and_die("No schema data to create nodes") if (!$schema);
457
458    $schema = $schema->{$rdnkey} || $schema->{default};
459    $self->_log_and_die("No schema data for create path item ($rdnkey)") if (!$schema);
460
461    my @attrib;
462    if (!$schema->{objectclass}) {
463        $self->_log_and_die("No objectclass defined for path item $rdnkey");
464    }
465
466    push @attrib, "objectclass";
467    if (ref $schema->{objectclass} eq 'ARRAY') {
468        push @attrib, $schema->{objectclass};
469    } else {
470        my @classnames = split " ", $schema->{objectclass};
471        push @attrib, \@classnames;
472    }
473
474    # Default Values to push
475    $values = { %{$values}, %{$schema->{values} } } if ($schema->{values});
476
477    # append attributes to value hash, this will overwrite values from the
478    $values = { %{$attributes}, %{$values} };
479
480    foreach my $key ( keys %{$values}) {
481        my $val = $values->{$key};
482        next unless defined $val;
483        if ($val eq 'copy:self') {
484            die "copy:self does not work with multivalued rdns" unless defined $nextComponentValue;
485            $val = $nextComponentValue;
486        }
487        push @attrib, $key, $val;
488    }
489
490    #print "Create Node $newDN \n";
491    #print Dumper( $attrib );
492
493    $self->log()->trace("Create Node $newDN  with attributes " . Dumper \@attrib) if ($self->log()->is_trace);
494    $self->log()->debug("Create Node $newDN  with attributes " . Dumper \@attrib);
495
496    my $result = $self->ldap()->add( $newDN, attr => \@attrib );
497
498    if ($result->is_error()) {
499        $self->_log_and_die($result->error_desc);
500    }
501
502    return $newDN;
503
504}
505
506sub _triggerAutoCreate {
507
508    my $self = shift;
509    my $args = shift;
510    my $data = shift || {};
511
512    my $path = $self->base();
513    my @rdn;
514
515    my $tt = Template->new({});
516
517    my $create_info = $self->create();
518    if ($create_info->{basedn}) {
519        $path = $create_info->{basedn};
520    }
521
522    if ($create_info->{dn}) {
523        my $dn;
524        $tt->process(\$create_info->{dn}, { ARGS => $args, DATA => $data }, \$dn) || $self->_log_and_die("Error processing argument template for DN.");
525        $self->log()->debug('Auto-Create with full dn from template');
526        if (!$dn || $dn !~ /(([^=]+)=(.*?[^\\])\s*,)(.+)/) {
527            $self->_log_and_die("Unable to split DN from template");
528        }
529        @rdn = ($2, $3);
530        # triggers creation of path components below later
531        $path = $4;
532
533    } elsif ($create_info->{rdn}) {
534        if (ref $create_info->{rdn}) {
535            my $hash;
536            foreach my $rdtpl (@{$create_info->{rdn}}){
537                my $rdn;
538                $tt->process(\$rdtpl, { ARGS => $args, DATA => $data }, \$rdn) || $self->_log_and_die("Error processing argument template for RDN $rdtpl.");
539                next unless ($rdn);
540                my @t = split("=", $rdn, 2);
541                $hash->{$t[0]} = $t[1];
542            }
543            @rdn = ($hash);
544            $self->log()->debug('Auto-Create with RDN template (Multivalued)');
545        } else {
546            my $rdn;
547            $tt->process(\$create_info->{rdn}, { ARGS => $args, DATA => $data }, \$rdn) || $self->_log_and_die("Error processing argument template for RDN " . $create_info->{rdn});
548            @rdn = split("=", $rdn, 2);
549            $self->log()->debug('Auto-Create with RDN template');
550        }
551
552    } elsif ($create_info->{rdnkey}) {
553        @rdn = ($create_info->{rdnkey}, $args->[0]);
554        $self->log()->debug('Auto-Create with RDN key') if($self->log()->is_debug);
555
556    } else {
557        my $schema = $self->schema();
558        if ($schema->{'cn'}) {
559            $self->log()->debug('Auto-Create with commonName based on schema:');
560            @rdn = ('cn', $args->[0]);
561        } else {
562            $self->log()->warn('Auto-Create not configured');
563            return;
564        }
565    }
566
567    # create the components for the path first, without usage of extra data
568    if ($path ne $self->base()) {
569        $path = $self->_getbyDN( $path, { create => 1 } );
570    }
571
572    # now create the leaf node now using the extra data
573    my $nodeDN = $self->_createPathItem( $path, @rdn, $data );
574    $self->log()->debug('Auto-Create done - nodeDN: '.$nodeDN);
575    return $nodeDN;
576
577}
578
579sub _splitDN {
580
581    my $self = shift;
582    my $dn = shift;
583
584    my @parsed;
585    while ($dn =~ /(([^=]+)=(.*?[^\\])\s*,)(.*)/) {
586        push @parsed, [ $2, $3 ];
587        $self->log()->debug(sprintf 'Split-Result: Key: %s, Value: %s, Remainder: %s ', $2, $3, $4);
588        $dn = $4;
589    };
590
591    # soemthing is really wrong - likely broken input with comma in the end
592    if (!$dn) {
593        $self->_log_and_die('Empty dn part in splitDN');
594    }
595
596    # Split last remainder at =
597    my @last = split ("=", $dn);
598    push @parsed, \@last;
599
600    return @parsed;
601}
602
603sub _run_search {
604
605    my $self = shift;
606    my $arg = shift;
607    my $params = shift;
608
609    my %option = $self->_build_search_options( $arg, $params );
610
611    my $mesg = $self->ldap()->search( %option );
612
613    # Lost connection, try to rebind and rerun query
614    if ($self->_is_transient_error($mesg)) {
615        $mesg = $self->ldap()->search( %option );
616    }
617
618    return $mesg;
619
620}
621
622no Moose;
623__PACKAGE__->meta->make_immutable;
624
6251;
626__END__
627
628=head1 NAME
629
630Connector::Proxy::Net::LDAP
631
632=head1 DESCRIPTION
633
634This is the base class for all LDAP Proxy modules. It does not offer any
635external functionality but bundles common configuration options.
636
637=head1 USAGE
638
639=head2 minimal setup
640
641    my $conn = Connector::Proxy::Net::LDAP->new({
642       LOCATION  => 'ldap://localhost:389',
643       base      => 'dc=example,dc=org',
644       filter  => '(cn=[% ARGS.0 %])',
645    });
646
647    $conn->get('John Doe');
648
649Above code will run a query of C<cn=test@example.org against the server>
650using an anonymous bind.
651
652=head2 using bind credentials
653
654    my $conn = Connector::Proxy::Net::LDAP->new( {
655        LOCATION  => 'ldap://localhost:389',
656        base      => 'dc=example,dc=org',
657        filter  => '(cn=[% ARGS.0 %])',
658        binddn    => 'cn=admin,dc=openxpki,dc=org',
659        password  => 'admin',
660        attrs => ['usercertificate;binary','usercertificate'],
661    });
662
663Uses bind credentials and queries for entries having (at least) one of the
664mentioned attributes.
665
666=head2 connection control
667
668Following controls are passed to Net::LDAP->new from class parameters
669with the same name, see Net::LDAP for details.
670
671=over
672
673=item timeout
674
675=item keepalive
676
677=item multihomed
678
679=item localaddr
680
681=item debug
682
683=item raw
684
685Enables utf8 for returned attribute values. The default value is
686qr/;binary/, set this to a Regex reference to change the attribute
687pattern for utf8 conversion or set I<undef> to disable it.
688
689=back
690
691=head3 SSL connection options
692
693SSl related options are passed to Net::LDAP->new, see Net::LDAP for
694details. The attribute names in brackets are identical to the ones
695used in the HTTP based connectors and mapped to their equivalents.
696
697Note that mapping takes place at first init, so modifications to those
698values after the first connection will not be visibile. The native
699parameter names are superior.
700
701=over
702
703=item verify (ssl_ignore_mode - 'reqiured' if true)
704
705=item sslversion
706
707=item ciphers
708
709=item capath (ca_certificate_path)
710
711=item cafile (ca_certificate_file)
712
713=item clientcert (certificate_file)
714
715=item clientkey (certificate_key_file)
716
717=item checkcrl
718
719=back
720
721=head2 setting values
722
723You can control how existing attributes in the node are treated setting the
724I<action> parameter in the connectors base configuration.
725
726    connector:
727        LOCATION:...
728        ....
729        action: replace
730
731=over
732
733=item replace
734
735This is the default (the action parameter may be omitted). The passed value is
736set as the only value in the attribute. Any values (even if there are more
737than one) are removed. If undef is passed, the whole attribute is removed
738from the node.
739
740=item append
741
742The given value is appended to exisiting attributes. If undef is passed, the request is ignored.
743
744=item delete
745
746The given value is deleted from the attribute entry. If there are more items in the attribute,
747the remaining values are left untouched. If the value is not present or undef is passed,
748the request is ignored.
749
750=back
751
752=head2 autocreation of missing nodes
753
754If you want the connector to autocreate missing nodes (on a set operation),
755you need to provide the ldap properties for each rdn item.
756
757    schema:
758        cn:
759            objectclass: inetOrgPerson pkiUser
760            values:
761                sn: copy:self
762                ou: IT Department
763
764You can specify multiple objectclass entries seperated by space or as list.
765
766The objects attribute matching the RDN component is always set, you can
767use the special word C<copy:self> to copy the attribute value within the
768object. The values section is optional.
769
770If schema for I<CN> is given and the filter does not find a result, the
771node name is constructed from using the first path argument as CN and the
772base dn of the connector as path. All defined attribute values that have
773been passed are also added to the object on creation. Auto-Creation is not
774applied if action is set to delete.
775
776For creating the actual leaf node, there are additional options by adding
777the node I<create> to the configuration.
778
779=head3 set another component class for the node
780
781    create:
782        rdnkey: emailAddress
783
784Will use the given class name with the first argument as value plus the
785base dn to build the node DN. The old syntax with rdnkey + value pattern
786(which was broken anyway) is no longer supported, use the full rdn template
787as given below if required.
788
789=head3 set another path to the node
790
791    create:
792        basedn: ou=Webservers,ou=Servers,dc=company,dc=org
793
794=head3 use templating to generate the local component
795
796The given base dn will be prefixed with the component assigned to the
797leaf, e.g. cn=www.example.org,ou=Webservers,ou=Servers,dc=company,dc=org
798
799=head3 use templating to generate the local component
800
801    create:
802        rdn: emailAddress=[% ARGS.0 %]
803
804Same result as the first example, the path arguments are all in ARGS,
805additional data (depends on the subclass implementation) are made
806available in the DATA key.
807
808Multivalued RDNs can be constructed using a list:
809
810    create:
811        rdn:
812         - emailAddress=[% ARGS.0 %]
813         - CN=[% ARGS.1 %]
814
815=head3 use temlating for full DN
816
817    create:
818        dn: emailAddress=[% ARGS.0 %],ou=People,dc=company,dc=org
819
820Same as setting basedn and rdn, components of the path are created if
821there is a matching schema definition. Limitation: this module does not
822support different value patterns for the same class name.
823
824=head2 Full example using Connector::Multi
825
826    [ca1]
827    myrepo@ = connector:connectors.ldap
828
829    [connectors]
830
831    [connectors.ldap]
832    class = Connector::Proxy::Net::LDAP
833    LOCATION = ldap://ldaphost:389
834    base     = dc=openxpki,dc=org
835    filter   = (cn=[% ARGS.0 %])
836    attrs    = userCertificate;binary
837    binddn   = cn=admin,dc=openxpki,dc=org
838    password = admin
839    action = replace
840
841    [connectors.ldap.create]
842    basedn: ou=Webservers,ou=Server CA3,dc=openxpki,dc=org
843    rdnkey: cn
844    value: [% ARGS.0 %]
845
846    [connectors.ldap.schema.cn]
847    objectclass: inetOrgPerson
848
849    [connectors.ldap.schema.cn.values]
850    sn: copy:self
851
852    [connectors.ldap.schema.ou]
853    objectclass: organizationalUnit
854
855=head1 internal methods
856
857=head2 _getByDN
858
859Search a node by DN.
860
861    $self->_getByDN( 'cn=John Doe,ou=people,dc=openxpki,dc=org' );
862
863Returns the ldap entry object or undef if not found. Pass C<{create => 1}> and
864configure your connector to auto create a new node if none is found.
865
866=head2 _createPathItem
867
868Used internally by _getByDN to create new nodes.
869
870=head2 _triggerAutoCreate
871
872Used internally to assemble the DN for a missing node.
873Returns the ldap entry or undef if autocreation is not possible.
874
875=head2 _splitDN
876
877Very simple approch to split a DN path into its components.
878Please B<do not> use quoting of path components, as this is
879not supported. RDNs must be split by a Comma, Comma inside a value
880must be escaped using a backslash character. Multivalued RDNs are not supported.
881
882=head2 _run_search
883
884This is a wrapper for
885
886  my $mesg = $ldap->search( $self->_build_search_options( $args, $param ) );
887
888that will take care of stale/lost connections to the server. The result
889object is returned by the method, the ldap object is taken from the class.
890