Tuesday, November 24, 2009

Sending Mail (SMTP)

SkyHi @ Tuesday, November 24, 2009


Before you send mail, the entire message needs to be composed.
You need to know where it is going, who gets it, and what the
text of the message is. When this information has been gathemiles,
you begin the process of transferring the information to a mail
server.


Note:
The mail service will be listening for your connection on TCP port 25.
But this information will not be important until you see some Perl code
later in the chapter.



The message that you prepare can only use alphanumeric characters.
If you need to send binary information (like files), use the MIME
protocol. The details of the MIME protocol can be found at the

http://ds.internic.net/ds/dspg0intdoc.html


Web site.


SMTP uses several commands to communicate with mail servers. These
commands are described below.

Note: The commands are not case sensitive,
which means you can use either Mail or MAIL. However, remember
that mail addresses are case sensitive.


The basic SMTP commands are:

HELO
-- Initiates a
conversation with the mail server. When using this command you can specify your domain
name so that the mail server knows who you are. For example, HELO mailhost2.
cf.ac.uk
.
MAIL
-- Indicates who is sending the mail. For example,

MAIL FROM:
<dave@cs.cf.ac.uk>
.


Remember this is not going to be your name -- it's the
name of the person who is sending the mail message. Any returned mail will be sent back
to this address.

RCPT
-- Indicates who is recieving the mail. For example,

RCPT
TO:
<user@email.com>
. You can indicate more than one user by issuing multiple
RCPT
commands.

DATA
-- Indicates that you are about to send the text (or body) of the
message. The message text must end with the following five letter sequence:
"\r\n.\r\n."
QUIT
-- Indicates that the
conversation is over.

EXPN

-- Indicates that you are using a mailing list.
HELP
-- Asks for help from the
mail server.
NOOP
-- Does nothing other than get a reponse from the mail server.
RSET
Aborts the current conversation.
SEND
-- Sends a message to a user's terminal
instead of a mailbox.
SAML
-- Sends a message to a user's terminal and to a user's
mailbox.
SOML
-- Sends a message to a user's terminal if they are logged on;
otherwise, sends the message to the user's mailbox.
TURN
-- Reverses the role of
client and server. This might be useful if the client program can also act as a server
and needs to receive mail from the remote computer.
VRFY
-- Verifies the existence and
user name of a given mail address. This command is not implemented in all mail servers.
And it can be blocked by firewalls.



Every command will receive a reply from the mail server in the
form of a three digit number followed by some text describing
the reply. For example,

250 OK


or

500 Syntax error, command unrecognized.


The complete list of reply codes is shown below: (you'll never see most of them if
you program your mail server correctly!!)

211
-- A system status or help reply.
214
-- Help
Message.
220
-- The server is ready.
221
-- The server is ending the conversation.
250
-- The requested action was completed.
251
-- The specified user is not local, but the server will forward the mail
message.
354
-- This is a reply to the DATA command. After getting this, start
sending the body of the mail message, ending with
"\r\n.\r\n."
421
-- The mail server will be shut down. Save the mail message and try again
later.
450
-- The mailbox that you are trying to reach is busy. Wait a little while and
try again.
451
-- The requested action was not done. Some error occurmiles in the mail server.
452
-- The requested action was not done. The mail server ran out of system
storage.
500
-- The last command contained a syntax error or the command line was too
long.
501
-- The parameters or arguments in the last command contained a syntax error.
502
-- The mail server has not implemented the last command.
503
-- The last command was sent out of sequence. For example, you might have
sent DATA before sending RECV.
504
-- One of the parameters of the last command has not been implemented by the
server.
550
-- The mailbox that you are trying to reach can't be found or you don't have
access rights.
551
-- The specified user is not local; part of the text of the message will
contain a forwarding address.
552
-- The mailbox that you are trying to reach has run out of space. Store the
message and try again tomorrow or in a few days-after the user gets a chance to delete
some messages.
553
-- The mail address that you specified was not syntactically correct.
554
-- The mail transaction has failed for unknown causes.



Now that you've seen all of the SMTP commands and reply codes,
let's see what a typical mail conversation might look like. In
the following conversation, the '>' lines are the SMTP commands
that your program issues. The '<' lines are the mail server's
replies.

