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 }