#!/usr/bin/perl
#
# ID synchronization command
#
# Copyright(c) SECIOSS CORPORATION 2010
#

use strict;
use lib '/opt/secioss/lib/perl';
use LISM;
use XML::Simple;
use Net::LDAP::Util qw(ldap_error_desc);
use Net::LDAP::Constant qw(:all);
use DBI;
use Encode;
use POSIX;
use Getopt::Std;
use Data::Dumper;

our $BASEDIR = '/opt/secioss';
our $CONFDIR = "$BASEDIR/etc";
our $BASEDN = 'o=secioss';
our $PASSWD = 'secioss';

$ENV{PERL_LWP_SSL_VERIFY_HOSTNAME} = 0;

my %opt;
getopts("f:d:l:rnoC", \%opt);

if (@ARGV < 1) {
    usage();
}

our $SYNCDIR = "$BASEDIR/var/lib/ldap";
if (defined($opt{d})) {
    $SYNCDIR = $opt{d};
}
our $DBFILE = "$BASEDIR/var/lib/ldap/user.db";
our $STATUSFILE = "$SYNCDIR/status";

sub updatesync
{
    my ($lism, $type, $data, $syncfilter, $continue) = @_;
    my @modinfo;
    my $rdn;

    if ($type eq 'master') {
        $rdn = 'cn=master-sync';
    } else {
        $rdn = 'cn=cluster-sync';
        push(@modinfo, 'DELETE', 'lismSyncErrNode', $data);
    }

    push(@modinfo, 'REPLACE', 'lismSyncStatus', 'sync');
    if ($syncfilter) {
        push(@modinfo, 'REPLACE', 'lismSyncFilter', $syncfilter);
    }
    if ($continue) {
        push(@modinfo, 'REPLACE', 'lismCmdOption', 'continue');
    }

    my @grpaddinfo = @modinfo;
    push(@grpaddinfo, 'REPLACE', 'lismSyncBase', "ou=Groups,ou=Master,$BASEDN");
    push(@grpaddinfo, 'REPLACE', 'lismCmdOption', 'add');
    push(@grpaddinfo, 'REPLACE', 'lismCmdOption', 'delete');
    my $rc = $lism->modify("$rdn,$BASEDN", @grpaddinfo);
    if ($rc) {
        print STDERR "Group add and delete failed: ".ldap_error_desc($rc)."($rc)\n";
        if (!$continue) {
            return $rc;
        }
    }

    my @orginfo = @modinfo;
    push(@orginfo, 'REPLACE', 'lismSyncBase', "ou=Organizations,ou=Master,$BASEDN");
    push(@orginfo, 'REPLACE', 'lismCmdOption', 'add');
    push(@orginfo, 'REPLACE', 'lismCmdOption', 'modify');
    my $rc = $lism->modify("$rdn,$BASEDN", @orginfo);
    if ($rc) {
        print STDERR "Organization add and modify failed: ".ldap_error_desc($rc)."($rc)\n";
        if (!$continue) {
            return $rc;
        }
    }

    my @userinfo = @modinfo;
    push(@userinfo, 'REPLACE', 'lismSyncBase', "ou=People,ou=Master,$BASEDN");
    my $rc = $lism->modify("$rdn,$BASEDN", @userinfo);
    if ($rc) {
        print STDERR "User synchronization failed: ".ldap_error_desc($rc)."($rc)\n";
        if (!$continue) {
            return $rc;
        }
    }

    if (defined($lism->{cluster}->{IDP}->{conf}->{object}->{Contact})) {
        my @contactinfo = @modinfo;
        push(@contactinfo, 'REPLACE', 'lismSyncBase', "ou=Contacts,ou=Master,$BASEDN");;
        my $rc = $lism->modify("$rdn,$BASEDN", @contactinfo);
        if ($rc) {
            print STDERR "Contact synchronization failed: ".ldap_error_desc($rc)."($rc)\n";
            if (!$continue) {
                return $rc;
            }
        }
    }
    sleep 3;

    my @groupinfo = @modinfo;
    push(@groupinfo, 'REPLACE', 'lismSyncBase', "ou=Groups,ou=Master,$BASEDN");
    my $rc = $lism->modify("$rdn,$BASEDN", @groupinfo);
    if ($rc) {
        print STDERR "Group synchronization failed: ".ldap_error_desc($rc)."($rc)\n";
        if (!$continue) {
            return $rc;
        }
    }


    my @sgroupinfo = @modinfo;
    push(@sgroupinfo, 'REPLACE', 'lismSyncBase', "ou=SecurityGroups,ou=Master,$BASEDN");
    my $rc = $lism->modify("$rdn,$BASEDN", @sgroupinfo);
    if ($rc) {
        print STDERR "Security Group synchronization failed: ".ldap_error_desc($rc)."($rc)\n";
        if (!$continue) {
            return $rc;
        }
    }

    @orginfo = @modinfo;
    push(@orginfo, 'REPLACE', 'lismSyncBase', "ou=Organizations,ou=Master,$BASEDN");
    push(@orginfo, 'REPLACE', 'lismCmdOption', 'delete');
    my $rc = $lism->modify("$rdn,$BASEDN", @orginfo);
    if ($rc) {
        print STDERR "Organization delete failed: ".ldap_error_desc($rc)."($rc)\n";
        if (!$continue) {
            return $rc;
        }
    }

    return $rc;
}

