gunnar: server/perl-kolab/perl-kolab/lib/Kolab/LDAP/Backend ad.pm, NONE, 1.1 fds.pm, NONE, 1.1 slurpd.pm, NONE, 1.1 syncrepl.pm, NONE, 1.1

cvs at kolab.org cvs at kolab.org
Tue Feb 16 06:33:44 CET 2010


Author: gunnar

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

Added Files:
	ad.pm fds.pm slurpd.pm syncrepl.pm 
Log Message:
Move the perl-kolab sources into their own subdirectory.

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

##  COPYRIGHT
##  ---------
##
##  See AUTHORS file
##
##
##  LICENSE
##  -------
##
##  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>.
##
##  $Revision: 1.1 $

use 5.008;
use strict;
use warnings;
use Kolab;
use Kolab::Util;
use Kolab::LDAP;
use Net::LDAP;
use Net::LDAP::Control;
use vars qw($ldap $cyrus);

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.9';

sub startup { 1; }

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

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

sub changeCallback
{
    Kolab::log('AD', 'Change notification received', KOLAB_DEBUG);

    ###   $_[0]   isa     Net::LDAP::Message
    ###   $_[1]   shouldbea   Net::LDAP::Entry

    my $mesg = shift || 0;
    my $entry = shift || 0;

    my $issearch = $mesg->isa("Net::LDAP::Search");

    if (!$issearch) {
    Kolab::log('AD', 'mesg is not a search object, testing code...', KOLAB_DEBUG);
    if ($mesg->code == 88) {
        Kolab::log('AD', 'changeCallback() -> Exit code received, returning', KOLAB_DEBUG);
        return;
    } elsif ($mesg->code) {
        Kolab::log('AD', "mesg->code = `" . $mesg->code . "', mesg->msg = `" . $mesg->error . "'", KOLAB_DEBUG);
        &abort;
    }   
    } else {
    Kolab::log('AD', 'mesg is a search object, not testing code', KOLAB_DEBUG);
    }

    if (!$entry) {
    Kolab::log('AD', 'changeCallback() called with a null entry', KOLAB_DEBUG);
    return;
    } elsif (!$entry->isa("Net::LDAP::Entry")) {
    Kolab::log('AD', 'changeCallback() called with an invalid entry', KOLAB_DEBUG);
    return;
    }

    if (!Kolab::LDAP::isObject($entry, $Kolab::config{'user_object_class'})) {
    Kolab::log('AD', "Entry is not a `" . $Kolab::config{'user_object_class'} . "', returning", KOLAB_DEBUG);
    return;
    }

    my $deleted = $entry->get_value($Kolab::config{'user_field_deleted'}) || 0;
    if ($deleted) {
    Kolab::LDAP::deleteObject($ldap, $cyrus, $entry);
    return;
    }

    Kolab::LDAP::createObject($ldap, $cyrus, $entry);
}

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;

    Kolab::log('AD', 'Listener starting up');

    $cyrus = Kolab::Cyrus::create;

    Kolab::log('AD', 'Cyrus connection established', KOLAB_DEBUG);

    while (1) {
    Kolab::log('AD', 'Creating LDAP connection to AD 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('AD', 'Sleeping 5 seconds...');
        sleep 5;
        next;
    }

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

    Kolab::LDAP::ensureAsync($ldap);

    Kolab::log('AD', 'Async checked', KOLAB_DEBUG);

    my $ctrl = Net::LDAP::Control->new(
        type    => '1.2.840.113556.1.4.528',
        critical    => 'true'
    );

    Kolab::log('AD', 'Control created', KOLAB_DEBUG);

    my @userdns = split(/;/, $Kolab::config{'user_dn_list'});
    my $userdn;

    Kolab::log('AD', 'User DN list = ' . $Kolab::config{'user_dn_list'}, KOLAB_DEBUG);

    if (length(@userdns) == 0) {
    Kolab::log('AD', 'No user DNs specified, exiting', KOLAB_ERROR);
    exit(1);
    }

    foreach $userdn (@userdns) {
        Kolab::log('AD', "Registering change notification on DN `$userdn'");

        $mesg = $ldap->search (
        base    => $userdn,
        scope       => 'one',
        control     => [ $ctrl ],
        callback    => \&changeCallback,
        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('AD', "Change notification registered on `$userdn'");
    }

    eval {
        local $SIG{ALRM} = sub {
        alarm 0;
        Kolab::log('AD', 'Connection refresh period expired; tearing down connection');

        Kolab::LDAP::destroy($ldap);
        next;
        };

        Kolab::log('AD', 'Waiting for changes (refresh period = ' . $Kolab::config{'conn_refresh_period'} . ' minutes)...');
        alarm $Kolab::config{'conn_refresh_period'} * 60;
        $mesg->sync;
        alarm 0;
    };
    }

    1;
}

