Added Net::LDAP to the distribution for easier installs.

This commit is contained in:
JT Smith 2002-11-25 07:43:41 +00:00
parent f51b335d74
commit 223c014813
47 changed files with 15060 additions and 2 deletions

View file

@ -49,6 +49,8 @@ Perl.................................Larry Wall / O'Reilly
Rich Edit............................interactivetools.com
Convert::ASN1........................Graham Barr
Data::Config.........................Sébastien Aperghis-Tramoni
HTML::CalendarMonthSimple............Gregor Mosheh
@ -57,6 +59,8 @@ HTML::TagFilter......................William Ross
HTML::Template.......................Sam Tregar
Net::LDAP............................Graham Barr
Tie::CPHash..........................Christopher J. Madsen
Tie::IxHash..........................Gurusamy Sarathy

View file

@ -18,10 +18,8 @@ QnD INSTALL INSTRUCTIONS:
DBI
DBD::mysql
Digest::MD5
Net::LDAP
Date::Calc
Image::Magick
HTML::Parser
3. Install Apache (with or without mod_perl) and set up your config.

47
lib/Bundle/Net/LDAP.pm Normal file
View file

@ -0,0 +1,47 @@
package Bundle::Net::LDAP;
$VERSION = '0.02';
1;
__END__
=head1 NAME
Bundle::Net::LDAP - A bundle for Net::LDAP
=head1 SYNOPSIS
C<perl -MCPAN -e 'install Bundle::Net::LDAP'>
=head1 CONTENTS
Convert::ASN1
Digest::MD5
URI
URI::ldap
IO::Socket::SSL
XML::Parser
Net::LDAP
=head1 DESCRIPTION
This bundle all modules that Net::LDAP depends on.
=head1 AUTHOR
Graham Barr
=for html <hr>
I<$Id$>
=cut

372
lib/Convert/ASN1.pm Normal file
View file

