#!/usr/bin/perl

eval 'exec /usr/bin/perl  -S $0 ${1+"$@"}'
    if 0; # not running under some shell
# forcer un upload si changement en local
# ajouter /etc/ocsinventory-agent.conf

use strict;
use warnings;

use lib 'lib';
# THIS IS AN UGLY WORKAROUND FOR
# http://rt.cpan.org/Ticket/Display.html?id=38067
use XML::Simple;

eval {XMLout("<a>b</a>");};
if ($@){
    no strict 'refs';
    ${*{"XML::SAX::"}{HASH}{parsers}} = sub {
        return [ {
            'Features' => {
                'http://xml.org/sax/features/namespaces' => '1'
            },
            'Name' => 'XML::SAX::PurePerl'
        }
        ]
    };
}

# END OF THE UGLY FIX!
use Getopt::Long;

use Ocsinventory::Logger;
use Ocsinventory::Agent::XML::Inventory;
use Ocsinventory::Agent::XML::Prolog;

use Ocsinventory::Agent::Network;
use Ocsinventory::Agent::Backend;
use Ocsinventory::Agent::AccountConfig;
use Ocsinventory::Agent::AccountInfo;
#use Ocsinventory::Agent::Pid;
use Ocsinventory::Agent::Config;

use Ocsinventory::Agent::CompatibilityLayer;

our $VERSION = '1.0';

my $basedir = '';
#$basedir = 'c:/cvs_ocs/ocsinventory-agent';
# default settings;
my $params = { 
  'daemon'    =>  0,
  'debug'     =>  0,
  'devlib'    =>  0,
  'force'     =>  0,
  'help'      =>  0,
  'info'      =>  1,
  'lazy'      =>  0,
  'local'     =>  '',
  #'logger'    =>  'Syslog,File,Stderr',
  'logger'    =>  'Stderr',
  'logfile'   =>  '',
  'password'  =>  '',
  'realm'     =>  '',
  'remotedir' =>  '/ocsinventory', # deprecated, give a complet URL to
                                   # --server instead
  'server'    =>  'http://ocsinventory-ng/ocsinventory',
  'stdout'    =>  0,
  'tag'       =>  '',
  'user'      =>  '',
  'version'   =>  0,
  'wait'      =>  '',
#  'xml'       =>  0,
  'nosoft'    =>  0, # DEPRECATED!
  'nosoftware'=>  0,
  'delaytime' =>  '3600', # max delay time (seconds)

  # Other values that can't be changed with the
  # CLI parameters
  'VERSION'   => $VERSION,
  'deviceid'  => '',
  'basevardir'=>  $basedir.'/var/lib/ocsinventory-agent',
  'logdir'    =>  $basedir.'/var/log/ocsinventory-agent',
#  'pidfile'   =>  $basedir.'/var/run/ocsinventory-agent.pid',
};

# Load setting from the config file
my $config = Ocsinventory::Agent::Config::get;
$params->{$_} = $config->{$_} foreach (keys %$config);

$ENV{LC_ALL} = 'C'; # Turn off localised output for commands
$ENV{LANG} = 'C'; # Turn off localised output for commands

my %options = (
  "basevardir=s"      =>   \$params->{basevardir},
  "d|daemon"        =>   \$params->{daemon},
  "debug"           =>   \$params->{debug},
  "devlib"          =>   \$params->{devlib},
  "f|force"         =>   \$params->{force},
  "h|help"          =>   \$params->{help},
  "i|info"          =>   \$params->{info},
  "lazy"            =>   \$params->{lazy},
  "l|local=s"       =>   \$params->{local},
  "logfile=s"       =>   \$params->{logfile},
  "nosoft"          =>   \$params->{nosoft},
  "nosoftware"      =>   \$params->{nosoftware},
  "p|password=s"    =>   \$params->{password},
  "r|realm=s"       =>   \$params->{realm},
  "R|remotedir=s"   =>   \$params->{remotedir},
  "s|server=s"      =>   \$params->{server},
  "stdout"          =>   \$params->{stdout},
  "t|tag=s"         =>   \$params->{tag},
  "u|user=s"        =>   \$params->{user},
  "version"         =>   \$params->{version},
  "w|wait=s"        =>   \$params->{wait},
#  "x|xml"          =>   \$params{xml},
  "delaytime"	    => 	 \$params->{delaytime},
);

