From 50bf84527e946c3bb6ef429376df1f0989a9bb96 Mon Sep 17 00:00:00 2001
From: Martin Haase <martin.haase@daasi.de>
Date: Mon, 27 Dec 2010 11:49:22 +0000
Subject: [PATCH] move completed, via three towns

git-svn-id: https://textgridlab.org/svn/textgrid/trunk/middleware/tgauth@8232 7c539038-3410-0410-b1ec-0f2a7bf1c452
---
 .../00Readme.txt                              |   1 +
 .../etc/syncFromRBAC.conf-dist                |  15 +
 .../etc/syncFromRBAC.sys                      | 118 +++++
 .../last_LDAP_sync_timestamp.txt              |   1 +
 .../syncFromRBAC                              | 459 ++++++++++++++++++
 5 files changed, 594 insertions(+)
 create mode 100644 info.textgrid.middleware.tgauth.pdp2acl/00Readme.txt
 create mode 100644 info.textgrid.middleware.tgauth.pdp2acl/etc/syncFromRBAC.conf-dist
 create mode 100644 info.textgrid.middleware.tgauth.pdp2acl/etc/syncFromRBAC.sys
 create mode 100644 info.textgrid.middleware.tgauth.pdp2acl/last_LDAP_sync_timestamp.txt
 create mode 100755 info.textgrid.middleware.tgauth.pdp2acl/syncFromRBAC

