Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

preparation for the next release #18

Merged
merged 11 commits into from

1 participant

@marschap
Owner

No description provided.

marschap added some commits
@marschap marschap LDAP.pod,FAQ.pod: harmonize documentation of scope values 3ace6f3
@marschap marschap FAQ.pod: update info on required/optional modules 1b837ab
@marschap marschap Makefile.PL: update recommends, add comments on core modules 0035644
@marschap marschap Util.pm: add time conversion functions 44daf3b
@marschap marschap RT#83815: fix typo/bug in example for root_dse
Thanks to Carsten Dumke <Carsten.Dumke@gmx.net> for reporting it.
2a13dd5
@marschap marschap RT#73202: LDIF.pm: Base64-encode values ending in spaces
Although not required by the RFC, Base64-encode those values that end in
spaces when writing LDIFs.
This makes it easier for the user to visually detect those values and
increases compatibility with other implementations (e.g. OpenLDAP's
ldapsearch).
df6cd07
@marschap marschap LDIF.pm: partially support controls when reading
LDIF change records may contain 'control:' stanzas.
Instead of chocking on them when reading, decode them properly.

In this commit, the controls read are not returned to the caller,
they are only parsed in Net::LDAP::LDIF internally and ignored.
This is done in order to leave the API unchanged.
f3885d4
@marschap marschap LDIF.pm: clean up handling of mode parameter
When operating on the standard IO handles, treat mode 'a'/'>>' (append)
the same way as mode 'w'/'>' (write) instead of 'r'/'<' (read).

Allow using the "translated" mode codes too: '<', '>', and '>>'.

Use 3-parameter open when possible.

Rearrange the code slightly to make it more readable.
8024f90
@marschap marschap Extra/eDirectory.pm: new; extensions for eDirectory ad881a7
@marschap marschap Constant.pm: add constants for Novell eDirectory e63ba33
@marschap marschap FAQ.pod: mention Dancer::Plugin::LDAP, update example 7201795
@marschap marschap merged commit c85d864 into perl-ldap:next
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Feb 3, 2013
  1. @marschap
  2. @marschap
  3. @marschap
Commits on Feb 11, 2013
  1. @marschap
Commits on Mar 9, 2013
  1. @marschap

    RT#83815: fix typo/bug in example for root_dse

    marschap authored
    Thanks to Carsten Dumke <Carsten.Dumke@gmx.net> for reporting it.
  2. @marschap

    RT#73202: LDIF.pm: Base64-encode values ending in spaces

    marschap authored
    Although not required by the RFC, Base64-encode those values that end in
    spaces when writing LDIFs.
    This makes it easier for the user to visually detect those values and
    increases compatibility with other implementations (e.g. OpenLDAP's
    ldapsearch).
  3. @marschap

    LDIF.pm: partially support controls when reading

    marschap authored
    LDIF change records may contain 'control:' stanzas.
    Instead of chocking on them when reading, decode them properly.
    
    In this commit, the controls read are not returned to the caller,
    they are only parsed in Net::LDAP::LDIF internally and ignored.
    This is done in order to leave the API unchanged.
Commits on Mar 27, 2013
  1. @marschap

    LDIF.pm: clean up handling of mode parameter

    marschap authored
    When operating on the standard IO handles, treat mode 'a'/'>>' (append)
    the same way as mode 'w'/'>' (write) instead of 'r'/'<' (read).
    
    Allow using the "translated" mode codes too: '<', '>', and '>>'.
    
    Use 3-parameter open when possible.
    
    Rearrange the code slightly to make it more readable.
  2. @marschap
  3. @marschap
Commits on Mar 29, 2013
  1. @marschap
