1 package org.apache.maven.wagon.providers.ssh.jsch;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.BufferedReader;
23 import java.io.File;
24 import java.io.FileNotFoundException;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.io.InputStreamReader;
28 import java.io.PrintWriter;
29 import java.io.StringWriter;
30 import java.util.List;
31 import java.util.Properties;
32
33 import org.apache.maven.wagon.CommandExecutionException;
34 import org.apache.maven.wagon.CommandExecutor;
35 import org.apache.maven.wagon.ResourceDoesNotExistException;
36 import org.apache.maven.wagon.StreamWagon;
37 import org.apache.maven.wagon.Streams;
38 import org.apache.maven.wagon.TransferFailedException;
39 import org.apache.maven.wagon.WagonConstants;
40 import org.apache.maven.wagon.authentication.AuthenticationException;
41 import org.apache.maven.wagon.authentication.AuthenticationInfo;
42 import org.apache.maven.wagon.authorization.AuthorizationException;
43 import org.apache.maven.wagon.events.TransferEvent;
44 import org.apache.maven.wagon.providers.ssh.CommandExecutorStreamProcessor;
45 import org.apache.maven.wagon.providers.ssh.ScpHelper;
46 import org.apache.maven.wagon.providers.ssh.SshWagon;
47 import org.apache.maven.wagon.providers.ssh.interactive.InteractiveUserInfo;
48 import org.apache.maven.wagon.providers.ssh.interactive.NullInteractiveUserInfo;
49 import org.apache.maven.wagon.providers.ssh.jsch.interactive.UserInfoUIKeyboardInteractiveProxy;
50 import org.apache.maven.wagon.providers.ssh.knownhost.KnownHostChangedException;
51 import org.apache.maven.wagon.providers.ssh.knownhost.KnownHostsProvider;
52 import org.apache.maven.wagon.providers.ssh.knownhost.UnknownHostException;
53 import org.apache.maven.wagon.proxy.ProxyInfo;
54 import org.apache.maven.wagon.resource.Resource;
55 import org.codehaus.plexus.util.IOUtil;
56 import org.codehaus.plexus.util.StringInputStream;
57
58 import com.jcraft.jsch.agentproxy.AgentProxyException;
59 import com.jcraft.jsch.agentproxy.Connector;
60 import com.jcraft.jsch.agentproxy.ConnectorFactory;
61 import com.jcraft.jsch.agentproxy.RemoteIdentityRepository;
62 import com.jcraft.jsch.ChannelExec;
63 import com.jcraft.jsch.HostKey;
64 import com.jcraft.jsch.HostKeyRepository;
65 import com.jcraft.jsch.IdentityRepository;
66 import com.jcraft.jsch.JSch;
67 import com.jcraft.jsch.JSchException;
68 import com.jcraft.jsch.Proxy;
69 import com.jcraft.jsch.ProxyHTTP;
70 import com.jcraft.jsch.ProxySOCKS5;
71 import com.jcraft.jsch.Session;
72 import com.jcraft.jsch.UIKeyboardInteractive;
73 import com.jcraft.jsch.UserInfo;
74
75
76
77
78 public abstract class AbstractJschWagon
79 extends StreamWagon
80 implements SshWagon, CommandExecutor
81 {
82 protected ScpHelper sshTool = new ScpHelper( this );
83
84 protected Session session;
85
86
87
88
89 private volatile KnownHostsProvider knownHostsProvider;
90
91
92
93
94 private volatile InteractiveUserInfo interactiveUserInfo;
95
96
97
98
99 private volatile UIKeyboardInteractive uIKeyboardInteractive;
100
101 private static final int SOCKS5_PROXY_PORT = 1080;
102
103 protected static final String EXEC_CHANNEL = "exec";
104
105 public void openConnectionInternal()
106 throws AuthenticationException
107 {
108 if ( authenticationInfo == null )
109 {
110 authenticationInfo = new AuthenticationInfo();
111 }
112
113 if ( !interactive )
114 {
115 uIKeyboardInteractive = null;
116 setInteractiveUserInfo( new NullInteractiveUserInfo() );
117 }
118
119 JSch sch = new JSch();
120
121 File privateKey;
122 try
123 {
124 privateKey = ScpHelper.getPrivateKey( authenticationInfo );
125 }
126 catch ( FileNotFoundException e )
127 {
128 throw new AuthenticationException( e.getMessage() );
129 }
130
131
132 if ( privateKey != null && privateKey.exists() )
133 {
134 fireSessionDebug( "Using private key: " + privateKey );
135 try
136 {
137 sch.addIdentity( privateKey.getAbsolutePath(), authenticationInfo.getPassphrase() );
138 }
139 catch ( JSchException e )
140 {
141 throw new AuthenticationException( "Cannot connect. Reason: " + e.getMessage(), e );
142 }
143 }
144 else
145 {
146 try
147 {
148 Connector connector = ConnectorFactory.getDefault().createConnector();
149 if ( connector != null )
150 {
151 IdentityRepository repo = new RemoteIdentityRepository( connector );
152 sch.setIdentityRepository( repo );
153 }
154 }
155 catch ( AgentProxyException e )
156 {
157 fireSessionDebug( "Unable to connect to agent: " + e.toString() );
158 }
159
160 }
161
162 String host = getRepository().getHost();
163 int port =
164 repository.getPort() == WagonConstants.UNKNOWN_PORT ? ScpHelper.DEFAULT_SSH_PORT : repository.getPort();
165 try
166 {
167 String userName = authenticationInfo.getUserName();
168 if ( userName == null )
169 {
170 userName = System.getProperty( "user.name" );
171 }
172 session = sch.getSession( userName, host, port );
173 session.setTimeout( getTimeout() );
174 }
175 catch ( JSchException e )
176 {
177 throw new AuthenticationException( "Cannot connect. Reason: " + e.getMessage(), e );
178 }
179
180 Proxy proxy = null;
181 ProxyInfo proxyInfo = getProxyInfo( ProxyInfo.PROXY_SOCKS5, getRepository().getHost() );
182 if ( proxyInfo != null && proxyInfo.getHost() != null )
183 {
184 proxy = new ProxySOCKS5( proxyInfo.getHost(), proxyInfo.getPort() );
185 ( (ProxySOCKS5) proxy ).setUserPasswd( proxyInfo.getUserName(), proxyInfo.getPassword() );
186 }
187 else
188 {
189 proxyInfo = getProxyInfo( ProxyInfo.PROXY_HTTP, getRepository().getHost() );
190 if ( proxyInfo != null && proxyInfo.getHost() != null )
191 {
192 proxy = new ProxyHTTP( proxyInfo.getHost(), proxyInfo.getPort() );
193 ( (ProxyHTTP) proxy ).setUserPasswd( proxyInfo.getUserName(), proxyInfo.getPassword() );
194 }
195 else
196 {
197
198 proxyInfo = getProxyInfo( getRepository().getProtocol(), getRepository().getHost() );
199 if ( proxyInfo != null && proxyInfo.getHost() != null )
200 {
201
202 if ( proxyInfo.getPort() == SOCKS5_PROXY_PORT )
203 {
204 proxy = new ProxySOCKS5( proxyInfo.getHost(), proxyInfo.getPort() );
205 ( (ProxySOCKS5) proxy ).setUserPasswd( proxyInfo.getUserName(), proxyInfo.getPassword() );
206 }
207 else
208 {
209 proxy = new ProxyHTTP( proxyInfo.getHost(), proxyInfo.getPort() );
210 ( (ProxyHTTP) proxy ).setUserPasswd( proxyInfo.getUserName(), proxyInfo.getPassword() );
211 }
212 }
213 }
214 }
215 session.setProxy( proxy );
216
217
218 UserInfo ui = new WagonUserInfo( authenticationInfo, getInteractiveUserInfo() );
219
220 if ( uIKeyboardInteractive != null )
221 {
222 ui = new UserInfoUIKeyboardInteractiveProxy( ui, uIKeyboardInteractive );
223 }
224
225 Properties config = new Properties();
226 if ( getKnownHostsProvider() != null )
227 {
228 try
229 {
230 String contents = getKnownHostsProvider().getContents();
231 if ( contents != null )
232 {
233 sch.setKnownHosts( new StringInputStream( contents ) );
234 }
235 }
236 catch ( JSchException e )
237 {
238
239 }
240 config.setProperty( "StrictHostKeyChecking", getKnownHostsProvider().getHostKeyChecking() );
241 }
242
243 if ( authenticationInfo.getPassword() != null )
244 {
245 config.setProperty( "PreferredAuthentications", "gssapi-with-mic,publickey,password,keyboard-interactive" );
246 }
247
248 config.setProperty( "BatchMode", interactive ? "no" : "yes" );
249
250 session.setConfig( config );
251
252 session.setUserInfo( ui );
253
254 StringWriter stringWriter = new StringWriter();
255 try
256 {
257 session.connect();
258
259 if ( getKnownHostsProvider() != null )
260 {
261 PrintWriter w = new PrintWriter( stringWriter );
262
263 HostKeyRepository hkr = sch.getHostKeyRepository();
264 HostKey[] keys = hkr.getHostKey();
265
266 for ( int i = 0; keys != null && i < keys.length; i++ )
267 {
268 HostKey key = keys[i];
269 w.println( key.getHost() + " " + key.getType() + " " + key.getKey() );
270 }
271 }
272 }
273 catch ( JSchException e )
274 {
275 if ( e.getMessage().startsWith( "UnknownHostKey:" ) || e.getMessage().startsWith( "reject HostKey:" ) )
276 {
277 throw new UnknownHostException( host, e );
278 }
279 else if ( e.getMessage().contains( "HostKey has been changed" ) )
280 {
281 throw new KnownHostChangedException( host, e );
282 }
283 else
284 {
285 throw new AuthenticationException( "Cannot connect. Reason: " + e.getMessage(), e );
286 }
287 }
288
289 try
290 {
291 getKnownHostsProvider().storeKnownHosts( stringWriter.toString() );
292 }
293 catch ( IOException e )
294 {
295 closeConnection();
296
297 throw new AuthenticationException(
298 "Connection aborted - failed to write to known_hosts. Reason: " + e.getMessage(), e );
299 }
300 }
301
302 public void closeConnection()
303 {
304 if ( session != null )
305 {
306 session.disconnect();
307 session = null;
308 }
309 }
310
311 public Streams executeCommand( String command, boolean ignoreFailures )
312 throws CommandExecutionException
313 {
314 ChannelExec channel = null;
315 BufferedReader stdoutReader = null;
316 BufferedReader stderrReader = null;
317 try
318 {
319 channel = (ChannelExec) session.openChannel( EXEC_CHANNEL );
320
321 channel.setCommand( command + "\n" );
322
323 InputStream stdout = channel.getInputStream();
324 InputStream stderr = channel.getErrStream();
325
326 channel.connect();
327
328 stdoutReader = new BufferedReader( new InputStreamReader( stdout ) );
329 stderrReader = new BufferedReader( new InputStreamReader( stderr ) );
330
331 Streams streams = CommandExecutorStreamProcessor.processStreams( stderrReader, stdoutReader );
332
333 if ( streams.getErr().length() > 0 && !ignoreFailures )
334 {
335 int exitCode = channel.getExitStatus();
336 throw new CommandExecutionException( "Exit code: " + exitCode + " - " + streams.getErr() );
337 }
338
339 return streams;
340 }
341 catch ( IOException e )
342 {
343 throw new CommandExecutionException( "Cannot execute remote command: " + command, e );
344 }
345 catch ( JSchException e )
346 {
347 throw new CommandExecutionException( "Cannot execute remote command: " + command, e );
348 }
349 finally
350 {
351 IOUtil.close( stdoutReader );
352 IOUtil.close( stderrReader );
353 if ( channel != null )
354 {
355 channel.disconnect();
356 }
357 }
358 }
359
360 protected void handleGetException( Resource resource, Exception e )
361 throws TransferFailedException
362 {
363 fireTransferError( resource, e, TransferEvent.REQUEST_GET );
364
365 String msg =
366 "Error occurred while downloading '" + resource + "' from the remote repository:" + getRepository() + ": "
367 + e.getMessage();
368
369 throw new TransferFailedException( msg, e );
370 }
371
372 public List<String> getFileList( String destinationDirectory )
373 throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
374 {
375 return sshTool.getFileList( destinationDirectory, repository );
376 }
377
378 public void putDirectory( File sourceDirectory, String destinationDirectory )
379 throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
380 {
381 sshTool.putDirectory( this, sourceDirectory, destinationDirectory );
382 }
383
384 public boolean resourceExists( String resourceName )
385 throws TransferFailedException, AuthorizationException
386 {
387 return sshTool.resourceExists( resourceName, repository );
388 }
389
390 public boolean supportsDirectoryCopy()
391 {
392 return true;
393 }
394
395 public void executeCommand( String command )
396 throws CommandExecutionException
397 {
398 fireTransferDebug( "Executing command: " + command );
399
400 executeCommand( command, false );
401 }
402
403 public InteractiveUserInfo getInteractiveUserInfo()
404 {
405 return this.interactiveUserInfo;
406 }
407
408 public KnownHostsProvider getKnownHostsProvider()
409 {
410 return this.knownHostsProvider;
411 }
412
413 public void setInteractiveUserInfo( InteractiveUserInfo interactiveUserInfo )
414 {
415 this.interactiveUserInfo = interactiveUserInfo;
416 }
417
418 public void setKnownHostsProvider( KnownHostsProvider knownHostsProvider )
419 {
420 this.knownHostsProvider = knownHostsProvider;
421 }
422
423 public void setUIKeyboardInteractive( UIKeyboardInteractive uIKeyboardInteractive )
424 {
425 this.uIKeyboardInteractive = uIKeyboardInteractive;
426 }
427 }