shell bypass 403
# Copyright (c) 1998-2004 Graham Barr <gbarr@pobox.com>. All rights reserved.
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.
package Net::LDAP::Schema;
use strict;
our $VERSION = '0.9908';
#
# Get schema from the server (or read from LDIF) and parse it into
# data structure
#
sub new {
my $self = shift;
my $type = ref($self) || $self;
my $schema = bless {}, $type;
@_ ? $schema->parse(@_) : $schema;
}
sub _error {
my $self = shift;
$self->{error} = shift;
return;
}
sub parse {
my $schema = shift;
my $arg = shift;
unless (defined($arg)) {
$schema->_error('Bad argument');
return undef;
}
%$schema = ();
my $entry;
if ( ref $arg ) {
if (eval { $arg->isa('Net::LDAP::Entry') }) {
$entry = $arg;
}
elsif (eval { $arg->isa('Net::LDAP::Search') }) {
unless ($entry = $arg->entry) {
$schema->_error('Bad Argument');
return undef;
}
}
else {
$schema->_error('Bad Argument');
return undef;
}
}
elsif ( -f $arg ) {
require Net::LDAP::LDIF;
my $ldif = Net::LDAP::LDIF->new( $arg, 'r' );
$entry = $ldif->read();
unless ( $entry ) {
$schema->_error("Cannot parse LDIF from file [$arg]");
return undef;
}
}
else {
$schema->_error("Can't load schema from [$arg]: $!");
return undef;
}
eval {
local $SIG{__DIE__} = sub {};
_parse_schema( $schema, $entry );
};
if ($@) {
$schema->_error($@);
return undef;
}
return $schema;
}
#
# Dump as LDIF
#
# XXX - We should really dump from the internal structure. That way we can
# have methods to modify the schema and write a new one -- GMB
sub dump {
my $self = shift;
my $fh = @_ ? shift : \*STDOUT;
my $entry = $self->{entry} or return;
require Net::LDAP::LDIF;
Net::LDAP::LDIF->new($fh, 'w', wrap => 0)->write($entry);
1;
}
#
# Given another Net::LDAP::Schema, merge the contents together.
# XXX - todo
#
sub merge {
my $self = shift;
my $new = shift;
# Go through structure of 'new', copying code to $self. Take some
# parameters describing what to do in the event of a clash.
}
sub all_attributes { values %{shift->{at}} }
sub all_objectclasses { values %{shift->{oc}} }
sub all_syntaxes { values %{shift->{syn}} }
sub all_matchingrules { values %{shift->{mr}} }
sub all_matchingruleuses { values %{shift->{mru}} }
sub all_ditstructurerules { values %{shift->{dts}} }
sub all_ditcontentrules { values %{shift->{dtc}} }
sub all_nameforms { values %{shift->{nfm}} }
sub superclass {
my $self = shift;
my $oc = shift;
my $elem = $self->objectclass( $oc )
or return scalar _error($self, 'Not an objectClass');
return @{$elem->{sup} || []};
}
sub must { _must_or_may(@_, 'must') }
sub may { _must_or_may(@_, 'may') }
#
# Return must or may attributes for this OC.
#
sub _must_or_may {
my $self = shift;
my $must_or_may = pop;
my @oc = @_ or return;
#
# If called with an entry, get the OC names and continue
#
if (eval { $oc[0]->isa('Net::LDAP::Entry') }) {
my $entry = $oc[0];
@oc = $entry->get_value( 'objectclass' )
or return;
}
my %res;
my %done;
while (@oc) {
my $oc = shift @oc;
$done{lc $oc}++ and next;
my $elem = $self->objectclass( $oc ) or next;
if (my $res = $elem->{$must_or_may}) {
@res{ @$res } = (); # Add in, getting uniqueness
}
my $sup = $elem->{sup} or next;
push @oc, @$sup;
}
my %unique = map { ($_, $_) } $self->attribute(keys %res);
values %unique;
}
#
# Given name or oid, return element or undef if not of appropriate type
#
sub _get {
my $self = shift;
my $type = pop(@_);
my $hash = $self->{$type};
my $oid = $self->{oid};
my @elem = grep $_, map {
my $elem = $hash->{lc $_};
($elem or ($elem = $oid->{$_} and $elem->{type} eq $type))
? $elem
: undef;
} @_;
wantarray ? @elem : $elem[0];
}
sub attribute { _get(@_, 'at') }
sub objectclass { _get(@_, 'oc') }
sub syntax { _get(@_, 'syn') }
sub matchingrule { _get(@_, 'mr') }
sub matchingruleuse { _get(@_, 'mru') }
sub ditstructurerule { _get(@_, 'dts') }
sub ditcontentrule { _get(@_, 'dtc') }
sub nameform { _get(@_, 'nfm') }
#
# XXX - TODO - move long comments to POD and write up interface
#
# Data structure is:
#
# $schema (hash ref)
#
# The {oid} piece here is a little redundant since we control the other
# top-level members. We promote the first listed name to be 'canonical' and
# also make up a name for syntaxes (from the description). Thus we always
# have a unique name. This avoids a lot of checking in the access routines.
#
# ->{oid}->{$oid}->{
# name => $canonical_name, (created for syn)
# aliases => list of non. canon names
# type => at/oc/syn
# desc => description
# must => list of can. names of mand. atts [if OC]
# may => list of can. names of opt. atts [if OC]
# syntax => can. name of syntax [if AT]
# ... etc per oid details
#
# These next items are optimisations, to avoid always searching the OID
# lists. Could be removed in theory. Each is a hash ref mapping
# lowercase names to the hash stored in the oid structure
#
# ->{at}
# ->{oc}
# ->{syn}
# ->{mr}
# ->{mru}
# ->{dts}
# ->{dtc}
# ->{nfm}
#
#
# These items have no following arguments
#
my %flags = map { ($_, 1) } qw(
single-value
obsolete
collective
no-user-modification
abstract
structural
auxiliary
);
my %xat_flags = map { ($_, 1) } qw(indexed system-only);
#
# These items can have lists arguments
# (name can too, but we treat it special)
#
my %listops = map { ($_, 1) } qw(must may sup);
#
# Map schema attribute names to internal names
#
my %type2attr = qw(
at attributetypes
xat extendedAttributeInfo
oc objectclasses
syn ldapsyntaxes
mr matchingrules
mru matchingruleuse
dts ditstructurerules
dtc ditcontentrules
nfm nameforms
);
#
# Return ref to hash containing schema data - undef on failure
#
sub _parse_schema {
my $schema = shift;
my $entry = shift;
return undef unless defined($entry);
keys %type2attr; # reset iterator
while (my($type, $attr) = each %type2attr) {
my $vals = $entry->get_value($attr, asref => 1);
my %names;
$schema->{$type} = \%names; # Save reference to hash of names => element
next unless $vals; # Just leave empty ref if nothing
foreach my $val (@$vals) {
#
# The following statement takes care of defined attributes
# that have no data associated with them.
#
next if $val eq '';
#
# We assume that each value can be turned into an OID, a canonical
# name and a 'schema_entry' which is a hash ref containing the items
# present in the value.
#
my %schema_entry = ( type => $type, aliases => [] );
my @tokens;
pos($val) = 0;
push @tokens, $+
while $val =~ /\G\s*(?:
([()])
|
([^"'\s()]+)
|
"([^"]*)"
|
'((?:[^']+|'[^\s)])*)'
)\s*/xcg;
die "Cannot parse [$val] [", substr($val, pos($val)), "]"
unless @tokens and pos($val) == length($val);
# remove () from start/end
shift @tokens if $tokens[0] eq '(';
pop @tokens if $tokens[-1] eq ')';
# The first token is the OID
my $oid = $schema_entry{oid} = shift @tokens;
my $flags = ($type eq 'xat') ? \%xat_flags : \%flags;
while (@tokens) {
my $tag = lc shift @tokens;
if (exists $flags->{$tag}) {
$schema_entry{$tag} = 1;
}
elsif (@tokens) {
if (($schema_entry{$tag} = shift @tokens) eq '(') {
my @arr;
$schema_entry{$tag} = \@arr;
while (1) {
my $tmp = shift @tokens;
last if $tmp eq ')';
push @arr, $tmp unless $tmp eq '$';
# Drop of end of list ?
die "Cannot parse [$val] {$tag}" unless @tokens;
}
}
# Ensure items that can be lists are stored as array refs
$schema_entry{$tag} = [ $schema_entry{$tag} ]
if exists $listops{$tag} and !ref $schema_entry{$tag};
}
else {
die "Cannot parse [$val] {$tag}";
}
}
#
# Extract the maximum length of a syntax
#
$schema_entry{max_length} = $1
if exists $schema_entry{syntax} and $schema_entry{syntax} =~ s/{(\d+)}//;
#
# Force a name if we don't have one
#
$schema_entry{name} = $schema_entry{oid}
unless exists $schema_entry{name};
#
# If we have multiple names, make the name be the first and demote the rest to aliases
#
if (ref $schema_entry{name}) {
my $aliases;
$schema_entry{name} = shift @{$aliases = $schema_entry{name}};
$schema_entry{aliases} = $aliases if @$aliases;
}
#
# Store the elements by OID
#
$schema->{oid}->{$oid} = \%schema_entry unless $type eq 'xat';
#
# We also index elements by name within each type
#
foreach my $name ( @{$schema_entry{aliases}}, $schema_entry{name} ) {
my $lc_name = lc $name;
$names{lc $name} = \%schema_entry;
}
}
}
# place extendedAttributeInfo into attribute types
if (my $xat = $schema->{xat}) {
foreach my $xat_ref (values %$xat) {
my $oid = $schema->{oid}{$xat_ref->{oid}} ||= {};
while (my($k, $v) = each %$xat_ref) {
$oid->{"x-$k"} = $v unless $k =~ /^(oid|type|name|aliases)$/;
}
}
}
$schema->{entry} = $entry;
return $schema;
}
#
# Get the syntax of an attribute
#
sub attribute_syntax {
my $self = shift;
my $attr = shift;
my $syntax;
while ($attr) {
my $elem = $self->attribute( $attr ) or return undef;
$syntax = $elem->{syntax} and return $self->syntax($syntax);
$attr = ${$elem->{sup} || []}[0];
}
return undef;
}
sub error {
$_[0]->{error};
}
#
# Return base entry
#
sub entry {
$_[0]->{entry};
}
sub matchingrule_for_attribute {
my $self = shift;
my $attr = shift;
my $matchtype = shift;
my $attrtype = $self->attribute( $attr );
if (exists $attrtype->{$matchtype}) {
return $attrtype->{$matchtype};
} elsif (exists $attrtype->{sup}) {
# the assumption is that all superiors result in the same ruleset
return $self->matchingrule_for_attribute(
$attrtype->{sup}[0],
$matchtype);
}
return undef;
}
1;