diff --git a/info.textgrid.middleware.tgauth.pdp2acl/00Readme.txt b/info.textgrid.middleware.tgauth.pdp2acl/00Readme.txt
new file mode 100644
index 0000000..95ce22c
--- /dev/null
+++ b/info.textgrid.middleware.tgauth.pdp2acl/00Readme.txt
@@ -0,0 +1 @@
+synchronizes Access Policy from openRBAC into POSIX ACL entries on a Grid Host. To be called by CRON with root access regularly.
diff --git a/info.textgrid.middleware.tgauth.pdp2acl/etc/syncFromRBAC.conf-dist b/info.textgrid.middleware.tgauth.pdp2acl/etc/syncFromRBAC.conf-dist
new file mode 100644
index 0000000..531b46e
--- /dev/null
+++ b/info.textgrid.middleware.tgauth.pdp2acl/etc/syncFromRBAC.conf-dist
@@ -0,0 +1,15 @@
+ldap_conf_host = ldap.example.org
+ldap_conf_port = 389
+ldap_conf_binddn = "cn=manager,dc=example,dc=org"
+ldap_conf_bindpw = secret
+ldap_conf_is_tls = 1 
+ldap_conf_tls_cafile = /path/to/cacert/directory/chain.pem
+ldap_conf_tls_verify = require
+ldap_conf_tls_cypher = AES256-SHA
+ldap_conf_basedn = dc=example,dc=org
+ldap_conf_scope = sub
+
+gridmapfilepath = /etc/grid-security/grid-mapfile
+groupfilepath = /etc/group
+
+slcs_dn_prefix = /C=DE/O=DFN-Verein/OU=DFN-PKI/OU=SLCS/
diff --git a/info.textgrid.middleware.tgauth.pdp2acl/etc/syncFromRBAC.sys b/info.textgrid.middleware.tgauth.pdp2acl/etc/syncFromRBAC.sys
new file mode 100644
index 0000000..cb5fc25
--- /dev/null
+++ b/info.textgrid.middleware.tgauth.pdp2acl/etc/syncFromRBAC.sys
@@ -0,0 +1,118 @@
+progname = "syncFromRBAC"
+
+version = 0.2
+
+date = "2010-12-06"
+<author>
+   name  = "Martin Haase" 
+   org= "DAASI International GmbH"
+   mail = "martin.haase@daasi.de"
+</author>
+
+<copyright>
+text1 = Copyright (c) 2010 DAASI International GmbH
+text2 = This library is free software; you can redistribute it and/or \
+modify it under the same terms as Perl itself.
+</copyright>
+
+progshortdescr = "Synchronize RBAC rules into POSIX ACLs"
+
+<progdescription>
+text01 = Syncs an external PDP's policy into extendeded Access Control entries
+text02 = in the resource's file system. The PDP for this proof-of-concept system 
+text03 = is an RBAC implementation (openRBAC), which is part of TextGrid's 
+text04 = TG-auth* infrastructure.
+</progdescription>
+ 
+<bugs>
+text = Please report bugs to martin.haase@daasi.de
+</bugs>
+
+<additions example>
+text1 = "For getting this manpage: "
+text2 = "   syncFromRBAC.pl  -h"
+</additions>
+
+<additions requirements>
+text1 = "Following modules are required: "
+text2 = Data::Dump 
+text3 = Net::LDAP
+text4 = Set::Scalar
+text5 = DAASIlib::CONF
+text5 = DAASIlib::DATA
+</additions>
+
+<options loglevel>
+        key = "l"
+        must = 0
+        description = "Loglevel for controlling logmessages. Currently unused, use -d."
+        arg = 1
+        argtype = "skalar"
+	values = "debug, info" 
+        default = "info"
+</options>
+
+<options logfile>
+        key = "f"
+        must = 0
+        description = "Name of the logfile with absolute or relative path. "
+        arg = 1
+	argtype = "filename_add_subdir_log"
+        default = "./log/syncFromRBAC.log"
+</options>
+
+<options debugmode>
+        key = "d"
+        must = 0
+        description = "sets debug mode to on"
+        arg = 0
+	default = 0
+</options>
+
+<options write_to_system>
+        key = "w"
+        must = 0
+        description = "does not make a dry run. Unless -w is specified, nothing is really written into the system"
+        arg = 0
+	default = 0
+</options>
+
+<options printhelp>
+        key = "h"
+        must = 0
+        description = "prints out the manpage"
+        arg = 0
+</options>
+
+<options helpfeature>
+        key = "H"
+        must = 0
+        description = "prints out description of the feature referenced by \
+        commandline flag or config file token. "
+        arg = 1
+</options>
+
+<options alternative_last_modify_timestamp>
+	key = "t"
+	must = 0
+	description = "Instead of reading from a file (see option -m), use this option to specify a timestamp directly on the command line in the same format."
+	arg = 1
+</options>
+
+<options last_modifyTimestamp_path>
+	key = "m"
+	must = 0
+	description = "File to read timestamp from when to start synchronizing. Format: YYYYMMDDhhmmssZ on first line of file. Needs to be Z time, aka GMT."
+	arg = 1
+	argtype = "filename_exist_subdir_etc"
+	default = "./last_LDAP_sync_timestamp.txt"
+</options>
+
+<options configfile>
+        key = "c"
+        must = 0
+        description = Name of the user config file with absolute or relative path.
+        arg = 1
+        argtype = "filename_exist_subdir_etc"
+        default = "./etc/syncFromRBAC.conf"
+</options>
diff --git a/info.textgrid.middleware.tgauth.pdp2acl/last_LDAP_sync_timestamp.txt b/info.textgrid.middleware.tgauth.pdp2acl/last_LDAP_sync_timestamp.txt
new file mode 100644
index 0000000..cd25fb3
--- /dev/null
+++ b/info.textgrid.middleware.tgauth.pdp2acl/last_LDAP_sync_timestamp.txt
@@ -0,0 +1 @@
+20101231235959Z
diff --git a/info.textgrid.middleware.tgauth.pdp2acl/syncFromRBAC b/info.textgrid.middleware.tgauth.pdp2acl/syncFromRBAC
new file mode 100755
index 0000000..68b4866
--- /dev/null
+++ b/info.textgrid.middleware.tgauth.pdp2acl/syncFromRBAC
@@ -0,0 +1,459 @@
+#!/usr/bin/perl -w
+############################################
+# Syncs an external PDP's policy (here: the contents of the LDAP Database 
+# of a TextGrid TG-auth* openRBAC instance) into group and extendeded Access 
+# Control entries in the Grid resource's file system.
+#
+# Author: Martin Haase / DAASI International GmbH / Gap-SLC
+#
+### Detailed function:
+# * Every role in a TextGrid project will be mapped to a system group entry.
+# * Every performer of this role will be added as a member to this group.
+# * A TextGrid project is mapped to a directory under <project creator's home>/.textgrid/
+# * A TextGrid object is mapped to a file in a project directory
+# * Extended POSIX ACLs will be used to ensure each resource can be accessed by the right project members.
+#
+### Some remarks:
+
+### A user's account must exist in the system's grid-mapfile. You can
+#use the companion gap-SLC script, Pseudo_dgridmap.perl, to accomplish
+#this.
+
+### Currently only a subset of the accounts in the gri-mapfile is
+#honored, i.e. only those which are issued by the Short-Lived
+#Certificate Service (SLCS). Why? We need a mapping from the PDP. A
+#user's identity in TG-auth* is by reference to his
+#eduPersonPrincipalName, and the SLCS DNs contain exactly the
+#ePPN. This way the mapping can be established.  
+
+### Permissions for roles are still hard-wired, as they are in
+#TG-auth* as well
+
+### we contact the LDAP database of TG-auth*/openRBAC
+#directly. Re-implementors might wish to create dedicated accessor
+#functions in the PDP they wish to contact.
+
+### see syncFromRBAC -h for the man page
+#
+#
+# History:
+# version 0.1 2010-07-13     first proof-of-concept
+# version 0.2 2010-12-06     separated configuration and code, documentation
+# version 0.2b 2010-12-23    error handling, configuration with DAASIlib, first check-in to SVN
+# version 0.2c 2010-12-27    further documentation
+
+### imports ===========================================
+use Data::Dump qw(dump);
+use Net::LDAP qw(LDAP_SUCCESS LDAP_TIMELIMIT_EXCEEDED LDAP_SIZELIMIT_EXCEEDED LDAP_NO_SUCH_OBJECT LDAP_ALREADY_EXISTS );
+use Set::Scalar;
+
+use DAASIlib::CONF qw (is_debug);
+use DAASIlib::Data;
+# these two need: IO::Prompt, Log::Log4perl, DBI, DAASIlib::Gettext, Config::General
+
+
+### configuration management ===========================
+my $data = new DAASIlib::Data;
+my ($progname, $progpath, $etcdir, $sysconfig) = $data->getProgramFiles($0);
+
+my $conf = new DAASIlib::CONF;
+$conf->loadConfig($sysconfig, $progpath, $etcdir);
+
+
+
+# GLOBAL CONSTANTS ======================================
+$DEBUG = 0;
+$DRY_RUN = 1;
+if (defined $conf->{data}->{debugmode} && $conf->{data}->{debugmode}) { $DEBUG = 1 }
+if (defined $conf->{data}->{write_to_system} && $conf->{data}->{write_to_system}) { $DRY_RUN = 0 }
+
+$gridmapfilepath = $conf->{data}->{gridmapfilepath};
+$groupfilepath = $conf->{data}->{groupfilepath};
+$logfilepath =  $conf->{data}->{logfile};
+
+$last_modifyTimestamp_path = $conf->{data}->{last_modifyTimestamp_path};
+$alternative_last_modify_timestamp = $conf->{data}->{alternative_last_modify_timestamp};
+
+# these roles are currently hard-wired in TG-auth*
+@standardroles = ( 
+	   {rolename => "Projektleiter", projectperms => "", resourceperms => ""},
+	   {rolename => "Administrator", projectperms => "wx", resourceperms => ""},
+	   {rolename => "Bearbeiter", projectperms => "rwx", resourceperms => "rw"},
+	   {rolename => "Beobachter", projectperms => "rx", resourceperms => "r"},
+	   );
+
+my %ldap_config;
+foreach $k (keys %{$conf->{data}}) {
+    if ($k =~ /^ldap_conf_(\S+)/) {
+        $ldap_config{$1} = $conf->{data}{$k};
+    }
+}
+$ldap_config{attribs} =  ['*', 'modifyTimestamp'];
+
+if (defined $alternative_last_modify_timestamp ) {
+    $last_modifyTimestamp = $alternative_last_modify_timestamp;
+} else {
+    open LMTS, $last_modifyTimestamp_path;
+    $last_modifyTimestamp = <LMTS>;
+    chomp $last_modifyTimestamp;
+    close LMTS;
+}
+
+open LOG, ">>$logfilepath"; # used by logg();
+
+if ($last_modifyTimestamp !~/^\d{4}[01]\d[0123]\d[012]\d[0-5]\d[0-5]\dZ$/) {
+    logg ("invalid timestamp ($last_modifyTimestamp) specified, should be YYYYMMDDhhmmssZ.");
+    exit 0;
+}
+logg ("Start synchronizing LDAP from timestamp ($last_modifyTimestamp)");
+
+
+############## MAIN ===========================================
+
+# user must exist in grid-mapfile, and we only take those DNs into account that are issued by DFN's SLCS
+$userhash = parse_gridmap ( $gridmapfilepath, $conf->{data}->{slcs_dn_prefix});
+$grouphash = parse_groupfile ($groupfilepath);
+
+debugg( dump $userhash);
+debugg ( "Log file is $logfilepath");
+
+
+# query BEFORE ldapsearch, to ldap write operations in this second are accounted for
+# doesnt matter if one entry gets processed twice
+$now_timestamp = construct_timestamp (gmtime()); 
+
+$result = getNewEntriesFromLDAP ( $last_modifyTimestamp, \%ldap_config);
+
+foreach $e ($result->entries) {
+
+    if (is_role($e)) {
+	logg ("found role, ". $e->dn());
+	handle_role($e);
+    } elsif (is_resource($e)) {
+	logg ("found resource, ". $e->get_value("TGResourceURI"));
+	handle_resource($e);
+    } else {
+	# do nothing
+    }
+}
+
+
+if (not $DRY_RUN) {
+    logg ("write system timestamp of last ldapsearch ($now_timestamp) to file for next run");
+    open LMTS, ">$last_modifyTimestamp_path";
+    print LMTS $now_timestamp;
+    close LMTS;
+} else {
+    logg ("This has been a DRY RUN, did NOT alter anything on the system nor write timestamp of last ldapsearch ($now_timestamp) to file. After verifying from the logs that everything is correct, you can force this with -w.");
+}
+
+close LOG;
+exit 0;
+
+# FUNCTIONS ======================================
+
+# this function handles ordinary TextGridObjects. As CRUD has written
+# them already, including the project directory they live in, no mkdir
+# has to be issued.
+sub handle_resource {
+    my $e = shift;
+    my ($tgpr, $dir, $uuid) = parse_uuid ($e->get_value("TGResourceUUID"));
+
+
+    logg ("Now setting ACLs of $dir and ./$uuid such that the right ones can access them.");
+
+    if (not exists $grouphash->{$tgpr."_Projektleiter"}) {
+	logg ( "WARNING: It seems that project $tgpr has not been created although you try to add resources! I will create it for you now...");
+	create_project ($tgpr);
+    }
+    
+    sssys ("chgrp ${tgpr}_Projektleiter $dir $dir/$uuid $dir/${uuid}.meta");
+    sssys ("setfacl -m m::rwx $dir; setfacl -m m::rw $dir/$uuid $dir/${uuid}.meta");
+    sssys ("chmod 700 $dir; chmod 600 $dir/$uuid $dir/${uuid}.meta");	
+
+    # TODO: query actual permissions instead of using the standard roles. However, this has no use as long as there is no Lab frontend for it...
+    foreach $r (@standardroles) {
+
+	if ($r->{projectperms}) {
+	    sssys ( "setfacl -m g:${tgpr}_".$r->{rolename}.":".$r->{projectperms} ." $dir");
+	}
+	if ($r->{resourceperms}) {
+	    sssys ( "setfacl -m g:${tgpr}_".$r->{rolename}.":".$r->{resourceperms} ." $dir/$uuid $dir/${uuid}.meta");
+	}
+    }
+}
+
+sub parse_uuid {
+    my $u = shift;
+    # format:
+    # gsiftp://ingrid.sub.uni-goettingen.de//home/ttest/.textgrid/TGPR29/a084e9c0-e7d4-3802-b16d-37a223414c82
+    if ($u =~ /gsiftp:\/\/ingrid.sub.uni-goettingen.de\/(\/home\/[^\/]+\/.textgrid)\/([^\/]+)\/(\S+)/) {
+	return ($2, "$1/$2", $3); # i.e. TGPR29, /home/ttest/.textgrid/TGPR29, a084e9c0-e7d4-3802-b16d-37a223414c82 
+    } else {
+	return (0,0,0)
+    }
+}
+
+# this functions handles any change in a project role entry, either
+# the project itself (it is a role as well in TG-auth*) or sub-roles.
+# takes existing group entries into account and only makes a diff
+
+sub handle_role {
+    my $e = shift;
+    my ($tgpr, $role) = parse_role_dn ($e->dn());
+    return if not $tgpr;
+    if (not $role) {
+	create_project($tgpr);
+	return;
+    }
+    my @eppnperformers = $e->get_value("rbacPerformer");
+    my @uidperformers = ();
+    foreach $eppn (@eppnperformers) {
+	if (exists $userhash->{$eppn} ) {
+	    push @uidperformers, $userhash->{$eppn};	    
+	} else {
+	    logg ("Warning: User $eppn not yet in Gridmap File");
+	}
+    }
+    my $newperformers = new Set::Scalar(@uidperformers);
+
+    if (not exists $grouphash->{$tgpr."_".$role}) {
+	logg ( "WARNING: It seems that project $tgpr has not been created although you try to add members to its $role role! I will create it for you now...");
+	create_project ($tgpr);
+    }
+    my $oldperformers = $grouphash->{$tgpr."_".$role};
+    debugg ("Old members in $tgpr: ". join ",", $oldperformers->members);
+      
+    foreach $out ($oldperformers->difference($newperformers)->members) {
+	logg ("Throwing out $out of $role in $tgpr");
+	sssys ("/usr/sbin/groupmod -R $out ${tgpr}_".$role);
+    }
+    foreach $in ($newperformers->difference($oldperformers)->members) {
+	logg ("Adding $in to $role in $tgpr"); 
+	sssys ("/usr/sbin/groupmod -A $in ${tgpr}_".$role);
+    }
+    $grouphash->{$tgpr."_".$role} = $newperformers;
+}
+
+
+sub create_project {
+    $p = shift;    
+    logg ("Creating role groups in $p IF they didnt exist before...");
+    foreach $r (@standardroles) {
+	if (not exists  $grouphash->{$p."_".$r->{rolename}}) {
+	    sssys("/usr/sbin/groupadd ${p}_".$r->{rolename});
+	    $grouphash->{$p."_".$r->{rolename}} = new Set::Scalar(());
+	}
+    }
+}
+
+sub parse_role_dn {
+    my $dn = shift;
+    # format:
+    # rbacName=Bearbeiter,rbacName=TGPR29,rbacName=Projekt-Teilnehmer,ou=roles,dc=rbac,dc=textgrid,dc=de
+    if ($dn =~ m/rbacName=([^,]+),rbacName=([^,]+),rbacName=Projekt-Teilnehmer,ou=roles,dc=rbac,dc=textgrid,dc=de/) {
+	return ($2, $1);
+    } elsif ($dn =~ m/rbacName=([^,]+),rbacName=Projekt-Teilnehmer,ou=roles,dc=rbac,dc=textgrid,dc=de/) {
+	return ($1,0);
+    } else {
+	return (0,0);
+    }
+}
+
+sub is_role {
+    my $e = shift;
+    my @oc = $e->get_value("objectClass");
+    return in_array("rbacRole",@oc);
+}
+
+sub is_resource {
+    my $e = shift;
+    my @oc = $e->get_value("objectClass");
+    return in_array("TextGridResource",@oc)
+}
+
+sub in_array {
+    my $x = shift;
+    foreach $e (@_) {
+	return 1 if ($x eq $e);
+    }
+    return 0;
+}
+
+sub logg {
+    if ($DRY_RUN) {
+	print "\n".scalar localtime(). " LOG: ";
+	print  shift;
+	print "\n";
+    } else {
+	print LOG "\n".scalar localtime(). " LOG: ";
+	print LOG shift;
+	print LOG "\n";
+    }
+}
+
+sub debugg {
+    return unless $DEBUG;
+    print "\n".scalar localtime(). " DEBUG: ";
+    print shift;
+    print "\n";
+}
+
+sub errorExit {
+    my $msg = shift;
+    logg ($msg);
+    exit 0;
+}
+
+sub sssys {
+    my $cmd = shift;
+    if ($DRY_RUN) {
+	print "\nSYSTEM CALL: >>>";
+	print $cmd;
+	print "<<<\n";
+    } else {
+	logg ($cmd);
+	system ($cmd);
+    }
+}
+
+sub parse_gridmap {
+    my ($path, $only) = @_;
+
+    my %h = ();
+    open F, $path;
+    while (<F>) {
+	# Format: 
+	# "/C=DE/O=DFN-Verein/OU=DFN-PKI/OU=SLCS/OU=DAASI International GmbH/CN=Tanja Test - tanja.test@idp01.nds.daasi.de" ttest
+	if (m/$only/) {
+	    m/ - (\S+)\" (\S+)/;
+	    $h{$1} = $2; # eppn => uid
+	}
+    }
+    close F;
+    return \%h;
+}
+
+sub parse_groupfile {
+    my ($path) = shift;
+
+    my %h = ();
+    open F, $path;
+    while (<F>) {
+	# TGPR29_Bearbeiter:!:1008:aausprobier,bbenutzer
+	if (/(TGPR[^:]+):[^:]+:\d+:(.*)/) {
+	    debugg ("found group $1, with members $2");
+	    $h{$1} = new Set::Scalar(split ",", $2);
+	}
+    }
+    close F;
+    return \%h;
+}
+
+sub getNewEntriesFromLDAP {
+    my ($since, $rh_ldap_config) = @_;
+
+    my $ldap = ldapConnect($rh_ldap_config);
+
+    my $filter = "(modifyTimestamp>=$since)";
+
+    my $mesg = ldapSearch($ldap, $filter, $rh_ldap_config);
+    # ingrid:~ # ldapsearch -x -H ldap://:5389 -b dc=rbac,dc=textgrid,dc=de -D cn=manager,dc=rbac,dc=textgrid,dc=de -w XhNtmqigeXz61pI9jfwmLyqUOTeJ "*" "+" | less
+
+    foreach $entry ($mesg->entries) { 
+#	$entry->dump; 
+	debugg (dump $entry);
+    }
+
+    logg "Found ". $mesg->count() . " new entries since last call.";
+
+    return $mesg;    
+}
+
+sub ldapConnect {
+    my ($rh_ldapdef) = @_;
+    
+    my $uri = 'ldap://'.$rh_ldapdef->{host}.':'.$rh_ldapdef->{port};
+
+    my $ldap = Net::LDAP->new( $uri); 
+    
+    
+    if ( !$ldap ) {
+     logg ("ERROR while creating connection to ldap server ".
+	   "$$rh_ldapdef{host}:$$rh_ldapdef{port}");
+     errorExit ( "   ERROR while creating connection to ldap server ".
+		 "$$rh_ldapdef{host}:$$rh_ldapdef{port} \n");
+     return(undef);
+  }
+    
+    if ( $rh_ldapdef->{is_tls} ) {
+	debugg("starting TLS ");
+	debugg("verify: $rh_ldapdef->{tls_verify}, cafile: $rh_ldapdef->{tls_cafile} cypher: $rh_ldapdef->{tls_cypher}");
+        my $tlsmesg = $ldap->start_tls ( verify => $rh_ldapdef->{tls_verify},
+                                         cafile => $rh_ldapdef->{tls_cafile},
+                                         ciphers => $rh_ldapdef->{tls_cypher});
+        
+        if ( $tlsmesg->code != Net::LDAP::LDAP_SUCCESS ) {
+	    logg ( "ERROR in ldap->start_tls: " . $tlsmesg->error );
+	    errorExit( "ERROR in ldap->start_tls: " . $tlsmesg->error) ; 
+        }
+        else {
+	    debugg ( "Start_TLS operation succeeded");
+	    debugg ( "tls cipher: " . $ldap->cipher . "\n" );
+	    debugg ( "tls certificate: " . $ldap->certificate . "\n" );
+        }
+    }
+    
+    my $mesg = $ldap->bind( $rh_ldapdef->{binddn}, 
+                            password => $rh_ldapdef->{bindpw} );
+    
+    unless ( $mesg->code == Net::LDAP::LDAP_SUCCESS ) {
+	logg ("Returning undef instead of ldap connection");
+        $ldap = undef;
+	errorExit( "Error in LDAP-Bind: ".$mesg->error);
+    }
+    
+    return $ldap;
+} 
+
+
+
+sub ldapSearch {
+    my ( $ldap, $filter, $rh_ldapdef ) = @_;
+
+    if ( ! $$rh_ldapdef{filter}) {
+        $$rh_ldapdef{filter}="(objectclass=*)";
+    }
+
+    my $mesg = $ldap->search(
+			     base      => $$rh_ldapdef{basedn},
+			     scope     => $$rh_ldapdef{scope},
+                             filter    => $filter,
+                             attrs     => $$rh_ldapdef{attribs},
+#                            deref     => $$rh_ldapdef{deref},
+#                            sizelimit => $$rh_ldapdef{sizelimit},
+#                            timelimit => $$rh_ldapdef{timelimit},
+#                            typesonly => $$rh_ldapdef{is_typesonly},
+#                            callback  => $$rh_ldapdef{callback},
+			     );
+ 
+    if ( $mesg->code == Net::LDAP::LDAP_SUCCESS ) {
+	debugg("search performed without error" );
+    } else {
+	errorExit ( "Error in search: ". $mesg->error);
+    }
+
+    return ($mesg);
+}
+
+### takes time as from localtime()
+### returns ldap timstamp as in attribute modifyTimestamp
+sub construct_timestamp {
+    my ($nsec,$nmin,$nhours,$nmday,$nmon,$nyear) = @_;
+    my $ntime = sprintf("%04d%02d%02d%02d%02d%02dZ", 
+                      ($nyear+1900),($nmon+1),
+			$nmday,$nhours,$nmin,$nsec);
+    return $ntime;
+}
+
+
-- 
GitLab