>HELO
<250 sentinel.cs.cf.ac.uk Hello dave@cs.cf.ac.uk [X.X.X.X],pleased to meet you


>MAIL From: <(Ralph Martin)>
<250 <(Ralph Martin)>... Sender ok


>RCPT To: <dave@cs.cf.ac.uk>
<250 <dave@cs.cf.ac.uk>... Recipient ok


>DATA
<354 Enter mail, end with "." on a line by itself


>From: (Ralph Martin)
>Subject: Arrows
>This is line one.
>This is line two.
>.
<250 AAA14672 Message accepted for delivery


>QUIT
<221 sentinel.cs.cf.ac.uk closing connection



Some
of the SMTP commands are a bit more complex than others. In the
next few sections, the MAIL,
RCPT, and DATA
commands are discussed. You will also see how to react to undeliverable
mail.








The MAIL Command

The MAIL command tells the mail server to start a new conversation. It's also used to let the mail server know where to send a mail message to report errors. The syntax looks like this:

MAIL FROM:

If the mail server accepts the command, it will reply with a code of 250. Otherwise, the reply code will be greater than 400.

In the example shown previously

>MAIL From:<(dave@cs.cf.ac.uk)>

<250>... Sender ok

The reverse-path is different from the name given as the sender following the DATA command.

You can use this technique to give a mailing list or yourself an alias. For example, if you are maintaining a mailing list to your colleaguse, you might want the name that appears in the reader's mailer to be 'MyNickname' instead of your own name.


The RCPT Command

You tell the mail server who the recipient of your message is by using the RCPT command. You can send more than one RCPT command for multiple recipients. The server will respond with a code of 250 to each command. The syntax for the RCPT is:

RCPT TO:

Only one recipient can be named per RCPT command. If the recipient is not known to the mail server, the response code will be 550. You might also get a response code indicating that the recipient is not local to the server. If that is the case, you will get one of two responses back from the server:

* 251 User not local; will forward to -This reply means that the server will forward the message. The correct mail address is returned so that you can store it for future use.
* 551 User not local; please try -This reply means that the server won't forward the message. You need to issue another RCPT command with the new address.

The DATA Command

After starting the mail conversation and telling the server who the recipient or recipients are, you use the DATA command to send the body of the message. The syntax for the DATA command is very simple:

DATA

After you get the standard 354 response, send the body of the message followed by a line with a single period to indicate that the body is finished. When the end of message line is received, the server will respond with a 250 reply code.

Note The body of the message can also include several header items like Date, Subject, To, Cc, and From.




Reporting Undeliverable Mail

The mail server is responsible for reporting undeliverable mail, so you may not need to know too much about this topic. However, this information may come in handy if you ever write/run a list service or if you send a message from a temporary account.

An endless loop happens when an error notification message is sent to a non-existent mailbox. The server keeps trying to send a notification message to the reverse-path specified in the MAIL command.

The answer to this dilemma is to specify an empty reverse path in the MAIL command of a notification message like this:

MAIL FROM:<>

An entire mail session that delivers an error notification message might look like the following:

MAIL FROM:<>
250 ok
RCPT TO:<@HOST.COM@HOSTW.ARPA>
250 ok
DATA
354 send the mail data, end with .
Date: 12 May 99 10:55:51
From: dave@cs.cf.ac.uk
To: user@net.com
Subject: Problem delivering mail.


your message to fmiles@net.com was not
delivemiles.


net.com said this:
"550 No Such User"
.
250 ok




Using Perl to Send Mail

Enough theory let's see some actual Perl code.

The sendmail.pl program below does just this. It' basic operation is as follows:

(Some comments have been added to indicate changes that are needed for porting to some machines)

