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