#! /usr/bin/perl -w

################################################################################
# Program Description
#
# Author: Fernando Freitas
# Contact: ffreitas@novell.com
# Description: Parse two LDIF exports, one only with users and the other only
#              with group information, and check the associations between both.
#              Generates an output LDIF file with the proper operations to add
#              the missing associations. After that importing the destination
#              LDIF file should fix missing user/group associations.
#
# Creation Date: March 04, 2006
# Modification Date: January 28, 2007
# Version: 0.01.002
#
# Copyright 2006 Fernando Freitas
# Licence: GPL
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
##########

use strict;

&main; # Calls Main SubRoutine
exit(0); # Exits program with success code.

################################################################################
# SubRoutine Description
#
# Version: 0.01.000
# Description: Calls all other subroutines.
#
# Author: Fernando Freitas
# Creation Date: March 04, 2006
# Modification Date: March 04, 2006
##########

sub main {

my $f_params;

# Parses command line parameters
$f_params = &f_cmdlineparams(@ARGV);

# Prints CopyRight Notice on screen
&f_copyleft();

# Writes the Output LDIF file that add any missing references found.
&f_createLDIF($f_params);

exit(0);
}


################################################################################
# SubRoutine Description
#
# Version: 0.01.001
# Description: Validates command line parameters, prints help on the prompt,
#              and returns the parameter in pipe delimited format, using
#              the following sequence:
#              User export filename and path
#              Group export filename and path
#              Output LDIF file and path
#
# Author: Fernando Freitas
# Creation Date: March 04, 2006
# Modification Date: January 11, 2006
##########

sub f_cmdlineparams {

my @f_parameters = @_;
my $f_paramcount = @f_parameters;
my $f_usagemsg = 0;
# Variables for the command line parameters themselves
my $f_usersfile = '';
my $f_groupsfile = '';
my $f_outputfile = '';
my $f_ret;

# Validates command line parameters
for (my $i = 0; $i < $f_paramcount; $i++) {
        if ($f_parameters[$i] eq '-u') {
                $i++;
                if (! defined($f_parameters[$i])) { $i--; next; }
                if ($f_parameters[$i] =~ /^-/) { $i--; next; }
                $f_usersfile = $f_parameters[$i];
                $f_usersfile =~ s/\|//g;
                next;
        }
        if ($f_parameters[$i] eq '-g') {
                $i++;
                if (! defined($f_parameters[$i])) { $i--; next; }
                if ($f_parameters[$i] =~ /^-/) { $i--; next; }
                $f_groupsfile = $f_parameters[$i];
                $f_groupsfile =~ s/\|//g;
                next;
        }
        if ($f_parameters[$i] eq '-o') {
                $i++;
                if (! defined($f_parameters[$i])) { $i--; next; }
                if ($f_parameters[$i] =~ /^-/) { $i--; next; }
                $f_outputfile = $f_parameters[$i];
                $f_outputfile =~ s/\|//g;
                next;
        }
        print "\nERROR: Invalid parameter found: $f_parameters[$i]\n";
        $f_usagemsg = 1;
}
if (! ($f_usersfile and $f_groupsfile and $f_outputfile)) {
        $f_usagemsg = 1;
}

if ($f_usagemsg) {

print <<EndofManPage;

################################################################################
Group and User Association Check Utility


SYNOPSIS

$0 -u <path to the LDIF file with the exported users>
   -g <path to the LDIF file with the exported groups>
   -o <path to create the LDIF output file>


DESCRIPTION

$0 Reads both an LDIF export with all the users on an eDirectory
tree, and an LDIF export with all groups in the same eDirectory tree, then check
the associations between users and groups. Those associations involve four
attributes, two on the user side and two on the group side. It then creates
an output LDIF file with the proper operations to add any missing associations
in the exported files.

After generating the destination LDIF file, this file should be imported back
in the eDirectory tree from which the user and group exports were generated.


SWITCHES

 -u <path to the LDIF file with the exported users>
    Set the path to the LDIF file with the exported users. This file should have
    ONLY user objects, and it has to have at the very minimum the following
    user attributes: SecurityEquals and GroupMembership
    
 -g <path to the LDIF file with the exported groups>
    Set the path to the LDIF file with the exported groups. This file should
    have ONLY group objects, and it has to have at the very minimum the
    following group attributes: EquivalentToMe and Member

 -o <path to create the LDIF output file>
    Set the path were the output LDIF file will be created, as well as its
    filename. This file will contain any LDIF operations necessary to add back
    missing associations between users and groups in the eDirectory tree, if any
################################################################################
EndofManPage

exit(0);
}

open(FHT,"< $f_usersfile") or
        die "Error: $! opening $f_usersfile for reading\n";
close(FHT);
open(FHT,"< $f_groupsfile") or
        die "Error: $! opening $f_groupsfile for reading\n";
close(FHT);
open(FHT,">> $f_outputfile") or
        die "Error: $! opening $f_outputfile for reading\n";
close(FHT);

$f_ret = "$f_usersfile|$f_groupsfile|$f_outputfile";

return $f_ret;
}


