SMTPTransportpublic class SMTPTransport extends Transport This class implements the Transport abstract class using SMTP for
message submission and transport.
See the com.sun.mail.smtp package
documentation for further information on the SMTP protocol provider.
This class includes many protected methods that allow a subclass to
extend this class and add support for non-standard SMTP commands.
The {@link #issueCommand} and {@link #sendCommand} methods can be
used to send simple SMTP commands. Other methods such as the
{@link #mailFrom} and {@link #data} methods can be overridden to
insert new commands before or after the corresponding SMTP commands.
For example, a subclass could do this to send the XACT command
before sending the DATA command:
protected OutputStream data() throws MessagingException {
if (supportsExtension("XACCOUNTING"))
issueCommand("XACT", 25);
return super.data();
}
|
Fields Summary |
---|
private String | name | private int | defaultPort | private boolean | isSSL | private MimeMessage | message | private Address[] | addresses | private Address[] | validSentAddr | private Address[] | validUnsentAddr | private Address[] | invalidAddr | private boolean | sendPartiallyFailed | private MessagingException | exception | private SMTPOutputStream | dataStream | private Hashtable | extMap | private boolean | quitWait | private String | saslRealm | private boolean | reportSuccess | private boolean | useStartTLS | private boolean | useRset | private PrintStream | out | private String | localHostName | private String | lastServerResponse | private int | lastReturnCode | private static final String[] | ignoreListHeaders that should not be included when sending | private static final byte[] | CRLF | private static final String | UNKNOWN | private DigestMD5 | md5support | private BufferedInputStream | serverInput | private LineInputStream | lineInputStream | private OutputStream | serverOutput | private Socket | serverSocket | private static char[] | hexchar |
Constructors Summary |
---|
public SMTPTransport(Session session, URLName urlname)Constructor that takes a Session object and a URLName
that represents a specific SMTP server. // place holder
this(session, urlname, "smtp", 25, false);
| protected SMTPTransport(Session session, URLName urlname, String name, int defaultPort, boolean isSSL)Constructor used by this class and by SMTPSSLTransport subclass.
super(session, urlname);
if (urlname != null)
name = urlname.getProtocol();
this.name = name;
this.defaultPort = defaultPort;
this.isSSL = isSSL;
out = session.getDebugOut();
// setting mail.smtp.quitwait to false causes us to not wait for the
// response from the QUIT command
String s = session.getProperty("mail." + name + ".quitwait");
quitWait = s == null || s.equalsIgnoreCase("true");
// mail.smtp.reportsuccess causes us to throw an exception on success
s = session.getProperty("mail." + name + ".reportsuccess");
reportSuccess = s != null && s.equalsIgnoreCase("true");
// mail.smtp.starttls.enable enables use of STARTTLS command
s = session.getProperty("mail." + name + ".starttls.enable");
useStartTLS = s != null && s.equalsIgnoreCase("true");
// mail.smtp.userset causes us to use RSET instead of NOOP
// for isConnected
s = session.getProperty("mail." + name + ".userset");
useRset = s != null && s.equalsIgnoreCase("true");
|
Methods Summary |
---|
protected void | checkConnected()Check if we're in the connected state. Don't bother checking
whether the server is still alive, that will be detected later.
if (!super.isConnected())
throw new IllegalStateException("Not connected");
| public synchronized void | close()Close the Transport and terminate the connection to the server.
if (!super.isConnected()) // Already closed.
return;
try {
if (serverSocket != null) {
sendCommand("QUIT");
if (quitWait) {
int resp = readServerResponse();
if (resp != 221 && resp != -1)
out.println("DEBUG SMTP: QUIT failed with " + resp);
}
}
} finally {
closeConnection();
}
| private void | closeConnection()
try {
if (serverSocket != null)
serverSocket.close();
} catch (IOException ioex) { // shouldn't happen
throw new MessagingException("Server Close Failed", ioex);
} finally {
serverSocket = null;
serverOutput = null;
serverInput = null;
lineInputStream = null;
if (super.isConnected()) // only notify if already connected
super.close();
}
| public synchronized void | connect(java.net.Socket socket)Start the SMTP protocol on the given socket, which was already
connected by the caller. Useful for implementing the SMTP ATRN
command (RFC 2645) where an existing connection is used when
the server reverses roles and becomes the client.
serverSocket = socket;
super.connect();
| private boolean | convertTo8Bit(javax.mail.internet.MimePart part)If the Part is a text part and has a Content-Transfer-Encoding
of "quoted-printable" or "base64", and it obeys the rules for
"8bit" encoding, change the encoding to "8bit". If the part is
a multipart, recursively process all its parts.
boolean changed = false;
try {
if (part.isMimeType("text/*")) {
String enc = part.getEncoding();
if (enc != null && (enc.equalsIgnoreCase("quoted-printable") ||
enc.equalsIgnoreCase("base64"))) {
InputStream is = part.getInputStream();
if (is8Bit(is)) {
/*
* If the message was created using an InputStream
* then we have to extract the content as an object
* and set it back as an object so that the content
* will be re-encoded.
*
* If the message was not created using an InputStream,
* the following should have no effect.
*/
part.setContent(part.getContent(),
part.getContentType());
part.setHeader("Content-Transfer-Encoding", "8bit");
changed = true;
}
}
} else if (part.isMimeType("multipart/*")) {
MimeMultipart mp = (MimeMultipart)part.getContent();
int count = mp.getCount();
for (int i = 0; i < count; i++) {
if (convertTo8Bit((MimePart)mp.getBodyPart(i)))
changed = true;
}
}
} catch (IOException ioex) {
// any exception causes us to give up
} catch (MessagingException mex) {
// any exception causes us to give up
}
return changed;
| protected java.io.OutputStream | data()Send the DATA command to the SMTP host and return
an OutputStream to which the data is to be written.
assert Thread.holdsLock(this);
issueSendCommand("DATA", 354);
dataStream = new SMTPOutputStream(serverOutput);
return dataStream;
| protected boolean | ehlo(java.lang.String domain)Issue the EHLO command.
Collect the returned list of service extensions.
String cmd;
if (domain != null)
cmd = "EHLO " + domain;
else
cmd = "EHLO";
sendCommand(cmd);
int resp = readServerResponse();
if (resp == 250) {
// extract the supported service extensions
BufferedReader rd =
new BufferedReader(new StringReader(lastServerResponse));
String line;
extMap = new Hashtable();
try {
boolean first = true;
while ((line = rd.readLine()) != null) {
if (first) { // skip first line which is the greeting
first = false;
continue;
}
if (line.length() < 5)
continue; // shouldn't happen
line = line.substring(4); // skip response code
int i = line.indexOf(' ");
String arg = "";
if (i > 0) {
arg = line.substring(i + 1);
line = line.substring(0, i);
}
if (debug)
out.println("DEBUG SMTP: Found extension \"" +
line + "\", arg \"" + arg + "\"");
extMap.put(line.toUpperCase(Locale.ENGLISH), arg);
}
} catch (IOException ex) { } // can't happen
}
return resp == 250;
| private void | expandGroups()Expand any group addresses.
Vector groups = null;
for (int i = 0; i < addresses.length; i++) {
InternetAddress a = (InternetAddress)addresses[i];
if (a.isGroup()) {
if (groups == null) {
// first group, catch up with where we are
groups = new Vector();
for (int k = 0; k < i; k++)
groups.addElement(addresses[k]);
}
// parse it and add each individual address
try {
InternetAddress[] ia = a.getGroup(true);
if (ia != null) {
for (int j = 0; j < ia.length; j++)
groups.addElement(ia[j]);
} else
groups.addElement(a);
} catch (ParseException pex) {
// parse failed, add the whole thing
groups.addElement(a);
}
} else {
// if we've started accumulating a list, add this to it
if (groups != null)
groups.addElement(a);
}
}
// if we have a new list, convert it back to an array
if (groups != null) {
InternetAddress[] newa = new InternetAddress[groups.size()];
groups.copyInto(newa);
addresses = newa;
}
| protected void | finalize()
super.finalize();
try {
closeConnection();
} catch (MessagingException mex) { } // ignore it
| protected void | finishData()Terminate the sent data.
assert Thread.holdsLock(this);
dataStream.ensureAtBOL();
issueSendCommand(".", 250);
| public java.lang.String | getExtensionParameter(java.lang.String ext)Return the parameter the server provided for the specified
service extension, or null if the extension isn't supported.
return extMap == null ? null :
(String)extMap.get(ext.toUpperCase(Locale.ENGLISH));
| public synchronized int | getLastReturnCode()Return the return code from the last response we got from the server.
return lastReturnCode;
| public synchronized java.lang.String | getLastServerResponse()Return the last response we got from the server.
A failed send is often followed by an RSET command,
but the response from the RSET command is not saved.
Instead, this returns the response from the command
before the RSET command.
return lastServerResponse;
| public synchronized java.lang.String | getLocalHost()Get the name of the local host, for use in the EHLO and HELO commands.
The property mail.smtp.localhost overrides mail.smtp.localaddress,
which overrides what InetAddress would tell us.
try {
// get our hostname and cache it for future use
if (localHostName == null || localHostName.length() <= 0)
localHostName =
session.getProperty("mail." + name + ".localhost");
if (localHostName == null || localHostName.length() <= 0)
localHostName =
session.getProperty("mail." + name + ".localaddress");
if (localHostName == null || localHostName.length() <= 0) {
InetAddress localHost = InetAddress.getLocalHost();
localHostName = localHost.getHostName();
// if we can't get our name, use local address literal
if (localHostName == null)
// XXX - not correct for IPv6
localHostName = "[" + localHost.getHostAddress() + "]";
}
} catch (UnknownHostException uhex) {
}
return localHostName;
| private synchronized com.sun.mail.smtp.DigestMD5 | getMD5()
if (md5support == null)
md5support = new DigestMD5(debug ? out : null);
return md5support;
| public synchronized boolean | getReportSuccess()Should we report even successful sends by throwing an exception?
If so, a SendFailedException will always be thrown and
an {@link com.sun.mail.smtp.SMTPAddressSucceededException
SMTPAddressSucceededException} will be included in the exception
chain for each successful address, along with the usual
{@link com.sun.mail.smtp.SMTPAddressFailedException
SMTPAddressFailedException} for each unsuccessful address.
return reportSuccess;
| public synchronized java.lang.String | getSASLRealm()Gets the SASL realm to be used for DIGEST-MD5 authentication.
if (saslRealm == UNKNOWN) {
saslRealm = session.getProperty("mail." + name + ".sasl.realm");
if (saslRealm == null) // try old name
saslRealm = session.getProperty("mail." + name + ".saslrealm");
}
return saslRealm;
| public synchronized boolean | getStartTLS()Should we use the STARTTLS command to secure the connection
if the server supports it?
return useStartTLS;
| public synchronized boolean | getUseRset()Should we use the RSET command instead of the NOOP command
in the @{link #isConnected isConnected} method?
return useRset;
| protected void | helo(java.lang.String domain)Issue the HELO command.
if (domain != null)
issueCommand("HELO " + domain, 250);
else
issueCommand("HELO", 250);
| private void | initStreams()
Properties props = session.getProperties();
PrintStream out = session.getDebugOut();
boolean debug = session.getDebug();
String s = props.getProperty("mail.debug.quote");
boolean quote = s != null && s.equalsIgnoreCase("true");
TraceInputStream traceInput =
new TraceInputStream(serverSocket.getInputStream(), out);
traceInput.setTrace(debug);
traceInput.setQuote(quote);
TraceOutputStream traceOutput =
new TraceOutputStream(serverSocket.getOutputStream(), out);
traceOutput.setTrace(debug);
traceOutput.setQuote(quote);
serverOutput =
new BufferedOutputStream(traceOutput);
serverInput =
new BufferedInputStream(traceInput);
lineInputStream = new LineInputStream(serverInput);
| private boolean | is8Bit(java.io.InputStream is)Check whether the data in the given InputStream follows the
rules for 8bit text. Lines have to be 998 characters or less
and no NULs are allowed. CR and LF must occur in pairs but we
don't check that because we assume this is text and we convert
all CR/LF combinations into canonical CRLF later.
int b;
int linelen = 0;
boolean need8bit = false;
try {
while ((b = is.read()) >= 0) {
b &= 0xff;
if (b == '\r" || b == '\n")
linelen = 0;
else if (b == 0)
return false;
else {
linelen++;
if (linelen > 998) // 1000 - CRLF
return false;
}
if (b > 0x7f)
need8bit = true;
}
} catch (IOException ex) {
return false;
}
if (debug && need8bit)
out.println("DEBUG SMTP: found an 8bit part");
return need8bit;
| public synchronized boolean | isConnected()Check whether the transport is connected. Override superclass
method, to actually ping our server connection.
if (!super.isConnected())
// if we haven't been connected at all, don't bother with NOOP
return false;
try {
// sendmail may respond slowly to NOOP after many requests
// so if mail.smtp.userset is set we use RSET instead of NOOP.
if (useRset)
sendCommand("RSET");
else
sendCommand("NOOP");
int resp = readServerResponse();
// NOOP should return 250 on success, however, SIMS 3.2 returns
// 200, so we work around it.
//
// Hotmail doesn't implement the NOOP command at all so assume
// any kind of response means we're still connected.
// That is, any response except 421, which means the server
// is shutting down the connection.
//
if (resp >= 0 && resp != 421) {
return true;
} else {
try {
closeConnection();
} catch (MessagingException mex) { } // ignore it
return false;
}
} catch (Exception ex) {
try {
closeConnection();
} catch (MessagingException mex) { } // ignore it
return false;
}
| private boolean | isNotLastLine(java.lang.String line)
return line != null && line.length() >= 4 && line.charAt(3) == '-";
| public synchronized void | issueCommand(java.lang.String cmd, int expect)Send the command to the server. If the expected response code
is not received, throw a MessagingException.
sendCommand(cmd);
// if server responded with an unexpected return code,
// throw the exception, notifying the client of the response
if (readServerResponse() != expect)
throw new MessagingException(lastServerResponse);
| private void | issueSendCommand(java.lang.String cmd, int expect)Issue a command that's part of sending a message.
sendCommand(cmd);
// if server responded with an unexpected return code,
// throw the exception, notifying the client of the response
int ret;
if ((ret = readServerResponse()) != expect) {
// assume message was not sent to anyone,
// combine valid sent & unsent addresses
int vsl = validSentAddr == null ? 0 : validSentAddr.length;
int vul = validUnsentAddr == null ? 0 : validUnsentAddr.length;
Address[] valid = new Address[vsl + vul];
if (vsl > 0)
System.arraycopy(validSentAddr, 0, valid, 0, vsl);
if (vul > 0)
System.arraycopy(validUnsentAddr, 0, valid, vsl, vul);
validSentAddr = null;
validUnsentAddr = valid;
if (debug)
out.println("DEBUG SMTP: got response code " + ret +
", with response: " + lastServerResponse);
String _lsr = lastServerResponse; // else rset will nuke it
int _lrc = lastReturnCode;
if (serverSocket != null) // hasn't already been closed
issueCommand("RSET", 250);
lastServerResponse = _lsr; // restore, for get
lastReturnCode = _lrc;
throw new SMTPSendFailedException(cmd, ret, lastServerResponse,
exception, validSentAddr, validUnsentAddr, invalidAddr);
}
| protected void | mailFrom()Issue the MAIL FROM: command to start sending a message.
Gets the sender's address in the following order:
- SMTPMessage.getEnvelopeFrom()
- mail.smtp.from property
- From: header in the message
- System username using the
InternetAddress.getLocalAddress() method
String from = null;
if (message instanceof SMTPMessage)
from = ((SMTPMessage)message).getEnvelopeFrom();
if (from == null || from.length() <= 0)
from = session.getProperty("mail." + name + ".from");
if (from == null || from.length() <= 0) {
Address[] fa;
Address me;
if (message != null && (fa = message.getFrom()) != null &&
fa.length > 0)
me = fa[0];
else
me = InternetAddress.getLocalAddress(session);
if (me != null)
from = ((InternetAddress)me).getAddress();
else
throw new MessagingException(
"can't determine local email address");
}
String cmd = "MAIL FROM:" + normalizeAddress(from);
// request delivery status notification?
if (supportsExtension("DSN")) {
String ret = null;
if (message instanceof SMTPMessage)
ret = ((SMTPMessage)message).getDSNRet();
if (ret == null)
ret = session.getProperty("mail." + name + ".dsn.ret");
// XXX - check for legal syntax?
if (ret != null)
cmd += " RET=" + ret;
}
/*
* If an RFC 2554 submitter has been specified, and the server
* supports the AUTH extension, include the AUTH= element on
* the MAIL FROM command.
*/
if (supportsExtension("AUTH")) {
String submitter = null;
if (message instanceof SMTPMessage)
submitter = ((SMTPMessage)message).getSubmitter();
if (submitter == null)
submitter = session.getProperty("mail." + name + ".submitter");
// XXX - check for legal syntax?
if (submitter != null) {
try {
String s = xtext(submitter);
cmd += " AUTH=" + s;
} catch (IllegalArgumentException ex) {
if (debug)
out.println("DEBUG SMTP: ignoring invalid submitter: " +
submitter + ", Exception: " + ex);
}
}
}
/*
* Have any extensions to the MAIL command been specified?
*/
String ext = null;
if (message instanceof SMTPMessage)
ext = ((SMTPMessage)message).getMailExtension();
if (ext == null)
ext = session.getProperty("mail." + name + ".mailextension");
if (ext != null && ext.length() > 0)
cmd += " " + ext;
issueSendCommand(cmd, 250);
| private java.lang.String | normalizeAddress(java.lang.String addr)
if ((!addr.startsWith("<")) && (!addr.endsWith(">")))
return "<" + addr + ">";
else
return addr;
| private void | openServer(java.lang.String server, int port)Connect to server on port and start the SMTP protocol.
if (debug)
out.println("DEBUG SMTP: trying to connect to host \"" + server +
"\", port " + port + ", isSSL " + isSSL);
try {
Properties props = session.getProperties();
serverSocket = SocketFetcher.getSocket(server, port,
props, "mail." + name, isSSL);
// socket factory may've chosen a different port,
// update it for the debug messages that follow
port = serverSocket.getPort();
initStreams();
int r = -1;
if ((r = readServerResponse()) != 220) {
serverSocket.close();
serverSocket = null;
serverOutput = null;
serverInput = null;
lineInputStream = null;
if (debug)
out.println("DEBUG SMTP: could not connect to host \"" +
server + "\", port: " + port +
", response: " + r + "\n");
throw new MessagingException(
"Could not connect to SMTP host: " + server +
", port: " + port +
", response: " + r);
} else {
if (debug)
out.println("DEBUG SMTP: connected to host \"" +
server + "\", port: " + port + "\n");
}
} catch (UnknownHostException uhex) {
throw new MessagingException("Unknown SMTP host: " + server, uhex);
} catch (IOException ioe) {
throw new MessagingException("Could not connect to SMTP host: " +
server + ", port: " + port, ioe);
}
| private void | openServer()Start the protocol to the server on serverSocket,
assumed to be provided and connected by the caller.
int port = -1;
String server = "UNKNOWN";
try {
port = serverSocket.getPort();
server = serverSocket.getInetAddress().getHostName();
if (debug)
out.println("DEBUG SMTP: starting protocol to host \"" +
server + "\", port " + port);
initStreams();
int r = -1;
if ((r = readServerResponse()) != 220) {
serverSocket.close();
serverSocket = null;
serverOutput = null;
serverInput = null;
lineInputStream = null;
if (debug)
out.println("DEBUG SMTP: got bad greeting from host \"" +
server + "\", port: " + port +
", response: " + r + "\n");
throw new MessagingException(
"Got bad greeting from SMTP host: " + server +
", port: " + port +
", response: " + r);
} else {
if (debug)
out.println("DEBUG SMTP: protocol started to host \"" +
server + "\", port: " + port + "\n");
}
} catch (IOException ioe) {
throw new MessagingException(
"Could not start protocol to SMTP host: " +
server + ", port: " + port, ioe);
}
| protected boolean | protocolConnect(java.lang.String host, int port, java.lang.String user, java.lang.String passwd)Performs the actual protocol-specific connection attempt.
Will attempt to connect to "localhost" if the host was null.
Unless mail.smtp.ehlo is set to false, we'll try to identify
ourselves using the ESMTP command EHLO.
If mail.smtp.auth is set to true, we insist on having a username
and password, and will try to authenticate ourselves if the server
supports the AUTH extension (RFC 2554).
// setting mail.smtp.ehlo to false disables attempts to use EHLO
String ehloStr = session.getProperty("mail." + name + ".ehlo");
boolean useEhlo = ehloStr == null || !ehloStr.equalsIgnoreCase("false");
// setting mail.smtp.auth to true enables attempts to use AUTH
String authStr = session.getProperty("mail." + name + ".auth");
boolean useAuth = authStr != null && authStr.equalsIgnoreCase("true");
DigestMD5 md5;
if (debug)
out.println("DEBUG SMTP: useEhlo " + useEhlo +
", useAuth " + useAuth);
/*
* If mail.smtp.auth is set, make sure we have a valid username
* and password, even if we might not end up using it (e.g.,
* because the server doesn't support ESMTP or doesn't support
* the AUTH extension).
*/
if (useAuth && (user == null || passwd == null))
return false;
/*
* If port is not specified, set it to value of mail.smtp.port
* property if it exists, otherwise default to 25.
*/
if (port == -1) {
String portstring = session.getProperty("mail." + name + ".port");
if (portstring != null) {
port = Integer.parseInt(portstring);
} else {
port = defaultPort;
}
}
if (host == null || host.length() == 0)
host = "localhost";
boolean succeed = false;
if (serverSocket != null)
openServer(); // only happens from connect(socket)
else
openServer(host, port);
if (useEhlo)
succeed = ehlo(getLocalHost());
if (!succeed)
helo(getLocalHost());
if (useStartTLS && supportsExtension("STARTTLS")) {
startTLS();
/*
* Have to issue another EHLO to update list of extensions
* supported, especially authentication mechanisms.
* Don't know if this could ever fail, but we ignore failure.
*/
ehlo(getLocalHost());
}
if ((useAuth || (user != null && passwd != null)) &&
(supportsExtension("AUTH") || supportsExtension("AUTH=LOGIN"))) {
if (debug) {
out.println("DEBUG SMTP: Attempt to authenticate");
if (!supportsAuthentication("LOGIN") &&
supportsExtension("AUTH=LOGIN"))
out.println("DEBUG SMTP: use AUTH=LOGIN hack");
}
// if authentication fails, close connection and return false
if (supportsAuthentication("LOGIN") ||
supportsExtension("AUTH=LOGIN")) {
// XXX - could use "initial response" capability
int resp = simpleCommand("AUTH LOGIN");
/*
* A 530 response indicates that the server wants us to
* issue a STARTTLS command first. Do that and try again.
*/
if (resp == 530) {
startTLS();
resp = simpleCommand("AUTH LOGIN");
}
/*
* Wrap a BASE64Encoder around a ByteArrayOutputstream
* to craft b64 encoded username and password strings.
*
* Also note that unlike the B64 definition in MIME, CRLFs
* should *not* be inserted during the encoding process.
* So I use Integer.MAX_VALUE (0x7fffffff (> 1G)) as the
* bytesPerLine, which should be sufficiently large!
*/
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
OutputStream b64os =
new BASE64EncoderStream(bos, Integer.MAX_VALUE);
if (resp == 334) {
// obtain b64 encoded bytes
b64os.write(ASCIIUtility.getBytes(user));
b64os.flush(); // complete the encoding
// send username
resp = simpleCommand(bos.toByteArray());
bos.reset(); // reset buffer
}
if (resp == 334) {
// obtain b64 encoded bytes
b64os.write(ASCIIUtility.getBytes(passwd));
b64os.flush(); // complete the encoding
// send passwd
resp = simpleCommand(bos.toByteArray());
bos.reset(); // reset buffer
}
} catch (IOException ex) { // should never happen, ignore
} finally {
if (resp != 235) {
closeConnection();
return false;
}
}
} else if (supportsAuthentication("PLAIN")) {
// XXX - could use "initial response" capability
int resp = simpleCommand("AUTH PLAIN");
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
OutputStream b64os =
new BASE64EncoderStream(bos, Integer.MAX_VALUE);
if (resp == 334) {
// send "<NUL>user<NUL>passwd"
// XXX - we don't send an authorization identity
b64os.write(0);
b64os.write(ASCIIUtility.getBytes(user));
b64os.write(0);
b64os.write(ASCIIUtility.getBytes(passwd));
b64os.flush(); // complete the encoding
// send username
resp = simpleCommand(bos.toByteArray());
}
} catch (IOException ex) { // should never happen, ignore
} finally {
if (resp != 235) {
closeConnection();
return false;
}
}
} else if (supportsAuthentication("DIGEST-MD5") &&
(md5 = getMD5()) != null) {
int resp = simpleCommand("AUTH DIGEST-MD5");
try {
if (resp == 334) {
byte[] b = md5.authClient(host, user, passwd,
getSASLRealm(), lastServerResponse);
resp = simpleCommand(b);
if (resp == 334) { // client authenticated by server
if (!md5.authServer(lastServerResponse)) {
// server NOT authenticated by client !!!
resp = -1;
} else {
// send null response
resp = simpleCommand(new byte[0]);
}
}
}
} catch (Exception ex) { // should never happen, ignore
if (debug)
out.println("DEBUG SMTP: DIGEST-MD5: " + ex);
} finally {
if (resp != 235) {
closeConnection();
return false;
}
}
}
}
// we connected correctly
return true;
| protected void | rcptTo()Sends each address to the SMTP host using the RCPT TO:
command and copies the address either into
the validSentAddr or invalidAddr arrays.
Sets the sendFailed
flag to true if any addresses failed.
Vector valid = new Vector();
Vector validUnsent = new Vector();
Vector invalid = new Vector();
int retCode = -1;
MessagingException mex = null;
boolean sendFailed = false;
MessagingException sfex = null;
validSentAddr = validUnsentAddr = invalidAddr = null;
boolean sendPartial = false;
if (message instanceof SMTPMessage)
sendPartial = ((SMTPMessage)message).getSendPartial();
if (!sendPartial) {
String sp = session.getProperty("mail." + name + ".sendpartial");
sendPartial = sp != null && sp.equalsIgnoreCase("true");
}
if (debug && sendPartial)
out.println("DEBUG SMTP: sendPartial set");
boolean dsn = false;
String notify = null;
if (supportsExtension("DSN")) {
if (message instanceof SMTPMessage)
notify = ((SMTPMessage)message).getDSNNotify();
if (notify == null)
notify = session.getProperty("mail." + name + ".dsn.notify");
// XXX - check for legal syntax?
if (notify != null)
dsn = true;
}
// try the addresses one at a time
for (int i = 0; i < addresses.length; i++) {
sfex = null;
InternetAddress ia = (InternetAddress)addresses[i];
String cmd = "RCPT TO:" + normalizeAddress(ia.getAddress());
if (dsn)
cmd += " NOTIFY=" + notify;
// send the addresses to the SMTP server
sendCommand(cmd);
// check the server's response for address validity
retCode = readServerResponse();
switch (retCode) {
case 250: case 251:
valid.addElement(ia);
if (!reportSuccess)
break;
// user wants exception even when successful, including
// details of the return code
// create and chain the exception
sfex = new SMTPAddressSucceededException(ia, cmd, retCode,
lastServerResponse);
if (mex == null)
mex = sfex;
else
mex.setNextException(sfex);
break;
case 550: case 553: case 503: case 551: case 501:
// given address is invalid
if (!sendPartial)
sendFailed = true;
invalid.addElement(ia);
// create and chain the exception
sfex = new SMTPAddressFailedException(ia, cmd, retCode,
lastServerResponse);
if (mex == null)
mex = sfex;
else
mex.setNextException(sfex);
break;
case 552: case 450: case 451: case 452:
// given address is valid
if (!sendPartial)
sendFailed = true;
validUnsent.addElement(ia);
// create and chain the exception
sfex = new SMTPAddressFailedException(ia, cmd, retCode,
lastServerResponse);
if (mex == null)
mex = sfex;
else
mex.setNextException(sfex);
break;
default:
// handle remaining 4xy & 5xy codes
if (retCode >= 400 && retCode <= 499) {
// assume address is valid, although we don't really know
validUnsent.addElement(ia);
} else if (retCode >= 500 && retCode <= 599) {
// assume address is invalid, although we don't really know
invalid.addElement(ia);
} else {
// completely unexpected response, just give up
if (debug)
out.println("DEBUG SMTP: got response code " + retCode +
", with response: " + lastServerResponse);
String _lsr = lastServerResponse; // else rset will nuke it
int _lrc = lastReturnCode;
if (serverSocket != null) // hasn't already been closed
issueCommand("RSET", 250);
lastServerResponse = _lsr; // restore, for get
lastReturnCode = _lrc;
throw new SMTPAddressFailedException(ia, cmd, retCode,
_lsr);
}
if (!sendPartial)
sendFailed = true;
// create and chain the exception
sfex = new SMTPAddressFailedException(ia, cmd, retCode,
lastServerResponse);
if (mex == null)
mex = sfex;
else
mex.setNextException(sfex);
break;
}
}
// if we're willing to send to a partial list, and we found no
// valid addresses, that's complete failure
if (sendPartial && valid.size() == 0)
sendFailed = true;
// copy the vectors into appropriate arrays
if (sendFailed) {
// copy invalid addrs
invalidAddr = new Address[invalid.size()];
invalid.copyInto(invalidAddr);
// copy all valid addresses to validUnsent, since something failed
validUnsentAddr = new Address[valid.size() + validUnsent.size()];
int i = 0;
for (int j = 0; j < valid.size(); j++)
validUnsentAddr[i++] = (Address)valid.elementAt(j);
for (int j = 0; j < validUnsent.size(); j++)
validUnsentAddr[i++] = (Address)validUnsent.elementAt(j);
} else if (reportSuccess || (sendPartial &&
(invalid.size() > 0 || validUnsent.size() > 0))) {
// we'll go on to send the message, but after sending we'll
// throw an exception with this exception nested
sendPartiallyFailed = true;
exception = mex;
// copy invalid addrs
invalidAddr = new Address[invalid.size()];
invalid.copyInto(invalidAddr);
// copy valid unsent addresses to validUnsent
validUnsentAddr = new Address[validUnsent.size()];
validUnsent.copyInto(validUnsentAddr);
// copy valid addresses to validSent
validSentAddr = new Address[valid.size()];
valid.copyInto(validSentAddr);
} else { // all addresses pass
validSentAddr = addresses;
}
// print out the debug info
if (debug) {
if (validSentAddr != null && validSentAddr.length > 0) {
out.println("DEBUG SMTP: Verified Addresses");
for (int l = 0; l < validSentAddr.length; l++) {
out.println("DEBUG SMTP: " + validSentAddr[l]);
}
}
if (validUnsentAddr != null && validUnsentAddr.length > 0) {
out.println("DEBUG SMTP: Valid Unsent Addresses");
for (int j = 0; j < validUnsentAddr.length; j++) {
out.println("DEBUG SMTP: " + validUnsentAddr[j]);
}
}
if (invalidAddr != null && invalidAddr.length > 0) {
out.println("DEBUG SMTP: Invalid Addresses");
for (int k = 0; k < invalidAddr.length; k++) {
out.println("DEBUG SMTP: " + invalidAddr[k]);
}
}
}
// throw the exception, fire TransportEvent.MESSAGE_NOT_DELIVERED event
if (sendFailed) {
if (debug)
out.println("DEBUG SMTP: Sending failed " +
"because of invalid destination addresses");
notifyTransportListeners(TransportEvent.MESSAGE_NOT_DELIVERED,
validSentAddr, validUnsentAddr,
invalidAddr, this.message);
// reset the connection so more sends are allowed
String lsr = lastServerResponse; // save, for get
int lrc = lastReturnCode;
try {
if (serverSocket != null)
issueCommand("RSET", 250);
} catch (MessagingException ex) {
// if can't reset, best to close the connection
try {
close();
} catch (MessagingException ex2) {
// thrown by close()--ignore, will close() later anyway
if (debug)
ex2.printStackTrace(out);
}
} finally {
lastServerResponse = lsr; // restore
lastReturnCode = lrc;
}
throw new SendFailedException("Invalid Addresses", mex,
validSentAddr,
validUnsentAddr, invalidAddr);
}
| protected int | readServerResponse()Reads server reponse returning the returnCode
as the number. Returns -1 on failure. Sets
lastServerResponse and lastReturnCode .
assert Thread.holdsLock(this);
String serverResponse = "";
int returnCode = 0;
StringBuffer buf = new StringBuffer(100);
// read the server response line(s) and add them to the buffer
// that stores the response
try {
String line = null;
do {
line = lineInputStream.readLine();
if (line == null) {
serverResponse = buf.toString();
if (serverResponse.length() == 0)
serverResponse = "[EOF]";
lastServerResponse = serverResponse;
lastReturnCode = -1;
if (debug)
out.println("DEBUG SMTP: EOF: " + serverResponse);
return -1;
}
buf.append(line);
buf.append("\n");
} while (isNotLastLine(line));
serverResponse = buf.toString();
} catch (IOException ioex) {
if (debug)
out.println("DEBUG SMTP: exception reading response: " + ioex);
//ioex.printStackTrace(out);
lastServerResponse = "";
lastReturnCode = 0;
throw new MessagingException("Exception reading response", ioex);
//returnCode = -1;
}
// print debug info
//if (debug)
//out.println("DEBUG SMTP RCVD: " + serverResponse);
// parse out the return code
if (serverResponse != null && serverResponse.length() >= 3) {
try {
returnCode = Integer.parseInt(serverResponse.substring(0, 3));
} catch (NumberFormatException nfe) {
try {
close();
} catch (MessagingException mex) {
// thrown by close()--ignore, will close() later anyway
if (debug)
mex.printStackTrace(out);
}
returnCode = -1;
} catch (StringIndexOutOfBoundsException ex) {
//if (debug) ex.printStackTrace(out);
try {
close();
} catch (MessagingException mex) {
// thrown by close()--ignore, will close() later anyway
if (debug)
mex.printStackTrace(out);
}
returnCode = -1;
}
} else {
returnCode = -1;
}
if (returnCode == -1 && debug)
out.println("DEBUG SMTP: bad server response: " + serverResponse);
lastServerResponse = serverResponse;
lastReturnCode = returnCode;
return returnCode;
| protected void | sendCommand(java.lang.String cmd)Sends command cmd to the server terminating
it with CRLF .
sendCommand(ASCIIUtility.getBytes(cmd));
| private void | sendCommand(byte[] cmdBytes)
assert Thread.holdsLock(this);
//if (debug)
//out.println("DEBUG SMTP SENT: " + new String(cmdBytes, 0));
try {
serverOutput.write(cmdBytes);
serverOutput.write(CRLF);
serverOutput.flush();
} catch (IOException ex) {
throw new MessagingException("Can't send command to SMTP host", ex);
}
| public synchronized void | sendMessage(Message message, javax.mail.Address[] addresses)Send the Message to the specified list of addresses.
If all the addresses succeed the SMTP check
using the RCPT TO: command, we attempt to send the message.
A TransportEvent of type MESSAGE_DELIVERED is fired indicating the
successful submission of a message to the SMTP host.
If some of the addresses fail the SMTP check,
and the mail.stmp.sendpartial property is not set,
sending is aborted. The TransportEvent of type MESSAGE_NOT_DELIVERED
is fired containing the valid and invalid addresses. The
SendFailedException is also thrown.
If some of the addresses fail the SMTP check,
and the mail.stmp.sendpartial property is set to true,
the message is sent. The TransportEvent of type
MESSAGE_PARTIALLY_DELIVERED
is fired containing the valid and invalid addresses. The
SMTPSendFailedException is also thrown.
MessagingException is thrown if the message can't write out
an RFC822-compliant stream using its writeTo method.
checkConnected();
// check if the message is a valid MIME/RFC822 message and that
// it has all valid InternetAddresses; fail if not
if (!(message instanceof MimeMessage)) {
if (debug)
out.println("DEBUG SMTP: Can only send RFC822 msgs");
throw new MessagingException("SMTP can only send RFC822 messages");
}
for (int i = 0; i < addresses.length; i++) {
if (!(addresses[i] instanceof InternetAddress)) {
throw new MessagingException(addresses[i] +
" is not an InternetAddress");
}
}
this.message = (MimeMessage)message;
this.addresses = addresses;
validUnsentAddr = addresses; // until we know better
expandGroups();
boolean use8bit = false;
if (message instanceof SMTPMessage)
use8bit = ((SMTPMessage)message).getAllow8bitMIME();
if (!use8bit) {
String ebStr =
session.getProperty("mail." + name + ".allow8bitmime");
use8bit = ebStr != null && ebStr.equalsIgnoreCase("true");
}
if (debug)
out.println("DEBUG SMTP: use8bit " + use8bit);
if (use8bit && supportsExtension("8BITMIME")) {
if (convertTo8Bit(this.message)) {
// in case we made any changes, save those changes
// XXX - this will change the Message-ID
try {
this.message.saveChanges();
} catch (MessagingException mex) {
// ignore it
}
}
}
try {
mailFrom();
rcptTo();
this.message.writeTo(data(), ignoreList);
finishData();
if (sendPartiallyFailed) {
// throw the exception,
// fire TransportEvent.MESSAGE_PARTIALLY_DELIVERED event
if (debug)
out.println("DEBUG SMTP: Sending partially failed " +
"because of invalid destination addresses");
notifyTransportListeners(
TransportEvent.MESSAGE_PARTIALLY_DELIVERED,
validSentAddr, validUnsentAddr, invalidAddr,
this.message);
throw new SMTPSendFailedException(".", lastReturnCode,
lastServerResponse, exception,
validSentAddr, validUnsentAddr, invalidAddr);
}
notifyTransportListeners(TransportEvent.MESSAGE_DELIVERED,
validSentAddr, validUnsentAddr,
invalidAddr, this.message);
} catch (MessagingException mex) {
if (debug)
mex.printStackTrace(out);
notifyTransportListeners(TransportEvent.MESSAGE_NOT_DELIVERED,
validSentAddr, validUnsentAddr,
invalidAddr, this.message);
throw mex;
} catch (IOException ex) {
if (debug)
ex.printStackTrace(out);
// if we catch an IOException, it means that we want
// to drop the connection so that the message isn't sent
try {
closeConnection();
} catch (MessagingException mex) { /* ignore it */ }
notifyTransportListeners(TransportEvent.MESSAGE_NOT_DELIVERED,
validSentAddr, validUnsentAddr,
invalidAddr, this.message);
throw new MessagingException("IOException while sending message",
ex);
} finally {
// no reason to keep this data around
validSentAddr = validUnsentAddr = invalidAddr = null;
this.addresses = null;
this.message = null;
this.exception = null;
sendPartiallyFailed = false;
}
| public synchronized void | setLocalHost(java.lang.String localhost)Set the name of the local host, for use in the EHLO and HELO commands.
localHostName = localhost;
| public synchronized void | setReportSuccess(boolean reportSuccess)Set whether successful sends should be reported by throwing
an exception.
this.reportSuccess = reportSuccess;
| public synchronized void | setSASLRealm(java.lang.String saslRealm)Sets the SASL realm to be used for DIGEST-MD5 authentication.
this.saslRealm = saslRealm;
| public synchronized void | setStartTLS(boolean useStartTLS)Set whether the STARTTLS command should be used.
this.useStartTLS = useStartTLS;
| public synchronized void | setUseRset(boolean useRset)Set whether the RSET command should be used instead of the
NOOP command in the @{link #isConnected isConnected} method.
this.useRset = useRset;
| public synchronized int | simpleCommand(java.lang.String cmd)Send the command to the server and return the response code
from the server.
sendCommand(cmd);
return readServerResponse();
| protected int | simpleCommand(byte[] cmd)Send the command to the server and return the response code
from the server.
assert Thread.holdsLock(this);
sendCommand(cmd);
return readServerResponse();
| protected void | startTLS()Issue the STARTTLS command and switch the socket to
TLS mode if it succeeds.
issueCommand("STARTTLS", 220);
// it worked, now switch the socket into TLS mode
try {
serverSocket = SocketFetcher.startTLS(serverSocket,
session.getProperties(), "mail." + name);
initStreams();
} catch (IOException ioex) {
closeConnection();
throw new MessagingException("Could not convert socket to TLS",
ioex);
}
| protected boolean | supportsAuthentication(java.lang.String auth)Does the server we're connected to support the specified
authentication mechanism? Uses the extension information
returned by the server from the EHLO command.
assert Thread.holdsLock(this);
if (extMap == null)
return false;
String a = (String)extMap.get("AUTH");
if (a == null)
return false;
StringTokenizer st = new StringTokenizer(a);
while (st.hasMoreTokens()) {
String tok = st.nextToken();
if (tok.equalsIgnoreCase(auth))
return true;
}
return false;
| public boolean | supportsExtension(java.lang.String ext)Return true if the SMTP server supports the specified service
extension. Extensions are reported as results of the EHLO
command when connecting to the server. See
RFC 1869
and other RFCs that define specific extensions.
return extMap != null &&
extMap.get(ext.toUpperCase(Locale.ENGLISH)) != null;
| protected static java.lang.String | xtext(java.lang.String s)Convert a string to RFC 1891 xtext format.
xtext = *( xchar / hexchar )
xchar = any ASCII CHAR between "!" (33) and "~" (126) inclusive,
except for "+" and "=".
; "hexchar"s are intended to encode octets that cannot appear
; as ASCII characters within an esmtp-value.
hexchar = ASCII "+" immediately followed by two upper case
hexadecimal digits
StringBuffer sb = null;
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c >= 128) // not ASCII
throw new IllegalArgumentException(
"Non-ASCII character in SMTP submitter: " + s);
if (c < '!" || c > '~" || c == '+" || c == '=") {
if (sb == null) {
sb = new StringBuffer(s.length() + 4);
sb.append(s.substring(0, i));
}
sb.append('+");
sb.append(hexchar[(((int)c)& 0xf0) >> 4]);
sb.append(hexchar[((int)c)& 0x0f]);
} else {
if (sb != null)
sb.append(c);
}
}
return sb != null ? sb.toString() : s;
|
|