File Coverage

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