#!/usr/bin/perl
#
# THIS SOFTWARE IS NOT PART OF NMIS AND IS COPYRIGHTED, PROTECTED AND LICENSED 
# BY OPMANTEK.  
# 
# YOU MUST NOT MODIFY OR DISTRIBUTE THIS CODE
# 
# This code is NOT Open Source
# 
# IT IS IMPORTANT THAT YOU HAVE READ CAREFULLY AND UNDERSTOOD THE END USER 
# LICENSE AGREEMENT THAT WAS SUPPLIED WITH THIS SOFTWARE.   BY USING THE 
# SOFTWARE  YOU ACKNOWLEDGE THAT (1) YOU HAVE READ AND REVIEWED THE LICENSE 
# AGREEMENT IN ITS ENTIRETY, (2) YOU AGREE TO BE BOUND BY THE AGREEMENT, (3) 
# THE INDIVIDUAL USING THE SOFTWARE HAS THE POWER, AUTHORITY AND LEGAL RIGHT 
# TO ENTER INTO THIS AGREEMENT ON BEHALF OF YOU (AS AN INDIVIDUAL IF ON YOUR 
# OWN BEHALF OR FOR THE ENTITY THAT EMPLOYS YOU )) AND, (4) BY SUCH USE, THIS 
# AGREEMENT CONSTITUTES BINDING AND ENFORCEABLE OBLIGATION BETWEEN YOU AND 
# OPMANTEK LTD. 
# 
# Opmantek is a passionate, committed open source software company - we really 
# are.  This particular piece of code was taken from a commercial module and 
# thus we can't legally supply under GPL. It is supplied in good faith as 
# source code so you can get more out of NMIS.  According to the license 
# agreement you can not modify or distribute this code, but please let us know 
# if you want to and we will certainly help -  in most cases just by empaling 
# you a different agreement that better suits what you want to do but covers 
# Opmantek legally too. 
# 
# contact opmantek by emailing code@opmantek.com
# 
# All licenses for all software obtained from Opmantek (GPL and commercial) 
# are viewable at http://opmantek.com/licensing
#   
# *****************************************************************************

use strict;
use Data::Dumper;
use File::Basename;
use File::Temp;
use POSIX qw();
use Cwd;
use FindBin;
use lib "$FindBin::RealBin/../lib";
use OMK::Common;

my $VERSION = "1.0.1";

my $usage = "Opmantek OMK Support Tool Version $VERSION\n
Usage: ".basename($0)." action=collect\n
action=collect: collect general support info in an archive file\n\n";
my $args = getArgs(@ARGV);

die $usage if (!defined $args->{action} or $args->{action} ne "collect");

my $configfile = $args->{config} || "opCommon.nmis";
my $maxzip = $args->{maxzipsize} || 10*1024*1024; # 10meg
my $tail = 5000;																# last 5000 lines

# first, load the global config
my $globalconf = loadOmkConfTable(conf => $configfile, dir => "$FindBin::RealBin/../conf");
warn "ERROR: cannot read the configuration from $configfile!\n" if (!$globalconf);

# make tempdir
my $td = File::Temp::tempdir("/tmp/support.XXXXXX", CLEANUP => 1);

if ($args->{action} eq "collect")
{
		# collect evidence
		my $timelabel = POSIX::strftime("%Y-%m-%d-%H%M",localtime);
		my $targetdir = "$td/collect.$timelabel";
		mkdir($targetdir);
		print "collecting support evidence...\n";
		my $status = collect_evidence($targetdir,$args);
		die "failed to collect evidence: $status\n" if ($status);

		my $nmiszfn;
		# nmis and its support tool found, run that as well
		if (-d $globalconf->{'nmis_dir'} && -f $globalconf->{'nmis_dir'}."/admin/support.pl" && !$args->{no_other_tools})
		{
			open(LF, ">$targetdir/nmis-support.log");

			print "\nFound local NMIS installation with NMIS support tool.
Please wait while we collect NMIS information as well.\n";
			open(F, $globalconf->{'nmis_dir'}."/admin/support.pl action=collect no_system_stats=1 no_other_tools=1 2>&1 |")
					or warn "cannot execute NMIS support tool: $!\n";
			while (my $line = <F>)
			{
				print LF $line;
				if ($line =~ /system information is in (\S+)/)
				{
					$nmiszfn = $1;
				}
			}
			close F;
			close LF;
		}

		print "\nEvidence collection complete, zipping things up...\n";

		# do we have zip? or only tar+gz?
		my $canzip=0;
		$status = system("zip --version >/dev/null 2>&1");
		$canzip=1 if (POSIX::WIFEXITED($status) && !POSIX::WEXITSTATUS($status));
		
		my $zfn = "/tmp/omk-support-$timelabel.".($canzip?"zip":"tgz");
		
		# zip mustn't become too large, hence we possibly tail/truncate some or all log files
		opendir(D,"$targetdir/logs") or return "can't read $targetdir/logs dir: $!";
		my @shrinkables = map { "$targetdir/logs/$_" } (grep($_ !~ /^\.{1,2}$/, readdir(D)));
		closedir(D);
		while (1)
		{
				# test zip, shrink logfiles, repeat until small enough or out of shrinkables
				my $curdir = getcwd;
				chdir($td);							# so that the zip file doesn't have to whole /tmp/this/n/that/ path in it
				if ($canzip)
				{
					$status = system("zip","-q","-r",$zfn, "collect.$timelabel");
				}
				else
				{
					$status = system("tar","-czf",$zfn,"collect.$timelabel");
				}
				chdir($curdir);

				die "cannot create support zip file $zfn: $!\n"
						if (POSIX::WEXITSTATUS($status));
				last if (-s $zfn < $maxzip);

				# hmm, too big: shrink the log files one by one until the size works out
				unlink($zfn);
				print "zipfile too big, trying to shrink some logfiles...\n";
				if (my $nextfile = pop @shrinkables)
				{
						$status = shrinkfile($nextfile,$tail);
						die "shrinking of $nextfile failed: $status\n" if ($status);
				}
				else
				{
						# nothing left to try :-(
						die "\nPROBLEM: cannot reduce zip file size any further!\nPlease rerun $0 with maxzipsize=N higher than $maxzip.\n";
				}
		}

		print "\nAll done.\n\nCollected system information is in $zfn\n";
		print "NMIS information is in $nmiszfn\n\n" if ($nmiszfn);
		print "Please include ".($nmiszfn? "these zip files": "this zip file"). " when you contact 
the NMIS Community or the Opmantek Team.\n\n";
}

