Wednesday, November 11, 2009

Chapter 8: Electronic Mail










Perl for System AdministrationSearch this book










Chapter 8. Electronic Mail



Contents:



Sending Mail


Common Mistakes in Sending Email


Receiving Mail


Module Information for This Chapter


References for More Information















Unlike the other chapters
in this book, this chapter does not discuss how to administer a
particular service, technology, or knowledge domain. Instead,
we're going to look at how to use email from Perl as a tool for
system administration.







Perl can help us in an administrative context with both sending and
receiving email. Email is a great notification mechanism: often we
want a program to tell us when something goes wrong, provide the
results of an automatic process (like a late night
cron or scheduler service job), or let us know
when something we care about changes. We'll explore how to send
mail from Perl for these purposes and then look at some of the
pitfalls associated with the practice of sending ourselves mail.







Similarly, we'll look at how Perl can be used to post-process
mail we receive to make it more useful to us. Perl can be useful for
dealing with spam and managing user questions.







This chapter will assume that you already have a solid and reliable
mail infrastructure. We're also going to assume that your mail
system, or one that you have access to, uses protocols that follow
the IETF specifications for sending and receiving mail. The examples
in this chapter will use protocols like SMTP (Simple Mail Transfer
Protocol, RFC821) and expect messages to be RFC822-compliant.
We'll go over these terms in due course.






































8.1. Sending Mail








Let's
talk about the mechanics of sending email first and then tackle the
more sophisticated issues. The traditional (Unix) Perl mail sending
code often looks something like this example from the Perl Frequently
Asked Questions list:








# assumes we have sendmail installed
open(SENDMAIL, "|/usr/lib/sendmail -oi -t -odq") or
die "Can't fork for sendmail: $!\n";
print SENDMAIL <<"EOF";
From: User Originating Mail <me\@host>
To: Final Destination <you\@otherhost>
Subject: A relevant subject line

Body of the message goes here after the blank line
in as many lines as you like.
EOF
close(SENDMAIL) or warn "sendmail didn't close nicely";









TIP


When the array interpolation rules were changed between Perl Version
4 and Perl Version 5, it broke many scripts that sent mail. Even now,
be on the lookout for code like this:








$address = "fred@example.com";







This needs to be changed to one of these lines to work properly:








$address="fred\@example.com";
$address='fred@example.com';
$address= join('@', 'fred', 'example.com');








Code that calls sendmail like our example above
works fine under many circumstances, but it doesn't work on any
operating system that lacks a mail transport agent called
"sendmail" installed (e.g., NT or MacOS). On those
operating systems, this leaves you with a few
choices.








8.1.1. Getting sendmail (or Similar Mail Transport Agent)








