#!/usr/bin/env perl
#
# BEGIN COPYRIGHT BLOCK
# Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
# Copyright (C) 2016 Red Hat, Inc.
# All rights reserved.
#
# License: GPL (version 3 or any later version).
# See LICENSE for details. 
# END COPYRIGHT BLOCK
#

use lib qw(/usr/lib/dirsrv/perl);
use DSUtil;
use Time::Local;

DSUtil::libpath_add("");
DSUtil::libpath_add("/usr/lib");
$ENV{'PATH'} = ":/usr/bin";
$ENV{'SHLIB_PATH'} = "$ENV{'LD_LIBRARY_PATH'}";

my $single = 0;
my $role = 0;
my $verbose = 0;

###############################
# SUB-ROUTINES
###############################

sub usage
{
    print (STDERR "ns-accountstatus.pl [-Z serverID] [-D rootdn] { -w password | -w - | -j filename }\n");
    print (STDERR "                    [-p port] [-h host] [-P protocol] {-I DN | -b basedn -f filter [-s scope]}\n");
    print (STDERR "                    [-i] [-g seconds]\n\n");
    print (STDERR "May be used to get the status a user or a domain of users\n\n");
    print (STDERR "Arguments:\n");
    print (STDERR "        -?                   - Display usage\n");
    print (STDERR "        -D rootdn            - Provide a Directory Manager DN\n");
    print (STDERR "        -w password          - Provide a password for the Directory Manager DN\n");
    print (STDERR "        -w -                 - Prompt for the Directory Manager's password\n");
    print (STDERR "        -Z serverID          - Server instance identifier\n");
    print (STDERR "        -j filename          - Read the Directory Manager's password from file\n");
    print (STDERR "        -p port              - Provide a port\n");
    print (STDERR "        -h host              - Provide a host name\n");
    print (STDERR "        -P protocol          - STARTTLS, LDAPS, LDAPI, LDAP (default: uses most secure protocol available)\n");
    print (STDERR "        -I DN                - Single entry DN or role DN to get status\n");
    print (STDERR "        -b basedn            - Search base for finding entries**\n");
    print (STDERR "        -f filter            - Search filter for finding entries**\n");
    print (STDERR "        -s scope             - Search scope (base, one, sub - default is sub)**\n");
    print (STDERR "        -i                   - Only display inactivated entries\n");
    print (STDERR "        -g seconds           - Only display entries that will become inactive within the timeframe\n");
    print (STDERR "        -V                   - Display verbose information\n");
}

sub debug
{
#    print " ==> @_";
}

sub out
{
    print "@_";
}

# --------------------------
# Check if the entry is part of a locked role:
# i.e.: for each role member (nsroledn) of nsdisabledrole, check if
#     * it is the same as the entry
#     * the entry is member of role (==has nsroledn attributes), compare each of
#        them with the nsroledn of nsdisabledrole
#    * if nsroledn of nsdisabledrole are complex, go through each of them
# argv[0] is the local file handler
# argv[1] is the entry (may be a single entry DN or a role DN)
# argv[2] is the base for the search
# --------------------------

$throughRole="";

