#!/usr/bin/perl -wl
# cpanel - chemailpass. Based on work by http://prajith.in
# Updated by: Carl Goodwin-Morgan <cgm@sub6.com>
# Version 1.3.0

use strict;
use warnings;

use version 0.77;

#use Getopt::Long;

# TODO:
# Clean up code using GetOpt
# Clean up some of the variable naming, such us user when its really mailbox name (eg before @)

# Generates a random password to use with upper/lowercase, numbers and select special characters, between 10 and 16 characters.
sub random_password {

    # No longer use this use perl builtins but reference incase any issues.
    #my $random_password = `/usr/bin/tr -dc 'A-Za-z0-9_!@.-' < /dev/urandom | /usr/bin/head -c $p_chars`;
    my @p_chars         = ( 'a' .. 'z', 'A' .. 'Z', '0' .. '9', '_', '!', '@', '.', '-' );
    my $p_charsize      = int( rand(6) ) + 10;
    my $random_password = "";
    foreach ( 1 .. $p_charsize ) {
        $random_password .= $p_chars[ rand @p_chars ];
    }
    die("Error: random string wasn't generated properly.") unless ( length($random_password) >= 10 && length($random_password) <= 16 );
    return $random_password;
}

sub get_os_version {
    my $version_file = '/etc/redhat-release';
    my $version      = 0;
    die "ERROR: OS release file $version_file missing.\n" if !-f $version_file;
    open( my $vfh, '<', $version_file ) or die "Cant open $version $!\n";
    while ( my $vfh_line = <$vfh> ) {
        if ( $vfh_line =~ m/^(AlmaLinux release|CentOS release|CentOS Linux release|CloudLinux Server release|CloudLinux release)\s+(\d\.\d+)\.?\d*\s+/ ) {
            $version = $2;
            chomp $version;
        }
        last if $version ne 0;
    }
    close($vfh) or warn "Cant close $vfh $!\n";
    return $version if $version ne 0;
}

my $hashtype   = 0;
my $os_version = get_os_version();
if ( version->parse($os_version) < version->parse('5.10') ) {
    $hashtype = 'md5';
} else {

    # AL/CL8 authconfig is provided by authselect-compat but not installed by default and isntalling this
    # Installs oddjob which don't need either, so instead handle with failover
    if ( -f '/usr/sbin/authconfig' ) {
        if ( `authconfig --test | grep hash` =~ /^\s*password\s+hashing\s+algorithm\s+is\s+(.+)$/ ) {
            $hashtype = $1 if ( $1 =~ m/^(md5|sha256|sha512)$/ );
        } else {
            die "Couldn't find hashtype: $hashtype.\n";
        }
    } else {
        if ( `grep ^ENCRYPT_METHOD /etc/login.defs` =~ /^\s*ENCRYPT_METHOD\s(.+)$/i ) {
            $hashtype = lc $1 if ( $1 =~ m/^(MD5|SHA256|SHA512)$/i );
        } else {
            die "Couldn't find hashtype: $hashtype.\n";
        }
    }
}

## Check args:
#my ( $email, $pass ) = @ARGV;
#my $cli = {};
#my @email;
#if ( @ARGV > 0 ) {
#	GetOptions(
#		'e=s', 		\@email,
#		'p=s',	\$cli->{'password'},
#		'norestart',	\$cli->{'norestart'}
#	);
#} else {
#	die "Usage: $0 -e <email address> -p <optional password> --norestart\n";
#}
my ( $email, $user, $domain, $pass ) = undef;
die "Usage: $0 -e <email address> -p <OPTIONAL_password> --norestart\n" if ( scalar(@ARGV) eq 0 );
if ( $ARGV[0] eq "-e" && grep( "@", $ARGV[1] ) ) {
    $email = $ARGV[1];
} elsif ( grep( "@", $ARGV[0] ) ) {
    $email = $ARGV[0];
} else {
    if ( !$email ) { die "Usage: $0 -e <email address> -p <OPTIONAL_password> --norestart\n"; }
}
if ( scalar(@ARGV) gt 2 ) {
    if ( $ARGV[2] eq "-p" && $ARGV[3] ne "--norestart" ) {
        $pass = $ARGV[3];
    }
}
if ( !$pass ) {
    $pass = random_password();
    print "No password defined, using random password: $pass";
}
my $norestart = undef;
$norestart = 1 if grep( /--norestart/, @ARGV );

