#{{PERL-EXEC}}
#
# PROPRIETARY/CONFIDENTIAL. Use of this product is subject to
# license terms. Copyright 2001 Sun Microsystems, Inc.
# Some preexisting portions Copyright 2001 Netscape Communications Corp.
# All rights reserved.
# 

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

sub usage_and_exit
{
	print (STDERR "$cmd [-D rootdn] { -w password | -w - | -j filename } \n");
	print (STDERR "     [-p port] [-h host] -I DN-to-$operation\n\n");
	print (STDERR "May be used to $operation a user or a domain of users\n\n");
	print (STDERR "Arguments:\n");
	print (STDERR "          -?                   - help\n");
	print (STDERR "          -D rootdn            - Provide a Directory Manager DN. Default= '$defrootdn'\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 "          -j filename          - Read the Directory Manager's password from file\n");
	print (STDERR "          -p port              - Provide a port. Default= '$defport'\n");
	print (STDERR "          -h host              - Provide a host name. Default= '$defhost'\n");
	print (STDERR "          -I DN-to-$operation  - Single entry DN or role DN to $operation\n");
	exit 100;
}

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;

`$ldapsearch -p $port -h $host -D \"$rootdn\" $pwoption \"$pwoptionvalue\" -s base -b \"$L_base\" \"(|(objectclass=*)(objectclass=ldapsubentry))\" nsroledn >> {{DEV-NULL}} 2>&1 `;
$retCode=$?;
if ( $retCode != 0 )
{
    $retCode=$?>>8;
    return 1;
}

	# Check if the role is a nested role
	@L_Nested="$ldapsearch -p $port -h $host -D \"$rootdn\" $pwoption \"$pwoptionvalue\" -s base -b \"$L_base\" \"(|(objectclass=nsNestedRoleDefinition)(objectclass=ldapsubentry))\"  ";
	# 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
	$L_search="$ldapsearch -p $port -h $host -D \"$rootdn\" $pwoption \"$pwoptionvalue\" -s base -b \"$L_base\" \"(|(objectclass=*)(objectclass=ldapsubentry))\" nsroledn ";

	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;

	$L_search="$ldapsearch -p $port -h $host -D \"$rootdn\" $pwoption \"$pwoptionvalue\" -s base -b \"$L_entry\" \"(|(objectclass=*)(objectclass=ldapsubentry))\" nsrole";

	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;
    }
}


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

# Generated variable
$dsroot="{{DS-ROOT}}";

# Determine which command we are running
if ( $0 =~ /ns-inactivate(.pl)?$/ )
{
	$cmd="ns-inactivate.pl";
	$operation="inactivate";
	$state="inactivated";
	$modrole="add";
	$already="already";
}
elsif ( $0 =~ /ns-activate(.pl)?$/ )
{
	$cmd="ns-activate.pl";
	$operation="activate";
	$state="activated";
	$modrole="delete";
	$already="already";
}
elsif ( $0 =~ /ns-accountstatus(.pl)?$/ )
{
	$cmd="ns-accountstatus.pl";
	$operation="get status of";
	$state="activated";
	# no need for $modrole as no operation is performed
	$already="";

}
else
{
	out("$0: unknown command\n");
	exit 100;
}

debug("Running ** $cmd ** $operation\n");

$dsbinroot="$dsroot{{SEP}}shared{{SEP}}bin";
$ldapsearch="$dsbinroot{{SEP}}ldapsearch -1";
$ldapmodify="$dsbinroot{{SEP}}ldapmodify";
 
# Default values
$defrootdn= "{{ROOT-DN}}";
$defhost= "{{SERVER-NAME}}";
$defport= "{{SERVER-PORT}}";

# User values
$rootdn= "{{ROOT-DN}}";
$rootpw= "";
$pwfile= "";
$host= "{{SERVER-NAME}}";
$port= "{{SERVER-PORT}}";
$entry= "";

$single=0;
$role=0;

# Process the command line arguments
while( $arg = shift)
{
    if($arg eq "-?")
    {
        usage_and_exit();
    }
    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;
    }
	else
	{
		print "$arg: Unknown command line argument.\n";
		usage_and_exit();
	}
}

if ($pwfile ne ""){
# Open file and get the password
	unless (open (RPASS, $pwfile)) {
		die "Error, cannot open password file $pwfile\n";
	}
	# Read the password for sanity check only
	# but give $passwdfile to ldapmodify and ldapsearch
	$rootpw = <RPASS>;
	chomp($rootpw);
	close(RPASS);
} elsif ($rootpw eq "-"){
# Read the password from terminal
	die "The '-w -' option requires an extension library (Term::ReadKey) which is not\n",
	    "part of the standard perl distribution. If you want to use it, you must\n",
	    "download and install the module. You can find it at\n",
	    "http://www.perl.com/CPAN/CPAN.html\n";
# Remove the previous line and uncomment the following 6 lines once you have installed Term::ReadKey module.
# use Term::ReadKey; 
#	print "Bind Password: ";
#	ReadMode('noecho'); 
#	$rootpw = ReadLine(0); 
#	chomp($rootpw);
#	ReadMode('normal');
}

