2020-10-16 00:32:37 +08:00
|
|
|
# vim: set filetype=perl ts=4 sw=4 sts=4 et:
|
|
|
|
package OVH::Bastion;
|
|
|
|
|
|
|
|
use common::sense;
|
|
|
|
|
|
|
|
use IO::Handle;
|
|
|
|
use IPC::Open3;
|
|
|
|
use Symbol 'gensym';
|
|
|
|
use IO::Select;
|
|
|
|
use POSIX ":sys_wait_h";
|
|
|
|
use JSON;
|
|
|
|
use Config;
|
|
|
|
|
|
|
|
# Get signal names, i.e. signal 9 is SIGKILL, etc.
|
|
|
|
my %signum2string;
|
|
|
|
@signum2string{split ' ', $Config{sig_num}} = map { "SIG$_" } split ' ', $Config{sig_name};
|
|
|
|
|
feat: revamp logs
All connections and plugin executions emit two logs, an 'open' and
a 'close' log. We now add all the details of the connection to
the 'close' logs, those that were previously only available in the
corresponding 'open' log. This way, it is no longer required to
correlate both logs with their uniqid to have all the data:
the 'close' log should suffice. The 'open' log is still there if
for some reason the 'close' log can't be emitted (kill -9, system
crash, etc.), or if the 'open' and the 'close' log are several
hours, days or months appart.
An additional field "duration" has been added to the 'close' logs,
this represents the number of seconds (with millisecond precision)
the connection lasted.
Two new fields "globalsql" and "accountsql" have been added to the
'open'-type logs. These will contain either "ok" if we successfully
logged to the corresponding log database, "no" if it is disabled,
or "error $aDetailedMessage" if we got an error trying to insert
the row. The 'close'-type log also has the new "accountsql_close"
field, but misses the "globalsql_close" field as we never update
the global database on this event. On the 'close' log, we can also
have the value "missing", indicating that we couldn't update the
access log row in the database, as the corresponding 'open' log
couldn't insert it.
The "ttyrecsize" log field for the 'close'-type logs has been removed,
as it was never completely implemented, and contains bogus data if
ttyrec log rotation occurs. It has also been removed from the sqlite
log databases.
The 'open' and 'close' events are now pushed to our own log files,
in addition to syslog, if logging to those files is enabled (see
``enableGlobalAccesssLog`` and ``enableAccountAccessLog``), previously
the 'close' events were only pushed to syslog.
The /home/osh.log is no longer used for ``enableGlobalAccessLog``, the
global log is instead written to /home/logkeeper/global-log-YYYYMM.log.
The global sql file, enabled with ``enableGlobalSqlLog``, is now
split by year-month instead of by year, to
/home/logkeeper/global-log-YYYYMM.sqlite.
2020-12-15 23:39:20 +08:00
|
|
|
sub sysret2human {
|
2020-10-16 00:32:37 +08:00
|
|
|
my $sysret = shift;
|
|
|
|
if ($sysret == -1) {
|
|
|
|
return R('OK', msg => "error: failed to execute ($!)");
|
|
|
|
}
|
|
|
|
elsif ($sysret & 127) {
|
|
|
|
my $signal = $sysret & 127;
|
|
|
|
my $coredump = $sysret & 128;
|
|
|
|
return R(
|
|
|
|
'OK',
|
|
|
|
value => {
|
|
|
|
coredump => $coredump ? \1 : \0,
|
|
|
|
signal => $signum2string{$signal} || $signal,
|
feat: revamp logs
All connections and plugin executions emit two logs, an 'open' and
a 'close' log. We now add all the details of the connection to
the 'close' logs, those that were previously only available in the
corresponding 'open' log. This way, it is no longer required to
correlate both logs with their uniqid to have all the data:
the 'close' log should suffice. The 'open' log is still there if
for some reason the 'close' log can't be emitted (kill -9, system
crash, etc.), or if the 'open' and the 'close' log are several
hours, days or months appart.
An additional field "duration" has been added to the 'close' logs,
this represents the number of seconds (with millisecond precision)
the connection lasted.
Two new fields "globalsql" and "accountsql" have been added to the
'open'-type logs. These will contain either "ok" if we successfully
logged to the corresponding log database, "no" if it is disabled,
or "error $aDetailedMessage" if we got an error trying to insert
the row. The 'close'-type log also has the new "accountsql_close"
field, but misses the "globalsql_close" field as we never update
the global database on this event. On the 'close' log, we can also
have the value "missing", indicating that we couldn't update the
access log row in the database, as the corresponding 'open' log
couldn't insert it.
The "ttyrecsize" log field for the 'close'-type logs has been removed,
as it was never completely implemented, and contains bogus data if
ttyrec log rotation occurs. It has also been removed from the sqlite
log databases.
The 'open' and 'close' events are now pushed to our own log files,
in addition to syslog, if logging to those files is enabled (see
``enableGlobalAccesssLog`` and ``enableAccountAccessLog``), previously
the 'close' events were only pushed to syslog.
The /home/osh.log is no longer used for ``enableGlobalAccessLog``, the
global log is instead written to /home/logkeeper/global-log-YYYYMM.log.
The global sql file, enabled with ``enableGlobalSqlLog``, is now
split by year-month instead of by year, to
/home/logkeeper/global-log-YYYYMM.sqlite.
2020-12-15 23:39:20 +08:00
|
|
|
status => undef,
|
2020-10-16 00:32:37 +08:00
|
|
|
},
|
|
|
|
msg => sprintf("signal %d (%s)%s", $signal, $signum2string{$signal}, $coredump ? ' and coredump' : '')
|
|
|
|
);
|
|
|
|
}
|
|
|
|
else {
|
feat: revamp logs
All connections and plugin executions emit two logs, an 'open' and
a 'close' log. We now add all the details of the connection to
the 'close' logs, those that were previously only available in the
corresponding 'open' log. This way, it is no longer required to
correlate both logs with their uniqid to have all the data:
the 'close' log should suffice. The 'open' log is still there if
for some reason the 'close' log can't be emitted (kill -9, system
crash, etc.), or if the 'open' and the 'close' log are several
hours, days or months appart.
An additional field "duration" has been added to the 'close' logs,
this represents the number of seconds (with millisecond precision)
the connection lasted.
Two new fields "globalsql" and "accountsql" have been added to the
'open'-type logs. These will contain either "ok" if we successfully
logged to the corresponding log database, "no" if it is disabled,
or "error $aDetailedMessage" if we got an error trying to insert
the row. The 'close'-type log also has the new "accountsql_close"
field, but misses the "globalsql_close" field as we never update
the global database on this event. On the 'close' log, we can also
have the value "missing", indicating that we couldn't update the
access log row in the database, as the corresponding 'open' log
couldn't insert it.
The "ttyrecsize" log field for the 'close'-type logs has been removed,
as it was never completely implemented, and contains bogus data if
ttyrec log rotation occurs. It has also been removed from the sqlite
log databases.
The 'open' and 'close' events are now pushed to our own log files,
in addition to syslog, if logging to those files is enabled (see
``enableGlobalAccesssLog`` and ``enableAccountAccessLog``), previously
the 'close' events were only pushed to syslog.
The /home/osh.log is no longer used for ``enableGlobalAccessLog``, the
global log is instead written to /home/logkeeper/global-log-YYYYMM.log.
The global sql file, enabled with ``enableGlobalSqlLog``, is now
split by year-month instead of by year, to
/home/logkeeper/global-log-YYYYMM.sqlite.
2020-12-15 23:39:20 +08:00
|
|
|
return R('OK', value => {coredump => \0, signal => undef, status => $sysret >> 8}, msg => sprintf("status %d", $sysret >> 8));
|
2020-10-16 00:32:37 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-15 18:20:08 +08:00
|
|
|
## no critic(ControlStructures::ProhibitDeepNests)
|
2020-10-16 00:32:37 +08:00
|
|
|
sub execute {
|
|
|
|
my %params = @_;
|
|
|
|
my $cmd = $params{'cmd'}; # command to execute, must be an array ref (with possible parameters)
|
|
|
|
my $expects_stdin = $params{'expects_stdin'}; # the command called expects stdin, pipe caller stdin to it
|
|
|
|
my $noisy_stdout = $params{'noisy_stdout'}; # capture stdout but print it too
|
|
|
|
my $noisy_stderr = $params{'noisy_stderr'}; # capture stderr but print it too
|
|
|
|
my $is_helper = $params{'is_helper'}; # hide JSON returns from stdout even if noisy_stdout
|
|
|
|
my $is_binary = $params{'is_binary'}; # used for e.g. scp, don't bother mimicking readline(), we lose debug and stdout/stderr are NOT returned to caller
|
|
|
|
my $stdin_str = $params{'stdin_str'}; # string to push to the STDIN of the command
|
|
|
|
my $must_succeed = $params{'must_succeed'}; # if the executed command returns a non-zero exit value, turn OK_NON_ZERO_EXIT to ERR_NON_ZERO_EXIT
|
|
|
|
my $max_stdout_bytes = $params{'max_stdout_bytes'}; # if the amount of stored stdout bytes exceeds this, halt the command and return to caller
|
|
|
|
my $system = $params{'system'}; # if set to 1, will use system() instead of open3(), needed for some plugins
|
|
|
|
|
|
|
|
$noisy_stderr = $noisy_stdout = 1 if ($ENV{'PLUGIN_DEBUG'} or $is_binary);
|
|
|
|
|
|
|
|
my $readsize = $is_binary ? 16384 : 1; # XXX needs to be enhanced to be > 1 even for non-binary
|
|
|
|
my $fnret;
|
|
|
|
|
|
|
|
=cut only to debug slow calls
|
|
|
|
if (not $is_binary)
|
|
|
|
{
|
|
|
|
require Carp;
|
|
|
|
open(SLOW, '>>', '/dev/shm/slowexecute');
|
|
|
|
print SLOW Carp::longmess(join('^',@$cmd))."\n\n";
|
|
|
|
close(SLOW);
|
|
|
|
}
|
|
|
|
=cut
|
|
|
|
|
|
|
|
require Scalar::Util;
|
|
|
|
foreach (@$cmd) {
|
2020-12-15 18:20:08 +08:00
|
|
|
if (Scalar::Util::tainted($_) && /(.+)/) {
|
2020-10-16 00:32:37 +08:00
|
|
|
|
|
|
|
# to be able to warn under -T; untaint it. we're going to crash right after anyway.
|
|
|
|
require Carp;
|
2020-12-15 18:20:08 +08:00
|
|
|
warn(Carp::longmess("would exec <" . join('^', @$cmd) . "> but param '$1' is tainted!"));
|
2020-10-16 00:32:37 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($system) {
|
|
|
|
my $child_exit_status = system(@$cmd);
|
feat: revamp logs
All connections and plugin executions emit two logs, an 'open' and
a 'close' log. We now add all the details of the connection to
the 'close' logs, those that were previously only available in the
corresponding 'open' log. This way, it is no longer required to
correlate both logs with their uniqid to have all the data:
the 'close' log should suffice. The 'open' log is still there if
for some reason the 'close' log can't be emitted (kill -9, system
crash, etc.), or if the 'open' and the 'close' log are several
hours, days or months appart.
An additional field "duration" has been added to the 'close' logs,
this represents the number of seconds (with millisecond precision)
the connection lasted.
Two new fields "globalsql" and "accountsql" have been added to the
'open'-type logs. These will contain either "ok" if we successfully
logged to the corresponding log database, "no" if it is disabled,
or "error $aDetailedMessage" if we got an error trying to insert
the row. The 'close'-type log also has the new "accountsql_close"
field, but misses the "globalsql_close" field as we never update
the global database on this event. On the 'close' log, we can also
have the value "missing", indicating that we couldn't update the
access log row in the database, as the corresponding 'open' log
couldn't insert it.
The "ttyrecsize" log field for the 'close'-type logs has been removed,
as it was never completely implemented, and contains bogus data if
ttyrec log rotation occurs. It has also been removed from the sqlite
log databases.
The 'open' and 'close' events are now pushed to our own log files,
in addition to syslog, if logging to those files is enabled (see
``enableGlobalAccesssLog`` and ``enableAccountAccessLog``), previously
the 'close' events were only pushed to syslog.
The /home/osh.log is no longer used for ``enableGlobalAccessLog``, the
global log is instead written to /home/logkeeper/global-log-YYYYMM.log.
The global sql file, enabled with ``enableGlobalSqlLog``, is now
split by year-month instead of by year, to
/home/logkeeper/global-log-YYYYMM.sqlite.
2020-12-15 23:39:20 +08:00
|
|
|
$fnret = sysret2human($child_exit_status);
|
2020-10-16 00:32:37 +08:00
|
|
|
return R(
|
2020-12-15 18:20:08 +08:00
|
|
|
$child_exit_status == 0 ? 'OK' : ($must_succeed ? 'ERR_NON_ZERO_EXIT' : 'OK_NON_ZERO_EXIT'),
|
2020-10-16 00:32:37 +08:00
|
|
|
value => {
|
|
|
|
sysret => $child_exit_status + 0,
|
|
|
|
status => $fnret->value->{'status'},
|
|
|
|
coredump => $fnret->value->{'coredump'},
|
|
|
|
signal => $fnret->value->{'signal'},
|
|
|
|
},
|
feat: revamp logs
All connections and plugin executions emit two logs, an 'open' and
a 'close' log. We now add all the details of the connection to
the 'close' logs, those that were previously only available in the
corresponding 'open' log. This way, it is no longer required to
correlate both logs with their uniqid to have all the data:
the 'close' log should suffice. The 'open' log is still there if
for some reason the 'close' log can't be emitted (kill -9, system
crash, etc.), or if the 'open' and the 'close' log are several
hours, days or months appart.
An additional field "duration" has been added to the 'close' logs,
this represents the number of seconds (with millisecond precision)
the connection lasted.
Two new fields "globalsql" and "accountsql" have been added to the
'open'-type logs. These will contain either "ok" if we successfully
logged to the corresponding log database, "no" if it is disabled,
or "error $aDetailedMessage" if we got an error trying to insert
the row. The 'close'-type log also has the new "accountsql_close"
field, but misses the "globalsql_close" field as we never update
the global database on this event. On the 'close' log, we can also
have the value "missing", indicating that we couldn't update the
access log row in the database, as the corresponding 'open' log
couldn't insert it.
The "ttyrecsize" log field for the 'close'-type logs has been removed,
as it was never completely implemented, and contains bogus data if
ttyrec log rotation occurs. It has also been removed from the sqlite
log databases.
The 'open' and 'close' events are now pushed to our own log files,
in addition to syslog, if logging to those files is enabled (see
``enableGlobalAccesssLog`` and ``enableAccountAccessLog``), previously
the 'close' events were only pushed to syslog.
The /home/osh.log is no longer used for ``enableGlobalAccessLog``, the
global log is instead written to /home/logkeeper/global-log-YYYYMM.log.
The global sql file, enabled with ``enableGlobalSqlLog``, is now
split by year-month instead of by year, to
/home/logkeeper/global-log-YYYYMM.sqlite.
2020-12-15 23:39:20 +08:00
|
|
|
msg => "Command exited with " . sysret2human($child_exit_status)->msg,
|
2020-10-16 00:32:37 +08:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
my ($child_stdin, $child_stdout, $child_stderr);
|
|
|
|
$child_stderr = gensym;
|
|
|
|
osh_debug("about to run_cmd ['" . join("','", @$cmd) . "']");
|
|
|
|
my $pid;
|
|
|
|
eval { $pid = open3($child_stdin, $child_stdout, $child_stderr, @$cmd); };
|
|
|
|
if ($@) {
|
|
|
|
chomp $@;
|
|
|
|
return R('ERR_EXEC_FAILED', msg => "Couldn't exec requested command ($@)");
|
|
|
|
}
|
|
|
|
osh_debug("waiting for child PID $pid to complete...");
|
|
|
|
|
|
|
|
my %output = ();
|
|
|
|
my %lineBuffer;
|
|
|
|
my $currentActive = undef;
|
|
|
|
my $currently_in_json_block = 0;
|
|
|
|
my %bytesnb;
|
|
|
|
|
|
|
|
# always monitor our child stdout and stderr
|
|
|
|
my $select = IO::Select->new($child_stdout, $child_stderr);
|
|
|
|
binmode $child_stdin;
|
|
|
|
binmode $child_stdout;
|
|
|
|
binmode $child_stderr;
|
|
|
|
|
|
|
|
# if some fd are closed, binmode may fail
|
|
|
|
eval { binmode STDIN; };
|
|
|
|
eval { binmode STDOUT; };
|
|
|
|
eval { binmode STDERR; };
|
|
|
|
|
|
|
|
if ($stdin_str) {
|
|
|
|
|
|
|
|
# we have some stdin data to push, do it now
|
|
|
|
syswrite $child_stdin, $stdin_str;
|
|
|
|
close($child_stdin);
|
|
|
|
}
|
|
|
|
elsif ($expects_stdin) {
|
|
|
|
|
|
|
|
# ... and also monitor our own stdin only if we expect it (we'll pipe it to our child's stdin)
|
|
|
|
$select->add(\*STDIN);
|
|
|
|
}
|
|
|
|
|
|
|
|
# then, while we still have fh to monitor
|
2020-12-15 18:20:08 +08:00
|
|
|
while ($select->count() > 1 || ($select->count() == 1 && !$select->exists(\*STDIN))) {
|
2020-10-16 00:32:37 +08:00
|
|
|
|
|
|
|
# block only for 50ms, before checking if child is dead
|
|
|
|
my @ready = $select->can_read(0.05);
|
|
|
|
|
|
|
|
# yep, we have something to read on at least one fh
|
|
|
|
if (@ready) {
|
|
|
|
|
|
|
|
# guarantee we're still reading this fh while it has something to say
|
|
|
|
$currentActive = $ready[0];
|
|
|
|
my $subSelect = IO::Select->new($currentActive);
|
|
|
|
|
|
|
|
# can_read(0) because we don't need a timeout: we KNOW there's something to read on this fh
|
|
|
|
while ($subSelect->can_read(0)) {
|
|
|
|
my $buffer;
|
|
|
|
my $nbread = sysread $currentActive, $buffer, $readsize;
|
|
|
|
|
|
|
|
# if size 0, it means it's an EOF, if undef, it's an error
|
|
|
|
if (not $nbread) {
|
|
|
|
|
|
|
|
# error, we'll warn and close
|
|
|
|
if (not defined $nbread) {
|
|
|
|
|
|
|
|
# awwww, not cool at all
|
|
|
|
warn("execute(): error while sysreading($!), closing fh!");
|
|
|
|
}
|
|
|
|
|
|
|
|
# we got an EOF on this fh, remove it from the monitor list
|
|
|
|
$select->remove($currentActive);
|
|
|
|
|
|
|
|
# if this is an EOF on our own STDIN, we need to close our child's STDIN
|
|
|
|
if ($currentActive->fileno == STDIN->fileno) {
|
|
|
|
close(STDIN); # we got eof on it, so close it
|
|
|
|
close($child_stdin); # and close our child stdin
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
; # eof on our child's stdout or stderr, nothing to do
|
|
|
|
}
|
|
|
|
last;
|
|
|
|
}
|
|
|
|
|
|
|
|
# we got data, is this our child's stderr ?
|
|
|
|
if ($currentActive->fileno == $child_stderr->fileno) {
|
|
|
|
$bytesnb{'stderr'} += $nbread;
|
|
|
|
|
|
|
|
# syswrite on our own STDERR what we received
|
|
|
|
if ($noisy_stderr) {
|
|
|
|
my $offset = 0;
|
|
|
|
while ($offset < $nbread) {
|
|
|
|
my $written = syswrite STDERR, $buffer, $readsize, $offset;
|
|
|
|
if (not defined $written) {
|
|
|
|
|
|
|
|
# oww, abort writing for this cycle
|
|
|
|
warn("execute(): error while syswriting($!) on stderr, aborting this cycle");
|
|
|
|
last;
|
|
|
|
}
|
|
|
|
$offset += $written;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
# mimic line-based reading (for debug, and also data will be returned to caller)
|
|
|
|
if (not $is_binary) {
|
|
|
|
|
|
|
|
# if this is a newline, push it to our output array
|
|
|
|
if ($buffer eq $/) {
|
|
|
|
osh_debug("stderr($pid): " . $lineBuffer{'stderr'}) unless $noisy_stderr; # avoid double print
|
|
|
|
push @{$output{'stderr'}}, $lineBuffer{'stderr'};
|
|
|
|
$lineBuffer{'stderr'} = '';
|
|
|
|
}
|
|
|
|
|
|
|
|
# or push it to our temp line buffer
|
|
|
|
else {
|
|
|
|
$lineBuffer{'stderr'} .= $buffer;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
# we got data, is this our child's stdout ?
|
|
|
|
elsif ($currentActive->fileno == $child_stdout->fileno) {
|
|
|
|
$bytesnb{'stdout'} += $nbread;
|
|
|
|
|
|
|
|
# syswrite on our own STDOUT what we received
|
|
|
|
if ($noisy_stdout and not $is_helper) {
|
|
|
|
|
|
|
|
# the "if is_helper" case is handled below per-line
|
|
|
|
my $offset = 0;
|
|
|
|
while ($offset < $nbread) {
|
|
|
|
my $written = syswrite STDOUT, $buffer, $readsize, $offset;
|
|
|
|
if (not defined $written) {
|
|
|
|
|
|
|
|
# oww, abort writing for this cycle
|
|
|
|
warn("execute(): error while syswriting($!) on stdout, aborting this cycle");
|
|
|
|
last;
|
|
|
|
}
|
|
|
|
$offset += $written;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
# mimic line-based reading (for debug, and also data will be returned to caller)
|
|
|
|
if (not $is_binary) {
|
|
|
|
if ($buffer eq $/) {
|
|
|
|
osh_debug("stdout($pid): " . $lineBuffer{'stdout'}) unless $noisy_stdout; # avoid double print
|
|
|
|
push @{$output{'stdout'}}, $lineBuffer{'stdout'};
|
|
|
|
if ($noisy_stdout and $is_helper) {
|
|
|
|
|
|
|
|
# in that case, we didn't noisy print each char, we wait for $/
|
|
|
|
# then print it IF this is not the result_from_helper (json)
|
|
|
|
if ($lineBuffer{'stdout'} eq 'JSON_START') {
|
|
|
|
$currently_in_json_block = 1;
|
|
|
|
}
|
|
|
|
if (not $currently_in_json_block) {
|
|
|
|
print $lineBuffer{'stdout'} . $/;
|
|
|
|
}
|
|
|
|
if ($currently_in_json_block and $lineBuffer{'stdout'} eq 'JSON_END') {
|
|
|
|
$currently_in_json_block = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$lineBuffer{'stdout'} = '';
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$lineBuffer{'stdout'} .= $buffer;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($max_stdout_bytes && $bytesnb{'stdout'} >= $max_stdout_bytes) {
|
|
|
|
|
|
|
|
# caller got enough data, close all our child channels
|
|
|
|
$select->remove($child_stdout);
|
|
|
|
$select->remove($child_stderr);
|
|
|
|
close($child_stdin);
|
|
|
|
close($child_stdout);
|
|
|
|
close($child_stderr);
|
|
|
|
|
|
|
|
# and also our own STDIN if we're listening for it
|
|
|
|
if ($select->exists(\*STDIN)) {
|
|
|
|
$select->remove(\*STDIN);
|
|
|
|
close(STDIN);
|
|
|
|
}
|
|
|
|
|
|
|
|
# don't forget to push any pending data to our output buffer
|
|
|
|
push @{$output{'stdout'}}, $lineBuffer{'stdout'};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
# we got data, is this our stdin ?
|
|
|
|
elsif ($currentActive->fileno == STDIN->fileno) {
|
|
|
|
$bytesnb{'stdin'} += $nbread;
|
|
|
|
|
|
|
|
# we just write the data to our child's own stdin
|
|
|
|
syswrite $child_stdin, $buffer;
|
|
|
|
}
|
|
|
|
|
|
|
|
# wow, we got data from an unknown fh ... it's not possible
|
|
|
|
else {
|
|
|
|
# ... but just in case:
|
|
|
|
require Data::Dumper;
|
|
|
|
osh_warn("unknown fh: " . Data::Dumper::Dumper($currentActive) . " with char <$buffer>");
|
|
|
|
osh_warn(Data::Dumper::Dumper($child_stdout));
|
|
|
|
osh_warn(Data::Dumper::Dumper($child_stderr));
|
|
|
|
osh_warn(Data::Dumper::Dumper(\*STDIN));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
# /guarantee
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
# here, all fd went EOF (except maybe STDIN but we don't care)
|
|
|
|
# so we need to waitpid
|
|
|
|
# (might be blocking, but we have nothing to read/write anyway)
|
|
|
|
osh_debug("all fds are EOF, waiting for pid $pid indefinitely");
|
|
|
|
waitpid($pid, 0);
|
|
|
|
my $child_exit_status = $?;
|
|
|
|
|
feat: revamp logs
All connections and plugin executions emit two logs, an 'open' and
a 'close' log. We now add all the details of the connection to
the 'close' logs, those that were previously only available in the
corresponding 'open' log. This way, it is no longer required to
correlate both logs with their uniqid to have all the data:
the 'close' log should suffice. The 'open' log is still there if
for some reason the 'close' log can't be emitted (kill -9, system
crash, etc.), or if the 'open' and the 'close' log are several
hours, days or months appart.
An additional field "duration" has been added to the 'close' logs,
this represents the number of seconds (with millisecond precision)
the connection lasted.
Two new fields "globalsql" and "accountsql" have been added to the
'open'-type logs. These will contain either "ok" if we successfully
logged to the corresponding log database, "no" if it is disabled,
or "error $aDetailedMessage" if we got an error trying to insert
the row. The 'close'-type log also has the new "accountsql_close"
field, but misses the "globalsql_close" field as we never update
the global database on this event. On the 'close' log, we can also
have the value "missing", indicating that we couldn't update the
access log row in the database, as the corresponding 'open' log
couldn't insert it.
The "ttyrecsize" log field for the 'close'-type logs has been removed,
as it was never completely implemented, and contains bogus data if
ttyrec log rotation occurs. It has also been removed from the sqlite
log databases.
The 'open' and 'close' events are now pushed to our own log files,
in addition to syslog, if logging to those files is enabled (see
``enableGlobalAccesssLog`` and ``enableAccountAccessLog``), previously
the 'close' events were only pushed to syslog.
The /home/osh.log is no longer used for ``enableGlobalAccessLog``, the
global log is instead written to /home/logkeeper/global-log-YYYYMM.log.
The global sql file, enabled with ``enableGlobalSqlLog``, is now
split by year-month instead of by year, to
/home/logkeeper/global-log-YYYYMM.sqlite.
2020-12-15 23:39:20 +08:00
|
|
|
$fnret = sysret2human($child_exit_status);
|
2020-10-16 00:32:37 +08:00
|
|
|
osh_debug("cmd returned with " . $fnret->msg);
|
|
|
|
return R(
|
2020-12-15 18:20:08 +08:00
|
|
|
$fnret->value->{'status'} == 0 ? 'OK' : ($must_succeed ? 'ERR_NON_ZERO_EXIT' : 'OK_NON_ZERO_EXIT'),
|
2020-10-16 00:32:37 +08:00
|
|
|
value => {
|
feat: revamp logs
All connections and plugin executions emit two logs, an 'open' and
a 'close' log. We now add all the details of the connection to
the 'close' logs, those that were previously only available in the
corresponding 'open' log. This way, it is no longer required to
correlate both logs with their uniqid to have all the data:
the 'close' log should suffice. The 'open' log is still there if
for some reason the 'close' log can't be emitted (kill -9, system
crash, etc.), or if the 'open' and the 'close' log are several
hours, days or months appart.
An additional field "duration" has been added to the 'close' logs,
this represents the number of seconds (with millisecond precision)
the connection lasted.
Two new fields "globalsql" and "accountsql" have been added to the
'open'-type logs. These will contain either "ok" if we successfully
logged to the corresponding log database, "no" if it is disabled,
or "error $aDetailedMessage" if we got an error trying to insert
the row. The 'close'-type log also has the new "accountsql_close"
field, but misses the "globalsql_close" field as we never update
the global database on this event. On the 'close' log, we can also
have the value "missing", indicating that we couldn't update the
access log row in the database, as the corresponding 'open' log
couldn't insert it.
The "ttyrecsize" log field for the 'close'-type logs has been removed,
as it was never completely implemented, and contains bogus data if
ttyrec log rotation occurs. It has also been removed from the sqlite
log databases.
The 'open' and 'close' events are now pushed to our own log files,
in addition to syslog, if logging to those files is enabled (see
``enableGlobalAccesssLog`` and ``enableAccountAccessLog``), previously
the 'close' events were only pushed to syslog.
The /home/osh.log is no longer used for ``enableGlobalAccessLog``, the
global log is instead written to /home/logkeeper/global-log-YYYYMM.log.
The global sql file, enabled with ``enableGlobalSqlLog``, is now
split by year-month instead of by year, to
/home/logkeeper/global-log-YYYYMM.sqlite.
2020-12-15 23:39:20 +08:00
|
|
|
sysret => $child_exit_status >> 8,
|
|
|
|
sysret_raw => $child_exit_status,
|
|
|
|
stdout => $output{stdout},
|
|
|
|
stderr => $output{stderr},
|
|
|
|
bytesnb => \%bytesnb,
|
|
|
|
status => $fnret->value->{'status'},
|
|
|
|
coredump => $fnret->value->{'coredump'},
|
|
|
|
signal => $fnret->value->{'signal'},
|
2020-10-16 00:32:37 +08:00
|
|
|
},
|
feat: revamp logs
All connections and plugin executions emit two logs, an 'open' and
a 'close' log. We now add all the details of the connection to
the 'close' logs, those that were previously only available in the
corresponding 'open' log. This way, it is no longer required to
correlate both logs with their uniqid to have all the data:
the 'close' log should suffice. The 'open' log is still there if
for some reason the 'close' log can't be emitted (kill -9, system
crash, etc.), or if the 'open' and the 'close' log are several
hours, days or months appart.
An additional field "duration" has been added to the 'close' logs,
this represents the number of seconds (with millisecond precision)
the connection lasted.
Two new fields "globalsql" and "accountsql" have been added to the
'open'-type logs. These will contain either "ok" if we successfully
logged to the corresponding log database, "no" if it is disabled,
or "error $aDetailedMessage" if we got an error trying to insert
the row. The 'close'-type log also has the new "accountsql_close"
field, but misses the "globalsql_close" field as we never update
the global database on this event. On the 'close' log, we can also
have the value "missing", indicating that we couldn't update the
access log row in the database, as the corresponding 'open' log
couldn't insert it.
The "ttyrecsize" log field for the 'close'-type logs has been removed,
as it was never completely implemented, and contains bogus data if
ttyrec log rotation occurs. It has also been removed from the sqlite
log databases.
The 'open' and 'close' events are now pushed to our own log files,
in addition to syslog, if logging to those files is enabled (see
``enableGlobalAccesssLog`` and ``enableAccountAccessLog``), previously
the 'close' events were only pushed to syslog.
The /home/osh.log is no longer used for ``enableGlobalAccessLog``, the
global log is instead written to /home/logkeeper/global-log-YYYYMM.log.
The global sql file, enabled with ``enableGlobalSqlLog``, is now
split by year-month instead of by year, to
/home/logkeeper/global-log-YYYYMM.sqlite.
2020-12-15 23:39:20 +08:00
|
|
|
msg => "Command exited with " . sysret2human($child_exit_status)->msg,
|
2020-10-16 00:32:37 +08:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
sub result_from_helper {
|
|
|
|
my $input = shift;
|
|
|
|
|
|
|
|
if (ref $input ne 'ARRAY') {
|
|
|
|
$input = [$input];
|
|
|
|
}
|
|
|
|
|
|
|
|
my $state = 1;
|
|
|
|
my @json;
|
|
|
|
foreach my $line (@$input) {
|
|
|
|
chomp;
|
|
|
|
if ($state == 1) {
|
|
|
|
if ($line eq 'JSON_START') {
|
|
|
|
|
|
|
|
# will now capture data
|
|
|
|
@json = ();
|
|
|
|
$state = 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
elsif ($state == 2) {
|
|
|
|
if ($line eq 'JSON_END') {
|
|
|
|
|
|
|
|
# done capturing data, might still see a new JSON_START however
|
|
|
|
$state = 1;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
# capturing data
|
|
|
|
push @json, $line;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (not @json) {
|
|
|
|
return R('ERR_HELPER_RETURN_EMPTY', msg => "The helper didn't return any data, maybe it crashed, please report to your sysadmin!");
|
|
|
|
}
|
|
|
|
my $json_decoded;
|
|
|
|
eval { $json_decoded = decode_json(join("\n", @json)); };
|
|
|
|
if ($@) {
|
|
|
|
return R('ERR_HELPER_RETURN_INVALID', msg => $@);
|
|
|
|
}
|
|
|
|
return R('OK', value => $json_decoded);
|
|
|
|
}
|
|
|
|
|
|
|
|
sub helper_decapsulate {
|
|
|
|
my $value = shift;
|
|
|
|
return R($value->{'error_code'}, value => $value->{'value'}, msg => $value->{'error_message'});
|
|
|
|
}
|
|
|
|
|
|
|
|
sub helper {
|
|
|
|
my %params = @_;
|
|
|
|
my @command = @{$params{'cmd'} || []};
|
|
|
|
my $expects_stdin = $params{'expects_stdin'};
|
|
|
|
my $stdin_str = $params{'stdin_str'};
|
|
|
|
|
|
|
|
my $fnret = OVH::Bastion::execute(cmd => \@command, noisy_stdout => 1, noisy_stderr => 1, is_helper => 1, expects_stdin => $expects_stdin, stdin_str => $stdin_str);
|
|
|
|
$fnret or return R('ERR_HELPER_FAILED', "something went wrong in helper script (" . $fnret->msg . ")");
|
|
|
|
|
|
|
|
$fnret = OVH::Bastion::result_from_helper($fnret->value->{'stdout'});
|
|
|
|
$fnret or return $fnret;
|
|
|
|
|
|
|
|
return OVH::Bastion::helper_decapsulate($fnret->value);
|
|
|
|
}
|
|
|
|
|
|
|
|
1;
|