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.File;
23 import java.io.InputStream;
24 import java.io.OutputStream;
25 import java.util.ArrayList;
26 import java.util.List;
27
28 import org.apache.maven.wagon.InputData;
29 import org.apache.maven.wagon.OutputData;
30 import org.apache.maven.wagon.PathUtils;
31 import org.apache.maven.wagon.ResourceDoesNotExistException;
32 import org.apache.maven.wagon.TransferFailedException;
33 import org.apache.maven.wagon.authentication.AuthenticationException;
34 import org.apache.maven.wagon.authorization.AuthorizationException;
35 import org.apache.maven.wagon.events.TransferEvent;
36 import org.apache.maven.wagon.providers.ssh.ScpHelper;
37 import org.apache.maven.wagon.repository.RepositoryPermissions;
38 import org.apache.maven.wagon.resource.Resource;
39
40 import com.jcraft.jsch.ChannelSftp;
41 import com.jcraft.jsch.JSchException;
42 import com.jcraft.jsch.SftpATTRS;
43 import com.jcraft.jsch.SftpException;
44
45
46
47
48
49
50
51
52
53
54
55
56
57 public class SftpWagon
58 extends AbstractJschWagon
59 {
60 private static final String SFTP_CHANNEL = "sftp";
61
62 private static final int S_IFDIR = 0x4000;
63
64 private static final long MILLIS_PER_SEC = 1000L;
65
66 private ChannelSftp channel;
67
68 public void closeConnection()
69 {
70 if ( channel != null )
71 {
72 channel.disconnect();
73 }
74 super.closeConnection();
75 }
76
77 public void openConnectionInternal()
78 throws AuthenticationException
79 {
80 super.openConnectionInternal();
81
82 try
83 {
84 channel = (ChannelSftp) session.openChannel( SFTP_CHANNEL );
85
86 channel.connect();
87 }
88 catch ( JSchException e )
89 {
90 throw new AuthenticationException( "Error connecting to remote repository: " + getRepository().getUrl(),
91 e );
92 }
93 }
94
95 private void returnToParentDirectory( Resource resource )
96 {
97 try
98 {
99 String dir = ScpHelper.getResourceDirectory( resource.getName() );
100 String[] dirs = PathUtils.dirnames( dir );
101 for ( int i = 0; i < dirs.length; i++ )
102 {
103 channel.cd( ".." );
104 }
105 }
106 catch ( SftpException e )
107 {
108 fireTransferDebug( "Error returning to parent directory: " + e.getMessage() );
109 }
110 }
111
112 private void putFile( File source, Resource resource, RepositoryPermissions permissions )
113 throws SftpException, TransferFailedException
114 {
115 resource.setContentLength( source.length() );
116
117 resource.setLastModified( source.lastModified() );
118
119 String filename = ScpHelper.getResourceFilename( resource.getName() );
120
121 firePutStarted( resource, source );
122
123 channel.put( source.getAbsolutePath(), filename );
124
125 postProcessListeners( resource, source, TransferEvent.REQUEST_PUT );
126
127 if ( permissions != null && permissions.getGroup() != null )
128 {
129 setGroup( filename, permissions );
130 }
131
132 if ( permissions != null && permissions.getFileMode() != null )
133 {
134 setFileMode( filename, permissions );
135 }
136
137 firePutCompleted( resource, source );
138 }
139
140 private void setGroup( String filename, RepositoryPermissions permissions )
141 {
142 try
143 {
144 int group = Integer.valueOf( permissions.getGroup() ).intValue();
145 channel.chgrp( group, filename );
146 }
147 catch ( NumberFormatException e )
148 {
149
150 fireTransferDebug( "Not setting group: must be a numerical GID for SFTP" );
151 }
152 catch ( SftpException e )
153 {
154 fireTransferDebug( "Not setting group: " + e.getMessage() );
155 }
156 }
157
158 private void setFileMode( String filename, RepositoryPermissions permissions )
159 {
160 try
161 {
162 int mode = getOctalMode( permissions.getFileMode() );
163 channel.chmod( mode, filename );
164 }
165 catch ( NumberFormatException e )
166 {
167
168 fireTransferDebug( "Not setting mode: must be a numerical mode for SFTP" );
169 }
170 catch ( SftpException e )
171 {
172 fireTransferDebug( "Not setting mode: " + e.getMessage() );
173 }
174 }
175
176 private void mkdirs( String resourceName, int mode )
177 throws SftpException, TransferFailedException
178 {
179 String[] dirs = PathUtils.dirnames( resourceName );
180 for ( String dir : dirs )
181 {
182 mkdir( dir, mode );
183
184 channel.cd( dir );
185 }
186 }
187
188 private void mkdir( String dir, int mode )
189 throws TransferFailedException, SftpException
190 {
191 try
192 {
193 SftpATTRS attrs = channel.stat( dir );
194 if ( ( attrs.getPermissions() & S_IFDIR ) == 0 )
195 {
196 throw new TransferFailedException( "Remote path is not a directory: " + dir );
197 }
198 }
199 catch ( SftpException e )
200 {
201
202 channel.mkdir( dir );
203 if ( mode != -1 )
204 {
205 try
206 {
207 channel.chmod( mode, dir );
208 }
209 catch ( SftpException e1 )
210 {
211
212
213 }
214 }
215 }
216 }
217
218 private SftpATTRS changeToRepositoryDirectory( String dir, String filename )
219 throws ResourceDoesNotExistException, SftpException
220 {
221
222 SftpATTRS attrs;
223 try
224 {
225 channel.cd( repository.getBasedir() );
226
227 if ( dir.length() > 0 )
228 {
229 channel.cd( dir );
230 }
231
232 if ( filename.length() == 0 )
233 {
234 filename = ".";
235 }
236
237 attrs = channel.stat( filename );
238 }
239 catch ( SftpException e )
240 {
241 if ( e.toString().trim().endsWith( "No such file" ) )
242 {
243 throw new ResourceDoesNotExistException( e.toString(), e );
244 }
245 else if ( e.toString().trim().indexOf( "Can't change directory" ) != -1 )
246 {
247 throw new ResourceDoesNotExistException( e.toString(), e );
248 }
249 else
250 {
251 throw e;
252 }
253 }
254 return attrs;
255 }
256
257 public void putDirectory( File sourceDirectory, String destinationDirectory )
258 throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
259 {
260 final RepositoryPermissions permissions = repository.getPermissions();
261
262 try
263 {
264 channel.cd( "/" );
265
266 String basedir = getRepository().getBasedir();
267 int directoryMode = getDirectoryMode( permissions );
268
269 mkdirs( basedir + "/", directoryMode );
270
271 fireTransferDebug( "Recursively uploading directory " + sourceDirectory.getAbsolutePath() + " as "
272 + destinationDirectory );
273
274 mkdirs( destinationDirectory, directoryMode );
275 ftpRecursivePut( sourceDirectory, null, ScpHelper.getResourceFilename( destinationDirectory ),
276 directoryMode );
277 }
278 catch ( SftpException e )
279 {
280 String msg =
281 "Error occurred while deploying '" + sourceDirectory.getAbsolutePath() + "' " + "to remote repository: "
282 + getRepository().getUrl() + ": " + e.getMessage();
283
284 throw new TransferFailedException( msg, e );
285 }
286 }
287
288 private void ftpRecursivePut( File sourceFile, String prefix, String fileName, int directoryMode )
289 throws TransferFailedException, SftpException
290 {
291 final RepositoryPermissions permissions = repository.getPermissions();
292
293 if ( sourceFile.isDirectory() )
294 {
295 if ( !fileName.equals( "." ) )
296 {
297 prefix = getFileName( prefix, fileName );
298 mkdir( fileName, directoryMode );
299 channel.cd( fileName );
300 }
301
302 File[] files = sourceFile.listFiles();
303 if ( files != null && files.length > 0 )
304 {
305
306 for ( File file : files )
307 {
308 if ( file.isDirectory() )
309 {
310 ftpRecursivePut( file, prefix, file.getName(), directoryMode );
311 }
312 }
313 for ( File file : files )
314 {
315 if ( !file.isDirectory() )
316 {
317 ftpRecursivePut( file, prefix, file.getName(), directoryMode );
318 }
319 }
320 }
321
322 channel.cd( ".." );
323 }
324 else
325 {
326 Resource resource = ScpHelper.getResource( getFileName( prefix, fileName ) );
327
328 firePutInitiated( resource, sourceFile );
329
330 putFile( sourceFile, resource, permissions );
331 }
332 }
333
334 private String getFileName( String prefix, String fileName )
335 {
336 if ( prefix != null )
337 {
338 prefix = prefix + "/" + fileName;
339 }
340 else
341 {
342 prefix = fileName;
343 }
344 return prefix;
345 }
346
347 public List<String> getFileList( String destinationDirectory )
348 throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
349 {
350 if ( destinationDirectory.length() == 0 )
351 {
352 destinationDirectory = ".";
353 }
354
355 String filename = ScpHelper.getResourceFilename( destinationDirectory );
356
357 String dir = ScpHelper.getResourceDirectory( destinationDirectory );
358
359
360 if ( dir.length() > 0 && dir.charAt( 0 ) == ScpHelper.PATH_SEPARATOR )
361 {
362 dir = dir.substring( 1 );
363 }
364
365 try
366 {
367 SftpATTRS attrs = changeToRepositoryDirectory( dir, filename );
368 if ( ( attrs.getPermissions() & S_IFDIR ) == 0 )
369 {
370 throw new TransferFailedException( "Remote path is not a directory:" + dir );
371 }
372
373 @SuppressWarnings( "unchecked" )
374 List<ChannelSftp.LsEntry> fileList = channel.ls( filename );
375 List<String> files = new ArrayList<String>( fileList.size() );
376 for ( ChannelSftp.LsEntry entry : fileList )
377 {
378 String name = entry.getFilename();
379 if ( entry.getAttrs().isDir() )
380 {
381 if ( !name.equals( "." ) && !name.equals( ".." ) )
382 {
383 if ( !name.endsWith( "/" ) )
384 {
385 name += "/";
386 }
387 files.add( name );
388 }
389 }
390 else
391 {
392 files.add( name );
393 }
394 }
395 return files;
396 }
397 catch ( SftpException e )
398 {
399 String msg =
400 "Error occurred while listing '" + destinationDirectory + "' " + "on remote repository: "
401 + getRepository().getUrl() + ": " + e.getMessage();
402
403 throw new TransferFailedException( msg, e );
404 }
405 }
406
407 public boolean resourceExists( String resourceName )
408 throws TransferFailedException, AuthorizationException
409 {
410 String filename = ScpHelper.getResourceFilename( resourceName );
411
412 String dir = ScpHelper.getResourceDirectory( resourceName );
413
414
415 if ( dir.length() > 0 && dir.charAt( 0 ) == ScpHelper.PATH_SEPARATOR )
416 {
417 dir = dir.substring( 1 );
418 }
419
420 try
421 {
422 changeToRepositoryDirectory( dir, filename );
423
424 return true;
425 }
426 catch ( ResourceDoesNotExistException e )
427 {
428 return false;
429 }
430 catch ( SftpException e )
431 {
432 String msg =
433 "Error occurred while looking for '" + resourceName + "' " + "on remote repository: "
434 + getRepository().getUrl() + ": " + e.getMessage();
435
436 throw new TransferFailedException( msg, e );
437 }
438 }
439
440 protected void cleanupGetTransfer( Resource resource )
441 {
442 returnToParentDirectory( resource );
443 }
444
445 protected void cleanupPutTransfer( Resource resource )
446 {
447 returnToParentDirectory( resource );
448 }
449
450 protected void finishPutTransfer( Resource resource, InputStream input, OutputStream output )
451 throws TransferFailedException
452 {
453 RepositoryPermissions permissions = getRepository().getPermissions();
454
455 String filename = ScpHelper.getResourceFilename( resource.getName() );
456 if ( permissions != null && permissions.getGroup() != null )
457 {
458 setGroup( filename, permissions );
459 }
460
461 if ( permissions != null && permissions.getFileMode() != null )
462 {
463 setFileMode( filename, permissions );
464 }
465 }
466
467 public void fillInputData( InputData inputData )
468 throws TransferFailedException, ResourceDoesNotExistException
469 {
470 Resource resource = inputData.getResource();
471
472 String filename = ScpHelper.getResourceFilename( resource.getName() );
473
474 String dir = ScpHelper.getResourceDirectory( resource.getName() );
475
476
477 if ( dir.length() > 0 && dir.charAt( 0 ) == ScpHelper.PATH_SEPARATOR )
478 {
479 dir = dir.substring( 1 );
480 }
481
482 try
483 {
484 SftpATTRS attrs = changeToRepositoryDirectory( dir, filename );
485
486 long lastModified = attrs.getMTime() * MILLIS_PER_SEC;
487 resource.setContentLength( attrs.getSize() );
488
489 resource.setLastModified( lastModified );
490
491 inputData.setInputStream( channel.get( filename ) );
492 }
493 catch ( SftpException e )
494 {
495 handleGetException( resource, e );
496 }
497 }
498
499 public void fillOutputData( OutputData outputData )
500 throws TransferFailedException
501 {
502 int directoryMode = getDirectoryMode( getRepository().getPermissions() );
503
504 Resource resource = outputData.getResource();
505
506 try
507 {
508 channel.cd( "/" );
509
510 String basedir = getRepository().getBasedir();
511 mkdirs( basedir + "/", directoryMode );
512
513 mkdirs( resource.getName(), directoryMode );
514
515 String filename = ScpHelper.getResourceFilename( resource.getName() );
516 outputData.setOutputStream( channel.put( filename ) );
517 }
518 catch ( TransferFailedException e )
519 {
520 fireTransferError( resource, e, TransferEvent.REQUEST_PUT );
521
522 throw e;
523 }
524 catch ( SftpException e )
525 {
526 fireTransferError( resource, e, TransferEvent.REQUEST_PUT );
527
528 String msg =
529 "Error occurred while deploying '" + resource.getName() + "' " + "to remote repository: "
530 + getRepository().getUrl() + ": " + e.getMessage();
531
532 throw new TransferFailedException( msg, e );
533 }
534 }
535
536
537
538
539
540
541 public int getDirectoryMode( RepositoryPermissions permissions )
542 {
543 int ret = -1;
544
545 if ( permissions != null )
546 {
547 ret = getOctalMode( permissions.getDirectoryMode() );
548 }
549
550 return ret;
551 }
552
553 public int getOctalMode( String mode )
554 {
555 int ret;
556 try
557 {
558 ret = Integer.valueOf( mode, 8 ).intValue();
559 }
560 catch ( NumberFormatException e )
561 {
562
563 fireTransferDebug( "the file mode must be a numerical mode for SFTP" );
564 ret = -1;
565 }
566 return ret;
567 }
568 }