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