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
65 /** The default SMTP port (25). */
66 public static final int DEFAULT_PORT = 25;
67
68 /**
69 * 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.
70 */
71 private static final String DEFAULT_ENCODING = StandardCharsets.ISO_8859_1.name();
72
73 /**
74 * The encoding to use (user-settable).
75 *
76 * @since 3.1 (changed from private to protected)
77 */
78 protected final String encoding;
79
80 /**
81 * A ProtocolCommandSupport object used to manage the registering of ProtocolCommandListeners and te firing of ProtocolCommandEvents.
82 */
83 protected ProtocolCommandSupport _commandSupport_;
84
85 BufferedReader reader;
86 BufferedWriter writer;
87
88 private int replyCode;
89 private final ArrayList<String> replyLines;
90 private boolean newReplyString;
91 private String replyString;
92
93 /**
94 * The default SMTP constructor. Sets the default port to {@code DEFAULT_PORT} and initializes internal data structures for saving SMTP reply
95 * information.
96 */
97 public SMTP() {
98 this(DEFAULT_ENCODING);
99 }
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 }