Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge pull request #17 from marschap/next

preparation for the next release
  • Loading branch information...
commit 30373aaa8d5f80474f2ef53760475dd0253bdb48 2 parents 95c0049 + 4a0c544
@marschap authored
View
7 lib/Net/LDAP.pm
@@ -5,7 +5,7 @@
package Net::LDAP;
use strict;
-use Socket qw(AF_INET AF_INET6 AF_UNSPEC);
+use Socket qw(AF_INET AF_INET6 AF_UNSPEC SOL_SOCKET SO_KEEPALIVE);
use IO::Socket;
use IO::Select;
use Tie::Hash;
@@ -105,7 +105,7 @@ sub new {
my $scheme = $arg->{scheme} || 'ldap';
my $h = $uri;
if (defined($h)) {
- $h =~ s,^(\w+)://,, and $scheme = $1;
+ $h =~ s,^(\w+)://,, and $scheme = lc($1);
$h =~ s,/.*,,; # remove path part
$h =~ s/%([A-Fa-f0-9]{2})/chr(hex($1))/eg; # unescape
}
@@ -119,6 +119,9 @@ sub new {
return undef unless $obj->{net_ldap_socket};
+ $obj->{net_ldap_socket}->setsockopt(SOL_SOCKET, SO_KEEPALIVE, $arg->{keepalive} ? 1 : 0)
+ if (defined($arg->{keepalive}));
+
$obj->{net_ldap_resp} = {};
$obj->{net_ldap_version} = $arg->{version} || $LDAP_VERSION;
$obj->{net_ldap_async} = $arg->{async} ? 1 : 0;
View
8 lib/Net/LDAP.pod
@@ -89,6 +89,14 @@ Port to connect to on the remote server. May be overridden by C<HOST>.
Connection scheme to use when not using an URI as C<HOST>.
(Default: ldap)
+=item keepalive =E<gt> 1
+
+If given, set the socket's SO_KEEPALIVE option depending on
+the boolean value of the option.
+(Default: use system default)
+
+Failures in changing the socket's SO_KEEPALIVE option are ignored.
+
=item timeout =E<gt> N
Timeout passed to L<IO::Socket> when connecting the remote server.
View
56 lib/Net/LDAP/Constant.pm
@@ -4,7 +4,7 @@
package Net::LDAP::Constant;
-our $VERSION = '0.17';
+our $VERSION = '0.18';
use Exporter qw(import);
@@ -632,6 +632,60 @@ Indicates if the server supports the Modify Increment extension (RFC 4525)
=back
+=head2 Active Directory Capability OIDs
+
+The following constants are specific to Microsoft Active Directory.
+They serve to denote capabilities via the non-standard attribute
+C<supportedCapabilities> in the Root DSE.
+
+=over 4
+
+=item LDAP_CAP_ACTIVE_DIRECTORY (1.2.840.113556.1.4.800)
+
+Indicates that the LDAP server is running Active Directory
+and is running as AD DS.
+
+=item LDAP_CAP_ACTIVE_DIRECTORY_LDAP_INTEG (1.2.840.113556.1.4.1791)
+
+Indicates that the LDAP server on the DC is capable of signing and sealing
+on an NTLM authenticated connection, and that the server is capable of
+performing subsequent binds on a signed or sealed connection.
+
+=item LDAP_CAP_ACTIVE_DIRECTORY_V51 (1.2.840.113556.1.4.1670)
+
+On an Active Directory DC operating as AD DS, the presence of this capability
+indicates that the LDAP server is running at least the Windows 2003.
+
+On an Active Directory DC operating as AD LDS, the presence of this capability
+indicates that the LDAP server is running at least the Windows 2008.
+
+=item LDAP_CAP_ACTIVE_DIRECTORY_ADAM (1.2.840.113556.1.4.1851)
+
+Indicates that the LDAP server is running Active Directory as AD LDS.
+
+=item LDAP_CAP_ACTIVE_DIRECTORY_ADAM_DIGEST (1.2.840.113556.1.4.1880)
+
+Indicates on a DC operating as AD LDS,
+that the DC accepts DIGEST-MD5 binds for AD LDS security principals.
+
+=item LDAP_CAP_ACTIVE_DIRECTORY_PARTIAL_SECRETS (1.2.840.113556.1.4.1920)
+
+Indicates that the Active Directory DC operating as AD DS, is an RODC.
+
+=item LDAP_CAP_ACTIVE_DIRECTORY_V60 (1.2.840.113556.1.4.1935)
+
+Indicates that the LDAP server is running at least the Windows 2008.
+
+=item LDAP_CAP_ACTIVE_DIRECTORY_V61_R2 (1.2.840.113556.1.4.2080)
+
+Indicates that the LDAP server is running at least the Windows 2008 R2.
+
+=item LDAP_CAP_ACTIVE_DIRECTORY_W8 (1.2.840.113556.1.4.2237)
+
+Indicates that the LDAP server is running at least the Windows 2012.
+
+=back
+
=head1 SEE ALSO
L<Net::LDAP>,
View
6 lib/Net/LDAP/Control/Paged.pm
@@ -106,7 +106,11 @@ Net::LDAP::Control::Paged - LDAPv3 Paged results control object
=head1 DESCRIPTION
C<Net::LDAP::Control::Paged> provides an interface for the creation and manipulation
-of objects that represent the C<pagedResultsControl> as described by RFC-2696.
+of objects that represent the C<pagedResultsControl> as described by RFC 2696.
+
+The control is allowed on LDAP search requests (L<Net::LDAP/search>) only.
+On other operations it will - depending on the value of the parameter
+C<critical> - either be ignored or lead to errors.
=head1 CONSTRUCTOR ARGUMENTS
View
2  lib/Net/LDAP/Control/ProxyAuth.pm
@@ -139,7 +139,7 @@ authorization or data confidentiality, a single C<Proxy Authorization Control>
may be included in any search, compare, modify, add, delete, or moddn or
extended operation.
-As cqrequired by the RFC, the criticality of this control is automatically set to
+As required by the RFC, the criticality of this control is automatically set to
TRUE in order to protect clients from submitting requests with other identities
that they intend to.
View
2  lib/Net/LDAP/Extension/Cancel.pm
@@ -80,7 +80,7 @@ Cancel an outstanding operation. C<OPERATION> may be a number or an
object which is a sub-class of L<Net::LDAP::Message>, returned from a
previous method call.
-OPTIONS is a list of key/value pairs. The following keys are reconized:
+OPTIONS is a list of key/value pairs. The following keys are recognized:
=over 4
View
2  lib/Net/LDAP/Extension/Refresh.pm
@@ -72,7 +72,7 @@ by another method:
Send a refresh operation for an object.
-OPTIONS is a list of key/value pairs. The following keys are reconized:
+OPTIONS is a list of key/value pairs. The following keys are recognized:
=over 4
View
81 lib/Net/LDAP/FAQ.pod
@@ -1220,6 +1220,87 @@ With groups, the same applies to the C<groupType> bit-field:
attrs => [ '1.1' ]
);
+=head2 How can I search for all members of a group in AD (including group nesting)?
+
+AD allows you to find all members of a specified group, the direct members
+plus those that are member of the group via group nesting.
+
+The trick to this is the special C<LDAP_MATCHING_RULE_IN_CHAIN> matching rule:
+
+ $mesg = $ldap->search( base => 'cn=Users,dc=your,dc=ads,dc=domain',
+ filter => '(memberOf:1.2.840.113556.1.4.1941:=cn=Testgroup,dc=your,dc=ads,dc=domain)',
+ attrs => [ '1.1' ]
+ );
+
+=head2 How can I search for all groups one user is a member of in AD (including group nesting)?
+
+Similarly you can search for all the groups one user is member of, either directly
+or via group nesting.
+
+ $mesg = $ldap->search( base => 'dc=your,dc=ads,dc=domain',
+ filter => '(member:1.2.840.113556.1.4.1941:=cn=TestUser,ou=Users,dc=your,dc=ads,dc=domain)',
+ attrs => [ '1.1' ]
+ );
+
+=head2 How do I search for all members of a large group in AD?
+
+AD normally restricts the number of attribute values returned in one query.
+The exact number depends on the AD server version: it was ~1000 in Win2000,
+1500 in Win2003 and is 5000 in Win2008 & Win2008R2.
+
+Performing the same standard search again will yield the same values again.
+
+So, how can you get all members of a really large AD group?
+
+The trick to use here is to use Microsoft's I<range option> when searching,
+i.e instead of doing one search for plain C<member>, perform multiple searches
+for e.g. C<member;range=1000-*> where the range starting index increases accordingly:
+
+ my $mesg;
+ my @members;
+ my $index = 0;
+
+ while ($index ne '*') {
+ $mesg = $ldap->search( base => 'cn=Testgroup,dc=your,dc=ads,dc=domain',
+ filter => '(objectclass=group)',
+ scope => 'base',
+ attrs => ($index > 0) ? "member;range=$index-*" : 'member'
+ );
+ if ($mesg->code == LDAP_SUCCESS) {
+ my $entry = $mesg->entry(0);
+ my $attr;
+
+ # large group: let's do the range option dance
+ if (($attr) = grep(/^member;range=/, $entry->attributes)) {
+ push(@members, $entry->get_value($attr));
+
+ if ($attr =~ /^member;range=\d+-(.*)$/) {
+ $index = $1;
+ $index++ if ($index ne '*');
+ }
+ }
+ # small group: no need for the range dance
+ else {
+ @members = $entry->get_value('member');
+ last;
+ }
+ }
+ # failure
+ else {
+ last;
+ }
+ }
+
+ if ($mesg->code == LDAP_SUCCESS) {
+ # success: @members contains the members of the group
+ }
+ else {
+ # failure: deal with the error in $mesg
+ }
+
+See L<http://msdn.microsoft.com/en-us/library/windows/desktop/aa367017.aspx>
+for more details.
+
=head2 How do I create a Microsoft Exchange 5.x user?
This is a solution provided by a perl-ldap user.
View
22 lib/Net/LDAP/Filter.pm
@@ -6,7 +6,7 @@ package Net::LDAP::Filter;
use strict;
-our $VERSION = '0.18';
+our $VERSION = '0.19';
# filter = "(" filtercomp ")"
# filtercomp = and / or / not / item
@@ -273,4 +273,24 @@ sub _string { # prints things of the form (<op> (<list>) ... )
die "Internal error $_[0]";
}
+sub negate {
+ my $self = shift;
+
+ %{$self} = _negate(%{$self});
+
+ $self;
+}
+
+sub _negate { # negate a filter tree
+ for ($_[0]) {
+ /^and/ and return ( 'or' => [ map { { _negate(%$_) }; } @{$_[1]} ] );
+ /^or/ and return ( 'and' => [ map { { _negate(%$_) }; } @{$_[1]} ] );
+ /^not/ and return %{$_[1]};
+ /^(present|equalityMatch|greaterOrEqual|lessOrEqual|approxMatch|substrings|extensibleMatch)/
+ and do return ( 'not' => { $_[0 ], $_[1] } );
+ }
+
+ die "Internal error $_[0]";
+}
+
1;
View
25 lib/Net/LDAP/Filter.pod
@@ -18,9 +18,10 @@ associated escaping mechanisms.
=over 4
-=item new ( FILTER )
+=item new ( [ FILTER ] )
-Create a new object and parse FILTER.
+Create a new object.
+If FILTER is given, parse it.
=back
@@ -30,7 +31,8 @@ Create a new object and parse FILTER.
=item parse ( FILTER )
-Parse FILTER. The next call to ber will return this filter encoded.
+Parse FILTER, updating the object to represent it.
+The next call to ber will return this filter encoded.
=item as_string
@@ -41,6 +43,23 @@ Return the filter in text form.
Print the text representation of the filter to FH, or the currently
selected output handle if FH is not given.
+=item negate ( )
+
+Logically negate/invert the filter object so that it matches the opposite
+set of entries as the original.
+
+Instead of simply negating the text form by surrounding it with the B<not>
+operator, the negation is done by recursively applying I<De Morgan's law>.
+
+Here is an example:
+
+ (|(&(cn=A)(cn=B))(|(!(cn=C))(cn=D)))
+
+gets negated to
+
+ (&(|(!(cn=A))(!(cn=B)))(&(cn=C)(!(cn=D))))
+
+
=back
=head1 FILTER SYNTAX
View
4 lib/Net/LDAP/Search.pod
@@ -41,8 +41,8 @@ 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
+Return the N'th entry (zero-based), which will be a L<Net::LDAP::Entry> object.
+If INDEX is greater or equal than the total number of entries returned then
C<undef> will be returned.
This method will block until the search request has returned enough
View
123 lib/Net/LDAP/Util.pm
@@ -44,6 +44,7 @@ our @EXPORT_OK = qw(
unescape_filter_value
escape_dn_value
unescape_dn_value
+ ldap_url_parse
);
our %EXPORT_TAGS = (
error => [ qw(ldap_error_name ldap_error_text ldap_error_desc) ],
@@ -52,9 +53,10 @@ our %EXPORT_TAGS = (
escape_dn_value unescape_dn_value) ],
escape => [ qw(escape_filter_value unescape_filter_value
escape_dn_value unescape_dn_value) ],
+ url => [ qw(ldap_url_parse) ],
);
-our $VERSION = '0.14';
+our $VERSION = '0.15';
=item ldap_error_name ( ERR )
@@ -600,6 +602,125 @@ my @values = @_;
}
+=item ldap_url_parse ( LDAP-URL [, OPTIONS ] )
+
+Parse an B<LDAP-URL> conforming to RFC 4516 into a hash containing its elements.
+
+For easy cooperation with LDAP queries, the hash keys for the elements
+used in LDAP search operations are named after the parameters to
+L<Net::LDAP/search>.
+
+In extension to RFC 4516, the socket path for URLs with the scheme C<ldapi>
+will be stored in the hash key named C<path>.
+
+If any element is omitted, the result depends on the setting of the option
+C<defaults>.
+
+B<OPTIONS> is a list of key/value pairs with the following keys recognized:
+
+=over 4
+
+=item defaults
+
+A boolean option that determines whether default values according to RFC 4516
+shall be returned for missing URL elements.
+
+If set to TRUE, default values are returned, with C<ldap_url_parse>
+using the following defaults in extension to RFC 4516.
+
+=over 4
+
+=item *
+
+The default port for C<ldaps> URLs is C<636>.
+
+=item *
+
+The default path for C<ldapi> URLs is the contents of the environment variable
+C<LDAPI_SOCK>. If that is not defined or empty, then C</var/run/ldapi> is used.
+
+This is consistent with the behaviour of L<Net::LDAP/new>.
+
+=item *
+
+The default C<host> name for C<ldap> and C<ldaps> URLs is C<localhost>.
+
+=back
+
+When set to FALSE, no default values are used.
+
+This leaves all keys in th resulting hash undefined where the corresponding
+URL element is empty.
+
+To distinguish between an empty base DN and an undefined base DN,
+C<ldap_parse_url> uses the slash between the host:port resp. path
+part of the URL and the base DN part of the URL.
+With the slash present, the hash key C<base> is set to the empty string,
+without it, it is left undefined.
+
+Leaving away the C<defaults> option entirely is equivalent to setting it to TRUE.
+
+=back
+
+Returns the hash in list mode, or the reference to the hash in scalar mode.
+
+=cut
+
+## parse an LDAP URL into its various elements
+# Synopsis: {$elementref,%elements} = ldap_url_parse($url)
+sub ldap_url_parse($@)
+{
+my $url = shift;
+my %opt = @_;
+
+ eval { require URI };
+ return wantarray ? () : undef
+ if ($@);
+
+ my $uri = URI->new($url);
+ return wantarray ? () : undef
+ unless ($uri && ref($uri) =~ /^URI::ldap[is]?$/);
+
+ $opt{defaults} = 1 unless (exists($opt{defaults}));
+
+ my %elements = ( scheme => $uri->scheme );
+
+ $uri = $uri->canonical; # canonical form
+ $url = $uri->as_string; # normalize
+
+ if ($elements{scheme} eq 'ldapi') {
+ $elements{path} = $uri->un_path || $ENV{LDAPI_SOCK} || '/var/run/ldapi'
+ if ($opt{defaults} || $uri->un_path);
+ }
+ else {
+ $elements{host} = $uri->host || 'localhost'
+ if ($opt{defaults} || $uri->host);
+
+ $elements{port} = $uri->port || ($elements{scheme} eq 'ldaps' ? 636 : 389)
+ if ($opt{defaults} || $uri->port);
+ }
+
+ $elements{base} = $uri->dn
+ if ($opt{defaults} || $uri->dn || $url =~ m{^ldap[is]?://[^/]*/});
+
+ $elements{attrs} = [ $uri->attributes ]
+ if ($opt{defaults} || $uri->attributes);
+
+ $elements{scope} = $uri->scope
+ if ($opt{defaults} || $uri->_scope);
+
+ $elements{filter} = $uri->filter
+ if ($opt{defaults} || $uri->_filter);
+
+ $elements{extensions} = [ $uri->extensions ]
+ if ($opt{defaults} || $uri->extensions);
+
+ #return _error($ldap, $mesg, LDAP_LOCAL_ERROR, "unhandled critical URL extension")
+ # if (grep(/^!/, keys(%extns)));
+
+ return wantarray ? %elements : \%elements;
+}
+
=back
View
4 t/71preread.t
@@ -54,7 +54,7 @@ SKIP: {
ok(!$mesg->code, "search: ". $mesg->code . ": " . $mesg->error);
my $entry = $mesg->entry(0);
- my $origValue = join(':', map { sort $entry->get_attribute($_) } @{$test->{attrs}});
+ my $origValue = join(':', map { sort $entry->get_value($_) } @{$test->{attrs}});
$control = Net::LDAP::Control::PreRead->new(attrs => $test->{attrs});
isa_ok($control, Net::LDAP::Control::PreRead, "control object");
@@ -71,7 +71,7 @@ SKIP: {
$entry = $previous->entry();
isa_ok($entry, Net::LDAP::Entry, "entry object");
- my $prereadValue = join(':', map { sort $entry->get_attribute($_) } @{$test->{attrs}});
+ my $prereadValue = join(':', map { sort $entry->get_value($_) } @{$test->{attrs}});
is($prereadValue, $origValue, "value in PreRead control matches");
}
}
View
4 t/72postread.t
@@ -66,7 +66,7 @@ SKIP: {
$entry = $previous->entry();
isa_ok($entry, Net::LDAP::Entry, "entry object");
- my $postreadValue = join(':', map { sort $entry->get_attribute($_) } @{$test->{attrs}});
+ my $postreadValue = join(':', map { sort $entry->get_value($_) } @{$test->{attrs}});
$mesg = $ldap->search(base => @{$test->{dn}} ? $test->{dn}[0] : '',
filter => '(objectclass=*)',
@@ -75,7 +75,7 @@ SKIP: {
ok(!$mesg->code, "search: ". $mesg->code . ": " . $mesg->error);
$entry = $mesg->entry(0);
- my $origValue = join(':', map { sort $entry->get_attribute($_) } @{$test->{attrs}});
+ my $origValue = join(':', map { sort $entry->get_value($_) } @{$test->{attrs}});
is($postreadValue, $origValue, "value in PostRead control matches");
}
}
Please sign in to comment.
Something went wrong with that request. Please try again.