#! /usr/bin/env perl
# vim: set filetype=perl ts=4 sw=4 sts=4 et:
use common::sense;
use Sys::Hostname ();
use Term::ANSIColor;

use File::Basename;
use lib dirname(__FILE__) . '/../../../lib/perl';
use OVH::Result;
use OVH::Bastion;
use OVH::Bastion::Plugin qw( :DEFAULT help );

# globally allow sys_getpw* and sys_getgr* cache use
$ENV{'PW_GR_CACHE'} = 1;

my ($name);
OVH::Bastion::Plugin::begin(
    loadConfig => 1,
    argv       => \@ARGV,
    header     => "information",
    options    => {'name' => \$name},
    helptext   => <<'EOF',
Displays some information about this bastion instance

Usage: --osh SCRIPT_NAME
EOF
);

=pod CONFIGURATION

=head1 items

=head2 admin_show_system_info (optional, boolean)

If enabled, bastion admins get more output regarding information of the underlying OS.
When omitted, this is enabled by default.

=head2 show_fortune (optional, boolean)

If enabled, and if the ``fortune`` package is installed on your OS, shows a fortune.
When omitted, this is enabled by default.

=head1 example

{
    "admin_show_system_info": false,
    "show_fortune": false
}

=cut

my $fnret = OVH::Bastion::load_configuration();
$fnret or osh_exit($fnret);
my $config = $fnret->value;

my %ret;

my $selfdisplay = $remoteself ? $remoteself : $self;
$ret{'account'} = $selfdisplay;
osh_info "You are " . colored($selfdisplay, 'green');
if ($realm) {
    osh_info "You are a citizen of a distant realm named " . colored($realm, 'green');
    $ret{'realm'} = $realm;
}
if (OVH::Bastion::is_auditor(account => $self)) {
    $ret{'is_auditor'} = 1;
    osh_info "You are a " . colored('bastion auditor!', 'green');
}
if (OVH::Bastion::is_super_owner(account => $self)) {
    $ret{'is_superowner'} = 1;
    osh_info "Look at you, you are a " . colored('bastion superowner!', 'green');
}
if (OVH::Bastion::is_admin(account => $self)) {
    $ret{'is_admin'} = 1;
    osh_info "Woosh, you are even a " . colored('bastion admin!', 'green');
}

if (!$realm) {
    osh_info "\nYour alias to connect to this bastion is:";
    my $bastionName    = OVH::Bastion::config('bastionName')->value();
    my $bastionCommand = OVH::Bastion::config('bastionCommand')->value();
    $bastionCommand =~ s/USER|ACCOUNT/$self/g;
    $bastionCommand =~ s/CACHENAME|BASTIONNAME/$bastionName/g;
    my $hostname = Sys::Hostname::hostname();
    $bastionCommand =~ s/HOSTNAME/$hostname/g;
    osh_info colored("alias $bastionName='$bastionCommand'", "magenta");
    $ret{'bastion_alias_command'} = $bastionCommand;

    if (OVH::Bastion::config('moshAllowed')->value) {
        osh_info "Your alias to connect to this bastion with MOSH is:";
        my ($cacheDestination) = $bastionCommand =~ m{(\S+@\S+)};
        $bastionCommand =~ s{\Q$cacheDestination\E}{};
        $bastionCommand =~ s{\s+--\s+$}{};
        $bastionCommand =~ s{ +}{ }g;
        osh_info colored("alias ${bastionName}m='mosh --ssh=\"$bastionCommand\" $cacheDestination -- '", "magenta");
        $ret{'bastion_alias_command_mosh'} = "mosh --ssh=\"$bastionCommand\" $cacheDestination -- ";
    }
}

osh_info "\nMulti-Factor Authentication (MFA) on your account:";
$ret{'mfa_password_required'} =
  OVH::Bastion::is_user_in_group(user => $self, group => OVH::Bastion::MFA_PASSWORD_REQUIRED_GROUP) ? 1 : 0;
