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