shell bypass 403
package FusionInventory::Agent::Config;
use strict;
use warnings;
use English qw(-no_match_vars);
use File::Spec;
use Cwd qw(abs_path);
use Getopt::Long;
use UNIVERSAL::require;
use FusionInventory::Agent::Version;
use FusionInventory::Agent::Tools;
my $default = {
'additional-content' => undef,
'backend-collect-timeout' => 180,
'ca-cert-dir' => undef,
'ca-cert-file' => undef,
'color' => undef,
'conf-reload-interval' => 0,
'debug' => undef,
'delaytime' => 3600,
'force' => undef,
'html' => undef,
'lazy' => undef,
'local' => undef,
'logger' => 'Stderr',
'logfile' => undef,
'logfacility' => 'LOG_USER',
'logfile-maxsize' => undef,
'no-category' => [],
'no-httpd' => undef,
'no-ssl-check' => undef,
'no-compression' => undef,
'no-task' => [],
'no-p2p' => undef,
'password' => undef,
'proxy' => undef,
'httpd-ip' => undef,
'httpd-port' => 62354,
'httpd-trust' => [],
'listen' => undef,
'scan-homedirs' => undef,
'scan-profiles' => undef,
'server' => undef,
'tag' => undef,
'tasks' => undef,
'timeout' => 180,
'user' => undef,
'vardir' => undef,
# deprecated options
'stdout' => undef,
};
my $deprecated = {
'stdout' => {
message => 'use --local - option instead',
new => { 'local' => '-' }
},
};
my $confReloadIntervalMinValue = 60;
sub new {
my ($class, %params) = @_;
my $self = {
'_confdir' => '/etc/fusioninventory',
'_options' => $params{options} // {},
};
bless $self, $class;
$self->_loadDefaults();
$self->_loadFromBackend($params{options}->{'conf-file'}, $params{options}->{config});
$self->_loadUserParams($params{options});
$self->{vardir} = $params{vardir};
# To also keep vardir during reload
$self->{_options}->{vardir} = $params{vardir};
$self->_checkContent();
return $self;
}
sub reload {
my ($self) = @_;
$self->_loadDefaults;
$self->_loadFromBackend($self->{'conf-file'}, $self->{config});
# Reload script options and vardir
$self->_loadUserParams($self->{_options});
$self->_checkContent();
# delaytime must not be used after a reload
$self->{delaytime} = 0;
}
sub _loadFromBackend {
my ($self, $confFile, $config) = @_;
my $backend =
$confFile ? 'file' :
$config ? $config :
$OSNAME eq 'MSWin32' ? 'registry' :
'file';
SWITCH: {
if ($backend eq 'registry') {
die "Config: Unavailable configuration backend\n"
unless $OSNAME eq 'MSWin32';
$self->_loadFromRegistry();
last SWITCH;
}
if ($backend eq 'file') {
# Handle loadedConfs to avoid loops
$self->{loadedConfs} = {};
$self->loadFromFile({
file => $confFile
});
delete $self->{loadedConfs};
last SWITCH;
}
if ($backend eq 'none') {
last SWITCH;
}
die "Config: Unknown configuration backend '$backend'\n";
}
}
sub _loadDefaults {
my ($self) = @_;
foreach my $key (keys %$default) {
$self->{$key} = $default->{$key};
}
# No need to reset confdir at each call
return if $self->{_confdir} && -d $self->{_confdir};
# Set absolute confdir from default if replaced by Makefile otherwise search
# from current path, mostly useful while running from source
$self->{_confdir} = abs_path(File::Spec->rel2abs(
$self->{_confdir} || first { -d $_ } qw{ ./etc ../etc }
));
}
sub _loadFromRegistry {
my ($self) = @_;
my $Registry;
Win32::TieRegistry->require();
Win32::TieRegistry->import(
Delimiter => '/',
ArrayValues => 0,
TiedRef => \$Registry
);
my $machKey = $Registry->Open('LMachine', {
Access => Win32::TieRegistry::KEY_READ()
}) or die "Config: Can't open HKEY_LOCAL_MACHINE key: $EXTENDED_OS_ERROR\n";
my $provider = $FusionInventory::Agent::Version::PROVIDER;
my $settings = $machKey->{"SOFTWARE/$provider-Agent"};
foreach my $rawKey (keys %$settings) {
next unless $rawKey =~ /^\/(\S+)/;
my $key = lc($1);
my $val = $settings->{$rawKey};
# Remove the quotes
$val =~ s/\s+$//;
$val =~ s/^'(.*)'$/$1/;
$val =~ s/^"(.*)"$/$1/;
if (exists $default->{$key}) {
$self->{$key} = $val;
} else {
warn "Config: unknown configuration directive $key\n";
}
}
}
sub confdir {
my ($self) = @_;
return $self->{_confdir};
}
sub loadFromFile {
my ($self, $params) = @_;
my $file = $params->{file} ?
$params->{file} : $self->{_confdir} . '/agent.cfg';
if ($file) {
die "Config: non-existing file $file\n" unless -f $file;
die "Config: non-readable file $file\n" unless -r $file;
} else {
die "Config: no configuration file\n";
}
# Don't reload conf if still loaded avoiding loops due to include directive
if ($self->{loadedConfs}->{$file}) {
warn "Config: $file configuration file still loaded\n"
if $self->{logger} && ucfirst($self->{logger}) eq 'Stderr';
return;
}
$self->{loadedConfs}->{$file} = 1;
my $handle;
if (!open $handle, '<', $file) {
warn "Config: Failed to open $file: $ERRNO\n";
return;
}
while (my $line = <$handle>) {
if ($line =~ /^\s*([\w-]+)\s*=\s*(.*)$/) {
my $key = $1;
my $val = $2;
# Cleanup value from ending spaces
$val =~ s/\s+$//;
# Extract value from quotes or clean any comment including preceding spaces
if ($val =~ /^(['"])([^\1]*)\1/) {
my ($quote, $extract) = ( $1, $2 );
$val =~ s/\s*#.+$//;
warn "Config: We may have been confused for $key quoted value, our extracted value: '$extract'\n"
if ($val ne "$quote$extract$quote");
$val = $extract ;
} else {
$val =~ s/\s*#.+$//;
}
if ($params->{defaults} && exists $params->{defaults}->{$key}) {
$self->{$key} = $val;
} elsif (!$params->{defaults} && exists $default->{$key}) {
$self->{$key} = $val;
} elsif (lc($key) eq 'include') {
$self->_includeDirective($val, $file);
} else {
warn "Config: unknown configuration directive $key\n";
}
} elsif ($line =~ /^\s*include\s+(.+)$/i) {
my $include = $1;
if ($include =~ /^(['"])([^\1]*)\1/) {
my ($quote, $extract) = ( $1, $2 );
$include =~ s/\s*#.+$//;
warn "Config: We may have been confused for include quoted path, our extracted path: '$extract'\n"
if ($include ne "$quote$extract$quote");
$include = $extract ;
} else {
$include =~ s/\s*#.+$//;
}
$self->_includeDirective($include, $file, $params->{defaults});
}
}
close $handle;
}
sub _includeDirective {
my ($self, $include, $currentconfig, $defaults) = @_;
# Make include path absolute, relatively to current file basedir
unless (File::Spec->file_name_is_absolute($include)) {
my @path = File::Spec->splitpath($currentconfig);
$path[2] = $include;
$include = File::Spec->catpath(@path);
}
# abs_path makes call die under windows if file doen't exist, so we need to eval it
eval {
$include = abs_path($include);
};
return unless $include;
if (-d $include) {
foreach my $cfg ( sort glob("$include/*.cfg") ) {
# Skip missing or non-readable file
next unless -f $cfg && -r $cfg;
$self->loadFromFile({ file => $cfg, defaults => $defaults });
}
} elsif ( -f $include && -r $include ) {
$self->loadFromFile({ file => $include, defaults => $defaults });
}
}
sub _loadUserParams {
my ($self, $params) = @_;
foreach my $key (keys %$params) {
$self->{$key} = $params->{$key};
}
}
sub _checkContent {
my ($self) = @_;
# check for deprecated options
foreach my $old (keys %$deprecated) {
next unless defined $self->{$old};
next if $old =~ /^no-/ and !$self->{$old};
my $handler = $deprecated->{$old};
# notify user of deprecation
warn "Config: the '$old' option is deprecated, $handler->{message}\n";
# transfer the value to the new option, if possible
if ($handler->{new}) {
if (ref $handler->{new} eq 'HASH') {
# old boolean option replaced by new non-boolean options
foreach my $key (keys %{$handler->{new}}) {
my $value = $handler->{new}->{$key};
if ($value =~ /^\+(\S+)/) {
# multiple values: add it to exiting one
$self->{$key} = $self->{$key} ?
$self->{$key} . ',' . $1 : $1;
} else {
# unique value: replace exiting value
$self->{$key} = $value;
}
}
} elsif (ref $handler->{new} eq 'ARRAY') {
# old boolean option replaced by new boolean options
foreach my $new (@{$handler->{new}}) {
$self->{$new} = $self->{$old};
}
} else {
# old non-boolean option replaced by new option
$self->{$handler->{new}} = $self->{$old};
}
}
# avoid cluttering configuration
delete $self->{$old};
}
# a logfile options implies a file logger backend
if ($self->{logfile}) {
$self->{logger} .= ',File';
}
# ca-cert-file and ca-cert-dir are antagonists
if ($self->{'ca-cert-file'} && $self->{'ca-cert-dir'}) {
die "Config: use either 'ca-cert-file' or 'ca-cert-dir' option, not both\n";
}
# logger backend without a logfile isn't enoguh
if ($self->{'logger'} =~ /file/i && ! $self->{'logfile'}) {
die "Config: usage of 'file' logger backend makes 'logfile' option mandatory\n";
}
# multi-values options, the default separator is a ','
foreach my $option (qw/
logger
local
server
httpd-trust
no-task
no-category
tasks
/) {
# Check if defined AND SCALAR
# to avoid split a ARRAY ref or HASH ref...
if ($self->{$option} && ref($self->{$option}) eq '') {
$self->{$option} = [split(/,/, $self->{$option})];
} else {
$self->{$option} = [];
}
}
# files location
$self->{'ca-cert-file'} =
File::Spec->rel2abs($self->{'ca-cert-file'}) if $self->{'ca-cert-file'};
$self->{'ca-cert-dir'} =
File::Spec->rel2abs($self->{'ca-cert-dir'}) if $self->{'ca-cert-dir'};
$self->{'logfile'} =
File::Spec->rel2abs($self->{'logfile'}) if $self->{'logfile'};
$self->{'vardir'} =
File::Spec->rel2abs($self->{'vardir'}) if $self->{'vardir'};
# conf-reload-interval option
# If value is less than the required minimum, we force it to that
# minimum because it's useless to reload the config so often and,
# furthermore, it can cause a loss of performance
if ($self->{'conf-reload-interval'} != 0) {
if ($self->{'conf-reload-interval'} < 0) {
$self->{'conf-reload-interval'} = 0;
} elsif ($self->{'conf-reload-interval'} < $confReloadIntervalMinValue) {
$self->{'conf-reload-interval'} = $confReloadIntervalMinValue;
}
}
}
sub isParamArrayAndFilled {
my ($self, $paramName) = @_;
return unless defined($self->{$paramName});
return unless ref($self->{$paramName}) eq 'ARRAY';
return scalar(@{$self->{$paramName}}) > 0;
}
sub logger {
my ($self) = @_;
return {
map { $_ => $self->{$_} }
qw/debug logger logfacility logfile logfile-maxsize color/
};
}
sub getTargets {
my ($self, %params) = @_;
my @targets = ();
# create target list
if ($self->{local}) {
FusionInventory::Agent::Target::Local->require();
FusionInventory::Agent::Target::Local->reset();
foreach my $path (@{$self->{local}}) {
push @targets,
FusionInventory::Agent::Target::Local->new(
logger => $params{logger},
delaytime => $self->{delaytime},
basevardir => $params{vardir},
path => $path,
html => $self->{html},
);
}
}
if ($self->{server}) {
FusionInventory::Agent::Target::Server->require();
FusionInventory::Agent::Target::Scheduler->require();
FusionInventory::Agent::Target::Server->reset();
FusionInventory::Agent::Target::Scheduler->reset();
foreach my $url (@{$self->{server}}) {
my $server = FusionInventory::Agent::Target::Server->new(
logger => $params{logger},
delaytime => $self->{delaytime},
basevardir => $params{vardir},
url => $url,
tag => $self->{tag},
);
# Also setup one Scheduler target for each target, actually
# it only used by Maintenance task to cleanup storage from
# expired files
# Schedule it to run every 2 minutes max by default
my $scheduler = FusionInventory::Agent::Target::Scheduler->new(
logger => $params{logger},
delaytime => $self->{delaytime} ? 60 : 0,
maxDelay => 120,
basevardir => $params{vardir},
storage => $server->getStorage(),
);
push @targets, $server, $scheduler;
}
}
# Only add listener target if no other target has been defined and
# httpd daemon is enabled. And anyway only one listener should be enabled
if ($self->{listen} && !@targets && !$self->{'no-httpd'}) {
FusionInventory::Agent::Target::Listener->require();
push @targets,
FusionInventory::Agent::Target::Listener->new(
logger => $params{logger},
delaytime => $self->{delaytime},
basevardir => $params{vardir},
);
}
return \@targets;
}
1;
__END__
=head1 NAME
FusionInventory::Agent::Config - Agent configuration
=head1 DESCRIPTION
This is the object used by the agent to store its configuration.
=head1 METHODS
=head2 new(%params)
The constructor. The following parameters are allowed, as keys of the %params
hash:
=over
=item I<options>
additional options override.
=back
=head2 logger()
Get logger only configuration.