2571 |
13 Aug 14 |
nicklas |
1 |
/** |
2571 |
13 Aug 14 |
nicklas |
$Id$ |
2571 |
13 Aug 14 |
nicklas |
3 |
|
2571 |
13 Aug 14 |
nicklas |
Copyright (C) 2014 Nicklas Nordborg |
2571 |
13 Aug 14 |
nicklas |
5 |
|
2571 |
13 Aug 14 |
nicklas |
This file is part of BASE - BioArray Software Environment. |
2571 |
13 Aug 14 |
nicklas |
Available at http://base.thep.lu.se/ |
2571 |
13 Aug 14 |
nicklas |
8 |
|
2571 |
13 Aug 14 |
nicklas |
BASE is free software; you can redistribute it and/or |
2571 |
13 Aug 14 |
nicklas |
modify it under the terms of the GNU General Public License |
2571 |
13 Aug 14 |
nicklas |
as published by the Free Software Foundation; either version 3 |
2571 |
13 Aug 14 |
nicklas |
of the License, or (at your option) any later version. |
2571 |
13 Aug 14 |
nicklas |
13 |
|
2571 |
13 Aug 14 |
nicklas |
BASE is distributed in the hope that it will be useful, |
2571 |
13 Aug 14 |
nicklas |
but WITHOUT ANY WARRANTY; without even the implied warranty of |
2571 |
13 Aug 14 |
nicklas |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2571 |
13 Aug 14 |
nicklas |
GNU General Public License for more details. |
2571 |
13 Aug 14 |
nicklas |
18 |
|
2571 |
13 Aug 14 |
nicklas |
You should have received a copy of the GNU General Public License |
2571 |
13 Aug 14 |
nicklas |
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 |
Wrapper class for a paired SSHClient and SFTPClient object |
2571 |
13 Aug 14 |
nicklas |
intended to be used with the connection cache. If the cache |
2571 |
13 Aug 14 |
nicklas |
is used, the wrapper will keep track of the number of opened files |
2571 |
13 Aug 14 |
nicklas |
and keep the connection opens until all files are closed |
2571 |
13 Aug 14 |
nicklas |
and the current transaction has ended. |
2571 |
13 Aug 14 |
nicklas |
53 |
|
2571 |
13 Aug 14 |
nicklas |
Note that the {@link #close()} method must be called |
2571 |
13 Aug 14 |
nicklas |
once for every file that is opened and once for the transation. |
2571 |
13 Aug 14 |
nicklas |
56 |
|
2571 |
13 Aug 14 |
nicklas |
If the cache is not used, the wrapper will close the SSH/SFTP client |
2571 |
13 Aug 14 |
nicklas |
objects immedately. |
2571 |
13 Aug 14 |
nicklas |
59 |
|
2571 |
13 Aug 14 |
nicklas |
@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 |
Create and initialize a private key provider. |
4753 |
16 Apr 18 |
nicklas |
@param config Required since we get supported key file formats from this |
4753 |
16 Apr 18 |
nicklas |
@param privateKey The private key file as a string (required) |
4753 |
16 Apr 18 |
nicklas |
@param password Optional |
4753 |
16 Apr 18 |
nicklas |
@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 |
// 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 |
// 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 |
} |