This page is out of date. Refresh to see the latest.
View
12 Makefile.PL
@@ -39,23 +39,26 @@ features
],
'Read/Write LDIF files' => [
-default => 0,
- 'MIME::Base64' => 0,
+ 'MIME::Base64' => 0, # core module since Perl 5.7.3
],
'SASL authentication' => [
-default => 0,
'Authen::SASL' => '2.00',
- recommends 'Digest::MD5', # for DIGEST-MD5 SASL mech
+ recommends 'Digest::MD5', # for DIGEST-MD5 SASL mech, core since Perl 5.7.3
recommends 'Digest::HMAC_MD5', # for CRAM-MD5 SASL mech
recommends 'GSSAPI', # for GSSAPI SASL mech
],
'LDAP URLs' => [
-default => 0,
'URI::ldap' => 1.10,
- recommends 'JSON',
+ ],
+ 'Time conversion functions' => [
+ -default => 0,
+ 'Time::Local' => 0,
],
'Read/Write DSML files' => [
-default => 0,
- 'MIME::Base64' => 0,
+ 'MIME::Base64' => 0, # core module since Perl 5.7.3
'XML::SAX::Writer' => 0,
'XML::SAX::Base' => 0,
],
@@ -66,6 +69,7 @@ features
'HTTP::Negotiate' => 0,
'HTTP::Response' => 0,
'HTTP::Status' => 0,
+ recommends 'JSON',
];
auto_install_now;
View
6 lib/Net/LDAP.pod
@@ -580,7 +580,7 @@ The result is an object of class L<Net::LDAP::Search>.
The DN that is the base object entry relative to which the search is
to be performed.
-=item scope =E<gt> 'base' | 'one' | 'sub' | 'children'
+=item scope =E<gt> 'base' | 'one' | 'sub' | 'subtree' | 'children'
By default the search is performed on the whole tree below
the specified base object. This maybe changed by specifying a C<scope>
@@ -598,6 +598,8 @@ Search the entries immediately below the base object.
=item sub
+=item subtree
+
Search the whole tree below (and including) the base object. This is
the default.
@@ -926,7 +928,7 @@ B<Example>
my $root = $ldap->root_dse;
# get naming Context
- $root->get_value( 'namingContext', asref => 1 );
+ $root->get_value( 'namingContexts', asref => 1 );
# get supported LDAP versions
$root->supported_version;
View
106 lib/Net/LDAP/Constant.pm
@@ -593,6 +593,112 @@ Indicates that the server supports the Refresh extension (RFC 2589)
Indicates the server supports the Cancel extension (RFC 3909)
+=head3 Novell eDirectory Extension OIDs
+
+=item LDAP_EXTENSION_NDSTOLDAP (2.16.840.1.113719.1.27.100.2)
+
+=item LDAP_EXTENSION_SPLIT_PARTITION (2.16.840.1.113719.1.27.100.3)
+
+=item LDAP_EXTENSION_MERGE_PARTITION (2.16.840.1.113719.1.27.100.5)
+
+=item LDAP_EXTENSION_ADD_REPLICA (2.16.840.1.113719.1.27.100.7)
+
+=item LDAP_EXTENSION_REFRESH_LDAP_SERVER (2.16.840.1.113719.1.27.100.9)
+
+=item LDAP_EXTENSION_REMOVE_REPLICA (2.16.840.1.113719.1.27.100.11)
+
+=item LDAP_EXTENSION_PARTITION_ENTRY_COUNT (2.16.840.1.113719.1.27.100.13)
+
+=item LDAP_EXTENSION_CHANGE_REPLICA_TYPE (2.16.840.1.113719.1.27.100.15)
+
+=item LDAP_EXTENSION_GET_REPLICA_INFO (2.16.840.1.113719.1.27.100.17)
+
+=item LDAP_EXTENSION_LIST_REPLICAS (2.16.840.1.113719.1.27.100.19)
+
+=item LDAP_EXTENSION_RECEIVE_ALL_UPDATES (2.16.840.1.113719.1.27.100.21)
+
+=item LDAP_EXTENSION_SEND_ALL_UPDATES (2.16.840.1.113719.1.27.100.23)
+
+=item LDAP_EXTENSION_REQUEST_PARTITIONSYNC (2.16.840.1.113719.1.27.100.25)
+
+=item LDAP_EXTENSION_REQUEST_SCHEMASYNC (2.16.840.1.113719.1.27.100.27)
+
+=item LDAP_EXTENSION_ABORT_PARTITION_OPERATION (2.16.840.1.113719.1.27.100.29)
+
+=item LDAP_EXTENSION_GET_BINDDN (2.16.840.1.113719.1.27.100.31)
+
+=item LDAP_EXTENSION_GET_EFFECTIVE_PRIVILEGES (2.16.840.1.113719.1.27.100.33)
+
+=item LDAP_EXTENSION_SET_REPLICATION_FILTER (2.16.840.1.113719.1.27.100.35)
+
+=item LDAP_EXTENSION_GET_REPLICATION_FILTER (2.16.840.1.113719.1.27.100.37)
+
+=item LDAP_EXTENSION_CREATE_ORPHAN_PARTITION (2.16.840.1.113719.1.27.100.39)
+
+=item LDAP_EXTENSION_REMOVE_ORPHAN_PARTITION (2.16.840.1.113719.1.27.100.41)
+
+=item LDAP_EXTENSION_TRIGGER_BACKLINKER (2.16.840.1.113719.1.27.100.43)
+
+=item LDAP_EXTENSION_TRIGGER_DRLPROCESS (2.16.840.1.113719.1.27.100.45)
+
+=item LDAP_EXTENSION_TRIGGER_JANITOR (2.16.840.1.113719.1.27.100.47)
+
+=item LDAP_EXTENSION_TRIGGER_LIMBER (2.16.840.1.113719.1.27.100.49)
+
+=item LDAP_EXTENSION_TRIGGER_SKULKER (2.16.840.1.113719.1.27.100.51)
+
+=item LDAP_EXTENSION_TRIGGER_SCHEMASYNC (2.16.840.1.113719.1.27.100.53)
+
+=item LDAP_EXTENSION_TRIGGER_PARTITIONPURGE (2.16.840.1.113719.1.27.100.55)
+
+=item LDAP_EXTENSION_MONITOR_EVENTS (2.16.840.1.113719.1.27.100.79)
+
+=item LDAP_EXTENSION_EVENT_NOTIFICATION (2.16.840.1.113719.1.27.100.81)
+
+=item LDAP_EXTENSION_FILTERED_EVENT_MONITOR (2.16.840.1.113719.1.27.100.84)
+
+=item LDAP_EXTENSION_LDAPBACKUP (2.16.840.1.113719.1.27.100.96)
+
+=item LDAP_EXTENSION_LDAPRESTORE (2.16.840.1.113719.1.27.100.98)
+
+=item LDAP_EXTENSION_GET_EFFECTIVE_LIST_PRIVILEGES (2.16.840.1.113719.1.27.100.103)
+
+=item LDAP_EXTENSION_CREATE_GROUPING (2.16.840.1.113719.1.27.103.1)
+
+=item LDAP_EXTENSION_END_GROUPING (2.16.840.1.113719.1.27.103.2)
+
+=item LDAP_EXTENSION_NMAS_PUT_LOGIN_CONFIGURATION (2.16.840.1.113719.1.39.42.100.1)
+
+=item LDAP_EXTENSION_NMAS_GET_LOGIN_CONFIGURATION (2.16.840.1.113719.1.39.42.100.3)
+
+=item LDAP_EXTENSION_NMAS_DELETE_LOGIN_CONFIGURATION (2.16.840.1.113719.1.39.42.100.5)
+
+=item LDAP_EXTENSION_NMAS_PUT_LOGIN_SECRET (2.16.840.1.113719.1.39.42.100.7)
+
+=item LDAP_EXTENSION_NMAS_DELETE_LOGIN_SECRET (2.16.840.1.113719.1.39.42.100.9)
+
+=item LDAP_EXTENSION_NMAS_SET_PASSWORD (2.16.840.1.113719.1.39.42.100.11)
+
+=item LDAP_EXTENSION_NMAS_GET_PASSWORD (2.16.840.1.113719.1.39.42.100.13)
+
+=item LDAP_EXTENSION_NMAS_DELETE_PASSWORD (2.16.840.1.113719.1.39.42.100.15)
+
+=item LDAP_EXTENSION_NMAS_PASSWORD_POLICYCHECK (2.16.840.1.113719.1.39.42.100.17)
+
+=item LDAP_EXTENSION_NMAS_GET_PASSWORD_POLICY_INFO (2.16.840.1.113719.1.39.42.100.19)
+
+=item LDAP_EXTENSION_NMAS_CHANGE_PASSWORD (2.16.840.1.113719.1.39.42.100.21)
+
+=item LDAP_EXTENSION_NMAS_GAMS (2.16.840.1.113719.1.39.42.100.23)
+
+=item LDAP_EXTENSION_NMAS_MANAGEMENT (2.16.840.1.113719.1.39.42.100.25)
+
+=item LDAP_EXTENSION_START_FRAMED_PROTOCOL (2.16.840.1.113719.1.142.100.1)
+
+=item LDAP_EXTENSION_END_FRAMED_PROTOCOL (2.16.840.1.113719.1.142.100.4)
+
+=item LDAP_EXTENSION_LBURP_OPERATION (2.16.840.1.113719.1.142.100.6)
+
=back
=head2 Feature OIDs
View
285 lib/Net/LDAP/Extra/eDirectory.pm
@@ -0,0 +1,285 @@
+package Net::LDAP::Extra::eDirectory;
+
+use strict;
+
+use Encode;
+use Exporter qw(import);
+use Convert::ASN1 qw(ASN_NULL);
+use Net::LDAP::RootDSE;
+
+require Net::LDAP::Extension;
+
+our @ISA = qw(Net::LDAP::Extension);
+our $VERSION = '0.01';
+
+our @EXPORT = qw(is_eDirectory
+ list_replicas
+ get_replica_info
+ _trigger_proccess
+ trigger_backlinker
+ trigger_janitor
+ trigger_limber
+ trigger_skulker
+ trigger_schemasync
+ trigger_partitionpurge
+ refresh_ldap_server);
+
+
+sub is_eDirectory {
+ my $self = shift;
+ my $rootdse = $self->root_dse()
+ or return undef;
+
+ return ($rootdse->supported_extension(qw(2.16.840.1.113719.1.27.100.9
+ 2.16.840.1.113719.1.27.100.19)))
+ ? 1 : 0;
+}
+
+my $listReplicasReq = Convert::ASN1->new;
+$listReplicasReq->prepare(q<serverDN OCTET STRING>);
+
+my $listReplicasRes = Convert::ASN1->new;
+$listReplicasRes->prepare(q<replicaList SEQUENCE OF OCTET STRING>);
+
+sub list_replicas {
+ my $ldap = shift;
+ my $serverDN = shift;
+ my %opt = @_;
+
+ my $res = $ldap->extension(
+ name => '2.16.840.1.113719.1.27.100.19',
+ value => $listReplicasReq->encode(serverDN => $serverDN),
+ ($opt{control} ? (control => $opt{control}) : ())
+ );
+
+ bless $res; # Naughty :-)
+}
+
+sub replicas {
+ my $self = shift;
+ my $out;
+
+ if ($self->code == 0 && $self->response_name eq '2.16.840.1.113719.1.27.100.20') {
+ $out = $listReplicasRes->decode($self->response);
+
+ return wantarray ? @{$out->{replicaList}} : $out->{replicaList}
+ if (ref($out) && exists($out->{replicaList}));
+ }
+
+ undef;
+}
+
+
+my $GetReplicaInfoReq = Convert::ASN1->new;
+$GetReplicaInfoReq->prepare(q<serverDN OCTET STRING,
+ partitionDN OCTET STRING>);
+
+my $GetReplicaInfoRes = Convert::ASN1->new;
+$GetReplicaInfoRes->prepare(q<partitionID INTEGER,
+ replicaState INTEGER,
+ modificationTime INTEGER,
+ purgeTime INTEGER,
+ localPartitionID INTEGER,
+ partitionDN OCTET STRING,
+ replicaType INTEGER,
+ flags INTEGER>);
+
+sub get_replica_info {
+ my $ldap = shift;
+ my $serverDN = shift;
+ my $partitionDN = shift;
+ my %opt = @_;
+
+ my $res = $ldap->extension(
+ name => '2.16.840.1.113719.1.27.100.17',
+ value => $GetReplicaInfoReq->encode(serverDN => $serverDN,
+ partitionDN => $partitionDN),
+ ($opt{control} ? (control => $opt{control}) : ())
+ );
+
+ bless $res; # Naughty :-)
+}
+
+sub replica_info {
+ my $self = shift;
+
+ if ($self->code == 0 && $self->response_name eq '2.16.840.1.113719.1.27.100.18') {
+ my $out = $GetReplicaInfoRes->decode($self->response);
+
+ return wantarray ? %{$out} : $out;
+ }
+
+ undef;
+}
+
+use constant {
+ EDIR_BK_PROCESS_BKLINKER => 1,
+ EDIR_BK_PROCESS_JANITOR => 2,
+ EDIR_BK_PROCESS_LIMBER => 3,
+ EDIR_BK_PROCESS_SKULKER => 4,
+ EDIR_BK_PROCESS_SCHEMA_SYNC => 5,
+ EDIR_BK_PROCESS_PART_PURGE => 6
+};
+
+sub _trigger_proccess()
+{
+my $ldap = shift;
+my $type = shift;
+my %opt = @_;
+my %typemap = ( 1 => '2.16.840.1.113719.1.27.100.43',
+ 2 => '2.16.840.1.113719.1.27.100.47',
+ 3 => '2.16.840.1.113719.1.27.100.49',
+ 4 => '2.16.840.1.113719.1.27.100.51',
+ 5 => '2.16.840.1.113719.1.27.100.53',
+ 6 => '2.16.840.1.113719.1.27.100.55',
+ 2.16.840.1.113719.1.27.100.9 => '2.16.840.1.113719.1.27.100.9' );
+
+ $type = $typemap{$type} if ($typemap{$type});
+
+ return undef if (!grep(/^\Q$type\E$/, values(%typemap)));
+
+ my $res = $ldap->extension(
+ name => $type,
+ value => ASN_NULL,
+ ($opt{control} ? (control => $opt{control}) : ())
+ );
+
+ bless $res; # Naughty :-)
+}
+
+sub trigger_backlinker {
+ my $ldap = shift;
+ $ldap->_trigger_proccess(EDIR_BK_PROCESS_BKLINKER, @_);
+}
+
+sub trigger_janitor {
+ my $ldap = shift;
+ $ldap->_trigger_proccess(EDIR_BK_PROCESS_JANITOR, @_);
+}
+
+sub trigger_limber {
+ my $ldap = shift;
+ $ldap->_trigger_proccess(EDIR_BK_PROCESS_LIMBER, @_);
+}
+
+sub trigger_skulker {
+ my $ldap = shift;
+ $ldap->_trigger_proccess(EDIR_BK_PROCESS_SKULKER, @_);
+}
+
+sub trigger_schemasync {
+ my $ldap = shift;
+ $ldap->_trigger_proccess(EDIR_BK_PROCESS_SCHEMA_SYNC, @_);
+}
+
+sub trigger_partitionpurge {
+ my $ldap = shift;
+ $ldap->_trigger_proccess(EDIR_BK_PROCESS_PART_PURGE, @_);
+}
+
+sub refresh_ldap_server {
+ my $ldap = shift;
+ $ldap->_trigger_proccess('2.16.840.1.113719.1.27.100.9', @_);
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Net::LDAP::Extra::eDirectory -- extensions for Novell eDirectory
+
+=head1 SYNOPSIS
+
+ use Net::LDAP::Extra qw(eDirectory);
+
+ $ldap = Net::LDAP->new( ... );
+
+ ...
+
+ if ($ldap->is_eDirectory)
+ my $mesg $ldap->list_replicas($server_dn);
+
+ print "Replicas on $server_dn\n* " . join("\n* ", $mesg->replicas) . "\n"
+ if (!$mesg->code);
+ }
+
+=head1 DESCRIPTION
+
+Net::LDAP::Extra::eDirectory provides functions / LDAP extensions
+specific to Novell eDirectory.
+
+To do so, it provides the following methods:
+
+=head1 METHODS
+
+=over 4
+
+=item is_eDirectory ( )
+
+Tell if the LDAP server queried is Novell eDirectory server.
+
+As the check is done by querying the root DSE of the directory,
+it works without being bound to the directory.
+
+In contrast to other Net::LDAP methods this method returns
+TRUE / FALSE respectively undef on error.
+
+=item list_replicas ( SERVER_DN, OPTIONS )
+
+Query the the replicas on the given server I<SERVER_DN>.
+
+On success, the resulting Net::LDAP::Message object supports the method
+C<replicas> that returns the list of replicas on I<SERVER_DN>.
+
+=item get_replica_info ( SERVER_DN, REPLICA_DN, OPTIONS )
+
+Query information of I<REPLICA_DN> on I<SERVER_DN>.
+
+On success, the resulting Net::LDAP::Message object supports the method
+C<replica_info> that returns a hash containing information on I<REPLICA_DN>.
+
+=item trigger_backlinker ( OPTIONS )
+
+Trigger the BackLinker process, which resolves external references
+to ensure they refer to real entries.
+
+=item trigger_janitor ( OPTIONS )
+
+Trigger the Janitor process, which checks connectivity to all servers in database.
+
+=item trigger_limber ( OPTIONS )
+
+Trigger the Limber process, which verifies the server name,
+internal ipx address and tree connectivity of all replicas.
+
+=item trigger_skulker ( OPTIONS )
+
+Trigger the Skulker process, which checks the synchronization status
+of every server in the replica ring.
+
+=item trigger_schemasync ( OPTIONS )
+
+Trigger SchemaSync.
+
+=item trigger_partitionpurge ( OPTIONS )
+
+Trigger PartitionPurge.
+
+=item refresh_ldap_server ( OPTIONS )
+
+Trigger refreshing the NLDAP service.
+
+=back
+
+=head1 AUTHOR
+
+Peter Marschall E<lt>peter@adpm.de<gt>
+
+=head1 COPYRIGHT
+
+Copyright (c) 2013 Peter Marschall. All rights reserved. This program is
+free software; you can redistribute it and/or modify it under the same
+terms as Perl itself.
+
View
120 lib/Net/LDAP/FAQ.pod
@@ -92,7 +92,7 @@ You can download it from CPAN by following the "Download" link on:
Example;
- http://search.cpan.org/CPAN/authors/id/M/MA/MARSCHAP/perl-ldap-0.44.tar.gz
+ http://search.cpan.org/CPAN/authors/id/M/MA/MARSCHAP/perl-ldap-0.54.tar.gz
=item Git - fork on GitHub
@@ -479,22 +479,52 @@ have make installed.
=head2 What other modules will I need?
-perl-ldap does use other modules. Some are required, but some are
-optional (ie required to use certain features)
+perl-ldap uses other Perl modules. Some are required, but some are
+optional (i.e. required to use certain features only).
+
+If you are using a Linux system, many of the distributions
+have packages that you can install using the distribution's
+package management tools (e.g. apt, rpm, ...).
+
+Alternatively, you may use your favorite web search engine
+to find the package that you need.
=over 4
=item Convert::ASN1
-This module is required for perl-ldap to work.
+This module converts between Perl data structures and ASN.1, and
+is required for perl-ldap to work.
You can obtain the latest release from
http://search.cpan.org/search?module=Convert::ASN1
+=item OpenSSL and IO::Socket::SSL
+
+If you want to use encrypted connections, either via L<start_tls|Net::LDAP/start_tls>
+or L<LDAPS connections|Net::LDAP::LDAPS>, you will need this module
+and the OpenSSL software package.
+
+You can obtain the latest release of IO::Socket::SSL from
+ http://search.cpan.org/search?module=IO::Socket::SSL
+
+You can obtain the latest release of OpenSSL from
+ http://www.openssl.org/
+
+=item IO::Socket::INET6
+
+For connecting to LDAP servers via IPv6, IO::Socket::INET6 is required.
+Its presence is detected at runtime, so that perl-ldap can be installed
+without it, and automatically gains IPv6 support as soon as
+IO::Socket::INET6 gets installed.
+
+You can obtain the latest releases from
+ http://search.cpan.org/search?module=IO::Socket::INET6
+
=item Authen::SASL
This module is optional. You only need to install Authen::SASL
-if you want to use the SASL authentication method.
+if you want to use the SASL authentication methods.
You can obtain the latest release from
http://search.cpan.org/search?module=Authen::SASL
@@ -503,38 +533,72 @@ You can obtain the latest release from
This module is optional. It also requires a C compiler when installing.
You only need to install Digest::MD5 if you want to use the SASL
-DIGEST-MD5 authentication method.
+DIGEST-MD5 authentication mechanism.
You can obtain the latest release from
http://search.cpan.org/search?module=Digest::MD5
-=item URI::ldap
+As Digest::MD5 is part of the Perl core modules since Perl 5.7.3,
+you only need a C compiler if you want to install a version that is
+newer than the version distributed with your Perl installation.
+
+=item Digest::HMAC_MD5
+
+This optional module is required only if you want to use the SASL
+CRAM-MD5 authentication mechanism.
+
+You can obtain the latest release from
+ http://search.cpan.org/search?module=Digest::HMAC_MD5
+
+=item GSSAPI
-This module is optional. You only need to install URI::ldap if you are
-going to need to parse ldap referrals. L<Net::LDAP> does not do this
-automatically yet, so this module is not used by perl-ldap.
+This optional module is required only if you want to use the SASL
+GSSAPI authentication mechanism (e.g. for Kerberos authentication).
You can obtain the latest release from
+ http://search.cpan.org/search?module=GSSAPI
+
+=item URI::ldap, URI::ldaps, and URI::ldapi
+
+These modules are optional. You only need to install them if you
+want to parse ldap://, ldaps:// or ldapi:// URIs using
+L<ldap_parse_uri in Net::LDAP::Util|Net::LDAP::Util/ldap_parse_uri>.
+or use LWP::Protocol::ldap, LWP::Protocol::ldaps, or LWP::Protocol::ldapi.
+
+You can obtain the latest releases from
http://search.cpan.org/search?module=URI::ldap
+ http://search.cpan.org/search?module=URI::ldaps
+ http://search.cpan.org/search?module=URI::ldapi
-=item OpenSSL and IO::Socket::SSL for Net::LDAPS
+=item LWP::Protocol, LWP::MediaTypes, HTTP::Negotiate, and HTTP::Response
-If you want to use Net::LDAP::LDAPS you will need this module
-and the OpenSSL software package.
+These optional modules are needed if you want to use perl-ldap's
+LWP::Protocol::ldap, LWP::Protocol::ldaps, or LWP::Protocol::ldapi modules.
-You can obtain the latest release of IO::Socket::SSL from
- http://search.cpan.org/search?module=IO::Socket::SSL
+You can obtain the latest releases from
+ http://search.cpan.org/search?module=LWP::Protocol
+ http://search.cpan.org/search?module=LWP::MediaTypes
+ http://search.cpan.org/search?module=HTTP::Negotiate
+ http://search.cpan.org/search?module=HTTP::Response
-You can obtain the latest release of OpenSSL from
- http://www.openssl.org/
+=item JSON
-If you are using a Linux system, many of the distributions
-have RPM packages that you can install. Use your favorite
-web search engine to find the package that you need.
+This optional module is required for JSON-formatted output of perl-ldap's
+LWP::Protocol::ldap, LWP::Protocol::ldaps, or LWP::Protocol::ldapi modules.
+
+If you need it, you can optain the latest releases from
+ http://search.cpan.org/search?module=JSON
+
+=item Time::Local
+
+This module is optional, and only required if you want to convert
+between UNIX time and generalizedTime using the functions provided
+in Net::LDAP::Util.
=item XML::SAX and XML::SAX::Writer
-If you want to use Net::LDAP::DSML you will need these modules.
+If you want to parse or write DSMLv1 documents with Net::LDAP::DSML
+to you will need these optional modules.
You can obtain the latest releases from
http://search.cpan.org/search?module=XML::SAX
@@ -731,9 +795,17 @@ Search the entries immediately below the base object.
=item sub
-Search the whole tree below the base object.
+=item subtree
+
+Search the whole tree below (and including) the base object.
This is the default.
+=item children
+
+Search the whole subtree below the base object, excluding the base object itself.
+
+Note: I<children> scope requires LDAPv3 subordinate feature extension.
+
=back
=head1 GETTING SEARCH RESULTS
@@ -1617,6 +1689,10 @@ https://github.com/russoz/Net-LDAP-SimpleServer
LemonLDAP::NG - Web SingleSignOn solution & SAML IdP in Perl
http://lemonldap-ng.org/
+Dancer::Plugin::LDAP - LDAP plugin for Dancer micro framework
+http://search.cpan.org/search?module=Dancer::Plugin::LDAP
+https://github.com/racke/Dancer-Plugin-LDAP
+
Directory Services Mark Language (DSML)
http://www.oasis-open.org/specs/
View
79 lib/Net/LDAP/LDIF.pm
@@ -17,8 +17,8 @@ BEGIN {
our $VERSION = '0.19';
-
-my %mode = qw(w > r < a >>);
+# allow the letters r,w,a as well as the well-known operators as modes
+my %mode = qw(r < < < w > > > a >> >> >>);
sub new {
my $pkg = shift;
@@ -28,26 +28,25 @@ sub new {
my $fh;
my $opened_fh = 0;
+ # harmonize mode, default to reading
+ $mode = $mode{$mode} || '<';
+
if (ref($file)) {
$fh = $file;
}
else {
if ($file eq '-') {
- if ($mode eq 'w') {
- ($file, $fh) = ('STDOUT', \*STDOUT);
- }
- else {
- ($file, $fh) = ('STDIN', \*STDIN);
- }
+ ($file,$fh) = ($mode eq '<')
+ ? ('STDIN', \*STDIN)
+ : ('STDOUT',\*STDOUT);
}
else {
require Symbol;
$fh = Symbol::gensym();
- my $open = $file =~ /^\| | \|$/x
- ? $file
- : (($mode{$mode} || '<') . $file);
- open($fh, $open) or return;
- $opened_fh = 1;
+ $opened_fh = ($file =~ /^\| | \|$/x)
+ ? open($fh, $file)
+ : open($fh, $mode, $file);
+ return unless ($opened_fh);
}
}
@@ -72,7 +71,7 @@ sub new {
file => "$file",
opened_fh => $opened_fh,
_eof => 0,
- write_count => ($mode eq 'a' and tell($fh) > 0) ? 1 : 0,
+ write_count => ($mode eq '>>' and tell($fh) > 0) ? 1 : 0,
};
# fetch glob for URL type attributes (one per LDIF object)
@@ -211,6 +210,51 @@ sub _read_entry {
if (CHECK_UTF8 && $self->{raw} && ('dn' !~ /$self->{raw}/));
$entry->dn($dn);
+ my @controls = ();
+
+ # optional control: line => change record
+ while (@ldif && ($ldif[0] =~ /^control:\s*/)) {
+ my $control = shift(@ldif);
+
+ if ($control =~ /^control:\s*(\d+(?:\.\d+)*)(?:\s+(true|false))?(?:\s*\:(.*))?$/) {
+ my($oid,$critical,$value) = ($1,$2,$3);
+ my $prefix = $1 if (defined($value) && $value =~ s/^([\<\:])\s*//);
+
+ $critical = ($critical && $critical =~ /true/) ? 1 : 0;
+
+ # base64 encoded value: decode it
+ if ($prefix && $prefix eq ':') {
+ eval { require MIME::Base64 };
+ if ($@) {
+ $self->_error($@, @ldif);
+ return;
+ }
+ $value = MIME::Base64::decode($value);
+ }
+ # url value: read in file:// url, fail on others
+ elsif ($prefix && $prefix eq '<' and $value =~ s/^(.*?)\s*$/$1/) {
+ $value = $self->_read_url_attribute($value, @ldif);
+ return if !defined($value);
+ }
+
+ require Net::LDAP::Control;
+ my $ctrl = Net::LDAP::Control->new(type => $oid,
+ value => $value,
+ critical => $critical);
+
+ push(@controls, $ctrl);
+
+ if (!@ldif) {
+ $self->_error('Illegally formatted control line given', @ldif);
+ return;
+ }
+ }
+ else {
+ $self->_error('Illegally formatted control line given', @ldif);
+ return;
+ }
+ }
+
if ((scalar @ldif) && ($ldif[0] =~ /^changetype:\s*/)) {
my $changetype = $ldif[0] =~ s/^changetype:\s*//
? shift(@ldif) : $self->{changetype};
@@ -308,6 +352,11 @@ sub _read_entry {
my $attr;
my $xattr;
+ if (@controls) {
+ $self->_error("Controls only allowed with LDIF change entries", @ldif);
+ return;
+ }
+
foreach my $line (@ldif) {
$line =~ s/^([-;\w]+):([\<\:]?)\s*// &&
(($attr, $xattr) = ($1, $2)) or next;
@@ -401,7 +450,7 @@ sub _write_attr {
$v = Encode::encode_utf8($v)
if (CHECK_UTF8 and Encode::is_utf8($v));
- if ($v =~ /(^[ :<]|[\x00-\x1f\x7f-\xff])/) {
+ if ($v =~ /(^[ :<]|[\x00-\x1f\x7f-\xff]| $)/) {
require MIME::Base64;
$ln .= ':: ' . MIME::Base64::encode($v, '');
}
View
152 lib/Net/LDAP/Util.pm
@@ -45,6 +45,8 @@ our @EXPORT_OK = qw(
escape_dn_value
unescape_dn_value
ldap_url_parse
+ generalizedTime_to_time
+ time_to_generalizedTime
);
our %EXPORT_TAGS = (
error => [ qw(ldap_error_name ldap_error_text ldap_error_desc) ],
@@ -54,6 +56,7 @@ our %EXPORT_TAGS = (
escape => [ qw(escape_filter_value unescape_filter_value
escape_dn_value unescape_dn_value) ],
url => [ qw(ldap_url_parse) ],
+ time => [ qw(generalizedTime_to_time time_to_generalizedTime) ],
);
our $VERSION = '0.15';
@@ -721,6 +724,155 @@ my %opt = @_;
return wantarray ? %elements : \%elements;
}
+
+=item generalizedTime_to_time ( GENERALIZEDTIME )
+
+Convert the generalizedTime string B<GENERALIZEDTIME>, which is expected
+to match the template C<YYYYmmddHH[MM[SS]][(./,)d...](Z|(+/-)HH[MM])>
+to a floating point number compatible with UNIX time
+(i.e. the integral part of the number is a UNIX time).
+
+Returns an extended UNIX time or C<undef> on error.
+
+Times in years smaller than 1000 will lead to C<undef> being returned.
+This restriction is a direct effect of the year value interpretation rules
+in Time::Local.
+
+B<Note:> this function depends on Perl's implementation of time and Time::Local.
+See L<Time::Local/Limits of time_t>, L<Time::Local/Negative Epoch Values>, and
+L<perlport/gmtime> for restrictions in older versions of Perl.
+
+=cut
+
+sub generalizedTime_to_time($)
+{
+my $generalizedTime = shift;
+
+ if ($generalizedTime =~ /^\s*(\d{4})(\d{2})(\d{2})
+ (\d{2})(?:(\d{2})(\d{2})?)?
+ (?:[.,](\d+))?\s*(Z|[+-]\d{2}(?:\d{2})?)\s*$/x) {
+ my ($year,$month,$day,$hour,$min,$sec,$dec,$offset) = ($1,$2,$3,$4,$5,$6,$7,$8);
+
+ # Time::Local's timegm() interpret years strangely
+ if ($year >= 1000) {
+ $dec = "0.$dec";
+
+ # decimals in case of missing minutes / seconds - see RFC 4517
+ if (!defined($min)) {
+ $min = 0;
+
+ if ($dec) {
+ $min = int(60 * $dec);
+ $dec = sprintf('%.4f', 60 * $dec - $min);
+ }
+ }
+ if (!defined($sec)) {
+ $sec = 0;
+
+ if ($dec) {
+ $sec = int(60 * $dec);
+ $dec = sprintf('%.2f', 60 * $dec - $sec);
+ }
+ }
+
+ eval { require Time::Local; };
+ unless ($@) {
+ my $time;
+
+ eval { $time = Time::Local::timegm($sec,$min,$hour,$day,$month-1,$year); };
+ unless ($@) {
+ if ($offset =~ /^([+-])(\d{2})(\d{2})?$/) {
+ my ($direction,$hourdelta,$mindelta) = ($1,$2,$3);
+
+ $mindelta = 0 if (!$mindelta);
+ $time += ($direction eq '-')
+ ? 3600 * $hourdelta + 60 * $mindelta
+ : -3600 * $hourdelta - 60 * $mindelta;
+ }
+
+ # make decimal part directional
+ if ($dec != 0) {
+ if ($time < 0) {
+ $dec = 1 - $dec;
+ $time++;
+ }
+ $dec =~ s/^0\.//;
+ $time .= ".$dec";
+ }
+
+ return $time;
+ }
+ }
+ }
+ }
+
+ return undef;
+}
+
+
+=item time_to_generalizedTime ( TIME [, OPTIONS ] )
+
+Convert the UNIX time B<TIME> to a generalizedTime string.
+
+In extension to UNIX times, B<TIME> may be a floating point number,
+the decimal part will be used for the resulting generalizedTime.
+
+B<OPTIONS> is a list of key/value pairs. The following keys are recognized:
+
+=over 4
+
+=item AD
+
+Take care of an ActiveDirectory peculiarity to always require decimals.
+
+=back
+
+Returns the generalizedTime string, or C<undef> on error.
+
+Times before BC or after year 9999 result in C<undef>
+as they cannot be represented in the generalizedTime format.
+
+B<Note:> this function depends on Perl's implementation of gmtime.
+See L<Time::Local/Limits of time_t>, L<Time::Local/Negative Epoch Values>, and
+L<perlport/gmtime> for restrictions in older versions of Perl.
+
+=cut
+
+sub time_to_generalizedTime($;@)
+{
+my $arg = shift;
+my %opt = @_;
+
+ if ($arg =~ /^(\-?\d*)(?:[.,](\d*))?$/) {
+ my ($time, $dec) = ($1, $2);
+
+ $dec = defined($dec) ? "0.$dec" : 0;
+
+ # decimal part of time is directional: make sure to have it positive
+ if ($time < 0 && $dec != 0) {
+ $time--;
+ $dec = 1 - $dec;
+ }
+
+ $time = int($time);
+
+ my ($sec,$min,$hour,$mday,$month,$year,$wday,$yday,$isdst) = gmtime(int($time));
+
+ # generalizedTime requires 4-digit year without sign
+ return undef if ($year < -1900 || $year > 8099);
+
+ $dec =~ s/^0?\.(\d*?)0*$/$1/;
+
+ return sprintf("%04d%02d%02d%02d%02d%02d%sZ",
+ $year+1900, $month+1, $mday, $hour, $min, $sec,
+ # AD peculiarity: if there are no decimals, add .0 as decimals
+ ($dec ? ('.'.$dec) : ($opt{AD} ? '.0' : '')));
+ }
+
+ return undef;
+}
+
+
=back
Something went wrong with that request. Please try again.