* Turn on the warning compiler option.
* Load the Socket.
* Turn on the strict pragma.
* Initialize $mail To which holds the recipient's mail address.
* Initialize $mailServer which holds the symbolic name of your mail server.
* Initialize $mailFrom which holds the originator's mail address.
* Initialize $realName which holds the text that appears in the From header field.
* Initialize $subject which holds the text that appears in the Subject header field.
* Initialize $body which holds the text of the letter.
* Declare a signal handler for the Interrupt signal. This handler will trap users hitting Ctrl+c or Ctrl+break.
* Get the protocol number for the tcp protocol and the port number for the smtp service. Recall: Windows 95 and NT do not implement the getprotobyname()or getservbyname() functions so default values are supplied.
* Initialize $serverAddr with the mail server's Internet address.
* The $length variable is tested to see if it is defined, if not, then the gethostbyname() function failed.
* Create a socket called SMTP using standard parameters.
* Initialize $packedFormat with format specifiers.
* Connect the socket to the port on the mail server.
* Change the socket to use unbuffer input/output. Normally, sends and receives are stomiles in an internal buffer before being sent to your script. This line of code eliminates the buffering steps.
* Create a temporary buffer. The buffer is temporary because it is local to the block surrounded by the curly brackets.
* Read two responses from the server. Some servers send two reponses when the connection is made. Your server may only send one response -- If so, delete one of the recv() calls.
* Send the HELO command. The sendSMTP() function will take care of reading the response.
* Send the MAIL command indicating where messages that the mail server sends back (like undeliverable mail messages) should be sent.
* Send the RCPT command to specify the recipient.
* Send the DATA command.
* Send the body of the letter. Note that no reponses are received from the mail server while the letter is sent.
* Send a line containing a single period indicating that you are finished sending the body of the letter.
* Send the QUIT command to end the conversation.
* Close the socket.
* Define the closeSocket() function which will act as a signal handler.
* Close the socket.
* Call die() to display a message and end the script.
* Define the send SMTP() function.
* Get the debug parameter.
* Get the smtp command from the parameter array.
* Send the smtp command to STDERR if the debug parameters were true.
* Send the smtp command to the mail server.
* Get the mail server's response. Send the response to STDERR if the debug parameter were true.
* Split the response into reply code and message, and return just the reply code.

The Perl code for sendmail.pl is as follows:

#!/usr/bin/perl -w


use Socket;
use strict;


my($mailTo) = 'dave@cs.cf.ac.uk';


my($mailServer) = 'mailhost2.cs.cf.ac.uk';


my($mailFrom) = 'dave@cs.cf.ac.uk';
my($realName) = "Ralph Martin";
my($subject) = 'Test';
my($body) = "Test Line One.\nTest Line Two.\n";


$main::SIG{'INT'} = 'closeSocket';


my($proto) = getprotobyname("tcp") || 6;
my($port) = getservbyname("SMTP", "tcp") || 25;
my($serverAddr) = (gethostbyname($mailServer))[4];


if (! defined($length)) {


die('gethostbyname failed.');
}


socket(SMTP, AF_INET(), SOCK_STREAM(), $proto)
or die("socket: $!");


$packFormat = 'S n a4 x8'; # Windows 95, SunOs 4.1+
#$packFormat = 'S n c4 x8'; # SunOs 5.4+ (Solaris 2)


connect(SMTP, pack($packFormat, AF_INET(), $port, $serverAddr))
or die("connect: $!");


select(SMTP); $| = 1; select(STDOUT); # use unbuffemiles i/o.


{
my($inpBuf) = '';


recv(SMTP, $inpBuf, 200, 0);
recv(SMTP, $inpBuf, 200, 0);
}


sendSMTP(1, "HELO\n");
sendSMTP(1, "MAIL From: <$mailFrom>\n");
sendSMTP(1, "RCPT To: <$mailTo>\n");
sendSMTP(1, "DATA\n");


send(SMTP, "From: $realName\n", 0);
send(SMTP, "Subject: $subject\n", 0);
send(SMTP, $body, 0);


sendSMTP(1, "\r\n.\r\n");
sendSMTP(1, "QUIT\n");


close(SMTP);


sub closeSocket { # close smtp socket on error
close(SMTP);
die("SMTP socket closed due to SIGINT\n");
}


sub sendSMTP {
my($debug) = shift;
my($buffer) = @_;


print STDERR ("> $buffer") if $debug;
send(SMTP, $buffer, 0);


recv(SMTP, $buffer, 200, 0);
print STDERR ("< $buffer") if $debug; return( (split(/ /, $buffer))[0] ); } This program displays: > HELO
<> MAIL From:
<>... Sender ok
> RCPT To:
<>... Recipient ok
> DATA
<>
.
<> QUIT
<>