diff --git a/Anvil/Tools.pm b/Anvil/Tools.pm index 75ddd2e5..e2791e9f 100644 --- a/Anvil/Tools.pm +++ b/Anvil/Tools.pm @@ -1061,6 +1061,7 @@ sub _set_paths 'anvil-file-details' => "/usr/sbin/anvil-file-details", 'anvil-maintenance-mode' => "/usr/sbin/anvil-maintenance-mode", 'anvil-manage-firewall' => "/usr/sbin/anvil-manage-firewall", + 'anvil-manage-keys' => "/usr/sbin/anvil-manage-keys", 'anvil-manage-power' => "/usr/sbin/anvil-manage-power", 'anvil-report-memory' => "/usr/sbin/anvil-report-memory", 'anvil-update-files' => "/usr/sbin/anvil-update-files", diff --git a/Anvil/Tools/Database.pm b/Anvil/Tools/Database.pm index 6ecf57f9..072f4983 100644 --- a/Anvil/Tools/Database.pm +++ b/Anvil/Tools/Database.pm @@ -4517,8 +4517,6 @@ sub insert_or_update_states my $state_host_uuid = defined $parameter->{state_host_uuid} ? $parameter->{state_host_uuid} : $anvil->data->{sys}{host_uuid}; my $state_note = defined $parameter->{state_note} ? $parameter->{state_note} : ""; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { - uuid => $uuid, - "cache::database_handle::${uuid}" => $anvil->data->{cache}{database_handle}{$uuid}, uuid => $uuid, file => $file, line => $line, diff --git a/Anvil/Tools/Remote.pm b/Anvil/Tools/Remote.pm index dba3542f..13791569 100644 --- a/Anvil/Tools/Remote.pm +++ b/Anvil/Tools/Remote.pm @@ -471,6 +471,7 @@ sub call elsif ($connect_output =~ /IDENTIFICATION HAS CHANGED/i) { # Host's ID has changed, rebuilt? Find the line and file to tell the user. + my $user = getpwuid($<); foreach my $line (split/\n/, $connect_output) { $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { line => $line }}); @@ -487,8 +488,23 @@ sub call $message_key = "message_0149"; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { i => $i, message_key => $message_key }}); - # TODO: Store this in the states table and have a function that makes - # removing the offending line from the WebUI. + # If I have a database connection, record this bad entry in 'states'. + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { 'sys::database::connections' => $anvil->data->{sys}{database}{connections} }}); + if (not $anvil->data->{sys}{database}{connections}) + { + # Try to connect + $anvil->Database->connect(); + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 3, secure => 0, key => "log_0132"}); + } + if ($anvil->data->{sys}{database}{connections}) + { + my ($state_uuid) = $anvil->Database->insert_or_update_states({ + debug => 2, + state_name => "host_key_changed::".$target."::".$user, + state_note => "file=".$bad_file.",line=".$bad_line, + }); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { state_uuid => $state_uuid }}); + } } elsif ($connect_output =~ /Host key verification failed/i) { diff --git a/cgi-bin/striker b/cgi-bin/striker index 7424cc00..b6e83549 100755 --- a/cgi-bin/striker +++ b/cgi-bin/striker @@ -388,6 +388,10 @@ sub process_striker_menu { process_power($anvil, "poweroff"); } + elsif ($anvil->data->{cgi}{task}{value} eq "keys") + { + process_keys($anvil, "poweroff"); + } else { # What we show for the reboot icon and text depends on if a reboot is pending. @@ -433,6 +437,23 @@ sub process_striker_menu install_target_subtask => $install_target_subtask, }}); + # Are there any bad keys? + my $query = "SELECT state_uuid, state_note FROM states WHERE state_name LIKE 'host_key_changed::%';"; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { query => $query }}); + + my $results = $anvil->Database->query({query => $query, source => $THIS_FILE, line => __LINE__}); + my $count = @{$results}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { + results => $results, + count => $count, + }}); + my $broken_key_icon = $count ? "broken_key_icon_on.png" : "broken_key_icon_off.png"; + my $broken_key_message = $count ? "#!string!striker_0132!#" : "#!string!striker_0131!#"; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { + broken_key_icon => $broken_key_icon, + broken_key_message => $broken_key_message, + }}); + # The 'back' goes home $anvil->data->{form}{back_link} = "?"; $anvil->data->{form}{refresh_link} = "?striker=true"; @@ -442,6 +463,8 @@ sub process_striker_menu install_target_icon => $install_target_icon, install_target_title => $install_target_title, install_target_subtask => $install_target_subtask, + broken_key_icon => $broken_key_icon, + broken_key_message => $broken_key_message, }}); } @@ -469,6 +492,104 @@ sub process_jobs_menu return(0); } +# This handles removing old keys. +sub process_keys +{ + my ($anvil) = @_; + + ### TODO: Left off here... Create a check-box list of state_uuid(s) the user selected, if any, and store them as a comma-separated list. + + ### NOTE: This doesn't update Striker (the Alteeve) stack yet, just the base OK. + $anvil->data->{cgi}{confirm}{value} = "" if not defined $anvil->data->{cgi}{confirm}{value}; + if ($anvil->data->{cgi}{confirm}{value}) + { + # Record the job! + my $job_data = ""; + + + die; + my ($job_uuid) = $anvil->Database->insert_or_update_jobs({ + file => $THIS_FILE, + line => __LINE__, + job_command => $anvil->data->{path}{exe}{'anvil-manage-keys'}, + job_data => "", + job_name => "manage::keys", + job_title => "job_0056", + job_description => "job_0057", + job_progress => 0, + job_host_uuid => $anvil->data->{sys}{host_uuid}, + }); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { job_uuid => $job_uuid }}); + + # We don't need to store anything as hidden variables, we'll read it back from the database + # later. + $anvil->data->{form}{body} = $anvil->Template->get({file => "striker.html", name => "job recorded", variables => { + title_id => "", + message_id => "", + reload_url => "/cgi-bin/".$THIS_FILE, + title => "#!string!job_0056!#", + description => "#!string!job_0057!#", + }}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { "form::body" => $anvil->data->{form}{body} }}); + } + else + { + # Get a list of bad keys we know about and ask the user which they want to remove + my $query = " +SELECT + state_uuid, + state_host_uuid, + state_name, + state_note +FROM + states +AND + state_name LIKE 'host_key_changed::%' +;"; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { query => $query }}); + my $results = $anvil->Database->query({query => $query, source => $THIS_FILE, line => __LINE__}); + my $count = @{$results}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { + results => $results, + count => $count, + }}); + if (not $count) + { + # No bad keys found on this host. + $anvil->data->{form}{body} = $anvil->Template->get({file => "striker.html", name => "no-bad-keys"}); + } + else + { + # Build a list of check-boxes / bad keys. + my $bad_key_list = ""; + foreach my $row (@{$results}) + { + + my $state_uuid = $row->[0]; + my $state_host_uuid = $row->[1]; + my $state_name = $row->[2]; + my $state_note = $row->[3]; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { + state_uuid => $state_uuid, + state_host_uuid => $state_host_uuid, + state_name => $state_name, + state_note => $state_note, + }}); + } + } + + die; + #Show the screen the confirm the addition. + $anvil->data->{form}{body} = $anvil->Template->get({file => "striker.html", name => "confirm-action", variables => { + title => "#!string!job_0056!#", + message => "#!string!!#", + hidden_fields => "", + }}); + } + + return(0); +} + # This handles files. sub process_file_menu { @@ -667,7 +788,58 @@ sub process_prep_host_page ### NOTE: Left off here. Need to pick up the message from a bad/changed fingerprint when it's the cause of a failed login. Create a button to remove the bad key. if (not $connected) { - $anvil->data->{form}{error_massage} = $anvil->Template->get({file => "main.html", name => "error_message", variables => { error_message => $anvil->Words->string({key => "striker_warning_0012"}) }}); + # Is it because the target's key is bad or has changed? + my $query = "SELECT state_uuid, state_note FROM states WHERE state_name LIKE ".$anvil->Database->quote("host_key_changed::".$host_ip_address."::%").";"; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { query => $query }}); + + my $results = $anvil->Database->query({query => $query, source => $THIS_FILE, line => __LINE__}); + my $count = @{$results}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { + results => $results, + count => $count, + }}); + if ($count) + { + # Yup, key has changed. + my $state_uuid = $results->[0]->[0]; + my $state_note = $results->[0]->[1]; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { + state_uuid => $state_uuid, + state_note => $state_note, + }}); + my $bad_file = ""; + my $bad_line = ""; + foreach my $pair (split/,/, $state_note) + { + my ($variable, $value) = ($pair =~ /^(.*?)=(.*)$/); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { + pair => $pair, + variable => $variable, + value => $value, + }}); + if ($variable eq "file") + { + $bad_file = $value; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { bad_file => $bad_file }}); + } + if ($variable eq "line") + { + $bad_line = $value; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { bad_line => $bad_line }}); + } + } + my $error_message = $anvil->Words->string({key => "striker_warning_0013", variables => { + file => $bad_file, + line => $bad_line, + }}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { error_message => $error_message }}); + $anvil->data->{form}{error_massage} = $anvil->Template->get({file => "main.html", name => "error_message", variables => { error_message => $error_message }}); + } + else + { + # Nope, some other issue + $anvil->data->{form}{error_massage} = $anvil->Template->get({file => "main.html", name => "error_message", variables => { error_message => $anvil->Words->string({key => "striker_warning_0012"}) }}); + } } elsif (not $target_host_uuid) { @@ -773,7 +945,6 @@ sub process_power my ($anvil, $task) = @_; $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { task => $task }}); - ### NOTE: This doesn't update Striker (the Alteeve) stack yet, just the base OK. $anvil->data->{cgi}{confirm}{value} = "" if not defined $anvil->data->{cgi}{confirm}{value}; if ($anvil->data->{cgi}{confirm}{value}) { @@ -836,7 +1007,6 @@ sub process_update { my ($anvil) = @_; - ### NOTE: This doesn't update Striker (the Alteeve) stack yet, just the base OK. $anvil->data->{cgi}{confirm}{value} = "" if not defined $anvil->data->{cgi}{confirm}{value}; if ($anvil->data->{cgi}{confirm}{value}) { @@ -1474,17 +1644,16 @@ sub check_availability { my ($anvil) = @_; - my $debug = 3; my $available = 1; # Check maintenance mode. - my $maintenance_mode = $anvil->System->maintenance_mode({debug => $debug}); - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { maintenance_mode => $maintenance_mode }}); + my $maintenance_mode = $anvil->System->maintenance_mode({debug => 3}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { maintenance_mode => $maintenance_mode }}); if ($maintenance_mode) { $available = 0; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { available => $available }}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { available => $available }}); # If we have any running or recently finished jobs, we'll desplay them below. $anvil->data->{say}{maintenance} = $anvil->Template->get({file => "striker.html", name => "striker-offline", variables => { @@ -1514,11 +1683,11 @@ AND AND job_host_uuid = ".$anvil->Database->quote($anvil->Get->host_uuid)." ;"; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { query => $query }}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { query => $query }}); my $results = $anvil->Database->query({query => $query, source => $THIS_FILE, line => __LINE__}); my $count = @{$results}; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { results => $results, count => $count, }}); @@ -1530,7 +1699,7 @@ AND my $unixtime = $results->[0]->[2]; my $seconds_ago = $anvil->Convert->add_commas({number => (time - $unixtime)}); $available = 0; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { available => $available, percent => $percent, seconds_ago => $seconds_ago, @@ -1544,11 +1713,11 @@ AND title => "#!string!striker_0046!#", description => $anvil->Words->string({key => "striker_0047", variables => { percent => $percent, timestamp => $timestamp, seconds_ago => $seconds_ago }}), }}); - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { 'say::maintenance' => $anvil->data->{say}{maintenance} }}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { 'say::maintenance' => $anvil->data->{say}{maintenance} }}); } } - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { available => $available }}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { available => $available }}); return($available); } @@ -3007,8 +3176,7 @@ z = Device Sequence sub generate_ip { my ($anvil, $network, $network_sequence, $device_sequence) = @_; - my $debug = 3; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { network => $network, network_sequence => $network_sequence, device_sequence => $device_sequence, @@ -3020,7 +3188,7 @@ sub generate_ip # The subnet's second octet will be '+X' where 'X' is the sequence. my $default_ip = $anvil->data->{defaults}{network}{$network}{subnet}; my $default_netmark = $anvil->data->{defaults}{network}{$network}{netmask}; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { default_ip => $default_ip, default_netmark => $default_netmark, }}); @@ -3029,7 +3197,7 @@ sub generate_ip { # Valid values. my ($ip_octet1, $ip_octet2, $ip_octet3, $ip_octet4) = (split/\./, $default_ip); - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { ip_octet1 => $ip_octet1, ip_octet2 => $ip_octet2, ip_octet3 => $ip_octet3, @@ -3046,7 +3214,7 @@ sub generate_ip $ip_octet3 = $anvil->data->{defaults}{network}{$network}{striker_octet3}; $ip_octet4 = $device_sequence; $ip = $ip_octet1.".".$ip_octet2.".".$ip_octet3.".".$ip_octet4; - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { "s1:ip_octet2" => $ip_octet2, "s2:ip_octet3" => $ip_octet3, "s3:ip_octet4" => $ip_octet4, @@ -3060,6 +3228,6 @@ sub generate_ip $ip = "#!error!#"; } - $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { ip => $ip }}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { ip => $ip }}); return($ip); } diff --git a/html/skins/alteeve/images/broken_key_icon_off.png b/html/skins/alteeve/images/broken_key_icon_off.png new file mode 100644 index 00000000..789c439b Binary files /dev/null and b/html/skins/alteeve/images/broken_key_icon_off.png differ diff --git a/html/skins/alteeve/images/broken_key_icon_on.png b/html/skins/alteeve/images/broken_key_icon_on.png new file mode 100644 index 00000000..ff68754b Binary files /dev/null and b/html/skins/alteeve/images/broken_key_icon_on.png differ diff --git a/html/skins/alteeve/images/sources.txt b/html/skins/alteeve/images/sources.txt index 293da6c5..3cc078c4 100644 --- a/html/skins/alteeve/images/sources.txt +++ b/html/skins/alteeve/images/sources.txt @@ -55,3 +55,6 @@ configure by iconesia from the Noun Project (https://thenounproject.com/term/con Folder by Mint Shirt from the Noun Project (https://thenounproject.com/term/folder/402164/) - files_icon.png + +Broken Key by I Putu Kharismayadi from the Noun Project (https://thenounproject.com/term/folder/1481931/) + - broken_key.png diff --git a/html/skins/alteeve/pxe.txt b/html/skins/alteeve/pxe.txt index e528a1a8..2c8044c2 100644 --- a/html/skins/alteeve/pxe.txt +++ b/html/skins/alteeve/pxe.txt @@ -193,7 +193,10 @@ chmod 755 /mnt/sysimage/usr/sbin/anvil-update-issue # Add this to crontab. cat << EOF > /mnt/sysimage/var/spool/cron/root MAILTO="" +# First one keeps the list updated, the second one to make sure the list is +# there before the first login page is shown. * * * * * /usr/sbin/anvil-update-issue >> /var/log/anvil.cron 2>&1 +@reboot /usr/sbin/anvil-update-issue >> /var/log/anvil.cron 2>&1 EOF chown 0:0 /mnt/sysimage/var/spool/cron/root chmod 0600 /mnt/sysimage/var/spool/cron/root diff --git a/html/skins/alteeve/striker.html b/html/skins/alteeve/striker.html index 664a006b..f192bbc9 100644 --- a/html/skins/alteeve/striker.html +++ b/html/skins/alteeve/striker.html @@ -161,6 +161,33 @@ + + +
+
+ + + + + + + + + + + + +
+   +
+ #!string!striker_0098!# +
+ + @@ -373,6 +400,19 @@ #!string!striker_0110!# + + + + + + +
+ +
diff --git a/share/words.xml b/share/words.xml index 3ffbf7fd..cc6d6626 100644 --- a/share/words.xml +++ b/share/words.xml @@ -932,6 +932,10 @@ Here we will inject 't_0006', which injects 't_0001' which has a variable: [#!st Initialize The target will now be initialized. How long this takes will depend on how fast files can be downloaded and, when needed, how long it takes to register with Red Hat and add the needed repositories. Configure the network on a node or DR host. + This option will allow old machine keys to be removed. This is not currently needed. + There are one or more broken keys, blocking access to target machines. If a target has been rebuilt, you can clear the old keys here. + Manage Changed Keys + There are no known bad keys at this time. #!variable!number!#/sec @@ -955,7 +959,7 @@ Here we will inject 't_0006', which injects 't_0001' which has a variable: [#!st This system will be rebooted momentarily. It will not respond until it has booted back up. Poweroff Striker This system will be powered off momentarily. It will not respond until it has turned back on. - Reboot.. + Reboot... Powering off... Add a Striker Peer The Striker peer will now be added to the local configuration. @@ -998,6 +1002,16 @@ Failure! The return code: [#!variable!return_code!#] was received ('0' was expec Success! Adding our database connection information to the target's anvil.conf file! Finished! The target should be ready for initial configuration shortly. If it isn't, please check that the 'anvil-daemon' daemon is running. + Removing bad machine keys. + Removing line: [#!variable!line!#] from: [#!variable!file!#] for the target machine: [#!variable!target!#]. + [ Error ] - The known hosts file: [#!variable!file!#] was not found. Skipping it. + Finished. + [ Error ] - There was a problem reading the known hosts file: [#!variable!file!#]. Skipping it. + Found the offending line, removing it. + [ Error ] - The line number: [#!variable!line!#] in: [#!variable!file!#] does not appear to be for the target: [#!variable!target!#]. Has the file already been updated? Skipping it. + Rewriing: [#!variable!file!#]. + Manage Keys + The bad keys will be removed from the specified files. The IP address will change. You will need to reconnect after applying these changes. @@ -1012,6 +1026,7 @@ Failure! The return code: [#!variable!return_code!#] was received ('0' was expec The IP address is not a valid IPv4 address The SSH port is not a valid (usually it is 22, but it has to be between 1 ~ 65536) Failed to log into the host. Is the IP or root user's password right? + The target's host key has changed. If the target has been rebuilt, or the target IP reused, the old key will need to be removed. Problem file: [#!variable!file!#:#!variable!line!#]. There are not enough network interfaces on this machine. You have: [#!variable!interface_count!#] interface(s), and you need at least: [#!variable!required_interfaces_for_single!#] interfaces to connect to the requested networks (one for Back-Channel and one for each Internet-Facing network). @@ -1105,6 +1120,8 @@ Failed to generate an RSA public key for the user: [#!variable!user!#]. The outp The initialization target: [#!variable!target!#] is not accessible. Will keep trying... There are no databases available. Will check periodically, waiting until one becomes available. There was a problem adding out database to the target's anvil.conf file. + Unable to connect to the database, unable to read the details of the key to remove. + Did not find any offending keys on this host, exiting. Yes diff --git a/tools/anvil-manage-keys b/tools/anvil-manage-keys new file mode 100755 index 00000000..46d7af6f --- /dev/null +++ b/tools/anvil-manage-keys @@ -0,0 +1,268 @@ +#!/usr/bin/perl +# +# This removes a bad key from a +# +# This program is setuid 'admin' and calls a (new) peer to read its hostname and system UUID. It takes the +# target's password in via a file. +# +# Exit codes; +# 0 = Normal exit. +# 1 = No database connection. +# 2 = No offending keys found. +# 3 = +# + +use strict; +use warnings; +use Anvil::Tools; + +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; + +my $anvil = Anvil::Tools->new(); +$anvil->Log->level({set => 2}); +$anvil->Log->secure({set => 1}); +$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, secure => 0, key => "log_0115", variables => { program => $THIS_FILE }}); + +# Read switches (target ([user@]host[:port]) and the file with the target's password. If the password is +# passed directly, it will be used. Otherwise, the password will be read from the database. +$anvil->Get->switches; + +$anvil->Database->connect(); +$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 3, secure => 0, key => "log_0132"}); +if (not $anvil->data->{sys}{database}{connections}) +{ + # No databases, update the job, sleep for a bit and then exit. The daemon will pick it up and try + # again after we exit. + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 0, priority => "err", key => "error_0077"}); + sleep 10; + $anvil->nice_exit({exit_code => 1}); +} + +### TODO: Store the state_uuid(s) of the key(s) to remove. +# If we don't have a state_uuid, pick it up from the job_data + +# Read in the details and make sure the bad the bad key is on our system. +my $query = "SELECT + state_uuid, + state_name, + state_note +FROM + states +WHERE + state_host_uuid = ".$anvil->Database->quote($anvil->data->{sys}{host_uuid})." +AND + state_name LIKE 'host_key_changed::%' +;"; +$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { query => $query }}); +my $results = $anvil->Database->query({query => $query, source => $THIS_FILE, line => __LINE__}); +my $count = @{$results}; +$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { + results => $results, + count => $count, +}}); +if (not $count) +{ + # No bad keys found on this host. + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 0, priority => "err", key => "error_0078"}); + sleep 10; + $anvil->nice_exit({exit_code => 2}); +} +my $progress = 0; +update_progress($anvil, 0, "clear"); +$progress += 5; +update_progress($anvil, $progress, "job_0048"); +$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 2, key => "job_0048"}); +foreach my $row (@{$results}) +{ + + my $state_uuid = $row->[0]; + my $state_name = $row->[1]; + my $state_note = $row->[2]; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { + state_uuid => $state_uuid, + state_name => $state_name, + state_note => $state_note, + }}); + + # Pull out the details. + my $bad_file = ""; + my $bad_line = ""; + foreach my $pair (split/,/, $state_note) + { + my ($variable, $value) = ($pair =~ /^(.*?)=(.*)$/); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { + pair => $pair, + variable => $variable, + value => $value, + }}); + if ($variable eq "file") + { + $bad_file = $value; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { bad_file => $bad_file }}); + } + if ($variable eq "line") + { + $bad_line = $value; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { bad_line => $bad_line }}); + } + } + my ($target, $user) = ($state_name =~ /host_key_changed::(.*)::(.*)$/); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { + target => $target, + user => $user, + bad_file => $bad_file, + bad_line => $bad_line, + }}); + + $progress += 5; $progress = 95 if $progress > 95; + update_progress($anvil, $progress, "job_0049,!!line!:".$bad_line.",!!file!".$bad_file."!!,!!target!".$target."!!"); + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 2, key => "job_0049", variables => { + line => $bad_line, + file => $bad_file, + target => $target, + }}); + + # Read in the file, if it exists. + if (not -e $bad_file) + { + $progress += 10; $progress = 95 if $progress > 95; + update_progress($anvil, $progress, "job_0050,!!file!".$bad_file."!!"); + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 2, key => "job_0050", variables => { file => $bad_file }}); + + # Remove this job and go on to the next bad key (if any). + delete_state($anvil, $state_uuid); + next; + } + + # Read in the file + my ($old_body) = $anvil->Storage->read_file({file => $bad_file}); + if ($old_body eq "!!error!!") + { + # Failed to read the file + $progress += 10; $progress = 95 if $progress > 95; + update_progress($anvil, $progress, "job_0052,!!file!".$bad_file."!!"); + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 2, key => "job_0052", variables => { file => $bad_file }}); + + # Remove this job and go on to the next bad key (if any). + delete_state($anvil, $state_uuid); + next; + } + + # Find our key + my $line_number = 0; + my $new_body = ""; + my $update = 0; + foreach my $line (split/\n/, $old_body) + { + $line_number++; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { + 's1:line_number' => $line_number, + 's2:bad_line' => $bad_line, + 's3:line' => $line, + }}); + if ($line_number eq $bad_line) + { + # Verify that this is, indeed, the right line. + if ($line =~ /^$target /) + { + # Found it! + $progress += 5; $progress = 95 if $progress > 95; + update_progress($anvil, $progress, "job_0053"); + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 2, key => "job_0053"}); + $update = 1; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { update => $update }}); + last; + } + else + { + # Line found, but not for the target. + $progress += 10; $progress = 95 if $progress > 95; + update_progress($anvil, $progress, "job_0054,!!line!".$bad_line."!!,!!file!".$bad_file."!!,!!target!".$target."!!"); + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 2, key => "job_0054", variables => { + line => $bad_line, + file => $bad_file, + target => $target, + }}); + + # Remove this job and go on to the next bad key (if any). + delete_state($anvil, $state_uuid); + last; + } + } + else + { + $new_body .= $line."\n"; + } + } + + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { + 's1:old_body' => $old_body', + 's2:new_body' => $new_body, + 's3:update' => $update, + }}); + if ($update) + { + # Write the file out. + $progress += 5; $progress = 95 if $progress > 95; + update_progress($anvil, $progress, "job_0055,!!file!".$bad_file."!!"); + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 2, key => "job_0055", variables => { file => $bad_file }}); + } + +} + +# Done. +update_progress($anvil, 100, "job_0051"); +$anvil->nice_exit({code => 0}); + + +############################################################################################################# +# Functions # +############################################################################################################# + +# This deletes a state entry. +sub delete_state +{ + my ($anvil, $state_uuid) = @_; + + # Delete it so long as we have a UUID. + if ($state_uuid) + { + my $query = "DELETE FROM states WHERE state_uuid = ".$anvil->Database->quote($state_uuid).";"; + $anvil->Database->write({debug => 2, query => $merged, source => $THIS_FILE, line => __LINE__}); + } + + return(0); +} + +# This updates the progress if we were called with a job UUID. +sub update_progress +{ + my ($anvil, $progress, $message) = @_; + + # Log the progress percentage. + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { + progress => $progress, + message => $message, + "jobs::job_uuid" => $anvil->data->{jobs}{job_uuid}, + }}); + + if ($anvil->data->{jobs}{job_uuid}) + { + $anvil->Job->update_progress({ + debug => 3, + progress => $progress, + message => $message, + job_uuid => $anvil->data->{jobs}{job_uuid}, + }); + } + + return(0); +}