Open Source Repository

Home /mail/mail-1.4.3 | Repository Home


com/sun/mail/smtp/SMTPTransport.java
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 1997-2009 Sun Microsystems, Inc. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code.  If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 *
 * Contributor(s):
 *
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package com.sun.mail.smtp;

import java.io.*;
import java.net.*;
import java.util.*;

import javax.mail.*;
import javax.mail.event.*;
import javax.mail.internet.*;

import com.sun.mail.util.*;
import com.sun.mail.auth.*;

/**
 * This class implements the Transport abstract class using SMTP for
 * message submission and transport. <p>
 *
 * See the <a href="package-summary.html">com.sun.mail.smtp</a> package
 * documentation for further information on the SMTP protocol provider. <p>
 *
 * 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:
 <pre>
 *  protected OutputStream data() throws MessagingException {
 *      if (supportsExtension("XACCOUNTING"))
 *          issueCommand("XACT", 25);
 *      return super.data();
 *  }
 </pre>
 *
 @author Max Spivak
 @author Bill Shannon
 @author Dean Gibson (DIGEST-MD5 authentication)
 @author Lu��s Serralheiro (NTLM authentication)
 *
 @see javax.mail.event.ConnectionEvent
 @see javax.mail.event.TransportEvent
 */

public class SMTPTransport extends Transport {

    private String name = "smtp";  // Name of this protocol
    private int defaultPort = 25;  // default SMTP port
    private boolean isSSL = false;  // use SSL?
    private String host;    // host we're connected to

    // Following fields valid only during the sendMessage method.
    private MimeMessage message;  // Message to be sent
    private Address[] addresses;  // Addresses to which to send the msg
    // Valid sent, valid unsent and invalid addresses
    private Address[] validSentAddr, validUnsentAddr, invalidAddr;
    // Did we send the message even though some addresses were invalid?
    private boolean sendPartiallyFailed = false;
    // If so, here's an exception we need to throw
    private MessagingException exception;
    // stream where message data is written
    private SMTPOutputStream dataStream;

    // Map of SMTP service extensions supported by server, if EHLO used.
    private Hashtable extMap;

    private Map authenticators = new HashMap();
    private String defaultAuthenticationMechanisms;  // set in constructor

    private boolean quitWait = false;  // true if we should wait
    private String saslRealm = UNKNOWN;
    private String ntlmDomain = UNKNOWN; // for ntlm authentication

    private boolean reportSuccess;  // throw an exception even on success
    private boolean useStartTLS;  // use STARTTLS command
    private boolean requireStartTLS;  // require STARTTLS command
    private boolean useRset;    // use RSET instead of NOOP
    private boolean noopStrict = true;  // NOOP must return 250 for success

    private PrintStream out;    // debug output stream
    private String localHostName;  // our own host name
    private String lastServerResponse;  // last SMTP response
    private int lastReturnCode;    // last SMTP return code
    private boolean notificationDone;  // only notify once per send

