COMP 361 Course Project : A SMTP Server in Java

In this programming assignment you will implement a SMTP server that receives mail from remote hosts. Your task is to:

  1. Program the SMTP interaction between the SMTP server and remote host.
  2. Save the mail content and attachments in system.

The Requirement

The SMTP server listens at port 25 for a remote connection. When a remote host connects to the server, the server spawns a new thread to serve this client. The parent process continues to listen port 25 for other connections.

In the thread, the client initiates the negotiation and the server replies to the client's request.

  1. The client first says HELLO to server and server replies.
  2. Client sends the sender address to the server, the server checks whether this is a valid email address.
  3. Client sends the receiver address to the server, the server must check whether the receiver address is a member of CS department, i.e. the domain should be "@cs.ust.hk", or otherwise, the server must reject this address.
  4. Client starts to send the mail content, this mail content may contain a MIME style message body and attachment.
  5. Server separates the data into plain-text message body and attachments. Conversion (base64 only) may be needed.
  6. Server creates a new directory and saves the plain-text message into "message.txt".
  7. If attachment is received, the attachment is saved according to the file name specified, or using a dummy name if the MIME header hasn't given a file name.
  8. Client may send another message by repeating step 2 to step 7, or terminate the SMTP connection.

During step 2 to step 8, if the client says HELLO again, the server needs to discard all the previous data (except the saved messages) and restarts from step 2. see RFC 821 and RFC 2821 for details.

To simplify the implementation, the server must understand the following SMTP commands, and answer them with appropriate reply codes, for the details of SMTP interaction, read RFC 821 and RFC 2821:

Command Reply Codes
HELO or EHLO250 500 501 503
MAIL FROM250 500 503
RCPT TO250 500 503
DATA354 451
QUIT 221

You only need to implement the following message header, if an unrecognized field is detected, treat the previous processed line(s) as message body and process the remaining content as message body:

Message Header FieldProcessing
From:no
To:no
Subject: yes
Date: yes
Content-Type: yes
Content-Transfer-Encoding: yes
MIME-Version:
yes
Message-ID:
no
Importance:
no
User-Agent:
no
field starts with "X"
no

A subset of MIME extension over SMTP is needed. This extension reserves the traditional SMTP commands and architecture, but it extends the capability of mail service to include items ssuch as such as file attachments, multimedia and richtext formatted email. You can look at RFC 2045 & RFC 2046 for details.

For the ease of programming, all file attachments only use Base64 or 7bit(plain-text) encoding. The Base64 class is already provided for you. This links to the Base64 class API homepage. If you want to use another Base64 java class from public domain, you are free to do so.

The MIME extension is capable to encapsulate 8-bit character as message text, for example, you can transfer Chinese characters as message text.

In this project, Java SDK Version 1.4.1 is used. Windows platform or UNIX platform are both accepted.

The Code

The program consists of four classes:
MailServer The Mail Server
MessageSave Save the message body to system
SMTPConnection Thread class for SMTP connection to client
Base64 Base64 encoder for MIME

You need to complete the code in the SMTPConnection class and MessageSave class so that in the end you will have a program that is capable of receiving  mail and storing  it in the system.  The code for the other two classes are provided.

The places where you need to complete the code have been marked with the comments /* Fill in */. Each of the places requires one or more lines of code.

The MailServer is the frontend of the SMTP server. When a new connection is established, MailServer creates a new object SMTPConnection for handling this client. SMTPConnection is the core part of the server, it negotiates with the client and responds to every request issued by the client. After the client  sends the whole message to the server, SMTPConnection creates another object called MessageSave and puts the message body, sender address and receiver address into it. MessageSave reads every line in the data and determines the message header, message body, MIME header and MIME body. if the message body or MIME body is base64 encoded, MessageSave calls Base64 for decoding. Afterwardsl, the message body is broken into the message body and attachments,  and saved into a new directory (directory name is generated by MessageSave.)

Hints

To make it easier to debug your program, do not, at first, include the code that opens the socket in  SMTPConnection, but use the following definitions for fromClient and toClient. This way, your program sends the commands to the terminal. Acting as the remote host, you will need to give the correct reply codes. When your program works, add the code to open the socket to the server.

	fromClient = new BufferedReader(new InputStreamReader(System.in));
	toClient = System.out;

The lines for opening and closing the socket, i.e., the lines connection = ... in the constructor and the line connection.close() in function close(), have been commented out by default.

Start by completing the method fetch() and parseHELO() You will need this function in many places.

In the function reply(), you should use the function writeBytes() to write the commands to the server. The advantage of using writeBytes() instead of write() is that the former automatically converts the strings to bytes which is what the server expects. Do not forget to terminate each command with the string CRLF.

In MessageSave class, you need to handle the message body received by the server; the simplest way to get it right is to investigate a real SMTP connection, or create a fake SMTP server and use Outlook or Eudora to send an email to your fake server, thus you can grab the raw data for testing. But remember to follow the RFC specification.

You need to identify message header and message body clearly, or otherwise, information may be lost and the whole message may be corrupted.

Don't use a huge file as attachment as the decoding time is long.

In  then laboratory, server cannot bind port 25 in UNIX platform; you need to use port number higher than 1024 for testing.