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 2.1.0, released 20 Jun 2004.
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 %Conf);
51 die("BackupPC::Lib->new failed\n") if ( !(my $bpc = BackupPC::Lib->new) );
52 $TopDir = $bpc->TopDir();
53 $BinDir = $bpc->BinDir();
59 use vars qw(%UserEmailInfo);
60 do "$TopDir/log/UserEmailInfo.pl";
63 if ( !getopts("t", \%opts) || @ARGV != 0 ) {
64 print("usage: $0 [-t]\n");
68 my $err = $bpc->ServerConnect($Conf{ServerHost}, $Conf{ServerPort});
70 print("Can't connect to server ($err)\n");
73 my $reply = $bpc->ServerMesg("status hosts");
74 $reply = $1 if ( $reply =~ /(.*)/s );
75 my(%Status, %Info, %Jobs, @BgQueue, @UserQueue, @CmdQueue);
78 ###########################################################################
79 # Generate sysadmin warning messages
80 ###########################################################################
84 foreach my $host ( sort(keys(%Status)) ) {
85 next if ( ($Status{$host}{reason} ne "Reason_backup_failed"
86 && $Status{$host}{reason} ne "Reason_restore_failed")
87 || $Status{$host}{error} =~ /^lost network connection to host/ );
88 push(@badHosts, "$host ($Status{$host}{error})");
91 my $badHosts = join("\n - ", sort(@badHosts));
93 The following hosts had an error that is probably caused by a
94 misconfiguration. Please fix these hosts:
101 # Report if we skipped backups because the disk was too full
103 if ( $Info{DUDailySkipHostCntPrev} > 0 ) {
104 my $n = $Info{DUDailySkipHostCntPrev};
105 my $m = $Conf{DfMaxUsagePct};
107 Yesterday $n hosts were skipped because the file system containing
108 $TopDir was too full. The threshold in the
109 configuration file is $m%, while yesterday the file system was
110 up to $Info{DUDailyMaxPrev}% full. Please find more space on the file system,
111 or reduce the number of full or incremental backups that we keep.
117 # Check for bogus directories (probably PCs that are no longer
118 # on the backup list)
120 my $d = DirHandle->new("$TopDir/pc") or die("Can't read $TopDir/pc: $!");
122 my @files = $d->read;
124 foreach my $host ( @files ) {
125 next if ( $host =~ /^\./ || defined($Status{$host}) );
126 push(@oldDirs, "$TopDir/pc/$host");
129 my $oldDirs = join("\n - ", sort(@oldDirs));
131 The following directories are bogus and are not being used by
132 BackupPC. This typically happens when PCs are removed from the
133 backup list. If you don't need any old backups from these PCs you
134 should remove these directories. If there are machines on this
135 list that should be backed up then there is a problem with the
142 if ( $mesg ne "" && $Conf{EMailAdminUserName} ne "" ) {
143 my $headers = $Conf{EMailHeaders};
144 $headers .= "\n" if ( $headers !~ /\n$/ );
146 To: $Conf{EMailAdminUserName}
147 Subject: BackupPC administrative attention needed
153 print("#" x 75, "\n");
160 ###########################################################################
161 # Generate per-host warning messages sent to each user
162 ###########################################################################
163 my $Hosts = $bpc->HostInfoRead();
165 foreach my $host ( sort(keys(%Status)) ) {
166 next if ( $Hosts->{$host}{user} eq "" );
168 # read any per-PC config settings (allowing per-PC email settings)
170 $bpc->ConfigRead($host);
171 %Conf = $bpc->Conf();
172 my $user = $Hosts->{$host}{user};
173 next if ( time - $UserEmailInfo{$user}{lastTime}
174 < $Conf{EMailNotifyMinDays} * 24*3600 );
175 next if ($Conf{XferMethod} eq "archive" );
176 my @Backups = $bpc->BackupInfoRead($host);
177 my $numBackups = @Backups;
178 if ( $numBackups == 0 ) {
179 my $subj = defined($Conf{EMailNoBackupEverSubj})
180 ? $Conf{EMailNoBackupEverSubj}
181 : $Lang->{EMailNoBackupEverSubj};
182 my $mesg = defined($Conf{EMailNoBackupEverMesg})
183 ? $Conf{EMailNoBackupEverMesg}
184 : $Lang->{EMailNoBackupEverMesg};
185 sendUserEmail($user, $host, $mesg, $subj, {
186 userName => user2name($user)
187 }) if ( !defined($Jobs{$host}) );
190 my $last = my $lastFull = my $lastIncr = 0;
191 my $lastGoodOutlook = 0;
193 my $numBadOutlook = 0;
194 for ( my $i = 0 ; $i < @Backups ; $i++ ) {
196 $lastNum = $Backups[$i]{num} if ( $lastNum < $Backups[$i]{num} );
197 if ( $Backups[$i]{type} eq "full" ) {
198 $lastFull = $Backups[$i]{startTime}
199 if ( $lastFull < $Backups[$i]{startTime} );
201 $lastIncr = $Backups[$i]{startTime}
202 if ( $lastIncr < $Backups[$i]{startTime} );
204 $last = $Backups[$i]{startTime}
205 if ( $last < $Backups[$i]{startTime} );
207 my $file = "$TopDir/pc/$host/SmbLOG.$Backups[$i]{num}";
210 $file = "$TopDir/pc/$host/XferLOG.$Backups[$i]{num}";
213 $file = "$TopDir/pc/$host/SmbLOG.$Backups[$i]{num}.z";
214 $file = "$TopDir/pc/$host/XferLOG.$Backups[$i]{num}.z"
218 next if ( !defined($fh = BackupPC::FileZIO->open($file, 0, $comp)) );
220 my $s = $fh->readLine();
221 last if ( $s eq "" );
222 if ( $s =~ /^\s*Error reading file.*\.pst : (ERRDOS - ERRlock|NT_STATUS_FILE_LOCK_CONFLICT)/
223 || $s =~ /^\s*Error reading file.*\.pst\. Got 0 bytes/ ) {
229 $numBadOutlook += $badOutlook;
230 if ( !$badOutlook ) {
231 $lastGoodOutlook = $Backups[$i]{startTime}
232 if ( $lastGoodOutlook < $Backups[$i]{startTime} );
235 if ( time - $last > $Conf{EMailNotifyOldBackupDays} * 24*3600 ) {
236 my $subj = defined($Conf{EMailNoBackupRecentSubj})
237 ? $Conf{EMailNoBackupRecentSubj}
238 : $Lang->{EMailNoBackupRecentSubj};
239 my $mesg = defined($Conf{EMailNoBackupRecentMesg})
240 ? $Conf{EMailNoBackupRecentMesg}
241 : $Lang->{EMailNoBackupRecentMesg};
242 my $firstTime = sprintf("%.1f",
243 (time - $Backups[0]{startTime}) / (24*3600));
244 my $days = sprintf("%.1f", (time - $last) / (24 * 3600));
245 sendUserEmail($user, $host, $mesg, $subj, {
246 firstTime => $firstTime,
248 userName => user2name($user),
249 numBackups => $numBackups,
250 }) if ( !defined($Jobs{$host}) );
253 if ( $numBadOutlook > 0
254 && time - $lastGoodOutlook > $Conf{EMailNotifyOldOutlookDays}
257 if ( $lastGoodOutlook == 0 ) {
258 $howLong = eval("qq{$Lang->{howLong_not_been_backed_up}}");
260 $days = sprintf("%.1f", (time - $lastGoodOutlook) / (24*3600));
261 $howLong = eval("qq{$Lang->{howLong_not_been_backed_up_for_days_days}}");
263 my $subj = defined($Conf{EMailOutlookBackupSubj})
264 ? $Conf{EMailOutlookBackupSubj}
265 : $Lang->{EMailOutlookBackupSubj};
266 my $mesg = defined($Conf{EMailOutlookBackupMesg})
267 ? $Conf{EMailOutlookBackupMesg}
268 : $Lang->{EMailOutlookBackupMesg};
269 my $firstTime = sprintf("%.1f",
270 (time - $Backups[0]{startTime}) / (24*3600));
271 my $lastTime = sprintf("%.1f",
272 (time - $Backups[$#Backups]{startTime}) / (24*3600));
273 sendUserEmail($user, $host, $mesg, $subj, {
275 firstTime => $firstTime,
276 lastTime => $lastTime,
277 numBackups => $numBackups,
278 userName => user2name($user),
280 serverHost => $Conf{ServerHost},
281 }) if ( !defined($Jobs{$host}) );
285 $Data::Dumper::Indent = 1;
286 my $dumpStr = Data::Dumper->Dump(
288 [qw(*UserEmailInfo)]);
289 if ( open(HOST, ">", "$TopDir/log/UserEmailInfo.pl") ) {
291 print(HOST $dumpStr);
299 my($name) = (getpwnam($user))[6];
301 $name = $user if ( $name eq "" );
307 my($user, $host, $mesg, $subj, $vars) = @_;
308 $vars->{user} = $user;
309 $vars->{host} = $host;
310 $vars->{headers} = $Conf{EMailHeaders};
311 $vars->{headers} .= "\n" if ( $vars->{headers} !~ /\n$/ );
312 $vars->{domain} = $Conf{EMailUserDestDomain};
313 $vars->{CgiURL} = $Conf{CgiURL};
314 $subj =~ s/\$(\w+)/defined($vars->{$1}) ? $vars->{$1} : "\$$1"/eg;
315 $vars->{subj} = $subj;
316 $mesg =~ s/\$(\w+)/defined($vars->{$1}) ? $vars->{$1} : "\$$1"/eg;
318 print("#" x 75, "\n");
323 $UserEmailInfo{$user}{lastTime} = time;
324 $UserEmailInfo{$user}{lastSubj} = $subj;
325 $UserEmailInfo{$user}{lastHost} = $host;
331 my($from) = $Conf{EMailFromUserName};
334 $from = "-f $from" if ( $from ne "" );
335 if ( !open(MAIL, "|$Conf{SendmailPath} -t $from") ) {
336 printf("Can't run sendmail ($Conf{SendmailPath}): $!\n");