if( $rootpw eq "" )
{
	usage_and_exit();
}

if( $entry eq "" )
{
	usage_and_exit();
}

# only test once to determine which option, -j or -w, to use
if ($pwfile ne ""){
  $pwoption="-j";
  $pwoptionvalue="$pwfile";
} else {
  $pwoption="-w";
  $pwoptionvalue="$rootpw"
}

#
# Check the actual existence of the entry to inactivate/activate
# and at the same time, validate the various parm: port, host, rootdn, rootpw
#
@exist=`$ldapsearch -p $port -h $host -D \"$rootdn\" $pwoption \"$pwoptionvalue\" -s base -b \"$entry\" \"(objectclass=*)\" dn`;
$retCode1=$?;
if ( $retCode1 != 0 )
{
	$retCode1=$?>>8;
	exit $retCode1;
}

@isRole=`$ldapsearch -p $port -h $host -D \"$rootdn\" $pwoption \"$pwoptionvalue\" -s base -b \"$entry\" \"(&(objectclass=LDAPsubentry)(objectclass=nsRoleDefinition))\" dn`;
$nbLineRole=@isRole;
$retCode2=$?;
if ( $retCode2 != 0 )
{
	$retCode2=$?>>8;
	exit $retCode2;
}

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

#
# First of all, check the existence of the nsaccountlock attribute in the entry
#
$isLocked=0;
if ( $single == 1 )
{
	$searchAccountLock="$ldapsearch -p $port -h $host -D \"$rootdn\" $pwoption \"$pwoptionvalue\" -s base -b \"$entry\" \"(objectclass=*)\" nsaccountlock";
	open (LDAP1, "$searchAccountLock |");
	while (<LDAP1>) {
		s/\n //g;
		if (/^nsaccountlock: (.*)\n/) {
			$L_currentvalue = $1;
			$L_currentvalue=~ tr/A-Z/a-z/;
			if ( $L_currentvalue eq "true")
			{
				$isLocked=1;
			}
			elsif ( $L_currentvalue eq "false" )
			{
				$isLocked=0;
			}
		}
	}
	close(LDAP1);
}
debug("Is the entry already locked? ==> $isLocked\n");

#
# Get the suffix name of that entry
#

# Remove the space at the beginning (just in case...)
#	-I "uid=jvedder , ou=People , o=sun.com"
@suffix=split /([,])/,$entry;
$result="";
foreach $part (@suffix)
{
	# $part=~s/^ +//;
	$part=~ tr/A-Z/a-z/;
	$result="$result$part";
}
@suffixN=$result;

debug("Entry to $operation: #@suffix#\n");
debug("Entry to $operation: #@suffixN#\n");

# Get the suffix
$cont=0;
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,o=sun.com\""
	#
	debug("\tSuffix from the entry: #@suffixN#\n");
	@mapping=`$ldapsearch -p $port -h $host -D \"$rootdn\" $pwoption \"$pwoptionvalue\" -s one -b \"cn=mapping tree, cn=config\" \"cn=\\"@suffixN\\"\" cn `;

	$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: "o=sun.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;

		$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;
		}
	}
}
if ( $cont == 2)
{
	out("Can not find suffix for entry $entry\n");
	exit 100;
}

if ( $operation eq "inactivate" )
{
	#
	# Now that we have the suffix and we know if we deal with a single entry or
	# a role, just try to create the COS and roles associated.
	#
	@base=(
		"cn=nsManagedDisabledRole,@suffixN",
		"cn=nsDisabledRole,@suffixN",
		"cn=nsAccountInactivationTmp,@suffixN",
		"\'cn=\"cn=nsDisabledRole,@suffixN\",cn=nsAccountInactivationTmp,@suffixN\'",
		"cn=nsAccountInactivation_cos,@suffixN" );

	$addrolescos="$ldapmodify -p $port -h $host -D \"$rootdn\" $pwoption \"$pwoptionvalue\" -c -a >> {{DEV-NULL}} 2>&1 ";
	@role1=(
		"dn: cn=nsManagedDisabledRole,@suffixN\n",
		"objectclass: LDAPsubentry\n",
		"objectclass: nsRoleDefinition\n",
		"objectclass: nsSimpleRoleDefinition\n",
		"objectclass: nsManagedRoleDefinition\n",
		"cn: nsManagedDisabledRole\n\n" );
	@role2=(
		"dn: cn=nsDisabledRole,@suffixN\n",
		"objectclass: top\n",
		"objectclass: LDAPsubentry\n",
		"objectclass: nsRoleDefinition\n",
		"objectclass: nsComplexRoleDefinition\n",
		"objectclass: nsNestedRoleDefinition\n",
		"nsRoleDN: cn=nsManagedDisabledRole,@suffixN\n",
		"cn: nsDisabledRole\n\n" );
	@cos1=(
		"dn: cn=nsAccountInactivationTmp,@suffixN\n",
		"objectclass: top\n",
		"objectclass: nsContainer\n\n" );
	@cos2=(
		"dn: cn=\"cn=nsDisabledRole,@suffixN\",cn=nsAccountInactivationTmp,@suffixN\n",
		"objectclass: top\n",
		"objectclass: extensibleObject\n",
		"objectclass: costemplate\n",
		"objectclass: ldapsubentry\n",
		"cosPriority: 1\n",
		"nsAccountLock: true\n\n" );
	@cos3=(
		"dn: cn=nsAccountInactivation_cos,@suffixN\n",
		"objectclass: top\n",
		"objectclass: LDAPsubentry\n",
		"objectclass: cosSuperDefinition\n",
		"objectclass: cosClassicDefinition\n",
		"cosTemplateDn: cn=nsAccountInactivationTmp,@suffixN\n",
		"cosSpecifier: nsRole\n",
		"cosAttribute: nsAccountLock operational\n\n" );

	@all=(\@role1, \@role2, \@cos1, \@cos2, \@cos3);

	$i=0;

	foreach $current (@base)
	{
		debug("Creating $current ??\n");
		open(FD,"| $addrolescos ");
		print FD @{$all[$i]};
		close(FD);
		if ( $? != 0 )
		{
			$retCode=$?>>8;
			if ( $retCode == 68 )
			{
				debug("Entry $current already exists, ignore error\n");
			}
			else
			{
				# Probably a more serious problem.
				# Exit with LDAP error
				exit $retCode;
			}
		}
		else
		{
			debug("Entry $current created\n");
		}
		$i=$i+1;
	}
}

