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}