1 |
|
package org.apache.maven.wagon.providers.ssh; |
2 |
|
|
3 |
|
|
4 |
|
|
5 |
|
|
6 |
|
|
7 |
|
|
8 |
|
|
9 |
|
|
10 |
|
|
11 |
|
|
12 |
|
|
13 |
|
|
14 |
|
|
15 |
|
|
16 |
|
|
17 |
|
|
18 |
|
|
19 |
|
import com.jcraft.jsch.ChannelExec; |
20 |
|
import com.jcraft.jsch.JSch; |
21 |
|
import com.jcraft.jsch.JSchException; |
22 |
|
import com.jcraft.jsch.Proxy; |
23 |
|
import com.jcraft.jsch.ProxyHTTP; |
24 |
|
import com.jcraft.jsch.ProxySOCKS5; |
25 |
|
import com.jcraft.jsch.Session; |
26 |
|
import com.jcraft.jsch.UIKeyboardInteractive; |
27 |
|
import com.jcraft.jsch.UserInfo; |
28 |
|
import org.apache.maven.wagon.AbstractWagon; |
29 |
|
import org.apache.maven.wagon.CommandExecutionException; |
30 |
|
import org.apache.maven.wagon.CommandExecutor; |
31 |
|
import org.apache.maven.wagon.PermissionModeUtils; |
32 |
|
import org.apache.maven.wagon.ResourceDoesNotExistException; |
33 |
|
import org.apache.maven.wagon.TransferFailedException; |
34 |
|
import org.apache.maven.wagon.WagonConstants; |
35 |
|
import org.apache.maven.wagon.authentication.AuthenticationException; |
36 |
|
import org.apache.maven.wagon.authentication.AuthenticationInfo; |
37 |
|
import org.apache.maven.wagon.authorization.AuthorizationException; |
38 |
|
import org.apache.maven.wagon.events.TransferEvent; |
39 |
|
import org.apache.maven.wagon.providers.ssh.interactive.InteractiveUserInfo; |
40 |
|
import org.apache.maven.wagon.providers.ssh.interactive.NullInteractiveUserInfo; |
41 |
|
import org.apache.maven.wagon.providers.ssh.interactive.UserInfoUIKeyboardInteractiveProxy; |
42 |
|
import org.apache.maven.wagon.providers.ssh.knownhost.KnownHostsProvider; |
43 |
|
import org.apache.maven.wagon.repository.RepositoryPermissions; |
44 |
|
import org.apache.maven.wagon.resource.Resource; |
45 |
|
import org.codehaus.plexus.util.FileUtils; |
46 |
|
import org.codehaus.plexus.util.IOUtil; |
47 |
|
import org.codehaus.plexus.util.StringUtils; |
48 |
|
|
49 |
|
import java.io.BufferedReader; |
50 |
|
import java.io.File; |
51 |
|
import java.io.IOException; |
52 |
|
import java.io.InputStream; |
53 |
|
import java.io.InputStreamReader; |
54 |
|
import java.io.OutputStream; |
55 |
|
import java.util.ArrayList; |
56 |
|
import java.util.List; |
57 |
|
import java.util.Properties; |
58 |
|
|
59 |
|
|
60 |
|
|
61 |
|
|
62 |
|
|
63 |
|
|
64 |
|
|
65 |
|
|
66 |
0 |
public abstract class AbstractSshWagon |
67 |
|
extends AbstractWagon |
68 |
|
implements CommandExecutor |
69 |
|
{ |
70 |
|
public static final int DEFAULT_SSH_PORT = 22; |
71 |
|
|
72 |
|
public static final int SOCKS5_PROXY_PORT = 1080; |
73 |
|
|
74 |
|
protected Session session; |
75 |
|
|
76 |
|
public static final String EXEC_CHANNEL = "exec"; |
77 |
|
|
78 |
|
private static final int LINE_BUFFER_SIZE = 8192; |
79 |
|
|
80 |
|
private static final byte LF = '\n'; |
81 |
|
|
82 |
|
private KnownHostsProvider knownHostsProvider; |
83 |
|
|
84 |
|
private InteractiveUserInfo interactiveUserInfo; |
85 |
|
|
86 |
|
private UIKeyboardInteractive uIKeyboardInteractive; |
87 |
|
|
88 |
|
public void openConnection() |
89 |
|
throws AuthenticationException |
90 |
|
{ |
91 |
0 |
if ( authenticationInfo == null ) |
92 |
|
{ |
93 |
0 |
authenticationInfo = new AuthenticationInfo(); |
94 |
|
} |
95 |
|
|
96 |
0 |
if ( authenticationInfo.getUserName() == null ) |
97 |
|
{ |
98 |
0 |
authenticationInfo.setUserName( System.getProperty( "user.name" ) ); |
99 |
|
} |
100 |
|
|
101 |
0 |
JSch sch = new JSch(); |
102 |
|
|
103 |
0 |
int port = getRepository().getPort(); |
104 |
|
|
105 |
0 |
if ( port == WagonConstants.UNKNOWN_PORT ) |
106 |
|
{ |
107 |
0 |
port = DEFAULT_SSH_PORT; |
108 |
|
} |
109 |
|
|
110 |
0 |
String host = getRepository().getHost(); |
111 |
|
|
112 |
|
try |
113 |
|
{ |
114 |
0 |
session = sch.getSession( authenticationInfo.getUserName(), host, port ); |
115 |
|
} |
116 |
0 |
catch ( JSchException e ) |
117 |
|
{ |
118 |
0 |
fireSessionError( e ); |
119 |
|
|
120 |
0 |
throw new AuthenticationException( "Cannot connect. Reason: " + e.getMessage(), e ); |
121 |
0 |
} |
122 |
|
|
123 |
|
|
124 |
0 |
if ( authenticationInfo.getPassword() == null ) |
125 |
|
{ |
126 |
|
File privateKey; |
127 |
|
|
128 |
0 |
if ( authenticationInfo.getPrivateKey() != null ) |
129 |
|
{ |
130 |
0 |
privateKey = new File( authenticationInfo.getPrivateKey() ); |
131 |
0 |
} |
132 |
|
else |
133 |
|
{ |
134 |
0 |
privateKey = findPrivateKey(); |
135 |
|
} |
136 |
|
|
137 |
0 |
if ( privateKey.exists() ) |
138 |
|
{ |
139 |
0 |
if ( authenticationInfo.getPassphrase() == null ) |
140 |
|
{ |
141 |
0 |
authenticationInfo.setPassphrase( "" ); |
142 |
|
} |
143 |
|
|
144 |
0 |
fireSessionDebug( "Using private key: " + privateKey ); |
145 |
|
|
146 |
|
try |
147 |
|
{ |
148 |
0 |
sch.addIdentity( privateKey.getAbsolutePath(), authenticationInfo.getPassphrase() ); |
149 |
|
} |
150 |
0 |
catch ( JSchException e ) |
151 |
|
{ |
152 |
0 |
fireSessionError( e ); |
153 |
|
|
154 |
0 |
throw new AuthenticationException( "Cannot connect. Reason: " + e.getMessage(), e ); |
155 |
0 |
} |
156 |
|
} |
157 |
|
} |
158 |
|
|
159 |
0 |
if ( proxyInfo != null && proxyInfo.getHost() != null ) |
160 |
|
{ |
161 |
|
Proxy proxy; |
162 |
|
|
163 |
0 |
int proxyPort = proxyInfo.getPort(); |
164 |
|
|
165 |
|
|
166 |
0 |
if ( proxyPort == SOCKS5_PROXY_PORT ) |
167 |
|
{ |
168 |
0 |
proxy = new ProxySOCKS5( proxyInfo.getHost() ); |
169 |
0 |
( (ProxySOCKS5) proxy ).setUserPasswd( proxyInfo.getUserName(), proxyInfo.getPassword() ); |
170 |
0 |
} |
171 |
|
else |
172 |
|
{ |
173 |
0 |
proxy = new ProxyHTTP( proxyInfo.getHost(), proxyPort ); |
174 |
0 |
( (ProxyHTTP) proxy ).setUserPasswd( proxyInfo.getUserName(), proxyInfo.getPassword() ); |
175 |
|
} |
176 |
|
|
177 |
0 |
session.setProxy( proxy ); |
178 |
0 |
} |
179 |
|
else |
180 |
|
{ |
181 |
0 |
session.setProxy( null ); |
182 |
|
} |
183 |
|
|
184 |
0 |
Properties config = new Properties(); |
185 |
0 |
config.setProperty( "BatchMode", interactive ? "no" : "yes" ); |
186 |
|
|
187 |
0 |
if ( !interactive ) |
188 |
|
{ |
189 |
0 |
interactiveUserInfo = new NullInteractiveUserInfo(); |
190 |
0 |
uIKeyboardInteractive = null; |
191 |
|
} |
192 |
|
|
193 |
|
|
194 |
0 |
UserInfo ui = new WagonUserInfo( authenticationInfo, interactiveUserInfo ); |
195 |
|
|
196 |
0 |
if ( uIKeyboardInteractive != null ) |
197 |
|
{ |
198 |
0 |
ui = new UserInfoUIKeyboardInteractiveProxy( ui, uIKeyboardInteractive ); |
199 |
|
} |
200 |
|
|
201 |
0 |
if ( knownHostsProvider != null ) |
202 |
|
{ |
203 |
|
try |
204 |
|
{ |
205 |
0 |
knownHostsProvider.addConfiguration( config ); |
206 |
0 |
knownHostsProvider.addKnownHosts( sch, ui ); |
207 |
|
} |
208 |
0 |
catch ( JSchException e ) |
209 |
|
{ |
210 |
0 |
fireSessionError( e ); |
211 |
|
|
212 |
0 |
} |
213 |
|
} |
214 |
|
|
215 |
0 |
session.setConfig( config ); |
216 |
|
|
217 |
0 |
session.setUserInfo( ui ); |
218 |
|
|
219 |
|
try |
220 |
|
{ |
221 |
0 |
session.connect(); |
222 |
|
|
223 |
0 |
if ( knownHostsProvider != null ) |
224 |
|
{ |
225 |
0 |
knownHostsProvider.storeKnownHosts( sch ); |
226 |
|
} |
227 |
|
} |
228 |
0 |
catch ( JSchException e ) |
229 |
|
{ |
230 |
0 |
fireSessionError( e ); |
231 |
|
|
232 |
0 |
throw new AuthenticationException( "Cannot connect. Reason: " + e.getMessage(), e ); |
233 |
0 |
} |
234 |
0 |
} |
235 |
|
|
236 |
|
private File findPrivateKey() |
237 |
|
{ |
238 |
0 |
String privateKeyDirectory = System.getProperty( "wagon.privateKeyDirectory" ); |
239 |
|
|
240 |
0 |
if ( privateKeyDirectory == null ) |
241 |
|
{ |
242 |
0 |
privateKeyDirectory = System.getProperty( "user.home" ); |
243 |
|
} |
244 |
|
|
245 |
0 |
File privateKey = new File( privateKeyDirectory, ".ssh/id_dsa" ); |
246 |
|
|
247 |
0 |
if ( !privateKey.exists() ) |
248 |
|
{ |
249 |
0 |
privateKey = new File( privateKeyDirectory, ".ssh/id_rsa" ); |
250 |
|
} |
251 |
|
|
252 |
0 |
return privateKey; |
253 |
|
} |
254 |
|
|
255 |
|
public void executeCommand( String command ) |
256 |
|
throws CommandExecutionException |
257 |
|
{ |
258 |
0 |
ChannelExec channel = null; |
259 |
|
|
260 |
0 |
InputStream in = null; |
261 |
0 |
InputStream err = null; |
262 |
0 |
OutputStream out = null; |
263 |
|
try |
264 |
|
{ |
265 |
0 |
fireTransferDebug( "Executing command: " + command ); |
266 |
|
|
267 |
0 |
channel = (ChannelExec) session.openChannel( EXEC_CHANNEL ); |
268 |
|
|
269 |
0 |
channel.setCommand( command + "\n" ); |
270 |
|
|
271 |
0 |
out = channel.getOutputStream(); |
272 |
|
|
273 |
0 |
in = channel.getInputStream(); |
274 |
|
|
275 |
0 |
err = channel.getErrStream(); |
276 |
|
|
277 |
0 |
channel.connect(); |
278 |
|
|
279 |
0 |
BufferedReader r = new BufferedReader( new InputStreamReader( err ) ); |
280 |
|
|
281 |
0 |
List output = null; |
282 |
|
|
283 |
|
while ( true ) |
284 |
|
{ |
285 |
0 |
String line = r.readLine(); |
286 |
0 |
if ( line == null ) |
287 |
|
{ |
288 |
0 |
break; |
289 |
|
} |
290 |
|
|
291 |
0 |
if ( output == null ) |
292 |
|
{ |
293 |
0 |
output = new ArrayList(); |
294 |
|
} |
295 |
|
|
296 |
|
|
297 |
|
|
298 |
0 |
if ( !line.startsWith( "Could not chdir to home directory" ) && !line.endsWith( "ttyname: Operation not supported" ) ) |
299 |
|
{ |
300 |
0 |
output.add( line ); |
301 |
|
} |
302 |
0 |
} |
303 |
|
|
304 |
|
|
305 |
|
|
306 |
|
|
307 |
|
|
308 |
|
|
309 |
|
|
310 |
|
|
311 |
|
|
312 |
|
|
313 |
|
|
314 |
|
|
315 |
0 |
if ( output != null && !output.isEmpty() ) |
316 |
|
{ |
317 |
0 |
throw new CommandExecutionException( |
318 |
|
"Exit code: " + channel.getExitStatus() + " - " + StringUtils.join( output.iterator(), "\n" ) ); |
319 |
|
} |
320 |
|
} |
321 |
0 |
catch ( JSchException e ) |
322 |
|
{ |
323 |
0 |
throw new CommandExecutionException( "Cannot execute remote command: " + command, e ); |
324 |
|
} |
325 |
0 |
catch ( IOException e ) |
326 |
|
{ |
327 |
0 |
throw new CommandExecutionException( "Cannot execute remote command: " + command, e ); |
328 |
|
} |
329 |
|
finally |
330 |
|
{ |
331 |
0 |
IOUtil.close( out ); |
332 |
0 |
IOUtil.close( in ); |
333 |
0 |
IOUtil.close( err ); |
334 |
0 |
if ( channel != null ) |
335 |
|
{ |
336 |
0 |
channel.disconnect(); |
337 |
0 |
} |
338 |
0 |
} |
339 |
0 |
} |
340 |
|
|
341 |
|
protected String readLine( InputStream in ) |
342 |
|
throws IOException |
343 |
|
{ |
344 |
0 |
StringBuffer sb = new StringBuffer(); |
345 |
|
|
346 |
|
while ( true ) |
347 |
|
{ |
348 |
0 |
if ( sb.length() > LINE_BUFFER_SIZE ) |
349 |
|
{ |
350 |
0 |
throw new IOException( "Remote server sent a too long line" ); |
351 |
|
} |
352 |
|
|
353 |
0 |
int c = in.read(); |
354 |
|
|
355 |
0 |
if ( c < 0 ) |
356 |
|
{ |
357 |
0 |
throw new IOException( "Remote connection terminated unexpectedly." ); |
358 |
|
} |
359 |
|
|
360 |
0 |
if ( c == LF ) |
361 |
|
{ |
362 |
0 |
break; |
363 |
|
} |
364 |
|
|
365 |
0 |
sb.append( (char) c ); |
366 |
0 |
} |
367 |
0 |
return sb.toString(); |
368 |
|
} |
369 |
|
|
370 |
|
protected static void sendEom( OutputStream out ) |
371 |
|
throws IOException |
372 |
|
{ |
373 |
0 |
out.write( 0 ); |
374 |
|
|
375 |
0 |
out.flush(); |
376 |
0 |
} |
377 |
|
|
378 |
|
public void closeConnection() |
379 |
|
{ |
380 |
0 |
if ( session != null ) |
381 |
|
{ |
382 |
0 |
session.disconnect(); |
383 |
0 |
session = null; |
384 |
|
} |
385 |
0 |
} |
386 |
|
|
387 |
|
protected void handleGetException( Resource resource, Exception e, File destination ) |
388 |
|
throws TransferFailedException, ResourceDoesNotExistException |
389 |
|
{ |
390 |
0 |
fireTransferError( resource, e, TransferEvent.REQUEST_GET ); |
391 |
|
|
392 |
0 |
if ( destination.exists() ) |
393 |
|
{ |
394 |
0 |
boolean deleted = destination.delete(); |
395 |
|
|
396 |
0 |
if ( !deleted ) |
397 |
|
{ |
398 |
0 |
destination.deleteOnExit(); |
399 |
|
} |
400 |
|
} |
401 |
|
|
402 |
0 |
String msg = "Error occured while downloading '" + resource + "' from the remote repository:" + getRepository(); |
403 |
|
|
404 |
0 |
throw new TransferFailedException( msg, e ); |
405 |
|
} |
406 |
|
|
407 |
0 |
private static class WagonUserInfo |
408 |
|
implements UserInfo |
409 |
|
{ |
410 |
|
private final InteractiveUserInfo userInfo; |
411 |
|
|
412 |
|
private String password; |
413 |
|
|
414 |
|
private String passphrase; |
415 |
|
|
416 |
|
WagonUserInfo( AuthenticationInfo authInfo, InteractiveUserInfo userInfo ) |
417 |
0 |
{ |
418 |
0 |
this.userInfo = userInfo; |
419 |
|
|
420 |
0 |
this.password = authInfo.getPassword(); |
421 |
|
|
422 |
0 |
this.passphrase = authInfo.getPassphrase(); |
423 |
0 |
} |
424 |
|
|
425 |
|
public String getPassphrase() |
426 |
|
{ |
427 |
0 |
return passphrase; |
428 |
|
} |
429 |
|
|
430 |
|
public String getPassword() |
431 |
|
{ |
432 |
0 |
return password; |
433 |
|
} |
434 |
|
|
435 |
|
public boolean promptPassphrase( String arg0 ) |
436 |
|
{ |
437 |
0 |
if ( passphrase == null && userInfo != null ) |
438 |
|
{ |
439 |
0 |
passphrase = userInfo.promptPassphrase( arg0 ); |
440 |
|
} |
441 |
0 |
return passphrase != null; |
442 |
|
} |
443 |
|
|
444 |
|
public boolean promptPassword( String arg0 ) |
445 |
|
{ |
446 |
0 |
if ( password == null && userInfo != null ) |
447 |
|
{ |
448 |
0 |
password = userInfo.promptPassword( arg0 ); |
449 |
|
} |
450 |
0 |
return password != null; |
451 |
|
} |
452 |
|
|
453 |
|
public boolean promptYesNo( String arg0 ) |
454 |
|
{ |
455 |
0 |
if ( userInfo != null ) |
456 |
|
{ |
457 |
0 |
return userInfo.promptYesNo( arg0 ); |
458 |
|
} |
459 |
|
else |
460 |
|
{ |
461 |
0 |
return false; |
462 |
|
} |
463 |
|
} |
464 |
|
|
465 |
|
public void showMessage( String message ) |
466 |
|
{ |
467 |
0 |
if ( userInfo != null ) |
468 |
|
{ |
469 |
0 |
userInfo.showMessage( message ); |
470 |
|
} |
471 |
0 |
} |
472 |
|
} |
473 |
|
|
474 |
|
public final KnownHostsProvider getKnownHostsProvider() |
475 |
|
{ |
476 |
0 |
return knownHostsProvider; |
477 |
|
} |
478 |
|
|
479 |
|
public final void setKnownHostsProvider( KnownHostsProvider knownHostsProvider ) |
480 |
|
{ |
481 |
0 |
if ( knownHostsProvider == null ) |
482 |
|
{ |
483 |
0 |
throw new IllegalArgumentException( "knownHostsProvider can't be null" ); |
484 |
|
} |
485 |
0 |
this.knownHostsProvider = knownHostsProvider; |
486 |
0 |
} |
487 |
|
|
488 |
|
public InteractiveUserInfo getInteractiveUserInfo() |
489 |
|
{ |
490 |
0 |
return interactiveUserInfo; |
491 |
|
} |
492 |
|
|
493 |
|
public void setInteractiveUserInfo( InteractiveUserInfo interactiveUserInfo ) |
494 |
|
{ |
495 |
0 |
if ( interactiveUserInfo == null ) |
496 |
|
{ |
497 |
0 |
throw new IllegalArgumentException( "interactiveUserInfo can't be null" ); |
498 |
|
} |
499 |
0 |
this.interactiveUserInfo = interactiveUserInfo; |
500 |
0 |
} |
501 |
|
|
502 |
|
public void putDirectory( File sourceDirectory, String destinationDirectory ) |
503 |
|
throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException |
504 |
|
{ |
505 |
0 |
String basedir = getRepository().getBasedir(); |
506 |
|
|
507 |
0 |
destinationDirectory = StringUtils.replace( destinationDirectory, "\\", "/" ); |
508 |
|
|
509 |
0 |
String path = getPath( basedir, destinationDirectory ); |
510 |
|
try |
511 |
|
{ |
512 |
0 |
if ( getRepository().getPermissions() != null ) |
513 |
|
{ |
514 |
0 |
String dirPerms = getRepository().getPermissions().getDirectoryMode(); |
515 |
|
|
516 |
0 |
if ( dirPerms != null ) |
517 |
|
{ |
518 |
0 |
String umaskCmd = "umask " + PermissionModeUtils.getUserMaskFor( dirPerms ); |
519 |
0 |
executeCommand( umaskCmd ); |
520 |
|
} |
521 |
|
} |
522 |
|
|
523 |
0 |
String mkdirCmd = "mkdir -p " + path; |
524 |
|
|
525 |
0 |
executeCommand( mkdirCmd ); |
526 |
|
} |
527 |
0 |
catch ( CommandExecutionException e ) |
528 |
|
{ |
529 |
0 |
throw new TransferFailedException( "Error performing commands for file transfer", e ); |
530 |
0 |
} |
531 |
|
|
532 |
|
File zipFile; |
533 |
|
try |
534 |
|
{ |
535 |
0 |
zipFile = File.createTempFile( "wagon", ".zip" ); |
536 |
0 |
zipFile.deleteOnExit(); |
537 |
|
|
538 |
0 |
List files = FileUtils.getFileNames( sourceDirectory, "**/**", "", false ); |
539 |
|
|
540 |
0 |
createZip( files, zipFile, sourceDirectory ); |
541 |
|
} |
542 |
0 |
catch ( IOException e ) |
543 |
|
{ |
544 |
0 |
throw new TransferFailedException( "Unable to create ZIP archive of directory", e ); |
545 |
0 |
} |
546 |
|
|
547 |
0 |
put( zipFile, getPath( destinationDirectory, zipFile.getName() ) ); |
548 |
|
|
549 |
|
try |
550 |
|
{ |
551 |
0 |
executeCommand( "cd " + path + "; unzip -q -o " + zipFile.getName() + "; rm -f " + zipFile.getName() ); |
552 |
|
|
553 |
0 |
zipFile.delete(); |
554 |
|
|
555 |
0 |
RepositoryPermissions permissions = getRepository().getPermissions(); |
556 |
|
|
557 |
0 |
if ( permissions != null && permissions.getGroup() != null ) |
558 |
|
{ |
559 |
0 |
executeCommand( "chgrp -Rf " + permissions.getGroup() + " " + path ); |
560 |
|
} |
561 |
|
|
562 |
0 |
if ( permissions != null && permissions.getFileMode() != null ) |
563 |
|
{ |
564 |
0 |
executeCommand( "chmod -Rf " + permissions.getFileMode() + " " + path ); |
565 |
|
} |
566 |
|
} |
567 |
0 |
catch ( CommandExecutionException e ) |
568 |
|
{ |
569 |
0 |
throw new TransferFailedException( "Error performing commands for file transfer", e ); |
570 |
0 |
} |
571 |
0 |
} |
572 |
|
|
573 |
|
public boolean supportsDirectoryCopy() |
574 |
|
{ |
575 |
0 |
return true; |
576 |
|
} |
577 |
|
} |