sub readsync
{
    my ($lism, $type, $data, $syncfilter) = @_;
    my $rdn;
    my $filter = '';

    if ($type eq 'master') {
        $rdn = 'cn=master-sync';
    } else {
        $rdn = 'cn=cluster-sync';
        $filter = "(lismSyncErrNode=$data)";
    }

    if ($syncfilter) {
        $syncfilter =~ s/\(/\\28/g;
        $syncfilter =~ s/\)/\\29/g;
        if ($filter) {
            $filter = "(&$filter(lismSyncFilter=$syncfilter))";
        } else {
            $filter = "(lismSyncFilter=$syncfilter)";
        }
    }
    if (!$filter) {
        $filter = '(objectClass=*)';
    }

    my ($rc, $entry) = $lism->search("$rdn,$BASEDN", 0, 1, 0, 0, $filter);
    if ($rc) {
        print STDERR ldap_error_desc($rc)."($rc)\n";
    } else {
        my @messages = ($entry =~ /^lismSyncErrMessage: (.+)$/gmi);
        if (@messages) {
            foreach my $message (@messages) {
                print encode('utf8', $message."\n");
            }
        }
    }

    return $rc;
}

sub factory
{
    my ($conf, $loglevel, %opt) = @_;

    my $lism = new LISM;
    $lism->config('basedn', $BASEDN);
    $lism->config('admindn', "cn=Idsync,$BASEDN");
    $lism->config('adminpw', $PASSWD);
    $lism->config('syncdir', $SYNCDIR);
    $lism->config('conf', $conf);
    if ($^O eq 'MSWin32') {
        $lism->config('logfile', "/opt/secioss/var/log/lism.log");
        $lism->config('auditfile', "/opt/secioss/var/log/audit.log");
    }
    if ($loglevel) {
        $lism->config('sysloglevel', $loglevel);
    }
    if (defined($opt{o})) {
        $lism->config('printlog', 'info,err');
    }

    return $lism;
}

sub usage
{
    print STDERR "Usage: idsync [-f config] [-d syncdir] [-l loglevel] [-r] [-n] [-o] [-C] idp|sp\n";
    exit 1;
}

my $list = "$BASEDIR/var/lib/csv/user.csv";
my $type = $ARGV[0];
if ($type ne 'idp' && $type ne 'sp') {
    usage();
}
my $rc = 0;

if ($type eq 'sp' && -f $list) {
    print STDERR "Delete $list\n";
    unlink($list);
}

