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-2007 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 Dec 2008.
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);
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();
61 use vars qw(%UserEmailInfo);
62 do "$LogDir/UserEmailInfo.pl";
65 if ( !getopts("ctu:", \%opts) || @ARGV != 0 ) {
67 usage: $0 [-t] [-c] [-u userEmail]
70 -t display the emails that would be sent, without sending them
72 -c check if BackupPC is alive and send an email if not
74 -u send a test email to userEmail
79 my $err = $bpc->ServerConnect($Conf{ServerHost}, $Conf{ServerPort});
81 if ( $opts{c} && $Conf{EMailAdminUserName} ne "" ) {
82 my $headers = $Conf{EMailHeaders};
83 $headers .= "\n" if ( $headers !~ /\n$/ );
85 To: $Conf{EMailAdminUserName}
86 Subject: BackupPC: can't connect to server
88 Error: cannot connect to BackupPC server.
96 print("Can't connect to server ($err)\n");
99 exit(0) if ( $opts{c} );
100 my $reply = $bpc->ServerMesg("status hosts info");
101 $reply = $1 if ( $reply =~ /(.*)/s );
102 my(%Status, %Info, %Jobs, @BgQueue, @UserQueue, @CmdQueue);
105 ###########################################################################
106 # Generate test message if required
107 ###########################################################################
108 if ( $opts{u} ne "" ) {
109 my $headers = $Conf{EMailHeaders};
110 $headers .= "\n" if ( $headers !~ /\n$/ );
113 Subject: BackupPC test email
115 This is a test message from $0.
124 ###########################################################################
125 # Generate per-host warning messages sent to each user
126 ###########################################################################
127 my $Hosts = $bpc->HostInfoRead();
128 my @AdminBadHosts = ();
130 foreach my $host ( sort(keys(%Status)) ) {
132 # read any per-PC config settings (allowing per-PC email settings)
134 $bpc->ConfigRead($host);
135 %Conf = $bpc->Conf();
136 my $user = $Hosts->{$host}{user};
139 # Accumulate host errors for the admin email below
141 if ( ($Status{$host}{reason} eq "Reason_backup_failed"
142 || $Status{$host}{reason} eq "Reason_restore_failed")
143 && $Status{$host}{error} !~ /^lost network connection to host/
144 && !$Conf{BackupsDisable}
146 push(@AdminBadHosts, "$host ($Status{$host}{error})");
149 next if ( time - $UserEmailInfo{$user}{lastTime}
150 < $Conf{EMailNotifyMinDays} * 24*3600
151 || $Conf{XferMethod} eq "archive"
152 || $Conf{BackupsDisable}
153 || $Hosts->{$host}{user} eq ""
155 my @Backups = $bpc->BackupInfoRead($host);
156 my $numBackups = @Backups;
157 if ( $numBackups == 0 ) {
158 my $subj = defined($Conf{EMailNoBackupEverSubj})
159 ? $Conf{EMailNoBackupEverSubj}
160 : $Lang->{EMailNoBackupEverSubj};
161 my $mesg = defined($Conf{EMailNoBackupEverMesg})
162 ? $Conf{EMailNoBackupEverMesg}
163 : $Lang->{EMailNoBackupEverMesg};
164 sendUserEmail($user, $host, $mesg, $subj, {
165 userName => user2name($user)
166 }) if ( !defined($Jobs{$host}) );
169 my $last = my $lastFull = my $lastIncr = 0;
170 my $lastGoodOutlook = 0;
172 my $numBadOutlook = 0;
173 for ( my $i = 0 ; $i < @Backups ; $i++ ) {
176 # ignore partials -> only fulls and incrs should be used
177 # in figuring out when the last good backup was
179 next if ( $Backups[$i]{type} eq "partial" );
180 $lastNum = $Backups[$i]{num} if ( $lastNum < $Backups[$i]{num} );
181 if ( $Backups[$i]{type} eq "full" ) {
182 $lastFull = $Backups[$i]{startTime}
183 if ( $lastFull < $Backups[$i]{startTime} );
185 $lastIncr = $Backups[$i]{startTime}
186 if ( $lastIncr < $Backups[$i]{startTime} );
188 $last = $Backups[$i]{startTime}
189 if ( $last < $Backups[$i]{startTime} );
191 my $file = "$TopDir/pc/$host/SmbLOG.$Backups[$i]{num}";
194 $file = "$TopDir/pc/$host/XferLOG.$Backups[$i]{num}";
197 $file = "$TopDir/pc/$host/SmbLOG.$Backups[$i]{num}.z";
198 $file = "$TopDir/pc/$host/XferLOG.$Backups[$i]{num}.z"
202 next if ( !defined($fh = BackupPC::FileZIO->open($file, 0, $comp)) );
204 my $s = $fh->readLine();
205 last if ( $s eq "" );
206 if ( $s =~ /^\s*Error reading file.*\.pst : (ERRDOS - ERRlock|NT_STATUS_FILE_LOCK_CONFLICT)/
207 || $s =~ /^\s*Error reading file.*\.pst\. Got 0 bytes/ ) {
213 $numBadOutlook += $badOutlook;
214 if ( !$badOutlook ) {
215 $lastGoodOutlook = $Backups[$i]{startTime}
216 if ( $lastGoodOutlook < $Backups[$i]{startTime} );
219 if ( time - $last > $Conf{EMailNotifyOldBackupDays} * 24*3600 ) {
220 my $subj = defined($Conf{EMailNoBackupRecentSubj})
221 ? $Conf{EMailNoBackupRecentSubj}
222 : $Lang->{EMailNoBackupRecentSubj};
223 my $mesg = defined($Conf{EMailNoBackupRecentMesg})
224 ? $Conf{EMailNoBackupRecentMesg}
225 : $Lang->{EMailNoBackupRecentMesg};
226 my $firstTime = sprintf("%.1f",
227 (time - $Backups[0]{startTime}) / (24*3600));
228 my $days = sprintf("%.1f", (time - $last) / (24 * 3600));
229 sendUserEmail($user, $host, $mesg, $subj, {
230 firstTime => $firstTime,
232 userName => user2name($user),
233 numBackups => $numBackups,
234 }) if ( !defined($Jobs{$host}) );
237 if ( $numBadOutlook > 0
238 && time - $lastGoodOutlook > $Conf{EMailNotifyOldOutlookDays}
241 if ( $lastGoodOutlook == 0 ) {
242 $howLong = eval("qq{$Lang->{howLong_not_been_backed_up}}");
244 $days = sprintf("%.1f", (time - $lastGoodOutlook) / (24*3600));
245 $howLong = eval("qq{$Lang->{howLong_not_been_backed_up_for_days_days}}");
247 my $subj = defined($Conf{EMailOutlookBackupSubj})
248 ? $Conf{EMailOutlookBackupSubj}
249 : $Lang->{EMailOutlookBackupSubj};
250 my $mesg = defined($Conf{EMailOutlookBackupMesg})
251 ? $Conf{EMailOutlookBackupMesg}
252 : $Lang->{EMailOutlookBackupMesg};
253 my $firstTime = sprintf("%.1f",
254 (time - $Backups[0]{startTime}) / (24*3600));
255 my $lastTime = sprintf("%.1f",
256 (time - $Backups[$#Backups]{startTime}) / (24*3600));
257 sendUserEmail($user, $host, $mesg, $subj, {
259 firstTime => $firstTime,
260 lastTime => $lastTime,
261 numBackups => $numBackups,
262 userName => user2name($user),
264 serverHost => $Conf{ServerHost},
265 }) if ( !defined($Jobs{$host}) );
269 ###########################################################################
270 # Generate sysadmin warning message
271 ###########################################################################
274 if ( @AdminBadHosts ) {
275 my $badHosts = join("\n - ", sort(@AdminBadHosts));
277 The following hosts had an error that is probably caused by a
278 misconfiguration. Please fix these hosts:
285 # Report if we skipped backups because the disk was too full
287 if ( $Info{DUDailySkipHostCntPrev} > 0 ) {
288 my $n = $Info{DUDailySkipHostCntPrev};
289 my $m = $Conf{DfMaxUsagePct};
291 Yesterday $n hosts were skipped because the file system containing
292 $TopDir was too full. The threshold in the
293 configuration file is $m%, while yesterday the file system was
294 up to $Info{DUDailyMaxPrev}% full. Please find more space on the file system,
295 or reduce the number of full or incremental backups that we keep.
301 # Check for bogus directories (probably PCs that are no longer
302 # on the backup list)
304 my $d = DirHandle->new("$TopDir/pc") or die("Can't read $TopDir/pc: $!");
306 my @files = $d->read;
308 foreach my $host ( @files ) {
309 next if ( $host =~ /^\./ || defined($Status{$host}) );
310 push(@oldDirs, "$TopDir/pc/$host");
313 my $oldDirs = join("\n - ", sort(@oldDirs));
315 The following directories are bogus and are not being used by
316 BackupPC. This typically happens when PCs are removed from the
317 backup list. If you don't need any old backups from these PCs you
318 should remove these directories. If there are machines on this
319 list that should be backed up then there is a problem with the
326 if ( $adminMesg ne "" && $Conf{EMailAdminUserName} ne "" ) {
327 my $headers = $Conf{EMailHeaders};
328 $headers .= "\n" if ( $headers !~ /\n$/ );
330 To: $Conf{EMailAdminUserName}
331 Subject: BackupPC administrative attention needed
336 SendMail($adminMesg);
339 ###########################################################################
340 # Save email state and exit
341 ###########################################################################
343 $Data::Dumper::Indent = 1;
344 my $dumpStr = Data::Dumper->Dump(
346 [qw(*UserEmailInfo)]);
347 if ( open(HOST, ">", "$LogDir/UserEmailInfo.pl") ) {
349 print(HOST $dumpStr);
358 my($name) = (getpwnam($user))[6];
360 $name = $user if ( $name eq "" );
366 my($user, $host, $mesg, $subj, $vars) = @_;
367 return if ( $Conf{BackupsDisable} );
369 $vars->{user} = $user;
370 $vars->{host} = $host;
371 $vars->{headers} = $Conf{EMailHeaders};
372 $vars->{headers} .= "\n" if ( $vars->{headers} !~ /\n$/ );
373 $vars->{domain} = $Conf{EMailUserDestDomain};
374 $vars->{CgiURL} = $Conf{CgiURL};
375 $subj =~ s/\$(\w+)/defined($vars->{$1}) ? $vars->{$1} : "\$$1"/eg;
376 $vars->{subj} = encode('MIME-Header', $subj);
377 $mesg =~ s/\$(\w+)/defined($vars->{$1}) ? $vars->{$1} : "\$$1"/eg;
379 $UserEmailInfo{$user}{lastTime} = time;
380 $UserEmailInfo{$user}{lastSubj} = $subj;
381 $UserEmailInfo{$user}{lastHost} = $host;
387 my $from = $Conf{EMailFromUserName};
389 if ( $Conf{EMailHeaders} =~ /Content-Type:.*charset="utf-?8"/i );
393 binmode(STDOUT, ":utf8") if ( $utf8 );
395 print("#" x 75, "\n");
399 $from = "-f $from" if ( $from ne "" );
400 print("Sending test email using $Conf{SendmailPath} -t $from\n")
401 if ( $opts{u} ne "" );
402 if ( !open(MAIL, "|$Conf{SendmailPath} -t $from") ) {
403 printf("Can't run sendmail ($Conf{SendmailPath}): $!\n");
406 binmode(MAIL, ":utf8") if ( $utf8 );