FileDocCategorySizeDatePackage
SMTPTransport.javaAPI DocGlassfish v2 API56092Tue Jul 03 12:20:50 BST 2007com.sun.mail.smtp

SMTPTransport

public class SMTPTransport extends Transport
This class implements the Transport abstract class using SMTP for message submission and transport.

See the com.sun.mail.smtp package documentation for further information on the SMTP protocol provider.

This class includes many protected methods that allow a subclass to extend this class and add support for non-standard SMTP commands. The {@link #issueCommand} and {@link #sendCommand} methods can be used to send simple SMTP commands. Other methods such as the {@link #mailFrom} and {@link #data} methods can be overridden to insert new commands before or after the corresponding SMTP commands. For example, a subclass could do this to send the XACT command before sending the DATA command:

protected OutputStream data() throws MessagingException {
if (supportsExtension("XACCOUNTING"))
issueCommand("XACT", 25);
return super.data();
}
author
Max Spivak
author
Bill Shannon
author
Dean Gibson (DIGEST-MD5 authentication)
version
1.89, 07/07/03
see
javax.mail.event.ConnectionEvent
see
javax.mail.event.TransportEvent

Fields Summary
private String
name
private int
defaultPort
private boolean
isSSL
private MimeMessage
message
private Address[]
addresses
private Address[]
validSentAddr
private Address[]
validUnsentAddr
private Address[]
invalidAddr
private boolean
sendPartiallyFailed
private MessagingException
exception
private SMTPOutputStream
dataStream
private Hashtable
extMap
private boolean
quitWait
private String
saslRealm
private boolean
reportSuccess
private boolean
useStartTLS
private boolean
useRset
private PrintStream
out
private String
localHostName
private String
lastServerResponse
private int
lastReturnCode
private static final String[]
ignoreList
Headers that should not be included when sending
private static final byte[]
CRLF
private static final String
UNKNOWN
private DigestMD5
md5support
private BufferedInputStream
serverInput
private LineInputStream
lineInputStream
private OutputStream
serverOutput
private Socket
serverSocket
private static char[]
hexchar
Constructors Summary
public SMTPTransport(Session session, URLName urlname)
Constructor that takes a Session object and a URLName that represents a specific SMTP server.

	// place holder

                        
         
	this(session, urlname, "smtp", 25, false);
    
protected SMTPTransport(Session session, URLName urlname, String name, int defaultPort, boolean isSSL)
Constructor used by this class and by SMTPSSLTransport subclass.

	super(session, urlname);
	if (urlname != null)
	    name = urlname.getProtocol();
	this.name = name;
	this.defaultPort = defaultPort;
	this.isSSL = isSSL;

	out = session.getDebugOut();

	// setting mail.smtp.quitwait to false causes us to not wait for the
	// response from the QUIT command
	String s = session.getProperty("mail." + name + ".quitwait");
	quitWait = s == null || s.equalsIgnoreCase("true");

	// mail.smtp.reportsuccess causes us to throw an exception on success
	s = session.getProperty("mail." + name + ".reportsuccess");
	reportSuccess = s != null && s.equalsIgnoreCase("true");

	// mail.smtp.starttls.enable enables use of STARTTLS command
	s = session.getProperty("mail." + name + ".starttls.enable");
	useStartTLS = s != null && s.equalsIgnoreCase("true");

	// mail.smtp.userset causes us to use RSET instead of NOOP
	// for isConnected
	s = session.getProperty("mail." + name + ".userset");
	useRset = s != null && s.equalsIgnoreCase("true");
    
Methods Summary
protected voidcheckConnected()
Check if we're in the connected state. Don't bother checking whether the server is still alive, that will be detected later.

exception
IllegalStateException if not connected
since
JavaMail 1.4.1

	if (!super.isConnected())
	    throw new IllegalStateException("Not connected");
    
public synchronized voidclose()
Close the Transport and terminate the connection to the server.

	if (!super.isConnected()) // Already closed.
	    return;
	try {
	    if (serverSocket != null) {
		sendCommand("QUIT");
		if (quitWait) {
		    int resp = readServerResponse();
		    if (resp != 221 && resp != -1)
			out.println("DEBUG SMTP: QUIT failed with " + resp);
		}
	    }
	} finally {
	    closeConnection();
	}
    
private voidcloseConnection()

	try {
	    if (serverSocket != null)
		serverSocket.close();
	} catch (IOException ioex) {	    // shouldn't happen
	    throw new MessagingException("Server Close Failed", ioex);
	} finally {
	    serverSocket = null;
	    serverOutput = null;
	    serverInput = null;
	    lineInputStream = null;
	    if (super.isConnected())	// only notify if already connected
		super.close();
	}
    
public synchronized voidconnect(java.net.Socket socket)
Start the SMTP protocol on the given socket, which was already connected by the caller. Useful for implementing the SMTP ATRN command (RFC 2645) where an existing connection is used when the server reverses roles and becomes the client.

since
JavaMail 1.3.3

	serverSocket = socket;
	super.connect();
    
private booleanconvertTo8Bit(javax.mail.internet.MimePart part)
If the Part is a text part and has a Content-Transfer-Encoding of "quoted-printable" or "base64", and it obeys the rules for "8bit" encoding, change the encoding to "8bit". If the part is a multipart, recursively process all its parts.

return
true if any changes were made XXX - This is really quite a hack.

	boolean changed = false;
	try {
	    if (part.isMimeType("text/*")) {
		String enc = part.getEncoding();
		if (enc != null && (enc.equalsIgnoreCase("quoted-printable") ||
		    enc.equalsIgnoreCase("base64"))) {
		    InputStream is = part.getInputStream();
		    if (is8Bit(is)) {
			/*
			 * If the message was created using an InputStream
			 * then we have to extract the content as an object
			 * and set it back as an object so that the content
			 * will be re-encoded.
			 *
			 * If the message was not created using an InputStream,
			 * the following should have no effect.
			 */
			part.setContent(part.getContent(),
					part.getContentType());
			part.setHeader("Content-Transfer-Encoding", "8bit");
			changed = true;
		    }
		}
	    } else if (part.isMimeType("multipart/*")) {
		MimeMultipart mp = (MimeMultipart)part.getContent();
		int count = mp.getCount();
		for (int i = 0; i < count; i++) {
		    if (convertTo8Bit((MimePart)mp.getBodyPart(i)))
			changed = true;
		}
	    }
	} catch (IOException ioex) {
	    // any exception causes us to give up
	} catch (MessagingException mex) {
	    // any exception causes us to give up
	}
	return changed;
    
protected java.io.OutputStreamdata()
Send the DATA command to the SMTP host and return an OutputStream to which the data is to be written.

since
JavaMail 1.4.1

	assert Thread.holdsLock(this);
	issueSendCommand("DATA", 354);
	dataStream = new SMTPOutputStream(serverOutput);
	return dataStream;
    
protected booleanehlo(java.lang.String domain)
Issue the EHLO command. Collect the returned list of service extensions.

param
domain our domain
return
true if command succeeds
since
JavaMail 1.4.1

	String cmd;
	if (domain != null)
	    cmd = "EHLO " + domain;
	else
	    cmd = "EHLO";
	sendCommand(cmd);
	int resp = readServerResponse();
	if (resp == 250) {
	    // extract the supported service extensions
	    BufferedReader rd =
		new BufferedReader(new StringReader(lastServerResponse));
	    String line;
	    extMap = new Hashtable();
	    try {
		boolean first = true;
		while ((line = rd.readLine()) != null) {
		    if (first) {	// skip first line which is the greeting
			first = false;
			continue;
		    }
		    if (line.length() < 5)
			continue;		// shouldn't happen
		    line = line.substring(4);	// skip response code
		    int i = line.indexOf(' ");
		    String arg = "";
		    if (i > 0) {
			arg = line.substring(i + 1);
			line = line.substring(0, i);
		    }
		    if (debug)
			out.println("DEBUG SMTP: Found extension \"" +
					    line + "\", arg \"" + arg + "\"");
		    extMap.put(line.toUpperCase(Locale.ENGLISH), arg);
		}
	    } catch (IOException ex) { }	// can't happen
	}
	return resp == 250;
    
private voidexpandGroups()
Expand any group addresses.

	Vector groups = null;
	for (int i = 0; i < addresses.length; i++) {
	    InternetAddress a = (InternetAddress)addresses[i];
	    if (a.isGroup()) {
		if (groups == null) {
		    // first group, catch up with where we are
		    groups = new Vector();
		    for (int k = 0; k < i; k++)
			groups.addElement(addresses[k]);
		}
		// parse it and add each individual address
		try {
		    InternetAddress[] ia = a.getGroup(true);
		    if (ia != null) {
			for (int j = 0; j < ia.length; j++)
			    groups.addElement(ia[j]);
		    } else
			groups.addElement(a);
		} catch (ParseException pex) {
		    // parse failed, add the whole thing
		    groups.addElement(a);
		}
	    } else {
		// if we've started accumulating a list, add this to it
		if (groups != null)
		    groups.addElement(a);
	    }
	}

	// if we have a new list, convert it back to an array
	if (groups != null) {
	    InternetAddress[] newa = new InternetAddress[groups.size()];
	    groups.copyInto(newa);
	    addresses = newa;
	}
    
protected voidfinalize()

	super.finalize();
	try {
	    closeConnection();
	} catch (MessagingException mex) { }	// ignore it
    
protected voidfinishData()
Terminate the sent data.

since
JavaMail 1.4.1

	assert Thread.holdsLock(this);
	dataStream.ensureAtBOL();
	issueSendCommand(".", 250);
    
public java.lang.StringgetExtensionParameter(java.lang.String ext)
Return the parameter the server provided for the specified service extension, or null if the extension isn't supported.

param
ext the service extension name
return
the extension parameter
since
JavaMail 1.3.2

	return extMap == null ? null :
			(String)extMap.get(ext.toUpperCase(Locale.ENGLISH));
    
public synchronized intgetLastReturnCode()
Return the return code from the last response we got from the server.

return
return code from last response from server
since
JavaMail 1.4.1

	return lastReturnCode;
    
public synchronized java.lang.StringgetLastServerResponse()
Return the last response we got from the server. A failed send is often followed by an RSET command, but the response from the RSET command is not saved. Instead, this returns the response from the command before the RSET command.

return
last response from server
since
JavaMail 1.3.2

	return lastServerResponse;
    
public synchronized java.lang.StringgetLocalHost()
Get the name of the local host, for use in the EHLO and HELO commands. The property mail.smtp.localhost overrides mail.smtp.localaddress, which overrides what InetAddress would tell us.

	try {
	    // get our hostname and cache it for future use
	    if (localHostName == null || localHostName.length() <= 0)
		localHostName =
			session.getProperty("mail." + name + ".localhost");
	    if (localHostName == null || localHostName.length() <= 0)
		localHostName =
			session.getProperty("mail." + name + ".localaddress");
	    if (localHostName == null || localHostName.length() <= 0) {
		InetAddress localHost = InetAddress.getLocalHost();
		localHostName = localHost.getHostName();
		// if we can't get our name, use local address literal
		if (localHostName == null)
		    // XXX - not correct for IPv6
		    localHostName = "[" + localHost.getHostAddress() + "]";
	    }
	} catch (UnknownHostException uhex) {
	}
	return localHostName;
    
private synchronized com.sun.mail.smtp.DigestMD5getMD5()

	if (md5support == null)
	    md5support = new DigestMD5(debug ? out : null);
	return md5support;
    
public synchronized booleangetReportSuccess()
Should we report even successful sends by throwing an exception? If so, a SendFailedException will always be thrown and an {@link com.sun.mail.smtp.SMTPAddressSucceededException SMTPAddressSucceededException} will be included in the exception chain for each successful address, along with the usual {@link com.sun.mail.smtp.SMTPAddressFailedException SMTPAddressFailedException} for each unsuccessful address.

return
true if an exception will be thrown on successful sends.
since
JavaMail 1.3.2

	return reportSuccess;
    
public synchronized java.lang.StringgetSASLRealm()
Gets the SASL realm to be used for DIGEST-MD5 authentication.

return
the name of the realm to use for SASL authentication.
since
JavaMail 1.3.1

	if (saslRealm == UNKNOWN) {
	    saslRealm = session.getProperty("mail." + name + ".sasl.realm");
	    if (saslRealm == null)	// try old name
		saslRealm = session.getProperty("mail." + name + ".saslrealm");
	}
	return saslRealm;
    
public synchronized booleangetStartTLS()
Should we use the STARTTLS command to secure the connection if the server supports it?

return
true if the STARTTLS command will be used
since
JavaMail 1.3.2

	return useStartTLS;
    
public synchronized booleangetUseRset()
Should we use the RSET command instead of the NOOP command in the @{link #isConnected isConnected} method?

return
true if RSET will be used
since
JavaMail 1.4

	return useRset;
    
protected voidhelo(java.lang.String domain)
Issue the HELO command.

param
domain our domain
since
JavaMail 1.4.1

	if (domain != null)
	    issueCommand("HELO " + domain, 250);
	else
	    issueCommand("HELO", 250);
    
private voidinitStreams()

	Properties props = session.getProperties();
	PrintStream out = session.getDebugOut();
	boolean debug = session.getDebug();

	String s = props.getProperty("mail.debug.quote");
	boolean quote = s != null && s.equalsIgnoreCase("true");

	TraceInputStream traceInput =
	    new TraceInputStream(serverSocket.getInputStream(), out);
	traceInput.setTrace(debug);
	traceInput.setQuote(quote);

	TraceOutputStream traceOutput =
	    new TraceOutputStream(serverSocket.getOutputStream(), out);
	traceOutput.setTrace(debug);
	traceOutput.setQuote(quote);

	serverOutput =
	    new BufferedOutputStream(traceOutput);
	serverInput =
	    new BufferedInputStream(traceInput);
	lineInputStream = new LineInputStream(serverInput);
    
private booleanis8Bit(java.io.InputStream is)
Check whether the data in the given InputStream follows the rules for 8bit text. Lines have to be 998 characters or less and no NULs are allowed. CR and LF must occur in pairs but we don't check that because we assume this is text and we convert all CR/LF combinations into canonical CRLF later.

	int b;
	int linelen = 0;
	boolean need8bit = false;
	try {
	    while ((b = is.read()) >= 0) {
		b &= 0xff;
		if (b == '\r" || b == '\n")
		    linelen = 0;
		else if (b == 0)
		    return false;
		else {
		    linelen++;
		    if (linelen > 998)	// 1000 - CRLF
			return false;
		}
		if (b > 0x7f)
		    need8bit = true;
	    }
	} catch (IOException ex) {
	    return false;
	}
	if (debug && need8bit)
	    out.println("DEBUG SMTP: found an 8bit part");
	return need8bit;
    
public synchronized booleanisConnected()
Check whether the transport is connected. Override superclass method, to actually ping our server connection.

	if (!super.isConnected())
	    // if we haven't been connected at all, don't bother with NOOP
	    return false;

	try {
	    // sendmail may respond slowly to NOOP after many requests
	    // so if mail.smtp.userset is set we use RSET instead of NOOP.
	    if (useRset)
		sendCommand("RSET");
	    else
		sendCommand("NOOP");
	    int resp = readServerResponse();

	    // NOOP should return 250 on success, however, SIMS 3.2 returns
	    // 200, so we work around it.
	    //
	    // Hotmail doesn't implement the NOOP command at all so assume
	    // any kind of response means we're still connected.
	    // That is, any response except 421, which means the server
	    // is shutting down the connection.
	    //
	    if (resp >= 0 && resp != 421) {
		return true;
	    } else {
		try {
		    closeConnection();
		} catch (MessagingException mex) { }	// ignore it
		return false;
	    }
	} catch (Exception ex) {
	    try {
		closeConnection();
	    } catch (MessagingException mex) { }	// ignore it
	    return false;
	}
    
private booleanisNotLastLine(java.lang.String line)

        return line != null && line.length() >= 4 && line.charAt(3) == '-";
    
public synchronized voidissueCommand(java.lang.String cmd, int expect)
Send the command to the server. If the expected response code is not received, throw a MessagingException.

param
cmd the command to send
param
expect the expected response code
since
JavaMail 1.4.1

	sendCommand(cmd);

	// if server responded with an unexpected return code,
	// throw the exception, notifying the client of the response
	if (readServerResponse() != expect)
	    throw new MessagingException(lastServerResponse);
    
private voidissueSendCommand(java.lang.String cmd, int expect)
Issue a command that's part of sending a message.

	sendCommand(cmd);

	// if server responded with an unexpected return code,
	// throw the exception, notifying the client of the response
	int ret;
	if ((ret = readServerResponse()) != expect) {
	    // assume message was not sent to anyone,
	    // combine valid sent & unsent addresses
	    int vsl = validSentAddr == null ? 0 : validSentAddr.length;
	    int vul = validUnsentAddr == null ? 0 : validUnsentAddr.length;
	    Address[] valid = new Address[vsl + vul];
	    if (vsl > 0)
		System.arraycopy(validSentAddr, 0, valid, 0, vsl);
	    if (vul > 0)
		System.arraycopy(validUnsentAddr, 0, valid, vsl, vul);
	    validSentAddr = null;
	    validUnsentAddr = valid;
	    if (debug)
		out.println("DEBUG SMTP: got response code " + ret +
		    ", with response: " + lastServerResponse);
	    String _lsr = lastServerResponse; // else rset will nuke it
	    int _lrc = lastReturnCode;
	    if (serverSocket != null)	// hasn't already been closed
		issueCommand("RSET", 250);
	    lastServerResponse = _lsr;	// restore, for get
	    lastReturnCode = _lrc;
	    throw new SMTPSendFailedException(cmd, ret, lastServerResponse,
			exception, validSentAddr, validUnsentAddr, invalidAddr);
	}
    
protected voidmailFrom()
Issue the MAIL FROM: command to start sending a message.

Gets the sender's address in the following order:

  1. SMTPMessage.getEnvelopeFrom()
  2. mail.smtp.from property
  3. From: header in the message
  4. System username using the InternetAddress.getLocalAddress() method

since
JavaMail 1.4.1

	String from = null;
	if (message instanceof SMTPMessage)
	    from = ((SMTPMessage)message).getEnvelopeFrom();
	if (from == null || from.length() <= 0)
	    from = session.getProperty("mail." + name + ".from");
	if (from == null || from.length() <= 0) {
	    Address[] fa;
	    Address me;
	    if (message != null && (fa = message.getFrom()) != null &&
		    fa.length > 0)
		me = fa[0];
	    else
		me = InternetAddress.getLocalAddress(session);

	    if (me != null)
		from = ((InternetAddress)me).getAddress();
	    else
		throw new MessagingException(
					"can't determine local email address");
	}

	String cmd = "MAIL FROM:" + normalizeAddress(from);

	// request delivery status notification?
	if (supportsExtension("DSN")) {
	    String ret = null;
	    if (message instanceof SMTPMessage)
		ret = ((SMTPMessage)message).getDSNRet();
	    if (ret == null)
		ret = session.getProperty("mail." + name + ".dsn.ret");
	    // XXX - check for legal syntax?
	    if (ret != null)
		cmd += " RET=" + ret;
	}

	/*
	 * If an RFC 2554 submitter has been specified, and the server
	 * supports the AUTH extension, include the AUTH= element on
	 * the MAIL FROM command.
	 */
	if (supportsExtension("AUTH")) {
	    String submitter = null;
	    if (message instanceof SMTPMessage)
		submitter = ((SMTPMessage)message).getSubmitter();
	    if (submitter == null)
		submitter = session.getProperty("mail." + name + ".submitter");
	    // XXX - check for legal syntax?
	    if (submitter != null) {
		try {
		    String s = xtext(submitter);
		    cmd += " AUTH=" + s;
		} catch (IllegalArgumentException ex) {
		    if (debug)
			out.println("DEBUG SMTP: ignoring invalid submitter: " +
			    submitter + ", Exception: " + ex);
		}
	    }
	}

	/*
	 * Have any extensions to the MAIL command been specified?
	 */
	String ext = null;
	if (message instanceof SMTPMessage)
	    ext = ((SMTPMessage)message).getMailExtension();
	if (ext == null)
	    ext = session.getProperty("mail." + name + ".mailextension");
	if (ext != null && ext.length() > 0)
	    cmd += " " + ext;

	issueSendCommand(cmd, 250);
    
private java.lang.StringnormalizeAddress(java.lang.String addr)

	if ((!addr.startsWith("<")) && (!addr.endsWith(">")))
	    return "<" + addr + ">";
	else
	    return addr;
    
private voidopenServer(java.lang.String server, int port)
Connect to server on port and start the SMTP protocol.


        if (debug)
	    out.println("DEBUG SMTP: trying to connect to host \"" + server +
				"\", port " + port + ", isSSL " + isSSL);

	try {
	    Properties props = session.getProperties();

	    serverSocket = SocketFetcher.getSocket(server, port,
		props, "mail." + name, isSSL);

	    // socket factory may've chosen a different port,
	    // update it for the debug messages that follow
	    port = serverSocket.getPort();

	    initStreams();

	    int r = -1;
	    if ((r = readServerResponse()) != 220) {
		serverSocket.close();
		serverSocket = null;
		serverOutput = null;
		serverInput = null;
		lineInputStream = null;
		if (debug)
		    out.println("DEBUG SMTP: could not connect to host \"" +
				    server + "\", port: " + port +
				    ", response: " + r + "\n");
		throw new MessagingException(
			"Could not connect to SMTP host: " + server +
				    ", port: " + port +
				    ", response: " + r);
	    } else {
		if (debug)
		    out.println("DEBUG SMTP: connected to host \"" +
				       server + "\", port: " + port + "\n");
	    }
	} catch (UnknownHostException uhex) {
	    throw new MessagingException("Unknown SMTP host: " + server, uhex);
	} catch (IOException ioe) {
	    throw new MessagingException("Could not connect to SMTP host: " +
				    server + ", port: " + port, ioe);
	}
    
private voidopenServer()
Start the protocol to the server on serverSocket, assumed to be provided and connected by the caller.

	int port = -1;
	String server = "UNKNOWN";
	try {
	    port = serverSocket.getPort();
	    server = serverSocket.getInetAddress().getHostName();
	    if (debug)
		out.println("DEBUG SMTP: starting protocol to host \"" +
					server + "\", port " + port);

	    initStreams();

	    int r = -1;
	    if ((r = readServerResponse()) != 220) {
		serverSocket.close();
		serverSocket = null;
		serverOutput = null;
		serverInput = null;
		lineInputStream = null;
		if (debug)
		    out.println("DEBUG SMTP: got bad greeting from host \"" +
				    server + "\", port: " + port +
				    ", response: " + r + "\n");
		throw new MessagingException(
			"Got bad greeting from SMTP host: " + server +
				    ", port: " + port +
				    ", response: " + r);
	    } else {
		if (debug)
		    out.println("DEBUG SMTP: protocol started to host \"" +
				       server + "\", port: " + port + "\n");
	    }
	} catch (IOException ioe) {
	    throw new MessagingException(
				    "Could not start protocol to SMTP host: " +
				    server + ", port: " + port, ioe);
	}
    
protected booleanprotocolConnect(java.lang.String host, int port, java.lang.String user, java.lang.String passwd)
Performs the actual protocol-specific connection attempt. Will attempt to connect to "localhost" if the host was null.

Unless mail.smtp.ehlo is set to false, we'll try to identify ourselves using the ESMTP command EHLO. If mail.smtp.auth is set to true, we insist on having a username and password, and will try to authenticate ourselves if the server supports the AUTH extension (RFC 2554).

param
host the name of the host to connect to
param
port the port to use (-1 means use default port)
param
user the name of the user to login as
param
passwd the user's password
return
true if connection successful, false if authentication failed
exception
MessagingException for non-authentication failures

	// setting mail.smtp.ehlo to false disables attempts to use EHLO
	String ehloStr = session.getProperty("mail." + name + ".ehlo");
	boolean useEhlo = ehloStr == null || !ehloStr.equalsIgnoreCase("false");
	// setting mail.smtp.auth to true enables attempts to use AUTH
	String authStr = session.getProperty("mail." + name + ".auth");
	boolean useAuth = authStr != null && authStr.equalsIgnoreCase("true");
	DigestMD5 md5;
	if (debug)
	    out.println("DEBUG SMTP: useEhlo " + useEhlo +
				", useAuth " + useAuth);

	/*
	 * If mail.smtp.auth is set, make sure we have a valid username
	 * and password, even if we might not end up using it (e.g.,
	 * because the server doesn't support ESMTP or doesn't support
	 * the AUTH extension).
	 */
	if (useAuth && (user == null || passwd == null))
	    return false;

	/*
	 * If port is not specified, set it to value of mail.smtp.port
         * property if it exists, otherwise default to 25.
	 */
        if (port == -1) {
	    String portstring = session.getProperty("mail." + name + ".port");
	    if (portstring != null) {
		port = Integer.parseInt(portstring);
	    } else {
		port = defaultPort;
	    }
	}

	if (host == null || host.length() == 0)
	    host = "localhost";

	boolean succeed = false;

	if (serverSocket != null)
	    openServer();	// only happens from connect(socket)
	else
	    openServer(host, port);

	if (useEhlo)
	    succeed = ehlo(getLocalHost());
	if (!succeed)
	    helo(getLocalHost());

	if (useStartTLS && supportsExtension("STARTTLS")) {
	    startTLS();
	    /*
	     * Have to issue another EHLO to update list of extensions
	     * supported, especially authentication mechanisms.
	     * Don't know if this could ever fail, but we ignore failure.
	     */
	    ehlo(getLocalHost());
	}

	if ((useAuth || (user != null && passwd != null)) &&
	      (supportsExtension("AUTH") || supportsExtension("AUTH=LOGIN"))) {
	    if (debug) {
		out.println("DEBUG SMTP: Attempt to authenticate");
		if (!supportsAuthentication("LOGIN") &&
			supportsExtension("AUTH=LOGIN"))
		    out.println("DEBUG SMTP: use AUTH=LOGIN hack");
	    }
	    // if authentication fails, close connection and return false
	    if (supportsAuthentication("LOGIN") ||
		    supportsExtension("AUTH=LOGIN")) {
		// XXX - could use "initial response" capability
		int resp = simpleCommand("AUTH LOGIN");

		/*
		 * A 530 response indicates that the server wants us to
		 * issue a STARTTLS command first.  Do that and try again.
		 */
		if (resp == 530) {
		    startTLS();
		    resp = simpleCommand("AUTH LOGIN");
		}

		/*
		 * Wrap a BASE64Encoder around a ByteArrayOutputstream
		 * to craft b64 encoded username and password strings.
		 *
		 * Also note that unlike the B64 definition in MIME, CRLFs 
		 * should *not* be inserted during the encoding process.
		 * So I use Integer.MAX_VALUE (0x7fffffff (> 1G)) as the
		 * bytesPerLine, which should be sufficiently large!
		 */
		try {
		    ByteArrayOutputStream bos = new ByteArrayOutputStream();
		    OutputStream b64os =
				new BASE64EncoderStream(bos, Integer.MAX_VALUE);

		    if (resp == 334) {
			// obtain b64 encoded bytes
			b64os.write(ASCIIUtility.getBytes(user));
			b64os.flush(); 	// complete the encoding

			// send username
			resp = simpleCommand(bos.toByteArray());
			bos.reset(); 	// reset buffer
		    }
		    if (resp == 334) {
			// obtain b64 encoded bytes
			b64os.write(ASCIIUtility.getBytes(passwd));
			b64os.flush(); 	// complete the encoding

			// send passwd
			resp = simpleCommand(bos.toByteArray());
			bos.reset(); 	// reset buffer
		    }
		} catch (IOException ex) {	// should never happen, ignore
		} finally {
		    if (resp != 235) {
			closeConnection();
			return false;
		    }
		}
	    } else if (supportsAuthentication("PLAIN")) {
		// XXX - could use "initial response" capability
		int resp = simpleCommand("AUTH PLAIN");
		try {
		    ByteArrayOutputStream bos = new ByteArrayOutputStream();
		    OutputStream b64os =
				new BASE64EncoderStream(bos, Integer.MAX_VALUE);
		    if (resp == 334) {
			// send "<NUL>user<NUL>passwd"
			// XXX - we don't send an authorization identity
			b64os.write(0);
			b64os.write(ASCIIUtility.getBytes(user));
			b64os.write(0);
			b64os.write(ASCIIUtility.getBytes(passwd));
			b64os.flush(); 	// complete the encoding

			// send username
			resp = simpleCommand(bos.toByteArray());
		    }
		} catch (IOException ex) {	// should never happen, ignore
		} finally {
		    if (resp != 235) {
			closeConnection();
			return false;
		    }
		}
	    } else if (supportsAuthentication("DIGEST-MD5") &&
		    (md5 = getMD5()) != null) {
		int resp = simpleCommand("AUTH DIGEST-MD5");
		try {
		    if (resp == 334) {
			byte[] b = md5.authClient(host, user, passwd,
					    getSASLRealm(), lastServerResponse);
			resp = simpleCommand(b);
			if (resp == 334) { // client authenticated by server
			    if (!md5.authServer(lastServerResponse)) {
				// server NOT authenticated by client !!!
				resp = -1;
			    } else {
				// send null response
				resp = simpleCommand(new byte[0]);
			    }
			}
		    }
		} catch (Exception ex) {	// should never happen, ignore
		    if (debug)
			out.println("DEBUG SMTP: DIGEST-MD5: " + ex);
		} finally {
		    if (resp != 235) {
			closeConnection();
			return false;
		    }
		}
	    }
	}

	// we connected correctly
	return true;
    
protected voidrcptTo()
Sends each address to the SMTP host using the RCPT TO: command and copies the address either into the validSentAddr or invalidAddr arrays. Sets the sendFailed flag to true if any addresses failed.

since
JavaMail 1.4.1

	Vector valid = new Vector();
	Vector validUnsent = new Vector();
	Vector invalid = new Vector();
	int retCode = -1;
	MessagingException mex = null;
	boolean sendFailed = false;
	MessagingException sfex = null;
	validSentAddr = validUnsentAddr = invalidAddr = null;
	boolean sendPartial = false;
	if (message instanceof SMTPMessage)
	    sendPartial = ((SMTPMessage)message).getSendPartial();
	if (!sendPartial) {
	    String sp = session.getProperty("mail." + name + ".sendpartial");
	    sendPartial = sp != null && sp.equalsIgnoreCase("true");
	}
	if (debug && sendPartial)
	    out.println("DEBUG SMTP: sendPartial set");

	boolean dsn = false;
	String notify = null;
	if (supportsExtension("DSN")) {
	    if (message instanceof SMTPMessage)
		notify = ((SMTPMessage)message).getDSNNotify();
	    if (notify == null)
		notify = session.getProperty("mail." + name + ".dsn.notify");
	    // XXX - check for legal syntax?
	    if (notify != null)
		dsn = true;
	}

	// try the addresses one at a time
	for (int i = 0; i < addresses.length; i++) {

	    sfex = null;
	    InternetAddress ia = (InternetAddress)addresses[i];
	    String cmd = "RCPT TO:" + normalizeAddress(ia.getAddress());
	    if (dsn)
		cmd += " NOTIFY=" + notify;
	    // send the addresses to the SMTP server
	    sendCommand(cmd);
	    // check the server's response for address validity
	    retCode = readServerResponse();
	    switch (retCode) {
	    case 250: case 251:
		valid.addElement(ia);
		if (!reportSuccess)
		    break;

		// user wants exception even when successful, including
		// details of the return code

		// create and chain the exception
		sfex = new SMTPAddressSucceededException(ia, cmd, retCode,
							lastServerResponse);
		if (mex == null)
		    mex = sfex;
		else
		    mex.setNextException(sfex);
		break;

	    case 550: case 553: case 503: case 551: case 501:
		// given address is invalid
		if (!sendPartial)
		    sendFailed = true;
		invalid.addElement(ia);
		// create and chain the exception
		sfex = new SMTPAddressFailedException(ia, cmd, retCode,
							lastServerResponse);
		if (mex == null)
		    mex = sfex;
		else
		    mex.setNextException(sfex);
		break;

	    case 552: case 450: case 451: case 452:
		// given address is valid
		if (!sendPartial)
		    sendFailed = true;
		validUnsent.addElement(ia);
		// create and chain the exception
		sfex = new SMTPAddressFailedException(ia, cmd, retCode,
							lastServerResponse);
		if (mex == null)
		    mex = sfex;
		else
		    mex.setNextException(sfex);
		break;

	    default:
		// handle remaining 4xy & 5xy codes
		if (retCode >= 400 && retCode <= 499) {
		    // assume address is valid, although we don't really know
		    validUnsent.addElement(ia);
		} else if (retCode >= 500 && retCode <= 599) {
		    // assume address is invalid, although we don't really know
		    invalid.addElement(ia);
		} else {
		    // completely unexpected response, just give up
		    if (debug)
			out.println("DEBUG SMTP: got response code " + retCode +
			    ", with response: " + lastServerResponse);
		    String _lsr = lastServerResponse; // else rset will nuke it
		    int _lrc = lastReturnCode;
		    if (serverSocket != null)	// hasn't already been closed
			issueCommand("RSET", 250);
		    lastServerResponse = _lsr;	// restore, for get
		    lastReturnCode = _lrc;
		    throw new SMTPAddressFailedException(ia, cmd, retCode,
								_lsr);
		}
		if (!sendPartial)
		    sendFailed = true;
		// create and chain the exception
		sfex = new SMTPAddressFailedException(ia, cmd, retCode,
							lastServerResponse);
		if (mex == null)
		    mex = sfex;
		else
		    mex.setNextException(sfex);
		break;
	    }
	}

	// if we're willing to send to a partial list, and we found no
	// valid addresses, that's complete failure
	if (sendPartial && valid.size() == 0)
	    sendFailed = true;

	// copy the vectors into appropriate arrays
	if (sendFailed) {
	    // copy invalid addrs
	    invalidAddr = new Address[invalid.size()];
	    invalid.copyInto(invalidAddr);

	    // copy all valid addresses to validUnsent, since something failed
	    validUnsentAddr = new Address[valid.size() + validUnsent.size()];
	    int i = 0;
	    for (int j = 0; j < valid.size(); j++)
		validUnsentAddr[i++] = (Address)valid.elementAt(j);
	    for (int j = 0; j < validUnsent.size(); j++)
		validUnsentAddr[i++] = (Address)validUnsent.elementAt(j);
	} else if (reportSuccess || (sendPartial &&
			(invalid.size() > 0 || validUnsent.size() > 0))) {
	    // we'll go on to send the message, but after sending we'll
	    // throw an exception with this exception nested
	    sendPartiallyFailed = true;
	    exception = mex;

	    // copy invalid addrs
	    invalidAddr = new Address[invalid.size()];
	    invalid.copyInto(invalidAddr);

	    // copy valid unsent addresses to validUnsent
	    validUnsentAddr = new Address[validUnsent.size()];
	    validUnsent.copyInto(validUnsentAddr);

	    // copy valid addresses to validSent
	    validSentAddr = new Address[valid.size()];
	    valid.copyInto(validSentAddr);
	} else {        // all addresses pass
	    validSentAddr = addresses;
	}


	// print out the debug info
	if (debug) {
	    if (validSentAddr != null && validSentAddr.length > 0) {
		out.println("DEBUG SMTP: Verified Addresses");
		for (int l = 0; l < validSentAddr.length; l++) {
		    out.println("DEBUG SMTP:   " + validSentAddr[l]);
		}
	    }
	    if (validUnsentAddr != null && validUnsentAddr.length > 0) {
		out.println("DEBUG SMTP: Valid Unsent Addresses");
		for (int j = 0; j < validUnsentAddr.length; j++) {
		    out.println("DEBUG SMTP:   " + validUnsentAddr[j]);
		}
	    }
	    if (invalidAddr != null && invalidAddr.length > 0) {
		out.println("DEBUG SMTP: Invalid Addresses");
		for (int k = 0; k < invalidAddr.length; k++) {
		    out.println("DEBUG SMTP:   " + invalidAddr[k]);
		}
	    }
	}

	// throw the exception, fire TransportEvent.MESSAGE_NOT_DELIVERED event
	if (sendFailed) {
	    if (debug)
		out.println("DEBUG SMTP: Sending failed " +
				   "because of invalid destination addresses");
	    notifyTransportListeners(TransportEvent.MESSAGE_NOT_DELIVERED,
				     validSentAddr, validUnsentAddr,
				     invalidAddr, this.message);

	    // reset the connection so more sends are allowed
	    String lsr = lastServerResponse;	// save, for get
	    int lrc = lastReturnCode;
	    try {
		if (serverSocket != null)
		    issueCommand("RSET", 250);
	    } catch (MessagingException ex) {
		// if can't reset, best to close the connection
		try {
		    close();
		} catch (MessagingException ex2) {
		    // thrown by close()--ignore, will close() later anyway
		    if (debug)
			ex2.printStackTrace(out);
		}
	    } finally {
		lastServerResponse = lsr;	// restore
		lastReturnCode = lrc;
	    }

	    throw new SendFailedException("Invalid Addresses", mex,
					  validSentAddr,
					  validUnsentAddr, invalidAddr);
	}
    
protected intreadServerResponse()
Reads server reponse returning the returnCode as the number. Returns -1 on failure. Sets lastServerResponse and lastReturnCode.

return
server response code
since
JavaMail 1.4.1

	assert Thread.holdsLock(this);
        String serverResponse = "";
        int returnCode = 0;
	StringBuffer buf = new StringBuffer(100);

	// read the server response line(s) and add them to the buffer
	// that stores the response
        try {
	    String line = null;

	    do {
		line = lineInputStream.readLine();
		if (line == null) {
		    serverResponse = buf.toString();
		    if (serverResponse.length() == 0)
			serverResponse = "[EOF]";
		    lastServerResponse = serverResponse;
		    lastReturnCode = -1;
		    if (debug)
			out.println("DEBUG SMTP: EOF: " + serverResponse);
		    return -1;
		}
		buf.append(line);
		buf.append("\n");
	    } while (isNotLastLine(line));

            serverResponse = buf.toString();
        } catch (IOException ioex) {
	    if (debug)
		out.println("DEBUG SMTP: exception reading response: " + ioex);
            //ioex.printStackTrace(out);
	    lastServerResponse = "";
	    lastReturnCode = 0;
	    throw new MessagingException("Exception reading response", ioex);
            //returnCode = -1;
        }

	// print debug info
        //if (debug)
            //out.println("DEBUG SMTP RCVD: " + serverResponse);

	// parse out the return code
        if (serverResponse != null && serverResponse.length() >= 3) {
            try {
                returnCode = Integer.parseInt(serverResponse.substring(0, 3));
            } catch (NumberFormatException nfe) {
		try {
		    close();
		} catch (MessagingException mex) {
		    // thrown by close()--ignore, will close() later anyway
		    if (debug)
			mex.printStackTrace(out);
		}
		returnCode = -1;
            } catch (StringIndexOutOfBoundsException ex) {
		//if (debug) ex.printStackTrace(out);
		try {
		    close();
		} catch (MessagingException mex) {
		    // thrown by close()--ignore, will close() later anyway
		    if (debug)
			mex.printStackTrace(out);
		}
                returnCode = -1;
	    }
	} else {
	    returnCode = -1;
	}
	if (returnCode == -1 && debug)
	    out.println("DEBUG SMTP: bad server response: " + serverResponse);

        lastServerResponse = serverResponse;
	lastReturnCode = returnCode;
        return returnCode;
    
protected voidsendCommand(java.lang.String cmd)
Sends command cmd to the server terminating it with CRLF.

since
JavaMail 1.4.1

	sendCommand(ASCIIUtility.getBytes(cmd));
    
private voidsendCommand(byte[] cmdBytes)

	assert Thread.holdsLock(this);
	//if (debug)
	    //out.println("DEBUG SMTP SENT: " + new String(cmdBytes, 0));

        try {
	    serverOutput.write(cmdBytes);
	    serverOutput.write(CRLF);
	    serverOutput.flush();
	} catch (IOException ex) {
	    throw new MessagingException("Can't send command to SMTP host", ex);
	}
    
public synchronized voidsendMessage(Message message, javax.mail.Address[] addresses)
Send the Message to the specified list of addresses.

If all the addresses succeed the SMTP check using the RCPT TO: command, we attempt to send the message. A TransportEvent of type MESSAGE_DELIVERED is fired indicating the successful submission of a message to the SMTP host.

If some of the addresses fail the SMTP check, and the mail.stmp.sendpartial property is not set, sending is aborted. The TransportEvent of type MESSAGE_NOT_DELIVERED is fired containing the valid and invalid addresses. The SendFailedException is also thrown.

If some of the addresses fail the SMTP check, and the mail.stmp.sendpartial property is set to true, the message is sent. The TransportEvent of type MESSAGE_PARTIALLY_DELIVERED is fired containing the valid and invalid addresses. The SMTPSendFailedException is also thrown.

MessagingException is thrown if the message can't write out an RFC822-compliant stream using its writeTo method.

param
message The MimeMessage to be sent
param
addresses List of addresses to send this message to
see
javax.mail.event.TransportEvent
exception
SMTPSendFailedException if the send failed because of an SMTP command error
exception
SendFailedException if the send failed because of invalid addresses.
exception
MessagingException if the connection is dead or not in the connected state or if the message is not a MimeMessage.


	checkConnected();

	// check if the message is a valid MIME/RFC822 message and that
	// it has all valid InternetAddresses; fail if not
        if (!(message instanceof MimeMessage)) {
	    if (debug)
		out.println("DEBUG SMTP: Can only send RFC822 msgs");
	    throw new MessagingException("SMTP can only send RFC822 messages");
	}
	for (int i = 0; i < addresses.length; i++) {
	    if (!(addresses[i] instanceof InternetAddress)) {
		throw new MessagingException(addresses[i] +
					     " is not an InternetAddress");
	    }
	}

	this.message = (MimeMessage)message;
	this.addresses = addresses;
	validUnsentAddr = addresses;	// until we know better
	expandGroups();

	boolean use8bit = false;
	if (message instanceof SMTPMessage)
	    use8bit = ((SMTPMessage)message).getAllow8bitMIME();
	if (!use8bit) {
	    String ebStr =
		    session.getProperty("mail." + name + ".allow8bitmime");
	    use8bit = ebStr != null && ebStr.equalsIgnoreCase("true");
	}
	if (debug)
	    out.println("DEBUG SMTP: use8bit " + use8bit);
	if (use8bit && supportsExtension("8BITMIME")) {
	    if (convertTo8Bit(this.message)) {
		// in case we made any changes, save those changes
		// XXX - this will change the Message-ID
		try {
		    this.message.saveChanges();
		} catch (MessagingException mex) {
		    // ignore it
		}
	    }
	}

	try {
	    mailFrom();
	    rcptTo();
	    this.message.writeTo(data(), ignoreList);
	    finishData();
	    if (sendPartiallyFailed) {
		// throw the exception,
		// fire TransportEvent.MESSAGE_PARTIALLY_DELIVERED event
		if (debug)
		    out.println("DEBUG SMTP: Sending partially failed " +
			"because of invalid destination addresses");
		notifyTransportListeners(
			TransportEvent.MESSAGE_PARTIALLY_DELIVERED,
			validSentAddr, validUnsentAddr, invalidAddr,
			this.message);

		throw new SMTPSendFailedException(".", lastReturnCode,
				lastServerResponse, exception,
				validSentAddr, validUnsentAddr, invalidAddr);
	    }
	    notifyTransportListeners(TransportEvent.MESSAGE_DELIVERED,
				     validSentAddr, validUnsentAddr,
				     invalidAddr, this.message);
	} catch (MessagingException mex) {
	    if (debug)
		mex.printStackTrace(out);
	    notifyTransportListeners(TransportEvent.MESSAGE_NOT_DELIVERED,
				     validSentAddr, validUnsentAddr,
				     invalidAddr, this.message);

	    throw mex;
	} catch (IOException ex) {
	    if (debug)
		ex.printStackTrace(out);
	    // if we catch an IOException, it means that we want
	    // to drop the connection so that the message isn't sent
	    try {
		closeConnection();
	    } catch (MessagingException mex) { /* ignore it */ }
	    notifyTransportListeners(TransportEvent.MESSAGE_NOT_DELIVERED,
				     validSentAddr, validUnsentAddr,
				     invalidAddr, this.message);

	    throw new MessagingException("IOException while sending message",
					 ex);
	} finally {
	    // no reason to keep this data around
	    validSentAddr = validUnsentAddr = invalidAddr = null;
	    this.addresses = null;
	    this.message = null;
	    this.exception = null;
	    sendPartiallyFailed = false;
	}
    
public synchronized voidsetLocalHost(java.lang.String localhost)
Set the name of the local host, for use in the EHLO and HELO commands.

since
JavaMail 1.3.1

	localHostName = localhost;
    
public synchronized voidsetReportSuccess(boolean reportSuccess)
Set whether successful sends should be reported by throwing an exception.

param
reportSuccess should we throw an exception on success?
since
JavaMail 1.3.2

	this.reportSuccess = reportSuccess;
    
public synchronized voidsetSASLRealm(java.lang.String saslRealm)
Sets the SASL realm to be used for DIGEST-MD5 authentication.

param
saslRealm the name of the realm to use for SASL authentication.
since
JavaMail 1.3.1

	this.saslRealm = saslRealm;
    
public synchronized voidsetStartTLS(boolean useStartTLS)
Set whether the STARTTLS command should be used.

param
useStartTLS should we use the STARTTLS command?
since
JavaMail 1.3.2

	this.useStartTLS = useStartTLS;
    
public synchronized voidsetUseRset(boolean useRset)
Set whether the RSET command should be used instead of the NOOP command in the @{link #isConnected isConnected} method.

param
useRset should we use the RSET command?
since
JavaMail 1.4

	this.useRset = useRset;
    
public synchronized intsimpleCommand(java.lang.String cmd)
Send the command to the server and return the response code from the server.

since
JavaMail 1.4.1

	sendCommand(cmd);
	return readServerResponse();
    
protected intsimpleCommand(byte[] cmd)
Send the command to the server and return the response code from the server.

since
JavaMail 1.4.1

	assert Thread.holdsLock(this);
	sendCommand(cmd);
	return readServerResponse();
    
protected voidstartTLS()
Issue the STARTTLS command and switch the socket to TLS mode if it succeeds.

since
JavaMail 1.4.1

	issueCommand("STARTTLS", 220);
	// it worked, now switch the socket into TLS mode
	try {
	    serverSocket = SocketFetcher.startTLS(serverSocket,
				session.getProperties(), "mail." + name);
	    initStreams();
	} catch (IOException ioex) {
	    closeConnection();
	    throw new MessagingException("Could not convert socket to TLS",
								ioex);
	}
    
protected booleansupportsAuthentication(java.lang.String auth)
Does the server we're connected to support the specified authentication mechanism? Uses the extension information returned by the server from the EHLO command.

param
auth the authentication mechanism
return
true if the authentication mechanism is supported
since
JavaMail 1.4.1

	assert Thread.holdsLock(this);
	if (extMap == null)
	    return false;
	String a = (String)extMap.get("AUTH");
	if (a == null)
	    return false;
	StringTokenizer st = new StringTokenizer(a);
	while (st.hasMoreTokens()) {
	    String tok = st.nextToken();
	    if (tok.equalsIgnoreCase(auth))
		return true;
	}
	return false;
    
public booleansupportsExtension(java.lang.String ext)
Return true if the SMTP server supports the specified service extension. Extensions are reported as results of the EHLO command when connecting to the server. See RFC 1869 and other RFCs that define specific extensions.

param
ext the service extension name
return
true if the extension is supported
since
JavaMail 1.3.2

	return extMap != null &&
			extMap.get(ext.toUpperCase(Locale.ENGLISH)) != null;
    
protected static java.lang.Stringxtext(java.lang.String s)
Convert a string to RFC 1891 xtext format.

xtext = *( xchar / hexchar )

xchar = any ASCII CHAR between "!" (33) and "~" (126) inclusive,
except for "+" and "=".

; "hexchar"s are intended to encode octets that cannot appear
; as ASCII characters within an esmtp-value.

hexchar = ASCII "+" immediately followed by two upper case
hexadecimal digits

since
JavaMail 1.4.1


                                                                                                         
         
	StringBuffer sb = null;
	for (int i = 0; i < s.length(); i++) {
	    char c = s.charAt(i);
	    if (c >= 128)	// not ASCII
		throw new IllegalArgumentException(
		    "Non-ASCII character in SMTP submitter: " + s);
	    if (c < '!" || c > '~" || c == '+" || c == '=") {
		if (sb == null) {
		    sb = new StringBuffer(s.length() + 4);
		    sb.append(s.substring(0, i));
		}
		sb.append('+");
		sb.append(hexchar[(((int)c)& 0xf0) >> 4]);
		sb.append(hexchar[((int)c)& 0x0f]);
	    } else {
		if (sb != null)
		    sb.append(c);
	    }
	}
	return sb != null ? sb.toString() : s;