#!/usr/bin/perl
# 
# This program sets/changes passwords on the Anvil! platform (nodes and dashboards).
# 
# Exit codes;
# 0 = Normal exit.
# 1 = The program is not running as root.
# 2 = Failed to connect to database(s).
# 3 = User didn't enter a password or the passwords didn't match.
# 4 = The password file doesn't exist, wasn't readable or was empty.
# 

use strict;
use warnings;
use Anvil::Tools;
use Data::Dumper;
use String::ShellQuote;

my $THIS_FILE           =  ($0 =~ /^.*\/(.*)$/)[0];
my $running_directory   =  ($0 =~ /^(.*?)\/$THIS_FILE$/)[0];
if (($running_directory =~ /^\./) && ($ENV{PWD}))
{
	$running_directory =~ s/^\./$ENV{PWD}/;
}

# Turn off buffering so that the pinwheel will display while waiting for the SSH call(s) to complete.
$| = 1;

# Prevent a discrepency between UID/GID and EUID/EGID from throwing an error.
$< = $>;
$( = $);

my $anvil = Anvil::Tools->new();

# Make sure we're running as 'root'
# $< == real UID, $> == effective UID
if (($< != 0) && ($> != 0))
{
	# Not root
	$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, key => "error_0005"});
	$anvil->nice_exit({exit_code => 1});
}

$anvil->Get->switches({list => [
	"confirm", 
	"delete-pw",
	"force", 
	"local", 
	"new-password", 
	"no-wait", 
	"password-file"], man => $THIS_FILE});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => $anvil->data->{switches}});
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 3, secure => 0, key => "log_0115", variables => { program => $THIS_FILE }});

# Connect
$anvil->Database->connect();
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, secure => 0, key => "log_0132"});
if (not $anvil->data->{sys}{database}{connections})
{
	# No databases, exit.
	$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, key => "error_0003"});
	$anvil->nice_exit({exit_code => 2});
}

# The order that we pick up the new password is;
# 1. If we've been told of a password file, read it
# 2. If the user passed the password with --new-password <secret>, use that.
# 3. Ask the user for the new password.
if ($anvil->data->{switches}{'password-file'})
{
	# Read the password in from the file.
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, secure => 0, list => { "switches::password-file" => $anvil->data->{switches}{'password-file'} }});
	if (-e $anvil->data->{switches}{'password-file'})
	{
		# Read it in and remove the new-line(s), if it(they) exist.
		$anvil->data->{switches}{'new-password'} =  $anvil->Storage->read_file({file => $anvil->data->{switches}{'password-file'}});
		$anvil->data->{switches}{'new-password'} =~ s/\n//gs;
		
		if ($anvil->data->{switches}{'delete-pw'})
		{
			# Delete the password file.
			$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, key => "log_0263", variables => {
				file => $anvil->data->{switches}{'password-file'}, 
			}});
			unlink $anvil->data->{switches}{'password-file'};
		}
	}
	else
	{
		# The file doesn't exist.
		$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, key => "error_0008", variables => { file => $anvil->data->{switches}{'password-file'} }});
		$anvil->nice_exit({exit_code => 4});
	}
}
elsif (not $anvil->data->{switches}{'new-password'})
{
	print $anvil->Words->string({key => "message_0018"})."\n";
	# Turn off echo
	$anvil->System->stty_echo({set => "off"});
	my $password1 = <STDIN>;
	chomp($password1);
	$password1 =~ s/^\s+//;
	$password1 =~ s/\s+$//;
	# Turn echo on
	$anvil->System->stty_echo({set => "on"});
	
	if (not $password1)
	{
		print $anvil->Words->string({key => "error_0006"})."\n";
		$anvil->nice_exit({exit_code => 3});
	}
	
	print $anvil->Words->string({key => "message_0019"})."\n";
	# Turn off echo
	$anvil->System->stty_echo({set => "off"});
	my $password2 = <STDIN>;
	chomp($password2);
	$password2 =~ s/^\s+//;
	$password2 =~ s/\s+$//;
	# Turn echo on
	$anvil->System->stty_echo({set => "on"});
	
	if ($password1 eq $password2)
	{
		$anvil->data->{switches}{'new-password'} = $password1;
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, secure => 1, list => { "switches::new-password" => $anvil->data->{switches}{'new-password'} }});
	}
	else
	{
		$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, key => "error_0007"});
		$anvil->nice_exit({exit_code => 3});
	}
}

