//package rblasch.Mail;
import java.io.*;
import java.net.*;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Vector;
/**
* This class implements the SMTP protocol.
*
*
This implementation is very limited. The only way to send is
* simpleSend
; see method description for details.
*
* The expected message format is: lines separated by newline
* '\n'
characters.
*
* You may turn on the debug flag via debugMode(true)
; debug
* output goes to stderr.
*
* @see RFC821, RFC974, RFC822
* @author Ronald Blaschke <rblasch@cs.bgsu.edu>
* @version 1.0
*/
public class SmtpMail
{
/**
* Create new Object.
*/
public SmtpMail()
{
super();
rcpts = new Vector();
}
/**
* Sets the debug mode.
*
* @param debug true for debug mode, otherwise false (default).
*/
public void debugMode(boolean debug)
{
this.debug = debug;
}
/**
* Outputs the String to stderr if debug is turned on.
*
* @param t String to trace.
*/
protected void trace(String t)
{
if (debug) {
System.err.println("D:" + t);
}
}
/**
* Minimalistic send method.
*
* @param serverName Address of SMTP server (numerical or by name)
* @param port SMTP port number (should be 25)
* @param sender Address of mail sender
* @param receiver Address of mail receiver
* @param subject Subject of message
* @param msg The message; line seperator is '\n'
.
* @exception UnknownHostException Unknown host
* @exception IOException Error while communicating
* @exception SmtpException Error returned by SMTP server
*
* @see #send
*/
public void simpleSend(String serverName, int port, MailAddress sender,
MailAddress receiver, String subject, String msg)
throws SmtpException, UnknownHostException, IOException
{
serverAddress = InetAddress.getByName(serverName);
serverPort = port;
this.sender = sender;
rcpts.addElement(receiver);
this.subject = subject;
message = msg;
send();
}
/**
* Sends a message to the SMTP server.
*
* Sending is done via the following sequence:
*
* - Connect to server
*
- HELO
*
- MAIL FROM
*
- RCPT TO
*
- DATA
*
- ...data...
*
- QUIT
*
*
* The following limitations apply:
*
* - No retry logic; all failures are considered as errors, although some
* may be recovered.
*
- Message is sent to the first receiver only.
*
- Header should contain more that
From
, To
* and Subject
(eg DATE, MIME type).
*
*
* @exception IOException Error while communicating
* @exception SmtpException Error returned by SMTP server
*/
protected void send()
throws SmtpException, IOException
{
Socket sock = null;
try {
// setup connection
trace("trying to connect to server");
sock = new Socket(serverAddress, serverPort);
BufferedReader in = new BufferedReader(new InputStreamReader(sock.getInputStream()));
PrintWriter out = new PrintWriter(sock.getOutputStream());
trace("connected");
int rc = getResponse(in);
if (rc/100 != 2) {
throw new SmtpException("CONNECT", rc, lastResponseText);
}
// connected, be nice and say hello
sendCommand(out, "HELO " + InetAddress.getLocalHost().getHostName(),
in, 2);
sendCommand(out, "MAIL from: " + sender.getAddress(),
in, 2);
// XXX only implemented sending to first rcpt
sendCommand(out, "RCPT to: " + ((MailAddress)(rcpts.firstElement())).getAddress(), in, 2);
sendCommand(out, "DATA", in, 3);
sendData(out);
rc = getResponse(in);
if (rc/100 != 2) {
throw new SmtpException("SEND-DATA", rc, lastResponseText);
}
sendCommand(out, "QUIT", in, 2);
}
catch (IOException e1) {
if (sock != null) sock.close();
/*re*/throw e1;
}
catch (SmtpException e2) {
if (sock != null) sock.close();
/*re*/throw e2;
}
}
/**
* Sends a single command to the SMTP server.
*
* @param out Outputstream to SMTP server
* @param cmd Command to send, without trailing <CR/LF>
.
*/
protected void sendCommand(PrintWriter out, String cmd)
{
trace("sending command: " + cmd);
out.write(cmd);
out.write("\r\n");
out.flush();
}
/**
* Sends a command and throws a SMTPException if the returncode is not in the
* expected class, ie the first digit of the returncode matches.
*
* @param out Outputstream to SMTP server
* @param cmd Command to send, without trailing <CR/LF>
.
* @param in Inputstream to SMTP server
* @param OkClass Class of returncodes that is ok
* @exception SmtpException Thrown if answer not in expected returncode
* class.
* @exception IOException Error while communicating
*/
protected void sendCommand(PrintWriter out, String cmd, BufferedReader in,
int OkClass)
throws SmtpException, IOException
{
sendCommand(out, cmd);
int rc = getResponse(in);
if (rc/100 != OkClass) {
throw new SmtpException(cmd, rc, lastResponseText);
}
}
/**
* Sends the data to the SMTP server.
*
* First a simple header, containing the From and To
* field is sent. The follows the Subject. After that we convert
* the message to the data format and send it. We finish the data with a
* <CR/LF>.<CR/LF>
sequence.
*
* @param out Outputstream to SMTP server
*/
protected void sendData(PrintWriter out)
{
// send header
out.write("From: " + sender + "\r\n");
out.write("To: " + rcpts.firstElement() + "\r\n");
out.write("Subject: " + subject + "\r\n");
out.write("\r\n"); // end header
// send message text
String data = msg2data(message);
trace(data);
out.write(data);
// end data with .
trace(".");
out.write("\r\n.\r\n");
out.flush();
}
/**
* Converts a message to the data format.
*
* The expected message format is: lines separated by newline
* ('\n'
) characters. The SMTP data format is: lines separated
* by <CR/LF>
; if a message line begins with a period
* another period is added in front of this line.
*
* @param msg Message to convert
* @return The converted message
*/
protected String msg2data(String msg)
{
StringBuffer buff = new StringBuffer();
String line;
int start=0;
int end=0;
if (msg != null) {
buff.ensureCapacity(msg.length()+100);
do {
end = msg.indexOf('\n', start);
if (end == -1) {
line = msg.substring(start);
}
else {
line = msg.substring(start, end);
end++; // skip newline character
}
if (line.length() > 0 && line.charAt(0) == '.') {
buff.append('.');
}
buff.append(line);
if (end != -1) {
buff.append("\r\n");
}
start = end;
} while (end != -1);
}
return buff.toString();
}
/**
* Gets a response from the SMTP server.
*
* Each line begins with a returncode, followed by either a space or a
* dash ('-'
). A dash indicates that there are following lines,
* a space that this is the last line. The rest of a line is addition text.
* Lines are separated by <CR/LF>
.
*
* @param in Inputstream to SMTP server
* @return Returncode from SMTP server
* @exception IOException Error while reading from server
*/
protected int getResponse(BufferedReader in)
throws IOException
{
int responseCode;
boolean moreLines;
String line;
StringBuffer text = new StringBuffer();
do {
// check if there are more lines and, if yes, skip them
line = in.readLine();
trace("response: " + line);
moreLines = (line.charAt(3) == '-');
text.append(line.substring(4, line.length()));
} while (moreLines);
// last line; chars 0-2 contain returncode, char 3 is space, rest is additional text
responseCode = Integer.parseInt(line.substring(0, 3));
// store last response
lastResponseText = text.toString();
return responseCode;
}
/**
* The internet address of the SMTP server
*/
private InetAddress serverAddress = null;
/**
* The port on the host the SMTP server is running.
*/
private int serverPort = -1;
/**
* Sender address.
*/
private MailAddress sender = null;
/**
* Receivers.
*/
private Vector rcpts = null;
/**
* Mail subject.
*/
private String subject = null;
/**
* The message. The expected message format is: lines separated by newline
* '\n'
characters.
*/
private String message = null;
/**
* Text of the last received response of server.
*/
private String lastResponseText = null;
/**
* Debugging?
*/
private boolean debug = false;
}