* Revision update for 2.1.0beta2.
[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.0beta2, released 23 May 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 "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     next if ($Conf{XferMethod} eq "archive" );
173     my @Backups = $bpc->BackupInfoRead($host);
174     my $numBackups = @Backups;
175     if ( $numBackups == 0 ) {
176         my $subj = defined($Conf{EMailNoBackupEverSubj})
177                         ? $Conf{EMailNoBackupEverSubj}
178                         : $Lang->{EMailNoBackupEverSubj};
179         my $mesg = defined($Conf{EMailNoBackupEverMesg})
180                         ? $Conf{EMailNoBackupEverMesg}
181                         : $Lang->{EMailNoBackupEverMesg};
182         sendUserEmail($user, $host, $mesg, $subj, {
183                             userName => user2name($user)
184                         }) if ( !defined($Jobs{$host}) );
185         next;
186     }
187     my $last = my $lastFull = my $lastIncr = 0;
188     my $lastGoodOutlook = 0;
189     my $lastNum = -1;
190     my $numBadOutlook = 0;
191     for ( my $i = 0 ; $i < @Backups ; $i++ ) {
192         my $fh;
193         $lastNum = $Backups[$i]{num} if ( $lastNum < $Backups[$i]{num} );
194         if ( $Backups[$i]{type} eq "full" ) {
195             $lastFull = $Backups[$i]{startTime}
196                     if ( $lastFull < $Backups[$i]{startTime} );
197         } else {
198             $lastIncr = $Backups[$i]{startTime}
199                     if ( $lastIncr < $Backups[$i]{startTime} );
200         }
201         $last = $Backups[$i]{startTime}
202                     if ( $last < $Backups[$i]{startTime} );
203         my $badOutlook = 0;
204         my $file = "$TopDir/pc/$host/SmbLOG.$Backups[$i]{num}";
205         my $comp = 0;
206         if ( !-f $file ) {
207             $file = "$TopDir/pc/$host/XferLOG.$Backups[$i]{num}";
208             if ( !-f $file ) {
209                 $comp = 1;
210                 $file = "$TopDir/pc/$host/SmbLOG.$Backups[$i]{num}.z";
211                 $file = "$TopDir/pc/$host/XferLOG.$Backups[$i]{num}.z"
212                                                         if ( !-f $file );
213             }
214         }
215         next if ( !defined($fh = BackupPC::FileZIO->open($file, 0, $comp)) );
216         while ( 1 ) {
217             my $s = $fh->readLine();
218             last if ( $s eq "" );
219             if ( $s =~ /^\s*Error reading file.*\.pst : ERRDOS - ERRlock/
220                   || $s =~ /^\s*Error reading file.*\.pst\. Got 0 bytes/ ) {
221                 $badOutlook = 1;
222                 last;
223             }
224         }
225         $fh->close();
226         $numBadOutlook += $badOutlook;
227         if ( !$badOutlook ) {
228             $lastGoodOutlook = $Backups[$i]{startTime}
229                     if ( $lastGoodOutlook < $Backups[$i]{startTime} );
230         }
231     }
232     if ( time - $last > $Conf{EMailNotifyOldBackupDays} * 24*3600 ) {
233         my $subj = defined($Conf{EMailNoBackupRecentSubj})
234                         ? $Conf{EMailNoBackupRecentSubj}
235                         : $Lang->{EMailNoBackupRecentSubj};
236         my $mesg = defined($Conf{EMailNoBackupRecentMesg})
237                         ? $Conf{EMailNoBackupRecentMesg}
238                         : $Lang->{EMailNoBackupRecentMesg};
239         my $firstTime = sprintf("%.1f",
240                         (time - $Backups[0]{startTime}) / (24*3600));
241         my $days = sprintf("%.1f", (time - $last) / (24 * 3600));
242         sendUserEmail($user, $host, $mesg, $subj, {
243                             firstTime  => $firstTime,
244                             days       => $days,
245                             userName   => user2name($user),
246                             numBackups => $numBackups,
247                         }) if ( !defined($Jobs{$host}) );
248         next;
249     }
250     if ( $numBadOutlook > 0
251           && time - $lastGoodOutlook > $Conf{EMailNotifyOldOutlookDays}
252                                              * 24 * 3600 ) {
253         my($days, $howLong);
254         if ( $lastGoodOutlook == 0 ) {
255             $howLong = eval("qq{$Lang->{howLong_not_been_backed_up}}");
256         } else {
257             $days = sprintf("%.1f", (time - $lastGoodOutlook) / (24*3600));
258             $howLong = eval("qq{$Lang->{howLong_not_been_backed_up_for_days_days}}");
259         }
260         my $subj = defined($Conf{EMailOutlookBackupSubj})
261                         ? $Conf{EMailOutlookBackupSubj}
262                         : $Lang->{EMailOutlookBackupSubj};
263         my $mesg = defined($Conf{EMailOutlookBackupMesg})
264                         ? $Conf{EMailOutlookBackupMesg}
265                         : $Lang->{EMailOutlookBackupMesg};
266         my $firstTime = sprintf("%.1f",
267                         (time - $Backups[0]{startTime}) / (24*3600));
268         my $lastTime = sprintf("%.1f",
269                         (time - $Backups[$#Backups]{startTime}) / (24*3600));
270         sendUserEmail($user, $host, $mesg, $subj, {
271                             days       => $days,
272                             firstTime  => $firstTime,
273                             lastTime   => $lastTime,
274                             numBackups => $numBackups,
275                             userName   => user2name($user),
276                             howLong    => $howLong,
277                             serverHost => $Conf{ServerHost},
278                         }) if ( !defined($Jobs{$host}) );
279     }
280 }
281 if ( !$opts{t} ) {
282     $Data::Dumper::Indent = 1;
283     my $dumpStr = Data::Dumper->Dump(
284              [\%UserEmailInfo],
285              [qw(*UserEmailInfo)]);
286     if ( open(HOST, ">", "$TopDir/log/UserEmailInfo.pl") ) {
287         binmode(HOST);
288         print(HOST $dumpStr);
289         close(HOST);
290     }
291 }
292
293 sub user2name
294 {
295     my($user) = @_;
296     my($name) = (getpwnam($user))[6];
297     $name =~ s/\s.*//;
298     $name = $user if ( $name eq "" );
299     return $name;
300 }
301
302 sub sendUserEmail
303 {
304     my($user, $host, $mesg, $subj, $vars) = @_;
305     $vars->{user}   = $user;
306     $vars->{host}   = $host;
307     $vars->{domain} = $Conf{EMailUserDestDomain};
308     $vars->{CgiURL} = $Conf{CgiURL};
309     $subj =~ s/\$(\w+)/defined($vars->{$1}) ? $vars->{$1} : "\$$1"/eg;
310     $vars->{subj}   = $subj;
311     $mesg =~ s/\$(\w+)/defined($vars->{$1}) ? $vars->{$1} : "\$$1"/eg;
312     if ( $opts{t} ) {
313         print("#" x 75, "\n");
314         print $mesg;
315     } else {
316         SendMail($mesg);
317     }
318     $UserEmailInfo{$user}{lastTime} = time;
319     $UserEmailInfo{$user}{lastSubj} = $subj;
320     $UserEmailInfo{$user}{lastHost} = $host;
321 }
322
323 sub SendMail
324 {
325     my($mesg) = @_;
326     my($from) = $Conf{EMailFromUserName};
327     local(*MAIL);
328
329     $from = "-f $from" if ( $from ne "" );
330     if ( !open(MAIL, "|$Conf{SendmailPath} -t $from") ) {
331         printf("Can't run sendmail ($Conf{SendmailPath}): $!\n");
332         return;
333     }
334     print MAIL $mesg;
335     close(MAIL);
336 }