]>
Commit | Line | Data |
---|---|---|
1 | /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ | |
2 | /* | |
3 | Copyright (c) 2002-2010 ymnk, JCraft,Inc. All rights reserved. | |
4 | ||
5 | Redistribution and use in source and binary forms, with or without | |
6 | modification, are permitted provided that the following conditions are met: | |
7 | ||
8 | 1. Redistributions of source code must retain the above copyright notice, | |
9 | this list of conditions and the following disclaimer. | |
10 | ||
11 | 2. Redistributions in binary form must reproduce the above copyright | |
12 | notice, this list of conditions and the following disclaimer in | |
13 | the documentation and/or other materials provided with the distribution. | |
14 | ||
15 | 3. The names of the authors may not be used to endorse or promote products | |
16 | derived from this software without specific prior written permission. | |
17 | ||
18 | THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, | |
19 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND | |
20 | FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, | |
21 | INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, | |
22 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, | |
24 | OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |
25 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | |
26 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, | |
27 | EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
28 | */ | |
29 | ||
30 | package com.jcraft.jsch; | |
31 | ||
32 | import java.io.PipedInputStream; | |
33 | import java.io.PipedOutputStream; | |
34 | import java.io.InputStream; | |
35 | import java.io.OutputStream; | |
36 | import java.io.IOException; | |
37 | ||
38 | ||
39 | public abstract class Channel implements Runnable{ | |
40 | ||
41 | static final int SSH_MSG_CHANNEL_OPEN_CONFIRMATION= 91; | |
42 | static final int SSH_MSG_CHANNEL_OPEN_FAILURE= 92; | |
43 | static final int SSH_MSG_CHANNEL_WINDOW_ADJUST= 93; | |
44 | ||
45 | static final int SSH_OPEN_ADMINISTRATIVELY_PROHIBITED= 1; | |
46 | static final int SSH_OPEN_CONNECT_FAILED= 2; | |
47 | static final int SSH_OPEN_UNKNOWN_CHANNEL_TYPE= 3; | |
48 | static final int SSH_OPEN_RESOURCE_SHORTAGE= 4; | |
49 | ||
50 | static int index=0; | |
51 | private static java.util.Vector pool=new java.util.Vector(); | |
52 | static Channel getChannel(String type){ | |
53 | if(type.equals("session")){ | |
54 | return new ChannelSession(); | |
55 | } | |
56 | if(type.equals("shell")){ | |
57 | return new ChannelShell(); | |
58 | } | |
59 | if(type.equals("exec")){ | |
60 | return new ChannelExec(); | |
61 | } | |
62 | if(type.equals("x11")){ | |
63 | return new ChannelX11(); | |
64 | } | |
65 | if(type.equals("auth-agent@openssh.com")){ | |
66 | return new ChannelAgentForwarding(); | |
67 | } | |
68 | if(type.equals("direct-tcpip")){ | |
69 | return new ChannelDirectTCPIP(); | |
70 | } | |
71 | if(type.equals("forwarded-tcpip")){ | |
72 | return new ChannelForwardedTCPIP(); | |
73 | } | |
74 | if(type.equals("sftp")){ | |
75 | return new ChannelSftp(); | |
76 | } | |
77 | if(type.equals("subsystem")){ | |
78 | return new ChannelSubsystem(); | |
79 | } | |
80 | return null; | |
81 | } | |
82 | static Channel getChannel(int id, Session session){ | |
83 | synchronized(pool){ | |
84 | for(int i=0; i<pool.size(); i++){ | |
85 | Channel c=(Channel)(pool.elementAt(i)); | |
86 | if(c.id==id && c.session==session) return c; | |
87 | } | |
88 | } | |
89 | return null; | |
90 | } | |
91 | static void del(Channel c){ | |
92 | synchronized(pool){ | |
93 | pool.removeElement(c); | |
94 | } | |
95 | } | |
96 | ||
97 | int id; | |
98 | int recipient=-1; | |
99 | byte[] type=Util.str2byte("foo"); | |
100 | int lwsize_max=0x100000; | |
101 | //int lwsize_max=0x20000; // 32*1024*4 | |
102 | int lwsize=lwsize_max; // local initial window size | |
103 | int lmpsize=0x4000; // local maximum packet size | |
104 | //int lmpsize=0x8000; // local maximum packet size | |
105 | ||
106 | long rwsize=0; // remote initial window size | |
107 | int rmpsize=0; // remote maximum packet size | |
108 | ||
109 | IO io=null; | |
110 | Thread thread=null; | |
111 | ||
112 | boolean eof_local=false; | |
113 | boolean eof_remote=false; | |
114 | ||
115 | boolean close=false; | |
116 | boolean connected=false; | |
117 | ||
118 | int exitstatus=-1; | |
119 | ||
120 | int reply=0; | |
121 | int connectTimeout=0; | |
122 | ||
123 | private Session session; | |
124 | ||
125 | int notifyme=0; | |
126 | ||
127 | Channel(){ | |
128 | synchronized(pool){ | |
129 | id=index++; | |
130 | pool.addElement(this); | |
131 | } | |
132 | } | |
133 | void setRecipient(int foo){ | |
134 | this.recipient=foo; | |
135 | } | |
136 | int getRecipient(){ | |
137 | return recipient; | |
138 | } | |
139 | ||
140 | void init() throws JSchException { | |
141 | } | |
142 | ||
143 | public void connect() throws JSchException{ | |
144 | connect(0); | |
145 | } | |
146 | ||
147 | public void connect(int connectTimeout) throws JSchException{ | |
148 | Session _session=getSession(); | |
149 | if(!_session.isConnected()){ | |
150 | throw new JSchException("session is down"); | |
151 | } | |
152 | this.connectTimeout=connectTimeout; | |
153 | try{ | |
154 | Buffer buf=new Buffer(100); | |
155 | Packet packet=new Packet(buf); | |
156 | // send | |
157 | // byte SSH_MSG_CHANNEL_OPEN(90) | |
158 | // string channel type // | |
159 | // uint32 sender channel // 0 | |
160 | // uint32 initial window size // 0x100000(65536) | |
161 | // uint32 maxmum packet size // 0x4000(16384) | |
162 | packet.reset(); | |
163 | buf.putByte((byte)90); | |
164 | buf.putString(this.type); | |
165 | buf.putInt(this.id); | |
166 | buf.putInt(this.lwsize); | |
167 | buf.putInt(this.lmpsize); | |
168 | _session.write(packet); | |
169 | int retry=1000; | |
170 | long start=System.currentTimeMillis(); | |
171 | long timeout=connectTimeout; | |
172 | while(this.getRecipient()==-1 && | |
173 | _session.isConnected() && | |
174 | retry>0){ | |
175 | if(timeout>0L){ | |
176 | if((System.currentTimeMillis()-start)>timeout){ | |
177 | retry=0; | |
178 | continue; | |
179 | } | |
180 | } | |
181 | try{Thread.sleep(50);}catch(Exception ee){} | |
182 | retry--; | |
183 | } | |
184 | if(!_session.isConnected()){ | |
185 | throw new JSchException("session is down"); | |
186 | } | |
187 | if(retry==0){ | |
188 | throw new JSchException("channel is not opened."); | |
189 | } | |
190 | ||
191 | /* | |
192 | * At the failure in opening the channel on the sshd, | |
193 | * 'SSH_MSG_CHANNEL_OPEN_FAILURE' will be sent from sshd and it will | |
194 | * be processed in Session#run(). | |
195 | */ | |
196 | if(this.isClosed()){ | |
197 | throw new JSchException("channel is not opened."); | |
198 | } | |
199 | connected=true; | |
200 | start(); | |
201 | } | |
202 | catch(Exception e){ | |
203 | connected=false; | |
204 | disconnect(); | |
205 | if(e instanceof JSchException) | |
206 | throw (JSchException)e; | |
207 | throw new JSchException(e.toString(), e); | |
208 | } | |
209 | } | |
210 | ||
211 | public void setXForwarding(boolean foo){ | |
212 | } | |
213 | ||
214 | public void start() throws JSchException{} | |
215 | ||
216 | public boolean isEOF() {return eof_remote;} | |
217 | ||
218 | void getData(Buffer buf){ | |
219 | setRecipient(buf.getInt()); | |
220 | setRemoteWindowSize(buf.getUInt()); | |
221 | setRemotePacketSize(buf.getInt()); | |
222 | } | |
223 | ||
224 | public void setInputStream(InputStream in){ | |
225 | io.setInputStream(in, false); | |
226 | } | |
227 | public void setInputStream(InputStream in, boolean dontclose){ | |
228 | io.setInputStream(in, dontclose); | |
229 | } | |
230 | public void setOutputStream(OutputStream out){ | |
231 | io.setOutputStream(out, false); | |
232 | } | |
233 | public void setOutputStream(OutputStream out, boolean dontclose){ | |
234 | io.setOutputStream(out, dontclose); | |
235 | } | |
236 | public void setExtOutputStream(OutputStream out){ | |
237 | io.setExtOutputStream(out, false); | |
238 | } | |
239 | public void setExtOutputStream(OutputStream out, boolean dontclose){ | |
240 | io.setExtOutputStream(out, dontclose); | |
241 | } | |
242 | public InputStream getInputStream() throws IOException { | |
243 | PipedInputStream in= | |
244 | new MyPipedInputStream( | |
245 | 32*1024 // this value should be customizable. | |
246 | ); | |
247 | io.setOutputStream(new PassiveOutputStream(in), false); | |
248 | return in; | |
249 | } | |
250 | public InputStream getExtInputStream() throws IOException { | |
251 | PipedInputStream in= | |
252 | new MyPipedInputStream( | |
253 | 32*1024 // this value should be customizable. | |
254 | ); | |
255 | io.setExtOutputStream(new PassiveOutputStream(in), false); | |
256 | return in; | |
257 | } | |
258 | public OutputStream getOutputStream() throws IOException { | |
259 | /* | |
260 | PipedOutputStream out=new PipedOutputStream(); | |
261 | io.setInputStream(new PassiveInputStream(out | |
262 | , 32*1024 | |
263 | ), false); | |
264 | return out; | |
265 | */ | |
266 | ||
267 | final Channel channel=this; | |
268 | OutputStream out=new OutputStream(){ | |
269 | private int dataLen=0; | |
270 | private Buffer buffer=null; | |
271 | private Packet packet=null; | |
272 | private boolean closed=false; | |
273 | private synchronized void init() throws java.io.IOException{ | |
274 | buffer=new Buffer(rmpsize); | |
275 | packet=new Packet(buffer); | |
276 | ||
277 | byte[] _buf=buffer.buffer; | |
278 | if(_buf.length-(14+0)-32-20<=0){ | |
279 | buffer=null; | |
280 | packet=null; | |
281 | throw new IOException("failed to initialize the channel."); | |
282 | } | |
283 | ||
284 | } | |
285 | byte[] b=new byte[1]; | |
286 | public void write(int w) throws java.io.IOException{ | |
287 | b[0]=(byte)w; | |
288 | write(b, 0, 1); | |
289 | } | |
290 | public void write(byte[] buf, int s, int l) throws java.io.IOException{ | |
291 | if(packet==null){ | |
292 | init(); | |
293 | } | |
294 | ||
295 | if(closed){ | |
296 | throw new java.io.IOException("Already closed"); | |
297 | } | |
298 | ||
299 | byte[] _buf=buffer.buffer; | |
300 | int _bufl=_buf.length; | |
301 | while(l>0){ | |
302 | int _l=l; | |
303 | if(l>_bufl-(14+dataLen)-32-20){ | |
304 | _l=_bufl-(14+dataLen)-32-20; | |
305 | } | |
306 | ||
307 | if(_l<=0){ | |
308 | flush(); | |
309 | continue; | |
310 | } | |
311 | ||
312 | System.arraycopy(buf, s, _buf, 14+dataLen, _l); | |
313 | dataLen+=_l; | |
314 | s+=_l; | |
315 | l-=_l; | |
316 | } | |
317 | } | |
318 | ||
319 | public void flush() throws java.io.IOException{ | |
320 | if(closed){ | |
321 | throw new java.io.IOException("Already closed"); | |
322 | } | |
323 | if(dataLen==0) | |
324 | return; | |
325 | packet.reset(); | |
326 | buffer.putByte((byte)Session.SSH_MSG_CHANNEL_DATA); | |
327 | buffer.putInt(recipient); | |
328 | buffer.putInt(dataLen); | |
329 | buffer.skip(dataLen); | |
330 | try{ | |
331 | int foo=dataLen; | |
332 | dataLen=0; | |
333 | getSession().write(packet, channel, foo); | |
334 | } | |
335 | catch(Exception e){ | |
336 | close(); | |
337 | throw new java.io.IOException(e.toString()); | |
338 | } | |
339 | ||
340 | } | |
341 | public void close() throws java.io.IOException{ | |
342 | if(packet==null){ | |
343 | try{ | |
344 | init(); | |
345 | } | |
346 | catch(java.io.IOException e){ | |
347 | // close should be finished silently. | |
348 | return; | |
349 | } | |
350 | } | |
351 | if(closed){ | |
352 | return; | |
353 | } | |
354 | if(dataLen>0){ | |
355 | flush(); | |
356 | } | |
357 | channel.eof(); | |
358 | closed=true; | |
359 | } | |
360 | }; | |
361 | return out; | |
362 | } | |
363 | ||
364 | class MyPipedInputStream extends PipedInputStream{ | |
365 | MyPipedInputStream() throws IOException{ super(); } | |
366 | MyPipedInputStream(int size) throws IOException{ | |
367 | super(); | |
368 | buffer=new byte[size]; | |
369 | } | |
370 | MyPipedInputStream(PipedOutputStream out) throws IOException{ super(out); } | |
371 | MyPipedInputStream(PipedOutputStream out, int size) throws IOException{ | |
372 | super(out); | |
373 | buffer=new byte[size]; | |
374 | } | |
375 | } | |
376 | void setLocalWindowSizeMax(int foo){ this.lwsize_max=foo; } | |
377 | void setLocalWindowSize(int foo){ this.lwsize=foo; } | |
378 | void setLocalPacketSize(int foo){ this.lmpsize=foo; } | |
379 | synchronized void setRemoteWindowSize(long foo){ this.rwsize=foo; } | |
380 | synchronized void addRemoteWindowSize(int foo){ | |
381 | this.rwsize+=foo; | |
382 | if(notifyme>0) | |
383 | notifyAll(); | |
384 | } | |
385 | void setRemotePacketSize(int foo){ this.rmpsize=foo; } | |
386 | ||
387 | public void run(){ | |
388 | } | |
389 | ||
390 | void write(byte[] foo) throws IOException { | |
391 | write(foo, 0, foo.length); | |
392 | } | |
393 | void write(byte[] foo, int s, int l) throws IOException { | |
394 | try{ | |
395 | io.put(foo, s, l); | |
396 | }catch(NullPointerException e){} | |
397 | } | |
398 | void write_ext(byte[] foo, int s, int l) throws IOException { | |
399 | try{ | |
400 | io.put_ext(foo, s, l); | |
401 | }catch(NullPointerException e){} | |
402 | } | |
403 | ||
404 | void eof_remote(){ | |
405 | eof_remote=true; | |
406 | try{ | |
407 | io.out_close(); | |
408 | } | |
409 | catch(NullPointerException e){} | |
410 | } | |
411 | ||
412 | void eof(){ | |
413 | if(eof_local)return; | |
414 | eof_local=true; | |
415 | ||
416 | try{ | |
417 | Buffer buf=new Buffer(100); | |
418 | Packet packet=new Packet(buf); | |
419 | packet.reset(); | |
420 | buf.putByte((byte)Session.SSH_MSG_CHANNEL_EOF); | |
421 | buf.putInt(getRecipient()); | |
422 | synchronized(this){ | |
423 | if(!close) | |
424 | getSession().write(packet); | |
425 | } | |
426 | } | |
427 | catch(Exception e){ | |
428 | //System.err.println("Channel.eof"); | |
429 | //e.printStackTrace(); | |
430 | } | |
431 | /* | |
432 | if(!isConnected()){ disconnect(); } | |
433 | */ | |
434 | } | |
435 | ||
436 | /* | |
437 | http://www1.ietf.org/internet-drafts/draft-ietf-secsh-connect-24.txt | |
438 | ||
439 | 5.3 Closing a Channel | |
440 | When a party will no longer send more data to a channel, it SHOULD | |
441 | send SSH_MSG_CHANNEL_EOF. | |
442 | ||
443 | byte SSH_MSG_CHANNEL_EOF | |
444 | uint32 recipient_channel | |
445 | ||
446 | No explicit response is sent to this message. However, the | |
447 | application may send EOF to whatever is at the other end of the | |
448 | channel. Note that the channel remains open after this message, and | |
449 | more data may still be sent in the other direction. This message | |
450 | does not consume window space and can be sent even if no window space | |
451 | is available. | |
452 | ||
453 | When either party wishes to terminate the channel, it sends | |
454 | SSH_MSG_CHANNEL_CLOSE. Upon receiving this message, a party MUST | |
455 | send back a SSH_MSG_CHANNEL_CLOSE unless it has already sent this | |
456 | message for the channel. The channel is considered closed for a | |
457 | party when it has both sent and received SSH_MSG_CHANNEL_CLOSE, and | |
458 | the party may then reuse the channel number. A party MAY send | |
459 | SSH_MSG_CHANNEL_CLOSE without having sent or received | |
460 | SSH_MSG_CHANNEL_EOF. | |
461 | ||
462 | byte SSH_MSG_CHANNEL_CLOSE | |
463 | uint32 recipient_channel | |
464 | ||
465 | This message does not consume window space and can be sent even if no | |
466 | window space is available. | |
467 | ||
468 | It is recommended that any data sent before this message is delivered | |
469 | to the actual destination, if possible. | |
470 | */ | |
471 | ||
472 | void close(){ | |
473 | if(close)return; | |
474 | close=true; | |
475 | eof_local=eof_remote=true; | |
476 | ||
477 | try{ | |
478 | Buffer buf=new Buffer(100); | |
479 | Packet packet=new Packet(buf); | |
480 | packet.reset(); | |
481 | buf.putByte((byte)Session.SSH_MSG_CHANNEL_CLOSE); | |
482 | buf.putInt(getRecipient()); | |
483 | synchronized(this){ | |
484 | getSession().write(packet); | |
485 | } | |
486 | } | |
487 | catch(Exception e){ | |
488 | //e.printStackTrace(); | |
489 | } | |
490 | } | |
491 | public boolean isClosed(){ | |
492 | return close; | |
493 | } | |
494 | static void disconnect(Session session){ | |
495 | Channel[] channels=null; | |
496 | int count=0; | |
497 | synchronized(pool){ | |
498 | channels=new Channel[pool.size()]; | |
499 | for(int i=0; i<pool.size(); i++){ | |
500 | try{ | |
501 | Channel c=((Channel)(pool.elementAt(i))); | |
502 | if(c.session==session){ | |
503 | channels[count++]=c; | |
504 | } | |
505 | } | |
506 | catch(Exception e){ | |
507 | } | |
508 | } | |
509 | } | |
510 | for(int i=0; i<count; i++){ | |
511 | channels[i].disconnect(); | |
512 | } | |
513 | } | |
514 | ||
515 | public void disconnect(){ | |
516 | //System.err.println(this+":disconnect "+io+" "+connected); | |
517 | //Thread.dumpStack(); | |
518 | ||
519 | try{ | |
520 | ||
521 | synchronized(this){ | |
522 | if(!connected){ | |
523 | return; | |
524 | } | |
525 | connected=false; | |
526 | } | |
527 | ||
528 | close(); | |
529 | ||
530 | eof_remote=eof_local=true; | |
531 | ||
532 | thread=null; | |
533 | ||
534 | try{ | |
535 | if(io!=null){ | |
536 | io.close(); | |
537 | } | |
538 | } | |
539 | catch(Exception e){ | |
540 | //e.printStackTrace(); | |
541 | } | |
542 | // io=null; | |
543 | } | |
544 | finally{ | |
545 | Channel.del(this); | |
546 | } | |
547 | } | |
548 | ||
549 | public boolean isConnected(){ | |
550 | Session _session=this.session; | |
551 | if(_session!=null){ | |
552 | return _session.isConnected() && connected; | |
553 | } | |
554 | return false; | |
555 | } | |
556 | ||
557 | public void sendSignal(String signal) throws Exception { | |
558 | RequestSignal request=new RequestSignal(); | |
559 | request.setSignal(signal); | |
560 | request.request(getSession(), this); | |
561 | } | |
562 | ||
563 | // public String toString(){ | |
564 | // return "Channel: type="+new String(type)+",id="+id+",recipient="+recipient+",window_size="+window_size+",packet_size="+packet_size; | |
565 | // } | |
566 | ||
567 | /* | |
568 | class OutputThread extends Thread{ | |
569 | Channel c; | |
570 | OutputThread(Channel c){ this.c=c;} | |
571 | public void run(){c.output_thread();} | |
572 | } | |
573 | */ | |
574 | ||
575 | class PassiveInputStream extends MyPipedInputStream{ | |
576 | PipedOutputStream out; | |
577 | PassiveInputStream(PipedOutputStream out, int size) throws IOException{ | |
578 | super(out, size); | |
579 | this.out=out; | |
580 | } | |
581 | PassiveInputStream(PipedOutputStream out) throws IOException{ | |
582 | super(out); | |
583 | this.out=out; | |
584 | } | |
585 | public void close() throws IOException{ | |
586 | if(out!=null){ | |
587 | this.out.close(); | |
588 | } | |
589 | out=null; | |
590 | } | |
591 | } | |
592 | class PassiveOutputStream extends PipedOutputStream{ | |
593 | PassiveOutputStream(PipedInputStream in) throws IOException{ | |
594 | super(in); | |
595 | } | |
596 | } | |
597 | ||
598 | void setExitStatus(int status){ exitstatus=status; } | |
599 | public int getExitStatus(){ return exitstatus; } | |
600 | ||
601 | void setSession(Session session){ | |
602 | this.session=session; | |
603 | } | |
604 | ||
605 | public Session getSession() throws JSchException{ | |
606 | Session _session=session; | |
607 | if(_session==null){ | |
608 | throw new JSchException("session is not available"); | |
609 | } | |
610 | return _session; | |
611 | } | |
612 | public int getId(){ return id; } | |
613 | ||
614 | protected void sendOpenConfirmation() throws Exception{ | |
615 | Buffer buf=new Buffer(100); | |
616 | Packet packet=new Packet(buf); | |
617 | packet.reset(); | |
618 | buf.putByte((byte)SSH_MSG_CHANNEL_OPEN_CONFIRMATION); | |
619 | buf.putInt(getRecipient()); | |
620 | buf.putInt(id); | |
621 | buf.putInt(lwsize); | |
622 | buf.putInt(lmpsize); | |
623 | getSession().write(packet); | |
624 | } | |
625 | ||
626 | protected void sendOpenFailure(int reasoncode){ | |
627 | try{ | |
628 | Buffer buf=new Buffer(100); | |
629 | Packet packet=new Packet(buf); | |
630 | packet.reset(); | |
631 | buf.putByte((byte)SSH_MSG_CHANNEL_OPEN_FAILURE); | |
632 | buf.putInt(getRecipient()); | |
633 | buf.putInt(reasoncode); | |
634 | buf.putString(Util.str2byte("open failed")); | |
635 | buf.putString(Util.empty); | |
636 | getSession().write(packet); | |
637 | } | |
638 | catch(Exception e){ | |
639 | } | |
640 | } | |
641 | } |