GERONIMO-4174 Restructure the SMTPTransport class to use the comment MailConnection class.
git-svn-id: https://svn.apache.org/repos/asf/geronimo/javamail/trunk@673649 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPConnection.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPConnection.java
index 00e7e8b..de490dc 100644
--- a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPConnection.java
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPConnection.java
@@ -50,9 +50,6 @@
/**
* Simple implementation of NNTP transport. Just does plain RFC977-ish delivery.
- * <p/> There is no way to indicate failure for a given recipient (it's possible
- * to have a recipient address rejected). The sun impl throws exceptions even if
- * others successful), but maybe we do a different way... <p/>
*
* @version $Rev$ $Date$
*/
@@ -79,9 +76,6 @@
// the last response line received from the server.
protected NNTPReply lastServerResponse = null;
- // map of server extension arguments
- protected HashMap serverExtensionArgs;
-
// the welcome string from the server.
protected String welcomeString = null;
@@ -318,8 +312,8 @@
* supported.
*/
public String extensionParameter(String name) {
- if (serverExtensionArgs != null) {
- return (String) serverExtensionArgs.get(name);
+ if (capabilities != null) {
+ return (String) capabilities.get(name);
}
return null;
}
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPTransport.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPTransport.java
index 93f4060..ec399c4 100644
--- a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPTransport.java
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPTransport.java
@@ -254,7 +254,7 @@
public void close() throws MessagingException {
// This is done to ensure proper event notification.
super.close();
+ // NB: We reuse the connection if asked to reconnect
connection.close();
- connection = null;
}
}
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPConnection.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPConnection.java
new file mode 100644
index 0000000..32f7572
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPConnection.java
@@ -0,0 +1,1345 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.geronimo.javamail.transport.smtp;
+
+import java.io.BufferedReader;
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.SocketException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import javax.mail.Address;
+import javax.mail.AuthenticationFailedException;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+import javax.mail.internet.MimePart;
+import javax.mail.Session;
+
+import org.apache.geronimo.javamail.authentication.ClientAuthenticator;
+import org.apache.geronimo.javamail.authentication.AuthenticatorFactory;
+import org.apache.geronimo.javamail.util.CountingOutputStream;
+import org.apache.geronimo.javamail.util.MailConnection;
+import org.apache.geronimo.javamail.util.MIMEOutputStream;
+import org.apache.geronimo.javamail.util.ProtocolProperties;
+import org.apache.geronimo.mail.util.Base64;
+import org.apache.geronimo.mail.util.XText;
+
+/**
+ * Simple implementation of SMTP transport. Just does plain RFC977-ish delivery.
+ *
+ * @version $Rev$ $Date$
+ */
+public class SMTPConnection extends MailConnection {
+ protected static final String MAIL_SMTP_QUITWAIT = "quitwait";
+ protected static final String MAIL_SMTP_EXTENSION = "mailextension";
+ protected static final String MAIL_SMTP_EHLO = "ehlo";
+ protected static final String MAIL_SMTP_ALLOW8BITMIME = "allow8bitmime";
+ protected static final String MAIL_SMTP_REPORT_SUCCESS = "reportsuccess";
+ protected static final String MAIL_SMTP_STARTTLS_ENABLE = "starttls.enable";
+ protected static final String MAIL_SMTP_AUTH = "auth";
+ protected static final String MAIL_SMTP_FROM = "from";
+ protected static final String MAIL_SMTP_DSN_RET = "dsn.ret";
+ protected static final String MAIL_SMTP_SUBMITTER = "submitter";
+
+ /**
+ * property keys for protocol properties.
+ */
+ protected static final int DEFAULT_NNTP_PORT = 119;
+
+ // the last response line received from the server.
+ protected SMTPReply lastServerResponse = null;
+
+ // input reader wrapped around the socket input stream
+ protected BufferedReader reader;
+ // output writer wrapped around the socket output stream.
+ protected PrintWriter writer;
+
+ // do we report success after completion of each mail send.
+ protected boolean reportSuccess;
+ // does the server support transport level security?
+ protected boolean serverTLS = false;
+ // is TLS enabled on our part?
+ protected boolean useTLS = false;
+ // should we use 8BITMIME encoding if supported by the server?
+ protected boolean use8bit = false;
+
+ /**
+ * Normal constructor for an SMTPConnection() object.
+ *
+ * @param props The property bundle for this protocol instance.
+ */
+ public SMTPConnection(ProtocolProperties props) {
+ super(props);
+
+ // check to see if we need to throw an exception after a send operation.
+ reportSuccess = props.getBooleanProperty(MAIL_SMTP_REPORT_SUCCESS, false);
+ // and also check for TLS enablement.
+ useTLS = props.getBooleanProperty(MAIL_SMTP_STARTTLS_ENABLE, false);
+ // and also check for 8bitmime support
+ use8bit = props.getBooleanProperty(MAIL_SMTP_ALLOW8BITMIME, false);
+ }
+
+
+ /**
+ * Connect to the server and do the initial handshaking.
+ *
+ * @param host The target host name.
+ * @param port The target port
+ * @param username The connection username (can be null)
+ * @param password The authentication password (can be null).
+ *
+ * @return true if we were able to obtain a connection and
+ * authenticate.
+ * @exception MessagingException
+ */
+ public boolean protocolConnect(String host, int port, String username, String password) throws MessagingException {
+
+ // now check to see if we need to authenticate. If we need this, then
+ // we must have a username and
+ // password specified. Failing this may result in a user prompt to
+ // collect the information.
+ boolean mustAuthenticate = props.getBooleanProperty(MAIL_SMTP_AUTH, false);
+
+ // if we need to authenticate, and we don't have both a userid and
+ // password, then we fail this
+ // immediately. The Service.connect() method will try to obtain the user
+ // information and retry the
+ // connection one time.
+ if (mustAuthenticate && (username == null || password == null)) {
+ debugOut("Failing connection for missing authentication information");
+ return false;
+ }
+
+ super.protocolConnect(host, port, username, password);
+
+ try {
+ // create socket and connect to server.
+ getConnection();
+
+ // receive welcoming message
+ if (!getWelcome()) {
+ debugOut("Error getting welcome message");
+ throw new MessagingException("Error in getting welcome msg");
+ }
+
+ // say hello
+ if (!sendHandshake()) {
+ debugOut("Error getting processing handshake message");
+ throw new MessagingException("Error in saying EHLO to server");
+ }
+
+ // authenticate with the server, if necessary
+ if (!processAuthentication()) {
+ debugOut("User authentication failure");
+ throw new AuthenticationFailedException("Error authenticating with server");
+ }
+ } catch (IOException e) {
+ debugOut("I/O exception establishing connection", e);
+ throw new MessagingException("Connection error", e);
+ }
+ debugOut("Successful connection");
+ return true;
+ }
+
+
+ /**
+ * Close the connection. On completion, we'll be disconnected from the
+ * server and unable to send more data.
+ *
+ * @exception MessagingException
+ */
+ public void close() throws MessagingException {
+ // if we're already closed, get outta here.
+ if (socket == null) {
+ return;
+ }
+ try {
+ // say goodbye
+ sendQuit();
+ } finally {
+ // and close up the connection. We do this in a finally block to
+ // make sure the connection
+ // is shut down even if quit gets an error.
+ closeServerConnection();
+ }
+ }
+
+ public String toString() {
+ return "SMTPConnection host: " + serverHost + " port: " + serverPort;
+ }
+
+
+ /**
+ * Set the sender for this mail.
+ *
+ * @param message
+ * The message we're sending.
+ *
+ * @return True if the command was accepted, false otherwise.
+ * @exception MessagingException
+ */
+ protected boolean sendMailFrom(Message message) throws MessagingException {
+
+ // need to sort the from value out from a variety of sources.
+ String from = null;
+
+ // first potential source is from the message itself, if it's an
+ // instance of SMTPMessage.
+ if (message instanceof SMTPMessage) {
+ from = ((SMTPMessage) message).getEnvelopeFrom();
+ }
+
+ // if not available from the message, check the protocol property next
+ if (from == null || from.length() == 0) {
+ // the from value can be set explicitly as a property
+ from = props.getProperty(MAIL_SMTP_FROM);
+ }
+
+ // if not there, see if we have something in the message header.
+ if (from == null || from.length() == 0) {
+ Address[] fromAddresses = message.getFrom();
+
+ // if we have some addresses in the header, then take the first one
+ // as our From: address
+ if (fromAddresses != null && fromAddresses.length > 0) {
+ from = ((InternetAddress) fromAddresses[0]).getAddress();
+ }
+ // get what the InternetAddress class believes to be the local
+ // address.
+ else {
+ InternetAddress local = InternetAddress.getLocalAddress(session);
+ if (local != null) {
+ from = local.getAddress();
+ }
+ }
+ }
+
+ if (from == null || from.length() == 0) {
+ throw new MessagingException("no FROM address");
+ }
+
+ StringBuffer command = new StringBuffer();
+
+ // start building up the command
+ command.append("MAIL FROM: ");
+ command.append(fixEmailAddress(from));
+
+ // If the server supports the 8BITMIME extension, we might need to change the
+ // transfer encoding for the content to allow for direct transmission of the
+ // 8-bit codes.
+ if (supportsExtension("8BITMIME")) {
+ // we only do this if the capability was enabled via a property option or
+ // by explicitly setting the property on the message object.
+ if (use8bit || (message instanceof SMTPMessage && ((SMTPMessage)message).getAllow8bitMIME())) {
+ // make sure we add the BODY= option to the FROM message.
+ command.append(" BODY=8BITMIME");
+
+ // go check the content and see if the can convert the transfer encoding to
+ // allow direct 8-bit transmission.
+ if (convertTransferEncoding((MimeMessage)message)) {
+ // if we changed the encoding on any of the parts, then we
+ // need to save the message again
+ message.saveChanges();
+ }
+ }
+ }
+
+ // some servers ask for a size estimate on the initial send
+ if (supportsExtension("SIZE")) {
+ int estimate = getSizeEstimate(message);
+ if (estimate > 0) {
+ command.append(" SIZE=" + estimate);
+ }
+ }
+
+ // does this server support Delivery Status Notification? Then we may
+ // need to add some extra to the command.
+ if (supportsExtension("DSN")) {
+ String returnNotification = null;
+
+ // the return notification stuff might be set as value on the
+ // message object itself.
+ if (message instanceof SMTPMessage) {
+ // we need to convert the option into a string value.
+ switch (((SMTPMessage) message).getReturnOption()) {
+ case SMTPMessage.RETURN_FULL:
+ returnNotification = "FULL";
+ break;
+
+ case SMTPMessage.RETURN_HDRS:
+ returnNotification = "HDRS";
+ break;
+ }
+ }
+
+ // if not obtained from the message object, it can also be set as a
+ // property.
+ if (returnNotification == null) {
+ // the DSN value is set by yet another property.
+ returnNotification = props.getProperty(MAIL_SMTP_DSN_RET);
+ }
+
+ // if we have a target, add the notification stuff to our FROM
+ // command.
+ if (returnNotification != null) {
+ command.append(" RET=");
+ command.append(returnNotification);
+ }
+ }
+
+ // if this server supports AUTH and we have submitter information, then
+ // we also add the
+ // "AUTH=" keyword to the MAIL FROM command (see RFC 2554).
+
+ if (supportsExtension("AUTH")) {
+ String submitter = null;
+
+ // another option that can be specified on the message object.
+ if (message instanceof SMTPMessage) {
+ submitter = ((SMTPMessage) message).getSubmitter();
+ }
+ // if not part of the object, try for a propery version.
+ if (submitter == null) {
+ // we only send the extra keyword is a submitter is specified.
+ submitter = props.getProperty(MAIL_SMTP_SUBMITTER);
+ }
+ // we have one...add the keyword, plus the submitter info in xtext
+ // format (defined by RFC 1891).
+ if (submitter != null) {
+ command.append(" AUTH=");
+ try {
+ // add this encoded
+ command.append(new String(XText.encode(submitter.getBytes("US-ASCII"))));
+ } catch (UnsupportedEncodingException e) {
+ throw new MessagingException("Invalid submitter value " + submitter);
+ }
+ }
+ }
+
+ String extension = null;
+
+ // now see if we need to add any additional extension info to this
+ // command. The extension is not
+ // checked for validity. That's the reponsibility of the caller.
+ if (message instanceof SMTPMessage) {
+ extension = ((SMTPMessage) message).getMailExtension();
+ }
+ // this can come either from the object or from a set property.
+ if (extension == null) {
+ extension = props.getProperty(MAIL_SMTP_EXTENSION);
+ }
+
+ // have something real to add?
+ if (extension != null && extension.length() != 0) {
+ // tack this on the end with a blank delimiter.
+ command.append(' ');
+ command.append(extension);
+ }
+
+ // and finally send the command
+ SMTPReply line = sendCommand(command.toString());
+
+ // 250 response indicates success.
+ return line.getCode() == SMTPReply.COMMAND_ACCEPTED;
+ }
+
+
+ /**
+ * Check to see if a MIME body part can have its
+ * encoding changed from quoted-printable or base64
+ * encoding to 8bit encoding. In order for this
+ * to work, it must follow the rules laid out in
+ * RFC 2045. To qualify for conversion, the text
+ * must be:
+ *
+ * 1) No more than 998 bytes long
+ * 2) All lines are terminated with CRLF sequences
+ * 3) CR and LF characters only occur in properly
+ * formed line separators
+ * 4) No null characters are allowed.
+ *
+ * The conversion will only be applied to text
+ * elements, and this will recurse through the
+ * different elements of MultiPart content.
+ *
+ * @param bodyPart The bodyPart to convert. Initially, this will be
+ * the message itself.
+ *
+ * @return true if any conversion was performed, false if
+ * nothing was converted.
+ */
+ protected boolean convertTransferEncoding(MimePart bodyPart)
+ {
+ boolean converted = false;
+ try {
+ // if this is a multipart element, apply the conversion rules
+ // to each of the parts.
+ if (bodyPart.isMimeType("multipart/")) {
+ MimeMultipart parts = (MimeMultipart)bodyPart.getContent();
+ for (int i = 0; i < parts.getCount(); i++) {
+ // convert each body part, and accumulate the conversion result
+ converted = converted && convertTransferEncoding((MimePart)parts.getBodyPart(i));
+ }
+ }
+ else {
+ // we only do this if the encoding is quoted-printable or base64
+ String encoding = bodyPart.getEncoding();
+ if (encoding != null) {
+ encoding = encoding.toLowerCase();
+ if (encoding.equals("quoted-printable") || encoding.equals("base64")) {
+ // this requires encoding. Read the actual content to see if
+ // it conforms to the 8bit encoding rules.
+ if (isValid8bit(bodyPart.getInputStream())) {
+ // There's a huge hidden gotcha lurking under the covers here.
+ // If the content just exists as an encoded byte array, then just
+ // switching the transfer encoding will mess things up because the
+ // already encoded data gets transmitted in encoded form, but with
+ // and 8bit encoding style. As a result, it doesn't get unencoded on
+ // the receiving end. This is a nasty problem to debug.
+ //
+ // The solution is to get the content as it's object type, set it back
+ // on the the message in raw form. Requesting the content will apply the
+ // current transfer encoding value to the data. Once we have set the
+ // content value back, we can reset the transfer encoding.
+ bodyPart.setContent(bodyPart.getContent(), bodyPart.getContentType());
+
+ // it's valid, so change the transfer encoding to just
+ // pass the data through.
+ bodyPart.setHeader("Content-Transfer-Encoding", "8bit");
+ converted = true; // we've changed something
+ }
+ }
+ }
+ }
+ } catch (MessagingException e) {
+ } catch (IOException e) {
+ }
+ return converted;
+ }
+
+
+ /**
+ * Get the server's welcome blob from the wire....
+ */
+ protected boolean getWelcome() throws MessagingException {
+ SMTPReply line = getReply();
+ // just return the error status...we don't care about any of the
+ // response information
+ return !line.isError();
+ }
+
+
+ /**
+ * Get an estimate of the transmission size for this
+ * message. This size is the complete message as it is
+ * encoded and transmitted on the DATA command, not counting
+ * the terminating ".CRLF".
+ *
+ * @param msg The message we're sending.
+ *
+ * @return The count of bytes, if it can be calculated.
+ */
+ protected int getSizeEstimate(Message msg) {
+ // now the data... I could look at the type, but
+ try {
+ CountingOutputStream outputStream = new CountingOutputStream();
+
+ // the data content has two requirements we need to meet by
+ // filtering the
+ // output stream. Requirement 1 is to conicalize any line breaks.
+ // All line
+ // breaks will be transformed into properly formed CRLF sequences.
+ //
+ // Requirement 2 is to perform byte-stuff for any line that begins
+ // with a "."
+ // so that data is not confused with the end-of-data marker (a
+ // "\r\n.\r\n" sequence.
+ //
+ // The MIME output stream performs those two functions on behalf of
+ // the content
+ // writer.
+ MIMEOutputStream mimeOut = new MIMEOutputStream(outputStream);
+
+ msg.writeTo(mimeOut);
+
+ // now to finish, we make sure there's a line break at the end.
+ mimeOut.forceTerminatingLineBreak();
+ // and flush the data to send it along
+ mimeOut.flush();
+
+ return outputStream.getCount();
+ } catch (IOException e) {
+ return 0; // can't get an estimate
+ } catch (MessagingException e) {
+ return 0; // can't get an estimate
+ }
+ }
+
+
+ /**
+ * Sends the data in the message down the socket. This presumes the server
+ * is in the right place and ready for getting the DATA message and the data
+ * right place in the sequence
+ */
+ protected void sendData(Message msg) throws MessagingException {
+
+ // send the DATA command
+ SMTPReply line = sendCommand("DATA");
+
+ if (line.isError()) {
+ throw new MessagingException("Error issuing SMTP 'DATA' command: " + line);
+ }
+
+ // now the data... I could look at the type, but
+ try {
+ // the data content has two requirements we need to meet by
+ // filtering the
+ // output stream. Requirement 1 is to conicalize any line breaks.
+ // All line
+ // breaks will be transformed into properly formed CRLF sequences.
+ //
+ // Requirement 2 is to perform byte-stuff for any line that begins
+ // with a "."
+ // so that data is not confused with the end-of-data marker (a
+ // "\r\n.\r\n" sequence.
+ //
+ // The MIME output stream performs those two functions on behalf of
+ // the content
+ // writer.
+ MIMEOutputStream mimeOut = new MIMEOutputStream(outputStream);
+
+ msg.writeTo(mimeOut);
+
+ // now to finish, we send a CRLF sequence, followed by a ".".
+ mimeOut.writeSMTPTerminator();
+ // and flush the data to send it along
+ mimeOut.flush();
+ } catch (IOException e) {
+ throw new MessagingException(e.toString());
+ } catch (MessagingException e) {
+ throw new MessagingException(e.toString());
+ }
+
+ // use a longer time out here to give the server time to process the
+ // data.
+ try {
+ line = new SMTPReply(receiveLine(TIMEOUT * 2));
+ } catch (MalformedSMTPReplyException e) {
+ throw new MessagingException(e.toString());
+ } catch (MessagingException e) {
+ throw new MessagingException(e.toString());
+ }
+
+ if (line.isError()) {
+ throw new MessagingException("Error issuing SMTP 'DATA' command: " + line);
+ }
+ }
+
+ /**
+ * Sends the QUIT message and receieves the response
+ */
+ protected void sendQuit() throws MessagingException {
+ // there's yet another property that controls whether we should wait for
+ // a reply for a QUIT command. If true, we're suppposed to wait for a response
+ // from the QUIT command. Otherwise we just send the QUIT and bail. The default
+ // is "false"
+ if (props.getBooleanProperty(MAIL_SMTP_QUITWAIT, true)) {
+ // handle as a real command...we're going to ignore the response.
+ sendCommand("QUIT");
+ } else {
+ // just send the command without waiting for a response.
+ sendLine("QUIT");
+ }
+ }
+
+ /**
+ * Sets a receiver address for the current message
+ *
+ * @param addr
+ * The target address.
+ * @param dsn
+ * An optional DSN option appended to the RCPT TO command.
+ *
+ * @return The status for this particular send operation.
+ * @exception MessagingException
+ */
+ public SendStatus sendRcptTo(InternetAddress addr, String dsn) throws MessagingException {
+ // compose the command using the fixed up email address. Normally, this
+ // involves adding
+ // "<" and ">" around the address.
+
+ StringBuffer command = new StringBuffer();
+
+ // compose the first part of the command
+ command.append("RCPT TO: ");
+ command.append(fixEmailAddress(addr.getAddress()));
+
+ // if we have DSN information, append it to the command.
+ if (dsn != null) {
+ command.append(" NOTIFY=");
+ command.append(dsn);
+ }
+
+ // get a string version of this command.
+ String commandString = command.toString();
+
+ SMTPReply line = sendCommand(commandString);
+
+ switch (line.getCode()) {
+ // these two are both successful transmissions
+ case SMTPReply.COMMAND_ACCEPTED:
+ case SMTPReply.ADDRESS_NOT_LOCAL:
+ // we get out of here with the status information.
+ return new SendStatus(SendStatus.SUCCESS, addr, commandString, line);
+
+ // these are considered invalid address errors
+ case SMTPReply.PARAMETER_SYNTAX_ERROR:
+ case SMTPReply.INVALID_COMMAND_SEQUENCE:
+ case SMTPReply.MAILBOX_NOT_FOUND:
+ case SMTPReply.INVALID_MAILBOX:
+ case SMTPReply.USER_NOT_LOCAL:
+ // we get out of here with the status information.
+ return new SendStatus(SendStatus.INVALID_ADDRESS, addr, commandString, line);
+
+ // the command was valid, but something went wrong in the server.
+ case SMTPReply.SERVICE_NOT_AVAILABLE:
+ case SMTPReply.MAILBOX_BUSY:
+ case SMTPReply.PROCESSING_ERROR:
+ case SMTPReply.INSUFFICIENT_STORAGE:
+ case SMTPReply.MAILBOX_FULL:
+ // we get out of here with the status information.
+ return new SendStatus(SendStatus.SEND_FAILURE, addr, commandString, line);
+
+ // everything else is considered really bad...
+ default:
+ // we get out of here with the status information.
+ return new SendStatus(SendStatus.GENERAL_ERROR, addr, commandString, line);
+ }
+ }
+
+ /**
+ * Send a command to the server, returning the first response line back as a
+ * reply.
+ *
+ * @param data
+ * The data to send.
+ *
+ * @return A reply object with the reply line.
+ * @exception MessagingException
+ */
+ protected SMTPReply sendCommand(String data) throws MessagingException {
+ sendLine(data);
+ return getReply();
+ }
+
+ /**
+ * Sends a message down the socket and terminates with the appropriate CRLF
+ */
+ protected void sendLine(String data) throws MessagingException {
+ if (socket == null || !socket.isConnected()) {
+ throw new MessagingException("no connection");
+ }
+ try {
+ outputStream.write(data.getBytes());
+ outputStream.write(CR);
+ outputStream.write(LF);
+ outputStream.flush();
+ } catch (IOException e) {
+ throw new MessagingException(e.toString());
+ }
+ }
+
+ /**
+ * Receives one line from the server. A line is a sequence of bytes
+ * terminated by a CRLF
+ *
+ * @return the line from the server as String
+ */
+ protected String receiveLine() throws MessagingException {
+ return receiveLine(TIMEOUT);
+ }
+
+ /**
+ * Get a reply line for an SMTP command.
+ *
+ * @return An SMTP reply object from the stream.
+ */
+ protected SMTPReply getReply() throws MessagingException {
+ try {
+ lastServerResponse = new SMTPReply(receiveLine());
+ // if the first line we receive is a continuation, continue
+ // reading lines until we reach the non-continued one.
+ while (lastServerResponse.isContinued()) {
+ lastServerResponse.addLine(receiveLine());
+ }
+ } catch (MalformedSMTPReplyException e) {
+ throw new MessagingException(e.toString());
+ } catch (MessagingException e) {
+ throw e;
+ }
+ return lastServerResponse;
+ }
+
+ /**
+ * Retrieve the last response received from the SMTP server.
+ *
+ * @return The raw response string (including the error code) returned from
+ * the SMTP server.
+ */
+ public SMTPReply getLastServerResponse() {
+ return lastServerResponse;
+ }
+
+
+ /**
+ * Receives one line from the server. A line is a sequence of bytes
+ * terminated by a CRLF
+ *
+ * @return the line from the server as String
+ */
+ protected String receiveLine(int delayMillis) throws MessagingException {
+ if (socket == null || !socket.isConnected()) {
+ throw new MessagingException("no connection");
+ }
+
+ int timeout = 0;
+
+ try {
+ // for now, read byte for byte, looking for a CRLF
+ timeout = socket.getSoTimeout();
+
+ socket.setSoTimeout(delayMillis);
+
+ StringBuffer buff = new StringBuffer();
+
+ int c;
+ boolean crFound = false, lfFound = false;
+
+ while ((c = inputStream.read()) != -1 && crFound == false && lfFound == false) {
+ // we're looking for a CRLF sequence, so mark each one as seen.
+ // Any other
+ // character gets appended to the end of the buffer.
+ if (c == CR) {
+ crFound = true;
+ } else if (c == LF) {
+ lfFound = true;
+ } else {
+ buff.append((char) c);
+ }
+ }
+
+ String line = buff.toString();
+ return line;
+
+ } catch (SocketException e) {
+ throw new MessagingException(e.toString());
+ } catch (IOException e) {
+ throw new MessagingException(e.toString());
+ } finally {
+ try {
+ socket.setSoTimeout(timeout);
+ } catch (SocketException e) {
+ // ignore - was just trying to do the decent thing...
+ }
+ }
+ }
+
+ /**
+ * Convert an InternetAddress into a form sendable on an SMTP mail command.
+ * InternetAddress.getAddress() generally returns just the address portion
+ * of the full address, minus route address markers. We need to ensure we
+ * have an address with '<' and '>' delimiters.
+ *
+ * @param mail
+ * The mail address returned from InternetAddress.getAddress().
+ *
+ * @return A string formatted for sending.
+ */
+ protected String fixEmailAddress(String mail) {
+ if (mail.charAt(0) == '<') {
+ return mail;
+ }
+ return "<" + mail + ">";
+ }
+
+ /**
+ * Start the handshake process with the server, including setting up and
+ * TLS-level work. At the completion of this task, we should be ready to
+ * authenticate with the server, if needed.
+ */
+ protected boolean sendHandshake() throws MessagingException {
+ // check to see what sort of initial handshake we need to make.
+ boolean useEhlo = props.getBooleanProperty(MAIL_SMTP_EHLO, true);
+ // if we're to use Ehlo, send it and then fall back to just a HELO
+ // message if it fails.
+ if (useEhlo) {
+ if (!sendEhlo()) {
+ sendHelo();
+ }
+ } else {
+ // send the initial hello response.
+ sendHelo();
+ }
+
+ if (useTLS) {
+ // if we've been told to use TLS, and this server doesn't support
+ // it, then this is a failure
+ if (!serverTLS) {
+ throw new MessagingException("Server doesn't support required transport level security");
+ }
+ // if the server supports TLS, then use it for the connection.
+ // on our connection.
+ getConnectedTLSSocket();
+
+ // some servers (gmail is one that I know of) only send a STARTTLS
+ // extension message on the
+ // first EHLO command. Now that we have the TLS handshaking
+ // established, we need to send a
+ // second EHLO message to retrieve the AUTH records from the server.
+ if (!sendEhlo()) {
+ throw new MessagingException("Failure sending EHLO command to SMTP server");
+ }
+ }
+
+ // this worked.
+ return true;
+ }
+
+
+ /**
+ * Switch the connection to using TLS level security, switching to an SSL
+ * socket.
+ */
+ protected void getConnectedTLSSocket() throws MessagingException {
+ debugOut("Attempting to negotiate STARTTLS with server " + serverHost);
+ // tell the server of our intention to start a TLS session
+ SMTPReply line = sendCommand("STARTTLS");
+
+ if (line.getCode() != SMTPReply.SERVICE_READY) {
+ debugOut("STARTTLS command rejected by SMTP server " + serverHost);
+ throw new MessagingException("Unable to make TLS server connection");
+ }
+
+ debugOut("STARTTLS command accepted");
+
+ // the base class handles the socket switch details
+ super.getConnectedTLSSocket();
+ }
+
+
+ /**
+ * Send the EHLO command to the SMTP server.
+ *
+ * @return True if the command was accepted ok, false for any errors.
+ * @exception SMTPTransportException
+ * @exception MalformedSMTPReplyException
+ * @exception MessagingException
+ */
+ protected boolean sendEhlo() throws MessagingException {
+ sendLine("EHLO " + getLocalHost());
+
+ SMTPReply reply = getReply();
+
+ // we get a 250 code back. The first line is just a greeting, and
+ // extensions are identifed on
+ // continuations. If this fails, then we'll try once more with HELO to
+ // establish bona fides.
+ if (reply.getCode() != SMTPReply.COMMAND_ACCEPTED) {
+ return false;
+ }
+
+ // create a fresh mapping and authentications table
+ capabilities = new HashMap();
+ authentications = new ArrayList();
+
+ List lines = reply.getLines();
+ // process all of the continuation lines
+ for (int i = 1; i < lines.size(); i++) {
+ // go process the extention
+ processExtension((String)lines.get(i));
+ }
+ return true;
+ }
+
+ /**
+ * Send the HELO command to the SMTP server.
+ *
+ * @exception MessagingException
+ */
+ protected void sendHelo() throws MessagingException {
+ // create a fresh mapping and authentications table
+ // these will be empty, but it will prevent NPEs
+ capabilities = new HashMap();
+ authentications = new ArrayList();
+
+ sendLine("HELO " + getLocalHost());
+
+ SMTPReply line = getReply();
+
+ // we get a 250 code back. The first line is just a greeting, and
+ // extensions are identifed on
+ // continuations. If this fails, then we'll try once more with HELO to
+ // establish bona fides.
+ if (line.getCode() != SMTPReply.COMMAND_ACCEPTED) {
+ throw new MessagingException("Failure sending HELO command to SMTP server");
+ }
+ }
+
+ /**
+ * Return the current startTLS property.
+ *
+ * @return The current startTLS property.
+ */
+ public boolean getStartTLS() {
+ return useTLS;
+ }
+
+
+ /**
+ * Set a new value for the startTLS property.
+ *
+ * @param start
+ * The new setting.
+ */
+ public void setStartTLS(boolean start) {
+ useTLS = start;
+ }
+
+
+ /**
+ * Process an extension string passed back as the EHLP response.
+ *
+ * @param extension
+ * The string value of the extension (which will be of the form
+ * "NAME arguments").
+ */
+ protected void processExtension(String extension) {
+ debugOut("Processing extension " + extension);
+ String extensionName = extension.toUpperCase();
+ String argument = "";
+
+ int delimiter = extension.indexOf(' ');
+ // if we have a keyword with arguments, parse them out and add to the
+ // argument map.
+ if (delimiter != -1) {
+ extensionName = extension.substring(0, delimiter).toUpperCase();
+ argument = extension.substring(delimiter + 1);
+ }
+
+ // add this to the map so it can be tested later.
+ capabilities.put(extensionName, argument);
+
+ // process a few special ones that don't require extra parsing.
+ // AUTH and AUTH=LOGIN are handled the same
+ if (extensionName.equals("AUTH")) {
+ // if we don't have an argument on AUTH, this means LOGIN.
+ if (argument == null) {
+ authentications.add("LOGIN");
+ } else {
+ // The security mechanisms are blank delimited tokens.
+ StringTokenizer tokenizer = new StringTokenizer(argument);
+
+ while (tokenizer.hasMoreTokens()) {
+ String mechanism = tokenizer.nextToken().toUpperCase();
+ authentications.add(mechanism);
+ }
+ }
+ }
+ // special case for some older servers.
+ else if (extensionName.equals("AUTH=LOGIN")) {
+ authentications.add("LOGIN");
+ }
+ // does this support transport level security?
+ else if (extensionName.equals("STARTTLS")) {
+ // flag this for later
+ serverTLS = true;
+ }
+ }
+
+
+ /**
+ * Retrieve any argument information associated with a extension reported
+ * back by the server on the EHLO command.
+ *
+ * @param name
+ * The name of the target server extension.
+ *
+ * @return Any argument passed on a server extension. Returns null if the
+ * extension did not include an argument or the extension was not
+ * supported.
+ */
+ public String extensionParameter(String name) {
+ if (capabilities != null) {
+ return (String)capabilities.get(name);
+ }
+ return null;
+ }
+
+
+ /**
+ * Tests whether the target server supports a named extension.
+ *
+ * @param name
+ * The target extension name.
+ *
+ * @return true if the target server reported on the EHLO command that is
+ * supports the targer server, false if the extension was not
+ * supported.
+ */
+ public boolean supportsExtension(String name) {
+ // this only returns null if we don't have this extension
+ return extensionParameter(name) != null;
+ }
+
+
+ /**
+ * Authenticate with the server, if necessary (or possible).
+ *
+ * @return true if we are ok to proceed, false for an authentication
+ * failures.
+ */
+ protected boolean processAuthentication() throws MessagingException {
+ // no authentication defined?
+ if (!props.getBooleanProperty(MAIL_SMTP_AUTH, false)) {
+ return true;
+ }
+
+ // we need to authenticate, but we don't have userid/password
+ // information...fail this
+ // immediately.
+ if (username == null || password == null) {
+ return false;
+ }
+
+ // if unable to get an appropriate authenticator, just fail it.
+ ClientAuthenticator authenticator = getSaslAuthenticator();
+ if (authenticator == null) {
+ throw new MessagingException("Unable to obtain SASL authenticator");
+ }
+
+
+ if (debug) {
+ debugOut("Authenticating for user: " + username + " using " + authenticator.getMechanismName());
+ }
+
+ // if the authenticator has some initial data, we compose a command
+ // containing the initial data.
+ if (authenticator.hasInitialResponse()) {
+ StringBuffer command = new StringBuffer();
+ // the auth command initiates the handshaking.
+ command.append("AUTH ");
+ // and tell the server which mechanism we're using.
+ command.append(authenticator.getMechanismName());
+ command.append(" ");
+ // and append the response data
+ command.append(new String(Base64.encode(authenticator.evaluateChallenge(null))));
+ // send the command now
+ sendLine(command.toString());
+ }
+ // we just send an auth command with the command type.
+ else {
+ StringBuffer command = new StringBuffer();
+ // the auth command initiates the handshaking.
+ command.append("AUTH ");
+ // and tell the server which mechanism we're using.
+ command.append(authenticator.getMechanismName());
+ // send the command now
+ sendLine(command.toString());
+ }
+
+ // now process the challenge sequence. We get a 235 response back when
+ // the server accepts the
+ // authentication, and a 334 indicates we have an additional challenge.
+ while (true) {
+ // get the next line, and if it is an error response, return now.
+ SMTPReply line;
+ try {
+ line = new SMTPReply(receiveLine());
+ } catch (MalformedSMTPReplyException e) {
+ throw new MessagingException(e.toString());
+ } catch (MessagingException e) {
+ throw e;
+ }
+
+ // if we get a completion return, we've passed muster, so give an
+ // authentication response.
+ if (line.getCode() == SMTPReply.AUTHENTICATION_COMPLETE) {
+ debugOut("Successful SMTP authentication");
+ return true;
+ }
+ // we have an additional challenge to process.
+ else if (line.getCode() == SMTPReply.AUTHENTICATION_CHALLENGE) {
+ // Does the authenticator think it is finished? We can't answer
+ // an additional challenge,
+ // so fail this.
+ if (authenticator.isComplete()) {
+ return false;
+ }
+
+ // we're passed back a challenge value, Base64 encoded.
+ byte[] challenge = Base64.decode(line.getMessage().getBytes());
+
+ // have the authenticator evaluate and send back the encoded
+ // response.
+ sendLine(new String(Base64.encode(authenticator.evaluateChallenge(challenge))));
+ }
+ // completion or challenge are the only responses we know how to
+ // handle. Anything else must
+ // be a failure.
+ else {
+ if (debug) {
+ debugOut("Authentication failure " + line);
+ }
+ return false;
+ }
+ }
+ }
+
+
+ /**
+ * Attempt to retrieve a SASL authenticator for this
+ * protocol.
+ *
+ * @return A SASL authenticator, or null if a suitable one
+ * was not located.
+ */
+ protected ClientAuthenticator getSaslAuthenticator() {
+ return AuthenticatorFactory.getAuthenticator(props, selectSaslMechanisms(), serverHost, username, password, authid, realm);
+ }
+
+
+ /**
+ * Read the bytes in a stream a test to see if this
+ * conforms to the RFC 2045 rules for 8bit encoding.
+ *
+ * 1) No more than 998 bytes long
+ * 2) All lines are terminated with CRLF sequences
+ * 3) CR and LF characters only occur in properly
+ * formed line separators
+ * 4) No null characters are allowed.
+ *
+ * @param inStream The source input stream.
+ *
+ * @return true if this can be transmitted successfully
+ * using 8bit encoding, false if an alternate encoding
+ * will be required.
+ */
+ protected boolean isValid8bit(InputStream inStream) {
+ try {
+ int ch;
+ int lineLength = 0;
+ while ((ch = inStream.read()) >= 0) {
+ // nulls are decidedly not allowed
+ if (ch == 0) {
+ return false;
+ }
+ // start of a CRLF sequence (potentially)
+ else if (ch == '\r') {
+ // check the next character. There must be one,
+ // and it must be a LF for this to be value
+ ch = inStream.read();
+ if (ch != '\n') {
+ return false;
+ }
+ // reset the line length
+ lineLength = 0;
+ }
+ else {
+ // a normal character
+ lineLength++;
+ // make sure the line is not too long
+ if (lineLength > 998) {
+ return false;
+ }
+ }
+
+ }
+ } catch (IOException e) {
+ return false; // can't read this, don't try passing it
+ }
+ // this converted ok
+ return true;
+ }
+
+
+ /**
+ * Simple holder class for the address/send status duple, as we can have
+ * mixed success for a set of addresses and a message
+ */
+ static public class SendStatus {
+ public final static int SUCCESS = 0;
+
+ public final static int INVALID_ADDRESS = 1;
+
+ public final static int SEND_FAILURE = 2;
+
+ public final static int GENERAL_ERROR = 3;
+
+ // the status type of the send operation.
+ int status;
+
+ // the address associated with this status
+ InternetAddress address;
+
+ // the command string send to the server.
+ String cmd;
+
+ // the reply from the server.
+ SMTPReply reply;
+
+ /**
+ * Constructor for a SendStatus item.
+ *
+ * @param s
+ * The status type.
+ * @param a
+ * The address this is the status for.
+ * @param c
+ * The command string associated with this status.
+ * @param r
+ * The reply information from the server.
+ */
+ public SendStatus(int s, InternetAddress a, String c, SMTPReply r) {
+ this.cmd = c;
+ this.status = s;
+ this.address = a;
+ this.reply = r;
+ }
+
+ /**
+ * Get the status information for this item.
+ *
+ * @return The current status code.
+ */
+ public int getStatus() {
+ return this.status;
+ }
+
+ /**
+ * Retrieve the InternetAddress object associated with this send
+ * operation.
+ *
+ * @return The associated address object.
+ */
+ public InternetAddress getAddress() {
+ return this.address;
+ }
+
+ /**
+ * Retrieve the reply information associated with this send operati
+ *
+ * @return The SMTPReply object received for the operation.
+ */
+ public SMTPReply getReply() {
+ return reply;
+ }
+
+ /**
+ * Get the command string sent for this send operation.
+ *
+ * @return The command string for the MAIL TO command sent to the
+ * server.
+ */
+ public String getCommand() {
+ return cmd;
+ }
+
+ /**
+ * Get an exception object associated with this send operation. There is
+ * a mechanism for reporting send success via a send operation, so this
+ * will be either a success or failure exception.
+ *
+ * @param reportSuccess
+ * Indicates if we want success operations too.
+ *
+ * @return A newly constructed exception object.
+ */
+ public MessagingException getException(boolean reportSuccess) {
+ if (status != SUCCESS) {
+ return new SMTPAddressFailedException(address, cmd, reply.getCode(), reply.getMessage());
+ } else {
+ if (reportSuccess) {
+ return new SMTPAddressSucceededException(address, cmd, reply.getCode(), reply.getMessage());
+ }
+ }
+ return null;
+ }
+ }
+
+
+ /**
+ * Reset the server connection after an error.
+ *
+ * @exception MessagingException
+ */
+ public void resetConnection() throws MessagingException {
+ // we want the caller to retrieve the last response responsbile for
+ // requiring the reset, so save and
+ // restore that info around the reset.
+ SMTPReply last = lastServerResponse;
+
+ // send a reset command.
+ SMTPReply line = sendCommand("RSET");
+
+ // if this did not reset ok, just close the connection
+ if (line.getCode() != SMTPReply.COMMAND_ACCEPTED) {
+ close();
+ }
+ // restore this.
+ lastServerResponse = last;
+ }
+
+
+ /**
+ * Return the current reportSuccess property.
+ *
+ * @return The current reportSuccess property.
+ */
+ public boolean getReportSuccess() {
+ return reportSuccess;
+ }
+
+ /**
+ * Set a new value for the reportSuccess property.
+ *
+ * @param report
+ * The new setting.
+ */
+ public void setReportSuccess(boolean report) {
+ reportSuccess = report;
+ }
+}
+
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPReply.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPReply.java
index ce564c7..ec9999f 100644
--- a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPReply.java
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPReply.java
@@ -28,6 +28,49 @@
* @version $Rev$ $Date$
*/
class SMTPReply {
+ // SMTP reply codes
+ public static final int SERVICE_READY = 220;
+
+ public static final int SERVICE_CLOSING = 221;
+
+ public static final int AUTHENTICATION_COMPLETE = 235;
+
+ public static final int COMMAND_ACCEPTED = 250;
+
+ public static final int ADDRESS_NOT_LOCAL = 251;
+
+ public static final int AUTHENTICATION_CHALLENGE = 334;
+
+ public static final int START_MAIL_INPUT = 354;
+
+ public static final int SERVICE_NOT_AVAILABLE = 421;
+
+ public static final int MAILBOX_BUSY = 450;
+
+ public static final int PROCESSING_ERROR = 451;
+
+ public static final int INSUFFICIENT_STORAGE = 452;
+
+ public static final int COMMAND_SYNTAX_ERROR = 500;
+
+ public static final int PARAMETER_SYNTAX_ERROR = 501;
+
+ public static final int COMMAND_NOT_IMPLEMENTED = 502;
+
+ public static final int INVALID_COMMAND_SEQUENCE = 503;
+
+ public static final int COMMAND_PARAMETER_NOT_IMPLEMENTED = 504;
+
+ public static final int MAILBOX_NOT_FOUND = 550;
+
+ public static final int USER_NOT_LOCAL = 551;
+
+ public static final int MAILBOX_FULL = 552;
+
+ public static final int INVALID_MAILBOX = 553;
+
+ public static final int TRANSACTION_FAILED = 553;
+
// The original reply string
private final String reply;
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPTransport.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPTransport.java
index 7b56a9d..4552f7f 100644
--- a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPTransport.java
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPTransport.java
@@ -20,20 +20,9 @@
package org.apache.geronimo.javamail.transport.smtp;
import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.net.InetAddress;
-import java.net.Socket;
-import java.net.SocketException;
-import java.net.UnknownHostException;
+import java.net.Socket;
import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.StringTokenizer;
import javax.mail.Address;
import javax.mail.AuthenticationFailedException;
@@ -47,19 +36,9 @@
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.internet.MimePart;
-import javax.net.ssl.SSLSocket;
-import org.apache.geronimo.javamail.authentication.ClientAuthenticator;
-import org.apache.geronimo.javamail.authentication.CramMD5Authenticator;
-import org.apache.geronimo.javamail.authentication.DigestMD5Authenticator;
-import org.apache.geronimo.javamail.authentication.LoginAuthenticator;
-import org.apache.geronimo.javamail.authentication.PlainAuthenticator;
-import org.apache.geronimo.javamail.util.CountingOutputStream;
-import org.apache.geronimo.javamail.util.MIMEOutputStream;
-import org.apache.geronimo.javamail.util.TraceInputStream;
-import org.apache.geronimo.javamail.util.TraceOutputStream;
-import org.apache.geronimo.mail.util.Base64;
-import org.apache.geronimo.mail.util.XText;
+import org.apache.geronimo.javamail.util.ProtocolProperties;
+import org.apache.geronimo.javamail.transport.smtp.SMTPConnection.SendStatus;
/**
* Simple implementation of SMTP transport. Just does plain RFC821-ish delivery.
@@ -77,203 +56,32 @@
* @version $Rev$ $Date$
*/
public class SMTPTransport extends Transport {
-
- /**
- * constants for EOL termination
- */
- protected static final char CR = '\r';
-
- protected static final char LF = '\n';
-
- /**
- * property keys for top level session properties.
- */
- protected static final String MAIL_LOCALHOST = "mail.localhost";
-
- protected static final String MAIL_SSLFACTORY_CLASS = "mail.SSLSocketFactory.class";
-
/**
* property keys for protocol properties. The actual property name will be
* appended with "mail." + protocol + ".", where the protocol is either
* "smtp" or "smtps".
*/
- protected static final String MAIL_SMTP_AUTH = "auth";
-
- protected static final String MAIL_SMTP_PORT = "port";
-
- protected static final String MAIL_SMTP_LOCALHOST = "localhost";
-
- protected static final String MAIL_SMTP_TIMEOUT = "timeout";
-
- protected static final String MAIL_SMTP_SASL_REALM = "sasl.realm";
-
- protected static final String MAIL_SMTP_TLS = "starttls.enable";
-
- protected static final String MAIL_SMTP_FACTORY_CLASS = "socketFactory.class";
-
- protected static final String MAIL_SMTP_FACTORY_FALLBACK = "socketFactory.fallback";
-
- protected static final String MAIL_SMTP_FACTORY_PORT = "socketFactory.port";
-
- protected static final String MAIL_SMTP_REPORT_SUCCESS = "reportsuccess";
-
- protected static final String MAIL_SMTP_STARTTLS_ENABLE = "starttls.enable";
-
protected static final String MAIL_SMTP_DSN_NOTIFY = "dsn.notify";
-
protected static final String MAIL_SMTP_SENDPARTIAL = "sendpartial";
-
- protected static final String MAIL_SMTP_LOCALADDRESS = "localaddress";
-
- protected static final String MAIL_SMTP_LOCALPORT = "localport";
-
- protected static final String MAIL_SMTP_QUITWAIT = "quitwait";
-
- protected static final String MAIL_SMTP_FROM = "from";
-
- protected static final String MAIL_SMTP_DSN_RET = "dsn.ret";
-
- protected static final String MAIL_SMTP_SUBMITTER = "submitter";
-
protected static final String MAIL_SMTP_EXTENSION = "mailextension";
-
- protected static final String MAIL_SMTP_EHLO = "ehlo";
-
- protected static final String MAIL_SMTP_ENCODE_TRACE = "encodetrace";
-
- protected static final String MAIL_SMTP_ALLOW8BITMIME = "allow8bitmime";
-
- protected static final String MAIL_SMTP_SSL_PROTOCOLS = "ssl.protocols";
-
- protected static final String MAIL_SMTP_SSL_CIPHERSUITES = "ssl.ciphersuites";
-
- protected static final int MIN_MILLIS = 1000 * 60;
-
- protected static final int TIMEOUT = MIN_MILLIS * 5;
-
protected static final String DEFAULT_MAIL_HOST = "localhost";
protected static final int DEFAULT_MAIL_SMTP_PORT = 25;
-
protected static final int DEFAULT_MAIL_SMTPS_PORT = 465;
- // SMTP reply codes
- protected static final int SERVICE_READY = 220;
-
- protected static final int SERVICE_CLOSING = 221;
-
- protected static final int AUTHENTICATION_COMPLETE = 235;
-
- protected static final int COMMAND_ACCEPTED = 250;
-
- protected static final int ADDRESS_NOT_LOCAL = 251;
-
- protected static final int AUTHENTICATION_CHALLENGE = 334;
-
- protected static final int START_MAIL_INPUT = 354;
-
- protected static final int SERVICE_NOT_AVAILABLE = 421;
-
- protected static final int MAILBOX_BUSY = 450;
-
- protected static final int PROCESSING_ERROR = 451;
-
- protected static final int INSUFFICIENT_STORAGE = 452;
-
- protected static final int COMMAND_SYNTAX_ERROR = 500;
-
- protected static final int PARAMETER_SYNTAX_ERROR = 501;
-
- protected static final int COMMAND_NOT_IMPLEMENTED = 502;
-
- protected static final int INVALID_COMMAND_SEQUENCE = 503;
-
- protected static final int COMMAND_PARAMETER_NOT_IMPLEMENTED = 504;
-
- protected static final int MAILBOX_NOT_FOUND = 550;
-
- protected static final int USER_NOT_LOCAL = 551;
-
- protected static final int MAILBOX_FULL = 552;
-
- protected static final int INVALID_MAILBOX = 553;
-
- protected static final int TRANSACTION_FAILED = 553;
-
- protected static final String AUTHENTICATION_PLAIN = "PLAIN";
-
- protected static final String AUTHENTICATION_LOGIN = "LOGIN";
-
- protected static final String AUTHENTICATION_CRAMMD5 = "CRAM-MD5";
-
- protected static final String AUTHENTICATION_DIGESTMD5 = "DIGEST-MD5";
-
- // the protocol we're working with. This will be either "smtp" or "smtps".
- protected String protocol;
-
- // the target host
- protected String host;
-
- // the default port to use for this protocol (differs between "smtp" and
- // "smtps").
- protected int defaultPort;
-
- // the target server port.
- protected int port;
-
- // the connection socket...can be a plain socket or SSLSocket, if TLS is
- // being used.
- protected Socket socket;
-
- // our local host name
- protected String localHost;
-
- // input stream used to read data. If Sasl is in use, this might be other
- // than the
- // direct access to the socket input stream.
- protected InputStream inputStream;
-
- // the other end of the connection pipeline.
- protected OutputStream outputStream;
-
- // list of authentication mechanisms supported by the server
- protected HashMap serverAuthenticationMechanisms;
-
- // map of server extension arguments
- protected HashMap serverExtensionArgs;
-
- // do we report success after completion of each mail send.
- protected boolean reportSuccess;
-
- // does the server support transport level security?
- protected boolean serverTLS = false;
-
- // is TLS enabled on our part?
- protected boolean useTLS = false;
-
- // should we use 8BITMIME encoding if supported by the server?
- protected boolean use8bit = false;
// do we use SSL for our initial connection?
protected boolean sslConnection = false;
-
- // the username we connect with
- protected String username;
-
- // the authentication password.
- protected String password;
-
- // the target SASL realm (normally null unless explicitly set or we have an
- // authentication mechanism that
- // requires it.
- protected String realm;
+
+ // our accessor for protocol properties and the holder of
+ // protocol-specific information
+ protected ProtocolProperties props;
+ // our active connection object
+ protected SMTPConnection connection;
// the last response line received from the server.
protected SMTPReply lastServerResponse = null;
- // our session provided debug output stream.
- protected PrintStream debugStream;
-
/**
* Normal constructor for an SMTPTransport() object. This constructor is
* used to build a transport instance for the "smtp" protocol.
@@ -286,6 +94,7 @@
public SMTPTransport(Session session, URLName name) {
this(session, name, "smtp", DEFAULT_MAIL_SMTP_PORT, false);
}
+
/**
* Common constructor used by the SMTPTransport and SMTPSTransport classes
@@ -309,22 +118,15 @@
*/
protected SMTPTransport(Session session, URLName name, String protocol, int defaultPort, boolean sslConnection) {
super(session, name);
- this.protocol = protocol;
-
- // these are defaults based on what the superclass specifies.
- this.defaultPort = defaultPort;
- this.sslConnection = sslConnection;
- // check to see if we need to throw an exception after a send operation.
- reportSuccess = isProtocolPropertyTrue(MAIL_SMTP_REPORT_SUCCESS);
- // and also check for TLS enablement.
- useTLS = isProtocolPropertyTrue(MAIL_SMTP_STARTTLS_ENABLE);
- // and also check for TLS enablement.
- use8bit = isProtocolPropertyTrue(MAIL_SMTP_ALLOW8BITMIME);
-
- // get our debug output.
- debugStream = session.getDebugOut();
+
+ // create the protocol property holder. This gives an abstraction over the different
+ // flavors of the protocol.
+ props = new ProtocolProperties(session, protocol, sslConnection, defaultPort);
+ // the connection manages connection for the transport
+ connection = new SMTPConnection(props);
}
+
/**
* Connect to a server using an already created socket. This connection is
* just like any other connection, except we will not create a new socket.
@@ -333,9 +135,10 @@
* The socket connection to use.
*/
public void connect(Socket socket) throws MessagingException {
- this.socket = socket;
+ connection.connect(socket);
super.connect();
}
+
/**
* Do the protocol connection for an SMTP transport. This handles server
@@ -358,71 +161,8 @@
*/
protected boolean protocolConnect(String host, int port, String username, String password)
throws MessagingException {
- if (debug) {
- debugOut("Connecting to server " + host + ":" + port + " for user " + username);
- }
-
- // now check to see if we need to authenticate. If we need this, then
- // we must have a username and
- // password specified. Failing this may result in a user prompt to
- // collect the information.
- boolean mustAuthenticate = isProtocolPropertyTrue(MAIL_SMTP_AUTH);
-
- // if we need to authenticate, and we don't have both a userid and
- // password, then we fail this
- // immediately. The Service.connect() method will try to obtain the user
- // information and retry the
- // connection one time.
- if (mustAuthenticate && (username == null || password == null)) {
- debugOut("Failing connection for missing authentication information");
- return false;
- }
-
- // if the port is defaulted, then see if we have something configured in
- // the session.
- // if not configured, we just use the default default.
- if (port == -1) {
- // take the default first.
- port = defaultPort;
- String configuredPort = getProtocolProperty(MAIL_SMTP_PORT);
- if (configuredPort != null) {
- port = Integer.parseInt(configuredPort);
- }
- }
-
- // Before we do anything, let's make sure that we succesfully received a host
- if ( host == null ) {
- host = DEFAULT_MAIL_HOST;
- }
-
- try {
-
- // create socket and connect to server.
- getConnection(host, port, username, password);
-
- // receive welcoming message
- if (!getWelcome()) {
- debugOut("Error getting welcome message");
- throw new MessagingException("Error in getting welcome msg");
- }
-
- // say hello
- if (!sendHandshake()) {
- debugOut("Error getting processing handshake message");
- throw new MessagingException("Error in saying EHLO to server");
- }
-
- // authenticate with the server, if necessary
- if (!processAuthentication()) {
- debugOut("User authentication failure");
- throw new AuthenticationFailedException("Error authenticating with server");
- }
- } catch (IOException e) {
- debugOut("I/O exception establishing connection", e);
- throw new MessagingException("Connection error", e);
- }
- debugOut("Successful connection");
- return true;
+ // the connection pool handles all of the details here.
+ return connection.protocolConnect(host, port, username, password);
}
/**
@@ -454,6 +194,22 @@
if (addresses == null || addresses.length == 0) {
throw new MessagingException("Null or empty address array");
}
+
+ boolean reportSuccess = getReportSuccess();
+
+ // now see how we're configured for this send operation.
+ boolean partialSends = false;
+
+ // this can be attached directly to the message.
+ if (message instanceof SMTPMessage) {
+ partialSends = ((SMTPMessage) message).getSendPartial();
+ }
+
+ // if still false on the message object, check for a property
+ // version also
+ if (!partialSends) {
+ partialSends = props.getBooleanProperty(MAIL_SMTP_SENDPARTIAL, false);
+ }
boolean haveGroup = false;
@@ -483,33 +239,12 @@
Address[] sent = null;
Address[] unsent = null;
Address[] invalid = null;
-
- boolean sendAs8bit = false;
-
- // If the server supports the 8BITMIME extension, we might need to change the
- // transfer encoding for the content to allow for direct transmission of the
- // 8-bit codes.
- if (supportsExtension("8BITMIME")) {
- // we only do this if the capability was enabled via a property option or
- // by explicitly setting the property on the message object.
- if (use8bit || (message instanceof SMTPMessage && ((SMTPMessage)message).getAllow8bitMIME())) {
- // make sure we add the BODY= option to the FROM message.
- sendAs8bit = true;
- // go check the content and see if the can convert the transfer encoding to
- // allow direct 8-bit transmission.
- if (convertTransferEncoding((MimeMessage)message)) {
- // if we changed the encoding on any of the parts, then we
- // need to save the message again
- message.saveChanges();
- }
- }
- }
try {
// send sender first. If this failed, send a failure notice of the
// event, using the full list of
// addresses as the unsent, and nothing for the rest.
- if (!sendMailFrom(message, sendAs8bit)) {
+ if (!connection.sendMailFrom(message)) {
unsent = addresses;
sent = new Address[0];
invalid = new Address[0];
@@ -517,70 +252,14 @@
notifyTransportListeners(TransportEvent.MESSAGE_NOT_DELIVERED, sent, unsent, invalid, message);
// include the reponse information here.
- SMTPReply last = lastServerResponse;
+ SMTPReply last = connection.getLastServerResponse();
// now send an "uber-exception" to indicate the failure.
throw new SMTPSendFailedException("MAIL FROM", last.getCode(), last.getMessage(), null, sent, unsent,
invalid);
}
- String dsn = null;
-
- // there's an optional notification argument that can be added to
- // MAIL TO. See if we've been
- // provided with one.
-
- // an SMTPMessage object is the first source
- if (message instanceof SMTPMessage) {
- // get the notification options
- int options = ((SMTPMessage) message).getNotifyOptions();
-
- switch (options) {
- // a zero value indicates nothing is set.
- case 0:
- break;
-
- case SMTPMessage.NOTIFY_NEVER:
- dsn = "NEVER";
- break;
-
- case SMTPMessage.NOTIFY_SUCCESS:
- dsn = "SUCCESS";
- break;
-
- case SMTPMessage.NOTIFY_FAILURE:
- dsn = "FAILURE";
- break;
-
- case SMTPMessage.NOTIFY_DELAY:
- dsn = "DELAY";
- break;
-
- // now for combinations...there are few enough combinations here
- // that we can just handle this in the switch statement rather
- // than have to
- // concatentate everything together.
- case (SMTPMessage.NOTIFY_SUCCESS + SMTPMessage.NOTIFY_FAILURE):
- dsn = "SUCCESS,FAILURE";
- break;
-
- case (SMTPMessage.NOTIFY_SUCCESS + SMTPMessage.NOTIFY_DELAY):
- dsn = "SUCCESS,DELAY";
- break;
-
- case (SMTPMessage.NOTIFY_FAILURE + SMTPMessage.NOTIFY_DELAY):
- dsn = "FAILURE,DELAY";
- break;
-
- case (SMTPMessage.NOTIFY_SUCCESS + SMTPMessage.NOTIFY_FAILURE + SMTPMessage.NOTIFY_DELAY):
- dsn = "SUCCESS,FAILURE,DELAY";
- break;
- }
- }
-
- // if still null, grab a property value (yada, yada, yada...)
- if (dsn == null) {
- dsn = getProtocolProperty(MAIL_SMTP_DSN_NOTIFY);
- }
+ // get the additional notification status, if available
+ String dsn = getDeliveryStatusNotification(message);
// we need to know about any failures once we've gone through the
// complete list, so keep a
@@ -606,50 +285,36 @@
InternetAddress target = (InternetAddress) addresses[i];
// write out the record now.
- SendStatus status = sendRcptTo(target, dsn);
+ SendStatus status = connection.sendRcptTo(target, dsn);
stats[i] = status;
switch (status.getStatus()) {
- // successfully sent
- case SendStatus.SUCCESS:
- sentAddresses.add(target);
- break;
+ // successfully sent
+ case SendStatus.SUCCESS:
+ sentAddresses.add(target);
+ break;
- // we have an invalid address of some sort, or a general sending
- // error (which we'll
- // interpret as due to an invalid address.
- case SendStatus.INVALID_ADDRESS:
- case SendStatus.GENERAL_ERROR:
- sendFailure = true;
- invalidAddresses.add(target);
- break;
+ // we have an invalid address of some sort, or a general sending
+ // error (which we'll
+ // interpret as due to an invalid address.
+ case SendStatus.INVALID_ADDRESS:
+ case SendStatus.GENERAL_ERROR:
+ sendFailure = true;
+ invalidAddresses.add(target);
+ break;
- // good address, but this was a send failure.
- case SendStatus.SEND_FAILURE:
- sendFailure = true;
- unsentAddresses.add(target);
- break;
- }
+ // good address, but this was a send failure.
+ case SendStatus.SEND_FAILURE:
+ sendFailure = true;
+ unsentAddresses.add(target);
+ break;
+ }
}
// if we had a send failure, then we need to check if we allow
// partial sends. If not allowed,
// we abort the send operation now.
if (sendFailure) {
- // now see how we're configured for this send operation.
- boolean partialSends = false;
-
- // this can be attached directly to the message.
- if (message instanceof SMTPMessage) {
- partialSends = ((SMTPMessage) message).getSendPartial();
- }
-
- // if still false on the message object, check for a property
- // version also
- if (!partialSends) {
- partialSends = isProtocolPropertyTrue(MAIL_SMTP_SENDPARTIAL);
- }
-
// if we're not allowing partial successes or we've failed on
// all of the addresses, it's
// time to abort.
@@ -669,7 +334,7 @@
// go reset our connection so we can process additional
// sends.
- resetConnection();
+ connection.resetConnection();
// get a list of chained exceptions for all of the failures.
MessagingException failures = generateExceptionChain(stats, false);
@@ -681,7 +346,7 @@
try {
// try to send the data
- sendData(message);
+ connection.sendData(message);
} catch (MessagingException e) {
// If there's an error at this point, this is a complete
// delivery failure.
@@ -721,7 +386,7 @@
// get a list of chained exceptions for all of the failures (and
// the successes, if reportSuccess has been
// turned on).
- MessagingException failures = generateExceptionChain(stats, getReportSuccess());
+ MessagingException failures = generateExceptionChain(stats, reportSuccess);
// now send an "uber-exception" to indicate the failure.
throw new SMTPSendFailedException("MAIL TO", 0, "Invalid Address", failures, sent, unsent, invalid);
@@ -751,30 +416,94 @@
throw e;
}
}
+
+
+ /**
+ * Determine what delivery status notification should
+ * be added to the RCPT TO: command.
+ *
+ * @param message The message we're sending.
+ *
+ * @return The string NOTIFY= value to add to the command.
+ */
+ protected String getDeliveryStatusNotification(Message message) {
+ String dsn = null;
+
+ // there's an optional notification argument that can be added to
+ // MAIL TO. See if we've been
+ // provided with one.
+
+ // an SMTPMessage object is the first source
+ if (message instanceof SMTPMessage) {
+ // get the notification options
+ int options = ((SMTPMessage) message).getNotifyOptions();
+
+ switch (options) {
+ // a zero value indicates nothing is set.
+ case 0:
+ break;
+
+ case SMTPMessage.NOTIFY_NEVER:
+ dsn = "NEVER";
+ break;
+
+ case SMTPMessage.NOTIFY_SUCCESS:
+ dsn = "SUCCESS";
+ break;
+
+ case SMTPMessage.NOTIFY_FAILURE:
+ dsn = "FAILURE";
+ break;
+
+ case SMTPMessage.NOTIFY_DELAY:
+ dsn = "DELAY";
+ break;
+
+ // now for combinations...there are few enough combinations here
+ // that we can just handle this in the switch statement rather
+ // than have to
+ // concatentate everything together.
+ case (SMTPMessage.NOTIFY_SUCCESS + SMTPMessage.NOTIFY_FAILURE):
+ dsn = "SUCCESS,FAILURE";
+ break;
+
+ case (SMTPMessage.NOTIFY_SUCCESS + SMTPMessage.NOTIFY_DELAY):
+ dsn = "SUCCESS,DELAY";
+ break;
+
+ case (SMTPMessage.NOTIFY_FAILURE + SMTPMessage.NOTIFY_DELAY):
+ dsn = "FAILURE,DELAY";
+ break;
+
+ case (SMTPMessage.NOTIFY_SUCCESS + SMTPMessage.NOTIFY_FAILURE + SMTPMessage.NOTIFY_DELAY):
+ dsn = "SUCCESS,FAILURE,DELAY";
+ break;
+ }
+ }
+
+ // if still null, grab a property value (yada, yada, yada...)
+ if (dsn == null) {
+ dsn = props.getProperty(MAIL_SMTP_DSN_NOTIFY);
+ }
+ return dsn;
+ }
+
+
/**
* Close the connection. On completion, we'll be disconnected from the
* server and unable to send more data.
- *
+ *
* @exception MessagingException
*/
public void close() throws MessagingException {
- // if we're already closed, get outta here.
- if (socket == null) {
- return;
- }
- try {
- // say goodbye
- sendQuit();
- } finally {
- // and close up the connection. We do this in a finally block to
- // make sure the connection
- // is shut down even if quit gets an error.
- closeServerConnection();
- super.close();
- }
+ // This is done to ensure proper event notification.
+ super.close();
+ // NB: We reuse the connection if asked to reconnect
+ connection.close();
}
-
+
+
/**
* Turn a series of send status items into a chain of exceptions indicating
* the state of each send operation.
@@ -810,28 +539,6 @@
}
/**
- * Reset the server connection after an error.
- *
- * @exception MessagingException
- */
- protected void resetConnection() throws MessagingException {
- // we want the caller to retrieve the last response responsbile for
- // requiring the reset, so save and
- // restore that info around the reset.
- SMTPReply last = lastServerResponse;
-
- // send a reset command.
- SMTPReply line = sendCommand("RSET");
-
- // if this did not reset ok, just close the connection
- if (line.getCode() != COMMAND_ACCEPTED) {
- close();
- }
- // restore this.
- lastServerResponse = last;
- }
-
- /**
* Expand the address list by converting any group addresses into single
* address targets.
*
@@ -864,1171 +571,7 @@
// convert back into an array.
return (Address[]) expandedAddresses.toArray(new Address[0]);
}
-
- /**
- * Create a transport connection object and connect it to the target server.
- *
- * @param host
- * The target server host.
- * @param port
- * The connection port.
- *
- * @exception MessagingException
- */
- protected void getConnection(String host, int port, String username, String password) throws IOException {
- this.host = host;
- this.port = port;
- this.username = username;
- this.password = password;
- // and see if STARTTLS is enabled.
- useTLS = isProtocolPropertyTrue(MAIL_SMTP_TLS);
- serverAuthenticationMechanisms = new HashMap();
- // We might have been passed a socket to connect with...if not, we need
- // to create one of the correct type.
- if (socket == null) {
- // if this is the "smtps" protocol, we start with an SSLSocket
- if (sslConnection) {
- getConnectedSSLSocket();
- } else {
- getConnectedSocket();
- }
- }
- // if we already have a socket, get some information from it and
- // override what we've been passed.
- else {
- port = socket.getPort();
- host = socket.getInetAddress().getHostName();
- }
- // now set up the input/output streams.
- inputStream = new TraceInputStream(socket.getInputStream(), debugStream, debug,
- isProtocolPropertyTrue(MAIL_SMTP_ENCODE_TRACE));
-
- outputStream = new TraceOutputStream(socket.getOutputStream(), debugStream, debug,
- isProtocolPropertyTrue(MAIL_SMTP_ENCODE_TRACE));
- }
-
- /**
- * Get a property associated with this mail protocol.
- *
- * @param name
- * The name of the property.
- *
- * @return The property value (returns null if the property has not been
- * set).
- */
- protected String getProtocolProperty(String name) {
- // the name we're given is the least qualified part of the name. We
- // construct the full property name
- // using the protocol (either "smtp" or "smtps").
- String fullName = "mail." + protocol + "." + name;
- return getSessionProperty(fullName);
- }
-
- /**
- * Get a property associated with this mail session.
- *
- * @param name
- * The name of the property.
- *
- * @return The property value (returns null if the property has not been
- * set).
- */
- protected String getSessionProperty(String name) {
- return session.getProperty(name);
- }
-
- /**
- * Get a property associated with this mail session. Returns the provided
- * default if it doesn't exist.
- *
- * @param name
- * The name of the property.
- * @param defaultValue
- * The default value to return if the property doesn't exist.
- *
- * @return The property value (returns defaultValue if the property has not
- * been set).
- */
- protected String getSessionProperty(String name, String defaultValue) {
- String result = session.getProperty(name);
- if (result == null) {
- return defaultValue;
- }
- return result;
- }
-
- /**
- * Get a property associated with this mail session. Returns the provided
- * default if it doesn't exist.
- *
- * @param name
- * The name of the property.
- * @param defaultValue
- * The default value to return if the property doesn't exist.
- *
- * @return The property value (returns defaultValue if the property has not
- * been set).
- */
- protected String getProtocolProperty(String name, String defaultValue) {
- // the name we're given is the least qualified part of the name. We
- // construct the full property name
- // using the protocol (either "smtp" or "smtps").
- String fullName = "mail." + protocol + "." + name;
- return getSessionProperty(fullName, defaultValue);
- }
-
- /**
- * Get a property associated with this mail session as an integer value.
- * Returns the default value if the property doesn't exist or it doesn't
- * have a valid int value.
- *
- * @param name
- * The name of the property.
- * @param defaultValue
- * The default value to return if the property doesn't exist.
- *
- * @return The property value converted to an int.
- */
- protected int getIntSessionProperty(String name, int defaultValue) {
- String result = getSessionProperty(name);
- if (result != null) {
- try {
- // convert into an int value.
- return Integer.parseInt(result);
- } catch (NumberFormatException e) {
- }
- }
- // return default value if it doesn't exist is isn't convertable.
- return defaultValue;
- }
-
- /**
- * Get a property associated with this mail session as an integer value.
- * Returns the default value if the property doesn't exist or it doesn't
- * have a valid int value.
- *
- * @param name
- * The name of the property.
- * @param defaultValue
- * The default value to return if the property doesn't exist.
- *
- * @return The property value converted to an int.
- */
- protected int getIntProtocolProperty(String name, int defaultValue) {
- // the name we're given is the least qualified part of the name. We
- // construct the full property name
- // using the protocol (either "smtp" or "smtps").
- String fullName = "mail." + protocol + "." + name;
- return getIntSessionProperty(fullName, defaultValue);
- }
-
- /**
- * Process a session property as a boolean value, returning either true or
- * false.
- *
- * @return True if the property value is "true". Returns false for any other
- * value (including null).
- */
- protected boolean isProtocolPropertyTrue(String name) {
- // the name we're given is the least qualified part of the name. We
- // construct the full property name
- // using the protocol (either "smtp" or "smtps").
- String fullName = "mail." + protocol + "." + name;
- return isSessionPropertyTrue(fullName);
- }
-
- /**
- * Process a session property as a boolean value, returning either true or
- * false.
- *
- * @return True if the property value is "true". Returns false for any other
- * value (including null).
- */
- protected boolean isSessionPropertyTrue(String name) {
- String property = session.getProperty(name);
- if (property != null) {
- return property.equals("true");
- }
- return false;
- }
-
- /**
- * Process a session property as a boolean value, returning either true or
- * false.
- *
- * @return True if the property value is "false". Returns false for other
- * value (including null).
- */
- protected boolean isSessionPropertyFalse(String name) {
- String property = session.getProperty(name);
- if (property != null) {
- return property.equals("false");
- }
- return false;
- }
-
- /**
- * Process a session property as a boolean value, returning either true or
- * false.
- *
- * @return True if the property value is "false". Returns false for other
- * value (including null).
- */
- protected boolean isProtocolPropertyFalse(String name) {
- // the name we're given is the least qualified part of the name. We
- // construct the full property name
- // using the protocol (either "smtp" or "smtps").
- String fullName = "mail." + protocol + "." + name;
- return isSessionPropertyFalse(fullName);
- }
-
- /**
- * Close the server connection at termination.
- */
- protected void closeServerConnection() {
- try {
- socket.close();
- } catch (IOException ignored) {
- }
-
- socket = null;
- inputStream = null;
- outputStream = null;
- }
-
- /**
- * Creates a connected socket
- *
- * @exception MessagingException
- */
- protected void getConnectedSocket() throws IOException {
- if (debug) {
- debugOut("Attempting plain socket connection to server " + host + ":" + port);
- }
-
- // the socket factory can be specified via a session property. By
- // default, we just directly
- // instantiate a socket without using a factor.
- String socketFactory = getProtocolProperty(MAIL_SMTP_FACTORY_CLASS);
-
- // there are several protocol properties that can be set to tune the
- // created socket. We need to
- // retrieve those bits before creating the socket.
- int timeout = getIntProtocolProperty(MAIL_SMTP_TIMEOUT, -1);
- InetAddress localAddress = null;
- // see if we have a local address override.
- String localAddrProp = getProtocolProperty(MAIL_SMTP_LOCALADDRESS);
- if (localAddrProp != null) {
- localAddress = InetAddress.getByName(localAddrProp);
- }
-
- // check for a local port...default is to allow socket to choose.
- int localPort = getIntProtocolProperty(MAIL_SMTP_LOCALPORT, 0);
-
- socket = null;
-
- // if there is no socket factory defined (normal), we just create a
- // socket directly.
- if (socketFactory == null) {
- socket = new Socket(host, port, localAddress, localPort);
- }
-
- else {
- try {
- int socketFactoryPort = getIntProtocolProperty(MAIL_SMTP_FACTORY_PORT, -1);
-
- // we choose the port used by the socket based on overrides.
- Integer portArg = new Integer(socketFactoryPort == -1 ? port : socketFactoryPort);
-
- // use the current context loader to resolve this.
- ClassLoader loader = Thread.currentThread().getContextClassLoader();
- Class factoryClass = loader.loadClass(socketFactory);
-
- // done indirectly, we need to invoke the method using
- // reflection.
- // This retrieves a factory instance.
- Method getDefault = factoryClass.getMethod("getDefault", new Class[0]);
- Object defFactory = getDefault.invoke(new Object(), new Object[0]);
-
- // now that we have the factory, there are two different
- // createSocket() calls we use,
- // depending on whether we have a localAddress override.
-
- if (localAddress != null) {
- // retrieve the createSocket(String, int, InetAddress, int)
- // method.
- Class[] createSocketSig = new Class[] { String.class, Integer.TYPE, InetAddress.class, Integer.TYPE };
- Method createSocket = factoryClass.getMethod("createSocket", createSocketSig);
-
- Object[] createSocketArgs = new Object[] { host, portArg, localAddress, new Integer(localPort) };
- socket = (Socket) createSocket.invoke(defFactory, createSocketArgs);
- } else {
- // retrieve the createSocket(String, int) method.
- Class[] createSocketSig = new Class[] { String.class, Integer.TYPE };
- Method createSocket = factoryClass.getMethod("createSocket", createSocketSig);
-
- Object[] createSocketArgs = new Object[] { host, portArg };
- socket = (Socket) createSocket.invoke(defFactory, createSocketArgs);
- }
- } catch (Throwable e) {
- // if a socket factor is specified, then we may need to fall
- // back to a default. This behavior
- // is controlled by (surprise) more session properties.
- if (isProtocolPropertyTrue(MAIL_SMTP_FACTORY_FALLBACK)) {
- debugOut("First plain socket attempt faile, falling back to default factory", e);
- socket = new Socket(host, port, localAddress, localPort);
- }
- // we have an exception. We're going to throw an IOException,
- // which may require unwrapping
- // or rewrapping the exception.
- else {
- // we have an exception from the reflection, so unwrap the
- // base exception
- if (e instanceof InvocationTargetException) {
- e = ((InvocationTargetException) e).getTargetException();
- }
-
- debugOut("Plain socket creation failure", e);
- // throw this as an IOException, with the original exception
- // attached.
- IOException ioe = new IOException("Error connecting to " + host + ", " + port);
- ioe.initCause(e);
- throw ioe;
- }
- }
- }
-
- if (timeout >= 0) {
- socket.setSoTimeout(timeout);
- }
- }
-
- /**
- * Creates a connected SSL socket for an initial SSL connection.
- *
- * @exception MessagingException
- */
- protected void getConnectedSSLSocket() throws IOException {
- if (debug) {
- debugOut("Attempting SSL socket connection to server " + host + ":" + port);
- }
- // the socket factory can be specified via a protocol property, a
- // session property, and if all else
- // fails (which it usually does), we fall back to the standard factory
- // class.
- String socketFactory = getProtocolProperty(MAIL_SMTP_FACTORY_CLASS, getSessionProperty(MAIL_SSLFACTORY_CLASS,
- "javax.net.ssl.SSLSocketFactory"));
-
- // there are several protocol properties that can be set to tune the
- // created socket. We need to
- // retrieve those bits before creating the socket.
- int timeout = getIntProtocolProperty(MAIL_SMTP_TIMEOUT, -1);
- InetAddress localAddress = null;
- // see if we have a local address override.
- String localAddrProp = getProtocolProperty(MAIL_SMTP_LOCALADDRESS);
- if (localAddrProp != null) {
- localAddress = InetAddress.getByName(localAddrProp);
- }
-
- // check for a local port...default is to allow socket to choose.
- int localPort = getIntProtocolProperty(MAIL_SMTP_LOCALPORT, 0);
-
- socket = null;
-
- // if there is no socket factory defined (normal), we just create a
- // socket directly.
- if (socketFactory == null) {
- socket = new Socket(host, port, localAddress, localPort);
- }
-
- else {
- // we'll try this with potentially two different factories if we're
- // allowed to fall back.
- boolean fallback = isProtocolPropertyTrue(MAIL_SMTP_FACTORY_FALLBACK);
-
- while (true) {
- try {
- if (debug) {
- debugOut("Creating SSL socket using factory " + socketFactory);
- }
-
- int socketFactoryPort = getIntProtocolProperty(MAIL_SMTP_FACTORY_PORT, -1);
-
- // we choose the port used by the socket based on overrides.
- Integer portArg = new Integer(socketFactoryPort == -1 ? port : socketFactoryPort);
-
- // use the current context loader to resolve this.
- ClassLoader loader = Thread.currentThread().getContextClassLoader();
- Class factoryClass = loader.loadClass(socketFactory);
-
- // done indirectly, we need to invoke the method using
- // reflection.
- // This retrieves a factory instance.
- Method getDefault = factoryClass.getMethod("getDefault", new Class[0]);
- Object defFactory = getDefault.invoke(new Object(), new Object[0]);
-
- // now that we have the factory, there are two different
- // createSocket() calls we use,
- // depending on whether we have a localAddress override.
-
- if (localAddress != null) {
- // retrieve the createSocket(String, int, InetAddress,
- // int) method.
- Class[] createSocketSig = new Class[] { String.class, Integer.TYPE, InetAddress.class,
- Integer.TYPE };
- Method createSocket = factoryClass.getMethod("createSocket", createSocketSig);
-
- Object[] createSocketArgs = new Object[] { host, portArg, localAddress, new Integer(localPort) };
- socket = (Socket) createSocket.invoke(defFactory, createSocketArgs);
- } else {
- // retrieve the createSocket(String, int) method.
- Class[] createSocketSig = new Class[] { String.class, Integer.TYPE };
- Method createSocket = factoryClass.getMethod("createSocket", createSocketSig);
-
- Object[] createSocketArgs = new Object[] { host, portArg };
- socket = (Socket) createSocket.invoke(defFactory, createSocketArgs);
- }
- // now break out and configure the socket.
- break;
- } catch (Throwable e) {
- // if we're allowed to fallback, then use the default
- // factory and try this again. We only
- // allow this to happen once.
- if (fallback) {
- debugOut("First attempt at creating SSL socket failed, falling back to default factory");
- socketFactory = "javax.net.ssl.SSLSocketFactory";
- fallback = false;
- continue;
- }
- // we have an exception. We're going to throw an
- // IOException, which may require unwrapping
- // or rewrapping the exception.
- else {
- // we have an exception from the reflection, so unwrap
- // the base exception
- if (e instanceof InvocationTargetException) {
- e = ((InvocationTargetException) e).getTargetException();
- }
-
- debugOut("Failure creating SSL socket", e);
-
- // throw this as an IOException, with the original
- // exception attached.
- IOException ioe = new IOException("Error connecting to " + host + ", " + port);
- ioe.initCause(e);
- throw ioe;
- }
- }
- }
- }
-
- if (timeout >= 0) {
- socket.setSoTimeout(timeout);
- }
-
- // if there is a list of protocols specified, we need to break this down into
- // the individual names
- String protocols = getProtocolProperty(MAIL_SMTP_SSL_PROTOCOLS);
- if (protocols != null) {
- ArrayList<String> list = new ArrayList<String>();
- StringTokenizer t = new StringTokenizer(protocols);
-
- while (t.hasMoreTokens()) {
- list.add(t.nextToken());
- }
-
- ((SSLSocket)socket).setEnabledProtocols(list.toArray(new String[list.size()]));
- }
-
- // and do the same for any cipher suites
- String suites = getProtocolProperty(MAIL_SMTP_SSL_CIPHERSUITES);
- if (suites != null) {
- ArrayList<String> list = new ArrayList<String>();
- StringTokenizer t = new StringTokenizer(suites);
-
- while (t.hasMoreTokens()) {
- list.add(t.nextToken());
- }
-
- ((SSLSocket)socket).setEnabledCipherSuites(list.toArray(new String[list.size()]));
- }
- }
-
- /**
- * Switch the connection to using TLS level security, switching to an SSL
- * socket.
- */
- protected void getConnectedTLSSocket() throws MessagingException {
- if (debug) {
- debugOut("Attempting to negotiate STARTTLS with server " + host);
- }
- // tell the server of our intention to start a TLS session
- SMTPReply line = sendCommand("STARTTLS");
-
- if (line.getCode() != SERVICE_READY) {
- if (debug) {
- debugOut("STARTTLS command rejected by SMTP server " + host);
- }
- throw new MessagingException("Unable to make TLS server connection");
- }
- // it worked, now switch the socket into TLS mode
- try {
-
- // we use the same target and port as the current connection.
- String host = socket.getInetAddress().getHostName();
- int port = socket.getPort();
-
- // the socket factory can be specified via a session property. By
- // default, we use
- // the native SSL factory.
- String socketFactory = getProtocolProperty(MAIL_SMTP_FACTORY_CLASS, "javax.net.ssl.SSLSocketFactory");
-
- // use the current context loader to resolve this.
- ClassLoader loader = Thread.currentThread().getContextClassLoader();
- Class factoryClass = loader.loadClass(socketFactory);
-
- // done indirectly, we need to invoke the method using reflection.
- // This retrieves a factory instance.
- Method getDefault = factoryClass.getMethod("getDefault", new Class[0]);
- Object defFactory = getDefault.invoke(new Object(), new Object[0]);
-
- // now we need to invoke createSocket()
- Class[] createSocketSig = new Class[] { Socket.class, String.class, Integer.TYPE, Boolean.TYPE };
- Method createSocket = factoryClass.getMethod("createSocket", createSocketSig);
-
- Object[] createSocketArgs = new Object[] { socket, host, new Integer(port), Boolean.TRUE };
-
- // and finally create the socket
- Socket sslSocket = (Socket) createSocket.invoke(defFactory, createSocketArgs);
-
- // if this is an instance of SSLSocket (very common), try setting
- // the protocol to be
- // "TLSv1". If this is some other class because of a factory
- // override, we'll just have to
- // accept that things will work.
- if (sslSocket instanceof SSLSocket) {
- ((SSLSocket) sslSocket).setEnabledProtocols(new String[] { "TLSv1" });
- ((SSLSocket) sslSocket).setUseClientMode(true);
- ((SSLSocket) sslSocket).startHandshake();
- }
-
- // and finally, as a last step, replace our input streams with the
- // secure ones.
- // now set up the input/output streams.
- inputStream = new TraceInputStream(sslSocket.getInputStream(), debugStream, debug,
- isProtocolPropertyTrue(MAIL_SMTP_ENCODE_TRACE));
- ;
- outputStream = new TraceOutputStream(sslSocket.getOutputStream(), debugStream, debug,
- isProtocolPropertyTrue(MAIL_SMTP_ENCODE_TRACE));
- // this is our active socket now
- socket = sslSocket;
-
- } catch (Exception e) {
- debugOut("Failure attempting to convert connection to TLS", e);
- throw new MessagingException("Unable to convert connection to SSL", e);
- }
- }
-
- /**
- * Get the servers welcome blob from the wire....
- */
- protected boolean getWelcome() throws MessagingException {
- SMTPReply line = getReply();
- // just return the error status...we don't care about any of the
- // response information
- return !line.isError();
- }
- /**
- * Get an estimate of the transmission size for this
- * message. This size is the complete message as it is
- * encoded and transmitted on the DATA command, not counting
- * the terminating ".CRLF".
- *
- * @param msg The message we're sending.
- *
- * @return The count of bytes, if it can be calculated.
- */
- protected int getSizeEstimate(Message msg) {
- // now the data... I could look at the type, but
- try {
- CountingOutputStream outputStream = new CountingOutputStream();
-
- // the data content has two requirements we need to meet by
- // filtering the
- // output stream. Requirement 1 is to conicalize any line breaks.
- // All line
- // breaks will be transformed into properly formed CRLF sequences.
- //
- // Requirement 2 is to perform byte-stuff for any line that begins
- // with a "."
- // so that data is not confused with the end-of-data marker (a
- // "\r\n.\r\n" sequence.
- //
- // The MIME output stream performs those two functions on behalf of
- // the content
- // writer.
- MIMEOutputStream mimeOut = new MIMEOutputStream(outputStream);
-
- msg.writeTo(mimeOut);
-
- // now to finish, we make sure there's a line break at the end.
- mimeOut.forceTerminatingLineBreak();
- // and flush the data to send it along
- mimeOut.flush();
-
- return outputStream.getCount();
- } catch (IOException e) {
- return 0; // can't get an estimate
- } catch (MessagingException e) {
- return 0; // can't get an estimate
- }
- }
-
-
- /**
- * Sends the data in the message down the socket. This presumes the server
- * is in the right place and ready for getting the DATA message and the data
- * right place in the sequence
- */
- protected void sendData(Message msg) throws MessagingException {
-
- // send the DATA command
- SMTPReply line = sendCommand("DATA");
-
- if (line.isError()) {
- throw new MessagingException("Error issuing SMTP 'DATA' command: " + line);
- }
-
- // now the data... I could look at the type, but
- try {
- // the data content has two requirements we need to meet by
- // filtering the
- // output stream. Requirement 1 is to conicalize any line breaks.
- // All line
- // breaks will be transformed into properly formed CRLF sequences.
- //
- // Requirement 2 is to perform byte-stuff for any line that begins
- // with a "."
- // so that data is not confused with the end-of-data marker (a
- // "\r\n.\r\n" sequence.
- //
- // The MIME output stream performs those two functions on behalf of
- // the content
- // writer.
- MIMEOutputStream mimeOut = new MIMEOutputStream(outputStream);
-
- msg.writeTo(mimeOut);
-
- // now to finish, we send a CRLF sequence, followed by a ".".
- mimeOut.writeSMTPTerminator();
- // and flush the data to send it along
- mimeOut.flush();
- } catch (IOException e) {
- throw new MessagingException(e.toString());
- } catch (MessagingException e) {
- throw new MessagingException(e.toString());
- }
-
- // use a longer time out here to give the server time to process the
- // data.
- try {
- line = new SMTPReply(receiveLine(TIMEOUT * 2));
- } catch (MalformedSMTPReplyException e) {
- throw new MessagingException(e.toString());
- } catch (MessagingException e) {
- throw new MessagingException(e.toString());
- }
-
- if (line.isError()) {
- throw new MessagingException("Error issuing SMTP 'DATA' command: " + line);
- }
- }
-
- /**
- * Sends the QUIT message and receieves the response
- */
- protected void sendQuit() throws MessagingException {
- // there's yet another property that controls whether we should wait for
- // a reply for a QUIT command. If true, we're suppposed to wait for a response
- // from the QUIT command. Otherwise we just send the QUIT and bail. The default
- // is "false"
- if (isProtocolPropertyTrue(MAIL_SMTP_QUITWAIT)) {
- // handle as a real command...we're going to ignore the response.
- sendCommand("QUIT");
- } else {
- // just send the command without waiting for a response.
- sendLine("QUIT");
- }
- }
-
- /**
- * Sets a receiver address for the current message
- *
- * @param addr
- * The target address.
- * @param dsn
- * An optional notification address appended to the MAIL command.
- *
- * @return The status for this particular send operation.
- * @exception MessagingException
- */
- protected SendStatus sendRcptTo(InternetAddress addr, String dsn) throws MessagingException {
- // compose the command using the fixed up email address. Normally, this
- // involves adding
- // "<" and ">" around the address.
-
- StringBuffer command = new StringBuffer();
-
- // compose the first part of the command
- command.append("RCPT TO: ");
- command.append(fixEmailAddress(addr.getAddress()));
-
- // if we have DSN information, append it to the command.
- if (dsn != null) {
- command.append(" NOTIFY=");
- command.append(dsn);
- }
-
- // get a string version of this command.
- String commandString = command.toString();
-
- SMTPReply line = sendCommand(commandString);
-
- switch (line.getCode()) {
- // these two are both successful transmissions
- case COMMAND_ACCEPTED:
- case ADDRESS_NOT_LOCAL:
- // we get out of here with the status information.
- return new SendStatus(SendStatus.SUCCESS, addr, commandString, line);
-
- // these are considered invalid address errors
- case PARAMETER_SYNTAX_ERROR:
- case INVALID_COMMAND_SEQUENCE:
- case MAILBOX_NOT_FOUND:
- case INVALID_MAILBOX:
- case USER_NOT_LOCAL:
- // we get out of here with the status information.
- return new SendStatus(SendStatus.INVALID_ADDRESS, addr, commandString, line);
-
- // the command was valid, but something went wrong in the server.
- case SERVICE_NOT_AVAILABLE:
- case MAILBOX_BUSY:
- case PROCESSING_ERROR:
- case INSUFFICIENT_STORAGE:
- case MAILBOX_FULL:
- // we get out of here with the status information.
- return new SendStatus(SendStatus.SEND_FAILURE, addr, commandString, line);
-
- // everything else is considered really bad...
- default:
- // we get out of here with the status information.
- return new SendStatus(SendStatus.GENERAL_ERROR, addr, commandString, line);
- }
- }
-
- /**
- * Set the sender for this mail.
- *
- * @param message
- * The message we're sending.
- * @param sendAs8bit Indicates we're going to transmit the message body
- * as 8BITMIME, so add the BODY= option to the FROM
- * message.
- *
- * @return True if the command was accepted, false otherwise.
- * @exception MessagingException
- */
- protected boolean sendMailFrom(Message message, boolean sendAs8bit) throws MessagingException {
-
- // need to sort the from value out from a variety of sources.
- String from = null;
-
- // first potential source is from the message itself, if it's an
- // instance of SMTPMessage.
- if (message instanceof SMTPMessage) {
- from = ((SMTPMessage) message).getEnvelopeFrom();
- }
-
- // if not available from the message, check the protocol property next
- if (from == null || from.length() == 0) {
- // the from value can be set explicitly as a property
- from = getProtocolProperty(MAIL_SMTP_FROM);
- }
-
- // if not there, see if we have something in the message header.
- if (from == null || from.length() == 0) {
- Address[] fromAddresses = message.getFrom();
-
- // if we have some addresses in the header, then take the first one
- // as our From: address
- if (fromAddresses != null && fromAddresses.length > 0) {
- from = ((InternetAddress) fromAddresses[0]).getAddress();
- }
- // get what the InternetAddress class believes to be the local
- // address.
- else {
- InternetAddress local = InternetAddress.getLocalAddress(session);
- if (local != null) {
- from = local.getAddress();
- }
- }
- }
-
- if (from == null || from.length() == 0) {
- throw new MessagingException("no FROM address");
- }
-
- StringBuffer command = new StringBuffer();
-
- // start building up the command
- command.append("MAIL FROM: ");
- command.append(fixEmailAddress(from));
- if (sendAs8bit) {
- command.append(" BODY=8BITMIME");
- }
-
- // some servers ask for a size estimate on the initial send
- if (supportsExtension("SIZE")) {
- int estimate = getSizeEstimate(message);
- if (estimate > 0) {
- command.append(" SIZE=" + estimate);
- }
- }
-
- // does this server support Delivery Status Notification? Then we may
- // need to add some extra to the command.
- if (supportsExtension("DSN")) {
- String returnNotification = null;
-
- // the return notification stuff might be set as value on the
- // message object itself.
- if (message instanceof SMTPMessage) {
- // we need to convert the option into a string value.
- switch (((SMTPMessage) message).getReturnOption()) {
- case SMTPMessage.RETURN_FULL:
- returnNotification = "FULL";
- break;
-
- case SMTPMessage.RETURN_HDRS:
- returnNotification = "HDRS";
- break;
- }
- }
-
- // if not obtained from the message object, it can also be set as a
- // property.
- if (returnNotification == null) {
- // the DSN value is set by yet another property.
- returnNotification = getProtocolProperty(MAIL_SMTP_DSN_RET);
- }
-
- // if we have a target, add the notification stuff to our FROM
- // command.
- if (returnNotification != null) {
- command.append(" RET=");
- command.append(returnNotification);
- }
- }
-
- // if this server supports AUTH and we have submitter information, then
- // we also add the
- // "AUTH=" keyword to the MAIL FROM command (see RFC 2554).
-
- if (supportsExtension("AUTH")) {
- String submitter = null;
-
- // another option that can be specified on the message object.
- if (message instanceof SMTPMessage) {
- submitter = ((SMTPMessage) message).getSubmitter();
- }
- // if not part of the object, try for a propery version.
- if (submitter == null) {
- // we only send the extra keyword is a submitter is specified.
- submitter = getProtocolProperty(MAIL_SMTP_SUBMITTER);
- }
- // we have one...add the keyword, plus the submitter info in xtext
- // format (defined by RFC 1891).
- if (submitter != null) {
- command.append(" AUTH=");
- try {
- // add this encoded
- command.append(new String(XText.encode(submitter.getBytes("US-ASCII"))));
- } catch (UnsupportedEncodingException e) {
- throw new MessagingException("Invalid submitter value " + submitter);
- }
- }
- }
-
- String extension = null;
-
- // now see if we need to add any additional extension info to this
- // command. The extension is not
- // checked for validity. That's the reponsibility of the caller.
- if (message instanceof SMTPMessage) {
- extension = ((SMTPMessage) message).getMailExtension();
- }
- // this can come either from the object or from a set property.
- if (extension == null) {
- extension = getProtocolProperty(MAIL_SMTP_EXTENSION);
- }
-
- // have something real to add?
- if (extension != null && extension.length() != 0) {
- // tack this on the end with a blank delimiter.
- command.append(' ');
- command.append(extension);
- }
-
- // and finally send the command
- SMTPReply line = sendCommand(command.toString());
-
- // 250 response indicates success.
- return line.getCode() == COMMAND_ACCEPTED;
- }
-
- /**
- * Send a command to the server, returning the first response line back as a
- * reply.
- *
- * @param data
- * The data to send.
- *
- * @return A reply object with the reply line.
- * @exception MessagingException
- */
- protected SMTPReply sendCommand(String data) throws MessagingException {
- sendLine(data);
- return getReply();
- }
-
- /**
- * Sends a message down the socket and terminates with the appropriate CRLF
- */
- protected void sendLine(String data) throws MessagingException {
- if (socket == null || !socket.isConnected()) {
- throw new MessagingException("no connection");
- }
- try {
- outputStream.write(data.getBytes());
- outputStream.write(CR);
- outputStream.write(LF);
- outputStream.flush();
- } catch (IOException e) {
- throw new MessagingException(e.toString());
- }
- }
-
- /**
- * Receives one line from the server. A line is a sequence of bytes
- * terminated by a CRLF
- *
- * @return the line from the server as String
- */
- protected String receiveLine() throws MessagingException {
- return receiveLine(TIMEOUT);
- }
-
- /**
- * Get a reply line for an SMTP command.
- *
- * @return An SMTP reply object from the stream.
- */
- protected SMTPReply getReply() throws MessagingException {
- try {
- lastServerResponse = new SMTPReply(receiveLine());
- // if the first line we receive is a continuation, continue
- // reading lines until we reach the non-continued one.
- while (lastServerResponse.isContinued()) {
- lastServerResponse.addLine(receiveLine());
- }
- } catch (MalformedSMTPReplyException e) {
- throw new MessagingException(e.toString());
- } catch (MessagingException e) {
- throw e;
- }
- return lastServerResponse;
- }
-
- /**
- * Retrieve the last response received from the SMTP server.
- *
- * @return The raw response string (including the error code) returned from
- * the SMTP server.
- */
- public String getLastServerResponse() {
- if (lastServerResponse == null) {
- return "";
- }
- return lastServerResponse.getReply();
- }
-
- /**
- * Receives one line from the server. A line is a sequence of bytes
- * terminated by a CRLF
- *
- * @return the line from the server as String
- */
- protected String receiveLine(int delayMillis) throws MessagingException {
- if (socket == null || !socket.isConnected()) {
- throw new MessagingException("no connection");
- }
-
- int timeout = 0;
-
- try {
- // for now, read byte for byte, looking for a CRLF
- timeout = socket.getSoTimeout();
-
- socket.setSoTimeout(delayMillis);
-
- StringBuffer buff = new StringBuffer();
-
- int c;
- boolean crFound = false, lfFound = false;
-
- while ((c = inputStream.read()) != -1 && crFound == false && lfFound == false) {
- // we're looking for a CRLF sequence, so mark each one as seen.
- // Any other
- // character gets appended to the end of the buffer.
- if (c == CR) {
- crFound = true;
- } else if (c == LF) {
- lfFound = true;
- } else {
- buff.append((char) c);
- }
- }
-
- String line = buff.toString();
- return line;
-
- } catch (SocketException e) {
- throw new MessagingException(e.toString());
- } catch (IOException e) {
- throw new MessagingException(e.toString());
- } finally {
- try {
- socket.setSoTimeout(timeout);
- } catch (SocketException e) {
- // ignore - was just trying to do the decent thing...
- }
- }
- }
-
- /**
- * Convert an InternetAddress into a form sendable on an SMTP mail command.
- * InternetAddress.getAddress() generally returns just the address portion
- * of the full address, minus route address markers. We need to ensure we
- * have an address with '<' and '>' delimiters.
- *
- * @param mail
- * The mail address returned from InternetAddress.getAddress().
- *
- * @return A string formatted for sending.
- */
- protected String fixEmailAddress(String mail) {
- if (mail.charAt(0) == '<') {
- return mail;
- }
- return "<" + mail + ">";
- }
-
- /**
- * Start the handshake process with the server, including setting up and
- * TLS-level work. At the completion of this task, we should be ready to
- * authenticate with the server, if needed.
- */
- protected boolean sendHandshake() throws MessagingException {
- // check to see what sort of initial handshake we need to make.
- boolean useEhlo = !isProtocolPropertyFalse(MAIL_SMTP_EHLO);
- // if we're to use Ehlo, send it and then fall back to just a HELO
- // message if it fails.
- if (useEhlo) {
- if (!sendEhlo()) {
- sendHelo();
- }
- } else {
- // send the initial hello response.
- sendHelo();
- }
-
- if (useTLS) {
- // if we've been told to use TLS, and this server doesn't support
- // it, then this is a failure
- if (!serverTLS) {
- throw new MessagingException("Server doesn't support required transport level security");
- }
- // if the server supports TLS, then use it for the connection.
- // on our connection.
- getConnectedTLSSocket();
-
- // some servers (gmail is one that I know of) only send a STARTTLS
- // extension message on the
- // first EHLO command. Now that we have the TLS handshaking
- // established, we need to send a
- // second EHLO message to retrieve the AUTH records from the server.
- serverAuthenticationMechanisms.clear();
- if (!sendEhlo()) {
- throw new MessagingException("Failure sending EHLO command to SMTP server");
- }
- }
-
- // this worked.
- return true;
- }
-
- /**
- * Send the EHLO command to the SMTP server.
- *
- * @return True if the command was accepted ok, false for any errors.
- * @exception SMTPTransportException
- * @exception MalformedSMTPReplyException
- * @exception MessagingException
- */
- protected boolean sendEhlo() throws MessagingException {
- sendLine("EHLO " + getLocalHost());
-
- SMTPReply reply = getReply();
-
- // we get a 250 code back. The first line is just a greeting, and
- // extensions are identifed on
- // continuations. If this fails, then we'll try once more with HELO to
- // establish bona fides.
- if (reply.getCode() != COMMAND_ACCEPTED) {
- return false;
- }
-
- // get a fresh extension mapping table.
- serverExtensionArgs = new HashMap();
-
- List lines = reply.getLines();
- // process all of the continuation lines
- for (int i = 1; i < lines.size(); i++) {
- // go process the extention
- processExtension((String)lines.get(i));
- }
- return true;
- }
-
- /**
- * Send the HELO command to the SMTP server.
- *
- * @exception MessagingException
- */
- protected void sendHelo() throws MessagingException {
- sendLine("HELO " + getLocalHost());
-
- SMTPReply line = getReply();
-
- // we get a 250 code back. The first line is just a greeting, and
- // extensions are identifed on
- // continuations. If this fails, then we'll try once more with HELO to
- // establish bona fides.
- if (line.getCode() != COMMAND_ACCEPTED) {
- throw new MessagingException("Failure sending HELO command to SMTP server");
- }
- }
/**
* Retrieve the local client host name.
@@ -2037,38 +580,28 @@
* @exception SMTPTransportException
*/
public String getLocalHost() throws MessagingException {
- if (localHost == null) {
-
- try {
- localHost = InetAddress.getLocalHost().getHostName();
- } catch (UnknownHostException e) {
- // fine, we're misconfigured - ignore
- }
-
- if (localHost == null) {
- localHost = getProtocolProperty(MAIL_SMTP_LOCALHOST);
- }
-
- if (localHost == null) {
- localHost = getSessionProperty(MAIL_LOCALHOST);
- }
-
- if (localHost == null) {
- throw new MessagingException("Can't get local hostname. "
- + " Please correctly configure JDK/DNS or set mail.smtp.localhost");
- }
- }
-
- return localHost;
+ return connection.getLocalHost();
}
+
+ /**
+ * Explicitly set the local host information.
+ *
+ * @param localHost
+ * The new localHost name.
+ */
+ public void setLocalHost(String localHost) {
+ connection.setLocalHost(localHost);
+ }
+
+
/**
* Return the current reportSuccess property.
*
* @return The current reportSuccess property.
*/
public boolean getReportSuccess() {
- return reportSuccess;
+ return connection.getReportSuccess();
}
/**
@@ -2078,7 +611,7 @@
* The new setting.
*/
public void setReportSuccess(boolean report) {
- reportSuccess = report;
+ connection.setReportSuccess(report);
}
/**
@@ -2087,7 +620,7 @@
* @return The current startTLS property.
*/
public boolean getStartTLS() {
- return reportSuccess;
+ return connection.getStartTLS();
}
/**
@@ -2097,7 +630,7 @@
* The new setting.
*/
public void setStartTLS(boolean start) {
- useTLS = start;
+ connection.setStartTLS(start);
}
/**
@@ -2108,11 +641,7 @@
* @return The current realm information (which can be null).
*/
public String getSASLRealm() {
- // if the realm is null, retrieve it using the realm session property.
- if (realm == null) {
- realm = getProtocolProperty(MAIL_SMTP_SASL_REALM);
- }
- return realm;
+ return connection.getSASLRealm();
}
/**
@@ -2122,487 +651,6 @@
* The new realm name.
*/
public void setSASLRealm(String name) {
- realm = name;
- }
-
- /**
- * Explicitly set the local host information.
- *
- * @param localHost
- * The new localHost name.
- */
- public void setLocalHost(String localHost) {
- this.localHost = localHost;
- }
-
- /**
- * Process an extension string passed back as the EHLP response.
- *
- * @param extension
- * The string value of the extension (which will be of the form
- * "NAME arguments").
- */
- protected void processExtension(String extension) {
- debugOut("Processing extension " + extension);
- String extensionName = extension.toUpperCase();
- String argument = "";
-
- int delimiter = extension.indexOf(' ');
- // if we have a keyword with arguments, parse them out and add to the
- // argument map.
- if (delimiter != -1) {
- extensionName = extension.substring(0, delimiter).toUpperCase();
- argument = extension.substring(delimiter + 1);
- }
-
- // add this to the map so it can be tested later.
- serverExtensionArgs.put(extensionName, argument);
-
- // process a few special ones that don't require extra parsing.
- // AUTH and AUTH=LOGIN are handled the same
- if (extensionName.equals("AUTH")) {
- // if we don't have an argument on AUTH, this means LOGIN.
- if (argument == null) {
- serverAuthenticationMechanisms.put("LOGIN", "LOGIN");
- } else {
- // The security mechanisms are blank delimited tokens.
- StringTokenizer tokenizer = new StringTokenizer(argument);
-
- while (tokenizer.hasMoreTokens()) {
- String mechanism = tokenizer.nextToken().toUpperCase();
- serverAuthenticationMechanisms.put(mechanism, mechanism);
- }
- }
- }
- // special case for some older servers.
- else if (extensionName.equals("AUTH=LOGIN")) {
- serverAuthenticationMechanisms.put("LOGIN", "LOGIN");
- }
- // does this support transport level security?
- else if (extensionName.equals("STARTTLS")) {
- // flag this for later
- serverTLS = true;
- }
- }
-
- /**
- * Retrieve any argument information associated with a extension reported
- * back by the server on the EHLO command.
- *
- * @param name
- * The name of the target server extension.
- *
- * @return Any argument passed on a server extension. Returns null if the
- * extension did not include an argument or the extension was not
- * supported.
- */
- public String extensionParameter(String name) {
- if (serverExtensionArgs != null) {
- return (String) serverExtensionArgs.get(name);
- }
- return null;
- }
-
- /**
- * Tests whether the target server supports a named extension.
- *
- * @param name
- * The target extension name.
- *
- * @return true if the target server reported on the EHLO command that is
- * supports the targer server, false if the extension was not
- * supported.
- */
- public boolean supportsExtension(String name) {
- // this only returns null if we don't have this extension
- return extensionParameter(name) != null;
- }
-
- /**
- * Determine if the target server supports a given authentication mechanism.
- *
- * @param mechanism
- * The mechanism name.
- *
- * @return true if the server EHLO response indicates it supports the
- * mechanism, false otherwise.
- */
- protected boolean supportsAuthentication(String mechanism) {
- return serverAuthenticationMechanisms.get(mechanism) != null;
- }
-
- /**
- * Authenticate with the server, if necessary (or possible).
- *
- * @return true if we are ok to proceed, false for an authentication
- * failures.
- */
- protected boolean processAuthentication() throws MessagingException {
- // no authentication defined?
- if (!isProtocolPropertyTrue(MAIL_SMTP_AUTH)) {
- return true;
- }
-
- // we need to authenticate, but we don't have userid/password
- // information...fail this
- // immediately.
- if (username == null || password == null) {
- return false;
- }
-
- ClientAuthenticator authenticator = null;
-
- // now go through the progression of mechanisms we support, from the
- // most secure to the
- // least secure.
-
- if (supportsAuthentication(AUTHENTICATION_DIGESTMD5)) {
- authenticator = new DigestMD5Authenticator(host, username, password, getSASLRealm());
- } else if (supportsAuthentication(AUTHENTICATION_CRAMMD5)) {
- authenticator = new CramMD5Authenticator(username, password);
- } else if (supportsAuthentication(AUTHENTICATION_LOGIN)) {
- authenticator = new LoginAuthenticator(username, password);
- } else if (supportsAuthentication(AUTHENTICATION_PLAIN)) {
- authenticator = new PlainAuthenticator(username, password);
- } else {
- // can't find a mechanism we support in common
- return false;
- }
-
- if (debug) {
- debugOut("Authenticating for user: " + username + " using " + authenticator.getMechanismName());
- }
-
- // if the authenticator has some initial data, we compose a command
- // containing the initial data.
- if (authenticator.hasInitialResponse()) {
- StringBuffer command = new StringBuffer();
- // the auth command initiates the handshaking.
- command.append("AUTH ");
- // and tell the server which mechanism we're using.
- command.append(authenticator.getMechanismName());
- command.append(" ");
- // and append the response data
- command.append(new String(Base64.encode(authenticator.evaluateChallenge(null))));
- // send the command now
- sendLine(command.toString());
- }
- // we just send an auth command with the command type.
- else {
- StringBuffer command = new StringBuffer();
- // the auth command initiates the handshaking.
- command.append("AUTH ");
- // and tell the server which mechanism we're using.
- command.append(authenticator.getMechanismName());
- // send the command now
- sendLine(command.toString());
- }
-
- // now process the challenge sequence. We get a 235 response back when
- // the server accepts the
- // authentication, and a 334 indicates we have an additional challenge.
- while (true) {
- // get the next line, and if it is an error response, return now.
- SMTPReply line;
- try {
- line = new SMTPReply(receiveLine());
- } catch (MalformedSMTPReplyException e) {
- throw new MessagingException(e.toString());
- } catch (MessagingException e) {
- throw e;
- }
-
- // if we get a completion return, we've passed muster, so give an
- // authentication response.
- if (line.getCode() == AUTHENTICATION_COMPLETE) {
- debugOut("Successful SMTP authentication");
- return true;
- }
- // we have an additional challenge to process.
- else if (line.getCode() == AUTHENTICATION_CHALLENGE) {
- // Does the authenticator think it is finished? We can't answer
- // an additional challenge,
- // so fail this.
- if (authenticator.isComplete()) {
- return false;
- }
-
- // we're passed back a challenge value, Base64 encoded.
- byte[] challenge = Base64.decode(line.getMessage().getBytes());
-
- // have the authenticator evaluate and send back the encoded
- // response.
- sendLine(new String(Base64.encode(authenticator.evaluateChallenge(challenge))));
- }
- // completion or challenge are the only responses we know how to
- // handle. Anything else must
- // be a failure.
- else {
- if (debug) {
- debugOut("Authentication failure " + line);
- }
- return false;
- }
- }
- }
-
- /**
- * Simple holder class for the address/send status duple, as we can have
- * mixed success for a set of addresses and a message
- */
- public class SendStatus {
- public final static int SUCCESS = 0;
-
- public final static int INVALID_ADDRESS = 1;
-
- public final static int SEND_FAILURE = 2;
-
- public final static int GENERAL_ERROR = 3;
-
- // the status type of the send operation.
- int status;
-
- // the address associated with this status
- InternetAddress address;
-
- // the command string send to the server.
- String cmd;
-
- // the reply from the server.
- SMTPReply reply;
-
- /**
- * Constructor for a SendStatus item.
- *
- * @param s
- * The status type.
- * @param a
- * The address this is the status for.
- * @param c
- * The command string associated with this status.
- * @param r
- * The reply information from the server.
- */
- public SendStatus(int s, InternetAddress a, String c, SMTPReply r) {
- this.cmd = c;
- this.status = s;
- this.address = a;
- this.reply = r;
- }
-
- /**
- * Get the status information for this item.
- *
- * @return The current status code.
- */
- public int getStatus() {
- return this.status;
- }
-
- /**
- * Retrieve the InternetAddress object associated with this send
- * operation.
- *
- * @return The associated address object.
- */
- public InternetAddress getAddress() {
- return this.address;
- }
-
- /**
- * Retrieve the reply information associated with this send operati
- *
- * @return The SMTPReply object received for the operation.
- */
- public SMTPReply getReply() {
- return reply;
- }
-
- /**
- * Get the command string sent for this send operation.
- *
- * @return The command string for the MAIL TO command sent to the
- * server.
- */
- public String getCommand() {
- return cmd;
- }
-
- /**
- * Get an exception object associated with this send operation. There is
- * a mechanism for reporting send success via a send operation, so this
- * will be either a success or failure exception.
- *
- * @param reportSuccess
- * Indicates if we want success operations too.
- *
- * @return A newly constructed exception object.
- */
- public MessagingException getException(boolean reportSuccess) {
- if (status != SUCCESS) {
- return new SMTPAddressFailedException(address, cmd, reply.getCode(), reply.getMessage());
- } else {
- if (reportSuccess) {
- return new SMTPAddressSucceededException(address, cmd, reply.getCode(), reply.getMessage());
- }
- }
- return null;
- }
- }
-
- /**
- * Internal debug output routine.
- *
- * @param value
- * The string value to output.
- */
- protected void debugOut(String message) {
- if (debug) {
- debugStream.println("SMTPTransport DEBUG: " + message);
- }
- }
-
- /**
- * Internal debugging routine for reporting exceptions.
- *
- * @param message
- * A message associated with the exception context.
- * @param e
- * The received exception.
- */
- protected void debugOut(String message, Throwable e) {
- if (debug) {
- debugOut("Received exception -> " + message);
- debugOut("Exception message -> " + e.getMessage());
- e.printStackTrace(debugStream);
- }
- }
-
-
- /**
- * Check to see if a MIME body part can have its
- * encoding changed from quoted-printable or base64
- * encoding to 8bit encoding. In order for this
- * to work, it must follow the rules laid out in
- * RFC 2045. To qualify for conversion, the text
- * must be:
- *
- * 1) No more than 998 bytes long
- * 2) All lines are terminated with CRLF sequences
- * 3) CR and LF characters only occur in properly
- * formed line separators
- * 4) No null characters are allowed.
- *
- * The conversion will only be applied to text
- * elements, and this will recurse through the
- * different elements of MultiPart content.
- *
- * @param bodyPart The bodyPart to convert. Initially, this will be
- * the message itself.
- *
- * @return true if any conversion was performed, false if
- * nothing was converted.
- */
- protected boolean convertTransferEncoding(MimePart bodyPart)
- {
- boolean converted = false;
- try {
- // if this is a multipart element, apply the conversion rules
- // to each of the parts.
- if (bodyPart.isMimeType("multipart/")) {
- MimeMultipart parts = (MimeMultipart)bodyPart.getContent();
- for (int i = 0; i < parts.getCount(); i++) {
- // convert each body part, and accumulate the conversion result
- converted = converted && convertTransferEncoding((MimePart)parts.getBodyPart(i));
- }
- }
- else {
- // we only do this if the encoding is quoted-printable or base64
- String encoding = bodyPart.getEncoding();
- if (encoding != null) {
- encoding = encoding.toLowerCase();
- if (encoding.equals("quoted-printable") || encoding.equals("base64")) {
- // this requires encoding. Read the actual content to see if
- // it conforms to the 8bit encoding rules.
- if (isValid8bit(bodyPart.getInputStream())) {
- // There's a huge hidden gotcha lurking under the covers here.
- // If the content just exists as an encoded byte array, then just
- // switching the transfer encoding will mess things up because the
- // already encoded data gets transmitted in encoded form, but with
- // and 8bit encoding style. As a result, it doesn't get unencoded on
- // the receiving end. This is a nasty problem to debug.
- //
- // The solution is to get the content as it's object type, set it back
- // on the the message in raw form. Requesting the content will apply the
- // current transfer encoding value to the data. Once we have set the
- // content value back, we can reset the transfer encoding.
- bodyPart.setContent(bodyPart.getContent(), bodyPart.getContentType());
-
- // it's valid, so change the transfer encoding to just
- // pass the data through.
- bodyPart.setHeader("Content-Transfer-Encoding", "8bit");
- converted = true; // we've changed something
- }
- }
- }
- }
- } catch (MessagingException e) {
- } catch (IOException e) {
- }
- return converted;
- }
-
-
- /**
- * Read the bytes in a stream a test to see if this
- * conforms to the RFC 2045 rules for 8bit encoding.
- *
- * 1) No more than 998 bytes long
- * 2) All lines are terminated with CRLF sequences
- * 3) CR and LF characters only occur in properly
- * formed line separators
- * 4) No null characters are allowed.
- *
- * @param inStream The source input stream.
- *
- * @return true if this can be transmitted successfully
- * using 8bit encoding, false if an alternate encoding
- * will be required.
- */
- protected boolean isValid8bit(InputStream inStream) {
- try {
- int ch;
- int lineLength = 0;
- while ((ch = inStream.read()) >= 0) {
- // nulls are decidedly not allowed
- if (ch == 0) {
- return false;
- }
- // start of a CRLF sequence (potentially)
- else if (ch == '\r') {
- // check the next character. There must be one,
- // and it must be a LF for this to be value
- ch = inStream.read();
- if (ch != '\n') {
- return false;
- }
- // reset the line length
- lineLength = 0;
- }
- else {
- // a normal character
- lineLength++;
- // make sure the line is not too long
- if (lineLength > 998) {
- return false;
- }
- }
-
- }
- } catch (IOException e) {
- return false; // can't read this, don't try passing it
- }
- // this converted ok
- return true;
+ connection.setSASLRealm(name);
}
}
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/MailConnection.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/MailConnection.java
index e8475b5..86e30f2 100644
--- a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/MailConnection.java
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/MailConnection.java
@@ -122,6 +122,9 @@
protected InetAddress localAddress;
// our local port value
protected int localPort;
+ // our local host name
+ protected String localHost;
+
// our timeout value
protected int timeout;
@@ -219,6 +222,17 @@
/**
+ * Establish a connection using an existing socket.
+ *
+ * @param s The socket to use.
+ */
+ public void connect(Socket s) {
+ // just save the socket connection
+ this.socket = s;
+ }
+
+
+ /**
* Create a transport connection object and connect it to the
* target server.
*
@@ -482,7 +496,6 @@
* switching to an SSL socket.
*/
protected void getConnectedTLSSocket() throws MessagingException {
- debugOut("Attempting to negotiate STARTTLS with server " + serverHost);
// it worked, now switch the socket into TLS mode
try {
@@ -518,6 +531,7 @@
if (sslSocket instanceof SSLSocket) {
((SSLSocket)sslSocket).setEnabledProtocols(new String[] {"TLSv1"} );
((SSLSocket)sslSocket).setUseClientMode(true);
+ debugOut("Initiating STARTTLS handshake");
((SSLSocket)sslSocket).startHandshake();
}
@@ -525,6 +539,7 @@
// this is our active socket now
socket = sslSocket;
getConnectionStreams();
+ debugOut("TLS connection established");
}
catch (Exception e) {
debugOut("Failure attempting to convert connection to TLS", e);
@@ -719,7 +734,7 @@
*/
protected void debugOut(String message) {
if (debug) {
- debugStream.println("IMAPStore DEBUG: " + message);
+ debugStream.println(protocol + " DEBUG: " + message);
}
}
@@ -780,4 +795,48 @@
public String getHost() {
return serverHost;
}
+
+
+ /**
+ * Retrieve the local client host name.
+ *
+ * @return The string version of the local host name.
+ * @exception SMTPTransportException
+ */
+ public String getLocalHost() throws MessagingException {
+ if (localHost == null) {
+
+ try {
+ localHost = InetAddress.getLocalHost().getHostName();
+ } catch (UnknownHostException e) {
+ // fine, we're misconfigured - ignore
+ }
+
+ if (localHost == null) {
+ localHost = props.getProperty(MAIL_LOCALHOST);
+ }
+
+ if (localHost == null) {
+ localHost = props.getSessionProperty(MAIL_LOCALHOST);
+ }
+
+ if (localHost == null) {
+ throw new MessagingException("Can't get local hostname. "
+ + " Please correctly configure JDK/DNS or set mail.smtp.localhost");
+ }
+ }
+
+ return localHost;
+ }
+
+
+ /**
+ * Explicitly set the local host information.
+ *
+ * @param localHost
+ * The new localHost name.
+ */
+ public void setLocalHost(String localHost) {
+ this.localHost = localHost;
+ }
}