sub indirectLock
{
    # For recursivity, file handler must be local
    my $L_filehandle=$_[0];
    $L_filehandle++;

    my $L_entry=$_[1];
    # Remove useless space
    my @L_intern=split /([,])/,$L_entry;
    my $L_result="";
    foreach $L_part (@L_intern){
        $L_part=~s/^ +//;
        $L_part=~ tr/A-Z/a-z/;
        $L_result="$L_result$L_part";
    }
    $L_entry=$L_result;

    my $L_base=$_[2];
    my $L_search;
    my $L_currentrole;
    my $L_retCode;
    my $L_local;

    $info{base} = $L_base;
    $info{filter} = "(|(objectclass=*)(objectclass=ldapsubentry))";
    $info{scope} = "base";
    $info{attrs} = "nsroledn";
    $info{redirect} = ">> /dev/null 2>&1";
    DSUtil::ldapsrch_ext(%info);
    $info{redirect} = "";
    $retCode=$?;
    if ( $retCode != 0 ){
        $retCode=$?>>8;
        return 1;
    }

    # Check if the role is a nested role
    $info{filter} = "(|(objectclass=nsNestedRoleDefinition)(objectclass=ldapsubentry))";
    $info{attrs} = "";
    @L_Nested=DSUtil::ldapsrch(%info);
    # L_isNested == 1 means that we are going through a nested role, so for each member of that
    # nested role, check that the member is below the scope of the nested
    $L_isNested=@L_Nested;

    # Not Direct Lock, Go through roles if any
    $info{attrs} = "nsroledn";
    $info{filter} = "(|(objectclass=*)(objectclass=ldapsubentry))";
    $L_search=DSUtil::ldapsrch(%info);

    debug("\t-->indirectLock: check if $L_entry is part of a locked role from base $L_base\n\n");

    unless (open ($L_filehandle, "$L_search |")){
        out("Can't open file $L_filehandle\n");
        exit;
    }
    while (<$L_filehandle>) {

        s/\n //g;
        if (/^nsroledn: (.*)\n/) {
            $L_currentrole = $1;

            # Remove useless space
            my @L_intern=split /([,])/,$L_currentrole;
            my $L_result="";
            foreach $L_part (@L_intern){
                $L_part=~s/^ +//;
                $L_part=~ tr/A-Z/a-z/;
                $L_result="$L_result$L_part";
            }
            $L_currentrole=$L_result;

            debug("\t-- indirectLock loop: current nsroledn $L_currentrole of base $L_base\n");
            if ( $L_isNested == 1 ){
                if ( checkScope($L_currentrole, $L_base) == 0 ){
                    # Scope problem probably a bad conf, skip the currentrole
                    next;    
                }
            }

            if ( $L_currentrole eq $L_entry ){
                # the entry is a role that is directly locked
                # i.e, nsroledn of nsdisabledrole contains the entry
                $throughRole=$L_base;
                $throughRole=~ tr/A-Z/a-z/;

                # skipDisabled means that we've just found that the entry (which is a role)
                # is locked directly (==its DN is part of nsroledn attributes)
                # we just want to know now, if it is locked through another role
                # at least, one
                if ( $skipDisabled == 1 ){
                    # direct inactivation
                    $directLocked=1;
                    # just go through that test once
                    $skipDisabled=0;
                    next;
                }
                debug("\t-- 1 indirectLock: $L_currentrole locked throughRole == $throughRole\n");
                return 0;
            }

            $L_retCode=memberOf($L_currentrole, $L_entry);
            if ( $L_retCode == 0 && $single == 1 ){
                $throughRole=$L_currentrole;
                $throughRole=~ tr/A-Z/a-z/;
                if ( $skipManaged == 1 ){
                    if ( $L_currentrole eq $nsManagedDisabledRole){
                        # Try next nsroledn
                        $directLocked=1;
                        $skipManaged=0;
                        next;
                    }
                } 
                debug("\t-- 2 indirectLock: $L_currentrole locked throughRole == $throughRole\n");
                return 0;
            }

            # Only for the first iteration
            # the first iteration is with nsdisabledrole as base, other
            # loops are deeper
            $L_local=$skipDisabled;
            $skipDisabled=0;

            # the current nsroledn may be a complex role, just go through
            # its won nsroledn
            $L_retCode=indirectLock($L_filehandle,$L_entry, $L_currentrole);

            # Because of recursivity, to keep the initial value for the first level
            $skipDisabled=$L_local;

            if ( $L_retCode == 0 ){
                $throughRole=$L_currentrole;
                $throughRole=~ tr/A-Z/a-z/;
                debug("\t-- 3 indirectLock: $L_entry locked throughRole == $throughRole\n");
                return 0;
            }
        }
    }

    close($L_filehandle);

    debug("\t<--indirectLock: no more nsroledn to process\n");
    return 1;
}

