001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * https://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.commons.net.smtp; 019 020import java.io.BufferedReader; 021import java.io.BufferedWriter; 022import java.io.IOException; 023import java.io.InputStreamReader; 024import java.io.OutputStreamWriter; 025import java.nio.charset.StandardCharsets; 026import java.util.ArrayList; 027 028import org.apache.commons.net.MalformedServerReplyException; 029import org.apache.commons.net.ProtocolCommandSupport; 030import org.apache.commons.net.SocketClient; 031import org.apache.commons.net.io.CRLFLineReader; 032import org.apache.commons.net.util.NetConstants; 033 034/** 035 * SMTP provides the basic the functionality necessary to implement your own SMTP client. To derive the full benefits of the SMTP class requires some knowledge 036 * of the FTP protocol defined in RFC 821. However, there is no reason why you should have to use the SMTP class. The 037 * {@link org.apache.commons.net.smtp.SMTPClient} class, derived from SMTP, implements all the functionality required of an SMTP client. The SMTP class is made 038 * public to provide access to various SMTP constants and to make it easier for adventurous programmers (or those with special needs) to interact with the SMTP 039 * protocol and implement their own clients. A set of methods with names corresponding to the SMTP command names are provided to facilitate this interaction. 040 * <p> 041 * You should keep in mind that the SMTP server may choose to prematurely close a connection for various reasons. The SMTP class will detect a premature SMTP 042 * server connection closing when it receives a {@link org.apache.commons.net.smtp.SMTPReply#SERVICE_NOT_AVAILABLE SMTPReply.SERVICE_NOT_AVAILABLE} response to 043 * a command. When that occurs, the SMTP class method encountering that reply will throw an {@link org.apache.commons.net.smtp.SMTPConnectionClosedException}. 044 * {@code SMTPConnectionClosedException} is a subclass of {@code IOException} and therefore need not be caught separately, but if you are going to 045 * catch it separately, its catch block must appear before the more general {@code IOException} catch block. When you encounter an 046 * {@link org.apache.commons.net.smtp.SMTPConnectionClosedException} , you must disconnect the connection with 047 * {@link org.apache.commons.net.SocketClient#disconnect disconnect()} to properly clean up the system resources used by SMTP. Before disconnecting, you may 048 * check the last reply code and text with {@link #getReplyCode getReplyCode}, {@link #getReplyString getReplyString}, and {@link #getReplyStrings 049 * getReplyStrings}. 050 * </p> 051 * <p> 052 * 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 053 * {@link org.apache.commons.net.MalformedServerReplyException} , which is a subclass of IOException. A MalformedServerReplyException will be thrown when the 054 * 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 055 * lenient as possible. 056 * </p> 057 * 058 * @see SMTPClient 059 * @see SMTPConnectionClosedException 060 * @see org.apache.commons.net.MalformedServerReplyException 061 */ 062 063public class SMTP extends SocketClient { 064 065 /** The default SMTP port (25). */ 066 public static final int DEFAULT_PORT = 25; 067 068 /** 069 * We have to ensure that the protocol communication is in ASCII, but we use ISO-8859-1 just in case 8-bit characters cross the wire. 070 */ 071 private static final String DEFAULT_ENCODING = StandardCharsets.ISO_8859_1.name(); 072 073 /** 074 * The encoding to use (user-settable). 075 * 076 * @since 3.1 (changed from private to protected) 077 */ 078 protected final String encoding; 079 080 /** 081 * A ProtocolCommandSupport object used to manage the registering of ProtocolCommandListeners and te firing of ProtocolCommandEvents. 082 */ 083 protected ProtocolCommandSupport _commandSupport_; 084 085 BufferedReader reader; 086 BufferedWriter writer; 087 088 private int replyCode; 089 private final ArrayList<String> replyLines; 090 private boolean newReplyString; 091 private String replyString; 092 093 /** 094 * The default SMTP constructor. Sets the default port to {@code DEFAULT_PORT} and initializes internal data structures for saving SMTP reply 095 * information. 096 */ 097 public SMTP() { 098 this(DEFAULT_ENCODING); 099 } 100 101 /** 102 * Overloaded constructor where the user may specify a default encoding. 103 * 104 * @param encoding the encoding to use 105 * @since 2.0 106 */ 107 public SMTP(final String encoding) { 108 setDefaultPort(DEFAULT_PORT); 109 replyLines = new ArrayList<>(); 110 newReplyString = false; 111 replyString = null; 112 _commandSupport_ = new ProtocolCommandSupport(this); 113 this.encoding = encoding; 114 } 115 116 /** Initiates control connections and gets initial reply. */ 117 @Override 118 protected void _connectAction_() throws IOException { 119 super._connectAction_(); 120 reader = new CRLFLineReader(new InputStreamReader(_input_, encoding)); 121 writer = new BufferedWriter(new OutputStreamWriter(_output_, encoding)); 122 getReply(); 123 } 124 125 /** 126 * A convenience method to send the SMTP DATA command to the server, receive the reply, and return the reply code. 127 * 128 * @return The reply code received from the server. 129 * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason 130 * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or 131 * independently as itself. 132 * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. 133 */ 134 public int data() throws IOException { 135 return sendCommand(SMTPCommand.DATA); 136 } 137 138 /** 139 * Closes the connection to the SMTP server and sets to null some internal data so that the memory may be reclaimed by the garbage collector. The reply text 140 * and code information from the last command is voided so that the memory it used may be reclaimed. 141 * 142 * @throws IOException If an error occurs while disconnecting. 143 */ 144 @Override 145 public void disconnect() throws IOException { 146 super.disconnect(); 147 reader = null; 148 writer = null; 149 replyString = null; 150 replyLines.clear(); 151 newReplyString = false; 152 } 153 154 /** 155 * A convenience method to send the SMTP VRFY command to the server, receive the reply, and return the reply code. 156 * 157 * @param name The name to expand. 158 * @return The reply code received from the server. 159 * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason 160 * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or 161 * independently as itself. 162 * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. 163 */ 164 public int expn(final String name) throws IOException { 165 return sendCommand(SMTPCommand.EXPN, name); 166 } 167 168 /** 169 * Provide command support to super-class 170 */ 171 @Override 172 protected ProtocolCommandSupport getCommandSupport() { 173 return _commandSupport_; 174 } 175 176 /** 177 * Gets a reply from the SMTP server and returns the integer reply code. After calling this method, the actual reply text can be accessed from either 178 * calling {@link #getReplyString getReplyString} or {@link #getReplyStrings getReplyStrings}. Only use this method if you are implementing your own SMTP 179 * client or if you need to fetch a secondary response from the SMTP server. 180 * 181 * @return The integer value of the reply code of the fetched SMTP reply. 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 receiving the server reply. 186 */ 187 public int getReply() throws IOException { 188 final int length; 189 190 newReplyString = true; 191 replyLines.clear(); 192 193 String line = reader.readLine(); 194 195 if (line == null) { 196 throw new SMTPConnectionClosedException("Connection closed without indication."); 197 } 198 199 // In case we run into an anomaly we don't want fatal index exceptions 200 // to be thrown. 201 length = line.length(); 202 if (length < 3) { 203 throw new MalformedServerReplyException("Truncated server reply: " + line); 204 } 205 206 try { 207 final String code = line.substring(0, 3); 208 replyCode = Integer.parseInt(code); 209 } catch (final NumberFormatException e) { 210 throw new MalformedServerReplyException("Could not parse response code.\nServer Reply: " + line); 211 } 212 213 replyLines.add(line); 214 215 // Get extra lines if message continues. 216 if (length > 3 && line.charAt(3) == '-') { 217 do { 218 line = reader.readLine(); 219 220 if (line == null) { 221 throw new SMTPConnectionClosedException("Connection closed without indication."); 222 } 223 224 replyLines.add(line); 225 226 // The length() check handles problems that could arise from readLine() 227 // returning too soon after encountering a naked CR or some other 228 // anomaly. 229 } while (!(line.length() >= 4 && line.charAt(3) != '-' && Character.isDigit(line.charAt(0)))); 230 // This is too strong a condition because a non-conforming server 231 // could screw things up like ftp.funet.fi does for FTP 232 // line.startsWith(code))); 233 } 234 235 fireReplyReceived(replyCode, getReplyString()); 236 237 if (replyCode == SMTPReply.SERVICE_NOT_AVAILABLE) { 238 throw new SMTPConnectionClosedException("SMTP response 421 received. Server closed connection."); 239 } 240 return replyCode; 241 } 242 243 /** 244 * Gets the integer value of the reply code of the last SMTP reply. You will usually only use this method after you connect to the SMTP server to check 245 * that the connection was successful since {@code connect} is of type void. 246 * 247 * @return The integer value of the reply code of the last SMTP reply. 248 */ 249 public int getReplyCode() { 250 return replyCode; 251 } 252 253 /** 254 * Gets the entire text of the last SMTP server response exactly as it was received, including all end of line markers in NETASCII format. 255 * 256 * @return The entire text from the last SMTP response as a String. 257 */ 258 public String getReplyString() { 259 final StringBuilder buffer; 260 261 if (!newReplyString) { 262 return replyString; 263 } 264 265 buffer = new StringBuilder(); 266 267 for (final String line : replyLines) { 268 buffer.append(line); 269 buffer.append(NETASCII_EOL); 270 } 271 272 newReplyString = false; 273 274 replyString = buffer.toString(); 275 return replyString; 276 } 277 278 /** 279 * Gets the lines of text from the last SMTP server response as an array of strings, one entry per line. The end of line markers of each are stripped 280 * from each line. 281 * 282 * @return The lines of text from the last SMTP response as an array. 283 */ 284 public String[] getReplyStrings() { 285 return replyLines.toArray(NetConstants.EMPTY_STRING_ARRAY); 286 } 287 288 /** 289 * A convenience method to send the SMTP HELO command to the server, receive the reply, and return the reply code. 290 * 291 * @param hostname The hostname of the sender. 292 * @return The reply code received from the server. 293 * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason 294 * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or 295 * independently as itself. 296 * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. 297 */ 298 public int helo(final String hostname) throws IOException { 299 return sendCommand(SMTPCommand.HELO, hostname); 300 } 301 302 /** 303 * A convenience method to send the SMTP HELP command to the server, receive the reply, and return the reply code. 304 * 305 * @return The reply code received from the server. 306 * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason 307 * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or 308 * independently as itself. 309 * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. 310 */ 311 public int help() throws IOException { 312 return sendCommand(SMTPCommand.HELP); 313 } 314 315 /** 316 * A convenience method to send the SMTP HELP command to the server, receive the reply, and return the reply code. 317 * 318 * @param command The command name on which to request help. 319 * @return The reply code received from the server. 320 * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason 321 * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or 322 * independently as itself. 323 * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. 324 */ 325 public int help(final String command) throws IOException { 326 return sendCommand(SMTPCommand.HELP, command); 327 } 328 329 /** 330 * A convenience method to send the SMTP MAIL command to the server, receive the reply, and return the reply code. 331 * 332 * @param reversePath The reverse path. 333 * @return The reply code received from the server. 334 * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason 335 * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or 336 * independently as itself. 337 * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. 338 */ 339 public int mail(final String reversePath) throws IOException { 340 return sendCommand(SMTPCommand.MAIL, reversePath, false); 341 } 342 343 /** 344 * A convenience method to send the SMTP NOOP command to the server, receive the reply, and return the reply code. 345 * 346 * @return The reply code received from the server. 347 * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason 348 * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or 349 * independently as itself. 350 * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. 351 */ 352 public int noop() throws IOException { 353 return sendCommand(SMTPCommand.NOOP); 354 } 355 356 /** 357 * A convenience method to send the SMTP QUIT command to the server, receive the reply, and return the reply code. 358 * 359 * @return The reply code received from the server. 360 * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason 361 * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or 362 * independently as itself. 363 * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. 364 */ 365 public int quit() throws IOException { 366 return sendCommand(SMTPCommand.QUIT); 367 } 368 369 /** 370 * A convenience method to send the SMTP RCPT command to the server, receive the reply, and return the reply code. 371 * 372 * @param forwardPath The forward path. 373 * @return The reply code received from the server. 374 * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason 375 * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or 376 * independently as itself. 377 * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. 378 */ 379 public int rcpt(final String forwardPath) throws IOException { 380 return sendCommand(SMTPCommand.RCPT, forwardPath, false); 381 } 382 383 /** 384 * Removes a ProtocolCommandListener. 385 * 386 * Delegates this incorrectly named method - removeProtocolCommandistener (note the missing "L")- to the correct method 387 * {@link SocketClient#removeProtocolCommandListener} 388 * 389 * @param listener The ProtocolCommandListener to remove 390 */ 391 public void removeProtocolCommandistener(final org.apache.commons.net.ProtocolCommandListener listener) { 392 removeProtocolCommandListener(listener); 393 } 394 395 /** 396 * A convenience method to send the SMTP RSET command to the server, receive the reply, and return the reply code. 397 * 398 * @return The reply code received from the server. 399 * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason 400 * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or 401 * independently as itself. 402 * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. 403 */ 404 public int rset() throws IOException { 405 return sendCommand(SMTPCommand.RSET); 406 } 407 408 /** 409 * A convenience method to send the SMTP SAML command to the server, receive the reply, and return the reply code. 410 * 411 * @param reversePath The reverse path. 412 * @return The reply code received from the server. 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 the command or receiving the server reply. 417 */ 418 public int saml(final String reversePath) throws IOException { 419 return sendCommand(SMTPCommand.SAML, reversePath); 420 } 421 422 /** 423 * A convenience method to send the SMTP SEND command to the server, receive the reply, and return the reply code. 424 * 425 * @param reversePath The reverse path. 426 * @return The reply code received from the server. 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 the command or receiving the server reply. 431 */ 432 public int send(final String reversePath) throws IOException { 433 return sendCommand(SMTPCommand.SEND, reversePath); 434 } 435 436 /** 437 * Sends an SMTP command with no arguments to the server, waits for a reply and returns the numerical response code. After invocation, for more detailed 438 * information, the actual reply text can be accessed by calling {@link #getReplyString getReplyString} or {@link #getReplyStrings getReplyStrings}. 439 * 440 * @param command The SMTPCommand constant corresponding to the SMTP command to send. 441 * @return The integer value of the SMTP reply code returned by the server in response to the command. 442 * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason 443 * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or 444 * independently as itself. 445 * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. 446 */ 447 public int sendCommand(final int command) throws IOException { 448 return sendCommand(command, null); 449 } 450 451 /** 452 * Sends an SMTP command to the server, waits for a reply and returns the numerical response code. After invocation, for more detailed information, the 453 * actual reply text can be accessed by calling {@link #getReplyString getReplyString} or {@link #getReplyStrings getReplyStrings}. 454 * 455 * @param command The SMTPCommand constant corresponding to the SMTP command to send. 456 * @param args The arguments to the SMTP command. If this parameter is set to null, then the command is sent with no argument. 457 * @return The integer value of the SMTP reply code returned by the server in response to the command. 458 * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason 459 * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or 460 * independently as itself. 461 * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. 462 */ 463 public int sendCommand(final int command, final String args) throws IOException { 464 return sendCommand(SMTPCommand.getCommand(command), args); 465 } 466 467 /** 468 * 469 * @param command the command to send (as an int defined in {@link SMTPCommand}) 470 * @param args the command arguments, may be {@code null} 471 * @param includeSpace if {@code true}, add a space between the command and its arguments 472 * @return the reply code 473 * @throws IOException 474 */ 475 private int sendCommand(final int command, final String args, final boolean includeSpace) throws IOException { 476 return sendCommand(SMTPCommand.getCommand(command), args, includeSpace); 477 } 478 479 /** 480 * Sends an SMTP command with no arguments to the server, waits for a reply and returns the numerical response code. After invocation, for more detailed 481 * information, the actual reply text can be accessed by calling {@link #getReplyString getReplyString} or {@link #getReplyStrings getReplyStrings}. 482 * 483 * @param command The text representation of the SMTP command to send. 484 * @return The integer value of the SMTP reply code returned by the server in response to the command. 485 * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason 486 * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or 487 * independently as itself. 488 * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. 489 */ 490 public int sendCommand(final String command) throws IOException { 491 return sendCommand(command, null); 492 } 493 494 /** 495 * Sends an SMTP command to the server, waits for a reply and returns the numerical response code. After invocation, for more detailed information, the 496 * actual reply text can be accessed by calling {@link #getReplyString getReplyString} or {@link #getReplyStrings getReplyStrings}. 497 * 498 * @param command The text representation of the SMTP command to send. 499 * @param args The arguments to the SMTP command. If this parameter is set to null, then the command is sent with no argument. 500 * @return The integer value of the SMTP reply code returned by the server in response to the command. 501 * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason 502 * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or 503 * independently as itself. 504 * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. 505 */ 506 public int sendCommand(final String command, final String args) throws IOException { 507 return sendCommand(command, args, true); 508 } 509 510 /** 511 * Send a command to the server. May also be used to send text data. 512 * 513 * @param command the command to send (as a plain String) 514 * @param args the command arguments, may be {@code null} 515 * @param includeSpace if {@code true}, add a space between the command and its arguments 516 * @return the reply code 517 * @throws IOException 518 */ 519 private int sendCommand(final String command, final String args, final boolean includeSpace) throws IOException { 520 final StringBuilder builder = new StringBuilder(command); 521 if (args != null) { 522 if (includeSpace) { 523 builder.append(' '); 524 } 525 builder.append(args); 526 } 527 builder.append(NETASCII_EOL); 528 final String message = builder.toString(); 529 writer.write(message); 530 writer.flush(); 531 fireCommandSent(command, message); 532 return getReply(); 533 } 534 535 /** 536 * A convenience method to send the SMTP SOML command to the server, receive the reply, and return the reply code. 537 * 538 * @param reversePath The reverse path. 539 * @return The reply code received from the server. 540 * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason 541 * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or 542 * independently as itself. 543 * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. 544 */ 545 public int soml(final String reversePath) throws IOException { 546 return sendCommand(SMTPCommand.SOML, reversePath); 547 } 548 549 /** 550 * A convenience method to send the SMTP TURN command to the server, receive the reply, and return the reply code. 551 * 552 * @return The reply code received from the server. 553 * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason 554 * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or 555 * independently as itself. 556 * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. 557 */ 558 public int turn() throws IOException { 559 return sendCommand(SMTPCommand.TURN); 560 } 561 562 /** 563 * A convenience method to send the SMTP VRFY command to the server, receive the reply, and return the reply code. 564 * 565 * @param user The user address to verify. 566 * @return The reply code received from the server. 567 * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason 568 * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or 569 * independently as itself. 570 * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. 571 */ 572 public int vrfy(final String user) throws IOException { 573 return sendCommand(SMTPCommand.VRFY, user); 574 } 575}