1;
__END__
# Below is stub documentation for your module. You'd better edit it!

=head1 NAME

Kolab::LDAP::Backend::ad - Perl extension for an Active Directory backend

=head1 ABSTRACT

  Kolab::LDAP::Backend::ad handles an Active Directory backend to the
  kolab daemon.

=head1 COPYRIGHT AND AUTHORS

Stuart Bingë and others (see AUTHORS file)

=head1 LICENSE

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>.

=cut

--- NEW FILE: fds.pm ---
(This appears to be a binary file; contents omitted.)

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

##  COPYRIGHT
##  ---------
##
##  See AUTHORS file
##
##
##  LICENSE
##  -------
##
##  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>.
##
##  $Revision: 1.1 $

use 5.008;
use strict;
use warnings;
use IO::Select;
use IO::Socket::INET;
use Convert::ASN1 qw(:io);
use Net::LDAP;
use Net::LDAP::Constant qw(LDAP_SUCCESS LDAP_PROTOCOL_ERROR);
use Net::LDAP::ASN qw(LDAPRequest LDAPResponse LDAPResult);
use Kolab;
use Kolab::Util;
use Kolab::LDAP;
use vars qw($conn $server);

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.9';

sub startup { 1; }

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

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

sub PROTOCOLOP_BINDREQUEST      () { 0x00 }
sub PROTOCOLOP_BINDRESPONSE     () { 0x01 }
sub PROTOCOLOP_UNBINDREQUEST    () { 0x02 }
sub PROTOCOLOP_SEARCHREQUEST    () { 0x03 }
sub PROTOCOLOP_SEARCHRESENTRY   () { 0x04 }
sub PROTOCOLOP_SEARCHRESDONE    () { 0x05 }
sub PROTOCOLOP_SEARCHRESREF     () { 0x06 }
sub PROTOCOLOP_MODIFYREQUEST    () { 0x07 }
sub PROTOCOLOP_MODIFYRESPONSE   () { 0x08 }
sub PROTOCOLOP_ADDREQUEST       () { 0x09 }
sub PROTOCOLOP_ADDRESPONSE      () { 0x10 }
sub PROTOCOLOP_DELREQUEST       () { 0x11 }
sub PROTOCOLOP_DELRESPONSE      () { 0x12 }
sub PROTOCOLOP_MODDNREQUEST     () { 0x13 }
sub PROTOCOLOP_MODDNRESPONSE    () { 0x14 }
sub PROTOCOLOP_COMPAREREQUEST   () { 0x15 }
sub PROTOCOLOP_COMPARERESPONSE  () { 0x16 }
sub PROTOCOLOP_ABANDONREQUEST   () { 0x17 }
sub PROTOCOLOP_EXTENDEDREQ      () { 0x18 }
sub PROTOCOLOP_EXTENDEDRESP     () { 0x19 }

sub getRequestType
{
    my $op = shift;
    if ($op->{bindRequest})     { return "bindRequest"; }
    if ($op->{unbindRequest})   { return "unbindRequest"; }
    if ($op->{addRequest})      { return "addRequest"; }
    if ($op->{delRequest})      { return "delRequest"; }
    if ($op->{modifyRequest})   { return "modifyRequest"; }
    if ($op->{modDNRequest})    { return "modDNRequest"; }
    if ($op->{searchRequest})   { return "searchRequest"; }
    if ($op->{compareRequest})  { return "compareRequest"; }
    if ($op->{abandonRequest})  { return "abandonRequest"; }
    if ($op->{extendedRequest}) { return "extendedRequest"; }
    return "";
}

sub responseBind
{
    my $req = shift;
    my $pdu = $LDAPResponse->encode(
        messageID       => $req->{messageID},
        protocolOp      => {
            choiceID        => PROTOCOLOP_BINDRESPONSE,
            bindResponse    => {
                resultCode      => LDAP_SUCCESS,
                matchedDN       => $req->{bindRequest}{name},
                errorMessage    => "",
                serverSaslCreds => ""
            }
        }
    );
    if (!$pdu) {
        Kolab::log('SD', "LDAPResponse error `" .  $LDAPResponse->error . "'");
        &abort;
    }
    return $pdu;
}