if (not $anvil->data->{switches}{confirm})
{
	print $anvil->Words->string({key => "message_0020"})."\n";
	print $anvil->Words->string({key => "message_0021"})." ";
	my $answer = <STDIN>;
	   $answer = "" if not defined $answer;
	chomp($answer);
	if ($answer !~ /^y/)
	{
		# Abort.
		print $anvil->Words->string({key => "message_0022"})."\n";
		$anvil->nice_exit({exit_code => 1});
	}
}

$anvil->Database->get_hosts();

# Check to see that all machines are online, unless we're a node or DR host and --local was used.
my $host_type = $anvil->Get->host_type();
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, secure => 1, list => { host_type => $host_type }});
if (($anvil->data->{switches}{'local'}) && ($host_type ne "striker"))
{
	# Updating passwords on this host only.
	$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, key => "log_0259"});
	sleep 2;
}
else
{
	# Are we a striker?
	if ($host_type ne "striker")
	{
		# This must be run on a Striker dashboard (unless used with '--local')
		$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, priority => "err", key => "error_0317"});
		$anvil->nice_exit({exit_code => 1});
	}
	
	check_host_access($anvil);
}

# Wait for any in-progress jobs to end.
if (not $anvil->data->{switches}{'no-wait'})
{
	wait_for_jobs($anvil);
}

update_passwords($anvil);

if (not $anvil->data->{switches}{'local'})
{
	# Updating passwords on all systems
	$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, key => "warning_0015"});
	sleep 2;

	# Update other hosts.
	update_other_hosts($anvil);
	
	# Update Anvil! node passwords.
	update_anvils($anvil);
}

$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, key => "log_0750"});
$anvil->nice_exit({exit_code => 0});


#############################################################################################################
# Functions                                                                                                 #
#############################################################################################################

# This updates the Anvil! node passwords (which will trigger IPMI BMC password updates)
sub update_anvils
{
	my ($anvil) = @_;
	
	my $anvil_count = keys %{$anvil->data->{anvils}{anvil_name}};
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { anvil_count => $anvil_count }});
	if (not $anvil_count)
	{
		# No Anvil! nodes found, updates not required.
		$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, key => "log_0260"});
		return(0);
	}
	
	# Updating the Anvil! node passwords.
	$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, key => "log_0261"});
	my $updated = 0;
	foreach my $anvil_name (sort {$a cmp $b} keys %{$anvil->data->{anvils}{anvil_name}})
	{
		my $anvil_uuid            = $anvil->data->{anvils}{anvil_name}{$anvil_name}{anvil_uuid};
		my $anvil_description     = $anvil->data->{anvils}{anvil_name}{$anvil_name}{anvil_description};
		my $old_anvil_password    = $anvil->data->{anvils}{anvil_name}{$anvil_name}{anvil_password};
		my $anvil_node1_host_uuid = $anvil->data->{anvils}{anvil_name}{$anvil_name}{anvil_node1_host_uuid};
		my $anvil_node2_host_uuid = $anvil->data->{anvils}{anvil_name}{$anvil_name}{anvil_node2_host_uuid};
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
			"s1:anvil_name"            => $anvil_name,
			"s2:anvil_uuid"            => $anvil_uuid, 
			"s3:anvil_description"     => $anvil_description, 
			"s4:old_anvil_password"    => $anvil->Log->is_secure($old_anvil_password), 
			"s5:anvil_node1_host_uuid" => $anvil_node1_host_uuid, 
			"s6:anvil_node2_host_uuid" => $anvil_node2_host_uuid, 
		}});
		
		next if $anvil_description eq "DELETED";
		$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, key => "log_0282", variables => {
			name        => $anvil_name, 
			description => $anvil_description, 
		}});
		if ($old_anvil_password ne $anvil->data->{switches}{'new-password'})
		{
			# Update
			$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, key => "log_0291"});
			$anvil->Database->insert_or_update_anvils({
				debug                 => 2, 
				anvil_uuid            => $anvil_uuid, 
				anvil_description     => $anvil_description, 
				anvil_name            => $anvil_name, 
				anvil_password        => $anvil->data->{switches}{'new-password'}, 
				anvil_node1_host_uuid => $anvil_node1_host_uuid, 
				anvil_node2_host_uuid => $anvil_node2_host_uuid, 
			});
			
			$updated = 1;
			$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { updated => $updated }});
		}
		else
		{
			# Update not needed
			$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, key => "log_0293"});
		}
	}
	
	if ($updated)
	{
		# Show the IPMI simplified password warning
		$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, key => "log_0304"});
	}
	
	return(0);
}

