richard: server/perl-kolab/lib/Kolab/LDAP/Backend syncrepl.pm, NONE, 1.1

cvs at kolab.org cvs at kolab.org
Thu Jul 17 10:59:10 CEST 2008


Author: richard

Update of /kolabrepository/server/perl-kolab/lib/Kolab/LDAP/Backend
In directory doto:/tmp/cvs-serv19897/lib/Kolab/LDAP/Backend

Added Files:
	syncrepl.pm 
Log Message:
Add syncrepl support, needed to support openldap-2.4.x and beyond.
See kolab/issue1755
https://www.intevation.de/roundup/kolab/issue1755



--- NEW FILE: syncrepl.pm ---
package Kolab::LDAP::Backend::syncrepl;

##
##  Copyright (c) 2008  Mathieu Parent <math.parent at gmail.com>
##
##  This  program is free  software; you can redistribute  it and/or
##  modify it  under the terms of the GNU  General Public License as
##  published by the  Free Software Foundation; either version 2, or
##  (at your option) any later version.
##
##  This program is  distributed in the hope that it will be useful,
##  but WITHOUT  ANY WARRANTY; without even the  implied warranty of
##  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
##  General Public License for more details.
##
##  You can view the  GNU General Public License, online, at the GNU
##  Project's homepage; see <http://www.gnu.org/licenses/gpl.html>.
##
use 5.008;
use strict;
use warnings;
use Kolab;
use Kolab::LDAP;
use Net::LDAP;
use Net::LDAP::Control;
use Net::LDAP::Entry;
use vars qw($ldap $cookie $disconnected);
my $cookie = '';
my $disconnected = 1;

require Exporter;

our @ISA = qw(Exporter);

our %EXPORT_TAGS = (
    'all' => [ qw(
    &startup
    &run
    ) ]
);

our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );

our @EXPORT = qw(
    
);

our $VERSION = '0.1';

# LDAP Content Synchronization Operation -- RFC 4533
use constant LDAP_SYNC_OID => "1.3.6.1.4.1.4203.1.9.1";
use constant {
	LDAP_CONTROL_SYNC => LDAP_SYNC_OID.".1",
	LDAP_CONTROL_SYNC_STATE => LDAP_SYNC_OID.".2",
	LDAP_CONTROL_SYNC_DONE => LDAP_SYNC_OID.".3",
	LDAP_SYNC_INFO => LDAP_SYNC_OID.".4",

	LDAP_SYNC_NONE => 0x00,
	LDAP_SYNC_REFRESH_ONLY => 0x01,
	LDAP_SYNC_RESERVED => 0x02,
	LDAP_SYNC_REFRESH_AND_PERSIST => 0x03,

	LDAP_SYNC_REFRESH_PRESENTS => 0,
	LDAP_SYNC_REFRESH_DELETES => 1,

	LDAP_TAG_SYNC_NEW_COOKIE => 0x80,
	LDAP_TAG_SYNC_REFRESH_DELETE => 0xa1,
	LDAP_TAG_SYNC_REFRESH_PRESENT => 0xa2,
	LDAP_TAG_SYNC_ID_SET => 0xa3,

	LDAP_TAG_SYNC_COOKIE => 0x04,
	LDAP_TAG_REFRESHDELETES => 0x01,
	LDAP_TAG_REFRESHDONE => 0x01,
	LDAP_TAG_RELOAD_HINT => 0x01,

	LDAP_SYNC_PRESENT => 0,
	LDAP_SYNC_ADD => 1,
	LDAP_SYNC_MODIFY => 2,
	LDAP_SYNC_DELETE => 3,
};

use Convert::ASN1;
use Data::Dumper;

my $asn = Convert::ASN1->new;

$asn->prepare(<<'LDAP_ASN') or die $asn->error;
syncUUID ::= OCTET STRING -- (SIZE(16))

syncCookie ::= OCTET STRING

syncRequestValue ::= SEQUENCE {
    mode ENUMERATED {
        -- 0 unused
        refreshOnly       (1),
        -- 2 reserved
        refreshAndPersist (3)
    }
    cookie     syncCookie OPTIONAL,
    reloadHint BOOLEAN -- DEFAULT FALSE
}

