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}