sub responseAdd
{
    my $req = shift;
    my $pdu = $LDAPResponse->encode(
        messageID       => $req->{messageID},
        protocolOp      => {
            choiceID        => PROTOCOLOP_ADDRESPONSE,
            addResponse     => {
                resultCode      => LDAP_SUCCESS,
                matchedDN       => $req->{addRequest}{objectName},
                errorMessage    => ""
            }
        }
    );
    if (!$pdu) {
        Kolab::log('SD', "LDAPResponse error `" .  $LDAPResponse->error . "'");
        &abort;
    }
    return $pdu;
}

sub responseDel
{
    my $req = shift;
    my $pdu = $LDAPResponse->encode(
        messageID       => $req->{messageID},
        protocolOp      => {
            choiceID        => PROTOCOLOP_DELRESPONSE,
            addResponse     => {
                resultCode      => LDAP_SUCCESS,
                matchedDN       => $req->{delRequest},
                errorMessage    => ""
            }
        }
    );
    if (!$pdu) {
        Kolab::log('SD', "LDAPResponse error `" .  $LDAPResponse->error . "'");
        &abort;
    }
    return $pdu;
}

sub responseMod
{
    my $req = shift;
    my $pdu = $LDAPResponse->encode(
        messageID       => $req->{messageID},
        protocolOp      => {
            choiceID        => PROTOCOLOP_MODIFYRESPONSE,
            addResponse     => {
                resultCode      => LDAP_SUCCESS,
                matchedDN       => $req->{modifyRequest}{object},
                errorMessage    => ""
            }
        }
    );
    if (!$pdu) {
        Kolab::log('SD', "LDAPResponse error `" .  $LDAPResponse->error . "'");
        &abort;
    }
    return $pdu;
}

