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