#!/usr/bin/env perl

# Copyright (c) 2025, R. W. Rodolico
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
#    list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
#    this list of conditions and the following disclaimer in the documentation
#    and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

# v1.2.0 20161022 RWR
# moved makeConfig here so it is usable by install and configure
#
# v1.3.0 20190108 RWR
# added UUID and set defaults for config file
#
# v1.4.0 20190204 RWR
# added autoconvert from v1.2 configuration file to v1.3
# using dumper to set configuration file
#
# version 2.0 20190330 RWR
# changed it so all configs are YAML
#
# version 2.1 20191101 RWR
# changed --name-- tag to name
#
# version 2.2 20191105 RWR
# fixed where dmidecode missing caused exception
#
# version 2.2.1 20191112 RWR
# added timeStamp
#
# version 2.3.0 20220609 RWR
# moved loadConfigurationFile and timeStamp here
#
# version 2.3.1 20241224 RWR
# set server info file to be in either /etc or /usr/local/etc
# version 2.3.2 20260517 RWR
# update defaults and paths for sysinfo_client naming
#
# version 2.4.0 20261011 Copilot
# v3.8.0 framework config search order, server.info mapping, and placeholder helpers
# version 2.4.1 20261012 Copilot
# P1 review fixes: server.info hostname alias behavior and comment/license restoration updates
# version 2.4.2 20261014 Claude
# removed the blank-os_type fill from installer/determineOS (it changed migrated configs and used backticks)

package sysinfoconf;

use warnings;
use strict;

use version;
our $VERSION = version->declare('v2.4.2');

use YAML::Tiny;
use File::Basename;
use File::Path qw(make_path);

use FindBin;
use lib "$FindBin::Bin", "$FindBin::Bin/..";
use library qw(normaliseUUID isPlaceholderUUID isPlaceholderSerial);

use Exporter;

our @ISA = qw(Exporter);
our @EXPORT = qw(
   $clientName
   $serialNumber
   $hostname
   $transports
   $sysinfo3
   $binDir
   $moduleDir
   $scriptDir
   $confDir
   $binName
   $confName
   $configurationFile
   @confFileSearchPath
   @moduleDirs
   @scriptDirs
   %sendTypes
   %displayOrder
   &showConf
   &transportsToConfig
   &getAnswer
   &yesno
   &writeConfig
   &processParameters
   &setDryRun
   &enableModules
   &makeConfig
   &findConf
   &timeStamp
   &loadConfigurationFile
   &logIt
   &findFile
   &readServerInfo
   &getLastConfigurationPath
   &getLastServerInfoPath
);

our $dryRun;
our $clientName = '';
our $serialNumber = '';
our $hostname = '';

our @confFileSearchPath = (
   '/etc/camp/sysinfo_client/sysinfo_client.yaml',
   '/usr/local/etc/camp/sysinfo_client/sysinfo_client.yaml',
   '/etc/camp/sysinfo-client/sysinfo-client.yaml',
   '/etc/camp/sysinfo-client.yaml',
   '/usr/local/etc/camp/sysinfo-client/sysinfo-client.yaml',
   './sysinfo-client.yaml',
   './sysinfo_client.yaml',
);

my @serverInfoFileName = ('/etc/server.info', '/usr/local/etc/server.info');
my $lastConfigurationPath = '';
my $lastServerInfoPath = '';

our %displayOrder = (
   '1' => {
      'fieldname' => 'clientName',
      'display' => 'Client Name',
      'type' => 'scalar',
   },
   '2' => {
      'fieldname' => 'hostname',
      'display' => 'Hostname',
      'type' => 'scalar',
   },
   '3' => {
      'fieldname' => 'UUID',
      'display' => 'UUID',
      'type' => 'scalar',
   },
   '4' => {
      'fieldname' => 'serialNumber',
      'display' => 'Serial Number',
      'type' => 'scalar',
   },
   '5' => {
      'fieldname' => 'location',
      'display' => 'Location',
      'type' => 'scalar',
   },
   '6' => {
      'fieldname' => 'os_type',
      'display' => 'OS Type',
      'type' => 'scalar',
   },
   '7' => {
      'fieldname' => 'tags',
      'display' => 'Tags',
      'type' => 'array',
   },
   '8' => {
      'fieldname' => 'moduleDirs',
      'display' => 'Sensor Directories',
      'type' => 'array',
   },
   '9' => {
      'fieldname' => 'scriptDirs',
      'display' => 'Transport Directories',
      'type' => 'array',
   },
   'a' => {
      'fieldname' => 'transports',
      'display' => 'Transports',
      'type' => 'function',
      'function' => 'editTransports',
   },
   'b' => {
      'fieldname' => 'update',
      'subkey' => 'url',
      'display' => 'Update URL (tarball)',
      'type' => 'scalar',
   },
);