sub responseModDN
{
    my $req = shift;
    my $pdu = $LDAPResponse->encode(
        messageID       => $req->{messageID},
        protocolOp      => {
            choiceID        => PROTOCOLOP_MODDNRESPONSE,
            addResponse     => {
                resultCode      => LDAP_SUCCESS,
                matchedDN       => $req->{modDNRequest}{entry},
                errorMessage    => ""
            }
        }
    );
    if (!$pdu) {
        Kolab::log('SD', "LDAPResponse error `" .  $LDAPResponse->error . "'");
        &abort;
    }
    return $pdu;
}

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 {
        if ($conn) { $conn->close; }
        if ($server) { $server->close; }
    }

    my $request;
    my $response;
    my $pdu;
    my $changes = 0;

    my $listenport = $Kolab::config{'slurpd_port'};
    my $listenaddr = $Kolab::config{'slurpd_addr'} || "127.0.0.1";
  TRYCONNECT:
    Kolab::log('SD', "Opening listen server on $listenaddr:$listenport");
    $server = IO::Socket::INET->new(
	LocalPort   => $listenport,
	Proto       => "tcp",
	ReuseAddr   => 1,
	Type        => SOCK_STREAM,
	LocalAddr   => $listenaddr,
	Listen      => 10
     );
    if (!$server) {
        Kolab::log('SD', "Unable to open TCP listen server on $listenaddr:$listenport, Error = $@", KOLAB_ERROR);
	sleep 1;
	goto TRYCONNECT;
    }

    Kolab::log('SD', 'Listen server opened, waiting for incoming connections');

    while ($conn = $server->accept()) {
      # PENDING: Only accept connections from localhost and
      # hosts listed in the kolabhost attribute

	my($peerport, $peeraddr) = sockaddr_in($conn->peername);
	$peeraddr = inet_ntoa( $peeraddr );
        Kolab::log('SD', "Incoming connection accepted, peer=$peeraddr");
	if( $Kolab::config{'slurpd_accept_addr'} && $peeraddr ne $Kolab::config{'slurpd_accept_addr'} ) {
	    Kolab::log('SD', "Unauthorized connection from $peeraddr, closing connection", KOLAB_WARN);
	    $conn->close;
	    undef $conn;
	    next;
	}

        my $select = IO::Select->new($conn);

        while ($conn) {
            undef $pdu;
            my $ready;
            my $offset = 0;

            if (!($select->can_read(0)) && $changes) {
	        Kolab::log('SD', 'Change detected w/ no pending LDAP messages; waiting a second...');
		if( !($select->can_read(1)) ) {
		  $changes = 0;
		  Kolab::log('SD', 'Change detected w/ no pending LDAP messages; reloading services if needed');
                  my $kidpid = fork();
		  unless (defined $kidpid) {
		      die("can't fork: $!");
		  }
		  if ($kidpid == 0 ) {
		      # child
		      Kolab::LDAP::sync;
		      exit(0);
		  }
		  waitpid($kidpid, 0);
		  Kolab::log('SD', "Running $Kolab::config{'kolabconf_script'}");
		  system($Kolab::config{'kolabconf_script'}) == 0
		    or Kolab::log('SD', "Failed to run $Kolab::config{'kolabconf_script'}: $?", KOLAB_ERROR);
		  Kolab::log('SD', "$Kolab::config{'kolabconf_script'} complete");
		}
            }

            Kolab::log('SD', 'Waiting for LDAP updates');

            for ($ready = 1; $conn && $ready; $ready = $select->can_read(0)) {
                Kolab::log('SD', 'Reading ASN', KOLAB_DEBUG);
                my $newoffset = asn_read($conn, $pdu, $offset);
		if( !$conn->connected() or $offset == $newoffset ) {
		  Kolab::log('SD', 'Connection closed', KOLAB_DEBUG);
		  $conn->close;
		  undef $conn;
		}
		$offset = $newoffset;
                defined($offset) or $offset = 0;
            }

            if ($pdu) {
                $request = $LDAPRequest->decode($pdu);
                if (!$request) {
                    Kolab::log('SD', "Unable to decode slurpd request, Error = `" . $LDAPRequest->error . "'", KOLAB_ERROR);
		    $conn->close if $conn;
		    undef $conn;
		    undef $pdu;
                } else {
		    $_ = getRequestType($request);
		    Kolab::log('SD', "Request $_ received", KOLAB_DEBUG);
		    undef $pdu;

		    SWITCH: {
		      if (/^bindRequest/) { $pdu = responseBind($request); last SWITCH; }
                      if (/addRequest/) { $pdu = responseAdd($request); $changes = 1; last SWITCH; }
                      if (/delRequest/) { $pdu = responseDel($request); $changes = 1; last SWITCH; }
                      if (/modifyRequest/) { $pdu = responseMod($request); $changes = 1; last SWITCH; }
                      if (/modDNRequest/) { $pdu = responseModDN($request); $changes = 1; last SWITCH; }

                      if( $conn ) {
		        Kolab::log('SD', 'Unknown request, connection closed', KOLAB_DEBUG);
		        $conn->close;
		        undef $conn;
		      }
		    }
		}
	    }

            if ($pdu) {
                Kolab::log('SD', 'Writing response', KOLAB_DEBUG);
                syswrite($conn, $pdu, length($pdu));
                $response = $LDAPResponse->decode($pdu);
                if (!$response) {
                    Kolab::log('SD', "Unable to decode slurpd request, Error = `" . $LDAPRequest->error . "'");
		    $conn->close;
		    undef $conn;
                }
            }
        }
    }

    $server->close;

    1;
}

1;
__END__
# Below is stub documentation for your module. You'd better edit it!

=head1 NAME

Kolab::LDAP::Backend::slurpd - Perl extension for a slurpd backend

=head1 ABSTRACT

  Kolab::LDAP::Backend::slurpd handles a slurpd backend to the
  kolab daemon.

=head1 COPYRIGHT AND AUTHORS

Stuart Bingë and others (see AUTHORS file)

=head1 LICENSE

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>.

=cut

--- 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 qw(
	LDAP_USER_CANCELED
	LDAP_SYNC_REFRESH_ONLY
	LDAP_SYNC_REFRESH_AND_PERSIST
);
use Net::LDAP::Control;
use Net::LDAP::Control::SyncRequest;
use Net::LDAP::Entry;
use vars qw($ldap $disconnected);
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.3';

sub mode { 
  LDAP_SYNC_REFRESH_ONLY;
  #LDAP_SYNC_REFRESH_AND_PERSIST;
}

