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