#!/usr/bin/perl -w

#  Script for copying account data from ActiveDirectory and other LDAP servers.
#  Use it as complementary to the external authentication helpers http://communigatepro.ru/CGAUTH/authLDAPNewAD.pl or http://communigatepro.ru/CGAUTH/authLDAPNew.pl
#  Revision 09.06.2022
#
#  Please mail your comments to <support@communigatepro.ru>



#  You may need to install the following modules:
#  ASN1 from <http://www.cpan.org/modules/by-module/Convert/>
#  LDAP from <http://www.cpan.org/modules/by-module/Net/>
use Net::LDAP;
use strict;
use MIME::Base64;

#  Take the CLI.pm module from <http://communigatepro.ru/CGPerl/>
use CLI;


#
# You should redefine these values
#

my %domains=( # e-mail domains
  'company.com' => { # need to create this for every domain you use with external authentication
    address=>'ldaps://127.0.0.1:636',  #the URI or address of LDAP server
    backupAddress=>'192.168.0.2',  # backup LDAP server address (optional)
    timeout=>5, # timeout in seconds, 20 by default
    adminDN=>'CN=Administrator,CN=Users,DC=company,DC=com',     # the DN for admin bind
    adminPassword=>'password',

    searchBase=>'CN=Users,DC=company,DC=com',                                             
    searchFilter=>'(&(sAMAccountName=<user>)(objectclass=*))',

  },
  'new.company.com' => {
    address=>'127.0.0.1',  
    adminDN=>'CN=Administrator,CN=Users,DC=new,DC=company,DC=com', 
    adminPassword=>'password',

    searchBase=>'CN=Users,DC=new,DC=company,DC=com',                                             
    searchFilter=>'(&(mail=<user>@<domain>)(objectclass=user))',
  },

);

my %attributes=(
 cn => 'RealName',
 userPassword => 'Password',
 o => 'Organization',
 ou => 'ou',
 st => 'st',
 l => 'l',
 sn => 'sn',
 givenName => 'givenName',
 title => 'title',
 telephoneNumber => 'telephoneNumber',
 mobile => 'mobile',
);


my $CGServerAddress =  '127.0.0.1';   
my $CLILogin = 'postmaster';
my $CLIPassword = 'pass';

#
# END of user customiseable parameters 
#

my $cli = new CGP::CLI( { PeerAddr => $CGServerAddress,
                          PeerPort => 106,
                          login    => $CLILogin,
                          password => $CLIPassword
                        } ) || die "*** Can't login to CGPro CLI: ".$CGP::ERR_STRING."\n";

my $ldap;

processDomain($_) foreach(keys %domains);

$cli->Logout();

exit(0);

sub processDomain {
  my ($domain)=@_;
  print "Domain: $domain\n";

  $ldap=tryConnectServer($domain);
  unless($ldap) {
    print "*** Error: failed to connect to LDAP server\n";
    return;
  }
  
  my $adminDN=$domains{$domain}->{adminDN};
  my $adminPassword=$domains{$domain}->{adminPassword};
 
  my $result;
  unless($result=$ldap->bind($adminDN,password=>$adminPassword)) {
    print "*** Error: Can't bind as admin\n";
    return;
  }
  if($result->code) {
    print "*** Error: Can't bind as admin: ".$result->error."\n";
    return;
  } 
  
  processDomainAccounts($domain); 
# my $errCode=processAccount($domain,"user");
# print "*** Error: $errCode\n" if($errCode); 
  
  $ldap->unbind();                        # unbind & disconnect
}

sub processDomainAccounts {
  my ($domain)=@_;
  my $cookie="";
  do {
    my $data=$cli->ListDomainObjects($domain,5000,undef,'ACCOUNTS',$cookie);
    unless($data) {
      print "*** Can't get accounts for $domain: ".$cli->getErrMessage."\n";
      return;
    }
    $cookie=$data->[4];
    foreach(keys %{$data->[1]} ) {
      my $errCode=processAccount($domain,$_);
      print "*** Error: $errCode\n" if($errCode); 
    }
  }while($cookie ne '');

}

