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