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.1.0beta0, released 3 Sep 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 per-host warning messages sent to each user
125 ###########################################################################
126 my $Hosts = $bpc->HostInfoRead();
127 my @AdminBadHosts = ();
129 foreach my $host ( sort(keys(%Status)) ) {
131 # read any per-PC config settings (allowing per-PC email settings)
133 $bpc->ConfigRead($host);
134 %Conf = $bpc->Conf();
135 my $user = $Hosts->{$host}{user};
138 # Accumulate host errors for the admin email below
140 if ( ($Status{$host}{reason} eq "Reason_backup_failed"
141 || $Status{$host}{reason} eq "Reason_restore_failed")
142 && $Status{$host}{error} !~ /^lost network connection to host/
143 && !$Conf{BackupsDisable}
145 push(@AdminBadHosts, "$host ($Status{$host}{error})");
148 next if ( time - $UserEmailInfo{$user}{lastTime}
149 < $Conf{EMailNotifyMinDays} * 24*3600
150 || $Conf{XferMethod} eq "archive"
151 || $Conf{BackupsDisable}
152 || $Hosts->{$host}{user} eq ""
154 my @Backups = $bpc->BackupInfoRead($host);
155 my $numBackups = @Backups;
156 if ( $numBackups == 0 ) {
157 my $subj = defined($Conf{EMailNoBackupEverSubj})
158 ? $Conf{EMailNoBackupEverSubj}
159 : $Lang->{EMailNoBackupEverSubj};
160 my $mesg = defined($Conf{EMailNoBackupEverMesg})
161 ? $Conf{EMailNoBackupEverMesg}
162 : $Lang->{EMailNoBackupEverMesg};
163 sendUserEmail($user, $host, $mesg, $subj, {
164 userName => user2name($user)
165 }) if ( !defined($Jobs{$host}) );
168 my $last = my $lastFull = my $lastIncr = 0;
169 my $lastGoodOutlook = 0;
171 my $numBadOutlook = 0;
172 for ( my $i = 0 ; $i < @Backups ; $i++ ) {
175 # ignore partials -> only fulls and incrs should be used
176 # in figuring out when the last good backup was
178 next if ( $Backups[$i]{type} eq "partial" );
179 $lastNum = $Backups[$i]{num} if ( $lastNum < $Backups[$i]{num} );
180 if ( $Backups[$i]{type} eq "full" ) {
181 $lastFull = $Backups[$i]{startTime}
182 if ( $lastFull < $Backups[$i]{startTime} );
184 $lastIncr = $Backups[$i]{startTime}
185 if ( $lastIncr < $Backups[$i]{startTime} );
187 $last = $Backups[$i]{startTime}
188 if ( $last < $Backups[$i]{startTime} );
190 my $file = "$TopDir/pc/$host/SmbLOG.$Backups[$i]{num}";
193 $file = "$TopDir/pc/$host/XferLOG.$Backups[$i]{num}";
196 $file = "$TopDir/pc/$host/SmbLOG.$Backups[$i]{num}.z";
197 $file = "$TopDir/pc/$host/XferLOG.$Backups[$i]{num}.z"
201 next if ( !defined($fh = BackupPC::FileZIO->open($file, 0, $comp)) );
203 my $s = $fh->readLine();
204 last if ( $s eq "" );
205 if ( $s =~ /^\s*Error reading file.*\.pst : (ERRDOS - ERRlock|NT_STATUS_FILE_LOCK_CONFLICT)/
206 || $s =~ /^\s*Error reading file.*\.pst\. Got 0 bytes/ ) {
212 $numBadOutlook += $badOutlook;
213 if ( !$badOutlook ) {
214 $lastGoodOutlook = $Backups[$i]{startTime}
215 if ( $lastGoodOutlook < $Backups[$i]{startTime} );
218 if ( time - $last > $Conf{EMailNotifyOldBackupDays} * 24*3600 ) {
219 my $subj = defined($Conf{EMailNoBackupRecentSubj})
220 ? $Conf{EMailNoBackupRecentSubj}
221 : $Lang->{EMailNoBackupRecentSubj};
222 my $mesg = defined($Conf{EMailNoBackupRecentMesg})
223 ? $Conf{EMailNoBackupRecentMesg}
224 : $Lang->{EMailNoBackupRecentMesg};
225 my $firstTime = sprintf("%.1f",
226 (time - $Backups[0]{startTime}) / (24*3600));
227 my $days = sprintf("%.1f", (time - $last) / (24 * 3600));
228 sendUserEmail($user, $host, $mesg, $subj, {
229 firstTime => $firstTime,
231 userName => user2name($user),
232 numBackups => $numBackups,
233 }) if ( !defined($Jobs{$host}) );
236 if ( $numBadOutlook > 0
237 && time - $lastGoodOutlook > $Conf{EMailNotifyOldOutlookDays}
240 if ( $lastGoodOutlook == 0 ) {
241 $howLong = eval("qq{$Lang->{howLong_not_been_backed_up}}");
243 $days = sprintf("%.1f", (time - $lastGoodOutlook) / (24*3600));
244 $howLong = eval("qq{$Lang->{howLong_not_been_backed_up_for_days_days}}");
246 my $subj = defined($Conf{EMailOutlookBackupSubj})
247 ? $Conf{EMailOutlookBackupSubj}
248 : $Lang->{EMailOutlookBackupSubj};
249 my $mesg = defined($Conf{EMailOutlookBackupMesg})
250 ? $Conf{EMailOutlookBackupMesg}
251 : $Lang->{EMailOutlookBackupMesg};
252 my $firstTime = sprintf("%.1f",
253 (time - $Backups[0]{startTime}) / (24*3600));
254 my $lastTime = sprintf("%.1f",
255 (time - $Backups[$#Backups]{startTime}) / (24*3600));
256 sendUserEmail($user, $host, $mesg, $subj, {
258 firstTime => $firstTime,
259 lastTime => $lastTime,
260 numBackups => $numBackups,
261 userName => user2name($user),
263 serverHost => $Conf{ServerHost},
264 }) if ( !defined($Jobs{$host}) );
268 ###########################################################################
269 # Generate sysadmin warning message
270 ###########################################################################
273 if ( @AdminBadHosts ) {
274 my $badHosts = join("\n - ", sort(@AdminBadHosts));
276 The following hosts had an error that is probably caused by a
277 misconfiguration. Please fix these hosts:
284 # Report if we skipped backups because the disk was too full
286 if ( $Info{DUDailySkipHostCntPrev} > 0 ) {
287 my $n = $Info{DUDailySkipHostCntPrev};
288 my $m = $Conf{DfMaxUsagePct};
290 Yesterday $n hosts were skipped because the file system containing
291 $TopDir was too full. The threshold in the
292 configuration file is $m%, while yesterday the file system was
293 up to $Info{DUDailyMaxPrev}% full. Please find more space on the file system,
294 or reduce the number of full or incremental backups that we keep.
300 # Check for bogus directories (probably PCs that are no longer
301 # on the backup list)
303 my $d = DirHandle->new("$TopDir/pc") or die("Can't read $TopDir/pc: $!");
305 my @files = $d->read;
307 foreach my $host ( @files ) {
308 next if ( $host =~ /^\./ || defined($Status{$host}) );
309 push(@oldDirs, "$TopDir/pc/$host");
312 my $oldDirs = join("\n - ", sort(@oldDirs));
314 The following directories are bogus and are not being used by
315 BackupPC. This typically happens when PCs are removed from the
316 backup list. If you don't need any old backups from these PCs you
317 should remove these directories. If there are machines on this
318 list that should be backed up then there is a problem with the
325 if ( $adminMesg ne "" && $Conf{EMailAdminUserName} ne "" ) {
326 my $headers = $Conf{EMailHeaders};
327 $headers .= "\n" if ( $headers !~ /\n$/ );
329 To: $Conf{EMailAdminUserName}
330 Subject: BackupPC administrative attention needed
335 SendMail($adminMesg);
338 ###########################################################################
339 # Save email state and exit
340 ###########################################################################
342 $Data::Dumper::Indent = 1;
343 my $dumpStr = Data::Dumper->Dump(
345 [qw(*UserEmailInfo)]);
346 if ( open(HOST, ">", "$LogDir/UserEmailInfo.pl") ) {
348 print(HOST $dumpStr);
357 my($name) = (getpwnam($user))[6];
359 $name = $user if ( $name eq "" );
365 my($user, $host, $mesg, $subj, $vars) = @_;
366 $vars->{user} = $user;
367 $vars->{host} = $host;
368 $vars->{headers} = $Conf{EMailHeaders};
369 $vars->{headers} .= "\n" if ( $vars->{headers} !~ /\n$/ );
370 $vars->{domain} = $Conf{EMailUserDestDomain};
371 $vars->{CgiURL} = $Conf{CgiURL};
372 $subj =~ s/\$(\w+)/defined($vars->{$1}) ? $vars->{$1} : "\$$1"/eg;
373 $vars->{subj} = $subj;
374 $mesg =~ s/\$(\w+)/defined($vars->{$1}) ? $vars->{$1} : "\$$1"/eg;
376 $UserEmailInfo{$user}{lastTime} = time;
377 $UserEmailInfo{$user}{lastSubj} = $subj;
378 $UserEmailInfo{$user}{lastHost} = $host;
384 my $from = $Conf{EMailFromUserName};
386 if ( $Conf{EMailHeaders} =~ /Content-Type:.*charset="utf-?8"/i );
390 binmode(STDOUT, ":utf8") if ( $utf8 );
391 print("#" x 75, "\n");
395 $from = "-f $from" if ( $from ne "" );
396 print("Sending test email using $Conf{SendmailPath} -t $from\n")
397 if ( $opts{u} ne "" );
398 if ( !open(MAIL, "|$Conf{SendmailPath} -t $from") ) {
399 printf("Can't run sendmail ($Conf{SendmailPath}): $!\n");
403 binmode(MAIL, ":utf8");
404 if ($mesg =~ /^Subject: (.*)$/m) {
405 my $new_subj = encode('MIME-Header', $1);
406 $mesg =~ s/^Subject: .*$/Subject: $new_subj/m;