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