# vim: set filetype=perl ts=4 sw=4 sts=4 et:
package OVH::Bastion;

use common::sense;

use constant {MINIJAIL_PATH => "/bin/minijail0",};

sub jailify {
    my %params = @_;

    my $required     = delete $params{'required'};
    my $use_sudo     = delete $params{'use_sudo'};
    my $user         = delete $params{'user'};
    my $group        = delete $params{'group'};
    my $no_new_privs = delete $params{'no_new_privs'};
    my $env_add      = delete $params{'env_add'};
    my $pid_ns       = delete $params{'pid_ns'};
    my $mount_mode   = delete $params{'mount_mode'};
    my $mount_ns     = delete $params{'mount_ns'};
    my $pivot_root   = delete $params{'pivot_root'};
    my $bind_mounts  = delete $params{'bind_mounts'};
    my $mounts       = delete $params{'mounts'};
    my $seccomp      = delete $params{'seccomp'};
    my $uts          = delete $params{'uts'};
    my $dev          = delete $params{'dev'};
    my $cmd          = delete $params{'cmd'};

    if (%params) {

        # this is a coding error => warn(), this'll make any test passing through it automatically fail
        warn("Spurious parameter passed to jailify(): " . join(", ", keys %params));
        return R('ERR_INTERNAL', msg => "Spurious parameter passed to jailify()");
    }

    if (!$cmd || ref $cmd ne 'ARRAY') {
        return R('ERR_INVALID_PARAMETER', msg => "Specified cmd is either missing or not an arrayref");
    }

    if (!-e -x OVH::Bastion::MINIJAIL_PATH) {
        return R('ERR_INTERNAL', msg => "minijail not available, please warn your bastion administrator") if $required;

        # not installed and not required? just return the @cmd untouched
        return R('OK_NO_CHANGE', value => $cmd);
    }

    # we need to be root for several cases
    if (($user || $group || $mount_ns || $pivot_root || $bind_mounts || $mounts) && ($> != 0) && !$use_sudo) {

        # this is a coding error => warn(), this'll make any test passing through it automatically fail
        warn(
            'Jailify attempted with a feature needing root without being root ',
            "\$user: ",
            ($user // "_undef_"),
            "\$group: ",
            ($group // "_undef_"),
            "\$mount_ns: ",
            ($mount_ns // "_undef_"),
            "\$pivot_root: ",
            ($pivot_root // "_undef_"),
            "\$bind_mounts: ",
            ($bind_mounts // "_undef_"),
            "\$mounts: ",
            ($mounts // "_undef_"),
        );
        return R('ERR_INTERNAL', msg => "Bad use of jailify(), please warn your bastion administrator");
    }

    my @jailcmd;
    if ($use_sudo) {
        push @jailcmd, qw{ sudo -n -u root -- };
    }

    push @jailcmd, OVH::Bastion::MINIJAIL_PATH, "--logging=stderr";
    push @jailcmd, "-u", $user  if $user;
    push @jailcmd, "-g", $group if $group;
    push @jailcmd, "-n"            if $no_new_privs;
    push @jailcmd, "-v"            if $mount_ns;
    push @jailcmd, "-p"            if $pid_ns;
    push @jailcmd, "--uts"         if $uts;
    push @jailcmd, "-K$mount_mode" if $mount_mode;
    push @jailcmd, "-d"            if $dev;

    if ($pivot_root) {
        return R('ERR_INVALID_PARAMETER', msg => "Specified pivot_root ($pivot_root) is not a directory")
          if !-d $pivot_root;
        push @jailcmd, "-P", $pivot_root;
    }

    if ($seccomp) {
        return R('ERR_INVALID_PARAMETER', msg => "Specified seccomp filter is not a file") if !-f -r $seccomp;
        push @jailcmd, "-S", $seccomp;
    }

    if (defined $bind_mounts) {
        return R('ERR_INVALID_PARAMETER', msg => "Specified bind_mounts is not an arrayref")
          if (ref $bind_mounts ne 'ARRAY');
        push @jailcmd, "-b", $_ for (@$bind_mounts);
    }

    if (defined $mounts) {
        return R('ERR_INVALID_PARAMETER', msg => "Specified mounts is not an arrayref") if (ref $mounts ne 'ARRAY');
        push @jailcmd, "-k", $_ for (@$mounts);
    }

    if (defined $env_add) {
        return R('ERR_INVALID_PARAMETER', msg => "Specified env_add is not an arrayref") if (ref $env_add ne 'ARRAY');
        push @jailcmd, "--env-add", $_ for (@$env_add);
    }

    push @jailcmd, '--', @$cmd;

    return R('OK', value => \@jailcmd);
}

1;
