I've attached a patch(against perl-ldap-0.34) implementing the modify/increment operation and the PreRead/PostRead controls from RFCs 4525 and 4527.
I'm having a little trouble with the ASN definition for the Read control request, but the operation is still successful and the controls return the full entry(instead of just the attributes I ask for). I've also attached a code skeleton demostrating usage. Does anybody have suggestions or comments? Is there any possibility of merging this into perl-ldap? Thanks, -Stephen
testinc.pl
Description: Perl program
diff -Nur perl-ldap-0.34/lib/Net/LDAP/ASN.pm perl-ldap-0.34+modify_increment/lib/Net/LDAP/ASN.pm --- perl-ldap-0.34/lib/Net/LDAP/ASN.pm 2007-02-10 15:44:18.000000000 -0800 +++ perl-ldap-0.34+modify_increment/lib/Net/LDAP/ASN.pm 2007-09-11 13:27:25.000000000 -0700 @@ -412,6 +412,16 @@ type [2] AttributeDescription OPTIONAL, --- at least one of the above must be present matchValue [3] AssertionValue } + + -- RFC 4527 - Read Control messages + ReadControlRequest ::= SEQUENCE { + attrs AttributeDescriptionList + } + + ReadControlResponse ::= SEQUENCE { + objectName LDAPDN, + attributes AttributeList + } LDAP_ASN diff -Nur perl-ldap-0.34/lib/Net/LDAP/Constant.pm perl-ldap-0.34+modify_increment/lib/Net/LDAP/Constant.pm --- perl-ldap-0.34/lib/Net/LDAP/Constant.pm 2007-02-10 15:44:18.000000000 -0800 +++ perl-ldap-0.34+modify_increment/lib/Net/LDAP/Constant.pm 2007-09-06 18:46:03.000000000 -0700 @@ -473,6 +473,10 @@ =item LDAP_CONTROL_REFERRALS (1.2.840.113556.1.4.616) +=item LDAP_CONTROL_PREREAD (1.3.6.1.1.13.1) + +=item LDAP_CONTROL_POSTREAD (1.3.6.1.1.13.2) + =back =head2 Extension OIDs diff -Nur perl-ldap-0.34/lib/Net/LDAP/Control/PostRead.pm perl-ldap-0.34+modify_increment/lib/Net/LDAP/Control/PostRead.pm --- perl-ldap-0.34/lib/Net/LDAP/Control/PostRead.pm 1969-12-31 16:00:00.000000000 -0800 +++ perl-ldap-0.34+modify_increment/lib/Net/LDAP/Control/PostRead.pm 2007-09-11 13:41:27.000000000 -0700 @@ -0,0 +1,141 @@ +# Copyright (c) 2007 Stephen Hock <[EMAIL PROTECTED]>. 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::PostRead; + +use vars qw(@ISA $VERSION); +use Net::LDAP::Control; +use Net::LDAP::Entry; + [EMAIL PROTECTED] = qw(Net::LDAP::Control); +$VERSION = "0.01"; + +use Net::LDAP::Constant qw(LDAP_CONTROL_POSTREAD); +use Net::LDAP::ASN qw(ReadControlRequest ReadControlResponse); +use Net::LDAP::Entry; +use strict; +use Data::Dumper; + +sub init { + my ($self) = @_; + + delete $self->{asn}; + + if (exists $self->{attrs}) { + $self->{asn} = {attrs => $self->{attrs}}; + $self->{value} = $ReadControlRequest->encode($self->{asn}); + die $ReadControlRequest->error if !defined $self->{value}; + } + + return $self; +} + +sub attrs { + my $self = shift; + $self->{attrs} = shift if @_; + + return $self->{attrs}; +} + + +sub entry { + my $self = shift; + $self->{asn} = $ReadControlResponse->decode($self->{value}) or + die "Can't decode: " . $ReadControlResponse->error; + + my $e = Net::LDAP::Entry->new(); + + $e->decode($self->{asn}); + return $e; +} + +sub value { + my $self = shift; + if(@_) { $self->{value} = $_; } + + return $self->{value}; +} + +1; + +__END__ + +=head1 NAME + +Net::LDAP::Control::PostRead - LDAPv3 PostRead control object + +=head1 SYNOPSIS + + use Net::LDAP; + use Net::LDAP::Entry; + use Net::LDAP::Control::PostRead; + use Net::LDAP::Constant qw(LDAP_CONTROL_POSTREAD); + + $ldap = Net::LDAP->new( "ldap.mydomain.eg" ); + $postread = Net::LDAP::Control::PostRead->new(critical => 1); + + $entry->increment("integerAttribute", $amount); + $mesg = $entry->update($ldap, control => $postread); + + if($mesg->code) { + die "Error: " . $mesg->error_text . "\n"; + } + + # Retrieve the entry from the control + ($control) = $mesg->control(LDAP_CONTROL_POSTREAD); + $entry = $control->entry; + print $entry->get_value("integerAttribute"); + +=head1 DESCRIPTION + +C<Net::LDAP::Control::PostRead> provides an interface for the creation and manipulation of objects that represent the C<postRead> as described by draft-zeilenga-ldap-readentry-04.txt. + +=head1 CONSTRUCTOR ARGUMENTS + +In addition to the constructor arguments described in +L<Net::LDAP::Control> the following are provided. + +=over 4 + +=item attrs + +The attribute return list. + +=back + +B<Please note:> + +=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. + +=item entry + +Return the L<Net::LDAP::Entry> object from the PostRead control response. + +=head1 SEE ALSO + +L<Net::LDAP>, +L<Net::LDAP::Control>, + +=head1 AUTHOR + +Stephen Hock, based on Net::LDAP::Control::ProxyAuth from +Graham Barr E<lt>[EMAIL PROTECTED]<gt>. + +Please report any bugs, or post any suggestions, to the perl-ldap +mailing list E<lt>[EMAIL PROTECTED]<gt> + +=head1 COPYRIGHT + +Copyright (c) 2007 Stephen Hock <[EMAIL PROTECTED]>. All rights reserved. +This program is free software; you can redistribute it and/or +modify it under the same terms as Perl itself. + +=cut + + diff -Nur perl-ldap-0.34/lib/Net/LDAP/Control/PreRead.pm perl-ldap-0.34+modify_increment/lib/Net/LDAP/Control/PreRead.pm --- perl-ldap-0.34/lib/Net/LDAP/Control/PreRead.pm 1969-12-31 16:00:00.000000000 -0800 +++ perl-ldap-0.34+modify_increment/lib/Net/LDAP/Control/PreRead.pm 2007-09-11 13:46:29.000000000 -0700 @@ -0,0 +1,139 @@ +# Copyright (c) 2007 Stephen Hock <[EMAIL PROTECTED]>. 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::PreRead; + +use vars qw(@ISA $VERSION); +use Net::LDAP::Control; +use Net::LDAP::Entry; + [EMAIL PROTECTED] = qw(Net::LDAP::Control); +$VERSION = "0.01"; + +use Net::LDAP::Constant qw(LDAP_CONTROL_PREREAD); +use Net::LDAP::ASN qw(ReadControlRequest ReadControlResponse); +use Net::LDAP::Entry; +use strict; +use Data::Dumper; + +sub init { + my ($self) = @_; + + delete $self->{asn}; + + if (exists $self->{attrs}) { + $self->{asn} = {attrs => $self->{attrs}}; + $self->{value} = $ReadControlRequest->encode($self->{asn}); + die $ReadControlRequest->error if !defined $self->{value}; + } + + return $self; +} + +sub attrs { + my $self = shift; + $self->{attrs} = shift if @_; + + return $self->{attrs}; +} + + +sub entry { + my $self = shift; + $self->{asn} = $ReadControlResponse->decode($self->{value}) or + die "Can't decode: " . $ReadControlResponse->error; + + my $e = Net::LDAP::Entry->new(); + + $e->decode($self->{asn}); + return $e; +} + +sub value { + my $self = shift; + if(@_) { $self->{value} = $_; } + + return $self->{value}; +} + +1; + +__END__ + +=head1 NAME + +Net::LDAP::Control::PreRead - LDAPv3 PreRead control object + +=head1 SYNOPSIS + + use Net::LDAP; + use Net::LDAP::Entry; + use Net::LDAP::Control::PreRead; + use Net::LDAP::Constant qw(LDAP_CONTROL_PREREAD); + + $ldap = Net::LDAP->new( "ldap.mydomain.eg" ); + $preread = Net::LDAP::Control::PreRead->new(critical => 1); + + $entry->increment("integerAttribute", $amount); + $mesg = $entry->update($ldap, control => $preread); + + if($mesg->code) { + die "Error: " . $mesg->error_text . "\n"; + } + + # Retrieve the entry from the control + ($control) = $mesg->control(LDAP_CONTROL_PREREAD); + $entry = $control->entry; + print $entry->get_value("integerAttribute"); + +=head1 DESCRIPTION + +C<Net::LDAP::Control::PreRead> provides an interface for the creation and manipulation of objects that represent the C<preRead> as described by draft-zeilenga-ldap-readentry-04.txt. + +=head1 CONSTRUCTOR ARGUMENTS + +In addition to the constructor arguments described in +L<Net::LDAP::Control> the following are provided. + +=over 4 + +=item attrs + +The attribute return list. + +=back + +B<Please note:> + +=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. + +=item entry + +Return the L<Net::LDAP::Entry> object from the PreRead control response. + +=head1 SEE ALSO + +L<Net::LDAP>, +L<Net::LDAP::Control>, + +=head1 AUTHOR + +Stephen Hock, based on Net::LDAP::Control::ProxyAuth from +Graham Barr E<lt>[EMAIL PROTECTED]<gt>. + +Please report any bugs, or post any suggestions, to the perl-ldap +mailing list E<lt>[EMAIL PROTECTED]<gt> + +=head1 COPYRIGHT + +Copyright (c) 2007 Stephen Hock <[EMAIL PROTECTED]>. All rights reserved. +This program is free software; you can redistribute it and/or +modify it under the same terms as Perl itself. + +=cut diff -Nur perl-ldap-0.34/lib/Net/LDAP/Control.pm perl-ldap-0.34+modify_increment/lib/Net/LDAP/Control.pm --- perl-ldap-0.34/lib/Net/LDAP/Control.pm 2007-02-10 15:44:18.000000000 -0800 +++ perl-ldap-0.34+modify_increment/lib/Net/LDAP/Control.pm 2007-09-10 15:04:45.000000000 -0700 @@ -18,6 +18,8 @@ LDAP_CONTROL_PERSISTENTSEARCH LDAP_CONTROL_ENTRYCHANGE LDAP_CONTROL_MATCHEDVALUES + LDAP_CONTROL_PREREAD + LDAP_CONTROL_POSTREAD ); $VERSION = "0.06"; @@ -40,6 +42,8 @@ 'Net::LDAP::Control::EntryChange' => LDAP_CONTROL_ENTRYCHANGE, 'Net::LDAP::Control::MatchedValues' => LDAP_CONTROL_MATCHEDVALUES, + 'Net::LDAP::Control::PreRead' => LDAP_CONTROL_PREREAD, + 'Net::LDAP::Control::PostRead' => LDAP_CONTROL_POSTREAD, # #LDAP_CONTROL_PWEXPIRED #LDAP_CONTROL_PWEXPIRING diff -Nur perl-ldap-0.34/lib/Net/LDAP/Entry.pm perl-ldap-0.34+modify_increment/lib/Net/LDAP/Entry.pm --- perl-ldap-0.34/lib/Net/LDAP/Entry.pm 2007-02-10 15:44:18.000000000 -0800 +++ perl-ldap-0.34+modify_increment/lib/Net/LDAP/Entry.pm 2007-09-10 14:23:41.000000000 -0700 @@ -255,6 +255,30 @@ return $self; } +sub increment { + my $self = shift; + my $cmd = $self->{'changetype'} eq 'modify' ? [] : undef; + my $attrs = $self->{attrs} ||= _build_attrs($self); + + while (my($type,$val) = splice(@_,0,2)) { + my $lc_type = lc $type; + + push @{$self->{asn}{attributes}}, { type => $type, vals => ($attrs->{$lc_type}=[])} + unless exists $attrs->{$lc_type}; + + push @{$attrs->{$lc_type}}, ref($val) ? @$val : $val; + + push @$cmd, $type, [ ref($val) ? @$val : $val ] + if $cmd; + + } + + push(@{$self->{'changes'}}, 'increment', $cmd) if $cmd; + + return $self; +} + + sub update { my $self = shift; diff -Nur perl-ldap-0.34/lib/Net/LDAP/LDIF.pm perl-ldap-0.34+modify_increment/lib/Net/LDAP/LDIF.pm --- perl-ldap-0.34/lib/Net/LDAP/LDIF.pm 2007-02-10 15:44:18.000000000 -0800 +++ perl-ldap-0.34+modify_increment/lib/Net/LDAP/LDIF.pm 2007-09-10 14:20:50.000000000 -0700 @@ -228,7 +228,7 @@ my $modattr; my $lastattr; if($changetype eq "modify") { - unless ( (my $tmp = shift @ldif) =~ s/^(add|delete|replace):\s*([-;\w]+)// ) { + unless ( (my $tmp = shift @ldif) =~ s/^(add|delete|replace|increment):\s*([-;\w]+)// ) { $self->_error("LDAP entry is not valid",@ldif); return; } diff -Nur perl-ldap-0.34/lib/Net/LDAP/Message.pm perl-ldap-0.34+modify_increment/lib/Net/LDAP/Message.pm --- perl-ldap-0.34/lib/Net/LDAP/Message.pm 2007-02-10 15:44:18.000000000 -0800 +++ perl-ldap-0.34+modify_increment/lib/Net/LDAP/Message.pm 2007-09-10 14:15:49.000000000 -0700 @@ -212,6 +212,7 @@ ## [EMAIL PROTECTED]::LDAP::Increment::ISA = qw(Net::LDAP::Message); @Net::LDAP::Add::ISA = qw(Net::LDAP::Message); @Net::LDAP::Delete::ISA = qw(Net::LDAP::Message); @Net::LDAP::Modify::ISA = qw(Net::LDAP::Message); diff -Nur perl-ldap-0.34/lib/Net/LDAP.pm perl-ldap-0.34+modify_increment/lib/Net/LDAP.pm --- perl-ldap-0.34/lib/Net/LDAP.pm 2007-02-10 15:44:18.000000000 -0800 +++ perl-ldap-0.34+modify_increment/lib/Net/LDAP.pm 2007-09-10 14:16:54.000000000 -0700 @@ -496,7 +496,7 @@ } -my %opcode = ( 'add' => 0, 'delete' => 1, 'replace' => 2); +my %opcode = ( 'add' => 0, 'delete' => 1, 'replace' => 2, 'increment' => 3); sub modify { my $ldap = shift; @@ -616,6 +616,35 @@ $ldap->_sendmesg($mesg); } +sub increment { + my $ldap = shift; + my $arg = &_dn_options; + + my $mesg = $ldap->message('Net::LDAP::Increment' => $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( + incrementRequest => $entry->asn, + controls => $control + ) or return _error($ldap, $mesg, LDAP_ENCODING_ERROR,"$@"); + + $ldap->_sendmesg($mesg); +} + + sub moddn { my $ldap = shift; my $arg = &_dn_options;