# calling without args means: get,
# giving an argument means: set
sub cookie {
  my($cookie) = @_;
  my $syncrepl_cookie_file = $Kolab::config{'syncrepl_cookie_file'} || '/tmp/kolab_syncrepl_cookie_file';
  if(defined($cookie)) {
    if(!open(COOKIE_FILE, '>', $syncrepl_cookie_file)) {
        Kolab::log("SYNCREPL', 'Cannot open file `".$syncrepl_cookie_file.
        "' for writing: $!", KOLAB_DEBUG);
        &abort;
    }
    Kolab::log("SYNCREPL', 'Writing cookie to file: ".$cookie, KOLAB_DEBUG);
    print COOKIE_FILE $cookie;
    close(COOKIE_FILE);
    return $cookie;
  } else {
    #create if it doesn't exists
    if(! -f $syncrepl_cookie_file) {
        open COOKIE_FILE, '>', $syncrepl_cookie_file;
        close COOKIE_FILE;
    }
    if(!open(COOKIE_FILE, '+<', $syncrepl_cookie_file)) {
        Kolab::log("SYNCREPL', 'Cannot open file `".$syncrepl_cookie_file.
        "' for reading: $!", KOLAB_DEBUG);
        &abort;
    }
    read COOKIE_FILE, $cookie, 1024, 0;
    close COOKIE_FILE;
    #an empty file means no cookie:
    $cookie = undef if !$cookie;
    return $cookie;
  }
}

sub startup { 1; }

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

sub abort
{
    Kolab::log('SYNCREPL', '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('SYNCREPL', '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('SYNCREPL', 'Sleeping 5 seconds...');
        sleep 5;
        next;
    }
    $disconnected = 0;  

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

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

    while($ldap and not $disconnected) {
      my $ctrl = Net::LDAP::Control::SyncRequest->new(
        mode       => Kolab::LDAP::Backend::syncrepl::mode(),
        cookie     => Kolab::LDAP::Backend::syncrepl::cookie(),
        reloadHint => 0);
      Kolab::log('SYNCREPL', 'Control created: mode='.$ctrl->mode().
      	'; cookie='.$ctrl->cookie().
      	'; reloadHint='.$ctrl->reloadHint(), 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('SYNCREPL', 'Search created', KOLAB_DEBUG);
      $mesg->sync;
      Kolab::log('SYNCREPL', "Finished Net::LDAP::Search::sync sleeping 10s", KOLAB_DEBUG);
      sleep 10;
    }
  }
  1;
}