$ret{'mfa_password_bypass'} =
  OVH::Bastion::is_user_in_group(user => $self, group => OVH::Bastion::MFA_PASSWORD_BYPASS_GROUP) ? 1 : 0;
$ret{'mfa_password_configured'} =
  OVH::Bastion::is_user_in_group(user => $self, group => OVH::Bastion::MFA_PASSWORD_CONFIGURED_GROUP) ? 1 : 0;
osh_info "- Additional password authentication is "
  . ($ret{'mfa_password_required'} ? colored('required', 'green') : colored('not required', 'blue'));
osh_info "- Additional password authentication bypass is "
  . ($ret{'mfa_password_bypass'} ? colored('enabled', 'green') : colored('disabled', 'blue'));
osh_info "- Additional password authentication is "
  . ($ret{'mfa_password_configured'} ? colored('enabled and active', 'green') : colored('disabled', 'blue'));

$ret{'mfa_totp_required'} =
  OVH::Bastion::is_user_in_group(user => $self, group => OVH::Bastion::MFA_TOTP_REQUIRED_GROUP) ? 1 : 0;
$ret{'mfa_totp_bypass'} =
  OVH::Bastion::is_user_in_group(user => $self, group => OVH::Bastion::MFA_TOTP_BYPASS_GROUP) ? 1 : 0;
$ret{'mfa_totp_configured'} =
  OVH::Bastion::is_user_in_group(user => $self, group => OVH::Bastion::MFA_TOTP_CONFIGURED_GROUP) ? 1 : 0;
osh_info "- Additional TOTP authentication is "
  . ($ret{'mfa_totp_required'} ? colored('required', 'green') : colored('not required', 'blue'));
osh_info "- Additional TOTP authentication bypass is "
  . ($ret{'mfa_totp_bypass'} ? colored('enabled', 'green') : colored('disabled', 'blue'));
osh_info "- Additional TOTP authentication is "
  . ($ret{'mfa_totp_configured'} ? colored('enabled and active', 'green') : colored('disabled', 'blue'));

$fnret = OVH::Bastion::sys_getpasswordinfo(user => $self);
if ($fnret) {
    $ret{"password_$_"} = $fnret->value->{$_} for (keys %{$fnret->value});
    osh_info "\nAccount PAM UNIX password information (used for password MFA):";
    osh_info sprintf("- Password is %s",
        $ret{'password_password'} eq 'locked'
        ? colored('unused', 'blue')
        : ($ret{'password_password'} eq 'set' ? colored('set', 'green') : colored($ret{'password_password'}, 'red')));
    osh_info "- Password was last changed on " . colored($ret{'password_date_changed'}, 'magenta');
    if ($ret{'password_max_days'} == -1) {
        osh_info "- Password will never expire";
    }
    else {
        osh_info "- Password must be changed every " . colored($ret{'password_max_days'}, 'magenta') . " days at least";
        osh_info "- A warning is displayed "
          . colored($ret{'password_warn_days'}, 'magenta')
          . " days before expiration";
    }
    if ($ret{'password_min_days'} != 0) {
        osh_info "- The minimum time between two password changes is " . $ret{'password_min_days'} . " days";
    }
    if ($ret{'password_max_days'} != -1) {
        if ($ret{'password_inactive_days'} != -1) {
            osh_info "- Account will be disabled "
              . colored($ret{'password_inactive_days'}, 'magenta')
              . " days after password expiration";
        }
        else {
            osh_info "- Account will " . colored('not', 'magenta') . " be disabled after password expiration";
        }
    }
}

osh_info
  sprintf("\nI am %s, aka %s", colored(Sys::Hostname::hostname(), 'green'), colored($config->{'bastionName'}, 'green'));
$ret{'hostname'}     = Sys::Hostname::hostname();
$ret{'bastion_name'} = $config->{'bastionName'};

$fnret = OVH::Bastion::get_account_list();
my $nbaccounts = $fnret ? keys %{$fnret->value} : '?';
$fnret = OVH::Bastion::get_group_list();
my $nbgroups = $fnret ? keys %{$fnret->value} : '?';
osh_info "I have "
  . colored($nbaccounts, 'green')
  . " registered accounts and "
  . colored($nbgroups, 'green')
  . " groups";
