# IBM(c) 2010 EPL license http://www.eclipse.org/legal/epl-v10.html
package xCAT_plugin::activedirectory;

BEGIN
{
    $::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : '/opt/xcat';
}
my $callback;
use lib "$::XCATROOT/lib/perl";
use Getopt::Long;
use xCAT::ADUtils;
use Net::DNS;
use xCAT::SvrUtils;
use xCAT::TableUtils;
use strict;

sub handled_commands {
    return {
        clusteruseradd  => 'site:directoryprovider',
        clusteruserdel  => 'site:directoryprovider',
        clusteruserlist => 'site:directoryprovider',
        hostaccountlist => 'site:directoryprovider',
        hostaccountadd  => 'site:directoryprovider',
        hostaccountdel  => 'site:directoryprovider',
    };
}

sub process_request {
    $ENV{LDAPRC} = 'ad.ldaprc'; #after examining openldap source, this is the best way to get their libraries to source /etc/xcat/ad.ldaprc
    $ENV{HOME}   = '/etc/xcat';
    my $request = shift;
    my $command = $request->{command}->[0];
    $callback = shift;
    my $doreq = shift;

    #use Data::Dumper;
    #my $sitetab = xCAT::Table->new('site');
    my $domain;

    #$domain = $sitetab->getAttribs({key=>'domain'},['value']);
    my @domains = xCAT::TableUtils->get_site_attribute("domain");
    my $tmp     = $domains[0];
    if (defined($tmp)) {
        $domain = $tmp;
    } else {
        $domain = undef;
    }

    #TODO: if multi-domain support implemented, use the domains table to reference between realm and domain
    #my $server = $sitetab->getAttribs({key=>'directoryserver'},['value']);
    #my $realm = $sitetab->getAttribs({key=>'realm'},['value']);
    my @directoryservers = xCAT::TableUtils->get_site_attribute("directoryserver");
    my @realms = xCAT::TableUtils->get_site_attribute("realm");
    my $tmp1   = $realms[0];
    my $server;
    my $realm;
    if (defined($tmp1)) {
        $realm = $tmp1;
    } else {
        $realm = uc($domain);
        $realm =~ s/\.$//;    #remove trailing dot if provided
    }
    my $passtab = xCAT::Table->new('passwd');
    my $adpent = $passtab->getAttribs({ key => 'activedirectory' }, [qw/username password/]);
    unless ($adpent and $adpent->{username} and $adpent->{password}) {
        xCAT::SvrUtils::sendmsg([ 1, "activedirectory entry missing from passwd table" ], $callback);
        return 1;
    }
    my $tmp2 = $directoryservers[0];
    if (defined($tmp2)) {
        $server = $tmp2;
    } else {
        my $res = Net::DNS::Resolver->new;
        my $query = $res->query("_ldap._tcp.$domain", "SRV");
        if ($query) {
            foreach my $srec ($query->answer) {
                $server = $srec->{target};
            }
        }
        unless ($server) {
            xCAT::SvrUtils::sendmsg([ 1, "Unable to determine a directory server to communicate with, try site.directoryserver" ], $callback);
            return;
        }
    }
    if ($command =~ /userlist/) {    #user management command, listing
        my $passwdfmt;
        if ($request->{arg}) {
            @ARGV = @{ $request->{arg} };
            Getopt::Long::Configure("bundling");
            Getopt::Long::Configure("no_pass_through");
            if (!GetOptions(
                    'p' => \$passwdfmt
                )) {
                die "TODO: usage message";
            }
        }
        unless ($domain and $realm) {
            xCAT::SvrUtils::sendmsg([ 1, "Unable to determine domain from arguments or site table" ], $callback);
            return undef;
        }
        $ENV{KRB5CCNAME} = "/tmp/xcat/krbcache.$realm.$$";
        my $err = xCAT::ADUtils::krb_login(username => $adpent->{username}, password => $adpent->{password}, realm => $realm);
        if ($err) {
            xCAT::SvrUtils::sendmsg([ 1, "Error authenticating to Active Directory" ], $callback);
            return 1;
        }
        my $accounts = xCAT::ADUtils::list_user_accounts(
            dnsdomain       => $domain,
            directoryserver => $server,
        );
        if ($passwdfmt) {
            my $account;
            foreach $account (keys %$accounts) {
                my $textout = ":" . $account . ":x:"; #first colon is because sendmsg would mistake it for a description
                foreach (qw/uid gid fullname homedir shell/) {
                    $textout .= $accounts->{$account}->{$_} . ":";
                }
                $textout =~ s/:$//;
                xCAT::SvrUtils::sendmsg($textout, $callback);
            }
        } else {
            my $account;
            foreach $account (keys %$accounts) {
                xCAT::SvrUtils::sendmsg($account, $callback);
            }
        }
    } elsif ($command =~ /hostaccountlist/) {
        unless ($domain and $realm) {
            xCAT::SvrUtils::sendmsg([ 1, "Unable to determine domain from arguments or site table" ], $callback);
            return undef;
        }
        $ENV{KRB5CCNAME} = "/tmp/xcat/krbcache.$realm.$$";
        my $err = xCAT::ADUtils::krb_login(username => $adpent->{username}, password => $adpent->{password}, realm => $realm);
        if ($err) {
            xCAT::SvrUtils::sendmsg([ 1, "Error authenticating to Active Directory" ], $callback);
            return 1;
        }
        my $accounts = xCAT::ADUtils::list_host_accounts(
            dnsdomain       => $domain,
            directoryserver => $server,
        );
        my $account;
        foreach $account (keys %$accounts) {
            xCAT::SvrUtils::sendmsg($account, $callback);
        }
    } elsif ($command =~ /hostaccountdel/) {
        my $accountname;
        my %loggedrealms = ();
        foreach $accountname (@{ $request->{node} }) {
            if ($request->{arg} and scalar @{ $request->{arg} }) {
                die "TODO: usage";
            }
            if ($accountname =~ /@/) {
                ($accountname, $domain) = split /@/, $accountname;
                $domain = lc($domain);
            }
            unless ($domain) {
                xCAT::SvrUtils::sendmsg([ 1, "Unable to determine domain from arguments or site table" ], $callback);
                return undef;
            }

            #my $domainstab = xCAT::Table->new('domains');
            #$realm = $domainstab->getAttribs({domain=>$domain},
            unless ($realm) {
                $realm = uc($domain);
                $realm =~ s/\.$//;    #remove trailing dot if provided
            }
            $ENV{KRB5CCNAME} = "/tmp/xcat/krbcache.$realm.$$";
            unless ($loggedrealms{$realm}) {
                my $err = xCAT::ADUtils::krb_login(username => $adpent->{username}, password => $adpent->{password}, realm => $realm);
                if ($err) {
                    xCAT::SvrUtils::sendmsg([ 1, "Error authenticating to Active Directory" ], $callback, $accountname);
                    next;
                }
                $loggedrealms{$realm} = 1;
            }
            my %args = (
                account         => $accountname,
                dnsdomain       => $domain,
                directoryserver => $server,
            );
            my $ret = xCAT::ADUtils::del_host_account(%args);
        }
        foreach my $realm (keys %loggedrealms) {
            unlink "/tmp/xcat/krbcache.$realm.$$";
        }
    } elsif ($command =~ /userdel/) {
        my $username = shift @{ $request->{arg} };
        if (scalar @{ $request->{arg} }) {
            die "TODO: usage";
        }
        if ($username =~ /@/) {
            ($username, $domain) = split /@/, $username;
            $domain = lc($domain);
        }
        unless ($domain) {
            xCAT::SvrUtils::sendmsg([ 1, "Unable to determine domain from arguments or site table" ], $callback);
            return undef;
        }

        #my $domainstab = xCAT::Table->new('domains');
        #$realm = $domainstab->getAttribs({domain=>$domain},
        unless ($realm) {
            $realm = uc($domain);
            $realm =~ s/\.$//;    #remove trailing dot if provided
        }
        $ENV{KRB5CCNAME} = "/tmp/xcat/krbcache.$realm.$$";

        my $err = xCAT::ADUtils::krb_login(username => $adpent->{username}, password => $adpent->{password}, realm => $realm);
        if ($err) {
            xCAT::SvrUtils::sendmsg([ 1, "Error authenticating to Active Directory" ], $callback);
            return 1;
        }
        my %args = (
            account         => $username,
            dnsdomain       => $domain,
            directoryserver => $server,
        );
        if ($command =~ /userdel/) {
            my $ret = xCAT::ADUtils::del_user_account(%args);
        } elsif ($command =~ /hostaccountdel/) {
            my $ret = xCAT::ADUtils::del_host_account(%args);
        }
    } elsif ($command =~ /useradd$/) {    #user management command, adding
        my $homedir;
        my $fullname;
        my $gid;
        my $uid;
        my $ou;
        @ARGV = @{ $request->{arg} };
        Getopt::Long::Configure("bundling");
        Getopt::Long::Configure("no_pass_through");

        if (!GetOptions(
                'd=s' => \$homedir,
                'c=s' => \$fullname,
                'g=s' => \$gid,
                'o=s' => \$ou,
                'u=s' => \$uid)) {
            die "TODO: usage message";
        }
        my $username = shift @ARGV;
        if ($username =~ /@/) {
            ($username, $domain) = split /@/, $username;
            $domain = lc($domain);
        }
        unless ($domain) {
            xCAT::SvrUtils::sendmsg([ 1, "Unable to determine domain from arguments or site table" ], $callback);
            return undef;
        }

        #my $domainstab = xCAT::Table->new('domains');
        #$realm = $domainstab->getAttribs({domain=>$domain},
        unless ($realm) {
            $realm = uc($domain);
            $realm =~ s/\.$//;    #remove trailing dot if provided
        }
        $ENV{KRB5CCNAME} = "/tmp/xcat/krbcache.$realm.$$";

        my $err = xCAT::ADUtils::krb_login(username => $adpent->{username}, password => $adpent->{password}, realm => $realm);
        if ($err) {
            xCAT::SvrUtils::sendmsg([ 1, "Error authenticating to Active Directory" ], $callback);
            return 1;
        }
        my %args = (
            username        => $username,
            dnsdomain       => $domain,
            directoryserver => $server,
        );
        if ($fullname) { $args{fullname} = $fullname }
        if ($ou)       { $args{ou}       = $ou }
        if ($request->{environment} and
            $request->{environment}->[0]->{XCAT_USERPASS}) {
            $args{password} = $request->{environment}->[0]->{XCAT_USERPASS}->[0];
        }

        #TODO: args password
        if (defined $gid) { $args{gid} = $gid }
        if (defined $uid) { $args{uid} = $uid }

        #TODO: smbHome for windows
        if (defined $homedir) { $args{homedir} = $homedir }
        my $ret = xCAT::ADUtils::add_user_account(%args);
        if (ref $ret and $ret->{error}) {
            xCAT::SvrUtils::sendmsg([ 1, $ret->{error} ], $callback);
        }
    } elsif ($command =~ /hostaccountadd$/) {   #user management command, adding
        my $ou;
        if ($request->{arg}) {
            @ARGV = @{ $request->{arg} };
            Getopt::Long::Configure("bundling");
            Getopt::Long::Configure("no_pass_through");

            if (!GetOptions('o=s' => \$ou)) {
                die "TODO: usage message";
            }
        }

        #my $domainents = $domaintab->getNodesAttribs($request->{node},['ou','domain']); #TODO: have this in schema
        my $nodename;
        my %loggedrealms = ();
        foreach $nodename (@{ $request->{node} }) {
            if ($nodename =~ /\./) {
                ($nodename, $domain) = split /\./, $nodename, 2;
                $domain = lc($domain);
            }
            unless ($domain) {
                xCAT::SvrUtils::sendmsg([ 1, "Unable to determine domain from arguments or site table" ], $callback);
                return undef;
            }

            #my $domainstab = xCAT::Table->new('domains');
            #$realm = $domainstab->getAttribs({domain=>$domain},
            unless ($realm) {
                $realm = uc($domain);
                $realm =~ s/\.$//;    #remove trailing dot if provided
            }
            $ENV{KRB5CCNAME} = "/tmp/xcat/krbcache.$realm.$$";
            unless ($loggedrealms{$realm}) {
                my $err = xCAT::ADUtils::krb_login(username => $adpent->{username}, password => $adpent->{password}, realm => $realm);
                if ($err) {
                    xCAT::SvrUtils::sendmsg([ 1, "Error authenticating to Active Directory" ], $callback, $nodename);
                    next;
                }
                $loggedrealms{$realm} = 1;
            }

            my %args = (
                node            => $nodename,
                dnsdomain       => $domain,
                directoryserver => $server,
            );
            if ($ou) { $args{ou} = $ou }
            if ($request->{environment} and
                $request->{environment}->[0]->{XCAT_HOSTPASS}) {
                $args{password} = $request->{environment}->[0]->{XCAT_HOSTPASS}->[0];
            }
            my $ret = xCAT::ADUtils::add_host_account(%args);
            if (ref $ret and $ret->{error}) {
                xCAT::SvrUtils::sendmsg([ 1, $ret->{error} ], $callback);
            } elsif (ref $ret) {
                xCAT::SvrUtils::sendmsg($ret->{password}, $callback, $nodename);
            }
        }
    }
}

1;
