#!/usr/pkg/bin/perl

use strict;
use warnings;

use lib "/usr/pkg/share/fusioninventory/lib";
use setup;

use English qw(-no_match_vars);
use Getopt::Long;
use Pod::Usage;
use XML::TreePP;

use FusionInventory::Agent::Task::NetInventory;
use FusionInventory::Agent::Task::NetInventory::Job;
use FusionInventory::Agent::Tools;
use FusionInventory::Agent::Logger;
use FusionInventory::Agent::Version;

my %types = (
    1 => 'COMPUTER',
    2 => 'NETWORKING',
    3 => 'PRINTER',
    4 => 'STORAGE',
    5 => 'POWER',
    6 => 'PHONE',
    7 => 'VIDEO',
);

my $options = {
    debug => 0,
    threads => 1
};

GetOptions(
    $options,
    'model=s',
    'type=s',
    'host=s@',
    'file=s@',
    'community=s',
    'credentials=s',
    'port=s@',
    'protocol=s@',
    'threads=i',
    'timeout=i',
    'control',
    'debug+',
    'help',
    'version',
) or pod2usage(-verbose => 0);

if ($options->{version}) {
    my $PROVIDER = $FusionInventory::Agent::Version::PROVIDER;
    map { print $_."\n" }
        "NetInventory task $FusionInventory::Agent::Task::NetInventory::VERSION",
        "based on $PROVIDER Agent v$FusionInventory::Agent::Version::VERSION",
        @{$FusionInventory::Agent::Version::COMMENTS}
        ;
    exit 0;
}
pod2usage(-verbose => 0, -exitval => 0) if $options->{help};

pod2usage(
    -message => "no host nor file given, aborting\n", -verbose => 0
) unless $options->{host} or $options->{file};

warn "deprecated option model, ignoring\n" if $options->{model};

# Split host, port and protocol options on comma
foreach my $opt (qw(host port protocol)) {
    $options->{$opt} = [ map { split(/\s*,\s*/, $_) } @{$options->{$opt} || []} ];
}

# Add hosts if --host option is not listing as much host as --file option is providing snmpwalk files
while ($options->{file} && @{$options->{file}} > @{$options->{host}}) {
    push @{$options->{host}}, $options->{host}[0] || '127.0.0.1';
}

# Add as ports as set hosts, setting 0 will force to select the default SNMP port
while ($options->{host} && @{$options->{host}} > @{$options->{port}}) {
    push @{$options->{port}}, $options->{port}[0] || 0;
}

# Add as protocols as set hosts, setting '' will force to select the default SNMP protocol
while ($options->{host} && @{$options->{host}} > @{$options->{protocol}}) {
    push @{$options->{protocol}}, $options->{protocol}[0] || '';
}

my $id = 0;
my @devices;

if ($options->{file}) {
    push @devices, {
        ID           => $id++,
        FILE         => $_,
        IP           => shift @{$options->{host}},
        AUTHSNMP_ID  => 1,
        MODELSNMP_ID => 1
    } foreach @{$options->{file}};
} else {
    push @devices, {
        ID           => $id++,
        IP           => $_,
        PORT         => shift @{$options->{port}},
        PROTOCOL     => shift @{$options->{protocol}},
        AUTHSNMP_ID  => 1,
        MODELSNMP_ID => 1
    } foreach @{$options->{host}};
}

my $credentials = { ID => 1 };

if ($options->{type}) {
    pod2usage(
        -message => "invalid type '$options->{type}', aborting\n",
        -verbose => 0
    ) unless any { $options->{type} eq $_ } values %types;
    map { $_->{TYPE} = $options->{type} } @devices;
}

if ($options->{community}) {
    $credentials->{COMMUNITY} = $options->{community};
} elsif (defined $options->{credentials}) {
    foreach my $parameter (split(',', $options->{credentials})) {
        my ($key, $value) = split(':', $parameter);
        my $newkey =
            $key eq 'authpassword' ? 'AUTHPASSPHRASE' :
            $key eq 'privpassword' ? 'PRIVPASSPHRASE' :
                                     uc($key);
        $credentials->{$newkey} = $value;
    }
} else {
    $credentials->{COMMUNITY} = 'public';
}