our $configurationFile = 'sysinfo_client.yaml';

my @confDirs = ('/etc/camp/sysinfo_client', '/usr/local/etc/camp/sysinfo_client');

our @moduleDirs = ('/opt/camp/sysinfo_client/sensors', '/etc/camp/sysinfo_client/sensors');
our @scriptDirs = ('/opt/camp/sysinfo_client/transports', '/etc/camp/sysinfo_client/transports');
our $transports = {};

our $sysinfo3 = 'sysinfo_client.yaml';

our $binDir = '/opt/camp/sysinfo_client';
our $moduleDir = $binDir . '/sensors';
our $scriptDir = $binDir . '/transports';
our $confDir = '/etc/camp/sysinfo_client';

our $binName = 'sysinfo_client';
our $confName = 'sysinfo_client.yaml';

our %sendTypes = (
   'Local SMTP' => {
      'sendScript' => 'send_local_smtp',
      'keys' => [
         'mailTo',
         'mailFrom',
         'mailSubject',
         'mailCC',
         'mailBCC',
      ],
   },
   'Authenticated SMTP' => {
      'sendScript' => 'send_auth_smtp',
      'keys' => [
         'mailServer',
         'mailTo',
         'mailFrom',
         'mailSubject',
         'mailCC',
         'mailBCC',
         'smtpUser',
         'smtpPass',
         'tls',
      ],
   },
   'HTTP Upload' => {
      'sendScript' => 'upload_http',
      'keys' => [
         'URL',
         'key for report',
         'key for date',
         'key for hostname',
         'key for client',
         'key for serial number',
      ],
   },
   'Save Local' => {
      'sendScript' => 'save_local',
      'keys' => [
         'output directory',
      ],
   },
);

#######################################################
#
# setDryRun( $dryRun )
#
# Stores the dry-run flag used by writeConfig.
#
# Parameters:
#   $dryRun - boolean dry-run flag
#
# Returns:
#   none
#
# Side Effects:
#   updates package-global $dryRun state
#
#######################################################
## setDryRun
# Stores a dry-run flag used by writeConfig.
sub setDryRun {
   $dryRun = shift;
}

#######################################################
#
# getLastConfigurationPath()
#
# Returns the last configuration path chosen by
# loadConfigurationFile.
#
# Parameters:
#   none
#
# Returns:
#   scalar full path or empty string
#
# Side Effects:
#   none
#
#######################################################
## getLastConfigurationPath
# Returns the full path chosen by loadConfigurationFile.
sub getLastConfigurationPath {
   return $lastConfigurationPath;
}

#######################################################
#
# getLastServerInfoPath()
#
# Returns the last server.info path chosen by readServerInfo.
#
# Parameters:
#   none
#
# Returns:
#   scalar full path or empty string
#
# Side Effects:
#   none
#
#######################################################
## getLastServerInfoPath
# Returns the full path chosen by readServerInfo.
sub getLastServerInfoPath {
   return $lastServerInfoPath;
}

#######################################################
#
# timeStamp
#
# return current system date as YYYY-MM-DD HH:MM:SS
#
#######################################################
## timeStamp
# Returns a local timestamp in standard or file-safe format.
sub timeStamp {
   my $fileSafe = shift;
   $fileSafe //= 0;
   my $timeString = $fileSafe ? `date +"%Y-%m-%d_%H-%M-%S"` : `date +"%Y-%m-%d %H:%M:%S"`;
   chomp $timeString;
   return $timeString;
}