@ -0,0 +1,372 @@
# Copyright (c) 2000-2002 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 Convert::ASN1;
# $Id$
use 5.004;
use strict;
use vars qw($VERSION @ISA @EXPORT_OK %EXPORT_TAGS @opParts @opName $AUTOLOAD);
use Exporter;
BEGIN {
@ISA = qw(Exporter);
$VERSION = '0.15';
%EXPORT_TAGS = (
io => [qw(asn_recv asn_send asn_read asn_write asn_get asn_ready)],
debug => [qw(asn_dump asn_hexdump)],
const => [qw(ASN_BOOLEAN ASN_INTEGER ASN_BIT_STR ASN_OCTET_STR
ASN_NULL ASN_OBJECT_ID ASN_REAL ASN_ENUMERATED
ASN_SEQUENCE ASN_SET ASN_PRINT_STR ASN_IA5_STR
ASN_UTC_TIME ASN_GENERAL_TIME ASN_RELATIVE_OID
ASN_UNIVERSAL ASN_APPLICATION ASN_CONTEXT ASN_PRIVATE
ASN_PRIMITIVE ASN_CONSTRUCTOR ASN_LONG_LEN ASN_EXTENSION_ID ASN_BIT)],
tag => [qw(asn_tag asn_decode_tag2 asn_decode_tag asn_encode_tag asn_decode_length asn_encode_length)]
);
@EXPORT_OK = map { @$_ } values %EXPORT_TAGS;
$EXPORT_TAGS{all} = \@EXPORT_OK;
@opParts = qw(
cTAG cTYPE cVAR cLOOP cOPT cCHILD
);
@opName = qw(
opUNKNOWN opBOOLEAN opINTEGER opBITSTR opSTRING opNULL opOBJID opREAL
opSEQUENCE opSET opUTIME opGTIME opUTF8 opANY opCHOICE opROID
);
foreach my $l (\@opParts, \@opName) {
my $i = 0;
foreach my $name (@$l) {
my $j = $i++;
no strict 'refs';
*{__PACKAGE__ . '::' . $name} = sub () { $j }
}
}
}
sub _internal_syms {
my $pkg = caller;
no strict 'refs';
for my $sub (@opParts,@opName,'dump_op') {
*{$pkg . '::' . $sub} = \&{__PACKAGE__ . '::' . $sub};
}
}
sub ASN_BOOLEAN () { 0x01 }
sub ASN_INTEGER () { 0x02 }
sub ASN_BIT_STR () { 0x03 }
sub ASN_OCTET_STR () { 0x04 }
sub ASN_NULL () { 0x05 }
sub ASN_OBJECT_ID () { 0x06 }
sub ASN_REAL () { 0x09 }
sub ASN_ENUMERATED () { 0x0A }
sub ASN_RELATIVE_OID () { 0x0D }
sub ASN_SEQUENCE () { 0x10 }
sub ASN_SET () { 0x11 }
sub ASN_PRINT_STR () { 0x13 }
sub ASN_IA5_STR () { 0x16 }
sub ASN_UTC_TIME () { 0x17 }
sub ASN_GENERAL_TIME () { 0x18 }
sub ASN_UNIVERSAL () { 0x00 }
sub ASN_APPLICATION () { 0x40 }
sub ASN_CONTEXT () { 0x80 }
sub ASN_PRIVATE () { 0xC0 }
sub ASN_PRIMITIVE () { 0x00 }
sub ASN_CONSTRUCTOR () { 0x20 }
sub ASN_LONG_LEN () { 0x80 }
sub ASN_EXTENSION_ID () { 0x1F }
sub ASN_BIT () { 0x80 }
sub new {
my $pkg = shift;
my $self = bless {}, $pkg;
$self->configure(@_);
$self;
}
sub configure {
my $self = shift;
my %opt = @_;
for my $type (qw(encode decode)) {
if (exists $opt{$type}) {
while(my($what,$value) = each %{$opt{$type}}) {
$self->{options}{"${type}_${what}"} = $value;
}
}
}
}
sub find {
my $self = shift;
my $what = shift;
return unless exists $self->{tree}{$what};
my %new = %$self;
$new{script} = $new{tree}->{$what};
bless \%new, ref($self);
}
sub prepare {
my $self = shift;
my $asn = shift;
$self = $self->new unless ref($self);
my $tree = Convert::ASN1::parser::parse($asn);
unless ($tree) {
$self->{error} = $@;
return;
}
$self->{tree} = _pack_struct($tree);
$self->{script} = (values %$tree)[0];
$self;
}
# In XS the will convert the tree between perl and C structs
sub _pack_struct { $_[0] }
sub _unpack_struct { $_[0] }
##
## Encoding
##
sub encode {
my $self = shift;
my $stash = @_ == 1 ? shift : { @_ };
my $buf = '';
local $SIG{__DIE__};
eval { _encode($self->{options}, $self->{script}, $stash, [], $buf) }
or do { $self->{error} = $@; undef }
}
# Encode tag value for encoding.
# We assume that the tag has been correclty generated with asn_tag()
sub asn_encode_tag {
$_[0] >> 8
? $_[0] & 0x8000
? $_[0] & 0x800000
? pack("V",$_[0])
: substr(pack("V",$_[0]),0,3)
: pack("v", $_[0])
: chr($_[0]);
}
# Encode a length. If < 0x80 then encode as a byte. Otherwise encode
# 0x80 | num_bytes followed by the bytes for the number. top end
# bytes of all zeros are not encoded
sub asn_encode_length {
if($_[0] >> 7) {
my $lenlen = &num_length;
return pack("Ca*", $lenlen | 0x80, substr(pack("N",$_[0]), -$lenlen));
}
return pack("C", $_[0]);
}
##
## Decoding
##
sub decode {
my $self = shift;
my $stash = {};
local $SIG{__DIE__};
eval { _decode($self->{options}, $self->{script}, $stash, 0, length $_[0], undef, [], $_[0]); $stash }
or do {
$self->{'error'} = $@;
undef;
};
}
sub asn_decode_length {
return unless length $_[0];
my $len = ord substr($_[0],0,1);
if($len & 0x80) {
$len &= 0x7f or return (1,-1);
return if $len >= length $_[0];
return (1+$len, unpack("N", "\0" x (4 - $len) . substr($_[0],1,$len)));
}
return (1, $len);
}
sub asn_decode_tag {
return unless length $_[0];
my $tag = ord $_[0];
my $n = 1;
if(($tag & 0x1f) == 0x1f) {
my $b;
do {
return if $n >= length $_[0];
$b = ord substr($_[0],$n,1);
$tag |= $b << (8 * $n++);
} while($b & 0x80);
}
($n, $tag);
}
sub asn_decode_tag2 {
return unless length $_[0];
my $tag = ord $_[0];
my $num = $tag & 0x1f;
my $len = 1;
if($num == 0x1f) {
$num = 0;
my $b;
do {
return if $len >= length $_[0];
$b = ord substr($_[0],$len++,1);
$num = ($num << 7) + ($b & 0x7f);
} while($b & 0x80);
}
($len, $tag, $num);
}
##
## Utilities
##
# How many bytes are needed to encode a number
sub num_length {
$_[0] >> 8
? $_[0] >> 16
? $_[0] >> 24
? 4
: 3
: 2
: 1
}
# Convert from a bigint to an octet string
sub i2osp {
my($num, $biclass) = @_;
eval "use $biclass";
$num = $biclass->new($num);
my $neg = $num < 0
and $num = abs($num+1);
my $base = $biclass->new(256);
my $result = '';
while($num != 0) {
my $r = $num % $base;
$num = ($num-$r) / $base;
$result .= chr($r);
}
$result ^= chr(255) x length($result) if $neg;
return scalar reverse $result;
}
# Convert from an octet string to a bigint
sub os2ip {
my($os, $biclass) = @_;
eval "require $biclass";
my $base = $biclass->new(256);
my $result = $biclass->new(0);
my $neg = ord($os) >= 0x80
and $os ^= chr(255) x length($os);
for (unpack("C*",$os)) {
$result = ($result * $base) + $_;
}
return $neg ? ($result + 1) * -1 : $result;
}
# Given a class and a tag, calculate an integer which when encoded
# will become the tag. This means that the class bits are always
# in the bottom byte, so are the tag bits if tag < 30. Otherwise
# the tag is in the upper 3 bytes. The upper bytes are encoded
# with bit8 representing that there is another byte. This
# means the max tag we can do is 0x1fffff
sub asn_tag {
my($class,$value) = @_;
die sprintf "Bad tag class 0x%x",$class
if $class & ~0xe0;
unless ($value & ~0x1f or $value == 0x1f) {
return (($class & 0xe0) | $value);
}
die sprintf "Tag value 0x%08x too big\n",$value
if $value & 0xffe00000;
$class = ($class | 0x1f) & 0xff;
my @t = ($value & 0x7f);
unshift @t, (0x80 | ($value & 0x7f)) while $value >>= 7;
unpack("V",pack("C4",$class,@t,0,0));
}
BEGIN {
# When we have XS &_encode will be defined by the XS code
# so will all the subs in these required packages
unless (defined &_encode) {
require Convert::ASN1::_decode;
require Convert::ASN1::_encode;
require Convert::ASN1::IO;
}
require Convert::ASN1::parser;
}
sub AUTOLOAD {
require Convert::ASN1::Debug if $AUTOLOAD =~ /dump/;
goto &{$AUTOLOAD} if defined &{$AUTOLOAD};
require Carp;
my $pkg = ref($_[0]) || ($_[0] =~ /^[\w\d]+(?:::[\w\d]+)*$/)[0];
if ($pkg and UNIVERSAL::isa($pkg, __PACKAGE__)) { # guess it was a method call
$AUTOLOAD =~ s/.*:://;
Carp::croak(sprintf q{Can't locate object method "%s" via package "%s"},$AUTOLOAD,$pkg);
}
else {
Carp::croak(sprintf q{Undefined subroutine &%s called}, $AUTOLOAD);
}
}
sub DESTROY {}
sub error { $_[0]->{error} }
1;

443
lib/Convert/ASN1.pod Normal file
View file

@ -0,0 +1,443 @@
=head1 NAME
Convert::ASN1 - ASN.1 Encode/Decode library
=head1 SYNOPSYS
use Convert::ASN1;
$asn = Convert::ASN1->new;
$asn->prepare(q<
[APPLICATION 7] SEQUENCE {
int INTEGER,
str OCTET STRING
}
>);
$pdu = $asn->encode( int => 7, str => "string");
$out = $asn->decode($pdu);
print $out->{int}," ",$out->{str},"\n";
use Convert::ASN1 qw(:io);
$peer = asn_recv($sock,$buffer,0);
$nbytes = asn_read($fh, $buffer);
$nbytes = asn_send($sock, $buffer, $peer);
$nbytes = asn_send($sock, $buffer);
$nbytes = asn_write($fh, $buffer);
$buffer = asn_get($fh);
$yes = asn_ready($fh)
=head1 DESCRIPTION
Convert::ASN1 encodes and decodes ASN.1 data structures using BER/DER
rules.
=head1 METHODS
=head2 new
Contructor, creates a new object.
=head2 error
Returns the last error.
=head2 configure ( OPTIONS )
Configure options to control how Convert::ASN1 will perform various tasks.
Options are passed as name-value pairs.
=over 4
=item encode
Reference to a hash which contains various encode options.
=item decode
Reference to a hash which contains various decode options.
=item encoding
One of 'ber', 'der', 'per'. I<Currently not used>
=back
Encode options
=over 4
=item real
Which encoding to use for real's. One of 'binary', 'nr1', 'nr2', 'nr3'
=item time
This controls how UTCTime and GeneralizedTime elements are encoded. The default
is C<withzone>.
=over 4
=item utctime
The value passed will be encoded without a zone, ie a UTC value.
=item withzone
The value will be encoded with a zone. By default it will be encoded
using the local time offset. The offset may be set using the C<timezone>
configure option.
=item raw
The value passed should already be in the correct format and will be copied
into the PDU as-is.
=back
=item timezone
By default UTCTime and GeneralizedTime will be encoded using the local
time offset from UTC. This will over-ride that. It is an offset from UTC
in seconds. This option can be overriden by passing a reference to a
list of two values as the time value. The list should contain the time
value and the offset from UTC in seconds.
=item bigint
If during encoding an value greater than 32 bits is discovered and
is not already a big integer object, then the value will first be
converted into a big integer object. This option controls the big
integer class into which the objects will be blessed. The default
is to use Math::BigInt
=back
Decode options
=over 4
=item time
This controls how a UTCTime or a GeneralizedTime element will be decoded. The default
is C<utctime>.
=over 4
=item utctime
The value returned will be a time value as returned by the C<time> function.
=item withzone
The value returned will be a reference to an array of two values. The first is the
same as with C<utctime>, the second is the timezone offset, in seconds, that was
used in the encoding.
=item raw
The value returned will be the raw encoding as extracted from the PDU.
=back
=item bigint
If during decoding any big integers are discovered (integers greater
than 32 bits), they will be decoded into big integer objects. This option
controls the big integer class into which the objects will be blessed.
The default is to use Math::BigInt.
=back
=head2 prepare ( ASN )
Compile the given ASN.1 descripton. The syntax used is very close to ASN.1, but has
a few differnces. If the ASN decribes only one macro then encode/decode can be
called on this object. If ASN describes more than one ASN.1 macro then C<find>
must be called.
=head2 find ( MACRO )
Find a macro froma prepared ASN.1 description. Returns an object which can
be used for encode/decode.
=head2 encode ( VARIABLES )
Encode a PDU. Top-level variable are passed as name-value pairs, or as a reference
to a hash containing them. Returns the encoded PDU, or undef on error.
=head2 decode ( PDU )
Decode the PDU, returns a reference to a hash containg the values for the PDU. Returns
undef if there was an error.
=head1 EXPORTS
As well as providing an object interface for encoding/decoding PDUs Convert::ASN1
also provides the follow functions.
=head2 IO Functions
=over 4
=item asn_recv SOCK, BUFFER, FLAGS
Will read a single element from the socket SOCK into BUFFER. FLAGS may
be MSG_PEEK as exported by C<Socket>. Returns the address of the sender,
or undef if there was an error. Some systems do not support the return
of the peer address when the socket is a connected socket, in these
cases the empty string will be returned. This is the same behaviour
as the C<recv> function in perl itself.
It is reccomended that if the socket is of type SOCK_DGRAM then C<recv>
be called directly instead of calling C<asn_recv>.
=item asn_read FH, BUFFER, OFFSET
=item asn_read FH, BUFFER
Will read a single element from the filehandle FH into BUFFER. Returns the
number of bytes read if a complete element was read, -1 if an incomplete
element was read or undef if there was an error. If OFFSET is specified
then it is assumed that BUFFER already contains an incomplete element
and new data will be appended starting at OFFSET.
If FH is a socket the asn_recv is used to read the element, so the same
restiction applies if FH is a socket of type SOCK_DGRAM.
=item asn_send SOCK, BUFFER, FLAGS, TO
=item asn_send SOCK, BUFFER, FLAGS
Identical to calling C<send>, see L<perlfunc>
=item asn_write FH, BUFFER
Identical to calling C<syswrite> with 2 arguments, see L<perlfunc>
=item asn_get FH
C<asn_get> provides buffered IO. Because it needs a buffer FH must be a GLOB
or a reference to a GLOB. C<asn_get> will use two entries in the hash element
of the GLOB to use as it's buffer
asn_buffer - input buffer
asn_need - number of bytes needed for the next element, if known
Returns an element or undef if there was an error.
=item asn_ready FH
C<asn_ready> works with C<asn_get>. It will return true if C<asn_get> has already
read enough data into the buffer to return a complete element.
=back
=head2 Encode/Decode Functions
=over 4
=item asn_tag
=item asn_decode_tag
=item asn_encode_tag
=item asn_decode_length
=item asn_encode_length
=back
=head2 Constants
=over 4
=item ASN_BIT_STR
=item ASN_BOOLEAN
=item ASN_ENUMERATED
=item ASN_GENERAL_TIME
=item ASN_IA5_STR
=item ASN_INTEGER
=item ASN_NULL
=item ASN_OBJECT_ID
=item ASN_OCTET_STR
=item ASN_PRINT_STR
=item ASN_REAL
=item ASN_SEQUENCE
=item ASN_SET
=item ASN_UTC_TIME
=item ASN_APPLICATION
=item ASN_CONTEXT
=item ASN_PRIVATE
=item ASN_UNIVERSAL
=item ASN_PRIMITIVE
=item ASN_CONSTRUCTOR
=item ASN_LONG_LEN
=item ASN_EXTENSION_ID
=item ASN_BIT
=back
=head2 Debug Functions
=over 4
=item asn_dump
=item asn_hexdump
=back
=head1 EXPORT TAGS
=over 4
=item :all
All exported functions
=item :const
ASN_BOOLEAN, ASN_INTEGER, ASN_BIT_STR, ASN_OCTET_STR,
ASN_NULL, ASN_OBJECT_ID, ASN_REAL, ASN_ENUMERATED,
ASN_SEQUENCE, ASN_SET, ASN_PRINT_STR, ASN_IA5_STR,
ASN_UTC_TIME, ASN_GENERAL_TIME,
ASN_UNIVERSAL, ASN_APPLICATION, ASN_CONTEXT, ASN_PRIVATE,
ASN_PRIMITIVE, ASN_CONSTRUCTOR, ASN_LONG_LEN, ASN_EXTENSION_ID, ASN_BIT
=item :debug
asn_dump, asn_dumphex
=item :io
asn_recv, asn_send, asn_read, asn_write, asn_get, asn_ready
=item :tag
asn_tag, asn_decode_tag, asn_encode_tag, asn_decode_length, asn_encode_length
=back
=head1 MAPPING ASN.1 TO PERL
Every element in the ASN.1 definition has a name, in perl a hash is used
with these names as an index and the element value as the hash value.
# ASN.1
int INTEGER,
str OCTET STRING
# Perl
{ int => 5, str => "text" }
In the case of a SEQUENCE, SET or CHOICE then the value in the namespace will
be a hash reference which will be the namespce for the elements with
that element.
# ASN.1
int INTEGER,
seq SEQUENCE {
str OCTET STRING,
bool BOOLEAN
}
# Perl
{ int => 5, seq => { str => "text", bool => 1}}
If the element is a SEQUENCE OF, or SET OF, then the value in the namespace
will be an array reference. The elements in the array will be of
the type expected by the type following the OF. For example
with "SEQUENCE OF STRING" the array would contain strings. With
"SEQUENCE OF SEQUENCE { ... }" the array will contain hash references
which will be used as namespaces
# ASN.1
int INTEGER,
str SEQUENCE OF OCTET STRING
# Perl
{ int => 5, str => [ "text1", "text2"]}
# ASN.1
int INTEGER,
str SEQUENCE OF SEQUENCE {
type OCTET STRING,
value INTEGER
}
# Perl
{ int => 5, str => [
{ type => "abc", value => 4 },
{ type => "def", value => -1 },
]}
=head2 Exceptions
There are some exceptions where Convert::ASN1 does not require an element to be named.
These are SEQUENCE {...}, SET {...} and CHOICE. In each case if the element is not
given a name then the elements inside the {...} will share the same namespace as
the elements outside of the {...}.
=head1 TODO
=over 4
=item *
Indefinite length encoding
=item *
XS implementation.
=item *
More documentation.
=item *
More tests.
=back
=head1 AUTHOR
Graham Barr <gbarr@pobox.xom>
=head1 COPYRIGHT
Copyright (c) 2000-2002 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.
=cut

228
lib/Convert/ASN1/Debug.pm Normal file
View file

@ -0,0 +1,228 @@
# Copyright (c) 2000-2002 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 Convert::ASN1;
# $Id$
##
## just for debug :-)
##
sub _hexdump {
my($fmt,$pos) = @_[1,2]; # Don't copy buffer
$pos ||= 0;
my $offset = 0;
my $cnt = 1 << 4;
my $len = length($_[0]);
my $linefmt = ("%02X " x $cnt) . "%s\n";
print "\n";
while ($offset < $len) {
my $data = substr($_[0],$offset,$cnt);
my @y = unpack("C*",$data);
printf $fmt,$pos if $fmt;
# On the last time through replace '%02X ' with '__ ' for the
# missing values
substr($linefmt, 5*@y,5*($cnt-@y)) = "__ " x ($cnt - @y)
if @y != $cnt;
# Change non-printable chars to '.'
$data =~ s/[\x00-\x1f\x7f-\xff]/./sg;
printf $linefmt, @y,$data;
$offset += $cnt;
$pos += $cnt;
}
}
my %type = (
split(/[\t\n]\s*/,
q(10 SEQUENCE
01 BOOLEAN
0A ENUM
0D RELATIVE-OID
11 SET
02 INTEGER
03 BIT STRING
C0 [PRIVATE %d]
04 STRING
40 [APPLICATION %d]
05 NULL
06 OBJECT ID
80 [CONTEXT %d]
)
)
);
BEGIN { undef &asn_dump }
sub asn_dump {
my $fh = @_>1 ? shift : \*STDERR;
my $ofh = select($fh);
my $pos = 0;
my $indent = "";
my @seqend = ();
my $length = length($_[0]);
my $fmt = $length > 0xffff ? "%08X" : "%04X";
while(1) {
while (@seqend && $pos >= $seqend[0]) {
$indent = substr($indent,2);
warn "Bad sequence length " unless $pos == shift @seqend;
printf "$fmt : %s}\n",$pos,$indent;
}
last unless $pos < $length;
my $start = $pos;
my($tb,$tag,$tnum) = asn_decode_tag2(substr($_[0],$pos,10));
$pos += $tb;
my($lb,$len) = asn_decode_length(substr($_[0],$pos,10));
$pos += $lb;
if($tag == 0 && $len == 0) {
$seqend[0] = $pos;
redo;
}
printf $fmt. " %4d: %s",$start,$len,$indent;
my $label = $type{sprintf("%02X",$tag & ~0x20)}
|| $type{sprintf("%02X",$tag & 0xC0)}
|| "[UNIVERSAL %d]";
printf $label, $tnum;
if ($tag & ASN_CONSTRUCTOR) {
print " {\n";
if($len < 0) {
unshift(@seqend, length $_[0]);
}
else {
unshift(@seqend, $pos + $len);
}
$indent .= " ";
next;
}
my $tmp;
for ($label) { # switch
/^(INTEGER|ENUM)/ && do {
Convert::ASN1::_dec_integer({},[],{},$tmp,$_[0],$pos,$len);
printf " = %d\n",$tmp;
last;
};
/^BOOLEAN/ && do {
Convert::ASN1::_dec_boolean({},[],{},$tmp,$_[0],$pos,$len);
printf " = %s\n",$tmp ? 'TRUE' : 'FALSE';
last;
};
/^(?:(OBJECT ID)|(RELATIVE-OID))/ && do {
my @op; $op[opTYPE] = $1 ? opOBJID : opROID;
Convert::ASN1::_dec_object_id({},\@op,{},$tmp,$_[0],$pos,$len);
printf " = %s\n",$tmp;
last;
};
/^NULL/ && do {
print "\n";
last;
};
/^STRING/ && do {
Convert::ASN1::_dec_string({},[],{},$tmp,$_[0],$pos,$len);
if ($tmp =~ /[\x00-\x1f\x7f-\xff]/s) {
_hexdump($tmp,$fmt . " : ".$indent, $pos);
}
else {
printf " = '%s'\n",$tmp;
}
last;
};
# /^BIT STRING/ && do {
# Convert::BER::BIT_STRING->unpack($ber,\$tmp);
# print " = ",$tmp,"\n";
# last;
# };
# default -- dump hex data
_hexdump(substr($_[0],$pos,$len),$fmt . " : ".$indent, $pos);
}
$pos += $len;
}
select($ofh);
}
BEGIN { undef &asn_hexdump }
sub asn_hexdump {
my $fh = @_>1 ? shift : \*STDERR;
my $ofh = select($fh);
_hexdump($_[0]);
print "\n";
select($ofh);
}
BEGIN { undef &dump }
sub dump {
my $self = shift;
for (@{$self->{script}}) {
dump_op($_,"",{},1);
}
}
BEGIN { undef &dump_all }
sub dump_all {
my $self = shift;
while(my($k,$v) = each %{$self->{tree}}) {
print STDERR "$k:\n";
for (@$v) {
dump_op($_,"",{},1);
}
}
}
BEGIN { undef &dump_op }
sub dump_op {
my($op,$indent,$done,$line) = @_;
$indent ||= "";
printf STDERR "%3d: ",$line;
if ($done->{$op}) {
print STDERR " $indent=",$done->{$op},"\n";
return ++$line;
}
$done->{$op} = $line++;
print STDERR $indent,"[ '",unpack("H*",$op->[cTAG]),"', ";
print STDERR $op->[cTYPE] =~ /\D/ ? $op->[cTYPE] : $opName[$op->[cTYPE]];
print STDERR ", ",defined($op->[cVAR]) ? $op->[cVAR] : "_";
print STDERR ", ",defined($op->[cLOOP]) ? $op->[cLOOP] : "_";
print STDERR ", ",defined($op->[cOPT]) ? $op->[cOPT] : "_";
print STDERR "]";
if ($op->[cCHILD]) {
print STDERR " ",scalar @{$op->[cCHILD]},"\n";
for (@{$op->[cCHILD]}) {
$line = dump_op($_,$indent . " ",$done,$line);
}
}
else {
print STDERR "\n";
}
print STDERR "\n" unless length $indent;
$line;
}
1;

261
lib/Convert/ASN1/IO.pm Normal file
View file

@ -0,0 +1,261 @@
# Copyright (c) 2000-2002 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 Convert::ASN1;
# $Id$
use strict;
use Socket;
BEGIN {
local $SIG{__DIE__};
eval { require bytes } and 'bytes'->import
}
sub asn_recv { # $socket, $buffer, $flags
my $peer;
my $buf;
my $n = 128;
my $pos = 0;
my $depth = 0;
my $len = 0;
my($tmp,$tb,$lb);
MORE:
for(
$peer = recv($_[0],$buf,$n,MSG_PEEK);
defined $peer;
$peer = recv($_[0],$buf,$n<<=1,MSG_PEEK)
) {
if ($depth) { # Are we searching of "\0\0"
unless (2+$pos <= length $buf) {
next MORE if $n == length $buf;
last MORE;
}
if(substr($buf,$pos,2) eq "\0\0") {
unless (--$depth) {
$len = $pos + 2;
last MORE;
}
}
}
# If we can decode a tag and length we can detemine the length
($tb,$tmp) = asn_decode_tag(substr($buf,$pos));
unless ($tb || $pos+$tb < length $buf) {
next MORE if $n == length $buf;
last MORE;
}
if (ord(substr($buf,$pos+$tb,1)) == 0x80) {
# indefinite length, grrr!
$depth++;
$pos += $tb + 1;
redo MORE;
}
($lb,$len) = asn_decode_length(substr($buf,$pos+$tb));
if ($lb) {
if ($depth) {
$pos += $tb + $lb + $len;
redo MORE;
}
else {
$len += $tb + $lb + $pos;
last MORE;
}
}
}
if (defined $peer) {
if ($len > length $buf) {
# Check we can read the whole element
goto error
unless defined($peer = recv($_[0],$buf,$len,MSG_PEEK));
if ($len > length $buf) {
# Cannot get whole element
$_[1]='';
return $peer;
}
}
elsif ($len == 0) {
$_[1] = '';
return $peer;
}
if ($_[2] & MSG_PEEK) {
$_[1] = substr($buf,0,$len);
}
elsif (!defined($peer = recv($_[0],$_[1],$len,0))) {
goto error;
}
return $peer;
}
error:
$_[1] = undef;
}
sub asn_read { # $fh, $buffer, $offset
# We need to read one packet, and exactly only one packet.
# So we have to read the first few bytes one at a time, until
# we have enough to decode a tag and a length. We then know
# how many more bytes to read
my $pos = 0;
my $need = 0;
if ($_[2]) {
if ($_[2] > length $_[1]) {
require Carp;
Carp::carp("Offset beyond end of buffer");
return;
}
substr($_[1],$_[2]) = '';
}
else {
$_[1] = '';
}
my $depth = 0;
my $ch;
my $n;
while(($n = $need - length $_[1]) > 0) {
sysread($_[0],$_[1],$n,length $_[1]) or
goto READ_ERR;
}
while(1) {
$need = $pos + 2;
my $tch = ord(substr($_[1],$pos++,1));
# Tag may be multi-byte
if(($tch & 0x1f) == 0x1f) {
my $ch;
do {
$need++;
while(($n = $need - length $_[1]) > 0) {
sysread($_[0],$_[1],$n,length $_[1]) or
goto READ_ERR;
}
$ch = ord(substr($_[1],$pos++,1));
} while($ch & 0x80);
}
$need = $pos + 1;
while(($n = $need - length $_[1]) > 0) {
sysread($_[0],$_[1],$n,length $_[1]) or
goto READ_ERR;
}
my $len = ord(substr($_[1],$pos++,1));
if($len & 0x80) {
unless ($len &= 0x7f) {
$depth++;
next;
}
$need = $pos + $len;
while(($n = $need - length $_[1]) > 0) {
sysread($_[0],$_[1],$n,length $_[1]) or
goto READ_ERR;
}
$pos += $len + unpack("N", "\0" x (4 - $len) . substr($_[1],$pos,$len));
}
elsif (!$len && !$tch) {
die "Bad ASN PDU" unless $depth;
unless (--$depth) {
last;
}
}
else {
$pos += $len;
}
$need = $pos + 2*$depth;
while(($n = $need - length $_[1]) > 0) {
sysread($_[0],$_[1],$n,length $_[1]) or
goto READ_ERR;
}
last unless $depth;
}
return length $_[1];
READ_ERR:
$@ = "I/O Error $! " . CORE::unpack("H*",$_[1]);
return undef;
}
sub asn_send { # $sock, $buffer, $flags, $to
@_ == 4
? send($_[0],$_[1],$_[2],$_[3])
: send($_[0],$_[1],$_[2]);
}
sub asn_write { # $sock, $buffer
syswrite($_[0],$_[1], length $_[1]);
}
sub asn_get { # $fh
my $fh = ref($_[0]) ? $_[0] : \($_[0]);
my $href = \%{*$fh};
$href->{'asn_buffer'} = '' unless exists $href->{'asn_buffer'};
my $need = delete $href->{'asn_need'} || 0;
while(1) {
next if $need;
my($tb,$tag) = asn_decode_tag($href->{'asn_buffer'}) or next;
my($lb,$len) = asn_decode_length(substr($href->{'asn_buffer'},$tb,8)) or next;
$need = $tb + $lb + $len;
}
continue {
if ($need && $need <= length $href->{'asn_buffer'}) {
my $ret = substr($href->{'asn_buffer'},0,$need);
substr($href->{'asn_buffer'},0,$need) = '';
return $ret;
}
my $get = $need > 1024 ? $need : 1024;
sysread($_[0], $href->{'asn_buffer'}, $get, length $href->{'asn_buffer'})
or return undef;
}
}
sub asn_ready { # $fh
my $fh = ref($_[0]) ? $_[0] : \($_[0]);
my $href = \%{*$fh};
return 0 unless exists $href->{'asn_buffer'};
return $href->{'asn_need'} <= length $href->{'asn_buffer'}
if exists $href->{'asn_need'};
my($tb,$tag) = asn_decode_tag($href->{'asn_buffer'}) or return 0;
my($lb,$len) = asn_decode_length(substr($href->{'asn_buffer'},$tb,8)) or return 0;
$href->{'asn_need'} = $tb + $lb + $len;
$href->{'asn_need'} <= length $href->{'asn_buffer'};
}
1;

613
lib/Convert/ASN1/_decode.pm Normal file
View file

@ -0,0 +1,613 @@
# Copyright (c) 2000-2002 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 Convert::ASN1;
# $Id$
BEGIN {
local $SIG{__DIE__};
eval { require bytes } and 'bytes'->import
}
# These are the subs that do the decode, they are called with
# 0 1 2 3 4
# $optn, $op, $stash, $var, $buf
# The order must be the same as the op definitions above
my @decode = (
sub { die "internal error\n" },
\&_dec_boolean,
\&_dec_integer,
\&_dec_bitstring,
\&_dec_string,
\&_dec_null,
\&_dec_object_id,
\&_dec_real,
\&_dec_sequence,
\&_dec_set,
\&_dec_time,
\&_dec_time,
\&_dec_utf8,
undef, # ANY
undef, # CHOICE
\&_dec_object_id,
);
my @ctr;
@ctr[opBITSTR, opSTRING, opUTF8] = (\&_ctr_bitstring,\&_ctr_string,\&_ctr_string);
sub _decode {
my ($optn, $ops, $stash, $pos, $end, $seqof, $larr) = @_;
my $idx = 0;
# we try not to copy the input buffer at any time
foreach my $buf ($_[-1]) {
OP:
foreach my $op (@{$ops}) {
my $var = $op->[cVAR];
if (length $op->[cTAG]) {
TAGLOOP: {
my($tag,$len,$npos,$indef) = _decode_tl($buf,$pos,$end,$larr)
or do {
next OP if $pos==$end and ($seqof || defined $op->[cOPT]);
die "decode error";
};
if ($tag eq $op->[cTAG]) {
&{$decode[$op->[cTYPE]]}(
$optn,
$op,
$stash,
# We send 1 if there is not var as if there is the decode
# should be getting undef. So if it does not get undef
# it knows it has no variable
($seqof ? $seqof->[$idx++] : defined($var) ? $stash->{$var} : 1),
$buf,$npos,$len, $indef ? $larr : []
);
$pos = $npos+$len+$indef;
redo TAGLOOP if $seqof && $pos < $end;
next OP;
}
if ($tag eq ($op->[cTAG] | chr(ASN_CONSTRUCTOR))
and my $ctr = $ctr[$op->[cTYPE]])
{
_decode(
$optn,
[$op],
undef,
$npos,
$npos+$len,
(\my @ctrlist),
$indef ? $larr : [],
$buf,
);
($seqof ? $seqof->[$idx++] : defined($var) ? $stash->{$var} : undef)
= &{$ctr}(@ctrlist);
$pos = $npos+$len+$indef;
redo TAGLOOP if $seqof && $pos < $end;
next OP;
}
if ($seqof || defined $op->[cOPT]) {
unshift @$larr, $len if $indef;
next OP;
}
die "decode error " . unpack("H*",$tag) ."<=>" . unpack("H*",$op->[cTAG]);
}
}
else { # opTag length is zero, so it must be an ANY or CHOICE
if ($op->[cTYPE] == opANY) {
ANYLOOP: {
my($tag,$len,$npos,$indef) = _decode_tl($buf,$pos,$end,$larr)
or do {
next OP if $pos==$end and ($seqof || defined $op->[cOPT]);
die "decode error";
};
$len += $npos-$pos;
($seqof ? $seqof->[$idx++] : $stash->{$var})
= substr($buf,$pos,$len);
$pos += $len + $indef;
redo ANYLOOP if $seqof && $pos < $end;
}
}
else {
CHOICELOOP: {
my($tag,$len,$npos,$indef) = _decode_tl($buf,$pos,$end,$larr)
or do {
next OP if $pos==$end and ($seqof || defined $op->[cOPT]);
die "decode error";
};
foreach my $cop (@{$op->[cCHILD]}) {
if ($tag eq $cop->[cTAG]) {
my $nstash = $seqof
? ($seqof->[$idx++]={})
: defined($var)
? ($stash->{$var}={}) : $stash;
&{$decode[$cop->[cTYPE]]}(
$optn,
$cop,
$nstash,
$nstash->{$cop->[cVAR]},
$buf,$npos,$len,$indef ? $larr : []
);
$pos = $npos+$len+$indef;
redo CHOICELOOP if $seqof && $pos < $end;
next OP;
}
if ($tag eq ($cop->[cTAG] | chr(ASN_CONSTRUCTOR))
and my $ctr = $ctr[$cop->[cTYPE]])
{
my $nstash = $seqof
? ($seqof->[$idx++]={})
: defined($var)
? ($stash->{$var}={}) : $stash;
_decode(
$optn,
[$cop],
undef,
$npos,
$npos+$len,
(\my @ctrlist),
$indef ? $larr : [],
$buf,
);
$nstash->{$cop->[cVAR]} = &{$ctr}(@ctrlist);
$pos = $npos+$len+$indef;
redo CHOICELOOP if $seqof && $pos < $end;
next OP;
}
}
}
die "decode error" unless $op->[cOPT];
}
}
}
}
die "decode error $pos $end" unless $pos == $end;
}
sub _dec_boolean {
# 0 1 2 3 4 5 6
# $optn, $op, $stash, $var, $buf, $pos, $len
$_[3] = ord(substr($_[4],$_[5],1)) ? 1 : 0;
1;
}
sub _dec_integer {
# 0 1 2 3 4 5 6
# $optn, $op, $stash, $var, $buf, $pos, $len
my $buf = substr($_[4],$_[5],$_[6]);
my $tmp = ord($buf) & 0x80 ? chr(255) : chr(0);
if ($_[6] > 4) {
$_[3] = os2ip($tmp x (4-$_[6]) . $buf, $_[0]->{decode_bigint} || 'Math::BigInt');
} else {
# N unpacks an unsigned value
$_[3] = unpack("l",pack("l",unpack("N", $tmp x (4-$_[6]) . $buf)));
}
1;
}
sub _dec_bitstring {
# 0 1 2 3 4 5 6
# $optn, $op, $stash, $var, $buf, $pos, $len
$_[3] = [ substr($_[4],$_[5]+1,$_[6]-1), ($_[6]-1)*8-ord(substr($_[4],$_[5],1)) ];
1;
}
sub _dec_string {
# 0 1 2 3 4 5 6
# $optn, $op, $stash, $var, $buf, $pos, $len
$_[3] = substr($_[4],$_[5],$_[6]);
1;
}
sub _dec_null {
# 0 1 2 3 4 5 6
# $optn, $op, $stash, $var, $buf, $pos, $len
$_[3] = 1;
1;
}
sub _dec_object_id {
# 0 1 2 3 4 5 6
# $optn, $op, $stash, $var, $buf, $pos, $len
my @data = unpack("w*",substr($_[4],$_[5],$_[6]));
splice(@data,0,1,int($data[0]/40),$data[0] % 40)
if $_[1]->[cTYPE] == opOBJID and $data[0];
$_[3] = join(".", @data);
1;
}
my @_dec_real_base = (2,8,16);
sub _dec_real {
# 0 1 2 3 4 5 6
# $optn, $op, $stash, $var, $buf, $pos, $len
$_[3] = 0.0, return unless $_[6];
my $first = ord(substr($_[4],$_[5],1));
if ($first & 0x80) {
# A real number
require POSIX;
my $exp;
my $expLen = $first & 0x3;
my $estart = $_[5]+1;
if($expLen == 3) {
$estart++;
$expLen = ord(substr($_[4],$_[5]+1,1));
}
else {
$expLen++;
}
_dec_integer(undef, undef, undef, $exp, $_[4],$estart,$expLen);
my $mant = 0.0;
for (reverse unpack("C*",substr($_[4],$estart+$expLen,$_[6]-1-$expLen))) {
$exp +=8, $mant = (($mant+$_) / 256) ;
}
$mant *= 1 << (($first >> 2) & 0x3);
$mant = - $mant if $first & 0x40;
$_[3] = $mant * POSIX::pow($_dec_real_base[($first >> 4) & 0x3], $exp);
return;
}
elsif($first & 0x40) {
$_[3] = POSIX::HUGE_VAL(),return if $first == 0x40;
$_[3] = - POSIX::HUGE_VAL(),return if $first == 0x41;
}
elsif(substr($_[4],$_[5],$_[6]) =~ /^.([-+]?)0*(\d+(?:\.\d+(?:[Ee][-+]?\d+)?)?)$/s) {
$_[3] = eval "$1$2";
return;
}
die "REAL decode error\n";
}
sub _dec_sequence {
# 0 1 2 3 4 5 6 7
# $optn, $op, $stash, $var, $buf, $pos, $len, $larr
if (defined( my $ch = $_[1]->[cCHILD])) {
_decode(
$_[0], #optn
$ch, #ops
(defined($_[3]) || $_[1]->[cLOOP]) ? $_[2] : ($_[3]= {}), #stash
$_[5], #pos
$_[5]+$_[6], #end
$_[1]->[cLOOP] && ($_[3]=[]), #loop
$_[7],
$_[4], #buf
);
}
else {
$_[3] = substr($_[4],$_[5],$_[6]);
}
1;
}
sub _dec_set {
# 0 1 2 3 4 5 6 7
# $optn, $op, $stash, $var, $buf, $pos, $len, $larr
# decode SET OF the same as SEQUENCE OF
my $ch = $_[1]->[cCHILD];
goto &_dec_sequence if $_[1]->[cLOOP] or !defined($ch);
my ($optn, $pos, $larr) = @_[0,5,7];
my $stash = defined($_[3]) ? $_[2] : ($_[3]={});
my $end = $pos + $_[6];
my @done;
while ($pos < $end) {
my($tag,$len,$npos,$indef) = _decode_tl($_[4],$pos,$end,$larr)
or die "decode error";
my ($idx, $any, $done) = (-1);
SET_OP:
foreach my $op (@$ch) {
$idx++;
if (length($op->[cTAG])) {
if ($tag eq $op->[cTAG]) {
my $var = $op->[cVAR];
&{$decode[$op->[cTYPE]]}(
$optn,
$op,
$stash,
# We send 1 if there is not var as if there is the decode
# should be getting undef. So if it does not get undef
# it knows it has no variable
(defined($var) ? $stash->{$var} : 1),
$_[4],$npos,$len,$indef ? $larr : []
);
$done = $idx;
last SET_OP;
}
if ($tag eq ($op->[cTAG] | chr(ASN_CONSTRUCTOR))
and my $ctr = $ctr[$op->[cTYPE]])
{
_decode(
$optn,
[$op],
undef,
$npos,
$npos+$len,
(\my @ctrlist),
$indef ? $larr : [],
$_[4],
);
$stash->{$op->[cVAR]} = &{$ctr}(@ctrlist)
if defined $op->[cVAR];
$done = $idx;
last SET_OP;
}
next SET_OP;
}
elsif ($op->[cTYPE] == opANY) {
$any = $idx;
}
elsif ($op->[cTYPE] == opCHOICE) {
foreach my $cop (@{$op->[cCHILD]}) {
if ($tag eq $cop->[cTAG]) {
my $nstash = defined($var) ? ($stash->{$var}={}) : $stash;
&{$decode[$cop->[cTYPE]]}(
$optn,
$cop,
$nstash,
$nstash->{$cop->[cVAR]},
$_[4],$npos,$len,$indef ? $larr : []
);
$done = $idx;
last SET_OP;
}
if ($tag eq ($cop->[cTAG] | chr(ASN_CONSTRUCTOR))
and my $ctr = $ctr[$cop->[cTYPE]])
{
my $nstash = defined($var) ? ($stash->{$var}={}) : $stash;
_decode(
$optn,
[$cop],
undef,
$npos,
$npos+$len,
(\my @ctrlist),
$indef ? $larr : [],
$_[4],
);
$nstash->{$cop->[cVAR]} = &{$ctr}(@ctrlist);
$done = $idx;
last SET_OP;
}
}
}
else {
die "internal error";
}
}
if (!defined($done) and defined($any)) {
my $var = $ch->[$any][cVAR];
$stash->{$var} = substr($_[4],$pos,$len+$npos-$pos) if defined $var;
$done = $any;
}
die "decode error" if !defined($done) or $done[$done]++;
$pos = $npos + $len + $indef;
}
die "decode error" unless $end == $pos;
foreach my $idx (0..$#{$ch}) {
die "decode error" unless $done[$idx] or $ch->[$idx][cOPT];
}
1;
}
my %_dec_time_opt = ( unixtime => 0, withzone => 1, raw => 2);
sub _dec_time {
# 0 1 2 3 4 5 6
# $optn, $op, $stash, $var, $buf, $pos, $len
my $mode = $_dec_time_opt{$_[0]->{'decode_time'} || ''} || 0;
if ($mode == 2) {
$_[3] = substr($_[4],$_[5],$_[6]);
return;
}
my @bits = (substr($_[4],$_[5],$_[6])
=~ /^((?:\d\d)?\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)((?:\.\d{1,3})?)(([-+])(\d\d)(\d\d)|Z)/)
or die "bad time format";
if ($bits[0] < 100) {
$bits[0] += 100 if $bits[0] < 50;
}
else {
$bits[0] -= 1900;
}
$bits[1] -= 1;
require Time::Local;
my $time = Time::Local::timegm(@bits[5,4,3,2,1,0]);
$time += $bits[6] if length $bits[6];
my $offset = 0;
if ($bits[7] ne 'Z') {
$offset = $bits[9] * 3600 + $bits[10] * 60;
$offset = -$offset if $bits[8] eq '-';
$time -= $offset;
}
$_[3] = $mode ? [$time,$offset] : $time;
}
sub _dec_utf8 {
# 0 1 2 3 4 5 6
# $optn, $op, $stash, $var, $buf, $pos, $len
BEGIN {
local $SIG{__DIE__};
eval { require bytes } and 'bytes'->unimport;
eval { require utf8 } and 'utf8'->import;
}
$_[3] = (substr($_[4],$_[5],$_[6]) =~ /(.*)/s)[0];
1;
}
sub _decode_tl {
my($pos,$end,$larr) = @_[1,2,3];
my $indef = 0;
my $tag = substr($_[0], $pos++, 1);
if((ord($tag) & 0x1f) == 0x1f) {
my $b;
my $n=1;
do {
$tag .= substr($_[0],$pos++,1);
$b = ord substr($tag,-1);
} while($b & 0x80);
}
return if $pos >= $end;
my $len = ord substr($_[0],$pos++,1);
if($len & 0x80) {
$len &= 0x7f;
if ($len) {
return if $pos+$len > $end ;
($len,$pos) = (unpack("N", "\0" x (4 - $len) . substr($_[0],$pos,$len)), $pos+$len);
}
else {
unless (@$larr) {
_scan_indef($_[0],$pos,$end,$larr) or return;
}
$indef = 2;
$len = shift @$larr;
}
}
return if $pos+$len+$indef > $end;
# return the tag, the length of the data, the position of the data
# and the number of extra bytes for indefinate encoding
($tag, $len, $pos, $indef);
}
sub _scan_indef {
my($pos,$end,$larr) = @_[1,2,3];
@$larr = ();
my @depth = ( $pos );
while(@depth) {
return if $pos+2 > $end;
if (substr($_[0],$pos,2) eq "\0\0") {
my $end = $pos;
my $start = shift @depth;
unshift @$larr, $end-$start;
$pos += 2;
next;
}
my $tag = substr($_[0], $pos++, 1);
if((ord($tag) & 0x1f) == 0x1f) {
my $b;
my $n=1;
do {
$tag .= substr($_[0],$pos++,1);
$b = ord substr($tag,-1);
} while($b & 0x80);
}
return if $pos >= $end;
my $len = ord substr($_[0],$pos++,1);
if($len & 0x80) {
if ($len &= 0x7f) {
return if $pos+$len > $end ;
$pos += $len + unpack("N", "\0" x (4 - $len) . substr($_[0],$pos,$len));
}
else {
unshift @depth, $pos;
}
}
else {
$pos += $len;
}
}
1;
}
sub _ctr_string { join '', @_ }
sub _ctr_bitstring {
[ join('', map { $_->[0] } @_), $_[-1]->[1] ]
}
1;

364
lib/Convert/ASN1/_encode.pm Normal file
View file

@ -0,0 +1,364 @@
# Copyright (c) 2000-2002 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 Convert::ASN1;
# $Id$
BEGIN {
local $SIG{__DIE__};
eval { require bytes } and 'bytes'->import
}
# These are the subs which do the encoding, they are called with
# 0 1 2 3 4 5
# $opt, $op, $stash, $var, $buf, $loop
# The order in the array must match the op definitions above
my @encode = (
sub { die "internal error\n" },
\&_enc_boolean,
\&_enc_integer,
\&_enc_bitstring,
\&_enc_string,
\&_enc_null,
\&_enc_object_id,
\&_enc_real,
\&_enc_sequence,
\&_enc_sequence, # SET is the same encoding as sequence
\&_enc_time,
\&_enc_time,
\&_enc_utf8,
\&_enc_any,
\&_enc_choice,
\&_enc_object_id,
);
sub _encode {
my ($optn, $ops, $stash, $path) = @_;
my $var;
foreach my $op (@{$ops}) {
if (defined(my $opt = $op->[cOPT])) {
next unless defined $stash->{$opt};
}
if (defined($var = $op->[cVAR])) {
push @$path, $var;
require Carp, Carp::croak(join(".", @$path)," is undefined") unless defined $stash->{$var};
}
$_[4] .= $op->[cTAG];
&{$encode[$op->[cTYPE]]}(
$optn,
$op,
$stash,
defined($var) ? $stash->{$var} : undef,
$_[4],
$op->[cLOOP],
$path,
);
pop @$path if defined $var;
}
$_[4];
}
sub _enc_boolean {
# 0 1 2 3 4 5 6
# $optn, $op, $stash, $var, $buf, $loop, $path
$_[4] .= pack("CC",1, $_[3] ? 0xff : 0);
}
sub _enc_integer {
# 0 1 2 3 4 5 6
# $optn, $op, $stash, $var, $buf, $loop, $path
if (abs($_[3]) >= 2**31) {
my $os = i2osp($_[3], ref($_[3]) || $_[0]->{encode_bigint} || 'Math::BigInt');
my $len = length $os;
my $msb = (vec($os, 0, 8) & 0x80) ? 0 : 255;
$len++, $os = chr($msb) . $os if $msb xor $_[3] > 0;
$_[4] .= asn_encode_length($len);
$_[4] .= $os;
}
else {
my $val = int($_[3]);
my $neg = ($val < 0);
my $len = num_length($neg ? ~$val : $val);
my $msb = $val & (0x80 << (($len - 1) * 8));
$len++ if $neg ? !$msb : $msb;
$_[4] .= asn_encode_length($len);
$_[4] .= substr(pack("N",$val), -$len);
}
}
sub _enc_bitstring {
# 0 1 2 3 4 5 6
# $optn, $op, $stash, $var, $buf, $loop, $path
if (ref($_[3])) {
my $less = (8 - ($_[3]->[1] & 7)) & 7;
my $len = ($_[3]->[1] + 7)/8;
$_[4] .= asn_encode_length(1+$len);
$_[4] .= chr($less);
$_[4] .= substr($_[3]->[0], 0, $len);
if ($less && $len) {
substr($_[4],-1) &= chr(0xff << $less);
}
}
else {
$_[4] .= asn_encode_length(1+length $_[3]);
$_[4] .= chr(0);
$_[4] .= $_[3];
}
}
sub _enc_string {
# 0 1 2 3 4 5 6
# $optn, $op, $stash, $var, $buf, $loop, $path
$_[4] .= asn_encode_length(length $_[3]);
$_[4] .= $_[3];
}
sub _enc_null {
# 0 1 2 3 4 5 6
# $optn, $op, $stash, $var, $buf, $loop, $path
$_[4] .= chr(0);
}
sub _enc_object_id {
# 0 1 2 3 4 5 6
# $optn, $op, $stash, $var, $buf, $loop, $path
my @data = ($_[3] =~ /(\d+)/g);
if ($_[1]->[cTYPE] == opOBJID) {
if(@data < 2) {
@data = (0);
}
else {
my $first = $data[1] + ($data[0] * 40);
splice(@data,0,2,$first);
}
}
my $l = length $_[4];
$_[4] .= pack("cw*", 0, @data);
substr($_[4],$l,1) = asn_encode_length(length($_[4]) - $l - 1);
}
sub _enc_real {
# 0 1 2 3 4 5 6
# $optn, $op, $stash, $var, $buf, $loop, $path
# Zero
unless ($_[3]) {
$_[4] .= chr(0);
return;
}
require POSIX;
# +oo (well we use HUGE_VAL as Infinity is not avaliable to perl)
if ($_[3] >= POSIX::HUGE_VAL()) {
$_[4] .= pack("C*",0x01,0x40);
return;
}
# -oo (well we use HUGE_VAL as Infinity is not avaliable to perl)
if ($_[3] <= - POSIX::HUGE_VAL()) {
$_[4] .= pack("C*",0x01,0x41);
return;
}
if (exists $_[0]->{'encode_real'} && $_[0]->{'encode_real'} ne 'binary') {
my $tmp = sprintf("%g",$_[3]);
$_[4] .= asn_encode_length(1+length $tmp);
$_[4] .= chr(1); # NR1?
$_[4] .= $tmp;
return;
}
# We have a real number.
my $first = 0x80;
my($mantissa, $exponent) = POSIX::frexp($_[3]);
if ($mantissa < 0.0) {
$mantissa = -$mantissa;
$first |= 0x40;
}
my($eMant,$eExp);
while($mantissa > 0.0) {
($mantissa, my $int) = POSIX::modf($mantissa * (1<<8));
$eMant .= chr($int);
}
$exponent -= 8 * length $eMant;
_enc_integer(undef, undef, undef, $exponent, $eExp);
# $eExp will br prefixed by a length byte
if (5 > length $eExp) {
$eExp =~ s/\A.//s;
$first |= length($eExp)-1;
}
else {
$first |= 0x3;
}
$_[4] .= asn_encode_length(1 + length($eMant) + length($eExp));
$_[4] .= chr($first);
$_[4] .= $eExp;
$_[4] .= $eMant;
}
sub _enc_sequence {
# 0 1 2 3 4 5 6
# $optn, $op, $stash, $var, $buf, $loop, $path
if (my $ops = $_[1]->[cCHILD]) {
my $l = length $_[4];
$_[4] .= "\0\0"; # guess
if (defined $_[5]) {
my $op = $ops->[0]; # there should only be one
my $enc = $encode[$op->[cTYPE]];
my $tag = $op->[cTAG];
my $loop = $op->[cLOOP];
push @{$_[6]}, -1;
foreach my $var (@{$_[3]}) {
$_[6]->[-1]++;
$_[4] .= $tag;
&{$enc}(
$_[0], # $optn
$op, # $op
$_[2], # $stash
$var, # $var
$_[4], # $buf
$loop, # $loop
$_[6], # $path
);
}
pop @{$_[6]};
}
else {
_encode($_[0],$_[1]->[cCHILD], defined($_[3]) ? $_[3] : $_[2], $_[6], $_[4]);
}
substr($_[4],$l,2) = asn_encode_length(length($_[4]) - $l - 2);
}
else {
$_[4] .= asn_encode_length(length $_[3]);
$_[4] .= $_[3];
}
}
my %_enc_time_opt = ( utctime => 1, withzone => 0, raw => 2);
sub _enc_time {
# 0 1 2 3 4 5 6
# $optn, $op, $stash, $var, $buf, $loop, $path
my $mode = $_enc_time_opt{$_[0]->{'encode_time'} || ''} || 0;
if ($mode == 2) {
$_[4] .= asn_encode_length(length $_[3]);
$_[4] .= $_[3];
return;
}
my @time;
my $offset;
my $isgen = $_[1]->[cTYPE] == opGTIME;
if (ref($_[3])) {
$offset = int($_[3]->[1] / 60);
$time = $_[3]->[0] + $_[3]->[1];
}
elsif ($mode == 0) {
if (exists $_[0]->{'encode_timezone'}) {
$offset = int($_[0]->{'encode_timezone'} / 60);
$time = $_[3] + $_[0]->{'encode_timezone'};
}
else {
@time = localtime($_[3]);
my @g = gmtime($_[3]);
$offset = ($time[1] - $g[1]) + ($time[2] - $g[2]) * 60;
$time = $_[3] + $offset*60;
}
}
else {
$time = $_[3];
}
@time = gmtime($time);
$time[4] += 1;
$time[5] = $isgen ? ($time[5] + 1900) : ($time[5] % 100);
my $tmp = sprintf("%02d"x6, @time[5,4,3,2,1,0]);
if ($isgen) {
my $sp = sprintf("%.03f",$time);
$tmp .= substr($sp,-4) unless $sp =~ /\.000$/;
}
$tmp .= $offset ? sprintf("%+03d%02d",$offset / 60, abs($offset % 60)) : 'Z';
$_[4] .= asn_encode_length(length $tmp);
$_[4] .= $tmp;
}
sub _enc_utf8 {
# 0 1 2 3 4 5 6
# $optn, $op, $stash, $var, $buf, $loop, $path
$_[4] .= asn_encode_length(length $_[3]);
$_[4] .= $_[3];
}
sub _enc_any {
# 0 1 2 3 4 5 6
# $optn, $op, $stash, $var, $buf, $loop, $path
$_[4] .= $_[3];
}
sub _enc_choice {
# 0 1 2 3 4 5 6
# $optn, $op, $stash, $var, $buf, $loop, $path
my $stash = defined($_[3]) ? $_[3] : $_[2];
for my $op (@{$_[1]->[cCHILD]}) {
my $var = $op->[cVAR];
if (exists $stash->{$var}) {
push @{$_[6]}, $var;
_encode($_[0],[$op], $stash, $_[6], $_[4]);
pop @{$_[6]};
return;
}
}
require Carp;
Carp::croak("No value found for CHOICE " . join(".", @{$_[6]}));
}
1;

910
lib/Convert/ASN1/parser.pm Normal file
View file

@ -0,0 +1,910 @@
# 1 "y.tab.pl"
#$yysccsid = "@(#)yaccpar 1.8 (Berkeley) 01/20/91 (Perl 2.0 12/31/92)";
# 20 "parser.y"
;# Copyright (c) 2000-2002 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 Convert::ASN1::parser;
;# $Id$
use strict;
use Convert::ASN1 qw(:all);
use vars qw(
$asn $yychar $yyerrflag $yynerrs $yyn @yyss
$yyssp $yystate @yyvs $yyvsp $yylval $yys $yym $yyval
);
BEGIN { Convert::ASN1->_internal_syms }
my $yydebug=0;
my %yystate;
my %base_type = (
BOOLEAN => [ asn_encode_tag(ASN_BOOLEAN), opBOOLEAN ],
INTEGER => [ asn_encode_tag(ASN_INTEGER), opINTEGER ],
BIT_STRING => [ asn_encode_tag(ASN_BIT_STR), opBITSTR ],
OCTET_STRING => [ asn_encode_tag(ASN_OCTET_STR), opSTRING ],
STRING => [ asn_encode_tag(ASN_OCTET_STR), opSTRING ],
NULL => [ asn_encode_tag(ASN_NULL), opNULL ],
OBJECT_IDENTIFIER => [ asn_encode_tag(ASN_OBJECT_ID), opOBJID ],
REAL => [ asn_encode_tag(ASN_REAL), opREAL ],
ENUMERATED => [ asn_encode_tag(ASN_ENUMERATED), opINTEGER ],
ENUM => [ asn_encode_tag(ASN_ENUMERATED), opINTEGER ],
'RELATIVE-OID' => [ asn_encode_tag(ASN_RELATIVE_OID), opROID ],
SEQUENCE => [ asn_encode_tag(ASN_SEQUENCE | ASN_CONSTRUCTOR), opSEQUENCE ],
SET => [ asn_encode_tag(ASN_SET | ASN_CONSTRUCTOR), opSET ],
ObjectDescriptor => [ asn_encode_tag(ASN_UNIVERSAL | 7), opSTRING ],
UTF8String => [ asn_encode_tag(ASN_UNIVERSAL | 12), opUTF8 ],
NumericString => [ asn_encode_tag(ASN_UNIVERSAL | 18), opSTRING ],
PrintableString => [ asn_encode_tag(ASN_UNIVERSAL | 19), opSTRING ],
TeletexString => [ asn_encode_tag(ASN_UNIVERSAL | 20), opSTRING ],
T61String => [ asn_encode_tag(ASN_UNIVERSAL | 20), opSTRING ],
VideotexString => [ asn_encode_tag(ASN_UNIVERSAL | 21), opSTRING ],
IA5String => [ asn_encode_tag(ASN_UNIVERSAL | 22), opSTRING ],
UTCTime => [ asn_encode_tag(ASN_UNIVERSAL | 23), opUTIME ],
GeneralizedTime => [ asn_encode_tag(ASN_UNIVERSAL | 24), opGTIME ],
GraphicString => [ asn_encode_tag(ASN_UNIVERSAL | 25), opSTRING ],
VisibleString => [ asn_encode_tag(ASN_UNIVERSAL | 26), opSTRING ],
ISO646String => [ asn_encode_tag(ASN_UNIVERSAL | 26), opSTRING ],
GeneralString => [ asn_encode_tag(ASN_UNIVERSAL | 27), opSTRING ],
CharacterString => [ asn_encode_tag(ASN_UNIVERSAL | 28), opSTRING ],
UniversalString => [ asn_encode_tag(ASN_UNIVERSAL | 28), opSTRING ],
BMPString => [ asn_encode_tag(ASN_UNIVERSAL | 30), opSTRING ],
CHOICE => [ '', opCHOICE ],
ANY => [ '', opANY ],
);
;# Given an OP, wrap it in a SEQUENCE
sub explicit {
my $op = shift;
my @seq = @$op;
@seq[cTYPE,cCHILD,cVAR,cLOOP] = ('SEQUENCE',[$op],undef,undef);
@{$op}[cTAG,cOPT] = ();
\@seq;
}
# 74 "y.tab.pl"
sub constWORD () { 1 }
sub constCLASS () { 2 }
sub constSEQUENCE () { 3 }
sub constSET () { 4 }
sub constCHOICE () { 5 }
sub constOF () { 6 }
sub constIMPLICIT () { 7 }
sub constEXPLICIT () { 8 }
sub constOPTIONAL () { 9 }
sub constLBRACE () { 10 }
sub constRBRACE () { 11 }
sub constCOMMA () { 12 }
sub constANY () { 13 }
sub constASSIGN () { 14 }
sub constNUMBER () { 15 }
sub constENUM () { 16 }
sub constCOMPONENTS () { 17 }
sub constPOSTRBRACE () { 18 }
sub constYYERRCODE () { 256 }
my @yylhs = ( -1,
0, 0, 2, 2, 3, 3, 6, 6, 6, 6,
8, 13, 13, 12, 14, 14, 14, 9, 9, 9,
10, 17, 17, 17, 17, 17, 11, 15, 15, 18,
18, 18, 19, 1, 1, 20, 20, 20, 22, 22,
22, 22, 21, 21, 21, 23, 23, 4, 4, 5,
5, 5, 16, 16, 24, 7, 7,
);
my @yylen = ( 2,
1, 1, 3, 4, 4, 1, 1, 1, 1, 1,
3, 1, 1, 5, 1, 1, 1, 4, 4, 4,
4, 1, 1, 1, 1, 1, 1, 1, 2, 1,
3, 3, 4, 1, 2, 1, 3, 3, 2, 1,
1, 1, 4, 1, 3, 0, 1, 0, 1, 0,
1, 1, 1, 3, 2, 0, 1,
);
my @yydefred = ( 0,
0, 49, 0, 0, 1, 0, 0, 44, 0, 36,
0, 0, 0, 0, 52, 51, 0, 0, 0, 3,
0, 6, 0, 11, 0, 0, 0, 0, 45, 0,
37, 38, 0, 22, 0, 0, 25, 0, 42, 40,
0, 41, 0, 27, 43, 4, 0, 0, 0, 0,
7, 8, 9, 10, 0, 47, 39, 0, 0, 0,
0, 0, 0, 30, 57, 5, 0, 0, 53, 0,
18, 19, 0, 20, 0, 0, 55, 21, 0, 0,
0, 32, 31, 54, 0, 0, 17, 15, 16, 14,
33,
);
my @yydgoto = ( 4,
5, 6, 20, 7, 17, 50, 66, 8, 51, 52,
53, 54, 43, 90, 62, 68, 44, 63, 64, 9,
10, 45, 57, 69,
);
my @yysindex = ( 53,
5, 0, -1, 0, 0, 12, 96, 0, 30, 0,
7, 96, 14, 4, 0, 0, 41, 70, 70, 0,
96, 0, 92, 0, 7, 17, 20, 43, 0, 33,
0, 0, 92, 0, 17, 20, 0, 82, 0, 0,
64, 0, 93, 0, 0, 0, 70, 70, 75, 91,
0, 0, 0, 0, 110, 0, 0, 33, 106, 117,
33, 131, 62, 0, 0, 0, 128, 95, 0, 96,
0, 0, 96, 0, 75, 75, 0, 0, 110, 97,
92, 0, 0, 0, 17, 20, 0, 0, 0, 0,
0,
);
my @yyrindex = ( 127,
78, 0, 0, 0, 0, 133, 85, 0, 21, 0,
78, 111, 0, 0, 0, 0, 0, 127, 118, 0,
111, 0, 0, 0, 78, 0, 0, 0, 0, 78,
0, 0, 0, 0, 11, 25, 0, 38, 0, 0,
57, 0, 0, 0, 0, 0, 127, 127, 0, 119,
0, 0, 0, 0, 0, 0, 0, 78, 0, 0,
78, 0, 134, 0, 0, 0, 0, 0, 0, 111,
0, 0, 111, 0, 0, 135, 0, 0, 0, 0,
0, 0, 0, 0, 40, 66, 0, 0, 0, 0,
0,
);
my @yygindex = ( 0,
89, 0, 123, 3, -11, 68, 0, -9, -17, -20,
-15, 121, 0, 0, 0, 0, 0, 0, 63, 0,
122, 0, 0, 71,
);
sub constYYTABLESIZE () { 150 }
my @yytable = ( 29,
23, 22, 40, 12, 13, 39, 2, 41, 2, 33,
23, 23, 14, 21, 24, 22, 12, 25, 11, 23,
34, 23, 23, 3, 24, 24, 47, 21, 23, 48,
13, 34, 12, 24, 2, 24, 24, 26, 26, 23,
23, 18, 24, 26, 27, 28, 26, 19, 26, 26,
23, 23, 49, 1, 2, 26, 46, 23, 80, 88,
70, 81, 87, 73, 89, 24, 24, 46, 46, 3,
30, 2, 56, 75, 46, 61, 24, 24, 48, 76,
48, 48, 48, 24, 48, 48, 3, 50, 50, 50,
48, 55, 34, 48, 35, 36, 28, 34, 58, 85,
86, 28, 15, 16, 37, 78, 79, 38, 65, 37,
67, 50, 38, 50, 50, 50, 71, 35, 56, 56,
48, 48, 48, 50, 48, 48, 50, 72, 35, 48,
48, 48, 2, 48, 48, 59, 60, 82, 83, 31,
32, 74, 77, 42, 28, 29, 0, 46, 91, 84,
);
my @yycheck = ( 17,
12, 11, 23, 1, 6, 23, 2, 23, 2, 21,
0, 1, 1, 11, 1, 25, 6, 14, 14, 9,
0, 11, 12, 17, 0, 1, 10, 25, 18, 10,
6, 11, 30, 9, 2, 11, 12, 0, 1, 0,
1, 12, 18, 3, 4, 5, 9, 18, 11, 12,
11, 12, 10, 1, 2, 18, 0, 18, 70, 80,
58, 73, 80, 61, 80, 0, 1, 11, 12, 17,
1, 2, 9, 12, 18, 1, 11, 12, 1, 18,
3, 4, 5, 18, 7, 8, 17, 3, 4, 5,
13, 10, 1, 16, 3, 4, 5, 1, 6, 3,
4, 5, 7, 8, 13, 11, 12, 16, 18, 13,
1, 1, 16, 3, 4, 5, 11, 0, 0, 1,
3, 4, 5, 13, 7, 8, 16, 11, 11, 3,
4, 5, 0, 7, 8, 47, 48, 75, 76, 18,
19, 11, 15, 23, 11, 11, -1, 25, 81, 79,
);
sub constYYFINAL () { 4 }
sub constYYMAXTOKEN () { 18 }
# 262 "y.tab.pl"
sub yyclearin { $yychar = -1; }
sub yyerrok { $yyerrflag = 0; }
sub YYERROR { ++$yynerrs; &yy_err_recover; }
sub yy_err_recover
{
if ($yyerrflag < 3)
{
$yyerrflag = 3;
while (1)
{
if (($yyn = $yysindex[$yyss[$yyssp]]) &&
($yyn += constYYERRCODE()) >= 0 &&
$yycheck[$yyn] == constYYERRCODE())
{
$yyss[++$yyssp] = $yystate = $yytable[$yyn];
$yyvs[++$yyvsp] = $yylval;
next yyloop;
}
else
{
return(1) if $yyssp <= 0;
--$yyssp;
--$yyvsp;
}
}
}
else
{
return (1) if $yychar == 0;
# 313 "y.tab.pl"
$yychar = -1;
next yyloop;
}
0;
} # yy_err_recover
sub yyparse
{
if ($yys = $ENV{'YYDEBUG'})
{
$yydebug = int($1) if $yys =~ /^(\d)/;
}
$yynerrs = 0;
$yyerrflag = 0;
$yychar = (-1);
$yyssp = 0;
$yyvsp = 0;
$yyss[$yyssp] = $yystate = 0;
yyloop: while(1)
{
yyreduce: {
last yyreduce if ($yyn = $yydefred[$yystate]);
if ($yychar < 0)
{
if (($yychar = &yylex) < 0) { $yychar = 0; }
# 352 "y.tab.pl"
}
if (($yyn = $yysindex[$yystate]) && ($yyn += $yychar) >= 0 &&
$yycheck[$yyn] == $yychar)
{
$yyss[++$yyssp] = $yystate = $yytable[$yyn];
$yyvs[++$yyvsp] = $yylval;
$yychar = (-1);
--$yyerrflag if $yyerrflag > 0;
next yyloop;
}
if (($yyn = $yyrindex[$yystate]) && ($yyn += $yychar) >= 0 &&
$yycheck[$yyn] == $yychar)
{
$yyn = $yytable[$yyn];
last yyreduce;
}
if (! $yyerrflag) {
&yyerror('syntax error');
++$yynerrs;
}
return undef if &yy_err_recover;
} # yyreduce
$yym = $yylen[$yyn];
$yyval = $yyvs[$yyvsp+1-$yym];
switch:
{
my $label = "State$yyn";
goto $label if exists $yystate{$label};
last switch;
State1: {
# 94 "parser.y"
{ $yyval = { '' => $yyvs[$yyvsp-0] };
last switch;
} }
State3: {
# 99 "parser.y"
{
$yyval = { $yyvs[$yyvsp-2], [$yyvs[$yyvsp-0]] };
last switch;
} }
State4: {
# 103 "parser.y"
{
$yyval=$yyvs[$yyvsp-3];
$yyval->{$yyvs[$yyvsp-2]} = [$yyvs[$yyvsp-0]];
last switch;
} }
State5: {
# 110 "parser.y"
{
$yyvs[$yyvsp-1]->[cTAG] = $yyvs[$yyvsp-3];
$yyval = $yyvs[$yyvsp-2] ? explicit($yyvs[$yyvsp-1]) : $yyvs[$yyvsp-1];
last switch;
} }
State11: {
# 124 "parser.y"
{
@{$yyval = []}[cTYPE,cCHILD] = ('COMPONENTS', $yyvs[$yyvsp-0]);
last switch;
} }
State14: {
# 134 "parser.y"
{
$yyvs[$yyvsp-0]->[cTAG] = $yyvs[$yyvsp-2];
@{$yyval = []}[cTYPE,cCHILD,cLOOP] = ($yyvs[$yyvsp-4], [$yyvs[$yyvsp-0]], 1);
$yyval = explicit($yyval) if $yyvs[$yyvsp-1];
last switch;
} }
State18: {
# 147 "parser.y"
{
@{$yyval = []}[cTYPE,cCHILD] = ('SEQUENCE', $yyvs[$yyvsp-1]);
last switch;
} }
State19: {
# 151 "parser.y"
{
@{$yyval = []}[cTYPE,cCHILD] = ('SET', $yyvs[$yyvsp-1]);
last switch;
} }
State20: {
# 155 "parser.y"
{
@{$yyval = []}[cTYPE,cCHILD] = ('CHOICE', $yyvs[$yyvsp-1]);
last switch;
} }
State21: {
# 161 "parser.y"
{
@{$yyval = []}[cTYPE] = ('ENUM');
last switch;
} }
State27: {
# 174 "parser.y"
{
@{$yyval = []}[cTYPE] = ($yyvs[$yyvsp-0]);
last switch;
} }
State28: {
# 179 "parser.y"
{ $yyval = $yyvs[$yyvsp-0];
last switch;
} }
State29: {
# 180 "parser.y"
{ $yyval = $yyvs[$yyvsp-1];
last switch;
} }
State30: {
# 184 "parser.y"
{
$yyval = [ $yyvs[$yyvsp-0] ];
last switch;
} }
State31: {
# 188 "parser.y"
{
push @{$yyval=$yyvs[$yyvsp-2]}, $yyvs[$yyvsp-0];
last switch;
} }
State32: {
# 192 "parser.y"
{
push @{$yyval=$yyvs[$yyvsp-2]}, $yyvs[$yyvsp-0];
last switch;
} }
State33: {
# 198 "parser.y"
{
@{$yyval=$yyvs[$yyvsp-0]}[cVAR,cTAG] = ($yyvs[$yyvsp-3],$yyvs[$yyvsp-2]);
$yyval = explicit($yyval) if $yyvs[$yyvsp-1];
last switch;
} }
State34: {
# 205 "parser.y"
{ $yyval = $yyvs[$yyvsp-0];
last switch;
} }
State35: {
# 206 "parser.y"
{ $yyval = $yyvs[$yyvsp-1];
last switch;
} }
State36: {
# 210 "parser.y"
{
$yyval = [ $yyvs[$yyvsp-0] ];
last switch;
} }
State37: {
# 214 "parser.y"
{
push @{$yyval=$yyvs[$yyvsp-2]}, $yyvs[$yyvsp-0];
last switch;
} }
State38: {
# 218 "parser.y"
{
push @{$yyval=$yyvs[$yyvsp-2]}, $yyvs[$yyvsp-0];
last switch;
} }
State39: {
# 224 "parser.y"
{
@{$yyval=$yyvs[$yyvsp-1]}[cOPT] = ($yyvs[$yyvsp-0]);
last switch;
} }
State43: {
# 233 "parser.y"
{
@{$yyval=$yyvs[$yyvsp-0]}[cVAR,cTAG] = ($yyvs[$yyvsp-3],$yyvs[$yyvsp-2]);
$yyval->[cOPT] = $yyvs[$yyvsp-3] if $yyval->[cOPT];
$yyval = explicit($yyval) if $yyvs[$yyvsp-1];
last switch;
} }
State45: {
# 240 "parser.y"
{
@{$yyval=$yyvs[$yyvsp-0]}[cTAG] = ($yyvs[$yyvsp-2]);
$yyval = explicit($yyval) if $yyvs[$yyvsp-1];
last switch;
} }
State46: {
# 246 "parser.y"
{ $yyval = undef;
last switch;
} }
State47: {
# 247 "parser.y"
{ $yyval = 1;
last switch;
} }
State48: {
# 251 "parser.y"
{ $yyval = undef;
last switch;
} }
State50: {
# 255 "parser.y"
{ $yyval = undef;
last switch;
} }
State51: {
# 256 "parser.y"
{ $yyval = 1;
last switch;
} }
State52: {
# 257 "parser.y"
{ $yyval = 0;
last switch;
} }
State53: {
# 260 "parser.y"
{
last switch;
} }
State54: {
# 261 "parser.y"
{
last switch;
} }
State55: {
# 264 "parser.y"
{
last switch;
} }
State56: {
# 267 "parser.y"
{
last switch;
} }
State57: {
# 268 "parser.y"
{
last switch;
} }
# 615 "y.tab.pl"
} # switch
$yyssp -= $yym;
$yystate = $yyss[$yyssp];
$yyvsp -= $yym;
$yym = $yylhs[$yyn];
if ($yystate == 0 && $yym == 0)
{
$yystate = constYYFINAL();
$yyss[++$yyssp] = constYYFINAL();
$yyvs[++$yyvsp] = $yyval;
if ($yychar < 0)
{
if (($yychar = &yylex) < 0) { $yychar = 0; }
# 641 "y.tab.pl"
}
return $yyvs[$yyvsp] if $yychar == 0;
next yyloop;
}
if (($yyn = $yygindex[$yym]) && ($yyn += $yystate) >= 0 &&
$yyn <= $#yycheck && $yycheck[$yyn] == $yystate)
{
$yystate = $yytable[$yyn];
} else {
$yystate = $yydgoto[$yym];
}
$yyss[++$yyssp] = $yystate;
$yyvs[++$yyvsp] = $yyval;
} # yyloop
} # yyparse
# 272 "parser.y"
my %reserved = (
'OPTIONAL' => constOPTIONAL(),
'CHOICE' => constCHOICE(),
'OF' => constOF(),
'IMPLICIT' => constIMPLICIT(),
'EXPLICIT' => constEXPLICIT(),
'SEQUENCE' => constSEQUENCE(),
'SET' => constSET(),
'ANY' => constANY(),
'ENUM' => constENUM(),
'ENUMERATED' => constENUM(),
'COMPONENTS' => constCOMPONENTS(),
'{' => constLBRACE(),
'}' => constRBRACE(),
',' => constCOMMA(),
'::=' => constASSIGN(),
);
my $reserved = join("|", reverse sort grep { /\w/ } keys %reserved);
my %tag_class = (
APPLICATION => ASN_APPLICATION,
UNIVERSAL => ASN_UNIVERSAL,
PRIVATE => ASN_PRIVATE,
CONTEXT => ASN_CONTEXT,
'' => ASN_CONTEXT # if not specified, its CONTEXT
);
;##
;## This is NOT thread safe !!!!!!
;##
my $pos;
my $last_pos;
my @stacked;
sub parse {
local(*asn) = \($_[0]);
($pos,$last_pos,@stacked) = ();
eval {
local $SIG{__DIE__};
compile(verify(yyparse()));
}
}
sub compile_one {
my $tree = shift;
my $ops = shift;
my $name = shift;
foreach my $op (@$ops) {
next unless ref($op) eq 'ARRAY';
bless $op;
my $type = $op->[cTYPE];
if (exists $base_type{$type}) {
$op->[cTYPE] = $base_type{$type}->[1];
$op->[cTAG] = defined($op->[cTAG]) ? asn_encode_tag($op->[cTAG]): $base_type{$type}->[0];
}
else {
die "Unknown type '$type'\n" unless exists $tree->{$type};
my $ref = compile_one(
$tree,
$tree->{$type},
defined($op->[cVAR]) ? $name . "." . $op->[cVAR] : $name
);
if (defined($op->[cTAG]) && $ref->[0][cTYPE] == opCHOICE) {
@{$op}[cTYPE,cCHILD] = (opSEQUENCE,$ref);
}
else {
@{$op}[cTYPE,cCHILD,cLOOP] = @{$ref->[0]}[cTYPE,cCHILD,cLOOP];
}
$op->[cTAG] = defined($op->[cTAG]) ? asn_encode_tag($op->[cTAG]): $ref->[0][cTAG];
}
$op->[cTAG] |= chr(ASN_CONSTRUCTOR)
if length $op->[cTAG] && ($op->[cTYPE] == opSET || $op->[cTYPE] == opSEQUENCE);
if ($op->[cCHILD]) {
;# If we have children we are one of
;# opSET opSEQUENCE opCHOICE
compile_one($tree, $op->[cCHILD], defined($op->[cVAR]) ? $name . "." . $op->[cVAR] : $name);
;# If a CHOICE is given a tag, then it must be EXPLICIT
$op = explicit($op) if $op->[cTYPE] == opCHOICE && defined($op->[cTAG]) && length($op->[cTAG]);
if ( @{$op->[cCHILD]} > 1) {
;#if ($op->[cTYPE] != opSEQUENCE) {
;# Here we need to flatten CHOICEs and check that SET and CHOICE
;# do not contain duplicate tags
;#}
}
else {
;# A SET of one element can be treated the same as a SEQUENCE
$op->[cTYPE] = opSEQUENCE if $op->[cTYPE] == opSET;
}
}
}
$ops;
}
sub compile {
my $tree = shift;
;# The tree should be valid enough to be able to
;# - resolve references
;# - encode tags
;# - verify CHOICEs do not contain duplicate tags
;# once references have been resolved, and also due to
;# flattening of COMPONENTS, it is possible for an op
;# to appear in multiple places. So once an op is
;# compiled we bless it. This ensure we dont try to
;# compile it again.
while(my($k,$v) = each %$tree) {
compile_one($tree,$v,$k);
}
$tree;
}
sub verify {
my $tree = shift or return;
my $err = "";
;# Well it parsed correctly, now we
;# - check references exist
;# - flatten COMPONENTS OF (checking for loops)
;# - check for duplicate var names
while(my($name,$ops) = each %$tree) {
my $stash = {};
my @scope = ();
my $path = "";
my $idx = 0;
while($ops) {
if ($idx < @$ops) {
my $op = $ops->[$idx++];
my $var;
if (defined ($var = $op->[cVAR])) {
$err .= "$name: $path.$var used multiple times\n"
if $stash->{$var}++;
}
if (defined $op->[cCHILD]) {
if (ref $op->[cCHILD]) {
push @scope, [$stash, $path, $ops, $idx];
if (defined $var) {
$stash = {};
$path .= "." . $var;
}
$idx = 0;
$ops = $op->[cCHILD];
}
elsif ($op->[cTYPE] eq 'COMPONENTS') {
splice(@$ops,--$idx,1,expand_ops($tree, $op->[cCHILD]));
}
else {
die "Internal error\n";
}
}
}
else {
my $s = pop @scope
or last;
($stash,$path,$ops,$idx) = @$s;
}
}
}
die $err if length $err;
$tree;
}
sub expand_ops {
my $tree = shift;
my $want = shift;
my $seen = shift || { };
die "COMPONENTS OF loop $want\n" if $seen->{$want}++;
die "Undefined macro $want\n" unless exists $tree->{$want};
my $ops = $tree->{$want};
die "Bad macro for COMPUNENTS OF '$want'\n"
unless @$ops == 1
&& ($ops->[0][cTYPE] eq 'SEQUENCE' || $ops->[0][cTYPE] eq 'SET')
&& ref $ops->[0][cCHILD];
$ops = $ops->[0][cCHILD];
for(my $idx = 0 ; $idx < @$ops ; ) {
my $op = $ops->[$idx++];
if ($op->[cTYPE] eq 'COMPONENTS') {
splice(@$ops,--$idx,1,expand_ops($tree, $op->[cCHILD], $seen));
}
}
@$ops;
}
sub _yylex {
my $ret = &_yylex;
warn $ret;
$ret;
}
sub yylex {
return shift @stacked if @stacked;
while ($asn =~ /\G(?:
(\s+|--[^\n]*)
|
([,{}]|::=)
|
($reserved)\b
|
(
(?:OCTET|BIT)\s+STRING
|
OBJECT\s+IDENTIFIER
|
RELATIVE-OID
)\b
|
(\w+)
|
\[\s*
(
(?:(?:APPLICATION|PRIVATE|UNIVERSAL|CONTEXT)\s+)?
\d+
)
\s*\]
|
\((\d+)\)
)/sxgo
) {
($last_pos,$pos) = ($pos,pos($asn));
next if defined $1; # comment or whitespace
if (defined $2 or defined $3) {
#A comma is not required after a '}' so to aid the
#parser we insert a fake token after any '}'
push @stacked, constPOSTRBRACE() if defined $2 and $+ eq '}';
return $reserved{$yylval = $+};
}
if (defined $4) {
($yylval = $+) =~ s/\s+/_/g;
return constWORD();
}
if (defined $5) {
$yylval = $+;
return constWORD();
}
if (defined $6) {
my($class,$num) = ($+ =~ /^([A-Z]*)\s*(\d+)$/);
$yylval = asn_tag($tag_class{$class}, $num);
return constCLASS();
}
if (defined $7) {
$yylval = $+;
return constNUMBER();
}
die "Internal error\n";
}
die "Parse error before ",substr($asn,$pos,40),"\n"
unless $pos == length($asn);
0
}
sub yyerror {
die @_," ",substr($asn,$last_pos,40),"\n";
}
1;
# 947 "y.tab.pl"
%yystate = ('State20','','State21','','State43','','State27','','State28',
'','State45','','State29','','State46','','State47','','State48','',
'State1','','State3','','State4','','State5','','State11','','State14','',
'State30','','State31','','State32','','State33','','State18','','State34',
'','State50','','State19','','State35','','State51','','State36','',
'State52','','State37','','State53','','State38','','State54','','State39',
'','State55','','State56','','State57','');
1;

143
lib/LWP/Protocol/ldap.pm Normal file
View file

@ -0,0 +1,143 @@
# Copyright (c) 1998 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 LWP::Protocol::ldap;
use Carp ();
use HTTP::Status ();
use HTTP::Negotiate ();
use HTTP::Response ();
use LWP::MediaTypes ();
require LWP::Protocol;
@ISA = qw(LWP::Protocol);
use strict;
eval {
require Net::LDAP;
};
my $init_failed = $@ ? $@ : undef;
sub request {
my($self, $request, $proxy, $arg, $size, $timeout) = @_;
$size = 4096 unless $size;
LWP::Debug::trace('()');
# check proxy
if (defined $proxy)
{
return new HTTP::Response &HTTP::Status::RC_BAD_REQUEST,
'You can not proxy through the ldap';
}
my $url = $request->url;
if ($url->scheme ne 'ldap') {
my $scheme = $url->scheme;
return new HTTP::Response &HTTP::Status::RC_INTERNAL_SERVER_ERROR,
"LWP::Protocol::ldap::request called for '$scheme'";
}
# check method
my $method = $request->method;
unless ($method eq 'GET') {
return new HTTP::Response &HTTP::Status::RC_BAD_REQUEST,
'Library does not allow method ' .
"$method for 'ldap:' URLs";
}
if ($init_failed) {
return new HTTP::Response &HTTP::Status::RC_INTERNAL_SERVER_ERROR,
$init_failed;
}
my $host = $url->host;
my $port = $url->port;
my $user = $url->user;
my $password = $url->password;
# Create an initial response object
my $response = new HTTP::Response &HTTP::Status::RC_OK, "Document follows";
$response->request($request);
my $ldap = new Net::LDAP($host, port => $port);
my $mesg = $ldap->bind;
if ($mesg->code) {
my $res = new HTTP::Response &HTTP::Status::RC_BAD_REQUEST,
"LDAP return code " . $ldap->code;
$res->content_type("text/plain");
$res->content($ldap->error);
return $res;
}
my $dn = $url->dn;
my @attrs = $url->attributes;
my $scope = $url->scope || "base";
my $filter = $url->filter;
my @opts = (scope => $scope);
push @opts, "base" => $dn if $dn;
push @opts, "filter" => $filter if $filter;
push @opts, "attrs" => \@attrs if @attrs;
$mesg = $ldap->search(@opts);
if ($mesg->code) {
my $res = new HTTP::Response &HTTP::Status::RC_BAD_REQUEST,
"LDAP return code " . $ldap->code;
$res->content_type("text/plain");
$res->content($ldap->error);
return $res;
}
else {
my $content = "<HEAD><TITLE>Directory Search Results</TITLE></HEAD>\n<BODY>";
my $entry;
my $index;
for($index = 0 ; $entry = $mesg->entry($index) ; $index++ ) {
my $attr;
$content .= $index ? "<TR><TH COLSPAN=2><hr>&nbsp</TR>\n"
: "<TABLE>";
$content .= "<TR><TH COLSPAN=2>" . $entry->dn . "</TH></TR>\n";
foreach $attr ($entry->attributes) {
my $vals = $entry->get_value($attr, asref => 1);
my $val;
$content .= "<TR><TD align=right valign=top";
$content .= " ROWSPAN=" . scalar(@$vals)
if (@$vals > 1);
$content .= ">" . $attr . "&nbsp</TD>\n";
my $j = 0;
foreach $val (@$vals) {
$val = qq!<a href="$val">$val</a>! if $val =~ /^https?:/;
$val = qq!<a href="mailto:$val">$val</a>! if $val =~ /^[-\w]+\@[-.\w]+$/;
$content .= "<TR>" if $j++;
$content .= "<TD>" . $val . "</TD></TR>\n";
}
}
}
$content .= "</TABLE>" if $index;
$content .= "<hr>";
$content .= $index ? sprintf("%s Match%s found",$index, $index>1 ? "es" : "")
: "<B>No Matches found</B>";
$content .= "</BODY>\n";
$response->header('Content-Type' => 'text/html');
$response->header('Content-Length', length($content));
$response = $self->collect_once($arg, $response, $content)
if ($method ne 'HEAD');
}
$ldap->unbind;
$response;
}

822
lib/Net/LDAP.pm Normal file
View file

@ -0,0 +1,822 @@
# Copyright (c) 1997-2000 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;
use strict;
use IO::Socket;
use IO::Select;
use vars qw($VERSION $LDAP_VERSION @ISA);
use Convert::ASN1 qw(asn_read);
use Net::LDAP::Message;
use Net::LDAP::ASN qw(LDAPResponse);
use Net::LDAP::Constant qw(LDAP_SUCCESS
LDAP_OPERATIONS_ERROR
LDAP_DECODING_ERROR
LDAP_PROTOCOL_ERROR
LDAP_ENCODING_ERROR
LDAP_FILTER_ERROR
LDAP_LOCAL_ERROR
LDAP_PARAM_ERROR
LDAP_INAPPROPRIATE_AUTH
);
$VERSION = 0.25;
@ISA = qw(Net::LDAP::Extra);
$LDAP_VERSION = 2; # default LDAP protocol version
# Net::LDAP::Extra will only exist is someone use's the module. But we need
# to ensure the package stash exists or perl will complain that we inherit
# from a non-existant package. I could just use the module, but I did not
# want to.
$Net::LDAP::Extra::create = $Net::LDAP::Extra::create = 0;
sub import {
shift;
unshift @_, 'Net::LDAP::Constant';
require Net::LDAP::Constant;
goto &{Net::LDAP::Constant->can('import')};
}
sub _options {
my %ret = @_;
my $once = 0;
for my $v (grep { /^-/ } keys %ret) {
require Carp;
$once++ or Carp::carp("deprecated use of leading - for options");
$ret{substr($v,1)} = $ret{$v};
}
$ret{control} = [ map { (ref($_) =~ /[^A-Z]/) ? $_->to_asn : $_ }
ref($ret{control}) eq 'ARRAY'
? @{$ret{control}}
: $ret{control}
]
if exists $ret{control};
\%ret;
}
sub _dn_options {
unshift @_, 'dn' if @_ & 1;
&_options;
}
sub _err_msg {
my $mesg = shift;
my $errstr = $mesg->dn || '';
$errstr .= ": " if $errstr;
$errstr . $mesg->error;
}
my %onerror = (
'die' => sub {
require Carp;
Carp::croak(_err_msg(@_))
},
'warn' => sub { require Carp; Carp::carp(_err_msg(@_)); $_[0] },
'undef' => sub { require Carp; Carp::carp(_err_msg(@_)) if $^W; undef },
);
sub _error {
my ($ldap, $mesg) = splice(@_,0,2);
$mesg->set_error(@_);
$ldap->{net_ldap_onerror} && !$ldap->{net_ldap_async}
? scalar &{$ldap->{net_ldap_onerror}}($mesg)
: $mesg;
}
sub new {
my $self = shift;
my $type = ref($self) || $self;
my $host = shift if @_ % 2;
my $arg = &_options;
my $obj = bless {}, $type;
$obj->_connect($host, $arg) or return;
$obj->{net_ldap_host} = $host;
$obj->{net_ldap_resp} = {};
$obj->{net_ldap_version} = $arg->{version} || $LDAP_VERSION;
$obj->{net_ldap_async} = $arg->{async} ? 1 : 0;
if (defined(my $onerr = $arg->{onerror})) {
$onerr = $onerror{$onerr} if exists $onerror{$onerr};
$obj->{net_ldap_onerror} = $onerr;
}
$obj->debug($arg->{debug} || 0 );
$obj;
}
sub _connect {
my ($ldap, $host, $arg) = @_;
$ldap->{net_ldap_socket} = IO::Socket::INET->new(
PeerAddr => $host,
PeerPort => $arg->{port} || '389',
Proto => 'tcp',
Timeout => defined $arg->{timeout}
? $arg->{timeout}
: 120
);
}
sub message {
my $ldap = shift;
shift->new($ldap, @_);
}
sub async {
my $ldap = shift;
@_
? ($ldap->{'net_ldap_async'},$ldap->{'net_ldap_async'} = shift)[0]
: $ldap->{'net_ldap_async'};
}
sub debug {
my $ldap = shift;
require Convert::ASN1::Debug if $_[0];
@_
? ($ldap->{net_ldap_debug},$ldap->{net_ldap_debug} = shift)[0]
: $ldap->{net_ldap_debug};
}
sub socket {
$_[0]->{net_ldap_socket};
}
sub unbind {
my $ldap = shift;
my $arg = &_options;
my $mesg = $ldap->message('Net::LDAP::Unbind' => $arg);
my $control = $arg->{control}
and $ldap->{net_ldap_version} < 3
and return _error($ldap, $mesg, LDAP_PARAM_ERROR, "Controls require LDAPv3");
$mesg->encode(
unbindRequest => 1,
controls => $control,
) or return _error($ldap, $mesg,LDAP_ENCODING_ERROR,"$@");
$ldap->_sendmesg($mesg);
}
sub ldapbind {
require Carp;
Carp::carp("->ldapbind deprecated, use ->bind") if $^W;
goto &bind;
}
my %ptype = qw(
password simple
krb41password krbv41
krb42password krbv42
kerberos41 krbv41
kerberos42 krbv42
sasl sasl
noauth anon
anonymous anon
);
sub bind {
my $ldap = shift;
my $arg = &_dn_options;
require Net::LDAP::Bind;
my $mesg = $ldap->message('Net::LDAP::Bind' => $arg);
$ldap->version(delete $arg->{version})
if exists $arg->{version};
my $dn = delete $arg->{dn} || '';
my $control = delete $arg->{control}
and $ldap->{net_ldap_version} < 3
and return _error($ldap, $mesg, LDAP_PARAM_ERROR, "Controls require LDAPv3");
my %stash = (
name => ref($dn) ? $dn->dn : $dn,
version => $ldap->version,
);
my($auth_type,$passwd) = scalar(keys %$arg) ? () : (simple => '');
keys %ptype; # Reset iterator
while(my($param,$type) = each %ptype) {
if (exists $arg->{$param}) {
($auth_type,$passwd) = $type eq 'anon' ? (simple => '') : ($type,$arg->{$param});
return _error($ldap, $mesg, LDAP_INAPPROPRIATE_AUTH, "No password, did you mean noauth or anonymous ?")
if $type eq 'simple' and $passwd eq '';
last;
}
}
return _error($ldap, $mesg, LDAP_INAPPROPRIATE_AUTH, "No AUTH supplied")
unless $auth_type;
if ($auth_type eq 'sasl') {
return _error($ldap, $mesg, LDAP_PARAM_ERROR, "SASL requires LDAPv3")
if $ldap->{net_ldap_version} < 3;
my $sasl = $passwd;
# Tell the SASL object our user identifier
$sasl->user("dn: $dn") unless $sasl->user;
$passwd = {
mechanism => $sasl->name,
credentials => $sasl->initial
};
# Save data, we will need it later
$mesg->_sasl_info($stash{name},$control,$sasl);
}
$stash{authentication} = { $auth_type => $passwd };
$mesg->encode(
bindRequest => \%stash,
controls => $control
) or return _error($ldap, $mesg, LDAP_ENCODING_ERROR,"$@");
$ldap->_sendmesg($mesg);
}
my %scope = qw(base 0 one 1 single 1 sub 2 subtree 2);
my %deref = qw(never 0 search 1 find 2 always 3);
sub search {
my $ldap = shift;
my $arg = &_options;
require Net::LDAP::Search;
my $mesg = $ldap->message('Net::LDAP::Search' => $arg);
my $control = $arg->{control}
and $ldap->{net_ldap_version} < 3
and return _error($ldap, $mesg, LDAP_PARAM_ERROR, "Controls require LDAPv3");
my $base = $arg->{base} || '';
my $filter;
unless (ref ($filter = $arg->{filter})) {
require Net::LDAP::Filter;
my $f = Net::LDAP::Filter->new;
$f->parse($filter)
or return _error($ldap, $mesg, LDAP_PARAM_ERROR,"Bad filter");
$filter = $f;
}
my %stash = (
baseObject => ref($base) ? $base->dn : $base,
scope => 2,
derefAliases => 2,
sizeLimit => $arg->{sizelimit} || 0,
timeLimit => $arg->{timelimit} || 0,
typesOnly => $arg->{typesonly} || $arg->{attrsonly} || 0,
filter => $filter,
attributes => $arg->{attrs} || []
);
if (exists $arg->{scope}) {
my $sc = lc $arg->{scope};
$stash{scope} = 0 + (exists $scope{$sc} ? $scope{$sc} : $sc);
}
if (exists $arg->{deref}) {
my $dr = lc $arg->{deref};
$stash{derefAliases} = 0 + (exists $deref{$dr} ? $deref{$dr} : $dr);
}
$mesg->encode(
searchRequest => \%stash,
controls => $control
) or return _error($ldap, $mesg, LDAP_ENCODING_ERROR,"$@");
$ldap->_sendmesg($mesg);
}
sub add {
my $ldap = shift;
my $arg = &_dn_options;
my $mesg = $ldap->message('Net::LDAP::Add' => $arg);
my $control = $arg->{control}
and $ldap->{net_ldap_version} < 3
and return _error($ldap, $mesg, LDAP_PARAM_ERROR, "Controls require LDAPv3");
my $entry = $arg->{dn}
or return _error($ldap, $mesg, LDAP_PARAM_ERROR,"No DN specified");
unless (ref $entry) {
require Net::LDAP::Entry;
$entry = Net::LDAP::Entry->new;
$entry->dn($arg->{dn});
$entry->add(@{$arg->{attrs} || $arg->{attr} || []});
}
$mesg->encode(
addRequest => $entry->asn,
controls => $control
) or return _error($ldap, $mesg, LDAP_ENCODING_ERROR,"$@");
$ldap->_sendmesg($mesg);
}
my %opcode = ( 'add' => 0, 'delete' => 1, 'replace' => 2);
sub modify {
my $ldap = shift;
my $arg = &_dn_options;
my $mesg = $ldap->message('Net::LDAP::Modify' => $arg);
my $control = $arg->{control}
and $ldap->{net_ldap_version} < 3
and return _error($ldap, $mesg, LDAP_PARAM_ERROR, "Controls require LDAPv3");
my $dn = $arg->{dn}
or return _error($ldap, $mesg, LDAP_PARAM_ERROR,"No DN specified");
my @ops;
my $opcode;
my $op;
if (exists $arg->{changes}) {
my $chg;
my $opcode;
my $j = 0;
while($j < @{$arg->{changes}}) {
return _error($ldap, $mesg, LDAP_PARAM_ERROR,"Bad change type '" . $arg->{changes}[--$j] . "'")
unless defined($opcode = $opcode{$arg->{changes}[$j++]});
$chg = $arg->{changes}[$j++];
if (ref($chg)) {
my $i = 0;
while ($i < @$chg) {
push @ops, {
operation => $opcode,
modification => {
type => $chg->[$i],
vals => ref($chg->[$i+1]) ? $chg->[$i+1] : [$chg->[$i+1]]
}
};
$i += 2;
}
}
}
}
else {
foreach $op (qw(add delete replace)) {
next unless exists $arg->{$op};
my $opt = $arg->{$op};
my $opcode = $opcode{$op};
my($k,$v);
if (ref($opt) eq 'HASH') {
while (($k,$v) = each %$opt) {
push @ops, {
operation => $opcode,
modification => {
type => $k,
vals => ref($v) ? $v : [$v]
}
};
}
}
elsif (ref($opt) eq 'ARRAY') {
$k = 0;
while ($k < @{$opt}) {
my $attr = ${$opt}[$k++];
my $val = $opcode == 1 ? [] : ${$opt}[$k++];
push @ops, {
operation => $opcode,
modification => {
type => $attr,
vals => $val
}
};
}
}
else {
push @ops, {
operation => $opcode,
modification => {
type => $opt,
vals => []
}
};
}
}
}
$mesg->encode(
modifyRequest => {
object => ref($dn) ? $dn->dn : $dn,
modification => \@ops
},
controls => $control
)
or return _error($ldap, $mesg, LDAP_ENCODING_ERROR,"$@");
$ldap->_sendmesg($mesg);
}
sub delete {
my $ldap = shift;
my $arg = &_dn_options;
my $mesg = $ldap->message('Net::LDAP::Delete' => $arg);
my $control = $arg->{control}
and $ldap->{net_ldap_version} < 3
and return _error($ldap, $mesg, LDAP_PARAM_ERROR, "Controls require LDAPv3");
my $dn = $arg->{dn}
or return _error($ldap, $mesg, LDAP_PARAM_ERROR,"No DN specified");
$mesg->encode(
delRequest => ref($dn) ? $dn->dn : $dn,
controls => $control
) or return _error($ldap, $mesg, LDAP_ENCODING_ERROR,"$@");
$ldap->_sendmesg($mesg);
}
sub moddn {
my $ldap = shift;
my $arg = &_dn_options;
my $del = $arg->{deleteoldrdn} || $arg->{'delete'} || 0;
my $newsup = $arg->{newsuperior};
my $mesg = $ldap->message('Net::LDAP::ModDN' => $arg);
my $control = $arg->{control}
and $ldap->{net_ldap_version} < 3
and return _error($ldap, $mesg, LDAP_PARAM_ERROR, "Controls require LDAPv3");
my $dn = $arg->{dn}
or return _error($ldap, $mesg, LDAP_PARAM_ERROR,"No DN specified");
my $new = $arg->{newrdn} || $arg->{'new'}
or return _error($ldap, $mesg, LDAP_PARAM_ERROR,"No NewRDN specified");
$mesg->encode(
modDNRequest => {
entry => ref($dn) ? $dn->dn : $dn,
newrdn => ref($new) ? $new->dn : $new,
deleteoldrdn => $del,
newSuperior => ref($newsup) ? $newsup->dn : $newsup,
},
controls => $control
) or return _error($ldap, $mesg, LDAP_ENCODING_ERROR,"$@");
$ldap->_sendmesg($mesg);
}
# now maps to the V3/X.500(93) modifydn map
sub modrdn { goto &moddn }
sub compare {
my $ldap = shift;
my $arg = &_dn_options;
my $mesg = $ldap->message('Net::LDAP::Compare' => $arg);
my $control = $arg->{control}
and $ldap->{net_ldap_version} < 3
and return _error($ldap, $mesg, LDAP_PARAM_ERROR, "Controls require LDAPv3");
my $dn = $arg->{dn}
or return _error($ldap, $mesg, LDAP_PARAM_ERROR,"No DN specified");
my $attr = exists $arg->{attr}
? $arg->{attr}
: exists $arg->{attrs} #compat
? $arg->{attrs}[0]
: "";
my $value = exists $arg->{value}
? $arg->{value}
: exists $arg->{attrs} #compat
? $arg->{attrs}[1]
: "";
$mesg->encode(
compareRequest => {
entry => ref($dn) ? $dn->dn : $dn,
ava => {
attributeDesc => $attr,
assertionValue => $value
}
},
controls => $control
) or return _error($ldap, $mesg, LDAP_ENCODING_ERROR,"$@");
$ldap->_sendmesg($mesg);
}
sub abandon {
my $ldap = shift;
unshift @_,'id' if @_ & 1;
my $arg = &_options;
my $id = $arg->{id};
my $mesg = $ldap->message('Net::LDAP::Abandon' => $arg);
my $control = $arg->{control}
and $ldap->{net_ldap_version} < 3
and return _error($ldap, $mesg, LDAP_PARAM_ERROR, "Controls require LDAPv3");
$mesg->encode(
abandonRequest => ref($id) ? $id->mesg_id : $id,
controls => $control
) or return _error($ldap, $mesg, LDAP_ENCODING_ERROR,"$@");
$ldap->_sendmesg($mesg);
}
sub extension {
my $ldap = shift;
my $arg = &_options;
require Net::LDAP::Extension;
my $mesg = $ldap->message('Net::LDAP::Extension' => $arg);
return _error($ldap, $mesg, LDAP_LOCAL_ERROR, "ExtendedRequest requires LDAPv3")
if $ldap->{net_ldap_version} < 3;
$mesg->encode(
extendedRequest => {
requestName => $arg->{name},
requestValue => $arg->{value}
},
controls => $arg->{control}
) or return _error($ldap, $mesg, LDAP_ENCODING_ERROR,"$@");
$ldap->_sendmesg($mesg);
}
sub sync {
my $ldap = shift;
my $mid = shift;
my $table = $ldap->{net_ldap_mesg};
my $err = LDAP_SUCCESS;
$mid = $mid->mesg_id if ref($mid);
while (defined($mid) ? exists $table->{$mid} : %$table) {
last if $err = $ldap->_recvresp($mid);
}
$err;
}
sub _sendmesg {
my $ldap = shift;
my $mesg = shift;
my $debug;
if ($debug = $ldap->debug) {
require Convert::ASN1::Debug;
print STDERR "$ldap sending:\n";
Convert::ASN1::asn_hexdump(*STDERR, $mesg->pdu)
if $debug & 1;
Convert::ASN1::asn_dump(*STDERR, $mesg->pdu)
if $debug & 4;
}
syswrite($ldap->socket, $mesg->pdu, length($mesg->pdu))
or return _error($ldap, $mesg, LDAP_LOCAL_ERROR,"$!");
# for CLDAP, here we need to recode when we were sent
# so that we can perform timeouts and resends
my $mid = $mesg->mesg_id;
my $sync = not $ldap->async;
unless ($mesg->done) { # may not have a responce
$ldap->{net_ldap_mesg}->{$mid} = $mesg;
if ($sync) {
my $err = $ldap->sync($mid);
return _error($ldap, $mesg, $err,$@) if $err;
}
}
$sync && $ldap->{net_ldap_onerror} && $mesg->is_error
? scalar &{$ldap->{net_ldap_onerror}}($mesg)
: $mesg;
}
sub _recvresp {
my $ldap = shift;
my $what = shift;
my $sock = $ldap->socket;
my $sel = IO::Select->new($sock);
my $ready;
for( $ready = 1 ; $ready ; $ready = $sel->can_read(0)) {
my $pdu;
asn_read($sock, $pdu)
or return LDAP_OPERATIONS_ERROR;
my $debug;
if ($debug = $ldap->debug) {
require Convert::ASN1::Debug;
print STDERR "$ldap received:\n";
Convert::ASN1::asn_hexdump(\*STDERR,$pdu)
if $debug & 2;
Convert::ASN1::asn_dump(\*STDERR,$pdu)
if $debug & 8;
}
my $result = $LDAPResponse->decode($pdu)
or return LDAP_DECODING_ERROR;
my $mid = $result->{messageID};
my $mesg = $ldap->{net_ldap_mesg}->{$mid} or
do {
print STDERR "Unexpected PDU, ignored\n" if $debug & 10;
next;
};
$mesg->decode($result) or
return $mesg->code;
last if defined $what && $what == $mid;
}
# FIXME: in CLDAP here we need to check if any message has timed out
# and if so do we resend it or what
return LDAP_SUCCESS;
}
sub _forgetmesg {
my $ldap = shift;
my $mesg = shift;
my $mid = $mesg->mesg_id;
delete $ldap->{net_ldap_mesg}->{$mid};
}
#Mark Wilcox 3-20-2000
#now accepts named parameters
#dn => "dn of subschema entry"
#
#
# Clif Harden 2-4-2001.
# corrected filter for subschema search.
# added attributes to retrieve on subschema search.
# added attributes to retrieve on rootDSE search.
# changed several double qoute character to single quote
# character, just to be consistent throughout the schema
# and root_dse functions.
#
sub schema {
require Net::LDAP::Schema;
my $self = shift;
my %arg = @_;
my $base;
my $mesg;
if (exists $arg{'dn'}) {
$base = $arg{'dn'};
}
else {
my $root = $self->root_dse( attrs => ['subschemaSubentry'] )
or return undef;
$base = $root->get_value('subschemaSubentry') || 'cn=schema';
}
$mesg = $self->search(
base => $base,
scope => 'base',
filter => '(objectClass=subschema)',
attrs => [qw(
objectClasses
attributeTypes
matchingRules
matchingRuleUse
dITStructureRules
dITContentRules
nameForms
ldapSyntaxes
)],
);
$mesg->code
? undef
: Net::LDAP::Schema->new($mesg->entry);
}
sub root_dse {
my $ldap = shift;
my %arg = @_;
my $attrs = $arg{attrs} || [qw(
subschemaSubentry
namingContexts
altServer
supportedExtension
supportedControl
supportedSASLMechanisms
supportedLDAPVersion
)];
my $mesg = $ldap->search(
base => '',
scope => 'base',
filter => '(objectClass=*)',
attrs => $attrs,
);
$mesg->entry;
}
sub start_tls {
my $ldap = shift;
my $arg = &_options;
my $sock = $ldap->socket;
require Net::LDAP::Extension;
my $mesg = $ldap->message('Net::LDAP::Extension' => $arg);
return _error($ldap, $mesg, LDAP_OPERATIONS_ERROR, "TLS already started")
if $sock->isa('IO::Socket::SSL');
return _error($ldap, $mesg, LDAP_PARAM_ERROR, "StartTLS requires LDAPv3")
if $ldap->version < 3;
$mesg->encode(
extendedReq => {
requestName => "1.3.6.1.4.1.1466.20037",
}
);
$ldap->_sendmesg($mesg);
$mesg->sync();
return $mesg
if $mesg->code;
require Net::LDAPS;
$arg->{sslversion} = 'tlsv1' unless defined $arg->{sslversion};
IO::Socket::SSL::context_init( { Net::LDAPS::SSL_context_init_args($arg) } );
(IO::Socket::SSL::socketToSSL($sock) and tie *{$sock}, 'IO::Socket::SSL', $sock)
? $mesg
: _error($ldap, $mesg, LDAP_OPERATIONS_ERROR, $@);
}
sub cipher {
my $ldap = shift;
$ldap->socket->isa('IO::Socket::SSL')
? $ldap->socket->get_cipher
: undef;
}
sub certificate {
my $ldap = shift;
$ldap->socket->isa('IO::Socket::SSL')
? $ldap->socket->get_peer_certificate
: undef;
}
# what version are we talking?
sub version {
my $ldap = shift;
@_
? ($ldap->{net_ldap_version},$ldap->{net_ldap_version} = shift)[0]
: $ldap->{net_ldap_version};
}
1;

875
lib/Net/LDAP.pod Normal file
View file

@ -0,0 +1,875 @@
=head1 NAME
Net::LDAP - Lightweight Directory Access Protocol
=head1 SYNOPSIS
use Net::LDAP;
$ldap = Net::LDAP->new('ldap.bigfoot.com') or die "$@";
$ldap->bind ; # an anonymous bind
$mesg = $ldap->search ( # perform a search
base => "c=US",
filter => "(&(sn=Barr) (o=Texas Instruments))"
);
$mesg->code && die $mesg->error;
foreach $entry ($mesg->all_entries) { $entry->dump; }
$ldap->unbind; # take down session
$ldap = Net::LDAP->new('ldap.umich.edu');
# bind to a directory with dn and password
$ldap->bind ( 'cn=root, o=University of Michigan, c=us',
password => 'secret'
);
$result = $ldap->add ( 'cn = Barbara Jensen, o=University of Michigan, c=us',
attr => [ 'cn' => ['Barbara Jensen', 'Barbs Jensen'],
'sn => 'Jensen',
'mail' => 'b.jensen@umich.edu',
'objectclass' => ['top', 'person',
'organizationalPerson',
'inetOrgPerson' ],
]
);
$result->code && warn "failed to add entry: ", $result->error ;
=head1 DESCRIPTION
B<Net::LDAP> is a collection of modules that implements a LDAP services API
for Perl programs. The module may be used to search directories or
perform maintenance functions such as add, deleting or modify entries in
an LDAP directory.
This document assumes that the reader has some knowledge of the LDAP
protocol.
=head1 CONSTRUCTOR
=over 4
=item new ( HOST [, OPTIONS ] )
Creates a new B<Net::LDAP> object and opens a connection to the named host.
OPTIONS is a list of key-value pairs, valid options are :-
=over 4
=item port
Port to connect to on the remote server.
=item timeout
Timeout passed to L<IO::Socket> when connecting the remote server.
(Default: 120)
=item debug
If passed a non-zero value then debug data will be sent to C<STDERR>. The
bits of this value are :-
1 Show outgoing packets (using asn_hexdump).
2 Show incoming packets (using asn_hexdump).
4 Show outgoing packets (using asn_dump).
8 Show incoming packets (using asn_dump).
=item async
Perform all operations asynchronously if passed a I<true> value.
=item onerror
If set then Net::LDAP will check all responses for errors on all methods
if the object is in synchronous mode. If an error is detected then the
specified action will be taken. Valid values and their actions are.
=over 4
=item die
Net::LDAP will croak with an appropriate message.
=item warn
Net::LDAP will warn with an appropriate message.
=item undef
Net::LDAP will warn with an appropriate message if C<-w> is in effect.
The method that was called will return C<undef>
=item CODEREF
The given coderef will be called in a scalar context with a single argument, the result
message. The value returned will be the return value for the method
that was called.
=back
=item version
Set the protocol version being used (default is LDAPv2). This is
useful if you want to avoid sending a bind operation and therefore
have to use LDAPv3.
=back
Example
$ldap = Net::LDAP->new('remote.host', async => 1);
=back
=head1 METHODS
Each of the following methods take as arguments some number of fixed
parameters followed by options, these options are passed in a named
fashion, for example
$mesg = $ldap->bind( "me", password => "mypasswd");
The return value from these methods is an object derived from the
L<Net::LDAP::Message> class. The methods of this class allow
you to examine the status of request.
=over 4
=item abandon ( ID [, OPTIONS ] )
Request server to abandon a request. The id to abandon may be passed as the
first parameter or as part of the options list. The B<ID> may be a number
or a object which is a sub-class of L<Net::LDAP::Message>, returned from
a previous method call.
=over 4
=item id
This option is here for B<compatibility only>, and may be removed in future.
Previous releases did not take the B<ID> argument which replaces this option.
=item control
See L</CONTROLS> below
=item callback
See L</CALLBACKS> below
=back
B<Example>
$mesg = $ldap->search( @search_args );
$ldap->abandon( $mesg ); # This could be written as $mesg->abandon
=item add ( DN [, OPTIONS ] )
Add an entry to the directory. The B<DN> argument can be either a
L<Net::LDAP::Entry> object or a string.
=over 4
=item dn
This option is here for B<compatibility only>, and may be removed in future.
Previous releases did not take the B<DN> argument which replaces this option.
=item attrs
This argument is a reference to a list of attribute-value pairs. Attributes
with multiple values can be added as either multiple entries or the value
could be a reference to a list of values.
This argument is not used if B<DN> is a L<Net::LDAP::Entry> object.
=item control
See L</CONTROLS> below
=item callback
See L</CALLBACKS> below
=back
B<Example>
# $entry is an object of class Net::LDAP::Entry
$mesg = $ldap->add( $entry );
$mesg = $ldap->add( $DN,
attrs => [
name => 'Graham Barr',
attr => 'value1',
attr => 'value2',
multi => [qw(value1 value2)]
]
);
=item bind ( [ DN [, OPTIONS ]] )
Bind to the server. B<DN> is the DN to bind as. An anonymous bind may be done
by calling bind without any arguments.
=over 4
=item dn
This option is here for B<compatibility only>, and may be removed in future.
Previous releases did not take the B<DN> argument which replaces this option.
=item control
See L</CONTROLS> below
=item callback
See L</CALLBACKS> below
=back
Only one of the following should be given, if none are given then B<noauth>
is assumed.
=over 4
=item noauth
=item anonymous
Bind without any password, the value passed with this option is ignored. This
is the default if no arguments are given.
=item password
Bind with the given password.
=item kerberos41
Bind using Kerberos V4.1 B<I<not supported>>.
=item kerberos42
Bind using Kerberos V4.2 B<I<not supported>>.
=item sasl
Bind using a SASL mechanism. The argument given should be a sub-class
of L<Authen::SASL>.
=back
B<Example>
$ldap->bind; # Anonymous bind
$ldap->bind( $DN, password => $password);
# $sasl is an object of class Authen::SASL
$ldap->bind( $DN, sasl => $sasl, version => 3);
=item compare ( DN, OPTIONS )
Perform a comparison on the server. B<DN> is the DN which the comparison is
to be performed. B<DN> May be a string or a L<Net::LDAP::Entry>
object.
=over 4
=item dn
This option is here for B<compatibility only>, and may be removed in future.
Previous releases did not take the B<DN> argument which replaces this option.
=item attr
The name of the attribute to compare.
=item value
The value to compare with.
=item control
See L</CONTROLS> below.
=item callback
See L</CALLBACKS> below.
=back
B<Example>
$ldap->compare( $DN,
attr => 'cn',
value => 'Graham Barr'
);
=item delete ( DN [, OPTIONS ] )
Delete B<DN> from the server. B<DN> May be a string or a L<Net::LDAP::Entry>
object.
=over 4
=item dn
This option is here for B<compatibility only>, and may be removed in future.
Previous releases did not take the B<DN> argument which replaces this option.
=item control
See L</CONTROLS> below.
=item callback
See L</CALLBACKS> below.
=back
B<Example>
$ldap->delete( $dn );
=item moddn ( DN, OPTIONS )
Modify the DN for B<DN> on the server. B<DN> May be a string or a
L<Net::LDAP::Entry> object.
=over 4
=item dn
This option is here for B<compatibility only>, and may be removed in future.
Previous releases did not take the B<DN> argument which replaces this option.
=item newrdn
This value should be a new RDN to assign to B<DN>.
=item deleteoldrdn
This value should be I<true> if the existing RDN is to be deleted.
=item newsuperior
If given this value should be the DN of the new superior for B<DN>.
=item control
See L</CONTROLS> below.
=item callback
See L</CALLBACKS> below.
=back
B<Example>
$ldap->moddn( $dn, newrdn => 'cn=Graham Barr');
=item modify ( DN, OPTIONS )
Modify the contents of B<DN> on the server. B<DN> May be a string or a
L<Net::LDAP::Entry> object.
=over 4
=item dn
This option is here for B<compatibility only>, and may be removed in future.
Previous releases did not take the B<DN> argument which replaces this option.
=item add
The B<add> option should be a reference to a HASH. The values of the
HASH are the attributes to add, and the values may be a string or a reference
to a list of values.
=item delete
A reference to an ARRAY of attributes to delete or a reference to a
HASH (as in B<add>) if only specific values should be deleted. If the
value for any attribute in the HASH is a reference to an empty ARRAY
the all instances of the attribute will be deleted.
=item replace
The <replace> option takes a argument in the same form as B<add>, but will
cause any existing attributes with the same name to be replaced. If the
value for any attribute in the HASH is a reference to an empty ARRAY
the all instances of the attribute will be deleted.
=item changes
This is an alternative to B<add>, B<delete> and B<replace> where the
whole operation can be given in a single argument. The argument should
be a reference to an ARRAY.
Values in the ARRAY are used in pairs, the first is the operation
B<add>, B<delete> or B<replace> and the second is a reference to an
ARRAY of attribute values.
The attribute value list is also used in pairs. The first value in each
pair is the attribute name and the second is a reference to a list of values.
Use this form if you want to control the order in which the operations will
be performed.
=item control
See L</CONTROLS> below.
=item callback
See L</CALLBACKS> below.
=back
B<Example>
$ldap->modify( $dn, add => { sn => 'Barr' } );
$ldap->modify( $dn, delete => [qw(faxNumber)]);
$ldap->modify( $dn, delete => { 'telephoneNumber' => '911' });
$ldap->modify( $dn, replace => { 'email' => 'gbarr@pobox.com' });
$ldap->modify( $dn,
changes => [
add => [ sn => 'Barr' ], # Add sn=Barr
delete => [ faxNumber => []], # Delete all fax numbers
delete => [ telephoneNumber => ['911']], # delete phone number 911
replace => [ email => 'gbarr@pobox.com'] # change email address
]
);
=item root_dse ( OPTIONS )
The root_dse method retrieves information from the server's
rootDSE entry.
=over 4
=item attrs
A reference to a list of attributes to be returned.
If not specified, then the following attributes will be requested
subschemaSubentry
namingContexts
altServer
supportedExtension
supportedControl
supportedSASLMechanisms
supportedLDAPVersion
=back
The result is an object of class L<Net::LDAP::Search>.
B<Example>
my $root = $ldap->root_dse();
# get naming Context
$root->get_value('namingContext', asref => 1);
# get supported LDAP versions
$root->get_value('supportedLDAPVersion', asref => 1);
=item schema ( OPTIONS )
Request that a schema search be performed. This can be used to read
schema information.
The result is an object of class L<Net::LDAP::Schema>.
Read this documentation for further information about methods that
can be preformed with this object.
=over 4
=item dn
If a DN is supplied, it will become the base object entry from
which the search for schema information will be conducted. If
no DN is supplied the base object entry will be determined from
the rootDSE entry.
B<Example>
my $schema = $ldap->schema();
# get objectClasses
@ocs = $schema->objectclasses();
# Get the attributes
@atts = $schema->attributes();
=item search ( OPTIONS )
Request that a search be performed. This can be used to read attributes
from a single entry, from entries immediately below a particular entry,
or a whole subtree of entries.
The result is an object of class L<Net::LDAP::Search>.
=over 4
=item base
The DN that is the base object entry relative to which the search is
to be performed.
=item scope
By default the search is performed on the whole tree below
the specified base object. This may be chaned by specifying a C<scope>
parameter with one of the following values.
=over 4
=item base
Search only the base object.
=item one
Search the entries immediately below the base object.
=item sub
Search the whole tree below the base object. This is the default.
=back
=item deref
By default aliases are
dereferenced to locate the base object for the search, but not when
searching subordinates of the base object. This may be changed by
specifying a C<deref> parameter with one of the following values.
=over 4
=item never
Do not dereference aliases in searching
or in locating the base object of the search.
=item search
Dereference aliases in subordinates of the base object in searching,
but not in locating the base object of the search.
=item find
Dereference aliases in locating the base object of the search, but not
when searching subordinates of the base object. This is the default.
=item always
Dereference aliases both in searching and in locating the base object
of the search.
=back
=item sizelimit
A sizelimit that restricts the maximum number of entries to be returned
as a result of the search. A value of 0, and the default, means that
no restriction is requested. Servers may enforce a maximum number of
entries to return.
=item timelimit
A timelimit that restricts the maximum time (in seconds) allowed for
a search. A value of 0, and the default, means that no timelimit will
be requested.
=item typesonly
An indicator as to whether search results should contain both attribute
types and values, or just attribute types. Setting this parameter to
a I<true> value causes only attribute types (no values) to be returned.
Setting this field to a I<false> causes both attribute types and values
to be returned. The default is to return both attribute types and values.
=item filter
A filter that defines the conditions an entry in the directory must meet
in order for it to be returned by the search. This may be a string or a
L<Net::LDAP::Filter> object. See L<Net::LDAP::Filter> for a defintion of
the filter format.
=item attrs
A reference to a list of attributes to be returned for each entry that
matches the search filter.
If not specified, then the server will return the attributes that are
specified as accessible by default given your bind credentials.
Certain additional attributes such as "createtimestamp" and other
operational attributes may also be available for the asking:
$ldap->search( ... , attrs => ['createtimestamp'] , ... );
To retreive the default attributes and additional ones, use '*'.
$ldap->search( ... , attrs => ['*', 'createtimestamp'] , ... );
=item control
See L</CONTROLS> below.
=item callback
See L</CALLBACKS> below.
=back
B<Example>
$mesg = $ldap->search(
base => $base_dn,
scope => 'sub',
filter => '(|(objectclass=rfc822mailgroup)(sn=jones))'
);
Net::LDAP::LDIF->new(\*STDOUT,"w")->write($mesg->entries);
=item unbind
The unbind method does not take any parameters and will unbind you
from the server. While some servers may allow you to re-bind or perform
other operations after unbinding, the only portable operation is closing
the connection. In the case that you wish to switch to another set of
credentials while continuing to use the same connection, re-binding with
another DN and password, without unbind-ing, will generally work.
B<Example>
$ldap->unbind;
=back
The following methods are for convenience.
=over 4
=item async
Returns I<true> if the LDAP operations are being performed asynchronously.
=item debug ( [ VALUE ] )
If B<VALUE> is given the debug bit-value will be set to B<VALUE> and the
previous value will be returned. If not given the bit-value will remain
unchanged and will be returned.
=item sync ( [ MESG ] )
Calling this method will synchronize the client with the server. It will
not return until all requests have been completed, or id B<MESG> is given
it will return when B<MESG> has been completed.
Returns an error code defined in L<Net::LDAP::Constant>.
=item start_tls ( [ OPTIONS ] )
Calling this method will convert the connection to using Transport
Layer Security (TLS), which potentially provides an encrypted
connection. This is I<only> possible if the connection is using
LDAPv3. OPTIONS is a number of key-value pairs which describe how to
configure the security of the connection:
=over 4
=item verify
How to verify the server's certificate, either 'none' (the server may
provide a certificate but it will not be checked - this may mean you
are be connected to the wrong server), 'optional' (verify if the
server offers a certificate), or 'require' (the server must provide a
certificate, and it must be valid.) If you set verify to optional or
require, you must also set either cafile or capath. The most secure
option is 'require'.
=item sslversion
This defines the version of the SSL/TLS protocol to use. Defaults to
'tlsv1', other possible values are 'sslv2', 'sslv3', and 'sslv2/3'.
=item ciphers
Specify which subset of cipher suites are permissible for this
connection, using the standard OpenSSL string format. The default
value for ciphers is 'ALL', which permits all ciphers, even those that
don't encrypt!
=item clientcert
=item clientkey
If you want to use the client to offer a certificate to the server for
SSL authentication (which is not the same as for the LDAP Bind
operation) then set clientcert to the user's certificate file, and
clientkey to the user's private key file. These files must be in PEM
format.
=item capath
=item cafile
When verifying the server's certificate, either set capath to the
pathname of the directory containing CA certificates, or set cafile to
the filename containing the certificate of the CA who signed the
server's certificate. These certificates must all be in PEM format.
The directory in 'capath' must contain certificates named using the
hash value of themselves. To generate these names, use OpenSSL like
this in Unix:
ln -s cacert.pem `openssl x509 -hash -noout < cacert.pem`.0
(assuming that the certificate of the CA is in cacert.pem.)
=back
=back
=over 4
=item cipher
Returns the cipher mode being used by the connection, in the string
format used by OpenSSL.
=item certificate
Returns an X509_Certificate object containing the server's
certificate. See the IO::Socket::SSL documentation for information
about this class.
For example, to get the subject name (in a peculiar OpenSSL-specific
format, different from RFC 1779 and RFC 2253) from the server's
certificate, do this:
print "Subject DN: " . $ldaps->certificate->subject_name . "\n";
=back
=item version
Returns the version of the LDAP protocol that is being used.
=back
=head1 CONTROLS
Many of the methods described above accept a control option.
This allows the user to pass controls to the server as described
in LDAPv3. The value to the control argument may be either a
single control or a reference to an array of controls.
A control is a reference to a HASH and should contain the three
elements below. If any of the controls are blessed then the
method C<to_asn> will be called which should return a reference
to a HASH containing the three elements described below.
=over 4
=item type
This element must be present and is the name of the type of control
being requested.
=item critical
critical is optional and should be a boolean value, if it is not specified
then it is assumed to be I<false>.
=item value
If the control being requested requires a value then this element should
hold the value for the server.
=back
=head1 CALLBACKS
Most of the above commands accept a callback option. This option
should be a reference to a subroutine. This subroutine will be called
for each packet received from the server as a response to the request
sent.
When the subroutine is called the first argument will be the
L<Net::LDAP::Message> object which was returned from the method.
If the request is a search then multiple packets can be received from
the server. Each entry is received as a separate packet. For each of these
the subroutine will be called with a L<Net::LDAP::Entry> object as the second
argument.
During a search the server may also send a list of references. When such
a list is received then the subroutine will be called with a
L<Net::LDAP::Reference> object as the second argument.
=head1 LDAP ERROR CODES
B<Net::LDAP> also exports constants for the error codes that can be received
from the server, see L<Net::LDAP::Constant>.
=head1 SEE ALSO
L<Net::LDAP::Constant>,
L<Net::LDAP::Control>,
L<Net::LDAP::Entry>,
L<Net::LDAP::Filter>,
L<Net::LDAP::Message>,
L<Net::LDAP::Reference>,
L<Net::LDAP::Search>,
L<Net::LDAP::RFC>
The homepage for the perl-ldap modules can be found at
http://www.pobox.com/~gbarr/perl-ldap/.
=head1 ACKNOWLEDGEMENTS
This document is based on a document originally written by Russell Fulton
<r.fulton@auckland.ac.nz>.
Chris Ridd @isode.com for the many hours spent testing and contribution
of the ldap* command line utilities.
=head1 AUTHOR
Graham Barr <gbarr@pobox.com>
Please report any bugs, or post any suggestions, to the perl-ldap mailing list
<perl-ldap-dev@lists.sourceforge.net>
=head1 COPYRIGHT
Copyright (c) 1997-2000 Graham Barr. All rights reserved. This program is
free software; you can redistribute it and/or modify it under the same
terms as Perl itself.
=for html <hr>
I<$Id$>
=cut

366
lib/Net/LDAP/ASN.pm Normal file
View file

@ -0,0 +1,366 @@
# $Id$
package Net::LDAP::ASN;
use Convert::ASN1;
my $asn = Convert::ASN1->new;
sub import {
my $pkg = shift;
my $caller = caller;
foreach my $macro (@_) {
my $obj = $asn->find($macro)
or require Carp and Carp::croak("Unknown macro '$macro'");
*{"$caller\::$macro"} = \$obj;
}
}
$asn->prepare(<<LDAP_ASN) or die $asn->error;
-- We have split LDAPMessage into LDAPResponse and LDAPRequest
-- The purpose of this is two fold
-- 1) for encode we don't want the protocolOp
-- in the hierarchy as it is not really neede
-- 2) For decode we do want it, this allows Net::LDAP::Message::decode
-- to be much simpler. Decode will also be faster due to
-- less elements in the CHOICE
LDAPRequest ::= SEQUENCE {
messageID MessageID,
--protocolOp
CHOICE {
bindRequest BindRequest,
unbindRequest UnbindRequest,
searchRequest SearchRequest,
modifyRequest ModifyRequest,
addRequest AddRequest,
delRequest DelRequest,
modDNRequest ModifyDNRequest,
compareRequest CompareRequest,
abandonRequest AbandonRequest,
extendedReq ExtendedRequest}
controls [0] Controls OPTIONAL }
LDAPResponse ::= SEQUENCE {
messageID MessageID,
protocolOp CHOICE {
bindResponse BindResponse,
searchResEntry SearchResultEntry,
searchResDone SearchResultDone,
searchResRef SearchResultReference,
modifyResponse ModifyResponse,
addResponse AddResponse,
delResponse DelResponse,
modDNResponse ModifyDNResponse,
compareResponse CompareResponse,
extendedResp ExtendedResponse }
controls [0] Controls OPTIONAL }
MessageID ::= INTEGER -- (0 .. maxInt)
-- maxInt INTEGER ::= 2147483647 -- (2^^31 - 1) --
LDAPString ::= OCTET STRING -- UTF8String ??
LDAPOID ::= OCTET STRING
LDAPDN ::= LDAPString
RelativeLDAPDN ::= LDAPString
AttributeType ::= LDAPString
AttributeDescription ::= LDAPString
AttributeDescriptionList ::= SEQUENCE OF
AttributeDescription
AttributeValue ::= OCTET STRING
AttributeValueAssertion ::= SEQUENCE {
attributeDesc AttributeDescription,
assertionValue AssertionValue }
AssertionValue ::= OCTET STRING
Attribute ::= SEQUENCE {
type AttributeDescription,
vals SET OF AttributeValue }
MatchingRuleId ::= LDAPString
LDAPResult ::= SEQUENCE {
resultCode ENUMERATED {
success (0),
operationsError (1),
protocolError (2),
timeLimitExceeded (3),
sizeLimitExceeded (4),
compareFalse (5),
compareTrue (6),
authMethodNotSupported (7),
strongAuthRequired (8),
-- 9 reserved --
referral (10), -- new
adminLimitExceeded (11), -- new
unavailableCriticalExtension (12), -- new
confidentialityRequired (13), -- new
saslBindInProgress (14), -- new
noSuchAttribute (16),
undefinedAttributeType (17),
inappropriateMatching (18),
constraintViolation (19),
attributeOrValueExists (20),
invalidAttributeSyntax (21),
-- 22-31 unused --
noSuchObject (32),
aliasProblem (33),
invalidDNSyntax (34),
-- 35 reserved for undefined isLeaf --
aliasDereferencingProblem (36),
-- 37-47 unused --
inappropriateAuthentication (48),
invalidCredentials (49),
insufficientAccessRights (50),
busy (51),
unavailable (52),
unwillingToPerform (53),
loopDetect (54),
-- 55-63 unused --
namingViolation (64),
objectClassViolation (65),
notAllowedOnNonLeaf (66),
notAllowedOnRDN (67),
entryAlreadyExists (68),
objectClassModsProhibited (69),
-- 70 reserved for CLDAP --
affectsMultipleDSAs (71), -- new
-- 72-79 unused --
other (80)}
-- 81-90 reserved for APIs --
matchedDN LDAPDN,
errorMessage LDAPString,
referral [3] Referral OPTIONAL }
Referral ::= SEQUENCE OF LDAPURL
LDAPURL ::= LDAPString -- limited to characters permitted in URLs
Controls ::= SEQUENCE OF Control
-- Names changed here for backwards compat with previous
-- Net::LDAP --GMB
Control ::= SEQUENCE {
type LDAPOID, -- controlType
critical BOOLEAN OPTIONAL, -- DEFAULT FALSE, -- criticality
value OCTET STRING OPTIONAL } -- controlValue
BindRequest ::= [APPLICATION 0] SEQUENCE {
version INTEGER, -- (1 .. 127),
name LDAPDN,
authentication AuthenticationChoice }
AuthenticationChoice ::= CHOICE {
simple [0] OCTET STRING,
-- 1 and 2 reserved
sasl [3] SaslCredentials }
SaslCredentials ::= SEQUENCE {
mechanism LDAPString,
credentials OCTET STRING OPTIONAL }
BindResponse ::= [APPLICATION 1] SEQUENCE {
COMPONENTS OF LDAPResult,
serverSaslCreds [7] OCTET STRING OPTIONAL }
UnbindRequest ::= [APPLICATION 2] NULL
SearchRequest ::= [APPLICATION 3] SEQUENCE {
baseObject LDAPDN,
scope ENUMERATED {
baseObject (0),
singleLevel (1),
wholeSubtree (2) }
derefAliases ENUMERATED {
neverDerefAliases (0),
derefInSearching (1),
derefFindingBaseObj (2),
derefAlways (3) }
sizeLimit INTEGER , -- (0 .. maxInt),
timeLimit INTEGER , -- (0 .. maxInt),
typesOnly BOOLEAN,
filter Filter,
attributes AttributeDescriptionList }
Filter ::= CHOICE {
and [0] SET OF Filter,
or [1] SET OF Filter,
not [2] Filter,
equalityMatch [3] AttributeValueAssertion,
substrings [4] SubstringFilter,
greaterOrEqual [5] AttributeValueAssertion,
lessOrEqual [6] AttributeValueAssertion,
present [7] AttributeDescription,
approxMatch [8] AttributeValueAssertion,
extensibleMatch [9] MatchingRuleAssertion }
SubstringFilter ::= SEQUENCE {
type AttributeDescription,
-- at least one must be present
substrings SEQUENCE OF CHOICE {
initial [0] LDAPString,
any [1] LDAPString,
final [2] LDAPString } }
MatchingRuleAssertion ::= SEQUENCE {
matchingRule [1] MatchingRuleId OPTIONAL,
type [2] AttributeDescription OPTIONAL,
matchValue [3] AssertionValue,
dnAttributes [4] BOOLEAN OPTIONAL } -- DEFAULT FALSE }
SearchResultEntry ::= [APPLICATION 4] SEQUENCE {
objectName LDAPDN,
attributes PartialAttributeList }
PartialAttributeList ::= SEQUENCE OF SEQUENCE {
type AttributeDescription,
vals SET OF AttributeValue }
SearchResultReference ::= [APPLICATION 19] SEQUENCE OF LDAPURL
SearchResultDone ::= [APPLICATION 5] LDAPResult
ModifyRequest ::= [APPLICATION 6] SEQUENCE {
object LDAPDN,
modification SEQUENCE OF SEQUENCE {
operation ENUMERATED {
add (0),
delete (1),
replace (2) }
modification AttributeTypeAndValues } }
AttributeTypeAndValues ::= SEQUENCE {
type AttributeDescription,
vals SET OF AttributeValue }
ModifyResponse ::= [APPLICATION 7] LDAPResult
AddRequest ::= [APPLICATION 8] SEQUENCE {
objectName LDAPDN,
attributes AttributeList }
AttributeList ::= SEQUENCE OF SEQUENCE {
type AttributeDescription,
vals SET OF AttributeValue }
AddResponse ::= [APPLICATION 9] LDAPResult
DelRequest ::= [APPLICATION 10] LDAPDN
DelResponse ::= [APPLICATION 11] LDAPResult
ModifyDNRequest ::= [APPLICATION 12] SEQUENCE {
entry LDAPDN,
newrdn RelativeLDAPDN,
deleteoldrdn BOOLEAN,
newSuperior [0] LDAPDN OPTIONAL }
ModifyDNResponse ::= [APPLICATION 13] LDAPResult
CompareRequest ::= [APPLICATION 14] SEQUENCE {
entry LDAPDN,
ava AttributeValueAssertion }
CompareResponse ::= [APPLICATION 15] LDAPResult
AbandonRequest ::= [APPLICATION 16] MessageID
ExtendedRequest ::= [APPLICATION 23] SEQUENCE {
requestName [0] LDAPOID,
requestValue [1] OCTET STRING OPTIONAL }
ExtendedResponse ::= [APPLICATION 24] SEQUENCE {
COMPONENTS OF LDAPResult,
responseName [10] LDAPOID OPTIONAL,
response [11] OCTET STRING OPTIONAL }
VirtualListViewRequest ::= SEQUENCE {
beforeCount INTEGER , --(0 .. maxInt),
afterCount INTEGER , --(0 .. maxInt),
CHOICE {
byoffset [0] SEQUENCE {
offset INTEGER , --(0 .. maxInt),
contentCount INTEGER } --(0 .. maxInt) }
byValue [1] AssertionValue }
-- byValue [1] greaterThanOrEqual assertionValue }
contextID OCTET STRING OPTIONAL }
VirtualListViewResponse ::= SEQUENCE {
targetPosition INTEGER , --(0 .. maxInt),
contentCount INTEGER , --(0 .. maxInt),
virtualListViewResult ENUMERATED {
success (0),
operatonsError (1),
unwillingToPerform (53),
insufficientAccessRights (50),
busy (51),
timeLimitExceeded (3),
adminLimitExceeded (11),
sortControlMissing (60),
indexRangeError (61),
other (80) } }
LDAPEntry ::= COMPONENTS OF AddRequest
-- Current parser does not allow a named entity following the ::=
-- so we use a COMPONENTS OF hack
SortRequestDummy ::= SEQUENCE {
order SEQUENCE OF SEQUENCE {
type OCTET STRING,
orderingRule [0] OCTET STRING OPTIONAL,
reverseOrder [1] BOOLEAN OPTIONAL } }
SortRequest ::= COMPONENTS OF SortRequestDummy
SortResult ::= SEQUENCE {
sortResult ENUMERATED {
success (0), -- results are sorted
operationsError (1), -- server internal failure
timeLimitExceeded (3), -- timelimit reached before
-- sorting was completed
strongAuthRequired (8), -- refused to return sorted
-- results via insecure
-- protocol
adminLimitExceeded (11), -- too many matching entries
-- for the server to sort
noSuchAttribute (16), -- unrecognized attribute
-- type in sort key
inappropriateMatching (18), -- unrecognized or inappro-
-- priate matching rule in
-- sort key
insufficientAccessRights (50), -- refused to return sorted
-- results to this client
busy (51), -- too busy to process
unwillingToPerform (53), -- unable to sort
other (80) }
attributeType [0] AttributeDescription OPTIONAL }
realSearchControlValue ::= SEQUENCE {
size INTEGER, -- (0..maxInt),
-- requested page size from client
-- result set size estimate from server
cookie OCTET STRING }
proxyAuthValue ::= SEQUENCE {
proxyDN LDAPDN
}
LDAP_ASN
1;