On Win32, you're in luck
because I know of at least three Win32 ports of
sendmail itself:









  • Cygwin sendmail port (http://dome/weeg.uiowa.edu/pub/domestic/sos/ports)




  • Mercury Systems' commercial sendmail port
    (http://www.demobuilder.com/sendmail.htm)




  • Sendmail, Inc.'s commercial Sendmail for NT
    (http://www.sendmail.com)





If you'd like something more
lightweight, and are willing to make small modifications to your Perl
code to support different command-line arguments, other Win32
programs like these will do the trick:










  • blat (http://www.interlog.com/~tcharron/blat.html)





  • netmail95 (http://www.geocities.com/SiliconValley/Lakes/2382/netmail.html)





  • wmailto (http://www.impaqcomp.com/jgaa/wmailto.html)




The advantage of this approach is it offloads much of the
mail-sending complexity from your script. A good Mail Transport Agent
(MTA) handles the process of retrying a destination mail server if
it's unreachable, selecting the right destination server
(finding and choosing between Mail eXchanger DNS records), rewriting
the headers if necessary, dealing with bounces, and so on. If you can
avoid having to take care of all of that in Perl, that's often
a good thing.




















8.1.2. Using the OS-Specific IPC Framework.








On MacOS or Windows NT, you can
drive a mail client using the native interprocess communication (IPC)
framework.







I haven't seen any MacOS ports of sendmail,
but under MacOS, we can ask Perl to use AppleScript to drive an email
client:








$to="someone\@example.com";
$from="me\@example.com";
$subject="Hi there";
$body="message body\n";

MacPerl::DoAppleScript(<<EOC);
tell application "Eudora"

make message at end of mailbox "out"

-- 0 is the current message
set field \"from\" of message 0 to \"$from\"
set field \"to\" of message 0 to \"$to\"
set field \"subject\" of message 0 to \"$subject\"
set body of message 0 to \"$body\"
queue message 0
connect with sending without checking
quit
end tell
EOC







This code executes a very simple AppleScript that communicates with
the email client Eudora by Qualcomm. The script
creates a new message, populates and queues the message for sending,
and then instructs Eudora to send its queued messages before
quitting.







Another slightly more efficient way to write this same code would be
to use the Mac::Glue module we saw in Chapter 2, "Filesystems":








use Mac::Glue ':glue';

$e=new Mac::Glue 'Eudora';
$to="someone\@example.com";
$from="me\@example.com";
$subject="Hi there";
$body="message body";

$e->make(
new => 'message',
at => location(end => $e->obj(mailbox => 'Out'))
);

$e->set($e->obj(field => from => message => 0), to => $from);
$e->set($e->obj(field => to => message => 0), to => $to);
$e->set($e->obj(field => subject => message => 0), to => $subject);
$e->set($e->prop(body => message => 0), to => $body);

$e->queue($e->obj(message => 0));
$e->connect(sending => 1, checking => 0);
$e->quit;








Under NT, we can use
Microsoft's Collaborative Data Objects Library (previously
called Active Messaging), an ease-of-use layer built on top of their
MAPI (Messaging Application Programming Interface) architecture. To
call this library to drive a mail client like Outlook, we could use
the Win32::OLE module like
so:








$to="me\@example.com";
$subject="Hi there";
$body="message body\n";

use Win32::OLE;

# init OLE, COINIT_OLEINITIALIZE required when using MAPI.Session objects
Win32::OLE->Initialize(Win32::OLE::COINIT_OLEINITIALIZE);
die Win32::OLE->LastError(),"\n" if Win32::OLE->LastError( );

# create a session object that will call Logoff when it is destroyed
my $session = Win32::OLE->new('MAPI.Session','Logoff');
die Win32::OLE->LastError(),"\n" if Win32::OLE->LastError( );

# log into that session using the default OL98 Internet Profile
$session->Logon('Microsoft Outlook Internet Settings');
die Win32::OLE->LastError(),"\n" if Win32::OLE->LastError( );

# create a message object
my $message = $session->Outbox->Messages->Add;
die Win32::OLE->LastError(),"\n" if Win32::OLE->LastError( );

# create a recipient object for that message object
my $recipient = $message->Recipients->Add;
die Win32::OLE->LastError(),"\n" if Win32::OLE->LastError( );

# populate the recipient object
$recipient->{Name} = $to;
$recipient->{Type} = 1; # 1 = "To:", 2 = "Cc:", 3 = "Bcc:"

# all addresses have to be resolved against a directory
# (in this case probably your Address book). Full addresses
# usually resolve to themselves, so this line in most cases will
# not modify the recipient object.
$recipient->Resolve( );
die Win32::OLE->LastError(),"\n" if Win32::OLE->LastError( );

# populate the Subject: line and message body
$message->{Subject} = $subject;
$message->{Text} = $body;

# queue the message to be sent
# 1st argument = save copy of message
# 2nd argument = allows user to change message w/dialog box before sent
# 3rd argument = parent window of dialog if 2nd argument is True
$message->Send(0, 0, 0);
die Win32::OLE->LastError(),"\n" if Win32::OLE->LastError( );

# explicitly destroy the $session object, calling $session->Logoff
# in the process
undef $session;







Unlike the previous example, this code just queues the message to be
sent. It is up to the mail client (like Outlook) or transport
infrastructure (like Exchange) to periodically initiate message
delivery. There is a CDO/AM 1.1 method for the
Session object called DeliverNow(
)
that is supposed to instruct MAPI to flush all incoming
and outgoing mail queues. Unfortunately, it is not available and does
not work under some circumstances, so it is not included in the
previous code example.







The previous code drives MAPI "by hand" using OLE calls.
If you'd like to use MAPI without getting your hands that
dirty, Amine Moulay Ramdane has put together a
Win32::MAPI module (found at http://www.generation.net/~aminer/Perl/)
that can take some of the work out of the process.







Programs that rely on AppleScript/Apple Events or MAPI are equally as
non-portable as calling a sendmail binary. They
offload some of the work, but are relatively inefficient. They should
probably be your methods of last
resort.




















8.1.3. Speaking to the Mail Protocols Directly








Our final
choice is to write code that speaks to the mail server in its native
language. Most of this language is documented in RFC821. Here's
a basic SMTP (Simple Mail Transport Protocol) conversation. The data
we send is in bold:








% telnet example.com 25         -- connect to the SMTP port on example.com








Trying 192.168.1.10 ...
Connected to example.com.
Escape character is '^]'.
220 mailhub.example.com ESMTP Sendmail 8.9.1a/8.9.1; Sun, 11 Apr 1999 15:32:16 -0400 (EDT)
HELO client.example.com -- identify the machine we are connecting from








                                                                             (can also use EHLO)








250 mailhub.example.com Hello dnb@client.example.com [192.168.1.11], pleased to meet you
MAIL FROM: <dnb@example.com> -- specify the sender








250 <dnb@example.com>... Sender ok
RCPT TO: <dnb@example.com> -- specify the recipient








250 <dnb@example.com>... Recipient ok
DATA -- begin to send message, note we send several key header lines








354 Enter mail, end with "." on a line by itself
From: David N. Blank-Edelman (David N. Blank-Edelman)
To: dnb@example.com
Subject: SMTP is a fine protocol

Just wanted to drop myself a note to remind myself how much I love SMTP.
Peace,
dNb
. -- finish sending the message








250 PAA26624 Message accepted for delivery
QUIT -- end the session








221 mailhub.example.com closing connection
Connection closed by foreign host.







It is not difficult to script a network conversation like this. We
could use the Socket module or even something like
Net::Telnet as seen in Chapter 6, "Directory Services". But there are good mail modules out there
that make our job easier, like Jenda Krynicky's
Mail::Sender, Milivoj Ivkovic's
Mail::Sendmail, and
Mail::Mailer in Graham Barr's
MailTools package. All three of these packages
are operating-system-independent and will work almost anywhere a
modern Perl distribution is available. We'll look at
Mail::Mailer because it offers a single interface
to two of the mail-sending methods we've discussed so
far. Like most Perl modules written
in an object-oriented style, the first step is to construct an
instance of new object:








use Mail::Mailer;

$from="me\@example.com";
$to="you\@example.com";
$subject="Hi there";
$body="message body\n";

$type="smtp";
$server="mail.example.com";

my $mailer = Mail::Mailer->new($type, Server => $server) or
die "Unable to create new mailer object:$!\n";







The $type variable allows you to choose one of the
following behaviors:









smtp



Send the
mail using the Net::SMTP module (part of
Barr's libnet package), available for most
non-Unix ports of Perl as well. If you are using
MailTools Version 1.13 and above, you can
specify the SMTP server name using the =>
notation as demonstrated above. If not, you will have to configure
the server name as part of the libnet install
procedure.









mail


Send the mail using the Unix mail user agent mail
(or whatever binary you specify as an optional second argument). This
is similar to our use of AppleScript and MAPI above.









sendmail



Send the
mail using the sendmail binary, like our first
method of this section.










You can also set the environment variable
PERL_MAILERS to change the default locations used
to find the binaries like sendmail on your system.







Calling the open( ) method of our
Mail::Mailer object causes our object to behave
like a filehandle to an outgoing message. In this call, we pass in
the headers of the message as a reference to an anonymous hash:








$mailer->open({From => $from, 
To => $to,
Subject => $subject}) or
die "Unable to populate mailer object:$!\n";







We print our message body to this
pseudo-filehandle and then close it to send the message:








print $mailer $body;
$mailer->close;







That's all it takes to send mail portably via Perl.







Depending on which $type behavior we choose when
using this module, we may or may not be covered regarding the harder
MTA issues mentioned earlier. The previous code uses the
smtp behavior, which means our code needs to be
smart enough to handle error conditions like unreachable servers. As
written, it's not that smart. Be sure any production code you
write is prepared to deal with these issues.

























































































































































Copyright © 2001 O'Reilly & Associates. All rights reserved.







No comments:

Post a Comment