2 #============================================================= -*-perl-*-
4 # BackupPC_sendEmail: send status emails to users and admins
8 # BackupPC_sendEmail: send status emails to users and admins.
9 # BackupPC_sendEmail is run by BackupPC_nightly, so it runs
13 # Craig Barratt <cbarratt@users.sourceforge.net>
16 # Copyright (C) 2001-2003 Craig Barratt
18 # This program is free software; you can redistribute it and/or modify
19 # it under the terms of the GNU General Public License as published by
20 # the Free Software Foundation; either version 2 of the License, or
21 # (at your option) any later version.
23 # This program is distributed in the hope that it will be useful,
24 # but WITHOUT ANY WARRANTY; without even the implied warranty of
25 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 # GNU General Public License for more details.
28 # You should have received a copy of the GNU General Public License
29 # along with this program; if not, write to the Free Software
30 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
32 #========================================================================
34 # Version 3.0.0, released 28 Jan 2007.
36 # See http://backuppc.sourceforge.net.
38 #========================================================================
42 use lib "/usr/local/BackupPC/lib";
44 use BackupPC::FileZIO;
49 use vars qw($Lang $TopDir $BinDir $LogDir %Conf);
51 die("BackupPC::Lib->new failed\n") if ( !(my $bpc = BackupPC::Lib->new) );
52 $TopDir = $bpc->TopDir();
53 $LogDir = $bpc->LogDir();
54 $BinDir = $bpc->BinDir();
60 use vars qw(%UserEmailInfo);
61 do "$LogDir/UserEmailInfo.pl";
64 if ( !getopts("ctu:", \%opts) || @ARGV != 0 ) {
66 usage: $0 [-t] [-c] [-u userEmail]
69 -t display the emails that would be sent, without sending them
71 -c check if BackupPC is alive and send an email if not
73 -u send a test email to userEmail
78 my $err = $bpc->ServerConnect($Conf{ServerHost}, $Conf{ServerPort});
80 if ( $opts{c} && $Conf{EMailAdminUserName} ne "" ) {
81 my $headers = $Conf{EMailHeaders};
82 $headers .= "\n" if ( $headers !~ /\n$/ );
84 To: $Conf{EMailAdminUserName}
85 Subject: BackupPC: can't connect to server
87 Error: cannot connect to BackupPC server.
95 print("Can't connect to server ($err)\n");
98 exit(0) if ( $opts{c} );
99 my $reply = $bpc->ServerMesg("status hosts info");
100 $reply = $1 if ( $reply =~ /(.*)/s );
101 my(%Status, %Info, %Jobs, @BgQueue, @UserQueue, @CmdQueue);
104 ###########################################################################
105 # Generate test message if required
106 ###########################################################################
107 if ( $opts{u} ne "" ) {
108 my $headers = $Conf{EMailHeaders};
109 $headers .= "\n" if ( $headers !~ /\n$/ );
112 Subject: BackupPC test email
114 This is a test message from $0.
123 ###########################################################################
124 # Generate sysadmin warning messages
125 ###########################################################################
129 foreach my $host ( sort(keys(%Status)) ) {
130 next if ( ($Status{$host}{reason} ne "Reason_backup_failed"
131 && $Status{$host}{reason} ne "Reason_restore_failed")
132 || $Status{$host}{error} =~ /^lost network connection to host/ );
133 push(@badHosts, "$host ($Status{$host}{error})");
136 my $badHosts = join("\n - ", sort(@badHosts));
138 The following hosts had an error that is probably caused by a
139 misconfiguration. Please fix these hosts:
146 # Report if we skipped backups because the disk was too full
148 if ( $Info{DUDailySkipHostCntPrev} > 0 ) {
149 my $n = $Info{DUDailySkipHostCntPrev};
150 my $m = $Conf{DfMaxUsagePct};
152 Yesterday $n hosts were skipped because the file system containing
153 $TopDir was too full. The threshold in the
154 configuration file is $m%, while yesterday the file system was
155 up to $Info{DUDailyMaxPrev}% full. Please find more space on the file system,
156 or reduce the number of full or incremental backups that we keep.
162 # Check for bogus directories (probably PCs that are no longer
163 # on the backup list)
165 my $d = DirHandle->new("$TopDir/pc") or die("Can't read $TopDir/pc: $!");
167 my @files = $d->read;
169 foreach my $host ( @files ) {
170 next if ( $host =~ /^\./ || defined($Status{$host}) );
171 push(@oldDirs, "$TopDir/pc/$host");
174 my $oldDirs = join("\n - ", sort(@oldDirs));
176 The following directories are bogus and are not being used by
177 BackupPC. This typically happens when PCs are removed from the
178 backup list. If you don't need any old backups from these PCs you
179 should remove these directories. If there are machines on this
180 list that should be backed up then there is a problem with the
187 if ( $mesg ne "" && $Conf{EMailAdminUserName} ne "" ) {
188 my $headers = $Conf{EMailHeaders};
189 $headers .= "\n" if ( $headers !~ /\n$/ );
191 To: $Conf{EMailAdminUserName}
192 Subject: BackupPC administrative attention needed
200 ###########################################################################
201 # Generate per-host warning messages sent to each user
202 ###########################################################################
203 my $Hosts = $bpc->HostInfoRead();
205 foreach my $host ( sort(keys(%Status)) ) {
206 next if ( $Hosts->{$host}{user} eq "" );
208 # read any per-PC config settings (allowing per-PC email settings)
210 $bpc->ConfigRead($host);
211 %Conf = $bpc->Conf();
212 my $user = $Hosts->{$host}{user};
213 next if ( time - $UserEmailInfo{$user}{lastTime}
214 < $Conf{EMailNotifyMinDays} * 24*3600 );
215 next if ($Conf{XferMethod} eq "archive" );
216 my @Backups = $bpc->BackupInfoRead($host);
217 my $numBackups = @Backups;
218 if ( $numBackups == 0 ) {
219 my $subj = defined($Conf{EMailNoBackupEverSubj})
220 ? $Conf{EMailNoBackupEverSubj}
221 : $Lang->{EMailNoBackupEverSubj};
222 my $mesg = defined($Conf{EMailNoBackupEverMesg})
223 ? $Conf{EMailNoBackupEverMesg}
224 : $Lang->{EMailNoBackupEverMesg};
225 sendUserEmail($user, $host, $mesg, $subj, {
226 userName => user2name($user)
227 }) if ( !defined($Jobs{$host}) );
230 my $last = my $lastFull = my $lastIncr = 0;
231 my $lastGoodOutlook = 0;
233 my $numBadOutlook = 0;
234 for ( my $i = 0 ; $i < @Backups ; $i++ ) {
237 # ignore partials -> only fulls and incrs should be used
238 # in figuring out when the last good backup was
240 next if ( $Backups[$i]{type} eq "partial" );
241 $lastNum = $Backups[$i]{num} if ( $lastNum < $Backups[$i]{num} );
242 if ( $Backups[$i]{type} eq "full" ) {
243 $lastFull = $Backups[$i]{startTime}
244 if ( $lastFull < $Backups[$i]{startTime} );
246 $lastIncr = $Backups[$i]{startTime}
247 if ( $lastIncr < $Backups[$i]{startTime} );
249 $last = $Backups[$i]{startTime}
250 if ( $last < $Backups[$i]{startTime} );
252 my $file = "$TopDir/pc/$host/SmbLOG.$Backups[$i]{num}";
255 $file = "$TopDir/pc/$host/XferLOG.$Backups[$i]{num}";
258 $file = "$TopDir/pc/$host/SmbLOG.$Backups[$i]{num}.z";
259 $file = "$TopDir/pc/$host/XferLOG.$Backups[$i]{num}.z"
263 next if ( !defined($fh = BackupPC::FileZIO->open($file, 0, $comp)) );
265 my $s = $fh->readLine();
266 last if ( $s eq "" );
267 if ( $s =~ /^\s*Error reading file.*\.pst : (ERRDOS - ERRlock|NT_STATUS_FILE_LOCK_CONFLICT)/
268 || $s =~ /^\s*Error reading file.*\.pst\. Got 0 bytes/ ) {
274 $numBadOutlook += $badOutlook;
275 if ( !$badOutlook ) {
276 $lastGoodOutlook = $Backups[$i]{startTime}
277 if ( $lastGoodOutlook < $Backups[$i]{startTime} );
280 if ( time - $last > $Conf{EMailNotifyOldBackupDays} * 24*3600 ) {
281 my $subj = defined($Conf{EMailNoBackupRecentSubj})
282 ? $Conf{EMailNoBackupRecentSubj}
283 : $Lang->{EMailNoBackupRecentSubj};
284 my $mesg = defined($Conf{EMailNoBackupRecentMesg})
285 ? $Conf{EMailNoBackupRecentMesg}
286 : $Lang->{EMailNoBackupRecentMesg};
287 my $firstTime = sprintf("%.1f",
288 (time - $Backups[0]{startTime}) / (24*3600));
289 my $days = sprintf("%.1f", (time - $last) / (24 * 3600));
290 sendUserEmail($user, $host, $mesg, $subj, {
291 firstTime => $firstTime,
293 userName => user2name($user),
294 numBackups => $numBackups,
295 }) if ( !defined($Jobs{$host}) );
298 if ( $numBadOutlook > 0
299 && time - $lastGoodOutlook > $Conf{EMailNotifyOldOutlookDays}
302 if ( $lastGoodOutlook == 0 ) {
303 $howLong = eval("qq{$Lang->{howLong_not_been_backed_up}}");
305 $days = sprintf("%.1f", (time - $lastGoodOutlook) / (24*3600));
306 $howLong = eval("qq{$Lang->{howLong_not_been_backed_up_for_days_days}}");
308 my $subj = defined($Conf{EMailOutlookBackupSubj})
309 ? $Conf{EMailOutlookBackupSubj}
310 : $Lang->{EMailOutlookBackupSubj};
311 my $mesg = defined($Conf{EMailOutlookBackupMesg})
312 ? $Conf{EMailOutlookBackupMesg}
313 : $Lang->{EMailOutlookBackupMesg};
314 my $firstTime = sprintf("%.1f",
315 (time - $Backups[0]{startTime}) / (24*3600));
316 my $lastTime = sprintf("%.1f",
317 (time - $Backups[$#Backups]{startTime}) / (24*3600));
318 sendUserEmail($user, $host, $mesg, $subj, {
320 firstTime => $firstTime,
321 lastTime => $lastTime,
322 numBackups => $numBackups,
323 userName => user2name($user),
325 serverHost => $Conf{ServerHost},
326 }) if ( !defined($Jobs{$host}) );
330 $Data::Dumper::Indent = 1;
331 my $dumpStr = Data::Dumper->Dump(
333 [qw(*UserEmailInfo)]);
334 if ( open(HOST, ">", "$LogDir/UserEmailInfo.pl") ) {
336 print(HOST $dumpStr);
344 my($name) = (getpwnam($user))[6];
346 $name = $user if ( $name eq "" );
352 my($user, $host, $mesg, $subj, $vars) = @_;
353 $vars->{user} = $user;
354 $vars->{host} = $host;
355 $vars->{headers} = $Conf{EMailHeaders};
356 $vars->{headers} .= "\n" if ( $vars->{headers} !~ /\n$/ );
357 $vars->{domain} = $Conf{EMailUserDestDomain};
358 $vars->{CgiURL} = $Conf{CgiURL};
359 $subj =~ s/\$(\w+)/defined($vars->{$1}) ? $vars->{$1} : "\$$1"/eg;
360 $vars->{subj} = $subj;
361 $mesg =~ s/\$(\w+)/defined($vars->{$1}) ? $vars->{$1} : "\$$1"/eg;
363 $UserEmailInfo{$user}{lastTime} = time;
364 $UserEmailInfo{$user}{lastSubj} = $subj;
365 $UserEmailInfo{$user}{lastHost} = $host;
371 my $from = $Conf{EMailFromUserName};
373 if ( $Conf{EMailHeaders} =~ /Content-Type:.*charset="utf-?8"/i );
377 binmode(STDOUT, ":utf8") if ( $utf8 );
378 print("#" x 75, "\n");
382 $from = "-f $from" if ( $from ne "" );
383 print("Sending test email using $Conf{SendmailPath} -t $from\n")
384 if ( $opts{u} ne "" );
385 if ( !open(MAIL, "|$Conf{SendmailPath} -t $from") ) {
386 printf("Can't run sendmail ($Conf{SendmailPath}): $!\n");
389 binmode(MAIL, ":utf8") if ( $utf8 );