55
lib/Net/LDAP/Bind.pm Normal file
View file

@ -0,0 +1,55 @@
# Copyright (c) 1998-2000 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::Bind;
use strict;
use Net::LDAP qw(LDAP_SASL_BIND_IN_PROGRESS LDAP_DECODING_ERROR);
use Net::LDAP::Message;
use vars qw(@ISA);
@ISA = qw(Net::LDAP::Message);
sub _sasl_info {
my $self = shift;
@{$self}{qw(dn saslctrl sasl)} = @_;
}
sub decode {
my $self = shift;
my $result = shift;
my $bind = $result->{protocolOp}{bindResponse}
or $self->set_error(LDAP_DECODING_ERROR,"LDAP decode error")
and return;
return $self->SUPER::decode($result)
unless $bind->{resultCode} == LDAP_SASL_BIND_IN_PROGRESS;
# tell our LDAP client to forget us as this message has now completed
# all communications with the server
$self->parent->_forgetmesg($self);
$self->{mesgid} = Net::LDAP::Message->NewMesgID(); # Get a new message ID
my $sasl = $self->{sasl};
my $ldap = $self->parent;
my $resp = $sasl->challenge($bind->{serverSaslCreds});
$self->encode(
bindRequest => {
version => $ldap->version,
name => $self->{dn},
authentication => {
sasl => {
mechanism => $sasl->name,
credentials => $resp
}
},
control => $self->{saslcontrol}
});
$ldap->_sendmesg($self);
}
1;

