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.ByteArrayInputStream;
24 import java.io.File;
25 import java.io.FileNotFoundException;
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.io.InputStreamReader;
29 import java.io.OutputStream;
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.KnownHostEntry;
52 import org.apache.maven.wagon.providers.ssh.knownhost.KnownHostsProvider;
53 import org.apache.maven.wagon.providers.ssh.knownhost.UnknownHostException;
54 import org.apache.maven.wagon.proxy.ProxyInfo;
55 import org.apache.maven.wagon.resource.Resource;
56 import org.codehaus.plexus.util.IOUtil;
57
58 import com.jcraft.jsch.ChannelExec;
59 import com.jcraft.jsch.HostKey;
60 import com.jcraft.jsch.HostKeyRepository;
61 import com.jcraft.jsch.IdentityRepository;
62 import com.jcraft.jsch.JSch;
63 import com.jcraft.jsch.JSchException;
64 import com.jcraft.jsch.Proxy;
65 import com.jcraft.jsch.ProxyHTTP;
66 import com.jcraft.jsch.ProxySOCKS5;
67 import com.jcraft.jsch.Session;
68 import com.jcraft.jsch.UIKeyboardInteractive;
69 import com.jcraft.jsch.UserInfo;
70 import com.jcraft.jsch.agentproxy.AgentProxyException;
71 import com.jcraft.jsch.agentproxy.Connector;
72 import com.jcraft.jsch.agentproxy.ConnectorFactory;
73 import com.jcraft.jsch.agentproxy.RemoteIdentityRepository;
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 private String strictHostKeyChecking;
87
88
89
90
91 private volatile KnownHostsProvider knownHostsProvider;
92
93
94
95
96 private volatile InteractiveUserInfo interactiveUserInfo;
97
98
99
100
101 private volatile String preferredAuthentications;
102
103
104
105
106 private volatile UIKeyboardInteractive uIKeyboardInteractive;
107
108 private static final int SOCKS5_PROXY_PORT = 1080;
109
110 protected static final String EXEC_CHANNEL = "exec";
111
112 public void openConnectionInternal()
113 throws AuthenticationException
114 {
115 if ( authenticationInfo == null )
116 {
117 authenticationInfo = new AuthenticationInfo();
118 }
119
120 if ( !interactive )
121 {
122 uIKeyboardInteractive = null;
123 setInteractiveUserInfo( new NullInteractiveUserInfo() );
124 }
125
126 JSch sch = new JSch();
127
128 File privateKey;
129 try
130 {
131 privateKey = ScpHelper.getPrivateKey( authenticationInfo );
132 }
133 catch ( FileNotFoundException e )
134 {
135 throw new AuthenticationException( e.getMessage() );
136 }
137
138
139 if ( privateKey != null && privateKey.exists() )
140 {
141 fireSessionDebug( "Using private key: " + privateKey );
142 try
143 {
144 sch.addIdentity( privateKey.getAbsolutePath(), authenticationInfo.getPassphrase() );
145 }
146 catch ( JSchException e )
147 {
148 throw new AuthenticationException( "Cannot connect. Reason: " + e.getMessage(), e );
149 }
150 }
151 else
152 {
153 try
154 {
155 Connector connector = ConnectorFactory.getDefault().createConnector();
156 if ( connector != null )
157 {
158 IdentityRepository repo = new RemoteIdentityRepository( connector );
159 sch.setIdentityRepository( repo );
160 }
161 }
162 catch ( AgentProxyException e )
163 {
164 fireSessionDebug( "Unable to connect to agent: " + e.toString() );
165 }
166
167 }
168
169 String host = getRepository().getHost();
170 int port =
171 repository.getPort() == WagonConstants.UNKNOWN_PORT ? ScpHelper.DEFAULT_SSH_PORT : repository.getPort();
172 try
173 {
174 String userName = authenticationInfo.getUserName();
175 if ( userName == null )
176 {
177 userName = System.getProperty( "user.name" );
178 }
179 session = sch.getSession( userName, host, port );
180 session.setTimeout( getTimeout() );
181 }
182 catch ( JSchException e )
183 {
184 throw new AuthenticationException( "Cannot connect. Reason: " + e.getMessage(), e );
185 }
186
187 Proxy proxy = null;
188 ProxyInfo proxyInfo = getProxyInfo( ProxyInfo.PROXY_SOCKS5, getRepository().getHost() );
189 if ( proxyInfo != null && proxyInfo.getHost() != null )
190 {
191 proxy = new ProxySOCKS5( proxyInfo.getHost(), proxyInfo.getPort() );
192 ( (ProxySOCKS5) proxy ).setUserPasswd( proxyInfo.getUserName(), proxyInfo.getPassword() );
193 }
194 else
195 {
196 proxyInfo = getProxyInfo( ProxyInfo.PROXY_HTTP, getRepository().getHost() );
197 if ( proxyInfo != null && proxyInfo.getHost() != null )
198 {
199 proxy = new ProxyHTTP( proxyInfo.getHost(), proxyInfo.getPort() );
200 ( (ProxyHTTP) proxy ).setUserPasswd( proxyInfo.getUserName(), proxyInfo.getPassword() );
201 }
202 else
203 {
204
205 proxyInfo = getProxyInfo( getRepository().getProtocol(), getRepository().getHost() );
206 if ( proxyInfo != null && proxyInfo.getHost() != null )
207 {
208
209 if ( proxyInfo.getPort() == SOCKS5_PROXY_PORT )
210 {
211 proxy = new ProxySOCKS5( proxyInfo.getHost(), proxyInfo.getPort() );
212 ( (ProxySOCKS5) proxy ).setUserPasswd( proxyInfo.getUserName(), proxyInfo.getPassword() );
213 }
214 else
215 {
216 proxy = new ProxyHTTP( proxyInfo.getHost(), proxyInfo.getPort() );
217 ( (ProxyHTTP) proxy ).setUserPasswd( proxyInfo.getUserName(), proxyInfo.getPassword() );
218 }
219 }
220 }
221 }
222 session.setProxy( proxy );
223
224
225 UserInfo ui = new WagonUserInfo( authenticationInfo, getInteractiveUserInfo() );
226
227 if ( uIKeyboardInteractive != null )
228 {
229 ui = new UserInfoUIKeyboardInteractiveProxy( ui, uIKeyboardInteractive );
230 }
231
232 Properties config = new Properties();
233 if ( getKnownHostsProvider() != null )
234 {
235 try
236 {
237 String contents = getKnownHostsProvider().getContents();
238 if ( contents != null )
239 {
240 sch.setKnownHosts( new ByteArrayInputStream( contents.getBytes() ) );
241 }
242 }
243 catch ( JSchException e )
244 {
245
246 }
247 if ( strictHostKeyChecking == null )
248 {
249 strictHostKeyChecking = getKnownHostsProvider().getHostKeyChecking();
250 }
251 config.setProperty( "StrictHostKeyChecking", strictHostKeyChecking );
252 }
253
254 if ( preferredAuthentications != null )
255 {
256 config.setProperty( "PreferredAuthentications", preferredAuthentications );
257 }
258
259 config.setProperty( "BatchMode", interactive ? "no" : "yes" );
260
261 session.setConfig( config );
262
263 session.setUserInfo( ui );
264
265 try
266 {
267 session.connect();
268 }
269 catch ( JSchException e )
270 {
271 if ( e.getMessage().startsWith( "UnknownHostKey:" ) || e.getMessage().startsWith( "reject HostKey:" ) )
272 {
273 throw new UnknownHostException( host, e );
274 }
275 else if ( e.getMessage().contains( "HostKey has been changed" ) )
276 {
277 throw new KnownHostChangedException( host, e );
278 }
279 else
280 {
281 throw new AuthenticationException( "Cannot connect. Reason: " + e.getMessage(), e );
282 }
283 }
284
285 if ( getKnownHostsProvider() != null )
286 {
287 HostKeyRepository hkr = sch.getHostKeyRepository();
288
289 HostKey[] hk = hkr.getHostKey( host, null );
290 try
291 {
292 if ( hk != null )
293 {
294 for ( HostKey hostKey : hk )
295 {
296 KnownHostEntry knownHostEntry = new KnownHostEntry( hostKey.getHost(), hostKey.getType(),
297 hostKey.getKey() );
298 getKnownHostsProvider().addKnownHost( knownHostEntry );
299 }
300 }
301 }
302 catch ( IOException e )
303 {
304 closeConnection();
305
306 throw new AuthenticationException(
307 "Connection aborted - failed to write to known_hosts. Reason: " + e.getMessage(), e );
308 }
309 }
310 }
311
312 public void closeConnection()
313 {
314 if ( session != null )
315 {
316 session.disconnect();
317 session = null;
318 }
319 }
320
321 public Streams executeCommand( String command, boolean ignoreStdErr, boolean ignoreNoneZeroExitCode )
322 throws CommandExecutionException
323 {
324 ChannelExec channel = null;
325 BufferedReader stdoutReader = null;
326 BufferedReader stderrReader = null;
327 Streams streams = null;
328 try
329 {
330 channel = (ChannelExec) session.openChannel( EXEC_CHANNEL );
331
332 fireSessionDebug( "Executing: " + command );
333 channel.setCommand( command + "\n" );
334
335 stdoutReader = new BufferedReader( new InputStreamReader( channel.getInputStream() ) );
336 stderrReader = new BufferedReader( new InputStreamReader( channel.getErrStream() ) );
337
338 channel.connect();
339
340 streams = CommandExecutorStreamProcessor.processStreams( stderrReader, stdoutReader );
341
342 stdoutReader.close();
343 stdoutReader = null;
344
345 stderrReader.close();
346 stderrReader = null;
347
348 int exitCode = channel.getExitStatus();
349
350 if ( streams.getErr().length() > 0 && !ignoreStdErr )
351 {
352 throw new CommandExecutionException( "Exit code: " + exitCode + " - " + streams.getErr() );
353 }
354
355 if ( exitCode != 0 && !ignoreNoneZeroExitCode )
356 {
357 throw new CommandExecutionException( "Exit code: " + exitCode + " - " + streams.getErr() );
358 }
359
360 return streams;
361 }
362 catch ( IOException e )
363 {
364 throw new CommandExecutionException( "Cannot execute remote command: " + command, e );
365 }
366 catch ( JSchException e )
367 {
368 throw new CommandExecutionException( "Cannot execute remote command: " + command, e );
369 }
370 finally
371 {
372 if ( streams != null )
373 {
374 fireSessionDebug( "Stdout results:" + streams.getOut() );
375 fireSessionDebug( "Stderr results:" + streams.getErr() );
376 }
377
378 IOUtil.close( stdoutReader );
379 IOUtil.close( stderrReader );
380 if ( channel != null )
381 {
382 channel.disconnect();
383 }
384 }
385 }
386
387 protected void handleGetException( Resource resource, Exception e )
388 throws TransferFailedException
389 {
390 fireTransferError( resource, e, TransferEvent.REQUEST_GET );
391
392 String msg =
393 "Error occurred while downloading '" + resource + "' from the remote repository:" + getRepository() + ": "
394 + e.getMessage();
395
396 throw new TransferFailedException( msg, e );
397 }
398
399 public List<String> getFileList( String destinationDirectory )
400 throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
401 {
402 return sshTool.getFileList( destinationDirectory, repository );
403 }
404
405 public void putDirectory( File sourceDirectory, String destinationDirectory )
406 throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
407 {
408 sshTool.putDirectory( this, sourceDirectory, destinationDirectory );
409 }
410
411 public boolean resourceExists( String resourceName )
412 throws TransferFailedException, AuthorizationException
413 {
414 return sshTool.resourceExists( resourceName, repository );
415 }
416
417 public boolean supportsDirectoryCopy()
418 {
419 return true;
420 }
421
422 public void executeCommand( String command )
423 throws CommandExecutionException
424 {
425 fireTransferDebug( "Executing command: " + command );
426
427
428 executeCommand( command, false, true );
429 }
430
431 public Streams executeCommand( String command, boolean ignoreFailures )
432 throws CommandExecutionException
433 {
434 fireTransferDebug( "Executing command: " + command );
435
436
437 return executeCommand( command, ignoreFailures, true );
438 }
439
440 public InteractiveUserInfo getInteractiveUserInfo()
441 {
442 return this.interactiveUserInfo;
443 }
444
445 public KnownHostsProvider getKnownHostsProvider()
446 {
447 return this.knownHostsProvider;
448 }
449
450 public void setInteractiveUserInfo( InteractiveUserInfo interactiveUserInfo )
451 {
452 this.interactiveUserInfo = interactiveUserInfo;
453 }
454
455 public void setKnownHostsProvider( KnownHostsProvider knownHostsProvider )
456 {
457 this.knownHostsProvider = knownHostsProvider;
458 }
459
460 public void setUIKeyboardInteractive( UIKeyboardInteractive uIKeyboardInteractive )
461 {
462 this.uIKeyboardInteractive = uIKeyboardInteractive;
463 }
464
465 public String getPreferredAuthentications()
466 {
467 return preferredAuthentications;
468 }
469
470 public void setPreferredAuthentications( String preferredAuthentications )
471 {
472 this.preferredAuthentications = preferredAuthentications;
473 }
474
475 public String getStrictHostKeyChecking()
476 {
477 return strictHostKeyChecking;
478 }
479
480 public void setStrictHostKeyChecking( String strictHostKeyChecking )
481 {
482 this.strictHostKeyChecking = strictHostKeyChecking;
483 }
484
485
486
487 @Override
488 protected void transfer( Resource resource, InputStream input, OutputStream output, int requestType, long maxSize )
489 throws IOException
490 {
491 byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
492
493 TransferEvent transferEvent = new TransferEvent( this, resource, TransferEvent.TRANSFER_PROGRESS, requestType );
494 transferEvent.setTimestamp( System.currentTimeMillis() );
495
496 long remaining = maxSize;
497 while ( remaining > 0L )
498 {
499
500 int n = input.read( buffer, 0, (int) Math.min( buffer.length, remaining ) );
501
502 if ( n == -1 )
503 {
504 break;
505 }
506
507 fireTransferProgress( transferEvent, buffer, n );
508
509 output.write( buffer, 0, n );
510
511 remaining -= n;
512 }
513 output.flush();
514 }
515 }