##########################################
##########################################
##########################################
##########################################
sub recMkdir {
  my $dir = shift;

  my @t = split /\//, $dir;
  shift @t;
  return unless @t;

  my $t;
  foreach (@t) {
    $t .= '/'.$_;
    if ((!-d $t) && (!mkdir $t)) {
      return;
    }
  }
  1;
}


sub help {
  my $error = shift;
  if ($error) {
    chomp $error;
    print "ERROR: $error\n\n";
  }

  if ($config->{configfile}) {
      print STDERR "Setting initialised with values retrieved from ".
      "the config found at ".$config->{configfile}."\n";
  }

  print STDERR "\n";
  print STDERR "Usage:\n";
  print STDERR "\t--basevardir=/path  indicate the directory where should the agent store its files (".$params->{basevardir}.")\n";
  print STDERR "\t-d  --daemon        detach the agent in background (".$params->{daemon}.")\n";
  print STDERR "\t    --debug         debug mode (".$params->{debug}.")\n";
  print STDERR "\t    --devlib        search for Backend mod in ./lib only (".$params->{devlib}.")\n";
  print STDERR "\t-f --force          always send data to server (Don't ask before) (".$params->{force}.")\n";
  print STDERR "\t-i --info           verbose mode (".$params->{info}.")\n";
  print STDERR "\t--lazy              do not contact the server more than one time during the PROLOG_FREQ (".$params->{lazy}.")\n";
  print STDERR "\t-l --local=DIR      do not contact server but write ".
  "inventory in DIR directory in XML (".$params->{local}.")\n";
  print STDERR "\t   --logfile=FILE   log message in FILE (".$params->{logfile}.")\n";
  print STDERR "\t-p --password=PWD   password for server auth\n";
  print STDERR "\t-r --realm=REALM    realm for server auth. e.g: 'Restricted Area' (".$params->{realm}.")\n";
  print STDERR "\t-s --server=uri     server uri (".$params->{server}.")\n";
  print STDERR "\t   --stdout         do not write or post the inventory but print it on STDOUT\n";
  print STDERR "\t-t --tag=TAG        use TAG as tag (".$params->{tag}."). ".
  "Will be ignored by server if a value already exists.\n";
  print STDERR "\t-u --user=USER      user for server auth (".$params->{user}.")\n";
  print STDERR "\t   --version        print the version\n";
  print STDERR "\t-w --wait=server||nbr_seconds           wait during a random periode before".
  "  contacting server like --daemon do (".$params->{wait}.")\n";
#  print STDERR "\t-x --xml            write output in a xml file ($params->{xml})\n";
  print STDERR "\t--nosoft           DEPRECATED, use --nosoftware instead\n";
  print STDERR "\t--nosoftware       do not return installed software list (".$params->{nosoftware}.")\n";
  print STDERR "\t--delaytime	     set a max delay time if no PROLOG_FREQ is set (".$params->{delaytime}.")\n";

  print STDERR "\n";
  print STDERR "Manpage:\n";
  print STDERR "\tSee man ocsinventory-agent\n";

  print STDERR "\n";
  print STDERR "Ocsinventory-Agent is released under GNU GPL 2 license\n";
  exit 1;
}

sub version {
  print "Ocsinventory unified agent for UNIX and Linux (".$VERSION.")\n";
  exit 0;
}

sub isAgentAlreadyRunning {
  my $params = shift;
  my $logger = $params->{logger};
  # TODO add a workaround if Proc::PID::File is not installed
  eval { require Proc::PID::File; };
  if(!$@) {
    $logger->debug('Proc::PID::File avalaible, checking for pid file');
    if (Proc::PID::File->running()) {
      $logger->debug('parent process already exists');
      return 1;
    }
  }

  return 0;
}


