001package org.apache.maven.wagon.providers.ssh;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *   http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import org.apache.maven.wagon.CommandExecutionException;
023import org.apache.maven.wagon.CommandExecutor;
024import org.apache.maven.wagon.PathUtils;
025import org.apache.maven.wagon.PermissionModeUtils;
026import org.apache.maven.wagon.ResourceDoesNotExistException;
027import org.apache.maven.wagon.Streams;
028import org.apache.maven.wagon.TransferFailedException;
029import org.apache.maven.wagon.Wagon;
030import org.apache.maven.wagon.authentication.AuthenticationInfo;
031import org.apache.maven.wagon.authorization.AuthorizationException;
032import org.apache.maven.wagon.repository.Repository;
033import org.apache.maven.wagon.repository.RepositoryPermissions;
034import org.apache.maven.wagon.resource.Resource;
035import org.codehaus.plexus.util.FileUtils;
036import org.codehaus.plexus.util.IOUtil;
037import org.codehaus.plexus.util.StringUtils;
038
039import java.io.File;
040import java.io.FileInputStream;
041import java.io.FileNotFoundException;
042import java.io.FileOutputStream;
043import java.io.IOException;
044import java.util.List;
045import java.util.zip.ZipEntry;
046import java.util.zip.ZipOutputStream;
047
048/**
049 * Scp helper for general algorithms on ssh server.
050 * See {@link #putDirectory(Wagon, File, String) putDirectory(...)} for more info on bulk directory upload.
051 */
052public class ScpHelper
053{
054    public static final char PATH_SEPARATOR = '/';
055
056    public static final int DEFAULT_SSH_PORT = 22;
057
058    private final CommandExecutor executor;
059
060    public ScpHelper( CommandExecutor executor )
061    {
062        this.executor = executor;
063    }
064
065    public static String getResourceDirectory( String resourceName )
066    {
067        String dir = PathUtils.dirname( resourceName );
068        dir = StringUtils.replace( dir, "\\", "/" );
069        return dir;
070    }
071
072    public static String getResourceFilename( String r )
073    {
074        String filename;
075        if ( r.lastIndexOf( PATH_SEPARATOR ) > 0 )
076        {
077            filename = r.substring( r.lastIndexOf( PATH_SEPARATOR ) + 1 );
078        }
079        else
080        {
081            filename = r;
082        }
083        return filename;
084    }
085
086    public static Resource getResource( String resourceName )
087    {
088        String r = StringUtils.replace( resourceName, "\\", "/" );
089        return new Resource( r );
090    }
091
092    public static File getPrivateKey( AuthenticationInfo authenticationInfo )
093        throws FileNotFoundException
094    {
095        // If user don't define a password, he want to use a private key
096        File privateKey = null;
097        if ( authenticationInfo.getPassword() == null )
098        {
099
100            if ( authenticationInfo.getPrivateKey() != null )
101            {
102                privateKey = new File( authenticationInfo.getPrivateKey() );
103                if ( !privateKey.exists() )
104                {
105                    throw new FileNotFoundException( "Private key '" + privateKey + "' not found" );
106                }
107            }
108            else
109            {
110                privateKey = findPrivateKey();
111            }
112
113            if ( privateKey != null && privateKey.exists() )
114            {
115                if ( authenticationInfo.getPassphrase() == null )
116                {
117                    authenticationInfo.setPassphrase( "" );
118                }
119            }
120        }
121        return privateKey;
122    }
123
124    private static File findPrivateKey()
125    {
126        String privateKeyDirectory = System.getProperty( "wagon.privateKeyDirectory" );
127
128        if ( privateKeyDirectory == null )
129        {
130            privateKeyDirectory = System.getProperty( "user.home" );
131        }
132
133        File privateKey = new File( privateKeyDirectory, ".ssh/id_dsa" );
134
135        if ( !privateKey.exists() )
136        {
137            privateKey = new File( privateKeyDirectory, ".ssh/id_rsa" );
138            if ( !privateKey.exists() )
139            {
140                privateKey = null;
141            }
142        }
143
144        return privateKey;
145    }
146
147    public static void createZip( List<String> files, File zipName, File basedir )
148        throws IOException
149    {
150        ZipOutputStream zos = null;
151        try
152        {
153            zos = new ZipOutputStream( new FileOutputStream( zipName ) );
154
155            for ( String file : files )
156            {
157                file = file.replace( '\\', '/' );
158
159                writeZipEntry( zos, new File( basedir, file ), file );
160            }
161
162            zos.close();
163            zos = null;
164        }
165        finally
166        {
167            IOUtil.close( zos );
168        }
169    }
170
171    private static void writeZipEntry( ZipOutputStream jar, File source, String entryName )
172        throws IOException
173    {
174        byte[] buffer = new byte[1024];
175
176        int bytesRead;
177
178        FileInputStream is = new FileInputStream( source );
179
180        try
181        {
182            ZipEntry entry = new ZipEntry( entryName );
183
184            jar.putNextEntry( entry );
185
186            while ( ( bytesRead = is.read( buffer ) ) != -1 )
187            {
188                jar.write( buffer, 0, bytesRead );
189            }
190        }
191        finally
192        {
193            is.close();
194        }
195    }
196
197    protected static String getPath( String basedir, String dir )
198    {
199        String path;
200        path = basedir;
201        if ( !basedir.endsWith( "/" ) && !dir.startsWith( "/" ) )
202        {
203            path += "/";
204        }
205        path += dir;
206        return path;
207    }
208
209    /**
210     * Put a whole directory content, by transferring a unique zip file and uncompressing it
211     * on the target ssh server with <code>unzip</code> command.
212     */
213    public void putDirectory( Wagon wagon, File sourceDirectory, String destinationDirectory )
214        throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
215    {
216        Repository repository = wagon.getRepository();
217
218        String basedir = repository.getBasedir();
219
220        String destDir = StringUtils.replace( destinationDirectory, "\\", "/" );
221
222        String path = getPath( basedir, destDir );
223        try
224        {
225            if ( repository.getPermissions() != null )
226            {
227                String dirPerms = repository.getPermissions().getDirectoryMode();
228
229                if ( dirPerms != null )
230                {
231                    String umaskCmd = "umask " + PermissionModeUtils.getUserMaskFor( dirPerms );
232                    executor.executeCommand( umaskCmd );
233                }
234            }
235
236            //String mkdirCmd = "mkdir -p " + path;
237            String mkdirCmd = "mkdir -p \"" + path + "\"";
238
239            executor.executeCommand( mkdirCmd );
240        }
241        catch ( CommandExecutionException e )
242        {
243            throw new TransferFailedException( "Error performing commands for file transfer", e );
244        }
245
246        File zipFile;
247        try
248        {
249            zipFile = File.createTempFile( "wagon", ".zip" );
250            zipFile.deleteOnExit();
251
252            List<String> files = FileUtils.getFileNames( sourceDirectory, "**/**", "", false );
253
254            createZip( files, zipFile, sourceDirectory );
255        }
256        catch ( IOException e )
257        {
258            throw new TransferFailedException( "Unable to create ZIP archive of directory", e );
259        }
260
261        wagon.put( zipFile, getPath( destDir, zipFile.getName() ) );
262
263        try
264        {
265            //executor.executeCommand(
266            //    "cd " + path + "; unzip -q -o " + zipFile.getName() + "; rm -f " + zipFile.getName() );
267            executor.executeCommand( "cd \"" + path + "\"; unzip -q -o \"" + zipFile.getName() + "\"; rm -f \""
268                + zipFile.getName() + "\"" );
269
270            zipFile.delete();
271
272            RepositoryPermissions permissions = repository.getPermissions();
273
274            if ( permissions != null && permissions.getGroup() != null )
275            {
276                //executor.executeCommand( "chgrp -Rf " + permissions.getGroup() + " " + path );
277                executor.executeCommand( "chgrp -Rf " + permissions.getGroup() + " \"" + path + "\"" );
278            }
279
280            if ( permissions != null && permissions.getFileMode() != null )
281            {
282                //executor.executeCommand( "chmod -Rf " + permissions.getFileMode() + " " + path );
283                executor.executeCommand( "chmod -Rf " + permissions.getFileMode() + " \"" + path + "\"" );
284            }
285        }
286        catch ( CommandExecutionException e )
287        {
288            throw new TransferFailedException( "Error performing commands for file transfer", e );
289        }
290    }
291
292    public List<String> getFileList( String destinationDirectory, Repository repository )
293        throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
294    {
295        try
296        {
297            String path = getPath( repository.getBasedir(), destinationDirectory );
298            //Streams streams = executor.executeCommand( "ls -FlA " + path, false );
299            Streams streams = executor.executeCommand( "ls -FlA \"" + path + "\"", false );
300
301            return new LSParser().parseFiles( streams.getOut() );
302        }
303        catch ( CommandExecutionException e )
304        {
305            if ( e.getMessage().trim().endsWith( "No such file or directory" ) )
306            {
307                throw new ResourceDoesNotExistException( e.getMessage().trim(), e );
308            }
309            else if ( e.getMessage().trim().endsWith( "Not a directory" ) )
310            {
311                throw new ResourceDoesNotExistException( e.getMessage().trim(), e );
312            }
313            else
314            {
315                throw new TransferFailedException( "Error performing file listing.", e );
316            }
317        }
318    }
319
320    public boolean resourceExists( String resourceName, Repository repository )
321        throws TransferFailedException, AuthorizationException
322    {
323        try
324        {
325            String path = getPath( repository.getBasedir(), resourceName );
326            //executor.executeCommand( "ls " + path, false );
327            executor.executeCommand( "ls \"" + path + "\"" );
328
329            // Parsing of output not really needed.  As a failed ls results in a
330            // CommandExectionException on the 'ls' command.
331
332            return true;
333        }
334        catch ( CommandExecutionException e )
335        {
336            // Error?  Then the 'ls' command failed.  No such file found.
337            return false;
338        }
339    }
340
341    public void createRemoteDirectories( String path, RepositoryPermissions permissions )
342        throws CommandExecutionException
343    {
344        String umaskCmd = null;
345        if ( permissions != null )
346        {
347            String dirPerms = permissions.getDirectoryMode();
348
349            if ( dirPerms != null )
350            {
351                umaskCmd = "umask " + PermissionModeUtils.getUserMaskFor( dirPerms );
352            }
353        }
354
355        //String mkdirCmd = "mkdir -p " + path;
356        String mkdirCmd = "mkdir -p \"" + path + "\"";
357
358        if ( umaskCmd != null )
359        {
360            mkdirCmd = umaskCmd + "; " + mkdirCmd;
361        }
362
363        executor.executeCommand( mkdirCmd );
364    }
365}