# --------------------------
# Check if nsroledn is part of the entry attributes
# argv[0] is a role DN (nsroledn attribute)
# argv[1] is the entry
# --------------------------
sub memberOf
{
    my $L_nsroledn=$_[0];
    $L_nsroledn =~ tr/A-Z/a-z/;
    my $L_entry=$_[1];
    my $L_search;
    my $L_currentrole;

    $info{base} = $L_entry;
    $info{filter} = "(|(objectclass=*)(objectclass=ldapsubentry))";
    $info{scope} = "base";
    $info{attrs} = "nsrole";
    $L_search = DSUtil::ldapsrch(%info);

    debug("\t\t-->memberOf: $L_search: check if $L_entry has $L_nsroledn as nsroledn attribute\n");

    open (LDAP2, "$L_search |");
    while (<LDAP2>) {
        s/\n //g;
        if (/^nsrole: (.*)\n/) {
            $L_currentrole = $1;
            $L_currentrole=~ tr/A-Z/a-z/;
            if ( $L_currentrole eq $L_nsroledn ){
                # the parm is part of the $L_entry nsroledn
                debug("\t\t<--memberOf: $L_entry locked through $L_nsroledn\n");
                return 0;
            }
        }
    }
    close(LDAP2);

    # the parm is not part of the $L_entry nsroledn
    debug("\t\t<--memberOf: $L_entry not locked through $L_nsroledn\n");
    return 1;
}


# --------------------------
# Remove the rdn of a DN
# argv[0] is a DN
# --------------------------
sub removeRdn
{
    $L_entry=$_[0];

    @L_entryToTest=split /([,])/,$L_entry;
    debug("removeRdn: entry to split: $L_entry**@L_entryToTest\n");

    $newDN="";
    $removeRDN=1;
    foreach $part (@L_entryToTest){
        $part=~ s/^ +//;
        $part=~ tr/A-Z/a-z/;
        if ( $removeRDN <= 2 ){
            $removeRDN=$removeRDN+1;
        } else {
            $newDN="$newDN$part";
        }
    }

    debug("removeRdn: new DN **$newDN**\n");
}

# --------------------------
# Check if L_current is below the scope of 
# L_nestedRole
# argv[0] is a role
# argv[1] is the nested role
# --------------------------
sub checkScope
{
    $L_current=$_[0];
    $L_nestedRole=$_[1];

    debug("checkScope: check if $L_current is below $L_nestedRole\n");

    removeRdn($L_nestedRole);
    $L_nestedRoleSuffix=$newDN;
    debug("checkScope: nested role based:  $L_nestedRoleSuffix\n");

    $cont=1;
    while ( ($cont == 1) && ($L_current ne "") ){
        removeRdn($L_current);
        $currentDn=$newDN;
        debug("checkScope: current DN to check: $currentDn\n");
 
        if ( $currentDn eq $L_nestedRoleSuffix ){
            debug("checkScope: DN match!!!\n");
            $cont = 0;
        } else {
            $L_current=$currentDn;
        }
    }
 
    if ( $cont == 1 ){
        debug("checkScope: $_[0] and $_[1] are not compatible\n");
        return 0;
    } else {
        debug("checkScope: $_[0] and $_[1] are compatible\n");
        return 1;
    }
}

