View Javadoc

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