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