#####################################
################ MAIN ###############
#####################################


############################
#### CLI parameters ########
############################
help() if (!GetOptions(%options) || $params->{help});
version() if $params->{version};

# I close STDERR to avoid error message during the module execution
# at the begining I was doing shell redirection:
#  my @ret = `cmd 2> /dev/null`;
# but this syntax is not supported on (at least) FreeBSD and Solaris
# c.f: http://www.perlmonks.org/?node_id=571072
#my $tmp;
#open ($tmp, ">&STDERR");
#$params->{"savedstderr"} = $tmp;
#if($params->{debug}) {
#  $params->{verbose} = 1;
#} else {
#  close(STDERR);
#}

if ($params->{logfile}) {
    $params->{logger} = 'File';
}

my $logger = new Ocsinventory::Logger ({

    params => $params

  });

if ($params->{nosoft}) {
    $logger->info("the parameter --nosoft is deprecated and may be removed in a futur release, please use --nosoftware instead.");
    $params->{nosoftware} = 1 
}


if (!$params->{'stdout'} && !$params->{'local'} && $params->{server} !~ /^http(|s):\/\//) {
    $logger->debug("the --server passed doesn't have a protocle, assume http as default");
    $params->{server} = "http://".$params->{server}.'/ocsinventory';
}

############################
#### Objects initilisation 
############################

# The agent can contact different servers. Each server accountconfig is
# stored in a specific file:
if (!recMkdir ($params->{basevardir})) {

  if (! -d $ENV{HOME}."/.ocsinventory/var") {
    $logger->info("Failed to create $params->{basevardir} directory: $!. ".
      "I'm going to use the home directory instead (~/.ocsinventory/var).");
  }

  $params->{basevardir} = $ENV{HOME}."/.ocsinventory/var";
  if (!recMkdir ($params->{basevardir})) {
    $logger->error("Failed to create $params->{basedir} directory: $!".
    "The HOSTID will not be written on the harddrive. You may have duplicated ".
    "entry of this computer in your OCS database");
  }
  $logger->debug("var files are stored in ".$params->{basevardir});
}

if (defined($params->{server}) && $params->{server}) {
  my $dir = $params->{server};
  $dir =~ s/\//_/g;
  $params->{vardir} = $params->{basevardir}."/".$dir;
  if (defined ($params->{local}) && $params->{local}) {
      $logger->debug ("--server ignored since you also use --local");
      $params->{server} = undef;
  }
} elsif (defined($params->{local}) && $params->{local}) {
  $params->{vardir} = $params->{basevardir}."/__LOCAL__";
}

if (!recMkdir ($params->{vardir})) {
  $logger->error("Failed to create $params->{vardir} directory: $!");
}

if (-d $params->{vardir}) {
  $params->{accountconfig} = $params->{vardir}."/ocsinv.conf";
  $params->{accountinfofile} = $params->{vardir}."/ocsinv.adm";
  $params->{last_statefile} = $params->{vardir}."/last_state";
  $params->{next_timefile} = $params->{vardir}."/next_timefile";
}
######


# load CFG files
my $accountconfig = new Ocsinventory::Agent::AccountConfig({
    logger => $logger,
    params => $params,
  });

my $srv = $accountconfig->get('OCSFSERVER');
$params->{server} = $srv if $srv;
$params->{deviceid}   = $accountconfig->get('DEVICEID');

# Should I create a new deviceID?
chomp(my $hostname = `uname -n| cut -d . -f 1`);
if ((!$params->{deviceid}) || $params->{deviceid} !~ /\Q$hostname\E-(?:\d{4})(?:-\d{2}){5}/) {
  my ($YEAR, $MONTH , $DAY, $HOUR, $MIN, $SEC) = (localtime
    (time))[5,4,3,2,1,0];
  $params->{old_deviceid} = $params->{deviceid};
  $params->{deviceid} =sprintf "%s-%02d-%02d-%02d-%02d-%02d-%02d",
  $hostname, ($YEAR+1900), ($MONTH+1), $DAY, $HOUR, $MIN, $SEC;
  $accountconfig->set('DEVICEID',$params->{deviceid});
  $accountconfig->write();
}

