3
0
mirror of https://github.com/pragma-/pbot.git synced 2025-10-14 15:07:22 +02:00
Pragmatic Software 483a782021
applets/pbot-vm: restore VSOCK functionality
- restore ability to execute VM commands concurrently via VSOCK
- add PID to log messages and truncate overly long messages
2025-10-05 06:22:44 -07:00

213 lines
5.4 KiB
Perl

#!/usr/bin/env perl
# File: Guest.pm
#
# Purpose: Collection of functions to interface with the PBot VM Guest and
# execute VM commands.
# SPDX-FileCopyrightText: 2022-2024 Pragmatic Software <pragma78@gmail.com>
# SPDX-License-Identifier: MIT
package Guest;
use 5.020;
use warnings;
use strict;
use feature qw/signatures/;
no warnings qw(experimental::signatures);
use English;
use Encode;
use File::Basename;
use JSON::XS;
use Time::HiRes qw/gettimeofday/;
use POSIX;
use Data::Dumper;
$Data::Dumper::Useqq = 1;
$Data::Dumper::Terse = 1;
$Data::Dumper::Indent = 0;
sub info($text, $maxlen = 255) {
my $rest;
($text, $rest) = $text =~ m/^(.{0,$maxlen})(.*)/ms;
$rest = length $rest;
$text .= " [... $rest more]" if $rest;
$text .= "\n" if $text !~ /\n$/;
my ($sec, $usec) = gettimeofday;
my $time = strftime "%a %b %e %Y %H:%M:%S", localtime $sec;
$time .= sprintf ".%03d", $usec / 1000;
print STDERR "$$ $time :: $text";
}
sub read_input($input, $buffer, $tag) {
info("$tag waiting for input...\n");
my $ret = sysread($input, my $buf, 4096);
if (not defined $ret) {
info("Error reading $tag: $!\n");
return undef;
}
if ($ret == 0) {
info("$tag input closed.\n");
return 0;
}
info("$tag read $ret [" . (Dumper $buf) . "]\n");
$$buffer .= $buf;
# info("$tag buffer [" . (Dumper $$buffer) . "]\n", 8192);
if ($$buffer !~ /\n\n/) {
return undef;
}
my $line;
($line, $$buffer) = split /\n\n/, $$buffer, 2;
$line =~ s/\n//g;
info(("-" x 40) . "\n");
# info("$tag got [" . (Dumper $line) . "]\n", 8192);
# info("$tag buffer [" . (Dumper $$buffer) . "]\n", 8192);
my $command = eval { decode_json($line) };
if ($@) {
info("Failed to decode JSON: $@\n", 1024);
return {
arguments => '',
cmdline => 'sh prog.sh',
code => "echo 'Failed to decode JSON: $@'",
date => 0,
execfile => 'prog.sh',
input => '',
lang => 'sh',
sourcefile => 'prog.sh'
};
}
$command->{arguments} //= '';
$command->{input} //= '';
info("command: " . Dumper($command), 2048);
return $command;
}
sub process_command($command, $mod, $user, $tag) {
my ($uid, $gid, $home) = (getpwnam $user)[2, 3, 7];
if (not $uid and not $gid) {
info("Could not find user $user: $!\n");
return undef;
}
my $pid = fork;
if (not defined $pid) {
info("process_command: fork failed: $!\n");
return undef;
}
if ($pid == 0) {
if ($command->{'persist-key'}) {
system ("rm -rf \"/home/$user/$command->{'persist-key'}\" 1>&2");
system("mount /dev/vdb1 /root/factdata 1>&2");
system("mkdir -p \"/root/factdata/$command->{'persist-key'}\" 1>&2");
system("cp -R -p \"/root/factdata/$command->{'persist-key'}\" \"/home/$user/$command->{'persist-key'}\" 1>&2");
}
my $dir = "/home/$user/$$";
system("mkdir -p $dir 1>&2");
system("chmod -R 755 $dir 1>&2");
system("chown -R $user $dir 1>&2");
system("chgrp -R $user $dir 1>&2");
if (time - $command->{date} > 60) {
system("date -s \@$command->{date} 1>&2");
}
$ENV{USER} = $user;
$ENV{LOGNAME} = $user;
$ENV{HOME} = $home;
chdir("/home/$user/$$");
$GID = $gid;
$EGID = "$gid $gid";
$EUID = $UID = $uid;
my $result = run_command($command, $mod);
info(("=" x 40) . "\n");
# ensure output is newline-terminated
$result .= "\n" unless $result =~ /\n$/;
return $result;
} else {
# wait for child to finish
my $kid = waitpid($pid, 0);
my $status = $?;
if (WIFEXITED($status)) {
info("child normal exit: " . WEXITSTATUS($status) . "\n");
}
if (WIFSIGNALED($status)) {
info("child signaled exit: " . WTERMSIG($status) . "\n");
}
# clean up persistent factoid storage
if ($command->{'persist-key'}) {
system("cp -R -p \"/home/$user/$command->{'persist-key'}\" \"/root/factdata/$command->{'persist-key'}\"");
system("umount /root/factdata");
system ("rm -rf \"/home/$user/$command->{'persist-key'}\"");
}
# kill any left-over processes started by user
system("pkill -P $pid");
system("rm -rf /home/$user/$pid");
return 0;
}
}
sub run_command($command, $mod) {
local $SIG{CHLD} = 'DEFAULT';
$mod->preprocess;
$mod->postprocess if not $mod->{error} and not $mod->{done};
if (exists $mod->{no_output} or not length $mod->{output}) {
if ($command->{factoid}) {
$mod->{output} = '';
} else {
$mod->{output} .= "\n" if length $mod->{output};
if (not $mod->{error}) {
$mod->{output} .= "Success (no output).\n";
} else {
$mod->{output} .= "Exit $mod->{error}.\n";
}
}
} elsif ($mod->{error}) {
$mod->{output} .= " [Exit $mod->{error}]";
}
return $mod->{output};
}
sub send_output($output, $result, $tag) {
my $json = encode_json({ result => $result });
print $output "result:$json\n";
print $output "result:end\n";
}
1;