#!/usr/bin/perl
###############################################################################
#
# Copyright (c) 2009  Artica Soluciones Tecnologicas S.L.
#
# 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';

# 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;

	# 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;
				push (@{$module{'_keys'}}, $key);
			}
		}
	}

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

	push (@{$modules->{$name}}, \%module);
}

# Get a list of information file system in machine
sub get_file_system($$) {
	my ($name, $modules) = @_;

	my @fileSystems = `df -h | 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 = `runlevel | cut -d' ' -f2`;

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

	# We ned to strip ";" characters because will be used as field separator!
	my $script = "ls /etc/rc" . $runlevel .".d/ -l | grep \"^l.*\" | grep \" S.* \" | cut -d' ' -f11 | tr -d \";\" ";

	my @services = `$script`;
	foreach my $row (@services) {
		next unless ($row =~ /^\S+\/(\S+)$/);
                
		my %module;
                $module{'service'} = $1;
                $module{'_keys'} = ['service'];
                push (@{$modules->{$name}}, \%module);
	}
}

# Get a list of running processes
sub get_processes ($$) {
	my ($name, $modules) = @_;

	# We ned to strip ";" characters because will be used as field separator!
	my $script = "ps -eo command |  tr -d \";\" ";

        my @services = `$script`;
        foreach my $row (@services) {
                my %module;
				# Remove carriage returns				
				$row =~ s/[\n\l\f]//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\-\_]*)/);

                $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 ( -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`;
	} 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;
		$module{'_keys'} = ['program', 'version','description'];
		push (@{$modules->{$name}}, \%module);
	}
}

# 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 = '';
		foreach my $key (@{$item->{'_keys'}}) {
			next unless defined ($item->{$key});
			$data .= ($data eq '' ? '' : ';') . $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] [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;
}

foreach my $module (@ARGV) { 
	if ($module eq "all"){
		$enable_all = 1;
	}
	$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 = '';
my @hwinfo = `lshw 2>/dev/null`;
if ($? != 0) {
	$Mode = 'HWINFO';
	$Separator = 'Hardware Class:';
	@hwinfo = `hwinfo --cpu --memory --gfxcard --netcard --cdrom --disk 2>/dev/null`;
}

# Parse hardware information
my %modules;
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 =~ /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'], \%modules);
		} else {
			# Spaces before Device and Vendor are intentional to avoid matching SubDevice and SubVendor
			get_module_data ('NIC', \@hwinfo, ['Model', ' Device', ' Vendor'], \%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);
}

#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;
