package CGI::Request_mailer;
use Exporter;
@ISA= qw(Exporter);
@EXPORT=(Run);
use CGI qw(:standard); use Net::SMTP; use Net::DNS;
sub new
{
my $type= shift;
my $self= {};
bless $self, $type; $self->Initialise(@_); return $self; }
sub Initialise
{
my $self= shift; my %requiredFields= (
'Fields' => 'Fields',
'MailSubject' => 'MailSubject',
'OperatorName' => 'OperatorName',
);
$self->{CGI}= new CGI();
$self->True(1==1); $self->False(1==0);
$self->FieldGroupDelimiter(";");
$self->FieldDelimiter(",");
$self->FieldOptionDelimiter("r");
$self->LogicDelimiterAnd(" and ");
$self->LogicDelimiterOr(" or ");
$self->HTMLLineBreak($self->{CGI}->br);
$self->Error("No error."); $self->ErrorDescription("No error.");
$self->RequiredFields(\%requiredFields);
$self->Fields(0); $self->Message(""); $self->MailToAddress(shift); $self->ReportHeader(shift); $self->ReportFooter(shift); $self->Valid($self->False()); }
sub Run
{
my $self= shift;
return $self->False() unless $self->ConfirmData();
return $self->False() unless $self->ConfirmRequiredFields();
$self->CollectMailToAddress() unless $self->MailToAddress();
$self->CollectRequestedFields();
$self->ProcessRequest();
}
sub True
{
my $self = shift;
if (@_) { $self->{TRUE}= shift; } return $self->{TRUE}; }
sub False
{
my $self= shift;
if (@_) { $self->{FALSE}= shift; }
return $self->{FALSE};
}
sub FieldGroupDelimiter
{
my $self= shift;
if (@_) { $self->{FIELDGROUPDELIMITER}= shift; }
return $self->{FIELDGROUPDELIMITER};
}
sub FieldDelimiter
{
my $self= shift;
if (@_) { $self->{FIELDDELIMITER}= shift; }
return $self->{FIELDDELIMITER};
}
sub FieldOptionDelimiter
{
my $self= shift;
if (@_) { $self->{FIELDOPTIONDELIMITER}= shift; }
return $self->{FIELDOPTIONDELIMITER};
}
sub LogicDelimiterAnd
{
my $self= shift;
if (@_) { $self->{LOGICDELIMITERAND}= shift; }
return $self->{LOGICDELIMITERAND};
}
sub LogicDelimiterOr
{
my $self= shift;
if (@_) { $self->{LOGICDELIMITEROR}= shift; }
return $self->{LOGICDELIMITEROR};
}
sub HTMLLineBreak
{
my $self= shift;
if (@_) { $self->{HTMLLINEBREAK}= shift; }
return $self->{HTMLLINEBREAK};
}
sub Error
{
my $self= shift;
if (@_) { $self->{ERROR}= shift; }
return $self->{ERROR};
}
sub ErrorDescription
{
my $self= shift;
if (@_) { $self->{ERRORDESCRIPTION}= shift; }
return $self->{ERRORDESCRIPTION};
}
sub Valid
{
my $self= shift;
if (@_) { $self->{VALID}= shift; }
return $self->{VALID};
}
sub RequiredFields
{
my $self= shift;
if (@_) { $self->{REQUIREDFIELDS}= shift; }
return $self->{REQUIREDFIELDS};
}
sub Fields
{
my $self= shift;
if (@_) { $self->{FIELDS}= shift; }
return $self->{FIELDS};
}
sub Message
{
my $self= shift;
if (@_) { $self->{MESSAGE}= shift; }
return $self->{MESSAGE};
}
sub MailToAddress
{
my $self= shift;
if (@_) { $self->{MAILTOADDRESS}= shift; }
return $self->{MAILTOADDRESS};
}
sub ReportHeader
{
my $self= shift;
if (@_) { $self->{REPORTHEADER}= shift; }
return $self->{REPORTHEADER};
}
sub ReportFooter
{
my $self= shift;
if (@_) { $self->{REPORTFOOTER}= shift; }
return $self->{REPORTFOOTER};
}
sub ConfirmData
{
my $self= shift;
if (!$self->{CGI}->param())
{ print $self->{CGI}->header();
print $self->{CGI}->start_html('No arguments passed'),
$self->{CGI}->h3('Error: No data!'),
"<p>Apparently, you invoked this program from something other \n",
"than its parent form. It simply won't do you any good this way.</p>\n";
print $self->{CGI}->end_html;
return $self->False();
}
else { return $self->True(); }
}
sub ConfirmRequiredFields
{
my $self= shift;
my @missingFields= (); my $error; my %requiredFields; my $requiredFieldKey; my $requiredFieldName;
%requiredFields= %{$self->RequiredFields()};
while (($requiredFieldKey, $requiredFieldName)= each %requiredFields)
{
push(@missingFields, $requiredFieldName)
unless $self->{CGI}->param($requiredFieldKey);
}
if (@missingFields > 0)
{ if (@missingFields == 1)
{
$error= "<p>Required field ommitted:</p>\n";
}
else
{
$error= "<p>Required fields ommitted:</p>\n";
}
$error.= "<ol>\n";
foreach $missingField (@missingFields)
{ $error.= "<li>". $missingField."\n";
}
$error.= "</ol>\n";
$self->ErrorDescription($error);
$self->ReportMisconfiguration();
return $self->False();
}
else { return $self->True(); }
}
sub CollectMailToAddress
{
my $self= shift;
$self->MailToAddress($self->{CGI}->param('MailToAddress'));
}
sub CollectRequestedFields
{
my $self= shift;
my $requestedFieldTokens; my @requestedFields= (); my @missingFields= (); my $fieldGroup; my $fieldName; my @fields; my $fieldGroupCount; my $fieldGroupOption; my $fieldLogicDelimiter;
$requestedFieldTokens= $self->{CGI}->param('Fields');
$requestedFieldTokens=~ s/\s+//g;
ProcessGroups: foreach $fieldGroup
(split($self->FieldGroupDelimiter(), $requestedFieldTokens))
{
if ($fieldGroup=~ /^\d/) { @fields= split($self->FieldDelimiter(), $fieldGroup);
$fieldGroupCount= shift(@fields);
($fieldGroupCount, $fieldGroupOption)=
split($self->FieldOptionDelimiter(), $fieldGroupCount);
if ($fieldGroupOption)
{ if ($fieldGroupCount < $fieldGroupOption)
{ $self->ErrorDescription("Misconfigured field group: "
. "Mandatory v. optional miscount [$fieldGroup]");
$self->ReportMisconfiguration();
return ($self->False());
}
$fieldLogicDelimiter= $self->LogicDelimiterOr();
}
else
{ $fieldGroupOption= $fieldGroupCount;
$fieldLogicDelimiter= $self->LogicDelimiterAnd();
}
}
else
{ push(@requestedFields, $self->HTMLLineBreak());
next ProcessGroups; }
if ($fieldGroupCount == 0)
{ $fieldLogicDelimiter= ""; foreach $fieldName (@fields)
{
if ($self->ConfirmField($fieldName))
{ push(@requestedFields, $fieldLogicDelimiter)
if $fieldLogicDelimiter;
push(@requestedFields, $fieldName);
$fieldLogicDimiter= $self->LogicDelimiterAnd();
}
}
next ProcessGroups; }
if ($fieldGroupCount != @fields)
{ $self->ErrorDescription("Misconfigured field group: "
. "Count v. list mismatch [$fieldGroup]");
$self->ReportMisconfiguration();
return ($self->False());
}
foreach $fieldName (@fields)
{
if ($self->ConfirmField($fieldName))
{ push(@requestedFields, $fieldName);
if (--$fieldGroupOption)
{ push(@requestedFields, $self->LogicDelimiterAnd());
}
else
{
next ProcessGroups; }
}
}
push(@missingFields, join($fieldLogicDelimiter, @fields));
}
if (@missingFields > 0)
{ $self->Valid($self->False());
$self->Fields(\@missingFields);
}
else
{ $self->Valid($self->True());
$self->Fields(\@requestedFields);
}
}
sub ConfirmField
{
my $self= shift;
my $fieldName= shift;
if ($self->{CGI}->param($fieldName)=~ /\S/)
{ return $self->True(); }
else { return $self->False(); }
}
sub ReportMisconfiguration
{
my $self= shift;
my $description;
$self->Error("Request form misconfigured");
$description=
"<p>The request form you just submitted contained errors.</p>\n" .
"<p>Please alert its author to this problem.\n";
if ($self->MailToAddress())
{ $description.= " <a href=\"mailto:";
$description.= $self->MailToAddress();
$description.= "\">";
if ($self->{CGI}->param('OperatorName'))
{ $description.= $self->{CGI}->param('OperatorName');
}
else
{ $description.= $self->MailToAddress();
}
$description.=
"</a> may be an appropriate contact. \n" .
"However, since the form was misconfigured in the first place, \n" .
"this email address may be bogus.</p>\n";
}
$description.= "<hr>\n" . $self->ErrorDescription();
$self->ErrorDescription($description);
$self->ReportError();
}
sub ProcessRequest
{
my $self= shift;
if ($self->Valid()) { $self->CompileRequest();
$self->SendRequest();
$self->ReportSuccess();
}
else { $self->ReportMissingRequestedFields();
}
}
sub ReportMissingRequestedFields
{
my $self= shift;
my $error;
$self->Error("Requested fields missing");
$error=
"<p>Apparently, you forgot to complete all mandatory fields. \n" .
"Please return and enter the missing information.</p>\n" .
"<hr>\n";
if (@{$self->Fields()} > 0)
{ if (@{$self->Fields()} == 1)
{
$error= "<p>Requested field left blank:</p>\n";
}
else
{
$error= "<p>Requested fields left blank:</p>\n";
}
$error.=
"<ol>\n" .
"<li>" .
join("\n<li>", @{$self->Fields()}) .
"</ol>\n";
}
$self->ErrorDescription($error);
$self->ReportError();
}
sub ReportError
{
my $self= shift;
my $errorReport= "";
if ($self->ReportHeader())
{
$errorReport= $self->CreateReport($self->{CGI}->h3("Error: " . $self->Error()) .
$self->ErrorDescription());
}
if ($errorReport)
{
print $self->{CGI}->header();
print $errorReport;
}
else
{
print $self->{CGI}->header();
print
$self->{CGI}->start_html($self->Error()),
$self->{CGI}->h3("Error: " . $self->Error()),
$self->ErrorDescription();
print $self->{CGI}->end_html;
}
}
sub CreateReport
{
my $self= shift;
my $message= shift; my $text= "";
my $headerFile= $self->ReportHeader();
my $footerFile= $self->ReportFooter();
open(Header, $headerFile) or return $text;
while (<Header>)
{ $text.= $_;
}
close(Header);
$text.= $message;
open(Footer, $footerFile) or return $text . $self->{CGI}->end_html;
while (<Footer>)
{ $text.= $_;
}
close(Footer);
return $text;
}
sub CompileRequest
{
my $self= shift;
my $field; my $message= ""; my $values; my $value; my $valueTrailer= ""; my $endOfLine;
$endOfLine= $self->False(); $values= ": "; foreach $field (@{$self->Fields()})
{
if ($field eq $self->HTMLLineBreak())
{ $values.= "\n";
$endOfLine= $self->True();
next;
}
if ($field eq $self->LogicDelimiterAnd())
{ $message.= $self->LogicDelimiterAnd();
$values.= " "; $endOfLine= $self->False();
next;
}
if ($endOfLine)
{
$message.= "$values"; $message.= "\n";
$values= ": "; if ($self->{CGI}->param($field) =~ /\n/)
{ $values.= "\n"; $valueTrailer= "\n"; } $value= $self->{CGI}->param($field);
$value=~ s/^\s+//; $value=~ s/\s+$//; $values.= $value . $valueTrailer;
$valueTrailer= "";
}
else
{
$endOfLine= $self->True(); $value= $self->{CGI}->param($field);
$value=~ s/^\s+//; $value=~ s/\s+$//; $values.= $value . $valueTrailer;
$valueTrailer= "";
}
$field=~ s/_/ /g; $message.= $field; }
$message.= "$values\n"; $self->Message($message); return;
}
sub SendRequest
{
my $self= shift;
my $mailMessage; my $mailer; my $address;
$address= $self->ConfirmEmailAddress();
$_= $self->{CGI}->param('MailStart');
s/\\n/\n/g; $mailMessage= $_ . $self->Message();
$_= $self->{CGI}->param("MailEnd");
s/\\n/\n/g; $mailMessage.= $_;
unless ($mailer= new Net::SMTP())
{ unless ($mailer= new Net::SMTP("localhost"))
{ $self->Error("Mailer error");
$self->ErrorDescription("Net:SMTP likely misconfigured: Cound not create connections to send mail.");
$self->ReportError();
exit;
}
}
if ($mailer->mail($address)
&& $mailer->to($self->MailToAddress())
&& $mailer->to($address))
{
$mailer->data();
$mailMessage=
"To: ". $self->MailToAddress(). "\n".
"Cc: ". $self->{CGI}->param('Email'). "\n".
"From: ". $self->{CGI}->param('Email'). "\n".
"Reply-To: ". $self->{CGI}->param('Email'). "\n".
"X-mailer: ". "request-mailer.pl <".
$self->{CGI}->param("CallerURL"). ">\n".
"X-Remote-Host: $ENV{'REMOTE_HOST'} ($ENV{'REMOTE_ADDR'})\n".
"X-Remote-User: $ENV{'REMOTE_USER'}\n".
"Subject: ". $self->{CGI}->param('MailSubject'). "\n".
"\n". $mailMessage;
$mailer->datasend($mailMessage);
$mailer->dataend;
}
else
{
$self->Error("Mailer error");
$self->ErrorDescription($mailer->message());
$self->ReportError();
exit;
}
$mailer->quit;
}
sub ReportSuccess
{
my $self= shift;
my $successReport= "";
my $message= "Here's a copy of your request:<br>\n" .
"<blockquote><pre>" .
$self->Message() .
"</pre></blockquote>";
if ($self->ReportHeader())
{
$successReport= $self->CreateReport($self->SuccessHeader() . $message);
}
if ($successReport)
{
print $self->{CGI}->header();
print $successReport;
}
else
{
print
$self->{CGI}->header(),
$self->{CGI}->start_html('Success message: Request sent'),
$self->SuccessHeader(),
$message,
$self->SuccessFooter(),
$self->{CGI}->end_html;
}
}
sub SuccessHeader
{
my $self= shift;
my $text= "";
my $successNote= "<p>You will hear back from a human soon.</p>\n";
$successNote= $self->{CGI}->param('SuccessNote')
if $self->{CGI}->param('SuccessNote');
$text= $self->{CGI}->h3('Success!')
. "<p>I sent your request to \n"
. "<a href=\"mailto:". $self->MailToAddress(). "\">"
. $self->{CGI}->param('OperatorName'). "</a>, \n"
. "along with a copy to you.</p>\n"
. $successNote;
return $text;
}
sub SuccessFooter
{
my $self= shift;
my $pointerAddress= $self->{CGI}->param('CallerURL');
my $pointerText= $self->{CGI}->param('CallerTitle');
my $text= "";
return $text unless $pointerAddress;
$pointerText= $pointerAddress unless $pointerText;
$text .= "<center><a href=\"$pointerAddress\">";
$text .= "Return to $pointerText...";
$text .= "</a></center>\n";
return $text;
}
sub ConfirmEmailAddress
{
my $self= shift;
my $address; my $user; my $mailhost; my $confirmed; my $smtp; my $dns; my @mx; my $a; my @hosts; my $rr; my $host; my $lastPreference; my $errorMessage;
$self->Error("E-mail address error");
$address= $self->{CGI}->param('Email');
($user)= split(/[!%@]/, $address);
$address=~ /^$user[!%@](.+)$/;
$mailhost= $1;
if ($user eq "")
{ $self->ErrorDescription("Your email address is missing.");
$self->ReportError();
exit;
}
unless ($user =~ /^([\-\+\w\d\._]+)$/)
{ $self->ErrorDescription("Bad user-id format: [$user].");
$self->ReportError();
exit;
}
if ($mailhost)
{ $host= $mailhost;
$dns= new Net::DNS::Resolver;
if (@mx= mx($dns, $host))
{ $lastPreference= 1000000;
foreach $rr (@mx)
{ last if ($lastPreference < $rr->preference);
$lastPreference= $rr->preference;
push(@hosts, $rr->exchange);
}
}
else
{
$a= $dns->query($host, "A");
if ($a)
{ foreach $rr ($a->answer)
{ push(@hosts, $rr->address) if ($rr->type eq "A");
}
}
}
unless (@hosts)
{ $self->Error("DNS error");
$self->ErrorDescription
("Could not resolve [$host] for address confirmation.");
$self->ReportError();
exit(0);
}
foreach $host (@hosts)
{
if ($smtp= new Net::SMTP($host, Timeout => 300))
{
$smtp->mail($self->MailToAddress());
$confirmed= $smtp->to($address);
$smtp->quit();
}
last if $confirmed;
}
}
else
{ if ($smtp= new Net::SMTP())
{
$address= $user . "\@" . $smtp->domain();
$smtp->mail($self->MailToAddress());
$confirmed= $smtp->to($address);
$smtp->quit();
}
}
unless ($confirmed)
{
$errorMessage= "Could not confirm [$address] via ";
if (@hosts)
{ $errorMessage.= "[" . join(",", @hosts) . "]";
}
else
{ $errorMessage.= "the local server";
}
if ($@)
{ $errorMessage.= " ($@).";
}
else
{
$errorMessage.= ".";
}
$self->ErrorDescription($errorMessage);
$self->ReportError();
exit;
}
return $address;
}
1;