$ret{'registered_accounts'} = $nbaccounts;
$ret{'registered_groups'}   = $nbgroups;

if ($config->{'readOnlySlaveMode'}) {
    osh_info "I am a " . colored("SLAVE", "cyan") . ", which means modifications must be made through my master";
}
else {
    osh_info "I am a " . colored("MASTER", "cyan") . ", which means I accept modifications";
}
$ret{'slave_mode'} = $config->{'readOnlySlaveMode'};

if (OVH::Bastion::is_auditor(account => $self)) {
    my @adminAccounts = @{$config->{'adminAccounts'}};
    osh_info "My admins are: " . colored(@adminAccounts ? join(", ", @adminAccounts) : "-", "magenta");
    $ret{'adminAccounts'} = \@adminAccounts;

    my @superOwnerAccounts = @{$config->{'superOwnerAccounts'}};
    osh_info "My super owners are: " . colored(@superOwnerAccounts ? join(", ", @superOwnerAccounts) : "-", "magenta");
    $ret{'superOwnerAccounts'} = \@superOwnerAccounts;
}

my @allowedNets = @{$config->{'allowedNetworks'}};
osh_info "The networks I'm able to connect you to on the egress side are: "
  . colored(@allowedNets ? join(", ", @allowedNets) : "all", "magenta");
$ret{'allowed_networks_list'} = \@allowedNets;

my @forbiddenNets = @{$config->{'forbiddenNetworks'}};
osh_info "The networks that are explicitly forbidden on the egress side are: "
  . colored(@forbiddenNets ? join(", ", @forbiddenNets) : "none", "magenta");
$ret{'forbidden_networks_list'} = \@forbiddenNets;

$fnret = OVH::Bastion::get_bastion_ips();
if ($fnret) {
    my @ips = grep { !/^127\./ } @{$fnret->value};
    if (@ips > 1) {
        osh_info "My egress connection IPs to remote servers are " . colored(join(", ", @ips), "magenta");
        osh_info
          "...this includes the IPs of my potential siblings, don't forget to whitelist those in your firewalls!";
    }
    else {
        osh_info "My egress connection IP to remote servers is " . colored(join(", ", @ips), "magenta");
        osh_info "...don't forget to whitelist me in your firewalls!";
    }
    $ret{'egress_ip_list'} = \@ips;
}

osh_info "\nThe following policy applies on this bastion:";
osh_info "- The interactive mode (-i) is "
  . ($config->{'interactiveModeAllowed'} ? colored('ENABLED', 'green') : colored('DISABLED', 'red'));
$ret{'interactive_mode_allowed'} = $config->{'interactiveModeAllowed'};
osh_info "- The support of mosh is "
  . ($config->{'moshAllowed'} ? colored('ENABLED', 'green') : colored('DISABLED', 'red'));
$ret{'mosh_allowed'} = $config->{'moshAllowed'};
if ($config->{'accountMaxInactiveDays'}) {
    osh_info "- Account expiration is "
      . colored('ENABLED', 'green')
      . ", with an expiration time of "
      . colored($config->{'accountMaxInactiveDays'}, 'magenta') . " days";
}
else {
    osh_info "- Account expiration is " . colored('DISABLED', 'red');
}
$ret{'account_expiration_days'} = $config->{'accountMaxInactiveDays'};

if ($config->{'idleLockTimeout'}) {
    osh_info "- Keyboard input idle time for session locking is "
      . colored('ENABLED', 'green')
      . ", kicking in after "
      . colored($config->{'idleLockTimeout'}, 'magenta')
      . " seconds";
}
else {
    osh_info "- Keyboard input idle time for session locking is " . colored('DISABLED', 'red');
}
$ret{'idle_lock_timeout'} = $config->{'idleLockTimeout'};