# Validate email, domain,user
( $user, $domain ) = split( /\@/, $email );
if ( $email !~ /\@/ || !$domain ) { die "Invalid email address specified\n"; }

## Find user in /etc/userdatadomains and get homedir:
my $homedir;
my $cpanel_user;
open( UDD, "/etc/userdatadomains" ) or die "Can't open /etc/userdatadomains\n";
for (<UDD>) {
    my @bits = split( /==/, $_ );
    my ( $uddom, $uduser ) = split( /: /, $bits[0] );
    if ( $uddom eq $domain ) {
        #$homedir = $bits[4];
        #$homedir =~ s/\/public_html.*//; # This causes issues with domains outside of public_html.
        if ( ! -d "/home/".$uduser ) {
            die "No home directory where expected: /home/${uduser}, /etc/userdatadomains has: " . $bits[4] . "\n";
        }
        $homedir = "/home/".$uduser; # Going to assume using /home
        $cpanel_user = $uduser;
    }

    # print "Debug: $uddom,$uduser,$bits[4],$homedir\n";
}
close UDD;
if ( !$homedir ) {
    die "Couldn't work out homedir. Check email address is correct\n";
}
if ( !defined($cpanel_user) || length $cpanel_user == 0 || $cpanel_user eq '' ) {
    die "Couldn't work out cpanel user.\n";
}

my $newenc = 0;
if ( $hashtype eq 'md5' ) {
    ## Encrypt new password using MD5 salt
    my $md5_salt = '$1$' . substr( crypt( rand() . rand(), '$1$aaaaaaaa$' ), -9, 8 ) . '$';
    $newenc = crypt( $pass, $md5_salt );
} elsif ( $hashtype eq 'sha512' ) {
    my $sha512_salt = '$6$' . substr( crypt( rand() . rand(), '$6$' ), -9, 8 ) . '$';
    $newenc = crypt( $pass, $sha512_salt );
} else {
    die "Sorry don't support hashtype $hashtype.\n";
}

## Alter password file with new password:
#print "Debug: $homedir/etc/$domain/shadow\n";
open( FILE, "$homedir/etc/$domain/shadow" ) or die "Can't open shadow file. Bailing\n";
my @pws = <FILE>;
close FILE;
my @f      = grep( /^$user:/, @pws );
my $fcount = ( $#f + 1 );
if ( !@f || $fcount < 1 ) {
    die "Email account doesn't exist - not created yet?\n";
}
if ( $fcount > 1 ) {
    die "More than one account found, bailing\n";
}

my @bits = split( /:/, $f[0] );
$bits[1] = $newenc;
my $newpwline = join( ':', @bits );
chomp $newpwline;

## Write a new file:
open( OUT, ">$homedir/etc/$domain/shadow" ) or die "Can't write shadow file. Bailing\n";
for (@pws) {
    chomp $_;
    if ( $_ =~ /^$user:/ ) {
        print OUT "$newpwline";
    } else {
        print OUT $_;
    }
}
close OUT;

# Using API set it as well, ideally split the raw writing into its own function.
if ( -f "/usr/bin/uapi" ) {
    my $uapi_changepass = `/usr/bin/uapi --user=$cpanel_user Email passwd_pop email=$user domain='$domain' password='$pass'`;
    print $uapi_changepass;
}

print "Email $email password changed to $pass";
if ( !$norestart ) {
    if ( -f "/scripts/restartsrv_exim" ) {
        system("/scripts/restartsrv_exim");
        print "Restarted exim.\n";
    } else {
        print "Error: cant restart exim, restart script not where expected.\n";
    }
} else {
    print "Skipping exim restart, don't forget to do it.\n";
}

exit 0;