my $accountinfo = new Ocsinventory::Agent::AccountInfo({

    logger => $logger,
    params => $params,

  });

# --lazy
if ($params->{lazy}) {
  my $nexttime = (stat($params->{next_timefile}))[9];

  if ($nexttime && $nexttime > time) {
    $logger->info("Must wait until ".localtime($nexttime)." exiting...");
    exit 0;
  }
}


if ($params->{tag}) {
  if ($accountinfo->get("TAG")) {
    $logger->debug("A TAG seems to already exist in the server for this".
      "machine. If so, the -t paramter is useless. Please change the TAG".
      "directly on the server.");
  } else {
    $accountinfo->set("TAG",$params->{tag});
  }
}

# Create compatibility layer. It's used to keep compatibility with the
# linux_agent 1.x and below
my $compatibilityLayer = new Ocsinventory::Agent::CompatibilityLayer({

    accountinfo => $accountinfo,
    accountconfig => $accountconfig,
    logger => $logger,
    params => $params,

  });

if ($params->{daemon}) {
  
  $logger->debug("Time to call Proc::Daemon");
  eval { require Proc::Daemon; };
  if ($@) {
      print "Can't load Proc::Daemon. Is the module installed?";
      exit 1;
  }
  Proc::Daemon::Init();
  $logger->debug("Daemon started");
  if (isAgentAlreadyRunning({
      logger => $logger,
  })) {
    exit 1;
  }


}

$logger->debug("OCS Agent initialised");
#######################################################
#######################################################
while (1) {

  my $exitcode = 0;
  my $wait;
  if ($params->{daemon} || $params->{wait}) {
    my $serverdelay;
    if(($params->{wait} eq 'server') || ($params->{wait}!~/^\d+$/)){
      $serverdelay = $accountconfig->get('PROLOG_FREQ')*3600;
    }
    else{
      $serverdelay = $params->{wait};
    }
    $wait = int rand($serverdelay?$serverdelay:$params->{delaytime});
    $logger->info("Going to sleep for $wait second(s)");
    sleep ($wait);

  }

  $compatibilityLayer->hook({name => 'start_handler'});

  my $inventory = new Ocsinventory::Agent::XML::Inventory ({

	  accountinfo => $accountinfo,
	  accountconfig => $accountinfo,
	  logger => $logger,
	  params => $params,

      });


  if ($params->{stdout} || $params->{local}) { # Local mode

      my $inventory = new Ocsinventory::Agent::XML::Inventory ({

	      # TODO, check if the accoun{info,config} are needed in localmode
	      accountinfo => $accountinfo,
	      accountconfig => $accountinfo,
	      logger => $logger,
	      params => $params,

	  });

      if ($params->{stdout}) {
	  $inventory->printXML();
      } elsif ($params->{local}) {
	  $inventory->writeXML();
      }

  } else { # I've to contact the server

    my $net = new Ocsinventory::Agent::Network ({
	
	accountconfig => $accountconfig,
	accountinfo => $accountinfo,
	compatibilityLayer => $compatibilityLayer,
	logger => $logger,
	params => $params,

      });

    my $sendInventory = 1;
    my $prologresp;
    if (!$params->{force}) {
      my $prolog = new Ocsinventory::Agent::XML::Prolog({

	  accountinfo => $accountinfo,
	  logger => $logger,
	  params => $params,

	});

      $prologresp = $net->send({message => $prolog});
      
      if (!$prologresp) { # Failed to reach the server
        if ($params->{lazy}) {
          # To avoid flooding a heavy loaded server
	  my $previousPrologFreq;
	  if( ! ($previousPrologFreq = $accountconfig->get('PROLOG_FREQ') ) ){
	    $previousPrologFreq = $params->{delaytime};
	    $logger->info("No previous PROLOG_FREQ found - using fallback delay($params->{delaytime} seconds)");
	  }
	  else{
	    $logger->info("Previous PROLOG_FREQ found ($previousPrologFreq)");
	    $previousPrologFreq = $previousPrologFreq*3600;
	  }
          my $time = time + $previousPrologFreq;
          utime $time,$time,$params->{next_timefile};
        }
        exit 1 unless $params->{daemon};
        $sendInventory = 0;
      } elsif (!$prologresp->isInventoryAsked()) {
	$sendInventory = 0;
      }
    }

    if (!$sendInventory) {

      $logger->info("Don't send the inventory");

    } else { # Send the inventory!

	my $inventory = new Ocsinventory::Agent::XML::Inventory ({

		# TODO, check if the accoun{info,config} are needed in localmode
		accountinfo => $accountinfo,
		accountconfig => $accountinfo,
		logger => $logger,
		params => $params,
		prologresp => $prologresp,

	    });

      if (my $response = $net->send({message => $inventory})) {
	#if ($response->isAccountUpdated()) {
	  $inventory->saveLastState();
	#}
      } else {
          exit (1) unless $params->{daemon};
      }
  }

  }

  $compatibilityLayer->hook({name => 'end_handler'});
  exit (0) unless $params->{daemon};

}

