File Coverage

blib/lib/Net/LDAP.pm
Criterion Covered Total %
statement 141 465 30.3
branch 43 372 11.5
condition 7 119 5.8
subroutine 26 58 44.8
pod 27 37 72.9
total 244 1051 23.2


line stmt bran cond sub pod time code
1             # Copyright (c) 1997-2004 Graham Barr . All rights reserved.
2             # This program is free software; you can redistribute it and/or
3             # modify it under the same terms as Perl itself.
4              
5             package Net::LDAP;
6              
7 21     21   449105 use strict;
  21         94  
  21         668  
8 21     21   9819 use Socket qw(AF_INET AF_INET6 AF_UNSPEC SOL_SOCKET SO_KEEPALIVE);
  21         64069  
  21         3190  
9 21     21   9017 use IO::Socket;
  21         316157  
  21         82  
10 21     21   16630 use IO::Select;
  21         29856  
  21         887  
11 21     21   9538 use Tie::Hash;
  21         16788  
  21         664  
12 21     21   8251 use Convert::ASN1 qw(asn_read);
  21         566721  
  21         1141  
13 21     21   8854 use Net::LDAP::Message;
  21         66  
  21         724  
14 21     21   130 use Net::LDAP::ASN qw(LDAPResponse);
  21         36  
  21         73  
15 21         3062 use Net::LDAP::Constant qw(LDAP_SUCCESS
16             LDAP_OPERATIONS_ERROR
17             LDAP_SASL_BIND_IN_PROGRESS
18             LDAP_DECODING_ERROR
19             LDAP_PROTOCOL_ERROR
20             LDAP_ENCODING_ERROR
21             LDAP_FILTER_ERROR
22             LDAP_LOCAL_ERROR
23             LDAP_PARAM_ERROR
24             LDAP_INAPPROPRIATE_AUTH
25             LDAP_SERVER_DOWN
26             LDAP_USER_CANCELED
27             LDAP_EXTENSION_START_TLS
28             LDAP_UNAVAILABLE
29 21     21   104 );
  21         34  
30              
31             # check for IPv6 support: prefer IO::Socket::IP 0.20+ over IO::Socket::INET6
32 21         38 use constant CAN_IPV6 => do {
33 21         77 local $SIG{__DIE__};
34              
35 21         13868 eval { require IO::Socket::IP; IO::Socket::IP->VERSION(0.20); }
  21         372326  
36             ? 'IO::Socket::IP'
37 21 0       58 : eval { require IO::Socket::INET6; }
  0 50       0  
38             ? 'IO::Socket::INET6'
39             : '';
40 21     21   156 };
  21         49  