$skipManaged=0;
$skipDisabled=0;
$directLocked=0;

$nsDisabledRole="cn=nsDisabledRole,@suffixN";
$nsDisabledRole=~ tr/A-Z/a-z/;

$nsManagedDisabledRole="cn=nsManagedDisabledRole,@suffixN";
$nsManagedDisabledRole=~ tr/A-Z/a-z/;

if ( $operation eq "inactivate" )
{
	# Go through all the roles part of nsdisabledrole to check if the entry
	# is a member of one of those roles
	$ret=indirectLock("LDAP00", $entry, $nsDisabledRole);
	if ( $ret == 0 )
	{
		if ( $throughRole ne $nsDisabledRole && $throughRole ne $nsManagedDisabledRole )
		{
			# indirect lock
			out("$entry already $state through $throughRole.\n");
		}
		else
		{
			# direct lock
			out("$entry already $state.\n");
		}
		exit 100;
	}
	elsif ( $isLocked == 1 )
	{
		# the entry is not locked through a role, may be nsaccountlock is "hardcoded" ?
		out("$entry already $state (probably directly).\n");
		exit 103;
	}
}
elsif ( $operation eq "activate" || $operation eq "get status of" )
{
	$skipManaged=$single;
	$skipDisabled=$role;

	$ret=indirectLock("LDAP00",$entry, $nsDisabledRole);

	if ( $ret == 0 )
	{
		# undirectly locked
		if ( $throughRole ne $nsDisabledRole && $throughRole ne $nsManagedDisabledRole )
		{
			if ( $operation eq "activate" )
			{
				out("$entry inactivated through $throughRole. Can not activate it individually.\n");
				exit 100;
			}
			else
			{
				out("$entry inactivated through $throughRole.\n");
				exit 104;
			}
		}
		debug("$entry locked individually\n");

		if ( $operation ne "activate" )
		{
			out("$entry inactivated.\n");
			exit 103;
		}
	}
	elsif ( $directLocked == 0 )
	{
		if ( $operation eq "activate" && $isLocked != 1 )
		{
			out("$entry $already $state.\n");
			exit 100;
		}
		elsif ( $isLocked != 1 )
		{
			out("$entry $already $state.\n");
			exit 102;
		}
		else
		{
			# not locked using our schema, but nsaccountlock is probably present
			out("$entry inactivated (probably directly).\n");
			exit 103;
		}
	}
	elsif ( $operation ne "activate" )
	{
		out("$entry inactivated.\n");
		exit 103;
	}
	# else Locked directly, juste unlock it!
	debug("$entry locked individually\n");
}

#
# Inactivate/activate the entry
#
$action="$ldapmodify -p $port -h $host -D \"$rootdn\" $pwoption \"$pwoptionvalue\" -c >> {{DEV-NULL}} 2>&1";
if ( $single == 1 )
{
	@record=(
		"dn: $entry\n",
		"changetype: modify\n",
		"$modrole: nsRoleDN\n",
		"nsRoleDN: cn=nsManagedDisabledRole,@suffixN\n\n" );
}
else
{
	@record=(
		"dn: cn=nsDisabledRole,@suffixN\n",
		"changetype: modify\n",
		"$modrole: nsRoleDN\n",
		"nsRoleDN: $entry\n\n" );
}
open(FD,"| $action ");
print FD @record;
close(FD);
if ( $? != 0 )
{
debug("$modrole, $entry\n");
	$retCode=$?>>8;
	exit $retCode;
}

out("$entry $state.\n");
exit 0;
