#!/usr/bin/perl -w
#======================================================
# Change password script for EOLE
#
# AUTHOR Clement OUDOT AT linagora DOT com
#======================================================

use strict;
use Net::LDAP;
use Crypt::SmbHash qw(lmhash nthash);
use Crypt::SaltedHash;
use Unicode::String qw(utf8);
use smbldap_tools;

## PARAMETERS
my ( $user, $password, $changePwd ) = splice @ARGV;
unless ( $user && $password ) {
    print STDERR "Usage: $0 [user] [password] [changePwd]\n";
    exit 1;
}

## CONFIG
# OpenLDAP/Samba (use $config from smbldap-tools)
my $openldap_server   = "ldap://$config{masterLDAP}";
my $openldap_binddn   = $config{masterDN};
my $openldap_bindpw   = $config{masterPw};
my $openldap_base     = $config{usersdn};
my $openldap_filter   = "(&(objectClass=inetOrgPerson)(uid=$user))";
my $openldap_scope    = "sub";
my $openldap_attrs    = ['1.1'];
my $openldap_use_ssha = 1;

# Active Directory
my $ad_server = "$config{adserver}";
my $ad_binddn = "$config{aduser}";
my $ad_bindpw = "$config{adpassword}";
my $ad_base   = "$config{adbase}";
my $ad_filter = "(&(objectClass=person)(sAMAccountName=$user))";
my $ad_scope  = "sub";
my $ad_attrs  = ['1.1'];

## INIT CHECKS
unless ( defined $openldap_server ) {
    print STDERR "OpenLDAP configuration missing\n";
    exit 1;
}

## SUBROUTINES
sub connect {
    my ( $server, $binddn, $bindpw ) = splice @_;

    my $ldap = Net::LDAP->new( $server, version => 3 );

    if ($@) {
        print STDERR "Unable to connect to $server ($@)\n";
        exit 1;
    }

    my $bind = $ldap->bind( $binddn, password => $bindpw );

    if ( $bind->is_error ) {
        print STDERR "Unable to bind to $server with $binddn ("
          . $bind->error . ")\n";
        exit 1;
    }

    return $ldap;
}

sub get_user {
    my ( $ldap, $base, $filter, $scope, $attrs ) = splice @_;

    my $search = $ldap->search(
        base   => $base,
        filter => $filter,
        scope  => $scope,
        attrs  => $attrs
    );

    if ( $search->is_error ) {
        print STDERR "Unable to search user $user (" . $search->error . ")\n";
        exit 1;
    }

    if ( $search->count > 1 ) {
        print STDERR "More than one user $user found\n";
        exit 1;
    }

    if ( $search->count < 1 ) {
        print STDERR "User $user not found\n";
        exit 1;
    }

    my $entry = $search->shift_entry;

    return $entry;
}

sub modify_password {
    my ( $ldap, $dn, $attr, $value ) = splice @_;

    my $modify = $ldap->modify( $dn, replace => { $attr => $value } );

    if ( $modify->is_error ) {
        print STDERR "Unable to modify $attr for $dn ("
          . $modify->error . ")\n";
        exit 1;
    }

    return 1;
}

## MAIN
# Change password in OpenLDAP
my $openldap = &connect( $openldap_server, $openldap_binddn, $openldap_bindpw );
my $openldap_entry = &get_user(
    $openldap,       $openldap_base, $openldap_filter,
    $openldap_scope, $openldap_attrs
);

my $userpassword = $password;
if ($openldap_use_ssha) {
    my $csh = Crypt::SaltedHash->new( algorithm => 'SHA-1' );
    $csh->add($password);
    my $salted = $csh->generate;
    $userpassword = $salted;
}

&modify_password( $openldap, $openldap_entry->dn, 'userPassword',
    $userpassword );
&modify_password( $openldap, $openldap_entry->dn, 'sambaLMPassword',
    lmhash $password );
&modify_password( $openldap, $openldap_entry->dn, 'sambaNTPassword',
    nthash $password );

# Change passord in AD if configured
if ($ad_server) {
    my $ad = &connect( $ad_server, $ad_binddn, $ad_bindpw );
    my $ad_entry = &get_user( $ad, $ad_base, $ad_filter, $ad_scope, $ad_attrs );
    &modify_password( $ad, $ad_entry->dn, 'unicodePwd', utf8( chr(34) . ${password} . chr(34) )->utf16le() );
    if( defined $changePwd && $changePwd == 1 ) {
      &modify_password( $ad, $ad_entry->dn, 'pwdLastSet', '0' );
    }
}

## END
print "Password changed\n";
exit 0;