my $inventory = FusionInventory::Agent::Task::NetInventory->new(
    %setup,
    target => FusionInventory::Agent::Task::NetInventory::Target->new(),
    logger => FusionInventory::Agent::Logger->new(config => $options)
);

$inventory->{jobs} = [
    FusionInventory::Agent::Task::NetInventory::Job->new(
        params => {
            PID           => 1,
            THREADS_QUERY => $options->{threads},
            TIMEOUT       => $options->{timeout},
        },
        devices     => \@devices,
        credentials => [ $credentials ]
    )
];

$inventory->{client} =
    FusionInventory::Agent::Task::NetInventory::Client->new(
        control => $options->{control}
    );

$inventory->run();

package FusionInventory::Agent::Task::NetInventory::Client;

sub new {
    my ($class, %params) = @_;

    return bless {
        control => $params{control}
    }, $class;
}

sub send {
    my ($self, %params) = @_;

    # don't display control message by default
    return unless $self->{control}
        or $params{message}->{h}->{CONTENT}->{DEVICE};

    print $params{message}->getContent();
}

## no critic (ProhibitMultiplePackages)
package FusionInventory::Agent::Task::NetInventory::Target;

sub new {
    my ($class) = @_;

    return bless {}, $class;
}

sub getUrl {
    my ($self, %params) = @_;

    ## no critic (ExplicitReturnUndef)
    return undef;
}

__END__

=head1 NAME

fusioninventory-netinventory - Standalone network inventory

=head1 SYNOPSIS

fusioninventory-netinventory [options] [--host <host>|--file <file>]

  Options:
    --host <HOST>          target host
    --port <PORT[,PORT2]>  SNMP port (161)
    --protocol <PROT[,P2]> SNMP protocol/domain (udp/ipv4)
    --file <FILE>          snmpwalk output file
    --community <STRING>   community string (public)
    --credentials <STRING> SNMP credentials (version:1,community:public)
    --timeout <TIME>       SNMP timeout, in seconds (15)
    --type <TYPE>          force device type
    --threads <COUNT>      number of inventory threads (1)
    --control              output control messages
    --debug                debug output
    -h --help              print this message and exit
    --version              print the task version and exit

=head1 DESCRIPTION

F<fusioninventory-netinventory> can be used to run a network inventory task without
a GLPI server.

=head1 OPTIONS

=over

=item B<--host> I<HOST>

Run an online inventory against given host. Multiple usage allowed, for
multiple hosts.

=item B<--port> I<PORT[,PORT2]>

List of ports to try, defaults to: 161

Set it to 161,16100 to first try on default port and then on 16100.

=item B<--protocol> I<PROTOCOL[,PROTOCOL2]>

List of protocols to try, defaults to: udp/ipv4

Possible values are: udp/ipv4,udp/ipv6,tcp/ipv4,tcp/ipv6

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

Run an offline inventory against snmpwalk output, stored in given file.
Multiple usage allowed, for multiple files.

=item B<--communty> I<STRING>

Use given string as SNMP community (assume SNMPv1)

=item B<--credentials> I<STRING>

Use given string as SNMP credentials specification. This specification is a
comma-separated list of key:value authentication parameters, such as:

=over

=item * version:2c,community:public

=item * version:3,username:admin,authprotocol:sha,authpassword:s3cr3t

=item * etc.

=back

=item B<--timeout> I<TIME>

Set SNMP timeout, in seconds.

=item B<--type> I<TYPE>

Force device type, instead of relying on automatic identification. Currently
allowed types:

=over

=item * COMPUTER

=item * NETWORKING

=item * PRINTER

=item * STORAGE

=item * POWER

=item * PHONE

=back

=item B<--threads> I<count>

Use given number of inventory threads.

=item B<--control>

Output server-agent control messages, in addition to inventory result itself.

=item B<--debug>

Turn the debug mode on. Multiple usage allowed, for additional verbosity.

=back

=head1 EXAMPLES

Run an inventory against a network device, using SNMP version 2c authentication:

    $> fusioninventory-netinventory --host 192.168.0.1 \
    --credentials version:2c,community:public

Run an inventory against a network device, using SNMP version 3 authentication
and forcing its type:

    $> fusioninventory-netinventory --host my.device --type NETWORKING \
    --credentials version:3,username:admin,authprotocol:sha,authpassword:s3cr3t