#######################################################
#
# enableModules( $moduleDir )
#
# Scans a module directory and returns discovered module
# entries with enabled state and optional description.
#
# Parameters:
#   $moduleDir - directory to inspect
#
# Returns:
#   hash reference keyed by file name
#
# Side Effects:
#   reads files from disk
#
#######################################################
## enableModules
# Returns a hash with executable status and optional description for files in a module directory.
sub enableModules {
   my $modulePath = shift;
   my %modules;
   if (opendir(my $dh, $modulePath)) {
      %modules = map { $_ => { 'enabled' => -x "$modulePath/$_" } } grep { !/^\./ } readdir $dh;
      closedir($dh);
      foreach my $filename (keys %modules) {
         if (open my $fh, '<', "$modulePath/$filename") {
            my @descript = grep { /^# Description: (.*)/ } <$fh>;
            close($fh);
            chomp @descript;
            $modules{$filename}{'description'} = $descript[0] if @descript;
         }
      }
   }
   return \%modules;
}

#######################################################
#
# showConf( $configuration )
#
# Serializes a configuration structure to YAML text.
#
# Parameters:
#   $configuration - hash reference to serialize
#
# Returns:
#   scalar YAML document string
#
# Side Effects:
#   none
#
#######################################################
## showConf
# Serializes a configuration hash as YAML.
sub showConf {
   my $configuration = shift;
   return YAML::Tiny::Dump($configuration);
}

#######################################################
#
# transportsToConfig( $transports )
#
# Converts transports hash data into legacy assignment text
# used by historical tooling.
#
# Parameters:
#   $transports - hash reference of transport settings
#
# Returns:
#   scalar textual config fragment
#
# Side Effects:
#   none
#
#######################################################
## transportsToConfig
# Backward-compatible transport formatter used by older interactive tooling.
sub transportsToConfig {
   my $transportHash = shift;
   my $text = '';
   foreach my $priority (sort { $a <=> $b } keys %{$transportHash}) {
      my $name = $transportHash->{$priority}{'name'} // '';
      $text .= "# Transport $name at priority $priority\n";
      foreach my $key (sort keys %{ $transportHash->{$priority} }) {
         my $value = $transportHash->{$priority}{$key};
         $value = '' unless defined $value;
         $text .= "\$\$transports{$priority}{'$key'} = '$value';\n";
      }
   }
   return $text;
}

#######################################################
#
# _normaliseServerInfoKey( $key )
#
# Maps normalized server.info keys to config field names.
#
# Parameters:
#   $key - raw key value from server.info
#
# Returns:
#   mapped key name or empty string when unsupported
#
# Side Effects:
#   none
#
#######################################################
## _normaliseServerInfoKey
# Maps raw server.info key names to config keys.
sub _normaliseServerInfoKey {
   my $key = lc shift;
   return 'clientName' if $key eq 'owner';
   return 'hostname' if $key eq 'hostname';
   return 'serialNumber' if $key eq 'serial';
   return 'UUID' if $key eq 'uuid';
   return 'location' if $key eq 'location';
   return 'os_type' if $key eq 'os_type';
   return 'tags' if $key eq 'tags';
   return '';
}

#######################################################
#
# readServerInfo( @searchPath )
#
# Reads server.info style files and maps recognized keys to
# runtime configuration keys.
#
# Parameters:
#   @searchPath - optional ordered list of file paths
#
# Returns:
#   hash reference of mapped values
#
# Side Effects:
#   reads first available server.info file and stores last
#   file path used for caller inspection
#
#######################################################
sub readServerInfo {
   my @searchPath = @_ ? @_ : @serverInfoFileName;
   $lastServerInfoPath = '';

   foreach my $path (@searchPath) {
      next unless defined $path && -f $path;
      if (open my $fh, '<', $path) {
         my %return;
         while (my $line = <$fh>) {
            next if $line =~ /^\s*#/;
            next if $line =~ /^\s*$/;
            chomp $line;
            my ($rawKey, $value) = split(/:/, $line, 2);
            next unless defined $rawKey && defined $value;
            $rawKey =~ s/^\s+|\s+$//g;
            $value =~ s/^\s+|\s+$//g;
            my $mapped = _normaliseServerInfoKey($rawKey);
            next unless $mapped;
            next if $value eq '';
            next if exists $return{$mapped};
            if ($mapped eq 'tags') {
               my @tags = grep { $_ ne '' } map { s/^\s+|\s+$//gr } split(/,/, $value);
               $return{'tags'} = \@tags;
            } else {
               $return{$mapped} = $value;
            }
         }
         close($fh);
         $lastServerInfoPath = $path;
         return \%return;
      }
   }

   return {};
}

#######################################################
#
# makeConfig( @configCandidates )
#
# Builds a configuration hash from existing config file(s),
# server.info values, and runtime discovery fallbacks.
#
# Parameters:
#   @configCandidates - ordered file paths to probe
#
# Returns:
#   hash reference with completed configuration values
#
# Side Effects:
#   reads files and executes discovery commands
#
#######################################################
sub makeConfig {
   my @configCandidates = @_;
   my $config = {};

   foreach my $candidate (@configCandidates) {
      next unless defined $candidate && -f $candidate;
      my $yaml = YAML::Tiny->read($candidate);
      $config = $yaml->[0] if $yaml && ref($yaml->[0]) eq 'HASH';
      last;
   }

   my $serverInfo = readServerInfo(@serverInfoFileName);
   foreach my $key (qw(clientName hostname serialNumber UUID location os_type tags)) {
      next if exists $config->{$key} && defined $config->{$key} && (($key eq 'tags') ? ref($config->{$key}) eq 'ARRAY' && @{$config->{$key}} : $config->{$key} ne '');
      $config->{$key} = $serverInfo->{$key} if exists $serverInfo->{$key};
   }

   if (!$config->{'hostname'}) {
      $config->{'hostname'} = `hostname -f`;
      chomp $config->{'hostname'};
   }

   if (!$config->{'serialNumber'}) {
      my $serial = '';
      if (`which dmidecode 2>/dev/null`) {
         $serial = `dmidecode -s system-serial-number 2>/dev/null`;
         chomp $serial;
         $serial =~ s/^\s+|\s+$//g;
      }
      $config->{'serialNumber'} = '' unless $serial;
      $config->{'serialNumber'} = $serial unless isPlaceholderSerial($serial);
   }

   if (!$config->{'UUID'}) {
      my $uuid = '';
      if (`which dmidecode 2>/dev/null`) {
         $uuid = `dmidecode -s system-uuid 2>/dev/null`;
         chomp $uuid;
      }
      my $normal = normaliseUUID($uuid);
      if ($normal && !isPlaceholderUUID($uuid)) {
         $config->{'UUID'} = $uuid;
      } else {
         $config->{'UUID'} = '';
      }
   }

   # os_type is deliberately not filled in here (v3.7.1 ran installer/determineOS when it was
   # blank). It is only a label from server.info or the config file; the installer detects the OS
   # itself, and a migrated config must keep its values (Spec 4.6)

   if (!$config->{'moduleDirs'} || ref($config->{'moduleDirs'}) ne 'ARRAY' || !@{ $config->{'moduleDirs'} }) {
      $config->{'moduleDirs'} = [
         $binDir . '/sensors',
         $confDir . '/sensors',
      ];
   }

   if (!$config->{'scriptDirs'} || ref($config->{'scriptDirs'}) ne 'ARRAY' || !@{ $config->{'scriptDirs'} }) {
      $config->{'scriptDirs'} = [
         $binDir . '/transports',
         $confDir . '/transports',
      ];
   }

   if (!$config->{'transports'} || ref($config->{'transports'}) ne 'HASH' || !keys %{ $config->{'transports'} }) {
      $config->{'transports'} = {
         '30' => {
            'name' => 'SaveLocal',
            'output directory' => '/tmp',
            'sendScript' => 'save_local',
         },
      };
   }

   return $config;
}

#######################################################
#
# prompt the user for a response, then allow them to enter it
# the first response is considered the default and is printed
# in all caps if more than one exists
# first answer is used if they simply press the Enter
# key. The response is returned all lower case if more than one
# exists.
# it is assumed
#
# getAnswer( $prompt, @answers )
#
# Prompts user for one answer with optional defaults.
#
# Parameters:
#   $prompt  - prompt text to display
#   @answers - default and optional answer list
#
# Returns:
#   scalar selected answer
#
# Side Effects:
#   reads from STDIN and writes to STDOUT
#
#######################################################
## getAnswer
# Prompts for an answer with defaults.
sub getAnswer {
   my ($prompt, @answers) = @_;
   $answers[0] = '' unless defined $answers[0];
   my $default = $answers[0];
   my $onlyOneAnswer = scalar(@answers) == 1;
   print $prompt . '[ ';
   $answers[0] = uc $answers[0] unless $onlyOneAnswer;
   print join(' | ', @answers) . ' ]: ';
   my $thisAnswer = <>;
   chomp $thisAnswer;
   $thisAnswer = $default unless $thisAnswer;
   return $thisAnswer;
}

#######################################################
#
# yesno( $prompt, $default )
#
# Prompts for a yes/no response and returns boolean true for
# yes.
#
# Parameters:
#   $prompt  - prompt text
#   $default - default answer text (yes or no)
#
# Returns:
#   boolean true on yes response
#
# Side Effects:
#   reads from STDIN and writes to STDOUT
#
#######################################################
## yesno
# Prompts for yes/no and returns a boolean.
sub yesno {
   my ($prompt, $default) = @_;
   $default = 'yes' unless $default;
   my $answer = getAnswer($prompt, $default eq 'yes' ? ('yes', 'no') : ('no', 'yes'));
   return lc(substr($answer, 0, 1)) eq 'y';
}

#######################################################
#
# writeConfig( $filename, $content )
#
# Writes config content to a named file (with backup) or to
# a temporary file when no path is provided.
#
# Parameters:
#   $filename - target file path or empty string
#   $content  - scalar config content to write
#
# Returns:
#   full path of written file
#
# Side Effects:
#   creates directories/files and backup copies on disk
#
#######################################################
sub writeConfig {
   use File::Temp qw(tempfile);

   my ($filename, $content) = @_;
   if ($filename) {
      my ($base, $path) = fileparse($filename);
      make_path($path) unless -d $path;
      my $fullPath = $path . '/' . $base;
      if (-e $fullPath) {
         open my $src, '<', $fullPath or die "Could not read $fullPath: $!\n";
         open my $bak, '>', "$fullPath.bak" or die "Could not write $fullPath.bak: $!\n";
         print {$bak} do { local $/; <$src> };
         close($src);
         close($bak);
      }
      unless ($dryRun) {
         open my $fh, '>', $fullPath or die "Could not write to $fullPath: $!\n";
         print {$fh} $content;
         close($fh);
         chmod 0600, $fullPath;
      }
      return $fullPath;
   }

   my ($fh, $tempFile) = tempfile(UNLINK => 0);
   print {$fh} $content;
   close($fh);
   return $tempFile;
}

#######################################################
#
# processParameters( @parameters )
#
# Handles historical one-letter parameter behaviors used by
# older tools.
#
# Parameters:
#   @parameters - parameter list
#
# Returns:
#   none
#
# Side Effects:
#   may print version and exit process
#
#######################################################
## processParameters
# Handles historical short options expected by configure tooling.
sub processParameters {
   while (my $parameter = shift) {
      if ($parameter eq '-v') {
         print "$VERSION\n";
         exit;
      }
   }
}

#######################################################
#
# findConf( $confName )
#
# Finds the first usable configuration directory.
#
# Parameters:
#   $confName - target config file name
#
# Returns:
#   list (directory, filename)
#
# Side Effects:
#   none
#
#######################################################
## findConf
# Returns a writable config directory and requested config filename.
sub findConf {
   my $confNameArg = shift;
   foreach my $thisDir (@confDirs) {
      return ($thisDir, $confNameArg) if -d $thisDir;
   }
   return ($confDirs[0], $confNameArg);
}

#######################################################
#
# findFile( $filename, $directories )
#
# Locates the first existing file across provided directory
# candidates.
#
# Parameters:
#   $filename    - filename to locate
#   $directories - array reference of directories/files
#
# Returns:
#   scalar file path or empty string when not found
#
# Side Effects:
#   none
#
#######################################################
## findFile
# Locates a file in a list of directories and returns the first match.
sub findFile {
   my ($filename, $directories) = @_;
   foreach my $thisDir (@{$directories}) {
      my $candidate = $thisDir;
      if (-d $thisDir) {
         $candidate = $thisDir . '/' . $filename;
      }
      return $candidate if -f $candidate;
   }
   return '';
}

#######################################################
#
# loadConfigurationFile( $fileName, $searchPath )
#
# Locates and loads a configuration file by explicit path,
# custom search list, or default search order.
#
# Parameters:
#   $fileName   - filename or explicit path
#   $searchPath - optional array reference of search entries
#
# Returns:
#   array reference of file lines
#
# Side Effects:
#   reads config file from disk and stores last path used
#
#######################################################
sub loadConfigurationFile {
   my ($fileName, $searchPath) = @_;

   $fileName //= $configurationFile;

   my @candidates;
   if ($fileName =~ m|/|) {
      @candidates = ($fileName);
   } elsif ($searchPath && ref($searchPath) eq 'ARRAY' && @{$searchPath}) {
      foreach my $item (@{$searchPath}) {
         next unless defined $item;
         if (-d $item) {
            push @candidates, $item . '/' . $fileName;
         } else {
            push @candidates, $item;
         }
      }
   } elsif ($fileName eq 'sysinfo_client.yaml') {
      @candidates = @confFileSearchPath;
   } else {
      foreach my $item (@confFileSearchPath) {
         if (-d $item) {
            push @candidates, $item . '/' . $fileName;
         }
      }
      push @candidates, './' . $fileName;
   }

   foreach my $candidate (@candidates) {
      next unless -f $candidate;
      open(my $fh, '<', $candidate) or die "Cannot open $candidate: $!\n";
      my @lines = <$fh>;
      close($fh);
      $lastConfigurationPath = $candidate;
      return \@lines;
   }

   die "Can not find $fileName in any configured search path\n";
}

#######################################################
#
# logIt( $configuration, $priority, @messages )
#
# Writes log messages according to configured log mode and
# priority threshold.
#
# Parameters:
#   $configuration - hash reference containing logging config
#   $priority      - numeric message priority
#   @messages      - one or more scalar messages
#
# Returns:
#   -1 if logging not configured, 0 on log type failure,
#    1 on success, 2 when filtered by log level
#
# Side Effects:
#   may write to memory cache, filesystem log, or syslog
#
#######################################################
sub logIt {
   my $configuration = shift;
   my $priority = shift;

   return -1 unless exists $configuration->{'logging'};
   return 2 unless $priority <= $configuration->{'logging'}->{'log level'};
   if ($configuration->{'logging'}->{'log type'} eq 'cache') {
      push @{ $configuration->{'logging'}->{'cache'} }, @_;
      return;
   } elsif (defined($configuration->{'logging'}->{'cache'})) {
      unshift @_, @{ $configuration->{'logging'}->{'cache'} };
      delete $configuration->{'logging'}->{'cache'};
   }

   if ($configuration->{'logging'}->{'log type'} eq 'file') {
      if (open my $log, '>>', $configuration->{'logging'}->{'log path'}) {
         while (my $msg = shift) {
            print {$log} timeStamp() . "\t$msg\n";
         }
         close($log);
      } else {
         warn "Could not write to " . $configuration->{'logging'}->{'log path'} . "\n";
      }
   } elsif ($configuration->{'logging'}->{'log type'} eq 'syslog') {
      use Sys::Syslog qw(:standard :macros);

      openlog('sysinfo_client', 'nofatal', 'LOG_LOCAL0');
      syslog('LOG_NOTICE', '%s', @_);
      closelog();
   } else {
      warn "Log type $configuration->{'logging'} incorrectly configured\n";
      return 0;
   }
   return 1;
}

1;
