#!/usr/bin/perl

use Net::LDAP;
use Term::ReadPassword;
use Digest::SHA1;
use MIME::Base64;
use Data::UUID;
use Crypt::Cracklib;

my $PASSWD_MIN_LEN = 8;
my $password;

# parse RC file
# $ENV{GL_RC} = "/home/gitolite/.gitolite.rc";
die "parse $ENV{GL_RC} failed: " . ($! or $@) unless do $ENV{GL_RC};

# These come from .gitolite.rc file
our ($GL_LDAP_HOST, $GL_LDAP_BIND_DN, $GL_LDAP_BIND_PASSWORD, $GL_LDAP_USER_DN);

$Term::ReadPassword::ALLOW_STDIN = 1;

# NOTICE: For some reason Perl fails to disable terminal echo
# so following warning about ECHO must be given to the user

# Warn about password echo because of bugs in Perl ReadPasword
print "\nNOTE THAT THE PASSWORD WILL BE ECHOED TO THE SCREEN!\n" .
"Please make sure no one is shoulder-surfing, and make sure\n" .
"you clear your screen and scrollback history after you are done\n" .
"(or close your terminal session).\n\n";

print "Please type in your new password at the prompt.\n\n" .
"Following special keys are available while typing:\n" .
"  <BackSpace> key to remove the last character\n" .
"  <Ctrl-U> to remove all characters\n" .
"  <Ctrl-C> to terminate password change operation\n" .
"  <Enter>  to end password typing\n";

while ( 1 ) {

      print "\n";  # Start reading with new line
      $password = read_password("Enter new password: ", 0, 1);

      # Check the validity of new password
      if ( length( $password ) >= $PASSWD_MIN_LEN  # require minimum length
      && $password =~ /([\x20-\x7E])/   # require printable characters
      && $password =~ /[a-z]/           # require lower case letter
      && $password =~ /[A-Z]/           # require upper case letter
      && $password =~ /[0-9]/           # require number
      && check( $password ) )           # require other than dictionary words
      {
        # Re-enter new password to check possible typos
        if ( $password ne read_password("Enter password again: ") ) {

          print "Passwords do not match!\n";
          redo;
        } else {

          last; # Password is valid and there are no typos, so break out
        }
      } else { # Given password is not valid

        print "Password must contain at least $PASSWD_MIN_LEN characters and numbers,\n" .
        "must have both upper and lower case characters,\n" .
        "can have special characters like !,",#,...\n" .
        "but cannot be any valid dictionary word.\n";
        redo;
      }
}

# Create hash from the password to be stored to the LDAP
my $ctx = Digest::SHA1->new();
my $ug = new Data::UUID;
my $salt = $ug->create_b64();
$ctx->add( $password );
$ctx->add( $salt );
$password = '{SSHA}' . encode_base64( $ctx->digest . $salt, '' );

# Create communication structure for LDAP connection
my $ldap = Net::LDAP->new( $GL_LDAP_HOST ) or die "$@";
my $r = $ldap->start_tls( verify => 'none',
                          sslversion => 'tlsv1' );
if ( $r->code ) {
      print "Password handling failed with $r->code return code!\n";
      log_it( "Password change, LDAP connection failed for $ENV{GL_USER}" );
      exit 1;
}

# Bind to LDAP with proper user
$r = $ldap->bind( $GL_LDAP_BIND_DN,
                  password => $GL_LDAP_BIND_PASSWORD );
if ( $r->code ) {
      print "Password update failed with $r->code return code!\n";
      log_it( "Password change, LDAP bind failed for $ENV{GL_USER}" );
      exit 1;
}

# Update new password to the LDAP
$r = $ldap->modify( "uid=$ENV{GL_USER},
                    $GL_LDAP_USER_DN",
                    replace => { 'userPassword', $password } );

if ( $r->code ) {
      print "Password change failed!\n" .
      "Please contact administrator to change password.\n";
#      log_it( "Password change, LDAP modify failed for $ENV{GL_USER}" );
} else {
      print "Password changed succesfully.\n";
#      log_it( "Password change, LDAP modify done for $ENV{GL_USER}" );
}

$r = $ldap->unbind();