0
|
1
|
|
2 package com.trilead.ssh2.channel;
|
|
3
|
|
4 import java.io.IOException;
|
|
5 import java.util.HashMap;
|
|
6 import java.util.Vector;
|
|
7
|
|
8 import com.trilead.ssh2.AuthAgentCallback;
|
|
9 import com.trilead.ssh2.ChannelCondition;
|
|
10 import com.trilead.ssh2.log.Logger;
|
|
11 import com.trilead.ssh2.packets.PacketChannelAuthAgentReq;
|
|
12 import com.trilead.ssh2.packets.PacketChannelOpenConfirmation;
|
|
13 import com.trilead.ssh2.packets.PacketChannelOpenFailure;
|
|
14 import com.trilead.ssh2.packets.PacketChannelTrileadPing;
|
|
15 import com.trilead.ssh2.packets.PacketGlobalCancelForwardRequest;
|
|
16 import com.trilead.ssh2.packets.PacketGlobalForwardRequest;
|
|
17 import com.trilead.ssh2.packets.PacketGlobalTrileadPing;
|
|
18 import com.trilead.ssh2.packets.PacketOpenDirectTCPIPChannel;
|
|
19 import com.trilead.ssh2.packets.PacketOpenSessionChannel;
|
|
20 import com.trilead.ssh2.packets.PacketSessionExecCommand;
|
|
21 import com.trilead.ssh2.packets.PacketSessionPtyRequest;
|
|
22 import com.trilead.ssh2.packets.PacketSessionPtyResize;
|
|
23 import com.trilead.ssh2.packets.PacketSessionStartShell;
|
|
24 import com.trilead.ssh2.packets.PacketSessionSubsystemRequest;
|
|
25 import com.trilead.ssh2.packets.PacketSessionX11Request;
|
|
26 import com.trilead.ssh2.packets.Packets;
|
|
27 import com.trilead.ssh2.packets.TypesReader;
|
|
28 import com.trilead.ssh2.transport.MessageHandler;
|
|
29 import com.trilead.ssh2.transport.TransportManager;
|
|
30
|
|
31 /**
|
|
32 * ChannelManager. Please read the comments in Channel.java.
|
|
33 * <p>
|
|
34 * Besides the crypto part, this is the core of the library.
|
|
35 *
|
|
36 * @author Christian Plattner, plattner@trilead.com
|
|
37 * @version $Id: ChannelManager.java,v 1.2 2008/03/03 07:01:36 cplattne Exp $
|
|
38 */
|
|
39 public class ChannelManager implements MessageHandler {
|
|
40 private static final Logger log = Logger.getLogger(ChannelManager.class);
|
|
41
|
|
42 private HashMap<String, X11ServerData> x11_magic_cookies = new HashMap<String, X11ServerData>();
|
|
43
|
|
44 private TransportManager tm;
|
|
45
|
|
46 private Vector<Channel> channels = new Vector<Channel>();
|
|
47 private int nextLocalChannel = 100;
|
|
48 private boolean shutdown = false;
|
|
49 private int globalSuccessCounter = 0;
|
|
50 private int globalFailedCounter = 0;
|
|
51
|
|
52 private HashMap<Integer, RemoteForwardingData> remoteForwardings = new HashMap<Integer, RemoteForwardingData>();
|
|
53
|
|
54 private AuthAgentCallback authAgent;
|
|
55
|
|
56 private Vector<IChannelWorkerThread> listenerThreads = new Vector<IChannelWorkerThread>();
|
|
57
|
|
58 private boolean listenerThreadsAllowed = true;
|
|
59
|
|
60 public ChannelManager(TransportManager tm) {
|
|
61 this.tm = tm;
|
|
62 tm.registerMessageHandler(this, 80, 100);
|
|
63 }
|
|
64
|
|
65 private Channel getChannel(int id) {
|
|
66 synchronized (channels) {
|
|
67 for (int i = 0; i < channels.size(); i++) {
|
|
68 Channel c = channels.elementAt(i);
|
|
69
|
|
70 if (c.localID == id)
|
|
71 return c;
|
|
72 }
|
|
73 }
|
|
74
|
|
75 return null;
|
|
76 }
|
|
77
|
|
78 private void removeChannel(int id) {
|
|
79 synchronized (channels) {
|
|
80 for (int i = 0; i < channels.size(); i++) {
|
|
81 Channel c = channels.elementAt(i);
|
|
82
|
|
83 if (c.localID == id) {
|
|
84 channels.removeElementAt(i);
|
|
85 break;
|
|
86 }
|
|
87 }
|
|
88 }
|
|
89 }
|
|
90
|
|
91 private int addChannel(Channel c) {
|
|
92 synchronized (channels) {
|
|
93 channels.addElement(c);
|
|
94 return nextLocalChannel++;
|
|
95 }
|
|
96 }
|
|
97
|
|
98 private void waitUntilChannelOpen(Channel c) throws IOException {
|
|
99 synchronized (c) {
|
|
100 while (c.state == Channel.STATE_OPENING) {
|
|
101 try {
|
|
102 c.wait();
|
|
103 }
|
|
104 catch (InterruptedException ignore) {
|
|
105 }
|
|
106 }
|
|
107
|
|
108 if (c.state != Channel.STATE_OPEN) {
|
|
109 removeChannel(c.localID);
|
|
110 String detail = c.getReasonClosed();
|
|
111
|
|
112 if (detail == null)
|
|
113 detail = "state: " + c.state;
|
|
114
|
|
115 throw new IOException("Could not open channel (" + detail + ")");
|
|
116 }
|
|
117 }
|
|
118 }
|
|
119
|
|
120 private final boolean waitForGlobalRequestResult() throws IOException {
|
|
121 synchronized (channels) {
|
|
122 while ((globalSuccessCounter == 0) && (globalFailedCounter == 0)) {
|
|
123 if (shutdown) {
|
|
124 throw new IOException("The connection is being shutdown");
|
|
125 }
|
|
126
|
|
127 try {
|
|
128 channels.wait();
|
|
129 }
|
|
130 catch (InterruptedException ignore) {
|
|
131 }
|
|
132 }
|
|
133
|
|
134 if ((globalFailedCounter == 0) && (globalSuccessCounter == 1))
|
|
135 return true;
|
|
136
|
|
137 if ((globalFailedCounter == 1) && (globalSuccessCounter == 0))
|
|
138 return false;
|
|
139
|
|
140 throw new IOException("Illegal state. The server sent " + globalSuccessCounter
|
|
141 + " SSH_MSG_REQUEST_SUCCESS and " + globalFailedCounter + " SSH_MSG_REQUEST_FAILURE messages.");
|
|
142 }
|
|
143 }
|
|
144
|
|
145 private final boolean waitForChannelRequestResult(Channel c) throws IOException {
|
|
146 synchronized (c) {
|
|
147 while ((c.successCounter == 0) && (c.failedCounter == 0)) {
|
|
148 if (c.state != Channel.STATE_OPEN) {
|
|
149 String detail = c.getReasonClosed();
|
|
150
|
|
151 if (detail == null)
|
|
152 detail = "state: " + c.state;
|
|
153
|
|
154 throw new IOException("This SSH2 channel is not open (" + detail + ")");
|
|
155 }
|
|
156
|
|
157 try {
|
|
158 c.wait();
|
|
159 }
|
|
160 catch (InterruptedException ignore) {
|
|
161 }
|
|
162 }
|
|
163
|
|
164 if ((c.failedCounter == 0) && (c.successCounter == 1))
|
|
165 return true;
|
|
166
|
|
167 if ((c.failedCounter == 1) && (c.successCounter == 0))
|
|
168 return false;
|
|
169
|
|
170 throw new IOException("Illegal state. The server sent " + c.successCounter
|
|
171 + " SSH_MSG_CHANNEL_SUCCESS and " + c.failedCounter + " SSH_MSG_CHANNEL_FAILURE messages.");
|
|
172 }
|
|
173 }
|
|
174
|
|
175 public void registerX11Cookie(String hexFakeCookie, X11ServerData data) {
|
|
176 synchronized (x11_magic_cookies) {
|
|
177 x11_magic_cookies.put(hexFakeCookie, data);
|
|
178 }
|
|
179 }
|
|
180
|
|
181 public void unRegisterX11Cookie(String hexFakeCookie, boolean killChannels) {
|
|
182 if (hexFakeCookie == null)
|
|
183 throw new IllegalStateException("hexFakeCookie may not be null");
|
|
184
|
|
185 synchronized (x11_magic_cookies) {
|
|
186 x11_magic_cookies.remove(hexFakeCookie);
|
|
187 }
|
|
188
|
|
189 if (killChannels == false)
|
|
190 return;
|
|
191
|
|
192 if (log.isEnabled())
|
|
193 log.log(50, "Closing all X11 channels for the given fake cookie");
|
|
194
|
|
195 Vector<Channel> channel_copy;
|
|
196
|
|
197 synchronized (channels) {
|
|
198 channel_copy = (Vector<Channel>) channels.clone();
|
|
199 }
|
|
200
|
|
201 for (int i = 0; i < channel_copy.size(); i++) {
|
|
202 Channel c = channel_copy.elementAt(i);
|
|
203
|
|
204 synchronized (c) {
|
|
205 if (hexFakeCookie.equals(c.hexX11FakeCookie) == false)
|
|
206 continue;
|
|
207 }
|
|
208
|
|
209 try {
|
|
210 closeChannel(c, "Closing X11 channel since the corresponding session is closing", true);
|
|
211 }
|
|
212 catch (IOException e) {
|
|
213 }
|
|
214 }
|
|
215 }
|
|
216
|
|
217 public X11ServerData checkX11Cookie(String hexFakeCookie) {
|
|
218 synchronized (x11_magic_cookies) {
|
|
219 if (hexFakeCookie != null)
|
|
220 return x11_magic_cookies.get(hexFakeCookie);
|
|
221 }
|
|
222
|
|
223 return null;
|
|
224 }
|
|
225
|
|
226 public void closeAllChannels() {
|
|
227 if (log.isEnabled())
|
|
228 log.log(50, "Closing all channels");
|
|
229
|
|
230 Vector<Channel> channel_copy;
|
|
231
|
|
232 synchronized (channels) {
|
|
233 channel_copy = (Vector<Channel>) channels.clone();
|
|
234 }
|
|
235
|
|
236 for (int i = 0; i < channel_copy.size(); i++) {
|
|
237 Channel c = channel_copy.elementAt(i);
|
|
238
|
|
239 try {
|
|
240 closeChannel(c, "Closing all channels", true);
|
|
241 }
|
|
242 catch (IOException e) {
|
|
243 }
|
|
244 }
|
|
245 }
|
|
246
|
|
247 public void closeChannel(Channel c, String reason, boolean force) throws IOException {
|
|
248 byte msg[] = new byte[5];
|
|
249
|
|
250 synchronized (c) {
|
|
251 if (force) {
|
|
252 c.state = Channel.STATE_CLOSED;
|
|
253 c.EOF = true;
|
|
254 }
|
|
255
|
|
256 c.setReasonClosed(reason);
|
|
257 msg[0] = Packets.SSH_MSG_CHANNEL_CLOSE;
|
|
258 msg[1] = (byte)(c.remoteID >> 24);
|
|
259 msg[2] = (byte)(c.remoteID >> 16);
|
|
260 msg[3] = (byte)(c.remoteID >> 8);
|
|
261 msg[4] = (byte)(c.remoteID);
|
|
262 c.notifyAll();
|
|
263 }
|
|
264
|
|
265 synchronized (c.channelSendLock) {
|
|
266 if (c.closeMessageSent == true)
|
|
267 return;
|
|
268
|
|
269 tm.sendMessage(msg);
|
|
270 c.closeMessageSent = true;
|
|
271 }
|
|
272
|
|
273 if (log.isEnabled())
|
|
274 log.log(50, "Sent SSH_MSG_CHANNEL_CLOSE (channel " + c.localID + ")");
|
|
275 }
|
|
276
|
|
277 public void sendEOF(Channel c) throws IOException {
|
|
278 byte[] msg = new byte[5];
|
|
279
|
|
280 synchronized (c) {
|
|
281 if (c.state != Channel.STATE_OPEN)
|
|
282 return;
|
|
283
|
|
284 msg[0] = Packets.SSH_MSG_CHANNEL_EOF;
|
|
285 msg[1] = (byte)(c.remoteID >> 24);
|
|
286 msg[2] = (byte)(c.remoteID >> 16);
|
|
287 msg[3] = (byte)(c.remoteID >> 8);
|
|
288 msg[4] = (byte)(c.remoteID);
|
|
289 }
|
|
290
|
|
291 synchronized (c.channelSendLock) {
|
|
292 if (c.closeMessageSent == true)
|
|
293 return;
|
|
294
|
|
295 tm.sendMessage(msg);
|
|
296 }
|
|
297
|
|
298 if (log.isEnabled())
|
|
299 log.log(50, "Sent EOF (Channel " + c.localID + "/" + c.remoteID + ")");
|
|
300 }
|
|
301
|
|
302 public void sendOpenConfirmation(Channel c) throws IOException {
|
|
303 PacketChannelOpenConfirmation pcoc = null;
|
|
304
|
|
305 synchronized (c) {
|
|
306 if (c.state != Channel.STATE_OPENING)
|
|
307 return;
|
|
308
|
|
309 c.state = Channel.STATE_OPEN;
|
|
310 pcoc = new PacketChannelOpenConfirmation(c.remoteID, c.localID, c.localWindow, c.localMaxPacketSize);
|
|
311 }
|
|
312
|
|
313 synchronized (c.channelSendLock) {
|
|
314 if (c.closeMessageSent == true)
|
|
315 return;
|
|
316
|
|
317 tm.sendMessage(pcoc.getPayload());
|
|
318 }
|
|
319 }
|
|
320
|
|
321 public void sendData(Channel c, byte[] buffer, int pos, int len) throws IOException {
|
|
322 while (len > 0) {
|
|
323 int thislen = 0;
|
|
324 byte[] msg;
|
|
325
|
|
326 synchronized (c) {
|
|
327 while (true) {
|
|
328 if (c.state == Channel.STATE_CLOSED)
|
|
329 throw new IOException("SSH channel is closed. (" + c.getReasonClosed() + ")");
|
|
330
|
|
331 if (c.state != Channel.STATE_OPEN)
|
|
332 throw new IOException("SSH channel in strange state. (" + c.state + ")");
|
|
333
|
|
334 if (c.remoteWindow != 0)
|
|
335 break;
|
|
336
|
|
337 try {
|
|
338 c.wait();
|
|
339 }
|
|
340 catch (InterruptedException ignore) {
|
|
341 }
|
|
342 }
|
|
343
|
|
344 /* len > 0, no sign extension can happen when comparing */
|
|
345 thislen = (c.remoteWindow >= len) ? len : (int) c.remoteWindow;
|
|
346 int estimatedMaxDataLen = c.remoteMaxPacketSize - (tm.getPacketOverheadEstimate() + 9);
|
|
347
|
|
348 /* The worst case scenario =) a true bottleneck */
|
|
349
|
|
350 if (estimatedMaxDataLen <= 0) {
|
|
351 estimatedMaxDataLen = 1;
|
|
352 }
|
|
353
|
|
354 if (thislen > estimatedMaxDataLen)
|
|
355 thislen = estimatedMaxDataLen;
|
|
356
|
|
357 c.remoteWindow -= thislen;
|
|
358 msg = new byte[1 + 8 + thislen];
|
|
359 msg[0] = Packets.SSH_MSG_CHANNEL_DATA;
|
|
360 msg[1] = (byte)(c.remoteID >> 24);
|
|
361 msg[2] = (byte)(c.remoteID >> 16);
|
|
362 msg[3] = (byte)(c.remoteID >> 8);
|
|
363 msg[4] = (byte)(c.remoteID);
|
|
364 msg[5] = (byte)(thislen >> 24);
|
|
365 msg[6] = (byte)(thislen >> 16);
|
|
366 msg[7] = (byte)(thislen >> 8);
|
|
367 msg[8] = (byte)(thislen);
|
|
368 System.arraycopy(buffer, pos, msg, 9, thislen);
|
|
369 }
|
|
370
|
|
371 synchronized (c.channelSendLock) {
|
|
372 if (c.closeMessageSent == true)
|
|
373 throw new IOException("SSH channel is closed. (" + c.getReasonClosed() + ")");
|
|
374
|
|
375 tm.sendMessage(msg);
|
|
376 }
|
|
377
|
|
378 pos += thislen;
|
|
379 len -= thislen;
|
|
380 }
|
|
381 }
|
|
382
|
|
383 public int requestGlobalForward(String bindAddress, int bindPort, String targetAddress, int targetPort)
|
|
384 throws IOException {
|
|
385 RemoteForwardingData rfd = new RemoteForwardingData();
|
|
386 rfd.bindAddress = bindAddress;
|
|
387 rfd.bindPort = bindPort;
|
|
388 rfd.targetAddress = targetAddress;
|
|
389 rfd.targetPort = targetPort;
|
|
390
|
|
391 synchronized (remoteForwardings) {
|
|
392 Integer key = Integer.valueOf(bindPort);
|
|
393
|
|
394 if (remoteForwardings.get(key) != null) {
|
|
395 throw new IOException("There is already a forwarding for remote port " + bindPort);
|
|
396 }
|
|
397
|
|
398 remoteForwardings.put(key, rfd);
|
|
399 }
|
|
400
|
|
401 synchronized (channels) {
|
|
402 globalSuccessCounter = globalFailedCounter = 0;
|
|
403 }
|
|
404
|
|
405 PacketGlobalForwardRequest pgf = new PacketGlobalForwardRequest(true, bindAddress, bindPort);
|
|
406 tm.sendMessage(pgf.getPayload());
|
|
407
|
|
408 if (log.isEnabled())
|
|
409 log.log(50, "Requesting a remote forwarding ('" + bindAddress + "', " + bindPort + ")");
|
|
410
|
|
411 try {
|
|
412 if (waitForGlobalRequestResult() == false)
|
|
413 throw new IOException("The server denied the request (did you enable port forwarding?)");
|
|
414 }
|
|
415 catch (IOException e) {
|
|
416 synchronized (remoteForwardings) {
|
|
417 remoteForwardings.remove(rfd);
|
|
418 }
|
|
419
|
|
420 throw e;
|
|
421 }
|
|
422
|
|
423 return bindPort;
|
|
424 }
|
|
425
|
|
426 public void requestCancelGlobalForward(int bindPort) throws IOException {
|
|
427 RemoteForwardingData rfd = null;
|
|
428
|
|
429 synchronized (remoteForwardings) {
|
|
430 rfd = remoteForwardings.get(Integer.valueOf(bindPort));
|
|
431
|
|
432 if (rfd == null)
|
|
433 throw new IOException("Sorry, there is no known remote forwarding for remote port " + bindPort);
|
|
434 }
|
|
435
|
|
436 synchronized (channels) {
|
|
437 globalSuccessCounter = globalFailedCounter = 0;
|
|
438 }
|
|
439
|
|
440 PacketGlobalCancelForwardRequest pgcf = new PacketGlobalCancelForwardRequest(true, rfd.bindAddress,
|
|
441 rfd.bindPort);
|
|
442 tm.sendMessage(pgcf.getPayload());
|
|
443
|
|
444 if (log.isEnabled())
|
|
445 log.log(50, "Requesting cancelation of remote forward ('" + rfd.bindAddress + "', " + rfd.bindPort + ")");
|
|
446
|
|
447 try {
|
|
448 if (waitForGlobalRequestResult() == false)
|
|
449 throw new IOException("The server denied the request.");
|
|
450 }
|
|
451 finally {
|
|
452 synchronized (remoteForwardings) {
|
|
453 /* Only now we are sure that no more forwarded connections will arrive */
|
|
454 remoteForwardings.remove(rfd);
|
|
455 }
|
|
456 }
|
|
457 }
|
|
458
|
|
459 /**
|
|
460 * @param agent
|
|
461 * @throws IOException
|
|
462 */
|
|
463 public boolean requestChannelAgentForwarding(Channel c, AuthAgentCallback authAgent) throws IOException {
|
|
464 synchronized (this) {
|
|
465 if (this.authAgent != null)
|
|
466 throw new IllegalStateException("Auth agent already exists");
|
|
467
|
|
468 this.authAgent = authAgent;
|
|
469 }
|
|
470
|
|
471 synchronized (channels) {
|
|
472 globalSuccessCounter = globalFailedCounter = 0;
|
|
473 }
|
|
474
|
|
475 if (log.isEnabled())
|
|
476 log.log(50, "Requesting agent forwarding");
|
|
477
|
|
478 PacketChannelAuthAgentReq aar = new PacketChannelAuthAgentReq(c.remoteID);
|
|
479 tm.sendMessage(aar.getPayload());
|
|
480
|
|
481 if (waitForChannelRequestResult(c) == false) {
|
|
482 authAgent = null;
|
|
483 return false;
|
|
484 }
|
|
485
|
|
486 return true;
|
|
487 }
|
|
488
|
|
489 public void registerThread(IChannelWorkerThread thr) throws IOException {
|
|
490 synchronized (listenerThreads) {
|
|
491 if (listenerThreadsAllowed == false)
|
|
492 throw new IOException("Too late, this connection is closed.");
|
|
493
|
|
494 listenerThreads.addElement(thr);
|
|
495 }
|
|
496 }
|
|
497
|
|
498 public Channel openDirectTCPIPChannel(String host_to_connect, int port_to_connect, String originator_IP_address,
|
|
499 int originator_port) throws IOException {
|
|
500 Channel c = new Channel(this);
|
|
501
|
|
502 synchronized (c) {
|
|
503 c.localID = addChannel(c);
|
|
504 // end of synchronized block forces writing out to main memory
|
|
505 }
|
|
506
|
|
507 PacketOpenDirectTCPIPChannel dtc = new PacketOpenDirectTCPIPChannel(c.localID, c.localWindow,
|
|
508 c.localMaxPacketSize, host_to_connect, port_to_connect, originator_IP_address, originator_port);
|
|
509 tm.sendMessage(dtc.getPayload());
|
|
510 waitUntilChannelOpen(c);
|
|
511 return c;
|
|
512 }
|
|
513
|
|
514 public Channel openSessionChannel() throws IOException {
|
|
515 Channel c = new Channel(this);
|
|
516
|
|
517 synchronized (c) {
|
|
518 c.localID = addChannel(c);
|
|
519 // end of synchronized block forces the writing out to main memory
|
|
520 }
|
|
521
|
|
522 if (log.isEnabled())
|
|
523 log.log(50, "Sending SSH_MSG_CHANNEL_OPEN (Channel " + c.localID + ")");
|
|
524
|
|
525 PacketOpenSessionChannel smo = new PacketOpenSessionChannel(c.localID, c.localWindow, c.localMaxPacketSize);
|
|
526 tm.sendMessage(smo.getPayload());
|
|
527 waitUntilChannelOpen(c);
|
|
528 return c;
|
|
529 }
|
|
530
|
|
531 public void requestGlobalTrileadPing() throws IOException {
|
|
532 synchronized (channels) {
|
|
533 globalSuccessCounter = globalFailedCounter = 0;
|
|
534 }
|
|
535
|
|
536 PacketGlobalTrileadPing pgtp = new PacketGlobalTrileadPing();
|
|
537 tm.sendMessage(pgtp.getPayload());
|
|
538
|
|
539 if (log.isEnabled())
|
|
540 log.log(50, "Sending SSH_MSG_GLOBAL_REQUEST 'trilead-ping'.");
|
|
541
|
|
542 try {
|
|
543 if (waitForGlobalRequestResult() == true)
|
|
544 throw new IOException("Your server is alive - but buggy. "
|
|
545 + "It replied with SSH_MSG_REQUEST_SUCCESS when it actually should not.");
|
|
546 }
|
|
547 catch (IOException e) {
|
|
548 throw(IOException) new IOException("The ping request failed.").initCause(e);
|
|
549 }
|
|
550 }
|
|
551
|
|
552 public void requestChannelTrileadPing(Channel c) throws IOException {
|
|
553 PacketChannelTrileadPing pctp;
|
|
554
|
|
555 synchronized (c) {
|
|
556 if (c.state != Channel.STATE_OPEN)
|
|
557 throw new IOException("Cannot ping this channel (" + c.getReasonClosed() + ")");
|
|
558
|
|
559 pctp = new PacketChannelTrileadPing(c.remoteID);
|
|
560 c.successCounter = c.failedCounter = 0;
|
|
561 }
|
|
562
|
|
563 synchronized (c.channelSendLock) {
|
|
564 if (c.closeMessageSent)
|
|
565 throw new IOException("Cannot ping this channel (" + c.getReasonClosed() + ")");
|
|
566
|
|
567 tm.sendMessage(pctp.getPayload());
|
|
568 }
|
|
569
|
|
570 try {
|
|
571 if (waitForChannelRequestResult(c) == true)
|
|
572 throw new IOException("Your server is alive - but buggy. "
|
|
573 + "It replied with SSH_MSG_SESSION_SUCCESS when it actually should not.");
|
|
574 }
|
|
575 catch (IOException e) {
|
|
576 throw(IOException) new IOException("The ping request failed.").initCause(e);
|
|
577 }
|
|
578 }
|
|
579
|
|
580 public void requestPTY(Channel c, String term, int term_width_characters, int term_height_characters,
|
|
581 int term_width_pixels, int term_height_pixels, byte[] terminal_modes) throws IOException {
|
|
582 PacketSessionPtyRequest spr;
|
|
583
|
|
584 synchronized (c) {
|
|
585 if (c.state != Channel.STATE_OPEN)
|
|
586 throw new IOException("Cannot request PTY on this channel (" + c.getReasonClosed() + ")");
|
|
587
|
|
588 spr = new PacketSessionPtyRequest(c.remoteID, true, term, term_width_characters, term_height_characters,
|
|
589 term_width_pixels, term_height_pixels, terminal_modes);
|
|
590 c.successCounter = c.failedCounter = 0;
|
|
591 }
|
|
592
|
|
593 synchronized (c.channelSendLock) {
|
|
594 if (c.closeMessageSent)
|
|
595 throw new IOException("Cannot request PTY on this channel (" + c.getReasonClosed() + ")");
|
|
596
|
|
597 tm.sendMessage(spr.getPayload());
|
|
598 }
|
|
599
|
|
600 try {
|
|
601 if (waitForChannelRequestResult(c) == false)
|
|
602 throw new IOException("The server denied the request.");
|
|
603 }
|
|
604 catch (IOException e) {
|
|
605 throw(IOException) new IOException("PTY request failed").initCause(e);
|
|
606 }
|
|
607 }
|
|
608
|
|
609
|
|
610 public void resizePTY(Channel c, int term_width_characters, int term_height_characters,
|
|
611 int term_width_pixels, int term_height_pixels) throws IOException {
|
|
612 PacketSessionPtyResize spr;
|
|
613
|
|
614 synchronized (c) {
|
|
615 if (c.state != Channel.STATE_OPEN)
|
|
616 throw new IOException("Cannot request PTY on this channel ("
|
|
617 + c.getReasonClosed() + ")");
|
|
618
|
|
619 spr = new PacketSessionPtyResize(c.remoteID, term_width_characters, term_height_characters,
|
|
620 term_width_pixels, term_height_pixels);
|
|
621 c.successCounter = c.failedCounter = 0;
|
|
622 }
|
|
623
|
|
624 synchronized (c.channelSendLock) {
|
|
625 if (c.closeMessageSent)
|
|
626 throw new IOException("Cannot request PTY on this channel ("
|
|
627 + c.getReasonClosed() + ")");
|
|
628
|
|
629 tm.sendMessage(spr.getPayload());
|
|
630 }
|
|
631 }
|
|
632
|
|
633
|
|
634 public void requestX11(Channel c, boolean singleConnection, String x11AuthenticationProtocol,
|
|
635 String x11AuthenticationCookie, int x11ScreenNumber) throws IOException {
|
|
636 PacketSessionX11Request psr;
|
|
637
|
|
638 synchronized (c) {
|
|
639 if (c.state != Channel.STATE_OPEN)
|
|
640 throw new IOException("Cannot request X11 on this channel (" + c.getReasonClosed() + ")");
|
|
641
|
|
642 psr = new PacketSessionX11Request(c.remoteID, true, singleConnection, x11AuthenticationProtocol,
|
|
643 x11AuthenticationCookie, x11ScreenNumber);
|
|
644 c.successCounter = c.failedCounter = 0;
|
|
645 }
|
|
646
|
|
647 synchronized (c.channelSendLock) {
|
|
648 if (c.closeMessageSent)
|
|
649 throw new IOException("Cannot request X11 on this channel (" + c.getReasonClosed() + ")");
|
|
650
|
|
651 tm.sendMessage(psr.getPayload());
|
|
652 }
|
|
653
|
|
654 if (log.isEnabled())
|
|
655 log.log(50, "Requesting X11 forwarding (Channel " + c.localID + "/" + c.remoteID + ")");
|
|
656
|
|
657 try {
|
|
658 if (waitForChannelRequestResult(c) == false)
|
|
659 throw new IOException("The server denied the request.");
|
|
660 }
|
|
661 catch (IOException e) {
|
|
662 throw(IOException) new IOException("The X11 request failed.").initCause(e);
|
|
663 }
|
|
664 }
|
|
665
|
|
666 public void requestSubSystem(Channel c, String subSystemName) throws IOException {
|
|
667 PacketSessionSubsystemRequest ssr;
|
|
668
|
|
669 synchronized (c) {
|
|
670 if (c.state != Channel.STATE_OPEN)
|
|
671 throw new IOException("Cannot request subsystem on this channel (" + c.getReasonClosed() + ")");
|
|
672
|
|
673 ssr = new PacketSessionSubsystemRequest(c.remoteID, true, subSystemName);
|
|
674 c.successCounter = c.failedCounter = 0;
|
|
675 }
|
|
676
|
|
677 synchronized (c.channelSendLock) {
|
|
678 if (c.closeMessageSent)
|
|
679 throw new IOException("Cannot request subsystem on this channel (" + c.getReasonClosed() + ")");
|
|
680
|
|
681 tm.sendMessage(ssr.getPayload());
|
|
682 }
|
|
683
|
|
684 try {
|
|
685 if (waitForChannelRequestResult(c) == false)
|
|
686 throw new IOException("The server denied the request.");
|
|
687 }
|
|
688 catch (IOException e) {
|
|
689 throw(IOException) new IOException("The subsystem request failed.").initCause(e);
|
|
690 }
|
|
691 }
|
|
692
|
|
693 public void requestExecCommand(Channel c, String cmd) throws IOException {
|
|
694 PacketSessionExecCommand sm;
|
|
695
|
|
696 synchronized (c) {
|
|
697 if (c.state != Channel.STATE_OPEN)
|
|
698 throw new IOException("Cannot execute command on this channel (" + c.getReasonClosed() + ")");
|
|
699
|
|
700 sm = new PacketSessionExecCommand(c.remoteID, true, cmd);
|
|
701 c.successCounter = c.failedCounter = 0;
|
|
702 }
|
|
703
|
|
704 synchronized (c.channelSendLock) {
|
|
705 if (c.closeMessageSent)
|
|
706 throw new IOException("Cannot execute command on this channel (" + c.getReasonClosed() + ")");
|
|
707
|
|
708 tm.sendMessage(sm.getPayload());
|
|
709 }
|
|
710
|
|
711 if (log.isEnabled())
|
|
712 log.log(50, "Executing command (channel " + c.localID + ", '" + cmd + "')");
|
|
713
|
|
714 try {
|
|
715 if (waitForChannelRequestResult(c) == false)
|
|
716 throw new IOException("The server denied the request.");
|
|
717 }
|
|
718 catch (IOException e) {
|
|
719 throw(IOException) new IOException("The execute request failed.").initCause(e);
|
|
720 }
|
|
721 }
|
|
722
|
|
723 public void requestShell(Channel c) throws IOException {
|
|
724 PacketSessionStartShell sm;
|
|
725
|
|
726 synchronized (c) {
|
|
727 if (c.state != Channel.STATE_OPEN)
|
|
728 throw new IOException("Cannot start shell on this channel (" + c.getReasonClosed() + ")");
|
|
729
|
|
730 sm = new PacketSessionStartShell(c.remoteID, true);
|
|
731 c.successCounter = c.failedCounter = 0;
|
|
732 }
|
|
733
|
|
734 synchronized (c.channelSendLock) {
|
|
735 if (c.closeMessageSent)
|
|
736 throw new IOException("Cannot start shell on this channel (" + c.getReasonClosed() + ")");
|
|
737
|
|
738 tm.sendMessage(sm.getPayload());
|
|
739 }
|
|
740
|
|
741 try {
|
|
742 if (waitForChannelRequestResult(c) == false)
|
|
743 throw new IOException("The server denied the request.");
|
|
744 }
|
|
745 catch (IOException e) {
|
|
746 throw(IOException) new IOException("The shell request failed.").initCause(e);
|
|
747 }
|
|
748 }
|
|
749
|
|
750 public void msgChannelExtendedData(byte[] msg, int msglen) throws IOException {
|
|
751 if (msglen <= 13)
|
|
752 throw new IOException("SSH_MSG_CHANNEL_EXTENDED_DATA message has wrong size (" + msglen + ")");
|
|
753
|
|
754 int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff);
|
|
755 int dataType = ((msg[5] & 0xff) << 24) | ((msg[6] & 0xff) << 16) | ((msg[7] & 0xff) << 8) | (msg[8] & 0xff);
|
|
756 int len = ((msg[9] & 0xff) << 24) | ((msg[10] & 0xff) << 16) | ((msg[11] & 0xff) << 8) | (msg[12] & 0xff);
|
|
757 Channel c = getChannel(id);
|
|
758
|
|
759 if (c == null)
|
|
760 throw new IOException("Unexpected SSH_MSG_CHANNEL_EXTENDED_DATA message for non-existent channel " + id);
|
|
761
|
|
762 if (dataType != Packets.SSH_EXTENDED_DATA_STDERR)
|
|
763 throw new IOException("SSH_MSG_CHANNEL_EXTENDED_DATA message has unknown type (" + dataType + ")");
|
|
764
|
|
765 if (len != (msglen - 13))
|
|
766 throw new IOException("SSH_MSG_CHANNEL_EXTENDED_DATA message has wrong len (calculated " + (msglen - 13)
|
|
767 + ", got " + len + ")");
|
|
768
|
|
769 if (log.isEnabled())
|
|
770 log.log(80, "Got SSH_MSG_CHANNEL_EXTENDED_DATA (channel " + id + ", " + len + ")");
|
|
771
|
|
772 synchronized (c) {
|
|
773 if (c.state == Channel.STATE_CLOSED)
|
|
774 return; // ignore
|
|
775
|
|
776 if (c.state != Channel.STATE_OPEN)
|
|
777 throw new IOException("Got SSH_MSG_CHANNEL_EXTENDED_DATA, but channel is not in correct state ("
|
|
778 + c.state + ")");
|
|
779
|
|
780 if (c.localWindow < len)
|
|
781 throw new IOException("Remote sent too much data, does not fit into window.");
|
|
782
|
|
783 c.localWindow -= len;
|
|
784 System.arraycopy(msg, 13, c.stderrBuffer, c.stderrWritepos, len);
|
|
785 c.stderrWritepos += len;
|
|
786 c.notifyAll();
|
|
787 }
|
|
788 }
|
|
789
|
|
790 /**
|
|
791 * Wait until for a condition.
|
|
792 *
|
|
793 * @param c
|
|
794 * Channel
|
|
795 * @param timeout
|
|
796 * in ms, 0 means no timeout.
|
|
797 * @param condition_mask
|
|
798 * minimum event mask
|
|
799 * @return all current events
|
|
800 *
|
|
801 */
|
|
802 public int waitForCondition(Channel c, long timeout, int condition_mask) {
|
|
803 long end_time = 0;
|
|
804 boolean end_time_set = false;
|
|
805
|
|
806 synchronized (c) {
|
|
807 while (true) {
|
|
808 int current_cond = 0;
|
|
809 int stdoutAvail = c.stdoutWritepos - c.stdoutReadpos;
|
|
810 int stderrAvail = c.stderrWritepos - c.stderrReadpos;
|
|
811
|
|
812 if (stdoutAvail > 0)
|
|
813 current_cond = current_cond | ChannelCondition.STDOUT_DATA;
|
|
814
|
|
815 if (stderrAvail > 0)
|
|
816 current_cond = current_cond | ChannelCondition.STDERR_DATA;
|
|
817
|
|
818 if (c.EOF)
|
|
819 current_cond = current_cond | ChannelCondition.EOF;
|
|
820
|
|
821 if (c.getExitStatus() != null)
|
|
822 current_cond = current_cond | ChannelCondition.EXIT_STATUS;
|
|
823
|
|
824 if (c.getExitSignal() != null)
|
|
825 current_cond = current_cond | ChannelCondition.EXIT_SIGNAL;
|
|
826
|
|
827 if (c.state == Channel.STATE_CLOSED)
|
|
828 return current_cond | ChannelCondition.CLOSED | ChannelCondition.EOF;
|
|
829
|
|
830 if ((current_cond & condition_mask) != 0)
|
|
831 return current_cond;
|
|
832
|
|
833 if (timeout > 0) {
|
|
834 if (!end_time_set) {
|
|
835 end_time = System.currentTimeMillis() + timeout;
|
|
836 end_time_set = true;
|
|
837 }
|
|
838 else {
|
|
839 timeout = end_time - System.currentTimeMillis();
|
|
840
|
|
841 if (timeout <= 0)
|
|
842 return current_cond | ChannelCondition.TIMEOUT;
|
|
843 }
|
|
844 }
|
|
845
|
|
846 try {
|
|
847 if (timeout > 0)
|
|
848 c.wait(timeout);
|
|
849 else
|
|
850 c.wait();
|
|
851 }
|
|
852 catch (InterruptedException e) {
|
|
853 }
|
|
854 }
|
|
855 }
|
|
856 }
|
|
857
|
|
858 public int getAvailable(Channel c, boolean extended) throws IOException {
|
|
859 synchronized (c) {
|
|
860 int avail;
|
|
861
|
|
862 if (extended)
|
|
863 avail = c.stderrWritepos - c.stderrReadpos;
|
|
864 else
|
|
865 avail = c.stdoutWritepos - c.stdoutReadpos;
|
|
866
|
|
867 return ((avail > 0) ? avail : (c.EOF ? -1 : 0));
|
|
868 }
|
|
869 }
|
|
870
|
|
871 public int getChannelData(Channel c, boolean extended, byte[] target, int off, int len) throws IOException {
|
|
872 int copylen = 0;
|
|
873 int increment = 0;
|
|
874 int remoteID = 0;
|
|
875 int localID = 0;
|
|
876
|
|
877 synchronized (c) {
|
|
878 int stdoutAvail = 0;
|
|
879 int stderrAvail = 0;
|
|
880
|
|
881 while (true) {
|
|
882 /*
|
|
883 * Data available? We have to return remaining data even if the
|
|
884 * channel is already closed.
|
|
885 */
|
|
886 stdoutAvail = c.stdoutWritepos - c.stdoutReadpos;
|
|
887 stderrAvail = c.stderrWritepos - c.stderrReadpos;
|
|
888
|
|
889 if ((!extended) && (stdoutAvail != 0))
|
|
890 break;
|
|
891
|
|
892 if ((extended) && (stderrAvail != 0))
|
|
893 break;
|
|
894
|
|
895 /* Do not wait if more data will never arrive (EOF or CLOSED) */
|
|
896
|
|
897 if ((c.EOF) || (c.state != Channel.STATE_OPEN))
|
|
898 return -1;
|
|
899
|
|
900 try {
|
|
901 c.wait();
|
|
902 }
|
|
903 catch (InterruptedException ignore) {
|
|
904 }
|
|
905 }
|
|
906
|
|
907 /* OK, there is some data. Return it. */
|
|
908
|
|
909 if (!extended) {
|
|
910 copylen = (stdoutAvail > len) ? len : stdoutAvail;
|
|
911 System.arraycopy(c.stdoutBuffer, c.stdoutReadpos, target, off, copylen);
|
|
912 c.stdoutReadpos += copylen;
|
|
913
|
|
914 if (c.stdoutReadpos != c.stdoutWritepos)
|
|
915 System.arraycopy(c.stdoutBuffer, c.stdoutReadpos, c.stdoutBuffer, 0, c.stdoutWritepos
|
|
916 - c.stdoutReadpos);
|
|
917
|
|
918 c.stdoutWritepos -= c.stdoutReadpos;
|
|
919 c.stdoutReadpos = 0;
|
|
920 }
|
|
921 else {
|
|
922 copylen = (stderrAvail > len) ? len : stderrAvail;
|
|
923 System.arraycopy(c.stderrBuffer, c.stderrReadpos, target, off, copylen);
|
|
924 c.stderrReadpos += copylen;
|
|
925
|
|
926 if (c.stderrReadpos != c.stderrWritepos)
|
|
927 System.arraycopy(c.stderrBuffer, c.stderrReadpos, c.stderrBuffer, 0, c.stderrWritepos
|
|
928 - c.stderrReadpos);
|
|
929
|
|
930 c.stderrWritepos -= c.stderrReadpos;
|
|
931 c.stderrReadpos = 0;
|
|
932 }
|
|
933
|
|
934 if (c.state != Channel.STATE_OPEN)
|
|
935 return copylen;
|
|
936
|
|
937 if (c.localWindow < ((Channel.CHANNEL_BUFFER_SIZE + 1) / 2)) {
|
|
938 int minFreeSpace = Math.min(Channel.CHANNEL_BUFFER_SIZE - c.stdoutWritepos, Channel.CHANNEL_BUFFER_SIZE
|
|
939 - c.stderrWritepos);
|
|
940 increment = minFreeSpace - c.localWindow;
|
|
941 c.localWindow = minFreeSpace;
|
|
942 }
|
|
943
|
|
944 remoteID = c.remoteID; /* read while holding the lock */
|
|
945 localID = c.localID; /* read while holding the lock */
|
|
946 }
|
|
947
|
|
948 /*
|
|
949 * If a consumer reads stdout and stdin in parallel, we may end up with
|
|
950 * sending two msgWindowAdjust messages. Luckily, it
|
|
951 * does not matter in which order they arrive at the server.
|
|
952 */
|
|
953
|
|
954 if (increment > 0) {
|
|
955 if (log.isEnabled())
|
|
956 log.log(80, "Sending SSH_MSG_CHANNEL_WINDOW_ADJUST (channel " + localID + ", " + increment + ")");
|
|
957
|
|
958 synchronized (c.channelSendLock) {
|
|
959 byte[] msg = c.msgWindowAdjust;
|
|
960 msg[0] = Packets.SSH_MSG_CHANNEL_WINDOW_ADJUST;
|
|
961 msg[1] = (byte)(remoteID >> 24);
|
|
962 msg[2] = (byte)(remoteID >> 16);
|
|
963 msg[3] = (byte)(remoteID >> 8);
|
|
964 msg[4] = (byte)(remoteID);
|
|
965 msg[5] = (byte)(increment >> 24);
|
|
966 msg[6] = (byte)(increment >> 16);
|
|
967 msg[7] = (byte)(increment >> 8);
|
|
968 msg[8] = (byte)(increment);
|
|
969
|
|
970 if (c.closeMessageSent == false)
|
|
971 tm.sendMessage(msg);
|
|
972 }
|
|
973 }
|
|
974
|
|
975 return copylen;
|
|
976 }
|
|
977
|
|
978 public void msgChannelData(byte[] msg, int msglen) throws IOException {
|
|
979 if (msglen <= 9)
|
|
980 throw new IOException("SSH_MSG_CHANNEL_DATA message has wrong size (" + msglen + ")");
|
|
981
|
|
982 int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff);
|
|
983 int len = ((msg[5] & 0xff) << 24) | ((msg[6] & 0xff) << 16) | ((msg[7] & 0xff) << 8) | (msg[8] & 0xff);
|
|
984 Channel c = getChannel(id);
|
|
985
|
|
986 if (c == null)
|
|
987 throw new IOException("Unexpected SSH_MSG_CHANNEL_DATA message for non-existent channel " + id);
|
|
988
|
|
989 if (len != (msglen - 9))
|
|
990 throw new IOException("SSH_MSG_CHANNEL_DATA message has wrong len (calculated " + (msglen - 9) + ", got "
|
|
991 + len + ")");
|
|
992
|
|
993 if (log.isEnabled())
|
|
994 log.log(80, "Got SSH_MSG_CHANNEL_DATA (channel " + id + ", " + len + ")");
|
|
995
|
|
996 synchronized (c) {
|
|
997 if (c.state == Channel.STATE_CLOSED)
|
|
998 return; // ignore
|
|
999
|
|
1000 if (c.state != Channel.STATE_OPEN)
|
|
1001 throw new IOException("Got SSH_MSG_CHANNEL_DATA, but channel is not in correct state (" + c.state + ")");
|
|
1002
|
|
1003 if (c.localWindow < len)
|
|
1004 throw new IOException("Remote sent too much data, does not fit into window.");
|
|
1005
|
|
1006 c.localWindow -= len;
|
|
1007 System.arraycopy(msg, 9, c.stdoutBuffer, c.stdoutWritepos, len);
|
|
1008 c.stdoutWritepos += len;
|
|
1009 c.notifyAll();
|
|
1010 }
|
|
1011 }
|
|
1012
|
|
1013 public void msgChannelWindowAdjust(byte[] msg, int msglen) throws IOException {
|
|
1014 if (msglen != 9)
|
|
1015 throw new IOException("SSH_MSG_CHANNEL_WINDOW_ADJUST message has wrong size (" + msglen + ")");
|
|
1016
|
|
1017 int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff);
|
|
1018 int windowChange = ((msg[5] & 0xff) << 24) | ((msg[6] & 0xff) << 16) | ((msg[7] & 0xff) << 8) | (msg[8] & 0xff);
|
|
1019 Channel c = getChannel(id);
|
|
1020
|
|
1021 if (c == null)
|
|
1022 throw new IOException("Unexpected SSH_MSG_CHANNEL_WINDOW_ADJUST message for non-existent channel " + id);
|
|
1023
|
|
1024 synchronized (c) {
|
|
1025 final long huge = 0xFFFFffffL; /* 2^32 - 1 */
|
|
1026 c.remoteWindow += (windowChange & huge); /* avoid sign extension */
|
|
1027
|
|
1028 /* TODO - is this a good heuristic? */
|
|
1029
|
|
1030 if ((c.remoteWindow > huge))
|
|
1031 c.remoteWindow = huge;
|
|
1032
|
|
1033 c.notifyAll();
|
|
1034 }
|
|
1035
|
|
1036 if (log.isEnabled())
|
|
1037 log.log(80, "Got SSH_MSG_CHANNEL_WINDOW_ADJUST (channel " + id + ", " + windowChange + ")");
|
|
1038 }
|
|
1039
|
|
1040 public void msgChannelOpen(byte[] msg, int msglen) throws IOException {
|
|
1041 TypesReader tr = new TypesReader(msg, 0, msglen);
|
|
1042 tr.readByte(); // skip packet type
|
|
1043 String channelType = tr.readString();
|
|
1044 int remoteID = tr.readUINT32(); /* sender channel */
|
|
1045 int remoteWindow = tr.readUINT32(); /* initial window size */
|
|
1046 int remoteMaxPacketSize = tr.readUINT32(); /* maximum packet size */
|
|
1047
|
|
1048 if ("x11".equals(channelType)) {
|
|
1049 synchronized (x11_magic_cookies) {
|
|
1050 /* If we did not request X11 forwarding, then simply ignore this bogus request. */
|
|
1051 if (x11_magic_cookies.size() == 0) {
|
|
1052 PacketChannelOpenFailure pcof = new PacketChannelOpenFailure(remoteID,
|
|
1053 Packets.SSH_OPEN_ADMINISTRATIVELY_PROHIBITED, "X11 forwarding not activated", "");
|
|
1054 tm.sendAsynchronousMessage(pcof.getPayload());
|
|
1055
|
|
1056 if (log.isEnabled())
|
|
1057 log.log(20, "Unexpected X11 request, denying it!");
|
|
1058
|
|
1059 return;
|
|
1060 }
|
|
1061 }
|
|
1062
|
|
1063 String remoteOriginatorAddress = tr.readString();
|
|
1064 int remoteOriginatorPort = tr.readUINT32();
|
|
1065 Channel c = new Channel(this);
|
|
1066
|
|
1067 synchronized (c) {
|
|
1068 c.remoteID = remoteID;
|
|
1069 c.remoteWindow = remoteWindow & 0xFFFFffffL; /* properly convert UINT32 to long */
|
|
1070 c.remoteMaxPacketSize = remoteMaxPacketSize;
|
|
1071 c.localID = addChannel(c);
|
|
1072 }
|
|
1073
|
|
1074 /*
|
|
1075 * The open confirmation message will be sent from another thread
|
|
1076 */
|
|
1077 RemoteX11AcceptThread rxat = new RemoteX11AcceptThread(c, remoteOriginatorAddress, remoteOriginatorPort);
|
|
1078 rxat.setDaemon(true);
|
|
1079 rxat.start();
|
|
1080 return;
|
|
1081 }
|
|
1082
|
|
1083 if ("forwarded-tcpip".equals(channelType)) {
|
|
1084 String remoteConnectedAddress = tr.readString(); /* address that was connected */
|
|
1085 int remoteConnectedPort = tr.readUINT32(); /* port that was connected */
|
|
1086 String remoteOriginatorAddress = tr.readString(); /* originator IP address */
|
|
1087 int remoteOriginatorPort = tr.readUINT32(); /* originator port */
|
|
1088 RemoteForwardingData rfd = null;
|
|
1089
|
|
1090 synchronized (remoteForwardings) {
|
|
1091 rfd = remoteForwardings.get(Integer.valueOf(remoteConnectedPort));
|
|
1092 }
|
|
1093
|
|
1094 if (rfd == null) {
|
|
1095 PacketChannelOpenFailure pcof = new PacketChannelOpenFailure(remoteID,
|
|
1096 Packets.SSH_OPEN_ADMINISTRATIVELY_PROHIBITED,
|
|
1097 "No thanks, unknown port in forwarded-tcpip request", "");
|
|
1098 /* Always try to be polite. */
|
|
1099 tm.sendAsynchronousMessage(pcof.getPayload());
|
|
1100
|
|
1101 if (log.isEnabled())
|
|
1102 log.log(20, "Unexpected forwarded-tcpip request, denying it!");
|
|
1103
|
|
1104 return;
|
|
1105 }
|
|
1106
|
|
1107 Channel c = new Channel(this);
|
|
1108
|
|
1109 synchronized (c) {
|
|
1110 c.remoteID = remoteID;
|
|
1111 c.remoteWindow = remoteWindow & 0xFFFFffffL; /* convert UINT32 to long */
|
|
1112 c.remoteMaxPacketSize = remoteMaxPacketSize;
|
|
1113 c.localID = addChannel(c);
|
|
1114 }
|
|
1115
|
|
1116 /*
|
|
1117 * The open confirmation message will be sent from another thread.
|
|
1118 */
|
|
1119 RemoteAcceptThread rat = new RemoteAcceptThread(c, remoteConnectedAddress, remoteConnectedPort,
|
|
1120 remoteOriginatorAddress, remoteOriginatorPort, rfd.targetAddress, rfd.targetPort);
|
|
1121 rat.setDaemon(true);
|
|
1122 rat.start();
|
|
1123 return;
|
|
1124 }
|
|
1125
|
|
1126 if ("auth-agent@openssh.com".equals(channelType)) {
|
|
1127 Channel c = new Channel(this);
|
|
1128
|
|
1129 synchronized (c) {
|
|
1130 c.remoteID = remoteID;
|
|
1131 c.remoteWindow = remoteWindow & 0xFFFFffffL; /* properly convert UINT32 to long */
|
|
1132 c.remoteMaxPacketSize = remoteMaxPacketSize;
|
|
1133 c.localID = addChannel(c);
|
|
1134 }
|
|
1135
|
|
1136 AuthAgentForwardThread aat = new AuthAgentForwardThread(c, authAgent);
|
|
1137 aat.setDaemon(true);
|
|
1138 aat.start();
|
|
1139 return;
|
|
1140 }
|
|
1141
|
|
1142 /* Tell the server that we have no idea what it is talking about */
|
|
1143 PacketChannelOpenFailure pcof = new PacketChannelOpenFailure(remoteID, Packets.SSH_OPEN_UNKNOWN_CHANNEL_TYPE,
|
|
1144 "Unknown channel type", "");
|
|
1145 tm.sendAsynchronousMessage(pcof.getPayload());
|
|
1146
|
|
1147 if (log.isEnabled())
|
|
1148 log.log(20, "The peer tried to open an unsupported channel type (" + channelType + ")");
|
|
1149 }
|
|
1150
|
|
1151 public void msgChannelRequest(byte[] msg, int msglen) throws IOException {
|
|
1152 TypesReader tr = new TypesReader(msg, 0, msglen);
|
|
1153 tr.readByte(); // skip packet type
|
|
1154 int id = tr.readUINT32();
|
|
1155 Channel c = getChannel(id);
|
|
1156
|
|
1157 if (c == null)
|
|
1158 throw new IOException("Unexpected SSH_MSG_CHANNEL_REQUEST message for non-existent channel " + id);
|
|
1159
|
|
1160 String type = tr.readString("US-ASCII");
|
|
1161 boolean wantReply = tr.readBoolean();
|
|
1162
|
|
1163 if (log.isEnabled())
|
|
1164 log.log(80, "Got SSH_MSG_CHANNEL_REQUEST (channel " + id + ", '" + type + "')");
|
|
1165
|
|
1166 if (type.equals("exit-status")) {
|
|
1167 if (wantReply != false)
|
|
1168 throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message, 'want reply' is true");
|
|
1169
|
|
1170 int exit_status = tr.readUINT32();
|
|
1171
|
|
1172 if (tr.remain() != 0)
|
|
1173 throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message");
|
|
1174
|
|
1175 synchronized (c) {
|
|
1176 c.exit_status = Integer.valueOf(exit_status);
|
|
1177 c.notifyAll();
|
|
1178 }
|
|
1179
|
|
1180 if (log.isEnabled())
|
|
1181 log.log(50, "Got EXIT STATUS (channel " + id + ", status " + exit_status + ")");
|
|
1182
|
|
1183 return;
|
|
1184 }
|
|
1185
|
|
1186 if (type.equals("exit-signal")) {
|
|
1187 if (wantReply != false)
|
|
1188 throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message, 'want reply' is true");
|
|
1189
|
|
1190 String signame = tr.readString("US-ASCII");
|
|
1191 tr.readBoolean();
|
|
1192 tr.readString();
|
|
1193 tr.readString();
|
|
1194
|
|
1195 if (tr.remain() != 0)
|
|
1196 throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message");
|
|
1197
|
|
1198 synchronized (c) {
|
|
1199 c.exit_signal = signame;
|
|
1200 c.notifyAll();
|
|
1201 }
|
|
1202
|
|
1203 if (log.isEnabled())
|
|
1204 log.log(50, "Got EXIT SIGNAL (channel " + id + ", signal " + signame + ")");
|
|
1205
|
|
1206 return;
|
|
1207 }
|
|
1208
|
|
1209 /* We simply ignore unknown channel requests, however, if the server wants a reply,
|
|
1210 * then we signal that we have no idea what it is about.
|
|
1211 */
|
|
1212
|
|
1213 if (wantReply) {
|
|
1214 byte[] reply = new byte[5];
|
|
1215 reply[0] = Packets.SSH_MSG_CHANNEL_FAILURE;
|
|
1216 reply[1] = (byte)(c.remoteID >> 24);
|
|
1217 reply[2] = (byte)(c.remoteID >> 16);
|
|
1218 reply[3] = (byte)(c.remoteID >> 8);
|
|
1219 reply[4] = (byte)(c.remoteID);
|
|
1220 tm.sendAsynchronousMessage(reply);
|
|
1221 }
|
|
1222
|
|
1223 if (log.isEnabled())
|
|
1224 log.log(50, "Channel request '" + type + "' is not known, ignoring it");
|
|
1225 }
|
|
1226
|
|
1227 public void msgChannelEOF(byte[] msg, int msglen) throws IOException {
|
|
1228 if (msglen != 5)
|
|
1229 throw new IOException("SSH_MSG_CHANNEL_EOF message has wrong size (" + msglen + ")");
|
|
1230
|
|
1231 int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff);
|
|
1232 Channel c = getChannel(id);
|
|
1233
|
|
1234 if (c == null)
|
|
1235 throw new IOException("Unexpected SSH_MSG_CHANNEL_EOF message for non-existent channel " + id);
|
|
1236
|
|
1237 synchronized (c) {
|
|
1238 c.EOF = true;
|
|
1239 c.notifyAll();
|
|
1240 }
|
|
1241
|
|
1242 if (log.isEnabled())
|
|
1243 log.log(50, "Got SSH_MSG_CHANNEL_EOF (channel " + id + ")");
|
|
1244 }
|
|
1245
|
|
1246 public void msgChannelClose(byte[] msg, int msglen) throws IOException {
|
|
1247 if (msglen != 5)
|
|
1248 throw new IOException("SSH_MSG_CHANNEL_CLOSE message has wrong size (" + msglen + ")");
|
|
1249
|
|
1250 int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff);
|
|
1251 Channel c = getChannel(id);
|
|
1252
|
|
1253 if (c == null)
|
|
1254 throw new IOException("Unexpected SSH_MSG_CHANNEL_CLOSE message for non-existent channel " + id);
|
|
1255
|
|
1256 synchronized (c) {
|
|
1257 c.EOF = true;
|
|
1258 c.state = Channel.STATE_CLOSED;
|
|
1259 c.setReasonClosed("Close requested by remote");
|
|
1260 c.closeMessageRecv = true;
|
|
1261 removeChannel(c.localID);
|
|
1262 c.notifyAll();
|
|
1263 }
|
|
1264
|
|
1265 if (log.isEnabled())
|
|
1266 log.log(50, "Got SSH_MSG_CHANNEL_CLOSE (channel " + id + ")");
|
|
1267 }
|
|
1268
|
|
1269 public void msgChannelSuccess(byte[] msg, int msglen) throws IOException {
|
|
1270 if (msglen != 5)
|
|
1271 throw new IOException("SSH_MSG_CHANNEL_SUCCESS message has wrong size (" + msglen + ")");
|
|
1272
|
|
1273 int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff);
|
|
1274 Channel c = getChannel(id);
|
|
1275
|
|
1276 if (c == null)
|
|
1277 throw new IOException("Unexpected SSH_MSG_CHANNEL_SUCCESS message for non-existent channel " + id);
|
|
1278
|
|
1279 synchronized (c) {
|
|
1280 c.successCounter++;
|
|
1281 c.notifyAll();
|
|
1282 }
|
|
1283
|
|
1284 if (log.isEnabled())
|
|
1285 log.log(80, "Got SSH_MSG_CHANNEL_SUCCESS (channel " + id + ")");
|
|
1286 }
|
|
1287
|
|
1288 public void msgChannelFailure(byte[] msg, int msglen) throws IOException {
|
|
1289 if (msglen != 5)
|
|
1290 throw new IOException("SSH_MSG_CHANNEL_FAILURE message has wrong size (" + msglen + ")");
|
|
1291
|
|
1292 int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff);
|
|
1293 Channel c = getChannel(id);
|
|
1294
|
|
1295 if (c == null)
|
|
1296 throw new IOException("Unexpected SSH_MSG_CHANNEL_FAILURE message for non-existent channel " + id);
|
|
1297
|
|
1298 synchronized (c) {
|
|
1299 c.failedCounter++;
|
|
1300 c.notifyAll();
|
|
1301 }
|
|
1302
|
|
1303 if (log.isEnabled())
|
|
1304 log.log(50, "Got SSH_MSG_CHANNEL_FAILURE (channel " + id + ")");
|
|
1305 }
|
|
1306
|
|
1307 public void msgChannelOpenConfirmation(byte[] msg, int msglen) throws IOException {
|
|
1308 PacketChannelOpenConfirmation sm = new PacketChannelOpenConfirmation(msg, 0, msglen);
|
|
1309 Channel c = getChannel(sm.recipientChannelID);
|
|
1310
|
|
1311 if (c == null)
|
|
1312 throw new IOException("Unexpected SSH_MSG_CHANNEL_OPEN_CONFIRMATION message for non-existent channel "
|
|
1313 + sm.recipientChannelID);
|
|
1314
|
|
1315 synchronized (c) {
|
|
1316 if (c.state != Channel.STATE_OPENING)
|
|
1317 throw new IOException("Unexpected SSH_MSG_CHANNEL_OPEN_CONFIRMATION message for channel "
|
|
1318 + sm.recipientChannelID);
|
|
1319
|
|
1320 c.remoteID = sm.senderChannelID;
|
|
1321 c.remoteWindow = sm.initialWindowSize & 0xFFFFffffL; /* convert UINT32 to long */
|
|
1322 c.remoteMaxPacketSize = sm.maxPacketSize;
|
|
1323 c.state = Channel.STATE_OPEN;
|
|
1324 c.notifyAll();
|
|
1325 }
|
|
1326
|
|
1327 if (log.isEnabled())
|
|
1328 log.log(50, "Got SSH_MSG_CHANNEL_OPEN_CONFIRMATION (channel " + sm.recipientChannelID + " / remote: "
|
|
1329 + sm.senderChannelID + ")");
|
|
1330 }
|
|
1331
|
|
1332 public void msgChannelOpenFailure(byte[] msg, int msglen) throws IOException {
|
|
1333 if (msglen < 5)
|
|
1334 throw new IOException("SSH_MSG_CHANNEL_OPEN_FAILURE message has wrong size (" + msglen + ")");
|
|
1335
|
|
1336 TypesReader tr = new TypesReader(msg, 0, msglen);
|
|
1337 tr.readByte(); // skip packet type
|
|
1338 int id = tr.readUINT32(); /* sender channel */
|
|
1339 Channel c = getChannel(id);
|
|
1340
|
|
1341 if (c == null)
|
|
1342 throw new IOException("Unexpected SSH_MSG_CHANNEL_OPEN_FAILURE message for non-existent channel " + id);
|
|
1343
|
|
1344 int reasonCode = tr.readUINT32();
|
|
1345 String description = tr.readString("UTF-8");
|
|
1346 String reasonCodeSymbolicName = null;
|
|
1347
|
|
1348 switch (reasonCode) {
|
|
1349 case 1:
|
|
1350 reasonCodeSymbolicName = "SSH_OPEN_ADMINISTRATIVELY_PROHIBITED";
|
|
1351 break;
|
|
1352
|
|
1353 case 2:
|
|
1354 reasonCodeSymbolicName = "SSH_OPEN_CONNECT_FAILED";
|
|
1355 break;
|
|
1356
|
|
1357 case 3:
|
|
1358 reasonCodeSymbolicName = "SSH_OPEN_UNKNOWN_CHANNEL_TYPE";
|
|
1359 break;
|
|
1360
|
|
1361 case 4:
|
|
1362 reasonCodeSymbolicName = "SSH_OPEN_RESOURCE_SHORTAGE";
|
|
1363 break;
|
|
1364
|
|
1365 default:
|
|
1366 reasonCodeSymbolicName = "UNKNOWN REASON CODE (" + reasonCode + ")";
|
|
1367 }
|
|
1368
|
|
1369 StringBuffer descriptionBuffer = new StringBuffer();
|
|
1370 descriptionBuffer.append(description);
|
|
1371
|
|
1372 for (int i = 0; i < descriptionBuffer.length(); i++) {
|
|
1373 char cc = descriptionBuffer.charAt(i);
|
|
1374
|
|
1375 if ((cc >= 32) && (cc <= 126))
|
|
1376 continue;
|
|
1377
|
|
1378 descriptionBuffer.setCharAt(i, '\uFFFD');
|
|
1379 }
|
|
1380
|
|
1381 synchronized (c) {
|
|
1382 c.EOF = true;
|
|
1383 c.state = Channel.STATE_CLOSED;
|
|
1384 c.setReasonClosed("The server refused to open the channel (" + reasonCodeSymbolicName + ", '"
|
|
1385 + descriptionBuffer.toString() + "')");
|
|
1386 c.notifyAll();
|
|
1387 }
|
|
1388
|
|
1389 if (log.isEnabled())
|
|
1390 log.log(50, "Got SSH_MSG_CHANNEL_OPEN_FAILURE (channel " + id + ")");
|
|
1391 }
|
|
1392
|
|
1393 public void msgGlobalRequest(byte[] msg, int msglen) throws IOException {
|
|
1394 /* Currently we do not support any kind of global request */
|
|
1395 TypesReader tr = new TypesReader(msg, 0, msglen);
|
|
1396 tr.readByte(); // skip packet type
|
|
1397 String requestName = tr.readString();
|
|
1398 boolean wantReply = tr.readBoolean();
|
|
1399
|
|
1400 if (wantReply) {
|
|
1401 byte[] reply_failure = new byte[1];
|
|
1402 reply_failure[0] = Packets.SSH_MSG_REQUEST_FAILURE;
|
|
1403 tm.sendAsynchronousMessage(reply_failure);
|
|
1404 }
|
|
1405
|
|
1406 /* We do not clean up the requestName String - that is OK for debug */
|
|
1407
|
|
1408 if (log.isEnabled())
|
|
1409 log.log(80, "Got SSH_MSG_GLOBAL_REQUEST (" + requestName + ")");
|
|
1410 }
|
|
1411
|
|
1412 public void msgGlobalSuccess() throws IOException {
|
|
1413 synchronized (channels) {
|
|
1414 globalSuccessCounter++;
|
|
1415 channels.notifyAll();
|
|
1416 }
|
|
1417
|
|
1418 if (log.isEnabled())
|
|
1419 log.log(80, "Got SSH_MSG_REQUEST_SUCCESS");
|
|
1420 }
|
|
1421
|
|
1422 public void msgGlobalFailure() throws IOException {
|
|
1423 synchronized (channels) {
|
|
1424 globalFailedCounter++;
|
|
1425 channels.notifyAll();
|
|
1426 }
|
|
1427
|
|
1428 if (log.isEnabled())
|
|
1429 log.log(80, "Got SSH_MSG_REQUEST_FAILURE");
|
|
1430 }
|
|
1431
|
|
1432 public void handleMessage(byte[] msg, int msglen) throws IOException {
|
|
1433 if (msg == null) {
|
|
1434 if (log.isEnabled())
|
|
1435 log.log(50, "HandleMessage: got shutdown");
|
|
1436
|
|
1437 synchronized (listenerThreads) {
|
|
1438 for (int i = 0; i < listenerThreads.size(); i++) {
|
|
1439 IChannelWorkerThread lat = listenerThreads.elementAt(i);
|
|
1440 lat.stopWorking();
|
|
1441 }
|
|
1442
|
|
1443 listenerThreadsAllowed = false;
|
|
1444 }
|
|
1445
|
|
1446 synchronized (channels) {
|
|
1447 shutdown = true;
|
|
1448
|
|
1449 for (int i = 0; i < channels.size(); i++) {
|
|
1450 Channel c = channels.elementAt(i);
|
|
1451
|
|
1452 synchronized (c) {
|
|
1453 c.EOF = true;
|
|
1454 c.state = Channel.STATE_CLOSED;
|
|
1455 c.setReasonClosed("The connection is being shutdown");
|
|
1456 c.closeMessageRecv = true; /*
|
|
1457 * You never know, perhaps
|
|
1458 * we are waiting for a
|
|
1459 * pending close message
|
|
1460 * from the server...
|
|
1461 */
|
|
1462 c.notifyAll();
|
|
1463 }
|
|
1464 }
|
|
1465
|
|
1466 /* Works with J2ME */
|
|
1467 channels.setSize(0);
|
|
1468 channels.trimToSize();
|
|
1469 channels.notifyAll(); /* Notify global response waiters */
|
|
1470 return;
|
|
1471 }
|
|
1472 }
|
|
1473
|
|
1474 switch (msg[0]) {
|
|
1475 case Packets.SSH_MSG_CHANNEL_OPEN_CONFIRMATION:
|
|
1476 msgChannelOpenConfirmation(msg, msglen);
|
|
1477 break;
|
|
1478
|
|
1479 case Packets.SSH_MSG_CHANNEL_WINDOW_ADJUST:
|
|
1480 msgChannelWindowAdjust(msg, msglen);
|
|
1481 break;
|
|
1482
|
|
1483 case Packets.SSH_MSG_CHANNEL_DATA:
|
|
1484 msgChannelData(msg, msglen);
|
|
1485 break;
|
|
1486
|
|
1487 case Packets.SSH_MSG_CHANNEL_EXTENDED_DATA:
|
|
1488 msgChannelExtendedData(msg, msglen);
|
|
1489 break;
|
|
1490
|
|
1491 case Packets.SSH_MSG_CHANNEL_REQUEST:
|
|
1492 msgChannelRequest(msg, msglen);
|
|
1493 break;
|
|
1494
|
|
1495 case Packets.SSH_MSG_CHANNEL_EOF:
|
|
1496 msgChannelEOF(msg, msglen);
|
|
1497 break;
|
|
1498
|
|
1499 case Packets.SSH_MSG_CHANNEL_OPEN:
|
|
1500 msgChannelOpen(msg, msglen);
|
|
1501 break;
|
|
1502
|
|
1503 case Packets.SSH_MSG_CHANNEL_CLOSE:
|
|
1504 msgChannelClose(msg, msglen);
|
|
1505 break;
|
|
1506
|
|
1507 case Packets.SSH_MSG_CHANNEL_SUCCESS:
|
|
1508 msgChannelSuccess(msg, msglen);
|
|
1509 break;
|
|
1510
|
|
1511 case Packets.SSH_MSG_CHANNEL_FAILURE:
|
|
1512 msgChannelFailure(msg, msglen);
|
|
1513 break;
|
|
1514
|
|
1515 case Packets.SSH_MSG_CHANNEL_OPEN_FAILURE:
|
|
1516 msgChannelOpenFailure(msg, msglen);
|
|
1517 break;
|
|
1518
|
|
1519 case Packets.SSH_MSG_GLOBAL_REQUEST:
|
|
1520 msgGlobalRequest(msg, msglen);
|
|
1521 break;
|
|
1522
|
|
1523 case Packets.SSH_MSG_REQUEST_SUCCESS:
|
|
1524 msgGlobalSuccess();
|
|
1525 break;
|
|
1526
|
|
1527 case Packets.SSH_MSG_REQUEST_FAILURE:
|
|
1528 msgGlobalFailure();
|
|
1529 break;
|
|
1530
|
|
1531 default:
|
|
1532 throw new IOException("Cannot handle unknown channel message " + (msg[0] & 0xff));
|
|
1533 }
|
|
1534 }
|
|
1535 }
|