    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.telnet;
    019
    020import java.io.BufferedInputStream;
    021import java.io.IOException;
    022import java.io.InputStream;
    023import java.io.OutputStream;
    024import java.time.Duration;
    025
    026import org.apache.commons.io.IOUtils;
    027
    028/**
    029 * The TelnetClient class implements the simple network virtual terminal (NVT) for the Telnet protocol according to RFC 854. It does not implement any of the
    030 * extra Telnet options because it is meant to be used within a Java program providing automated access to Telnet accessible resources.
    031 * <p>
    032 * The class can be used by first connecting to a server using the SocketClient {@link org.apache.commons.net.SocketClient#connect connect} method. Then an
    033 * InputStream and OutputStream for sending and receiving data over the Telnet connection can be obtained by using the {@link #getInputStream getInputStream()}
    034 * and {@link #getOutputStream getOutputStream()} methods. When you finish using the streams, you must call {@link #disconnect disconnect} rather than simply
    035 * closing the streams.
    036 * </p>
    037 */
    038public class TelnetClient extends Telnet {
    039    private static final int DEFAULT_MAX_SUBNEGOTIATION_LENGTH = 512;
    040
    041    /**
    042     * the size of the subnegotiation buffer.
    043     */
    044    final int maxSubnegotiationLength;
    045
    046    /** Input stream. */
    047    private InputStream input;
    048
    049    /** Output stream. */
    050    private OutputStream output;
    051
    052    /**
    053     * Whether to enable the reader thread.
    054     */
    055    protected boolean readerThread = true;
    056
    057    /**
    058     * Telnet input listener.
    059     */
    060    private TelnetInputListener inputListener;
    061
    062    /**
    063     * Default TelnetClient constructor, sets terminal-type {@code VT100}.
    064     */
    065    public TelnetClient() {
    066        this("VT100", DEFAULT_MAX_SUBNEGOTIATION_LENGTH);
    067    }
    068
    069    /**
    070     * Constructs an instance with the specified max subnegotiation length and the default terminal-type {@code VT100}.
    071     *
    072     * @param maxSubnegotiationLength the size of the subnegotiation buffer.
    073     */
    074    public TelnetClient(final int maxSubnegotiationLength) {
    075        this("VT100", maxSubnegotiationLength);
    076    }
    077
    078    /**
    079     * Constructs an instance with the specified terminal type.
    080     *
    081     * @param termtype the terminal type to use, e.g. {@code VT100}
    082     */
    083    public TelnetClient(final String termtype) {
    084        this(termtype, DEFAULT_MAX_SUBNEGOTIATION_LENGTH);
    085    }
    086
    087    /**
    088     * Constructs an instance with the specified terminal type and max subnegotiation length
    089     *
    090     * @param termType                the terminal type to use, e.g. {@code VT100}
    091     * @param maxSubnegotiationLength the size of the subnegotiation buffer
    092     */
    093    public TelnetClient(final String termType, final int maxSubnegotiationLength) {
    094        /* TERMINAL-TYPE option (start) */
    095        super(termType);
    096        /* TERMINAL-TYPE option (end) */
    097        this.input = null;
    098        this.output = null;
    099        this.maxSubnegotiationLength = maxSubnegotiationLength;
    100    }
    101
    102    /**
    103     * Handles special connection requirements.
    104     *
    105     * @throws IOException If an error occurs during connection setup.
    106     */
    107    @Override
    108    protected void _connectAction_() throws IOException {
    109        super._connectAction_();
    110        final TelnetInputStream tmp = new TelnetInputStream(_input_, this, readerThread);
    111        if (readerThread) {
    112            tmp.start();
    113        }
    114        // input CANNOT refer to the TelnetInputStream. We run into
    115        // blocking problems when some classes use TelnetInputStream, so
    116        // we wrap it with a BufferedInputStream which we know is safe.
    117        // This blocking behavior requires further investigation, but right
    118        // now it looks like classes like InputStreamReader are not implemented
    119        // in a safe manner.
    120        input = new BufferedInputStream(tmp);
    121        output = new TelnetOutputStream(this);
    122    }
    123
    124    /**
    125     * Registers a new TelnetOptionHandler for this Telnet client to use.
    126     *
    127     * @param opthand   option handler to be registered.
    128     * @throws InvalidTelnetOptionException on error
    129     * @throws IOException                  on error
    130     */
    131    @Override
    132    public void addOptionHandler(final TelnetOptionHandler opthand) throws InvalidTelnetOptionException, IOException {
    133        super.addOptionHandler(opthand);
    134    }
    135    /* open TelnetOptionHandler functionality (end) */
    136
    137    void closeOutputStream() throws IOException {
    138        if (_output_ == null) {
    139            return;
    140        }
    141        try {
    142            _output_.close();
    143        } finally {
    144            _output_ = null;
    145        }
    146    }
    147
    148    /**
    149     * Unregisters a TelnetOptionHandler.
    150     *
    151     * @param optcode   Code of the option to be unregistered.
    152     * @throws InvalidTelnetOptionException on error
    153     * @throws IOException                  on error
    154     */
    155    @Override
    156    public void deleteOptionHandler(final int optcode) throws InvalidTelnetOptionException, IOException {
    157        super.deleteOptionHandler(optcode);
    158    }
    159
    160    /**
    161     * Disconnects the Telnet session, closing the input and output streams as well as the socket. If you have references to the input and output streams of the
    162     * Telnet connection, you should not close them yourself, but rather call disconnect to properly close the connection.
    163     */
    164    @Override
    165    public void disconnect() throws IOException {
    166        try {
    167            IOUtils.close(input);
    168            IOUtils.close(output);
    169        } finally { // NET-594
    170            output = null;
    171            input = null;
    172            super.disconnect();
    173        }
    174    }
    175
    176    void flushOutputStream() throws IOException {
    177        if (_output_ == null) {
    178            throw new IOException("Stream closed");
    179        }
    180        _output_.flush();
    181    }
    182
    183    /**
    184     * Gets the Telnet connection input stream. You should not close the stream when you finish with it. Rather, you should call {@link #disconnect
    185     * disconnect }.
    186     *
    187     * @return The Telnet connection input stream.
    188     */
    189    public InputStream getInputStream() {
    190        return input;
    191    }
    192
    193    /**
    194     * Gets the state of the option on the local side.
    195     *
    196     * @param option   Option to be checked.
    197     * @return The state of the option on the local side.
    198     */
    199    public boolean getLocalOptionState(final int option) {
    200        /* BUG (option active when not already acknowledged) (start) */
    201        return stateIsWill(option) && requestedWill(option);
    202        /* BUG (option active when not already acknowledged) (end) */
    203    }
    204
    205    /* Code Section added for supporting AYT (start) */
    206
    207    /**
    208     * Gets the Telnet connection output stream. You should not close the stream when you finish with it. Rather, you should call {@link #disconnect
    209     * disconnect }.
    210     *
    211     * @return The Telnet connection output stream.
    212     */
    213    public OutputStream getOutputStream() {
    214        return output;
    215    }
    216
    217    /**
    218     * Gets the status of the reader thread.
    219     *
    220     * @return true if the reader thread is enabled, false otherwise
    221     */
    222    public boolean getReaderThread() {
    223        return readerThread;
    224    }
    225
    226    /**
    227     * Gets the state of the option on the remote side.
    228     *
    229     * @param option   Option to be checked.
    230     * @return The state of the option on the remote side.
    231     */
    232    public boolean getRemoteOptionState(final int option) {
    233        /* BUG (option active when not already acknowledged) (start) */
    234        return stateIsDo(option) && requestedDo(option);
    235        /* BUG (option active when not already acknowledged) (end) */
    236    }
    237    /* open TelnetOptionHandler functionality (end) */
    238
    239    /* open TelnetOptionHandler functionality (start) */
    240
    241    // Notify input listener
    242    void notifyInputListener() {
    243        final TelnetInputListener listener;
    244        synchronized (this) {
    245            listener = this.inputListener;
    246        }
    247        if (listener != null) {
    248            listener.telnetInputAvailable();
    249        }
    250    }
    251
    252    /**
    253     * Register a listener to be notified when new incoming data is available to be read on the {@link #getInputStream input stream}. Only one listener is
    254     * supported at a time.
    255     *
    256     * <p>
    257     * More precisely, notifications are issued whenever the number of bytes available for immediate reading (i.e., the value returned by
    258     * {@link InputStream#available}) transitions from zero to non-zero. Note that (in general) multiple reads may be required to empty the buffer and reset
    259     * this notification, because incoming bytes are being added to the internal buffer asynchronously.
    260     * </p>
    261     *
    262     * <p>
    263     * Notifications are only supported when a {@link #setReaderThread reader thread} is enabled for the connection.
    264     * </p>
    265     *
    266     * @param listener listener to be registered, replaces any previous listener.
    267     * @since 3.0
    268     */
    269    public synchronized void registerInputListener(final TelnetInputListener listener) {
    270        this.inputListener = listener;
    271    }
    272
    273    /* Code Section added for supporting spystreams (start) */
    274    /**
    275     * Registers an OutputStream for spying what's going on in the TelnetClient session.
    276     *
    277     * @param spystream   OutputStream on which session activity will be echoed.
    278     */
    279    public void registerSpyStream(final OutputStream spystream) {
    280        super._registerSpyStream(spystream);
    281    }
    282
    283    /**
    284     * Sends an {@code Are You There (AYT)} sequence and waits for the result.
    285     *
    286     * @param timeout   Time to wait for a response.
    287     * @return true if AYT received a response, false otherwise.
    288     * @throws InterruptedException     on error
    289     * @throws IllegalArgumentException on error
    290     * @throws IOException              on error
    291     * @since 3.10.0
    292     */
    293    public boolean sendAYT(final Duration timeout) throws IOException, IllegalArgumentException, InterruptedException {
    294        return _sendAYT(timeout);
    295    }
    296
    297    /**
    298     * Sends an {@code Are You There (AYT)} sequence and waits for the result.
    299     *
    300     * @param timeout   Time to wait for a response (millis.)
    301     * @return true if AYT received a response, false otherwise
    302     * @throws InterruptedException     on error
    303     * @throws IllegalArgumentException on error
    304     * @throws IOException              on error
    305     * @deprecated Use {@link #sendAYT(Duration)}.
    306     */
    307    @Deprecated
    308    public boolean sendAYT(final long timeout) throws IOException, IllegalArgumentException, InterruptedException {
    309        return _sendAYT(Duration.ofMillis(timeout));
    310    }
    311
    312    /* Code Section added for supporting AYT (start) */
    313
    314    /**
    315     * Sends a command byte to the remote peer, adding the IAC prefix.
    316     *
    317     * <p>
    318     * This method does not wait for any response. Messages sent by the remote end can be handled by registering an approrpriate {@link TelnetOptionHandler}.
    319     * </p>
    320     *
    321     * @param command the code for the command
    322     * @throws IOException              if an I/O error occurs while writing the message
    323     * @throws IllegalArgumentException on error
    324     * @since 3.0
    325     */
    326    public void sendCommand(final byte command) throws IOException, IllegalArgumentException {
    327        _sendCommand(command);
    328    }
    329
    330    /**
    331     * Sends a protocol-specific subnegotiation message to the remote peer. {@link TelnetClient} will add the IAC SB &amp; IAC SE framing bytes; the first byte
    332     * in {@code message} should be the appropriate Telnet option code.
    333     *
    334     * <p>
    335     * This method does not wait for any response. Subnegotiation messages sent by the remote end can be handled by registering an approrpriate
    336     * {@link TelnetOptionHandler}.
    337     * </p>
    338     *
    339     * @param message option code followed by subnegotiation payload
    340     * @throws IllegalArgumentException if {@code message} has length zero
    341     * @throws IOException              if an I/O error occurs while writing the message
    342     * @since 3.0
    343     */
    344    public void sendSubnegotiation(final int[] message) throws IOException, IllegalArgumentException {
    345        if (message.length < 1) {
    346            throw new IllegalArgumentException("zero length message");
    347        }
    348        _sendSubnegotiation(message);
    349    }
    350
    351    /**
    352     * Sets the status of the reader thread.
    353     *
    354     * <p>
    355     * When enabled, a seaparate internal reader thread is created for new connections to read incoming data as it arrives. This results in immediate handling
    356     * of option negotiation, notifications, etc. (at least until the fixed-size internal buffer fills up). Otherwise, no thread is created an all negotiation
    357     * and option handling is deferred until a read() is performed on the {@link #getInputStream input stream}.
    358     * </p>
    359     *
    360     * <p>
    361     * The reader thread must be enabled for {@link TelnetInputListener} support.
    362     * </p>
    363     *
    364     * <p>
    365     * When this method is invoked, the reader thread status will apply to all subsequent connections; the current connection (if any) is not affected.
    366     * </p>
    367     *
    368     * @param readerThread true to enable the reader thread, false to disable.
    369     * @see #registerInputListener
    370     */
    371    public void setReaderThread(final boolean readerThread) {
    372        this.readerThread = readerThread;
    373    }
    374
    375    /**
    376     * Stops spying this TelnetClient.
    377     */
    378    public void stopSpyStream() {
    379        super._stopSpyStream();
    380    }
    381    /* Code Section added for supporting spystreams (end) */
    382
    383    /**
    384     * Unregisters the current {@link TelnetInputListener}, if any.
    385     *
    386     * @since 3.0
    387     */
    388    public synchronized void unregisterInputListener() {
    389        this.inputListener = null;
    390    }
    391
    392}