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}