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 ( String d : dirs )
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().contains( "Can't change directory" ) )
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
296 if ( !fileName.equals( "." ) && !fileName.equals( "" ) )
297 {
298 prefix = getFileName( prefix, fileName );
299 mkdir( fileName, directoryMode );
300 channel.cd( fileName );
301 }
302
303 File[] files = sourceFile.listFiles();
304 if ( files != null && files.length > 0 )
305 {
306
307 for ( File file : files )
308 {
309 if ( file.isDirectory() )
310 {
311 ftpRecursivePut( file, prefix, file.getName(), directoryMode );
312 }
313 }
314 for ( File file : files )
315 {
316 if ( !file.isDirectory() )
317 {
318 ftpRecursivePut( file, prefix, file.getName(), directoryMode );
319 }
320 }
321 }
322
323 channel.cd( ".." );
324 }
325 else
326 {
327 Resource resource = ScpHelper.getResource( getFileName( prefix, fileName ) );
328
329 firePutInitiated( resource, sourceFile );
330
331 putFile( sourceFile, resource, permissions );
332 }
333 }
334
335 private String getFileName( String prefix, String fileName )
336 {
337 if ( prefix != null )
338 {
339 prefix = prefix + "/" + fileName;
340 }
341 else
342 {
343 prefix = fileName;
344 }
345 return prefix;
346 }
347
348 public List<String> getFileList( String destinationDirectory )
349 throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
350 {
351 if ( destinationDirectory.length() == 0 )
352 {
353 destinationDirectory = ".";
354 }
355
356 String filename = ScpHelper.getResourceFilename( destinationDirectory );
357
358 String dir = ScpHelper.getResourceDirectory( destinationDirectory );
359
360
361 if ( dir.length() > 0 && dir.charAt( 0 ) == ScpHelper.PATH_SEPARATOR )
362 {
363 dir = dir.substring( 1 );
364 }
365
366 try
367 {
368 SftpATTRS attrs = changeToRepositoryDirectory( dir, filename );
369 if ( ( attrs.getPermissions() & S_IFDIR ) == 0 )
370 {
371 throw new TransferFailedException( "Remote path is not a directory:" + dir );
372 }
373
374 @SuppressWarnings( "unchecked" )
375 List<ChannelSftp.LsEntry> fileList = channel.ls( filename );
376 List<String> files = new ArrayList<String>( fileList.size() );
377 for ( ChannelSftp.LsEntry entry : fileList )
378 {
379 String name = entry.getFilename();
380 if ( entry.getAttrs().isDir() )
381 {
382 if ( !name.equals( "." ) && !name.equals( ".." ) )
383 {
384 if ( !name.endsWith( "/" ) )
385 {
386 name += "/";
387 }
388 files.add( name );
389 }
390 }
391 else
392 {
393 files.add( name );
394 }
395 }
396 return files;
397 }
398 catch ( SftpException e )
399 {
400 String msg =
401 "Error occurred while listing '" + destinationDirectory + "' " + "on remote repository: "
402 + getRepository().getUrl() + ": " + e.getMessage();
403
404 throw new TransferFailedException( msg, e );
405 }
406 }
407
408 public boolean resourceExists( String resourceName )
409 throws TransferFailedException, AuthorizationException
410 {
411 String filename = ScpHelper.getResourceFilename( resourceName );
412
413 String dir = ScpHelper.getResourceDirectory( resourceName );
414
415
416 if ( dir.length() > 0 && dir.charAt( 0 ) == ScpHelper.PATH_SEPARATOR )
417 {
418 dir = dir.substring( 1 );
419 }
420
421 try
422 {
423 changeToRepositoryDirectory( dir, filename );
424
425 return true;
426 }
427 catch ( ResourceDoesNotExistException e )
428 {
429 return false;
430 }
431 catch ( SftpException e )
432 {
433 String msg =
434 "Error occurred while looking for '" + resourceName + "' " + "on remote repository: "
435 + getRepository().getUrl() + ": " + e.getMessage();
436
437 throw new TransferFailedException( msg, e );
438 }
439 }
440
441 protected void cleanupGetTransfer( Resource resource )
442 {
443 returnToParentDirectory( resource );
444 }
445
446 protected void cleanupPutTransfer( Resource resource )
447 {
448 returnToParentDirectory( resource );
449 }
450
451 protected void finishPutTransfer( Resource resource, InputStream input, OutputStream output )
452 throws TransferFailedException
453 {
454 RepositoryPermissions permissions = getRepository().getPermissions();
455
456 String filename = ScpHelper.getResourceFilename( resource.getName() );
457 if ( permissions != null && permissions.getGroup() != null )
458 {
459 setGroup( filename, permissions );
460 }
461
462 if ( permissions != null && permissions.getFileMode() != null )
463 {
464 setFileMode( filename, permissions );
465 }
466 }
467
468 public void fillInputData( InputData inputData )
469 throws TransferFailedException, ResourceDoesNotExistException
470 {
471 Resource resource = inputData.getResource();
472
473 String filename = ScpHelper.getResourceFilename( resource.getName() );
474
475 String dir = ScpHelper.getResourceDirectory( resource.getName() );
476
477
478 if ( dir.length() > 0 && dir.charAt( 0 ) == ScpHelper.PATH_SEPARATOR )
479 {
480 dir = dir.substring( 1 );
481 }
482
483 try
484 {
485 SftpATTRS attrs = changeToRepositoryDirectory( dir, filename );
486
487 long lastModified = attrs.getMTime() * MILLIS_PER_SEC;
488 resource.setContentLength( attrs.getSize() );
489
490 resource.setLastModified( lastModified );
491
492 inputData.setInputStream( channel.get( filename ) );
493 }
494 catch ( SftpException e )
495 {
496 handleGetException( resource, e );
497 }
498 }
499
500 public void fillOutputData( OutputData outputData )
501 throws TransferFailedException
502 {
503 int directoryMode = getDirectoryMode( getRepository().getPermissions() );
504
505 Resource resource = outputData.getResource();
506
507 try
508 {
509 channel.cd( "/" );
510
511 String basedir = getRepository().getBasedir();
512 mkdirs( basedir + "/", directoryMode );
513
514 mkdirs( resource.getName(), directoryMode );
515
516 String filename = ScpHelper.getResourceFilename( resource.getName() );
517 outputData.setOutputStream( channel.put( filename ) );
518 }
519 catch ( TransferFailedException e )
520 {
521 fireTransferError( resource, e, TransferEvent.REQUEST_PUT );
522
523 throw e;
524 }
525 catch ( SftpException e )
526 {
527 fireTransferError( resource, e, TransferEvent.REQUEST_PUT );
528
529 String msg =
530 "Error occurred while deploying '" + resource.getName() + "' " + "to remote repository: "
531 + getRepository().getUrl() + ": " + e.getMessage();
532
533 throw new TransferFailedException( msg, e );
534 }
535 }
536
537
538
539
540
541
542 public int getDirectoryMode( RepositoryPermissions permissions )
543 {
544 int ret = -1;
545
546 if ( permissions != null )
547 {
548 ret = getOctalMode( permissions.getDirectoryMode() );
549 }
550
551 return ret;
552 }
553
554 public int getOctalMode( String mode )
555 {
556 int ret;
557 try
558 {
559 ret = Integer.valueOf( mode, 8 ).intValue();
560 }
561 catch ( NumberFormatException e )
562 {
563
564 fireTransferDebug( "the file mode must be a numerical mode for SFTP" );
565 ret = -1;
566 }
567 return ret;
568 }
569 }