# This walks through the other hosts and updates them using this same program.
sub update_other_hosts
{
	my ($anvil) = @_;
	
	# Updating the passwords on all other hosts now.
	$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, key => "log_0305"});
	foreach my $host_type ("striker", "node", "dr")
	{
		my $type_count = exists $anvil->data->{sys}{hosts}{by_type} ? keys %{$anvil->data->{sys}{hosts}{by_type}{$host_type}{host_name}} : 0;
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
			"s1:host_type"  => $host_type, 
			"s2:type_count" => $type_count, 
		}});
		next if not $type_count;
		foreach my $host_name (sort {$a cmp $b} keys %{$anvil->data->{sys}{hosts}{by_type}{$host_type}{host_name}})
		{
			my $host_uuid       = $anvil->data->{sys}{hosts}{by_type}{$host_type}{host_name}{$host_name}{host_uuid};
			my $short_host_name = $anvil->data->{hosts}{host_uuid}{$host_uuid}{short_host_name};
			my $host_status     = $anvil->data->{hosts}{host_uuid}{$host_uuid}{host_status};
			my $anvil_uuid      = $anvil->data->{hosts}{host_uuid}{$host_uuid}{anvil_uuid};
			my $offline_host    = exists $anvil->data->{offline_hosts}{$host_uuid} ? $anvil->data->{offline_hosts}{$host_uuid} : 0;
			my $target_ip       = $anvil->data->{'connect'}{$host_uuid}{target_ip};
			my $password        = $anvil->data->{'connect'}{$host_uuid}{password};
			my $password_file   = "/tmp/anvil-pw.txt";
			$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
				"s1:host_name"       => $host_name, 
				"s2:host_uuid"       => $host_uuid,
				"s3:short_host_name" => $short_host_name, 
				"s4:host_status"     => $host_status, 
				"s5:anvil_uuid"      => $anvil_uuid, 
				"s6:offline_host"    => $offline_host, 
				"s7:target_ip"       => $target_ip, 
				"s8:password"        => $anvil->Log->is_secure($password), 
				"s9:password_file"   => $password_file, 
			}});
			next if $host_uuid eq $anvil->Get->host_uuid;
			next if $offline_host;
			
			my ($failed) = $anvil->Storage->write_file({
				debug     => 2,
				secure    => 1, 
				file      => $password_file, 
				body      => $anvil->data->{switches}{'new-password'}, 
				user      => "root", 
				group     => "root", 
				mode      => "0600",
				overwrite => 1,
				backup    => 0,
				target    => $target_ip,
				password  => $password, 
			});
			$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { failed => $failed }});
			
			if ($host_type eq "striker")
			{
				# Updating the passwords on the striker peer
				$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, key => "log_0306", variables => {
					host_name => $short_host_name,
				}});
			}
			elsif ($host_type eq "node")
			{
				# Updating the passwords on the subnode
				$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, key => "log_0307", variables => {
					host_name => $short_host_name,
				}});
			}
			else
			{
				# Updating the passwords on the DR host
				$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, key => "log_0308", variables => {
					host_name => $short_host_name,
				}});
			}
			my $force      = $anvil->data->{switches}{force} ? " --force" : "";
			my $shell_call = $anvil->data->{path}{exe}{'anvil-change-password'}.$anvil->Log->switches.$force." --local --confirm --delete-pw --password-file ".$password_file;
			$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, secure => 1, list => { shell_call => $shell_call }});

			my ($output, $error, $return_code) = $anvil->Remote->call({
				debug      => 2, 
				shell_call => $shell_call, 
				target     => $target_ip, 
				password   => $password, 
			});
			$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
				output      => $output, 
				error       => $error, 
				return_code => $return_code, 
			}});
			
			# If there was a failure, abort.
			if ($return_code)
			{
				# Failed!
				$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, priority => "err", key => "error_0318", variables => {
					host_name => $short_host_name, 
					output    => $output, 
				}});
				$anvil->nice_exit({exit_code => 1});
			}
		}
	}
	
	return(0);
}

