View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.net.telnet;
19  
20  import java.io.BufferedInputStream;
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.io.OutputStream;
24  
25  /***
26   * The TelnetClient class implements the simple network virtual
27   * terminal (NVT) for the Telnet protocol according to RFC 854.  It
28   * does not implement any of the extra Telnet options because it
29   * is meant to be used within a Java program providing automated
30   * access to Telnet accessible resources.
31   * <p>
32   * The class can be used by first connecting to a server using the
33   * SocketClient
34   * {@link org.apache.commons.net.SocketClient#connect connect}
35   * method.  Then an InputStream and OutputStream for sending and
36   * receiving data over the Telnet connection can be obtained by
37   * using the {@link #getInputStream  getInputStream() } and
38   * {@link #getOutputStream  getOutputStream() } methods.
39   * When you finish using the streams, you must call
40   * {@link #disconnect  disconnect } rather than simply
41   * closing the streams.
42   * <p>
43   * <p>
44   * @author Bruno D'Avanzo
45   ***/
46  
47  public class TelnetClient extends Telnet
48  {
49      private InputStream __input;
50      private OutputStream __output;
51      protected boolean readerThread = true;
52      private TelnetInputListener inputListener;
53  
54      /***
55       * Default TelnetClient constructor, sets terminal-type {@code VT100}.
56       ***/
57      public TelnetClient()
58      {
59          /* TERMINAL-TYPE option (start)*/
60          super ("VT100");
61          /* TERMINAL-TYPE option (end)*/
62          __input = null;
63          __output = null;
64      }
65  
66      /**
67       * Construct an instance with the specified terminal type.
68       *
69       * @param termtype the terminal type to use, e.g. {@code VT100}
70       */
71      /* TERMINAL-TYPE option (start)*/
72      public TelnetClient(String termtype)
73      {
74          super (termtype);
75          __input = null;
76          __output = null;
77      }
78      /* TERMINAL-TYPE option (end)*/
79  
80      void _flushOutputStream() throws IOException
81      {
82          _output_.flush();
83      }
84      void _closeOutputStream() throws IOException
85      {
86          _output_.close();
87      }
88  
89      /***
90       * Handles special connection requirements.
91       * <p>
92       * @exception IOException  If an error occurs during connection setup.
93       ***/
94      @Override
95      protected void _connectAction_() throws IOException
96      {
97          super._connectAction_();
98          TelnetInputStream tmp = new TelnetInputStream(_input_, this, readerThread);
99          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 }