108
lib/Net/LDAP/Constant.pm Normal file
View file

@ -0,0 +1,108 @@
# Copyright (c) 1998-2000 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::Constant;
use Exporter ();
@ISA = qw(Exporter);
@EXPORT_OK = ( grep /^LDAP_/, keys %{'Net::LDAP::Constant::'} );
%EXPORT_TAGS = ( 'all' => \@EXPORT_OK );
##
## The constants
##
sub LDAP_SUCCESS () { 0x00 }
sub LDAP_OPERATIONS_ERROR () { 0x01 }
sub LDAP_PROTOCOL_ERROR () { 0x02 }
sub LDAP_TIMELIMIT_EXCEEDED () { 0x03 }
sub LDAP_SIZELIMIT_EXCEEDED () { 0x04 }
sub LDAP_COMPARE_FALSE () { 0x05 }
sub LDAP_COMPARE_TRUE () { 0x06 }
sub LDAP_STRONG_AUTH_NOT_SUPPORTED () { 0x07 }
sub LDAP_AUTH_METHOD_NOT_SUPPORTED () { 0x07 }
sub LDAP_STRONG_AUTH_REQUIRED () { 0x08 }
sub LDAP_PARTIAL_RESULTS () { 0x09 }
sub LDAP_REFERRAL () { 0x0a } # V3
sub LDAP_ADMIN_LIMIT_EXCEEDED () { 0x0b } # V3
sub LDAP_UNAVAILABLE_CRITICAL_EXT () { 0x0c } # V3
sub LDAP_CONFIDENTIALITY_REQUIRED () { 0x0d } # V3
sub LDAP_SASL_BIND_IN_PROGRESS () { 0x0e } # V3
sub LDAP_NO_SUCH_ATTRIBUTE () { 0x10 }
sub LDAP_UNDEFINED_TYPE () { 0x11 }
sub LDAP_INAPPROPRIATE_MATCHING () { 0x12 }
sub LDAP_CONSTRAINT_VIOLATION () { 0x13 }
sub LDAP_TYPE_OR_VALUE_EXISTS () { 0x14 }
sub LDAP_INVALID_SYNTAX () { 0x15 }
sub LDAP_NO_SUCH_OBJECT () { 0x20 }
sub LDAP_ALIAS_PROBLEM () { 0x21 }
sub LDAP_INVALID_DN_SYNTAX () { 0x22 }
sub LDAP_IS_LEAF () { 0x23 }
sub LDAP_ALIAS_DEREF_PROBLEM () { 0x24 }
sub LDAP_INAPPROPRIATE_AUTH () { 0x30 }
sub LDAP_INVALID_CREDENTIALS () { 0x31 }
sub LDAP_INSUFFICIENT_ACCESS () { 0x32 }
sub LDAP_BUSY () { 0x33 }
sub LDAP_UNAVAILABLE () { 0x34 }
sub LDAP_UNWILLING_TO_PERFORM () { 0x35 }
sub LDAP_LOOP_DETECT () { 0x36 }
sub LDAP_SORT_CONTROL_MISSING () { 0x3C }
sub LDAP_INDEX_RANGE_ERROR () { 0x3D }
sub LDAP_NAMING_VIOLATION () { 0x40 }
sub LDAP_OBJECT_CLASS_VIOLATION () { 0x41 }
sub LDAP_NOT_ALLOWED_ON_NONLEAF () { 0x42 }
sub LDAP_NOT_ALLOWED_ON_RDN () { 0x43 }
sub LDAP_ALREADY_EXISTS () { 0x44 }
sub LDAP_NO_OBJECT_CLASS_MODS () { 0x45 }
sub LDAP_RESULTS_TOO_LARGE () { 0x46 }
sub LDAP_AFFECTS_MULTIPLE_DSAS () { 0x47 } # V3
sub LDAP_OTHER () { 0x50 }
sub LDAP_SERVER_DOWN () { 0x51 }
sub LDAP_LOCAL_ERROR () { 0x52 }
sub LDAP_ENCODING_ERROR () { 0x53 }
sub LDAP_DECODING_ERROR () { 0x54 }
sub LDAP_TIMEOUT () { 0x55 }
sub LDAP_AUTH_UNKNOWN () { 0x56 }
sub LDAP_FILTER_ERROR () { 0x57 }
sub LDAP_USER_CANCELED () { 0x58 }
sub LDAP_PARAM_ERROR () { 0x59 }
sub LDAP_NO_MEMORY () { 0x5a }
sub LDAP_CONNECT_ERROR () { 0x5b }
sub LDAP_NOT_SUPPORTED () { 0x5c } # V3
sub LDAP_CONTROL_NOT_FOUND () { 0x5d } # V3
sub LDAP_NO_RESULTS_RETURNED () { 0x5e } # V3
sub LDAP_MORE_RESULTS_TO_RETURN () { 0x5f } # V3
sub LDAP_CLIENT_LOOP () { 0x60 } # V3
sub LDAP_REFERRAL_LIMIT_EXCEEDED () { 0x61 } # V3
# LDAP Controls
sub LDAP_CONTROL_SORTREQUEST () { "1.2.840.113556.1.4.473" }
sub LDAP_CONTROL_SORTRESULT () { "1.2.840.113556.1.4.474" }
sub LDAP_CONTROL_VLVREQUEST () { "2.16.840.1.113730.3.4.9" }
sub LDAP_CONTROL_VLVRESPONSE () { "2.16.840.1.113730.3.4.10" }
sub LDAP_CONTROL_PROXYAUTHENTICATION () { "2.16.840.1.113730.3.4.12" }
sub LDAP_CONTROL_PAGED () { "1.2.840.113556.1.4.319" }
sub LDAP_CONTROL_MATCHEDVALS () { "1.2.826.0.1.3344810.2.2" }
sub LDAP_CONTROL_MANAGEDSAIT () { "2.16.840.1.113730.3.4.2" }
sub LDAP_CONTROL_PERSISTENTSEARCH () { "2.16.840.1.113730.3.4.3" }
sub LDAP_CONTROL_ENTRYCHANGE () { "2.16.840.1.113730.3.4.7" }
# Password information sent back to client
sub LDAP_CONTROL_PWEXPIRED () { "2.16.840.1.113730.3.4.4" }
sub LDAP_CONTROL_PWEXPIRING () { "2.16.840.1.113730.3.4.5" }
# Client controls we know about
sub LDAP_CONTROL_REFERRALS () { "1.2.840.113556.1.4.616" }
1;

347
lib/Net/LDAP/Constant.pod Normal file
View file

@ -0,0 +1,347 @@
=head1 NAME
Net::LDAP::Constant - Constants for use with Net::LDAP
=head1 SYNOPSIS
use Net::LDAP qw(LDAP_SUCCESS LDAP_PROTOCOL_ERROR);
# import all constants
use Net::LDAP qw(:all);
=head1 DESCRIPTION
B<Net::LDAP::Constant> exports constant subroutines for the following LDAP
error codes.
=over 4
=item LDAP_SUCCESS
Operation completed without error
=item LDAP_OPERATIONS_ERROR
Server encountered an internal error
=item LDAP_PROTOCOL_ERROR
Unrecognized version number or incorrect PDU structure
=item LDAP_TIMELIMIT_EXCEEDED
The time limit on a search operation has been exceeded
=item LDAP_SIZELIMIT_EXCEEDED
The maximum number of search results to return has been exceeded.
=item LDAP_COMPARE_FALSE
This code is returned when a compare request completes and the attribute value
given is not in the entry specified
=item LDAP_COMPARE_TRUE
This code is returned when a compare request completes and the attribute value
given is in the entry specified
=item LDAP_AUTH_METHOD_NOT_SUPPORTED
Unrecognized SASL mechanism name
=item LDAP_STRONG_AUTH_REQUIRED
The server requires authentication be performed with a SASL mechanism
=item LDAP_PARTIAL_RESULTS
Returned to version 2 clients when a referral is returned. The response
will contain a list of URL's for other servers.
=item LDAP_REFERRAL
The server is referring the client to another server. The response will
contain a list of URL's
=item LDAP_ADMIN_LIMIT_EXCEEDED
The server has exceed the maximum number of entries to search while gathering
a list of search result candidates
=item LDAP_UNAVAILABLE_CRITICAL_EXT
A control or matching rule specified in the request is not supported by
the server
=item LDAP_CONFIDENTIALITY_REQUIRED
This result code is returned when confidentiality is required to perform
a given operation
=item LDAP_SASL_BIND_IN_PROGRESS
The server requires the client to send a new bind request, with the same SASL
mechanism, to continue the authentication process
=item LDAP_NO_SUCH_ATTRIBUTE
The request referenced an attribute that does not exist
=item LDAP_UNDEFINED_TYPE
The request contains an undefined attribute type
=item LDAP_INAPPROPRIATE_MATCHING
An extensible matching rule in the given filter does not apply to the specified
attribute
=item LDAP_CONSTRAINT_VIOLATION
The request contains a value which does not meet with certain constraints.
This result can be returned as a consequence of
=over 8
=item *
The request was to add or modify a user password, and the password fails to
meet the criteria the server is configured to check. This could be that the
password is too short, or a recognizable word (e.g. it matches one of the
attributes in the users entry) or it matches a previous password used by
the same user.
=item *
The request is a bind request to a user account that has been locked
=back
=item LDAP_TYPE_OR_VALUE_EXISTS
The request attempted to add an attribute type or value that already exists
=item LDAP_INVALID_SYNTAX
Some part of the request contained an invalid syntax. It could be a search
with an invalid filter or a request to modify the schema and the given
schema has a bad syntax.
=item LDAP_NO_SUCH_OBJECT
The server cannot find an object specified in the request
=item LDAP_ALIAS_PROBLEM
Server encountered a problem while attempting to dereference an alias
=item LDAP_INVALID_DN_SYNTAX
The request contained an invalid DN
=item LDAP_IS_LEAF
The specified entry is a leaf entry
=item LDAP_ALIAS_DEREF_PROBLEM
Server encountered a problem while attempting to dereference an alias
=item LDAP_INAPPROPRIATE_AUTH
The server requires the client which had attempted to bind anonymously or
without supplying credentials to provide some form of credentials
=item LDAP_INVALID_CREDENTIALS
The wrong password was supplied or the SASL credentials could not be processed
=item LDAP_INSUFFICIENT_ACCESS
The client does not have sufficient access to perform the requested
operation
=item LDAP_BUSY
The server is too busy to perform requested operation
=item LDAP_UNAVAILABLE
The server in unavailable to perform the request, or the server is
shutting down
=item LDAP_UNWILLING_TO_PERFORM
The server is unwilling to perform the requested operation
=item LDAP_LOOP_DETECT
The server was unable to perform the request due to an internal loop detected
=item LDAP_SORT_CONTROL_MISSING
The search contained a "virtual list view" control, but not a server-side
sorting control, which is required when a "virtual list view" is given.
=item LDAP_INDEX_RANGE_ERROR
The search contained a control for a "virtual list view" and the results
exceeded the range specified by the requested offsets.
=item LDAP_NAMING_VIOLATION
The request violates the structure of the DIT
=item LDAP_OBJECT_CLASS_VIOLATION
The request specifies a change to an existing entry or the addition of a new
entry that does not comply with the servers schema
=item LDAP_NOT_ALLOWED_ON_NONLEAF
The requested operation is not allowed on an entry that has child entries
=item LDAP_NOT_ALLOWED_ON_RDN
The requested operation ill affect the RDN of the entry
=item LDAP_ALREADY_EXISTS
The client attempted to add an entry that already exists. This can occur as
a result of
=over 8
=item *
An add request was submitted with a DN that already exists
=item *
A modify DN requested was submitted, where the requested new DN already exists
=item *
The request is adding an attribute to the schema and an attribute with the
given OID or name already exists
=back
=item LDAP_NO_OBJECT_CLASS_MODS
Request attempt to modify the object class of an entry that should not be
modified
=item LDAP_RESULTS_TOO_LARGE
The results of the request are to large
=item LDAP_AFFECTS_MULTIPLE_DSAS
The requested operation needs to be performed on multiple servers where
the requested operation is not permitted
=item LDAP_OTHER
An unknown error has occurred
=item LDAP_SERVER_DOWN
C<Net::LDAP> cannot establish a connection or the connection has been lost
=item LDAP_LOCAL_ERROR
An error occurred in C<Net::LDAP>
=item LDAP_ENCODING_ERROR
C<Net::LDAP> encountered an error while encoding the request packet that would
have been sent to the server
=item LDAP_DECODING_ERROR
C<Net::LDAP> encountered an error while decoding a response packet from
the server.
=item LDAP_TIMEOUT
C<Net::LDAP> timeout while waiting for a response from the server
=item LDAP_AUTH_UNKNOWN
The method of authentication requested in a bind request is unknown to
the server
=item LDAP_FILTER_ERROR
An error occurred while encoding the given search filter.
=item LDAP_USER_CANCELED
The user canceled the operation
=item LDAP_PARAM_ERROR
An invalid parameter was specified
=item LDAP_NO_MEMORY
Out of memory error
=item LDAP_CONNECT_ERROR
A connection to the server could not be established
=item LDAP_NOT_SUPPORTED
An attempt has been made to use a feature not supported by Net::LDAP
=item LDAP_CONTROL_NOT_FOUND
The controls required to perform the requested operation were not
found.
=item LDAP_NO_RESULTS_RETURNED
No results were returned from the server.
=item LDAP_MORE_RESULTS_TO_RETURN
There are more results in the chain of results.
=item LDAP_CLIENT_LOOP
A loop has been detected. For example when following referals.
=item LDAP_REFERRAL_LIMIT_EXCEEDED
The referral hop limit has been exceeded.
=back
=head1 SEE ALSO
L<Net::LDAP>,
L<Net::LDAP::Message>
=head1 AUTHOR
Graham Barr <gbarr@pobox.com>
Please report any bugs, or post any suggestions, to the perl-ldap mailing list
<perl-ldap-dev@lists.sourceforge.net>
=head1 COPYRIGHT
Copyright (c) 1998-2000 Graham Barr. All rights reserved. This program is
free software; you can redistribute it and/or modify it under the same
terms as Perl itself.
=for html <hr>
I<$Id$>
=cut