# This waits until all jobs are done.
sub wait_for_jobs
{
	my ($anvil) = @_;
	
	my $waiting = 1;
	# To minimize the chance of interruption, we will now wait for any running jobs to finish.
	$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, key => "log_0312"});
	while ($waiting)
	{
		$waiting = 0;
		$anvil->Database->get_jobs();
		foreach my $job_uuid (sort {$a cmp $b} keys %{$anvil->data->{jobs}{running}})
		{
			my $job_host_uuid       = $anvil->data->{jobs}{running}{$job_uuid}{job_host_uuid};
			my $job_short_host_name = $anvil->data->{hosts}{host_uuid}{$job_host_uuid}{short_host_name};
			my $job_host_offline    = exists $anvil->data->{offline_hosts}{$job_host_uuid} ? $anvil->data->{offline_hosts}{$job_host_uuid} : 0;
			my $job_progress        = $anvil->data->{jobs}{running}{$job_uuid}{job_progress}; 
			my $job_command         = $anvil->data->{jobs}{running}{$job_uuid}{job_command};
			$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
				"s1:job_uuid"            => $job_uuid, 
				"s2:job_host_uuid"       => $job_host_uuid, 
				"s3:job_short_host_name" => $job_short_host_name, 
				"s4:job_host_offline"    => $job_host_offline, 
				"s5:job_progress"        => $job_progress, 
				"s6:job_command"         => $job_command, 
			}});
			
			next if $job_host_offline;
			next if (($job_progress == 0) or ($job_progress == 100));
			
			# Show the job we're waiting on
			$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, key => "log_0326", variables => {
				time_stamp => $anvil->Get->date_and_time({time_only => 1}), 
				host_name  => $job_short_host_name, 
				progress   => $job_progress, 
				command    => $job_command, 
			}});
			$waiting = 1;
			$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { waiting => $waiting }});
		}
		
		if ($waiting)
		{
			# Waiting for job(s) to finish.
			$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, key => "log_0321"});
			sleep 5;
		}
	}
	# No running jobs, proceeding.
	$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, key => "log_0323"});
	
	return(0);
}

