1 /* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ 2 /* 3 Copyright (c) 2002-2015 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.InputStream; 33 import java.util.Vector; 34 35 public class JSch{ 36 /** 37 * The version number. 38 */ 39 public static final String VERSION = "0.1.53"; 40 41 static java.util.Hashtable config=new java.util.Hashtable(); 42 static{ 43 config.put("kex", "ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group14-sha1,diffie-hellman-group-exchange-sha256,diffie-hellman-group-exchange-sha1,diffie-hellman-group1-sha1"); 44 config.put("server_host_key", "ssh-rsa,ssh-dss,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521"); 45 config.put("cipher.s2c", 46 "aes128-ctr,aes128-cbc,3des-ctr,3des-cbc,blowfish-cbc,aes192-ctr,aes192-cbc,aes256-ctr,aes256-cbc"); 47 config.put("cipher.c2s", 48 "aes128-ctr,aes128-cbc,3des-ctr,3des-cbc,blowfish-cbc,aes192-ctr,aes192-cbc,aes256-ctr,aes256-cbc"); 49 50 config.put("mac.s2c", "hmac-md5,hmac-sha1,hmac-sha2-256,hmac-sha1-96,hmac-md5-96"); 51 config.put("mac.c2s", "hmac-md5,hmac-sha1,hmac-sha2-256,hmac-sha1-96,hmac-md5-96"); 52 config.put("compression.s2c", "none"); 53 config.put("compression.c2s", "none"); 54 55 config.put("lang.s2c", ""); 56 config.put("lang.c2s", ""); 57 58 config.put("compression_level", "6"); 59 60 config.put("diffie-hellman-group-exchange-sha1", 61 "com.jcraft.jsch.DHGEX"); 62 config.put("diffie-hellman-group1-sha1", 63 "com.jcraft.jsch.DHG1"); 64 config.put("diffie-hellman-group14-sha1", 65 "com.jcraft.jsch.DHG14"); // available since JDK8. 66 config.put("diffie-hellman-group-exchange-sha256", 67 "com.jcraft.jsch.DHGEX256"); // available since JDK1.4.2. 68 // On JDK8, 2048bits will be used. 69 config.put("ecdsa-sha2-nistp256", "com.jcraft.jsch.jce.SignatureECDSA"); 70 config.put("ecdsa-sha2-nistp384", "com.jcraft.jsch.jce.SignatureECDSA"); 71 config.put("ecdsa-sha2-nistp521", "com.jcraft.jsch.jce.SignatureECDSA"); 72 73 config.put("ecdh-sha2-nistp256", "com.jcraft.jsch.DHEC256"); 74 config.put("ecdh-sha2-nistp384", "com.jcraft.jsch.DHEC384"); 75 config.put("ecdh-sha2-nistp521", "com.jcraft.jsch.DHEC521"); 76 77 config.put("ecdh-sha2-nistp", "com.jcraft.jsch.jce.ECDHN"); 78 79 config.put("dh", "com.jcraft.jsch.jce.DH"); 80 config.put("3des-cbc", "com.jcraft.jsch.jce.TripleDESCBC"); 81 config.put("blowfish-cbc", "com.jcraft.jsch.jce.BlowfishCBC"); 82 config.put("hmac-sha1", "com.jcraft.jsch.jce.HMACSHA1"); 83 config.put("hmac-sha1-96", "com.jcraft.jsch.jce.HMACSHA196"); 84 config.put("hmac-sha2-256", "com.jcraft.jsch.jce.HMACSHA256"); 85 // The "hmac-sha2-512" will require the key-length 2048 for DH, 86 // but Sun's JCE has not allowed to use such a long key. 87 //config.put("hmac-sha2-512", "com.jcraft.jsch.jce.HMACSHA512"); 88 config.put("hmac-md5", "com.jcraft.jsch.jce.HMACMD5"); 89 config.put("hmac-md5-96", "com.jcraft.jsch.jce.HMACMD596"); 90 config.put("sha-1", "com.jcraft.jsch.jce.SHA1"); 91 config.put("sha-256", "com.jcraft.jsch.jce.SHA256"); 92 config.put("sha-384", "com.jcraft.jsch.jce.SHA384"); 93 config.put("sha-512", "com.jcraft.jsch.jce.SHA512"); 94 config.put("md5", "com.jcraft.jsch.jce.MD5"); 95 config.put("signature.dss", "com.jcraft.jsch.jce.SignatureDSA"); 96 config.put("signature.rsa", "com.jcraft.jsch.jce.SignatureRSA"); 97 config.put("signature.ecdsa", "com.jcraft.jsch.jce.SignatureECDSA"); 98 config.put("keypairgen.dsa", "com.jcraft.jsch.jce.KeyPairGenDSA"); 99 config.put("keypairgen.rsa", "com.jcraft.jsch.jce.KeyPairGenRSA"); 100 config.put("keypairgen.ecdsa", "com.jcraft.jsch.jce.KeyPairGenECDSA"); 101 config.put("random", "com.jcraft.jsch.jce.Random"); 102 103 config.put("none", "com.jcraft.jsch.CipherNone"); 104 105 config.put("aes128-cbc", "com.jcraft.jsch.jce.AES128CBC"); 106 config.put("aes192-cbc", "com.jcraft.jsch.jce.AES192CBC"); 107 config.put("aes256-cbc", "com.jcraft.jsch.jce.AES256CBC"); 108 109 config.put("aes128-ctr", "com.jcraft.jsch.jce.AES128CTR"); 110 config.put("aes192-ctr", "com.jcraft.jsch.jce.AES192CTR"); 111 config.put("aes256-ctr", "com.jcraft.jsch.jce.AES256CTR"); 112 config.put("3des-ctr", "com.jcraft.jsch.jce.TripleDESCTR"); 113 config.put("arcfour", "com.jcraft.jsch.jce.ARCFOUR"); 114 config.put("arcfour128", "com.jcraft.jsch.jce.ARCFOUR128"); 115 config.put("arcfour256", "com.jcraft.jsch.jce.ARCFOUR256"); 116 117 config.put("userauth.none", "com.jcraft.jsch.UserAuthNone"); 118 config.put("userauth.password", "com.jcraft.jsch.UserAuthPassword"); 119 config.put("userauth.keyboard-interactive", "com.jcraft.jsch.UserAuthKeyboardInteractive"); 120 config.put("userauth.publickey", "com.jcraft.jsch.UserAuthPublicKey"); 121 config.put("userauth.gssapi-with-mic", "com.jcraft.jsch.UserAuthGSSAPIWithMIC"); 122 config.put("gssapi-with-mic.krb5", "com.jcraft.jsch.jgss.GSSContextKrb5"); 123 124 config.put("zlib", "com.jcraft.jsch.jcraft.Compression"); 125 config.put("zlib@openssh.com", "com.jcraft.jsch.jcraft.Compression"); 126 127 config.put("pbkdf", "com.jcraft.jsch.jce.PBKDF"); 128 129 config.put("StrictHostKeyChecking", "ask"); 130 config.put("HashKnownHosts", "no"); 131 132 config.put("PreferredAuthentications", "gssapi-with-mic,publickey,keyboard-interactive,password"); 133 134 config.put("CheckCiphers", "aes256-ctr,aes192-ctr,aes128-ctr,aes256-cbc,aes192-cbc,aes128-cbc,3des-ctr,arcfour,arcfour128,arcfour256"); 135 config.put("CheckKexes", "diffie-hellman-group14-sha1,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521"); 136 config.put("CheckSignatures", "ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521"); 137 138 config.put("MaxAuthTries", "6"); 139 config.put("ClearAllForwardings", "no"); 140 } 141 142 private java.util.Vector sessionPool = new java.util.Vector(); 143 144 private IdentityRepository defaultIdentityRepository = 145 new LocalIdentityRepository(this); 146 147 private IdentityRepository identityRepository = defaultIdentityRepository; 148 149 private ConfigRepository configRepository = null; 150 151 /** 152 * Sets the <code>identityRepository</code>, which will be referred 153 * in the public key authentication. 154 * 155 * @param identityRepository if <code>null</code> is given, 156 * the default repository, which usually refers to ~/.ssh/, will be used. 157 * 158 * @see #getIdentityRepository() 159 */ setIdentityRepository(IdentityRepository identityRepository)160 public synchronized void setIdentityRepository(IdentityRepository identityRepository){ 161 if(identityRepository == null){ 162 this.identityRepository = defaultIdentityRepository; 163 } 164 else{ 165 this.identityRepository = identityRepository; 166 } 167 } 168 getIdentityRepository()169 public synchronized IdentityRepository getIdentityRepository(){ 170 return this.identityRepository; 171 } 172 getConfigRepository()173 public ConfigRepository getConfigRepository() { 174 return this.configRepository; 175 } 176 setConfigRepository(ConfigRepository configRepository)177 public void setConfigRepository(ConfigRepository configRepository) { 178 this.configRepository = configRepository; 179 } 180 181 private HostKeyRepository known_hosts=null; 182 183 private static final Logger DEVNULL=new Logger(){ 184 public boolean isEnabled(int level){return false;} 185 public void log(int level, String message){} 186 }; 187 static Logger logger=DEVNULL; 188 JSch()189 public JSch(){ 190 /* 191 // The JCE of Sun's Java5 on Mac OS X has the resource leak bug 192 // in calculating HMAC, so we need to use our own implementations. 193 try{ 194 String osname=(String)(System.getProperties().get("os.name")); 195 if(osname!=null && osname.equals("Mac OS X")){ 196 config.put("hmac-sha1", "com.jcraft.jsch.jcraft.HMACSHA1"); 197 config.put("hmac-md5", "com.jcraft.jsch.jcraft.HMACMD5"); 198 config.put("hmac-md5-96", "com.jcraft.jsch.jcraft.HMACMD596"); 199 config.put("hmac-sha1-96", "com.jcraft.jsch.jcraft.HMACSHA196"); 200 } 201 } 202 catch(Exception e){ 203 } 204 */ 205 } 206 207 /** 208 * Instantiates the <code>Session</code> object with 209 * <code>host</code>. The user name and port number will be retrieved from 210 * ConfigRepository. If user name is not given, 211 * the system property "user.name" will be referred. 212 * 213 * @param host hostname 214 * 215 * @throws JSchException 216 * if <code>username</code> or <code>host</code> are invalid. 217 * 218 * @return the instance of <code>Session</code> class. 219 * 220 * @see #getSession(String username, String host, int port) 221 * @see com.jcraft.jsch.Session 222 * @see com.jcraft.jsch.ConfigRepository 223 */ getSession(String host)224 public Session getSession(String host) 225 throws JSchException { 226 return getSession(null, host, 22); 227 } 228 229 /** 230 * Instantiates the <code>Session</code> object with 231 * <code>username</code> and <code>host</code>. 232 * The TCP port 22 will be used in making the connection. 233 * Note that the TCP connection must not be established 234 * until Session#connect(). 235 * 236 * @param username user name 237 * @param host hostname 238 * 239 * @throws JSchException 240 * if <code>username</code> or <code>host</code> are invalid. 241 * 242 * @return the instance of <code>Session</code> class. 243 * 244 * @see #getSession(String username, String host, int port) 245 * @see com.jcraft.jsch.Session 246 */ getSession(String username, String host)247 public Session getSession(String username, String host) 248 throws JSchException { 249 return getSession(username, host, 22); 250 } 251 252 /** 253 * Instantiates the <code>Session</code> object with given 254 * <code>username</code>, <code>host</code> and <code>port</code>. 255 * Note that the TCP connection must not be established 256 * until Session#connect(). 257 * 258 * @param username user name 259 * @param host hostname 260 * @param port port number 261 * 262 * @throws JSchException 263 * if <code>username</code> or <code>host</code> are invalid. 264 * 265 * @return the instance of <code>Session</code> class. 266 * 267 * @see #getSession(String username, String host, int port) 268 * @see com.jcraft.jsch.Session 269 */ getSession(String username, String host, int port)270 public Session getSession(String username, String host, int port) throws JSchException { 271 if(host==null){ 272 throw new JSchException("host must not be null."); 273 } 274 Session s = new Session(this, username, host, port); 275 return s; 276 } 277 addSession(Session session)278 protected void addSession(Session session){ 279 synchronized(sessionPool){ 280 sessionPool.addElement(session); 281 } 282 } 283 removeSession(Session session)284 protected boolean removeSession(Session session){ 285 synchronized(sessionPool){ 286 return sessionPool.remove(session); 287 } 288 } 289 290 /** 291 * Sets the hostkey repository. 292 * 293 * @param hkrepo 294 * 295 * @see com.jcraft.jsch.HostKeyRepository 296 * @see com.jcraft.jsch.KnownHosts 297 */ setHostKeyRepository(HostKeyRepository hkrepo)298 public void setHostKeyRepository(HostKeyRepository hkrepo){ 299 known_hosts=hkrepo; 300 } 301 302 /** 303 * Sets the instance of <code>KnownHosts</code>, which refers 304 * to <code>filename</code>. 305 * 306 * @param filename filename of known_hosts file. 307 * 308 * @throws JSchException 309 * if the given filename is invalid. 310 * 311 * @see com.jcraft.jsch.KnownHosts 312 */ setKnownHosts(String filename)313 public void setKnownHosts(String filename) throws JSchException{ 314 if(known_hosts==null) known_hosts=new KnownHosts(this); 315 if(known_hosts instanceof KnownHosts){ 316 synchronized(known_hosts){ 317 ((KnownHosts)known_hosts).setKnownHosts(filename); 318 } 319 } 320 } 321 322 /** 323 * Sets the instance of <code>KnownHosts</code> generated with 324 * <code>stream</code>. 325 * 326 * @param stream the instance of InputStream from known_hosts file. 327 * 328 * @throws JSchException 329 * if an I/O error occurs. 330 * 331 * @see com.jcraft.jsch.KnownHosts 332 */ setKnownHosts(InputStream stream)333 public void setKnownHosts(InputStream stream) throws JSchException{ 334 if(known_hosts==null) known_hosts=new KnownHosts(this); 335 if(known_hosts instanceof KnownHosts){ 336 synchronized(known_hosts){ 337 ((KnownHosts)known_hosts).setKnownHosts(stream); 338 } 339 } 340 } 341 342 /** 343 * Returns the current hostkey repository. 344 * By the default, this method will the instance of <code>KnownHosts</code>. 345 * 346 * @return current hostkey repository. 347 * 348 * @see com.jcraft.jsch.HostKeyRepository 349 * @see com.jcraft.jsch.KnownHosts 350 */ getHostKeyRepository()351 public HostKeyRepository getHostKeyRepository(){ 352 if(known_hosts==null) known_hosts=new KnownHosts(this); 353 return known_hosts; 354 } 355 356 /** 357 * Sets the private key, which will be referred in 358 * the public key authentication. 359 * 360 * @param prvkey filename of the private key. 361 * 362 * @throws JSchException if <code>prvkey</code> is invalid. 363 * 364 * @see #addIdentity(String prvkey, String passphrase) 365 */ addIdentity(String prvkey)366 public void addIdentity(String prvkey) throws JSchException{ 367 addIdentity(prvkey, (byte[])null); 368 } 369 370 /** 371 * Sets the private key, which will be referred in 372 * the public key authentication. 373 * Before registering it into identityRepository, 374 * it will be deciphered with <code>passphrase</code>. 375 * 376 * @param prvkey filename of the private key. 377 * @param passphrase passphrase for <code>prvkey</code>. 378 * 379 * @throws JSchException if <code>passphrase</code> is not right. 380 * 381 * @see #addIdentity(String prvkey, byte[] passphrase) 382 */ addIdentity(String prvkey, String passphrase)383 public void addIdentity(String prvkey, String passphrase) throws JSchException{ 384 byte[] _passphrase=null; 385 if(passphrase!=null){ 386 _passphrase=Util.str2byte(passphrase); 387 } 388 addIdentity(prvkey, _passphrase); 389 if(_passphrase!=null) 390 Util.bzero(_passphrase); 391 } 392 393 /** 394 * Sets the private key, which will be referred in 395 * the public key authentication. 396 * Before registering it into identityRepository, 397 * it will be deciphered with <code>passphrase</code>. 398 * 399 * @param prvkey filename of the private key. 400 * @param passphrase passphrase for <code>prvkey</code>. 401 * 402 * @throws JSchException if <code>passphrase</code> is not right. 403 * 404 * @see #addIdentity(String prvkey, String pubkey, byte[] passphrase) 405 */ addIdentity(String prvkey, byte[] passphrase)406 public void addIdentity(String prvkey, byte[] passphrase) throws JSchException{ 407 Identity identity=IdentityFile.newInstance(prvkey, null, this); 408 addIdentity(identity, passphrase); 409 } 410 411 /** 412 * Sets the private key, which will be referred in 413 * the public key authentication. 414 * Before registering it into identityRepository, 415 * it will be deciphered with <code>passphrase</code>. 416 * 417 * @param prvkey filename of the private key. 418 * @param pubkey filename of the public key. 419 * @param passphrase passphrase for <code>prvkey</code>. 420 * 421 * @throws JSchException if <code>passphrase</code> is not right. 422 */ addIdentity(String prvkey, String pubkey, byte[] passphrase)423 public void addIdentity(String prvkey, String pubkey, byte[] passphrase) throws JSchException{ 424 Identity identity=IdentityFile.newInstance(prvkey, pubkey, this); 425 addIdentity(identity, passphrase); 426 } 427 428 /** 429 * Sets the private key, which will be referred in 430 * the public key authentication. 431 * Before registering it into identityRepository, 432 * it will be deciphered with <code>passphrase</code>. 433 * 434 * @param name name of the identity to be used to 435 retrieve it in the identityRepository. 436 * @param prvkey private key in byte array. 437 * @param pubkey public key in byte array. 438 * @param passphrase passphrase for <code>prvkey</code>. 439 * 440 */ addIdentity(String name, byte[]prvkey, byte[]pubkey, byte[] passphrase)441 public void addIdentity(String name, byte[]prvkey, byte[]pubkey, byte[] passphrase) throws JSchException{ 442 Identity identity=IdentityFile.newInstance(name, prvkey, pubkey, this); 443 addIdentity(identity, passphrase); 444 } 445 446 /** 447 * Sets the private key, which will be referred in 448 * the public key authentication. 449 * Before registering it into identityRepository, 450 * it will be deciphered with <code>passphrase</code>. 451 * 452 * @param identity private key. 453 * @param passphrase passphrase for <code>identity</code>. 454 * 455 * @throws JSchException if <code>passphrase</code> is not right. 456 */ addIdentity(Identity identity, byte[] passphrase)457 public void addIdentity(Identity identity, byte[] passphrase) throws JSchException{ 458 if(passphrase!=null){ 459 try{ 460 byte[] goo=new byte[passphrase.length]; 461 System.arraycopy(passphrase, 0, goo, 0, passphrase.length); 462 passphrase=goo; 463 identity.setPassphrase(passphrase); 464 } 465 finally{ 466 Util.bzero(passphrase); 467 } 468 } 469 470 if(identityRepository instanceof LocalIdentityRepository){ 471 ((LocalIdentityRepository)identityRepository).add(identity); 472 } 473 else if(identity instanceof IdentityFile && !identity.isEncrypted()) { 474 identityRepository.add(((IdentityFile)identity).getKeyPair().forSSHAgent()); 475 } 476 else { 477 synchronized(this){ 478 if(!(identityRepository instanceof IdentityRepository.Wrapper)){ 479 setIdentityRepository(new IdentityRepository.Wrapper(identityRepository)); 480 } 481 } 482 ((IdentityRepository.Wrapper)identityRepository).add(identity); 483 } 484 } 485 486 /** 487 * @deprecated use #removeIdentity(Identity identity) 488 */ removeIdentity(String name)489 public void removeIdentity(String name) throws JSchException{ 490 Vector identities = identityRepository.getIdentities(); 491 for(int i=0; i<identities.size(); i++){ 492 Identity identity=(Identity)(identities.elementAt(i)); 493 if(!identity.getName().equals(name)) 494 continue; 495 if(identityRepository instanceof LocalIdentityRepository){ 496 ((LocalIdentityRepository)identityRepository).remove(identity); 497 } 498 else 499 identityRepository.remove(identity.getPublicKeyBlob()); 500 } 501 } 502 503 /** 504 * Removes the identity from identityRepository. 505 * 506 * @param identity the indentity to be removed. 507 * 508 * @throws JSchException if <code>identity</code> is invalid. 509 */ removeIdentity(Identity identity)510 public void removeIdentity(Identity identity) throws JSchException{ 511 identityRepository.remove(identity.getPublicKeyBlob()); 512 } 513 514 /** 515 * Lists names of identities included in the identityRepository. 516 * 517 * @return names of identities 518 * 519 * @throws JSchException if identityReposory has problems. 520 */ getIdentityNames()521 public Vector getIdentityNames() throws JSchException{ 522 Vector foo=new Vector(); 523 Vector identities = identityRepository.getIdentities(); 524 for(int i=0; i<identities.size(); i++){ 525 Identity identity=(Identity)(identities.elementAt(i)); 526 foo.addElement(identity.getName()); 527 } 528 return foo; 529 } 530 531 /** 532 * Removes all identities from identityRepository. 533 * 534 * @throws JSchException if identityReposory has problems. 535 */ removeAllIdentity()536 public void removeAllIdentity() throws JSchException{ 537 identityRepository.removeAll(); 538 } 539 540 /** 541 * Returns the config value for the specified key. 542 * 543 * @param key key for the configuration. 544 * @return config value 545 */ getConfig(String key)546 public static String getConfig(String key){ 547 synchronized(config){ 548 return (String)(config.get(key)); 549 } 550 } 551 552 /** 553 * Sets or Overrides the configuration. 554 * 555 * @param newconf configurations 556 */ setConfig(java.util.Hashtable newconf)557 public static void setConfig(java.util.Hashtable newconf){ 558 synchronized(config){ 559 for(java.util.Enumeration e=newconf.keys() ; e.hasMoreElements() ;) { 560 String key=(String)(e.nextElement()); 561 config.put(key, (String)(newconf.get(key))); 562 } 563 } 564 } 565 566 /** 567 * Sets or Overrides the configuration. 568 * 569 * @param key key for the configuration 570 * @param value value for the configuration 571 */ setConfig(String key, String value)572 public static void setConfig(String key, String value){ 573 config.put(key, value); 574 } 575 576 /** 577 * Sets the logger 578 * 579 * @param logger logger 580 * 581 * @see com.jcraft.jsch.Logger 582 */ setLogger(Logger logger)583 public static void setLogger(Logger logger){ 584 if(logger==null) logger=DEVNULL; 585 JSch.logger=logger; 586 } 587 getLogger()588 static Logger getLogger(){ 589 return logger; 590 } 591 } 592