__END__

=head1 NAME

ocsinventory-agent - Unified client for OCS-Inventory

=head1 SYNOPSIS

B<ocsinventory-agent> S<[ B<-fhilpruw> ]> S<[ I<--server server> | I<--local /tmp> ]>...

=head1 EXAMPLES

    % ocsinventory-agent --server localhost
    # sent an inventory to the OCS server
    
    % ocsinventory-agent --server http://localhost/ocsinventory2
    # sent an inventory over http to a server with a non standard
    # virtual directory

    % ocsinventory-agent --server https://localhost/ocsinventory
    # sent an inventory over https to the OCS server

    % ocsinventory-agent --local /tmp
    # write an inventory in the /tmp directory
    
    % ocsinventory-agent --server localhost --user=toto --password=pw --realm="Restricted Area"
    # send a report to a server protected by a basic authentification 
    % ocsinventory-agent --lazy
    # send an inventory only if the a random delay between 0 and PROLOG_FREQ had been run over. Usefull for package maintainer.

    % ocsinventory-agent --delaytime 60 -d
    # If NO PROLOG_FREQ has been preset, pick a time between execution and --delaytime for the agent to contact the server [default is 3600 seconds]

=head1 DESCRIPTION

F<ocsinventory-agent> creates inventory and sent or write them. This agent is the
successor of the former linux_agent which was release with OCS 1.01 and prior. It also
replaces the Solaris/AIX/BSD unofficial agents. It supports the following operating
systems:

=over 4

=item F<Linux 2.4 and 2.6>

any distribution on x86, x86_64/AMD64, Sparc64, ARM and PowerPC. Other architectures may work.

=item F<Solaris>

tested on Solaris 8, 9 and 10 (x86 and Sparc)

=item F<FreeBSD>

x86 and Sparc

=item F<NetBSD>

x86 and Sparc

=item F<OpenBSD>

x86 and Sparc

=item F<AIX>

tested on AIX 5.1, 5.2 and 5.3 (PPC)

=item F<MacOSX>

tested with system from 10.3.9 to 10.5.2 

=back

=head1 OPTIONS

Most of the options are available in a I<short> form and a I<long> form.  For
example, the two lines below are all equivalent:

    % ocsinventory-agent -s localhost 
    % ocsinventory-agent --server localhost 

=over 4

=item B<--basevardir>=I<DIR>

Indicate the place where the agent should store its files.

=item B<-d>, B<--daemon>

Launch ocsinventory-agent in background. Proc::Daemon is needed.

=item B<--debug>

Turn the debug mode on.

=item B<--devlib>

This option is designed for backend module developer. With it enabled, ocsinventry-agent won't try to load the Backend module installed on the system. Instead it will scan the ./lib directory.

=item B<--delaytime=SECONDS_TO_WAIT>

