1#!/usr/bin/env perl 2 3# $Id: $ 4 5######################################################################## 6# MAIA MAILGUARD LICENSE v.1.0 7# 8# Copyright 2004 by Robert LeBlanc <rjl@renaissoft.com> 9# David Morton <mortonda@dgrmm.net> 10# All rights reserved. 11# 12# PREAMBLE 13# 14# This License is designed for users of Maia Mailguard 15# ("the Software") who wish to support the Maia Mailguard project by 16# leaving "Maia Mailguard" branding information in the HTML output 17# of the pages generated by the Software, and providing links back 18# to the Maia Mailguard home page. Users who wish to remove this 19# branding information should contact the copyright owner to obtain 20# a Rebranding License. 21# 22# DEFINITION OF TERMS 23# 24# The "Software" refers to Maia Mailguard, including all of the 25# associated PHP, Perl, and SQL scripts, documentation files, graphic 26# icons and logo images. 27# 28# GRANT OF LICENSE 29# 30# Redistribution and use in source and binary forms, with or without 31# modification, are permitted provided that the following conditions 32# are met: 33# 34# 1. Redistributions of source code must retain the above copyright 35# notice, this list of conditions and the following disclaimer. 36# 37# 2. Redistributions in binary form must reproduce the above copyright 38# notice, this list of conditions and the following disclaimer in the 39# documentation and/or other materials provided with the distribution. 40# 41# 3. The end-user documentation included with the redistribution, if 42# any, must include the following acknowledgment: 43# 44# "This product includes software developed by Robert LeBlanc 45# <rjl@renaissoft.com>." 46# 47# Alternately, this acknowledgment may appear in the software itself, 48# if and wherever such third-party acknowledgments normally appear. 49# 50# 4. At least one of the following branding conventions must be used: 51# 52# a. The Maia Mailguard logo appears in the page-top banner of 53# all HTML output pages in an unmodified form, and links 54# directly to the Maia Mailguard home page; or 55# 56# b. The "Powered by Maia Mailguard" graphic appears in the HTML 57# output of all gateway pages that lead to this software, 58# linking directly to the Maia Mailguard home page; or 59# 60# c. A separate Rebranding License is obtained from the copyright 61# owner, exempting the Licensee from 4(a) and 4(b), subject to 62# the additional conditions laid out in that license document. 63# 64# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS 65# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 66# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 67# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 68# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 69# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 70# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 71# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 72# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 73# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 74# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 75######################################################################## 76 77use Getopt::Long; 78use DBI; 79use Net::SMTP; 80 81# prototypes 82sub output($); 83sub fatal($); 84sub dedupe($); 85sub read_users_file($$); 86sub send_email($$$$$); 87sub resend_mail($$$$$$); 88 89# name of this script 90my $script_name = "resend"; 91 92# read configuration file (/usr/local/etc/maia/maia.conf) 93my $config_file = "/usr/local/etc/maia/maia.conf"; 94unless (my $rv = do $config_file) { 95 fatal(sprintf("Couldn't parse %s: %s", $config_file, $@)) if $@; 96 fatal(sprintf("Couldn't open %s", $config_file)) if (!defined($rv) || !$rv); 97}; 98 99# defaults 100my @users = (); 101my $users_from_file = undef; 102my $from_date = undef; 103my $to_date = undef; 104my $from_id = undef; 105my $to_id = undef; 106my $debug = 0; 107my $help = 0; 108my $delay = 0; 109my $sysconfig_ref = {}; 110 111GetOptions("users=s" => \@users, 112 "users-from-file=s" => \$users_from_file, 113 "from-date=s" => \$from_date, 114 "to-date=s" => \$to_date, 115 "from-id=i" => \$from_id, 116 "to-id=i" => \$to_id, 117 "delay=i" => \$delay, 118 "debug" => \$debug, 119 "help" => \$help); 120 121@users = split(/,/, join(',', @users)); 122 123if ($help) { 124 output("resend.pl\n" . 125 " --users addr1,addr2,... : resend mail for selected users\n" . 126 " --users-from-file file : same as --users, but reads user list from a file\n" . 127 " --users all : same as --users, except for all users\n" . 128 " --from-date yyyy-mm-dd : oldest mail to resend (default: oldest in cache)\n" . 129 " --to-date yyyy-mm-dd : newest mail to resend (default: newest in cache)\n" . 130 " --from-id id : lowest mail ID to resend\n" . 131 " --to-id id : highest mail ID to resend\n" . 132 " --delay seconds : seconds to delay between emails (default: 0)\n" . 133 " --debug : display detailed debugging information\n" . 134 " --help : display this help text\n"); 135 exit; 136} 137 138# database configuration 139if (defined($dsn) && defined($username) && defined($password)) { 140 141 # connect to the database 142 $dbh = DBI->connect($dsn, $username, $password) 143 or fatal("Can't connect to the Maia database (verify \$dsn, \$username, and \$password in maia.conf)"); 144 145 # do a one-time load of some system configuration parameters 146 my $query = "SELECT smtp_server, smtp_port FROM maia_config WHERE id = 0"; 147 my $sth = $dbh->prepare($query) 148 or fatal(sprintf("Couldn't prepare query: %s", $dbh->errstr)); 149 $sth->execute() 150 or fatal(sprintf("Couldn't execute query: %s", $dbh->errstr)); 151 if (my @row = $sth->fetchrow_array()) { 152 $sysconfig_ref->{'smtp_server'} = $1 if $row[0] =~ /^(.+)$/si; # untaint 153 $sysconfig_ref->{'smtp_port'} = $1 if $row[1] =~ /^([1-9]+[0-9]*)$/si; # untaint 154 }; 155 $sth->finish; 156 157} else { 158 fatal("Can't connect to the Maia database (missing \$dsn, \$username, or \$password in maia.conf)"); 159} 160 161 162# Read the (optional) input files for user addresses 163read_users_file(\@users, $users_from_file) 164 if (defined($users_from_file)); 165 166# Remove any duplicates 167dedupe(\@users); 168 169# Sanity-check user-supplied arguments 170if (!@users) { 171 fatal("No users specified."); 172} 173if (defined($from_date)) { 174 if ($from_date =~ /^(\d{4})[\-\/:](\d{2})[\-\/:](\d{2})$/) { 175 my $year = $1; 176 my $month = $2; 177 my $day = $3; 178 my $yearnow = (localtime)[5] + 1900; 179 if ($year < 1900 || $year > $yearnow || $month < 1 || $month > 12 || $day < 1 || $day > 31) { 180 fatal("'From' date parameters out of range, use YYYY-MM-DD."); 181 } 182 } else { 183 fatal("Invalid 'From' date, use YYYY-MM-DD."); 184 } 185} 186if (defined($to_date)) { 187 if ($to_date =~ /^(\d{4})[\-\/:](\d{2})[\-\/:](\d{2})$/) { 188 my $year = $1; 189 my $month = $2; 190 my $day = $3; 191 my $yearnow = (localtime)[5] + 1900; 192 if ($year < 1900 || $year > $yearnow || $month < 1 || $month > 12 || $day < 1 || $day > 31) { 193 fatal("'To' date parameters out of range, use YYYY-MM-DD."); 194 } 195 } else { 196 fatal("Invalid 'To' date, use YYYY-MM-DD."); 197 } 198} 199if (defined($from_id)) { 200 if ($from_id < 1) { 201 fatal("Invalid mail ID, must be a positive integer."); 202 } 203} 204if (defined($to_id)) { 205 if ($to_id < 1) { 206 fatal("Invalid mail ID, must be a positive integer."); 207 } 208} 209 210 211# Resend cached email 212foreach my $user (@users) { 213 resend_mail($dbh, $user, $from_date, $to_date, $from_id, $to_id); 214} 215 216# Disconnect from the database 217$dbh->disconnect; 218 219exit; 220 221######################################################################## 222### End of Script: Subroutines begin below ### 223######################################################################## 224 225# Die, printing a time-stamped error message. 226sub fatal($) { 227 my ($msg) = @_; 228 229 output("FATAL ERROR: " . $msg); 230 exit 1; 231} 232 233 234# Write a time-stamped string to stdout for logging purposes. 235sub output($) { 236 my ($msg) = @_; 237 my ($second, $minute, $hour, $day, $month, $year) = (localtime)[0,1,2,3,4,5]; 238 239 printf("%04d-%02d-%02d %02d:%02d:%02d Maia: [%s] %s\n", 240 $year+1900, $month+1, $day, $hour, $minute, $second, $script_name, $msg); 241} 242 243 244# Remove any duplicate array elements 245sub dedupe($) { 246 my($array_ref) = @_; 247 248 my %seen = (); 249 foreach my $element (@$array_ref) { 250 $seen{$element}++; 251 } 252 @$array_ref = keys %seen; 253} 254 255 256# Sends an e-mail via the downstream MTA 257sub send_email($$$$$) { 258 my ($smtp_server, $smtp_port, $from, $to, $body) = @_; 259 260 my($smtp) = Net::SMTP->new($smtp_server, Port => $smtp_port) ; 261 fatal("Couldn't connect to SMTP server: $!") unless $smtp; 262 $smtp->mail($from) ; 263 $smtp->to($to) ; 264 $smtp->data() ; 265 $smtp->datasend($body . "\n") ; 266 $smtp->dataend() ; 267 $smtp->quit() ; 268} 269 270 271# Parse an input file for lines containing e-mail addresses. Blank lines 272# and whitespace should be ignored. 273sub read_users_file($$) { 274 my($addresses_ref, $infile) = @_; 275 276 open (INFILE, "< $infile") 277 or fatal("Can't read input file $infile: $!"); 278 while (<INFILE>) { 279 chomp; 280 if ($_ =~ /^\s*(.+@.+)\s*$/) { 281 push(@$addresses_ref, $1); 282 } elsif ($_ =~ /^all$/i) { 283 push(@$addresses_ref, '*'); 284 } else { 285 # skip blank lines 286 } 287 } 288 close INFILE; 289} 290 291 292sub resend_mail($$$$$$) { 293 my($dbh, $user, $from_date, $to_date, $from_id, $to_id) = @_; 294 my $user_clause = ''; 295 my $from_clause = ''; 296 my $to_clause = ''; 297 my $fromid_clause = ''; 298 my $toid_clause = ''; 299 my @params = (); 300 301 if ($user ne '*') { 302 $user_clause = " AND users.email = ?"; 303 push @params, $user; 304 output("Resending cached emails for $user") if ($debug); 305 } else { 306 output("Resending cached emails for all users") if ($debug); 307 } 308 309 if (defined($from_date)) { 310 $from_date .= ' 00:00:00'; 311 $from_clause = " AND maia_mail.received_date >= ?"; 312 push @params, $from_date; 313 output("Items received no earlier than $from_date") if ($debug); 314 } 315 316 if (defined($to_date)) { 317 $to_date .= ' 23:59:59'; 318 $to_clause = " AND maia_mail.received_date <= ?"; 319 push @params, $to_date; 320 output("Items received no later than $to_date") if ($debug); 321 } 322 323 if (defined($from_id)) { 324 $fromid_clause = " AND maia_mail.id >= ?"; 325 push @params, $from_id; 326 output("Item ID no lower than $from_id") if ($debug); 327 } 328 329 if (defined($to_id)) { 330 $toid_clause = " AND maia_mail.id <= ?"; 331 push @params, $to_id; 332 output("Item ID no higher than $to_id") if ($debug); 333 } 334 335 if ($delay && $debug) { 336 output("Pausing for $delay seconds between mailings"); 337 } 338 339 my $query = <<EOQ; 340SELECT maia_mail.id, email, sender_email, received_date, contents 341FROM maia_mail, maia_mail_recipients, maia_users, users 342WHERE maia_mail.id = maia_mail_recipients.mail_id 343AND maia_mail_recipients.recipient_id = maia_users.id 344AND maia_users.primary_email_id = users.id 345AND maia_mail_recipients.type = 'H' 346EOQ 347 348 $query .= $user_clause . $from_clause . $to_clause . $fromid_clause . $toid_clause; 349 $query .= " ORDER BY received_date ASC"; 350 351 my $sth = $dbh->prepare($query) 352 or fatal(sprintf("Couldn't prepare query: %s", $dbh->errstr)); 353 354 $sth->execute(@params) 355 or fatal(sprintf("Couldn't execute query: %s", $dbh->errstr)); 356 357 my $resent_count = 0; 358 while (my @row = $sth->fetchrow()) { 359 my $mail_id = $row[0]; 360 my $mail_recipient = $row[1]; 361 my $mail_sender = $row[2]; 362 my $mail_date = $row[3]; 363 my $mail_contents = $row[4]; 364 365 if ($debug) { 366 output("Mail ID $mail_id: Received $mail_date : From $mail_sender : To $mail_recipient"); 367 } 368 369 send_email($sysconfig_ref->{'smtp_server'}, 370 $sysconfig_ref->{'smtp_port'}, 371 $mail_sender, 372 $mail_recipient, 373 $mail_contents); 374 375 $resent_count++; 376 377 if ($delay) { 378 sleep($delay); 379 } 380 } 381 $sth->finish; 382 383 output("$resent_count emails redelivered."); 384 385} 386 387