1 /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ 2 /* 3 Copyright (c) 2002-2018 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(); getChannel(String type)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 } getChannel(int id, Session session)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 } del(Channel c)91 static void del(Channel c){ 92 synchronized(pool){ 93 pool.removeElement(c); 94 } 95 } 96 97 int id; 98 volatile int recipient=-1; 99 protected byte[] type=Util.str2byte("foo"); 100 volatile int lwsize_max=0x100000; 101 volatile int lwsize=lwsize_max; // local initial window size 102 volatile int lmpsize=0x4000; // local maximum packet size 103 104 volatile long rwsize=0; // remote initial window size 105 volatile int rmpsize=0; // remote maximum packet size 106 107 IO io=null; 108 Thread thread=null; 109 110 volatile boolean eof_local=false; 111 volatile boolean eof_remote=false; 112 113 volatile boolean close=false; 114 volatile boolean connected=false; 115 volatile boolean open_confirmation=false; 116 117 volatile int exitstatus=-1; 118 119 volatile int reply=0; 120 volatile int connectTimeout=0; 121 122 private Session session; 123 124 int notifyme=0; 125 Channel()126 Channel(){ 127 synchronized(pool){ 128 id=index++; 129 pool.addElement(this); 130 } 131 } setRecipient(int foo)132 synchronized void setRecipient(int foo){ 133 this.recipient=foo; 134 if(notifyme>0) 135 notifyAll(); 136 } getRecipient()137 int getRecipient(){ 138 return recipient; 139 } 140 init()141 void init() throws JSchException { 142 } 143 connect()144 public void connect() throws JSchException{ 145 connect(0); 146 } 147 connect(int connectTimeout)148 public void connect(int connectTimeout) throws JSchException{ 149 this.connectTimeout=connectTimeout; 150 try{ 151 sendChannelOpen(); 152 start(); 153 } 154 catch(Exception e){ 155 connected=false; 156 disconnect(); 157 if(e instanceof JSchException) 158 throw (JSchException)e; 159 throw new JSchException(e.toString(), e); 160 } 161 } 162 setXForwarding(boolean foo)163 public void setXForwarding(boolean foo){ 164 } 165 start()166 public void start() throws JSchException{} 167 isEOF()168 public boolean isEOF() {return eof_remote;} 169 getData(Buffer buf)170 void getData(Buffer buf){ 171 setRecipient(buf.getInt()); 172 setRemoteWindowSize(buf.getUInt()); 173 setRemotePacketSize(buf.getInt()); 174 } 175 setInputStream(InputStream in)176 public void setInputStream(InputStream in){ 177 io.setInputStream(in, false); 178 } setInputStream(InputStream in, boolean dontclose)179 public void setInputStream(InputStream in, boolean dontclose){ 180 io.setInputStream(in, dontclose); 181 } setOutputStream(OutputStream out)182 public void setOutputStream(OutputStream out){ 183 io.setOutputStream(out, false); 184 } setOutputStream(OutputStream out, boolean dontclose)185 public void setOutputStream(OutputStream out, boolean dontclose){ 186 io.setOutputStream(out, dontclose); 187 } setExtOutputStream(OutputStream out)188 public void setExtOutputStream(OutputStream out){ 189 io.setExtOutputStream(out, false); 190 } setExtOutputStream(OutputStream out, boolean dontclose)191 public void setExtOutputStream(OutputStream out, boolean dontclose){ 192 io.setExtOutputStream(out, dontclose); 193 } getInputStream()194 public InputStream getInputStream() throws IOException { 195 int max_input_buffer_size = 32*1024; 196 try { 197 max_input_buffer_size = 198 Integer.parseInt(getSession().getConfig("max_input_buffer_size")); 199 } 200 catch(Exception e){} 201 PipedInputStream in = 202 new MyPipedInputStream( 203 32*1024, // this value should be customizable. 204 max_input_buffer_size 205 ); 206 boolean resizable = 32*1024<max_input_buffer_size; 207 io.setOutputStream(new PassiveOutputStream(in, resizable), false); 208 return in; 209 } 210 public InputStream getExtInputStream() throws IOException { 211 int max_input_buffer_size = 32*1024; 212 try { 213 max_input_buffer_size = 214 Integer.parseInt(getSession().getConfig("max_input_buffer_size")); 215 } 216 catch(Exception e){} 217 PipedInputStream in = 218 new MyPipedInputStream( 219 32*1024, // this value should be customizable. 220 max_input_buffer_size 221 ); 222 boolean resizable = 32*1024<max_input_buffer_size; 223 io.setExtOutputStream(new PassiveOutputStream(in, resizable), false); 224 return in; 225 } 226 public OutputStream getOutputStream() throws IOException { 227 228 final Channel channel=this; 229 OutputStream out=new OutputStream(){ 230 private int dataLen=0; 231 private Buffer buffer=null; 232 private Packet packet=null; 233 private boolean closed=false; 234 private synchronized void init() throws java.io.IOException{ 235 buffer=new Buffer(rmpsize); 236 packet=new Packet(buffer); 237 238 byte[] _buf=buffer.buffer; 239 if(_buf.length-(14+0)-Session.buffer_margin<=0){ 240 buffer=null; 241 packet=null; 242 throw new IOException("failed to initialize the channel."); 243 } 244 245 } 246 byte[] b=new byte[1]; 247 public void write(int w) throws java.io.IOException{ 248 b[0]=(byte)w; 249 write(b, 0, 1); 250 } 251 public void write(byte[] buf, int s, int l) throws java.io.IOException{ 252 if(packet==null){ 253 init(); 254 } 255 256 if(closed){ 257 throw new java.io.IOException("Already closed"); 258 } 259 260 byte[] _buf=buffer.buffer; 261 int _bufl=_buf.length; 262 while(l>0){ 263 int _l=l; 264 if(l>_bufl-(14+dataLen)-Session.buffer_margin){ 265 _l=_bufl-(14+dataLen)-Session.buffer_margin; 266 } 267 268 if(_l<=0){ 269 flush(); 270 continue; 271 } 272 273 System.arraycopy(buf, s, _buf, 14+dataLen, _l); 274 dataLen+=_l; 275 s+=_l; 276 l-=_l; 277 } 278 } 279 280 public void flush() throws java.io.IOException{ 281 if(closed){ 282 throw new java.io.IOException("Already closed"); 283 } 284 if(dataLen==0) 285 return; 286 packet.reset(); 287 buffer.putByte((byte)Session.SSH_MSG_CHANNEL_DATA); 288 buffer.putInt(recipient); 289 buffer.putInt(dataLen); 290 buffer.skip(dataLen); 291 try{ 292 int foo=dataLen; 293 dataLen=0; 294 synchronized(channel){ 295 if(!channel.close) 296 getSession().write(packet, channel, foo); 297 } 298 } 299 catch(Exception e){ 300 close(); 301 throw new java.io.IOException(e.toString()); 302 } 303 304 } 305 public void close() throws java.io.IOException{ 306 if(packet==null){ 307 try{ 308 init(); 309 } 310 catch(java.io.IOException e){ 311 // close should be finished silently. 312 return; 313 } 314 } 315 if(closed){ 316 return; 317 } 318 if(dataLen>0){ 319 flush(); 320 } 321 channel.eof(); 322 closed=true; 323 } 324 }; 325 return out; 326 } 327 328 class MyPipedInputStream extends PipedInputStream{ 329 private int BUFFER_SIZE = 1024; 330 private int max_buffer_size = BUFFER_SIZE; 331 MyPipedInputStream() throws IOException{ super(); } 332 MyPipedInputStream(int size) throws IOException{ 333 super(); 334 buffer=new byte[size]; 335 BUFFER_SIZE = size; 336 max_buffer_size = size; 337 } 338 MyPipedInputStream(int size, int max_buffer_size) throws IOException{ 339 this(size); 340 this.max_buffer_size = max_buffer_size; 341 } 342 MyPipedInputStream(PipedOutputStream out) throws IOException{ super(out); } 343 MyPipedInputStream(PipedOutputStream out, int size) throws IOException{ 344 super(out); 345 buffer=new byte[size]; 346 BUFFER_SIZE=size; 347 } 348 349 /* 350 * TODO: We should have our own Piped[I/O]Stream implementation. 351 * Before accepting data, JDK's PipedInputStream will check the existence of 352 * reader thread, and if it is not alive, the stream will be closed. 353 * That behavior may cause the problem if multiple threads make access to it. 354 */ 355 public synchronized void updateReadSide() throws IOException { 356 if(available() != 0){ // not empty 357 return; 358 } 359 in = 0; 360 out = 0; 361 buffer[in++] = 0; 362 read(); 363 } 364 365 private int freeSpace(){ 366 int size = 0; 367 if(out < in) { 368 size = buffer.length-in; 369 } 370 else if(in < out){ 371 if(in == -1) size = buffer.length; 372 else size = out - in; 373 } 374 return size; 375 } 376 synchronized void checkSpace(int len) throws IOException { 377 int size = freeSpace(); 378 if(size<len){ 379 int datasize=buffer.length-size; 380 int foo = buffer.length; 381 while((foo - datasize) < len){ 382 foo*=2; 383 } 384 385 if(foo > max_buffer_size){ 386 foo = max_buffer_size; 387 } 388 if((foo - datasize) < len) return; 389 390 byte[] tmp = new byte[foo]; 391 if(out < in) { 392 System.arraycopy(buffer, 0, tmp, 0, buffer.length); 393 } 394 else if(in < out){ 395 if(in == -1) { 396 } 397 else { 398 System.arraycopy(buffer, 0, tmp, 0, in); 399 System.arraycopy(buffer, out, 400 tmp, tmp.length-(buffer.length-out), 401 (buffer.length-out)); 402 out = tmp.length-(buffer.length-out); 403 } 404 } 405 else if(in == out){ 406 System.arraycopy(buffer, 0, tmp, 0, buffer.length); 407 in=buffer.length; 408 } 409 buffer=tmp; 410 } 411 else if(buffer.length == size && size > BUFFER_SIZE) { 412 int i = size/2; 413 if(i<BUFFER_SIZE) i = BUFFER_SIZE; 414 byte[] tmp = new byte[i]; 415 buffer=tmp; 416 } 417 } 418 } 419 void setLocalWindowSizeMax(int foo){ this.lwsize_max=foo; } 420 void setLocalWindowSize(int foo){ this.lwsize=foo; } 421 void setLocalPacketSize(int foo){ this.lmpsize=foo; } 422 synchronized void setRemoteWindowSize(long foo){ this.rwsize=foo; } 423 synchronized void addRemoteWindowSize(long foo){ 424 this.rwsize+=foo; 425 if(notifyme>0) 426 notifyAll(); 427 } 428 void setRemotePacketSize(int foo){ this.rmpsize=foo; } 429 430 public void run(){ 431 } 432 433 void write(byte[] foo) throws IOException { 434 write(foo, 0, foo.length); 435 } 436 void write(byte[] foo, int s, int l) throws IOException { 437 try{ 438 io.put(foo, s, l); 439 }catch(NullPointerException e){} 440 } 441 void write_ext(byte[] foo, int s, int l) throws IOException { 442 try{ 443 io.put_ext(foo, s, l); 444 }catch(NullPointerException e){} 445 } 446 447 void eof_remote(){ 448 eof_remote=true; 449 try{ 450 io.out_close(); 451 } 452 catch(NullPointerException e){} 453 } 454 455 void eof(){ 456 if(eof_local)return; 457 eof_local=true; 458 459 int i = getRecipient(); 460 if(i == -1) return; 461 462 try{ 463 Buffer buf=new Buffer(100); 464 Packet packet=new Packet(buf); 465 packet.reset(); 466 buf.putByte((byte)Session.SSH_MSG_CHANNEL_EOF); 467 buf.putInt(i); 468 synchronized(this){ 469 if(!close) 470 getSession().write(packet); 471 } 472 } 473 catch(Exception e){ 474 //System.err.println("Channel.eof"); 475 //e.printStackTrace(); 476 } 477 /* 478 if(!isConnected()){ disconnect(); } 479 */ 480 } 481 482 /* 483 http://www1.ietf.org/internet-drafts/draft-ietf-secsh-connect-24.txt 484 485 5.3 Closing a Channel 486 When a party will no longer send more data to a channel, it SHOULD 487 send SSH_MSG_CHANNEL_EOF. 488 489 byte SSH_MSG_CHANNEL_EOF 490 uint32 recipient_channel 491 492 No explicit response is sent to this message. However, the 493 application may send EOF to whatever is at the other end of the 494 channel. Note that the channel remains open after this message, and 495 more data may still be sent in the other direction. This message 496 does not consume window space and can be sent even if no window space 497 is available. 498 499 When either party wishes to terminate the channel, it sends 500 SSH_MSG_CHANNEL_CLOSE. Upon receiving this message, a party MUST 501 send back a SSH_MSG_CHANNEL_CLOSE unless it has already sent this 502 message for the channel. The channel is considered closed for a 503 party when it has both sent and received SSH_MSG_CHANNEL_CLOSE, and 504 the party may then reuse the channel number. A party MAY send 505 SSH_MSG_CHANNEL_CLOSE without having sent or received 506 SSH_MSG_CHANNEL_EOF. 507 508 byte SSH_MSG_CHANNEL_CLOSE 509 uint32 recipient_channel 510 511 This message does not consume window space and can be sent even if no 512 window space is available. 513 514 It is recommended that any data sent before this message is delivered 515 to the actual destination, if possible. 516 */ 517 518 void close(){ 519 if(close)return; 520 close=true; 521 eof_local=eof_remote=true; 522 523 int i = getRecipient(); 524 if(i == -1) return; 525 526 try{ 527 Buffer buf=new Buffer(100); 528 Packet packet=new Packet(buf); 529 packet.reset(); 530 buf.putByte((byte)Session.SSH_MSG_CHANNEL_CLOSE); 531 buf.putInt(i); 532 synchronized(this){ 533 getSession().write(packet); 534 } 535 } 536 catch(Exception e){ 537 //e.printStackTrace(); 538 } 539 } 540 public boolean isClosed(){ 541 return close; 542 } 543 static void disconnect(Session session){ 544 Channel[] channels=null; 545 int count=0; 546 synchronized(pool){ 547 channels=new Channel[pool.size()]; 548 for(int i=0; i<pool.size(); i++){ 549 try{ 550 Channel c=((Channel)(pool.elementAt(i))); 551 if(c.session==session){ 552 channels[count++]=c; 553 } 554 } 555 catch(Exception e){ 556 } 557 } 558 } 559 for(int i=0; i<count; i++){ 560 channels[i].disconnect(); 561 } 562 } 563 564 public void disconnect(){ 565 //System.err.println(this+":disconnect "+io+" "+connected); 566 //Thread.dumpStack(); 567 568 try{ 569 570 synchronized(this){ 571 if(!connected){ 572 return; 573 } 574 connected=false; 575 } 576 577 close(); 578 579 eof_remote=eof_local=true; 580 581 thread=null; 582 583 try{ 584 if(io!=null){ 585 io.close(); 586 } 587 } 588 catch(Exception e){ 589 //e.printStackTrace(); 590 } 591 // io=null; 592 } 593 finally{ 594 Channel.del(this); 595 } 596 } 597 598 public boolean isConnected(){ 599 Session _session=this.session; 600 if(_session!=null){ 601 return _session.isConnected() && connected; 602 } 603 return false; 604 } 605 606 public void sendSignal(String signal) throws Exception { 607 RequestSignal request=new RequestSignal(); 608 request.setSignal(signal); 609 request.request(getSession(), this); 610 } 611 612 // public String toString(){ 613 // return "Channel: type="+new String(type)+",id="+id+",recipient="+recipient+",window_size="+window_size+",packet_size="+packet_size; 614 // } 615 616 /* 617 class OutputThread extends Thread{ 618 Channel c; 619 OutputThread(Channel c){ this.c=c;} 620 public void run(){c.output_thread();} 621 } 622 */ 623 624 class PassiveInputStream extends MyPipedInputStream{ 625 PipedOutputStream out; 626 PassiveInputStream(PipedOutputStream out, int size) throws IOException{ 627 super(out, size); 628 this.out=out; 629 } 630 PassiveInputStream(PipedOutputStream out) throws IOException{ 631 super(out); 632 this.out=out; 633 } 634 public void close() throws IOException{ 635 if(out!=null){ 636 this.out.close(); 637 } 638 out=null; 639 } 640 } 641 class PassiveOutputStream extends PipedOutputStream{ 642 private MyPipedInputStream _sink=null; 643 PassiveOutputStream(PipedInputStream in, 644 boolean resizable_buffer) throws IOException{ 645 super(in); 646 if(resizable_buffer && (in instanceof MyPipedInputStream)) { 647 this._sink=(MyPipedInputStream)in; 648 } 649 } 650 public void write(int b) throws IOException { 651 if(_sink != null) { 652 _sink.checkSpace(1); 653 } 654 super.write(b); 655 } 656 public void write(byte[] b, int off, int len) throws IOException { 657 if(_sink != null) { 658 _sink.checkSpace(len); 659 } 660 super.write(b, off, len); 661 } 662 } 663 664 void setExitStatus(int status){ exitstatus=status; } 665 public int getExitStatus(){ return exitstatus; } 666 667 void setSession(Session session){ 668 this.session=session; 669 } 670 671 public Session getSession() throws JSchException{ 672 Session _session=session; 673 if(_session==null){ 674 throw new JSchException("session is not available"); 675 } 676 return _session; 677 } 678 public int getId(){ return id; } 679 680 protected void sendOpenConfirmation() throws Exception{ 681 Buffer buf=new Buffer(100); 682 Packet packet=new Packet(buf); 683 packet.reset(); 684 buf.putByte((byte)SSH_MSG_CHANNEL_OPEN_CONFIRMATION); 685 buf.putInt(getRecipient()); 686 buf.putInt(id); 687 buf.putInt(lwsize); 688 buf.putInt(lmpsize); 689 getSession().write(packet); 690 } 691 692 protected void sendOpenFailure(int reasoncode){ 693 try{ 694 Buffer buf=new Buffer(100); 695 Packet packet=new Packet(buf); 696 packet.reset(); 697 buf.putByte((byte)SSH_MSG_CHANNEL_OPEN_FAILURE); 698 buf.putInt(getRecipient()); 699 buf.putInt(reasoncode); 700 buf.putString(Util.str2byte("open failed")); 701 buf.putString(Util.empty); 702 getSession().write(packet); 703 } 704 catch(Exception e){ 705 } 706 } 707 708 protected Packet genChannelOpenPacket(){ 709 Buffer buf=new Buffer(100); 710 Packet packet=new Packet(buf); 711 // byte SSH_MSG_CHANNEL_OPEN(90) 712 // string channel type // 713 // uint32 sender channel // 0 714 // uint32 initial window size // 0x100000(65536) 715 // uint32 maxmum packet size // 0x4000(16384) 716 packet.reset(); 717 buf.putByte((byte)90); 718 buf.putString(this.type); 719 buf.putInt(this.id); 720 buf.putInt(this.lwsize); 721 buf.putInt(this.lmpsize); 722 return packet; 723 } 724 725 protected void sendChannelOpen() throws Exception { 726 Session _session=getSession(); 727 if(!_session.isConnected()){ 728 throw new JSchException("session is down"); 729 } 730 731 Packet packet = genChannelOpenPacket(); 732 _session.write(packet); 733 734 int retry=2000; 735 long start=System.currentTimeMillis(); 736 long timeout=connectTimeout; 737 if(timeout!=0L) retry = 1; 738 synchronized(this){ 739 while(this.getRecipient()==-1 && 740 _session.isConnected() && 741 retry>0){ 742 if(timeout>0L){ 743 if((System.currentTimeMillis()-start)>timeout){ 744 retry=0; 745 continue; 746 } 747 } 748 try{ 749 long t = timeout==0L ? 10L : timeout; 750 this.notifyme=1; 751 wait(t); 752 } 753 catch(java.lang.InterruptedException e){ 754 } 755 finally{ 756 this.notifyme=0; 757 } 758 retry--; 759 } 760 } 761 if(!_session.isConnected()){ 762 throw new JSchException("session is down"); 763 } 764 if(this.getRecipient()==-1){ // timeout 765 throw new JSchException("channel is not opened."); 766 } 767 if(this.open_confirmation==false){ // SSH_MSG_CHANNEL_OPEN_FAILURE 768 throw new JSchException("channel is not opened."); 769 } 770 connected=true; 771 } 772 } 773