#!/usr/bin/perl -w

# LDAP to unix password sync script for samba

#  This code is derivated from smbldap-passwd.pl developped by IDEALX (http://IDEALX.org/) and
#  contributors (their names can be found in the CONTRIBUTORS file).
#
#                 Copyright (C) 2001-2002 IDEALX
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#
#  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.

#  Purpose :
#       ldap-unix passwd sync for samba to use if samba
#       use ITDS e AIX 5.x RFC2307AIX objectclass and you want
#       SSO

use strict;
use diagnostics;

use Net::LDAP qw(:all);  
use Net::LDAP::Util qw(ldap_error_name ldap_error_text) ;
use POSIX;
use Sys::Syslog qw(:DEFAULT setlogsock);
my %config_bind_conf;
my %config_conf;
my $smbldap_bind_conf;
my $smbldap_conf;
my $ldap_user;
my $ldap;
my $result_ldap;
my $progname = $0;
$progname =~ s,.*/,,;



sub CreateCryptSalt  {
 my $salt;
 my $md5salt = shift;
 my @valid = split(//, "./0123456789abcdefghilmnopqrstuvwxyzABCDEFGHILMNOPQRSTUVWXYZ") ;
 my $cryptsaltlen = ($md5salt ? 8 : 2);
 open(F, "< /dev/urandom") || die "No /dev/urandom found!";
 foreach (1..$cryptsaltlen) {
  my $in;
  read(F, $in, 1);
  $salt .= $valid[ord($in) % ($#valid + 1)];
 }
close F;
return ($md5salt ? "\$1\$$salt\$" : $salt );
}

############################
# LogDie
############################
sub LogDie()  {
my $facility="authpriv";
my $priority="err";
my $debug=0;
my ($msg,$code)=@_;
my $message=$msg;
$message="$msg"." $code" if defined($code);
closelog;
openlog "$progname", 'ndelay,pid', $facility or die "$0: openlog: $!\n";
syslog $priority, "%s", $message unless $debug;
die "$progname $facility/$priority $message\n" if $debug;
} 
###########################
# LogInfo
###########################
sub LogInfo()  {
my $facility="authpriv";
my $priority="info";
my $debug=0;
my ($msg,$code)=@_;
my $message=$msg;
$message="$msg"." $code" if defined($code);
closelog;
openlog "$progname", 'ndelay,pid', $facility or die "$0: openlog: $!\n";
syslog $priority, "%s", $msg unless $debug;
warn "$progname $facility/$priority $msg\n" if $debug;
} 

$ldap_user = shift @ARGV;
usage() if (! $ldap_user );

if ($< != 0) {
  print STDERR "You must be root to modify an user\n";
  exit (1);
}

setlogsock('unix')
        if grep /^ $^O $/xo, ("linux", "openbsd", "freebsd", "netbsd");


if (-e "/etc/smbldap-tools/smbldap_bind.conf") {
	$smbldap_bind_conf="/etc/smbldap-tools/smbldap_bind.conf";
} else { &LogDie("Unable to open /etc/smbldap-tools/smbldap_bind.conf for reading !\n"); };

if (-e "/etc/smbldap-tools/smbldap.conf") {
	$smbldap_conf="/etc/smbldap-tools/smbldap.conf";
} else { &LogDie("Unable to open /etc/smbldap-tools/smbldap.conf for reading !\n") ; };

open (CONFIGFILE, "$smbldap_bind_conf") || &LogDie("Unable to open $smbldap_bind_conf for reading !\n");

while (<CONFIGFILE>) {
        chomp($_);
        ## eat leading whitespace
        $_=~s/^\s*//;
        ## eat trailing whitespace
        $_=~s/\s*$//;
        ## throw away comments
        next if (($_=~/^#/) || ($_=~/^;/));
        ## check for a param = value
        if ($_=~/=/) {
                #my ($param, $value) = split (/=/, $_);
                my ($param, $value) = ($_=~/([^=]*)=(.*)/i);
                $param=~s/./\l$&/g;
                $param=~s/\s+//g;
                $value=~s/^\s+//;
                $value=~s/"//g;
                $config_bind_conf{$param} = $value;
                next;
        }
}
close (CONFIGFILE);

open (CONFIGFILE, "$smbldap_conf") || &LogDie("Unable to open $smbldap_conf for reading !\n");

while (<CONFIGFILE>) {
        chomp($_);
        ## eat leading whitespace
        $_=~s/^\s*//;
        ## eat trailing whitespace
        $_=~s/\s*$//;
        ## throw away comments
        next if (($_=~/^#/) || ($_=~/^;/));
        ## check for a param = value
        if ($_=~/=/) {
                #my ($param, $value) = split (/=/, $_);
                my ($param, $value) = ($_=~/([^=]*)=(.*)/i);
                $param=~s/./\l$&/g;
                $param=~s/\s+//g;
                $value=~s/^\s+//;
                $value=~s/"//g;
                $config_conf{$param} = $value;
                next;
        }
}
close (CONFIGFILE);

my $masterdn   = $config_bind_conf{'masterdn'};
my $masterpw   = $config_bind_conf{'masterpw'};
my $masterldap = $config_conf{'masterldap'};
my $masterport = $config_conf{'masterport'};
my $slaveldap = $config_conf{'slaveldap'};
my $usersdn    = $config_conf{'usersdn'};
my $computersdn =  $config_conf{'computersdn'};
my $suffix =  $config_conf{'suffix'};
my $scope =  $config_conf{'scope'};
my $ldap_server_timeout=3 ;

&LogDie("Unable to read smbldap configuration paramaters") unless  $masterdn && $masterpw && $masterldap && $masterport && $usersdn && $slaveldap;

# Build Rdn
my $target_userdn;
$ldap = Net::LDAP -> new($masterldap,port=>$masterport,timeout=>$ldap_server_timeout,version=>3) ;
if ( ! $ldap )  {
 $ldap = Net::LDAP -> new($slaveldap,port=>$masterport,timeout=>$ldap_server_timeout,version=>3) or &LogDie("Unable to open $slaveldap: $@\n");
 $result_ldap = $ldap->bind ($masterdn, password => $masterpw);
 $result_ldap->code && &LogDie("Failed to bind to $slaveldap $result_ldap->error");
} else {
  $result_ldap = $ldap->bind ($masterdn, password => $masterpw);
  $result_ldap->code && &LogDie("Failed to bind to $masterldap $result_ldap->error");
}

# Find users dn
my $mesg = $ldap->search ( # perform a search
				base   => $suffix,
				scope => $scope,
				filter => "(&(objectclass=posixAccount)(uid=$ldap_user))"
					   );

$mesg->code && &LogDie("$mesg->error");
foreach my $entry ($mesg->all_entries) {
	  $target_userdn= $entry->dn;
}
unless ($target_userdn ) { &LogDie("Error: the user $ldap_user doesn't exists\n") ;}


print "Changing password for user $ldap_user\n";


# prompt for new password

my $pass;
my $pass2;

system "/bin/stty -echo";
print "New UNIX password for user $ldap_user: ";
chomp($pass=<STDIN>);
print "\n";
system "/bin/stty echo";

system "/bin/stty -echo";
print "Retype new UNIX password for user $ldap_user: ";
chomp($pass2=<STDIN>);
print "\n";
system "/bin/stty echo";

if ($pass ne $pass2) {
  print "Sorry, passwords do not match\n";
  exit (10);
}


# change unix password
my $hash_password = "{crypt}".crypt($pass, CreateCryptSalt(0)) ;
chomp($hash_password);
my $shadowlastchange=POSIX::ceil(time()/(24*3600));

my @whatToChange;
my @ReplaceArray;
my @DeleteArray;
my @AddArray;
my @NullArray;

push (@ReplaceArray, 'userPassword', $hash_password);
push (@ReplaceArray, 'shadowLastChange', $shadowlastchange);
##################################
# Delete ADMCHG from RFC2307 aix
##################################
push (@ReplaceArray, 'passwordflags',\@NullArray);

if ( @ReplaceArray  ) {
    push @whatToChange, 'replace', \@ReplaceArray;
}
if ( @DeleteArray  ) {
   push ( @whatToChange, 'delete', \@DeleteArray);
}
if ( @AddArray  ) {
   push ( @whatToChange, 'add', \@AddArray) ;
}
if ( @whatToChange  ) {
  $result_ldap=$ldap->modify ( $target_userdn,
    changes => [
	       @whatToChange
    ]
  );
  $result_ldap->code && &LogDie("Failed to update of $target_userdn ldap_error_name($result_ldap->code)");
}

$ldap->unbind;

print "passwd: all authentication tokens updated successfully.\n";

&LogInfo("Updated $target_userdn password successfully.\n");

exit 0;

#########
sub usage {
  print STDERR <<EOM;
Usage: smbpasswd-sync.pl  user
EOM
  exit 2;
 }
__END__

# - The End

=head1 NAME
       
smbpasswd-sync - change user (RFC2307) password in LDAP directory

=head1 SYNOPSIS
       
  smbpasswd-sync.pl user

=head1 DESCRIPTION

smbpasswd-sync.pl change password for LDAP user(RFC2307) accounts. A normal user
cannot change the password for their own account, but only the super user may
change the password for any account. It then delete the aixauxaccount passwordflags 
attribute from the LDAP Directory.

   Password Changes
       The root user is prompted twice for the password.
       
   Configuration
       Al the configuration (LDAP server, binddn and so on) are read from
       the smbldap-tools configuration files.
   
   Usage    
       The principal usage is from smb.conf as follow:
       .
       .
  unix password sync = Yes
  passwd program = /usr/sbin/smbpasswd-sync.pl %u
  passwd chat = *New*UNIX*password* %n\n *Retype*new*UNIX*password* %n\n *passwd:*all*authentication*tokens*updated*successfully*
       .
       .

It log to syslog with facility authpriv.

=head1 SEE ALSO

       passwd(1) 
       smb.conf(5)

=cut

#'
