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 & 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 }