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.0alpha, released 23 Jan 2006.
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("t", \%opts) || @ARGV != 0 ) {
65 print("usage: $0 [-t]\n");
69 my $err = $bpc->ServerConnect($Conf{ServerHost}, $Conf{ServerPort});
71 print("Can't connect to server ($err)\n");
74 my $reply = $bpc->ServerMesg("status hosts");
75 $reply = $1 if ( $reply =~ /(.*)/s );
76 my(%Status, %Info, %Jobs, @BgQueue, @UserQueue, @CmdQueue);
79 ###########################################################################
80 # Generate sysadmin warning messages
81 ###########################################################################
85 foreach my $host ( sort(keys(%Status)) ) {
86 next if ( ($Status{$host}{reason} ne "Reason_backup_failed"
87 && $Status{$host}{reason} ne "Reason_restore_failed")
88 || $Status{$host}{error} =~ /^lost network connection to host/ );
89 push(@badHosts, "$host ($Status{$host}{error})");
92 my $badHosts = join("\n - ", sort(@badHosts));
94 The following hosts had an error that is probably caused by a
95 misconfiguration. Please fix these hosts:
102 # Report if we skipped backups because the disk was too full
104 if ( $Info{DUDailySkipHostCntPrev} > 0 ) {
105 my $n = $Info{DUDailySkipHostCntPrev};
106 my $m = $Conf{DfMaxUsagePct};
108 Yesterday $n hosts were skipped because the file system containing
109 $TopDir was too full. The threshold in the
110 configuration file is $m%, while yesterday the file system was
111 up to $Info{DUDailyMaxPrev}% full. Please find more space on the file system,
112 or reduce the number of full or incremental backups that we keep.
118 # Check for bogus directories (probably PCs that are no longer
119 # on the backup list)
121 my $d = DirHandle->new("$TopDir/pc") or die("Can't read $TopDir/pc: $!");
123 my @files = $d->read;
125 foreach my $host ( @files ) {
126 next if ( $host =~ /^\./ || defined($Status{$host}) );
127 push(@oldDirs, "$TopDir/pc/$host");
130 my $oldDirs = join("\n - ", sort(@oldDirs));
132 The following directories are bogus and are not being used by
133 BackupPC. This typically happens when PCs are removed from the
134 backup list. If you don't need any old backups from these PCs you
135 should remove these directories. If there are machines on this
136 list that should be backed up then there is a problem with the
143 if ( $mesg ne "" && $Conf{EMailAdminUserName} ne "" ) {
144 my $headers = $Conf{EMailHeaders};
145 $headers .= "\n" if ( $headers !~ /\n$/ );
147 To: $Conf{EMailAdminUserName}
148 Subject: BackupPC administrative attention needed
154 print("#" x 75, "\n");
161 ###########################################################################
162 # Generate per-host warning messages sent to each user
163 ###########################################################################
164 my $Hosts = $bpc->HostInfoRead();
166 foreach my $host ( sort(keys(%Status)) ) {
167 next if ( $Hosts->{$host}{user} eq "" );
169 # read any per-PC config settings (allowing per-PC email settings)
171 $bpc->ConfigRead($host);
172 %Conf = $bpc->Conf();
173 my $user = $Hosts->{$host}{user};
174 next if ( time - $UserEmailInfo{$user}{lastTime}
175 < $Conf{EMailNotifyMinDays} * 24*3600 );
176 next if ($Conf{XferMethod} eq "archive" );
177 my @Backups = $bpc->BackupInfoRead($host);
178 my $numBackups = @Backups;
179 if ( $numBackups == 0 ) {
180 my $subj = defined($Conf{EMailNoBackupEverSubj})
181 ? $Conf{EMailNoBackupEverSubj}
182 : $Lang->{EMailNoBackupEverSubj};
183 my $mesg = defined($Conf{EMailNoBackupEverMesg})
184 ? $Conf{EMailNoBackupEverMesg}
185 : $Lang->{EMailNoBackupEverMesg};
186 sendUserEmail($user, $host, $mesg, $subj, {
187 userName => user2name($user)
188 }) if ( !defined($Jobs{$host}) );
191 my $last = my $lastFull = my $lastIncr = 0;
192 my $lastGoodOutlook = 0;
194 my $numBadOutlook = 0;
195 for ( my $i = 0 ; $i < @Backups ; $i++ ) {
197 $lastNum = $Backups[$i]{num} if ( $lastNum < $Backups[$i]{num} );
198 if ( $Backups[$i]{type} eq "full" ) {
199 $lastFull = $Backups[$i]{startTime}
200 if ( $lastFull < $Backups[$i]{startTime} );
202 $lastIncr = $Backups[$i]{startTime}
203 if ( $lastIncr < $Backups[$i]{startTime} );
205 $last = $Backups[$i]{startTime}
206 if ( $last < $Backups[$i]{startTime} );
208 my $file = "$TopDir/pc/$host/SmbLOG.$Backups[$i]{num}";
211 $file = "$TopDir/pc/$host/XferLOG.$Backups[$i]{num}";
214 $file = "$TopDir/pc/$host/SmbLOG.$Backups[$i]{num}.z";
215 $file = "$TopDir/pc/$host/XferLOG.$Backups[$i]{num}.z"
219 next if ( !defined($fh = BackupPC::FileZIO->open($file, 0, $comp)) );
221 my $s = $fh->readLine();
222 last if ( $s eq "" );
223 if ( $s =~ /^\s*Error reading file.*\.pst : (ERRDOS - ERRlock|NT_STATUS_FILE_LOCK_CONFLICT)/
224 || $s =~ /^\s*Error reading file.*\.pst\. Got 0 bytes/ ) {
230 $numBadOutlook += $badOutlook;
231 if ( !$badOutlook ) {
232 $lastGoodOutlook = $Backups[$i]{startTime}
233 if ( $lastGoodOutlook < $Backups[$i]{startTime} );
236 if ( time - $last > $Conf{EMailNotifyOldBackupDays} * 24*3600 ) {
237 my $subj = defined($Conf{EMailNoBackupRecentSubj})
238 ? $Conf{EMailNoBackupRecentSubj}
239 : $Lang->{EMailNoBackupRecentSubj};
240 my $mesg = defined($Conf{EMailNoBackupRecentMesg})
241 ? $Conf{EMailNoBackupRecentMesg}
242 : $Lang->{EMailNoBackupRecentMesg};
243 my $firstTime = sprintf("%.1f",
244 (time - $Backups[0]{startTime}) / (24*3600));
245 my $days = sprintf("%.1f", (time - $last) / (24 * 3600));
246 sendUserEmail($user, $host, $mesg, $subj, {
247 firstTime => $firstTime,
249 userName => user2name($user),
250 numBackups => $numBackups,
251 }) if ( !defined($Jobs{$host}) );
254 if ( $numBadOutlook > 0
255 && time - $lastGoodOutlook > $Conf{EMailNotifyOldOutlookDays}
258 if ( $lastGoodOutlook == 0 ) {
259 $howLong = eval("qq{$Lang->{howLong_not_been_backed_up}}");
261 $days = sprintf("%.1f", (time - $lastGoodOutlook) / (24*3600));
262 $howLong = eval("qq{$Lang->{howLong_not_been_backed_up_for_days_days}}");
264 my $subj = defined($Conf{EMailOutlookBackupSubj})
265 ? $Conf{EMailOutlookBackupSubj}
266 : $Lang->{EMailOutlookBackupSubj};
267 my $mesg = defined($Conf{EMailOutlookBackupMesg})
268 ? $Conf{EMailOutlookBackupMesg}
269 : $Lang->{EMailOutlookBackupMesg};
270 my $firstTime = sprintf("%.1f",
271 (time - $Backups[0]{startTime}) / (24*3600));
272 my $lastTime = sprintf("%.1f",
273 (time - $Backups[$#Backups]{startTime}) / (24*3600));
274 sendUserEmail($user, $host, $mesg, $subj, {
276 firstTime => $firstTime,
277 lastTime => $lastTime,
278 numBackups => $numBackups,
279 userName => user2name($user),
281 serverHost => $Conf{ServerHost},
282 }) if ( !defined($Jobs{$host}) );
286 $Data::Dumper::Indent = 1;
287 my $dumpStr = Data::Dumper->Dump(
289 [qw(*UserEmailInfo)]);
290 if ( open(HOST, ">", "$LogDir/UserEmailInfo.pl") ) {
292 print(HOST $dumpStr);
300 my($name) = (getpwnam($user))[6];
302 $name = $user if ( $name eq "" );
308 my($user, $host, $mesg, $subj, $vars) = @_;
309 $vars->{user} = $user;
310 $vars->{host} = $host;
311 $vars->{headers} = $Conf{EMailHeaders};
312 $vars->{headers} .= "\n" if ( $vars->{headers} !~ /\n$/ );
313 $vars->{domain} = $Conf{EMailUserDestDomain};
314 $vars->{CgiURL} = $Conf{CgiURL};
315 $subj =~ s/\$(\w+)/defined($vars->{$1}) ? $vars->{$1} : "\$$1"/eg;
316 $vars->{subj} = $subj;
317 $mesg =~ s/\$(\w+)/defined($vars->{$1}) ? $vars->{$1} : "\$$1"/eg;
319 print("#" x 75, "\n");
324 $UserEmailInfo{$user}{lastTime} = time;
325 $UserEmailInfo{$user}{lastSubj} = $subj;
326 $UserEmailInfo{$user}{lastHost} = $host;
332 my($from) = $Conf{EMailFromUserName};
335 $from = "-f $from" if ( $from ne "" );
336 if ( !open(MAIL, "|$Conf{SendmailPath} -t $from") ) {
337 printf("Can't run sendmail ($Conf{SendmailPath}): $!\n");