#!/usr/bin/perl -w
#
#  ldapMerge.pl
#
#  The script for merging several Directory branches (possible on
#  different servers) into one to use as address book.
#
#  Please mail your comments to <support@communigate.ru> 
#
#  Revision: 18-Oct-2023

use Net::LDAP;
use strict;

#
# You should redefine these values
#


my $TargetServerAddress =  '10.3.8.1';   # You should redefine these values
my $TargetServerLogin = 'postmaster';
my $TargetServerPassword = 'pass';
my $TargetServerBaseDN = 'o=common_addressbook';

my @Sources = (

{ address => '10.3.1.216',
  bindDN => 'postmaster',
  password => 'admin',
  base => 'cn=d.dom'
},
{ address => '10.3.1.216',
  bindDN => 'postmaster',
  password => 'admin',
  base => 'cn=somedom.ru'
},


);

my @attributesToReturn = qw/
mail
cn
sn
description
telephoneNumber
l
st
street
ou
title
postalAddress
postalCode
o
company
givenName
initials
uid
mobile
photo
jpegPhoto
userCertificate
displayName
/;

my @attributesToTranlsate = qw/
company o
street st
/;

#
# END of user customiseable parameters 
#



#print "Started\n";

  my %cgpData;
  my %extData;
  my $result;

foreach my $ref (@Sources) {
  print "Reading from external LDAP server at $ref->{address} $ref->{base}\n";
  my $eldap = Net::LDAP->new($ref->{address}, port=>389)
   || die "can't connect to $ref->{address} via LDAP: $@";

  $result=$eldap->bind($ref->{bindDN},password=>$ref->{password} );
  $result && $result->code && die "Can't bind as $ref->bindDN: ".$result->error;
  
  
  my @args = (
    base => $ref->{base},
    scope => 'sub',
    filter => '(&(objectClass=inetOrgPerson)(mail=*)(!(|(objectClass=CommuniGateNamedTask)(objectClass=CommuniGateGroup)(objectClass=CommuniGateMailList)(objectClass=CommuniGateForwarder))))',
    attrs   => \@attributesToReturn,
  );
  my $cnt = 0;
  my $mesg = $eldap->search( @args );
  $mesg->code && print "search failed: ".$mesg->error."\n";
  foreach my $entry ($mesg->entries) {
    for(my $idx=0;$idx<scalar(@attributesToTranlsate);$idx+=2) {
      if((my $v = $entry->get_value( $attributesToTranlsate[$idx] )) && !$entry->get_value( $attributesToTranlsate[$idx+1] )) {
        $entry->add( $attributesToTranlsate[$idx+1] => $v );
        $entry->delete( $attributesToTranlsate[$idx] );
      }
    }
    $extData{ makeDN($entry) } = $entry;
    ++$cnt;
  }
  $eldap->unbind(); 
  print " $cnt records have been read\n";
}
  
  my $ldap = Net::LDAP->new($TargetServerAddress, port=>389)
   || die "can't connect to $TargetServerAddress via LDAP: $@";
   
  $result=$ldap->bind($TargetServerLogin,password=>$TargetServerPassword );
  $result && $result->code && die "Can't bind as $TargetServerLogin: ".$result->error;

  $result = $ldap->add ( $TargetServerBaseDN , attrs => [ ou => "address book", objectClass => [ "top","organizationalUnit" ] ] );

{
  print "Reading from CGPro at $TargetServerAddress\n";
  my @args = (
    base => $TargetServerBaseDN,
    scope => 'sub',
    filter => '(objectClass=*)',
  );
  my $cnt = 0;
  my $mesg = $ldap->search( @args );
  $mesg->code && print "search failed: ".$mesg->error."\n";
    foreach my $entry ($mesg->entries) {
      $cgpData{ $entry->dn() } = $entry;
      ++$cnt;
    }
  print " $cnt records have been read\n";
}

{
  my $cntAdd = 0;
  my $cntUpd = 0;
  foreach my $cdn (keys %extData) {
    my $dn = "uid=$cdn,$TargetServerBaseDN";
    unless(exists $cgpData{$dn}) {
      print "adding $dn\n";
      my $CreateArray = [
        objectClass => [ "top", "person", "organizationalPerson", "inetOrgPerson" ],
      ];
      foreach my $attr ( $extData{$cdn}->attributes ) {
        push(@$CreateArray,$attr,$extData{$cdn}->get_value($attr));
      }
      $result = $ldap->add ( $dn , attrs => [@$CreateArray] );
      if($result->code) {
        print "Can't add $dn: ".$result->error."\n";
      } else {
        ++$cntAdd;
      }
    } else {
      my %changes;
      foreach my $attr ( $extData{$cdn}->attributes ) {
        next if($attr eq 'uid');
        my $v = $extData{$cdn}->get_value($attr);
        my $v2 = $cgpData{$dn}->get_value($attr);
        if(!defined($v2) || $v2 ne $v) {
          $changes{$attr} = $v;         
        }
      }
      if(%changes) {
        print "updating $dn\n";
        $result = $ldap->modify ( $dn, replace => { %changes } );
        if($result->code) {
          print "Can't update $dn: ".$result->error."\n";
        } else {
          ++$cntUpd;
        }
      }
    }
  }
  print " $cntAdd records have been added, $cntUpd records have been updated\n";
}

{
  my $cntDel = 0;
  foreach my $dn (keys %cgpData) {
    $dn=~/uid=(.*),$TargetServerBaseDN/;
    my $cdn = $1;
    unless(exists $extData{$cdn}) {
      print "deleting $dn\n";
      $cgpData{$dn}->delete();
      $result = $cgpData{$dn}->update ( $ldap );
      if($result->code) {
        print "Can't delete $dn: ".$result->error."\n";
      } else {
        ++$cntDel;
      }
    }
  }
  print " $cntDel records have been deleted\n";
}

  $ldap->unbind(); 
  print "Done\n";
  
exit(0);

sub makeDN {
  my ($entry) = @_;
  my $v = $entry->get_value('cn');
  $v = '' if($v eq 'none');
  $v=~s/([,=+<>#;"\\])/\\$1/g;
  return join(" ",$v,$entry->get_value('mail'));
}


