File Coverage

blib/lib/Net/LDAP/Schema.pm
Criterion Covered Total %
statement 126 180 70.0
branch 54 96 56.2
condition 10 17 58.8
subroutine 20 33 60.6
pod 24 27 88.8
total 234 353 66.2


line stmt bran cond sub pod time code
1             # Copyright (c) 1998-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::Schema;
6              
7 2     2   67043 use strict;
  2         11  
  2         4290  
8              
9             our $VERSION = '0.9908';
10              
11             #
12             # Get schema from the server (or read from LDIF) and parse it into
13             # data structure
14             #
15             sub new {
16 2     2 0 476 my $self = shift;
17 2   33     13 my $type = ref($self) || $self;
18 2         6 my $schema = bless {}, $type;
19              
20 2 100       10 @_ ? $schema->parse(@_) : $schema;
21             }
22              
23             sub _error {
24 0     0   0 my $self = shift;
25 0         0 $self->{error} = shift;
26 0         0 return;
27             }
28              
29              
30             sub parse {
31 2     2 1 289 my $schema = shift;
32 2         5 my $arg = shift;
33              
34 2 50       8 unless (defined($arg)) {
35 0         0 $schema->_error('Bad argument');
36 0         0 return undef;
37             }
38              
39 2         8 %$schema = ();
40              
41 2         11 my $entry;
42 2 50       41 if ( ref $arg ) {
    50          
43 0 0       0 if (eval { $arg->isa('Net::LDAP::Entry') }) {
  0 0       0  
44 0         0 $entry = $arg;
45             }
46 0         0 elsif (eval { $arg->isa('Net::LDAP::Search') }) {
47 0 0       0 unless ($entry = $arg->entry) {
48 0         0 $schema->_error('Bad Argument');
49 0         0 return undef;
50             }
51             }
52             else {
53 0         0 $schema->_error('Bad Argument');
54 0         0 return undef;
55             }
56             }
57             elsif ( -f $arg ) {
58 2         961 require Net::LDAP::LDIF;
59 2         14 my $ldif = Net::LDAP::LDIF->new( $arg, 'r' );
60 2         10 $entry = $ldif->read();
61 2 50       23 unless ( $entry ) {
62 0         0 $schema->_error("Cannot parse LDIF from file [$arg]");
63 0         0 return undef;
64             }
65             }
66             else {
67 0         0 $schema->_error("Can't load schema from [$arg]: $!");
68 0         0 return undef;
69             }
70              
71 2         7 eval {
72 2     0   23 local $SIG{__DIE__} = sub {};
73 2         12 _parse_schema( $schema, $entry );
74             };
75              
76 2 50       9 if ($@) {
77 0         0 $schema->_error($@);
78 0         0 return undef;
79             }
80              
81 2         29 return $schema;
82             }
83              
84             #
85             # Dump as LDIF
86             #
87             # XXX - We should really dump from the internal structure. That way we can
88             # have methods to modify the schema and write a new one -- GMB
89             sub dump {
90 0     0 1 0 my $self = shift;
91 0 0       0 my $fh = @_ ? shift : \*STDOUT;
92 0 0       0 my $entry = $self->{entry} or return;
93 0         0 require Net::LDAP::LDIF;
94 0         0 Net::LDAP::LDIF->new($fh, 'w', wrap => 0)->write($entry);
95 0         0 1;
96             }
97              
98             #
99             # Given another Net::LDAP::Schema, merge the contents together.
100             # XXX - todo
101             #
102             sub merge {
103 0     0 0 0 my $self = shift;
104 0         0 my $new = shift;
105              
106             # Go through structure of 'new', copying code to $self. Take some
107             # parameters describing what to do in the event of a clash.
108             }
109              
110              
111 2     2 1 1121 sub all_attributes { values %{shift->{at}} }
  2         92  
112 1     1 1 654 sub all_objectclasses { values %{shift->{oc}} }
  1         14  
113 2     2 1 1034 sub all_syntaxes { values %{shift->{syn}} }
  2         17  
114 1     1 1 554 sub all_matchingrules { values %{shift->{mr}} }
  1         11  