290
lib/Net/LDAP/Control.pm Normal file
View file

@ -0,0 +1,290 @@
# $Id$
# Copyright (c) 1999-2000 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::Control;
use Net::LDAP::Constant qw(/^LDAP_CONTROL/);
use vars qw($VERSION);
use strict;
$VERSION = "0.04";
my %Pkg2Type = (
'Net::LDAP::Control::Sort' => LDAP_CONTROL_SORTREQUEST,
'Net::LDAP::Control::SortResult' => LDAP_CONTROL_SORTRESULT,
'Net::LDAP::Control::VLV' => LDAP_CONTROL_VLVREQUEST,
'Net::LDAP::Control::VLVResponse' => LDAP_CONTROL_VLVRESPONSE,
'Net::LDAP::Control::Paged' => LDAP_CONTROL_PAGED,
'Net::LDAP::Control::ProxyAuth' => LDAP_CONTROL_PROXYAUTHENTICATION,
#LDAP_CONTROL_MANAGEDSAIT
#LDAP_CONTROL_PERSISTENTSEARCH
#LDAP_CONTROL_ENTRYCHANGE
#
#LDAP_CONTROL_PWEXPIRED
#LDAP_CONTROL_PWEXPIRING
#
#LDAP_CONTROL_REFERRALS
);
my %Type2Pkg = reverse %Pkg2Type;
sub register {
my($class,$oid) = @_;
require Carp and Carp::croak("$oid is already registered to $Type2Pkg{$oid}")
if exists $Type2Pkg{$oid} and $Type2Pkg{$oid} ne $class;
require Carp and Carp::croak("$class is already registered to $Pkg2Type{$class}")
if exists $Pkg2Type{$class} and $Pkg2Type{$class} ne $oid;
$Type2Pkg{$oid} = $class;
$Pkg2Type{$class} = $oid;
}
sub new {
my $self = shift;
my $pkg = ref($self) || $self;
my $oid = (@_ & 1) ? shift : undef;
my %args = @_;
$args{'type'} ||= $oid || $Pkg2Type{$pkg} || '';
unless ($args{type} =~ /^\d+(?:\.\d+)+$/) {
$args{error} = 'Invalid OID';
return bless \%args;
}
if ($pkg eq __PACKAGE__ and exists $Type2Pkg{$args{type}}) {
$pkg = $Type2Pkg{$args{type}};
eval "require $pkg" or die $@;
}
delete $args{error};
bless(\%args, $pkg)->init;
}
sub from_asn {
my $self = shift;
my $asn = shift;
my $class = ref($self) || $self;
if ($class eq __PACKAGE__ and exists $Type2Pkg{$asn->{type}}) {
$class = $Type2Pkg{$asn->{type}};
eval "require $class" or die $@;
}
delete $asn->{error};
bless($asn, $class)->init;
}
sub to_asn {
my $self = shift;
$self->value; # Ensure value is there
delete $self->{critical} unless $self->{critical};
$self;
}
sub critical {
my $self = shift;
$self->{critical} = shift if @_;
$self->{critical} || 0;
}
sub value {
my $self = shift;
$self->{value} = shift if @_;
$self->{value} || undef
}
sub type { shift->{type} }
sub valid { ! exists shift->{error} }
sub error { shift->{error} }
sub init { shift }
1;
__END__
=head1 NAME
Net::LDAP::Control - LDAPv3 control object base class
=head1 SYNOPSIS
use Net::LDAP::Control;
use Net::LDAP::Constant qw( LDAP_CONTROL_MATCHEDVALS );
$ctrl = Net::LDAP::Control->new(
type => "1.2.3.4",
value => "help",
critical => 0
);
$mesg = $ldap->search( @args, control => [ $ctrl ]);
$ctrl = Net::LDAP::Control->new( type => LDAP_CONTROL_MATCHEDVALS );
=head1 DESCRIPTION
C<Net::LDAP::Control> is a base-class for LDAPv3 control objects.
=cut
##
## Need more blurb in here about controls
##
=head1 CONSTRUCTORS
=over 4
=item new ARGS
ARGS is a list of name/value pairs, valid arguments are.
=over 4
=item critical
A booloean value, if TRUE and the control is unrecognized by the server or
is inappropriate for the requested operation then the server will return
an error and the operation will not be performed.
If FALSE and the control is unrecognized by the server or
is inappropriate for the requested operation then the server will ignore
the control and perform the requested operation as if the control was
not given.
If absent, FALSE is assume.
=item type
A dotted-decimal representation of an OBJECT IDENTIFIER which
uniquely identifies the control. This prevents conflicts between
control names.
This may be ommitted if the contructor is being called on a sub-class of
Net::LDAP::Control which has registered to be associated with an OID.
If the contructor is being called on the Net::LDAP::Control
package, then this argument must be given. If the given OID has been
registered by a package, then the returned object will be of the type
registered to handle that OID.
=item value
Optional information associated with the control. It's format is specific
to the particular control.
=back
=item from_asn ASN
ASN is a HASH reference, normally extracted from a PDU. It will contain
a C<type> element and optionally C<critical> and C<value> elements. On
return ASN will be blessed into a package. If C<type> is a registered
OID, then ASN will be blessed into the registered package, if not then ASN
will be blessed into Net::LDAP::Control.
This constructor is used internally by Net::LDAP and assumes that HASH
passed contains a valid control. It should be used with B<caution>.
=back
=head1 METHODS
In addition to the methods listed below, each of the named parameters
to C<new> is also avaliable as a method. C<type> will return the OID of
the control object. C<value> and C<critical> are set/get methods and will
return the current value for each attribute if called without arguments,
but may also be called with arguments to set new values.
=over 4
=item error
If there has been an error returns a description of the error, otherwise it will
return C<undef>
=item init
C<init> will be called as the last step in both contructors. What it does will depend
on the sub-class. It must always return the object.
=item register OID
C<register> is provided for sub-class implementors. It should be called as a class method
on a sub-class of Net::LDAP::Control with the OID that the class will handle. Net::LDAP::Control
will remember this class and OID pair and use it in the following
situations.
=over 4
=item *
C<new> is called as a class method on the Net::LDAP::Control package and OID is passed
as the type. The returned object will be blessed into the package that registered
the OID.
=item *
C<new> is called as a class method on a registered package and the C<type> is not
specified. The C<type> will be set to the OID registered by that package.
=item *
C<from_asn> is called to construct an object from ASN. The returned object will be
blessed into the package which was registered to handle the OID in the ASN.
=back
=item to_asn
Returns a structure suitable for passing to Convert::ASN1 for
encoding. This method will be called by L<Net::LDAP> when the
control is used.
The base class implementation of this method will call the C<value> method
without arguments to allow a sub-class to encode it's value. Sub-classes
should not need to override this method.
=item valid
Returns true if the object is valid and can be encoded. The default implementation
for this method is to return TRUE if there is no error, but sub-classes may override that.
=back
=head1 SEE ALSO
L<Net::LDAP>
=head1 AUTHOR
Graham Barr <gbarr@pobox.com>
Please report any bugs, or post any suggestions, to the perl-ldap mailing list
<perl-ldap-dev@lists.sourceforge.net>
=head1 COPYRIGHT
Copyright (c) 1999-2000 Graham Barr. All rights reserved. This program is
free software; you can redistribute it and/or modify it under the same
terms as Perl itself.
=for html <hr>
I<$Id$>
=cut

View file

@ -0,0 +1,165 @@
# $Id$
# Copyright (c) 2000 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::Control::Paged;
use vars qw(@ISA $VERSION);
use Net::LDAP::Control;
@ISA = qw(Net::LDAP::Control);
$VERSION = "0.01";
use Net::LDAP::ASN qw(realSearchControlValue);
use strict;
sub init {
my($self) = @_;
delete $self->{asn};
unless (exists $self->{value}) {
$self->{asn} = {
size => $self->{size} || 0,
cookie => defined($self->{cookie}) ? $self->{cookie} : ''
};
}
$self;
}
sub cookie {
my $self = shift;
$self->{asn} ||= $realSearchControlValue->decode($self->{value});
if (@_) {
delete $self->{value};
return $self->{asn}{cookie} = defined($_[0]) ? $_[0] : '';
}
$self->{asn}{cookie};
}
sub size {
my $self = shift;
$self->{asn} ||= $realSearchControlValue->decode($self->{value});
if (@_) {
delete $self->{value};
return $self->{asn}{size} = shift || 0;
}
$self->{asn}{size};
}
sub value {
my $self = shift;
exists $self->{value}
? $self->{value}
: $self->{value} = $realSearchControlValue->encode($self->{asn});
}
1;
__END__
=head1 NAME
Net::LDAP::Control::Paged - LDAPv3 Paged results control object
=head1 SYNOPSIS
use Net::LDAP;
use Net::LDAP::Control::Paged;
use Net::LDAP::Constant qw( LDAP_CONTROL_PAGED );
$ldap = Net::LDAP->new( "ldap.mydomain.eg" );
$page = Net::LDAP::Control::Paged->new( size => 100 );
@args = ( base => "cn=subnets,cn=sites,cn=configuration,$BASE_DN",
scope => "subtree",
filter => "(objectClass=subnet)",
callback => \&process_entry, # Call this sub for each entry
control => [ $page ],
);
my $cookie;
while(1) {
# Perform search
my $mesg = $ldap->search( @args );
# Only continue on LDAP_SUCCESS
$mesg->code and last;
# Get cookie from paged control
my($resp) = $mesg->control( LDAP_CONTROL_PAGED ) or last;
$cookie = $resp->cookie or last;
# Set cookie in paged control
$page->cookie($cookie);
}
if ($cookie) {
# We had an abnormal exit, so let the server know we do not want any more
$page->cookie($cookie);
$page->size(0);
$ldap->search( @args );
}
=head1 DESCRIPTION
C<Net::LDAP::Control::Paged> provides an interface for the creation and manipulatrion
of objects that represent the C<pagedResultsControl> as described by RFC-2696.
=head1 CONSTRUCTOR ARGUMENTS
In addition to the constructor arguments described in
L<Net::LDAP::Control> the following are provided.
=over 4
=item cookie
The value to use as the cookie. This is not normally set when an object is
created, but is set from the cookie value returned bu the server. This associates
a search with a previous search, so the server knows to return the page
of entries following the entries it returned the previous time.
=item size
The page size that is required. This is the maximum number of entries that the
server will return to the search request.
=back
=head1 METHODS
As with L<Net::LDAP::Control> each constructor argument
described above is also avaliable as a method on the object which will
return the current value for the attribute if called without an argument,
and set a new value for the attribute if called with an argument.
=head1 SEE ALSO
L<Net::LDAP>,
L<Net::LDAP::Control>,
http://info.internet.isi.edu/in-notes/rfc/files/rfc2696.txt
=head1 AUTHOR
Graham Barr <gbarr@pobox.com>
Please report any bugs, or post any suggestions, to the perl-ldap mailing list
<perl-ldap-dev@lists.sourceforge.net>
=head1 COPYRIGHT
Copyright (c) 2000 Graham Barr. All rights reserved. This program is
free software; you can redistribute it and/or modify it under the same
terms as Perl itself.
=for html <hr>
I<$Id$>
=cut

View file

