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.BufferedReader;
21 import java.io.BufferedWriter;
22 import java.io.IOException;
23 import java.io.InputStreamReader;
24 import java.io.OutputStreamWriter;
25 import java.nio.charset.StandardCharsets;
26 import java.util.ArrayList;
27
28 import org.apache.commons.net.MalformedServerReplyException;
29 import org.apache.commons.net.ProtocolCommandSupport;
30 import org.apache.commons.net.SocketClient;
31 import org.apache.commons.net.io.CRLFLineReader;
32 import org.apache.commons.net.util.NetConstants;
33
34 /**
35 * 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
36 * of the FTP protocol defined in RFC 821. However, there is no reason why you should have to use the SMTP class. The
37 * {@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
38 * 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
39 * protocol and implement their own clients. A set of methods with names corresponding to the SMTP command names are provided to facilitate this interaction.
40 * <p>
41 * 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
42 * server connection closing when it receives a {@link org.apache.commons.net.smtp.SMTPReply#SERVICE_NOT_AVAILABLE SMTPReply.SERVICE_NOT_AVAILABLE} response to
43 * a command. When that occurs, the SMTP class method encountering that reply will throw an {@link org.apache.commons.net.smtp.SMTPConnectionClosedException}.
44 * {@code SMTPConnectionClosedException} is a subclass of {@code IOException} and therefore need not be caught separately, but if you are going to
45 * catch it separately, its catch block must appear before the more general {@code IOException} catch block. When you encounter an
46 * {@link org.apache.commons.net.smtp.SMTPConnectionClosedException} , you must disconnect the connection with
47 * {@link org.apache.commons.net.SocketClient#disconnect disconnect()} to properly clean up the system resources used by SMTP. Before disconnecting, you may
48 * check the last reply code and text with {@link #getReplyCode getReplyCode}, {@link #getReplyString getReplyString}, and {@link #getReplyStrings
49 * getReplyStrings}.
50 * </p>
51 * <p>
52 * 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
53 * {@link org.apache.commons.net.MalformedServerReplyException} , which is a subclass of IOException. A MalformedServerReplyException will be thrown when the
54 * 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
55 * lenient as possible.
56 * </p>
57 *
58 * @see SMTPClient
59 * @see SMTPConnectionClosedException
60 * @see org.apache.commons.net.MalformedServerReplyException
61 */
62
63 public class SMTP extends SocketClient {
64 /** The default SMTP port (25). */
65 public static final int DEFAULT_PORT = 25;
66
67 /**
68 * 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.
69 */
70 private static final String DEFAULT_ENCODING = StandardCharsets.ISO_8859_1.name();
71
72 /**
73 * The encoding to use (user-settable).
74 *
75 * @since 3.1 (changed from private to protected)
76 */
77 protected final String encoding;
78
79 /**
80 * A ProtocolCommandSupport object used to manage the registering of ProtocolCommandListeners and te firing of ProtocolCommandEvents.
81 */
82 protected ProtocolCommandSupport _commandSupport_;
83
84 BufferedReader reader;
85 BufferedWriter writer;
86
87 private int replyCode;
88 private final ArrayList<String> replyLines;
89 private boolean newReplyString;
90 private String replyString;
91
92 /**
93 * The default SMTP constructor. Sets the default port to {@code DEFAULT_PORT} and initializes internal data structures for saving SMTP reply
94 * information.
95 */
96 public SMTP() {
97 this(DEFAULT_ENCODING);
98 }
99
100 /**
101 * Overloaded constructor where the user may specify a default encoding.
102 *
103 * @param encoding the encoding to use
104 * @since 2.0
105 */
106 public SMTP(final String encoding) {
107 setDefaultPort(DEFAULT_PORT);
108 replyLines = new ArrayList<>();
109 newReplyString = false;
110 replyString = null;
111 _commandSupport_ = new ProtocolCommandSupport(this);
112 this.encoding = encoding;
113 }
114
115 /** Initiates control connections and gets initial reply. */
116 @Override
117 protected void _connectAction_() throws IOException {
118 super._connectAction_();
119 reader = new CRLFLineReader(new InputStreamReader(_input_, encoding));
120 writer = new BufferedWriter(new OutputStreamWriter(_output_, encoding));
121 getReply();
122 }
123
124 /**
125 * A convenience method to send the SMTP DATA command to the server, receive the reply, and return the reply code.
126 *
127 * @return The reply code received from the server.
128 * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
129 * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
130 * independently as itself.
131 * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply.
132 */
133 public int data() throws IOException {
134 return sendCommand(SMTPCommand.DATA);
135 }
136
137 /**
138 * 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
139 * and code information from the last command is voided so that the memory it used may be reclaimed.
140 *
141 * @throws IOException If an error occurs while disconnecting.
142 */
143 @Override
144 public void disconnect() throws IOException {
145 super.disconnect();
146 reader = null;
147 writer = null;
148 replyString = null;
149 replyLines.clear();
150 newReplyString = false;
151 }
152
153 /**
154 * A convenience method to send the SMTP VRFY command to the server, receive the reply, and return the reply code.
155 *
156 * @param name The name to expand.
157 * @return The reply code received from the server.
158 * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
159 * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
160 * independently as itself.
161 * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply.
162 */
163 public int expn(final String name) throws IOException {
164 return sendCommand(SMTPCommand.EXPN, name);
165 }
166
167 /**
168 * Provide command support to super-class
169 */
170 @Override
171 protected ProtocolCommandSupport getCommandSupport() {
172 return _commandSupport_;
173 }
174
175 /**
176 * 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
177 * calling {@link #getReplyString getReplyString} or {@link #getReplyStrings getReplyStrings}. Only use this method if you are implementing your own SMTP
178 * client or if you need to fetch a secondary response from the SMTP server.
179 *
180 * @return The integer value of the reply code of the fetched SMTP reply.
181 * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
182 * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
183 * independently as itself.
184 * @throws IOException If an I/O error occurs while receiving the server reply.
185 */
186 public int getReply() throws IOException {
187 final int length;
188
189 newReplyString = true;
190 replyLines.clear();
191
192 String line = reader.readLine();
193
194 if (line == null) {
195 throw new SMTPConnectionClosedException("Connection closed without indication.");
196 }
197
198 // In case we run into an anomaly we don't want fatal index exceptions
199 // to be thrown.
200 length = line.length();
201 if (length < 3) {
202 throw new MalformedServerReplyException("Truncated server reply: " + line);
203 }
204
205 try {
206 final String code = line.substring(0, 3);
207 replyCode = Integer.parseInt(code);
208 } catch (final NumberFormatException e) {
209 throw new MalformedServerReplyException("Could not parse response code.\nServer Reply: " + line);
210 }
211
212 replyLines.add(line);
213
214 // Get extra lines if message continues.
215 if (length > 3 && line.charAt(3) == '-') {
216 do {
217 line = reader.readLine();
218
219 if (line == null) {
220 throw new SMTPConnectionClosedException("Connection closed without indication.");
221 }
222
223 replyLines.add(line);
224
225 // The length() check handles problems that could arise from readLine()
226 // returning too soon after encountering a naked CR or some other
227 // anomaly.
228 } while (!(line.length() >= 4 && line.charAt(3) != '-' && Character.isDigit(line.charAt(0))));
229 // This is too strong a condition because a non-conforming server
230 // could screw things up like ftp.funet.fi does for FTP
231 // line.startsWith(code)));
232 }
233
234 fireReplyReceived(replyCode, getReplyString());
235
236 if (replyCode == SMTPReply.SERVICE_NOT_AVAILABLE) {
237 throw new SMTPConnectionClosedException("SMTP response 421 received. Server closed connection.");
238 }
239 return replyCode;
240 }
241
242 /**
243 * 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
244 * that the connection was successful since {@code connect} is of type void.
245 *
246 * @return The integer value of the reply code of the last SMTP reply.
247 */
248 public int getReplyCode() {
249 return replyCode;
250 }
251
252 /**
253 * Gets the entire text of the last SMTP server response exactly as it was received, including all end of line markers in NETASCII format.
254 *
255 * @return The entire text from the last SMTP response as a String.
256 */
257 public String getReplyString() {
258 final StringBuilder buffer;
259
260 if (!newReplyString) {
261 return replyString;
262 }
263
264 buffer = new StringBuilder();
265
266 for (final String line : replyLines) {
267 buffer.append(line);
268 buffer.append(NETASCII_EOL);
269 }
270
271 newReplyString = false;
272
273 replyString = buffer.toString();
274 return replyString;
275 }
276
277 /**
278 * 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
279 * from each line.
280 *
281 * @return The lines of text from the last SMTP response as an array.
282 */
283 public String[] getReplyStrings() {
284 return replyLines.toArray(NetConstants.EMPTY_STRING_ARRAY);
285 }
286
287 /**
288 * A convenience method to send the SMTP HELO command to the server, receive the reply, and return the reply code.
289 *
290 * @param hostname The hostname of the sender.
291 * @return The reply code received from the server.
292 * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
293 * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
294 * independently as itself.
295 * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply.
296 */
297 public int helo(final String hostname) throws IOException {
298 return sendCommand(SMTPCommand.HELO, hostname);
299 }
300
301 /**
302 * A convenience method to send the SMTP HELP command to the server, receive the reply, and return the reply code.
303 *
304 * @return The reply code received from the server.
305 * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
306 * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
307 * independently as itself.
308 * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply.
309 */
310 public int help() throws IOException {
311 return sendCommand(SMTPCommand.HELP);
312 }
313
314 /**
315 * A convenience method to send the SMTP HELP command to the server, receive the reply, and return the reply code.
316 *
317 * @param command The command name on which to request help.
318 * @return The reply code received from the server.
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 the command or receiving the server reply.
323 */
324 public int help(final String command) throws IOException {
325 return sendCommand(SMTPCommand.HELP, command);
326 }
327
328 /**
329 * A convenience method to send the SMTP MAIL command to the server, receive the reply, and return the reply code.
330 *
331 * @param reversePath The reverse path.
332 * @return The reply code received from the server.
333 * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
334 * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
335 * independently as itself.
336 * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply.
337 */
338 public int mail(final String reversePath) throws IOException {
339 return sendCommand(SMTPCommand.MAIL, reversePath, false);
340 }
341
342 /**
343 * A convenience method to send the SMTP NOOP command to the server, receive the reply, and return the reply code.
344 *
345 * @return The reply code received from the server.
346 * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
347 * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
348 * independently as itself.
349 * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply.
350 */
351 public int noop() throws IOException {
352 return sendCommand(SMTPCommand.NOOP);
353 }
354
355 /**
356 * A convenience method to send the SMTP QUIT command to the server, receive the reply, and return the reply code.
357 *
358 * @return The reply code received from the server.
359 * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
360 * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
361 * independently as itself.
362 * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply.
363 */
364 public int quit() throws IOException {
365 return sendCommand(SMTPCommand.QUIT);
366 }
367
368 /**
369 * A convenience method to send the SMTP RCPT command to the server, receive the reply, and return the reply code.
370 *
371 * @param forwardPath The forward path.
372 * @return The reply code received from the server.
373 * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
374 * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
375 * independently as itself.
376 * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply.
377 */
378 public int rcpt(final String forwardPath) throws IOException {
379 return sendCommand(SMTPCommand.RCPT, forwardPath, false);
380 }
381
382 /**
383 * Removes a ProtocolCommandListener.
384 *
385 * Delegates this incorrectly named method - removeProtocolCommandistener (note the missing "L")- to the correct method
386 * {@link SocketClient#removeProtocolCommandListener}
387 *
388 * @param listener The ProtocolCommandListener to remove
389 */
390 public void removeProtocolCommandistener(final org.apache.commons.net.ProtocolCommandListener listener) {
391 removeProtocolCommandListener(listener);
392 }
393
394 /**
395 * A convenience method to send the SMTP RSET command to the server, receive the reply, and return the reply code.
396 *
397 * @return The reply code received from the server.
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 the command or receiving the server reply.
402 */
403 public int rset() throws IOException {
404 return sendCommand(SMTPCommand.RSET);
405 }
406
407 /**
408 * A convenience method to send the SMTP SAML command to the server, receive the reply, and return the reply code.
409 *
410 * @param reversePath The reverse path.
411 * @return The reply code received from the server.
412 * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
413 * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
414 * independently as itself.
415 * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply.
416 */
417 public int saml(final String reversePath) throws IOException {
418 return sendCommand(SMTPCommand.SAML, reversePath);
419 }
420
421 /**
422 * A convenience method to send the SMTP SEND command to the server, receive the reply, and return the reply code.
423 *
424 * @param reversePath The reverse path.
425 * @return The reply code received from the server.
426 * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
427 * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
428 * independently as itself.
429 * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply.
430 */
431 public int send(final String reversePath) throws IOException {
432 return sendCommand(SMTPCommand.SEND, reversePath);
433 }
434
435 /**
436 * 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
437 * information, the actual reply text can be accessed by calling {@link #getReplyString getReplyString} or {@link #getReplyStrings getReplyStrings}.
438 *
439 * @param command The SMTPCommand constant corresponding to the SMTP command to send.
440 * @return The integer value of the SMTP reply code returned by the server in response to the command.
441 * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
442 * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
443 * independently as itself.
444 * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply.
445 */
446 public int sendCommand(final int command) throws IOException {
447 return sendCommand(command, null);
448 }
449
450 /**
451 * Sends an SMTP command to the server, waits for a reply and returns the numerical response code. After invocation, for more detailed information, the
452 * actual reply text can be accessed by calling {@link #getReplyString getReplyString} or {@link #getReplyStrings getReplyStrings}.
453 *
454 * @param command The SMTPCommand constant corresponding to the SMTP command to send.
455 * @param args The arguments to the SMTP command. If this parameter is set to null, then the command is sent with no argument.
456 * @return The integer value of the SMTP reply code returned by the server in response to the command.
457 * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
458 * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
459 * independently as itself.
460 * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply.
461 */
462 public int sendCommand(final int command, final String args) throws IOException {
463 return sendCommand(SMTPCommand.getCommand(command), args);
464 }
465
466 /**
467 *
468 * @param command the command to send (as an int defined in {@link SMTPCommand})
469 * @param args the command arguments, may be {@code null}
470 * @param includeSpace if {@code true}, add a space between the command and its arguments
471 * @return the reply code
472 * @throws IOException
473 */
474 private int sendCommand(final int command, final String args, final boolean includeSpace) throws IOException {
475 return sendCommand(SMTPCommand.getCommand(command), args, includeSpace);
476 }
477
478 /**
479 * 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
480 * information, the actual reply text can be accessed by calling {@link #getReplyString getReplyString} or {@link #getReplyStrings getReplyStrings}.
481 *
482 * @param command The text representation of the SMTP command to send.
483 * @return The integer value of the SMTP reply code returned by the server in response to the command.
484 * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
485 * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
486 * independently as itself.
487 * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply.
488 */
489 public int sendCommand(final String command) throws IOException {
490 return sendCommand(command, null);
491 }
492
493 /**
494 * Sends an SMTP command to the server, waits for a reply and returns the numerical response code. After invocation, for more detailed information, the
495 * actual reply text can be accessed by calling {@link #getReplyString getReplyString} or {@link #getReplyStrings getReplyStrings}.
496 *
497 * @param command The text representation of the SMTP command to send.
498 * @param args The arguments to the SMTP command. If this parameter is set to null, then the command is sent with no argument.
499 * @return The integer value of the SMTP reply code returned by the server in response to the command.
500 * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
501 * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
502 * independently as itself.
503 * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply.
504 */
505 public int sendCommand(final String command, final String args) throws IOException {
506 return sendCommand(command, args, true);
507 }
508
509 /**
510 * Send a command to the server. May also be used to send text data.
511 *
512 * @param command the command to send (as a plain String)
513 * @param args the command arguments, may be {@code null}
514 * @param includeSpace if {@code true}, add a space between the command and its arguments
515 * @return the reply code
516 * @throws IOException
517 */
518 private int sendCommand(final String command, final String args, final boolean includeSpace) throws IOException {
519 final StringBuilder builder = new StringBuilder(command);
520 if (args != null) {
521 if (includeSpace) {
522 builder.append(' ');
523 }
524 builder.append(args);
525 }
526 builder.append(NETASCII_EOL);
527 final String message = builder.toString();
528 writer.write(message);
529 writer.flush();
530 fireCommandSent(command, message);
531 return getReply();
532 }
533
534 /**
535 * A convenience method to send the SMTP SOML command to the server, receive the reply, and return the reply code.
536 *
537 * @param reversePath The reverse path.
538 * @return The reply code received from the server.
539 * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
540 * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
541 * independently as itself.
542 * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply.
543 */
544 public int soml(final String reversePath) throws IOException {
545 return sendCommand(SMTPCommand.SOML, reversePath);
546 }
547
548 /**
549 * A convenience method to send the SMTP TURN command to the server, receive the reply, and return the reply code.
550 *
551 * @return The reply code received from the server.
552 * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
553 * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
554 * independently as itself.
555 * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply.
556 */
557 public int turn() throws IOException {
558 return sendCommand(SMTPCommand.TURN);
559 }
560
561 /**
562 * A convenience method to send the SMTP VRFY command to the server, receive the reply, and return the reply code.
563 *
564 * @param user The user address to verify.
565 * @return The reply code received from the server.
566 * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason
567 * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or
568 * independently as itself.
569 * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply.
570 */
571 public int vrfy(final String user) throws IOException {
572 return sendCommand(SMTPCommand.VRFY, user);
573 }
574 }