# This checks for access to all hosts. If any are not accessible, exit (unless '--force' is used)
sub check_host_access
{
	my ($anvil) = @_;
	
	# Build a list of all known passwords
	my $new_password = $anvil->data->{switches}{'new-password'};
	$anvil->data->{passwords}{$new_password} = 1;
	foreach my $db_host_uuid (sort {$a cmp $b} keys %{$anvil->data->{database}})
	{
		my $password = $anvil->data->{database}{$db_host_uuid}{password};
		$anvil->data->{passwords}{$password} = 1;
	}
	foreach my $anvil_name (sort {$a cmp $b} keys %{$anvil->data->{anvils}{anvil_name}})
	{
		my $password = $anvil->data->{anvils}{anvil_name}{$anvil_name}{anvil_password};
		$anvil->data->{passwords}{$password} = 1;
	}
	foreach my $password (sort {$a cmp $b} keys %{$anvil->data->{passwords}})
	{
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, secure => 1, list => { password => $password }});
	}
	my $password_count = keys %{$anvil->data->{passwords}};
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { password_count => $password_count }});
	
	my $all_online = 1;
	if ($anvil->data->{switches}{'local'})
	{
		# We're a Striker, so we need to update other host's anvil.conf, so checking for access to all hosts.
		$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, key => "log_0327"});
	}
	else
	{
		# Verifying access to all hosts.
		$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, key => "log_0328"});
	}
	foreach my $host_type ("striker", "node", "dr")
	{
		my $type_count = exists $anvil->data->{sys}{hosts}{by_type} ? keys %{$anvil->data->{sys}{hosts}{by_type}{$host_type}{host_name}} : 0;
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
			"s1:host_type"  => $host_type, 
			"s2:type_count" => $type_count, 
		}});
		next if not $type_count;
		if ($host_type eq "striker")
		{
			# Checking that Strikers are online.
			$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, key => "log_0329"});
		}
		elsif ($host_type eq "node")
		{
			# Checking that sub-nodes are online.
			$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, key => "log_0330"});
		}
		elsif ($host_type eq "dr")
		{
			# Checking that sub-nodes are online.
			$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, key => "log_0332"});
		}
		foreach my $host_name (sort {$a cmp $b} keys %{$anvil->data->{sys}{hosts}{by_type}{$host_type}{host_name}})
		{
			my $host_uuid                    = $anvil->data->{sys}{hosts}{by_type}{$host_type}{host_name}{$host_name}{host_uuid};
			my $short_host_name              = $anvil->data->{hosts}{host_uuid}{$host_uuid}{short_host_name};
			my $host_status                  = $anvil->data->{hosts}{host_uuid}{$host_uuid}{host_status};
			my $anvil_uuid                   = $anvil->data->{hosts}{host_uuid}{$host_uuid}{anvil_uuid};
			my ($target_ip, $target_network) = $anvil->Network->find_target_ip({host_uuid => $host_uuid});
			$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
				"s1:host_name"       => $host_name, 
				"s2:host_uuid"       => $host_uuid,
				"s3:short_host_name" => $short_host_name, 
				"s4:host_status"     => $host_status, 
				"s5:anvil_uuid"      => $anvil_uuid, 
				"s6:target_ip"       => $target_ip, 
				"s7:target_network"  => $target_network, 
			}});
			
			next if $host_uuid eq $anvil->Get->host_uuid;
			# Testing access 
			$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, key => "log_0333", variables => {
				host_name => $short_host_name, 
				target_ip => $target_ip, 
			}});
			
			my $access = $anvil->Remote->test_access({target => $target_ip});
			$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { access => $access }});
			
			if ($access)
			{
				# OK.
				$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, key => "log_0334"});
				$anvil->data->{offline_hosts}{$host_uuid}        = 0;
				$anvil->data->{'connect'}{$host_uuid}{target_ip} = $target_ip;
				$anvil->data->{'connect'}{$host_uuid}{password}  = "";
				$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
					"s1:offline_hosts::${host_uuid}"      => $anvil->data->{offline_hosts}{$host_uuid}, 
					"s2:connect::${host_uuid}::target_ip" => $anvil->data->{'connect'}{$host_uuid}{target_ip},
					"s2:connect::${host_uuid}::password"  => $anvil->data->{'connect'}{$host_uuid}{target_ip},
				}});
			}
			else
			{
				# Try again with different passwords. 
				# Failed! Will now try with known password(s).\n";
				$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, key => "log_0335", variables => {
					count => $password_count, 
				}});
				my $connected = 0;
				my $i         = 1;
				foreach my $password (sort {$a cmp $b} keys %{$anvil->data->{passwords}})
				{
					$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, secure => 1, list => { password => $password }});
					
					# Testing access...
					$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, key => "log_0336", variables => {
						host_name => $short_host_name, 
						target_ip => $target_ip, 
						i         => $i, 
					}});
					my $access = $anvil->Remote->test_access({
						target   => $target_ip,
						password => $password, 
					});
					$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { access => $access }});
					
					if ($access)
					{
						# Success!
						$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, key => "log_0337"});
						$connected                                       = 1;
						$anvil->data->{'connect'}{$host_uuid}{target_ip} = $target_ip;
						$anvil->data->{'connect'}{$host_uuid}{password}  = "";
						$anvil->data->{offline_hosts}{$host_uuid}        = 0;
						$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
							"s1:connected"                        => $connected,
							"s2:offline_hosts::${host_uuid}"      => $anvil->data->{offline_hosts}{$host_uuid}, 
							"s3:connect::${host_uuid}::target_ip" => $anvil->data->{'connect'}{$host_uuid}{target_ip},
							"s4:connect::${host_uuid}::password"  => $anvil->data->{'connect'}{$host_uuid}{target_ip},
						}});
						last;
					}
					else
					{
						# Failed
						$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, key => "log_0338"});
					}
				}
				
				if (not $connected)
				{
					# All known passwords failed
					$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, key => "log_0339", variables => {
						host_name => $short_host_name, 
					}});
					$all_online                               = 0;
					$anvil->data->{offline_hosts}{$host_uuid} = 1;
					$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
						all_online                    => $all_online,
						"offline_hosts::${host_uuid}" => $anvil->data->{offline_hosts}{$host_uuid}, 
					}});
				}
			}
		}
	}
	
	if ($all_online)
	{
		# All hosts are accessible! Password update can proceed.
		$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, key => "log_0340"});
	}
	elsif ($anvil->data->{switches}{force})
	{
		# Not all hosts are online, but '--force' was used!
		$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, key => "log_0341"});
		sleep 5;
	}
	else
	{
		# One or more machines in the Anvil! are not accessible.
		$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, priority => "err", key => "error_0319"});
		$anvil->nice_exit({exit_code => 1});
	}
	
	return(0);
}