@ -0,0 +1,134 @@
# $Id$
# Copyright (c) 2001 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::Control::ProxyAuth;
use vars qw(@ISA $VERSION);
use Net::LDAP::Control;
@ISA = qw(Net::LDAP::Control);
$VERSION = do { my @r=(q$Revision$=~/\d+/g); sprintf "%d."."%02d"x$#r,@r};
use Net::LDAP::ASN qw(proxyAuthValue);
use strict;
sub init {
my($self) = @_;
delete $self->{asn};
unless (exists $self->{value}) {
$self->{asn} = {
proxyDN => $self->{proxyDN} || '',
};
}
$self->{critical}=1;
$self;
}
sub proxyDN {
my $self = shift;
$self->{asn} ||= $proxyAuthValue->decode($self->{value});
if (@_) {
delete $self->{value};
return $self->{asn}{proxyDN} = shift || 0;
}
$self->{asn}{proxyDN};
}
sub value {
my $self = shift;
exists $self->{value}
? $self->{value}
: $self->{value} = $proxyAuthValue->encode($self->{asn});
}
1;
__END__
=head1 NAME
Net::LDAP::Control::ProxyAuth - LDAPv3 Proxy Authentication control object
=head1 SYNOPSIS
use Net::LDAP;
use Net::LDAP::Control::ProxyAuth;
$ldap = Net::LDAP->new( "ldap.mydomain.eg" );
$auth = Net::LDAP::Control::ProxyAuth->new( proxyDN => 'cn=me,ou=people,o=myorg.com' );
@args = ( base => "cn=subnets,cn=sites,cn=configuration,$BASE_DN",
scope => "subtree",
filter => "(objectClass=subnet)",
callback => \&process_entry, # Call this sub for each entry
control => [ $auth ],
);
while(1) {
# Perform search
my $mesg = $ldap->search( @args );
# Only continue on LDAP_SUCCESS
$mesg->code and last;
}
=head1 DESCRIPTION
C<Net::LDAP::Control::ProxyAuth> provides an interface for the creation and manipulation
of objects that represent the C<proxyauthorisationControl> as described by draft-weltman-ldapv3-proxy-05.txt.
=head1 CONSTRUCTOR ARGUMENTS
In addition to the constructor arguments described in
L<Net::LDAP::Control> the following are provided.
=over 4
=item proxyDN
The proxyDN that is required. This is the identity we are requesting operations to use
=back
=head1 METHODS
As with L<Net::LDAP::Control> each constructor argument
described above is also available as a method on the object which will
return the current value for the attribute if called without an argument,
and set a new value for the attribute if called with an argument.
=head1 SEE ALSO
L<Net::LDAP>,
L<Net::LDAP::Control>,
http://info.internet.isi.edu/in-notes/rfc/files/rfc2696.txt
=head1 AUTHOR
Olivier Dubois, Swift sa/nv based on Net::LDAP::Control::Page from Graham Barr <gbarr@pobox.com>
Please report any bugs, or post any suggestions, to the perl-ldap mailing list
<perl-ldap-dev@lists.sourceforge.net>
=head1 COPYRIGHT
Copyright (c) 2001 Graham Barr. All rights reserved. This program is
free software; you can redistribute it and/or modify it under the same
terms as Perl itself.
=for html <hr>
I<$Id$>
=cut

View file

@ -0,0 +1,205 @@
# $Id$
# Copyright (c) 1999-2000 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::Control::Sort;
use vars qw(@ISA $VERSION);
use Net::LDAP::Control;
@ISA = qw(Net::LDAP::Control);
$VERSION = "0.01";
use Net::LDAP::ASN qw(SortRequest);
use strict;
sub init {
my($self) = @_;
if (exists $self->{value}) {
$self->value($self->{value});
}
elsif (exists $self->{order}) {
$self->order(ref($self->{order}) ? @{$self->{order}} : $self->{order});
}
$self;
}
sub value {
my $self = shift;
if (@_) {
my $value = shift;
delete $self->{value};
delete $self->{order};
delete $self->{error};
my $asn = $SortRequest->decode($value);
unless ($asn) {
$self->{error} = $@;
return undef;
}
$self->{order} = [ map {
($_->{reverseOrder} ? "-" : "")
. $_->{type}
. (defined($_->{orderingRule}) ? ":$_->{orderingRule}" : "")
} @{$asn->{order}}];
return $self->{value} = $value;
}
unless (defined $self->{value}) {
$self->{value} = $SortRequest->encode(
order => [
map {
/^(-)?([^:]+)(?::(.+))?/;
{
type => $2,
(defined $1 ? (reverseOrder => 1) : ()),
(defined $3 ? (orderingRule => $3) : ())
}
} @{$self->{order} || []}
]
) or $self->{error} = $@;
}
$self->{value};
}
sub valid { exists shift->{order} }
sub order {
my $self = shift;
if (@_) {
# @_ can either be a list, or a single item.
# if a single item it can be a string, which needs
# to be split on spaces, or a reference to a list
#
# Each element has three parts
# leading - (optional)
# an attribute name
# :match-rule (optional)
my @order = (@_ == 1) ? split(/\s+/, $_[0]) : @_;
delete $self->{'value'};
delete $self->{order};
delete $self->{error};
foreach (@order) {
next if /^-?[^:]+(?::.+)?$/;
$self->{error} = "Bad order argument '$_'";
return;
}
$self->{order} = \@order;
}
return @{$self->{order}};
}
1;
__END__
=head1 NAME
Net::LDAP::Control::Sort - Server Side Sort (SSS) control object
=head1 SYNOPSIS
use Net::LDAP::Control::Sort;
use Net::LDAP::Constant qw(LDAP_CONTROL_SORTRESULT);
$sort = Net::LDAP::Control::Sort->new(
order => "cn -phone"
);
$mesg = $ldap->search( @args, control => [ $sort ]);
($resp) = $mesg->control( LDAP_CONTROL_SORTRESULT );
print "Results are sorted\n" if $resp and !$resp->result;
=head1 DESCRIPTION
C<Net::LDAP::Control::Sort> is a sub-class of
L<Net::LDAP::Control>. It provides a class
for manipulating the LDAP Server Side Sort (SSS) request control
C<1.2.840.113556.1.4.473> as defined in RFC-2891
If the server supports sorting, then the response from a search
operation will include a sort result control. This control is handled
by L<Net::LDAP::Control::SortResult>.
=head1 CONSTRUCTOR ARGUMENTS
=over 4
=item order
A string which defines how entries may be sorted. It consists of
multiple directives, spearated by whitespace. Each directive describes how
to sort entries using a single attribute. If two entries have identical
attributes, then the next directive in the list is used.
Each directive specifies a sorting order as follows
-attributeType:orderingRule
The leading C<-> is optional, and if present indicates that the sorting order should
be reversed. C<attributeType> is the attribute name to sort by. C<orderingRule> is optional and
indicates the rule to use for the sort and should be valid for the given C<attributeType>.
Any one attributeType should only appear once in the sorting list.
B<Examples>
"cn" sort by cn using the default ordering rule for the cn attribute
"-cn" sort by cn using the reverse of the default ordering rule
"age cn" sort by age first, then by cn using the default ordering rules
"cn:1.2.3.4" sort by cn using the ordering rule defined as 1.2.3.4
=back
=head1 METHODS
As with L<Net::LDAP::Control> each constructor argument
described above is also avaliable as a method on the object which will
return the current value for the attribute if called without an argument,
and set a new value for the attribute if called with an argument.
=head1 SEE ALSO
L<Net::LDAP>,
L<Net::LDAP::Control::SortResult>,
L<Net::LDAP::Control>,
http://info.internet.isi.edu/in-notes/rfc/files/rfc2891.txt
=head1 AUTHOR
Graham Barr <gbarr@pobox.com>
Please report any bugs, or post any suggestions, to the perl-ldap mailing list
<perl-ldap-dev@lists.sourceforge.net>
=head1 COPYRIGHT
Copyright (c) 1999-2000 Graham Barr. All rights reserved. This program is
free software; you can redistribute it and/or modify it under the same
terms as Perl itself.
=for html <hr>
I<$Id$>
=cut

View file

@ -0,0 +1,178 @@
# $Id$
# Copyright (c) 1999-2000 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::Control::SortResult;
use Net::LDAP::ASN qw(SortResult);
use Net::LDAP::Control;
@ISA = qw(Net::LDAP::Control);
sub init {
my($self) = @_;
if (exists $self->{value}) {
$self->{asn} = $SortResult->decode(delete $self->{value});
}
else {
$self->{asn} = { sortResult => delete $self->{result} };
$self->{asn}{attributeType} = delete $self->{attr} if exists $self->{attr};
}
$self;
}
sub value {
my $self = shift;
$self->{value} = $SortResult->encode($self->{asn});
}
sub result {
my $self = shift;
@_ ? ($self->{asn}{sortResult}=shift)
: $self->{asn}{sortResult};
}
sub attr {
my $self = shift;
@_ ? ($self->{asn}{attributeType}=shift)
: $self->{asn}{attributeType};
}
1;
__END__
=head1 NAME
Net::LDAP::Control::SortResult - Server Side Sort (SSS) result control object
=head1 SYNOPSIS
use Net::LDAP::Control::Sort;
use Net::LDAP::Constant qw(LDAP_CONTROL_SORTRESULT);
use Net::LDAP::Util qw(ldap_error_name);
$sort = Net::LDAP::Control::Sort->new(
order => "cn -age"
);
$mesg = $ldap->search( @args, control => [ $sort ]);
($resp) = $mesg->control( LDAP_CONTROL_SORTRESULT );
if ($resp) {
if ($resp->result) {
my $attr = $resp->attr;
print "Problem sorting, ",ldap_error_name($resp->result);
print " ($attr)" if $attr;
print "\n";
}
else {
print "Results are sorted\n";
}
}
else {
print "Server does not support sorting\n";
}
=head1 DESCRIPTION
C<Net::LDAP::Control::SortResult> is a sub-class of
L<Net::LDAP::Control>. It provides a class for
manipulating the LDAP sort request control C<1.2.840.113556.1.4.474>
as defined in RFC-2891
A sort result control will be returned by the server in response to
a search with a Server Side Sort control. If a sort result control is
not returned then the user may assume that the server does not support
sorting and the results are not sorted.
=head1 CONSTRUCTOR ARGUMENTS
=over 4
=item attr
If C<result> indicates that there was a problem with sorting and that problem was
due to one of the attributes specified in the sort control. C<attr> is set to
the name of the attribute causing the problem.
=item result
This is the result code that describes if the sort operation was sucessful. If will
be one of the result codes describes below.
=back
=head1 METHODS
As with L<Net::LDAP::Control> each constructor argument
described above is also avaliable as a method on the object which will
return the current value for the attribute if called without an argument,
and set a new value for the attribute if called with an argument.
=head1 RESULT CODES
Possible results from a sort request are listed below. See L<Net::LDAP::Constant> for
a definition of each.
=over 4
=item LDAP_SUCCESS
=item LDAP_OPERATIONS_ERROR
=item LDAP_TIMELIMIT_EXCEEDED
=item LDAP_STRONG_AUTH_REQUIRED
=item LDAP_ADMIN_LIMIT_EXCEEDED
=item LDAP_NO_SUCH_ATTRIBUTE
=item LDAP_INAPPROPRIATE_MATCHING
=item LDAP_INSUFFICIENT_ACCESS
=item LDAP_BUSY
=item LDAP_UNWILLING_TO_PERFORM
=item LDAP_OTHER
=back
=head1 SEE ALSO
L<Net::LDAP>,
L<Net::LDAP::Control::Sort>,
L<Net::LDAP::Control>,
http://info.internet.isi.edu/in-notes/rfc/files/rfc2891.txt
=head1 AUTHOR
Graham Barr <gbarr@pobox.com>
Please report any bugs, or post any suggestions, to the perl-ldap mailing list
<perl-ldap-dev@lists.sourceforge.net>
=head1 COPYRIGHT
Copyright (c) 1999-2000 Graham Barr. All rights reserved. This program is
free software; you can redistribute it and/or modify it under the same
terms as Perl itself.
=for html <hr>
I<$Id$>
=cut

403
lib/Net/LDAP/Control/VLV.pm Normal file
View file

@ -0,0 +1,403 @@
# $Id$
# Copyright (c) 2000 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::Control::VLV;
use vars qw(@ISA $VERSION);
use Net::LDAP::Control;
@ISA = qw(Net::LDAP::Control);
$VERSION = "0.02";
use Net::LDAP::ASN qw(VirtualListViewRequest);
use strict;
sub init {
my($self) = @_;
# VLVREQUEST should always have a critical of true
$self->{'critical'} = 1 unless exists $self->{'critical'};
if (exists $self->{value}) {
$self->value($self->{value});
}
else {
my $asn = $self->{asn} = {};
$asn->{beforeCount} = $self->{before} || 0;
$asn->{afterCount} = $self->{after} || 0;
if (exists $self->{assert}) {
$asn->{byValue} = $self->{assert};
}
else {
$asn->{byoffset} = {
offset => $self->{offset} || 0,
contentCount => $self->{content} || 0
};
}
}
$self;
}
sub before {
my $self = shift;
if (@_) {
delete $self->{value};
return $self->{asn}{beforeCount} = shift;
}
$self->{asn}{beforeCount};
}
sub after {
my $self = shift;
if (@_) {
delete $self->{value};
return $self->{asn}{afterCount} = shift;
}
$self->{asn}{afterCount};
}
sub content {
my $self = shift;
if (@_) {
delete $self->{value};
if (exists $self->{asn}{byValue}) {
delete $self->{asn}{byValue};
$self->{asn}{byoffset} = { offset => 0 };
}
return $self->{asn}{byoffset}{contentCount} = shift;
}
exists $self->{asn}{byoffset}
? $self->{asn}{byoffset}{contentCount}
: undef;
}
sub assert {
my $self = shift;
if (@_) {
delete $self->{value};
delete $self->{asn}{byoffset};
return $self->{asn}{byValue} = shift;
}
exists $self->{asn}{byValue}
? $self->{asn}{byValue}
: undef;
}
sub context {
my $self = shift;
if (@_) {
delete $self->{value};
return $self->{asn}{contextID} = shift;
}
$self->{asn}{contextID};
}
# Update self with values from a response
sub response {
my $self = shift;
my $resp = shift;
my $asn = $self->{asn};
$asn->{contextID} = $resp->context;
$asn->{byoffset} = {
offset => $resp->target,
contentCount => $resp->content
};
delete $asn->{byValue};
1;
}
sub offset {
my $self = shift;
if (@_) {
delete $self->{value};
if (exists $self->{asn}{byValue}) {
delete $self->{asn}{byValue};
$self->{asn}{byoffset} = { contentCount => 0 };
}
return $self->{asn}{byoffset}{offset} = shift;
}
exists $self->{asn}{byoffset}
? $self->{asn}{byoffset}{offset}
: undef;
}
sub value {
my $self = shift;
if (@_) {
unless ($self->{asn} = $VirtualListViewRequest->decode($_[0])) {
delete $self->{value};
return undef;
}
$self->{value} = shift;
}
exists $self->{value}
? $self->{value}
: $self->{value} = $VirtualListViewRequest->encode($self->{asn});
}
sub scroll {
my $self = shift;
my $n = shift;
my $asn = $self->{asn};
my $byoffset = $asn->{byoffset}
or return undef;
my $offset = $byoffset->{offset} + $n;
my $content;
if ($offset < 1) {
$asn->{afterCount} += $asn->{beforeCount};
$asn->{beforeCount} = 0;
$offset = $byoffset->{offset} = 1;
}
elsif ($byoffset->{contentCount} and $asn->{afterCount}+$offset >$byoffset->{contentCount}) {
if ($offset > $byoffset->{contentCount}) {
$offset = $byoffset->{offset} = $byoffset->{contentCount};
$asn->{beforeCount} += $asn->{afterCount};
$asn->{afterCount} = 0;
}
else {
my $tmp = $byoffset->{contentCount} - $offset;
$asn->{beforeCount} += $tmp;
$asn->{afterCount} -= $tmp;
$byoffset->{offset} = $offset;
}
}
else {
$byoffset->{offset} = $offset;
}
$offset;
}
sub scroll_page {
my $self = shift;
my $n = shift;
my $asn = $self->{asn};
my $page_size = $asn->{beforeCount} + $asn->{afterCount} + 1;
$self->scroll( $page_size * $n);
}
sub start {
my $self = shift;
my $asn = $self->{asn};
$asn->{afterCount} += $asn->{beforeCount};
$asn->{beforeCount} = 0;
$self->offset(1);
}
sub end {
my $self = shift;
my $asn = $self->{asn};
my $content = $self->content || 0;
$asn->{beforeCount} += $asn->{afterCount};
$asn->{afterCount} = 0;
$self->offset($content);
}
1;
__END__
=head1 NAME
Net::LDAP::Control::VLV - LDAPv3 Virtual List View control object
=head1 SYNOPSIS
use Net::LDAP;
use Net::LDAP::Control::VLV;
use Net::LDAP::Constant qw( LDAP_CONTROL_VLVRESPONSE );
$ldap = Net::LDAP->new( "ldap.mydomain.eg" );
# Get the first 20 entries
$vlv = Net::LDAP::Control::VLV->new(
before => 0, # No entries from before target entry
after => 19, # 19 entries after target entry
content => 0, # List size unknown
offset => 1, # Target entry is the first
);
$sort = Net::LDAP::Control::Sort->new( sort => 'cn' );
@args = ( base => "o=Ace Industry, c=us",
scope => "subtree",
filter => "(objectClass=inetOrgPerson)",
callback => \&process_entry, # Call this sub for each entry
control => [ $vlv, $sort ],
);
$mesg = $ldap->search( @args );
# Get VLV response control
($resp) = $mesg->control( LDAP_CONTROL_VLVRESPONSE ) or die;
$vlv->response( $resp );
# Set the control to get the last 20 entries
$vlv->end;
$mesg = $ldap->search( @args );
# Get VLV response control
($resp) = $mesg->control( LDAP_CONTROL_VLVRESPONSE ) or die;
$vlv->response( $resp );
# Now get the previous page
$vlv->scroll_page( -1 );
$mesg = $ldap->search( @args );
# Get VLV response control
($resp) = $mesg->control( LDAP_CONTROL_VLVRESPONSE ) or die;
$vlv->response( $resp );
# Now page with first entry starting with "B" in the middle
$vlv->before(9); # Change page to show 9 before
$vlv->after(10); # Change page to show 10 after
$vlv->assert("B"); # assert "B"
$mesg = $ldap->search( @args );
=head1 DESCRIPTION
C<Net::LDAP::Control::VLV> provides an interface for the creation and
manipulation of objects that represent the Virtual List View as described
by draft-ietf-ldapext-ldapv3-vlv-03.txt.
When using a Virtual List View control in a search, it must be accompanied by a sort
control. See L<Net::LDAP::Control::Sort>
=cut
##
## Need some blurb here to describe the VLV control. Maybe extract some simple
## describtion from the draft RFC
##
=head1 CONSTRUCTOR ARGUMENTS
In addition to the constructor arguments described in
L<Net::LDAP::Control> the following are provided.
=over 4
=item after
Set the number of entries the server should return from the list after
the target entry.
=item assert
Set the assertion value user to locate the target entry. This value should
be a legal value to compare with the first attribute in the sort control
that is passed with the VLV control. The target entry is the first entry
in the list which is greater than or equal the assert value.
=item before
Set the number of entries the server should return from the list before
the target entry.
=item content
Set the number of entries in the list. On the first search this value
should be set to zero. On subsequent searches it should be set to the
length of the list, as returned by the server in the VLVResponse control.
=item context
Set the context identifier. On the first search this value should be
set to zero. On subsequent searches it should be set to the context
value returned by the server in the VLVResponse control.
=item offset
Set the offset of the target entry.
=back
=head2 METHODS
As with L<Net::LDAP::Control> each constructor argument
described above is also avaliable as a method on the object which will
return the current value for the attribute if called without an argument,
and set a new value for the attribute if called with an argument.
The C<offset> and C<assert> attributes are mutually exclusive. Setting
one or the other will cause previous values set by the other to
be forgotten. The C<content> attribute is also associated with the
C<offset> attribute, so setting C<assert> will cause any C<content>
value to be forgotten.
=over 4
=item end
Set the target entry to the end of the list. This method will change the C<before>
and C<after> attributes so that the target entry is the last in the page.
=item response VLV_RESPONSE
Set the attributes in the control as per VLV_RESPONSE. VLV_RESPONSE should be a control
of type L<Net::LDAP::Control::VLVResponse> returned
from the server. C<response> will populate the C<context>, C<offset> and C<content>
attibutes of the control with the values from VLV_RESPONSE. Because this sets the
C<offset> attribute, any previous setting of the C<assert> attribute will be forgotten.
=item scroll NUM
Move the target entry by NUM entries. A positive NUM will move the target entry towards
the end of the list and a negative NUM will move the target entry towards the
start of the list. Returns the index of the new target entry, or C<undef> if the current target
is identified by an assertion.
C<scroll> may change the C<before> and C<after> attributes if the scroll value would
cause the page to go off either end of the list. But the page size will be maintained.
=item scroll_page NUM
Scroll by NUM pages. This method simple calculates the current page size and calls
C<scroll> with C<NUM * $page_size>
=item start
Set the target entry to the start of the list. This method will change the C<before> and C<after>
attributes to the the target entry is the first entry in the page.
=back
=head1 SEE ALSO
L<Net::LDAP>,
L<Net::LDAP::Control>,
L<Net::LDAP::Control::Sort>,
L<Net::LDAP::Control::VLVResponse>
=head1 AUTHOR
Graham Barr <gbarr@pobox.com>
Please report any bugs, or post any suggestions, to the perl-ldap mailing list
<perl-ldap-dev@lists.sourceforge.net>
=head1 COPYRIGHT
Copyright (c) 2000 Graham Barr. All rights reserved. This program is
free software; you can redistribute it and/or modify it under the same
terms as Perl itself.
=for html <hr>
I<$Id$>

View file

@ -0,0 +1,198 @@
# Copyright (c) 2000 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::Control::VLVResponse;
use vars qw(@ISA $VERSION);
use Net::LDAP::Control;
@ISA = qw(Net::LDAP::Control);
$VERSION = "0.01";
use Net::LDAP::ASN qw(VirtualListViewResponse);
use strict;
sub init {
my($self) = @_;
if (exists $self->{value}) {
$self->value($self->{value});
}
else {
my $asn = $self->{asn} = {};
$asn->{targetPosition} = $self->{target} || 0;
$asn->{contentCount} = $self->{content} || 0;
$asn->{virtualListViewResult} = $self->{result} || 0;
$asn->{context} = $self->{context} || undef;
}
$self;
}
sub target {
my $self = shift;
if (@_) {
delete $self->{value};
return $self->{asn}{targetPosition} = shift;
}
$self->{asn}{targetPosition};
}
sub content {
my $self = shift;
if (@_) {
delete $self->{value};
return $self->{asn}{contentCount} = shift;
}
$self->{asn}{contentCount};
}
sub result {
my $self = shift;
if (@_) {
delete $self->{value};
return $self->{asn}{virtualListViewResult} = shift;
}
$self->{asn}{virtualListViewResult};
}
sub context {
my $self = shift;
if (@_) {
delete $self->{value};
return $self->{asn}{context} = shift;
}
$self->{asn}{context};
}
sub value {
my $self = shift;
if (@_) {
unless ($self->{asn} = $VirtualListViewResponse->decode($_[0])) {
delete $self->{value};
return undef;
}
$self->{value} = shift;
}
exists $self->{value}
? $self->{value}
: $self->{value} = $VirtualListViewResponse->encode($self->{asn});
}
1;
__END__
=head1 NAME
Net::LDAP::Control::VLVResponse -- LDAPv3 Virtual List View server response
=head1 SYNOPSIS
See L<Net::LDAP::Control::VLV>
=head1 DESCRIPTION
C<Net::LDAP::Control::VLVResponse> is a sub-class of L<Net::LDAP::Control>.
It provides a class for manipulating the LDAP Virtual List View Response control
C<>
If the server supports Virtual List Views, then the response from a search operation will
include a VLVResponse control.
=head1 CONSTRUCTOR ARGUMENTS
In addition to the constructor arguments described in
L<Net::LDAP::Control> the following are provided.
=over 4
=item content
An estimate of the number of entries in the complete list. This value should
be used in any subsequent Virtual List View control using the same list.
=item context
An arbitary value which is used to associate subsequent requests with the
request which this control is a response for. This value should be copied
by the client into the Virtual List View control for any subsequent
search that uses the same list.
=item result
A result code indicating the result of the Virtual List View request. This
may be any of the codes listed below.
=item target
The list offset of the target entry.
=back
=head1 METHODS
As with L<Net::LDAP::Control> each constructor argument
described above is also avaliable as a method on the object which will
return the current value for the attribute if called without an argument,
and set a new value for the attribute if called with an argument.
=head1 RESULT CODES
Possible results from a sort request are listed below. See L<Net::LDAP::Constant> for
a definition of each.
=over 4
=item LDAP_SUCCESS
=item LDAP_OPERATIONS_ERROR
=item LDAP_TIMELIMIT_EXCEEDED
=item LDAP_ADMIN_LIMIT_EXCEEDED
=item LDAP_INSUFFICIENT_ACCESS
=item LDAP_BUSY
=item LDAP_UNWILLING_TO_PERFORM
=item LDAP_OTHER
=item LDAP_SORT_CONTROL_MISSING
=item LDAP_INDEX_RANGE_ERROR
=back
=head1 SEE ALSO
L<Net::LDAP>,
L<Net::LDAP::Control>,
http://info.internet.isi.edu/in-notes/rfc/files/rfc2696.txt
=head1 AUTHOR
Graham Barr <gbarr@pobox.com>
Please report any bugs, or post any suggestions, to the perl-ldap mailing list
<perl-ldap-dev@lists.sourceforge.net>
=head1 COPYRIGHT
Copyright (c) 2000 Graham Barr. All rights reserved. This program is
free software; you can redistribute it and/or modify it under the same
terms as Perl itself.
=for html <hr>
I<$Id$>
=cut

340
lib/Net/LDAP/DSML.pm Executable file
View file

@ -0,0 +1,340 @@
package Net::LDAP::DSML;
# For schema parsing, add ability to Net::LDAP::Schema to accecpt a Net::LDAP::Entry object. First
# we'll convert XML into Net::LDAP::Entry with schema attributes and then pass to schema object constructor
#
# move XML::DSML to Net::LDAP::DSML::Parser
# change parser so that it uses callbacks
use strict;
use Net::LDAP::Entry;
use vars qw($VERSION);
$VERSION = "0.06";
sub new {
my $pkg = shift;
my $self = {};
bless $self, $pkg;
}
sub open {
my $self = shift;
my $file = shift ;
my $fh = $file;
my $close = 0;
$self->finish
if $self->{net_ldap_fh};
if (ref($file) or ref(\$file) eq "GLOB") {
$close = 0;
$fh = $file;
}
else {
local *FH;
unless (open(FH,$file)) {
$self->{error} = "Cannot open file '$file'";
return 0;
}
$close = 1;
$fh = \*FH;
}
$self->{net_ldap_fh} = $fh;
$self->{net_ldap_close} = $close;
print $fh $self->start_dsml;
1;
}
sub finish {
my $self = shift;
my $fh = $self->{net_ldap_fh};
if ($fh) {
print $fh $self->end_dsml;
close($fh) if $self->{net_ldap_close};
}
}
sub start_dsml {
qq!<?xml version="1.0" encoding="utf-8"?>\n<dsml:dsml xmlns:dsml="http://www.dsml.org/DSML">\n!;
}
sub end_dsml {
qq!</dsml:dsml>\n!;
}
sub DESTROY { shift->close }
#transform any entity chararcters
#must handle ourselves because I don't know of an XML module that does this
sub _normalize {
my $normal = shift;
$normal =~ s/&/&amp;/g;
$normal =~ s/</&lt;/g;
$normal =~ s/>/&gt;/g;
$normal =~ s/\"/&quot;/g;
$normal =~ s/\'/&apos;/g;
return $normal;
}
sub write {
my $self = shift;
my $entry = shift;
#my @unknown = _print_schema(_print_entries(@_));
if (ref $entry eq 'Net::LDAP::Entry') {
$self->_print_entry($entry)
}
elsif (ref $entry eq 'Net::LDAP::Schem') {
_print_schema($entry);
}
else {
return undef;
}
1;
}
#coming soon! ;)
sub _print_schema {
my $self = shift;
@_;
}
sub _print_entry {
my ($self,$entry) = @_;
my @unknown;
my $count;
my $fh = $self->{'net_ldap_fh'} or return;
return undef unless ($entry->isa('Net::LDAP::Entry'));
print $fh "<dsml:directory-entries>\n";
print $fh "<dsml:entry dn=\"",_normalize($entry->dn),"\">\n";
my @attributes = $entry->attributes();
#at some point integrate with Net::LDAP::Schema to determine if binary or not
#now look for ;binary tag
for my $attr (@attributes) {
my $isOC = 0;
if (lc($attr) eq 'objectclass') {
$isOC = 1;
}
if ($isOC) {
print $fh "<dsml:objectclass>\n";
}
else {
print $fh "<dsml:attr name=\"",_normalize($attr),"\">\n";
}
my @values = $entry->get_value($attr);
for my $value (@values) {
if ($isOC) {
print $fh "<dsml:oc-value>",_normalize($value),"</dsml:oc-value>\n";
}
else {
#at some point we'll use schema object to determine
#this but until then we'll borrow this from Net::LDAP::LDIF
if ($value=~ /(^[ :]|[\x00-\x1f\x7f-\xff])/) {
require MIME::Base64;
print $fh qq!<dsml:value encoding="base64">!,
MIME::Base64::encode($value),
"</dsml:value>\n";
}
else {
print $fh "<dsml:value>",_normalize($value),"</dsml:value>\n";
}
}
}
if ($isOC) {
print $fh "</dsml:objectclass>\n";
}
else {
print $fh "</dsml:attr>\n";
}
}
print $fh "</dsml:entry>\n";
print $fh "</dsml:directory-entries>\n";
1;
}
# only parse DSML entry elements, no schema here
sub read_entries {
my ($self, $file) = @_;
my @entries;
$self->process($file, entry => sub { push @entries, @_ });
@entries;
}
sub read_schema {
my ($self, $file) = @_;
my $schema;
$self->process($file, schema => sub { $schema = shift } );
$schema;
}
sub process {
my $self = shift;
my $file = shift;
my %arg = @_;
require XML::Parser;
require Net::LDAP::DSML::Parser;
my $xml = XML::Parser->new(
Style => 'Subs',
Pkg => 'Net::LDAP::DSML::Parser',
Handlers => {
ExternEnt => sub { "" },
Char => \&_Char
}
);
$xml->{net_ldap_entry_handler} = $arg{entry} if exists $arg{entry};
$xml->{net_ldap_schema_handler} = $arg{schema} if exists $arg{schema};
delete $self->{error};
my $ok = eval { local $SIG{__DIE__}; $xml->parsefile($file); 1 };
$self->{error} = $@ unless $ok;
$ok;
}
sub error { shift->{error} }
sub _Char {
my $self = shift;
my $tag = $self->current_element;
if ($tag =~ /^dsml:(oc-)?value$/) {
$self->{net_ldap_entry}->add(
($1 ? 'objectclass' : $self->{net_ldap_attr}),
$self->{net_ldap_base64}
? MIME::Base64::decode(shift)
: shift
);
}
elsif ($_[0] =~ /\S/) {
die "Unexpected text '$_[0]', while parsing $tag";
}
}
1;
__END__
=head1 NAME
Net::LDAP::DSML -- A DSML Writer and Reader for Net::LDAP
=head1 SYNOPSIS
use Net::LDAP;
use Net::LDAP::DSML;
use IO::File;
my $server = "localhost";
my $file = "testdsml.xml";
my $ldap = Net::LDAP->new($server);
$ldap->bind();
my $dsml = Net::LDAP::DSML->new();
my $file = "testdsml.xml";
my $io = IO::File->new($file,"w") or die ("failed to open $file as filehandle.$!\n");
$dsml->open($io) or die ("DSML problems opening $file.$!\n"); ;
#or
open (IO,">$file") or die("failed to open $file.$!");
$dsml->open(*IO) or die ("DSML problems opening $file.$!\n");
my $mesg = $ldap->search(
base => 'o=airius.com',
scope => 'sub',
filter => 'ou=accounting',
callback => sub {
my ($mesg,$entry) =@_;
$dsml->write($entry) if (ref $entry eq 'Net::LDAP::Entry');
}
);
die ("search failed with ",$mesg->code(),"\n") if $mesg->code();
$dsml->write($schema);
$dsml->finish();
print "Finished printing DSML\n";
print "Starting to process DSML\n";
$dsml = new Net::LDAP::DSML();
$dsml->process($file, entry => \&processEntry);
#future when schema support is available will be
#$dsml->process($file, entry => \&processEntry, schema => \&processSchema);
sub processEntry {
my $entry = shift;
$entry->dump();
}
=head1 DESCRIPTION
Directory Service Markup Language (DSML) is the XML standard for
representing directory service information in XML.
At the moment this module only reads and writes DSML entry entities. It
cannot process any schema entities because schema entities are processed
differently than elements.
Eventually this module will be a full level 2 consumer and producer
enabling you to give you full DSML conformance.
The module uses callbacks to improve performance (at least the appearance
of improving performance ;) and to reduce the amount of memory required to
parse large DSML files. Every time a single entry or schema is processed
we pass the Net::LDAP object (either an Entry or Schema object) to the
callback routine.
=head1 AUTHOR
Mark Wilcox mark@mwjilcox.com
=head1 SEE ALSO
L<Net::LDAP>,
L<XML::Parser>
=head1 COPYRIGHT
Copyright (c) 2000 Graham Barr and Mark Wilcox. All rights reserved. This program is
free software; you can redistribute it and/or modify it under the same
terms as Perl itself.
=cut

View file

@ -0,0 +1,72 @@
package Net::LDAP::DSML::Parser;
use Net::LDAP::Entry;
#use Net::LDAP::Schema;
use vars qw($VERSION);
$VERSION = "0.06";
# dsml:entry
*{'dsml:entry'} = sub {
my ($self,$tag, %attr) = @_;
my $entry = $self->{net_ldap_entry} = Net::LDAP::Entry->new;
$entry->dn( $attr{dn} );
};
*{'dsml:entry_'} = sub {
my $self = shift;
if ($self->{net_ldap_entry_handler}) {
&{$self->{net_ldap_entry_handler}}(delete $self->{net_ldap_entry});
}
};
# dsml:attr
*{'dsml:attr'} = sub {
my ($self,$tag, %attr) = @_;
$self->{net_ldap_attr} = $attr{name};
};
*{'dsml:attr_'} = sub {
my $self = shift;
delete $self->{net_ldap_attr};
};
# dsml:value
*{'dsml:value'} = sub {
my ($self,$tag, %attr) = @_;
$self->{net_ldap_base64} =
(exists $attr{encoding} && lc($attr{encoding}) eq 'base64')
and require MIME::Base64;
};
*{'dsml:value_'} = sub {
my $self = shift;
delete $self->{net_ldap_base64};
};
*{'dsml:oc-value'} = \&{'dsml:value'};
*{'dsml:oc-value_'} = \&{'dsml:value_'};
*{'dsml:objectclass'} = sub {};
*{'dsml:objectclass_'} = sub {};
*{'dsml:dsml'} = sub {};
*{'dsml:dsml_'} = sub {};
*{'dsml:directory-entries'} = sub {};
*{'dsml:directory-entries_'} = sub {};
sub AUTOLOAD {
my $tag = substr($AUTOLOAD,25);
die "Unknown tag '$tag'";
}
1;

293
lib/Net/LDAP/Entry.pm Normal file
View file

@ -0,0 +1,293 @@
# Copyright (c) 1997-2000 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::Entry;
use strict;
use Net::LDAP::ASN qw(LDAPEntry);
use Net::LDAP::Constant qw(LDAP_LOCAL_ERROR);
use vars qw($VERSION);
$VERSION = "0.15";
sub new {
my $self = shift;
my $type = ref($self) || $self;
my $entry = bless { 'changetype' => 'add', changes => [] }, $type;
$entry;
}
# Build attrs cache, created when needed
sub _build_attrs {
+{ map { (lc($_->{type}),$_->{vals}) } @{$_[0]->{asn}{attributes}} };
}
# If we are passed an ASN structure we really do nothing
sub decode {
my $self = shift;
my $result = ref($_[0]) ? shift : $LDAPEntry->decode(shift)
or return;
%{$self} = ( asn => $result, changetype => 'modify', changes => []);
$self;
}
sub encode {
$LDAPEntry->encode( shift->{asn} );
}
sub dn {
my $self = shift;
@_ ? ($self->{asn}{objectName} = shift) : $self->{asn}{objectName};
}
sub get_attribute {
require Carp;
Carp::carp("->get_attribute deprecated, use ->get_value") if $^W;
shift->get_value(@_, asref => !wantarray);
}
sub get {
require Carp;
Carp::carp("->get deprecated, use ->get_value") if $^W;
shift->get_value(@_, asref => !wantarray);
}
sub exists {
my $self = shift;
my $type = lc(shift);
my $attrs = $self->{attrs} ||= _build_attrs($self);
exists $attrs->{$type};
}
sub get_value {
my $self = shift;
my $type = lc(shift);
my %opt = @_;
if ($opt{alloptions}) {
my %ret = map {
$_->{type} =~ /^\Q$type\E(.*)/ ? (lc($1), $_->{vals}) : ()
} @{$self->{asn}{attributes}};
return %ret ? \%ret : undef;
}
my $attrs = $self->{attrs} ||= _build_attrs($self);
my $attr = $attrs->{$type} or return;
return $opt{asref}
? $attr
: wantarray
? @{$attr}
: $attr->[0];
}
sub changetype {
my $self = shift;
return $self->{'changetype'} unless @_;
$self->{'changes'} = [];
$self->{'changetype'} = shift;
}
sub add {
my $self = shift;
my $cmd = $self->{'changetype'} eq 'modify' ? [] : undef;
my $attrs = $self->{attrs} ||= _build_attrs($self);
while (my($type,$val) = splice(@_,0,2)) {
$type = lc $type;
push @{$self->{asn}{attributes}}, { type => $type, vals => ($attrs->{$type}=[])}
unless exists $attrs->{$type};
push @{$attrs->{$type}}, ref($val) ? @$val : $val;
push @$cmd, $type, [ ref($val) ? @$val : $val ]
if $cmd;
}
push(@{$self->{'changes'}}, 'add', $cmd) if $cmd;
}
sub replace {
my $self = shift;
my $cmd = $self->{'changetype'} eq 'modify' ? [] : undef;
my $attrs = $self->{attrs} ||= _build_attrs($self);
while(my($type, $val) = splice(@_,0,2)) {
$type = lc $type;
if (defined($val) and (!ref($val) or @$val)) {
push @{$self->{asn}{attributes}}, { type => $type, vals => ($attrs->{$type}=[])}
unless exists $attrs->{$type};
@{$attrs->{$type}} = ref($val) ? @$val : ($val);
push @$cmd, $type, [ ref($val) ? @$val : $val ]
if $cmd;
}
else {
delete $attrs->{$type};
@{$self->{asn}{attributes}}
= grep { $type ne lc($_->{type}) } @{$self->{asn}{attributes}};
push @$cmd, $type, []
if $cmd;
}
}
push(@{$self->{'changes'}}, 'replace', $cmd) if $cmd;
}
sub delete {
my $self = shift;
unless (@_) {
$self->changetype('delete');
return;
}
my $cmd = $self->{'changetype'} eq 'modify' ? [] : undef;
my $attrs = $self->{attrs} ||= _build_attrs($self);
while(my($type,$val) = splice(@_,0,2)) {
$type = lc $type;
if (defined($val) and (!ref($val) or @$val)) {
my %values;
@values{@$val} = ();
unless( @{$attrs->{$type}}
= grep { !exists $values{$_} } @{$attrs->{$type}})
{
delete $attrs->{$type};
@{$self->{asn}{attributes}}
= grep { $type ne lc($_->{type}) } @{$self->{asn}{attributes}};
}
push @$cmd, $type, [ ref($val) ? @$val : $val ]
if $cmd;
}
else {
delete $attrs->{$type};
@{$self->{asn}{attributes}}
= grep { $type ne lc($_->{type}) } @{$self->{asn}{attributes}};
push @$cmd, $type, [] if $cmd;
}
}
push(@{$self->{'changes'}}, 'delete', $cmd) if $cmd;
}
sub update {
my $self = shift;
my $ldap = shift;
my $mesg;
my $cb = sub { $self->changetype('modify') unless $_[0]->code };
if ($self->{'changetype'} eq 'add') {
$mesg = $ldap->add($self, 'callback' => $cb);
}
elsif ($self->{'changetype'} eq 'delete') {
$mesg = $ldap->delete($self, 'callback' => $cb);
}
elsif ($self->{'changetype'} =~ /modr?dn/) {
my @args = (newrdn => $self->get_value('newrdn'),
deleteoldrdn => $self->get_value('deleteoldrdn'));
my $newsuperior = $self->get_value('newsuperior');
push(@args, newsuperior => $newsuperior) if $newsuperior;
$mesg = $ldap->moddn($self, @args, 'callback' => $cb);
}
elsif (@{$self->{'changes'}}) {
$mesg = $ldap->modify($self, 'changes' => $self->{'changes'}, 'callback' => $cb);
}
else {
require Net::LDAP::Message;
$mesg = Net::LDAP::Message->new( {} );
$mesg->set_error(LDAP_LOCAL_ERROR,"No attributes to update");
}
return $mesg;
}
# Just for debugging
sub dump {
my $self = shift;
my $asn = $self->{asn};
print "-" x 72,"\n";
print "dn:",$asn->{objectName},"\n\n";
my($attr,$val);
my $l = 0;
for (keys %{ $self->{attrs} ||= _build_attrs($self) }) {
$l = length if length > $l;
}
my $spc = "\n " . " " x $l;
foreach $attr (@{$asn->{attributes}}) {
$val = $attr->{vals};
printf "%${l}s: ", $attr->{type};
my($i,$v);
$i = 0;
foreach $v (@$val) {
print $spc if $i++;
print $v;
}
print "\n";
}
}
sub attributes {
my $self = shift;
my %opt = @_;
if ($opt{nooptions}) {
my %done;
return map {
$_->{type} =~ /^([^;]+)/;
$done{lc $1}++ ? () : ($1);
} @{$self->{asn}{attributes}};
}
else {
return map { $_->{type} } @{$self->{asn}{attributes}};
}
}
sub asn {
shift->{asn}
}
sub changes {
@{shift->{'changes'}}
}
1;

295
lib/Net/LDAP/Entry.pod Normal file
View file

@ -0,0 +1,295 @@
=head1 NAME
Net::LDAP::Entry - An LDAP entry object
=head1 SYNOPSIS
use Net::LDAP;
$ldap = Net::LDAP->new($host);
$mesg = $ldap->search(@search_args);
my $max = $mesg->count;
for($i = 0 ; $i < $max ; $i++) {
my $entry = $mesg->entry($i);
foreach my $attr ($entry->attributes) {
print join("\n ",$attr, $entry->get_value($attr)),"\n";
}
}
# or
use Net::LDAP::Entry;
$entry = Net::LDAP::Entry->new;
$entry->add(
attr1 => 'value1',
attr2 => [qw(value1 value2)]
);
$entry->delete( 'unwanted' );
$entry->replace(
attr1 => 'newvalue'
attr2 => [qw(new values)]
);
$entry->update( $ldap ); # update directory server
=head1 DESCRIPTION
The B<Net::LDAP::Entry> object represents a single entry in the directory.
It is a container for attribute-value pairs.
A B<Net::LDAP::Entry> object can be used in two situations. The first and
probably most common use is in the result of a search to the directory
server.
The other is where a new object is created locally and then a single
command is sent to the directory server to add, modify or replace an
entry. Entries for this purpose can also be created by reading an
LDIF file with the L<Net::LDAP::LDIF> module.
=head1 CONSTRUCTOR
=over 4
=item new
Create a new entry object with the changetype set to C<'add'>
=back
=head1 METHODS
=over 4
=item add ( ATTR => VALUE [, ATTR2 => VALUE2 ... ] )
Add one or more new attributes to the entry. Each value
must be a scalar variable or a reference to an array. The
values given will be added to the values which already exist
for the given attributes.
$entry->add( 'sn' => 'Barr');
$entry->add( 'street' => [ '1 some road','nowhere']);
B<NOTE>: these changes are local to the client and will not
appear on the directory server until the C<update> method
is called.
=item attributes ( [ OPTIONS ] )
Return a list of attributes that this entry has.
OPTIONS is a list of name/value pairs, valid options are :-
=over 4
=item nooptions
If TRUE, return a list of the attribute names excluding any options. For example for the entry
name: Graham Barr
name;en-us: Bob
jpeg;binary: **binary data**
then
@values = $entry->attributes()
print "default: @values\n";
@values = $entry->attributes( nooptions => 1);
print "nooptions: @values\n";
will output
default: name name;en-us jpeg;binary
nooptions: name jpeg
=back
=item changetype ( [ TYPE ] )
If called without arguments it returns the type of operation that would
be performed when the update method is called. If called with an argument
it will set the changetype to TYPE.
Possible values for TYPE are
=over 4
=item add
The update method will call the add method on the client object, which
will result in the entry being added to the directory server.
=item delete
The update method will call the delete method on the client object, which
will result in the entry being removed from the directory server.
=item modify
The update method will call the modify method on the client object, which
will result in any changes that have been made locally being made to the
entry on the directory server.
=item moddn/modrdn
The update method will call the moddn method on the client object, which
will result in any DN changes that have been made locally being made
to the entry on the directory server. These DN changes are specified by
setting the entry attributes newrdn, deleteoldrdn, and (optionally) newsuperior.
=back
=item delete ( [ ATTR [, ATTR2 ... ]] )
Delete the given attributes from the entry. If no attributes
are passed then the next call to update will cause the entry
to be deleted from the server.
B<NOTE>: these changes are local to the client and will not
appear on the directory server until the C<update> method
is called.
=item dn ( [ DN ] )
Set or get the DN for the entry. With no arguments C<dn> will return
the current DN. If an argument is given then it will change the DN
for the entry and return the previous value.
B<NOTE>: these changes are local to the client and will not
appear on the directory server until the C<update> method
is called.
=item exists ( ATTR )
Returns TRUE if the entry has an attribute called ATTR.
=item get_value ( ATTR [, OPTIONS ] )
Get the values for the attribute ATTR. In a list context returns all
values for the given attribute, or the empty list if the attribute does
not exist. In a scalar context returns the first value for the attribute
or undef if the attribute does not exist.
The return value may be changed by OPTIONS, which is a list of name => value
pairs, valid options are :-
=over 4
=item alloptions
If TRUE then the result will be a hash reference. The keys of the hash
will be the options and the hash value will be the values for those attributes.
For example if an entry had
name: Graham Barr
name;en-us: Bob
Then a get for attribute "name" with alloptions set to a true value
$ref = $entry->get_value( 'name', alloptions => 1);
will return a hash reference that would be like
{
'' => [ 'Graham Barr' ],
';en-us' => [ 'Bob' ]
}
=item asref
If TRUE then the result will be a reference to an array containing all the
values for the attribute, or undef if the attribute does not exist.
$scalar = $entry->get_value('name');
$scalar will be the first value for the C<name> attribute, or C<undef> if the
entry does not contain a C<name> attribute.
$ref = $entry->get_value('name', asref => 1);
$ref will be a reference to an array, which will have all the values for
the C<name> attribute. If the entry does not have an attribute called C<name>
then $ref will be C<undef>
=back
B<NOTE>: In the interest of performance the array references returned by C<get_value>
are references to structures held inside the entry object. These values and
thier contents should B<NOT> be modified directly.
=item replace ( ATTR => VALUE [, ATTR2 => VALUE2 ... ] )
Similar to add, except that the values given will replace
any values that already exist for the given attributes.
B<NOTE>: these changes are local to the client and will not
appear on the directory server until the C<update> method
is called.
=item update ( CLIENT )
Update the directory server with any changes that have been made locally
to the attributes of this entry. This means any calls that have been
made to add, replace or delete since the last call to changetype or
update was made.
This method can also be used to modify the DN of the entry on the server,
by specifying moddn or modrdn as the changetype, and setting the entry
attributes newrdn, deleteoldrdn, and (optionally) newsuperior.
CLIENT is a C<Net::LDAP> object where the update will be sent to.
The result will be an object of type
L<Net::LDAP::Message> as returned by the add, modify
or delete method called on CLIENT.
=back
=head1 SEE ALSO
L<Net::LDAP>,
L<Net::LDAP::LDIF>
=head1 AUTHOR
Graham Barr <gbarr@pobox.com>.
Please report any bugs, or post any suggestions, to the perl-ldap mailing list
<perl-ldap-dev@lists.sourceforge.net>.
=head1 COPYRIGHT
Copyright (c) 1997-2000 Graham Barr. All rights reserved. This program is
free software; you can redistribute it and/or modify it under the same
terms as Perl itself.
=for html <hr>
I<$Id$>
=cut

556
lib/Net/LDAP/Examples.pod Normal file
View file

@ -0,0 +1,556 @@
=head1 NAME
Net::LDAP::Examples - PERL LDAP by Example
=head1 DESCRIPTION
The following examples are of course PERL code, found to work
with the Net::LDAP modules.
The intent of this document is to give the reader a I<cut and paste>
jump start to getting an LDAP application working.
Below you will find snippets of code that should work as-is with only
a small amount of work to correct any variable assignments and LDAP
specifics, e.g. Distinguished Name Syntax, related to the user's
own implementation.
The I<S>tandard I<O>perating I<P>roceedure that is followed here is:
=over 8
=item 1 Package - use Net::LDAP
=item 2 Initialization - new
=item 3 Binding - bind
=item 4 Operation - add modify moddn search
=item 4.1 Processing - displaying data from a search
=item 5 Error - displaying error information
=item 6 Unbinding - unbind
=back
Look to each of these for a snippet of code to meet your needs.
B<What is not covered in these examples at this time:>
=over 4
=item I<abandon> and I<compare> methods
=item I<callback> subroutines
=back
=head1 CODE
=head2 PACKAGE - Definitions
use Net::LDAP qw(:all); # use for all code
use Net::LDAP::Util qw(ldap_error_name
ldap_error_text) ; # use for Error handling
=head2 INITIALIZING
$ldap = Net::LDAP->new("yourLDAPhost.yourCompany.com") or die "$@";
=head2 BINDING
$mesg = $ldap->bind( version => 3 ); # use for searches
$mesg = $ldap->bind("$userToAuthenticate",
password => "$passwd",
version => 3 ); # use for changes/edits
# see your LDAP administrator for information concerning the
# user authentication setup at your site.
=head2 OPERATION - Generating a SEARCH
sub LDAPsearch
{
my ($ldap,$searchString,$attrs,$base) = @_ ;
# if they don't pass a base... set it for them
if (!$base ) { $base = "o=mycompany, c=mycountry"; }
# if they don't pass an array of attributes...
# set up something for them
if (!$attrs ) { $attrs = ['cn','mail' ]; }
my $result = $ldap->search (
base => "$base",
scope => "sub",
filter => "$searchString",
attrs => $attrs
);
}
my @Attrs = (); # request all available attributes
# to be returned.
my $result = LDAPsearch($ldap,"sn=*",\@Attrs);
=head2 PROCESSING - Displaying SEARCH Results
#------------
#
# Accessing the data as if in a structure
# i.e. Using the "as_struct" method
#
my $href = $result->as_struct;
# get an array of the DN names
my @arrayOfDNs = keys %$href ; # use DN hashes
# process each DN using it as a key
foreach (@arrayOfDNs) {
print $_,"\n";
my $valref = $$href{$_};
# get an array of the attribute names
# passed for this one DN.
my @arrayOfAttrs = sort keys %$valref; #use Attr hashes
my $attrName;
foreach $attrName (@arrayOfAttrs) {
# skip any binary data: yuck!
next if ( $attrName =~ /;binary$/ );
# get the attribute value (pointer) using the
# attribute name as the hash
my $attrVal = @$valref{$attrName} ;
print "\t $attrName: @$attrVal \n";
}
print "#-------------------------------\n";
# End of that DN
}
#
# end of as_struct method
#
#--------
#------------
#
# handle each of the results independently
# ... i.e. using the walk through method
#
my @entries = $result->entries;
my $entr ;
foreach $entr ( @entries )
{
print "DN: ",$entr->dn,"\n";
#my @attrs = sort $entr->attributes;
my $attr;
foreach $attr ( sort $entr->attributes ){
#skip binary we can't handle
next if ( $attr =~ /;binary$/ );
print " $attr : ",$entr->get_value($attr),"\n";
}
#print "@attrs\n";
print "#-------------------------------\n";
}
#
# end of walk through method
#------------
=head2 OPERATION - Modifying entries
#
# Modify
#
# for each of the modifies below you'll need to supply
# a full DN (Distinguished Name) for the $dn variable.
# example:
# cn=Jo User,ou=person,o=mycompany,c=mycountry
#
# I would recommend doing a search (listed above)
# then use the dn returned to populate the $dn variable.
#
# Do we only have one result returned from the search?
if ( $result->count != 1 ) { exit ; } # Nope.. exit
my $dn = $entries[0]->dn; # yes.. get the DN
#######################################
#
# MODIFY using a HASH
#
my %ReplaceHash = ( keyword => "x", proxy => "x" );
my $result = LDAPmodifyUsingHash($ldap,$dn, \%ReplaceHash );
sub LDAPmodifyUsingHash
{
my ($ldap,$dn,$whatToChange ) = @_ ;
my $result = $ldap->modify($dn,
replace => { %$whatToChange }
);
return ($result );
}
#######################################
#
# MODIFY using a ARRAY List
#
my @ReplaceArrayList = [ 'keyword', "xxxxxxxxxx" ,
'proxy' , "yyyyyyyyyy" ];
my $result = LDAPmodifyUsingArrayList($ldap,$dn, \@ReplaceArrayList );
sub LDAPmodifyUsingArrayList
{
my ($ldap,$dn,$whatToChange ) = @_ ;
my $result = $ldap->modify($dn,
changes => [
replace => @$whatToChange
]
);
return ($result );
}
#######################################
#
# MODIFY using a ARRAY
#
my @ReplaceArray = ( 'keyword', "xxxxxxxxxx" ,
'proxy' , "yyyyyyyyyy" );
my $result = LDAPmodifyUsingArray($ldap,$dn, \@ReplaceArray );
sub LDAPmodifyUsingArray
{
my ($ldap,$dn,$whatToChange ) = @_ ;
my $result = $ldap->modify($dn,
changes => [
replace => [ @$whatToChange ]
]
);
return ($result );
}
#######################################
#
# MODIFY an existing record using 'Changes'
# (or combination of add/delete/replace)
#
my @whatToChange ;
my @ReplaceArray ;
my @DeleteArray ;
my @AddArray ;
push @AddArray, 'cn',"me myself";
push @ReplaceArray, 'sn','!@#$%^&*()__+Hello THere';
push @ReplaceArray, 'cn',"me myself I";
push @DeleteArray, 'cn',"me myself";
if ( $#ReplaceArray > 0 ) {
push @whatToChange, 'replace' ;
push @whatToChange, \@ReplaceArray ;
}
if ( $#DeleteArray > 0 ) {
push @whatToChange, 'delete' ;
push @whatToChange, \@DeleteArray ;
}
if ( $#AddArray > 0 ) {
push @whatToChange, 'add' ;
push @whatToChange, \@AddArray ;
}
$result = LDAPmodify($ldap,$dn, \@whatToChange );
sub LDAPmodify
{
my ($ldap,$dn,$whatToChange) = @_ ;
my $result = $ldap->modify($dn,
changes => [
@$whatToChange
]
);
return ($result );
}
=head2 OPERATION - Changing the RDN
my $newRDN = "cn=Joseph User";
my $result = LDAPrdnChange($ldap,$dn,$newRDN,"archive");
sub LDAPrdnChange
{
my ($ldap,$dn,$whatToChange,$action) = @_ ;
my $branch ;
#
# if the archive action is selected, move this
# entry to another place in the directory.
#
if ( $action =~ /archive/i ) {
$branch = "ou=newbranch,o=mycompany,c=mycountry";
}
#
# use the 'deleteoldrdn' to keep from getting
# multivalues in the NAMING attribute.
# in most cases that would be the 'CN' attribute
#
my $result = $ldap->moddn($dn,
newrdn => $whatToChange,
deleteoldrdn => '1',
newsuperior => $branch
);
return ($result );
}
=head2 OPERATION - Adding a new Record
my $DNbranch = "ou=bailiwick, o=mycompany, c=mycountry";
#
# check with your Directory Schema or Administrator
# for the correct objectClass... I'm sure it'll be different
#
my $CreateArray = [
objectClass => ["top","person","organizationalPerson"],
cn => "Jane User",
uid => "0000001",
sn => "User",
mail => "JaneUser@mycompany.com"
];
#
# create the new DN to look like this
# " cn=Jo User + uid=0000001 , ou=bailiwick, o=mycompany, c=mycountry "
#
# NOTE: this DN MUST be changed to meet your implementation
#
my $NewDN = "@$CreateArray[2]=".
"@$CreateArray[3]+".
"@$CreateArray[4]=".
"@$CreateArray[5],".
$DNbranch;
LDAPentryCreate($ldap,$NewDN,$CreateArray);
#
# CreateArray is a reference to an anonymous array
# you have to dereference it in the subroutine it's
# passed to.
#
sub LDAPentryCreate
{
my ($ldap,$dn,$whatToCreate) = @_ ;
my $result = $ldap->add( $dn, attrs => [ @$whatToCreate ] );
return ($result );
}
=head2 ERROR - Retrieving and Displaying ERROR information
use Net::LDAP::Util qw( ldap_error_name
ldap_error_text) ;
if ( $result->code ) {
#
# if we've got an error... record it
#
LDAPerror("Searching",$result);
}
sub LDAPerror
{
my ($from,$mesg) = @_;
print "Return code: ",$mesg->code ;
print "\tMessage: ", ldap_error_name($mesg->code);
print " :", ldap_error_text($mesg->code);
print "MessageID: ",$mesg->mesg_id;
print "\tDN: ",$mesg->dn;
#---
# Programmer note:
#
# "$mesg->error" DOESN'T work!!!
#
#print "\tMessage: ", $mesg->error;
#-----
}
=head2 UNBIND
$ldap->unbind;
=head1 LDAP SCHEMA RETRIEVAL
The following code snippet shows how to retrieve schema information.
The first procedure is to initialize a new LDAP object using the
same procedures as listed at the beginning of this document.
The second procedure is to bind to your directory server. Some
servers may require authentication to retrieve the schema from the
directory server. This procedure is listed at the beginning of
this document too.
After a successful bind you are ready to retrieve the schema
information. You do this by initializing a schema object.
$schema = $ldap->schema();
In this case Net::LDAP will attempt to determine the dn under which
the schema can be found. First it will look for the attribute
C<subschemasubentry> in the root DSE. If that cannot be found then
it will default to the assumption of C<cn=schema>
Alternatively you can specify the dn where the schema is to be found
with
$schema = $ldap->schema(dn => $dn);
Once we have a dn to search for, Net::LDAP will fetch the schema entry with
$mesg = $self->search(
base => $dn,
scope => 'base',
filter => '(objectClass=*)',
);
Once the schema object has been initialized, schema methods
are used to retrieve the data. There are a number of ways this
can be done. Information on the schema methods can be found
in the Net::LDAP::Schema pod documentation.
The following is a code snippet showing how to get and display
information about returned attributes.
#
# Get the attributes
#
@attributes = $schema->attributes();
#
# Display the attributes
#
foreach ( @attributes)
{
print "attributeType\n";
#
# Get and display the oid number of the objectclass.
#
$oid = $schema->name2oid( "$_" );
#
# Get the various items associated with
# this attribute.
#
@attribute_items = $schema->items( "$oid" );
#
# Read returned item names and display their associated data.
#
foreach $value ( @attribute_items )
{
# We know we are dealing with an attribute, ignore type.
next if ( $value eq 'type'); # Type holds oc or at
#
# Read the data for this item of this oid.
#
@item = $schema->item( $oid, $value );
#
# Some item names have no data, the name itself is data.
# This type of item has 1 as data.
#
if ( defined(@item) && $item[0] == 1 )
{
print "\t$value\n";
next;
}
if ( defined(@item) && $#item >= 0 )
{
print "\t$value: @item\n";
}
}
}
The process is the basically the same for getting objectClass
information. Where schema->attributes() is used, substitute
schema->objectclasses(). From that point on the process is
the same for both objectClasses and attributes.
=head1 BUGS
None known, but there may be some
=head1 AUTHOR (of this document)
Russell Biggs <rgb@ticnet.com>
=head1 COPYRIGHT
All rights to this document are hereby relinquished to Graham Barr.
=for html <hr>
I<$Id$>
=cut

34
lib/Net/LDAP/Extension.pm Normal file
View file

@ -0,0 +1,34 @@
# Copyright (c) 1998-2000 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::Extension;
use vars qw(@ISA $VERSION);
@ISA = qw(Net::LDAP::Message);
$VERSION = "1.01";
#fetch the response name
sub response_name {
my $self = shift;
$self->sync unless exists $self->{Code};
exists $self->{responseName}
? $self->{responseName}
: undef;
}
# fetch the response.
sub response {
my $self = shift;
$self->sync unless exists $self->{Code};
exists $self->{response}
? $self->{response}
: undef;
}
1;

62
lib/Net/LDAP/Extra.pm Normal file
View file

@ -0,0 +1,62 @@
# Copyright (c) 2000 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::Extra;
use strict;
use vars qw($VERSION);
require Net::LDAP;
require Carp;
$VERSION = "0.01";
sub import {
shift;
local $SIG{__DIE__} = \&Carp::croak;
foreach (@_) {
my $file = "Net/LDAP/Extra/$_.pm";
next if exists $INC{$file};
require $file;
"Net::LDAP::Extra::$_"->import;
}
}
1;
__END__
=head1 NAME
Net::LDAP::Extra -- Load extra Net::LDAP methods
=head1 SYNOPSIS
use Net::LDAP::Extra qw(my_extn);
$ldap = Net::LDAP->new( ... );
$ldap->my_extn( ... );
=head1 DESCRIPTION
C<Net::LDAP::Extra> allows extra methods to be added to Net::LDAP.
Normally such methods would be added by sub-classing Net::LDAP, but this
proves to get messy as different people write different additions and
others want to use multiple of these sub-classes. Users end up having
to create sub-classes of their own which inherit from all the extension
sub-classes just so they can get all the features.
C<Net::LDAP::Extra> allows methods to be added directly to
all Net::LDAP objects. This can be done by creating a class
C<Net::LDAP::Extra::name> which exports functions. A
C<use Net::LDAP::Extra qw(name)> will then make these functions avaliable
as a methods on all C<Net::LDAP> objects.
Care should be taken when choosing names for the functions to export
to ensure that they do not clash with others.
=cut

1198
lib/Net/LDAP/FAQ.pod Normal file

File diff suppressed because it is too large Load diff

273
lib/Net/LDAP/Filter.pm Normal file
View file

@ -0,0 +1,273 @@
# Copyright (c) 1997-2000 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::Filter;
use strict;
use vars qw($VERSION);
$VERSION = "0.12";
# filter = "(" filtercomp ")"
# filtercomp = and / or / not / item
# and = "&" filterlist
# or = "|" filterlist
# not = "!" filter
# filterlist = 1*filter
# item = simple / present / substring / extensible
# simple = attr filtertype value
# filtertype = equal / approx / greater / less
# equal = "="
# approx = "~="
# greater = ">="
# less = "<="
# extensible = attr [":dn"] [":" matchingrule] ":=" value
# / [":dn"] ":" matchingrule ":=" value
# present = attr "=*"
# substring = attr "=" [initial] any [final]
# initial = value
# any = "*" *(value "*")
# final = value
# attr = AttributeDescription from Section 4.1.5 of [1]
# matchingrule = MatchingRuleId from Section 4.1.9 of [1]
# value = AttributeValue from Section 4.1.6 of [1]
#
# Special Character encodings
# ---------------------------
# * \2a, \*
# ( \28, \(
# ) \29, \)
# \ \5c, \\
# NUL \00
my $ErrStr;
sub new {
my $self = shift;
my $class = ref($self) || $self;
my $me = bless {}, $class;
if (@_) {
$me->parse(shift) or
return undef;
}
$me;
}
my $Attr = '[-;.:\d\w]*[-;\d\w]';
my %Op = qw(
& and
| or
! not
= equalityMatch
~= approxMatch
>= greaterOrEqual
<= lessOrEqual
:= extensibleMatch
);
my %Rop = reverse %Op;
# Unescape
# \xx where xx is a 2-digit hex number
# \y where y is one of ( ) \ *
sub errstr { $ErrStr }
sub _unescape {
$_[0] =~ s/
\\([\da-fA-F]{2}|.)
/
length($1) == 1
? $1
: chr(hex($1))
/soxeg;
$_[0];
}
my %ch = split(/\s+/, '( \\( ) \\) \\ \\\\ * \\*');
sub _escape { (my $t = $_[0]) =~ s/([\\\(\)\*\0-\37])/$ch{$1}||sprintf("\\%02x",ord($1))/sge; $t }
sub _encode {
my($attr,$op,$val) = @_;
# An extensible match
if ($op eq ':=') {
# attr must be in the form type:dn:1.2.3.4
unless ($attr =~ /^([-;\d\w]*)(:dn)?(:(\w+|[.\d]+))?$/) {
$ErrStr = "Bad attribute $attr";
return undef;
}
my($type,$dn,$rule) = ($1,$2,$4);
return ( {
extensibleMatch => {
matchingRule => $rule,
type => $type,
matchValue => _unescape($val),
dnAttributes => $dn ? 1 : 0
}
});
}
# If the op is = and contains one or more * not
# preceeded by \ then do partial matches
if ($op eq '=' && $val =~ /^(\\.|[^\\*]+)*\*/o ) {
my $n = [];
my $type = 'initial';
while ($val =~ s/^((\\.|[^\\*]+)*)\*+//) {
push(@$n, { $type, _unescape("$1") }) # $1 is readonly, copy it
if length $1;
$type = 'any';
}
push(@$n, { 'final', _unescape($val) })
if length $val;
return ({
substrings => {
type => $attr,
substrings => $n
}
});
}
# Well we must have an operator and no un-escaped *'s on the RHS
return {
$Op{$op} => {
attributeDesc => $attr, assertionValue => _unescape($val)
}
};
}
sub parse {
my $self = shift;
my $filter = shift;
my @stack = (); # stack
my $cur = [];
my $op;
undef $ErrStr;
# Algorithm depends on /^\(/;
$filter =~ s/^\s*//;
$filter = "(" . $filter . ")"
unless $filter =~ /^\(/;
while (length($filter)) {
# Process the start of (& (...)(...))
if ($filter =~ s/^\(\s*([&!|])\s*//) {
push @stack, [$op,$cur];
$op = $1;
$cur = [];
next;
}
# Process the end of (& (...)(...))
elsif ($filter =~ s/^\)\s*//o) {
unless (@stack) {
$ErrStr = "Bad filter, unmatched )";
return undef;
}
my($myop,$mydata) = ($op,$cur);
($op,$cur) = @{ pop @stack };
# Need to do more checking here
push @$cur, { $Op{$myop} => $myop eq '!' ? $mydata->[0] : $mydata };
next if @stack;
}
# present is a special case (attr=*)
elsif ($filter =~ s/^\(\s*($Attr)=\*\)\s*//o) {
push(@$cur, { present => $1 } );
next if @stack;
}
# process (attr op string)
elsif ($filter =~ s/^\(\s*
($Attr)\s*
([:~<>]?=)
((?:\\.|[^\\()]+)*)
\)\s*
//xo) {
push(@$cur, _encode($1,$2,$3));
next if @stack;
}
# If we get here then there is an error in the filter string
# so exit loop with data in $filter
last;
}
if (length $filter) {
# If we have anything left in the filter, then there is a problem
$ErrStr = "Bad filter, error before " . substr($filter,0,20);
return undef;
}
if (@stack) {
$ErrStr = "Bad filter, unmatched (";
return undef;
}
%$self = %{$cur->[0]};
$self;
}
sub print {
my $self = shift;
no strict 'refs'; # select may return a GLOB name
my $fh = @_ ? shift : select;
print $fh $self->as_string,"\n";
}
sub as_string { _string(%{$_[0]}) }
sub _string { # prints things of the form (<op> (<list>) ... )
my $i;
my $str = "";
for ($_[0]) {
/^and/ and return "(&" . join("", map { _string(%$_) } @{$_[1]}) . ")";
/^or/ and return "(|" . join("", map { _string(%$_) } @{$_[1]}) . ")";
/^not/ and return "(!" . _string(%{$_[1]}) . ")";
/^present/ and return "($_[1]=*)";
/^(equalityMatch|greaterOrEqual|lessOrEqual|approxMatch)/
and return "(" . $_[1]->{attributeDesc} . $Rop{$1} . _escape($_[1]->{assertionValue}) .")";
/^substrings/ and do {
my $str = join("*", "",map { _escape($_) } map { values %$_ } @{$_[1]->{substrings}});
$str =~ s/^.// if exists $_[1]->{substrings}[0]{initial};
$str .= '*' unless exists $_[1]->{substrings}[-1]{final};
return "($_[1]->{type}=$str)";
};
/^extensibleMatch/ and do {
my $str = "($_[1]->{type}";
$str .= ":dn" if $_[1]->{dnAttributes};
$str .= ":$_[1]->{matchingRule}" if defined $_[1]->{matchingRule};
$str .= ":=" . _escape($_[1]->{matchValue}) . ")";
return $str;
};
}
die "Internal error $_[0]";
}
1;

112
lib/Net/LDAP/Filter.pod Normal file
View file

@ -0,0 +1,112 @@
=head1 NAME
Net::LDAP::Filter - representation of LDAP filters
=head1 SYNOPSIS
use Net::LDAP::Filter;
$filter = Net::LDAP::Filter->new( $filter_str );
=head1 DESCRIPTION
=head1 CONSTRUCTOR
=over 4
=item new ( FILTER )
Create a new object and parse FILTER.
=back
=head1 METHODS
=over 4
=item parse ( FILTER )
Parse FILTER. The next call to ber will return this filter encoded.
=item asn
Return the data structure suitable for passing directly to L<Convert::ASN1>
to encode a filter object.
=item as_string
Return the filter in text form.
=item print ( [ FH ] )
Print the text representation of the filter to FH, or the currently
selected output handle if FH is not given.
=back
=head1 FILTER SYNTAX
Below is the syntax for a filter given in
RFC-2254 http://info.internet.isi.edu/in-notes/rfc/files/rfc2254.txt
filter = "(" filtercomp ")"
filtercomp = and / or / not / item
and = "&" filterlist
or = "|" filterlist
not = "!" filter
filterlist = 1*filter
item = simple / present / substring / extensible
simple = attr filtertype value
filtertype = equal / approx / greater / less
equal = "="
approx = "~="
greater = ">="
less = "<="
extensible = attr [":dn"] [":" matchingrule] ":=" value
/ [":dn"] ":" matchingrule ":=" value
present = attr "=*"
substring = attr "=" [initial] any [final]
initial = value
any = "*" *(value "*")
final = value
attr = AttributeDescription from Section 4.1.5 of RFC-2251
matchingrule = MatchingRuleId from Section 4.1.9 of RFC-2251
value = AttributeValue from Section 4.1.6 of RFC-2251
Special Character encodings
---------------------------
* \2a, \*
( \28, \(
) \29, \)
\ \5c, \\
NUL \00
=head1 SEE ALSO
L<Net::LDAP>,
L<Other online documentation|Net::LDAP::RFC>
=head1 ACKNOWLEDGEMENTS
This document is based on a document originally written by Russell Fulton
<r.fulton@auckland.ac.nz>.
=head1 AUTHOR
Graham Barr <gbarr@pobox.com>
Please report any bugs, or post any suggestions, to the perl-ldap mailing list
<perl-ldap-dev@lists.sourceforge.net>.
=head1 COPYRIGHT
Copyright (c) 1997-2000 Graham Barr. All rights reserved. This program is
free software; you can redistribute it and/or modify it under the same
terms as Perl itself.
=for html <hr>
I<$Id$>
=cut

508
lib/Net/LDAP/LDIF.pm Normal file
View file

@ -0,0 +1,508 @@
# Copyright (c) 1997-2000 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::LDIF;
use strict;
use SelectSaver;
require Net::LDAP::Entry;
use vars qw($VERSION);
$VERSION = "0.09";
my %mode = qw(w > r < a >>);
sub new {
my $pkg = shift;
my $file = shift || "-";
my $mode = shift || "r";
my %opt = @_;
my $fh;
my $opened_fh = 0;
if (ref($file)) {
$fh = $file;
}
else {
if ($file eq "-") {
if ($mode eq "w") {
($file,$fh) = ("STDOUT",\*STDOUT);
}
else {
($file,$fh) = ("STDIN",\*STDIN);
}
}
else {
require Symbol;
$fh = Symbol::gensym();
my $open = ($mode{$mode} || "<") . $file;
open($fh,$open) or return;
$opened_fh = 1;
}
}
# Default the encoding of DNs to 'none' unless the user specifies
$opt{'encode'} = 'none' unless exists $opt{'encode'};
# Default the error handling to die
$opt{'onerror'} = 'die' unless exists $opt{'onerror'};
$opt{'lowercase'} ||= 0;
my $self = {
change => 0,
changetype => "modify",
modify => 'add',
wrap => 78,
%opt,
fh => $fh,
file => "$file",
opened_fh => $opened_fh,
eof => 0,
};
bless $self, $pkg;
}
sub _read_lines {
my $self = shift;
my @ldif;
{
local $/ = "";
my $fh = $self->{'fh'};
my $ln = $self->{_next_lines} || scalar <$fh>;
unless ($ln) {
$self->{_next_lines} = '';
$self->{_current_lines} = '';
$self->eof(1);
return;
}
$ln =~ s/^#.*\n//mg;
$ln =~ s/\n //sg;
chomp($ln);
$self->{_current_lines} = $ln;
chomp(@ldif = split(/^/, $ln));
$self->{_next_lines} = scalar <$fh> || '';
$self->eof(1) unless $self->{_next_lines};
}
@ldif;
}
# _read_one() is deprecated and will be removed
# in a future version
*_read_one = \&_read_entry;
sub _read_entry {
my $self = shift;
my @ldif;
$self->_clear_error();
@ldif = $self->_read_lines;
return unless @ldif;
shift @ldif if @ldif && $ldif[0] !~ /\D/;
if (@ldif <= 1) {
$self->_error("LDIF entry is not valid", @ldif);
return;
}
elsif (not ( $ldif[0] =~ s/^dn:(:?) *//) ) {
$self->_error("First line of LDIF entry does not begin with 'dn:'", @ldif);
return;
}
my $dn = shift @ldif;
if (length($1)) {
eval { require MIME::Base64 };
if ($@) {
$self->_error($@, @ldif);
return;
}
$dn = MIME::Base64::decode($dn);
}
my $entry = Net::LDAP::Entry->new;
$entry->dn($dn);
if ($ldif[0] =~ /^changetype:\s*/) {
my $changetype = $ldif[0] =~ s/^changetype:\s*//
? shift(@ldif) : $self->{'changetype'};
$entry->changetype($changetype);
return $entry if ($changetype eq "delete");
unless (@ldif) {
$self->_error("LDAP entry is not valid",@ldif);
return;
}
while(@ldif) {
my $modify = $self->{'modify'};
my $modattr;
my $lastattr;
if($changetype eq "modify") {
unless ( (my $tmp = shift @ldif) =~ s/^(add|delete|replace):\s*([-;\w]+)// ) {
$self->_error("LDAP entry is not valid",@ldif);
return;
}
$lastattr = $modattr = $2;
$modify = $1;
}
my @values;
while(@ldif) {
my $line = shift @ldif;
my $attr;
if ($line eq "-") {
$entry->$modify($lastattr, \@values)
if defined $lastattr;
undef $lastattr;
@values = ();
last;
}
$line =~ s/^([-;\w]+):\s*// and $attr = $1;
if ($line =~ s/^:\s*//) {
eval { require MIME::Base64 };
if ($@) {
$self->_error($@, @ldif);
return;
}
$line = MIME::Base64::decode($line);
}
if( defined($modattr) && $attr ne $modattr ) {
$self->_error("LDAP entry is not valid", @ldif);
return;
}
if(!defined($lastattr) || $lastattr ne $attr) {
$entry->$modify($lastattr, \@values)
if defined $lastattr;
$lastattr = $attr;
@values = ($line);
next;
}
push @values, $line;
}
$entry->$modify($lastattr, \@values)
if defined $lastattr;
}
}
else {
my @attr;
my $last = "";
my $vals = [];
my $line;
my $attr;
foreach $line (@ldif) {
$line =~ s/^([-;\w]+):\s*// && ($attr = $1) or next;
if ($line =~ s/^:\s*//) {
eval { require MIME::Base64 };
if ($@) {
$self->_error($@, @ldif);
return;
}
$line = MIME::Base64::decode($line);
}
if ($attr eq $last) {
push @$vals, $line;
next;
}
else {
$vals = [$line];
push(@attr,$last=$attr,$vals);
}
}
$entry->add(@attr);
}
$self->{_current_entry} = $entry;
$entry;
}
sub read_entry {
my $self = shift;
unless ($self->{'fh'}) {
$self->_error("LDIF file handle not valid");
return;
}
$self->_read_entry();
}
# read() is deprecated and will be removed
# in a future version
sub read {
my $self = shift;
return $self->read_entry() unless wantarray;
my($entry, @entries);
push(@entries,$entry) while $entry = $self->read_entry;
@entries;
}
sub eof {
my $self = shift;
my $eof = shift;
if ($eof) {
$self->{_eof} = $eof;
}
$self->{_eof};
}
sub _wrap {
if($_[1] > 40) {
my $pos = $_[1];
while($pos < length($_[0])) {
substr($_[0],$pos,0) = "\n ";
$pos += $_[1]+1;
}
}
$_[0];
}
sub _write_attr {
my($attr,$val,$wrap,$lower) = @_;
my $v;
foreach $v (@$val) {
my $ln = $lower ? lc $attr : $attr;
if ($v =~ /(^[ :]|[\x00-\x1f\x7f-\xff])/) {
require MIME::Base64;
$ln .= ":: " . MIME::Base64::encode($v,"");
}
else {
$ln .= ": " . $v;
}
print _wrap($ln,$wrap),"\n";
}
}
sub _write_attrs {
my($entry,$wrap,$lower) = @_;
my $attr;
foreach $attr ($entry->attributes) {
my $val = $entry->get_value($attr, asref => 1);
_write_attr($attr,$val,$wrap,$lower);
}
}
sub _write_dn {
my($dn,$encode,$wrap) = @_;
if ($dn =~ /^[ :<]|[\x00-\x1f\x7f-\xff]/) {
if ($encode =~ /canonical/i) {
require Net::LDAP::Util;
$dn = Net::LDAP::Util::canonical_dn($dn);
# Canonicalizer won't fix leading spaces, colons or less-thans, which
# are special in LDIF, so we fix those up here.
$dn =~ s/^([ :<])/\\$1/;
} elsif ($encode =~ /base64/i) {
require MIME::Base64;
$dn = "dn:: " . MIME::Base64::encode($dn,"");
} else {
$dn = "dn: $dn";
}
} else {
$dn = "dn: $dn";
}
print _wrap($dn,$wrap), "\n";
}
# write() is deprecated and will be removed
# in a future version
sub write {
my $self = shift;
$self->{change} = 0;
$self->write_entry(@_);
}
sub write_entry {
my $self = shift;
my $entry;
my $change = $self->{change};
my $wrap = int($self->{'wrap'});
my $lower = $self->{'lowercase'};
local($\,$,); # output field and record separators
unless ($self->{'fh'}) {
$self->_error("LDIF file handle not valid");
return;
}
my $saver = SelectSaver->new($self->{'fh'});
my $fh = $self->{'fh'};
foreach $entry (@_) {
unless (ref $entry) {
$self->_error("Entry '$entry' is not a valid Net::LDAP::Entry object.");
next;
}
if ($change) {
my @changes = $entry->changes;
my $type = $entry->changetype;
# Skip entry if there is nothing to write
next if $type eq 'modify' and !@changes;
print "\n" if tell($self->{'fh'});
_write_dn($entry->dn,$self->{'encode'},$wrap);
print "changetype: $type\n";
if ($type eq 'delete') {
next;
}
elsif ($type eq 'add') {
_write_attrs($entry,$wrap,$lower);
next;
}
elsif ($type eq 'modrdn') {
print _write_attr('newrdn',$entry->get_value('newrdn', asref => 1),$wrap,$lower);
print 'deleteoldrdn: ', scalar $entry->get_value('deleteoldrdn'),"\n";
my $ns = $entry->get_value('newsuperior', asref => 1);
print _write_attr('newsuperior',$ns,$wrap,$lower) if defined $ns;
next;
}
my $dash=0;
foreach my $chg (@changes) {
unless (ref($chg)) {
$type = $chg;
next;
}
my $i = 0;
while ($i < @$chg) {
print "-\n" if $dash++;
my $attr = $chg->[$i++];
my $val = $chg->[$i++];
print $type,": ",$attr,"\n";
_write_attr($attr,$val,$wrap,$lower);
}
}
}
else {
print "\n" if tell($self->{'fh'});
_write_dn($entry->dn,$self->{'encode'},$wrap);
_write_attrs($entry,$wrap,$lower);
}
}
1;
}
# read_cmd() is deprecated in favor of read_entry()
# and will be removed in a future version
sub read_cmd {
my $self = shift;
return $self->read_entry() unless wantarray;
my($entry, @entries);
push(@entries,$entry) while $entry = $self->read_entry;
@entries;
}
# _read_one_cmd() is deprecated in favor of _read_one()
# and will be removed in a future version
*_read_one_cmd = \&_read_entry;
# write_cmd() is deprecated in favor of write_entry()
# and will be removed in a future version
sub write_cmd {
my $self = shift;
$self->{change} = 1;
$self->write_entry(@_);
}
sub done {
my $self = shift;
if ($self->{fh}) {
if ($self->{opened_fh}) {
close $self->{fh};
undef $self->{opened_fh};
}
delete $self->{fh};
}
1;
}
my %onerror = (
'die' => sub {
my $self = shift;
require Carp;
$self->done;
Carp::croak($self->error(@_));
},
'warn' => sub {
my $self = shift;
require Carp;
Carp::carp($self->error(@_));
},
'undef' => sub {
my $self = shift;
require Carp;
Carp::carp($self->error(@_)) if $^W;
},
);
sub _error {
my ($self,$errmsg,@errlines) = @_;
$self->{_err_msg} = $errmsg;
$self->{_err_lines} = join "\n",@errlines;
scalar &{ $onerror{ $self->{onerror} } }($self,$self->{_err_msg}) if $self->{onerror};
}
sub _clear_error {
my $self = shift;
undef $self->{_err_msg};
undef $self->{_err_lines};
}
sub error {
my $self = shift;
$self->{_err_msg};
}
sub error_lines {
my $self = shift;
$self->{_err_lines};
}
sub current_entry {
my $self = shift;
$self->{_current_entry};
}
sub current_lines {
my $self = shift;
$self->{_current_lines};
}
sub next_lines {
my $self = shift;
$self->{_next_lines};
}
sub DESTROY {
my $self = shift;
$self->done();
}
1;

141
lib/Net/LDAP/LDIF.pod Normal file
View file

@ -0,0 +1,141 @@
=head1 NAME
Net::LDAP::LDIF - LDIF reading and writing
=head1 SYNOPSIS
use Net::LDAP::LDIF;
$ldif = Net::LDAP::LDIF->new( "file.ldif", "r", onerror => 'undef' );
while( not $ldif->eof() ) {
$entry = $ldif->read_entry();
if ( $ldif->error() ) {
print "Error msg: ",$ldif->error(),"\n";
print "Error lines:\n",$ldif->error_lines(),"\n";
}
else {
# do stuff
}
}
$ldif->done();
=head1 DESCRIPTION
B<Net::LDAP::LDIF> provides a means to convert between
L<Net::LDAP::Entry> objects and LDAP entries represented
in LDIF format files. Reading and writing are supported and may manipulate
single entries or lists of entries.
As when reading an entire file into memory with perl normally, take into
account the possibility of memory use when loading an LDIF file in one go.
=head1 CONSTRUCTOR
=over 4
=item new ( [ FILE [, MODE [,OPTIONS ]]] )
FILE may be the name of a file or an already open filehandle. If a filename
is passed in then it will be opened with the mode specified.
MODE defaults to "r" for read. You may specify "w" to for write+truncate or
"a" for write+append.
OPTIONS is a list of key-value pairs. Valid options are:
=over 4
=item encode
Some values in LDIF cannot be written verbatim and have to be encoded
in some way. This option lets you specify how. Valid encoding options
are 'none' (the default), 'canonical' (see
L<Net::LDAP::Util/canonical_dn>), or 'base64'.
=back
=item onerror
If set then Net::LDAP::LDIF will check all responses for errors on all methods.
If an error is detected then the specified action will be taken. Valid values
and their actions are.
=over 4
=item die
Net::LDAP::LDIF will croak with an appropriate message.
=item warn
Net::LDAP::LDIF will warn with an appropriate message.
=item undef
Net::LDAP::LDIF will warn with an appropriate message if C<-w> is in effect.
The method that was called will return C<undef>
=back
=back
=head1 METHODS
=over 4
=item read_entry
Read one entry from the file and return it as a Net::LDAP::Entry object.
=item eof
Returns true when the end of the file is reached.
=item write_entry ( ENTRIES )
=item done
This method signals that the LDIF object is no longer needed. If a file was
opened automatically when the object was created it will be closed. This
method is called automatically via DESTROY when the object goes out of scope.
=item error
Returns error message if error was found.
=item error_lines
Returns lines that resulted in error.
=item current_entry
Returns the current Net::LDAP::Entry object.
=item current_lines
Returns the lines that generated the current Net::LDAP::Entry object.
=item next_lines
Returns the lines that will generate the next Net::LDAP::Entry object.
=back
=head1 AUTHOR
Graham Barr <gbarr@pobox.com>.
Please report any bugs, or post any suggestions, to the perl-ldap mailing list
<perl-ldap-dev@lists.sourceforge.net>.
=head1 COPYRIGHT
Copyright (c) 1997-2000 Graham Barr. All rights reserved. This program is
free software; you can redistribute it and/or modify it under the same
terms as Perl itself.
=for html <hr>
=cut

227
lib/Net/LDAP/Message.pm Normal file
View file

@ -0,0 +1,227 @@
# $Id$
# Copyright (c) 1997-2000 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::Message;
use Net::LDAP::Constant qw(LDAP_SUCCESS LDAP_COMPARE_TRUE LDAP_COMPARE_FALSE);
use Net::LDAP::ASN qw(LDAPRequest);
use strict;
use vars qw($VERSION);
$VERSION = "1.05";
my $MsgID = 0;
# We do this here so when we add threading we can lock it
sub NewMesgID {
$MsgID = 1 if ++$MsgID > 65535;
$MsgID;
}
sub new {
my $self = shift;
my $type = ref($self) || $self;
my $parent = shift;
my $arg = shift;
$self = bless {
parent => $parent,
mesgid => NewMesgID(),
callback => $arg->{callback} || undef,
}, $type;
$self;
}
sub code {
my $self = shift;
$self->sync unless exists $self->{resultCode};
exists $self->{resultCode}
? $self->{resultCode}
: undef
}
sub done {
my $self = shift;
exists $self->{resultCode};
}
sub dn {
my $self = shift;
$self->sync unless exists $self->{resultCode};
exists $self->{matchedDN}
? $self->{matchedDN}
: undef
}
sub referrals {
my $self = shift;
$self->sync unless exists $self->{resultCode};
exists $self->{referral}
? @{$self->{referral}}
: ();
}
sub server_error {
my $self = shift;
$self->sync unless exists $self->{resultCode};
exists $self->{errorMessage}
? $self->{errorMessage}
: undef
}
sub error {
my $self = shift;
$self->server_error
or require Net::LDAP::Util
and Net::LDAP::Util::ldap_error_desc( $self->code );
}
sub set_error {
my $self = shift;
($self->{resultCode},$self->{errorMessage}) = ($_[0]+0, "$_[1]");
$self;
}
sub sync {
my $self = shift;
my $ldap = $self->{parent};
my $err;
until(exists $self->{resultCode}) {
$err = $ldap->sync($self->mesg_id) or next;
$self->set_error($err,"Protocol Error")
unless exists $self->{resultCode};
return $err;
}
LDAP_SUCCESS;
}
sub decode { # $self, $pdu, $control
my $self = shift;
my $result = shift;
my $data = (values %{$result->{protocolOp}})[0];
@{$self}{keys %$data} = values %$data;
# Should the controls be associated with the whole request, or in
# the case of a search the entry in this packet ? -- GMB
$self->{controls} = $result->{controls}
if exists $result->{controls};
# free up memory as we have a result so we will not need to re-send it
delete $self->{pdu};
# tell our LDAP client to forget us as this message has now completed
# all communications with the server
$self->parent->_forgetmesg($self);
$self->{callback}->($self)
if (defined $self->{callback});
$self;
}
sub abandon {
my $self = shift;
return if exists $self->{resultCode}; # already complete
my $ldap = $self->{parent};
$ldap->abandon($self->{mesgid});
}
sub saslref {
my $self = shift;
$self->sync unless exists $self->{resultCode};
exists $self->{sasl}
? $self->{sasl}
: undef
}
sub encode {
my $self = shift;
$self->{pdu} = $LDAPRequest->encode(@_, messageID => $self->{mesgid})
or return;
1;
}
sub control {
my $self = shift;
if (exists $self->{controls}) {
require Net::LDAP::Control;
my $hash = $self->{ctrl_hash} = {};
foreach my $asn (@{delete $self->{controls}}) {
my $ctrl = Net::LDAP::Control->from_asn($asn);
push @{$hash->{$ctrl->type} ||= []}, $ctrl;
}
}
return unless exists $self->{ctrl_hash};
@_ ? exists $self->{ctrl_hash}{$_[0]}
? @{$self->{ctrl_hash}{$_[0]}}
: ()
: map { @$_ } values %{$self->{ctrl_hash}};
}
sub pdu { shift->{pdu} }
sub callback { shift->{callback} }
sub parent { shift->{parent} }
sub mesg_id { shift->{mesgid} }
sub is_error { shift->code }
##
##
##
@Net::LDAP::Add::ISA = qw(Net::LDAP::Message);
@Net::LDAP::Delete::ISA = qw(Net::LDAP::Message);
@Net::LDAP::Modify::ISA = qw(Net::LDAP::Message);
@Net::LDAP::ModDN::ISA = qw(Net::LDAP::Message);
@Net::LDAP::Compare::ISA = qw(Net::LDAP::Message);
@Net::LDAP::Unbind::ISA = qw(Net::LDAP::Message::Dummy);
@Net::LDAP::Abandon::ISA = qw(Net::LDAP::Message::Dummy);
sub Net::LDAP::Compare::is_error {
my $mesg = shift;
my $code = $mesg->code;
$code != LDAP_COMPARE_FALSE and $code != LDAP_COMPARE_TRUE
}
{
package Net::LDAP::Message::Dummy;
use vars qw(@ISA);
@ISA = qw(Net::LDAP::Message);
sub sync { shift }
sub decode { shift }
sub abandon { shift }
sub code { 0 }
sub error { "" }
sub dn { "" }
sub done { 1 }
}
1;

111
lib/Net/LDAP/Message.pod Normal file
View file

@ -0,0 +1,111 @@
=head1 NAME
Net::LDAP::Message - Message response from LDAP server
=head1 SYNOPSIS
use Net::LDAP;
=head1 DESCRIPTION
B<Net::LDAP::Message> is a base class for the objects returned by the
L<Net::LDAP> methods
L<abandon|Net::LDAP/item_abandon>,
L<add|Net::LDAP/item_add>,
L<bind|Net::LDAP/item_bind>,
L<compare|Net::LDAP/item_compare>,
L<delete|Net::LDAP/item_delete>,
L<modify|Net::LDAP/item_modify>,
L<moddn|Net::LDAP/item_moddn>,
L<search|Net::LDAP/item_search> and
L<unbind|Net::LDAP/item_unbind>.
The sub-class L<Net::LDAP::Search> returned by L<search|Net::LDAP/item_search> also
defines many methods.
If the L<Net::LDAP> object is in async mode then all these methods, except
C<done>, will cause a wait until the request is completed.
=head1 METHODS
=over 4
=item code
The code value in the result message from the server. Normally for
a success zero will be returned. Constants for the result codes
can be imported from the L<Net::LDAP> or L<Net::LDAP::Constant> module.
=item control [ OID ]
Return a list of controls returned from the server. If OID is given
then only controls with type equal to OID will be returned.
=item dn
The DN in the result message from the server.
=item done
Returns I<true> if the request has been completed.
=item error
The error message in the result message from the server. If the server did
not include an error message, then the result of
L<ldap_error_desc|Net::LDAP::Util/ldap_error_desc> with the
error code from the result message.
=item is_error
Returns I<true> if the result code is considered to be an error for the operation.
=item mesg_id
The message id of the request message sent to the server.
=item referrals
Returns a list of referrals from the result message.
=item server_error
The error message returned by the server, or undef if the server did not provide
a message.
=item sync
Wait for the server to complete the request.
=back
=head1 SEE ALSO
L<Net::LDAP>,
L<Net::LDAP::Search>,
L<Net::LDAP::Constant>,
L<Net::LDAP::Util>
=head1 ACKNOWLEDGEMENTS
This document is based on a document originally written by Russell Fulton
<r.fulton@auckland.ac.nz>.
=head1 AUTHOR
Graham Barr <gbarr@pobox.com>
Please report any bugs, or post any suggestions, to the perl-ldap mailing list
<perl-ldap-dev@lists.sourceforge.net>.
=head1 COPYRIGHT
Copyright (c) 1997-2000 Graham Barr. All rights reserved. This program is
free software; you can redistribute it and/or modify it under the same
terms as Perl itself.
=for html <hr>
I<$Id$>
=cut

1295
lib/Net/LDAP/RFC.pod Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,63 @@
=head1 NAME
Net::LDAP::Reference - search reference
=head1 SYNOPSIS
use Net::LDAP;
$ldap->search( @search_args, callback => \&process);
sub process {
my $mesg = shift;
my $obj = shift;
if (!$obj) {
# Search complete
}
elsif ($obj->isa('Net::LDAP::Reference')) {
my $ref;
foreach $ref ($obj->references) {
# process ref
}
}
else {
# Process Net::LDAP::Entry
}
}
=head1 DESCRIPTION
=head1 METHODS
=over 4
=item references
Returns a list of references from the server.
=back
=head1 SEE ALSO
L<Net::LDAP>,
L<Net::LDAP::Search>
=head1 AUTHOR
Graham Barr <gbarr@pobox.com>.
Please report any bugs, or post any suggestions, to the perl-ldap mailing list
<perl-ldap-dev@lists.sourceforge.net>.
=head1 COPYRIGHT
Copyright (c) 1997-2000 Graham Barr. All rights reserved. This program is
free software; you can redistribute it and/or modify it under the same
terms as Perl itself.
=for html <hr>
I<$Id$>
=cut

631
lib/Net/LDAP/Schema.pm Normal file
View file

@ -0,0 +1,631 @@
# Copyright (c) 1998-2000 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;
use vars qw($VERSION);
$VERSION = "0.10";
#
# 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;
return $schema unless @_;
return $schema->parse( shift ) ? $schema : undef;
}
sub _error {
my $self = shift;
$self->{error} = shift;
return;
}
sub parse {
my $schema = shift;
my $arg = shift;
unless ($arg) {
$schema->{error} = "Bad argument";
return undef;
}
%$schema = ();
my $entry;
if( ref $arg ) {
if (UNIVERSAL::isa($arg, 'Net::LDAP::Entry')) {
$entry = $arg;
}
elsif (UNIVERSAL::isa($arg, '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.
}
#
# The names of all the attributes.
# Or all atts in (one or more) objectclass(es).
#
sub attributes {
my $self = shift;
my @oc = @_;
my %res;
if( @oc ) {
@res{ $self->must( @oc ) } = ();
@res{ $self->may( @oc ) } = ();
}
else {
@res{ @{ $self->{at} } } = () if $self->{at};
}
return wantarray ? (keys %res) : [keys %res];
}
# The names of all the object classes
sub objectclasses {
my $self = shift;
my $res = $self->{oc};
return wantarray ? @$res : $res;
}
# Return all syntaxes
sub syntaxes {
my $self = shift;
my $res = $self->{syn};
return wantarray ? @$res : $res;
}
# The names of all the matchingrules
sub matchingrules {
my $self = shift;
my $res = $self->{mr};
return wantarray ? @$res : $res;
}
# The names of all the matchingruleuse
sub matchingruleuse {
my $self = shift;
my $res = $self->{mru};
return wantarray ? @$res : $res;
}
# The names of all the ditstructurerules
sub ditstructurerules {
my $self = shift;
my $res = $self->{dts};
return wantarray ? @$res : $res;
}
# The names of all the ditcontentrules
sub ditcontentrules {
my $self = shift;
my $res = $self->{dtc};
return wantarray ? @$res : $res;
}
# The names of all the nameforms
sub nameforms {
my $self = shift;
my $res = $self->{nfm};
return wantarray ? @$res : $res;
}
sub superclass {
my $self = shift;
my $oc = shift;
my $oid = $self->is_objectclass( $oc );
return scalar _error($self, "Not an objectClass") unless $oid;
my $res = $self->{oid}->{$oid}->{sup};
return scalar _error($self, "No superclass") unless $res;
return wantarray ? @$res : $res;
}
sub must {
my $self = shift;
$self->_must_or_may( "must", @_ );
}
sub may {
my $self = shift;
$self->_must_or_may( "may", @_ );
}
#
# Return must or may attributes for this OC. [As array or array ref]
# return empty array/undef on error
#
sub _must_or_may {
my $self = shift;
my $must_or_may = shift;
my @oc = @_ or return;
#
# If called with an entry, get the OC names and continue
#
if( UNIVERSAL::isa( $oc[0], "Net::LDAP::Entry" ) ) {
my $entry = $oc[0];
@oc = $entry->get_value( "objectclass" )
or return;
}
my %res; # Use hash to get uniqueness
foreach my $oc ( @oc ) {
my $oid = $self->is_objectclass( $oc );
if( $oid ) {
my $res = $self->{oid}->{$oid}->{$must_or_may} or next;
@res{ @$res } = (); # Add in, getting uniqueness
}
}
return wantarray ? (keys %res) : [ keys %res ];
}
#
# Return the value of an item, e.g. 'desc'. If item is array ref and we
# are called from array context, return an array, else scalar
#
sub item {
my $self = shift;
my $arg = shift;
my $item_name = shift; # May be undef. If so all are returned
my @oid = $self->name2oid( $arg );
return _error($self, @oid ? "Non-unique name" : "Unknown name")
unless @oid == 1;
my $item_ref = $self->{oid}->{$oid[0]} or return _error($self, "Unknown OID");
my $value = $item_ref->{$item_name} or return _error($self, "No such property");
delete $self->{error};
if( ref $value eq "ARRAY" && wantarray ) {
return @$value;
}
else {
return $value;
}
}
#
# Return a list of items for a particular name or oid
#
# BUG:Dumps internal representation rather than real info. E.g. shows
# the alias/name distinction we create and the 'type' field.
#
sub items {
my $self = shift;
my $arg = shift;
my @oid = $self->name2oid( $arg );
return _error($self, @oid ? "Non-unique name" : "Unknown name")
unless @oid == 1;
my $item_ref = $self->{oid}->{$oid[0]} or return _error($self, "Unknown OID");
delete $self->{error};
return wantarray ? (keys %$item_ref) : [keys %$item_ref];
}
#
# Given a name, alias or oid, return oid or undef. Undef if not known.
#
sub name2oid {
my $self = shift;
my $name = lc shift;
return _error($self, "Bad name") unless defined($name) && length($name);
return $name if exists $self->{oid}->{$name}; # Already an oid
my $oid = $self->{name}->{$name} || $self->{aliases}->{$name}
or return _error($self, "Unknown name");
return (wantarray && ref $oid) ? @$oid : $oid;
}
#
# Given an an OID (not a name) return the canonical name. Undef if not
# an OID
#
sub oid2name {
my $self = shift;
my $oid = shift;
return _error($self, "Bad OID") unless $oid;
return _error($self, "Unknown OID") unless $self->{oid}->{$oid};
delete $self->{error};
return $self->{oid}->{$oid}->{name};
}
#
# Given name or oid, return oid or undef if not of appropriate type
#
sub is_attribute {
my $self = shift;
return $self->_is_type( "at", @_ );
}
sub is_objectclass {
my $self = shift;
return $self->_is_type( "oc", @_ );
}
sub is_syntax {
my $self = shift;
return $self->_is_type( "syn", @_ );
}
sub is_matchingrule {
my $self = shift;
return $self->_is_type( "mr", @_ );
}
sub is_matchingruleuse {
my $self = shift;
return $self->_is_type( "mru", @_ );
}
sub is_ditstructurerule {
my $self = shift;
return $self->_is_type( "dts", @_ );
}
sub is_ditcontentrule {
my $self = shift;
return $self->_is_type( "dtc", @_ );
}
sub is_nameform {
my $self = shift;
return $self->_is_type( "nfm", @_ );
}
# --------------------------------------------------
# Internal functions
# --------------------------------------------------
#
# Given a type and a name_or_oid, return true (the oid) if the name_or_oid
# is of the appropriate type. Else return undef.
#
sub _is_type {
my ($self, $type, $name) = @_;
foreach my $oid ($self->name2oid( $name )) {
my $hash = $self->{oid}->{$oid} or next;
return $oid if $hash->{type} eq $type;
}
undef;
}
#
# 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.
#
# ->{at} = [ list of canonical names of attributes ]
# ->{oc} = [ list of can. names of objectclasses ]
# ->{syn} = [ list of can. names of syntaxes (we make names from descripts) ]
# ->{mr} = [ list of can. names of matchingrules ]
# ->{mru} = [ list of can. names of matchingruleuse ]
# ->{dts} = [ list of can. names of ditstructurerules ]
# ->{dtc} = [ list of can. names of ditcontentrules ]
# ->{nfm} = [ list of can. names of nameForms ]
#
# This is used to optimise name => oid lookups (to avoid searching).
# This could be removed or made into a cache to reduce memory usage.
# The names include any aliases.
#
# ->{name}->{ $lower_case_name } = $oid
#
#
# These items have no following arguments
#
my %flags = map { ($_,1) } qw(
single-value
obsolete
collective
no-user-modification
abstract
structural
auxiliary
);
#
# 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 = ( at => "attributetypes",
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 list of names
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*/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;
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]" 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]";
}
}
#
# Extract the maximum length of a syntax
#
if ( exists $schema_entry{syntax}) {
$schema_entry{syntax} =~ s/{(\d+)}//
and $schema_entry{max_length} = $1;
}
#
# Force a name if we don't have one
#
if (!exists $schema_entry{name}) {
if (exists $schema_entry{desc}) {
($schema_entry{name} = $schema_entry{desc}) =~ s/\s+//g
}
else {
$schema_entry{name} = "$type:$schema_entry{oid}"
}
}
#
# If we have multiple names, make the name be the first and demote the rest to aliases
#
$schema_entry{name} = shift @{$schema_entry{aliases} = $schema_entry{name}}
if ref $schema_entry{name};
#
# In the schema we store:
#
# 1 - The schema entry referenced by OID
# 2 - a list of canonical names of each type
# 3 - a (lower-cased) canonical name -> OID map
# 4 - a (lower-cased) alias -> OID map
#
$schema->{oid}->{$oid} = \%schema_entry;
my $uc_name = uc $schema_entry{name};
push @names, $uc_name;
foreach my $name ( @{$schema_entry{aliases}}, $uc_name ) {
if (exists $schema->{name}{lc $name}) {
$schema->{name}{lc $name} = [ $schema->{name}{lc $name} ] unless ref $schema->{name}{lc $name};
push @{$schema->{name}{lc $name}}, $oid;
}
else {
$schema->{name}{lc $name} = $oid;
}
}
}
}
$schema->{entry} = $entry;
return $schema;
}
#
# Get the syntax of an attribute
#
sub syntax {
my $self = shift;
my $attr = shift;
my $oid = $self->is_attribute( $attr ) or return undef;
my $syntax = $self->{oid}->{$oid}->{syntax};
unless( $syntax ) {
my @sup = @{$self->{oid}->{$oid}->{sup}};
$syntax = $self->syntax( $sup[0] );
}
return $syntax;
}
#
# Given an OID or name (or alias), return the canonical name
#
sub name {
my $self = shift;
my $arg = shift;
my @oid = $self->name2oid( $arg );
return undef unless @oid == 1;
return $self->oid2name( $oid[0] );
}
sub error {
$_[0]->{error};
}
#
# Return base entry
#
sub entry {
$_[0]->{entry};
}
1;

