Ever
wish you could send electronic mail from your web browser? This
gateway allows you to do just that.
#!/usr/local/bin/perl
$webmaster = "shishir\@bu\.edu";
$gateway = "CGI Mail Gateway [v1.0]";
$request_method = $ENV{'REQUEST_METHOD'};
$sendmail = "/usr/lib/sendmail -t -n -oi";
This program uses the UNIX
sendmail utility
to actually send the message. The -t option
instructs sendmail to scan the message for
the "To:" mail header, and the n
option prevents the user from entering aliases for the recipient's
email address; you would not want some remote user to use your system's
internal aliases, would you?
$address_file = "/home/shishir/httpd_1.4.2/cgi-bin/address.dat";
The address file consists of a list of recipients' mail addresses
from which the user is required to select one. The user cannot enter
an address that is not included in the address file. The address.dat
file should be formatted as follows:
I have chosen a comma to separate nicknames from addresses
because Internet standards prohibit a comma from being used in an
address.
When the mail form is displayed, the program inserts all of
the descriptive names in a scrolled list. If you do not want to
have such a file, remove or comment out the line defining $address_file.
$exclusive_lock = 2;
$unlock = 8;
if ( defined ($address_file) && (-e $address_file) ) {
&load_address (*address);
}
If the address_file variable is defined
and the file exists, the load_address subroutine
is called to load the list of addresses into the address
associative array (for easy retrieval).
&parse_form_data (*MAIL);
The form information is stored in the MAIL
associative array. The parse_form_data subroutine
is the same as the one used at the beginning of Chapter 7, Advanced Form Applications. Like the guestbook application I presented in Chapter 7, Advanced Form Applications, this program is two in one: Half of the program
displays a form, and the other half retrieves the data after the
user submits the form, and sends the mail.
if ($request_method eq "GET") {
&display_form ();
If the GET method was used to access this
program, the display_form subroutine displays the form.
This gateway can be accessed without
any query information:
https://your.machine/cgi-bin/mail.pl
in which case, a mail form is displayed. Or, you can also
access it by passing query information:
In this case, the "to" and "url" fields in the form will contain
the information passed to it. If an address file is being used,
the address specified by the "to" field has to match one of the
addresses in the list. Instead of specifying the full email address,
you can also use the descriptive title from the address file:
https://your.machine/cgi-bin/mail.pl?to=Author&url=/thanks.html
The advantage of passing queries like this is that you can
create links within a document, such as:
.
.
If you want to contact me, click <A HREF="/cgibin/mail.pl?to=Author">here.</A>
.
.
All of the fields in the form, including "to" and "url," will
be explained later in this section.
} elsif ($request_method eq "POST") {
if ( defined (%address) ) {
$check_status = &check_to_address ();
if (!$check_status) {
&return_error (500, "$gateway Error",
"The address you specified is not allowed.");
}
}
This block will be executed if the POST
method was used to access this gateway (which means that the user
filled out the form and submitted it). If the address
associative array is defined, the check_to_address
subroutine is called to check for the validity of the user-specified
address. In other words, the address has to be listed in the address
file. This subroutine returns a TRUE or FALSE
value. If the address is not valid, an error message is returned.
if ( (!$MAIL{'from'}) || (!$MAIL{'email'}) ) {
&return_error (500, "$gateway Error", "Who are you ?");
} else {
&send_mail ();
&return_thanks ();
}
If the user failed to enter any information into the "from"
and "email" fields in the form, an error message is returned (which
I will show later). Otherwise, the mail message is sent, and a thank-you
note is returned.
} else {
&return_error (500, "Server Error",
"Server uses unsupported method");
}
exit(0);
Now for the load_address subroutine,
which reads your address file:
sub load_address
{
local (*ADDRESS_DATA) = @_;
local ($name, $address);
open (FILE, $address_file) || &return_error (500, "$gateway Error",
"Cannot open the address file [$address_file].");
flock (FILE, $exclusive_lock);
This subroutine opens the address file, and loads all of the
entries into an associative array. Note that $exclusive_lock
and $unlock are global variables.
while (<FILE>) {
chop if (/\n$/);
($name, $address) = split (/,/, $_, 2);
$ADDRESS_DATA{$name} = $address;
}
The while loop iterates through the file
one line at a time. If a line ends with a newline character, it
is removed with the chop function. The chop
function removes the last character of the line. The if
clause is there as a precaution, because the last line of the file
may not have a newline character, in which case part of the data
would be lost. The split command, which should
be familiar by now, separates the name from the address. Then, an
entry in the associative array is created to hold the address.
flock (FILE, $unlock);
close (FILE);
}
The display_form subroutine is executed
when the client invokes the program without a query.
sub display_form
{
local ($address_to);
print "Content-type: text/html", "\n\n";
$address_to = &determine_to_field ();
The determine_to_field
subroutine creates a scrolled list if the address file is defined.
See Figure 9.4 for a snapshot of what this looks like. Otherwise,
a simple text field is used. The HTML needed
to accomplish these functions is returned by the subroutine, and
is stored in the address_to variable.
print <<End_of_Mail_Form;
<HTML>
<HEAD><TITLE>A WWW Gateway to Mail</TITLE></HEAD>
<BODY>
<H1>$gateway</H1>
This form can be used to send mail through the World Wide Web.
Please fill out all the necessary information.
<HR>
<FORM METHOD="POST">
<PRE>
Full Name: <INPUT TYPE="text" NAME="from" VALUE="$MAIL{'from'}" SIZE=40>
E-Mail: <INPUT TYPE="text" NAME="email" VALUE="$MAIL{'email'}" SIZE=40>
To: $address_to
CC: <INPUT TYPE="text" NAME="cc" VALUE="$MAIL{'cc'}" SIZE=40>
Subject: <INPUT TYPE="text" NAME="subject" VALUE="$MAIL{'subject'}" SIZE=40>
<HR>
Notice the use of the VALUE attributes in the INPUT
statements. These values represent the query information that is
passed to this program with a GET request.
Please type the message below:
<TEXTAREA ROWS=10 COLS=60 NAME="message"></TEXTAREA>
</PRE>
<INPUT TYPE="hidden" NAME="url" VALUE="$MAIL{'url'}">
<INPUT TYPE="submit" VALUE="Send the Message">
<INPUT TYPE="reset" VALUE="Clear the Message">
</FORM>
<HR>
</BODY></HTML>
End_of_Mail_Form
}
The "url" field is defined as a hidden field. This consists
of the URL of the document that is displayed after the user completes
the form.
The determine_to_field subroutine either
creates a scrolled list of all the addresses in the file, or a simple
text field in which the user can enter the recipient's address.
sub determine_to_field
{
local ($to_field, $key, $selected);
if (%address) {
$to_field = '<SELECT NAME="to">';
foreach $key (keys %address) {
The keys function returns a normal array
consisting of all of the keys of the associative array. The foreach
construct then iterates through each key.
if ( ($MAIL{'to'} eq $key) ||
($MAIL{'to'} eq $address{$key}) ) {
$selected = "<OPTION SELECTED>";
} else {
$selected = "<OPTION>";
}
If the recipient specified by the user (through a query string)
matches either the descriptive title in the address file--
the key--or
the actual address, it is highlighted. Remember, this is how you
can access this program with a query:
Now, the rest of the subroutine:
$to_field = join ("\n", $to_field,
$selected, $key);
}
$to_field = join ("\n", $to_field, "</SELECT>");
Finally, all of the <OPTION> tags are concatenated to
create the kind of scrolled list shown above.
} else {
$to_field =
qq/<INPUT TYPE="text" NAME="to" VALUE="$MAIL{'to'}" SIZE=40>/;
}
return ($to_field);
}
If an address file
is not used, a simple text field is displayed. The qq/../
construct builds a double-quoted string. It should be used when
there are many double quotation marks within the string. The same
string can be expressed inside the traditional double quotes:
$to_field = "<INPUT TYPE=\"text\" NAME=\"to\" VALUE=\"$MAIL{'to'}\" SIZE=40>";
As you can see, all of the other double quotation marks within
the string have to be escaped by putting backslashes in front of
them. Using the qq notation in the regular
expression is much easier.
Finally, the HTML needed to display the "to" field is returned.
The check_to_address subroutine checks
the user-specified recipient to make sure that it is valid. If it
is valid, the variable $MAIL{'to'} will be
set to the corresponding email address. Finally, a status indicating
success or failure is returned.
sub check_to_address
{
local ($status, $key);
$status = 0;
foreach $key (keys %address) {
if ( ($MAIL{'to'} eq $key) || ($MAIL{'to'} eq $address{$key}) ) {
$status = 1;
$MAIL{'to'} = $address{$key};
}
}
return ($status);
}
In this next subroutine, the mail is sent using the UNIX
sendmail utility.
sub send_mail
{
open (SENDMAIL, "| $sendmail");
A pipe to the sendmail utility is opened for input. We do
not need to check any of the form values for shell metacharacters
because none of the values are "exposed" on the command line. The
sendmail utility allows you to place the recipient's
name in the input stream, rather than on the command-line.
If the regular mail utility is used,
the form information must be checked for metacharacters. This is
how we can send mail with the mail utility:
if ($MAIL{'to'} =~ /([\w\-\+]+)@([\w\-\+\.]+)/) {
open (SENDMAIL, "/usr/ucb/mail $MAIL{'to'} |");
} else {
&return_error (500, "$gateway Error", "Address is not valid.");
}
The regular expression is described by the figure below. Of
course, this allows only Internet-style mail addresses; UUCP
addresses are not recognized.
print SENDMAIL <<Mail_Headers;
From: $MAIL{'from'} <$MAIL{'email'}>
To: $MAIL{'to'}
Reply-To: $MAIL{'email'}
Subject: $MAIL{'subject'}
X-Mailer: $gateway
X-Remote-Host: $ENV{'REMOTE_ADDR'}
Mail_Headers
Various
mail
headers are output. Any headers starting with "X-" are user/program
specified, and are usually ignored by mail readers. The remote IP
address of the user (the environment variable REMOTE_ADDRESS)
is output for possible security reasons. Imagine a situation where
someone fills out a form with obnoxious information, and includes
a "fake" address. This header will at least tell you where the message
came from.
if ($MAIL{'cc'}) {
print SENDMAIL "Cc: ", $MAIL{'cc'}, "\n";
}
print SENDMAIL "\n", $MAIL{'message'}, "\n";
close (MAIL);
}
If the user entered an address in the "Cc:" field, a mail
header is output. Finally, the body of the message is displayed,
and the pipe is closed.
It is courteous to output a thank-you message:
sub return_thanks
{
if ($MAIL{'url'}) {
print "Location: ", $MAIL{'url'}, "\n\n";
} else {
print "Content-type: text/html", "\n\n";
print <<Thanks;
<HTML>
<HEAD><TITLE>$gateway</TITLE></HEAD>
<BODY>
<H1>Thank You!</H1>
<HR>
Thanks for using the mail gateway. Please feel free to use it again.
</BODY></HTML>
Thanks
}
}
If a URL was specified as part of the GET
request, a server redirect is done with the "Location" HTTP header.
In other words, the server will get and display the specified document
after the user submits the form. Otherwise, a simple thank-you note
is issued.