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