#!/usr/bin/perl
#
# Script to be run by cron each night (or whatever) to backup locations
# specified in the configuration file (/etc/slbackup/slbackup.conf by default).
#
# $Id: slbackup-cron,v 1.22 2008-01-29 14:48:17 finnarne-guest Exp $
#

#use strict; # failed with getopts.pl
use Config::General;
use POSIX qw(strftime);
use Net::DNS;
use SLBackup;
require 'getopts.pl';


sub usage() {
    print  <<_EOUSAGE_;
Usage: $0 [-c <file>] [-l <logfile> ] [-r <predir>] [-o <postdir>]
          [-s <scriptlog>]
    -c <conffile>   Name and location of the configurationfile (default is
                    /etc/slbackup/slbackup.conf).
    -l <logfile>    Name and location for the logfile (default is
                    /var/log/slbackup/slbackup.log).
    -r <predir>     Name and location of the directory that contains
                    the scripts that shall run before the backup session
                    starts (default is /etc/slbackup/pre.d/).
    -o <postdir>    Name and location of the directory that contains
                    the scripts that shall run after the backup session
                    has finished (default is /etc/slbackup/post.d/).
    -s <scriptlog>  Name and location of the logfile pre- and
                    post-scripts (default is 
                    /var/log/slbackup/run_scripts.log).
    -h              Display this usage-info.
    -v              Verbose output to the logfile(s).
_EOUSAGE_
    exit 0;
}

# parse commandline
&Getopts ("c:l:r:o:s:hv") || &usage();
my $conffile = $opt_c || "/etc/slbackup/slbackup.conf";
my $logfile = $opt_l || "/var/log/slbackup/slbackup.log";
my $scripts_predir = $opt_r || "/etc/slbackup/pre.d";
my $scripts_postdir = $opt_o || "/etc/slbackup/post.d";
my $scripts_logfile = $opt_s || "/var/log/slbackup/run_scripts.log";
my $debug = 0;
$debug = 1 if $opt_v;
&usage() if $opt_h;

# errorsets = Number of backup sets (clients) from config for which
# rdiff-backup fails
my $errorclients = 0;

# open logfile
open (LOG, ">>$logfile") or die ("Unable to open $logfile\n");
logger ("Starting slbackup:");

# debug-output
if ($debug) {
    logger ("Debug-output:\n" .
	    "  conffile        : $conffile\n" .
	    "  logfile         : $logfile\n" .
	    "  scripts_predir  : $scripts_predir\n" .
	    "  scripts_postdir : $scripts_postdir\n" .
	    "  scripts_logfile : $scripts_logfile\n" .
	    "End debug-output.");
}


# fetch configuration
my $config;
if (-r $conffile) {
    $config = &slbackup_readconfig($conffile);
} else {
    logger ("Unable to read config file ($conffile), exiting.");
    logger ("Finished slbackup.");
    close (LOG);
    die ("Unable to open config file ($conffile), \nexiting ");
}

# run executables in $scripts_predir
my $retval_predir = &run_scripts($scripts_predir, $scripts_logfile, $debug);
if ( $retval_predir eq 1 ) {
    logger ("Successfully running scripts in $scripts_predir.");
} elsif ( $retval_predir eq -1 ) {
    logger ("Failed reading $scripts_predir.");
    $errorclients += 1;
} elsif ( $retval_predir eq -2 ) {
    logger ("Failed to run one or more scripts in $scripts_predir.");
    $errorclients += 1;
} else {
    logger ("Something strange happened when running scripts in\n" . \
	    "$scripts_predir");
    $errorclients += 1;
}    