my $conf_file = "$CONFDIR/lism-$type.conf";
if ($type eq 'idp' && defined($opt{f})) {
    $conf_file = $opt{f};
}
my $loglevel;
if (defined($opt{'l'})) {
    $loglevel = $opt{'l'};
}
my $lism = factory($conf_file, $loglevel, %opt);
$rc = $lism->init();
if ($rc) {
    print STDERR "Bad configuration\n";
    exit 1;
}
$lism->bind("cn=Idsync,$BASEDN", $PASSWD);

my $cflag = 0;
if (defined($opt{'C'})) {
    $cflag = 1;
}

if ($type eq 'idp') {
    if (defined($opt{r})) {
        $rc = readsync($lism, 'cluster', 'IDP');
        if ($rc) {
            print STDERR "Differential check failed\n";
            exit 1;
        }
    } else {
        $rc = updatesync($lism, 'cluster', 'IDP', undef, $cflag);
        if ($rc) {
            print STDERR "Synchronization failed\n";
            exit 1;
        }
    }
} else {
    my $prevtime;
    if (-f $STATUSFILE) {
        if (!open(STATUS, "<$STATUSFILE")) {
            print STDERR "Can't open status file\n";
            exit 1;
        }
        $prevtime = <STATUS>;
        $prevtime =~ s/\n//;
        close(STATUS);
    }
    if (!$prevtime) {
        $prevtime = '19700101000000';
    }

    my $ctime = strftime("%Y%m%d%H%M%S", localtime(time));
    $rc = updatesync($lism, 'master', 'SP', '(seciossPersonModifyTime>='.$prevtime.'Z)');
    if ($rc) {
        print STDERR "Can't get updated entries\n";
        exit 1;
    }

    if (defined($opt{n})) {
        if (!open(STATUS, ">$STATUSFILE")) {
            print STDERR "Can't open status file\n";
            exit 1;
        }
        print STATUS "$ctime";
        close(STATUS);
        if (-f $list) {
            my $bkfile = $list;
            $bkfile =~ s/\.csv$/-$ctime.csv/;
            rename $list, $bkfile;
        }

        print "Updated user list is created\n";
        exit 0;
    }

    if (!-f $list) {
        print "No updated user\n";
        exit 0;
    }

    my $file;
    if (defined($opt{f})) {
        $file = $opt{f};
    } else {
        $file = "$CONFDIR/lism.conf";
    }
    my $conf = XMLin("$CONFDIR/lism-sp.conf", ForceArray => 1);
    my $appconf = XMLin($file, ForceArray => 1);
    foreach my $key (keys %{$appconf->{sync}[0]->{data}}) {
        $conf->{sync}[0]->{data}{$key} = $appconf->{sync}[0]->{data}{$key};
    }
    foreach my $key (keys %{$appconf->{data}}) {
        $conf->{data}{$key} = $appconf->{data}{$key};
    }

    my $fh;
    open($fh, ">$CONFDIR/lism-all.conf");
    XMLout($conf, RootName => 'config', OutputFile => $fh);
    close $fh;

    $lism = factory("$CONFDIR/lism-all.conf");
    $rc = $lism->init();
    if ($rc) {
        print STDERR "Bad configuration\n";
        exit 1;
    }
    $lism->bind("cn=Idsync,$BASEDN", $PASSWD);

    if (!open(LIST, "<$list")) {
        print STDERR "Can't read $list\n";
        exit 1;
    }
    if (defined($opt{r})) {
        print "Differentail data:\n";
    }
    while (<LIST>) {
        my ($user) = split(/, */);
        if (defined($opt{r})) {
            $rc = readsync($lism, 'cluster', 'SP', "(uid=$user)");
        } else {
            $rc = updatesync($lism, 'cluster', 'SP', "(uid=$user)");
        }
        if ($rc) {
            print STDERR "Synchronization of $user failed\n";
            last;
        } elsif (!defined($opt{r})) {
            print "Synchronization of $user succeded\n";
        }
    }
    close(LIST);
    unlink($list);

    if (!$rc && !defined($opt{r})) {
        if (!open(STATUS, ">$STATUSFILE")) {
            print STDERR "Can't open status file\n";
            exit 1;
        }
        print STATUS "$ctime";
        close(STATUS);
    }
}

exit ($rc);