sub processAccount {
  my ($domain,$name)=@_;
  my $account="$name\@$domain";
  print "Account: $account\n";
  
  my $searchBase=$domains{$domain}->{searchBase};
  $searchBase=~s/<user>/$name/g;
  $searchBase=~s/<domain>/$domain/g;
  my $searchFilter=$domains{$domain}->{searchFilter};
  $searchFilter=~s/<user>/$name/g;
  $searchFilter=~s/<domain>/$domain/g;
  print "* searching $searchBase for $searchFilter\n";
 
  my $mesg = $ldap->search (  # perform a search
               base   => $searchBase,
               filter => $searchFilter
             );
  unless(defined $mesg) {
    return "LDAP search failed";   
  } 
  if($mesg->all_entries() eq 0) {
    print "$account is to be deleted\n";
    # $cli->DeleteAccount($account);
    return undef;
  }

  my %userData;  

  my($sn,$givenName,$title,$telephoneNumber,$mobile,$thumbnailPhoto);
  foreach my $entry ($mesg->all_entries) {
    my $ref1=@$entry{'asn'};
    my $attrs=@$ref1{'attributes'};
    foreach my $atrRef (@$attrs) {
      my $type=@$atrRef{'type'};
      my $vals=@$atrRef{'vals'};
      
      if($attributes{$type}) {
        $userData{$attributes{$type}}=@$vals[0];
      }
      
      $sn=@$vals[0] if($type eq 'sn');
      $givenName=@$vals[0] if($type eq 'givenName');
      $title=@$vals[0] if($type eq 'title');
      $telephoneNumber=@$vals[0] if($type eq 'telephoneNumber');
      $mobile=@$vals[0] if($type eq 'mobile');
      

      $thumbnailPhoto=@$vals[0] if($type eq 'thumbnailPhoto');
#      $jpegPhoto=@$vals[0] if($type eq 'jpegPhoto');
    }
    last; # we need only 1 entry
  }

  
  $cli->UpdateAccountSettings($account,\%userData)
    || return "Can't update account$account via CLI:".$cli->getErrMessage;

  if($thumbnailPhoto) {
    $cli->SendCommand("UpdateAccountSettings $account { jpegPhoto=[".encode_base64($thumbnailPhoto,'')."]; }");
  }
    my $data;
    $data="BEGIN:VCARD\r\n";
    $data.="VERSION:2.1\r\n";
    $data.="EMAIL:".$account."\r\n";
    $data.="N:".($sn || '').";".($givenName || '').";;". ($title || '').";\r\n";
    $data.="ORG:".($userData{'Organization'} || '').";".($userData{'ou'} || '')."\r\n" if($userData{'Organization'} || $userData{'ou'});

    $data.="TEL;WORK:".$telephoneNumber."\r\n" if($telephoneNumber);
    $data.="TEL;CELL:".$mobile."\r\n" if($mobile);

    $data.="PHOTO;JPEG;ENCODING=BASE64:".encode_base64($thumbnailPhoto,"\r\n ")."\r\n" if($thumbnailPhoto);
    $data.="X-FILE-AS:".$account."\n\r";
    $data.="END:VCARD\n\r";
    
    my ($slice,$offset,$size)=(44*1024,0,length($data));
    while($offset<$size) {
      $cli->WriteStorageFile($account,"profile.vcf",encode_base64( substr($data,$offset,$slice) ,''),$offset);
      $offset+=$slice;
    };  
    
  undef;
}

sub tryConnectServer {
  my ($domain)=@_;
  my $domData=$domains{$domain};
  my $adr=$domData->{address};

  print "* trying to connect to $adr\n";
  
  my $ldap = Net::LDAP->new($adr,timeout=>($domData->{timeout} || 20),inet4=>1,inet6=>0 );
  unless($ldap) {
    if($domData->{backupAddress}) {
      print "* connection failed, trying backup at $domData->{backupAddress}\n";
      $ldap = Net::LDAP->new($domData->{backupAddress},timeout=>($domData->{timeout} || 20),inet4=>1,inet6=>0 );
      $domData->{backupSwitchTime}=time() if($ldap); 
    }
  }
  return $ldap;
}


__END__