#search callback
sub searchCallback {
  my $mesg = shift;
  my $param2 = shift; # might be entry or intermediate
  my @controls = $mesg->control;
  my @sync_controls = ();
  if($param2 && $param2->isa("Net::LDAP::Entry")) {
    Kolab::log('SYNCREPL', 'Received Search Entry', KOLAB_DEBUG);
    #retrieve Sync State Control
    foreach my $ctrl (@controls) {
      push(@sync_controls, $ctrl)
        if $ctrl->isa('Net::LDAP::Control::SyncState');
    }
    if(@sync_controls>1) {
      Kolab::log('SYNCREPL', 'Got search entry with multiple Sync State controls',
        KOLAB_DEBUG);
      return;
    }
    if(!@sync_controls) {
      Kolab::log('SYNCREPL', 'Got search entry without Sync State control',
        KOLAB_DEBUG);
      return;
    }
    if(!$sync_controls[0]->entryUUID) {
      Kolab::log('SYNCREPL', 'Got empty entryUUID', KOLAB_DEBUG);
      return;
    }
    Kolab::log('SYNCREPL', 'Search Entry has Sync State Control: '.
      'state='.$sync_controls[0]->state().
      '; entryUUID='.unpack("H*",$sync_controls[0]->entryUUID()).
      '; cookie='.(defined($sync_controls[0]->cookie()) ? $sync_controls[0]->cookie() : 'UNDEF')
	, KOLAB_DEBUG);
    if(defined($sync_controls[0]->cookie)) {
      Kolab::LDAP::Backend::syncrepl::cookie($sync_controls[0]->cookie);
      Kolab::log('SYNCREPL',"New cookie: ".Kolab::LDAP::Backend::syncrepl::cookie(),
        KOLAB_DEBUG);
    }
    Kolab::log('SYNCREPL', "Entry (".$param2->changetype."): ".$param2->dn(), KOLAB_DEBUG);
  } elsif($param2 && $param2->isa("Net::LDAP::Reference")) {
    Kolab::log('SYNCREPL', 'Received Search Reference', KOLAB_DEBUG);
    return;
  #if it not first control?
  } elsif($controls[0] and $controls[0]->isa('Net::LDAP::Control::SyncDone')) {
    Kolab::log('SYNCREPL', 'Received Sync Done Control: '.
      'cookie='.(defined($controls[0]->cookie()) ? $controls[0]->cookie() : 'UNDEF').
      '; refreshDeletes='.$controls[0]->refreshDeletes(), KOLAB_DEBUG);
    #we have a new cookie
    if(defined($controls[0]->cookie())
        and not $controls[0]->cookie() eq '' 
        and not $controls[0]->cookie() eq Kolab::LDAP::Backend::syncrepl::cookie()) {
      Kolab::LDAP::Backend::syncrepl::cookie($controls[0]->cookie());
      Kolab::log('SYNCREPL', "New cookie: ".
        Kolab::LDAP::Backend::syncrepl::cookie(), KOLAB_DEBUG);
      Kolab::log('SYNCREPL', "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('SYNCREPL', "Finished Kolab::LDAP::sync sleeping 1s", KOLAB_DEBUG);
      sleep 1; # we get too many bogus change notifications!
	  } 
  } elsif($param2 && $param2->isa("Net::LDAP::Intermediate")) {
    Kolab::log('SYNCREPL', 'Received Intermediate Message', KOLAB_DEBUG);
    my $attrs = $param2->{asn};
    if($attrs->{newcookie}) {
      Kolab::LDAP::Backend::syncrepl::cookie($attrs->{newcookie});
      Kolab::log('SYNCREPL', "New cookie: ".
        Kolab::LDAP::Backend::syncrepl::cookie(), KOLAB_DEBUG);
    } elsif(my $refreshInfos = ($attrs->{refreshDelete} || $attrs->{refreshPresent})) {
      Kolab::LDAP::Backend::syncrepl::cookie($refreshInfos->{cookie})
        if defined($refreshInfos->{cookie});
      Kolab::log('SYNCREPL', 
        (defined($refreshInfos->{cookie}) ? 'New ' : 'Empty ').
        "cookie from ".
        ($attrs->{refreshDelete} ? 'refreshDelete' : 'refreshPresent').
        " (refreshDone=".$refreshInfos->{refreshDone}."): ".
        Kolab::LDAP::Backend::syncrepl::cookie(), KOLAB_DEBUG);
    } elsif(my $syncIdSetInfos = $attrs->{syncIdSet}) {
      Kolab::LDAP::Backend::syncrepl::cookie($syncIdSetInfos->{cookie})
        if defined($syncIdSetInfos->{cookie});
      Kolab::log('SYNCREPL', 
        (defined($syncIdSetInfos->{cookie}) ? 'Empty ' : 'New ').
        "cookie from syncIdSet".
        " (refreshDeletes=".$syncIdSetInfos->{refreshDeletes}."): ".
        Kolab::LDAP::Backend::syncrepl::cookie(), KOLAB_DEBUG);
      foreach my $syncUUID ($syncIdSetInfos->{syncUUIDs}) {
        Kolab::log('SYNCREPL', 'entryUUID='.
          unpack("H*",$syncUUID), KOLAB_DEBUG);
      }
    }
  } elsif($mesg->code) {
    if ($mesg->code == 1) {
      Kolab::log('SYNCREPL', 'Communication Error: disconnecting', KOLAB_DEBUG);
      $disconnected = 1;
      return 0;
    } elsif ($mesg->code == LDAP_USER_CANCELED) {
        Kolab::log('SYNCREPL', 'searchCallback() -> Exit code received, returning', KOLAB_DEBUG);
        return;
    } elsif ($mesg->code == 4096) {
        Kolab::log('SYNCREPL', 'Refresh required', KOLAB_DEBUG);
        Kolab::LDAP::Backend::syncrepl::cookie('');
    } else {
        Kolab::log('SYNCREPL', "searchCallback: mesg->code = `" . $mesg->code . "', mesg->msg = `" . $mesg->error . "'", KOLAB_DEBUG);
        &abort;
    }   
  } else {
    Kolab::log('SYNCREPL', 'Received something else', KOLAB_DEBUG);
  }
  return 0;
}

1;
__END__

=head1 NAME

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

=head1 ABSTRACT

  Kolab::LDAP::Backend::syncrepl 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