#!/bin/perl #============================================================= -*-perl-*- # # BackupPC_sendEmail: send status emails to users and admins # # DESCRIPTION # # BackupPC_sendEmail: send status emails to users and admins. # BackupPC_sendEmail is run by BackupPC_nightly, so it runs # once every night. # # AUTHOR # Craig Barratt # # COPYRIGHT # Copyright (C) 2001-2007 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 # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # 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. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # #======================================================================== # # Version 3.2.0, released 31 Dec 2008. # # See http://backuppc.sourceforge.net. # #======================================================================== use strict; no utf8; 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 $LogDir %Conf); 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(); $bpc->ChildInit(); use vars qw(%UserEmailInfo); do "$LogDir/UserEmailInfo.pl"; my %opts; if ( !getopts("ctu:", \%opts) || @ARGV != 0 ) { print <ServerConnect($Conf{ServerHost}, $Conf{ServerPort}); if ( $err ) { if ( $opts{c} && $Conf{EMailAdminUserName} ne "" ) { my $headers = $Conf{EMailHeaders}; $headers .= "\n" if ( $headers !~ /\n$/ ); my $mesg = <ServerMesg("status hosts info"); $reply = $1 if ( $reply =~ /(.*)/s ); my(%Status, %Info, %Jobs, @BgQueue, @UserQueue, @CmdQueue); eval($reply); ########################################################################### # Generate test message if required ########################################################################### if ( $opts{u} ne "" ) { my $headers = $Conf{EMailHeaders}; $headers .= "\n" if ( $headers !~ /\n$/ ); my $mesg = <HostInfoRead(); my @AdminBadHosts = (); foreach my $host ( sort(keys(%Status)) ) { # # read any per-PC config settings (allowing per-PC email settings) # $bpc->ConfigRead($host); %Conf = $bpc->Conf(); my $user = $Hosts->{$host}{user}; # # 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}{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 $subj = defined($Conf{EMailNoBackupEverSubj}) ? $Conf{EMailNoBackupEverSubj} : $Lang->{EMailNoBackupEverSubj}; my $mesg = defined($Conf{EMailNoBackupEverMesg}) ? $Conf{EMailNoBackupEverMesg} : $Lang->{EMailNoBackupEverMesg}; sendUserEmail($user, $host, $mesg, $subj, { userName => user2name($user) }) if ( !defined($Jobs{$host}) ); next; } my $last = my $lastFull = my $lastIncr = 0; my $lastGoodOutlook = 0; my $lastNum = -1; 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} if ( $lastFull < $Backups[$i]{startTime} ); } else { $lastIncr = $Backups[$i]{startTime} if ( $lastIncr < $Backups[$i]{startTime} ); } $last = $Backups[$i]{startTime} if ( $last < $Backups[$i]{startTime} ); my $badOutlook = 0; my $file = "$TopDir/pc/$host/SmbLOG.$Backups[$i]{num}"; my $comp = 0; if ( !-f $file ) { $file = "$TopDir/pc/$host/XferLOG.$Backups[$i]{num}"; if ( !-f $file ) { $comp = 1; $file = "$TopDir/pc/$host/SmbLOG.$Backups[$i]{num}.z"; $file = "$TopDir/pc/$host/XferLOG.$Backups[$i]{num}.z" if ( !-f $file ); } } next if ( !defined($fh = BackupPC::FileZIO->open($file, 0, $comp)) ); while ( 1 ) { my $s = $fh->readLine(); last if ( $s eq "" ); 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; } } $fh->close(); $numBadOutlook += $badOutlook; if ( !$badOutlook ) { $lastGoodOutlook = $Backups[$i]{startTime} if ( $lastGoodOutlook < $Backups[$i]{startTime} ); } } if ( time - $last > $Conf{EMailNotifyOldBackupDays} * 24*3600 ) { my $subj = defined($Conf{EMailNoBackupRecentSubj}) ? $Conf{EMailNoBackupRecentSubj} : $Lang->{EMailNoBackupRecentSubj}; my $mesg = defined($Conf{EMailNoBackupRecentMesg}) ? $Conf{EMailNoBackupRecentMesg} : $Lang->{EMailNoBackupRecentMesg}; my $firstTime = sprintf("%.1f", (time - $Backups[0]{startTime}) / (24*3600)); my $days = sprintf("%.1f", (time - $last) / (24 * 3600)); sendUserEmail($user, $host, $mesg, $subj, { firstTime => $firstTime, days => $days, userName => user2name($user), numBackups => $numBackups, }) if ( !defined($Jobs{$host}) ); next; } if ( $numBadOutlook > 0 && time - $lastGoodOutlook > $Conf{EMailNotifyOldOutlookDays} * 24 * 3600 ) { my($days, $howLong); if ( $lastGoodOutlook == 0 ) { $howLong = eval("qq{$Lang->{howLong_not_been_backed_up}}"); } else { $days = sprintf("%.1f", (time - $lastGoodOutlook) / (24*3600)); $howLong = eval("qq{$Lang->{howLong_not_been_backed_up_for_days_days}}"); } my $subj = defined($Conf{EMailOutlookBackupSubj}) ? $Conf{EMailOutlookBackupSubj} : $Lang->{EMailOutlookBackupSubj}; my $mesg = defined($Conf{EMailOutlookBackupMesg}) ? $Conf{EMailOutlookBackupMesg} : $Lang->{EMailOutlookBackupMesg}; my $firstTime = sprintf("%.1f", (time - $Backups[0]{startTime}) / (24*3600)); my $lastTime = sprintf("%.1f", (time - $Backups[$#Backups]{startTime}) / (24*3600)); sendUserEmail($user, $host, $mesg, $subj, { days => $days, firstTime => $firstTime, lastTime => $lastTime, numBackups => $numBackups, userName => user2name($user), howLong => $howLong, serverHost => $Conf{ServerHost}, }) if ( !defined($Jobs{$host}) ); } } ########################################################################### # Generate sysadmin warning message ########################################################################### my $adminMesg = ""; if ( @AdminBadHosts ) { my $badHosts = join("\n - ", sort(@AdminBadHosts)); $adminMesg .= < 0 ) { my $n = $Info{DUDailySkipHostCntPrev}; my $m = $Conf{DfMaxUsagePct}; $adminMesg .= <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 .= <Dump( [\%UserEmailInfo], [qw(*UserEmailInfo)]); if ( open(HOST, ">", "$LogDir/UserEmailInfo.pl") ) { binmode(HOST); print(HOST $dumpStr); close(HOST); } } exit(0); sub user2name { my($user) = @_; my($name) = (getpwnam($user))[6]; $name =~ s/\s.*//; $name = $user if ( $name eq "" ); return $name; } sub sendUserEmail { my($user, $host, $mesg, $subj, $vars) = @_; 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} = encode('MIME-Header', $subj); $mesg =~ s/\$(\w+)/defined($vars->{$1}) ? $vars->{$1} : "\$$1"/eg; SendMail($mesg); $UserEmailInfo{$user}{lastTime} = time; $UserEmailInfo{$user}{lastSubj} = $subj; $UserEmailInfo{$user}{lastHost} = $host; } sub SendMail { my($mesg) = @_; 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); }