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   ***/
43  
44  public class TelnetClient extends Telnet
45  {
46      private InputStream __input;
47      private OutputStream __output;
48      protected boolean readerThread = true;
49      private TelnetInputListener inputListener;
50  
51      /***
52       * Default TelnetClient constructor, sets terminal-type {@code VT100}.
53       ***/
54      public TelnetClient()
55      {
56          /* TERMINAL-TYPE option (start)*/
57          super ("VT100");
58          /* TERMINAL-TYPE option (end)*/
59          __input = null;
60          __output = null;
61      }
62  
63      /**
64       * Construct an instance with the specified terminal type.
65       *
66       * @param termtype the terminal type to use, e.g. {@code VT100}
67       */
68      /* TERMINAL-TYPE option (start)*/
69      public TelnetClient(String termtype)
70      {
71          super (termtype);
72          __input = null;
73          __output = null;
74      }
75      /* TERMINAL-TYPE option (end)*/
76  
77      void _flushOutputStream() throws IOException
78      {
79          _output_.flush();
80      }
81      void _closeOutputStream() throws IOException
82      {
83          try {
84              _output_.close();
85          } finally {
86              _output_ = null;
87          }
88      }
89  
90      /***
91       * Handles special connection requirements.
92       *
93       * @throws IOException  If an error occurs during connection setup.
94       ***/
95      @Override
96      protected void _connectAction_() throws IOException
97      {
98          super._connectAction_();
99          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 }