#
# Check if an account is locked by inactivity
# Take the lastlogintime (which is in Generalized Time), and convert it to its
# EPOCH time.  Then compare this to the current time and the inactivity limit
#
sub checkForInactivity
{
    my $gentime_lastlogin = shift;
    my $limit = shift;

    if ($limit == 0){
        return 0;
    }
    my ($year, $mon, $day, $hour, $min, $sec) =
        ($gentime_lastlogin =~ /(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/);
    my $lastlogin = timegm($sec, $min, $hour, $day, ($mon-1), $year); # EPOCH time
    my $now = time(); # EPOCH time

    if (($now - $lastlogin) > $limit){
        # Account has be inactive for too long
        return 1;
    }
    # Account is fine and active
    return 0;
}

sub checkForUpcomingInactivity
{
    my $gentime_lastlogin = shift;
    my $limit = shift;
    my $timeframe = shift;

    if ($limit == 0){
        return 0;
    }
    my ($year, $mon, $day, $hour, $min, $sec) =
        ($gentime_lastlogin =~ /(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/);
    my $lastlogin = timegm($sec, $min, $hour, $day, ($mon-1), $year); # EPOCH time
    my $now = time(); # EPOCH time
    my $time_to_inactive = ($limit - ($now - $lastlogin));
    if ($time_to_inactive <= $timeframe){
        return 1;
    } else {
        return 0;
    }
}

#
# Return the time in seconds until the account reaches the limit
#
sub getTimeToInactivity
{
    my $gentime_lastlogin = shift;
    my $limit = shift;

    my ($year, $mon, $day, $hour, $min, $sec) =
        ($gentime_lastlogin =~ /(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/);
    my $lastlogin = timegm($sec, $min, $hour, $day, ($mon-1), $year); # EPOCH time
    my $now = time(); # EPOCH time

    return ($limit - ($now - $lastlogin));
}

#
# Return the time in seconds until the account reaches the limit
#
sub getTimeSinceInactive
{
    my $gentime_lastlogin = shift;
    my $limit = shift;

    my ($year, $mon, $day, $hour, $min, $sec) =
        ($gentime_lastlogin =~ /(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/);
    my $lastlogin = timegm($sec, $min, $hour, $day, ($mon-1), $year); # EPOCH time
    my $now = time(); # EPOCH time

    return ($now - ($lastlogin + $limit));
    #return (($now - $lastlogin) - limit);
}

#
# Return various components of the acct policy
#
sub getAcctPolicy
{
    my %srch = %{$_[0]};
    my $entry = $_[1];

    my $enabled = 0;
    my $stateattr = 0;
    my $altstateattr = 0;
    my $cosspecattr = 0;
    my $limitattr = 0;
    my $limit = 0;
    my $configentry = 0;
    my $templateDN = "";

    $srch{base} = "cn=Account Policy Plugin,cn=plugins,cn=config";
    $srch{filter} = "(&(objectclass=top)(nsslapd-pluginarg0=*))";
    $srch{scope} = "base";
    $srch{attrs} = "nsslapd-pluginEnabled nsslapd-pluginarg0";

    #
    # Get the main plugin entry
    #
    $searchAccPolicy = DSUtil::ldapsrch(%srch);
    open (LDAP1, "$searchAccPolicy |");
    while (<LDAP1>) {
        s/\n //g;
        if( /^nsslapd-pluginenabled: on/i) {
            $enabled = 1;
        } elsif (/^nsslapd-pluginarg0: (.*)/i) {
            $configentry = $1;
        }
    }
    close(LDAP1);

    if ($enabled == 0){
        # Not using acct policy plugin, no reason to continue.
        return (0, 0, 0, 0);
    }

    #
    # Get the plugin config entry
    #
    $srch{base} = $configentry;
    $srch{filter} = "(objectclass=top)";
    $srch{scope} = "base";
    $srch{attrs} = "stateattrname altstateattrname specattrname limitattrname";
    $searchAccPolicy = DSUtil::ldapsrch(%srch);
    open (LDAP1, "$searchAccPolicy |");
    while (<LDAP1>) {
        s/\n //g;
        if( /^stateattrname: (.*)/i) {
            $stateattr = $1;
        } elsif (/^altstateattrname: (.*)/i) {
            $altstateattr = $1;
        } elsif (/^specattrname: (.*)/i) {
            $cosspecattr = $1;
        } elsif (/^limitattrname: (.*)/i) {
            $limitattr = $1;
        }
    }
    close(LDAP1);

    #
    # Now, get the DN for the account policy subEntry from the entry (if available)
    #
    $srch{base} = $entry;
    $srch{filter} = "(objectclass=*)";
    $srch{scope} = "base";
    $srch{attrs} = "$cosspecattr";
    $searchAccPolicy= DSUtil::ldapsrch(%srch);
    open (LDAP1, "$searchAccPolicy |");
    while (<LDAP1>) {
        s/\n //g;
        if (/^$cosspecattr: (.*)/i){
            $templateDN = $1;
            break;
        }
    }
    close(LDAP1);

    #
    # Get the inactivity limit
    #
    $srch{base} = $configentry;
    if ($templateDN){
        # Use subEntry DN
        $srch{base} = $templateDN;
    }
    $srch{filter} = "($limitattr=*)";
    $srch{scope} = "base";
    $srch{attrs} = "$limitattr";
    my @result = DSUtil::ldapsrch_ext(%srch);
    if ($#result > 1){
        if ($result[1] =~ /^$limitattr: *([0-9]+)/i){
            $limit = $1;
        }
    }

    return ($enabled, $stateattr, $altstateattr, $limit);
}

#
# Return a friendly time string for the client
#
sub get_time_from_epoch
{
    my $sec = shift;
    my $result = "";
    my $add_space = 0;

    if (int($sec/(24*60*60))){
        $result = int($sec/(24*60*60)) . " days";
        $add_space = 1;
    }
    if (($sec/(60*60))%24){
        if ($add_space){
            $result = $result . ", ";
        }
        $add_space = 1;
        $result = $result . ($sec/(60*60))%24 . " hours";
    }
    if ( ($sec/60)%60){
        if ($add_space){
            $result = $result . ", ";
        }
        $add_space = 1;
        $result = $result . ($sec/60)%60 . " minutes";
    }
    if ($sec%60){
        if ($add_space){
            $result = $result . ", ";
        }
        $result = $result . $sec%60 . " seconds";
    }
    return $result;
}

#
# Given a string in generalized time format, convert it to ascii time
#
sub get_time_from_gentime
{
    my $zstr = shift;
    return "n/a" if (! $zstr);
    my ($year, $mon, $day, $hour, $min, $sec) =
        ($zstr =~ /(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/);
    my $time = timegm($sec, $min, $hour, $day, ($mon-1), $year);
    ($sec, $min, $hour, $day, $mon, $year) = localtime($time);
    $mon++;
    $year += 1900;
    foreach ($sec, $min, $hour, $day, $mon) {
        $_ = "0".$_ if ($_ < 10);
    }

    return "$mon/$day/$year $hour:$min:$sec";
}

#
# Print Verbose output about the entry
#
sub printVerbose
{
    my %dsinfo = %{$_[0]};
    my $suffix = $_[1];
    my $entry = $_[2];
    my $createtime = $_[3];
    my $modifytime = $_[4];
    my $lastlogintime = $_[5];
    my $state = $_[6];
    my $limit = $_[7];
    my $usingAcct = $_[8];

    out("Entry:                   $entry\n");
    out("Entry Creation Date:     $createtime (" . get_time_from_gentime($createtime) . ")\n");
    out("Entry Modification Date: $modifytime (" . get_time_from_gentime($modifytime) . ")\n");
    if ($lastlogintime ne ""){
        out("Last Login Date:         $lastlogintime (" . get_time_from_gentime($lastlogintime) . ")\n");
    }
    if($usingAcct){
        if ($limit){
            out("Inactivity Limit:        $limit seconds (" . get_time_from_epoch($limit) . ")\n");
            if ($lastlogintime ne ""){
                my $remaining_time = getTimeToInactivity($lastlogintime, $limit);
                if($remaining_time < 0){
                    out("Time Until Inactive:     -\n");
                    # We only display elapsed time if the account was locked by inactivity
                    if($state =~ /inactivity limit exceeded/){
                        my $elapsed_time = getTimeSinceInactive($lastlogintime, $limit);
                        out("Time Since Inactivated:  $elapsed_time seconds (" . get_time_from_epoch($elapsed_time) . ")\n");
                    } else {
                        out("Time Since Inactive:     -\n");
                    }
                } else {
                    out("Time Until Inactive:     $remaining_time seconds (" . get_time_from_epoch($remaining_time) . ")\n");
                    out("Time Since Inactive:     -\n");
                }
            }
        }
    }
    out("Entry State:             $state\n\n");
}

#
# Just strip any unneeded spaces from the DN
#
sub normalizeDN
{
    my $entry = shift;
    my $result = "";
    my $part = "";

    @suffix=split /([,])/,$entry;
    $result="";
    foreach $part (@suffix){
        $part =~ s/^\s+|\s+$//g;
        $part=~ tr/A-Z/a-z/;
        $result="$result$part";
    }
    return $result;
}

#
# Get the suffix from the entry
#
sub getSuffix
{
    my $entry = shift;
    my $cont = 0;
    my @suffixN = normalizeDN($entry);
    my @suffix = split /([,])/,$entry;

    while ($cont == 0){
        # Look if suffix is the suffix of the entry
        #    ldapsearch -s one -b "cn=mapping tree,cn=config" "cn=\"uid=jvedder,ou=People,dc=example,dc=com\""
        #
        my $filter = normalizeDN("@suffix");

        debug("\tSuffix from the entry: #@suffixN#\n");
        $info{base} = "cn=mapping tree, cn=config";
        $info{filter} = "cn=$filter";
        $info{scope} = "one";
        $info{attrs} = "cn";
        @mapping = DSUtil::ldapsrch_ext(%info);
        my $retCode = $?;
        if ( $retCode != 0 ){
            $retCode = $?>>8;
            exit $retCode;
        }

        # If we get a result, remove the dn:
        #    dn: cn="o=sun.com",cn=mapping tree,cn=config
        #    cn: "dc=example,dc=com"
        #
        shift @mapping;

        foreach $res (@mapping){
            # Break the string cn: "o=sun.com" into pieces
            @cn = split(/ /,$res);

            # And remove the cn: part
            shift @cn;

            # Now compare the suffix we extract from the mapping tree
            # with the suffix derived from the entry
            debug("\tSuffix from mapping tree: #@cn#\n");
            if ( @cn eq @suffixN ){
                debug("Found matching suffix\n");
                $cont = 1;
            }
        }

        if ( $cont == 0 ){
            # Remove the current rdn to try another suffix
            shift @suffix;

            my $result="";
            foreach $part (@suffix){
                $part =~ s/^ +//;
                $part =~ tr/A-Z/a-z/;
                $result = "$result$part";
            }
            @suffixN = $result;

            debug("\t\tNothing found => go up one level in rdn #@suffix#\n");
            $len = @suffix;
            if ( $len == 0 ){
                debug("Can not find suffix. Problem\n");
                $cont=2;
            }
        }
    } # while cont = 0
    if ( $cont == 2){
        out("Can not find suffix for entry $entry\n");
        exit 100;
    }
    return @suffixN
}

###############################
# MAIN ROUTINE
###############################


my $state="activated";
my $acct_policy_enabled;
my $stateattr;
my $altstateattr;
my $limit;
my $filter = 0;
my $basedn = 0;
my $scope = "sub";
my $keep_processing = 0;
my $only_inactive = 0;
my $inactive_timeframe = 0;
my @entries;

# Process the command line arguments
while( $arg = shift){
    if($arg eq "-?"){
        usage();
        exit 0;
    } elsif($arg eq "-D"){
        $rootdn= shift @ARGV;
    } elsif($arg eq "-w"){
        $rootpw= shift @ARGV;
    } elsif($arg eq "-j"){
        $pwfile= shift @ARGV;
    } elsif($arg eq "-p"){
        $port= shift @ARGV;
    } elsif($arg eq "-h"){
        $host= shift @ARGV;
    } elsif($arg eq "-I"){
        $entry= shift @ARGV;
    } elsif($arg eq "-Z"){
        $servid= shift @ARGV;
    } elsif($arg eq "-b"){
        $basedn= shift @ARGV;
    } elsif($arg eq "-s"){
        $scope= shift @ARGV;
    } elsif($arg eq "-f"){
        $filter= shift @ARGV;
    } elsif($arg eq "-i"){
        $only_inactive = 1;
    } elsif($arg eq "-g"){
        $inactive_timeframe = shift @ARGV;
    } elsif($arg eq "-V"){
        $verbose = 1;
    } elsif ($arg eq "-P") { 
        $protocol = shift @ARGV;
    } else {
        print "ERROR - Unknown option: $ARGV[$i]\n";
        usage();
        exit 1
    }
}

#
# Gather all our config settings
#
($servid, $confdir) = DSUtil::get_server_id($servid, "/etc/dirsrv");
%info = DSUtil::get_info($confdir, $host, $port, $rootdn);
$info{rootdnpw} = DSUtil::get_password_from_file($rootpw, $pwfile);
$info{protocol} = $protocol;
$info{args} = "-c -a";
if($entry eq "" and (!$basedn or !$filter)){
    usage();
    exit 1;
}

#
# Check if we have a filter, and gather the dn's
#
if ($basedn && $filter){
    $info{base} = $basedn;
    $info{filter} = $filter;
    $info{scope} = $scope;
    $info{attrs} = "dn";

    @users=DSUtil::ldapsrch_ext(%info);
    $retCode1=$?;
    if ( $retCode1 != 0 ){
        $retCode1=$?>>8;
        exit $retCode1;
    }
    my $i = 0;
    my $c = 0;
    while($#users > 0 && $users[$i]){
        if($users[$i] =~ /^dn: (.*)/i){
            $entries[$c] = $1;
            $c++;
        }
        $i++;
    }
    if ($c > 1){
        # Mark that we are processing multiple entries
        $keep_processing = 1;
    }
} else {
    # Single entry
    #
    # Check the actual existence of the entry
    # and at the same time, validate the various
    # parm: port, host, rootdn, rootpw
    #
    $info{base} = $entry;
    $info{filter} = "(objectclass=*)";
    $info{scope} = "base";
    $info{attrs} = "dn";
    @exist=DSUtil::ldapsrch_ext(%info);
    $retCode1=$?;
    if ( $retCode1 != 0 ){
        $retCode1=$?>>8;
        exit $retCode1;
    }
    $entries[0] = $entry;
}

for(my $i = 0; $i <= $#entries; $i++){
    #
    # Process each entry
    #
    $entry = $entries[$i];

    #
    # Determine if we are deadling with a entry or a role
    #
    $info{base} = $entry;
    $info{filter} = "(&(objectclass=LDAPsubentry)(objectclass=nsRoleDefinition))";
    @isRole  = DSUtil::ldapsrch_ext(%info);
    $nbLineRole=@isRole;
    $retCode2=$?;
    if ( $retCode2 != 0 ){
        $retCode2=$?>>8;
        exit $retCode2;
    }

    if ( $nbLineRole > 0 ){
        debug("Groups of users\n");
        $role=1;
    } else {
        debug("Single user\n");
        $single=1;
    }

    #
    # Gather the Account Policy Plugin information (if available)
    #
    ($acct_policy_enabled, $stateattr, $altstateattr, $limit) = getAcctPolicy(\%info, $entry);

    #
    # First of all, check the existence of the nsaccountlock attribute in the entry
    #
    $isLocked = 0;
    my $lastlogintime = "";
    my $altlogintime = "";
    my $createtime = "";
    my $modifytime = "";

    if ( $single == 1 ){
        $info{filter} = "(objectclass=*)";
        $info{attrs} = "nsaccountlock lastLoginTime createtimestamp modifytimestamp";
        $info{scope} = "base";
        $searchAccountLock= DSUtil::ldapsrch(%info);
        open (LDAP1, "$searchAccountLock |");
        while (<LDAP1>) {
            s/\n //g;
            if (/^nsaccountlock: (.*)\n/i) {
                $L_currentvalue = $1;
                $L_currentvalue=~ tr/A-Z/a-z/;
                if ( $L_currentvalue eq "true"){
                    $isLocked=1;
                } elsif ( $L_currentvalue eq "false" ){
                    $isLocked=0;
                }
            }
            if (/^$stateattr: (.*)\n/i) {
                $lastlogintime = $1;
            }
            if (/^$altstateattr: (.*)\n/i) {
                $altlogintime = $1;
            }
            if (/^createtimestamp: (.*)\n/i) {
                $createtime = $1;
            }
            if (/^modifyTimeStamp: (.*)\n/i) {
                $modifytime = $1;
            }
        }
        close(LDAP1);

        if($lastlogintime eq ""){
            $lastlogintime = $altlogintime;
        }
    }
    debug("Is the entry already locked? ==> $isLocked\n");

    #
    # Get the suffix of the entry
    #
    @suffixN = getSuffix($entry);

    $skipManaged = $single;
    $skipDisabled = $role;
    $directLocked = 0;
    $nsDisabledRole = "cn=nsDisabledRole,@suffixN";
    $nsDisabledRole =~ tr/A-Z/a-z/;
    $nsManagedDisabledRole = "cn=nsManagedDisabledRole,@suffixN";
    $nsManagedDisabledRole =~ tr/A-Z/a-z/;

    $ret = indirectLock("LDAP00", $entry, $nsDisabledRole);
    if ( $ret == 0 && $inactive_timeframe == 0){
        # indirectly locked
        if ( $throughRole ne $nsDisabledRole && $throughRole ne $nsManagedDisabledRole ){
            if ($verbose){
                printVerbose(\%info, "@suffixN", $entry, $createtime,
                             $modifytime, $lastlogintime,
                             "inactivated (indirectly through role: $throughRole)", $limit,
                             $acct_policy_enabled);
            } else {
                out("$entry - inactivated (indirectly through role: $throughRole).\n");
            }
            if($keep_processing){
                next;
            }
            exit 104;
        }
        debug("$entry locked individually\n");
        if ($verbose){
            printVerbose(\%info, "@suffixN", $entry, $createtime,
                         $modifytime, $lastlogintime, "inactivated (directly locked)", $limit,
                         $acct_policy_enabled);
        } else {
            out("$entry - inactivated (directly locked).\n");
        }
        if($keep_processing){
            next;
        }
        exit 103;
    } elsif ( $directLocked == 0 ){
        if ( $isLocked != 1 ){
            #
            # We are not locked by account lockout, but we could be locked by
            # the Account Policy Plugin (inactivity)
            #
            if($acct_policy_enabled && $lastlogintime ne ""){
                #
                # Now check the Acount Policy Plugin inactivity limits
                #
                if(checkForInactivity($lastlogintime, $limit)){
                    if ($inactive_timeframe > 0){
                        # We are only looking for active entries that are about to expire
                        next;
                    }
                    # Account is inactive by inactivity!
                    if($verbose){
                        printVerbose(\%info, "@suffixN", $entry, $createtime,
                                     $modifytime, $lastlogintime,
                                     "inactivated (inactivity limit exceeded)",
                                     $limit, $acct_policy_enabled);
                    } else {
                        out("$entry - inactivated (inactivity limit exceeded).\n");
                    }
                    if($keep_processing){
                        next;
                    }
                    exit 103;
                } elsif (checkForUpcomingInactivity($lastlogintime, $limit, $inactive_timeframe)){
                    if($verbose){
                        printVerbose(\%info, "@suffixN", $entry, $createtime,
                                     $modifytime, $lastlogintime,
                                     "activated",
                                     $limit, $acct_policy_enabled);
                    } else {
                        out("$entry - activated\n");
                    }
                    if($keep_processing){
                        next;
                    }
                    exit 0;
                }
            }
            if(!$only_inactive and $inactive_timeframe == 0){
                if($verbose){
                    printVerbose(\%info, "@suffixN", $entry, $createtime,
                                 $modifytime, $lastlogintime, $state, $limit,
                                 $acct_policy_enabled);
                } else {
                    out("$entry - $state.\n");
                }
            }
            if($keep_processing){
                next;
            }
            exit 102;
        } else {
            # not locked using our schema, but nsaccountlock is probably present
            if ($inactive_timeframe > 0){
                # We are only looking for active entries that are about to expire,
                # so move on to the next entry
                next;
            }
            if($verbose){
                printVerbose(\%info, "@suffixN", $entry, $createtime,
                             $modifytime, $lastlogintime,
                             "inactivated (probably directly)", $limit,
                             $acct_policy_enabled);
            } else {
                out("$entry - inactivated (probably directly).\n");
            }
            if($keep_processing){
                next;
            }
            exit 103;
        }
    } else {
        if ($inactive_timeframe > 0){
            # We are only looking for active entries that are about to expire
            next;
        }
        if($verbose){
            printVerbose(\%info, "@suffixN", $entry, $createtime,
                         $modifytime, $lastlogintime, "inactivated (directly locked)", $limit,
                         $acct_policy_enabled);
        } else {
            out("$entry - inactivated (directly locked).\n");
        }
        if($keep_processing){
            next;
        }
        exit 103;
    }
}
