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