#!/usr/bin/perl
###############################################################################
#
# Copyright (c) 2009-2023 Pandora FMS.
#
# inventory	Generate a hardware/software inventory.
#
# Sample usage:	./inventory <interval in days> [cpu] [ram] [video] [nic] [hd] [cdrom] [software] [init_services] [filesystem] [process] [users]
#
# 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; version 2 of the License.
#
# 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.
#
###############################################################################

use strict;
use constant TSTAMP_FILE => '/tmp/pandora_inventory.tstamp';
use Scalar::Util qw(looks_like_number);

# Set environment language to English
$ENV{"LANG"} = "en_US";

# Check AIX system
my $AIX=0;
my $system = `uname -a | awk '{print $1}'`;
if ($system =~ 'AIX') {
    $AIX=1;
}

sub is_enabled {
    my $value = shift;
    if ((defined ($value)) && looks_like_number($value) && ($value > 0)){
        # return true
        return 1;
    }
    #return false
    return 0;
}
# Operation mode (LSHW or HWINFO)
my $Mode;

# Item separator
my $Separator;

# Parse module information
sub get_module_data ($$$$) {
	my ($name, $hwinfo, $keys, $modules) = @_;
	my %module;
	$Separator='\s+\*\-';
	# Store keys
	foreach my $key (@{$keys}) {
		push (@{$module{'_keys'}}, $key);
	}

	# Parse module data
	while (my $line = shift (@{$hwinfo})) {
		if ($line =~ /$Separator/) {
			unshift (@{$hwinfo}, $line);
			last;
		}
		foreach my $key (@{$keys}) {
			if ($line =~ /$key:\s+(.+)/) {
				$module{$key} = $1;
				# Replace semicolon by comma to avoid parse errors
				$module{$key} =~ s/;/,/g;
			}
		}
	}

	# No data found
	my @data = keys (%module);
	return unless ($#data >= 0);

	push (@{$modules->{$name}}, \%module);
}
sub test_contain ($$) {
	my ($value, $array)=@_;
	if ( grep( /$value/, @{$array} ) ) {
  		return 1;
	}
}
sub get_module_data_aix_ram_cpu ($$$$) {
	my ($name, $hwinfo, $keys, $modules) = @_;
	my %module;
	# Store keys
	foreach my $key (@{$keys}) {
		push (@{$module{'_keys'}}, $key);
	}
	# Parse module data
	foreach my $line (@{$hwinfo}) {
		foreach my $key (@{$keys}) {
			if ($line =~ /$key:\s+(.+)/) {
				$module{$key} = $1;
				$module{$key} =~ s/,/ /g;
			}
		}
	}

	# No data found
	#my @data = keys (%module);
	#return unless ($#data >= 0);

	push (@{$modules->{$name}}, \%module);
}
sub get_module_data_aix ($$$$) {
        my ($name,$hwinfo,$regex,$modules) = @_;
        my %module;
        foreach my $line (@{$hwinfo}) {
                $line =~ s/\s{2,}/;/g;
		$line =~ s/\+ //g;
		$line =~ s/\* //g;
        }
        foreach my $line (@{$hwinfo}) {
            if ($line =~ /$regex/){
                my ($var1, $var2, $var3) = split /;/, $line;
        		my %module;
        		$module{'device'} = $var1;
                $module{'serial'} = $var2;
        		$module{'description'}=$var3;
                $module{'_keys'} = ['device','serial','description'];
        		push (@{$modules->{$name}}, \%module);
            }
        }
}

# Get a list of information file system in machine
sub get_file_system($$) {
	my ($name, $modules) = @_;
    my @fileSystems;
    if (is_enabled $AIX){
        @fileSystems = `df -gP | tail -n +2`;
    } else {
        @fileSystems = `df -hP | tail -n +2`; #remove the titles of columns
    }

	foreach my $row (@fileSystems) {
		next unless ($row =~ /^(\S+)\s+\S+\s+(\S+)\s+(\S+)\s+\S+\s+(\S+)/);

		my %module;
		$module{'filesystem'} = $1;
		$module{'used'} = $2;
		$module{'avail'} = $3;
		$module{'mount'} = $4;
        $module{'_keys'} = ['filesystem', 'used','avail', 'mount'];
        push (@{$modules->{$name}}, \%module);
    }
}

# Get a list of services init in machine
sub get_servicies_init_machine($$) {
	my ($name, $modules) = @_;
    my $runlevel;
    if (is_enabled $AIX) {
        $runlevel = `who -r | awk '{print \$3}'`;
    } else {
        $runlevel = `who -r | awk '{print \$2}'`;
    }

	#ini trim($runlevel)
		$runlevel =~ s/^\s*//; #ltrim
		$runlevel =~ s/\s*$//; #rtrim
	#end trim($runlevel)

	my $script;

	if (-e "/etc/rc" . $runlevel .".d/") {
		$script = "ls -l  /etc/rc" . $runlevel .".d/ | grep \"^l.*\" | awk \"{print \\\$NF}\" | sed -e \"s/\\.\\.\\///g\" | sed -e \"s/.*init\\.d\\///g\"";
	}
	else {
		$script = "ls -l /etc/rc.d/rc" . $runlevel .".d/ | grep \"^l.*\" | grep \" S.* \" | awk \"{print \\\$NF}\" | sed -e \"s/\\.\\.\\///g\" | sed -e \"s/.*init\\.d\\///g\"";
	}

	my @services = `$script`;
	foreach my $row (@services) {
		my %module;
		$row =~ s/\n//;
        $module{'service'} = $row;
        $module{'_keys'} = ['service'];
        push (@{$modules->{$name}}, \%module);
	}
}

# Get a list of running processes
sub get_processes ($$) {
	my ($name, $modules) = @_;
	my $script;
    if (is_enabled $AIX) {
        $script = "ps -eo args | tail -n +2";
    } else {
        $script = "ps -eo command | tail -n +2";
    }

    my @services = `$script`;
    foreach my $row (@services) {
        my %module;
		# Remove carriage returns
		$row =~ s/[\n\l\f]//g;
		# Replace semicolon by comma to avoid parse errors
		$row =~ s/;/,/g;
        $module{'service'} = $row;
        $module{'_keys'} = ['service'];
        push (@{$modules->{$name}}, \%module);
    }
}

# Get a list of valid users in the system
sub get_users ($$) {
    my ($name, $modules) = @_;
    my $script = "cat /etc/passwd";
	my $user = "";
	my $estado = "";

    my @services = `$script`;
    foreach my $row (@services) {
            my %module;

	next unless ($row =~ /^([A-Za-z0-9\-\_]*)/);
        if (is_enabled $AIX) {
            $user = $1;
            $script = `lsuser $user`;
            if ( $script =~ /^(\S+)\sid./){
                $module{'user'} = $user;
                $module{'_keys'} = ['user'];
                push (@{$modules->{$name}}, \%module);
            }
        } else {
            $user = $1;
            $script = `passwd -S $user`;
            if ( $script =~ /^(\S+)\sP./){
                $module{'user'} = $user;
                $module{'_keys'} = ['user'];
                push (@{$modules->{$name}}, \%module);
		}
        }

    }
}

# Show Kernel Information
sub get_kernel_info ($$) {
    my ($name, $modules) = @_;
    my $script = `uname -a | tr -d \";\"`;
    my %module;

    $module{'Kernel'} = $script;
    $module{'_keys'} = ['Kernel'];
    push (@{$modules->{$name}}, \%module);
}


# Get a list of installed programs
sub get_software_module_data ($$) {
	my ($name, $modules) = @_;

	# Guess the current distribution
	my $distrib_id = "";

    if (is_enabled $AIX) {
       $distrib_id = "AIX";
    }elsif ( -e "/etc/SuSE-release"){
		$distrib_id = "SUSE";
	}elsif ( -e "/etc/redhat-release"){
		$distrib_id = "REDHAT";
	}else {
		$distrib_id = "DEBIAN";
	}

	# List installed programs
	my @soft;
	if ($distrib_id eq 'DEBIAN') {
		@soft = `dpkg -l | grep ii`;
	}elsif ($distrib_id eq 'AIX') {
        @soft = `lslpp -Lcq | awk -F: '{print "ii "\$1" "\$3" "\$8}'`;
    }else {
		# Sometimes rpm return data splitted in two lines, and with dupes. Thats bad for our inventory system
		@soft = `rpm -q -a --qf "ii %{NAME} %{VERSION} %{SUMMARY}\n" | grep "^ii" | sort -u`;
	}


	# Parse data
	foreach my $row (@soft) {
		next unless ($row =~ /^ii\s+(\S+)\s+(\S+)\s+([^\n]+)/);

		my %module;
		$module{'program'} = $1;
		$module{'version'} = $2;
		$module{'description'} = $3;
		# Replace semicolon by comma to avoid parse errors
		$module{'program'} =~ s/;/,/g;
		$module{'version'} =~ s/;/,/g;
		$module{'description'} =~ s/;/,/g;
		# Replace ellipsis character to avoid encoding errors
		$module{'description'} =~ s/…/.../g;
		$module{'_keys'} = ['program', 'version','description'];
		push (@{$modules->{$name}}, \%module);
	}
}

#Get the list of interfaces with the ip assigned
sub get_ips ($$) {
	my ($name, $modules) = @_;
    my @interfaces;
    my $ifconfig;
    if (is_enabled $AIX) {
        $ifconfig = `ifconfig -a`;
    } else {
        $ifconfig = `ifconfig`;
    }


    my @ifconfig_array = split("\n", $ifconfig);

    foreach (@ifconfig_array){
        if ($_=~/(.*)flags/){
            my $match;
            ($match)=$_=~/^(.*?)\: flags/;
            $match=~s/://;
            push @interfaces,$match;
        }
    }
    foreach (@interfaces) {
            my $ifconfig_item=`ifconfig $_`;
            my $interface=$_;
            my @ip_array = split("\n", $ifconfig_item);
            foreach (@ip_array) {
                if ($_=~/(?<=inet )(.*)(?= netmask)/){
                        my $ip;
                        ($ip)=$_=~/inet (.*) netmask/;
                        my %info;
                        $info{'interface'} = $interface;
                        $info{'ip'} = $ip;
                        $info{'_keys'} = ['interface','ip'];
                        push (@{$modules->{$name}}, \%info);
                }
		    }
	}
}

#Get route table
sub get_route_table ($$) {
    	my ($name, $modules) = @_;
        my $route_table;
        my @table_split;
    if (is_enabled $AIX) {
        $route_table = `netstat -rn`;
        @table_split = split("\n", $route_table);
        my $length=scalar @table_split;
        for (my $i=4; $i<=$length-4; $i++) {

            my @split = split(" ", $table_split[$i]);

            my %info;

            $info{'destination'} = $split[0];
            $info{'gateway'} = $split[1];
            $info{'mask'} = $split[2];
            $info{'flags'} = $split[3];
            $info{'metric'} = $split[4];
            $info{'ref'} = $split[5];
            $info{'use'} = $split[6];
            $info{'interface'} = $split[7];

            $info{'_keys'} = ['destination', 'gateway', 'mask', 'flags', 'metric', 'use', 'interface'];

            push (@{$modules->{$name}}, \%info);
        }
    } else {
        $route_table = `route`;
        my @table_split = split("\n", $route_table);

        for (my $i=2; $i<=$#table_split; $i++) {

            my @split = split(" ", $table_split[$i]);

            my %info;

            $info{'destination'} = $split[0];
            $info{'gateway'} = $split[1];
            $info{'mask'} = $split[2];
            $info{'flags'} = $split[3];
            $info{'metric'} = $split[4];
            $info{'ref'} = $split[5];
            $info{'use'} = $split[6];
            $info{'interface'} = $split[7];

            $info{'_keys'} = ['destination', 'gateway', 'mask', 'flags', 'metric', 'use', 'interface'];

            push (@{$modules->{$name}}, \%info);
        }
    }
}
# Print module data
sub print_module ($$) {
	my ($name, $module) = @_;

	print "  <inventory_module>\n";
	print "    <name><![CDATA[" . $name . "]]></name>\n";
	print "    <datalist>\n";
	foreach my $item (@{$module}) {
		# Compose module data
		my $data = undef;
		foreach my $key (@{$item->{'_keys'}}) {
			$data = (!defined($data) ? '' : "$data;");
			if (defined($item->{$key})) {
				$data .= $item->{$key};
			}
		}

		print "      <data><![CDATA[$data]]></data>\n";
	}
	print "    </datalist>\n";
	print "  </inventory_module>\n";
}

# Check command line parameters
if ($#ARGV < 0) {
	print "Usage: $0 <interval> [cpu] [ram] [video] [nic] [hd] [cdrom] [software] [init_services] [filesystem] [users] [process] [ip] [route] [kernel] \n\n";
	exit 1;
}

# Parse command line parameters
my %enabled;
my $interval = 1;
my $enable_all = 0;

$interval = $ARGV[0];
if ($#ARGV == 0){
    $enable_all = 1;
}
if ($interval!=/[:alpha:]/){
    splice @ARGV,0,1;
}

foreach my $module (@ARGV) {
	if ($module eq "all"){
	    $enable_all = 1;
	}else {
	    $enabled{$module} = 1;
    }
}

# Check execution interval
if (-f TSTAMP_FILE) {
	open (FILE, '<' . TSTAMP_FILE) || exit 1;
	my $last_execution = <FILE>;
	close (FILE);
	exit 0 if ($last_execution + 86400 * $interval > time ());
}
open (FILE, '>' . TSTAMP_FILE) || exit 1;
print FILE time ();
close (FILE);

# Retrieve hardware information
$Mode = 'LSHW';
$Separator = '\s+\*\-';
my @hwinfo;
my $lshwpath = $0 =~ s/inventory/lshw/r ;
if (is_enabled $AIX) {
    $Separator = '^\s*$';
    @hwinfo=`prtconf 2>/dev/null`;
} else {
    @hwinfo = `lshw 2>/dev/null`;
    if ($? != 0) {
        @hwinfo = `$lshwpath 2>/dev/null`;
        if ($? != 0) {
                $Mode = 'HWINFO';
                $Separator = 'Hardware Class:';
                @hwinfo = `hwinfo --cpu --memory --gfxcard --netcard --cdrom --disk 2>/dev/null`;
        }
    }
}

my %modules;

    if (is_enabled $AIX) {
        #CPU
        # VIDEO
        ### Not avilable in AIX ###
        # NIC
        ### Not relevant in AIX ###
        if ((test_contain('ent',\@hwinfo)) && ($enable_all == 1 || $enabled{'nic'} == 1)) {
        	get_module_data_aix ('NIC',\@hwinfo,'^ent',\%modules);
        }
        if ((test_contain('hdisk',\@hwinfo)) && ($enable_all == 1 || $enabled{'hd'} == 1)) {
        	get_module_data_aix ('HD',\@hwinfo,'^hdisk',\%modules);
        }

    	foreach my $line (@hwinfo) {
    		chomp ($line);
    	        #CPU
    	    if (($line =~ /^Memory Size:/) && ($enable_all == 1 || $enabled{'ram'} == 1)) {
    			get_module_data_aix_ram_cpu ('RAM', \@hwinfo, ['Memory Size','Good Memory Size'], \%modules);
    		}

    	    if (($line =~ /^System Model/) && ($enable_all == 1 || $enabled{'cpu'} == 1)) {
    	        get_module_data_aix_ram_cpu ('CPU', \@hwinfo, ['System Model', 'Processor Implementation Mode', 'Number Of Processors'], \%modules);
    		}
        }
} else {
	# Parse hardware information
	while (my $line= shift  (@hwinfo)) {
	chomp ($line);
    	# CPU
    	if (($line =~ /\*\-cpu/ || $line =~ /Hardware Class: cpu/) && ($enable_all == 1 || $enabled{'cpu'} == 1)) {
    		if ($Mode eq 'LSHW') {
        			get_module_data ('CPU', \@hwinfo, ['product', 'vendor', 'capacity'], \%modules);
            } else {
        			get_module_data ('CPU', \@hwinfo, ['Model', 'Vendor', 'Clock'], \%modules);
        	}
        	}

    	# RAM
    	if (($line =~ /\*\-bank/ || $line =~ /\*\-memory/ || $line =~ /Hardware Class: memory/) && ($enable_all == 1 || $enabled{'ram'} == 1)) {
    		if ($Mode eq 'LSHW') {
    			get_module_data ('RAM', \@hwinfo, ['description', 'size'], \%modules);
    		} else {
    			get_module_data ('RAM', \@hwinfo, ['Model', 'Memory Size'], \%modules);
    		}
    	}

    	# VIDEO
    	if (($line =~ /\*\-display/ || $line =~ /Hardware Class: graphics card/) && ($enable_all == 1 || $enabled{'video'} == 1)) {
    		if ($Mode eq 'LSHW') {
    			get_module_data ('VIDEO', \@hwinfo, ['product', 'description', 'vendor'], \%modules);
    		} else {
    			# Spaces before Device and Vendor are intentional to avoid matching SubDevice and SubVendor
    			get_module_data ('VIDEO', \@hwinfo, ['Model', ' Device', ' Vendor'], \%modules);
    		}
    	}

    	# NIC
    	if (($line =~ /\*\-network/ || $line =~ /Hardware Class: network/) && ($enable_all == 1 || $enabled{'nic'} == 1)) {
    		if ($Mode eq 'LSHW') {
    			get_module_data ('NIC', \@hwinfo, ['product', 'description', 'vendor', 'serial'], \%modules);
    		} else {
    			# Spaces before Device and Vendor are intentional to avoid matching SubDevice and SubVendor
    			get_module_data ('NIC', \@hwinfo, ['Model', ' Device', ' Vendor', 'HW Address'], \%modules);
    		}
    	}

    	# CDROM
    	if (($line =~ /\*\-cdrom/ || $line =~ /Hardware Class: cdrom/) && ($enable_all == 1 || $enabled{'cdrom'} == 1)) {
    		if ($Mode eq 'LSHW') {
    			get_module_data ('CDROM', \@hwinfo, ['product', 'description', 'vendor'], \%modules);
    		} else {
    			# Spaces before Device and Vendor are intentional to avoid matching SubDevice and SubVendor
    			get_module_data ('CDROM', \@hwinfo, ['Model', ' Device', ' Vendor'], \%modules);
    		}
    	}

    	# HD
    	if (($line =~ /\*\-disk/ || $line =~ /Hardware Class: disk/) && ($enable_all == 1 || $enabled{'hd'} == 1)) {
    		if ($Mode eq 'LSHW') {
    			get_module_data ('HD', \@hwinfo, ['product', 'description', 'size'], \%modules);
    		} else {
    			get_module_data ('HD', \@hwinfo, ['Model', 'Serial ID', 'Size'], \%modules);
    		}
    	}
    }
}
# Software
if ($enable_all == 1 || $enabled{'software'} == 1) {
	get_software_module_data ('Software', \%modules);
}

#init_services
if ($enable_all == 1 || $enabled{'init_services'} == 1) {
        get_servicies_init_machine ('Init services', \%modules);
}

#filesystem
if ($enable_all == 1 || $enabled{'filesystem'} == 1) {
	get_file_system('File system', \%modules);
}

#processes
if ($enable_all == 1 || $enabled{'process'} == 1) {
	get_processes('Process', \%modules);
}

#users
if ($enable_all == 1 || $enabled{'users'} == 1){
	get_users ('Users', \%modules);
}

#ip
if ($enable_all == 1 || $enabled{'ip'} == 1) {
	get_ips('IP', \%modules);
}

#route
if ($enable_all == 1 || $enabled{'route'} == 1) {
	get_route_table('Routes', \%modules);
}

#kernel
if ($enable_all == 1 || $enabled{'kernel'} == 1){
	get_kernel_info ('Kernel', \%modules);
}
# Print module data
print "<inventory>\n";
while (my ($name, $module) = each (%modules)) {
	print_module ($name, $module);
}
print "</inventory>\n";

exit 0;