View Javadoc
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  
18  package org.apache.commons.net.smtp;
19  
20  import java.io.IOException;
21  import java.io.Writer;
22  import java.net.InetAddress;
23  
24  import org.apache.commons.net.io.DotTerminatedMessageWriter;
25  
26  /**
27   * SMTPClient encapsulates all the functionality necessary to send files through an SMTP server. This class takes care of all low level details of interacting
28   * 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
29   * first connect to the server with {@link org.apache.commons.net.SocketClient#connect connect } before doing anything, and finally
30   * {@link org.apache.commons.net.SocketClient#disconnect disconnect } after you're completely finished interacting with the server. Then you need to check the
31   * SMTP reply code to see if the connection was successful. For example:
32   *
33   * <pre>
34   *    try {
35   *      int reply;
36   *      client.connect("mail.foobar.com");
37   *      System.out.print(client.getReplyString());
38   *
39   *      // After connection attempt, you should check the reply code to verify
40   *      // success.
41   *      reply = client.getReplyCode();
42   *
43   *      if (!SMTPReply.isPositiveCompletion(reply)) {
44   *        client.disconnect();
45   *        System.err.println("SMTP server refused connection.");
46   *        System.exit(1);
47   *      }
48   *
49   *      // Do useful stuff here.
50   *      ...
51   *    } catch (IOException e) {
52   *      if (client.isConnected()) {
53   *        try {
54   *          client.disconnect();
55   *        } catch (IOException f) {
56   *          // do nothing
57   *        }
58   *      }
59   *      System.err.println("Could not connect to server.");
60   *      e.printStackTrace();
61   *      System.exit(1);
62   *    }
63   * </pre>
64   * <p>
65   * 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
66   * 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
67   * 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
68   * 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
69   * 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.
70   * </p>
71   * <p>
72   * 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
73   * SMTP server connection closing when it receives a {@link org.apache.commons.net.smtp.SMTPReply#SERVICE_NOT_AVAILABLE SMTPReply.SERVICE_NOT_AVAILABLE }
74   * response to a command. When that occurs, the method encountering that reply will throw an {@link org.apache.commons.net.smtp.SMTPConnectionClosedException} .
75   * <code>SMTPConnectionClosedException</code> is a subclass of <code> IOException </code> and therefore need not be caught separately, but if you are going to
76   * catch it separately, its catch block must appear before the more general <code> IOException </code> catch block. When you encounter an
77   * {@link org.apache.commons.net.smtp.SMTPConnectionClosedException} , you must disconnect the connection with {@link #disconnect disconnect() } to properly
78   * clean up the system resources used by SMTPClient. Before disconnecting, you may check the last reply code and text with
79   * {@link org.apache.commons.net.smtp.SMTP#getReplyCode getReplyCode }, {@link org.apache.commons.net.smtp.SMTP#getReplyString getReplyString }, and
80   * {@link org.apache.commons.net.smtp.SMTP#getReplyStrings getReplyStrings}.
81   * </p>
82   * <p>
83   * 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
84   * {@link org.apache.commons.net.MalformedServerReplyException} , which is a subclass of IOException. A MalformedServerReplyException will be thrown when the
85   * 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
86   * lenient as possible.
87   * </p>
88   *
89   * @see SMTP
90   * @see SimpleSMTPHeader
91   * @see RelayPath
92   * @see SMTPConnectionClosedException
93   * @see org.apache.commons.net.MalformedServerReplyException
94   */
95  public class SMTPClient extends SMTP {
96  
97      /**
98       * Default SMTPClient constructor. Creates a new SMTPClient instance.
99       */
100     public SMTPClient() {
101     }
102 
103     /**
104      * Overloaded constructor that takes an encoding specification
105      *
106      * @param encoding The encoding to use
107      * @since 2.0
108      */
109     public SMTPClient(final String encoding) {
110         super(encoding);
111     }
112 
113     /**
114      * Add 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
115      * specified, otherwise the mail server will reject your commands.
116      *
117      * @param path The forward relay path pointing to the recipient.
118      * @return True if successfully completed, false if not.
119      * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
120      *                                       causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
121      *                                       independently as itself.
122      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
123      */
124     public boolean addRecipient(final RelayPath path) throws IOException {
125         return SMTPReply.isPositiveCompletion(rcpt(path.toString()));
126     }
127 
128     /**
129      * Add 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
130      * specified, otherwise the mail server will reject your commands.
131      *
132      * @param address The recipient's email address.
133      * @return True if successfully completed, false if not.
134      * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
135      *                                       causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
136      *                                       independently as itself.
137      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
138      */
139     public boolean addRecipient(final String address) throws IOException {
140         return SMTPReply.isPositiveCompletion(rcpt("<" + address + ">"));
141     }
142 
143     /**
144      * At least one SMTPClient method ({@link #sendMessageData sendMessageData }) does not complete the entire sequence of SMTP commands to complete a
145      * transaction. These types of commands require some action by the programmer after the reception of a positive intermediate command. After the programmer's
146      * 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.
147      * <p>
148      * For example,
149      * </p>
150      * <pre>
151      * writer = client.sendMessageData();
152      * if (writer == null) // failure
153      *     return false;
154      * header = new SimpleSMTPHeader("foobar@foo.com", "foo@foobar.com", "Re: Foo");
155      * writer.write(header.toString());
156      * writer.write("This is just a test");
157      * writer.close();
158      * if (!client.completePendingCommand()) // failure
159      *     return false;
160      * </pre>
161      *
162      * @return True if successfully completed, false if not.
163      * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
164      *                                       causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
165      *                                       independently as itself.
166      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
167      */
168     public boolean completePendingCommand() throws IOException {
169         return SMTPReply.isPositiveCompletion(getReply());
170     }
171 
172     /**
173      * Fetches the system help information from the server and returns the full string.
174      *
175      * @return The system help string obtained from the server. null if the information could not be obtained.
176      * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
177      *                                       causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
178      *                                       independently as itself.
179      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
180      */
181     public String listHelp() throws IOException {
182         if (SMTPReply.isPositiveCompletion(help())) {
183             return getReplyString();
184         }
185         return null;
186     }
187 
188     /**
189      * Fetches the help information for a given command from the server and returns the full string.
190      *
191      * @param command The command on which to ask for help.
192      * @return The command help string obtained from the server. null if the information could not be obtained.
193      * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
194      *                                       causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
195      *                                       independently as itself.
196      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
197      */
198     public String listHelp(final String command) throws IOException {
199         if (SMTPReply.isPositiveCompletion(help(command))) {
200             return getReplyString();
201         }
202         return null;
203     }
204 
205     /**
206      * Login to the SMTP server by sending the {@code HELO} command with the client hostname as an argument.
207      * Before performing any mail commands, you must first log in.
208      *
209      * @return True if successfully completed, false if not.
210      * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
211      *                                       causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
212      *                                       independently as itself.
213      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
214      */
215     public boolean login() throws IOException {
216         final String name;
217         final InetAddress host;
218 
219         host = getLocalAddress();
220         name = host.getHostName();
221 
222         if (name == null) {
223             return false;
224         }
225 
226         return SMTPReply.isPositiveCompletion(helo(name));
227     }
228 
229     /**
230      * Login to the SMTP server by sending the {@code HELO} command with the given hostname as an argument.
231      * Before performing any mail commands, you must first log in.
232      *
233      * @param hostname The hostname with which to greet the SMTP server.
234      * @return True if successfully completed, false if not.
235      * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
236      *                                       causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
237      *                                       independently as itself.
238      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
239      */
240     public boolean login(final String hostname) throws IOException {
241         return SMTPReply.isPositiveCompletion(helo(hostname));
242     }
243 
244     /**
245      * Logout of the SMTP server by sending the QUIT command.
246      *
247      * @return True if successfully completed, false if not.
248      * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
249      *                                       causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
250      *                                       independently as itself.
251      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
252      */
253     public boolean logout() throws IOException {
254         return SMTPReply.isPositiveCompletion(quit());
255     }
256 
257     /**
258      * Aborts the current mail transaction, resetting all server stored sender, recipient, and mail data, cleaning all buffers and tables.
259      *
260      * @return True if successfully completed, false if not.
261      * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
262      *                                       causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
263      *                                       independently as itself.
264      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
265      */
266     public boolean reset() throws IOException {
267         return SMTPReply.isPositiveCompletion(rset());
268     }
269 
270     /**
271      * Send the SMTP DATA command in preparation to send an email message. This method returns a DotTerminatedMessageWriter instance to which the message can be
272      * written. Null is returned if the DATA command fails.
273      * <p>
274      * 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.
275      * The SMTP protocol uses the same stream for issuing commands as it does for returning results. Therefore, the returned Writer actually writes directly to
276      * 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
277      * properly.
278      * </p>
279      * <p>
280      * You can use the provided {@link org.apache.commons.net.smtp.SimpleSMTPHeader} class to construct a bare minimum header. To construct more complicated
281      * 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.
282      * 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
283      * about is writing the header and the message.
284      * </p>
285      * <p>
286      * Upon closing the returned Writer, you need to call {@link #completePendingCommand completePendingCommand() } to finalize the transaction and verify its
287      * success or failure from the server reply.
288      * </p>
289      *
290      * @return A DotTerminatedMessageWriter to which the message (including header) can be written. Returns null if the command fails.
291      * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
292      *                                       causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
293      *                                       independently as itself.
294      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
295      * @see #sendShortMessageData(String)
296      */
297     public Writer sendMessageData() throws IOException {
298         if (!SMTPReply.isPositiveIntermediate(data())) {
299             return null;
300         }
301 
302         return new DotTerminatedMessageWriter(writer);
303     }
304 
305     /**
306      * Sends a NOOP command to the SMTP server. This is useful for preventing server timeouts.
307      *
308      * @return True if successfully completed, false if not.
309      * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
310      *                                       causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
311      *                                       independently as itself.
312      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
313      */
314     public boolean sendNoOp() throws IOException {
315         return SMTPReply.isPositiveCompletion(noop());
316     }
317 
318     /**
319      * A convenience method for sending short messages. This method fetches the Writer returned by {@link #sendMessageData sendMessageData() } and writes the
320      * specified String to it. After writing the message, this method calls {@link #completePendingCommand completePendingCommand() } to finalize the
321      * transaction and returns its success or failure.
322      *
323      * @param message The short email message to send. This must include the headers and the body, but not the trailing "."
324      * @return True if successfully completed, false if not.
325      * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
326      *                                       causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
327      *                                       independently as itself.
328      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
329      */
330     public boolean sendShortMessageData(final String message) throws IOException {
331         try (final Writer writer = sendMessageData()) {
332 
333             if (writer == null) {
334                 return false;
335             }
336 
337             writer.write(message);
338         }
339 
340         return completePendingCommand();
341     }
342 
343     /**
344      * A convenience method for a sending short email without having to explicitly set the sender and recipient(s). This method sets the sender and recipient
345      * using {@link #setSender setSender } and {@link #addRecipient addRecipient }, and then sends the message using {@link #sendShortMessageData
346      * sendShortMessageData }.
347      *
348      * @param sender    The email address of the sender.
349      * @param recipient The email address of the recipient.
350      * @param message   The short email message to send. This must include the headers and the body, but not the trailing "."
351      * @return True if successfully completed, false if not.
352      * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
353      *                                       causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
354      *                                       independently as itself.
355      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
356      */
357     public boolean sendSimpleMessage(final String sender, final String recipient, final String message) throws IOException {
358         if (!setSender(sender)) {
359             return false;
360         }
361 
362         if (!addRecipient(recipient)) {
363             return false;
364         }
365 
366         return sendShortMessageData(message);
367     }
368 
369     /**
370      * A convenience method for a sending short email without having to explicitly set the sender and recipient(s). This method sets the sender and recipients
371      * using {@link #setSender(String) setSender} and {@link #addRecipient(String) addRecipient}, and then sends the message using
372      * {@link #sendShortMessageData(String) sendShortMessageData}.
373      * <p>
374      * Note that the method ignores failures when calling {@link #addRecipient(String) addRecipient} so long as at least one call succeeds. If no recipients can
375      * be successfully added then the method will fail (and does not attempt to send the message)
376      * </p>
377      *
378      * @param sender     The email address of the sender.
379      * @param recipients An array of recipient email addresses.
380      * @param message    The short email message to send. This must include the headers and the body, but not the trailing "."
381      * @return True if successfully completed, false if not.
382      * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
383      *                                       causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
384      *                                       independently as itself.
385      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
386      */
387     public boolean sendSimpleMessage(final String sender, final String[] recipients, final String message) throws IOException {
388         boolean oneSuccess = false;
389         int count;
390 
391         if (!setSender(sender)) {
392             return false;
393         }
394 
395         for (count = 0; count < recipients.length; count++) {
396             if (addRecipient(recipients[count])) {
397                 oneSuccess = true;
398             }
399         }
400 
401         if (!oneSuccess) {
402             return false;
403         }
404 
405         return sendShortMessageData(message);
406     }
407 
408     /**
409      * Set 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
410      * specified, otherwise the mail server will reject your commands.
411      *
412      * @param path The reverse relay path pointing back to the sender.
413      * @return True if successfully completed, false if not.
414      * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
415      *                                       causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
416      *                                       independently as itself.
417      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
418      */
419     public boolean setSender(final RelayPath path) throws IOException {
420         return SMTPReply.isPositiveCompletion(mail(path.toString()));
421     }
422 
423     /**
424      * Set 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 be
425      * specified, otherwise the mail server will reject your commands.
426      *
427      * @param address The sender's email address.
428      * @return True if successfully completed, false if not.
429      * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
430      *                                       causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
431      *                                       independently as itself.
432      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
433      */
434     public boolean setSender(final String address) throws IOException {
435         return SMTPReply.isPositiveCompletion(mail("<" + address + ">"));
436     }
437 
438     /**
439      * Verify that a user or email address is valid, i.e., that mail can be delivered to that mailbox on the server.
440      *
441      * @param user The user name or email address to validate.
442      * @return True if the user name is valid, false if not.
443      * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
444      *                                       causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
445      *                                       independently as itself.
446      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
447      */
448     public boolean verify(final String user) throws IOException {
449         final int result;
450 
451         result = vrfy(user);
452 
453         return result == SMTPReply.ACTION_OK || result == SMTPReply.USER_NOT_LOCAL_WILL_FORWARD;
454     }
455 
456 }