265
lib/Net/LDAP/Schema.pod Normal file
View file

@ -0,0 +1,265 @@
=head1 NAME
Net::LDAP::Schema - Load and manipulate an LDAP v3 Schema
=head1 SYNOPSIS
use Net::LDAP;
use Net::LDAP::Schema;
#
# Read schema from server
#
$ldap = Net::LDAP->new( $server );
$ldap->bind();
$schema = $ldap->schema();
#
# Load from LDIF
#
$schema = Net::LDAP::Schema->new;
$schema->parse( "schema.ldif" ) or die $schema->error;
=head1 DESCRIPTION
B<Net::LDAP::Schema> provides a means to load an LDAP schema and query it
for information regarding supported objectclasses, attributes and syntaxes.
=head1 METHODS
Where a method is stated as taking the 'name or oid' of a schema item (which
may be an object class, attribute or syntax) then a case-insensitive name
or raw oid (object identifier, in dotted numeric string form, e.g. 2.5.4.0)
may be supplied.
=over 4
=item attributes
With no arguments, returns a list of the names all attributes in the schema.
@atts = $schema->attributes();
If called with an argument which is the name or oid of a known object class,
returns a list of the attributes which may (or must) be present in the OC.
@person_atts = $schema->attributes( "person" );
Return value is an array or array reference depending on calling context, or
empty list on error.
=item ditstructurerules
Returns a list of the names of all ditstructurerules in the schema.
@dts = $schema->ditstructurerules();
Return value is an array or array reference depending on calling context.
=item ditcontentrules
Returns a list of the names of all ditcontentrules in the schema.
@dtc = $schema->ditcontentrules();
Return value is an array or array reference depending on calling context.
=item dump
Given an argument which is the name of a file, and the file or
directory has write permission, will dump the raw schema
information to a file. If no argument is given the raw schema
information is dumped to standard out.
$result = $schema->dump( "./schema.dump" );
or
$result = $schema->dump();
If no schema data is returned from directory server, the method
will return undefined. Otherwise a value of 1 is always returned.
=item error
Returns the last error encountered.
=item is_objectclass, is_attribute, is_syntax, is_matchingrule
Given the name or oid of a schema item (object class, attribute,
syntax or matchingrule respectively) returns the assoicated OID
or undef if the name or oid is not of the appropriate type.
# Is foo a known OC?
$oid = $schema->is_objectclass( "foo" );
# No? Bale out.
die( "Not an objectclass" ) unless $oid;
# Yes...so we can do this
@must = $schema->must( $oid );
=item is_matchingruleuse, is_ditstructurerule, is_ditcontentrule, is_nameform
Given the name or oid of a schema item (matchingruleuse, ditstructurerule,
ditcontentrule or nameform respectively) returns the assoicated OID
or undef if the name or oid is not of the appropriate type.
# Is foo a known OC?
$oid = $schema->is_nameform( "foo" );
# No? Bale out.
die( "Not a nameform" ) unless $oid;
=item item
Given two arguments, first of which is the name or oid of a known
object class or attribute and second of which is the name of the
item, returns the item's data value. The item's value may be
undefined.
@item = $schema->item( $oid, "desc" );
Return value is an array or a value depending on calling context.
If the first argument is a name and there is more than one item in the
schema with that name then undef, or the empty list, will be returned.
=item items
Given an argument which is the name or oid of a known object class or
attribute, returns the items available for this attribute or object class.
The returned item name may have an undefined value.
@items = $schema->items( $oid );
Return value is a list or array reference depending on calling context.
If the argument given is a name and there is more than one item in the
schema with that name then undef, or the empty list, will be returned.
=item matchingrules
Returns a list of the names of all matchingrules in the schema.
@mrs = $schema->matchingrules();
Return value is an array or array reference depending on calling context.
=item matchingruleuse
Returns a list of the names of all matchingruleuse in the schema.
@mru = $schema->matchingruleuse();
Return value is an array or array reference depending on calling context.
=item may
Given an argument which is the name or oid of a known object class, returns
the name of the attributes which are optional in the class.
@may = $schema->may( $oc );
Return value is an array or array reference depending on calling context.
=item must
Given an argument which is the name or oid of a known object class, returns
the name of the attributes which are mandatory in the class
@must = $schema->must( $oc );
Return value is an array or array reference depending on calling context.
=item name
Given an argument which is the name or oid of an item,
returns the items canonical name or undef if the name or oid is not known.
If the argument given is a name and there is more than one item in the
schema with that name then undef will be returned.
=item name2oid
Given the name of a schema item (object class, attribute or syntax) returns
the assoicated OID or undef if it is not recognised.
It is possible that two objects, of different types, have the same name.
In this case C<name2oid> will return a list of OIDs in an array context.
In a scalar context it will return undef if there is more than one object
with the given name.
=item nameforms
Returns a list of the names of all nameforms in the schema.
@nfm = $schema->nameforms();
Return value is an array or array reference depending on calling context.
=item objectclasses
Returns a list of the names of all objectclasses in the schema.
@ocs = $schema->objectclasses();
Return value is an array or array reference depending on calling context.
=item parse
Takes a single argument which can be any of, A message objected returned from
an LDAP search, a Net::LDAP::Entry object or the name of a file containing
an LDIF form of the schema.
If the argument is a message result from a search, Net::LDAP::Schema will parse
the schema from the first entry returned.
Returns true on success and C<undef> on error.
=item superclass
Given an argument which is the name or oid of a known objectclass, returns
the list of names of the immediate superclasses.
=item syntax
Given an argument which is the name or oid of a known attribute, returns the
name of the attribute's syntax (or the syntax of the attributes superior
if the syntax is inherited).
$name_syntax = $schema->syntax( "commonName" );
=item syntaxes
Returns a list of the names of all ldapSyntaxes in the schema. (The name of
a syntax is not well defined. It may be an OID or abbreviated description).
@syns = $schema->syntaxes();
Return value is an array or array reference depending on calling context.
=back
=head1 SEE ALSO
L<Net::LDAP>,
L<Net::LDAP::RFC>
=head1 AUTHORS
Graham Barr <gbarr@pobox.com>
John Berthels <jjb@nexor.co.uk>
Please report any bugs, or post any suggestions, to the perl-ldap mailing list
<perl-ldap-dev@lists.sourceforge.net>.
=head1 COPYRIGHT
Copyright (c) 1998-2000 Graham Barr. All rights reserved. This program is
free software; you can redistribute it and/or modify it under the same
terms as Perl itself.
=for html <hr>
I<$Id$>
=cut

