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-2009 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.2.0, released 31 Jul 2010.
36 # See http://backuppc.sourceforge.net.
38 #========================================================================
42 use lib "/usr/local/BackupPC/lib";
44 use BackupPC::FileZIO;
50 use vars qw($Lang $TopDir $BinDir $LogDir %Conf $Hosts);
52 die("BackupPC::Lib->new failed\n") if ( !(my $bpc = BackupPC::Lib->new) );
53 $TopDir = $bpc->TopDir();
54 $LogDir = $bpc->LogDir();
55 $BinDir = $bpc->BinDir();
58 $Hosts = $bpc->HostInfoRead();
62 use vars qw(%UserEmailInfo);
63 do "$LogDir/UserEmailInfo.pl";
66 if ( !getopts("ctu:", \%opts) || @ARGV != 0 ) {
68 usage: $0 [-t] [-c] [-u userEmail]
71 -t display the emails that would be sent, without sending them
73 -c check if BackupPC is alive and send an email if not
75 -u send a test email to userEmail
81 # Upgrade legacy version of %UserEmailInfo
83 # Prior to 3.2.0, it was a hash with entries:
85 # $UserEmailInfo{$user}{lastTime}
86 # $UserEmailInfo{$user}{lastSubj}
87 # $UserEmailInfo{$user}{lastHost}
89 # However, if a user had multiple hosts, then an email about one
90 # host prevents mail delivery about other hosts. Starting in 3.2.0
93 # $UserEmailInfo{$user}{$host}{lastTime}
94 # $UserEmailInfo{$user}{$host}{lastSubj}
97 foreach my $user ( keys(%UserEmailInfo) ) {
98 if ( defined($UserEmailInfo{$user}{lastTime})
99 && ref($UserEmailInfo{$user}{lastTime}) ne 'HASH' ) {
106 # Convert to the new format
108 my %UserEmailInfoOld = %UserEmailInfo;
110 foreach my $user ( keys(%UserEmailInfoOld) ) {
111 next if ( $user eq "" );
112 my $host = $UserEmailInfoOld{$user}{lastHost};
113 next if ( !defined($host) );
114 $UserEmailInfo{$user}{$host}{lastTime} = $UserEmailInfoOld{$user}{lastTime};
115 $UserEmailInfo{$user}{$host}{lastSubj} = $UserEmailInfoOld{$user}{lastSubj};
120 # Prune hosts that no longer exist
122 foreach my $user ( keys(%UserEmailInfo) ) {
123 foreach my $host ( keys(%{$UserEmailInfo{$user}}) ) {
124 next if ( defined($Hosts->{$host}) );
125 delete($UserEmailInfo{$user}{$host});
127 next if ( $UserEmailInfo{$user} );
128 delete($UserEmailInfo{$user});
131 my $err = $bpc->ServerConnect($Conf{ServerHost}, $Conf{ServerPort});
133 if ( $opts{c} && $Conf{EMailAdminUserName} ne "" ) {
134 my $headers = $Conf{EMailHeaders};
135 $headers .= "\n" if ( $headers !~ /\n$/ );
137 To: $Conf{EMailAdminUserName}
138 Subject: BackupPC: can't connect to server
140 Error: cannot connect to BackupPC server.
148 print("Can't connect to server ($err)\n");
151 exit(0) if ( $opts{c} );
152 my $reply = $bpc->ServerMesg("status hosts info");
153 $reply = $1 if ( $reply =~ /(.*)/s );
154 my(%Status, %Info, %Jobs, @BgQueue, @UserQueue, @CmdQueue);
157 ###########################################################################
158 # Generate test message if required
159 ###########################################################################
160 if ( $opts{u} ne "" ) {
161 my $headers = $Conf{EMailHeaders};
162 $headers .= "\n" if ( $headers !~ /\n$/ );
165 Subject: BackupPC test email
167 This is a test message from $0.
176 ###########################################################################
177 # Generate per-host warning messages sent to each user
178 ###########################################################################
179 my @AdminBadHosts = ();
181 foreach my $host ( sort(keys(%Status)) ) {
183 # read any per-PC config settings (allowing per-PC email settings)
185 $bpc->ConfigRead($host);
186 %Conf = $bpc->Conf();
187 my $user = $Hosts->{$host}{user};
189 next if ( $user eq "" );
192 # Accumulate host errors for the admin email below
194 if ( ($Status{$host}{reason} eq "Reason_backup_failed"
195 || $Status{$host}{reason} eq "Reason_restore_failed")
196 && $Status{$host}{error} !~ /^lost network connection to host/
197 && !$Conf{BackupsDisable}
199 push(@AdminBadHosts, "$host ($Status{$host}{error})");
202 next if ( time - $UserEmailInfo{$user}{$host}{lastTime}
203 < $Conf{EMailNotifyMinDays} * 24*3600
204 || $Conf{XferMethod} eq "archive"
205 || $Conf{BackupsDisable}
206 || $Hosts->{$host}{user} eq ""
208 my @Backups = $bpc->BackupInfoRead($host);
209 my $numBackups = @Backups;
210 if ( $numBackups == 0 ) {
211 my $subj = defined($Conf{EMailNoBackupEverSubj})
212 ? $Conf{EMailNoBackupEverSubj}
213 : $Lang->{EMailNoBackupEverSubj};
214 my $mesg = defined($Conf{EMailNoBackupEverMesg})
215 ? $Conf{EMailNoBackupEverMesg}
216 : $Lang->{EMailNoBackupEverMesg};
217 sendUserEmail($user, $host, $mesg, $subj, {
218 userName => user2name($user)
219 }) if ( !defined($Jobs{$host}) );
222 my $last = my $lastFull = my $lastIncr = 0;
223 my $lastGoodOutlook = 0;
225 my $numBadOutlook = 0;
226 for ( my $i = 0 ; $i < @Backups ; $i++ ) {
229 # ignore partials -> only fulls and incrs should be used
230 # in figuring out when the last good backup was
232 next if ( $Backups[$i]{type} eq "partial" );
233 $lastNum = $Backups[$i]{num} if ( $lastNum < $Backups[$i]{num} );
234 if ( $Backups[$i]{type} eq "full" ) {
235 $lastFull = $Backups[$i]{startTime}
236 if ( $lastFull < $Backups[$i]{startTime} );
238 $lastIncr = $Backups[$i]{startTime}
239 if ( $lastIncr < $Backups[$i]{startTime} );
241 $last = $Backups[$i]{startTime}
242 if ( $last < $Backups[$i]{startTime} );
244 my $file = "$TopDir/pc/$host/SmbLOG.$Backups[$i]{num}";
247 $file = "$TopDir/pc/$host/XferLOG.$Backups[$i]{num}";
250 $file = "$TopDir/pc/$host/SmbLOG.$Backups[$i]{num}.z";
251 $file = "$TopDir/pc/$host/XferLOG.$Backups[$i]{num}.z"
255 next if ( !defined($fh = BackupPC::FileZIO->open($file, 0, $comp)) );
257 my $s = $fh->readLine();
258 last if ( $s eq "" );
259 if ( $s =~ /^\s*Error reading file.*\.pst : (ERRDOS - ERRlock|NT_STATUS_FILE_LOCK_CONFLICT)/
260 || $s =~ /^\s*Error reading file.*\.pst\. Got 0 bytes/ ) {
266 $numBadOutlook += $badOutlook;
267 if ( !$badOutlook ) {
268 $lastGoodOutlook = $Backups[$i]{startTime}
269 if ( $lastGoodOutlook < $Backups[$i]{startTime} );
272 if ( time - $last > $Conf{EMailNotifyOldBackupDays} * 24*3600 ) {
273 my $subj = defined($Conf{EMailNoBackupRecentSubj})
274 ? $Conf{EMailNoBackupRecentSubj}
275 : $Lang->{EMailNoBackupRecentSubj};
276 my $mesg = defined($Conf{EMailNoBackupRecentMesg})
277 ? $Conf{EMailNoBackupRecentMesg}
278 : $Lang->{EMailNoBackupRecentMesg};
279 my $firstTime = sprintf("%.1f",
280 (time - $Backups[0]{startTime}) / (24*3600));
281 my $days = sprintf("%.1f", (time - $last) / (24 * 3600));
282 sendUserEmail($user, $host, $mesg, $subj, {
283 firstTime => $firstTime,
285 userName => user2name($user),
286 numBackups => $numBackups,
287 }) if ( !defined($Jobs{$host}) );
290 if ( $numBadOutlook > 0
291 && time - $lastGoodOutlook > $Conf{EMailNotifyOldOutlookDays}
294 if ( $lastGoodOutlook == 0 ) {
295 $howLong = eval("qq{$Lang->{howLong_not_been_backed_up}}");
297 $days = sprintf("%.1f", (time - $lastGoodOutlook) / (24*3600));
298 $howLong = eval("qq{$Lang->{howLong_not_been_backed_up_for_days_days}}");
300 my $subj = defined($Conf{EMailOutlookBackupSubj})
301 ? $Conf{EMailOutlookBackupSubj}
302 : $Lang->{EMailOutlookBackupSubj};
303 my $mesg = defined($Conf{EMailOutlookBackupMesg})
304 ? $Conf{EMailOutlookBackupMesg}
305 : $Lang->{EMailOutlookBackupMesg};
306 my $firstTime = sprintf("%.1f",
307 (time - $Backups[0]{startTime}) / (24*3600));
308 my $lastTime = sprintf("%.1f",
309 (time - $Backups[$#Backups]{startTime}) / (24*3600));
310 sendUserEmail($user, $host, $mesg, $subj, {
312 firstTime => $firstTime,
313 lastTime => $lastTime,
314 numBackups => $numBackups,
315 userName => user2name($user),
317 serverHost => $Conf{ServerHost},
318 }) if ( !defined($Jobs{$host}) );
322 ###########################################################################
323 # Generate sysadmin warning message
324 ###########################################################################
327 if ( @AdminBadHosts ) {
328 my $badHosts = join("\n - ", sort(@AdminBadHosts));
330 The following hosts had an error that is probably caused by a
331 misconfiguration. Please fix these hosts:
338 # Report if we skipped backups because the disk was too full
340 if ( $Info{DUDailySkipHostCntPrev} > 0 ) {
341 my $n = $Info{DUDailySkipHostCntPrev};
342 my $m = $Conf{DfMaxUsagePct};
344 Yesterday $n hosts were skipped because the file system containing
345 $TopDir was too full. The threshold in the
346 configuration file is $m%, while yesterday the file system was
347 up to $Info{DUDailyMaxPrev}% full. Please find more space on the file system,
348 or reduce the number of full or incremental backups that we keep.
354 # Check for bogus directories (probably PCs that are no longer
355 # on the backup list)
357 my $d = DirHandle->new("$TopDir/pc") or die("Can't read $TopDir/pc: $!");
359 my @files = $d->read;
361 foreach my $host ( @files ) {
362 next if ( $host =~ /^\./ || defined($Status{$host}) );
363 push(@oldDirs, "$TopDir/pc/$host");
366 my $oldDirs = join("\n - ", sort(@oldDirs));
368 The following directories are bogus and are not being used by
369 BackupPC. This typically happens when PCs are removed from the
370 backup list. If you don't need any old backups from these PCs you
371 should remove these directories. If there are machines on this
372 list that should be backed up then there is a problem with the
379 if ( $adminMesg ne "" && $Conf{EMailAdminUserName} ne "" ) {
380 my $headers = $Conf{EMailHeaders};
381 $headers .= "\n" if ( $headers !~ /\n$/ );
383 To: $Conf{EMailAdminUserName}
384 Subject: BackupPC administrative attention needed
389 SendMail($adminMesg);
392 ###########################################################################
393 # Save email state and exit
394 ###########################################################################
396 $Data::Dumper::Indent = 1;
397 my $dumpStr = Data::Dumper->Dump(
399 [qw(*UserEmailInfo)]);
400 if ( open(HOST, ">", "$LogDir/UserEmailInfo.pl") ) {
402 print(HOST $dumpStr);
411 my($name) = (getpwnam($user))[6];
413 $name = $user if ( $name eq "" );
419 my($user, $host, $mesg, $subj, $vars) = @_;
420 return if ( $Conf{BackupsDisable} );
422 $vars->{user} = $user;
423 $vars->{host} = $host;
424 $vars->{headers} = $Conf{EMailHeaders};
425 $vars->{headers} .= "\n" if ( $vars->{headers} !~ /\n$/ );
426 $vars->{domain} = $Conf{EMailUserDestDomain};
427 $vars->{CgiURL} = $Conf{CgiURL};
428 $subj =~ s/\$(\w+)/defined($vars->{$1}) ? $vars->{$1} : "\$$1"/eg;
429 $vars->{subj} = encode('MIME-Header', $subj);
430 $mesg =~ s/\$(\w+)/defined($vars->{$1}) ? $vars->{$1} : "\$$1"/eg;
432 $UserEmailInfo{$user}{$host}{lastTime} = time;
433 $UserEmailInfo{$user}{$host}{lastSubj} = $subj;
439 my $from = $Conf{EMailFromUserName};
441 if ( $Conf{EMailHeaders} =~ /Content-Type:.*charset="utf-?8"/i );
445 binmode(STDOUT, ":utf8") if ( $utf8 );
447 print("#" x 75, "\n");
451 $from = "-f $from" if ( $from ne "" );
452 print("Sending test email using $Conf{SendmailPath} -t $from\n")
453 if ( $opts{u} ne "" );
454 if ( !open(MAIL, "|$Conf{SendmailPath} -t $from") ) {
455 printf("Can't run sendmail ($Conf{SendmailPath}): $!\n");
458 binmode(MAIL, ":utf8") if ( $utf8 );