This option defaults to waiting a random() time between 0 and 3600 before initially contacting the server assuming NO PROLOG_FREQ has been set. Once PROLOG_FREQ has been set, it uses that number at the top end of it's random setting. Useful for pre-setting a deployed agent's initial start time (so you don't jam the server all at once, but don't have to wait an hour to make sure the deployment worked).

=item B<-f>, B<--force>

The agent will first contact the server during the PROLOG period. If the server doesn't know the machin or have outdated information, it will ask for an inventory.
With this option, the agent doesn't run the PROLOG with the server first but directly send an inventory.

=item B<-i>, B<--info>

Turn the verbose mode on. The flag is ignored if B<--debug> is enable.

=item B<--lazy>

Do not contact the server more than one time during the PROLOG_FREQ. This option is useful for package. Thanks to it they can start the script regulary from the crontab.

=item B<-l>, B<--local>=I<DIR>

Write an inventory in the I<DIR> directory. A new file will be created if needed.

=item B<--logfile>=I<FILE>

Log message in I<FILE> and turn off STDERR

=item B<-p>, B<--password>=I<PASSWORD>

Use I<PASSWORD> for an HTTP identification with the server.

=item B<-r>, B<--realm>=I<REALM>

Use I<REALM> for an HTTP identification with the server. For example, the value can be 'Restricted Area'. You can find it in the login popup of your Internet browser.

=item B<-s>, B<--server>=I<URI>

The uri of the server. If I<URI> doesn't start with http:// or https://, the assume the parameter is a hostname and rewrite it like that:

    % http://servername/ocsinventory

If you want to use https or another virtual directory you need to enter the full path.

B<--server> is ignored if B<--local> is in use.

=item B<--stdout>

Print the inventory on stdout.

    % ocsinventory-agent --stdout > /tmp/report.xml
    # prepare an inventory and write it in the /tmp/report.xml file.
    # A file will be created.

=item B<--tag>=I<TAG>

Mark the machin with the I<TAG> tag. Once the initial inventory is accepted by the server this value is ignored and you've to change the information directly on the server. The server do so in order to centralize the administration of the machine.

=item B<-u> I<USER>, B<--user>=I<USER>

Use I<USER> for the server authentification.

=item B<--version>=I<USER>

Print the version and exit.

=item B<-w> I<DURATION>, B<--wait>=I<DURATION>

Wait before initializing the connexion with the server. If I<DURATION> equal I<server> the agent will use the PROLOG_FREQ of the server to determine the duration of this periode. Exactly like it would had done if it was in --daemon mode.
Else if duration is a numerical value, it will be used directly.

    % ocsinventory-agent --wait 5 --server localhost

=item B<--nosoftware>, B<--nosoft>

Do not inventory the software installed on the machin. B<--nosoft> is deperecated, use B<--nosoftware>

=back

=head1 AUTHORS

Goneri LE BOUDER (maintainer), Pascal DANEK, Olivier ANDREOTTI, Thierry LACOSTE, Didier LIROULET

=head1 SEE ALSO 

=over 4

=item OCS-Inventory website,

 http://www.ocsinventory-ng.org/ 

=item forum,

 http://forums.ocsinventory-ng.org/

=item and wiki 

 http://wiki.ocsinventory-ng.org/

=back

=head1 BUGS

Please, use the forum as much as possible. You can send your patches to <goneri@rulezlan.org>.

=head1 COPYRIGHT

Copyright (C) 2006 2008 Gonéri LE BOUDER <goneri@rulezlan.org> 

Copyright (C) 2005 2008 Pascal DANEK <pascal@ocsinventory-ng.org>

Copyright (C) 2007 Olivier ANDREOTTI <olivier.andreotti@gmail.com>

Copyright (C) 2007 Thierry LACOSTE <lacoste@univ-paris12.fr>

Copyright (C) 2007 Didier LIROULET <dliroulet@users.sourceforge.net>

Copyright (C) 2008 Nicolas DORFSMAN

Copyright (C) 2008 Wes YOUNG <http://claimid.com/saxjazman9>

Copyright (C) 2008 Edouard GAMIN

Copyright (C) 2008 Christoph HALMES

 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

=cut