174
lib/Net/LDAP/Search.pm Normal file
View file

@ -0,0 +1,174 @@
# Copyright (c) 1997-2000 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::Search;
use strict;
use vars qw(@ISA $VERSION);
use Net::LDAP::Message;
use Net::LDAP::Entry;
use Net::LDAP::Filter;
use Net::LDAP::Constant qw(LDAP_SUCCESS LDAP_DECODING_ERROR);
@ISA = qw(Net::LDAP::Message);
$VERSION = "0.07";
sub first_entry { # compat
my $self = shift;
$self->entry(0);
}
sub next_entry { # compat
my $self = shift;
$self->entry( defined $self->{'CurrentEntry'}
? $self->{'CurrentEntry'} + 1
: 0);
}
sub decode {
my $self = shift;
my $result = shift;
return $self->SUPER::decode($result)
if exists $result->{protocolOp}{searchResDone};
my $data;
if ($data = delete $result->{protocolOp}{searchResEntry}) {
my $entry = Net::LDAP::Entry->new;
$entry->decode($data)
or $self->set_error(LDAP_DECODING_ERROR,"LDAP decode error")
and return;
push(@{$self->{entries} ||= []}, $entry);
$self->{callback}->($self,$entry)
if (defined $self->{callback});
return $self;
}
elsif ($data = delete $result->{protocolOp}{searchResRef}) {
push(@{$self->{'reference'} ||= []}, @$data);
$self->{callback}->($self, bless $data, 'Net::LDAP::Reference')
if (defined $self->{callback});
return $self;
}
$self->set_error(LDAP_DECODING_ERROR, "LDAP decode error");
return;
}
sub entry {
my $self = shift;
my $index = shift || 0; # avoid undef warning and default to first entry
my $entries = $self->{entries} ||= [];
my $ldap = $self->parent;
# There could be multiple response to a search request
# but only the last will set {resultCode}
until (exists $self->{resultCode} || (@{$entries} > $index)) {
return
unless $ldap->_recvresp($self->mesg_id) == LDAP_SUCCESS;
}
return
unless (@{$entries} > $index);
$self->{current_entry} = $index; # compat
return $entries->[$index];
}
sub all_entries { goto &entries } # compat
sub count {
my $self = shift;
scalar entries($self);
}
sub shift_entry {
my $self = shift;
entry($self, 0) ? shift @{$self->{entries}} : undef;
}
sub pop_entry {
my $self = shift;
entry($self, 0) ? pop @{$self->{entries}} : undef;
}
sub sorted {
my $self = shift;
$self->sync unless exists $self->{resultCode};
return unless exists $self->{entries} && ref($self->{entries});
return @{$self->{entries}} unless @{$self->{entries}} > 1;
require Net::LDAP::Util;
map { $_->[0] }
sort {
my $v;
my $i = 2;
foreach my $attr (@_) {
$v = ($a->[$i] ||= join("\000", @{$a->[0]->get_value($attr, asref => 1) || []}))
cmp
($b->[$i] ||= join("\000", @{$b->[0]->get_value($attr, asref => 1) || []}))
and last;
$i++;
}
$v ||= ($a->[1] ||= Net::LDAP::Util::canonical_dn( $a->[0]->dn, 1))
cmp
($b->[1] ||= Net::LDAP::Util::canonical_dn( $b->[0]->dn, 1));
}
map { [ $_ ] } @{$self->{entries}};
}
sub references {
my $self = shift;
$self->sync unless exists $self->{resultCode};
return unless exists $self->{'reference'} && ref($self->{'reference'});
@{$self->{'reference'} || []}
}
sub as_struct {
my $self = shift;
my %result = map { ( $_->dn, ($_->{'attrs'} || $_->_build_attrs) ) } entries($self);
return \%result;
}
sub entries {
my $self = shift;
$self->sync unless exists $self->{resultCode};
@{$self->{entries} || []}
}
package Net::LDAP::Reference;
sub references {
my $self = shift;
@{$self}
}
1;

117
lib/Net/LDAP/Search.pod Normal file
View file

@ -0,0 +1,117 @@
=head1 NAME
Net::LDAP::Search - Object returned by Net::LDAP search method
=head1 SYNOPSIS
use Net::LDAP;
$mesg = $ldap->search( @search_args );
@entries = $mesg->entries;
=head1 DESCRIPTION
A B<Net::LDAP::Search> object is returned from the
L<search|Net::LDAP/item_search> method of a L<Net::LDAP>
object. It is a container object which holds the results of the search.
=head1 METHODS
B<Net::LDAP::Search> inherits from L<Net::LDAP::Message>, and so supports
all methods defined in L<Net::LDAP::Message>.
=over 4
=item as_struct
Returns a reference to a HASH, where the keys are the DN's of the results
and the values are HASH references. These second level HASH's hold the
attributes such that the keys are the attribute names, in lowercase, and
the values are references to and ARRAY holding the values.
This method will block until the whole search request has finished.
=item count
Returns the number of entries returned by the server.
This method will block until the whole search request has finished.
=item entry ( INDEX )
Return the N'th entry, which will be a L<Net::LDAP::Entry> object. If
INDEX is greater than the total number of entries returned then
undef will be returned.
This method will block until the search request has returned enough
entries.
=item entries
Return an array of L<Net::LDAP::Entry> objects hat were returned from the
server.
This method will block until the whole search request has finished.
=item pop_entry
Pop an entry from the internal list of L<Net::LDAP::Entry> objects for this
search. If there are not more entries then undef is returned.
This call will block, if the list is empty, until the server returns
another entry.
=item references
Return a list of references that the server returned. This will be a list
of L<Net::LDAP::Reference> objects.
=item sorted ( [ ATTR_LIST ] )
Return a list of L<Net::LDAP::Entry> objects,
sorted by the attributes given in ATTR_LIST. The attributes are
compared in the order specified, each only being compared if all
the prior attributes compare equal. If all the specified attributes
compare equal then the DN is used to determine order.
=item shift_entry
Shift an entry from the internal list of L<Net::LDAP::Entry> objects for this
search. If there are not more entries then undef is returned.
This call will block, if the list is empty, until the server returns
another entry.
=back
=head1 SEE ALSO
L<Net::LDAP>,
L<Net::LDAP::Message>,
L<Net::LDAP::Entry>,
L<Net::LDAP::Reference>
=head1 ACKNOWLEDGEMENTS
This document is based on a document originally written by Russell Fulton
<r.fulton@auckland.ac.nz>.
=head1 AUTHOR
Graham Barr <gbarr@pobox.com>
Please report any bugs, or post any suggestions, to the perl-ldap mailing list
<perl-ldap-dev@lists.sourceforge.net>.
=head1 COPYRIGHT
Copyright (c) 1997-2000 Graham Barr. All rights reserved. This program is
free software; you can redistribute it and/or modify it under the same
terms as Perl itself.
=for html <hr>
I<$Id$>
=cut

197
lib/Net/LDAP/Security.pod Normal file
View file

@ -0,0 +1,197 @@
=head1 NAME
Net::LDAP::Security - Security issues with LDAP connections
=head1 SYNOPSIS
none
=head1 DESCRIPTION
This document discusses various security issues relating to using LDAP
and connecting to LDAP servers, notably how to manage these potential
vulnerabilities:
=over 4
=item *
do you know that you are connected to the right server
=item *
can someone sniff your passwords/userids from the directory connection
=item *
can someone sniff other confidential information from the directory
connection
=back
B<Net::LDAP> provides ways to address these vulnerabilities: through the
use of LDAPS, or LDAPv3 and TLS, and/or the use of SASL. Each of these
will be explained below.
=head2 How does an LDAP connection work
A normal LDAPv2 or LDAPv3 connection works by the client connecting
directly to port 389 (by default), and then issuing various LDAP
requests like search, add, etc.
There is no way to guarantee that an LDAP client is connected to the
right LDAP server. Hackers could have poisoned your DNS, so
'ldap.example.com' could be made to point to 'ldap.hacker.com'. Or
they could have installed their own server on the correct machine.
It is in the nature of the LDAP protocol that all information goes
between the client and the server in 'plain text'. This is a term used
by cryptographers to describe unencrypted and recoverable data, so
even though LDAP can transfer binary values like JPEG photographs,
audio clips and X.509 certificates, everything is still considered
'plain text'.
If these vulnerabilities are an issue to, then you should consider the
other possibilities described below, namely LDAPS, LDAPv3 and TLS, and
SASL.
=head2 How does an LDAPS connection work
LDAPS is an unofficial protocol. It is to LDAP what HTTPS is to HTTP,
namely the exact same protocol (but in this case LDAPv2 or LDAPv3)
running over a I<secured> SSL ("Secure Socket Layer") connection to
port 636 (by default).
Not all servers will be configured to listen for LDAPS connections,
but if they do, it will commonly be on a different port from the normal
plain text LDAP port.
Using LDAPS can I<potentially> solve the vulnerabilities described
above, but you should be aware that simply "using" SSL is not a magic
bullet that automatically makes your system "secure".
First of all, LDAPS can solve the problem of verifying that you are
connected to the correct server. When the client and server connect,
they perform a special SSL 'handshake', part of which involves the
server and client exchanging cryptographic keys, which are described
using X.509 certificates. If the client wishes to confirm that it is
connected to the correct server, all it needs to do is verify the
server's certificate which is sent in the handshake. This is done in
two ways:
=over 4
=item 1
check that the certificate is signed (trusted) by someone that you
trust, and that the certificate hasn't been revoked. For instance, the
server's certificate may have been signed by Verisign
(www.verisign.com), and you decide that you want to trust Verisign to
sign legitimate certificates.
=item 2
check that the least-significant cn RDN in the server's
certificate's DN is the fully-qualified hostname of the hostname that
you connected to when creating the LDAPS object. For example if the
server is <cn=ldap.example.com,ou=My department,o=My company>, then
the RDN to check is cn=ldap.example.com.
=back
You can do this by using the cafile and capath options when creating a
B<Net::LDAPS> object, I<and> by setting the verify option to 'require'.
To prevent hackers 'sniffing' passwords and other information on your
connection, you also have to make sure the encryption algorithm used
by the SSL connection is good enough. This is also something that gets
decided by the SSL handshake - if the client and server cannot agree
on an acceptable algorithm the connection is not made.
B<Net::LDAPS> will by default use all the algorithms built into your copy
of OpenSSL, except for ones considered to use "low" strength
encryption, and those using export strength encryption. You can
override this when you create the B<Net::LDAPS> object using the
'ciphers' option.
Once you've made the secure connection, you should also check that the
encryption algorithm that is actually being used is one that you find
acceptable. Broken servers have been observed in the field which 'fail
over' and give you an unencrypted connection, so you ought to check
for that.
=head2 How does LDAP and TLS work
SSL is a good solution to many network security problems, but it is
not a standard. The IETF corrected some defects in the SSL mechanism
and published a standard called RFC 2246 which describes TLS
("Transport Layer Security"), which is simply a cleaned up and
standardized version of SSL.
You can only use TLS with an LDAPv3 server. That is because the
standard (RFC 2830) for LDAP and TLS requires that the I<normal> LDAP
connection (ie., on port 389) can be switched on demand from plain text
into a TLS connection. The switching mechanism uses a special extended
LDAP operation, and since these are not legal in LDAPv2, you can only
switch to TLS on an LDAPv3 connection.
So the way you use TLS with LDAPv3 is that you create your normal
LDAPv3 connection using C<Net::LDAP::new()>, and then you perform the
switch using C<Net::LDAP::start_tls()>. The C<start_tls()> method takes
pretty much the same arguments as C<Net::LDAPS::new()>, so check above for
details.
=head2 How does SASL work
SASL is an authentication framework that can be used by a number of
different Internet services, including LDAPv3. Because it is only a
framework, it doesn't provide any way to authenticate by itself; to
actually authenticate to a service you need to use a specific SASL
I<mechanism>. A number of mechanisms are defined, such as CRAM-MD5.
The use of a mechanism like CRAM-MD5 provides a solution to the
password sniffing vulnerability, because these mechanisms typically do
not require the user to send across a secret (eg., a password) in the
clear across the network. Instead, authentication is carried out in a
clever way which avoids this, and so prevents passwords from being
sniffed.
B<Net::LDAP> supports SASL using the B<Authen::SASL> class. Currently the
only B<Authen::SASL> subclasses (ie., SASL mechanism) available are
CRAM-MD5 and EXTERNAL.
Some SASL mechanisms provide a general solution to the sniffing of all
data on the network vulnerability, as they can negotiate confidential
(ie., encrypted) network connections. Note that this is over and above
any SSL or TLS encryption! Unfortunately, perl's B<Authen::SASL> code
cannot negotiate this.
=head1 SEE ALSO
L<Net::LDAP>,
L<Net::LDAPS>,
L<Authen::SASL>
=head1 ACKNOWLEDGEMENTS
Jim Dutton <jimd@dutton3.it.siu.edu> provided lots of useful feedback
on the early drafts.
=head1 AUTHOR
Chris Ridd <chris.ridd@messagingdirect.com>
Please report any bugs, or post any suggestions, to the perl-ldap mailing list
<perl-ldap-dev@lists.sourceforge.net>
=head1 COPYRIGHT
Copyright (c) 2001 Chris Ridd. All rights reserved. This program is
free software; you can redistribute it and/or modify it under the same
terms as Perl itself.
=for html <hr>
I<$Id$>
=cut

346
lib/Net/LDAP/Util.pm Normal file
View file

@ -0,0 +1,346 @@
# Copyright (c) 1999-2000 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::Util;
=head1 NAME
Net::LDAP::Util - Utility functions
=head1 SYNOPSIS
use Net::LDAP::Util qw(ldap_error_text
ldap_error_name
ldap_error_desc
);
$mesg = $ldap->search( .... );
die "Error ",ldap_error_name($mesg->code) if $mesg->code;
=head1 DESCRIPTION
B<Net::LDAP::Util> is a collection of utility functions for use with
the L<Net::LDAP> modules.
=head1 FUNCTIONS
=over 4
=cut
use vars qw($VERSION);
require Exporter;
@ISA = qw(Exporter);
@EXPORT_OK = qw(
ldap_error_name
ldap_error_text
ldap_error_desc
canonical_dn
);
$VERSION = "0.06";
=item ldap_error_name ( NUM )
Returns the name corresponding with the error number passed in. If the
error is not known the a string in the form C<"LDAP error code %d(0x%02X)">
is returned.
=cut
my @err2name;
sub ldap_error_name {
my $code = 0+ shift;
require Net::LDAP::Constant;
unless (@err2name) {
local *FH;
if (open(FH,$INC{'Net/LDAP/Constant.pm'})) {
while(<FH>) {
($err2name[hex($2)] = $1) if /^sub\s+(LDAP_\S+)\s+\(\)\s+\{\s+0x([0-9a-fA-f]{2})\s+\}/;
}
close(FH);
}
}
$err2name[$code] || sprintf("LDAP error code %d(0x%02X)",$code,$code);
}
=item ldap_error_text ( NUM )
Returns the text from the POD description for the given error. If the
error code given is unknown then C<undef> is returned.
=cut
sub ldap_error_text {
my $name = ldap_error_name(shift);
my $text;
if($name =~ /^LDAP_/) {
my $pod = $INC{'Net/LDAP/Constant.pm'};
substr($pod,-3) = ".pod";
local *F;
open(F,$pod) or return;
local $/ = "";
local $_;
my $len = length($name);
my $indent = 0;
while(<F>) {
if(substr($_,0,11) eq "=item LDAP_") {
last if defined $text;
$text = "" if /^=item $name\b/;
}
elsif(defined $text && /^=(\S+)/) {
$indent = 1 if $1 eq "over";
$indent = 0 if $1 eq "back";
$text .= " * " if $1 eq "item";
}
elsif(defined $text) {
if($indent) {
s/\n(?=.)/\n /sog;
}
$text .= $_;
}
}
close(F);
$text =~ s/\n+\Z/\n/ if defined $text;
}
$text;
}
=item ldap_error_desc ( NUM )
Returns a short text description of the error.
=cut
my @err2desc = (
"Success", # 0x00 LDAP_SUCCESS
"Operations error", # 0x01 LDAP_OPERATIONS_ERROR
"Protocol error", # 0x02 LDAP_PROTOCOL_ERROR
"Timelimit exceeded", # 0x03 LDAP_TIMELIMIT_EXCEEDED
"Sizelimit exceeded", # 0x04 LDAP_SIZELIMIT_EXCEEDED
"Compare false", # 0x05 LDAP_COMPARE_FALSE
"Compare true", # 0x06 LDAP_COMPARE_TRUE
"Strong authentication not supported", # 0x07 LDAP_STRONG_AUTH_NOT_SUPPORTED
"Strong authentication required", # 0x08 LDAP_STRONG_AUTH_REQUIRED
"Partial results and referral received", # 0x09 LDAP_PARTIAL_RESULTS
"Referral received", # 0x0a LDAP_REFERRAL
"Admin limit exceeded", # 0x0b LDAP_ADMIN_LIMIT_EXCEEDED
"Critical extension not available", # 0x0c LDAP_UNAVAILABLE_CRITICAL_EXT
"Confidentiality required", # 0x0d LDAP_CONFIDENTIALITY_REQUIRED
"SASL bind in progress", # 0x0e LDAP_SASL_BIND_IN_PROGRESS
undef,
"No such attribute", # 0x10 LDAP_NO_SUCH_ATTRIBUTE
"Undefined attribute type", # 0x11 LDAP_UNDEFINED_TYPE
"Inappropriate matching", # 0x12 LDAP_INAPPROPRIATE_MATCHING
"Constraint violation", # 0x13 LDAP_CONSTRAINT_VIOLATION
"Type or value exists", # 0x14 LDAP_TYPE_OR_VALUE_EXISTS
"Invalid syntax", # 0x15 LDAP_INVALID_SYNTAX
undef,
undef,
undef,
undef,
undef,
undef,
undef,
undef,
undef,
undef,
"No such object", # 0x20 LDAP_NO_SUCH_OBJECT
"Alias problem", # 0x21 LDAP_ALIAS_PROBLEM
"Invalid DN syntax", # 0x22 LDAP_INVALID_DN_SYNTAX
"Object is a leaf", # 0x23 LDAP_IS_LEAF
"Alias dereferencing problem", # 0x24 LDAP_ALIAS_DEREF_PROBLEM
undef,
undef,
undef,
undef,
undef,
undef,
undef,
undef,
undef,
undef,
undef,
"Inappropriate authentication", # 0x30 LDAP_INAPPROPRIATE_AUTH
"Invalid credentials", # 0x31 LDAP_INVALID_CREDENTIALS
"Insufficient access", # 0x32 LDAP_INSUFFICIENT_ACCESS
"DSA is busy", # 0x33 LDAP_BUSY
"DSA is unavailable", # 0x34 LDAP_UNAVAILABLE
"DSA is unwilling to perform", # 0x35 LDAP_UNWILLING_TO_PERFORM
"Loop detected", # 0x36 LDAP_LOOP_DETECT
undef,
undef,
undef,
undef,
undef,
"Sort control missing", # 0x3C LDAP_SORT_CONTROL_MISSING
"Index range error", # 0x3D LDAP_INDEX_RANGE_ERROR
undef,
undef,
"Naming violation", # 0x40 LDAP_NAMING_VIOLATION
"Object class violation", # 0x41 LDAP_OBJECT_CLASS_VIOLATION
"Operation not allowed on nonleaf", # 0x42 LDAP_NOT_ALLOWED_ON_NONLEAF
"Operation not allowed on RDN", # 0x43 LDAP_NOT_ALLOWED_ON_RDN
"Already exists", # 0x44 LDAP_ALREADY_EXISTS
"Cannot modify object class", # 0x45 LDAP_NO_OBJECT_CLASS_MODS
"Results too large", # 0x46 LDAP_RESULTS_TOO_LARGE
"Affects multiple servers", # 0x47 LDAP_AFFECTS_MULTIPLE_DSAS
undef,
undef,
undef,
undef,
undef,
undef,
undef,
undef,
"Unknown error", # 0x50 LDAP_OTHER
"Can't contact LDAP server", # 0x51 LDAP_SERVER_DOWN
"Local error", # 0x52 LDAP_LOCAL_ERROR
"Encoding error", # 0x53 LDAP_ENCODING_ERROR
"Decoding error", # 0x54 LDAP_DECODING_ERROR
"Timed out", # 0x55 LDAP_TIMEOUT
"Unknown authentication method", # 0x56 LDAP_AUTH_UNKNOWN
"Bad search filter", # 0x57 LDAP_FILTER_ERROR
"Canceled", # 0x58 LDAP_USER_CANCELED
"Bad parameter to an ldap routine", # 0x59 LDAP_PARAM_ERROR
"Out of memory", # 0x5a LDAP_NO_MEMORY
"Can't connect to the LDAP server", # 0x5b LDAP_CONNECT_ERROR
"Not supported by this version of the LDAP protocol", # 0x5c LDAP_NOT_SUPPORTED
"Requested LDAP control not found", # 0x5d LDAP_CONTROL_NOT_FOUND
"No results returned", # 0x5e LDAP_NO_RESULTS_RETURNED
"More results to return", # 0x5f LDAP_MORE_RESULTS_TO_RETURN
"Client detected loop", # 0x60 LDAP_CLIENT_LOOP
"Referral hop limit exceeded", # 0x61 LDAP_REFERRAL_LIMIT_EXCEEDED
);
sub ldap_error_desc {
my $code = shift;
$err2desc[$code] || sprintf("LDAP error code %d(0x%02X)",$code,$code);
}
=item canonical_dn ( DN [, FOR_SORT ])
Returns the given DN in a canonical form. Returns undef if DN is
not a valid Distinguished Name
If FOR_SORT is specified and is a I<true> value, the the DNs returned
will have their RDN components in reverse order. This is primarily
used for sorting.
It performs the following operations on the given DN
=over 4
=item *
Lowercases values that are # followed by hex.
=item *
Uppercases type names.
=item *
Removes the leading OID. characters if the type is an OID instead
of a name.
=item *
Escapes all RFC 2253 special characters, and any other character
where the ASCII code is <32 or >= 127, with a backslash and a two
digit hex code.
=item *
Converts all leading and trailing spaces in values to be \20.
=item *
If an RDN contains multiple parts, the parts are re-ordered so that
the attribute names are in alphabetical order.
=back
B<Note> values that are hex encoded (ie start with a #) are not
decoded. So C<SN=Barr> is not treated the same as C<SN=#42617272>
=cut
sub canonical_dn {
my ($dn, $rev) = @_;
$dn = $dn->dn if ref($dn);
my (@dn, @rdn);
while (
$dn =~ /\G(?:
\s*
([a-zA-Z][-a-zA-Z0-9]*|(?:[Oo][Ii][Dd]\.)?\d+(?:\.\d+)*)
\s*
=
\s*
(
(?:[^\\",=+<>\#;]*[^\\",=+<>\#;\s]|\\(?:[\\ ",=+<>#;]|[0-9a-fA-F]{2}))*
|
\#(?:[0-9a-fA-F]{2})+
|
"(?:[^\\"]+|\\(?:[\\",=+<>#;]|[0-9a-fA-F]{2}))*"
)
\s*
(?:([;,+])\s*(?=\S)|$)
)\s*/gcx)
{
my($type,$val,$sep) = ($1,$2,$3);
$type =~ s/^oid\.(\d+(\.\d+)*)$/$1/i;
if ($val !~ /^#/) {
$val =~ s/^"(.*)"$/$1/;
$val =~ s/\\([\\ ",=+<>#;]|[0-9a-fA-F]{2})
/length($1)==1 ? $1 : chr(hex($1))
/xeg;
$val =~ s/([\\",=+<>#;])/\\$1/g;
$val =~ s/([\x00-\x1f\x7f-\xff])/sprintf("\\%02x",ord($1))/eg;
$val =~ s/(^\s+|\s+$)/"\\20" x length $1/ge;
}
push @rdn, "\U$type\E=$val";
unless (defined $sep and $sep eq '+') {
push @dn, join($rev ? "\001" : "+", sort @rdn);
@rdn = ();
}
}
(length($dn) != (pos($dn)||0))
? undef
: join($rev ? "\000" : ",",$rev ? (reverse @dn) : @dn);
}
=back
=head1 AUTHOR
Graham Barr <gbarr@pobox.com>
=head1 COPYRIGHT
Copyright (c) 1999-2000 Graham Barr. All rights reserved. This program is
free software; you can redistribute it and/or modify it under the same
terms as Perl itself.
=for html <hr>
I<$Id$>
=cut
1;

219
lib/Net/LDAPS.pm Normal file
View file

@ -0,0 +1,219 @@
# Copyright (c) 2000-2001 Chris Ridd <chris.ridd@messagingdirect.com> and
# 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::LDAPS;
@Net::LDAPS::ISA = ( 'Net::LDAP' );
$Net::LDAPS::VERSION = "0.03";
use strict;
use Net::LDAP;
use IO::Socket::SSL;
# Different OpenSSL verify modes.
my %verify = qw(none 0 optional 1 require 3);
sub _connect {
my ($ldap, $host, $arg) = @_;
$ldap->{'net_ldap_socket'} = IO::Socket::SSL->new(
PeerAddr => $host,
PeerPort => $arg->{'port'} || '636',
Proto => 'tcp',
Timeout => defined $arg->{'timeout'} ? $arg->{'timeout'} : 120,
SSL_context_init_args($arg)
);
}
sub SSL_context_init_args {
my $arg = shift;
my $verify = 0;
my ($clientcert,$clientkey);
if (exists $arg->{'verify'}) {
my $v = lc $arg->{'verify'};
$verify = 0 + (exists $verify{$v} ? $verify{$v} : $verify);
}
if (exists $arg->{'clientcert'}) {
$clientcert = $arg->{'clientcert'};
if (exists $arg->{'clientkey'}) {
$clientkey = $arg->{'clientkey'};
} else {
require Carp;
Carp::croak("Setting client public key but not client private key");
}
}
(
SSL_cipher_list => defined $arg->{'ciphers'} ? $arg->{'ciphers'} : 'ALL',
SSL_ca_file => exists $arg->{'cafile'} ? $arg->{'cafile'} : '',
SSL_ca_path => exists $arg->{'capath'} ? $arg->{'capath'} : '',
SSL_key_file => $clientcert ? $clientkey : undef,
SSL_use_cert => $clientcert ? 1 : 0,
SSL_cert_file => $clientcert,
SSL_verify_mode => $verify,
SSL_version => defined $arg->{'sslversion'} ? $arg->{'sslversion'} :
'sslv2/3',
);
}
1;
__END__
=head1 NAME
Net::LDAPS - use LDAP over an SSL connection
=head1 SYNOPSIS
use Net::LDAPS;
$ldaps = new Net::LDAPS('myhost.example.com',
port => '10000',
verify => 'require',
capath => '/usr/local/cacerts/');
=head1 DESCRIPTION
Communicate using the LDAP protocol to a directory server using a
potentially encrypted (SSL) network connection.
This class is a subclass of Net::LDAP so all the normal Net::LDAP
methods can be used with a Net::LDAPS object; see the documentation
for Net::LDAP to find out how to query a directory server using the
LDAP protocol.
Note that the use of LDAPS is not recommended, because it is not
described by any IETF documents. Instead, you should consider using
LDAPv3 with the TLS extension defined in RFC 2830. This will give you
the same functionality as LDAPS, but using recognized standards. See
L<Net::LDAP/start_tls>.
=head1 CONSTRUCTOR
=over 4
=item new ( HOST [, OPTIONS ] )
Create a new connection. HOST is the hostname to contact. OPTIONS is a
number of key/value pairs - additional keys to those understood by
Net::LDAP::new are:
=over 4
=item verify
How to verify the server's certificate, either 'none' (the server may
provide a certificate but it will not be checked - this may mean you
are be connected to the wrong server), 'optional' (verify if the
server offers a certificate), or 'require' (the server must provide a
certificate, and it must be valid.) If you set verify to optional or
require, you must also set either cafile or capath. The most secure
option is 'require'.
=item sslversion
This defines the version of the SSL/TLS protocol to use. Defaults to
'sslv2/3', other possible values are 'sslv2', 'sslv3', and 'tlsv1'.
=item ciphers
Specify which subset of cipher suites are permissible for this
connection, using the standard OpenSSL string format. The default
value for ciphers is 'ALL', which permits all ciphers, even those that
don't encrypt!
=item clientcert
=item clientkey
If you want to use the client to offer a certificate to the server for
SSL authentication (which is not the same as for the LDAP Bind
operation) then set clientcert to the user's certificate file, and
clientkey to the user's private key file. These files must be in PEM
format.
=item capath
=item cafile
When verifying the server's certificate, either set capath to the
pathname of the directory containing CA certificates, or set cafile to
the filename containing the certificate of the CA who signed the
server's certificate. These certificates must all be in PEM format.
The directory in 'capath' must contain certificates named using the
hash value of themselves. To generate these names, use OpenSSL like
this in Unix:
ln -s cacert.pem `openssl x509 -hash -noout < cacert.pem`.0
(assuming that the certificate of the CA is in cacert.pem.)
=back
=back
=head1 ADDITIONAL METHODS
=over 4
=item cipher
Returns the cipher mode being used by the connection, in the string
format used by OpenSSL.
=item certificate
Returns an X509_Certificate object containing the server's
certificate. See the IO::Socket::SSL documentation for information
about this class.
For example, to get the subject name (in a peculiar OpenSSL-specific
format, different from RFC 1779 and RFC 2253) from the server's
certificate, do this:
print "Subject DN: " . $ldaps->certificate->subject_name . "\n";
=back
=head1 SEE ALSO
L<Net::LDAP>,
L<IO::Socket::SSL>
=head1 BUGS
Several apparently bogus warnings are emitted when initializing the
two underlying modules used by Net::LDAPS, namely IO::Socket::SSL and
Net::SSLeay. To avoid these, don't initialize via 'use Net::LDAPS' and
instead try initializing Net::LDAPS like this:
BEGIN {
# Turn off all warnings etc whilst initializing
# IO::Socket::SSL and Net::SSLeay.
local $^W = 0;
no strict;
require Net::SSLeay;
# The /dev/urandom is a device on Linux that returns
# random data.
Net::SSLeay::randomize('/dev/urandom');
require Net::LDAPS;
}
=head1 AUTHOR
Chris Ridd <chris.ridd@messagingdirect.com>
=head1 COPYRIGHT
Copyright (c) 2000-2001, Chris Ridd and Graham Barr. All rights reserved. This
library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.
=cut