41              
42             our $VERSION = '0.66';
43             our @ISA = qw(Tie::StdHash Net::LDAP::Extra);
44             our $LDAP_VERSION = 3; # default LDAP protocol version
45              
46             # Net::LDAP::Extra will only exist is someone use's the module. But we need
47             # to ensure the package stash exists or perl will complain that we inherit
48             # from a non-existent package. I could just use the module, but I did not
49             # want to.
50              
51             $Net::LDAP::Extra::create = $Net::LDAP::Extra::create = 0;
52              
53             sub import {
54 27     27   1919 shift;
55 27         72 unshift @_, 'Net::LDAP::Constant';
56 27         194 require Net::LDAP::Constant;
57 27         48 goto &{Net::LDAP::Constant->can('import')};
  27         1696  
58             }
59              
60             sub _options {
61 4     4   11 my %ret = @_;
62 4         6 my $once = 0;
63 4         15 for my $v (grep { /^-/ } keys %ret) {
  2         11  
64 0         0 require Carp;
65 0 0       0 $once++ or Carp::carp('deprecated use of leading - for options');
66 0         0 $ret{substr($v, 1)} = $ret{$v};
67             }
68              
69 0 0       0 $ret{control} = [ map { (ref($_) =~ /[^A-Z]/) ? $_->to_asn : $_ }
70             ref($ret{control}) eq 'ARRAY'
71 0         0 ? @{$ret{control}}
72             : $ret{control}
73             ]
74 4 0       12 if exists $ret{control};
    50          
75              
76 4         18 \%ret;
77             }
78              
79             sub _dn_options {
80 2 50   2   17 unshift @_, 'dn' if @_ & 1;
81 2         5 &_options;
82             }
83              
84             sub _err_msg {
85 0     0   0 my $mesg = shift;
86 0   0     0 my $errstr = $mesg->dn || '';
87 0 0       0 $errstr .= ': ' if $errstr;
88 0         0 $errstr . $mesg->error;
89             }
90              
91             my %onerror = (
92             die => sub { require Carp; Carp::croak(_err_msg(@_)) },
93             warn => sub { require Carp; Carp::carp(_err_msg(@_)); $_[0] },
94             undef => sub { require Carp; Carp::carp(_err_msg(@_)) if $^W; undef },
95             );
96              
97             sub _error {
98 0     0   0 my ($ldap, $mesg) = splice(@_, 0, 2);
99              
100 0         0 $mesg->set_error(@_);
101             $ldap->{net_ldap_onerror} && !$ldap->{net_ldap_async}
102 0 0 0     0 ? scalar &{$ldap->{net_ldap_onerror}}($mesg)
  0         0  
103             : $mesg;
104             }
105              
106             sub new {
107 2     2 1 1373 my $self = shift;
108 2   33     16 my $type = ref($self) || $self;
109 2 50       9 my $host = shift if @_ % 2;
110 2         6 my $arg = &_options;
111 2         6 my $obj = bless {}, $type;
112              
113 2 50       8 foreach my $uri (ref($host) ? @$host : ($host)) {
114 2   50     8 my $scheme = $arg->{scheme} || 'ldap';
115 2         4 my $h = $uri;
116 2 50       6 if (defined($h)) {
117 2 50       7 $h =~ s,^(\w+)://,, and $scheme = lc($1);
118 2         6 $h =~ s,/.*,,; # remove path part
119 2         4 $h =~ s/%([A-Fa-f0-9]{2})/chr(hex($1))/eg; # unescape
  0         0  
120             }
121 2 50       20 my $meth = $obj->can("connect_$scheme") or next;
122 2 50       8 if (&$meth($obj, $h, $arg)) {
123 2         254 $obj->{net_ldap_uri} = $uri;
124 2         4 $obj->{net_ldap_scheme} = $scheme;
125 2         5 last;
126             }
127             }
128              
129 2 50       6 return undef unless $obj->{net_ldap_socket};
130              
131             $obj->{net_ldap_socket}->setsockopt(SOL_SOCKET, SO_KEEPALIVE, $arg->{keepalive} ? 1 : 0)
132 2 0       6 if (defined($arg->{keepalive}));
    50          
133              
134 2         5 $obj->{net_ldap_rawsocket} = $obj->{net_ldap_socket};
135 2         4 $obj->{net_ldap_resp} = {};
136 2   33     11 $obj->{net_ldap_version} = $arg->{version} || $LDAP_VERSION;
137 2 50       6 $obj->{net_ldap_async} = $arg->{async} ? 1 : 0;
138 2 50       13 $obj->{raw} = $arg->{raw} if ($arg->{raw});
139              
140 2 50       8 if (defined(my $onerr = $arg->{onerror})) {
141 0 0       0 $onerr = $onerror{$onerr} if exists $onerror{$onerr};
142 0         0 $obj->{net_ldap_onerror} = $onerr;
143             }
144              
145 2   50     18 $obj->debug($arg->{debug} || 0 );
146              
147 2         8 $obj->outer;
148             }
149              
150             sub connect_ldap {
151 0     0 0 0 my ($ldap, $host, $arg) = @_;
152 0   0     0 my $port = $arg->{port} || 389;
153 0         0 my $class = (CAN_IPV6) ? CAN_IPV6 : 'IO::Socket::INET';
154 0 0       0 my $domain = $arg->{inet4} ? AF_INET : ($arg->{inet6} ? AF_INET6 : AF_UNSPEC);
    0          
155              
156             # separate port from host overwriting given/default port
157 0 0       0 $host =~ s/^([^:]+|\[.*\]):(\d+)$/$1/ and $port = $2;
158              
159 0 0 0     0 if ($arg->{inet6} && !CAN_IPV6) {
160 0         0 $@ = 'unable to load IO::Socket::INET6; no IPv6 support';
161 0         0 return undef;
162             }
163              
164             $ldap->{net_ldap_socket} = $class->new(
165             PeerAddr => $host,
166             PeerPort => $port,
167             LocalAddr => $arg->{localaddr} || undef,
168             Proto => 'tcp',
169             Domain => $domain,
170             MultiHomed => $arg->{multihomed},
171             Timeout => defined $arg->{timeout}
172             ? $arg->{timeout}
173 0 0 0     0 : 120
    0          
174             ) or return undef;
175              
176 0         0 $ldap->{net_ldap_host} = $host;
177 0         0 $ldap->{net_ldap_port} = $port;
178             }
179              
180              
181             # Different OpenSSL verify modes.
182             my %ssl_verify = qw(none 0 optional 1 require 3);
183              
184             sub connect_ldaps {
185 0     0 0 0 my ($ldap, $host, $arg) = @_;
186 0   0     0 my $port = $arg->{port} || 636;
187 0 0       0 my $domain = $arg->{inet4} ? AF_INET : ($arg->{inet6} ? AF_INET6 : AF_UNSPEC);
    0          
188              
189 0 0 0     0 if ($arg->{inet6} && !CAN_IPV6) {
190 0         0 $@ = 'unable to load IO::Socket::INET6; no IPv6 support';
191 0         0 return undef;
192             }
193              
194 0         0 require IO::Socket::SSL;
195              
196             # separate port from host overwriting given/default port
197 0 0       0 $host =~ s/^([^:]+|\[.*\]):(\d+)$/$1/ and $port = $2;
198              
199 0 0       0 $arg->{sslserver} = $host unless defined $arg->{sslserver};
200              
201             $ldap->{net_ldap_socket} = IO::Socket::SSL->new(
202             PeerAddr => $host,
203             PeerPort => $port,
204             LocalAddr => $arg->{localaddr} || undef,
205             Proto => 'tcp',
206             Domain => $domain,
207 0 0 0     0 Timeout => defined $arg->{timeout} ? $arg->{timeout} : 120,
    0          
208             _SSL_context_init_args($arg)
209             ) or return undef;
210              
211 0         0 $ldap->{net_ldap_host} = $host;
212 0         0 $ldap->{net_ldap_port} = $port;
213             }
214              
215             sub _SSL_context_init_args {
216 0     0   0 my $arg = shift;
217              
218 0         0 my $verify = 0;
219 0         0 my %verifycn_ctx = ();
220 0         0 my ($clientcert, $clientkey, $passwdcb);
221              
222 0 0       0 if (exists $arg->{verify}) {
223 0         0 my $v = lc $arg->{verify};
224 0 0       0 $verify = 0 + (exists $ssl_verify{$v} ? $ssl_verify{$v} : $verify);
225              
226 0 0       0 if ($verify) {
227 0         0 $verifycn_ctx{SSL_verifycn_scheme} = 'ldap';
228             $verifycn_ctx{SSL_verifycn_name} = $arg->{sslserver}
229 0 0       0 if (defined $arg->{sslserver});
230             }
231             }
232              
233 0 0       0 if (exists $arg->{clientcert}) {
234 0         0 $clientcert = $arg->{clientcert};
235 0 0       0 if (exists $arg->{clientkey}) {
236 0         0 $clientkey = $arg->{clientkey};
237             } else {
238 0         0 require Carp;
239 0         0 Carp::croak('Setting client public key but not client private key');
240             }
241             }
242              
243 0 0 0     0 if ($arg->{checkcrl} && !$arg->{capath}) {
244 0         0 require Carp;
245 0         0 Carp::croak('Cannot check CRL without having CA certificates');
246             }
247              
248 0 0       0 if (exists $arg->{keydecrypt}) {
249 0         0 $passwdcb = $arg->{keydecrypt};
250             }
251              
252             # allow deprecated "sslv2/3" in addition to IO::Socket::SSL's "sslv23"
253 0 0       0 if (defined $arg->{sslversion}) {
254 0         0 $arg->{sslversion} =~ s:sslv2/3:sslv23:io;
255             }
256              
257             (
258             defined $arg->{ciphers} ?
259             ( SSL_cipher_list => $arg->{ciphers} ) : (),
260             defined $arg->{sslversion} ?
261             ( SSL_version => $arg->{sslversion} ) : (),
262             SSL_ca_file => exists $arg->{cafile} ? $arg->{cafile} : '',
263             SSL_ca_path => exists $arg->{capath} ? $arg->{capath} : '',
264             SSL_key_file => $clientcert ? $clientkey : undef,
265             SSL_passwd_cb => $passwdcb,
266 0 0       0 SSL_check_crl => $arg->{checkcrl} ? 1 : 0,
    0          
    0          
    0          
    0          
    0          
    0          
267             SSL_use_cert => $clientcert ? 1 : 0,
268             SSL_cert_file => $clientcert,
269             SSL_verify_mode => $verify,
270             %verifycn_ctx,
271             );
272             }
273              
274             sub connect_ldapi {
275 0     0 0 0 my ($ldap, $peer, $arg) = @_;
276              
277 0 0 0     0 $peer = $ENV{LDAPI_SOCK} || '/var/run/ldapi'
278             unless length $peer;
279              
280 0         0 require IO::Socket::UNIX;
281              
282             $ldap->{net_ldap_socket} = IO::Socket::UNIX->new(
283             Peer => $peer,
284             Timeout => defined $arg->{timeout}
285             ? $arg->{timeout}
286 0 0       0 : 120
    0          
287             ) or return undef;
288              
289             # try to get canonical host name [to allow start_tls on the connection]
290 0         0 require Socket;
291 0 0 0     0 if (Socket->can('getnameinfo') && Socket->can('getaddrinfo')) {
292 0         0 my @addrs;
293 0         0 my ($err, $host, $path) = Socket::getnameinfo($ldap->{net_ldap_socket}->peername, &Socket::AI_CANONNAME);
294              
295 0 0       0 ($err, @addrs) = Socket::getaddrinfo($host, 0, { flags => &Socket::AI_CANONNAME } )
296             unless ($err);
297 0 0       0 map { $ldap->{net_ldap_host} = $_->{canonname} if ($_->{canonname}) } @addrs
  0 0       0  
298             unless ($err);
299             }
300              
301 0   0     0 $ldap->{net_ldap_host} ||= 'localhost';
302 0         0 $ldap->{net_ldap_peer} = $peer;
303             }
304              
305             sub message {
306 2     2 0 5 my $ldap = shift;
307 2         16 shift->new($ldap, @_);
308             }
309              
310             sub async {
311 2     2 1 3 my $ldap = shift;
312              
313             @_
314             ? ($ldap->{net_ldap_async}, $ldap->{net_ldap_async} = shift)[0]
315 2 50       11 : $ldap->{net_ldap_async};
316             }
317              
318             sub debug {
319 4     4 1 7 my $ldap = shift;
320              
321 4 50       10 require Convert::ASN1::Debug if $_[0];
322              
323             @_
324             ? ($ldap->{net_ldap_debug}, $ldap->{net_ldap_debug} = shift)[0]
325 4 100       20 : $ldap->{net_ldap_debug};
326             }
327              
328             sub sasl {
329 0     0 1 0 $_[0]->{sasl};
330             }
331              
332             sub socket {
333 2     2 1 4 my $ldap = shift;
334 2         4 my %opt = @_;
335              
336             (exists($opt{sasl_layer}) && !$opt{sasl_layer})
337             ? $ldap->{net_ldap_rawsocket}
338 2 50 33     13 : $ldap->{net_ldap_socket};
339             }
340              
341             sub host {
342 0     0 1 0 my $ldap = shift;
343             ($ldap->{net_ldap_scheme} ne 'ldapi')
344             ? $ldap->{net_ldap_host}
345 0 0       0 : $ldap->{net_ldap_peer};
346             }
347              
348             sub port {
349 0 0   0 1 0 $_[0]->{net_ldap_port} || undef;
350             }
351              
352             sub scheme {
353 0     0 1 0 $_[0]->{net_ldap_scheme};
354             }
355              
356             sub uri {
357 0     0 1 0 $_[0]->{net_ldap_uri};
358             }
359              
360              
361             sub unbind {
362 0     0 1 0 my $ldap = shift;
363 0         0 my $arg = &_options;
364              
365 0         0 my $mesg = $ldap->message('Net::LDAP::Unbind' => $arg);
366              
367             my $control = $arg->{control}
368 0 0 0     0 and $ldap->{net_ldap_version} < 3
369             and return _error($ldap, $mesg, LDAP_PARAM_ERROR, 'Controls require LDAPv3');
370              
371 0 0       0 $mesg->encode(
372             unbindRequest => 1,
373             controls => $control,
374             ) or return _error($ldap, $mesg, LDAP_ENCODING_ERROR, "$@");
375              
376 0         0 $ldap->_sendmesg($mesg);
377             }
378              
379             # convenience alias
380             *done = \&unbind;
381              
382              
383             sub ldapbind {
384 0     0 0 0 require Carp;
385 0 0       0 Carp::carp('->ldapbind deprecated, use ->bind') if $^W;
386 0         0 goto &bind;
387             }
388              
389              
390             my %ptype = qw(
391             password simple
392             krb41password krbv41
393             krb42password krbv42
394             kerberos41 krbv41
395             kerberos42 krbv42
396             sasl sasl
397             noauth anon
398             anonymous anon
399             );
400              
401             sub bind {
402 2     2 1 13 my $ldap = shift;
403 2         5 my $arg = &_dn_options;
404              
405 2         469 require Net::LDAP::Bind;
406 2         22 my $mesg = $ldap->message('Net::LDAP::Bind' => $arg);
407              
408             $ldap->version(delete $arg->{version})
409 2 50       8 if exists $arg->{version};
410              
411 2   50     8 my $dn = delete $arg->{dn} || '';
412             my $control = delete $arg->{control}
413 2 50 33     7 and $ldap->{net_ldap_version} < 3
414             and return _error($ldap, $mesg, LDAP_PARAM_ERROR, 'Controls require LDAPv3');
415              
416 2 50       12 my %stash = (
417             name => ref($dn) ? $dn->dn : $dn,
418             version => $ldap->version,
419             );
420              
421 2 50       24 my($auth_type, $passwd) = scalar(keys %$arg) ? () : (simple => '');
422              
423 2         5 keys %ptype; # Reset iterator
424 2         20 while (my($param, $type) = each %ptype) {
425 16 50       48 if (exists $arg->{$param}) {
426 0 0       0 ($auth_type, $passwd) = $type eq 'anon' ? (simple => '') : ($type, $arg->{$param});
427 0 0 0     0 return _error($ldap, $mesg, LDAP_INAPPROPRIATE_AUTH, 'No password, did you mean noauth or anonymous ?')
428             if $type eq 'simple' and $passwd eq '';
429 0         0 last;
430             }
431             }
432              
433 2 50       5 return _error($ldap, $mesg, LDAP_INAPPROPRIATE_AUTH, 'No AUTH supplied')
434             unless $auth_type;
435              
436 2 50       7 if ($auth_type eq 'sasl') {
437              
438             return _error($ldap, $mesg, LDAP_PARAM_ERROR, 'SASL requires LDAPv3')
439 0 0       0 if $ldap->{net_ldap_version} < 3;
440              
441 0         0 my $sasl = $passwd;
442 0         0 my $sasl_conn;
443              
444 0 0 0     0 if (ref($sasl) and $sasl->isa('Authen::SASL')) {
445              
446             # If we're talking to a round-robin, the canonical name of
447             # the host we are talking to might not match the name we
448             # requested. Look at the rawsocket because SASL layer filehandles
449             # don't support socket methods.
450 0         0 my $sasl_host;
451              
452 0 0       0 if (exists($arg->{sasl_host})) {
453 0 0       0 if ($arg->{sasl_host}) {
    0          
454 0         0 $sasl_host = $arg->{sasl_host};
455             }
456             elsif ($ldap->{net_ldap_rawsocket}->can('peerhost')) {
457 0         0 $sasl_host = $ldap->{net_ldap_rawsocket}->peerhost;
458             }
459             }
460 0   0     0 $sasl_host ||= $ldap->{net_ldap_host};
461              
462 0         0 $sasl_conn = eval {
463 0         0 local ($SIG{__DIE__});
464 0         0 $sasl->client_new('ldap', $sasl_host);
465             };
466             }
467             else {
468 0         0 $sasl_conn = $sasl;
469             }
470              
471 0 0       0 return _error($ldap, $mesg, LDAP_LOCAL_ERROR, "$@")
472             unless defined($sasl_conn);
473              
474             # Tell SASL the local and server IP addresses
475             $sasl_conn->property(
476             sockname => $ldap->{net_ldap_rawsocket}->sockname,
477             peername => $ldap->{net_ldap_rawsocket}->peername,
478 0         0 );
479              
480 0         0 my $initial = $sasl_conn->client_start;
481              
482 0 0       0 return _error($ldap, $mesg, LDAP_LOCAL_ERROR, $sasl_conn->error)
483             unless defined($initial);
484              
485 0         0 $passwd = {
486             mechanism => $sasl_conn->mechanism,
487             credentials => $initial,
488             };
489              
490             # Save data, we will need it later
491 0         0 $mesg->_sasl_info($stash{name}, $control, $sasl_conn);
492             }
493              
494 2         7 $stash{authentication} = { $auth_type => $passwd };
495              
496 2 50       12 $mesg->encode(
497             bindRequest => \%stash,
498             controls => $control
499             ) or return _error($ldap, $mesg, LDAP_ENCODING_ERROR, "$@");
500              
501 2         12 $ldap->_sendmesg($mesg);
502             }
503              
504              
505             my %scope = qw(base 0 one 1 single 1 sub 2 subtree 2 children 3);
506             my %deref = qw(never 0 search 1 find 2 always 3);
507              
508             sub search {
509 0     0 1 0 my $ldap = shift;
510 0         0 my $arg = &_options;
511              
512 0         0 require Net::LDAP::Search;
513              
514             $arg->{raw} = $ldap->{raw}
515 0 0 0     0 if ($ldap->{raw} && !defined($arg->{raw}));
516              
517 0         0 my $mesg = $ldap->message('Net::LDAP::Search' => $arg);
518              
519             my $control = $arg->{control}
520 0 0 0     0 and $ldap->{net_ldap_version} < 3
521             and return _error($ldap, $mesg, LDAP_PARAM_ERROR, 'Controls require LDAPv3');
522              
523 0   0     0 my $base = $arg->{base} || '';
524 0         0 my $filter;
525              
526 0 0       0 unless (ref ($filter = $arg->{filter})) {
527 0         0 require Net::LDAP::Filter;
528 0         0 my $f = Net::LDAP::Filter->new;
529 0 0       0 $f->parse($filter)
530             or return _error($ldap, $mesg, LDAP_PARAM_ERROR, 'Bad filter');
531 0         0 $filter = $f;
532             }
533              
534             my %stash = (
535             baseObject => ref($base) ? $base->dn : $base,
536             scope => 2,
537             derefAliases => 2,
538             sizeLimit => $arg->{sizelimit} || 0,
539             timeLimit => $arg->{timelimit} || 0,
540             typesOnly => $arg->{typesonly} || $arg->{attrsonly} || 0,
541             filter => $filter,
542 0 0 0     0 attributes => $arg->{attrs} || []
      0        
      0        
      0        
543             );
544              
545 0 0       0 if (exists $arg->{scope}) {
546 0         0 my $sc = lc $arg->{scope};
547 0 0       0 $stash{scope} = 0 + (exists $scope{$sc} ? $scope{$sc} : $sc);
548             }
549              
550 0 0       0 if (exists $arg->{deref}) {
551 0         0 my $dr = lc $arg->{deref};
552 0 0       0 $stash{derefAliases} = 0 + (exists $deref{$dr} ? $deref{$dr} : $dr);
553             }
554              
555             $mesg->encode(
556 0 0       0 searchRequest => \%stash,
557             controls => $control
558             ) or return _error($ldap, $mesg, LDAP_ENCODING_ERROR, "$@");
559              
560 0         0 $ldap->_sendmesg($mesg);
561             }
562              
563              
564             sub add {
565 0     0 1 0 my $ldap = shift;
566 0         0 my $arg = &_dn_options;
567              
568 0         0 my $mesg = $ldap->message('Net::LDAP::Add' => $arg);
569              
570             my $control = $arg->{control}
571 0 0 0     0 and $ldap->{net_ldap_version} < 3
572             and return _error($ldap, $mesg, LDAP_PARAM_ERROR, 'Controls require LDAPv3');
573              
574             my $entry = $arg->{dn}
575 0 0       0 or return _error($ldap, $mesg, LDAP_PARAM_ERROR, 'No DN specified');
576              
577 0 0       0 unless (ref $entry) {
578 0         0 require Net::LDAP::Entry;
579 0         0 $entry = Net::LDAP::Entry->new;
580 0         0 $entry->dn($arg->{dn});
581 0 0 0     0 $entry->add(@{$arg->{attrs} || $arg->{attr} || []});
  0         0  
582             }
583              
584             $mesg->encode(
585 0 0       0 addRequest => $entry->asn,
586             controls => $control
587             ) or return _error($ldap, $mesg, LDAP_ENCODING_ERROR, "$@");
588              
589 0         0 $ldap->_sendmesg($mesg);
590             }
591              
592              
593             my %opcode = ( add => 0, delete => 1, replace => 2, increment => 3 );
594              
595             sub modify {
596 0     0 1 0 my $ldap = shift;
597 0         0 my $arg = &_dn_options;
598              
599 0         0 my $mesg = $ldap->message('Net::LDAP::Modify' => $arg);
600              
601             my $control = $arg->{control}
602 0 0 0     0 and $ldap->{net_ldap_version} < 3
603             and return _error($ldap, $mesg, LDAP_PARAM_ERROR, 'Controls require LDAPv3');
604              
605             my $dn = $arg->{dn}
606 0 0       0 or return _error($ldap, $mesg, LDAP_PARAM_ERROR, 'No DN specified');
607              
608 0         0 my @ops;
609             my $opcode;
610              
611 0 0       0 if (exists $arg->{changes}) {
612 0         0 my $opcode;
613 0         0 my $j = 0;
614 0         0 while ($j < @{$arg->{changes}}) {
  0         0  
615             return _error($ldap, $mesg, LDAP_PARAM_ERROR, "Bad change type '" . $arg->{changes}[--$j] . "'")
616 0 0       0 unless defined($opcode = $opcode{$arg->{changes}[$j++]});
617              
618 0         0 my $chg = $arg->{changes}[$j++];
619 0 0       0 if (ref($chg)) {
620 0         0 my $i = 0;
621 0         0 while ($i < @$chg) {
622 0 0       0 push @ops, {
623             operation => $opcode,
624             modification => {
625             type => $chg->[$i],
626             vals => ref($chg->[$i+1]) ? $chg->[$i+1] : [$chg->[$i+1]]
627             }
628             };
629 0         0 $i += 2;
630             }
631             }
632             }
633             }
634             else {
635 0         0 foreach my $op (qw(add delete replace increment)) {
636 0 0       0 next unless exists $arg->{$op};
637 0         0 my $opt = $arg->{$op};
638 0         0 my $opcode = $opcode{$op};
639              
640 0 0       0 if (ref($opt) eq 'HASH') {
    0          
641 0         0 while (my ($k, $v) = each %$opt) {
642 0 0       0 push @ops, {
643             operation => $opcode,
644             modification => {
645             type => $k,
646             vals => ref($v) ? $v : [$v]
647             }
648             };
649             }
650             }
651             elsif (ref($opt) eq 'ARRAY') {
652 0         0 my $k = 0;
653              
654 0         0 while ($k < @{$opt}) {
  0         0  
655 0         0 my $attr = ${$opt}[$k++];
  0         0  
656 0 0       0 my $val = $opcode == 1 ? [] : ${$opt}[$k++];
  0         0  
657 0 0       0 push @ops, {
658             operation => $opcode,
659             modification => {
660             type => $attr,
661             vals => ref($val) ? $val : [$val]
662             }
663             };
664             }
665             }
666             else {
667 0         0 push @ops, {
668             operation => $opcode,
669             modification => {
670             type => $opt,
671             vals => []
672             }
673             };
674             }
675             }
676             }
677              
678 0 0       0 $mesg->encode(
    0          
679             modifyRequest => {
680             object => ref($dn) ? $dn->dn : $dn,
681             modification => \@ops
682             },
683             controls => $control
684             ) or return _error($ldap, $mesg, LDAP_ENCODING_ERROR, "$@");
685              
686 0         0 $ldap->_sendmesg($mesg);
687             }
688              
689             sub delete {
690 0     0 1 0 my $ldap = shift;
691 0         0 my $arg = &_dn_options;
692              
693 0         0 my $mesg = $ldap->message('Net::LDAP::Delete' => $arg);
694              
695             my $control = $arg->{control}
696 0 0 0     0 and $ldap->{net_ldap_version} < 3
697             and return _error($ldap, $mesg, LDAP_PARAM_ERROR, 'Controls require LDAPv3');
698              
699             my $dn = $arg->{dn}
700 0 0       0 or return _error($ldap, $mesg, LDAP_PARAM_ERROR, 'No DN specified');
701              
702 0 0       0 $mesg->encode(
    0          
703             delRequest => ref($dn) ? $dn->dn : $dn,
704             controls => $control
705             ) or return _error($ldap, $mesg, LDAP_ENCODING_ERROR, "$@");
706              
707 0         0 $ldap->_sendmesg($mesg);
708             }
709              
710             sub moddn {
711 0     0 1 0 my $ldap = shift;
712 0         0 my $arg = &_dn_options;
713 0   0     0 my $del = $arg->{deleteoldrdn} || $arg->{delete} || 0;
714 0         0 my $newsup = $arg->{newsuperior};
715              
716 0         0 my $mesg = $ldap->message('Net::LDAP::ModDN' => $arg);
717              
718             my $control = $arg->{control}
719 0 0 0     0 and $ldap->{net_ldap_version} < 3
720             and return _error($ldap, $mesg, LDAP_PARAM_ERROR, 'Controls require LDAPv3');
721              
722             my $dn = $arg->{dn}
723 0 0       0 or return _error($ldap, $mesg, LDAP_PARAM_ERROR, 'No DN specified');
724              
725             my $new = $arg->{newrdn} || $arg->{new}
726 0 0 0     0 or return _error($ldap, $mesg, LDAP_PARAM_ERROR, 'No NewRDN specified');
727              
728 0 0       0 $mesg->encode(
    0          
    0          
    0          
729             modDNRequest => {
730             entry => ref($dn) ? $dn->dn : $dn,
731             newrdn => ref($new) ? $new->dn : $new,
732             deleteoldrdn => $del,
733             newSuperior => ref($newsup) ? $newsup->dn : $newsup,
734             },
735             controls => $control
736             ) or return _error($ldap, $mesg, LDAP_ENCODING_ERROR, "$@");
737              
738 0         0 $ldap->_sendmesg($mesg);
739             }
740              
741             # now maps to the V3/X.500(93) modifydn map
742 0     0 0 0 sub modrdn { goto &moddn }
743              
744             sub compare {
745 0     0 1 0 my $ldap = shift;
746 0         0 my $arg = &_dn_options;
747              
748 0         0 my $mesg = $ldap->message('Net::LDAP::Compare' => $arg);
749              
750             my $control = $arg->{control}
751 0 0 0     0 and $ldap->{net_ldap_version} < 3
752             and return _error($ldap, $mesg, LDAP_PARAM_ERROR, 'Controls require LDAPv3');
753              
754             my $dn = $arg->{dn}
755 0 0       0 or return _error($ldap, $mesg, LDAP_PARAM_ERROR, 'No DN specified');
756              
757             my $attr = exists $arg->{attr}
758             ? $arg->{attr}
759             : exists $arg->{attrs} #compat
760 0 0       0 ? $arg->{attrs}[0]
    0          
761             : '';
762              
763             my $value = exists $arg->{value}
764             ? $arg->{value}
765             : exists $arg->{attrs} #compat
766 0 0       0 ? $arg->{attrs}[1]
    0          
767             : '';
768              
769              
770 0 0       0 $mesg->encode(
    0          
771             compareRequest => {
772             entry => ref($dn) ? $dn->dn : $dn,
773             ava => {
774             attributeDesc => $attr,
775             assertionValue => $value
776             }
777             },
778             controls => $control
779             ) or return _error($ldap, $mesg, LDAP_ENCODING_ERROR, "$@");
780              
781 0         0 $ldap->_sendmesg($mesg);
782             }
783              
784             sub abandon {
785 0     0 1 0 my $ldap = shift;
786 0 0       0 unshift @_, 'id' if @_ & 1;
787 0         0 my $arg = &_options;
788              
789 0         0 my $id = $arg->{id};
790              
791 0         0 my $mesg = $ldap->message('Net::LDAP::Abandon' => $arg);
792              
793             my $control = $arg->{control}
794 0 0 0     0 and $ldap->{net_ldap_version} < 3
795             and return _error($ldap, $mesg, LDAP_PARAM_ERROR, 'Controls require LDAPv3');
796              
797 0 0       0 $mesg->encode(
    0          
798             abandonRequest => ref($id) ? $id->mesg_id : $id,
799             controls => $control
800             ) or return _error($ldap, $mesg, LDAP_ENCODING_ERROR, "$@");
801              
802 0         0 $ldap->_sendmesg($mesg);
803             }
804              
805             sub extension {
806 0     0 0 0 my $ldap = shift;
807 0         0 my $arg = &_options;
808              
809 0         0 require Net::LDAP::Extension;
810 0         0 my $mesg = $ldap->message('Net::LDAP::Extension' => $arg);
811              
812             return _error($ldap, $mesg, LDAP_LOCAL_ERROR, 'ExtendedRequest requires LDAPv3')
813 0 0       0 if $ldap->{net_ldap_version} < 3;
814              
815             $mesg->encode(
816             extendedReq => {
817             requestName => $arg->{name},
818             requestValue => $arg->{value}
819             },
820             controls => $arg->{control}
821 0 0       0 ) or return _error($ldap, $mesg, LDAP_ENCODING_ERROR, "$@");
822              
823 0         0 $ldap->_sendmesg($mesg);
824             }
825              
826             sub sync {
827 0     0 1 0 my $ldap = shift;
828 0         0 my $mid = shift;
829 0         0 my $table = $ldap->{net_ldap_mesg};
830 0         0 my $err = LDAP_SUCCESS;
831              
832 0 0       0 return $err unless defined $table;
833              
834 0 0       0 $mid = $mid->mesg_id if ref($mid);
835 0 0       0 while (defined($mid) ? exists $table->{$mid} : %$table) {
836 0 0       0 last if $err = $ldap->process($mid);
837             }
838              
839 0         0 $err;
840             }
841              
842             sub disconnect {
843 0     0 1 0 my $self = shift;
844 0         0 _drop_conn($self, LDAP_USER_CANCELED, 'Explicit disconnect');
845             }
846              
847             sub _sendmesg {
848 2     2   4 my $ldap = shift;
849 2         4 my $mesg = shift;
850              
851 2         3 my $debug;
852 2 50       9 if ($debug = $ldap->debug) {
853 0         0 require Convert::ASN1::Debug;
854 0         0 print STDERR "$ldap sending:\n";
855              
856 0 0       0 Convert::ASN1::asn_hexdump(*STDERR, $mesg->pdu)
857             if $debug & 1;
858              
859 0 0       0 Convert::ASN1::asn_dump(*STDERR, $mesg->pdu)
860             if $debug & 4;
861             }
862              
863 2 50       19 my $socket = $ldap->socket
864             or return _error($ldap, $mesg, LDAP_SERVER_DOWN, "$!");
865              
866             # send packets in sizes that IO::Socket::SSL can chew
867             # originally it was:
868             #syswrite($socket, $mesg->pdu, length($mesg->pdu))
869             # or return _error($ldap, $mesg, LDAP_LOCAL_ERROR, "$!")
870 2         18 my $to_send = \( $mesg->pdu );
871 2         4 my $offset = 0;
872 2         6 while ($offset < length($$to_send)) {
873 2         14 my $s = substr($$to_send, $offset, 15000);
874 2 50       22 my $n = syswrite($socket, $s, length($s))
875             or return _error($ldap, $mesg, LDAP_LOCAL_ERROR, "$!");
876 2         8 $offset += $n;
877             }
878              
879             # for CLDAP, here we need to recode when we were sent
880             # so that we can perform timeouts and resends
881              
882 2         10 my $mid = $mesg->mesg_id;
883 2         9 my $sync = not $ldap->async;
884              
885 2 50       16 unless ($mesg->done) { # may not have a response
886              
887 2         7 $ldap->{net_ldap_mesg}->{$mid} = $mesg;
888              
889 2 50       27 if ($sync) {
890 0         0 my $err = $ldap->sync($mid);
891 0 0       0 return _error($ldap, $mesg, $err, $@) if $err;
892             }
893             }
894              
895             $sync && $ldap->{net_ldap_onerror} && $mesg->is_error
896 2 50 0     15 ? scalar &{$ldap->{net_ldap_onerror}}($mesg)
  0         0  
897             : $mesg;
898             }
899              
900             sub data_ready {
901 0     0 0 0 my $ldap = shift;
902 0 0       0 my $sock = $ldap->socket or return;
903 0         0 my $sel = IO::Select->new($sock);
904              
905 0   0     0 return defined $sel->can_read(0) || (ref($sock) eq 'IO::Socket::SSL' && $sock->pending());
906             }
907              
908             sub process {
909 0     0 1 0 my $ldap = shift;
910 0         0 my $what = shift;
911 0 0       0 my $sock = $ldap->socket or return LDAP_SERVER_DOWN;
912              
913 0         0 for (my $ready = 1; $ready; $ready = $ldap->data_ready) {
914 0         0 my $pdu;
915 0 0       0 asn_read($sock, $pdu)
916             or return _drop_conn($ldap, LDAP_OPERATIONS_ERROR, 'Communications Error');
917              
918 0         0 my $debug;
919 0 0       0 if ($debug = $ldap->debug) {
920 0         0 require Convert::ASN1::Debug;
921 0         0 print STDERR "$ldap received:\n";
922              
923 0 0       0 Convert::ASN1::asn_hexdump(\*STDERR, $pdu)
924             if $debug & 2;
925              
926 0 0       0 Convert::ASN1::asn_dump(\*STDERR, $pdu)
927             if $debug & 8;
928             }
929              
930 0 0       0 my $result = $LDAPResponse->decode($pdu)
931             or return LDAP_DECODING_ERROR;
932              
933 0         0 my $mid = $result->{messageID};
934 0         0 my $mesg = $ldap->{net_ldap_mesg}->{$mid};
935              
936 0 0       0 unless ($mesg) {
937 0 0       0 if (my $ext = $result->{protocolOp}{extendedResp}) {
938 0 0 0     0 if (($ext->{responseName} || '') eq '1.3.6.1.4.1.1466.20036') {
939             # notice of disconnection
940 0         0 return _drop_conn($ldap, LDAP_SERVER_DOWN, 'Notice of Disconnection');
941             }
942             }
943              
944 0 0       0 print STDERR "Unexpected PDU, ignored\n" if $debug & 10;
945 0         0 next;
946             }
947              
948 0 0       0 $mesg->decode($result)
949             or return $mesg->code;
950              
951 0 0 0     0 last if defined $what && $what == $mid;
952             }
953              
954             # FIXME: in CLDAP here we need to check if any message has timed out
955             # and if so do we resend it or what
956              
957 0         0 return LDAP_SUCCESS;
958             }
959              
960             *_recvresp = \&process; # compat
961              
962             sub _drop_conn {
963 2     2   7 my ($self, $err, $etxt) = @_;
964              
965 2         5 delete $self->{net_ldap_rawsocket};
966 2         5 my $sock = delete $self->{net_ldap_socket};
967 2 50       22 close($sock) if $sock;
968              
969 2 50       9 if (my $msgs = delete $self->{net_ldap_mesg}) {
970 2         7 foreach my $mesg (values %$msgs) {
971 2 50       12 next unless (defined $mesg);
972 2         14 $mesg->set_error($err, $etxt);
973             }
974             }
975              
976 2         19 $err;
977             }
978              
979              
980             sub _forgetmesg {
981 0     0   0 my $ldap = shift;
982 0         0 my $mesg = shift;
983              
984 0         0 my $mid = $mesg->mesg_id;
985              
986 0         0 delete $ldap->{net_ldap_mesg}->{$mid};
987             }
988              
989             #Mark Wilcox 3-20-2000
990             #now accepts named parameters
991             #dn => "dn of subschema entry"
992             #
993             #
994             # Clif Harden 2-4-2001.
995             # corrected filter for subschema search.
996             # added attributes to retrieve on subschema search.
997             # added attributes to retrieve on rootDSE search.
998             # changed several double quote character to single quote
999             # character, just to be consistent throughout the schema
1000             # and root_dse functions.
1001             #
1002              
1003             sub schema {
1004 0     0 1 0 require Net::LDAP::Schema;
1005 0         0 my $self = shift;
1006 0         0 my %arg = @_;
1007 0         0 my $base;
1008             my $mesg;
1009              
1010 0 0       0 if (exists $arg{dn}) {
1011 0         0 $base = $arg{dn};
1012             }
1013             else {
1014 0 0       0 my $root = $self->root_dse( attrs => ['subschemaSubentry'] )
1015             or return undef;
1016              
1017 0   0     0 $base = $root->get_value('subschemaSubentry') || 'cn=schema';
1018             }
1019              
1020 0         0 $mesg = $self->search(
1021             base => $base,
1022             scope => 'base',
1023             filter => '(objectClass=subschema)',
1024             attrs => [qw(
1025             objectClasses
1026             attributeTypes
1027             matchingRules
1028             matchingRuleUse
1029             dITStructureRules
1030             dITContentRules
1031             nameForms
1032             ldapSyntaxes
1033             extendedAttributeInfo
1034             )],
1035             );
1036              
1037 0 0       0 $mesg->code
1038             ? undef
1039             : Net::LDAP::Schema->new($mesg->entry);
1040             }
1041              
1042              
1043             sub root_dse {
1044 0     0 1 0 my $ldap = shift;
1045 0         0 my %arg = @_;
1046 0   0     0 my $attrs = $arg{attrs} || [qw(
1047             subschemaSubentry
1048             namingContexts
1049             altServer
1050             supportedExtension
1051             supportedControl
1052             supportedFeatures
1053             supportedSASLMechanisms
1054             supportedLDAPVersion
1055             vendorName
1056             vendorVersion
1057             )];
1058 0   0     0 my $root = $arg{attrs} && $ldap->{net_ldap_root_dse};
1059              
1060 0 0       0 return $root if $root;
1061              
1062 0         0 my $mesg = $ldap->search(
1063             base => '',
1064             scope => 'base',
1065             filter => '(objectClass=*)',
1066             attrs => $attrs,
1067             );
1068              
1069 0         0 require Net::LDAP::RootDSE;
1070 0         0 $root = $mesg->entry;
1071 0 0       0 bless $root, 'Net::LDAP::RootDSE' if $root; # Naughty, but there you go :-)
1072              
1073 0 0       0 $ldap->{net_ldap_root_dse} = $root unless $arg{attrs};
1074              
1075 0         0 return $root;
1076             }
1077              
1078             sub start_tls {
1079 0     0 1 0 my $ldap = shift;
1080 0         0 my $arg = &_options;
1081 0         0 my $sock = $ldap->socket;
1082              
1083 0         0 require IO::Socket::SSL;
1084 0         0 require Net::LDAP::Extension;
1085 0         0 my $mesg = $ldap->message('Net::LDAP::Extension' => $arg);
1086              
1087 0 0       0 return _error($ldap, $mesg, LDAP_OPERATIONS_ERROR, 'TLS already started')
1088             if $sock->isa('IO::Socket::SSL');
1089              
1090 0 0       0 return _error($ldap, $mesg, LDAP_PARAM_ERROR, 'StartTLS requires LDAPv3')
1091             if $ldap->version < 3;
1092              
1093 0         0 $mesg->encode(
1094             extendedReq => {
1095             requestName => LDAP_EXTENSION_START_TLS,
1096             }
1097             );
1098              
1099 0         0 $ldap->_sendmesg($mesg);
1100 0         0 $mesg->sync();
1101              
1102 0 0       0 return $mesg
1103             if $mesg->code;
1104              
1105 0         0 delete $ldap->{net_ldap_root_dse};
1106              
1107 0 0       0 $arg->{sslserver} = $ldap->{net_ldap_host} unless defined $arg->{sslserver};
1108              
1109 0         0 my $sock_class = ref($sock);
1110              
1111 0 0       0 return $mesg
1112             if IO::Socket::SSL->start_SSL($sock, {_SSL_context_init_args($arg)});
1113              
1114 0   0     0 my $err = $@ || $IO::Socket::SSL::SSL_ERROR || $IO::Socket::SSL::SSL_ERROR || ''; # avoid use on once warning
1115              
1116 0 0       0 if ($sock_class ne ref($sock)) {
1117 0         0 $err = $sock->errstr;
1118 0         0 bless $sock, $sock_class;
1119             }
1120              
1121 0         0 _error($ldap, $mesg, LDAP_OPERATIONS_ERROR, $err);
1122             }
1123              
1124             sub cipher {
1125 0     0 1 0 my $ldap = shift;
1126 0 0       0 $ldap->socket->isa('IO::Socket::SSL')
1127             ? $ldap->socket->get_cipher
1128             : undef;
1129             }
1130              
1131             sub certificate {
1132 0     0 1 0 my $ldap = shift;
1133 0 0       0 $ldap->socket->isa('IO::Socket::SSL')
1134             ? $ldap->socket->get_peer_certificate
1135             : undef;
1136             }
1137              
1138             # what version are we talking?
1139             sub version {
1140 2     2 1 4 my $ldap = shift;
1141              
1142             @_
1143             ? ($ldap->{net_ldap_version}, $ldap->{net_ldap_version} = shift)[0]
1144 2 50       29 : $ldap->{net_ldap_version};
1145             }
1146              
1147             sub outer {
1148 3     3 0 4 my $self = shift;
1149 3 50       7 return $self if tied(%$self);
1150 3         6 my %outer;
1151 3         16 tie %outer, ref($self), $self;
1152 3         8 ++$self->{net_ldap_refcnt};
1153 3         13 bless \%outer, ref($self);
1154             }
1155              
1156             sub inner {
1157 3 50   3 0 6 tied(%{$_[0]}) || $_[0];
  3         15  
1158             }
1159              
1160             sub TIEHASH {
1161 3     3   7 $_[1];
1162             }
1163              
1164             sub DESTROY {
1165 5     5   959 my $ldap = shift;
1166 5 100       26 my $inner = tied(%$ldap) or return;
1167             _drop_conn($inner, LDAP_UNAVAILABLE, 'Implicit disconnect')
1168 3 100       21 unless --$inner->{net_ldap_refcnt};
1169             }
1170              
1171             1;
1172