Users consider electronic mail the most important network service because they use it for interpersonal communications. Some applications are newer and fancier. Other applications consume more network bandwidth. Others are more important for the continued operation of the network. But email is the application people use to communicate with each other. It isn't very fancy, but it's vital.
TCP/IP provides a reliable, flexible email system built on a few basic protocols. These are: Simple Mail Transfer Protocol (SMTP), Post Office Protocol (POP), and Multipurpose Internet Mail Extensions (MIME). There are other TCP/IP mail protocols. Interactive Mail Access Protocol, defined in RFC 1176, is an interesting protocol designed to supplant POP. It provides remote text searches and message parsing features not found in POP. We will touch only briefly on IMAP. It and other protocols have some very interesting features, but they are not yet widely implemented.
Our coverage concentrates on the three protocols you are most likely to use building your network: SMTP, POP, and MIME. We start with SMTP, the foundation of all TCP/IP email systems.
SMTP is the TCP/IP mail delivery protocol. It moves mail across the Internet and across your local network. SMTP is defined in RFC 821, A Simple Mail Transfer Protocol. It runs over the reliable, connection-oriented service provided by Transmission Control Protocol (TCP), and it uses well-known port number 25. [7] Table 3.1 lists some of the simple, human-readable commands used by SMTP.
[7] Most standard TCP/IP applications are assigned a well-known port in the Assigned Numbers RFC, so that remote systems know how to connect the service.
Table 3.1: SMTP Commands |
||
Command |
Syntax |
Function |
Hello |
HELO < |
Identify sending
SMTP |
From |
MAIL FROM:< |
Sender address |
Recipient |
RCPT TO:< |
Recipient address |
Data |
DATA |
Begin a message |
Reset |
RSET |
Abort a message |
Verify |
VRFY < |
Verify a username |
Expand |
EXPN < |
Expand a mailing
list |
Help |
HELP [ |
Request online
help |
Quit |
QUIT |
End the SMTP
session |
SMTP is such a simple protocol you can literally do it yourself. telnet to port 25 on a remote host and type mail in from the command line using the SMTP commands. This technique is sometimes used to test a remote system's SMTP server, but we use it here to illustrate how mail is delivered between systems. The example below shows mail manually input from Daniel on peanut.nuts.com to Tyler on almond.nuts.com.
% telnet almond.nuts.com 25
Trying 172.16.12.1 ...
Connected to almond.nuts.com.
Escape character is '^]'.
220 almond Sendmail 4.1/1.41 ready at Tue, 29 Mar 94 17:21:26 EST
helo peanut.nuts.com
250 almond Hello peanut.nuts.com, pleased to meet you
mail from:<daniel@peanut.nuts.com>
250 <daniel@peanut.nuts.com>... Sender ok
rcpt to:<tyler@almond.nuts.com>
250 <tyler@almond.nuts.com>... Recipient ok
data
354 Enter mail, end with "." on a line by itself
Hi Tyler!
.
250 Mail accepted
quit
221 almond delivering mail
Connection closed by foreign host.
The user input is shown in bold type. All of the other lines are output from the system. This example shows how simple it is. A TCP connection is opened. The sending system identifies itself. The From address and the To address are provided. The message transmission begins with the DATA command and ends with a line that contains only a period (.). The session terminates with a QUIT command. Very simple, and very few commands are used.
There are other commands (SEND, SOML, SAML, and TURN) defined in RFC 821 that are optional and not widely implemented. Even some of the commands that are implemented are not commonly used. The commands HELP, VRFY, and EXPN are designed more for interactive use than for the normal machine-to-machine interaction used by SMTP. The following excerpt from a SMTP session shows how these odd commands work.
HELP
214-Commands:
214- HELO MAIL RCPT DATA RSET
214- NOOP QUIT HELP VRFY EXPN
214-For more info use "HELP <topic>".
214-For local information contact postmaster at this site.
214 End of HELP info
HELP RSET
214-RSET
214- Resets the system.
214 End of HELP info
VRFY <jane>
250 <jane@brazil.nuts.com>
VRFY <mac>
250 Kathy McCafferty <<mac>>
EXPN <admin>
250-<sara@pecan.nuts.com>
250 David Craig <<david>>
250-<tyler@nuts.com>
The HELP command prints out a summary of the commands implemented on the system. The HELP RSET command specifically requests information about the RSET command. Frankly, this help system isn't very helpful!
The VRFY and EXPN commands are more useful, but are often disabled for security reasons because they provide user account information that might be exploited by network intruders. The EXPN <admin> command asks for a listing of the email addresses in the mailing list admin, and that is what the system provides. The VRFY command asks for information about an individual instead of a mailing list. In the case of the VRFY <mac> command, mac is a local user account and the user's account information is returned. In the case of VRFY <jane>, jane is an alias in the /etc/aliases file. The value returned is the email address for jane found in that file. The three commands in this example are interesting, but rarely used. SMTP depends on the other commands to get the real work done.
SMTP provides direct end-to-end mail delivery. This is unusual. Most mail systems use store and forward protocols like UUCP and X.400 that move mail toward its destination one hop at a time, storing the complete message at each hop and then forwarding it on to the next system. The message proceeds in this manner until final delivery is made. Figure 3.3 illustrates both store and forward and direct delivery mail systems. The UUCP address clearly shows the path that the mail takes to its destination, while the SMTP mail address implies direct delivery. The address doesn't have anything to do with whether or not a system is store and forward or direct delivery. It just happens that UUCP provides an address that helps to illustrate this point.
Direct delivery allows SMTP to deliver mail without relying on intermediate hosts. If the delivery fails, the local system knows it right away. It can inform the user that sent the mail or queue the mail for later delivery without reliance on remote systems. The disadvantage of direct delivery is that it requires both systems to be fully capable of handling mail. Some systems cannot handle mail, particularly small systems such as PCs or mobile systems such as laptops. These systems are usually shut down at the end of the day and are frequently offline. Mail directed from a remote host fails with a "cannot connect" error when the local system is turned off or offline. To handle these cases, features in the DNS system are used to route the message to a mail server in lieu of direct delivery. The mail is then moved from the server to the client system when the client is back online. The protocol most TCP/IP networks use for this task is POP.
/**
* SMTPSession - Class for sending e-mails
using SMTP protocol.
*/
import java.io.*;
import java.net.*;
import java.util.*;
public class
SMTPSession
{
/** 15 sec. socket read timeout */
public static
final int SOCKET_READ_TIMEOUT = 15*1000;
private String host;
private int port;
private String recipient;
private String sender;
private String subject;
private String message;
protected Socket smtpSocket;
protected BufferedReader in;
protected OutputStreamWriter out;
/**
* Creates new SMTP session by given SMTP
host and port, recipient's email
* address, sender's email address, email
subject and email message text.
*/
public SMTPSession(String host,
int port, String recipient,
String sender, String subject, String message)
{
this.host = host;
this.port = port;
this.recipient = recipient;
this.message = message;
this.sender = sender;
this.subject = subject;
}
/**
* Creates new SMTP session by given SMTP
host, recipient's email address,
* sender's email address, email subject
and email message text. Assumes
* SMTP port is 25 (default for SMTP
service).
*/
public SMTPSession(String host,
String recipient,
String sender, String subject, String message)
{
this(host, 25, recipient, sender, subject, message);
}
/**
* Closes down the connection to SMTP
server (if open).
* Should be called if an exception is
raised during the SMTP session.
*/
public void
close()
{
try {
in.close();
out.close();
smtpSocket.close();
} catch (Exception ex) {
// Ignore the exception.
Probably the socket is not open.
}
}
/**
* Connects to the SMTP server and gets
input and output streams (in, out).
*/
protected void connect()
throws IOException
{
smtpSocket = new
Socket(host, port);
smtpSocket.setSoTimeout(SOCKET_READ_TIMEOUT);
in = new BufferedReader(new
InputStreamReader(smtpSocket.getInputStream()));
out = new OutputStreamWriter(smtpSocket.getOutputStream());
}
/**
* Sends given command and waits for a
response from server.
* @return response received from the
server.
*/
protected String
sendCommand(String commandString)
throws IOException
{
out.write(commandString + "\n");
out.flush();
String response = getResponse();
return response;
}
/**
* Sends given commandString to the server,
gets its reply and checks if
* it starts with expectedResponseStart.
If not, throws IOException with
* server's reply (which is unexpected).
*/
protected void doCommand(String commandString, char expectedResponseStart)
throws IOException
{
String response = sendCommand(commandString);
checkServerResponse(response, expectedResponseStart);
}
/**
* Checks if given server reply starts
with expectedResponseStart.
* If not, throws IOException with this
reply (because it is unexpected).
*/
protected void checkServerResponse(String response, char expectedResponseStart)
throws IOException
{
if (response.charAt(0) != expectedResponseStart)
throw new IOException(response);
}
/**
* Gets a response back from the server.
Handles multi-line responses
* (according to SMTP protocol) and
returns them as multi-line string.
* Each line of the server's reply
consists of 3-digit number followed
* by some text. If there is a '-'
immediately after the number, the SMTP
* response continues on the next line.
Otherwise it finished at this line.
*/
protected String getResponse()
throws IOException
{
String response = "";
String line;
do {
line = in.readLine();
if ((line == null) || (line.length() < 3))
{
// SMTP response lines
should at the very least have a 3-digit number
throw new IOException("Bad
response from server.");
}
response += line + "\n";
} while ((line.length()
> 3) && (line.charAt(3) == '-'));
return response;
}
/**
* Prepares and returns e-mail message
headers.
*/
protected String getMessageHeaders()
{
// Most header are less than 1024
characters long
String headers = "";
headers = headers + "Date:
" + new Date().toString() + "\n";
headers = headers + "Sender:
" + sender + "\n";
headers = headers + "From:
" + sender + "\n";
headers = headers + "To:
" + recipient + "\n";
headers = headers + "Subject:
" + subject + "\n";
return headers + "\n\n";
}
/**
* Sends a message using the SMTP
protocol.
*/
public
void sendMessage()
throws IOException
{
connect();
// After connecting, the SMTP
server will send a response string.
// Make sure it starts with a '2'
(reponses in the 200's are positive).
String response = getResponse();
checkServerResponse(response,'2');
// Introduce ourselves to the
SMTP server with a polite "HELO localhostname"
doCommand("HELO "
+ smtpSocket.getLocalAddress().toString(), '2');
// Tell the server who this message
is from
doCommand("MAIL FROM:
<" + sender + ">",
'2');
// Now tell the server who we
want to send a message to
doCommand("RCPT TO:
<" + recipient + ">",
'2');
// Okay, now send the mail
message. We expect a response beginning
// with '3' indicating that the
server is ready for data.
doCommand("DATA",
'3');
// Send the message headers
out.write(getMessageHeaders());
BufferedReader msgBodyReader = new
BufferedReader(new StringReader(message));
// Send each line of the message
String line;
while
((line=msgBodyReader.readLine()) != null) {
// If the line begins with a
".", put an extra "." in front of it.
if (line.startsWith("."))
out.write('.');
out.write(line + "\n");
}
// A "." on a line by
itself ends a message.
doCommand(".", '2');
// Message is sent. Close the
connection to the server
close();
}
}
/**
* SMTPSession example. Sends emails using
SMTP protocol.
* (c) 2002 by Svetlin Nakov
*/
import SMTPSession;
public class
SMTPClient
{
public static
void main(String[] args)
{
SMTPSession smtp = new
SMTPSession(
"localhost",
"recipient@mycompany.com",
"sender@mycompany.com",
"Some subject",
"... Message text ...");
try {
System.out.println("Sending e-mail...");
smtp.sendMessage();
System.out.println("E-mail sent.");
} catch (Exception e) {
smtp.close();
System.out.println("Can not send e-mail!");
e.printStackTrace();
}
}
}
There are two versions of POP in widespread use: POP2 and POP3. POP2 is defined in RFC 937 and POP3 is defined in RFC 1725. POP2 uses port 109 and POP3 uses port 110. These are incompatible protocols that use different commands, but they perform the same basic functions. The POP protocols verify the user's login name and password, and move the user's mail from the server to the user's local mail reader.
A sample POP2 session clearly illustrates how a POP protocol works. POP2 is a simple request/response protocol, and just as with SMTP, you can type POP2 commands directly into its well-known port (109) and observe their effect. Here's an example with the user input shown in bold type:
% telnet almond.nuts.com 109
Trying 172.16.12.1 ...
Connected to almond.nuts.com.
Escape character is '^]'.
+ POP2 almond POP2 Server at Wed 30-Mar-94 3:48PM-EST
HELO hunt WatsWatt
#3 ...(From folder 'NEWMAIL')
READ
=496
RETR
{The full text of message 1}
ACKD
=929
RETR
{The full text of message 2}
ACKD
=624
RETR
{The full text of message 3}
ACKD
=0
QUIT
+OK POP2 Server exiting (0 NEWMAIL messages left)
Connection closed by foreign host.
The HELO command provides the username and password for the account of the mailbox that is being retrieved. (This is the same username and password used to log into the mail server.) In response to the HELO command the server sends a count of the number of messages in the mailbox, three (#3) in our example. The READ command begins reading the mail. RETR retrieves the full text of the current message. ACKD acknowledges receipt of the message and deletes it from the server. After each acknowledgment the server sends a count of the number of bytes in the new message. If the byte count is zero (=0) it indicates that there are no more messages to be retrieved and the client ends the session with the QUIT command. Simple! Table 3.2 lists the full set of POP2 commands.
Table 3.2: POP2 Commands |
||
Command |
Syntax |
Function |
Hello |
HELO |
Identify user
account |
Folder |
FOLD |
Select mail folder |
Read |
READ [ |
Read mail,
optionally start with message |
Retrieve |
RETR |
Retrieve message |
Save |
ACKS |
Acknowledge and
save |
Delete |
ACKD |
Acknowledge and
delete |
Failed |
NACK |
Negative
acknowledgement |
Quit |
QUIT |
End the POP2
session |
The commands for POP3 are completely different from the commands used for POP2. Table 3.3 shows the set of POP3 commands defined in RFC 1725.
Table 3.3: POP3 Commands |
|
Command |
Function |
USER |
The user's account
name |
PASS |
The
user's password |
STAT |
Display the number
of unread messages/bytes |
RETR |
Retrieve message
number |
DELE |
Delete message
number |
LAST |
Display the number
of the last message accessed |
LIST [ |
Display the size
of message |
RSET |
Undelete all
messages; reset message number to 1 |
TOP |
Print the headers
and |
NOOP |
Do nothing |
QUIT |
End the POP3
session |
Despite the fact that these commands are different from those used by POP2, they can be used to perform similar functions. In the POP2 example we logged into the server and read and deleted three mail messages. Here's a similar session using POP3:
% telnet almond 110
Trying 172.16.12.1 ...
Connected to almond.nuts.com.
Escape character is '^]'.
+OK almond POP3 Server Process 3.3(1) at Mon 15-May-95 4:48PM-EDT
user hunt
+OK User name (hunt) ok. Password, please.
pass Watts?Watt?
+OK 3 messages in folder NEWMAIL (V3.3 Rev B04)
stat
+OK 3 459
retr 1
+OK 146 octets
The full text of message 1
dele 1
+OK message # 1 deleted
retr 2
+OK 155 octets
The full text of message 2
dele 2
+OK message # 2 deleted
retr 3
+OK 158 octets
The full text of message 3
dele 3
+OK message # 3 deleted
quit
+OK POP3 almond Server exiting (0 NEWMAIL messages left)
Connection closed by foreign host.
Naturally you don't really type these commands in yourself, but experiencing hands-on interaction with SMTP and POP gives you a clearer understanding of what these programs do and why they are needed.
/**
* POP3Session - Class for checking e-mail via
POP3 protocol.
*/
import java.io.*;
import java.net.*;
import java.util.*;
public class
POP3Session extends Object
{
/** 15 sec. socket read timeout */
public static
final int SOCKET_READ_TIMEOUT = 15*1000;
protected Socket pop3Socket;
protected BufferedReader in;
protected PrintWriter out;
private String host;
private int port;
private String userName;
private String password;
/**
* Creates new POP3 session by given POP3
host, username and password.
* Assumes POP3 port is 110 (default for
POP3 service).
*/
public POP3Session(String host,
String userName, String password)
{
this(host, 110, userName, password);
}
/**
* Creates new POP3 session by given POP3
host and port, username and password.
*/
public POP3Session(String host,
int port, String userName, String password)
{
this.host = host;
this.port = port;
this.userName = userName;
this.password = password;
}
/**
* Throws exception if given server
response if negative. According to POP3
* protocol, positive responses start with
a '+' and negative start with '-'.
*/
protected void checkForError(String response)
throws IOException
{
if (response.charAt(0) != '+')
throw new IOException(response);
}
/**
* @return the current number of messages
using the POP3 STAT command.
*/
public int
getMessageCount()
throws IOException
{
// Send STAT command
String response = doCommand("STAT");
// The format of the response is
+OK msg_count size_in_bytes
// We take the substring from
offset 4 (the start of the msg_count) and
// go up to the first space, then convert that string to a
number.
try {
String countStr = response.substring(4, response.indexOf(' ',
4));
int count = (new Integer(countStr)).intValue();
return count;
} catch (Exception e) {
throw new IOException("Invalid
response - " + response);
}
}
/**
* Get headers returns a list of message
numbers along with some sizing
* information, and possibly other
information depending on the server.
*/
public String[] getHeaders()
throws IOException
{
doCommand("LIST");
return
getMultilineResponse();
}
/**
* Gets header returns the message number
and message size for a particular
* message number. It may also contain
other information.
*/
public String getHeader(String
messageId)
throws IOException
{
String response = doCommand("LIST
" + messageId);
return response;
}
/**
* Retrieves the entire text of a message
using the POP3 RETR command.
*/
public String getMessage(String
messageId)
throws IOException
{
doCommand("RETR "
+ messageId);
String[] messageLines = getMultilineResponse();
StringBuffer message = new
StringBuffer();
for (int i=0;
i<messageLines.length; i++) {
message.append(messageLines[i]);
message.append("\n");
}
return new String(message);
}
/**
* Retrieves the first <linecount> lines
of a message using the POP3 TOP
* command. Note: this command may not be
available on all servers. If
* it isn't available, you'll get an
exception.
*/
public String[]
getMessageHead(String messageId, int lineCount)
throws IOException
{
doCommand("TOP "
+ messageId + " " + lineCount);
return
getMultilineResponse();
}
/**
* Deletes a particular message with DELE
command.
*/
public void
deleteMessage(String messageId)
throws IOException
{
doCommand("DELE "
+ messageId);
}
/**
* Initiates a graceful exit by sending
QUIT command.
*/
public void
quit()
throws IOException
{
doCommand("QUIT");
}
/**
* Connects to the POP3 server and logs on
it
* with the USER and PASS commands.
*/
public void
connectAndAuthenticate()
throws IOException
{
// Make the connection
pop3Socket = new
Socket(host, port);
pop3Socket.setSoTimeout(SOCKET_READ_TIMEOUT);
in = new BufferedReader(new
InputStreamReader(pop3Socket.getInputStream()));
out = new PrintWriter(new
OutputStreamWriter(pop3Socket.getOutputStream()));
// Receive the welcome message
String response = in.readLine();
checkForError(response);
// Send a USER command to
authenticate
doCommand("USER "
+ userName);
// Send a PASS command to finish
authentication
doCommand("PASS "
+ password);
}
/**
* Closes down the connection to POP3
server (if open).
* Should be called if an exception is
raised during the POP3 session.
*/
public void
close()
{
try {
in.close();
out.close();
pop3Socket.close();
} catch (Exception ex) {
// Ignore the exception.
Probably the socket is not open.
}
}
/**
* Sends a POP3 command and retrieves the
response. If the response is
* negative (begins with '-'), throws an
IOException with received response.
*/
protected String
doCommand(String command)
throws IOException
{
out.println(command);
out.flush();
String response = in.readLine();
checkForError(response);
return response;
}
/**
* Retrieves a multi-line POP3 response.
If a line contains "." by itself,
* it is the end of the response. If a
line starts with a ".", it should
* really have two "."'s. We
strip off the leading ".". If a line does not
* start with ".", there should
be at least one line more.
*/
protected String[]
getMultilineResponse()
throws IOException
{
ArrayList lines = new
ArrayList();
while (true) {
String line = in.readLine();
if (line == null) {
// Server closed
connection
throw new IOException("Server
unawares closed the connection.");
}
if (line.equals(".")) {
// No more lines in the
server response
break;
}
if ((line.length() >
0) && (line.charAt(0) == '.')) {
// The line starts with a
"." - strip it off.
line = line.substring(1);
}
// Add read line to the list
of lines
lines.add(line);
}
String response[] = new
String[lines.size()];
lines.toArray(response);
return response;
}
}
/**
* POP3Session example. Receives email using
POP3 protocol.
*/
import POP3Session;
import
java.util.StringTokenizer;
public class
POP3Client
{
public static
void main(String[] args)
{
POP3Session pop3 = new
POP3Session("pop.mycompany.com", "username", "password");
try {
System.out.println("Connecting to POP3 server...");
pop3.connectAndAuthenticate();
System.out.println("Connected to POP3 server.");
int messageCount =
pop3.getMessageCount();
System.out.println("\nWaiting massages on POP3 server : " +
messageCount);
String[] messages = pop3.getHeaders();
for (int i=0;
i<messages.length; i++) {
StringTokenizer messageTokens = new StringTokenizer(messages[i]);
String messageId = messageTokens.nextToken();
String messageSize = messageTokens.nextToken();
String messageBody = pop3.getMessage(messageId);
System.out.println(
"\n-------------------- messsage " +
messageId +
", size=" + messageSize + " --------------------");
System.out.print(messageBody);
System.out.println("-------------------- end of message " +
messageId + " --------------------");
}
} catch (Exception e) {
pop3.close();
System.out.println("Can not receive e-mail!");
e.printStackTrace();
}
}
}