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