* Several improvements to restore: cancel now reports the correct
[BackupPC.git] / bin / BackupPC_sendEmail
1 #!/bin/perl -T
2 #============================================================= -*-perl-*-
3 #
4 # BackupPC_sendEmail: send status emails to users and admins
5 #
6 # DESCRIPTION
7 #
8 #   BackupPC_sendEmail: send status emails to users and admins.
9 #   BackupPC_sendEmail is run by BackupPC_nightly, so it runs
10 #   once every night.
11 #
12 # AUTHOR
13 #   Craig Barratt  <cbarratt@users.sourceforge.net>
14 #
15 # COPYRIGHT
16 #   Copyright (C) 2001  Craig Barratt
17 #
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.
22 #
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.
27 #
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
31 #
32 #========================================================================
33 #
34 # Version 2.0.0beta3, released 1 Jun 2003.
35 #
36 # See http://backuppc.sourceforge.net.
37 #
38 #========================================================================
39
40 use strict;
41 use lib "/usr/local/BackupPC/lib";
42 use BackupPC::Lib;
43 use BackupPC::FileZIO;
44
45 use Data::Dumper;
46 use Getopt::Std;
47 use DirHandle ();
48 use vars qw($Lang $TopDir $BinDir %Conf);
49
50 die("BackupPC::Lib->new failed\n") if ( !(my $bpc = BackupPC::Lib->new) );
51 $TopDir = $bpc->TopDir();
52 $BinDir = $bpc->BinDir();
53 %Conf   = $bpc->Conf();
54 $Lang   = $bpc->Lang();
55
56 $bpc->ChildInit();
57
58 use vars qw(%UserEmailInfo);
59 do "$TopDir/log/UserEmailInfo.pl";
60
61 my %opts;
62 if ( !getopts("t", \%opts) || @ARGV != 0 ) {
63     print("usage: $0 [-t]\n");
64     exit(1);
65 }
66
67 my $err = $bpc->ServerConnect($Conf{ServerHost}, $Conf{ServerPort});
68 if ( $err ) {
69     print("Can't connect to server ($err)\n");
70     exit(1);
71 }
72 my $reply = $bpc->ServerMesg("status hosts");
73 $reply = $1 if ( $reply =~ /(.*)/s );
74 my(%Status, %Info, %Jobs, @BgQueue, @UserQueue, @CmdQueue);
75 eval($reply);
76
77 ###########################################################################
78 # Generate sysadmin warning messages
79 ###########################################################################
80 my $mesg = "";
81
82 my @badHosts = ();
83 foreach my $host ( sort(keys(%Status)) ) {
84     next if ( $Status{$host}{reason} ne "backup failed"
85            || $Status{$host}{error} =~ /^lost network connection to host/ );
86     push(@badHosts, "$host ($Status{$host}{error})");
87 }
88 if ( @badHosts ) {
89     my $badHosts = join("\n  - ", sort(@badHosts));
90     $mesg .= <<EOF;
91 The following hosts had an error that is probably caused by a
92 misconfiguration.  Please fix these hosts:
93   - $badHosts
94
95 EOF
96 }
97
98 #
99 # Report if we skipped backups because the disk was too full
100 #
101 if ( $Info{DUDailySkipHostCntPrev} > 0 ) {
102     my $n = $Info{DUDailySkipHostCntPrev};
103     my $m = $Conf{DfMaxUsagePct};
104     $mesg .= <<EOF;
105 Yesterday $n hosts were skipped because the file system containing
106 $TopDir was too full.  The threshold in the
107 configuration file is $m%, while yesterday the file system was
108 up to $Info{DUDailyMaxPrev}% full.  Please find more space on the file system,
109 or reduce the number of full or incremental backups that we keep.
110
111 EOF
112 }
113
114 #
115 # Check for bogus directories (probably PCs that are no longer
116 # on the backup list)
117 #
118 my $d = DirHandle->new("$TopDir/pc") or die("Can't read $TopDir/pc: $!");
119 my @oldDirs = ();
120 my @files = $d->read;
121 $d->close;
122 foreach my $host ( @files ) {
123     next if ( $host eq "." || $host eq ".." || defined($Status{$host}) );
124     push(@oldDirs, "$TopDir/pc/$host");
125 }
126 if ( @oldDirs ) {
127     my $oldDirs = join("\n  - ", sort(@oldDirs));
128     $mesg .= <<EOF;
129 The following directories are bogus and are not being used by
130 BackupPC.  This typically happens when PCs are removed from the
131 backup list.  If you don't need any old backups from these PCs you
132 should remove these directories.  If there are machines on this
133 list that should be backed up then there is a problem with the
134 hosts file:
135   - $oldDirs
136
137 EOF
138 }
139
140 if ( $mesg ne "" && $Conf{EMailAdminUserName} ne "" ) {
141     $mesg = <<EOF;
142 To: $Conf{EMailAdminUserName}
143 Subject: BackupPC administrative attention needed
144
145 ${mesg}Regards,
146 PC Backup Genie
147 EOF
148     if ( $opts{t} ) {
149         print("#" x 75, "\n");
150         print $mesg;
151     } else {
152         SendMail($mesg);
153     }
154 }
155
156 ###########################################################################
157 # Generate per-host warning messages sent to each user
158 ###########################################################################
159 my $Hosts = $bpc->HostInfoRead();
160
161 foreach my $host ( sort(keys(%Status)) ) {
162     next if ( $Hosts->{$host}{user} eq "" );
163     #
164     # read any per-PC config settings (allowing per-PC email settings)
165     #
166     $bpc->ConfigRead($host);
167     %Conf = $bpc->Conf();
168     my $user = $Hosts->{$host}{user};
169     next if ( time - $UserEmailInfo{$user}{lastTime}
170                         < $Conf{EMailNotifyMinDays} * 24*3600 );
171     my @Backups = $bpc->BackupInfoRead($host);
172     my $numBackups = @Backups;
173     if ( $numBackups == 0 ) {
174         my $subj = defined($Conf{EMailNoBackupEverSubj})
175                         ? $Conf{EMailNoBackupEverSubj}
176                         : $Lang->{EMailNoBackupEverSubj};
177         my $mesg = defined($Conf{EMailNoBackupEverMesg})
178                         ? $Conf{EMailNoBackupEverMesg}
179                         : $Lang->{EMailNoBackupEverMesg};
180         sendUserEmail($user, $host, $mesg, $subj, {
181                             userName => user2name($user)
182                         }) if ( !defined($Jobs{$host}) );
183         next;
184     }
185     my $last = my $lastFull = my $lastIncr = 0;
186     my $lastGoodOutlook = 0;
187     my $lastNum = -1;
188     my $numBadOutlook = 0;
189     for ( my $i = 0 ; $i < @Backups ; $i++ ) {
190         my $fh;
191         $lastNum = $Backups[$i]{num} if ( $lastNum < $Backups[$i]{num} );
192         if ( $Backups[$i]{type} eq "full" ) {
193             $lastFull = $Backups[$i]{startTime}
194                     if ( $lastFull < $Backups[$i]{startTime} );
195         } else {
196             $lastIncr = $Backups[$i]{startTime}
197                     if ( $lastIncr < $Backups[$i]{startTime} );
198         }
199         $last = $Backups[$i]{startTime}
200                     if ( $last < $Backups[$i]{startTime} );
201         my $badOutlook = 0;
202         my $file = "$TopDir/pc/$host/SmbLOG.$Backups[$i]{num}";
203         my $comp = 0;
204         if ( !-f $file ) {
205             $file = "$TopDir/pc/$host/XferLOG.$Backups[$i]{num}";
206             if ( !-f $file ) {
207                 $comp = 1;
208                 $file = "$TopDir/pc/$host/SmbLOG.$Backups[$i]{num}.z";
209                 $file = "$TopDir/pc/$host/XferLOG.$Backups[$i]{num}.z"
210                                                         if ( !-f $file );
211             }
212         }
213         next if ( !defined($fh = BackupPC::FileZIO->open($file, 0, $comp)) );
214         while ( 1 ) {
215             my $s = $fh->readLine();
216             last if ( $s eq "" );
217             if ( $s =~ /^Error reading file.*\.pst : ERRDOS - ERRlock/
218                   || $s =~ /^Error reading file.*\.pst\. Got 0 bytes/ ) {
219                 $badOutlook = 1;
220                 last;
221             }
222         }
223         $fh->close();
224         $numBadOutlook += $badOutlook;
225         if ( !$badOutlook ) {
226             $lastGoodOutlook = $Backups[$i]{startTime}
227                     if ( $lastGoodOutlook < $Backups[$i]{startTime} );
228         }
229     }
230     if ( time - $last > $Conf{EMailNotifyOldBackupDays} * 24*3600 ) {
231         my $subj = defined($Conf{EMailNoBackupRecentSubj})
232                         ? $Conf{EMailNoBackupRecentSubj}
233                         : $Lang->{EMailNoBackupRecentSubj};
234         my $mesg = defined($Conf{EMailNoBackupRecentMesg})
235                         ? $Conf{EMailNoBackupRecentMesg}
236                         : $Lang->{EMailNoBackupRecentMesg};
237         my $firstTime = sprintf("%.1f",
238                         (time - $Backups[0]{startTime}) / (24*3600));
239         my $days = sprintf("%.1f", (time - $last) / (24 * 3600));
240         sendUserEmail($user, $host, $mesg, $subj, {
241                             firstTime  => $firstTime,
242                             days       => $days,
243                             userName   => user2name($user),
244                             numBackups => $numBackups,
245                         }) if ( !defined($Jobs{$host}) );
246         next;
247     }
248     if ( $numBadOutlook > 0
249           && time - $lastGoodOutlook > $Conf{EMailNotifyOldOutlookDays}
250                                              * 24 * 3600 ) {
251         my($days, $howLong);
252         if ( $lastGoodOutlook == 0 ) {
253             $howLong = eval("qq{$Lang->{howLong_not_been_backed_up}}");
254         } else {
255             $days = sprintf("%.1f", (time - $lastGoodOutlook) / (24*3600));
256             $howLong = eval("qq{$Lang->{howLong_not_been_backed_up_for_days_days}}");
257         }
258         my $subj = defined($Conf{EMailOutlookBackupSubj})
259                         ? $Conf{EMailOutlookBackupSubj}
260                         : $Lang->{EMailOutlookBackupSubj};
261         my $mesg = defined($Conf{EMailOutlookBackupMesg})
262                         ? $Conf{EMailOutlookBackupMesg}
263                         : $Lang->{EMailOutlookBackupMesg};
264         my $firstTime = sprintf("%.1f",
265                         (time - $Backups[0]{startTime}) / (24*3600));
266         my $lastTime = sprintf("%.1f",
267                         (time - $Backups[$#Backups]{startTime}) / (24*3600));
268         sendUserEmail($user, $host, $mesg, $subj, {
269                             days       => $days,
270                             firstTime  => $firstTime,
271                             lastTime   => $lastTime,
272                             numBackups => $numBackups,
273                             userName   => user2name($user),
274                             howLong    => $howLong,
275                             serverHost => $Conf{ServerHost},
276                         }) if ( !defined($Jobs{$host}) );
277     }
278 }
279 if ( !$opts{t} ) {
280     $Data::Dumper::Indent = 1;
281     my $dumpStr = Data::Dumper->Dump(
282              [\%UserEmailInfo],
283              [qw(*UserEmailInfo)]);
284     if ( open(HOST, ">", "$TopDir/log/UserEmailInfo.pl") ) {
285         print(HOST $dumpStr);
286         close(HOST);
287     }
288 }
289
290 sub user2name
291 {
292     my($user) = @_;
293     my($name) = (getpwnam($user))[6];
294     $name =~ s/\s.*//;
295     $name = $user if ( $name eq "" );
296     return $name;
297 }
298
299 sub sendUserEmail
300 {
301     my($user, $host, $mesg, $subj, $vars) = @_;
302     $vars->{user}   = $user;
303     $vars->{host}   = $host;
304     $vars->{domain} = $Conf{EMailUserDestDomain};
305     $vars->{CgiURL} = $Conf{CgiURL};
306     $subj =~ s/\$(\w+)/defined($vars->{$1}) ? $vars->{$1} : "\$$1"/eg;
307     $vars->{subj}   = $subj;
308     $mesg =~ s/\$(\w+)/defined($vars->{$1}) ? $vars->{$1} : "\$$1"/eg;
309     if ( $opts{t} ) {
310         print("#" x 75, "\n");
311         print $mesg;
312     } else {
313         SendMail($mesg);
314     }
315     $UserEmailInfo{$user}{lastTime} = time;
316     $UserEmailInfo{$user}{lastSubj} = $subj;
317     $UserEmailInfo{$user}{lastHost} = $host;
318 }
319
320 sub SendMail
321 {
322     my($mesg) = @_;
323     my($from) = $Conf{EMailFromUserName};
324     local(*MAIL);
325
326     $from = "-f $from" if ( $from ne "" );
327     if ( !open(MAIL, "|$Conf{SendmailPath} -t $from") ) {
328         printf("Can't run sendmail ($Conf{SendmailPath}): $!\n");
329         return;
330     }
331     print MAIL $mesg;
332     close(MAIL);
333 }