    /** Headers that should not be included when sending */
    private static final String[] ignoreList = "Bcc""Content-Length" };
    private static final byte[] CRLF = { (byte)'\r'(byte)'\n' };
    private static final String UNKNOWN = "UNKNOWN";  // place holder

    /**
     * Constructor that takes a Session object and a URLName
     * that represents a specific SMTP server.
     */
    public SMTPTransport(Session session, URLName urlname) {
  this(session, urlname, "smtp"false);
    }

    /**
     * Constructor used by this class and by SMTPSSLTransport subclass.
     */
    protected SMTPTransport(Session session, URLName urlname,
        String name, boolean isSSL) {
  super(session, urlname);
  if (urlname != null)
      name = urlname.getProtocol();
  this.name = name;
  if (!isSSL)
      isSSL = PropUtil.getBooleanSessionProperty(session,
        "mail." + name + ".ssl.enable"false);
  if (isSSL)
      this.defaultPort = 465;
  else
      this.defaultPort = 25;
  this.isSSL = isSSL;

  out = session.getDebugOut();

  // setting mail.smtp.quitwait to false causes us to not wait for the
  // response from the QUIT command
  quitWait = PropUtil.getBooleanSessionProperty(session,
        "mail." + name + ".quitwait"true);

  // mail.smtp.reportsuccess causes us to throw an exception on success
  reportSuccess = PropUtil.getBooleanSessionProperty(session,
        "mail." + name + ".reportsuccess"false);

  // mail.smtp.starttls.enable enables use of STARTTLS command
  useStartTLS = PropUtil.getBooleanSessionProperty(session,
        "mail." + name + ".starttls.enable"false);

  // mail.smtp.starttls.required requires use of STARTTLS command
  requireStartTLS = PropUtil.getBooleanSessionProperty(session,
        "mail." + name + ".starttls.required"false);

  // mail.smtp.userset causes us to use RSET instead of NOOP
  // for isConnected
  useRset = PropUtil.getBooleanSessionProperty(session,
        "mail." + name + ".userset"false);

  // mail.smtp.noop.strict requires 250 response to indicate success
  noopStrict = PropUtil.getBooleanSessionProperty(session,
        "mail." + name + ".noop.strict"true);

  // created here, because they're inner classes that reference "this"
  Authenticator[] a = new Authenticator[] {
      new LoginAuthenticator(),
      new PlainAuthenticator(),
      new DigestMD5Authenticator(),
      new NtlmAuthenticator()
  };
  StringBuffer sb = new StringBuffer();
  for (int i = 0; i < a.length; i++) {
      authenticators.put(a[i].getMechanism(), a[i]);
      sb.append(a[i].getMechanism()).append(' ');
  }
  defaultAuthenticationMechanisms = sb.toString();
    }

    /**
     * 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.
     */
    public synchronized String getLocalHost() {
  // 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");
  try {
      if (localHostName == null || localHostName.length() <= 0) {
    InetAddress localHost = InetAddress.getLocalHost();
    localHostName = localHost.getCanonicalHostName();
    // 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) {
  }

  // last chance, try to get our address from our socket
  if (localHostName == null || localHostName.length() <= 0) {
      if (serverSocket != null && serverSocket.isBound()) {
    InetAddress localHost = serverSocket.getLocalAddress();
    localHostName = localHost.getCanonicalHostName();
    // if we can't get our name, use local address literal
    if (localHostName == null)
        // XXX - not correct for IPv6
        localHostName = "[" + localHost.getHostAddress() "]";
      }
  }
  return localHostName;
    }

    /**
     * Set the name of the local host, for use in the EHLO and HELO commands.
     *
     @since JavaMail 1.3.1
     */
    public synchronized void setLocalHost(String localhost) {
  localHostName = localhost;
    }

    /**
     * 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
     */
    public synchronized void connect(Socket socketthrows MessagingException {
  serverSocket = socket;
  super.connect();
    }

    /**
     * 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
     */
    public synchronized String getSASLRealm() {
  if (saslRealm == UNKNOWN) {
      saslRealm = session.getProperty("mail." + name + ".sasl.realm");
      if (saslRealm == null)  // try old name
    saslRealm = session.getProperty("mail." + name + ".saslrealm");
  }
  return 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
     */
    public synchronized void setSASLRealm(String saslRealm) {
  this.saslRealm = saslRealm;
    }

    /**
     * Gets the NTLM domain to be used for NTLM authentication.
     *
     @return  the name of the domain to use for NTLM authentication.
     *
     @since JavaMail 1.4.3
     */
    public synchronized String getNTLMDomain() {
  if (ntlmDomain == UNKNOWN) {
      ntlmDomain =
    session.getProperty("mail." + name + ".auth.ntlm.domain");
  }
  return ntlmDomain;
    }

    /**
     * Sets the NTLM domain to be used for NTLM authentication.
     *
     @param  ntlmDomain  the name of the domain to use for
     *        NTLM authentication.
     *
     @since JavaMail 1.4.3
     */
    public synchronized void setNTLMDomain(String ntlmDomain) {
  this.ntlmDomain = ntlmDomain;
    }

    /**
     * Should we report even successful sends by throwing an exception?
     * If so, a <code>SendFailedException</code> 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
     */
    public synchronized boolean getReportSuccess() {
  return 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
     */
    public synchronized void setReportSuccess(boolean reportSuccess) {
  this.reportSuccess = reportSuccess;
    }

    /**
     * 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
     */
    public synchronized boolean getStartTLS() {
  return useStartTLS;
    }

    /**
     * Set whether the STARTTLS command should be used.
     *
     @param  useStartTLS  should we use the STARTTLS command?
     *
     @since JavaMail 1.3.2
     */
    public synchronized void setStartTLS(boolean useStartTLS) {
  this.useStartTLS = useStartTLS;
    }

    /**
     * Should we require the STARTTLS command to secure the connection?
     *
     @return  true if the STARTTLS command will be required
     *
     @since JavaMail 1.4.2
     */
    public synchronized boolean getRequireStartTLS() {
  return requireStartTLS;
    }

    /**
     * Set whether the STARTTLS command should be required.
     *
     @param  requireStartTLS  should we require the STARTTLS command?
     *
     @since JavaMail 1.4.2
     */
    public synchronized void setRequireStartTLS(boolean requireStartTLS) {
  this.requireStartTLS = requireStartTLS;
    }

    /**
     * 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
     */
    public synchronized boolean getUseRset() {
  return 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
     */
    public synchronized void setUseRset(boolean useRset) {
  this.useRset = useRset;
    }

    /**
     * Is the NOOP command required to return a response code
     * of 250 to indicate success?
     *
     @return  true if NOOP must return 250
     *
     @since JavaMail 1.4.3
     */
    public synchronized boolean getNoopStrict() {
  return noopStrict;
    }

    /**
     * Set whether the NOOP command is required to return a response code
     * of 250 to indicate success.
     *
     @param  noopStrict is NOOP required to return 250?
     *
     @since JavaMail 1.4.3
     */
    public synchronized void setNoopStrict(boolean noopStrict) {
  this.noopStrict = noopStrict;
    }

    /**
     * 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
     */
    public synchronized String getLastServerResponse() {
  return lastServerResponse;
    }

    /**
     * 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
     */
    public synchronized int getLastReturnCode() {
  return lastReturnCode;
    }

    /**
     * Performs the actual protocol-specific connection attempt.
     * Will attempt to connect to "localhost" if the host was null. <p>
     *
     * 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
     */
    protected boolean protocolConnect(String host, int port, String user,
            String passwdthrows MessagingException {
  // setting mail.smtp.ehlo to false disables attempts to use EHLO
  boolean useEhlo =  PropUtil.getBooleanSessionProperty(session,
          "mail." + name + ".ehlo"true);
  // setting mail.smtp.auth to true enables attempts to use AUTH
  boolean useAuth = PropUtil.getBooleanSessionProperty(session,
          "mail." + name + ".auth"false);
  // setting mail.smtp.auth.mechanisms controls which mechanisms will
  // be used, and in what order they'll be considered.  only the first
  // match is used.
  String mechs = session.getProperty("mail." + name + ".auth.mechanisms");
  if (mechs == null)
      mechs = defaultAuthenticationMechanisms;

  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)
      port = PropUtil.getIntSessionProperty(session,
          "mail." + name + ".port", -1);
        if (port == -1)
      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 || requireStartTLS) {
      if (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());
      else if (requireStartTLS) {
    if (debug)
        out.println(
      "DEBUG SMTP: STARTTLS required but not supported");
    try {
        closeConnection();
    catch (MessagingException mex) { /* ignore it */ }
    throw new MessagingException(
        "STARTTLS is required but host does not support STARTTLS");
      }
  }

  if ((useAuth || (user != null && passwd != null)) &&
        (supportsExtension("AUTH"|| supportsExtension("AUTH=LOGIN"))) {
      if (debug) {
    out.println("DEBUG SMTP: Attempt to authenticate");
    out.println("DEBUG SMTP: check mechanisms: " + mechs);
      }

      /*
       * Loop through the list of mechanisms supplied by the user
       * (or defaulted) and try each in turn.  If the server supports
       * the mechanism and we have an authenticator for the mechanism,
       * use it.
       */
      StringTokenizer st = new StringTokenizer(mechs);
      while (st.hasMoreTokens()) {
    String m = st.nextToken().toUpperCase(Locale.ENGLISH);
    if (!supportsAuthentication(m)) {
        if (debug)
      out.println("DEBUG SMTP: mechanism " + m +
            " not supported by server");
        continue;
    }
    Authenticator a = (Authenticator)authenticators.get(m);
    if (a == null) {
        if (debug)
      out.println("DEBUG SMTP: " +
          "no authenticator for mechanism " + m);
        continue;
    }
    // only first supported mechanism is used
    return a.authenticate(host, user, passwd);
      }

      // if no authentication mechanism found, close connection and fail
      try {
    closeConnection();
      catch (MessagingException mex) { /* ignore it */ }
      throw new AuthenticationFailedException(
    "No authentication mechansims supported by both " +
    "server and client");
  }

  // we connected correctly
  return true;
    }

    /**
     * Abstract base class for SMTP authentication mechanism implementations.
     */
    private abstract class Authenticator {
  protected int resp;  // the response code, used by subclasses
  private String mech;  // the mechanism name, set in the constructor

  Authenticator(String mech) {
      this.mech = mech.toUpperCase(Locale.ENGLISH);
  }

  String getMechanism() {
      return mech;
  }

  /**
   * Start the authentication handshake by issuing the AUTH command.
   * Delegate to the doAuth method to do the mechanism-specific
   * part of the handshake.
   */
  boolean authenticate(String host, String user, String passwd)
        throws MessagingException {
      try {
    // use "initial response" capability, if supported
    String ir = getInitialResponse(host, user, passwd);
    if (ir != null)
        resp = simpleCommand("AUTH " + mech + " " + ir);
    else
        resp = simpleCommand("AUTH " + mech);

    /*
     * A 530 response indicates that the server wants us to
     * issue a STARTTLS command first.  Do that and try again.
     */
    if (resp == 530) {
        startTLS();
        if (ir != null)
      resp = simpleCommand("AUTH " + mech + " " + ir);
        else
      resp = simpleCommand("AUTH " + mech);
    }
    if (resp == 334)
        doAuth(host, user, passwd);
      catch (IOException ex) {  // should never happen, ignore
    if (debug)
        out.println("DEBUG: SMTP: " + mech + " failed: " + ex);
      finally {
    if (resp != 235) {
        closeConnection();
        throw new AuthenticationFailedException(
              getLastServerResponse());
    }
      }
      return true;
  }

  /**
   * Provide the initial response to use in the AUTH command,
   * or null if not supported.  Subclasses that support the
   * initial response capability will override this method.
   */
  String getInitialResponse(String host, String user, String passwd)
        throws MessagingException, IOException {
      return null;
  }

  abstract void doAuth(String host, String user, String passwd)
        throws MessagingException, IOException;
    }

    /**
     * Perform the authentication handshake for LOGIN authentication.
     */
    private class LoginAuthenticator extends Authenticator {
  LoginAuthenticator() {
      super("LOGIN");
  }

  void doAuth(String host, String user, String passwd)
            throws MessagingException, IOException {
      // send username
      resp = simpleCommand(
    BASE64EncoderStream.encode(ASCIIUtility.getBytes(user)));
      if (resp == 334) {
    // send passwd
    resp = simpleCommand(
        BASE64EncoderStream.encode(ASCIIUtility.getBytes(passwd)));
      }
  }
    }

    /**
     * Perform the authentication handshake for PLAIN authentication.
     */
    private class PlainAuthenticator extends Authenticator {
  PlainAuthenticator() {
      super("PLAIN");
  }

  void doAuth(String host, String user, String passwd)
            throws MessagingException, IOException {
      // send "<NUL>user<NUL>passwd"
      // XXX - we don't send an authorization identity
      ByteArrayOutputStream bos = new ByteArrayOutputStream();
      OutputStream b64os =
      new BASE64EncoderStream(bos, Integer.MAX_VALUE);
      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());
  }
    }

    /**
     * Perform the authentication handshake for DIGEST-MD5 authentication.
     */
    private class DigestMD5Authenticator extends Authenticator {
  private DigestMD5 md5support;  // only create if needed

  DigestMD5Authenticator() {
      super("DIGEST-MD5");
  }

  private synchronized DigestMD5 getMD5() {
      if (md5support == null)
    md5support = new DigestMD5(debug ? out : null);
      return md5support;
  }

  void doAuth(String host, String user, String passwd)
            throws MessagingException, IOException {
      DigestMD5 md5 = getMD5();
      if (md5 == null) {
    resp = -1;
    return;    // XXX - should never happen
      }

      byte[] b = md5.authClient(host, user, passwd, getSASLRealm(),
          getLastServerResponse());
      resp = simpleCommand(b);
      if (resp == 334) { // client authenticated by server
    if (!md5.authServer(getLastServerResponse())) {
        // server NOT authenticated by client !!!
        resp = -1;
    else {
        // send null response
        resp = simpleCommand(new byte[0]);
    }
      }
  }
    }

    /**
     * Perform the authentication handshake for NTLM authentication.
     */
    private class NtlmAuthenticator extends Authenticator {
  private Ntlm ntlm;
  private int flags;

  NtlmAuthenticator() {
      super("NTLM");
  }

  private synchronized Ntlm getNtlm() {
      if (ntlm == null) {
    ntlm = new Ntlm(debug ? out : null);
      }
      return ntlm;
  }

  String getInitialResponse(String host, String user, String passwd)
    throws MessagingException, IOException {
      Ntlm ntlm = getNtlm();

      flags = PropUtil.getIntProperty(
        session.getProperties(),
        "mail." + name + ".auth.ntlm.flags"0);
      boolean useUnicode = PropUtil.getBooleanProperty(
        session.getProperties(),
        "mail." + name + ".auth.ntlm.unicode"true);

      String type1 = ntlm.generateType1Msg(
        useUnicode, flags, getNTLMDomain(), getLocalHost());
      return type1;
  }

  void doAuth(String host, String user, String passwd)
    throws MessagingException, IOException {
      int lmCompatibility = PropUtil.getIntProperty(
        session.getProperties(),
        "mail." + name + ".auth.ntlm.lmcompat"3);
      String type3 = ntlm.generateType3Msg(user, passwd,
        getNTLMDomain(), getLocalHost(),
        getLastServerResponse().substring(4),
        flags, lmCompatibility);

      resp = simpleCommand(type3);
  }
    }

    /**
     * Send the Message to the specified list of addresses.<p>
     *
     * If all the <code>addresses</code> succeed the SMTP check
     * using the <code>RCPT TO:</code> 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.<p>
     *
     * If some of the <code>addresses</code> fail the SMTP check,
     * and the <code>mail.stmp.sendpartial</code> 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. <p>
     *
     * If some of the <code>addresses</code> fail the SMTP check,
     * and the <code>mail.stmp.sendpartial</code> 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. <p>
     *
     * MessagingException is thrown if the message can't write out
     * an RFC822-compliant stream using its <code>writeTo</code> method. <p>
     *
     @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.
     */
    public synchronized void sendMessage(Message message, Address[] addresses)
        throws MessagingException, SendFailedException {

  sendMessageStart(message != null ? message.getSubject() "");
  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[iinstanceof InternetAddress)) {
    throw new MessagingException(addresses[i+
               " is not an InternetAddress");
      }
  }
  if (addresses.length == 0)
      throw new SendFailedException("No recipient addresses");

  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)
      use8bit = PropUtil.getBooleanSessionProperty(session,
        "mail." + name + ".allow8bitmime"false);
  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);
      addressesFailed();
      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 */ }
      addressesFailed();
      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;
      notificationDone = false;  // reset for next send
  }
  sendMessageEnd();
    }

    /**
     * The send failed, fix the address arrays to report the failure correctly.
     */
    private void addressesFailed() {
  if (validSentAddr != null) {
      if (validUnsentAddr != null) {
    Address newa[] =
        new Address[validSentAddr.length + validUnsentAddr.length];
    System.arraycopy(validSentAddr, 0,
      newa, 0, validSentAddr.length);
    System.arraycopy(validUnsentAddr, 0,
      newa, validSentAddr.length, validUnsentAddr.length);
    validSentAddr = null;
    validUnsentAddr = newa;
      else {
    validUnsentAddr = validSentAddr;
    validSentAddr = null;
      }
  }
    }

    /**
     * Close the Transport and terminate the connection to the server.
     */
    public synchronized void close() throws MessagingException {
  if (!super.isConnected()) // Already closed.
      return;
  try {
      if (serverSocket != null) {
    sendCommand("QUIT");
    if (quitWait) {
        int resp = readServerResponse();
        if (resp != 221 && resp != -&& debug)
      out.println("DEBUG SMTP: QUIT failed with " + resp);
    }
      }
  finally {
      closeConnection();
  }
    }

    private void closeConnection() throws MessagingException {
  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();
  }
    }

    /**
     * Check whether the transport is connected. Override superclass
     * method, to actually ping our server connection.
     */
    public synchronized boolean isConnected() {
  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 didn't used to 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.
       *
       * Some versions of Exchange return 451 instead of 421 when
       * timing out a connection.
       *
       * Argh!
       *
       * If mail.smtp.noop.strict is set to false, be tolerant of
       * servers that return the wrong response code for success.
       */
      if (resp >= && (noopStrict ? resp == 250 : 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;
  }
    }

    /**
     * Notify all TransportListeners.  Keep track of whether notification
     * has been done so as to only notify once per send.
     *
     @since  JavaMail 1.4.2
     */
    protected void notifyTransportListeners(int type, Address[] validSent,
              Address[] validUnsent,
              Address[] invalid, Message msg) {

  if (!notificationDone) {
      super.notifyTransportListeners(type, validSent, validUnsent,
    invalid, msg);
      notificationDone = true;
  }
    }

    /**
     * Expand any group addresses.
     */
    private void expandGroups() {
  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;
  }
    }

    /**
     * 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.
     */
    private boolean convertTo8Bit(MimePart part) {
  boolean changed = false;
  try {
      if (part.isMimeType("text/*")) {
    String enc = part.getEncoding();
    if (enc != null && (enc.equalsIgnoreCase("quoted-printable"||
        enc.equalsIgnoreCase("base64"))) {
        InputStream is = null;
        try {
      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;
      }
        finally {
      if (is != null) {
          try {
        is.close();
          catch (IOException ex2) {
        // ignore it
          }
      }
        }
    }
      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;
    }

    /**
     * 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.
     */
    private boolean is8Bit(InputStream is) {
  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;
    }

    protected void finalize() throws Throwable {
  super.finalize();
  try {
      closeConnection();
  catch (MessagingException mex) { }  // ignore it
    }

    ///////////////////// smtp stuff ///////////////////////
    private BufferedInputStream  serverInput;
    private LineInputStream      lineInputStream;
    private OutputStream         serverOutput;
    private Socket               serverSocket;

    /////// smtp protocol //////

    /**
     * Issue the <code>HELO</code> command.
     *
     @param  domain  our domain
     *
     @since JavaMail 1.4.1
     */
    protected void helo(String domainthrows MessagingException {
  if (domain != null)
      issueCommand("HELO " + domain, 250);
  else
      issueCommand("HELO"250);
    }

    /**
     * Issue the <code>EHLO</code> command.
     * Collect the returned list of service extensions.
     *
     @param  domain  our domain
     @return    true if command succeeds
     *
     @since JavaMail 1.4.1
     */
    protected boolean ehlo(String domainthrows MessagingException {
  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;
    }

    /**
     * Issue the <code>MAIL FROM:</code> command to start sending a message. <p>
     *
     * Gets the sender's address in the following order:
     <ol>
     <li>SMTPMessage.getEnvelopeFrom()</li>
     <li>mail.smtp.from property</li>
     <li>From: header in the message</li>
     <li>System username using the
     * InternetAddress.getLocalAddress() method</li>
     </ol>
     *
     @since JavaMail 1.4.1
     */
    protected void mailFrom() throws MessagingException {
  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);
    }

    /**
     * Sends each address to the SMTP host using the <code>RCPT TO:</code>
     * command and copies the address either into
     * the validSentAddr or invalidAddr arrays.
     * Sets the <code>sendFailed</code>
     * flag to true if any addresses failed.
     *
     @since JavaMail 1.4.1
     */
    /*
     * success/failure/error possibilities from the RCPT command
     * from rfc821, section 4.3
     * S: 250, 251
     * F: 550, 551, 552, 553, 450, 451, 452
     * E: 500, 501, 503, 421
     *
     * and how we map the above error/failure conditions to valid/invalid
     * address vectors that are reported in the thrown exception:
     * invalid addr: 550, 501, 503, 551, 553
     * valid addr: 552 (quota), 450, 451, 452 (quota), 421 (srvr abort)
     */
    protected void rcptTo() throws MessagingException {
  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)
      sendPartial = PropUtil.getBooleanSessionProperty(session,
          "mail." + name + ".sendpartial"false);
  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 250case 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 550case 553case 503case 551case 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 552case 450case 451case 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", -1);
        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() || 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", -1);
      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);
  }
    }

    /**
     * Send the <code>DATA</code> command to the SMTP host and return
     * an OutputStream to which the data is to be written.
     *
     @since JavaMail 1.4.1
     */
    protected OutputStream data() throws MessagingException {
  assert Thread.holdsLock(this);
  issueSendCommand("DATA"354);
  dataStream = new SMTPOutputStream(serverOutput);
  return dataStream;
    }

    /**
     * Terminate the sent data.
     *
     @since JavaMail 1.4.1
     */
    protected void finishData() throws IOException, MessagingException {
  assert Thread.holdsLock(this);
  dataStream.ensureAtBOL();
  issueSendCommand("."250);
    }

    /**
     * Issue the <code>STARTTLS</code> command and switch the socket to
     * TLS mode if it succeeds.
     *
     @since JavaMail 1.4.1
     */
    protected void startTLS() throws MessagingException {
  issueCommand("STARTTLS"220);
  // it worked, now switch the socket into TLS mode
  try {
      serverSocket = SocketFetcher.startTLS(serverSocket, host,
        session.getProperties()"mail." + name);
      initStreams();
  catch (IOException ioex) {
      closeConnection();
      throw new MessagingException("Could not convert socket to TLS",
                ioex);
  }
    }

    /////// primitives ///////

    /**
     * Connect to host on port and start the SMTP protocol.
     */
    private void openServer(String host, int port)
        throws MessagingException {

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

  try {
      Properties props = session.getProperties();

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

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

      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 \"" +
            host + "\", port: " + port +
            ", response: " + r + "\n");
    throw new MessagingException(
      "Could not connect to SMTP host: " + host +
            ", port: " + port +
            ", response: " + r);
      else {
    if (debug)
        out.println("DEBUG SMTP: connected to host \"" +
               host + "\", port: " + port + "\n");
      }
  catch (UnknownHostException uhex) {
      throw new MessagingException("Unknown SMTP host: " + host, uhex);
  catch (IOException ioe) {
      throw new MessagingException("Could not connect to SMTP host: " +
            host + ", port: " + port, ioe);
  }
    }

    /**
     * Start the protocol to the server on serverSocket,
     * assumed to be provided and connected by the caller.
     */
    private void openServer() throws MessagingException {
  int port = -1;
  host = "UNKNOWN";
  try {
      port = serverSocket.getPort();
      host = serverSocket.getInetAddress().getHostName();
      if (debug)
    out.println("DEBUG SMTP: starting protocol to host \"" +
          host + "\", 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 \"" +
            host + "\", port: " + port +
            ", response: " + r + "\n");
    throw new MessagingException(
      "Got bad greeting from SMTP host: " + host +
            ", port: " + port +
            ", response: " + r);
      else {
    if (debug)
        out.println("DEBUG SMTP: protocol started to host \"" +
               host + "\", port: " + port + "\n");
      }
  catch (IOException ioe) {
      throw new MessagingException(
            "Could not start protocol to SMTP host: " +
            host + ", port: " + port, ioe);
  }
    }


    private void initStreams() throws IOException {
  PrintStream out = session.getDebugOut();
  boolean debug = session.getDebug();
  boolean quote = PropUtil.getBooleanSessionProperty(session,
          "mail.debug.quote"false);

  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);
    }

    /**
     * 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 (-1 means don't care)
     *
     @since JavaMail 1.4.1
     */
    public synchronized void issueCommand(String cmd, int expect)
        throws MessagingException {
  sendCommand(cmd);

  // if server responded with an unexpected return code,
  // throw the exception, notifying the client of the response
  int resp = readServerResponse();
  if (expect != -&& resp != expect)
      throw new MessagingException(lastServerResponse);
    }

    /**
     * Issue a command that's part of sending a message.
     */
    private void issueSendCommand(String cmd, int expect)
        throws MessagingException {
  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 : validSentAddr.length;
      int vul = validUnsentAddr == null : 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", -1);
      lastServerResponse = _lsr;  // restore, for get
      lastReturnCode = _lrc;
      throw new SMTPSendFailedException(cmd, ret, lastServerResponse,
      exception, validSentAddr, validUnsentAddr, invalidAddr);
  }
    }

    /**
     * Send the command to the server and return the response code
     * from the server.
     *
     @since JavaMail 1.4.1
     */
    public synchronized int simpleCommand(String cmd)
        throws MessagingException {
  sendCommand(cmd);
  return readServerResponse();
    }

    /**
     * Send the command to the server and return the response code
     * from the server.
     *
     @since JavaMail 1.4.1
     */
    protected int simpleCommand(byte[] cmdthrows MessagingException {
  assert Thread.holdsLock(this);
  sendCommand(cmd);
  return readServerResponse();
    }

    /**
     * Sends command <code>cmd</code> to the server terminating
     * it with <code>CRLF</code>.
     *
     @since JavaMail 1.4.1
     */
    protected void sendCommand(String cmdthrows MessagingException {
  sendCommand(ASCIIUtility.getBytes(cmd));
    }

    private void sendCommand(byte[] cmdBytesthrows MessagingException {
  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);
  }
    }

    /**
     * Reads server reponse returning the <code>returnCode</code>
     * as the number.  Returns -1 on failure. Sets
     <code>lastServerResponse</code> and <code>lastReturnCode</code>.
     *
     @return    server response code
     *
     @since JavaMail 1.4.1
     */
    protected int readServerResponse() throws MessagingException {
  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(03));
            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 == -&& debug)
      out.println("DEBUG SMTP: bad server response: " + serverResponse);

        lastServerResponse = serverResponse;
  lastReturnCode = returnCode;
        return returnCode;
    }

    /**
     * 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
     */
    protected void checkConnected() {
  if (!super.isConnected())
      throw new IllegalStateException("Not connected");
    }

    // tests if the <code>line</code> is an intermediate line according to SMTP
    private boolean isNotLastLine(String line) {
        return line != null && line.length() >= && line.charAt(3== '-';
    }

    // wraps an address in "<>"'s if necessary
    private String normalizeAddress(String addr) {
  if ((!addr.startsWith("<")) && (!addr.endsWith(">")))
      return "<" + addr + ">";
  else
      return addr;
    }

    /**
     * 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
     * <A HREF="http://www.ietf.org/rfc/rfc1869.txt">RFC 1869</A>
     * 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
     */
    public boolean supportsExtension(String ext) {
  return extMap != null &&
      extMap.get(ext.toUpperCase(Locale.ENGLISH)) != null;
    }

    /**
     * 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
     */
    public String getExtensionParameter(String ext) {
  return extMap == null null :
      (String)extMap.get(ext.toUpperCase(Locale.ENGLISH));
    }

    /**
     * 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
     */
    protected boolean supportsAuthentication(String auth) {
  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;
  }
  // hack for buggy servers that advertise capability incorrectly
  if (auth.equalsIgnoreCase("LOGIN"&& supportsExtension("AUTH=LOGIN")) {
      out.println("DEBUG SMTP: use AUTH=LOGIN hack");
      return true;
  }
  return false;
    }

    private static char[] hexchar = {
  '0''1''2''3''4''5''6''7',
  '8''9''A''B''C''D''E''F'
    };

    /**
     * Convert a string to RFC 1891 xtext format.
     *
     <p><pre>
     *     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
     </pre></p>
     *
     @since JavaMail 1.4.1
     */
    protected static String xtext(String s) {
  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;
    }

    /*
     * Probe points for GlassFish monitoring.
     */
    private void sendMessageStart(String subject) { }
    private void sendMessageEnd() { }
}