# This updates the local passwords.
sub update_passwords
{
	my ($anvil) = @_;
	
	# First, we update ourself (whether local or all)
	my $host_type    = $anvil->Get->host_type;
	my $host_uuid    = $anvil->Get->host_uuid;
	my $old_password = $anvil->data->{database}{$host_uuid}{password};
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
		"s1:host_type"    => $host_type, 
		"s2:host_uuid"    => $host_uuid, 
		"s3:old_password" => $anvil->Log->is_secure($old_password), 
	}});
	
	if ($host_type eq "striker")
	{
		# Update the 'admin' user password in the database.
		my $user = "admin";
		$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, key => "message_0024", variables => { user => $user }});
		
		my $user_uuid = $anvil->Database->insert_or_update_users({
			debug               => 2,
			user_name           => $user, 
			user_password_hash  => $anvil->data->{switches}{'new-password'}, 
			user_is_admin       => 1, 
			user_is_experienced => 1, 
			user_is_trusted     => 1, 
		});
		
		# Log out any Striker sessions.
		$anvil->Account->logout({host_uuid => "all"});
		$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, key => "message_0025"});
		
		# Validate
		my $valid = $anvil->Account->validate_password({
			debug    => 2,
			user     => $user,
			password => $anvil->data->{switches}{'new-password'},
		});
		
		### NOTE: We directly connect to the local 'template1' database as postgres
		# Update the database passwords
		my $dbh = DBI->connect("DBI:Pg:dbname=template1;host=localhost;port=5432", "postgres", $old_password, {
			RaiseError     => 1,
			AutoCommit     => 1,
			pg_enable_utf8 => 1
		});
		my $query = "SELECT a.datname, b.usename FROM pg_catalog.pg_database a, pg_catalog.pg_user b WHERE a.datdba = b.usesysid AND a.datistemplate IS NOT TRUE AND a.datname != 'postgres'";
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { query => $query }});
		my $DBreq = $dbh->prepare($query) or $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0075", variables => { 
			query    => $query, 
			server   => "localhost",
			db_error => $DBI::errstr, 
		}});
		
		# Execute on the query
		$DBreq->execute() or $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0076", variables => { 
			query    => $query, 
			server   => "localhost",
			db_error => $DBI::errstr, 
		}});
		
		# Return the array
		my $results       = $DBreq->fetchall_arrayref();
		my $database_name = $results->[0]->[0];
		my $owner_name    = $results->[0]->[1];
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
			database_name => $database_name,
			owner_name    => $owner_name, 
		}});
		foreach my $user ("postgres", $owner_name)
		{
			$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, key => "message_0026", variables => { user => $user }});
			my $query = "ALTER ROLE ".$user." WITH PASSWORD ".$dbh->quote($anvil->data->{switches}{'new-password'});
			$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { query => $query }});
			$dbh->do($query) or $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0090", variables => { 
				query    => $anvil->Log->is_secure($query), 
				server   => "localhost",
				db_error => $DBI::errstr, 
			}});
			$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, key => "message_0025"});
		}
		
		# Update our anvil.conf and all the other machines in the cluster.
		$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, key => "message_0027", variables => { file => $anvil->data->{path}{configs}{'anvil.conf'} }});
		$anvil->Storage->update_config({
			debug    => 2,
			secure   => 1, 
			variable => "database::${host_uuid}::password", 
			value    => $anvil->data->{switches}{'new-password'},
		});
		$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, key => "message_0025"});
		
		foreach my $host_type ("striker", "node", "dr")
		{
			my $type_count = exists $anvil->data->{sys}{hosts}{by_type} ? keys %{$anvil->data->{sys}{hosts}{by_type}{$host_type}{host_name}} : 0;
			$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
				"s1:host_type"  => $host_type, 
				"s2:type_count" => $type_count, 
			}});
			next if not $type_count;
			foreach my $host_name (sort {$a cmp $b} keys %{$anvil->data->{sys}{hosts}{by_type}{$host_type}{host_name}})
			{
				my $host_uuid       = $anvil->data->{sys}{hosts}{by_type}{$host_type}{host_name}{$host_name}{host_uuid};
				my $short_host_name = $anvil->data->{hosts}{host_uuid}{$host_uuid}{short_host_name};
				my $host_status     = $anvil->data->{hosts}{host_uuid}{$host_uuid}{host_status};
				my $anvil_uuid      = $anvil->data->{hosts}{host_uuid}{$host_uuid}{anvil_uuid};
				my $offline_host    = exists $anvil->data->{offline_hosts}{$host_uuid} ? $anvil->data->{offline_hosts}{$host_uuid} : 0;
				my $target_ip       = $anvil->data->{'connect'}{$host_uuid}{target_ip};
				my $password        = $anvil->data->{'connect'}{$host_uuid}{password};
				$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
					"s1:host_name"       => $host_name, 
					"s2:host_uuid"       => $host_uuid,
					"s3:short_host_name" => $short_host_name, 
					"s4:host_status"     => $host_status, 
					"s5:anvil_uuid"      => $anvil_uuid, 
					"s6:offline_host"    => $offline_host, 
					"s7:target_ip"       => $target_ip, 
					"s8:password"        => $anvil->Log->is_secure($password), 
				}});
				next if $host_uuid eq $anvil->Get->host_uuid;
				next if $offline_host;
				
				$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, key => "message_0146", variables => { 
					file      => $anvil->data->{path}{configs}{'anvil.conf'},
					host_name => $short_host_name, 
				}});
				my $problem = $anvil->Storage->update_config({
					debug    => 2,
					secure   => 1, 
					target   => $target_ip, 
					password => $password, 
					variable => "database::".$anvil->Get->host_uuid."::password", 
					value    => $anvil->data->{switches}{'new-password'},
				});
				$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { problem => $problem }});
				if ($problem)
				{
					# NOTE: This prints the new password to the screen, but given the user gave it to us, I think it's OK.
					$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, key => "warning_0014", variables => {
						file => $anvil->data->{switches}{'new-password'}, 
						line => "database::".$anvil->Get->host_uuid."::password	=	".$anvil->data->{switches}{'new-password'},
					}});
				}
				else
				{
					$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, key => "message_0025"});
				}
			}
		}
		
	}
	
	# Update the local users.
	my $users   = ["admin", "root"];
	my $vncuser = "";
	
	# Is the hacluster user here?
	if ($host_type eq "node")
	{
		# Yup.
		push @{$users}, "hacluster";
	}
	
	my $vnc_user_file = "/etc/tigervnc/vncserver.users";
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { vnc_user_file => $vnc_user_file }});
	if (-f $vnc_user_file)
	{
		# Do we have a vnc user?
		my $body = $anvil->Storage->read_file({debug => 2, file => $vnc_user_file});
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { body => $body }});
		foreach my $line (split/\n/, $body)
		{
			if ($line =~ /^:2=(.*)$/)
			{
				$vncuser = $1;
				$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { vncuser => $vncuser }});
				
				if (($vncuser) && ($vncuser ne "admin") && ($vncuser ne "root") && ($vncuser ne "hacluster"))
				{
					push @{$users}, $vncuser;
				}
			}
		}
	}
	
	foreach my $user (@{$users})
	{
		$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, key => "message_0028", variables => { user => $user }});
		$anvil->System->change_shell_user_password({debug => 2, user => $user, new_password => $anvil->data->{switches}{'new-password'}});
		$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, key => "message_0025"});
	}
	
	if ($vncuser)
	{
		# Update the VNC password.
		$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, key => "log_0342", variables => {
			vncuser => $vncuser, 
		}});
		my $escaped_password =  shell_quote($anvil->data->{switches}{'new-password'});
		my $password_file    =  $anvil->Get->users_home({user => $vncuser})."/.config/tigervnc/passwd";
		   $password_file    =~ s/\/\//\//g;
		my $shell_call       =  $anvil->data->{path}{exe}{echo}." ".$escaped_password." | ".$anvil->data->{path}{exe}{vncpasswd}." -f > ".$password_file;
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, secure => 1, list => { shell_call => $shell_call }});
		my ($output, $return_code) = $anvil->System->call({debug => 2, shell_call => $shell_call});
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
			output      => $output, 
			return_code => $return_code, 
		}});
	}
	
	# All done!
	$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, key => "message_0029"});
	
	return(0);
}