################################################################################
# SubRoutine Description
#
# Version: 0.01.001
# Description: Read both LDIF exports, verify user and group associations and
#              returns and output LDIF with the proper operations to fix
#              missing associations.
#
# Author: Fernando Freitas
# Creation Date: March 04, 2006
# Modification Date: January 28, 2007
##########

sub f_createLDIF {
my $f_params = $_[0];
my $f_ldiffile1 = ''; # Users
my $f_ldiffile2 = ''; # Groups
my $f_outputfile = ''; # Resulting LDIF file
($f_ldiffile1,$f_ldiffile2,$f_outputfile) = split/\|/,$f_params;

my $f_fileimport1 = '';
my $f_fileimport2 = '';
my @f_ldiffile1; # Users
my @f_ldiffile2; # Groups
my $f_line;
my $f_line2;
my $f_currentdn = '';
my $f_value = '';
my $f_out = '';
my %f_users;
my %f_groups;

local $/ = "\n\n";

# Reading Users export file
open(FHR,"< $f_ldiffile1") or die "Error $! opening $f_ldiffile1 for reading\n";
foreach $f_line ((<FHR>)) {
        undef $f_fileimport1; undef @f_ldiffile1;
        $f_fileimport1 .= $f_line;
        $f_fileimport1 =~ s/\n //g;
        @f_ldiffile1 = split/\n/,$f_fileimport1;
        # Reads file with user export,
        # assemble a hash {user dn}->{desired attr}->{attr value}
        foreach $f_line (@f_ldiffile1) {
                if ($f_line =~ /^dn: cn=/) {
                        $f_currentdn = $f_line;
                        $f_currentdn =~ s/^dn: (cn=.+)/$1/;;
                } else {
                        if ($f_line =~ /^changetype:/) { next; }
                        if ($f_line =~ /^$/) { next; }
                        if ($f_line =~ /^securityequals:/i) {
                                $f_value = $f_line;
                                $f_value =~ s/^securityequals: (.+)/$1/i;
                                $f_users{$f_currentdn}->{'securityequals'}
                                        ->{$f_value}=1;
                        }
                        if ($f_line =~ /^groupmembership:/i) {
                                $f_value = $f_line;
                                $f_value =~ s/^groupmembership: (.+)/$1/i;
                                $f_users{$f_currentdn}->{'groupmembership'}
                                        ->{$f_value}=1;
                        }
                }
        }
}
close(FHR);

# Reading Group export file
open(FHR,"< $f_ldiffile2") or die "Error $! opening $f_ldiffile2 for reading\n";
foreach $f_line ((<FHR>)) {
        undef $f_fileimport2; undef @f_ldiffile2;
	$f_fileimport2 .= $f_line;
        $f_fileimport2 =~ s/\n //g;
        @f_ldiffile2 = split/\n/,$f_fileimport2;
        # Reads file with group export,
        # assemble a hash {group dn}->{desired attr}->{attr value}
        foreach $f_line (@f_ldiffile2) {
        	chomp($f_line);
        	if ($f_line =~ /^dn: cn=/) {
        		$f_currentdn = $f_line;
        		$f_currentdn =~ s/^dn: (cn=.+)/$1/;;
        	} else {
        		if ($f_line =~ /^changetype:/) { next; }
        		if ($f_line =~ /^$/) { next; }
        		if ($f_line =~ /^equivalentToMe:/i) {
	               		$f_value = $f_line;
        			$f_value =~ s/^equivalentToMe: (.+)/$1/i;
                                $f_groups{$f_currentdn}->{'equivalenttome'}
                                        ->{$f_value}=1;
        		}
        		if ($f_line =~ /^member:/i) {
        			$f_value = $f_line;
        			$f_value =~ s/^member: (.+)/$1/i;
                                $f_groups{$f_currentdn}->{'member'}
                                        ->{$f_value}=1;
        		}
               	}
        }
}
close(FHR);

# Verify associations, parsing the User objects
foreach $f_line (sort keys %f_users) {
	if(defined($f_users{$f_line}->{'groupmembership'})) {
		foreach $f_line2 (sort keys %{$f_users{$f_line}->{'groupmembership'}}) {
			# Checks if groupmembership and securityequals match on the same user
			if(! defined($f_users{$f_line}->{'securityequals'}->{$f_line2})) {
				$f_out .= "\ndn: $f_line\nchangetype: modify\n";
				$f_out .= "add: securityEquals\n";
				$f_out .= "securityEquals: $f_line2\n";
				$f_users{$f_line}->{'securityequals'}->{$f_line2} = 1;
			}
			# Check if the group has the user as a member
			if(! defined($f_groups{$f_line2}->{'member'}->{$f_line})) {
				$f_out .= "\ndn: $f_line2\nchangetype: modify\n";
				$f_out .= "add: member\n";
				$f_out .= "member: $f_line\n";
				$f_groups{$f_line2}->{'member'}->{$f_line} = 1;
			}
			# Checks if the group has the user as security equivalent to the group
			if(! defined($f_groups{$f_line2}->{'equivalenttome'}->{$f_line})) {
				$f_out .= "\ndn: $f_line2\nchangetype: modify\n";
				$f_out .= "add: equivalentToMe\n";
				$f_out .= "equivalentToMe: $f_line\n";
				$f_groups{$f_line2}->{'equivalenttome'}->{$f_line} = 1;
			}
		}
	}
}

# Verify associations, parsing the Group objects
foreach $f_line (sort keys %f_groups) {
	if(defined($f_groups{$f_line}->{'member'})) {
		foreach $f_line2 (sort keys %{$f_groups{$f_line}->{'member'}}) {
			# Checks if member and equivalentToMe match on the same group
			if(! defined($f_groups{$f_line}->{'equivalenttome'}->{$f_line2})) {
				$f_out .= "\ndn: $f_line\nchangetype: modify\n";
				$f_out .= "add: equivalentToMe\n";
				$f_out .= "equivalentToMe: $f_line2\n";
				$f_groups{$f_line}->{'equivalenttome'}->{$f_line2} = 1;
			}
			# Check if the user has the group in groupMembership
			if(! defined($f_users{$f_line2}->{'groupmembership'}->{$f_line})) {
				$f_out .= "\ndn: $f_line2\nchangetype: modify\n";
				$f_out .= "add: groupMembership\n";
				$f_out .= "groupMembership: $f_line\n";
				$f_users{$f_line2}->{'groupmembership'}->{$f_line} = 1;
			}
			# Checks if the user is security equivalent to the group
			if(! defined($f_users{$f_line2}->{'securityequals'}->{$f_line})) {
				$f_out .= "\ndn: $f_line2\nchangetype: modify\n";
				$f_out .= "add: securityEquals\n";
				$f_out .= "securityEquals: $f_line\n";
				$f_users{$f_line2}->{'securityequals'}->{$f_line} = 1;
			}
		}
	}
}

open(FHW,"> $f_outputfile") or die "Error: $! opening $f_outputfile for writing\n";
        print FHW "version: 1\n";
        print FHW $f_out;
close(FHW);

return 0;
}


################################################################################
# SubRoutine Description
#
# Version: 0.01.000
# Description: Prints Program CopyRight on screen
#
# Author: Fernando Freitas
# Creation Date: March 04, 2006
# Modification Date: March 04, 2006
##########

sub f_copyleft {

print <<CopyleftMessage;
Group and User Association Check Utility - NTS ASC Team

Copyright 2006 Fernando Freitas
Licence: GPL (GNU Public License)

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

CopyleftMessage

return 1;
}

