Mercurial > 510Connectbot
comparison src/com/trilead/ssh2/channel/ChannelManager.java @ 0:0ce5cc452d02
initial version
author | Carl Byington <carl@five-ten-sg.com> |
---|---|
date | Thu, 22 May 2014 10:41:19 -0700 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:0ce5cc452d02 |
---|---|
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 } |