if ($config->{'idleKillTimeout'}) {
    osh_info "- Keyboard input idle time for session killing is "
      . colored('ENABLED', 'green')
      . ", kicking in after "
      . colored($config->{'idleKillTimeout'}, 'magenta')
      . " seconds";
}
else {
    osh_info "- Keyboard input idle time for session killing is " . colored('DISABLED', 'red');
}
$ret{'idle_kill_timeout'} = $config->{'idleKillTimeout'};

$fnret = OVH::Bastion::get_from_for_user_key();
if ($fnret && $fnret->value->{'from'}) {
    osh_info "- The forced \"from\" prepend on ingress keys is "
      . colored('ENABLED', 'green')
      . ", with the following value: "
      . colored($fnret->value->{'from'}, 'magenta');
    $ret{'ingress_keys_from_ip_list'} = $fnret->value->{'ipList'};
}
else {
    osh_info "- The forced \"from\" prepend on ingress keys is " . colored('DISABLED', 'red');
    $ret{'ingress_keys_from_ip_list'} = [];
}

foreach my $way (qw{ ingress egress }) {
    $fnret = OVH::Bastion::get_supported_ssh_algorithms_list(way => $way);
    if ($fnret) {
        osh_info "- The following algorithms are allowed for $way SSH keys: "
          . colored(join(', ', @{$fnret->value}), 'magenta');
        $ret{"${way}_ssh_key_algorithms"} = $fnret->value;
    }
    if (grep { $_ eq 'rsa' } @{$fnret->value}) {
        osh_info "- The RSA key size for $way SSH keys must be between "
          . colored($config->{"minimum" . ucfirst($way) . "RsaKeySize"}, "magenta") . " and "
          . colored($config->{"maximum" . ucfirst($way) . "RsaKeySize"}, "magenta") . " bits";
        $ret{"${way}_rsa_min_size"} = $config->{"minimum" . ucfirst($way) . "RsaKeySize"};
        $ret{"${way}_rsa_max_size"} = $config->{"maximum" . ucfirst($way) . "RsaKeySize"};
    }
}
osh_info "- The Multi-Factor Authentication (MFA) policy is "
  . colored(uc($config->{'accountMFAPolicy'}), $config->{'accountMFAPolicy'} eq 'disabled' ? 'red' : 'green');

osh_info "- The PIV-enforced ingress keys policy is "
  . colored($config->{'ingressRequirePIV'} ? 'ENABLED' : 'DISABLED', $config->{'ingressRequirePIV'} ? 'red' : 'green');

my $showSystemInfo = 0;
if (   !$pluginConfig
    || !defined $pluginConfig->{'admin_show_system_info'}
    || $pluginConfig->{'admin_show_system_info'})
{
    $showSystemInfo = 1;
}
if ($showSystemInfo && OVH::Bastion::is_admin(account => $self)) {
    osh_info "\nAs you are a bastion admin, more information follows:";
    $fnret = OVH::Bastion::sysinfo();
    if ($fnret and $fnret->value) {
        osh_info("We're running under " . $fnret->value->{'pretty_name'});
    }

    $fnret = OVH::Bastion::execute(cmd => ['uptime']);
    if ($fnret) {
        $fnret->value->{'stdout'}->[0] =~ s/^\s+//;
        osh_info $fnret->value->{'stdout'}->[0];
    }

    $fnret = OVH::Bastion::execute(cmd => ['free', '-h']);
    if ($fnret) {
        osh_info "Memory info:";
        osh_info join("\n", @{$fnret->value->{'stdout'}});
    }
}

my $showFortune = 0;
if (!$pluginConfig || !defined $pluginConfig->{'show_fortune'} || $pluginConfig->{'show_fortune'}) {
    $showFortune = 1;
}
if ($showFortune && -x "/usr/games/fortune") {
    my @command = qw{ /usr/games/fortune bofh-excuses };
    $fnret = OVH::Bastion::execute(cmd => \@command);
    if ($fnret) {
        my $fortune = join("\n", grep { $_ } @{$fnret->value->{'stdout'}});
        osh_info "\nHere is your excuse for anything not working today:";
        osh_info $fortune;
        $ret{'fortune'} = $fortune;
    }
}

osh_ok \%ret;
