SMTPClient.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */

  17. package org.apache.commons.net.smtp;

  18. import java.io.IOException;
  19. import java.io.Writer;
  20. import java.net.InetAddress;

  21. import org.apache.commons.net.io.DotTerminatedMessageWriter;

  22. /**
  23.  * SMTPClient encapsulates all the functionality necessary to send files through an SMTP server. This class takes care of all low level details of interacting
  24.  * with an SMTP server and provides a convenient higher level interface. As with all classes derived from {@link org.apache.commons.net.SocketClient}, you must
  25.  * first connect to the server with {@link org.apache.commons.net.SocketClient#connect connect } before doing anything, and finally
  26.  * {@link org.apache.commons.net.SocketClient#disconnect disconnect } after you're completely finished interacting with the server. Then you need to check the
  27.  * SMTP reply code to see if the connection was successful. For example:
  28.  *
  29.  * <pre>
  30.  *    try {
  31.  *      int reply;
  32.  *      client.connect("mail.foobar.com");
  33.  *      System.out.print(client.getReplyString());
  34.  *
  35.  *      // After connection attempt, you should check the reply code to verify
  36.  *      // success.
  37.  *      reply = client.getReplyCode();
  38.  *
  39.  *      if (!SMTPReply.isPositiveCompletion(reply)) {
  40.  *        client.disconnect();
  41.  *        System.err.println("SMTP server refused connection.");
  42.  *        System.exit(1);
  43.  *      }
  44.  *
  45.  *      // Do useful stuff here.
  46.  *      ...
  47.  *    } catch (IOException e) {
  48.  *      if (client.isConnected()) {
  49.  *        try {
  50.  *          client.disconnect();
  51.  *        } catch (IOException f) {
  52.  *          // do nothing
  53.  *        }
  54.  *      }
  55.  *      System.err.println("Could not connect to server.");
  56.  *      e.printStackTrace();
  57.  *      System.exit(1);
  58.  *    }
  59.  * </pre>
  60.  * <p>
  61.  * Immediately after connecting is the only real time you need to check the reply code (because connect is of type void). The convention for all the SMTP
  62.  * command methods in SMTPClient is such that they either return a boolean value or some other value. The boolean methods return true on a successful completion
  63.  * reply from the SMTP server and false on a reply resulting in an error condition or failure. The methods returning a value other than boolean return a value
  64.  * containing the higher level data produced by the SMTP command, or null if a reply resulted in an error condition or failure. If you want to access the exact
  65.  * SMTP reply code causing a success or failure, you must call {@link org.apache.commons.net.smtp.SMTP#getReplyCode getReplyCode } after a success or failure.
  66.  * </p>
  67.  * <p>
  68.  * You should keep in mind that the SMTP server may choose to prematurely close a connection for various reasons. The SMTPClient class will detect a premature
  69.  * SMTP server connection closing when it receives a {@link org.apache.commons.net.smtp.SMTPReply#SERVICE_NOT_AVAILABLE SMTPReply.SERVICE_NOT_AVAILABLE }
  70.  * response to a command. When that occurs, the method encountering that reply will throw an {@link org.apache.commons.net.smtp.SMTPConnectionClosedException} .
  71.  * <code>SMTPConnectionClosedException</code> is a subclass of <code>IOException</code> and therefore need not be caught separately, but if you are going to
  72.  * catch it separately, its catch block must appear before the more general <code>IOException</code> catch block. When you encounter an
  73.  * {@link org.apache.commons.net.smtp.SMTPConnectionClosedException} , you must disconnect the connection with {@link #disconnect disconnect() } to properly
  74.  * clean up the system resources used by SMTPClient. Before disconnecting, you may check the last reply code and text with
  75.  * {@link org.apache.commons.net.smtp.SMTP#getReplyCode getReplyCode }, {@link org.apache.commons.net.smtp.SMTP#getReplyString getReplyString }, and
  76.  * {@link org.apache.commons.net.smtp.SMTP#getReplyStrings getReplyStrings}.
  77.  * </p>
  78.  * <p>
  79.  * Rather than list it separately for each method, we mention here that every method communicating with the server and throwing an IOException can also throw a
  80.  * {@link org.apache.commons.net.MalformedServerReplyException} , which is a subclass of IOException. A MalformedServerReplyException will be thrown when the
  81.  * reply received from the server deviates enough from the protocol specification that it cannot be interpreted in a useful manner despite attempts to be as
  82.  * lenient as possible.
  83.  * </p>
  84.  *
  85.  * @see SMTP
  86.  * @see SimpleSMTPHeader
  87.  * @see RelayPath
  88.  * @see SMTPConnectionClosedException
  89.  * @see org.apache.commons.net.MalformedServerReplyException
  90.  */
  91. public class SMTPClient extends SMTP {

  92.     /**
  93.      * Default SMTPClient constructor. Creates a new SMTPClient instance.
  94.      */
  95.     public SMTPClient() {
  96.     }

  97.     /**
  98.      * Overloaded constructor that takes an encoding specification
  99.      *
  100.      * @param encoding The encoding to use
  101.      * @since 2.0
  102.      */
  103.     public SMTPClient(final String encoding) {
  104.         super(encoding);
  105.     }

  106.     /**
  107.      * Adds a recipient for a message using the SMTP RCPT command, specifying a forward relay path. The sender must be set first before any recipients may be
  108.      * specified, otherwise the mail server will reject your commands.
  109.      *
  110.      * @param path The forward relay path pointing to the recipient.
  111.      * @return True if successfully completed, false if not.
  112.      * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
  113.      *                                       causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
  114.      *                                       independently as itself.
  115.      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
  116.      */
  117.     public boolean addRecipient(final RelayPath path) throws IOException {
  118.         return SMTPReply.isPositiveCompletion(rcpt(path.toString()));
  119.     }

  120.     /**
  121.      * Adds a recipient for a message using the SMTP RCPT command, the recipient's email address. The sender must be set first before any recipients may be
  122.      * specified, otherwise the mail server will reject your commands.
  123.      *
  124.      * @param address The recipient's email address.
  125.      * @return True if successfully completed, false if not.
  126.      * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
  127.      *                                       causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
  128.      *                                       independently as itself.
  129.      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
  130.      */
  131.     public boolean addRecipient(final String address) throws IOException {
  132.         return SMTPReply.isPositiveCompletion(rcpt("<" + address + ">"));
  133.     }

  134.     /**
  135.      * At least one SMTPClient method ({@link #sendMessageData sendMessageData }) does not complete the entire sequence of SMTP commands to complete a
  136.      * transaction. These types of commands require some action by the programmer after the reception of a positive intermediate command. After the programmer's
  137.      * code completes its actions, it must call this method to receive the completion reply from the server and verify the success of the entire transaction.
  138.      * <p>
  139.      * For example,
  140.      * </p>
  141.      * <pre>
  142.      * writer = client.sendMessageData();
  143.      * if (writer == null) // failure
  144.      *     return false;
  145.      * header = new SimpleSMTPHeader("foobar@foo.com", "foo@foobar.com", "Re: Foo");
  146.      * writer.write(header.toString());
  147.      * writer.write("This is just a test");
  148.      * writer.close();
  149.      * if (!client.completePendingCommand()) // failure
  150.      *     return false;
  151.      * </pre>
  152.      *
  153.      * @return True if successfully completed, false if not.
  154.      * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
  155.      *                                       causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
  156.      *                                       independently as itself.
  157.      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
  158.      */
  159.     public boolean completePendingCommand() throws IOException {
  160.         return SMTPReply.isPositiveCompletion(getReply());
  161.     }

  162.     /**
  163.      * Fetches the system help information from the server and returns the full string.
  164.      *
  165.      * @return The system help string obtained from the server. null if the information could not be obtained.
  166.      * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
  167.      *                                       causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
  168.      *                                       independently as itself.
  169.      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
  170.      */
  171.     public String listHelp() throws IOException {
  172.         if (SMTPReply.isPositiveCompletion(help())) {
  173.             return getReplyString();
  174.         }
  175.         return null;
  176.     }

  177.     /**
  178.      * Fetches the help information for a given command from the server and returns the full string.
  179.      *
  180.      * @param command The command on which to ask for help.
  181.      * @return The command help string obtained from the server. null if the information could not be obtained.
  182.      * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
  183.      *                                       causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
  184.      *                                       independently as itself.
  185.      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
  186.      */
  187.     public String listHelp(final String command) throws IOException {
  188.         if (SMTPReply.isPositiveCompletion(help(command))) {
  189.             return getReplyString();
  190.         }
  191.         return null;
  192.     }

  193.     /**
  194.      * Login to the SMTP server by sending the {@code HELO} command with the client hostname as an argument.
  195.      * Before performing any mail commands, you must first log in.
  196.      *
  197.      * @return True if successfully completed, false if not.
  198.      * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
  199.      *                                       causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
  200.      *                                       independently as itself.
  201.      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
  202.      */
  203.     public boolean login() throws IOException {
  204.         final InetAddress host = getLocalAddress();
  205.         final String name = host.getHostName();
  206.         if (name == null) {
  207.             return false;
  208.         }
  209.         return SMTPReply.isPositiveCompletion(helo(name));
  210.     }

  211.     /**
  212.      * Login to the SMTP server by sending the {@code HELO} command with the given hostname as an argument.
  213.      * Before performing any mail commands, you must first log in.
  214.      *
  215.      * @param hostname The hostname with which to greet the SMTP server.
  216.      * @return True if successfully completed, false if not.
  217.      * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
  218.      *                                       causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
  219.      *                                       independently as itself.
  220.      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
  221.      */
  222.     public boolean login(final String hostname) throws IOException {
  223.         return SMTPReply.isPositiveCompletion(helo(hostname));
  224.     }

  225.     /**
  226.      * Logout of the SMTP server by sending the QUIT command.
  227.      *
  228.      * @return True if successfully completed, false if not.
  229.      * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
  230.      *                                       causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
  231.      *                                       independently as itself.
  232.      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
  233.      */
  234.     public boolean logout() throws IOException {
  235.         return SMTPReply.isPositiveCompletion(quit());
  236.     }

  237.     /**
  238.      * Aborts the current mail transaction, resetting all server stored sender, recipient, and mail data, cleaning all buffers and tables.
  239.      *
  240.      * @return True if successfully completed, false if not.
  241.      * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
  242.      *                                       causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
  243.      *                                       independently as itself.
  244.      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
  245.      */
  246.     public boolean reset() throws IOException {
  247.         return SMTPReply.isPositiveCompletion(rset());
  248.     }

  249.     /**
  250.      * Sends the SMTP DATA command in preparation to send an email message. This method returns a DotTerminatedMessageWriter instance to which the message can
  251.      * be written. Null is returned if the DATA command fails.
  252.      * <p>
  253.      * You must not issue any commands to the SMTP server (i.e., call any (other methods) until you finish writing to the returned Writer instance and close it.
  254.      * The SMTP protocol uses the same stream for issuing commands as it does for returning results. Therefore, the returned Writer actually writes directly to
  255.      * the SMTP connection. After you close the writer, you can execute new commands. If you do not follow these requirements your program will not work
  256.      * properly.
  257.      * </p>
  258.      * <p>
  259.      * You can use the provided {@link org.apache.commons.net.smtp.SimpleSMTPHeader} class to construct a bare minimum header. To construct more complicated
  260.      * headers you should refer to RFC 5322. When the Java Mail API is finalized, you will be able to use it to compose fully compliant Internet text messages.
  261.      * The DotTerminatedMessageWriter takes care of doubling line-leading dots and ending the message with a single dot upon closing, so all you have to worry
  262.      * about is writing the header and the message.
  263.      * </p>
  264.      * <p>
  265.      * Upon closing the returned Writer, you need to call {@link #completePendingCommand completePendingCommand() } to finalize the transaction and verify its
  266.      * success or failure from the server reply.
  267.      * </p>
  268.      *
  269.      * @return A DotTerminatedMessageWriter to which the message (including header) can be written. Returns null if the command fails.
  270.      * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
  271.      *                                       causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
  272.      *                                       independently as itself.
  273.      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
  274.      * @see #sendShortMessageData(String)
  275.      */
  276.     public Writer sendMessageData() throws IOException {
  277.         if (!SMTPReply.isPositiveIntermediate(data())) {
  278.             return null;
  279.         }
  280.         return new DotTerminatedMessageWriter(writer);
  281.     }

  282.     /**
  283.      * Sends a NOOP command to the SMTP server. This is useful for preventing server timeouts.
  284.      *
  285.      * @return True if successfully completed, false if not.
  286.      * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
  287.      *                                       causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
  288.      *                                       independently as itself.
  289.      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
  290.      */
  291.     public boolean sendNoOp() throws IOException {
  292.         return SMTPReply.isPositiveCompletion(noop());
  293.     }

  294.     /**
  295.      * Sends a short messages. This method fetches the Writer returned by {@link #sendMessageData sendMessageData() } and writes the
  296.      * specified String to it. After writing the message, this method calls {@link #completePendingCommand completePendingCommand() } to finalize the
  297.      * transaction and returns its success or failure.
  298.      *
  299.      * @param message The short email message to send. This must include the headers and the body, but not the trailing "."
  300.      * @return True if successfully completed, false if not.
  301.      * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
  302.      *                                       causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
  303.      *                                       independently as itself.
  304.      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
  305.      */
  306.     public boolean sendShortMessageData(final String message) throws IOException {
  307.         try (final Writer writer = sendMessageData()) {
  308.             if (writer == null) {
  309.                 return false;
  310.             }
  311.             writer.write(message);
  312.         }
  313.         return completePendingCommand();
  314.     }

  315.     /**
  316.      * Sends a short email without having to explicitly set the sender and recipient(s). This method sets the sender and recipient
  317.      * using {@link #setSender setSender } and {@link #addRecipient addRecipient }, and then sends the message using {@link #sendShortMessageData
  318.      * sendShortMessageData }.
  319.      *
  320.      * @param sender    The email address of the sender.
  321.      * @param recipient The email address of the recipient.
  322.      * @param message   The short email message to send. This must include the headers and the body, but not the trailing "."
  323.      * @return True if successfully completed, false if not.
  324.      * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
  325.      *                                       causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
  326.      *                                       independently as itself.
  327.      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
  328.      */
  329.     public boolean sendSimpleMessage(final String sender, final String recipient, final String message) throws IOException {
  330.         if (!setSender(sender)) {
  331.             return false;
  332.         }
  333.         if (!addRecipient(recipient)) {
  334.             return false;
  335.         }
  336.         return sendShortMessageData(message);
  337.     }

  338.     /**
  339.      * Sends a short email without having to explicitly set the sender and recipient(s). This method sets the sender and recipients
  340.      * using {@link #setSender(String) setSender} and {@link #addRecipient(String) addRecipient}, and then sends the message using
  341.      * {@link #sendShortMessageData(String) sendShortMessageData}.
  342.      * <p>
  343.      * Note that the method ignores failures when calling {@link #addRecipient(String) addRecipient} so long as at least one call succeeds. If no recipients can
  344.      * be successfully added then the method will fail (and does not attempt to send the message)
  345.      * </p>
  346.      *
  347.      * @param sender     The email address of the sender.
  348.      * @param recipients An array of recipient email addresses.
  349.      * @param message    The short email message to send. This must include the headers and the body, but not the trailing "."
  350.      * @return True if successfully completed, false if not.
  351.      * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
  352.      *                                       causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
  353.      *                                       independently as itself.
  354.      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
  355.      */
  356.     public boolean sendSimpleMessage(final String sender, final String[] recipients, final String message) throws IOException {
  357.         if (!setSender(sender)) {
  358.             return false;
  359.         }
  360.         boolean oneSuccess = false;
  361.         for (final String recipient : recipients) {
  362.             if (addRecipient(recipient)) {
  363.                 oneSuccess = true;
  364.             }
  365.         }
  366.         if (!oneSuccess) {
  367.             return false;
  368.         }
  369.         return sendShortMessageData(message);
  370.     }

  371.     /**
  372.      * Sets the sender of a message using the SMTP MAIL command, specifying a reverse relay path. The sender must be set first before any recipients may be
  373.      * specified, otherwise the mail server will reject your commands.
  374.      *
  375.      * @param path The reverse relay path pointing back to the sender.
  376.      * @return True if successfully completed, false if not.
  377.      * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
  378.      *                                       causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
  379.      *                                       independently as itself.
  380.      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
  381.      */
  382.     public boolean setSender(final RelayPath path) throws IOException {
  383.         return SMTPReply.isPositiveCompletion(mail(path.toString()));
  384.     }

  385.     /**
  386.      * Sets the sender of a message using the SMTP MAIL command, specifying the sender's email address. The sender must be set first before any recipients may
  387.      * be specified, otherwise the mail server will reject your commands.
  388.      *
  389.      * @param address The sender's email address.
  390.      * @return True if successfully completed, false if not.
  391.      * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
  392.      *                                       causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
  393.      *                                       independently as itself.
  394.      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
  395.      */
  396.     public boolean setSender(final String address) throws IOException {
  397.         return SMTPReply.isPositiveCompletion(mail("<" + address + ">"));
  398.     }

  399.     /**
  400.      * Verifies that a user or email address is valid, i.e., that mail can be delivered to that mailbox on the server.
  401.      *
  402.      * @param user The user name or email address to validate.
  403.      * @return True if the user name is valid, false if not.
  404.      * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
  405.      *                                       causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
  406.      *                                       independently as itself.
  407.      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
  408.      */
  409.     public boolean verify(final String user) throws IOException {
  410.         final int result = vrfy(user);
  411.         return result == SMTPReply.ACTION_OK || result == SMTPReply.USER_NOT_LOCAL_WILL_FORWARD;
  412.     }

  413. }