syncStateValue ::= SEQUENCE {
    state ENUMERATED {
        present (0),
        add (1),
        modify (2),
        delete (3)
    }
    entryUUID syncUUID,
    cookie    syncCookie OPTIONAL
}

syncDoneValue ::= SEQUENCE {
    cookie          syncCookie OPTIONAL,
    refreshDeletes  BOOLEAN -- DEFAULT FALSE
}

syncInfoValue ::= CHOICE {
      newcookie      [0] syncCookie,
      refreshDelete  [1] SEQUENCE {
          refreshDeleteCookie         syncCookie OPTIONAL,
          refreshDeleteDone    BOOLEAN -- DEFAULT TRUE
      }
      refreshPresent [2] SEQUENCE {
          refreshDeletecookie         syncCookie OPTIONAL,
          refreshDeleteDone    BOOLEAN -- DEFAULT TRUE
      }
      syncIdSet      [3] SEQUENCE {
          cookie         syncCookie OPTIONAL,
          refreshDeletes BOOLEAN, -- DEFAULT FALSE
          syncUUIDs      SET OF syncUUID
      }
}

LDAP_ASN


sub startup { 1; }

sub shutdown
{
  Kolab::log('SYNC', 'Shutting down');
  exit(0);
}

sub abort
{
    Kolab::log('SYNC', 'Aborting');
    exit(1);
}

sub run {
  # This should be called from a separate thread, as we set our
  # own interrupt handlers here

  $SIG{'INT'} = \&shutdown;
  $SIG{'TERM'} = \&shutdown;

  END {
    alarm 0;
    Kolab::LDAP::destroy($ldap);
  }
  my $mesg;

  while (1) {
    Kolab::log('SYNC', 'Creating LDAP connection to LDAP server', KOLAB_DEBUG);

    $ldap = Kolab::LDAP::create($Kolab::config{'user_ldap_ip'},
                                $Kolab::config{'user_ldap_port'},
                                $Kolab::config{'user_bind_dn'},
                                $Kolab::config{'user_bind_pw'},
                                1
                               );
    if (!$ldap) {
        Kolab::log('SYNC', 'Sleeping 5 seconds...');
        sleep 5;
        next;
    }
    $disconnected = 0;  

    Kolab::log('SYNC', 'LDAP connection established', KOLAB_DEBUG);

    Kolab::LDAP::ensureAsync($ldap);
    Kolab::log('SYNC', 'Async checked', KOLAB_DEBUG);

    Kolab::log('SYNC', "Cookie: $cookie", KOLAB_DEBUG);

    while($ldap and not $disconnected) {
	    #sync control
      my $asn_syncRequestValue = $asn->find('syncRequestValue');
	    my $ctrl = Net::LDAP::Control->new(type  => LDAP_CONTROL_SYNC, 
		                                     value => $asn_syncRequestValue->encode(mode       => LDAP_SYNC_REFRESH_ONLY,
                                                                                cookie     => $cookie,
                                                                                reloadHint => 0
                                                                               ),
                                         critical   => 0
	                                      );
      Kolab::log('SYNC', 'Control created', KOLAB_DEBUG);
    
	    #search
	    my $mesg = $ldap->search(base     => $Kolab::config{'base_dn'},
                               scope    => 'sub',
                               control  => [ $ctrl ],
                               callback => \&searchCallback, # call for each entry
                               filter   => "(objectClass=*)",
                               attrs    => [ '*',
                                             $Kolab::config{'user_field_guid'},
                                             $Kolab::config{'user_field_modified'},
                                             $Kolab::config{'user_field_quota'},
                                             $Kolab::config{'user_field_deleted'},
                                           ],
				                      );
      Kolab::log('SYNC', 'Search created', KOLAB_DEBUG);
      $mesg->sync;
      Kolab::log('SYNC', "Finished Net::LDAP::Search::sync sleeping 10s", KOLAB_DEBUG);
      sleep 10;
    }
  }
  1;
}