115 1     1 1 694 sub all_matchingruleuses { values %{shift->{mru}} }
  1         12  
116 0     0 1 0 sub all_ditstructurerules { values %{shift->{dts}} }
  0         0  
117 0     0 1 0 sub all_ditcontentrules { values %{shift->{dtc}} }
  0         0  
118 0     0 1 0 sub all_nameforms { values %{shift->{nfm}} }
  0         0  
119              
120             sub superclass {
121 1     1 1 280 my $self = shift;
122 1         2 my $oc = shift;
123              
124 1 50       11 my $elem = $self->objectclass( $oc )
125             or return scalar _error($self, 'Not an objectClass');
126              
127 1 50       2 return @{$elem->{sup} || []};
  1         8  
128             }
129              
130 1     1 1 1222 sub must { _must_or_may(@_, 'must') }
131 1     1 1 545 sub may { _must_or_may(@_, 'may') }
132              
133             #
134             # Return must or may attributes for this OC.
135             #
136             sub _must_or_may {
137 2     2   5 my $self = shift;
138 2         4 my $must_or_may = pop;
139 2 50       10 my @oc = @_ or return;
140              
141             #
142             # If called with an entry, get the OC names and continue
143             #
144 2 50       3 if (eval { $oc[0]->isa('Net::LDAP::Entry') }) {
  2         19  
145 0         0 my $entry = $oc[0];
146 0 0       0 @oc = $entry->get_value( 'objectclass' )
147             or return;
148             }
149              
150 2         5 my %res;
151             my %done;
152              
153 2         8 while (@oc) {
154 3         6 my $oc = shift @oc;
155              
156 3 50       13 $done{lc $oc}++ and next;
157              
158 3 100       7 my $elem = $self->objectclass( $oc ) or next;
159 2 50       7 if (my $res = $elem->{$must_or_may}) {
160 2         6 @res{ @$res } = (); # Add in, getting uniqueness
161             }
162 2 100       8 my $sup = $elem->{sup} or next;
163 1         4 push @oc, @$sup;
164             }
165              
166 2         9 my %unique = map { ($_, $_) } $self->attribute(keys %res);
  3         9  
167 2         16 values %unique;
168             }
169              
170             #
171             # Given name or oid, return element or undef if not of appropriate type
172             #
173              
174             sub _get {
175 55     55   362 my $self = shift;
176 55         77 my $type = pop(@_);
177 55         124 my $hash = $self->{$type};
178 55         61 my $oid = $self->{oid};
179              
180             my @elem = grep $_, map {
181 55         89 my $elem = $hash->{lc $_};
  56         111  
182              
183 56 100 66     211 ($elem or ($elem = $oid->{$_} and $elem->{type} eq $type))
184             ? $elem
185             : undef;
186             } @_;
187              
188 55 100       143 wantarray ? @elem : $elem[0];
189             }
190              
191 44     44 1 615 sub attribute { _get(@_, 'at') }
192 4     4 1 11 sub objectclass { _get(@_, 'oc') }
193 0     0 1 0 sub syntax { _get(@_, 'syn') }
194 4     4 1 7 sub matchingrule { _get(@_, 'mr') }
195 3     3 1 501 sub matchingruleuse { _get(@_, 'mru') }
196 0     0 1 0 sub ditstructurerule { _get(@_, 'dts') }
197 0     0 1 0 sub ditcontentrule { _get(@_, 'dtc') }
198 0     0 1 0 sub nameform { _get(@_, 'nfm') }
199              
200              
201             #
202             # XXX - TODO - move long comments to POD and write up interface
203             #
204             # Data structure is:
205             #
206             # $schema (hash ref)
207             #
208             # The {oid} piece here is a little redundant since we control the other
209             # top-level members. We promote the first listed name to be 'canonical' and
210             # also make up a name for syntaxes (from the description). Thus we always
211             # have a unique name. This avoids a lot of checking in the access routines.
212             #
213             # ->{oid}->{$oid}->{
214             # name => $canonical_name, (created for syn)
215             # aliases => list of non. canon names
216             # type => at/oc/syn
217             # desc => description
218             # must => list of can. names of mand. atts [if OC]
219             # may => list of can. names of opt. atts [if OC]
220             # syntax => can. name of syntax [if AT]
221             # ... etc per oid details
222             #
223             # These next items are optimisations, to avoid always searching the OID
224             # lists. Could be removed in theory. Each is a hash ref mapping
225             # lowercase names to the hash stored in the oid structure
226             #
227             # ->{at}
228             # ->{oc}
229             # ->{syn}
230             # ->{mr}
231             # ->{mru}
232             # ->{dts}
233             # ->{dtc}
234             # ->{nfm}
235             #
236              
237             #
238             # These items have no following arguments
239             #
240             my %flags = map { ($_, 1) } qw(
241             single-value
242             obsolete
243             collective
244             no-user-modification
245             abstract
246             structural
247             auxiliary
248             );
249              
250             my %xat_flags = map { ($_, 1) } qw(indexed system-only);
251              
252             #
253             # These items can have lists arguments
254             # (name can too, but we treat it special)
255             #
256             my %listops = map { ($_, 1) } qw(must may sup);
257              
258             #
259             # Map schema attribute names to internal names
260             #
261             my %type2attr = qw(
262             at attributetypes
263             xat extendedAttributeInfo
264             oc objectclasses
265             syn ldapsyntaxes
266             mr matchingrules
267             mru matchingruleuse
268             dts ditstructurerules
269             dtc ditcontentrules
270             nfm nameforms
271             );
272              
273             #
274             # Return ref to hash containing schema data - undef on failure
275             #
276              
277             sub _parse_schema {
278 2     2   4 my $schema = shift;
279 2         3 my $entry = shift;
280              
281 2 50       6 return undef unless defined($entry);
282              
283 2         7 keys %type2attr; # reset iterator
284 2         12 while (my($type, $attr) = each %type2attr) {
285 18         70 my $vals = $entry->get_value($attr, asref => 1);
286              
287 18         25 my %names;
288 18         41 $schema->{$type} = \%names; # Save reference to hash of names => element
289              
290 18 100       51 next unless $vals; # Just leave empty ref if nothing
291              
292 10         22 foreach my $val (@$vals) {
293             #
294             # The following statement takes care of defined attributes
295             # that have no data associated with them.
296             #
297 828 50       1443 next if $val eq '';
298              
299             #
300             # We assume that each value can be turned into an OID, a canonical
301             # name and a 'schema_entry' which is a hash ref containing the items
302             # present in the value.
303             #
304 828         1962 my %schema_entry = ( type => $type, aliases => [] );
305              
306 828         1017 my @tokens;
307 828         1604 pos($val) = 0;
308              
309 828         25102 push @tokens, $+
310             while $val =~ /\G\s*(?:
311             ([()])
312             |
313             ([^"'\s()]+)
314             |
315             "([^"]*)"
316             |
317             '((?:[^']+|'[^\s)])*)'
318             )\s*/xcg;
319 828 50 33     2710 die "Cannot parse [$val] [", substr($val, pos($val)), "]"
320             unless @tokens and pos($val) == length($val);
321              
322             # remove () from start/end
323 828 50       1532 shift @tokens if $tokens[0] eq '(';
324 828 50       1343 pop @tokens if $tokens[-1] eq ')';
325              
326             # The first token is the OID
327 828         1489 my $oid = $schema_entry{oid} = shift @tokens;
328              
329 828 50       1632 my $flags = ($type eq 'xat') ? \%xat_flags : \%flags;
330 828         1352 while (@tokens) {
331 2998         3989 my $tag = lc shift @tokens;
332              
333 2998 100       5086 if (exists $flags->{$tag}) {
    50          
334 350         665 $schema_entry{$tag} = 1;
335             }
336             elsif (@tokens) {
337 2648 100       5010 if (($schema_entry{$tag} = shift @tokens) eq '(') {
338 192         223 my @arr;
339 192         271 $schema_entry{$tag} = \@arr;
340 192         218 while (1) {
341 4790         5477 my $tmp = shift @tokens;
342 4790 100       7053 last if $tmp eq ')';
343 4598 100       6957 push @arr, $tmp unless $tmp eq '$';
344              
345             # Drop of end of list ?
346 4598 50       6676 die "Cannot parse [$val] {$tag}" unless @tokens;
347             }
348             }
349              
350             # Ensure items that can be lists are stored as array refs
351             $schema_entry{$tag} = [ $schema_entry{$tag} ]
352 2648 100 100     6292 if exists $listops{$tag} and !ref $schema_entry{$tag};
353             }
354             else {
355 0         0 die "Cannot parse [$val] {$tag}";
356             }
357             }
358              
359             #
360             # Extract the maximum length of a syntax
361             #
362             $schema_entry{max_length} = $1
363 828 100 100     2445 if exists $schema_entry{syntax} and $schema_entry{syntax} =~ s/{(\d+)}//;
364              
365             #
366             # Force a name if we don't have one
367             #
368             $schema_entry{name} = $schema_entry{oid}
369 828 100       1355 unless exists $schema_entry{name};
370              
371             #
372             # If we have multiple names, make the name be the first and demote the rest to aliases
373             #
374 828 100       1272 if (ref $schema_entry{name}) {
375 44         58 my $aliases;
376 44         48 $schema_entry{name} = shift @{$aliases = $schema_entry{name}};
  44         82  
377 44 50       102 $schema_entry{aliases} = $aliases if @$aliases;
378             }
379              
380             #
381             # Store the elements by OID
382             #
383 828 50       2229 $schema->{oid}->{$oid} = \%schema_entry unless $type eq 'xat';
384              
385             #
386             # We also index elements by name within each type
387             #
388 828         1010 foreach my $name ( @{$schema_entry{aliases}}, $schema_entry{name} ) {
  828         1467  
389 874         1227 my $lc_name = lc $name;
390 874         2566 $names{lc $name} = \%schema_entry;
391             }
392             }
393             }
394              
395             # place extendedAttributeInfo into attribute types
396 2 50       10 if (my $xat = $schema->{xat}) {
397 2         9 foreach my $xat_ref (values %$xat) {
398 0   0     0 my $oid = $schema->{oid}{$xat_ref->{oid}} ||= {};
399 0         0 while (my($k, $v) = each %$xat_ref) {
400 0 0       0 $oid->{"x-$k"} = $v unless $k =~ /^(oid|type|name|aliases)$/;
401             }
402             }
403             }
404              
405 2         8 $schema->{entry} = $entry;
406 2         24 return $schema;
407             }
408              
409              
410              
411              
412             #
413             # Get the syntax of an attribute
414             #
415             sub attribute_syntax {
416 0     0 1 0 my $self = shift;
417 0         0 my $attr = shift;
418 0         0 my $syntax;
419              
420 0         0 while ($attr) {
421 0 0       0 my $elem = $self->attribute( $attr ) or return undef;
422              
423 0 0       0 $syntax = $elem->{syntax} and return $self->syntax($syntax);
424              
425 0 0       0 $attr = ${$elem->{sup} || []}[0];
  0         0  
426             }
427              
428 0         0 return undef;
429             }
430              
431              
432             sub error {
433 1     1 1 6 $_[0]->{error};
434             }
435              
436             #
437             # Return base entry
438             #
439             sub entry {
440 0     0 0 0 $_[0]->{entry};
441             }
442              
443             sub matchingrule_for_attribute {
444 40     40 1 74 my $self = shift;
445 40         49 my $attr = shift;
446 40         46 my $matchtype = shift;
447              
448 40         78 my $attrtype = $self->attribute( $attr );
449 40 100       98 if (exists $attrtype->{$matchtype}) {
    100          
450 30         72 return $attrtype->{$matchtype};
451             } elsif (exists $attrtype->{sup}) {
452             # the assumption is that all superiors result in the same ruleset
453             return $self->matchingrule_for_attribute(
454 8         21 $attrtype->{sup}[0],
455             $matchtype);
456             }
457 2         5 return undef;
458             }
459              
460             1;