#!/usr/bin/perl -w

package esmith;

use strict;
use Errno;
use esmith::ConfigDB;
use esmith::AccountsDB;
use esmith::util;
use Net::LDAP;
use Date::Parse;

$ENV{'LANG'} = 'C';
$ENV{'TZ'} = '';

my $c = esmith::ConfigDB->open_ro;
my $a = esmith::AccountsDB->open_ro;

my $l = $c->get('ldap');
my $status = $l->prop('status') || "disabled";
unless ($status eq "enabled" )
{
    warn "Not running action script $0, LDAP service not enabled!\n";
    exit(0);
}

exit(0) if ($c->get('ldap')->prop('Authentication') || 'disabled') eq 'enabled';

my $domain = $c->get('DomainName')
    || die("Couldn't determine domain name");
$domain = $domain->value;

my $schema = '/etc/openldap/schema/samba.schema';

my $event = shift || die "Event name must be specified";

my @name = @ARGV;
die "Account name argument missing." unless scalar (@name) >= 1;

#------------------------------------------------------------
# Update LDAP database entry.
#------------------------------------------------------------
my $base = esmith::util::ldapBase ($domain);
my $pw = esmith::util::LdapPassword();

my $ldap = Net::LDAP->new('localhost')
    or die "$@";

$ldap->bind(
    dn => "cn=root,$base",
    password => $pw
);

my @accounts;
my $account;
foreach my $name (@name)
{   
    $account = $a->get($name);
    die "Account $name not found.\n" unless defined $account;
    my $type = $account->prop('type') || "unknown";

    die "Account $name is not a user, group, ibay, machine account; update LDAP entry failed.\n"
        unless ($type =~ m{^(?:user|group|ibay|machine)$} or $name eq 'admin');

    push @accounts, $account;
}

#------------------------------------------------------------
# Read all samba groups (can't do individual lookups)
#------------------------------------------------------------

my $groupmap = ();

# Only do if schema is found
if ( -f "$schema" and -x '/usr/bin/net' )
{
    foreach (`/usr/bin/net groupmap list 2> /dev/null`){
        chomp;
        next if m{\(S-1-5-32-\d+\)};
        $groupmap->{$3} = { name => "$1", sid => "$2" } if (/^(.*) \((S-.*-\d+)\) -> (.*)$/);
    }
}

#------------------------------------------------------------
# Create a list of updates that need to happen
#------------------------------------------------------------

