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 * https://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} is a subclass of {@code IOException} 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} 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 * 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
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 * 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
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 InetAddress host = getLocalAddress();
217 final String name = host.getHostName();
218 if (name == null) {
219 return false;
220 }
221 return SMTPReply.isPositiveCompletion(helo(name));
222 }
223
224 /**
225 * Login to the SMTP server by sending the {@code HELO} command with the given hostname as an argument.
226 * Before performing any mail commands, you must first log in.
227 *
228 * @param hostname The hostname with which to greet the SMTP server.
229 * @return True if successfully completed, false if not.
230 * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
231 * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
232 * independently as itself.
233 * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
234 */
235 public boolean login(final String hostname) throws IOException {
236 return SMTPReply.isPositiveCompletion(helo(hostname));
237 }
238
239 /**
240 * Logout of the SMTP server by sending the QUIT command.
241 *
242 * @return True if successfully completed, false if not.
243 * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
244 * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
245 * independently as itself.
246 * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
247 */
248 public boolean logout() throws IOException {
249 return SMTPReply.isPositiveCompletion(quit());
250 }
251
252 /**
253 * Aborts the current mail transaction, resetting all server stored sender, recipient, and mail data, cleaning all buffers and tables.
254 *
255 * @return True if successfully completed, false if not.
256 * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
257 * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
258 * independently as itself.
259 * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
260 */
261 public boolean reset() throws IOException {
262 return SMTPReply.isPositiveCompletion(rset());
263 }
264
265 /**
266 * Sends the SMTP DATA command in preparation to send an email message. This method returns a DotTerminatedMessageWriter instance to which the message can
267 * be written. Null is returned if the DATA command fails.
268 * <p>
269 * 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.
270 * The SMTP protocol uses the same stream for issuing commands as it does for returning results. Therefore, the returned Writer actually writes directly to
271 * 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
272 * properly.
273 * </p>
274 * <p>
275 * You can use the provided {@link org.apache.commons.net.smtp.SimpleSMTPHeader} class to construct a bare minimum header. To construct more complicated
276 * 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.
277 * 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
278 * about is writing the header and the message.
279 * </p>
280 * <p>
281 * Upon closing the returned Writer, you need to call {@link #completePendingCommand completePendingCommand()} to finalize the transaction and verify its
282 * success or failure from the server reply.
283 * </p>
284 *
285 * @return A DotTerminatedMessageWriter to which the message (including header) can be written. Returns null if the command fails.
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 * @see #sendShortMessageData(String)
291 */
292 public Writer sendMessageData() throws IOException {
293 if (!SMTPReply.isPositiveIntermediate(data())) {
294 return null;
295 }
296 return new DotTerminatedMessageWriter(writer);
297 }
298
299 /**
300 * Sends a NOOP command to the SMTP server. This is useful for preventing server timeouts.
301 *
302 * @return True if successfully completed, false if not.
303 * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
304 * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
305 * independently as itself.
306 * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
307 */
308 public boolean sendNoOp() throws IOException {
309 return SMTPReply.isPositiveCompletion(noop());
310 }
311
312 /**
313 * Sends a short messages. This method fetches the Writer returned by {@link #sendMessageData sendMessageData()} and writes the
314 * specified String to it. After writing the message, this method calls {@link #completePendingCommand completePendingCommand()} to finalize the
315 * transaction and returns its success or failure.
316 *
317 * @param message The short email message to send. This must include the headers and the body, but not the trailing "."
318 * @return True if successfully completed, false if not.
319 * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
320 * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
321 * independently as itself.
322 * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
323 */
324 public boolean sendShortMessageData(final String message) throws IOException {
325 try (Writer writer = sendMessageData()) {
326 if (writer == null) {
327 return false;
328 }
329 writer.write(message);
330 }
331 return completePendingCommand();
332 }
333
334 /**
335 * Sends a short email without having to explicitly set the sender and recipient(s). This method sets the sender and recipient
336 * using {@link #setSender setSender} and {@link #addRecipient addRecipient}, and then sends the message using {@link #sendShortMessageData
337 * sendShortMessageData }.
338 *
339 * @param sender The email address of the sender.
340 * @param recipient The email address of the recipient.
341 * @param message The short email message to send. This must include the headers and the body, but not the trailing "."
342 * @return True if successfully completed, false if not.
343 * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
344 * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
345 * independently as itself.
346 * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
347 */
348 public boolean sendSimpleMessage(final String sender, final String recipient, final String message) throws IOException {
349 if (!setSender(sender)) {
350 return false;
351 }
352 if (!addRecipient(recipient)) {
353 return false;
354 }
355 return sendShortMessageData(message);
356 }
357
358 /**
359 * Sends a short email without having to explicitly set the sender and recipient(s). This method sets the sender and recipients
360 * using {@link #setSender(String) setSender} and {@link #addRecipient(String) addRecipient}, and then sends the message using
361 * {@link #sendShortMessageData(String) sendShortMessageData}.
362 * <p>
363 * Note that the method ignores failures when calling {@link #addRecipient(String) addRecipient} so long as at least one call succeeds. If no recipients can
364 * be successfully added then the method will fail (and does not attempt to send the message)
365 * </p>
366 *
367 * @param sender The email address of the sender.
368 * @param recipients An array of recipient email addresses.
369 * @param message The short email message to send. This must include the headers and the body, but not the trailing "."
370 * @return True if successfully completed, false if not.
371 * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
372 * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
373 * independently as itself.
374 * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
375 */
376 public boolean sendSimpleMessage(final String sender, final String[] recipients, final String message) throws IOException {
377 if (!setSender(sender)) {
378 return false;
379 }
380 boolean oneSuccess = false;
381 for (final String recipient : recipients) {
382 if (addRecipient(recipient)) {
383 oneSuccess = true;
384 }
385 }
386 if (!oneSuccess) {
387 return false;
388 }
389 return sendShortMessageData(message);
390 }
391
392 /**
393 * 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
394 * specified, otherwise the mail server will reject your commands.
395 *
396 * @param path The reverse relay path pointing back to the sender.
397 * @return True if successfully completed, false if not.
398 * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
399 * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
400 * independently as itself.
401 * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
402 */
403 public boolean setSender(final RelayPath path) throws IOException {
404 return SMTPReply.isPositiveCompletion(mail(path.toString()));
405 }
406
407 /**
408 * 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
409 * be specified, otherwise the mail server will reject your commands.
410 *
411 * @param address The sender's email address.
412 * @return True if successfully completed, false if not.
413 * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
414 * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
415 * independently as itself.
416 * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
417 */
418 public boolean setSender(final String address) throws IOException {
419 return SMTPReply.isPositiveCompletion(mail("<" + address + ">"));
420 }
421
422 /**
423 * Verifies that a user or email address is valid, i.e., that mail can be delivered to that mailbox on the server.
424 *
425 * @param user The user name or email address to validate.
426 * @return True if the user name is valid, false if not.
427 * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
428 * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
429 * independently as itself.
430 * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
431 */
432 public boolean verify(final String user) throws IOException {
433 final int result = vrfy(user);
434 return result == SMTPReply.ACTION_OK || result == SMTPReply.USER_NOT_LOCAL_WILL_FORWARD;
435 }
436
437 }