cb5c5331e34788335f27f2a66a9f2cea2eb80d62
[BackupPC.git] / bin / BackupPC_sendEmail
1 #!/bin/perl
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, released 20 Jun 2004.
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 "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})");
89 }
90 if ( @badHosts ) {
91     my $badHosts = join("\n  - ", sort(@badHosts));
92     $mesg .= <<EOF;
93 The following hosts had an error that is probably caused by a
94 misconfiguration.  Please fix these hosts:
95   - $badHosts
96
97 EOF
98 }
99
100 #
101 # Report if we skipped backups because the disk was too full
102 #
103 if ( $Info{DUDailySkipHostCntPrev} > 0 ) {
104     my $n = $Info{DUDailySkipHostCntPrev};
105     my $m = $Conf{DfMaxUsagePct};
106     $mesg .= <<EOF;
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.
112
113 EOF
114 }
115
116 #
117 # Check for bogus directories (probably PCs that are no longer
118 # on the backup list)
119 #
120 my $d = DirHandle->new("$TopDir/pc") or die("Can't read $TopDir/pc: $!");
121 my @oldDirs = ();
122 my @files = $d->read;
123 $d->close;
124 foreach my $host ( @files ) {
125     next if ( $host =~ /^\./ || defined($Status{$host}) );
126     push(@oldDirs, "$TopDir/pc/$host");
127 }
128 if ( @oldDirs ) {
129     my $oldDirs = join("\n  - ", sort(@oldDirs));
130     $mesg .= <<EOF;
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
136 hosts file:
137   - $oldDirs
138
139 EOF
140 }
141
142 if ( $mesg ne "" && $Conf{EMailAdminUserName} ne "" ) {
143     $mesg = <<EOF;
144 To: $Conf{EMailAdminUserName}
145 Subject: BackupPC administrative attention needed
146
147 ${mesg}Regards,
148 PC Backup Genie
149 EOF
150     if ( $opts{t} ) {
151         print("#" x 75, "\n");
152         print $mesg;
153     } else {
154         SendMail($mesg);
155     }
156 }
157
158 ###########################################################################
159 # Generate per-host warning messages sent to each user
160 ###########################################################################
161 my $Hosts = $bpc->HostInfoRead();
162
163 foreach my $host ( sort(keys(%Status)) ) {
164     next if ( $Hosts->{$host}{user} eq "" );
165     #
166     # read any per-PC config settings (allowing per-PC email settings)
167     #
168     $bpc->ConfigRead($host);
169     %Conf = $bpc->Conf();
170     my $user = $Hosts->{$host}{user};
171     next if ( time - $UserEmailInfo{$user}{lastTime}
172                         < $Conf{EMailNotifyMinDays} * 24*3600 );
173     next if ($Conf{XferMethod} eq "archive" );
174     my @Backups = $bpc->BackupInfoRead($host);
175     my $numBackups = @Backups;
176     if ( $numBackups == 0 ) {
177         my $subj = defined($Conf{EMailNoBackupEverSubj})
178                         ? $Conf{EMailNoBackupEverSubj}
179                         : $Lang->{EMailNoBackupEverSubj};
180         my $mesg = defined($Conf{EMailNoBackupEverMesg})
181                         ? $Conf{EMailNoBackupEverMesg}
182                         : $Lang->{EMailNoBackupEverMesg};
183         sendUserEmail($user, $host, $mesg, $subj, {
184                             userName => user2name($user)
185                         }) if ( !defined($Jobs{$host}) );
186         next;
187     }
188     my $last = my $lastFull = my $lastIncr = 0;
189     my $lastGoodOutlook = 0;
190     my $lastNum = -1;
191     my $numBadOutlook = 0;
192     for ( my $i = 0 ; $i < @Backups ; $i++ ) {
193         my $fh;
194         $lastNum = $Backups[$i]{num} if ( $lastNum < $Backups[$i]{num} );
195         if ( $Backups[$i]{type} eq "full" ) {
196             $lastFull = $Backups[$i]{startTime}
197                     if ( $lastFull < $Backups[$i]{startTime} );
198         } else {
199             $lastIncr = $Backups[$i]{startTime}
200                     if ( $lastIncr < $Backups[$i]{startTime} );
201         }
202         $last = $Backups[$i]{startTime}
203                     if ( $last < $Backups[$i]{startTime} );
204         my $badOutlook = 0;
205         my $file = "$TopDir/pc/$host/SmbLOG.$Backups[$i]{num}";
206         my $comp = 0;
207         if ( !-f $file ) {
208             $file = "$TopDir/pc/$host/XferLOG.$Backups[$i]{num}";
209             if ( !-f $file ) {
210                 $comp = 1;
211                 $file = "$TopDir/pc/$host/SmbLOG.$Backups[$i]{num}.z";
212                 $file = "$TopDir/pc/$host/XferLOG.$Backups[$i]{num}.z"
213                                                         if ( !-f $file );
214             }
215         }
216         next if ( !defined($fh = BackupPC::FileZIO->open($file, 0, $comp)) );
217         while ( 1 ) {
218             my $s = $fh->readLine();
219             last if ( $s eq "" );
220             if ( $s =~ /^\s*Error reading file.*\.pst : (ERRDOS - ERRlock|NT_STATUS_FILE_LOCK_CONFLICT)/
221                   || $s =~ /^\s*Error reading file.*\.pst\. Got 0 bytes/ ) {
222                 $badOutlook = 1;
223                 last;
224             }
225         }
226         $fh->close();
227         $numBadOutlook += $badOutlook;
228         if ( !$badOutlook ) {
229             $lastGoodOutlook = $Backups[$i]{startTime}
230                     if ( $lastGoodOutlook < $Backups[$i]{startTime} );
231         }
232     }
233     if ( time - $last > $Conf{EMailNotifyOldBackupDays} * 24*3600 ) {
234         my $subj = defined($Conf{EMailNoBackupRecentSubj})
235                         ? $Conf{EMailNoBackupRecentSubj}
236                         : $Lang->{EMailNoBackupRecentSubj};
237         my $mesg = defined($Conf{EMailNoBackupRecentMesg})
238                         ? $Conf{EMailNoBackupRecentMesg}
239                         : $Lang->{EMailNoBackupRecentMesg};
240         my $firstTime = sprintf("%.1f",
241                         (time - $Backups[0]{startTime}) / (24*3600));
242         my $days = sprintf("%.1f", (time - $last) / (24 * 3600));
243         sendUserEmail($user, $host, $mesg, $subj, {
244                             firstTime  => $firstTime,
245                             days       => $days,
246                             userName   => user2name($user),
247                             numBackups => $numBackups,
248                         }) if ( !defined($Jobs{$host}) );
249         next;
250     }
251     if ( $numBadOutlook > 0
252           && time - $lastGoodOutlook > $Conf{EMailNotifyOldOutlookDays}
253                                              * 24 * 3600 ) {
254         my($days, $howLong);
255         if ( $lastGoodOutlook == 0 ) {
256             $howLong = eval("qq{$Lang->{howLong_not_been_backed_up}}");
257         } else {
258             $days = sprintf("%.1f", (time - $lastGoodOutlook) / (24*3600));
259             $howLong = eval("qq{$Lang->{howLong_not_been_backed_up_for_days_days}}");
260         }
261         my $subj = defined($Conf{EMailOutlookBackupSubj})
262                         ? $Conf{EMailOutlookBackupSubj}
263                         : $Lang->{EMailOutlookBackupSubj};
264         my $mesg = defined($Conf{EMailOutlookBackupMesg})
265                         ? $Conf{EMailOutlookBackupMesg}
266                         : $Lang->{EMailOutlookBackupMesg};
267         my $firstTime = sprintf("%.1f",
268                         (time - $Backups[0]{startTime}) / (24*3600));
269         my $lastTime = sprintf("%.1f",
270                         (time - $Backups[$#Backups]{startTime}) / (24*3600));
271         sendUserEmail($user, $host, $mesg, $subj, {
272                             days       => $days,
273                             firstTime  => $firstTime,
274                             lastTime   => $lastTime,
275                             numBackups => $numBackups,
276                             userName   => user2name($user),
277                             howLong    => $howLong,
278                             serverHost => $Conf{ServerHost},
279                         }) if ( !defined($Jobs{$host}) );
280     }
281 }
282 if ( !$opts{t} ) {
283     $Data::Dumper::Indent = 1;
284     my $dumpStr = Data::Dumper->Dump(
285              [\%UserEmailInfo],
286              [qw(*UserEmailInfo)]);
287     if ( open(HOST, ">", "$TopDir/log/UserEmailInfo.pl") ) {
288         binmode(HOST);
289         print(HOST $dumpStr);
290         close(HOST);
291     }
292 }
293
294 sub user2name
295 {
296     my($user) = @_;
297     my($name) = (getpwnam($user))[6];
298     $name =~ s/\s.*//;
299     $name = $user if ( $name eq "" );
300     return $name;
301 }
302
303 sub sendUserEmail
304 {
305     my($user, $host, $mesg, $subj, $vars) = @_;
306     $vars->{user}   = $user;
307     $vars->{host}   = $host;
308     $vars->{domain} = $Conf{EMailUserDestDomain};
309     $vars->{CgiURL} = $Conf{CgiURL};
310     $subj =~ s/\$(\w+)/defined($vars->{$1}) ? $vars->{$1} : "\$$1"/eg;
311     $vars->{subj}   = $subj;
312     $mesg =~ s/\$(\w+)/defined($vars->{$1}) ? $vars->{$1} : "\$$1"/eg;
313     if ( $opts{t} ) {
314         print("#" x 75, "\n");
315         print $mesg;
316     } else {
317         SendMail($mesg);
318     }
319     $UserEmailInfo{$user}{lastTime} = time;
320     $UserEmailInfo{$user}{lastSubj} = $subj;
321     $UserEmailInfo{$user}{lastHost} = $host;
322 }
323
324 sub SendMail
325 {
326     my($mesg) = @_;
327     my($from) = $Conf{EMailFromUserName};
328     local(*MAIL);
329
330     $from = "-f $from" if ( $from ne "" );
331     if ( !open(MAIL, "|$Conf{SendmailPath} -t $from") ) {
332         printf("Can't run sendmail ($Conf{SendmailPath}): $!\n");
333         return;
334     }
335     print MAIL $mesg;
336     close(MAIL);
337 }