#search callback
sub searchCallback {
  my $mesg = shift;
  my $entry = shift;
  my $issearch = $mesg->isa("Net::LDAP::Search");
  my @controls = $mesg->control;
  if(not $issearch) {
    Kolab::log('SYNC', 'mesg is not a search object, testing code...', KOLAB_DEBUG);
    if ($mesg->code == 88) {
        Kolab::log('SYNC', 'searchCallback() -> Exit code received, returning', KOLAB_DEBUG);
        return;
    } elsif ($mesg->code) {
        Kolab::log('SYNC', "Not a search: mesg->code = `" . $mesg->code . "', mesg->msg = `" . $mesg->error . "'", KOLAB_DEBUG);
        &abort;
    }   
  } elsif(@controls == 0) {
    if ($mesg->code == 1) {
        Kolab::log('SYNC', 'No control: Communications Error: disconnecting', KOLAB_DEBUG);
        $disconnected = 1;
        return;
    } elsif ($mesg->code) {
      Kolab::log('SYNC', "No control: mesg->code = `" . $mesg->code . "', mesg->msg = `" . $mesg->error . "'", KOLAB_DEBUG);
        &abort;
    }   
  } elsif($controls[0]->type eq LDAP_CONTROL_SYNC_STATE) {
    Kolab::log('SYNC', 'Received Sync State Control', KOLAB_DEBUG);
    Kolab::log('SYNC', "Entry (".$entry->changetype."): ".$entry->dn(), KOLAB_DEBUG);
  } elsif($controls[0]->type eq LDAP_CONTROL_SYNC_DONE) {
    Kolab::log('SYNC', 'Received Sync Done Control', KOLAB_DEBUG);
    my $asn_syncDoneValue = $asn->find('syncDoneValue');
    my $out = $asn_syncDoneValue->decode($controls[0]->value);
	  #we have a new cookie
	  if(defined($out->{cookie}) and not $out->{cookie} eq '' and not $out->{cookie} eq $cookie) {
		  $cookie = $out->{cookie};
		  Kolab::log('SYNC', "New cookie: $cookie", KOLAB_DEBUG);
      Kolab::log('SYNC', "Calling Kolab::LDAP::sync", KOLAB_DEBUG);
      Kolab::LDAP::sync;
      system($Kolab::config{'kolabconf_script'}) == 0 || Kolab::log('SD', "Failed to run kolabconf: $?", KOLAB_ERROR);
      Kolab::log('SYNC', "Finished Kolab::LDAP::sync sleeping 1s", KOLAB_DEBUG);
      sleep 1; # we get too many bogus change notifications!
	  } 
  } else {
    Kolab::log('SYNC', 'Received unknown control: '.$controls[0]->type, KOLAB_DEBUG);
  }
  return 0;
}

1;
__END__

=head1 NAME

Kolab::LDAP::Backend::sync - Perl extension for RFC 4533 compliant LDAP server backend

=head1 ABSTRACT

  Kolab::LDAP::Backend::sync handles OpenLDAP backend to the kolab daemon.

=head1 AUTHOR

Mathieu Parent <math.parent at gmail.com>

=head1 COPYRIGHT AND LICENSE

Copyright (c) 2008  Mathieu Parent <math.parent at gmail.com>


This  program is free  software; you can redistribute  it and/or
modify it  under the terms of the GNU  General Public License as
published by the  Free Software Foundation; either version 2, or
(at your option) any later version.

This program is  distributed in the hope that it will be useful,
but WITHOUT  ANY WARRANTY; without even the  implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.

You can view the  GNU General Public License, online, at the GNU
Project's homepage; see <http://www.gnu.org/licenses/gpl.html>.

=head1 NOTES
We use refreshOnly mode as refreshAndPersist mode uses LDAP Intermediate
Response Messages [RFC4511] that are not supported by current Net::LDAP.

However (quoting from RFC, page 21):

   The server SHOULD transfer a new cookie frequently to avoid having to
   transfer information already provided to the client.  Even where DIT
   changes do not cause content synchronization changes to be
   transferred, it may be advantageous to provide a new cookie using a
   Sync Info Message.  However, the server SHOULD avoid overloading the
   client or network with Sync Info Messages.



=cut





More information about the commits mailing list