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