package DADA::Mail::Send; =pod =head1 NAME DADA::Mail::Send =head1 SYNOPSIS use DADA::Mail::Send my $mh = DADA::Mail::Send->new; Mail Routines for the Dada Mail MLM, UC? =head1 DESCRIPTION Cool name huh? You have found the heart of the beast, this is where ALL mailings will find themselves, one way or another, let's see if we can get this all straightened out so you can customize this to your heart's delight. Follow me... First off, there are TWO different ways mailings happen, either by sending one e-mail, using the send() method, or when sending to a group, using the bulk_send() method. This is somewhat of a fib, since mailings called by bulk_send() actually use the send() method to do its dirty work. Well, that is, if you're not using a SMTP feature, then both the send() and bulk_send() have their own way of doing it... kinda. DADA::Mail::Send uses the Mail::Bulkmail CPAN module, written by James A Thomason III (thomasoniii@yahoo.com) for ALL its SMTP services. And we pretty much love him for that. B That the version of Mail::Bulkmail.pm provided in this distribution B the same version as is on CPAN, the version in this distribution has 2 bug fixes, one relating to having the correct date applied to messages, the other relating to the fact that there are top level domains with more than 3 letters in 'em. You create a single address object like so: my $mh = DADA::Mail::Send->new(\%list_info); my %mailing = ( To => 'justin@example.com', From => 'alex@example.com', Subject => 'party over here!', Body => 'yo yo yo, wheres the flava at? we need some action!', ); $mh->bulk_send(%mailing); Pretty fricken hard eh? Well, it can get a bit harder than that, but thats a pretty stripped down version, If you wanted, you could theoretically use this as a do all mail sender + all the features that are in the Guts.pm module. Its pretty easy to make some crazy... stuff once you've got a handle on it. =cut use lib '.'; use lib '../'; use lib './'; use lib 'DADA'; use Fcntl qw( LOCK_SH O_RDONLY O_CREAT ); use DADA::Config; use DADA::App::Guts; use DADA::MailingList::Subscribers; use DADA::Logging::Usage; my $log = new DADA::Logging::Usage;; use strict; use vars qw($AUTOLOAD); use Carp; my %allowed = ( list_info => {}, list_type => 'list', bulk_test => 0, bulk_start_email => undef, bulk_start_num => undef, do_not_send_to => [], ignore_schedule_bulk_mailings => 0, saved_message => undef, also_send_to => [], im_bulk_sending => 0, ); =pod =head1 new my $mh = DADA::Mail::Send->new(-list => $list_info_hashref); =head1 Default Headers DADA::Mail::Send has a wide variety of both e-mail headers you can send to it. you do this through either the B or B methods =over 4 =item * From This should hold where the e-mail is from. Tricky, eh? example: From => '"Alex Skazat" ', =item * To Mail Header, This should hold where the e-mail is going to, example: To => '"Justin Simoni" ', =item * Bcc This is used as a "Blank Carbon Copy", meaning a copygoes to whoever is specified in the Bcc header, without it showing up in the To's header. B Send one 100,000 emails, some poor fella is going to get 100,000 copies example: same as the To or From =item * Return-Path Specifies what address an e-mail will get sent to when someone replies to an e-mail address. Sometimes works, sometimes doesn't, very fluky, better to set with the sendmail '-f' flag example: 'Return-Path' => 'justin@example.com', =item * Reply-To Very similar to the Return-Path, =item * Errors-To Mail Header, specifies where errors, like if the e-mail address you're trying to send isn't real, or their mailbox is full, a message I to use this address to mail the error to. example: 'Errors-To' => 'errors@example.com', =item * Precedence Mail Header, sets the so called 'precedence' of a bulk message, the valid values are list, bulk and junk. Certain e-mail clients may use this to filter out spam, or list messages, example: Precedence => 'junk', default is list. =item * Content-type sets the content type of the email message, something like text/plain or text/html, it tells the mail reader how to show the mail message. This is also where you can specify certain character sets, since not everyone uses english as their first language, like: Content-type => 'text/html; charset=us-ascii'; =item * Content-Disposition (ah hem) "Whether a MIME body part is to be shown inline or is an attachment; can also indicate a suggested filename for use when saving an attachment to a file." example: 'Content-Disposition => 'inline; filename="index.html"', =item * Content-Transfer-Encoding (ah hem) "Coding method used in a MIME message body." example: 'Content-Transfer-Encoding' => 7bit', you could also say 'base64', '8bit' or 'quote-printable' =item * MIME-Version this specifies what MIME version your using, usually this is set as 1.0, without setting a MIMIE version HTML e-mails don't come out well, example: 'MIME-Version' => '1.0', =item * List This can be a mail header, Dada Mail uses it internally to say what list is sending bulk messages, the list name is important to fetch all sorts of things, write temp files, etc, example List => 'my list', =item * Mailing List Headers Theres a whole slew of Mailing List related headers, that are pretty self-explainatory: List-Archive List-Digest List-Help List-ID List-Owner List-Post List-Subscribe List-Unsubscribe List-URL =item * References this tag is used by popular Mail readers to figure out how different messages are related. This isn't used in Dada Mail, but it may one day for its archives, this usually holds a weird numerical value, =item * In-Reply-To Again, this is used by mail readers to keep track of e-mail messages. =item * Subject The subject of your message example: Subject => 'mail server is about to explode', =item * Body This is the body message, usually I make the value before hand, and stick the variable in, like this: my $Body = < $Body, This really isn't a mail header, but this is how you get the Body of a message to Dada Mail =back B In earlier versions of this module, certain key/value pairs were passed to this script to change the way mailings were done. No more. Settings that afect mailings are either passed in the hash ref you passed to new() or, by some handy dandy methods =head1 Handy Dandy Methods These methods are used to change how Dada Mail sends mostly Bulk messages, these are the fun ones, =over 4 =item * do_not_send_to This ones kinda cool, give it a reference to an array of addresses you do not want sent the email. you could also specify your entire black list. The possibilities, kids, are endless. example: $mh->do_not_send_to(['justin@example.com', 'alex@example.com']); This array ref is then passed to MailingList::*::create_bulk_sending_file function that makes the actual list to send to. Basically create_bulk_sending_file weeds out the emails in a list we don't want to send to =item * bulk_test This is a magical paramater that, when changed to one, will only send bulk messages to the list owner. "What's the point of that?" well, it'll set up DADA::Mail::Send as I it was sending a bulk message, instread of being in "send one message mode" This makes sure all your configs are peachy keen. 1 for test, nothing for no test, =back =cut # these are all the headers Dada Mail understands. # if you don't WANT a header shown, in ANY # message, simple take it outta here. my %defaults = %EMAIL_HEADERS; my @default_headers = @EMAIL_HEADERS_ORDER; sub new { my $that = shift; my $class = ref($that) || $that; my $self = { _permitted => \%allowed, %allowed, }; bless $self, $class; my $list_info = shift; $self->{list_info} = $list_info; $self->_init; return $self; } sub AUTOLOAD { my $self = shift; my $type = ref($self) or croak "$self is not an object"; my $name = $AUTOLOAD; $name =~ s/.*://; #strip fully qualifies portion unless (exists $self -> {_permitted} -> {$name}) { croak "Can't access '$name' field in object of class $type"; } if(@_) { return $self->{$name} = shift; } else { return $self->{$name}; } } sub _init { my $self = shift; # passing the $list_info hashref is *optional* unless($self->{list_info}->{smtp_server}){ $self->{list_info}->{smtp_server} = $SMTP_ADDRESS if $SMTP_ADDRESS; } $self->{mj_log} = $log; } =pod =head2 return_headers my %headers = $mh->return_headers($string); This is a funky little subroutine that'll take a string that holds the header of a mail message, and gives you back a hash of all the headers separated, each key in the hash holds a different header, so if I say my $mh = DADA::Mail::Send -> new(); my %headers = $mh -> return_headers($header_glob); I can then say: my $to = $headers{To}; This subroutine is used quite a bit to take out put from the MIME::Lite module, which allows you to get the whole header with its header_to_string() subroutine and hack it up into something Dada Mail can use. =cut sub return_headers { my $self = shift; #get the blob my $header_blob = shift || ""; #init a new %hash my %new_header; # split.. logically my @logical_lines = split /\n(?!\s)/, $header_blob; # make the hash foreach my $line(@logical_lines) { my ($label, $value) = split(/:\s*/, $line, 2); $new_header{$label} = $value; } return %new_header; } =pod =head2 clean_headers %squeaky_clean_headers = $mh->clean_headers(%these_be_the_heaers); this method does a little munging to the mail headers for better absorbtion; basically, it changes the case of some of the mail headers so everyone's on the same page =cut sub clean_headers { my $self = shift; my %mail_headers = @_; if((exists($mail_headers{'Content-Type'})) && ($mail_headers{'Content-Type'} ne "")){ $mail_headers{'Content-type'} = $mail_headers{'Content-Type'}; delete($mail_headers{'Content-Type'}); } $mail_headers{'Content-Transfer-Encoding'} = $mail_headers{'Content-transfer-encoding'} if defined $mail_headers{'Content-transfer-encoding'}; $mail_headers{'Content-Base'} = $mail_headers{'Content-base'} if defined $mail_headers{'Content-base'}; $mail_headers{'Cc'} = $mail_headers{'CC'} if defined $mail_headers{'CC'}; foreach(keys %mail_headers){ my $tmp_h = $mail_headers{$_}; if($tmp_h){ $tmp_h =~ s/\n$//; $mail_headers{$_} = $tmp_h; } } delete($mail_headers{'X-Mailer}'}) if exists $mail_headers{'X-Mailer}'}; return %mail_headers; } =pod =head2 send This method sends an email, it takes a hash of the mail headers, plus the body of the message: $mh->send(To => 'justin@example.com', From => 'secret@admirer.com', Subject => 'smooch!', Body => 'you are so cute, you little Perl Coder you' ); =cut sub send { my $self = shift; my %fields = ( %defaults, $self->_make_general_headers, $self->_make_list_headers, @_ ); %fields = $self->clean_headers(%fields); # TODO I don't know what $tmp_body is! my $tmp_body = $defaults{Body}; $tmp_body =~ s/\[program_url\]/$PROGRAM_URL/g; # or this! $defaults{Body} = $tmp_body; unless($self->im_bulk_sending == 1){ $fields{Body} =~ s/\[list\]/$self->{list_info}->{list}/g; $fields{Body} =~ s/\[list_name\]/$self->{list_info}->{list_name}/g; } %fields = $self->_strip_fields(%fields) if ($self->{list_info}->{strip_message_headers} == 1); if($self->{list_info}->{smtp_server} ne "" and $self->{list_info}->{send_via_smtp} eq "1"){ warn "SMTP mailing is about to fail, your Perl version (Perl ". $]. ") isn't up to date, you'll need at least Perl 5.6 for SMTP sending! " if $] < 5.006; $self->_pop_before_smtp; require Mail::Bulkmail; require Mail::Bulkmail::DadaMailServer; my $server = Mail::Bulkmail::DadaMailServer->new('Smtp' => $self->{list_info}->{smtp_server}, 'Port' => $self->{list_info}->{smtp_port}, 'Tries' => $self->{list_info}->{smtp_connect_tries}, 'Domain' => $self->_domain_for_smtp, (($self->{list_info}->{use_sasl_smtp_auth} == 1) ? ( SASL_username => $self->{list_info}->{sasl_smtp_username}, SASL_password => $self->_cipher_decrypt($self->{list_info}->{sasl_smtp_password}), ) : ()), ( ($SMTP_CONVERSATION_LOG) ? ('CONVERSATION' => $SMTP_CONVERSATION_LOG,) : () ), ) || die Mail::Bulkmail::DadaMailServer->error(); my $bulk = Mail::Bulkmail->new( (($self->{list_info}->{set_smtp_sender} == 1) ? (Sender => $self->{list_info}->{admin_email},) : (Sender => $self->{list_info}->{list_owner_email},)), Subject => $fields{Subject}, Message => $fields{Body}, #From => $fields{From}, servers => [$server], ( ($SMTP_ERROR_LOG) ? (ERRFILE => $SMTP_ERROR_LOG,) : () ), ) || die Mail::Bulkmail->error(); $bulk->VERP(1) if $self->{list_info}->{verp_return_path} == 1; $bulk->Trusting(1) if $SMTP_TRUSTING == 1; $fields{'X-Mailer'} .= ' SMTP'; # nice for debuggin' if($self->{list_info}->{print_return_path_header} == 1){ if($self->{list_info}->{verp_return_path} == 1){ $fields{'Return-Path'} = $self->_verp($fields{To}); }else{ $fields{'Return-Path'} = $self->{list_info}->{admin_email}; } } $fields{'Content-type'} .= '; charset='. $self->{list_info}->{charset_value} if((defined($self->{list_info}->{charset_value})) && (defined($fields{'Content-type'}) ) && ($fields{'Content-type'} !~ /charset\=/) #ie, wasn't set before. ); foreach my $f (@default_headers){ next if $f eq 'Message'; $bulk->header($f, $fields{$f}) if((defined $fields{$f}) || ($fields{$f} ne "")); } $bulk->mail($fields{To}) || die Mail::Bulkmail->error(); $self->{mj_log}->mj_log( $self->{list_info}->{list}, 'mail_sent', "recipient:$fields{To}, subject:$fields{Subject}", ) if $LOG{mailings}; }else{ my $live_mailing_settings; if($MAIL_SETTINGS =~ /\-f/){ warn "$PROGRAM_NAME $VER, \$MAIL_SETTINGS variable already has the -f flag set ($MAIL_SETTINGS), not setting again $!"; $live_mailing_settings = $MAIL_SETTINGS; }elsif($self->{list_info}->{add_sendmail_f_flag} == 1){ if($self->{list_info}->{verp_return_path} == 1){ $live_mailing_settings = $MAIL_SETTINGS . ' -f'. $self->_verp($fields{To}); }else{ $live_mailing_settings = $MAIL_SETTINGS . ' -f'. $self->{list_info}->{admin_email}; } }else{ $live_mailing_settings = $MAIL_SETTINGS; } $live_mailing_settings = make_safer($live_mailing_settings); # pipe it to sendmail.. $MAIL_SETTINGS is set in the Config.pm file... worth a good look. warn "MAIL is already open....?" if (defined fileno *FH); open(MAIL,$live_mailing_settings) or $self->_send_die($fields{Debug}); # write the header, if its set. $fields{'Content-type'} .= '; charset='. $self->{list_info}->{charset_value} if( (defined($self->{list_info}->{charset_value})) && (defined($fields{'Content-type'})) && ($fields{'Content-type'} !~ /charset\=/) #ie, wasn't set before. ); if($self->{list_info}->{print_return_path_header} == 1){ if($self->{list_info}->{verp_return_path} == 1){ print MAIL 'Return-Path: <'. $self->_verp($fields{To}) .'>' ."\n" }else{ print MAIL 'Return-Path: <'. $self->{list_info}->{admin_email} . '>' ."\n" } } foreach my $field (@default_headers){ print MAIL "$field: $fields{$field}\n" if( (defined $fields{$field}) && ($fields{$field} ne "") ); } print MAIL "\n"; # if Mail->smart(); # little stupid joke, perhaps better: # if $self -> smart_ass(); print MAIL $fields{Body} . "\n"; close(MAIL) or warn "didn't close pipe to '$live_mailing_settings' - $!"; $self->{mj_log}->mj_log($self->{list_info}->{list}, 'Mail Sent', "recipient:$fields{To}, subject:$fields{Subject}") if $LOG{mailings}; return 1; } } =pod =head2 bulk_send Sends a message to everyone on your list, (that you specified, by passing a hash ref with the list settings.. right?) Takes the same arguments as send() returns (for now) the Message-ID of the message being sent. You can use this as the key for an archived message (let's say) =cut sub bulk_send { my $self = shift; my %fields = ( %defaults, $self->_make_general_headers, $self->_make_list_headers, @_); $self->im_bulk_sending(1); if( ($self->{list_info}->{schedule_bulk_mailings} == 1) && ($self->ignore_schedule_bulk_mailings() != 1) ){ $self->_schedule_bulk_send(\%fields); return undef; } %fields = $self->clean_headers(%fields); $defaults{Body} =~ s/\[program_url\]/$PROGRAM_URL/g; my $lh = DADA::MailingList::Subscribers->new(-List => $self->{list_info}->{list}); my ($path_to_list, $bsf_errors) = $lh->create_bulk_sending_file(-List => $self->{list_info}->{list}, -ID => $fields{'Message-ID'}, -Ban => $self->{do_not_send_to}, -Bulk_Test => $self->{bulk_test}, -Type => $self->{list_type}, -Bulk_Start_Email => $self->bulk_start_email, -Bulk_Start_Num => $self->bulk_start_num, -Sending_Lists => $self->also_send_to, ); my $num_subscribers = $lh->num_subscribers; if(keys %$bsf_errors){ if($bsf_errors->{start_email_not_found} == 1){ $self->_mail_error_no_start_email(-fields => {%fields}, -start_email => $self->bulk_start_email, ); $self->{mj_log}->mj_log( $self->{list_info}->{list}, 'mailing_list_message_sending_failed', "starting email never found!", ) if $LOG{mailings}; }elsif($bsf_errors->{start_number_never_reached} == 1){ $self->_mail_error_no_start_num(-fields => {%fields}, -start_num => $self->bulk_start_num, -email_count => $num_subscribers, ); $self->{mj_log}->mj_log( $self->{list_info}->{list}, 'mailing_list_message_sending_failed', "starting number never found!", ) if $LOG{mailings}; }else{ warn "unknown error occured when creating sending list file, argh!"; } return undef; } if( ($self->{list_info}->{smtp_server} ne "") && ($self->{list_info}->{send_via_smtp} eq "1") ){ %fields = $self->_strip_fields(%fields) if defined($self->{list_info}->{strip_message_headers}) && $self->{list_info}->{strip_message_headers} == 1; $self->bulk_send_bulk_smtp(-fields =>{%fields}, -list_file => $path_to_list); }else{ # else we send it using sendmail. # this is a bit tricky, since we have to do batching here, # as well. Its quite a bitch. lets go! # how long do we what between batches? my $seconds = 0; if(defined($self->{list_info}->{bulk_sleep_amount})){ $seconds = $self->{list_info}->{bulk_sleep_amount}; # why am I making this its own variable?! } # how many messages get sent between batches? my $letters = 1; if(defined($self->{list_info}->{bulk_send_amount})){ $letters = $self->{list_info}->{bulk_send_amount}; # why am I making this its own variable?! } # we need to create a new file that has the subscribers and their pin # number. Those two things will be separated with a '::' so we can split # it apart later. undef $lh; my $pid; FORK: { if ($pid = fork) { $self->{mj_log}->mj_log( $self->{list_info}->{list}, 'Message pid: ' . $pid, "Subject:".$fields{Subject} ) if $LOG{mass_mailings}; $self->{mj_log}->close_log if $LOG{mass_mailings}; $self->_log_sub_count(-msg_id => $fields{'Message-ID'}, -num_subscribers => $num_subscribers); # save a copy of the message for later pickup. $self->saved_message($self->_massaged_for_archive(\%fields)); return $fields{'Message-ID'}; } elsif (defined $pid) { # $pid is zero here if defined if($NULL_DEVICE){ open(STDIN, ">>$NULL_DEVICE") or warn "couldn't open '$NULL_DEVICE' - $!"; open(STDOUT, ">>$NULL_DEVICE") or warn "couldn't open '$NULL_DEVICE' - $!"; } # child here # parent process pid is available with getppid my $mailing; my $n_letters = $letters; my $n_people = 0; my $sleep_num = 0; my $send_body = $fields{Body}; my $batch_num = 1; # this is annoyingly complicated my $mail_info; my $list_pin; my $mailing_count; my $stop_email; my $mailing_amount; # let's take count of the start time my $mail_start_time = time; my ($ssec, $smin, $shour, $sday, $smonth, $syear) = (localtime($mail_start_time))[0,1,2,3,4,5]; my $log_mail_start_time = sprintf("Mailing Started: %02d/%02d/%02d %02d:%02d:%02d", $smonth+1, $sday, $syear+1900, $shour, $smin, $ssec); # ok, now lets open that list up sysopen(MAILLIST, $path_to_list, O_RDONLY|O_CREAT, $FILE_CHMOD) or die "$PROGRAM_NAME $VER Error: can't open mailing list to send a Batch Message: $!"; flock(MAILLIST, LOCK_SH) or warn "$PROGRAM_NAME $VER Warning - cannot lock sending file $!"; # while we have people on the list.. while(defined($mail_info = )){ # rid of the dreaded new line... chomp($mail_info); # get the email, and its pin... # TODO: # These variables, so far as I know, aren't used and should be removed. # After much careful testing. # This whole subroutine is sloppy Justin coding from 2001. my @ml_info = split('::', $mail_info); my $mailing = $ml_info[0]; my $email_name = $ml_info[1]; my $email_domain = $ml_info[2]; my $list_pin = $ml_info[3]; my $list_sn = $ml_info[4]; # The list short name - honestly. my $list_name = $ml_info[5]; # The list name - honestly. # keep count of how many people we have $mailing_count++; # This is new - see the note in the 2nd if statement below. $stop_email = $mailing; # if we're sending using batching... if($self->{list_info}->{enable_bulk_batching} == 1){ # this looks to see if we've filled our quota of # sending for this batch, if it gets tripped, # we'll sleep() for a few. if($n_people == $n_letters){ # q: justin - why is this in this if statement? # a: I don't know, justin, why?! # $stop_email = $mailing; # if we're sending using batching... # yeah, I know repeats.. if($self->{list_info}->{enable_bulk_batching} == 1){ my $sent_time = time; #this is entirely weird to me... my ($e_sec, $e_min, $e_hour, $e_day, $e_month, $e_year) = (localtime($sent_time))[0,1,2,3,4,5]; my $log_sent_time = sprintf("Batch Completed: %02d/%02d/%02d %02d:%02d:%02d", $e_month+1, $e_day, $e_year+1900, $e_hour, $e_min, $e_sec); # lets tell these people we are done with this batch if($self->{list_info}->{get_batch_notification} eq "1"){ if(($batch_num % $self->{list_info}->{batch_notification_every_n}) == 0){ $self->_email_batch_notification(-fields => \%fields, -batch_num => $batch_num, -start_time => $mail_start_time, -sent_time => $sent_time, -emails_sent => $n_people, -last_email => $stop_email, -num_subscribers => $num_subscribers); } } $self->{mj_log}->mj_log( $self->{list_info}->{list}, "Batch $batch_num Completed", "Subject:$fields{Subject}, " . "$log_mail_start_time, " . "$log_sent_time, " . "Last Email Sent To: $stop_email, " . "Emails Sent: " . $mailing_count . ', ' . "Sleeping: " . $self->{list_info}->{bulk_sleep_amount} . ', ', ) if $LOG{mass_mailings}; $self->{mj_log}->close_log if $LOG{mass_mailings}; } # sleep a few, wake up! sleep($seconds); # keep a count on how many batches we had. $batch_num++; # and figure out where we are in this batch. $n_letters+=$letters; $sleep_num++; } } # we've gotten to the actual mailing of the list message # zowee. my $mailing_body = $send_body; # find the email, pin $mailing_body = $self->_merge_fields(-body => $mailing_body, -merge_fields => \@ml_info, -mail_fields => \%fields, ); $fields{Body} = $mailing_body ; $fields{To} = $mailing; # Debug Information, Always nice $fields{Debug} = {-Messages_Sent => $n_people, -Last_Email => $mailing, -Message_Subject => $fields{Subject}, -List_File => $path_to_list, -List_File_Size => -s"$path_to_list"}; # send, pretty easy, eh? $self->send(%fields); # keep count $n_people++; } $mailing_amount = $mailing_count; my $mail_end_time = time; my ($dsec, $dmin, $dhour, $dday, $dmonth, $dyear) = (localtime($mail_end_time))[0,1,2,3,4,5]; my $log_mail_end_time = sprintf("Mailing Completed: %02d/%02d/%02d %02d:%02d:%02d", $dmonth+1, $dday, $dyear+1900, $dhour, $dmin, $dsec); if(( $self->{list_info}->{get_finished_notification} == 1)){ $self->_email_batched_finished_notification(-fields => \%fields, -batch_num => $batch_num, -start_time => $mail_start_time, -end_time => $mail_end_time, -emails_sent => $n_people, -last_email => $stop_email); } $self->{mj_log}->mj_log( $self->{list_info}->{list}, 'List Mailing Completed', "subject:$fields{Subject}, $log_mail_start_time, $log_mail_end_time, Mailing Amount:$mailing_amount" ) if $LOG{mass_mailings}; $self->{mj_log}->close_log if $LOG{mass_mailings}; # last thing, get rid of the temp file we had the subscriber list in. # nothing to it. if($self->{list_type} eq 'invitelist'){ my $lh = DADA::MailingList::Subscribers->new(-List => $self->{list_info}->{list}); $lh->remove_this_listtype(-Type=> 'invitelist'); } close(MAILLIST); unlink($path_to_list) or warn "$PROGRAM_NAME $VER error in DADA::Mail::Send.pm Can't remove temporary list file: '$path_to_list' $!\n"; exit(0); } elsif ($! =~ /No more process/) { # EAGAIN, supposedly recoverable fork error sleep 5; redo FORK; } else { # weird fork error die "$PROGRAM_NAME $VER Error in Mail.pm, Unable to Fork new process to mass e-mail list message: $!\n"; } } } } sub bulk_send_bulk_smtp { my $self = shift; my %args = (-fields => {}, -list_file => undef, @_); # here's the deal, if you don't have an SMTP server set, we're not even going # to TRY to send this email. NO DEFAULTS EXCEPTED. die "$PROGRAM_NAME $VER SMTP server must be explictely set!" if(!$self->{list_info}->{smtp_server}); my $mail_start_time = time; my $fields = $args{-fields}; #$self->_pop_before_smtp; warn "SMTP mailing is about to fail, your Perl version (Perl ". $]. ") isn't up to date, you'll need at least Perl 5.6 for SMTP sending! " if $] < 5.006; require Mail::Bulkmail::Dynamic; require Mail::Bulkmail::DadaMailServer; #max_messages_per_connection => $self->{list_info}->{smtp_max_messages_per_connection}, my $server = Mail::Bulkmail::DadaMailServer->new('Smtp' => $self->{list_info}->{smtp_server}, 'Port' => $self->{list_info}->{smtp_port}, 'Tries' => $self->{list_info}->{smtp_connect_tries}, 'Domain' => $self->_domain_for_smtp, 'max_connection_attempts' => $self->{list_info}->{smtp_connect_tries}, (( $self->{list_info}->{enable_bulk_batching} ) ? ( sleep_length => $self->{list_info}->{bulk_sleep_amount}, max_messages_while_awake => $self->{list_info}->{bulk_send_amount}, ) : () ), # this...doesn't work... #'max_messages_per_connection' => $self->{list_info}->{smtp_max_messages_per_connection}, (($self->{list_info}->{use_sasl_smtp_auth} == 1) ? ( SASL_username => $self->{list_info}->{sasl_smtp_username}, SASL_password => $self->_cipher_decrypt($self->{list_info}->{sasl_smtp_password}), ) : ()), dada_mail_log_mass_mailings => $LOG{mass_mailings}, dada_mail_send_obj => $self, ( ($SMTP_CONVERSATION_LOG) ? ('CONVERSATION' => $SMTP_CONVERSATION_LOG,) : () ), ) || die Mail::Bulkmail::DadaMailServer->error(); # lets do a quick convert here... # this is for mail merging and stuff $fields->{Body} =~ s/\[email\]/BULK_EMAIL/g; $fields->{Body} =~ s/\[subscriber_email\]/BULK_EMAIL/g; #$fields->{Body} =~ s/\[pin\]/PIN/g; # wonder why this was commented... $fields->{Body} =~ s/\[program_url\]/$PROGRAM_URL/g; $fields->{Body} = $self->redirect_tags(-string => $fields->{Body}, -mid => $fields->{'Message-ID'} ) if $self->{list_info}->{clickthrough_tracking} == 1; $fields->{'X-Mailer'} .= ' SMTP'; my $bulk = Mail::Bulkmail::Dynamic->new( (($self->{list_info}->{set_smtp_sender} == 1) ? (Sender => $self->{list_info}->{admin_email},) : (Sender => $self->{list_info}->{list_owner_email},)), LIST => $args{-list_file}, Subject => $fields->{Subject}, Message => $fields->{Body}, # From => $fields->{From}, servers => [$server], (($SMTP_ERROR_LOG) ? (ERRFILE => $SMTP_ERROR_LOG, ) : ( ) ), merge_keys => ['BULK_EMAIL', '\[email_name\]', '\[email_domain\]', '\[pin\]', '\[list\]', '\[list_name\]', '\[message_id\]', @{$self->_merge_fields_array_ref()}], merge_delimiter => '::', dynamic_header_delimiter => '=', dynamic_header_value_delimiter => '::', ) || die Mail::Bulkmail::Dynamic->error(); $bulk->VERP(1) if $self->{list_info}->{verp_return_path} == 1; $bulk->Trusting(1) if $SMTP_TRUSTING == 1; $fields->{'Content-type'} .= '; charset='. $self->{list_info}->{charset_value} if( (defined($self->{list_info}->{charset_value})) && (defined($fields->{'Content-type'})) && ($fields->{'Content-type'} !~ /charset\=/) #ie, wasn't set before. ); $fields->{'Return-Path'} = $self->{list_info}->{admin_email} if($self->{list_info}->{print_return_path_header} == 1); my %bulk_send_headers; foreach my $f (@default_headers){ next if $f eq 'Body'; next if !$fields->{"$f"}; next if $fields->{"$f"} eq ''; # warn "setting header '" . $f . "' value - '" . $fields->{$f} . "'"; $bulk->header("$f", $fields->{"$f"}); } if($FORK_SMTP_BULK_MAILINGS == 1){ my $pid; FORK: { if ($pid = fork) { $self->{mj_log}->mj_log( $self->{list_info}->{list}, 'Message pid: ' . $pid, "Subject:".$fields->{Subject} ) if $LOG{mass_mailings}; $self->{mj_log}->close_log if $LOG{mass_mailings}; $self->saved_message($self->_massaged_for_archive($fields)); my $lh = DADA::MailingList::Subscribers->new(-List => $self->{list_info}->{list}); $self->_log_sub_count(-msg_id => $fields->{'Message-ID'}, -num_subscribers => $lh->num_subscribers); return $fields->{'Message-ID'}; #return 1; } elsif (defined $pid) { # $pid is zero here if defined if($NULL_DEVICE){ open(STDIN, ">>$NULL_DEVICE") or warn "couldn't open '$NULL_DEVICE' - $!"; open(STDOUT, ">>$NULL_DEVICE") or warn "couldn't open '$NULL_DEVICE' - $!"; } # child $bulk->bulkmail || die $bulk->error(); if($self->{list_info}->{get_finished_notification} == 1){ # if(($self->{list_info}->{get_finished_notification} == 1) && ($self->{list_info}->{enable_bulk_batching} == 1)){ $self->_email_batched_finished_notification(-fields => $fields, -batch_num => $server->dada_mail_batch_count, #not currently used...? -start_time => $server->start_time, -end_time => $server->time_of_last_message, -emails_sent => ($server->dada_mail_batch_count * $server->max_messages_while_awake), # -last_email => $stop_email ); } $self->{mj_log}->mj_log( $self->{list_info}->{list}, 'Bulk Mail Sent', "subject:".$fields->{Subject} ) if $LOG{mass_mailings}; $self->{mj_log}->close_log if $LOG{mass_mailings}; unlink($args{-list_file}) or warn "$PROGRAM_NAME $VER error in DADA::Mail::Send.pm Can't remove temporary list file: '$args{-list_file}' $!\n"; if($self->{list_type} eq 'invitelist'){ my $lh = DADA::MailingList::Subscribers->new(-List => $self->{list_info}->{list}); $lh->remove_this_listtype(-Type=> 'invitelist'); } exit(0); }elsif($! =~ /No more process/) { # EAGAIN, supposedly recoverable fork error sleep 5; redo FORK; }else{ # weird fork error die "$PROGRAM_NAME $VER Error in Mail.pm, Unable to Fork new process to mass e-mail list message using Mail::Bulkmail::Dynamic: $!\n"; } } }else{ $bulk->bulkmail || die $bulk->error(); if($self->{list_info}->{get_finished_notification} == 1){ $self->_email_batched_finished_notification(-fields => $fields, -batch_num => $server->dada_mail_batch_count, #not currently used...? -start_time => $server->start_time, -end_time => $server->time_of_last_message, -emails_sent => ($server->dada_mail_batch_count * $server->max_messages_while_awake), # -last_email => $stop_email ); } $self->{mj_log}->mj_log( $self->{list_info}->{list}, 'Bulk Mail Sent', "subject:".$fields->{Subject} ) if $LOG{mass_mailings}; $self->{mj_log}->close_log if $LOG{mass_mailings}; unlink($args{-list_file}) or warn "$PROGRAM_NAME $VER error in Mail.pm Can't remove temporary list file: '$args{-list_file}' $!\n"; if($self->{list_type} eq 'invitelist'){ my $lh = DADA::MailingList::Subscribers->new(-List => $self->{list_info}->{list}); $lh->remove_this_listtype(-Type=> 'invitelist'); $self->saved_message($self->_massaged_for_archive($fields)); return $fields->{'Message-ID'}; } } } sub _schedule_bulk_send { my $self = shift; my $fields = shift; require DADA::MailingList::Schedules; my $mss = DADA::MailingList::Schedules->new(-List => $self->{list_info}->{list}); my $raw_message = ''; foreach(keys %$fields){ next if $_ eq 'Body'; $raw_message .= $_ . ':' . ' ' . $fields->{$_} . "\n" if $fields->{$_}; } $raw_message .= "\n\n" . $fields->{Body}; my $schedule = { message_name => $fields->{Subject}, mailing_date => time+5, last_schedule_run => time, active => 1, archive_mailings => 1, repeat_mailing => 0, self_destruct => 1, only_send_to_list_owner => $self->bulk_test, PlainText_ver => { source => 'from_text', use_email_template => 0, grab_headers_from_message => 1, text => $raw_message, } }; my $key = $mss->save_record(-mode => 'writeover', -data => $schedule ); return; } sub _domain_for_smtp { my $self = shift; my ($user, $domain) = split('@', $self->{list_info}->{list_owner_email}); return $domain; } sub _strip_fields { my $self = shift; my %fields = @_; require Mail::Address; if(my $to_temp = (Mail::Address->parse($fields{To}))[0]){ $fields{To} = $to_temp->address(); } if(my $from_temp = (Mail::Address->parse($fields{From}))[0]){ $fields{From} = $from_temp->address(); } return %fields; } =cut =pod =head2 _make_general_headers (private) return a hash containing: =over =item * From Currently makes From: with only the list owner's address - should be changed in the future =item * Reply-To Set to whatever From: is set to =item * Errors-To Off, by default, gets set to the admin_email if print_errors_to_header is set to '1' =item * Message-ID This requires Net::Domain (included with the distro - I hope this doesn't break things...) Format: # time + random number + sender, woot! 'time' is whatever format is created by DADA::App::Guts::message_id() This is done for backwards compatibility sake. Example: Message-ID: <20031227013306.20240243.user@domain.com> It's easily seen that this was sent at 12/27/2003 =back =cut sub _make_general_headers { my $self = shift; my %gh; if($self->{list_info}->{list}){ # PHRASE, ADDRESS, [ COMMENT ] require Mail::Address; #hack #Mail::Address should.. *Address* (pun) this, but it doesn't... why? why? why? # Why, it's a bug in Mail::Address, and it's not getting fixed... grr! my $ln = $self->{list_info}->{list_name}; $ln = DADA::App::Guts::escape_for_sending($ln); #/hack # NOTE: There is a module called, Email::Address that is supposed # to solve the problems of Mail::Address, althouigh more testing # has to be done. my $From_obj = Mail::Address->new($ln , $self->{list_info}->{list_owner_email}); $gh{From} = $From_obj->format; $gh{'Reply-To'} = $From_obj->format; # Deprecated. if($self->{list_info}->{print_errors_to_header} == 1){ my $Errors_To_obj = Mail::Address->new(undef, $self->{list_info}->{admin_email}); $gh{'Errors-To'} = $Errors_To_obj->format; } # time + random number + sender, woot! require DADA::Security::Password; my $ran_number = DADA::Security::Password::generate_rand_string('1234567890'); $gh{'Message-ID'} = '<' . DADA::App::Guts::message_id() . '.'. $ran_number . '@' . $From_obj->host . '>'; if($self->{list_info}->{use_habeas_headers} == 1){ require DADA::Security::Password; $gh{'X-Habeas-SWE-1'} = DADA::Security::Password::rot13('jvagre vagb fcevat'); $gh{'X-Habeas-SWE-2'} = DADA::Security::Password::rot13('oevtugyl nagvpvcngrq'); $gh{'X-Habeas-SWE-3'} = DADA::Security::Password::rot13('yvxr Unornf FJR (gz)'); $gh{'X-Habeas-SWE-4'} = DADA::Security::Password::rot13('Pbclevtug 2002 Unornf (gz)'); $gh{'X-Habeas-SWE-5'} = DADA::Security::Password::rot13('Fraqre Jneenagrq Rznvy (FJR) (gz). Gur fraqre bs guvf'); $gh{'X-Habeas-SWE-6'} = DADA::Security::Password::rot13('rznvy va rkpunatr sbe n yvprafr sbe guvf Unornf'); $gh{'X-Habeas-SWE-7'} = DADA::Security::Password::rot13('jneenag znex jneenagf gung guvf vf n Unornf Pbzcyvnag'); $gh{'X-Habeas-SWE-8'} = DADA::Security::Password::rot13('Zrffntr (UPZ) naq abg fcnz. Cyrnfr ercbeg hfr bs guvf'); $gh{'X-Habeas-SWE-9'} = DADA::Security::Password::rot13('znex va fcnz gb .'); } } return %gh; } sub _make_list_headers { my $self = shift; my %lh; if($self->{list_info}->{list}){ if($self->{list_info}->{print_list_headers} != 0){ $lh{List} = $self->{list_info}->{list}; $lh{'List-URL'} = '<' . $PROGRAM_URL . '/list/'.$self->{list_info}->{list} . '/>'; $lh{'List-Unsubscribe'} = '<' . $PROGRAM_URL . '/u/' . $self->{list_info}->{list} . '/>'; $lh{'List-Subscribe'} = '<' . $PROGRAM_URL . '/s/' . $self->{list_info}->{list} . '/>'; $lh{'List-Owner'} = '<' . $self->{list_info}->{list_owner_email}.'>'; if($self->{list_info}->{show_archives} ne "0"){ $lh{'List-Archive'} = '<' . $PROGRAM_URL.'/archive/'. $self->{list_info}->{list} . '/>'; } # http://www.faqs.org/rfcs/rfc2369.html if($self->{list_info}->{group_list} == 1 && $self->{list_info}->{discussion_pop_email}){ $lh{'List-Post'} = '{list_info}->{discussion_pop_email} . '>'; } # http://www.faqs.org/rfcs/rfc2111.html eval "require Net::Domain"; if(!$@){ my $domain = undef; $domain = Net::Domain::hostfqdn() || warn "no domain found for: Net::Domain::hostfqdn()"; $domain ||= 'localhost'; # not sure about this one, I believe if you use localhost, you need a random # as well... $lh{'List-ID'} = '<' . $self->{list_info}->{list} .'.'. $domain .'>'; }else{ warn "Net::Domain should be installed!"; } } } return %lh; } sub _cipher_decrypt { my $self = shift; my $str = shift; require DADA::Security::Password; # why wasn't this here before?! return DADA::Security::Password::cipher_decrypt($self->{list_info}->{cipher_key}, $str); } sub _pop_before_smtp { my $self = shift; require DADA::Security::Password; my %args = (-pop3_server => $self->{list_info}->{pop3_server}, -pop3_username => $self->{list_info}->{pop3_username}, -pop3_password => $self->_cipher_decrypt($self->{list_info}->{pop3_password}), @_); if(($self->{list_info}->{use_pop_before_smtp} == 1) && ($args{-pop3_server}) && ($args{-pop3_username}) && ($args{-pop3_password})){ $args{-pop3_server} = make_safer($args{-pop3_server}); $args{-pop3_username} = make_safer($args{-pop3_username}); $args{-pop3_password} = make_safer($args{-pop3_password}); my $messagecount = undef; my $pop = undef; return undef if ! $args{-pop3_server}; return undef if ! $args{-pop3_username}; return undef if ! $args{-pop3_password}; eval {require Net::POP3}; if(!$@){ $pop = Net::POP3->new($args{-pop3_server}); if($pop){ eval {require Digest::MD5}; if(!$@){ $messagecount = $pop->apop($args{-pop3_username},$args{-pop3_password}); if(!$messagecount){ $pop = Net::POP3->new($args{-pop3_server}); $messagecount = $pop->login($args{-pop3_username},$args{-pop3_password}); } }else{ $messagecount = $pop->login($args{-pop3_username},$args{-pop3_password}); } return $messagecount; }else{ return undef; } }else{ warn("Cannot find Net::POP3, is it installed? - $!"); return undef; } } } sub _email_batch_notification { my $self = shift; my %args = (-fields => {}, -batch_num => undef, -start_time => undef, -sent_time => undef, -emails_sent => undef, -last_email => undef, -num_subscribers => undef, @_); my $formatted_start_time; my $formatted_sent_time; my $raw_eta; my $eta = undef; # this doesn't work well. if( ($args{-num_subscribers} > 0) && ($args{-emails_sent} > 0) && ( ($args{-sent_time} - $args{-start_time}) > 0) ){ $raw_eta = ((($args{-num_subscribers} * ($args{-sent_time} - $args{-start_time})) / $args{-emails_sent})) - ($args{-sent_time} - $args{-start_time}); $eta = $self->_formatted_runtime($raw_eta); } if($args{-start_time}){ my ($s_sec, $s_min, $s_hour, $s_day, $s_month, $s_year) = (localtime($args{-start_time}))[0,1,2,3,4,5]; $formatted_start_time = sprintf("%02d/%02d/%02d %02d:%02d:%02d", $s_month+1, $s_day, $s_year+1900, $s_hour, $s_min, $s_sec); } if($args{-sent_time}){ my ($e_sec, $e_min, $e_hour, $e_day, $e_month, $e_year) = (localtime($args{-sent_time}))[0,1,2,3,4,5]; $formatted_sent_time = sprintf("%02d/%02d/%02d %02d:%02d:%02d", $e_month+1, $e_day, $e_year+1900, $e_hour, $e_min, $e_sec); } my $fields = $args{-fields}; my %status_mailing = $self->_make_general_headers; $status_mailing{To} = $self->{list_info}->{list_owner_email}; $status_mailing{Subject} = $self->{list_info}->{list_name} . " Batch \#$args{-batch_num} Complete. - " . $fields->{Subject}; my $status_body; $status_body = "\nMailing Summary:\n"; $status_body .= '_' x 72; $status_body .= "\n\nBatch number: $args{-batch_num} has been completed!\n" if $args{-batch_num}; $status_body .= "Your list mailing has reached: $args{-emails_sent} e-mail address(es)\n\n" if $args{-emails_sent}; $status_body .= 'Mailing Started: ' . $formatted_start_time . "\n" if $args{-start_time}; $status_body .= 'Batch Completed: ' . $formatted_sent_time . "\n" if $args{-sent_time}; $status_body .= "Mailing Has Taken: " . $self->_formatted_runtime(($args{-sent_time} - $args{-start_time})) . "\n\n" if defined($args{-start_time}) && defined($args{-sent_time}); $status_body .= "Approximate Time Left: " . $eta . "\n" if $eta; $status_body .= 'Last email of this batch was sent to: ' . $args{-last_email} . "\n\n" if $args{-last_email}; $status_body .= 'Waiting ' . $self->{list_info}->{bulk_sleep_amount} . ' second(s) until next batch. ' . "\n" if $self->{list_info}->{bulk_sleep_amount}; $status_body .= "\n - " . $PROGRAM_NAME . "\n\n"; $status_mailing{Body} = $status_body; $self->send(%status_mailing); } sub _email_batched_finished_notification { my $self = shift; my %args = (-fields => {}, -batch_num => undef, -start_time => undef, -end_time => undef, -emails_sent => undef, -last_email => undef, @_); my $fields = $args{-fields}; my %done_mailing = $self->_make_general_headers; $done_mailing{To} = $self->{list_info}->{list_owner_email}; $done_mailing{Subject} = $self->{list_info}->{list_name} . ' Mailing Complete. - ' . $fields->{Subject}; my $formatted_start_time; my $formatted_end_time; if($args{-start_time}){ my ($s_sec, $s_min, $s_hour, $s_day, $s_month, $s_year) = (localtime($args{-start_time}))[0,1,2,3,4,5]; $formatted_start_time = sprintf("%02d/%02d/%02d %02d:%02d:%02d", $s_month+1, $s_day, $s_year+1900, $s_hour, $s_min, $s_sec); } if($args{-end_time}){ my ($e_sec, $e_min, $e_hour, $e_day, $e_month, $e_year) = (localtime($args{-end_time}))[0,1,2,3,4,5]; $formatted_end_time = sprintf("%02d/%02d/%02d %02d:%02d:%02d", $e_month+1, $e_day, $e_year+1900, $e_hour, $e_min, $e_sec); } my $total_time = $self->_formatted_runtime(($args{-end_time} - $args{-start_time})); my $done_body = "Your List Mailing has been successful!\n"; $done_body .= '_' x 72 . "\n\n"; $done_body .= "Your mailing has reached: $args{-emails_sent} e-mail address(es)\n\n" if $args{-emails_sent}; $done_body .= 'Mailing Started: ' . $formatted_start_time . "\n" if $args{-start_time}; $done_body .= 'Mailing Ended: ' . $formatted_end_time . "\n" if $args{-end_time}; $done_body .= "Total Mailing Time: " . $total_time . "\n" if defined($total_time) && defined($args{-start_time}) && defined($args{-end_time}); $done_body .= 'Last Email sent to: ' . $args{-last_email} . "\n" if $args{-last_email}; $done_body .= "\nA copy of your Mailing List Message has been attached.\n" if $fields->{Body}; $done_body .= "\n-" . $PROGRAM_NAME . "\n\n"; require MIME::Lite; my $msg = MIME::Lite->new(Type => 'multipart/mixed', From => $done_mailing{From}, To => $done_mailing{To} ); $msg->attach(Type => 'TEXT', Data => $done_body, Disposition => 'inline', ); my $att; foreach(keys %$fields){ next if $_ eq 'Body'; $att .= $_ . ': ' . $fields->{$_} . "\n" if defined($fields->{$_}) && $fields->{$_} ne ""; } $att .= "\n" . $fields->{Body}; $msg->attach(Type => 'message/rfc822', Disposition => "inline", Data => $att); my %new_headers = $self->return_headers($msg->header_as_string); $done_mailing{Body} = $msg->body_as_string; $self->send(%new_headers, %done_mailing); } sub _mail_error_no_start_email { my $self = shift; my %args = (-fields => {}, -start_email => undef, @_); my $fields = $args{-fields}; my $start_mail_body; $start_mail_body .= "$PROGRAM_NAME Warning!\n\n"; $start_mail_body .= "It appears that no email was sent during your last mailing, since the\n"; $start_mail_body .= "email address you were looking to start at :\n\n"; $start_mail_body .= "$args{-start_email}\n\n"; $start_mail_body .= "was never found. It's possible that this email address isn't\n"; $start_mail_body .= "subscribed to your list. Please make sure you entered the\n"; $start_mail_body .= "correct email address.\n"; $start_mail_body .= " -$PROGRAM_NAME\n"; my %start_mail_error = ( From => $fields->{From}, To => $fields->{From}, List => $fields->{List}, Subject => "$fields->{List} - Possible Mailing Error, please read below:", Body => $start_mail_body); $self->send(%start_mail_error); } sub _mail_error_no_start_num { my $self = shift; my %args = (-fields => {}, -start_num => undef, -email_count => undef, @_); my $fields = $args{-fields}; my $start_mail_body; $start_mail_body = "$PROGRAM_NAME Warning!\n\n"; $start_mail_body .= "It appears that no email was sent during your last mailing, since mailing\n"; $start_mail_body .= "was supposed to being at email number: \n\n$args{-start_num} \n\nThere are about: \n\n"; $start_mail_body .= "$args{-email_count} \n\n"; $start_mail_body .= "email address(es) in your list. It's possible that you gave a number to start\n"; $start_mail_body .= "at that's larger than the list itself\n\n"; $start_mail_body .= " -$PROGRAM_NAME\n"; my %start_mail_error = ( From => $fields->{From}, To => $fields->{From}, List => $fields->{List}, Subject => "$fields->{List} - Possible Mailing Error, please read below:", Body => $start_mail_body); $self->send(%start_mail_error); } sub _send_die { my $self = shift; my $debug = shift; my $report; if($debug){ $report = "$PROGRAM_NAME $VER Mass Mailing Error! INFORMATION: Messages Sent: $debug->{-Messages_Sent}, Mailing Failed At Address: $debug->{-Last_Email}, Message Subject: $debug->{-Message_Subject}, Using List File: $debug->{-List_File}, List File Size: $debug->{-List_File_Size} bytes, Details: $!"; die($report); }else{ die("$PROGRAM_NAME $VER Error: can't pipe to mail program using settings: '$MAIL_SETTINGS': $!\n"); } } sub _merge_fields_string { my $self = shift; my @merge_fields = split(',', $self->{list_info}->{merge_fields}); my $merge_fields; foreach(@merge_fields){ $merge_fields .= '::' . '\['.$_.'\]'; } return $merge_fields; } sub _merge_fields_array_ref { my $self = shift; my @merge_fields = split(',', $self->{list_info}->{merge_fields}); my $merge_fields = []; foreach(@merge_fields){ push(@$merge_fields, '['.$_.']'); } return $merge_fields; } sub _verp { my $self = shift; my $to = shift; die "no email passed!" if ! $to; require Mail::Address; require Mail::Verp; if(my $to_temp = (Mail::Address->parse($to))[0]){ $to = $to_temp->address(); } my $mv = Mail::Verp->new; $mv->separator($MAIL_VERP_SEPARATOR); return $mv->encode( $self->{list_info}->{admin_email}, $to ); } sub _merge_fields { my $self = shift; my %args = (-body => undef, -merge_fields => [], -mail_fields => {}, @_); $args{-body} =~ s/\[email\]/$args{-merge_fields}->[0]/g; $args{-body} =~ s/\[email_name\]/$args{-merge_fields}->[1]/g; $args{-body} =~ s/\[email_domain\]/$args{-merge_fields}->[2]/g; $args{-body} =~ s/\[pin\]/$args{-merge_fields}->[3]/g; $args{-body} =~ s/\[list\]/$args{-merge_fields}->[4]/g; # the list short name. $args{-body} =~ s/\[list_name\]/$args{-merge_fields}->[5]/g; # the list name. my @merge_fields = split(',', $self->{list_info}->{merge_fields}); my $i; my $message_id = $args{-mail_fields}->{'Message-ID'}; $message_id =~ s/\<|\>//g; $message_id =~ s/\.(.*)//; #greedy $args{-body} =~ s/\[message_id\]/$message_id/g; # or, #$args{-body} =~ s/\[message_id\]/$args{-merge_fields}->[6]/g; # the list name. # I guess... $args{-body} = $self->redirect_tags(-string => $args{-body}, -mid => $message_id) if $self->{list_info}->{clickthrough_tracking} == 1; for($i=0;$i<=$#merge_fields;$i++){ $args{-body} =~ s/\[$merge_fields[$i]\]/$args{-merge_fields}->[$i+2]/g; } return $args{-body}; } sub redirect_tags { my $self = shift; my %args = (-string => undef, -mid => undef, @_); my $s = $args{-string}; my $mid = $args{-mid}; $mid =~ s/\<|\>//g; $mid =~ s/\.(.*)//; #greedy $s =~ s/\[redirect\=(.*?)\]/&DADA::Mail::Send::redirect_encode($self, $1, $mid)/eg; return $s; } sub redirect_encode { my ($self, $url, $mid) = @_; $mid =~ s/\<|\>//g; $mid =~ s/\.(.*)//; #greedy my $k = ''; if($url =~ m/^http:\/\//){ $url =~ s/^http:\/\///; $k = 'h'; }elsif($url =~ m/^https:\/\//){ $url =~ s/^https:\/\///; $k = 's'; } my $e_url; eval {require URI::Escape}; if(!$@){ $e_url = URI::Escape::uri_escape($url, "\200-\377"); }else{ $e_url = DADA::App::Guts::uriescape($url); } $e_url =~ s/\?/%3F/g; return $PROGRAM_URL . '/r/' . $self->{list_info}->{list} . '/' . $k . '/' . $mid . '/' . $e_url . '/'; } sub _formatted_runtime { my $self = shift; my $d = shift; my @int = ( [ 'second', 1 ], [ 'minute', 60 ], [ 'hour', 60*60 ], [ 'day', 60*60*24 ], [ 'week', 60*60*24*7 ], [ 'month', 60*60*24*30.5 ], [ 'year', 60*60*24*30.5*12 ] ); my $i = $#int; my @r; while ( ($i>=0) && ($d) ) { if ($d / $int[$i] -> [1] >= 1) { push @r, sprintf "%d %s%s", $d / $int[$i] -> [1], $int[$i]->[0], ( sprintf "%d", $d / $int[$i] -> [1] ) > 1 ? 's' : ''; } $d %= $int[$i] -> [1]; $i--; } my $runtime = join ", ", @r if @r; return $runtime; } sub _massaged_for_archive { my $self = shift; my $fields = shift; my $msg; foreach(@EMAIL_HEADERS_ORDER){ next if $_ eq 'Body'; next if $_ eq 'Message'; # Do I need this?! $msg .= $_ . ': ' . $fields->{$_} . "\n" if((defined $fields->{$_}) && ($fields->{$_} ne "")); } $msg .= "\n" . $fields->{Body}; return $msg; } sub _log_sub_count { my $self = shift; my %args = (-msg_id => undef, -num_subscribers => undef, @_ ); return if $self->bulk_test; return if $self->{list_info}->{enable_subscriber_count_logging} != 1; my $msg_id = $args{-msg_id}; $msg_id =~ s/\<|\>//g; $msg_id =~ s/\.(.*)//; my $num_subscribers = $args{-num_subscribers}; require DADA::Logging::Clickthrough; my $r = DADA::Logging::Clickthrough->new($self->{list_info}->{list}); $r->sc_log($msg_id, $num_subscribers); } sub check_sasl_settings { my $self = shift; my %args = (-smtp_server => undef, -sasl_smtp_username => undef, -sasl_smtp_password => undef, -smtp_connect_tries => undef, -set_smtp_sender => undef, -smtp_port => undef, @_ ); my @conversation; require Mail::Bulkmail; require Mail::Bulkmail::DadaMailServer; my @report = (); my $server = Mail::Bulkmail::DadaMailServer->new('Smtp' => $args{-smtp_server}, 'Port' => $args{-smtp_port}, 'Tries' => $args{-smtp_connect_tries}, 'Domain' => $self->_domain_for_smtp, 'max_connection_attempts' => $self->{list_info}->{smtp_connect_tries}, # this...doesn't work. #'max_messages_per_connection' => $self->{list_info}->{smtp_max_messages_per_connection}, SASL_username => $args{-sasl_smtp_username}, SASL_password => $args{-sasl_smtp_password}, CONVERSATION => \@conversation, ) || die Mail::Bulkmail::DadaMailServer->error(); my $bulk = Mail::Bulkmail->new( (($args{-set_smtp_sender} == 1) ? (Sender => $self->{list_info}->{admin_email},) : (Sender => $self->{list_info}->{list_owner_email},)), LIST => [$self->{list_info}->{list_owner_email}], From => $self->{list_info}->{list_owner_email}, Subject => 'Test SMTP Message', Message => 'This is a test to see if SMTP Sending Works - if you have received this message, SMTP sending is probably working!', servers => [$server], ( ($SMTP_ERROR_LOG) ? (ERRFILE => $SMTP_ERROR_LOG,) : () ), ) || die Mail::Bulkmail->error(); $bulk->bulkmail || die $bulk->error; # There's a better way to do this, I'm sure. foreach my $l(@conversation){ if($l =~ m/502 unimplemented/i){ push (@report, "$l - SASL Authentication may not be available on this SMTP server - try POP-before-SMTP Authentication."); }elsif($l =~ m/250\-AUTH PLAIN LOGIN|250 AUTH LOGIN PLAIN|250\-AUTH\=LOGIN PLAIN/i){ push(@report, "$l - Looks like Plain SASL Authentication is Supported!"); }elsif($l =~ m/535 Incorrect authentication data|535 authorization failed/i){ push(@report, "$l - Looks like there's something wrong with your username/password - double check that you entered them right."); }elsif($l =~ m/Authentication succeeded|OK Authenticated/i){ push(@report, "$l - Looks like we logged in OK!"); } } return (\@conversation, \@report); } sub DESTROY { # DESTROY ALL ASTROMEN! my $self = shift; } 1; =pod =head1 COPYRIGHT Copyright (c) 1999 - 2005 Justin Simoni me - justinsimoni.com http://justinsimoni.com All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. =cut