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