# remove tempdir (done automatically on exit)
exit 0;


# shrinks given file to the last maxlines lines
# returns undef if ok, error message otherwise
sub shrinkfile
{
		my ($fn,$maxlines)=@_;

		my $tfn = File::Temp->new(DIR => $td, UNLINK => 1);
		system("tail -$maxlines $fn > $tfn") == 0 or return "can't tail $fn: $!";
		rename($tfn,"$fn.truncated") or return "couldn't rename $tfn to $fn.truncated: $!";
		unlink($fn) or return "couldn't remove $fn: $!\n";
		
		return undef;
}

# collects logfiles, config, generic information from var
# and parks all of that in/beneath target dir
# the target dir must already exist
#
# args: no_system_stats, optional; if set, skip the system stats
# returns nothing if ok, error message otherwise
sub collect_evidence
{
		my ($targetdir,$args) = @_;
		my $basedir = $globalconf->{'<omk_base>'};

		mkdir("$targetdir/system_status");
		# dump an ls -laRH, H for follow symlinks
		system("ls -laRH $basedir > $targetdir/system_status/filelist.txt") == 0
				or warn "can't list nmis dir: $!\n";
		
		# get md5 sums of the relevant installation files
		print "please wait while we collect file status information...\n";
		system("find -L $basedir -type f|grep -v -e /logs/|xargs md5sum -- >$targetdir/system_status/md5sum 2>&1");
		
		# verify the relevant users and groups, dump groups and passwd (not shadow)
		system("cp","/etc/group","/etc/passwd","$targetdir/system_status/");
		system("id nmis >$targetdir/system_status/nmis_userinfo 2>&1");
		system("id apache >$targetdir/system_status/web_userinfo 2>&1");
		system("id www-data >>$targetdir/system_status/web_userinfo 2>&1");
		# dump the process table
		system("ps ax >$targetdir/system_status/processlist.txt") == 0 
				or warn "can't list processes: $!\n";
		# dump the memory info, free
		system("cp","/proc/meminfo","$targetdir/system_status/meminfo") == 0
				or warn "can't save memory information: $!\n";
		chmod(0644,"$targetdir/system_status/meminfo"); # /proc/meminfo isn't writable
		system("free >> $targetdir/system_status/meminfo");

		system("df >> $targetdir/system_status/disk_info");
		system("mount >> $targetdir/system_status/disk_info");

		system("uname -av > $targetdir/system_status/uname");
		mkdir("$targetdir/system_status/osrelease");
		system("cp -a /etc/*release /etc/*version $targetdir/system_status/osrelease/ 2>/dev/null");
		
		if (!$args->{no_system_stats})
		{
			print "please wait while we gather statistics for about 15 seconds...\n";
			# dump 5 seconds of vmstat, two runs of top, 5 seconds of iostat
			# these tools aren't necessarily installed, so we ignore errors
			system("vmstat 1 5 > $targetdir/system_status/vmstat");
			system("top -b -n 2 > $targetdir/system_status/top");	
			system("iostat -kx 1 5 > $targetdir/system_status/iostat");
		}

		# copy /etc/hosts, /etc/resolv.conf, interface and route status
		system("cp","/etc/hosts","/etc/resolv.conf","/etc/nsswitch.conf","$targetdir/system_status/") == 0
				or warn "can't save dns configuration files: $!\n";
		system("/sbin/ifconfig -a > $targetdir/system_status/ifconfig") == 0
				or warn "can't save interface status: $!\n";
		system("/sbin/route -n > $targetdir/system_status/route") == 0
				or warn "can't save routing table: $!\n";

		# copy the install log if there is one
		if (-f "$basedir/install.log")
		{
				system("cp","$basedir/install.log",$targetdir) == 0
						or warn "can't copy install.log: $!\n";
		}
		# ditto the manifest
		if (-f "$basedir/manifest")
		{
			system("cp","$basedir/manifest", $targetdir) == 0
					or warn "can't copy manifest: $!\n";
		}

		# collect log files (current and newest .N rotated)
		mkdir("$targetdir/logs");
		my @logfiles;
		opendir(D, $globalconf->{"<omk_logs>"});
		@logfiles = map { $globalconf->{"<omk_logs>"}."/$_" } (grep /\.log(\.1)?$/, readdir(D));
		closedir(D);

		for my $aperrlog ("/var/log/httpd/error_log", "/var/log/apache/error.log", "/var/log/apache2/error.log")
		{
				push @logfiles, $aperrlog if (-f $aperrlog);
		}
		for my $lfn (@logfiles)
		{
			system("cp","$lfn","$targetdir/logs") == 0
					or warn "ATTENTION: can't copy logfile $lfn: $!\n";
		}
		
		# copy all of conf/
		system("cp","-r","$basedir/conf",$targetdir) == 0
				or warn "can't copy conf to $targetdir: $!\n";

		return undef;
}