my $updates;
foreach my $acct (@accounts)
{
    my $key = $acct->key;
    my $type = $acct->prop('type');
    my $desc = undef;
    my $dn;

    if ($type =~ m{^(?:user|group|ibay|machine)$} or $key eq 'admin')
    {
        #------------------------------------------------------------
        # Do the user portion
        #------------------------------------------------------------
        if ($type eq 'machine')
        {
            $dn = "uid=$key,ou=Computers,$base";
        }
        else
        {
            $dn = "uid=$key,ou=Users,$base";
        }
        utf8::upgrade($dn);

        # Read information from getent passwd
        @{$updates->{$dn}}{'uid','userPassword'} = getpwnam($key);
        unless ($updates->{$dn}->{uid})
        {
            delete $updates->{$dn};
            next;
        }
        $updates->{$dn}->{userPassword} = "!*" if $updates->{$dn}->{userPassword} eq '!!';
        $updates->{$dn}->{userPassword} =~ s/^/{CRYPT}/ unless $updates->{$dn}->{userPassword} =~ m/^{/;

        # Samba parameters if we find the samba.schema
        if ( -f "$schema" and -x '/usr/bin/pdbedit' )
        {
            my $line = `/usr/bin/pdbedit -wu '$key' 2> /dev/null`;
            chomp($line);
            if ($line)
            {
                @{$updates->{$dn}}{'junk','junk','sambaLMPassword','sambaNTPassword'} = split(/:/,$line);
                foreach $line (`/usr/bin/pdbedit -vu '$key' 2> /dev/null`)
                {
                    chomp($line);
                    $updates->{$dn}->{sambaSID} = $1 if $line =~ m{User SID:\s+(S-.*)$};
                    $updates->{$dn}->{displayName} = $1 if $line =~ m{Full Name:\s+(.*)$};
                    $updates->{$dn}->{sambaPrimaryGroupSID} = $1 if $line =~ m{Primary Group SID:\s+(S-.*)$};
                    $updates->{$dn}->{sambaAcctFlags} = $1 if $line =~ m{Account Flags:\s+(.*)$};
                    $updates->{$dn}->{sambaPwdLastSet} = str2time($1) if $line =~ m{Password last set:\s+(.*)$};
                }
                push @{$updates->{$dn}->{objectClass}}, 'sambaSamAccount';
            }
            else
            {
                $updates->{$dn}->{sambaLMPassword} = [];
                $updates->{$dn}->{sambaNTPassword} = [];
                $updates->{$dn}->{sambaSID} = [];
                $updates->{$dn}->{displayName} = [];
                $updates->{$dn}->{sambaPrimaryGroupSID} = [];
                $updates->{$dn}->{sambaAcctFlags} = [];
                $updates->{$dn}->{sambaPwdLastSet} = [];
            }
        }
    }
}
endpwent();

#------------------------------------------------------------
# Do the group portion (only if we have samba)
#------------------------------------------------------------
if ( -f "$schema" )
{
    foreach my $group ( (map { $_->key } $a->users), (map { $_->key } $a->groups), qw/admin nobody shared/ ){
        my $dn = "cn=$group,ou=Groups,$base";
        utf8::upgrade($dn);

        if ( exists $groupmap->{$group} )
        {
            push @{$updates->{$dn}->{objectClass}}, 'sambaGroupMapping';
            $updates->{$dn}->{displayName} = $groupmap->{$group}->{name};
            $updates->{$dn}->{sambaSID} = $groupmap->{$group}->{sid};
            $updates->{$dn}->{sambaGroupType} = '2';
        }
        else
        {
            $updates->{$dn}->{displayName} = [];
            $updates->{$dn}->{sambaSID} = [];
            $updates->{$dn}->{sambaGroupType} = [];
        }
    }
}

#------------------------------------------------------------
# Update LDAP database entry.
#------------------------------------------------------------
foreach my $dn (keys %$updates)
{
    # Try and find record
    my $result = $ldap->search( base => $dn, filter => '(objectClass=*)', scope => 'base' );
    warn "failed looking up entry $dn: ", $result->error if $result->code && $result->code != 32;
    my $code = $result->code;
    my @objectClass = $code == 32 ? () : $result->entry(0)->get_value('objectClass');

    # Clean up attributes and convert to utf8
    delete $updates->{$dn}->{'junk'};
    foreach my $attr ( keys %{$updates->{$dn}} )
    {
        if ( ref($updates->{$dn}->{$attr}) eq 'ARRAY' )
        {
            if ( $code == 32 and scalar(@{$updates->{$dn}->{$attr}}) == 0 )
            {
                delete $updates->{$dn}->{$attr};
            }
            else
            {
                for (my $c = 0; $c < scalar(@{$updates->{$dn}->{$attr}}); $c++)
                {
                    utf8::upgrade($updates->{$dn}->{$attr}[$c]);
                }
            }
        }
        else
        {
            if ($updates->{$dn}->{$attr} !~ /^\s*$/)
            {
                utf8::upgrade($updates->{$dn}->{$attr});
            }
            elsif ( $code == 32 )
            {
                delete $updates->{$dn}->{$attr};
            }
            else
            {
                $updates->{$dn}->{$attr} = [];
            }
        }
    }

    # Perform insert or update
    if ( $code == 32 )
    {
        $result = $ldap->add( $dn, attrs => [ %{$updates->{$dn}} ] );
        $result->code && warn "failed to add entry $dn: ", $result->error;
    }
    else
    {
        # Don't overwrite objectClass (just update if necessary)
        my $seen = ();

        # Remove samba objectClasses if removing samba attributes
        @{$seen}{'sambaSamAccount','sambaGroupMapping'} = (1,1) if ref($updates->{$dn}->{sambaSID}) eq 'ARRAY';

        @{$updates->{$dn}->{objectClass}} = grep { ! $seen->{$_}++ } (@{$updates->{$dn}->{objectClass}}, @objectClass );

        $result = $ldap->modify( $dn, replace => $updates->{$dn});
        $result->code && warn "failed to modify entry $dn: ", $result->error;
    }
}
$ldap->unbind;

exit (0);
