extensions/net.sf.basedb.xfiles/trunk/src/net/sf/basedb/xfiles/sftp/SSHClientWrapper.java

Code
Comments
Other
Rev Date Author Line
2571 13 Aug 14 nicklas 1 /**
2571 13 Aug 14 nicklas 2   $Id$
2571 13 Aug 14 nicklas 3
2571 13 Aug 14 nicklas 4   Copyright (C) 2014 Nicklas Nordborg
2571 13 Aug 14 nicklas 5
2571 13 Aug 14 nicklas 6   This file is part of BASE - BioArray Software Environment.
2571 13 Aug 14 nicklas 7   Available at http://base.thep.lu.se/
2571 13 Aug 14 nicklas 8
2571 13 Aug 14 nicklas 9   BASE is free software; you can redistribute it and/or
2571 13 Aug 14 nicklas 10   modify it under the terms of the GNU General Public License
2571 13 Aug 14 nicklas 11   as published by the Free Software Foundation; either version 3
2571 13 Aug 14 nicklas 12   of the License, or (at your option) any later version.
2571 13 Aug 14 nicklas 13
2571 13 Aug 14 nicklas 14   BASE is distributed in the hope that it will be useful,
2571 13 Aug 14 nicklas 15   but WITHOUT ANY WARRANTY; without even the implied warranty of
2571 13 Aug 14 nicklas 16   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
2571 13 Aug 14 nicklas 17   GNU General Public License for more details.
2571 13 Aug 14 nicklas 18
2571 13 Aug 14 nicklas 19   You should have received a copy of the GNU General Public License
2571 13 Aug 14 nicklas 20   along with BASE. If not, see <http://www.gnu.org/licenses/>.
2571 13 Aug 14 nicklas 21 */
2571 13 Aug 14 nicklas 22 package net.sf.basedb.xfiles.sftp;
2571 13 Aug 14 nicklas 23
2571 13 Aug 14 nicklas 24 import java.io.Closeable;
2571 13 Aug 14 nicklas 25 import java.io.IOException;
2571 13 Aug 14 nicklas 26 import java.net.URI;
2571 13 Aug 14 nicklas 27 import java.util.EnumSet;
2571 13 Aug 14 nicklas 28
4753 16 Apr 18 nicklas 29 import net.schmizz.sshj.Config;
2571 13 Aug 14 nicklas 30 import net.schmizz.sshj.SSHClient;
4753 16 Apr 18 nicklas 31 import net.schmizz.sshj.common.Factory;
4753 16 Apr 18 nicklas 32 import net.schmizz.sshj.common.SSHException;
2571 13 Aug 14 nicklas 33 import net.schmizz.sshj.sftp.OpenMode;
2571 13 Aug 14 nicklas 34 import net.schmizz.sshj.sftp.RemoteFile;
2571 13 Aug 14 nicklas 35 import net.schmizz.sshj.sftp.SFTPClient;
4753 16 Apr 18 nicklas 36 import net.schmizz.sshj.userauth.keyprovider.FileKeyProvider;
4753 16 Apr 18 nicklas 37 import net.schmizz.sshj.userauth.keyprovider.KeyFormat;
4753 16 Apr 18 nicklas 38 import net.schmizz.sshj.userauth.keyprovider.KeyProvider;
4753 16 Apr 18 nicklas 39 import net.schmizz.sshj.userauth.keyprovider.KeyProviderUtil;
4753 16 Apr 18 nicklas 40 import net.schmizz.sshj.userauth.password.PasswordFinder;
4753 16 Apr 18 nicklas 41 import net.schmizz.sshj.userauth.password.PasswordUtils;
4453 10 Apr 17 nicklas 42 import net.sf.basedb.core.FileServer;
2572 13 Aug 14 nicklas 43 import net.sf.basedb.xfiles.ConnectionInfo;
2571 13 Aug 14 nicklas 44 import net.sf.basedb.xfiles.MultiCloseable;
2571 13 Aug 14 nicklas 45 import net.sf.basedb.xfiles.XFiles;
2571 13 Aug 14 nicklas 46
2571 13 Aug 14 nicklas 47 /**
2571 13 Aug 14 nicklas 48   Wrapper class for a paired SSHClient and SFTPClient object
2571 13 Aug 14 nicklas 49   intended to be used with the connection cache. If the cache
2571 13 Aug 14 nicklas 50   is used, the wrapper will keep track of the number of opened files
2571 13 Aug 14 nicklas 51   and keep the connection opens until all files are closed
2571 13 Aug 14 nicklas 52   and the current transaction has ended. 
2571 13 Aug 14 nicklas 53   
2571 13 Aug 14 nicklas 54   Note that the {@link #close()} method must be called
2571 13 Aug 14 nicklas 55   once for every file that is opened and once for the transation.
2571 13 Aug 14 nicklas 56
2571 13 Aug 14 nicklas 57   If the cache is not used, the wrapper will close the SSH/SFTP client
2571 13 Aug 14 nicklas 58   objects immedately.
2571 13 Aug 14 nicklas 59
2571 13 Aug 14 nicklas 60   @since 1.1
2571 13 Aug 14 nicklas 61 */
2571 13 Aug 14 nicklas 62 public class SSHClientWrapper 
2571 13 Aug 14 nicklas 63   implements Closeable
2571 13 Aug 14 nicklas 64 {
2571 13 Aug 14 nicklas 65
2571 13 Aug 14 nicklas 66   public final SSHClient ssh;
2571 13 Aug 14 nicklas 67   public final SFTPClient sftp;
2571 13 Aug 14 nicklas 68   
2571 13 Aug 14 nicklas 69   private int openFiles;
2571 13 Aug 14 nicklas 70   private boolean isCached;
2571 13 Aug 14 nicklas 71   
2572 13 Aug 14 nicklas 72   public SSHClientWrapper(ConnectionInfo info)
2571 13 Aug 14 nicklas 73     throws IOException
2571 13 Aug 14 nicklas 74   {
3804 22 Mar 16 nicklas 75     this.ssh = new SSHClient(info.sshConfig);
2572 13 Aug 14 nicklas 76     this.isCached = SftpConnectionManager.CONNECTION_CACHE.setClient(info, this);
4453 10 Apr 17 nicklas 77     String fingerprint = info.parameters.getSshFingerprint();
4453 10 Apr 17 nicklas 78     if (FileServer.SHA256_FINGERPRINT_PATTERN.matcher(fingerprint).matches())
4453 10 Apr 17 nicklas 79     {
4453 10 Apr 17 nicklas 80       this.ssh.addHostKeyVerifier(new Sha256Verifier(fingerprint));
4453 10 Apr 17 nicklas 81     }
4453 10 Apr 17 nicklas 82     else if (FileServer.MD5_FINGERPRINT_PATTERN.matcher(fingerprint).matches())
4453 10 Apr 17 nicklas 83     {
4453 10 Apr 17 nicklas 84       this.ssh.addHostKeyVerifier(fingerprint);
4453 10 Apr 17 nicklas 85     }
2571 13 Aug 14 nicklas 86     this.ssh.setConnectTimeout(2000);
2572 13 Aug 14 nicklas 87     int port = info.uri.getPort();
2571 13 Aug 14 nicklas 88     if (port == -1) port = XFiles.DEFAULT_SSH_PORT;
2572 13 Aug 14 nicklas 89     this.ssh.connect(info.uri.getHost(), port);
4753 16 Apr 18 nicklas 90     
4753 16 Apr 18 nicklas 91     byte[] privateKey = info.parameters.getSshPrivateKey();
4753 16 Apr 18 nicklas 92     if (privateKey != null && privateKey.length > 0)
4753 16 Apr 18 nicklas 93     {
4753 16 Apr 18 nicklas 94       KeyProvider kp = createFileKeyProvider(ssh.getTransport().getConfig(), 
4753 16 Apr 18 nicklas 95           privateKey, info.parameters.getSshPrivateKeyPassword(), info.parameters.getSshPrivateKeyFormat());
4753 16 Apr 18 nicklas 96       this.ssh.authPublickey(info.parameters.getUsername(), kp);
4753 16 Apr 18 nicklas 97     }
4753 16 Apr 18 nicklas 98     else
4753 16 Apr 18 nicklas 99     {
4753 16 Apr 18 nicklas 100       this.ssh.authPassword(info.parameters.getUsername(), info.parameters.getPassword());
4753 16 Apr 18 nicklas 101     }
2571 13 Aug 14 nicklas 102     this.sftp = ssh.newSFTPClient();
2571 13 Aug 14 nicklas 103     this.openFiles = 0;
2571 13 Aug 14 nicklas 104   }
2571 13 Aug 14 nicklas 105
2571 13 Aug 14 nicklas 106   @Override
2571 13 Aug 14 nicklas 107   public void close() 
2571 13 Aug 14 nicklas 108   {
2571 13 Aug 14 nicklas 109     if (openFiles == 0 || !isCached)
2571 13 Aug 14 nicklas 110     {
2571 13 Aug 14 nicklas 111       MultiCloseable.closeAll(sftp, ssh);
2571 13 Aug 14 nicklas 112     }
2571 13 Aug 14 nicklas 113     else
2571 13 Aug 14 nicklas 114     {
2571 13 Aug 14 nicklas 115       openFiles--;
2571 13 Aug 14 nicklas 116     }
2571 13 Aug 14 nicklas 117   }
2571 13 Aug 14 nicklas 118   
2571 13 Aug 14 nicklas 119   public RemoteFile openUri(URI uri)
2571 13 Aug 14 nicklas 120     throws IOException
2571 13 Aug 14 nicklas 121   {
2571 13 Aug 14 nicklas 122     RemoteFile file = sftp.open(uri.getPath(), EnumSet.of(OpenMode.READ));
2571 13 Aug 14 nicklas 123     openFiles++;
2571 13 Aug 14 nicklas 124     return file;
2571 13 Aug 14 nicklas 125   }
2571 13 Aug 14 nicklas 126   
2571 13 Aug 14 nicklas 127   
4753 16 Apr 18 nicklas 128   /**
4753 16 Apr 18 nicklas 129     Create and initialize a private key provider. 
4753 16 Apr 18 nicklas 130     @param config Required since we get supported key file formats from this
4753 16 Apr 18 nicklas 131     @param privateKey The private key file as a string (required)
4753 16 Apr 18 nicklas 132     @param password Optional
4753 16 Apr 18 nicklas 133     @param format Optional (if not specified, auto-detection will be attempted)
4753 16 Apr 18 nicklas 134   */
4753 16 Apr 18 nicklas 135   private KeyProvider createFileKeyProvider(Config config, byte[] privateKey, String password, String format)
4753 16 Apr 18 nicklas 136     throws IOException
4753 16 Apr 18 nicklas 137   {
4753 16 Apr 18 nicklas 138     String key = new String(privateKey);
4753 16 Apr 18 nicklas 139     if (format == null)
4753 16 Apr 18 nicklas 140     {
4753 16 Apr 18 nicklas 141       // No format specified, try auto-detecting which is supported for PuTTY and OpenSSH
4753 16 Apr 18 nicklas 142       KeyFormat kf = KeyProviderUtil.detectKeyFileFormat(key, true);
4753 16 Apr 18 nicklas 143       if (kf == null || kf == KeyFormat.Unknown) 
4753 16 Apr 18 nicklas 144       {
4753 16 Apr 18 nicklas 145         throw new SSHException("Could not detect format of private key");
4753 16 Apr 18 nicklas 146       }
4753 16 Apr 18 nicklas 147       format = kf.toString();
4753 16 Apr 18 nicklas 148     }
4753 16 Apr 18 nicklas 149     
4753 16 Apr 18 nicklas 150     FileKeyProvider provider = Factory.Named.Util.create(config.getFileKeyProviderFactories(), format);
4753 16 Apr 18 nicklas 151     if (provider == null)
4753 16 Apr 18 nicklas 152     {
4753 16 Apr 18 nicklas 153       throw new SSHException("No provider available for '" + format + "' key file format");
4753 16 Apr 18 nicklas 154     }
4753 16 Apr 18 nicklas 155   
4753 16 Apr 18 nicklas 156     PasswordFinder pwd = password == null ? null : PasswordUtils.createOneOff(password.toCharArray());
4753 16 Apr 18 nicklas 157     provider.init(key, null, pwd);
4753 16 Apr 18 nicklas 158     try
4753 16 Apr 18 nicklas 159     {
4753 16 Apr 18 nicklas 160       // Try to get the private key here to catch problems early and get better error messages for the user
4753 16 Apr 18 nicklas 161       provider.getPrivate();
4753 16 Apr 18 nicklas 162     }
4753 16 Apr 18 nicklas 163     catch (RuntimeException ex)
4753 16 Apr 18 nicklas 164     {
4753 16 Apr 18 nicklas 165       String msg = "Invalid '" + format + "' private key";
4753 16 Apr 18 nicklas 166       if (ex.getMessage() != null) msg += " (" + ex.getMessage() + ")";
4753 16 Apr 18 nicklas 167       throw new SSHException(msg, ex);
4753 16 Apr 18 nicklas 168     }
4753 16 Apr 18 nicklas 169     
4753 16 Apr 18 nicklas 170     return provider;
4753 16 Apr 18 nicklas 171   }
4753 16 Apr 18 nicklas 172
4753 16 Apr 18 nicklas 173   
2571 13 Aug 14 nicklas 174 }