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