# run rdiff-backup for each client in configuration
for my $key (keys %{$config->{client}}) {
    my $client = $config->{client}->{$key};
    my $execstr = "";
    my $execstr_serverpart = "";
    my $execstr_clientpart = "";

    # check if server is not of type "local" ->
    #   add server-part of the exeecstr in a 
    if (exists ($config->{server_type}) and
	$config->{server_type} ne "local") {
	
	# check if server_address is present in configuration
	if (!exists ($config->{server_address})) {
	    logger ("Address for server is not present in configuration " .
		    "file... please fix!");
	    logger("Failed backing up clients.");
	    $errorclients += 1;
	    last;
	}
	
	# check if server_address is valid:
	if (!$config->{server_address} =~ '^([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])$') {
	    # server_address doesn't seem to be an IP...
	    # let's try to look up the host's addresses:
	    
	    my $dns_res   = Net::DNS::Resolver->new;
	    my $query = $dns_res->search($config->{server_address});
	    
	    if (!$query) {
		logger ("Couldn't resolve host " .
			"\'$config->{server_address}\' ");
		logger ("Failed backing up clients.");
		$errorclients += 1;
		last;
	    }
	}
	
	# check if server_user is present in configuration
	if (!exists ($config->{server_user})) {
	    logger ("Username for server is not present in configuration " .
		    "file... please fix!");
	    logger("Failed backing up clients.");
	    $errorclients += 1;
	    last;
	}
	
	# test if ssh-connection to server works ok
	my $sshteststr = "ssh -o BatchMode=yes " .
	    "$config->{server_user}" . "@" . 
		"$config->{server_address} 'echo -n 1'";
	if (`$sshteststr` ne "1") {
	    logger ("ssh-connection to server $key failed...");
	    logger ("Failed backing up clients.");
	    $errorclients += 1;
	    last;
	}

	# test that rdiff-backup has the same version as here
	$sshteststr = "ssh $config->{server_user}" . "@" . 
		"$config->{server_address} 'rdiff-backup -V'";
	if (`$sshteststr` ne `rdiff-backup -V`) {
	    logger ("rdiff-backup does not have the same version on " . 
		    "this computer and the backup server... please fix!");
	    logger ("Failed backing up clients.");
	    $errorclients += 1;
	    last;
	}

	# test that the destination dir exists
	my $testcondition = "test -d $config->{server_destdir} " .
	    "&& echo -n ok";
	$sshteststr = "ssh $config->{server_user}" . "@" . 
		"$config->{server_address} '$testcondition'";
	if (`$sshteststr` ne `echo -n ok`) {
	    logger ("Destination directory (server_destdir) does not seem \n" . 
		    "to exist on the backup server... please fix!");
	    logger ("Failed backing up clients.");
	    $errorclients += 1;
	    last;
	}

	# the server-part of the configuration shall be ok, so
	# build server-part of execstr and continue
	$execstr_serverpart = 
	    "$config->{server_user}\@$config->{server_address}::";
    } else {
	# the server is the localhost, then checking that the destination
	# directory exists
	if (! -d $config->{server_destdir}) {
	    logger ("Destination directory (server_destdir) does not seem \n" . 
		    "to exist on the backup server... please fix!");
	    logger ("Failed backing up clients.");
	    $errorclients += 1;
	    last;
	}
    }

    # check if destination directory on backup server is represented in
    # configuration file -> return, else add it :)
    if (!exists ($config->{server_destdir})) {
	logger ("Destination directory on the server is not specified " .
		"in the configuration... please fix!");
	logger ("Failed backing up clients.");
	$errorclients += 1;
	last;
    }
    $execstr_serverpart .= "$config->{server_destdir}/$key";


    # start with the client-handling
    logger ("Starting backup of client $key");

    # check if client not is of type "local" ->
    #  - check if necessary configuration options are present
    #  - check if ssh-connection is ok
    #  - check if rdiff-backup is the same version as here
    if (exists ($config->{client}->{$key}->{type}) and
	$config->{client}->{$key}->{type} ne "local") {

	# check that address is provided
	if (!exists ($config->{client}->{$key}->{address})) {
	    logger ("Address for client $key is not present in " .
		    "configuration... please fix!");
	    logger ("Backup of client $key failed.");
	    $errorclients += 1;
	    next;
	}

	# check if the client's address is valid:
	if (!$config->{client}->{$key}->{address} =~ '^([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])$')
	{
	    # address doesn't seem to be an IP...
	    # let's try to look up the host's addresses:
	    
	    my $dns_res   = Net::DNS::Resolver->new;
	    my $query = $dns_res->search($config->{client}->{$key}->{address});
	    
	    if (!$query)
	    {
		logger ("Couldn't resolve host " .
			"\'$config->{client}->{$key}->{address}\' ");
		logger ("Backup of client $key failed.");
		$errorclients += 1;
		next;
	    }
	}

	# check that username is provided
	if (!exists ($config->{client}->{$key}->{user})) {
	    logger ("Username for client $key is not present in " .
		    "configuration... please fix!");
	    logger ("Backup of client $key failed");
	    $errorclients += 1;
	    next;
	}

	# test that ssh connection to the client works ok
	my $sshteststr = "ssh -o BatchMode=yes " .
	    "$config->{client}->{$key}->{user}" . "@" . 
		"$config->{client}->{$key}->{address} 'echo -n 1'";
	if (`$sshteststr` ne "1") {
	    logger ("ssh-connection to $key failed...");
	    logger ("Failed backing up client $key.");
	    $errorclients += 1;
	    next;
	}
	
	# test that rdiff-backup on the client is the same version as here
	$sshteststr = "ssh $config->{client}->{$key}->{user}" . "@" . 
	    "$config->{client}->{$key}->{address} 'rdiff-backup -V'";
	if (`$sshteststr` ne `rdiff-backup -V`) {
	    logger ("rdiff-backup does not have the same version on this " .
		    "computer and the client $key... please fix!");
	    logger ("Failed backing up client $key.");
	    $errorclients += 1;
	    next;
	}

	# client configuration shall be ok, so we continue:
	# add client address in the client-part of execstr
	$execstr_clientpart .= 
	    "$config->{client}->{$key}->{user}\@" .
	    "$config->{client}->{$key}->{address}::";
    }
    
    # add the common part of the client execstring
    # (specify '/' as the location)
    $execstr_clientpart .= "/";

    # build execute string
    $execstr = "rdiff-backup --print-statistics ";

    # support for the --exclude-regexp option to rdiff-backup
    if (exists ($config->{exclude_regexp}) ) {
	$execstr .= "--exclude-regexp '$config->{exclude_regexp}' ";
    }
    
    if (exists ($config->{client}->{$key}->{exclude})) {
	if (ref ($config->{client}->{$key}->{exclude}) eq "ARRAY") {
	    # there is more than one location to exclude (=> exclude
	    # is an array)
	    for my $loc (@{$config->{client}->{$key}->{exclude}}) {
		$execstr .= "--exclude '$loc' ";
	    }
	} else {
	    # there is only one location to exclude (=> exclude is a string)
	    my $loc = $config->{client}->{$key}->{exclude};
	    $execstr .= "--exclude '$loc' ";
	}
    }
    
    # We don't want to cause an endless loop... ;)
    if (exists ($config->{client}->{$key}->{type}) and
       $config->{client}->{$key}->{type} eq "local") {
       $execstr .= "--exclude $config->{server_destdir} ";
    }

    # include clients locations if exists
    if (!exists ($config->{client}->{$key}->{location})) {
	logger ("Locations for client $key is not present in " .
		"configuration... please fix!");
	logger ("No files from client $key will be backed up.");
	$errorclients += 1;
	next;
    } elsif (ref ($config->{client}->{$key}->{location}) eq "ARRAY") {
	# there are more than one location => location is an array
	for my $loc (@{$config->{client}->{$key}->{location}}) {
	    $execstr .= "--include '$loc' ";
	}
    } else {
	# there is only one location => location is a string
	my $loc = $config->{client}->{$key}->{location};
	$execstr .= "--include '$loc' ";
    }

    # exclude everything else
    $execstr .= "--exclude '/*' ";
    
    # include client-part and server-part
    $execstr .= "$execstr_clientpart $execstr_serverpart";

    # before backing up, remove old backups
    my $client_keep;
    if (($client_keep = $config->{client}->{$key}->{keep}) and 
	($client_keep gt 0)) {
	my $removestr = "rdiff-backup --force --remove-older-than ";
	$removestr .= "$client_keep" . "D ";
	my $server_type = $config->{server_type};
	my $server_destdir = $config->{server_destdir};
	my $server_address = $config->{server_address};
	my $server_user = $config->{server_user};

	if ($server_type ne "local") {
	    $removestr .= "$server_user" . "@" . "$server_address" . "::";
	}
	if (grep (/\/$/, $server_destdir)) {
	    $removestr .= "$server_destdir";
	} else {
	    $removestr .= "$server_destdir" . "/";
	}
	$removestr .= "$key";

	# remove backups older than $client_keep
	#FIXME - check if there are backups there...
	logger ("Trying to remove backups older than $client_keep days:");
	my $output .= `$removestr 2>&1`;
	logger ("$output");
    
	# 0 mean success -> invert it
	my $retval = ! $?; 

	# log
	if ($retval) {
	    logger ("Removing backups older than $client_keep days succeeded!");
	} else {
	    logger ("Failed removing backups older than $client_keep (not critical).");
	}
    }    

    if ($debug) {
        logger ("Will run: $execstr\n") ;
    }
    # run rdiff-backup for client $key
    my $output .= `$execstr 2>&1`;
    logger ("\n$output");
    
    # 0 mean success -> invert it
    my $retval = ! $?; 

    # log
    if ($retval) {
	logger ("Successfully finished backing up client $key");
    } else {
	logger ("Failed backing up client $key");
	$errorclients += 1;
    }
}

# run executables in $scripts_postdir
my $retval_postdir = &run_scripts($scripts_postdir, $scripts_logfile, $debug);
if ( $retval_postdir eq 1 ) {
    logger ("Successfully running of scripts in $scripts_postdir.");
} elsif ( $retval_postdir eq -1 ) {
    logger ("Failed reading $scripts_postdir.");
    $errorclients += 1;
} elsif ( $retval_postdir eq -2 ) {
    logger ("Failed to run one or more scripts in $scripts_postdir.");
    $errorclients += 1;
} else {
    logger ("Something strange happened when running scripts in\n" . \
	    "$scripts_postdir");
    $errorclients += 1;
}

logger ("Finished slbackup.");
close (LOG);


sub logger {
    my ($comment) = @_;
    my $now = strftime "%b %d %H:%M:%S", localtime;
    printflush LOG ("$now - $comment\n");
}

if ( $errorclients gt 0 ) {
    # exit 1 if any of the client's failed
    print STDERR "Error: backup failed for one or more of your clients.\n";
    print STDERR "       Please take a look at $logfile for details.\n";
    exit 1;
}

exit 0;
