#!/usr/bin/env perl # passwd - change Webmin users password use strict; use warnings; use 5.010; use File::Basename; use Getopt::Long; use Pod::Usage; use Term::ANSIColor qw(:constants); use lib (dirname(dirname($0))); use WebminCore; sub main { my %opt; GetOptions('help|h' => \$opt{'help'}, 'config|c=s' => \$opt{'config'}, 'user|u=s' => \$opt{'user'}, 'password|p=s' => \$opt{'password'}, 'stdout|o!' => \$opt{'stdout'}); # If username passed as regular param my $user = scalar(@ARGV) == 1 && $ARGV[0]; # Show usage pod2usage(0) if ($opt{'help'} || (!$opt{'user'} && !$user)); # Assign defaults $opt{'config'} ||= "/etc/webmin"; $opt{'user'} = $user if ($user && !$opt{'user'}); # Catch kill signal my $sigkill = sub { system("stty echo"); print "\n^C"; print "\n"; exit 1; }; $SIG{INT} = \&$sigkill; # Run change password command change_password(\%opt); return 0; } exit main(\@ARGV) if !caller(0); sub change_password { my ($optref) = @_; my ($minserv_uconf_file, %lusers, @users, %uinfos, %ulines); my $user = $optref->{'user'}; my $pass = $optref->{'password'}; my $confdif = $optref->{'config'}; my $conf = "$confdif/config"; my $mconf = "$confdif/miniserv.conf"; my $conf_check = sub { my ($configs) = @_; foreach my $config (@{$configs}) { if (!-r $config) { say BRIGHT_RED, "Error: ", RESET, "Failed to read Webmin essential config file: ", BRIGHT_YELLOW, $config, RESET, " doesn't exist"; exit 1; } } }; my $root = root($confdif, \&$conf_check); my $encrypt_password = sub { my ($pass, $gconfig, $config) = @_; my $root = root($confdif, \&$conf_check); # Use pre-defined encryption (forced by Webmin config) if (!$optref->{'stdout'} && ($gconfig->{'md5pass'} == 1 || $gconfig->{'md5pass'} == 2)) { do "$root/acl/md5-lib.pl"; # Use MD5 encryption return &encrypt_md5($pass) if ($gconfig->{'md5pass'}) == 1; # Use SHA512 encryption return &encrypt_sha512($pass) if ($gconfig->{'md5pass'}) == 2; } else { # Try detecting system default first my $module = 'useradmin'; if (-d "$root/$module") { $ENV{'PERLLIB'} = "$root"; $ENV{'WEBMIN_CONFIG'} = "$confdif"; $ENV{'FOREIGN_ROOT_DIRECTORY'} = "$root/$module"; $ENV{'FOREIGN_MODULE_NAME'} = "$module"; chdir("$root/$module"); require "$root/useradmin/user-lib.pl"; # We need to set third parameter to make sure useradmin's config # won't be used for hashing format, as we need to auto detect it return &encrypt_password($pass, undef, 'force_system_detection'); } else { # Use old Unix DES srand(time() ^ $$); return crypt($pass, chr(int(rand(26)) + 65) . chr(int(rand(26)) + 65)); } } }; # Check for main config and miniserv config files &$conf_check([$conf, $mconf]); # Read and parse configs my (%config, %gconfig, %uconfig); read_file($mconf, \%config); read_file($conf, \%gconfig); $minserv_uconf_file = $config{'userfile'}; # Check for main user file &$conf_check([$minserv_uconf_file]); # Read and parse `miniserv.users` config file read_file($minserv_uconf_file, \%lusers, undef, undef, ":"); @users = keys %lusers; map {my @uinfo = split(':', "$lusers{$_}"); $uinfos{$_} = \@uinfo} @users; # Check if user exists if (!defined($uinfos{$user})) { my $user_str = scalar(@users) > 1 ? 'users' : 'user'; my $user_str2 = scalar(@users) > 1 ? 'are' : 'is'; die(BRIGHT_RED, "Error: ", RESET . "Webmin user ", BRIGHT_YELLOW, $user, RESET, " doesn't exist. Existing Webmin $user_str on your system $user_str2 — ", BRIGHT_YELLOW, join(", ", sort(@users)), RESET, "\n"); } # Ask for password on stdin my $suc_pre_msg = ""; my $suc_msg = 'updated successfully'; if (!$pass) { print "Enter password for user ", BRIGHT_YELLOW, $user, RESET, ":"; system("stty -echo"); $pass = ; system("stty echo"); print "\nRetype new password:"; system("stty -echo"); my $pass2 = ; system("stty echo"); print "\n"; if ($pass ne $pass2) { say BRIGHT_RED, "Error: ", RESET, "Passwords do not match"; exit 1; } chomp $pass; if (!$pass) { $suc_pre_msg = BOLD BRIGHT_RED ON_WHITE . 'Warning:' . RESET . " "; $suc_msg = "has been removed, enabling anyone to login without authentication"; } } # Update with new password and store timestamp $uinfos{$user}->[0] = &$encrypt_password($pass, \%gconfig, \%config); # Print the hash and exit if ($optref->{'stdout'}) { say $uinfos{$user}->[0]; exit 0; } $uinfos{$user}->[5] = time() if ($uinfos{$user}->[5]); map {$ulines{$_} = join(":", @{ $uinfos{$_} })} keys %uinfos; # Store original file first copy_source_dest($minserv_uconf_file, "$minserv_uconf_file-"); # Restart Webmin and write new user config file system("$confdif/stop >/dev/null 2>&1"); write_file($minserv_uconf_file, \%ulines, ":"); system("$confdif/start >/dev/null 2>&1"); # Print user message say "${suc_pre_msg}Password for Webmin user ", BRIGHT_YELLOW, $user, RESET, " $suc_msg"; exit 0; } sub root { my ($config, $conf_check) = @_; my $mconf = "$config/miniserv.conf"; $conf_check->([$mconf]); open(my $CONF, "<", $mconf); my $root; while (<$CONF>) { if (/^root=(.*)/) { $root = $1; } } close($CONF); # Does the Webmin root exist? if ($root) { die BRIGHT_RED, "Error: ", BRIGHT_YELLOW, $root, RESET, " is not a directory\n" unless (-d $root); } else { # Try to guess where Webmin lives, since config file didn't know. die BRIGHT_RED, "Error: ", RESET, "Unable to determine Webmin installation directory\n"; } return $root; } 1; =pod =head1 NAME passwd =head1 DESCRIPTION This program allows you to change the password of a user in the Webmin password file =head1 SYNOPSIS webmin passwd [options] =head1 OPTIONS =over =item --help, -h Print this usage summary and exit. Examples of usage: - webmin passwd root - webmin passwd --user root - webmin passwd --user root --password ycwyMQRVAZY - webmin passwd --config /usr/local/etc/webmin --user root --password ycwyMQRVAZY - webmin passwd --config /usr/local/etc/webmin --user root --password ycwyMQRVAZY --stdout =item --config, -c Specify the full path to the Webmin configuration directory. Defaults to C =item --user, -u Existing Webmin user to change password for =item --password, -p Set new user password. Using this option may be unsecure. =back =head1 LICENSE AND COPYRIGHT Copyright 2018 Jamie Cameron Joe Cooper Ilia Rostovtsev