-#!/bin/perl -T
+#!/usr/bin/perl
#============================================================= -*-perl-*-
#
# BackupPC_sendEmail: send status emails to users and admins
# Craig Barratt <cbarratt@users.sourceforge.net>
#
# COPYRIGHT
-# Copyright (C) 2001 Craig Barratt
+# Copyright (C) 2001-2009 Craig Barratt
#
# 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
#
#========================================================================
#
-# Version 2.0.0, released 14 Jun 2003.
+# Version 3.2.0, released 31 Jul 2010.
#
# See http://backuppc.sourceforge.net.
#
use lib "/usr/local/BackupPC/lib";
use BackupPC::Lib;
use BackupPC::FileZIO;
+use Encode;
use Data::Dumper;
use Getopt::Std;
use DirHandle ();
-use vars qw($Lang $TopDir $BinDir %Conf);
+use vars qw($Lang $TopDir $BinDir $LogDir %Conf $Hosts);
die("BackupPC::Lib->new failed\n") if ( !(my $bpc = BackupPC::Lib->new) );
$TopDir = $bpc->TopDir();
+$LogDir = $bpc->LogDir();
$BinDir = $bpc->BinDir();
%Conf = $bpc->Conf();
$Lang = $bpc->Lang();
+$Hosts = $bpc->HostInfoRead();
$bpc->ChildInit();
use vars qw(%UserEmailInfo);
-do "$TopDir/log/UserEmailInfo.pl";
+do "$LogDir/UserEmailInfo.pl";
my %opts;
-if ( !getopts("t", \%opts) || @ARGV != 0 ) {
- print("usage: $0 [-t]\n");
- exit(1);
-}
+if ( !getopts("ctu:", \%opts) || @ARGV != 0 ) {
+ print <<EOF;
+usage: $0 [-t] [-c] [-u userEmail]
+options:
-my $err = $bpc->ServerConnect($Conf{ServerHost}, $Conf{ServerPort});
-if ( $err ) {
- print("Can't connect to server ($err)\n");
- exit(1);
-}
-my $reply = $bpc->ServerMesg("status hosts");
-$reply = $1 if ( $reply =~ /(.*)/s );
-my(%Status, %Info, %Jobs, @BgQueue, @UserQueue, @CmdQueue);
-eval($reply);
+ -t display the emails that would be sent, without sending them
-###########################################################################
-# Generate sysadmin warning messages
-###########################################################################
-my $mesg = "";
-
-my @badHosts = ();
-foreach my $host ( sort(keys(%Status)) ) {
- next if ( $Status{$host}{reason} ne "backup failed"
- || $Status{$host}{error} =~ /^lost network connection to host/ );
- push(@badHosts, "$host ($Status{$host}{error})");
-}
-if ( @badHosts ) {
- my $badHosts = join("\n - ", sort(@badHosts));
- $mesg .= <<EOF;
-The following hosts had an error that is probably caused by a
-misconfiguration. Please fix these hosts:
- - $badHosts
+ -c check if BackupPC is alive and send an email if not
+ -u send a test email to userEmail
EOF
+ exit(1);
}
#
-# Report if we skipped backups because the disk was too full
+# Upgrade legacy version of %UserEmailInfo
#
-if ( $Info{DUDailySkipHostCntPrev} > 0 ) {
- my $n = $Info{DUDailySkipHostCntPrev};
- my $m = $Conf{DfMaxUsagePct};
- $mesg .= <<EOF;
-Yesterday $n hosts were skipped because the file system containing
-$TopDir was too full. The threshold in the
-configuration file is $m%, while yesterday the file system was
-up to $Info{DUDailyMaxPrev}% full. Please find more space on the file system,
-or reduce the number of full or incremental backups that we keep.
-
-EOF
+# Prior to 3.2.0, it was a hash with entries:
+#
+# $UserEmailInfo{$user}{lastTime}
+# $UserEmailInfo{$user}{lastSubj}
+# $UserEmailInfo{$user}{lastHost}
+#
+# However, if a user had multiple hosts, then an email about one
+# host prevents mail delivery about other hosts. Starting in 3.2.0
+# the hash is:
+#
+# $UserEmailInfo{$user}{$host}{lastTime}
+# $UserEmailInfo{$user}{$host}{lastSubj}
+#
+my $oldFormat = 0;
+foreach my $user ( keys(%UserEmailInfo) ) {
+ if ( defined($UserEmailInfo{$user}{lastTime})
+ && ref($UserEmailInfo{$user}{lastTime}) ne 'HASH' ) {
+ $oldFormat = 1;
+ last;
+ }
+}
+if ( $oldFormat ) {
+ #
+ # Convert to the new format
+ #
+ my %UserEmailInfoOld = %UserEmailInfo;
+ %UserEmailInfo = ();
+ foreach my $user ( keys(%UserEmailInfoOld) ) {
+ next if ( $user eq "" );
+ my $host = $UserEmailInfoOld{$user}{lastHost};
+ next if ( !defined($host) );
+ $UserEmailInfo{$user}{$host}{lastTime} = $UserEmailInfoOld{$user}{lastTime};
+ $UserEmailInfo{$user}{$host}{lastSubj} = $UserEmailInfoOld{$user}{lastSubj};
+ }
}
#
-# Check for bogus directories (probably PCs that are no longer
-# on the backup list)
+# Prune hosts that no longer exist
#
-my $d = DirHandle->new("$TopDir/pc") or die("Can't read $TopDir/pc: $!");
-my @oldDirs = ();
-my @files = $d->read;
-$d->close;
-foreach my $host ( @files ) {
- next if ( $host eq "." || $host eq ".." || defined($Status{$host}) );
- push(@oldDirs, "$TopDir/pc/$host");
+foreach my $user ( keys(%UserEmailInfo) ) {
+ foreach my $host ( keys(%{$UserEmailInfo{$user}}) ) {
+ next if ( defined($Hosts->{$host}) );
+ delete($UserEmailInfo{$user}{$host});
+ }
+ next if ( $UserEmailInfo{$user} );
+ delete($UserEmailInfo{$user});
}
-if ( @oldDirs ) {
- my $oldDirs = join("\n - ", sort(@oldDirs));
- $mesg .= <<EOF;
-The following directories are bogus and are not being used by
-BackupPC. This typically happens when PCs are removed from the
-backup list. If you don't need any old backups from these PCs you
-should remove these directories. If there are machines on this
-list that should be backed up then there is a problem with the
-hosts file:
- - $oldDirs
+my $err = $bpc->ServerConnect($Conf{ServerHost}, $Conf{ServerPort});
+if ( $err ) {
+ if ( $opts{c} && $Conf{EMailAdminUserName} ne "" ) {
+ my $headers = $Conf{EMailHeaders};
+ $headers .= "\n" if ( $headers !~ /\n$/ );
+ my $mesg = <<EOF;
+To: $Conf{EMailAdminUserName}
+Subject: BackupPC: can't connect to server
+$headers
+Error: cannot connect to BackupPC server.
+
+Regards,
+PC Backup Genie
EOF
+ SendMail($mesg);
+ exit(1);
+ }
+ print("Can't connect to server ($err)\n");
+ exit(1);
}
+exit(0) if ( $opts{c} );
+my $reply = $bpc->ServerMesg("status hosts info");
+$reply = $1 if ( $reply =~ /(.*)/s );
+my(%Status, %Info, %Jobs, @BgQueue, @UserQueue, @CmdQueue);
+eval($reply);
-if ( $mesg ne "" && $Conf{EMailAdminUserName} ne "" ) {
- $mesg = <<EOF;
-To: $Conf{EMailAdminUserName}
-Subject: BackupPC administrative attention needed
+###########################################################################
+# Generate test message if required
+###########################################################################
+if ( $opts{u} ne "" ) {
+ my $headers = $Conf{EMailHeaders};
+ $headers .= "\n" if ( $headers !~ /\n$/ );
+ my $mesg = <<EOF;
+To: $opts{u}
+Subject: BackupPC test email
+$headers
+This is a test message from $0.
-${mesg}Regards,
+Regards,
PC Backup Genie
EOF
- if ( $opts{t} ) {
- print("#" x 75, "\n");
- print $mesg;
- } else {
- SendMail($mesg);
- }
+ SendMail($mesg);
+ exit(0);
}
###########################################################################
# Generate per-host warning messages sent to each user
###########################################################################
-my $Hosts = $bpc->HostInfoRead();
+my @AdminBadHosts = ();
foreach my $host ( sort(keys(%Status)) ) {
- next if ( $Hosts->{$host}{user} eq "" );
#
# read any per-PC config settings (allowing per-PC email settings)
#
$bpc->ConfigRead($host);
%Conf = $bpc->Conf();
my $user = $Hosts->{$host}{user};
- next if ( time - $UserEmailInfo{$user}{lastTime}
- < $Conf{EMailNotifyMinDays} * 24*3600 );
+
+ next if ( $user eq "" );
+
+ #
+ # Accumulate host errors for the admin email below
+ #
+ if ( ($Status{$host}{reason} eq "Reason_backup_failed"
+ || $Status{$host}{reason} eq "Reason_restore_failed")
+ && $Status{$host}{error} !~ /^lost network connection to host/
+ && !$Conf{BackupsDisable}
+ ) {
+ push(@AdminBadHosts, "$host ($Status{$host}{error})");
+ }
+
+ next if ( time - $UserEmailInfo{$user}{$host}{lastTime}
+ < $Conf{EMailNotifyMinDays} * 24*3600
+ || $Conf{XferMethod} eq "archive"
+ || $Conf{BackupsDisable}
+ || $Hosts->{$host}{user} eq ""
+ );
my @Backups = $bpc->BackupInfoRead($host);
my $numBackups = @Backups;
if ( $numBackups == 0 ) {
my $numBadOutlook = 0;
for ( my $i = 0 ; $i < @Backups ; $i++ ) {
my $fh;
+ #
+ # ignore partials -> only fulls and incrs should be used
+ # in figuring out when the last good backup was
+ #
+ next if ( $Backups[$i]{type} eq "partial" );
$lastNum = $Backups[$i]{num} if ( $lastNum < $Backups[$i]{num} );
if ( $Backups[$i]{type} eq "full" ) {
$lastFull = $Backups[$i]{startTime}
while ( 1 ) {
my $s = $fh->readLine();
last if ( $s eq "" );
- if ( $s =~ /^Error reading file.*\.pst : ERRDOS - ERRlock/
- || $s =~ /^Error reading file.*\.pst\. Got 0 bytes/ ) {
+ if ( $s =~ /^\s*Error reading file.*\.pst : (ERRDOS - ERRlock|NT_STATUS_FILE_LOCK_CONFLICT)/
+ || $s =~ /^\s*Error reading file.*\.pst\. Got 0 bytes/ ) {
$badOutlook = 1;
last;
}
}) if ( !defined($Jobs{$host}) );
}
}
+
+###########################################################################
+# Generate sysadmin warning message
+###########################################################################
+my $adminMesg = "";
+
+if ( @AdminBadHosts ) {
+ my $badHosts = join("\n - ", sort(@AdminBadHosts));
+ $adminMesg .= <<EOF;
+The following hosts had an error that is probably caused by a
+misconfiguration. Please fix these hosts:
+ - $badHosts
+
+EOF
+}
+
+#
+# Report if we skipped backups because the disk was too full
+#
+if ( $Info{DUDailySkipHostCntPrev} > 0 ) {
+ my $n = $Info{DUDailySkipHostCntPrev};
+ my $m = $Conf{DfMaxUsagePct};
+ $adminMesg .= <<EOF;
+Yesterday $n hosts were skipped because the file system containing
+$TopDir was too full. The threshold in the
+configuration file is $m%, while yesterday the file system was
+up to $Info{DUDailyMaxPrev}% full. Please find more space on the file system,
+or reduce the number of full or incremental backups that we keep.
+
+EOF
+}
+
+#
+# Check for bogus directories (probably PCs that are no longer
+# on the backup list)
+#
+my $d = DirHandle->new("$TopDir/pc") or die("Can't read $TopDir/pc: $!");
+my @oldDirs = ();
+my @files = $d->read;
+$d->close;
+foreach my $host ( @files ) {
+ next if ( $host =~ /^\./ || defined($Status{$host}) );
+ push(@oldDirs, "$TopDir/pc/$host");
+}
+if ( @oldDirs ) {
+ my $oldDirs = join("\n - ", sort(@oldDirs));
+ $adminMesg .= <<EOF;
+The following directories are bogus and are not being used by
+BackupPC. This typically happens when PCs are removed from the
+backup list. If you don't need any old backups from these PCs you
+should remove these directories. If there are machines on this
+list that should be backed up then there is a problem with the
+hosts file:
+ - $oldDirs
+
+EOF
+}
+
+if ( $adminMesg ne "" && $Conf{EMailAdminUserName} ne "" ) {
+ my $headers = $Conf{EMailHeaders};
+ $headers .= "\n" if ( $headers !~ /\n$/ );
+ $adminMesg = <<EOF;
+To: $Conf{EMailAdminUserName}
+Subject: BackupPC administrative attention needed
+$headers
+${adminMesg}Regards,
+PC Backup Genie
+EOF
+ SendMail($adminMesg);
+}
+
+###########################################################################
+# Save email state and exit
+###########################################################################
if ( !$opts{t} ) {
$Data::Dumper::Indent = 1;
my $dumpStr = Data::Dumper->Dump(
[\%UserEmailInfo],
[qw(*UserEmailInfo)]);
- if ( open(HOST, ">", "$TopDir/log/UserEmailInfo.pl") ) {
+ if ( open(HOST, ">", "$LogDir/UserEmailInfo.pl") ) {
binmode(HOST);
print(HOST $dumpStr);
close(HOST);
}
}
+exit(0);
sub user2name
{
sub sendUserEmail
{
my($user, $host, $mesg, $subj, $vars) = @_;
- $vars->{user} = $user;
- $vars->{host} = $host;
- $vars->{domain} = $Conf{EMailUserDestDomain};
- $vars->{CgiURL} = $Conf{CgiURL};
+ return if ( $Conf{BackupsDisable} );
+
+ $vars->{user} = $user;
+ $vars->{host} = $host;
+ $vars->{headers} = $Conf{EMailHeaders};
+ $vars->{headers} .= "\n" if ( $vars->{headers} !~ /\n$/ );
+ $vars->{domain} = $Conf{EMailUserDestDomain};
+ $vars->{CgiURL} = $Conf{CgiURL};
$subj =~ s/\$(\w+)/defined($vars->{$1}) ? $vars->{$1} : "\$$1"/eg;
- $vars->{subj} = $subj;
+ $vars->{subj} = encode('MIME-Header', $subj);
$mesg =~ s/\$(\w+)/defined($vars->{$1}) ? $vars->{$1} : "\$$1"/eg;
- if ( $opts{t} ) {
- print("#" x 75, "\n");
- print $mesg;
- } else {
- SendMail($mesg);
- }
- $UserEmailInfo{$user}{lastTime} = time;
- $UserEmailInfo{$user}{lastSubj} = $subj;
- $UserEmailInfo{$user}{lastHost} = $host;
+ SendMail($mesg);
+ $UserEmailInfo{$user}{$host}{lastTime} = time;
+ $UserEmailInfo{$user}{$host}{lastSubj} = $subj;
}
sub SendMail
{
my($mesg) = @_;
- my($from) = $Conf{EMailFromUserName};
+ my $from = $Conf{EMailFromUserName};
+ my $utf8 = 1
+ if ( $Conf{EMailHeaders} =~ /Content-Type:.*charset="utf-?8"/i );
local(*MAIL);
+ if ( $opts{t} ) {
+ binmode(STDOUT, ":utf8") if ( $utf8 );
+
+ print("#" x 75, "\n");
+ print $mesg;
+ return;
+ }
$from = "-f $from" if ( $from ne "" );
+ print("Sending test email using $Conf{SendmailPath} -t $from\n")
+ if ( $opts{u} ne "" );
if ( !open(MAIL, "|$Conf{SendmailPath} -t $from") ) {
printf("Can't run sendmail ($Conf{SendmailPath}): $!\n");
return;
}
+ binmode(MAIL, ":utf8") if ( $utf8 );
print MAIL $mesg;
close(MAIL);
}