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