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, 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    }