File Coverage

blib/lib/Net/LDAP/Util.pm
Criterion Covered Total %
statement 89 145 61.3
branch 62 118 52.5
condition 13 58 22.4
subroutine 4 10 40.0
pod 10 10 100.0
total 178 341 52.2


line stmt bran cond sub pod time code
1             # Copyright (c) 1999-2004 Graham Barr and
2             # Norbert Klasen All Rights Reserved.
3             # This program is free software; you can redistribute it and/or modify
4             # it under the same terms as Perl itself.
5              
6             package Net::LDAP::Util;
7              
8             =head1 NAME
9              
10             Net::LDAP::Util - Utility functions
11              
12             =head1 SYNOPSIS
13              
14             use Net::LDAP::Util qw(ldap_error_text
15             ldap_error_name
16             ldap_error_desc
17             );
18              
19             $mesg = $ldap->search( .... );
20              
21             die "Error ",ldap_error_name($mesg) if $mesg->code;
22              
23             =head1 DESCRIPTION
24              
25             B is a collection of utility functions for use with
26             the L modules.
27              
28             =head1 FUNCTIONS
29              
30             =over 4
31              
32             =cut
33              
34             require Exporter;
35             require Net::LDAP::Constant;
36             our @ISA = qw(Exporter);
37             our @EXPORT_OK = qw(
38             ldap_error_name
39             ldap_error_text
40             ldap_error_desc
41             canonical_dn
42             ldap_explode_dn
43             escape_filter_value
44             unescape_filter_value
45             escape_dn_value
46             unescape_dn_value
47             ldap_url_parse
48             generalizedTime_to_time
49             time_to_generalizedTime
50             );
51             our %EXPORT_TAGS = (
52             error => [ qw(ldap_error_name ldap_error_text ldap_error_desc) ],
53             filter => [ qw(escape_filter_value unescape_filter_value) ],
54             dn => [ qw(canonical_dn ldap_explode_dn
55             escape_dn_value unescape_dn_value) ],
56             escape => [ qw(escape_filter_value unescape_filter_value
57             escape_dn_value unescape_dn_value) ],
58             url => [ qw(ldap_url_parse) ],
59             time => [ qw(generalizedTime_to_time time_to_generalizedTime) ],
60             );
61              
62             our $VERSION = '0.20';
63              
64             =item ldap_error_name ( ERR )
65              
66             Returns the name corresponding with ERR. ERR can either be an LDAP
67             error number, or a C object containing an error
68             code. If the error is not known the a string in the form C<"LDAP error
69             code %d(0x%02X)"> is returned.
70              
71             =cut
72              
73             # Defined in Constant.pm
74              
75             =item ldap_error_text ( ERR )
76              
77             Returns the text from the POD description for the given error. ERR can
78             either be an LDAP error code, or a C object
79             containing an LDAP error code. If the error code given is unknown then
80             C is returned.
81              
82             =cut
83              
84             # Defined in Constant.pm
85              
86             =item ldap_error_desc ( ERR )
87              
88             Returns a short text description of the error. ERR can either be an
89             LDAP error code or a C object containing an LDAP
90             error code.
91              
92             =cut
93              
94             my @err2desc = (
95             'Success', # 0x00 LDAP_SUCCESS
96             'Operations error', # 0x01 LDAP_OPERATIONS_ERROR
97             'Protocol error', # 0x02 LDAP_PROTOCOL_ERROR
98             'Timelimit exceeded', # 0x03 LDAP_TIMELIMIT_EXCEEDED
99             'Sizelimit exceeded', # 0x04 LDAP_SIZELIMIT_EXCEEDED
100             'Compare false', # 0x05 LDAP_COMPARE_FALSE
101             'Compare true', # 0x06 LDAP_COMPARE_TRUE
102             'Strong authentication not supported', # 0x07 LDAP_STRONG_AUTH_NOT_SUPPORTED
103             'Strong authentication required', # 0x08 LDAP_STRONG_AUTH_REQUIRED
104             'Partial results and referral received', # 0x09 LDAP_PARTIAL_RESULTS
105             'Referral received', # 0x0a LDAP_REFERRAL
106             'Admin limit exceeded', # 0x0b LDAP_ADMIN_LIMIT_EXCEEDED
107             'Critical extension not available', # 0x0c LDAP_UNAVAILABLE_CRITICAL_EXT
108             'Confidentiality required', # 0x0d LDAP_CONFIDENTIALITY_REQUIRED
109             'SASL bind in progress', # 0x0e LDAP_SASL_BIND_IN_PROGRESS
110             undef,
111             'No such attribute', # 0x10 LDAP_NO_SUCH_ATTRIBUTE
112             'Undefined attribute type', # 0x11 LDAP_UNDEFINED_TYPE
113             'Inappropriate matching', # 0x12 LDAP_INAPPROPRIATE_MATCHING
114             'Constraint violation', # 0x13 LDAP_CONSTRAINT_VIOLATION
115             'Type or value exists', # 0x14 LDAP_TYPE_OR_VALUE_EXISTS
116             'Invalid syntax', # 0x15 LDAP_INVALID_SYNTAX
117             undef,
118             undef,
119             undef,
120             undef,
121             undef,
122             undef,
123             undef,
124             undef,
125             undef,
126             undef,
127             'No such object', # 0x20 LDAP_NO_SUCH_OBJECT
128             'Alias problem', # 0x21 LDAP_ALIAS_PROBLEM
129             'Invalid DN syntax', # 0x22 LDAP_INVALID_DN_SYNTAX
130             'Object is a leaf', # 0x23 LDAP_IS_LEAF
131             'Alias dereferencing problem', # 0x24 LDAP_ALIAS_DEREF_PROBLEM
132             undef,
133             undef,
134             undef,
135             undef,
136             undef,
137             undef,
138             undef,
139             undef,
140             undef,
141             undef,
142             'Proxy authorization failure', # 0x2F LDAP_PROXY_AUTHZ_FAILURE
143             'Inappropriate authentication', # 0x30 LDAP_INAPPROPRIATE_AUTH
144             'Invalid credentials', # 0x31 LDAP_INVALID_CREDENTIALS
145             'Insufficient access', # 0x32 LDAP_INSUFFICIENT_ACCESS
146             'DSA is busy', # 0x33 LDAP_BUSY
147             'DSA is unavailable', # 0x34 LDAP_UNAVAILABLE
148             'DSA is unwilling to perform', # 0x35 LDAP_UNWILLING_TO_PERFORM
149             'Loop detected', # 0x36 LDAP_LOOP_DETECT
150             undef,
151             undef,
152             undef,
153             undef,
154             undef,
155             'Sort control missing', # 0x3C LDAP_SORT_CONTROL_MISSING
156             'Index range error', # 0x3D LDAP_INDEX_RANGE_ERROR
157             undef,
158             undef,
159             'Naming violation', # 0x40 LDAP_NAMING_VIOLATION
160             'Object class violation', # 0x41 LDAP_OBJECT_CLASS_VIOLATION
161             'Operation not allowed on non-leaf', # 0x42 LDAP_NOT_ALLOWED_ON_NONLEAF
162             'Operation not allowed on RDN', # 0x43 LDAP_NOT_ALLOWED_ON_RDN
163             'Already exists', # 0x44 LDAP_ALREADY_EXISTS
164             'Cannot modify object class', # 0x45 LDAP_NO_OBJECT_CLASS_MODS
165             'Results too large', # 0x46 LDAP_RESULTS_TOO_LARGE
166             'Affects multiple servers', # 0x47 LDAP_AFFECTS_MULTIPLE_DSAS
167             undef,
168             undef,
169             undef,
170             undef,
171             'VLV error', # 0x4C LDAP_VLV_ERROR
172             undef,
173             undef,
174             undef,
175             'Unknown error', # 0x50 LDAP_OTHER
176             'Can\'t contact LDAP server', # 0x51 LDAP_SERVER_DOWN
177             'Local error', # 0x52 LDAP_LOCAL_ERROR
178             'Encoding error', # 0x53 LDAP_ENCODING_ERROR
179             'Decoding error', # 0x54 LDAP_DECODING_ERROR
180             'Timed out', # 0x55 LDAP_TIMEOUT
181             'Unknown authentication method', # 0x56 LDAP_AUTH_UNKNOWN
182             'Bad search filter', # 0x57 LDAP_FILTER_ERROR
183             'Canceled', # 0x58 LDAP_USER_CANCELED
184             'Bad parameter to an ldap routine', # 0x59 LDAP_PARAM_ERROR
185             'Out of memory', # 0x5a LDAP_NO_MEMORY
186             'Can\'t connect to the LDAP server', # 0x5b LDAP_CONNECT_ERROR
187             'Not supported by this version of the LDAP protocol', # 0x5c LDAP_NOT_SUPPORTED
188             'Requested LDAP control not found', # 0x5d LDAP_CONTROL_NOT_FOUND
189             'No results returned', # 0x5e LDAP_NO_RESULTS_RETURNED
190             'More results to return', # 0x5f LDAP_MORE_RESULTS_TO_RETURN
191             'Client detected loop', # 0x60 LDAP_CLIENT_LOOP
192             'Referral hop limit exceeded', # 0x61 LDAP_REFERRAL_LIMIT_EXCEEDED
193             );
194              
195             sub ldap_error_desc {
196 0 0   0 1 0 my $code = (ref($_[0]) ? $_[0]->code : $_[0]);
197 0 0       0 $err2desc[$code] || sprintf('LDAP error code %d(0x%02X)', $code, $code);
198             }
199              
200              
201              
202              
203              
204             =item canonical_dn ( DN [ , OPTIONS ] )
205              
206             Returns the given B in a canonical form. Returns undef if B is
207             not a valid Distinguished Name. (Note: The empty string "" is a valid DN.)
208             B can either be a string or reference to an array of hashes as returned by
209             ldap_explode_dn, which is useful when constructing a DN.
210              
211             It performs the following operations on the given B:
212              
213             =over 4
214              
215             =item *
216              
217             Removes the leading 'OID.' characters if the type is an OID instead
218             of a name.
219              
220             =item *
221              
222             Escapes all RFC 4514 special characters (",", "+", """, "\", "E",
223             "E", ";", "#", "=", " "), slashes ("/"), and any other character
224             where the ASCII code is E 32 as \hexpair.
225              
226             =item *
227              
228             Converts all leading and trailing spaces in values to be \20.
229              
230             =item *
231              
232             If an RDN contains multiple parts, the parts are re-ordered so that
233             the attribute type names are in alphabetical order.
234              
235             =back
236              
237             B is a list of name/value pairs, valid options are:
238              
239             =over 4
240              
241             =item casefold
242              
243             Controls case folding of attribute type names. Attribute values are not
244             affected by this option. The default is to uppercase. Valid values are:
245              
246             =over 4
247              
248             =item lower
249              
250             Lowercase attribute type names.
251              
252             =item upper
253              
254             Uppercase attribute type names. This is the default.
255              
256             =item none
257              
258             Do not change attribute type names.
259              
260             =back
261              
262             =item mbcescape
263              
264             If TRUE, characters that are encoded as a multi-octet UTF-8 sequence
265             will be escaped as \(hexpair){2,*}.
266              
267             =item reverse
268              
269             If TRUE, the RDN sequence is reversed.
270              
271             =item separator
272              
273             Separator to use between RDNs. Defaults to comma (',').
274              
275             =back
276              
277             =cut
278              
279             sub canonical_dn($%) {
280 73     73 1 22063 my ($dn, %opt) = @_;
281              
282 73 100 66     329 return $dn unless defined $dn and $dn ne '';
283              
284             # create array of hash representation
285             my $rdns = ref($dn) eq 'ARRAY'
286             ? $dn
287 72 50 50     298 : ldap_explode_dn( $dn, casefold => $opt{casefold} || 'upper')
    100          
288             or return undef; #error condition
289              
290             # assign specified or default separator value
291 54   50     170 my $separator = $opt{separator} || ',';
292              
293             # flatten all RDNs into strings
294             my @flatrdns =
295             map {
296 54         113 my $rdn = $_;
  175         248  
297 175         471 my @types = sort keys %$rdn;
298             join('+',
299             map {
300 175         273 my $val = $rdn->{$_};
  191         313  
301              
302 191 100       279 if ( ref($val) ) {
303 4         19 $val = '#' . unpack('H*', $$val);
304             } else {
305             #escape insecure characters and optionally MBCs
306 187 50       278 if ( $opt{mbcescape} ) {
307 0         0 $val =~ s/([\x00-\x1f\/\\",=+<>#;\x7f-\xff])/
308 0         0 sprintf('\\%02x', ord($1))/xeg;
309             } else {
310 187         350 $val =~ s/([\x00-\x1f\/\\",=+<>#;])/
311 28         154 sprintf('\\%02x', ord($1))/xeg;
312             }
313             #escape leading and trailing whitespace
314 187         606 $val =~ s/(^\s+|\s+$)/
315 13         50 '\\20' x length $1/xeg;
316             #compact multiple spaces
317 187         393 $val =~ s/\s+/ /g;
318             }
319              
320             # case fold attribute type and create return value
321 191 50 33     422 if ( !$opt{casefold} || $opt{casefold} eq 'upper' ) {
    0          
322 191         734 (uc $_)."=$val";
323             } elsif ( $opt{casefold} eq 'lower' ) {
324 0         0 (lc $_)."=$val";
325             } else {
326 0         0 "$_=$val";
327             }
328             } @types);
329             } @$rdns;
330              
331             # join RDNs into string, optionally reversing order
332             $opt{reverse}
333 54 50       318 ? join($separator, reverse @flatrdns)
334             : join($separator, @flatrdns);
335             }
336              
337              
338             =item ldap_explode_dn ( DN [ , OPTIONS ] )
339              
340             Explodes the given B into an array of hashes and returns a reference to this
341             array. Returns undef if B is not a valid Distinguished Name.
342              
343             A Distinguished Name is a sequence of Relative Distinguished Names (RDNs), which
344             themselves are sets of Attributes. For each RDN a hash is constructed with the
345             attribute type names as keys and the attribute values as corresponding values.
346             These hashes are then stored in an array in the order in which they appear
347             in the DN.
348              
349             For example, the DN 'OU=Sales+CN=J. Smith,DC=example,DC=net' is exploded to:
350             [
351             {
352             'OU' =E 'Sales',
353             'CN' =E 'J. Smith'
354             },
355             {
356             'DC' =E 'example'
357             },
358             {
359             'DC' =E 'net'
360             }
361             ]
362              
363             (RFC4514 string) DNs might also contain values, which are the bytes of the
364             BER encoding of the X.500 AttributeValue rather than some LDAP string syntax.
365             These values are hex-encoded and prefixed with a #. To distinguish such BER
366             values, ldap_explode_dn uses references to the actual values,
367             e.g. '1.3.6.1.4.1.1466.0=#04024869,DC=example,DC=com' is exploded to:
368             [
369             {
370             '1.3.6.1.4.1.1466.0' =E "\004\002Hi"
371             },
372             {
373             'DC' =E 'example'
374             },
375             {
376             'DC' =E 'com'
377             }
378             ];
379              
380             It also performs the following operations on the given DN:
381              
382             =over 4
383              
384             =item *
385              
386             Unescape "\" followed by ",", "+", """, "\", "E", "E", ";",
387             "#", "=", " ", or a hexpair and strings beginning with "#".
388              
389             =item *
390              
391             Removes the leading 'OID.' characters if the type is an OID instead
392             of a name.
393              
394             =back
395              
396             B is a list of name/value pairs, valid options are:
397              
398             =over 4
399              
400             =item casefold
401              
402             Controls case folding of attribute types names. Attribute values are not
403             affected by this option. The default is to uppercase. Valid values are:
404              
405             =over 4
406              
407             =item lower
408              
409             Lowercase attribute types names.
410              
411             =item upper
412              
413             Uppercase attribute type names. This is the default.
414              
415             =item none
416              
417             Do not change attribute type names.
418              
419             =back
420              
421             =item reverse
422              
423             If TRUE, the RDN sequence is reversed.
424              
425             =back
426              
427             =cut
428              
429             sub ldap_explode_dn($%) {
430 76     76 1 183 my ($dn, %opt) = @_;
431 76 50       148 return undef unless defined $dn;
432 76 50       146 return [] if $dn eq '';
433              
434 76         260 my $pair = qr/\\(?:[\\"+,;<> #=]|[0-9A-F]{2})/i;
435              
436 76         119 my (@dn, %rdn);
437 76         1582 while (
438             $dn =~ /\G(?:
439             \s*
440             ((?i)[A-Z][-A-Z0-9]*|(?:oid\.)?\d+(?:\.\d+)*) # attribute type
441             \s*
442             =
443             [ ]*
444             ( # attribute value
445             (?:(?:[^\x00 "\#+,;<>\\\x80-\xBF]|$pair) # string
446             (?:(?:[^\x00"+,;<>\\]|$pair)*
447             (?:[^\x00 "+,;<>\\]|$pair))?)?
448             |
449             \#(?:[0-9a-fA-F]{2})+ # hex string
450             |
451             "(?:[^\\"]+|$pair)*" # "-quoted string, only for v2
452             )
453             [ ]*
454             (?:([;,+])\s*(?=\S)|$) # separator
455             )\s*/gcx)
456             {
457 217         776 my($type, $val, $sep) = ($1, $2, $3);
458              
459 217         310 $type =~ s/^oid\.//i; #remove leading "oid."
460              
461 217 100 66     728 if ( !$opt{casefold} || $opt{casefold} eq 'upper' ) {
    50          
462 197         336 $type = uc $type;
463             } elsif ( $opt{casefold} eq 'lower' ) {
464 20         35 $type = lc($type);
465             }
466              
467 217 100       396 if ( $val =~ s/^#// ) {
468             # decode hex-encoded BER value
469 4         19 my $tmp = pack('H*', $val);
470 4         8 $val = \$tmp;
471             } else {
472             # remove quotes
473 213         307 $val =~ s/^"(.*)"$/$1/;
474             # unescape characters
475 213         360 $val =~ s/\\([\\ ",=+<>#;]|[0-9a-fA-F]{2})
476 44 100       208 /length($1)==1 ? $1 : chr(hex($1))
477             /xeg;
478             }
479              
480 217         433 $rdn{$type} = $val;
481              
482 217 100 100     810 unless (defined $sep and $sep eq '+') {
483 199 50       1420 if ( $opt{reverse} ) {
484 0         0 unshift @dn, { %rdn };
485             } else {
486 199         571 push @dn, { %rdn };
487             }
488 199         1878 %rdn = ();
489             }
490             }
491              
492 76 100 100     462 length($dn) == (pos($dn)||0)
493             ? \@dn
494             : undef;
495             }
496              
497              
498             =item escape_filter_value ( VALUES )
499              
500             Escapes the given B according to RFC 4515 so that they
501             can be safely used in LDAP filters.
502              
503             Any control characters with an ASCII code E 32 as well as the
504             characters with special meaning in LDAP filters "*", "(", ")",
505             and "\" the backslash are converted into the representation
506             of a backslash followed by two hex digits representing the
507             hexadecimal value of the character.
508              
509             Returns the converted list in list mode and the first element
510             in scalar mode.
511              
512             =cut
513              
514             ## convert a list of values into its LDAP filter encoding ##
515             # Synopsis: @escaped = escape_filter_value(@values)
516             sub escape_filter_value(@)
517             {
518 0     0 1 0 my @values = @_;
519              
520 0         0 map { $_ =~ s/([\x00-\x1F\*\(\)\\])/'\\'.unpack('H2', $1)/oge; } @values;
  0         0  
  0         0  
521              
522 0 0       0 return(wantarray ? @values : $values[0]);
523             }
524              
525              
526             =item unescape_filter_value ( VALUES )
527              
528             Undoes the conversion done by B.
529              
530             Converts any sequences of a backslash followed by two hex digits
531             into the corresponding character.
532              
533             Returns the converted list in list mode and the first element
534             in scalar mode.
535              
536             =cut
537              
538             ## convert a list of values from its LDAP filter encoding ##
539             # Synopsis: @values = unescape_filter_value(@escaped)
540             sub unescape_filter_value(@)
541             {
542 0     0 1 0 my @values = @_;
543              
544 0         0 map { $_ =~ s/\\([0-9a-fA-F]{2})/pack('H2', $1)/oge; } @values;
  0         0  
  0         0  
545              
546 0 0       0 return(wantarray ? @values : $values[0]);
547             }
548              
549              
550             =item escape_dn_value ( VALUES )
551              
552             Escapes the given B according to RFC 4514 so that they
553             can be safely used in LDAP DNs.
554              
555             The characters ",", "+", """, "\", "E", "E", ";", "#", "=" with
556             a special meaning in section 2.4 of RFC 4514 are preceded by a backslash.
557             Control characters with an ASCII code E 32 are represented
558             as \hexpair.
559             Finally all leading and trailing spaces are converted to
560             sequences of \20.
561              
562             Returns the converted list in list mode and the first element
563             in scalar mode.
564              
565             =cut
566              
567             ## convert a list of values into its DN encoding ##
568             # Synopsis: @escaped = escape_dn_value(@values)
569             sub escape_dn_value(@)
570             {
571 0     0 1 0 my @values = @_;
572              
573 0         0 map { $_ =~ s/([\\",=+<>#;])/\\$1/og;
  0         0  
574 0         0 $_ =~ s/([\x00-\x1F])/'\\'.unpack('H2', $1)/oge;
  0         0  
575 0         0 $_ =~ s/(^ +| +$)/'\\20' x length($1)/oge; } @values;
  0         0  
576              
577 0 0       0 return(wantarray ? @values : $values[0]);
578             }
579              
580              
581             =item unescape_dn_value ( VALUES )
582              
583             Undoes the conversion done by B.
584              
585             Any escape sequence starting with a backslash - hexpair or
586             special character - will be transformed back to the
587             corresponding character.
588              
589             Returns the converted list in list mode and the first element
590             in scalar mode.
591              
592             =cut
593              
594             ## convert a list of values from its LDAP filter encoding ##
595             # Synopsis: @values = unescape_dn_value(@escaped)
596             sub unescape_dn_value(@)
597             {
598 0     0 1 0 my @values = @_;
599              
600 0         0 map { $_ =~ s/\\([\\",=+<>#;]|[0-9a-fA-F]{2})
  0         0  
601 0 0       0 /(length($1)==1) ? $1 : pack('H2', $1)
602             /ogex; } @values;
603              
604 0 0       0 return(wantarray ? @values : $values[0]);
605             }
606              
607              
608             =item ldap_url_parse ( LDAP-URL [, OPTIONS ] )
609              
610             Parse an B conforming to RFC 4516 into a hash containing its elements.
611              
612             For easy cooperation with LDAP queries, the hash keys for the elements
613             used in LDAP search operations are named after the parameters to
614             L.
615              
616             In extension to RFC 4516, the socket path for URLs with the scheme C
617             will be stored in the hash key named C.
618              
619             If any element is omitted, the result depends on the setting of the option
620             C.
621              
622             B is a list of key/value pairs with the following keys recognized:
623              
624             =over 4
625              
626             =item defaults
627              
628             A Boolean option that determines whether default values according to RFC 4516
629             shall be returned for missing URL elements.
630              
631             If set to TRUE, default values are returned, with C
632             using the following defaults in extension to RFC 4516.
633              
634             =over 4
635              
636             =item *
637              
638             The default port for C URLs is C<636>.
639              
640             =item *
641              
642             The default path for C URLs is the contents of the environment variable
643             C. If that is not defined or empty, then C is used.
644              
645             This is consistent with the behaviour of L.
646              
647             =item *
648              
649             The default C name for C and C URLs is C.
650              
651             =back
652              
653             When set to FALSE, no default values are used.
654              
655             This leaves all keys in the resulting hash undefined where the corresponding
656             URL element is empty.
657              
658             To distinguish between an empty base DN and an undefined base DN,
659             C uses the slash between the host:port resp. path
660             part of the URL and the base DN part of the URL.
661             With the slash present, the hash key C is set to the empty string,
662             without it, it is left undefined.
663              
664             Leaving away the C option entirely is equivalent to setting it to TRUE.
665              
666             =back
667              
668             Returns the hash in list mode, or the reference to the hash in scalar mode.
669              
670             =cut
671              
672             ## parse an LDAP URL into its various elements
673             # Synopsis: {$elementref,%elements} = ldap_url_parse($url)
674             sub ldap_url_parse($@)
675             {
676 0     0 1 0 my $url = shift;
677 0         0 my %opt = @_;
678              
679 0         0 eval { require URI };
  0         0  
680 0 0       0 return if ($@);
681              
682 0         0 my $uri = URI->new($url);
683 0 0 0     0 return unless ($uri && ref($uri) =~ /^URI::ldap[is]?$/);
684              
685 0 0       0 $opt{defaults} = 1 unless (exists($opt{defaults}));
686              
687 0         0 my %elements = ( scheme => $uri->scheme );
688              
689 0         0 $uri = $uri->canonical; # canonical form
690 0         0 $url = $uri->as_string; # normalize
691              
692 0 0       0 if ($elements{scheme} eq 'ldapi') {
693             $elements{path} = $uri->un_path || $ENV{LDAPI_SOCK} || '/var/run/ldapi'
694 0 0 0     0 if ($opt{defaults} || $uri->un_path);
      0        
695             }
696             else {
697             $elements{host} = $uri->host || 'localhost'
698 0 0 0     0 if ($opt{defaults} || $uri->host);
      0        
699              
700             $elements{port} = $uri->port || ($elements{scheme} eq 'ldaps' ? 636 : 389)
701 0 0 0     0 if ($opt{defaults} || $uri->port);
      0        
702             }
703              
704             $elements{base} = $uri->dn
705 0 0 0     0 if ($opt{defaults} || $uri->dn || $url =~ m{^ldap[is]?://[^/]*/});
      0        
706              
707             $elements{attrs} = [ $uri->attributes ]
708 0 0 0     0 if ($opt{defaults} || $uri->attributes);
709              
710             $elements{scope} = $uri->scope
711 0 0 0     0 if ($opt{defaults} || $uri->_scope);
712              
713             $elements{filter} = $uri->filter
714 0 0 0     0 if ($opt{defaults} || $uri->_filter);
715              
716             $elements{extensions} = [ $uri->extensions ]
717 0 0 0     0 if ($opt{defaults} || $uri->extensions);
718              
719             #return _error($ldap, $mesg, LDAP_LOCAL_ERROR, "unhandled critical URL extension")
720             # if (grep(/^!/, keys(%extns)));
721              
722 0 0       0 return wantarray ? %elements : \%elements;
723             }
724              
725              
726             =item generalizedTime_to_time ( GENERALIZEDTIME )
727              
728             Convert the generalizedTime string B, which is expected
729             to match the template C
730             to a floating point number compatible with UNIX time
731             (i.e. the integral part of the number is a UNIX time).
732              
733             Returns an extended UNIX time or C on error.
734              
735             Times in years smaller than 1000 will lead to C being returned.
736             This restriction is a direct effect of the year value interpretation rules
737             in Time::Local.
738              
739             B this function depends on Perl's implementation of time and Time::Local.
740             See L, L, and
741             L for restrictions in older versions of Perl.
742              
743             =cut
744              
745             sub generalizedTime_to_time($)
746             {
747 32     32 1 18115 my $generalizedTime = shift;
748              
749 32 100       231 if ($generalizedTime =~ /^\s*(\d{4})(\d{2})(\d{2})
750             (\d{2})(?:(\d{2})(\d{2})?)?
751             (?:[.,](\d+))?\s*(Z|[+-]\d{2}(?:\d{2})?)\s*$/x) {
752 24         115 my ($year,$month,$day,$hour,$min,$sec,$dec,$offset) = ($1,$2,$3,$4,$5,$6,$7,$8);
753              
754             # Time::Local's timegm() interpret years strangely
755 24 100       71 if ($year >= 1000) {
756 22 100       54 $dec = defined($dec) ? "0.$dec" : 0;
757              
758             # decimals in case of missing minutes / seconds - see RFC 4517
759 22 100       48 if (!defined($min)) {
760 3         6 $min = 0;
761              
762 3 50       5 if ($dec) {
763 0         0 $min = int(60 * $dec);
764 0         0 $dec = sprintf('%.4f', 60 * $dec - $min);
765             }
766             }
767 22 100       40 if (!defined($sec)) {
768 3         4 $sec = 0;
769              
770 3 50       6 if ($dec) {
771 0         0 $sec = int(60 * $dec);
772 0         0 $dec = sprintf('%.2f', 60 * $dec - $sec);
773             }
774             }
775              
776 22         32 eval { require Time::Local; };
  22         717  
777 22 50       2555 unless ($@) {
778 22         26 my $time;
779              
780 22         31 eval { $time = Time::Local::timegm($sec,$min,$hour,$day,$month-1,$year); };
  22         88  
781 22 100       1615 unless ($@) {
782 14 100       39 if ($offset =~ /^([+-])(\d{2})(\d{2})?$/) {
783 4         14 my ($direction,$hourdelta,$mindelta) = ($1,$2,$3);
784              
785 4 100       10 $mindelta = 0 if (!$mindelta);
786 4 100       19 $time += ($direction eq '-')
787             ? 3600 * $hourdelta + 60 * $mindelta
788             : -3600 * $hourdelta - 60 * $mindelta;
789             }
790              
791             # make decimal part directional
792 14 100       34 if ($dec != 0) {
793 4         9 my $sign = '';
794              
795 4 100       7 if ($time < 0) {
796 2         5 $dec = 1 - $dec;
797 2         2 $time++;
798 2 100       6 $sign = '-' if ($time == 0);
799             }
800 4         34 $dec =~ s/^0\.//;
801 4         13 $time = "${sign}${time}.${dec}";
802             }
803              
804 14         97 return $time;
805             }
806             }
807             }
808             }
809              
810 18         47 return undef;
811             }
812              
813              
814             =item time_to_generalizedTime ( TIME [, OPTIONS ] )
815              
816             Convert the UNIX time B
817              
818             In extension to UNIX times, B
819             the decimal part will be used for the resulting generalizedTime.
820              
821             B is a list of key/value pairs. The following keys are recognized:
822              
823             =over 4
824              
825             =item AD
826              
827             Take care of an ActiveDirectory peculiarity to always require decimals.
828              
829             =back
830              
831             Returns the generalizedTime string, or C on error.
832              
833             Times before BC or after year 9999 result in C
834             as they cannot be represented in the generalizedTime format.
835              
836             B this function depends on Perl's implementation of gmtime.
837             See L, L, and
838             L for restrictions in older versions of Perl.
839              
840             =cut
841              
842             sub time_to_generalizedTime($;@)
843             {
844 8     8 1 5377 my $arg = shift;
845 8         18 my %opt = @_;
846              
847 8 50       55 if ($arg =~ /^(\-?)(\d*)(?:[.,](\d*))?$/) {
848 8         29 my ($sign, $time, $dec) = ($1, $2, $3);
849              
850 8 100       24 $dec = defined($dec) ? "0.$dec" : 0;
851              
852             # decimal part of time is directional: make sure to have it positive
853 8 100       17 if ($sign) {
854 4 100       14 if ($dec != 0) {
855 2         4 $time++;
856 2         4 $dec = 1 - $dec;
857             }
858 4         9 $time = -$time;
859             }
860              
861 8         50 my ($sec,$min,$hour,$mday,$month,$year,$wday,$yday,$isdst) = gmtime(int($time));
862              
863             # generalizedTime requires 4-digit year without sign
864 8 50 33     41 return undef if ($year < -1900 || $year > 8099);
865              
866 8         114 $dec =~ s/^0?\.(\d*?)0*$/$1/;
867              
868             return sprintf("%04d%02d%02d%02d%02d%02d%sZ",
869             $year+1900, $month+1, $mday, $hour, $min, $sec,
870             # AD peculiarity: if there are no decimals, add .0 as decimals
871 8 50       71 ($dec ? ('.'.$dec) : ($opt{AD} ? '.0' : '')));
    100          
872             }
873              
874 0           return undef;
875             }
876              
877              
878             =back
879              
880              
881             =head1 AUTHOR
882              
883             Graham Barr Egbarr@pobox.comE
884              
885             =head1 COPYRIGHT
886              
887             Copyright (c) 1999-2004 Graham Barr. All rights reserved. This program is
888             free software; you can redistribute it and/or modify it under the same
889             terms as Perl itself.
890              
891             ldap_explode_dn and canonical_dn also
892              
893             (c) 2002 Norbert Klasen, norbert.klasen@daasi.de, All Rights Reserved.
894              
895             =cut
896              
897             1;