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 = new ZipOutputStream( new FileOutputStream( zipName ) );
151
152        try
153        {
154            for ( String file : files )
155            {
156                file = file.replace( '\\', '/' );
157
158                writeZipEntry( zos, new File( basedir, file ), file );
159            }
160        }
161        finally
162        {
163            IOUtil.close( zos );
164        }
165    }
166
167    private static void writeZipEntry( ZipOutputStream jar, File source, String entryName )
168        throws IOException
169    {
170        byte[] buffer = new byte[1024];
171
172        int bytesRead;
173
174        FileInputStream is = new FileInputStream( source );
175
176        try
177        {
178            ZipEntry entry = new ZipEntry( entryName );
179
180            jar.putNextEntry( entry );
181
182            while ( ( bytesRead = is.read( buffer ) ) != -1 )
183            {
184                jar.write( buffer, 0, bytesRead );
185            }
186        }
187        finally
188        {
189            is.close();
190        }
191    }
192
193    protected static String getPath( String basedir, String dir )
194    {
195        String path;
196        path = basedir;
197        if ( !basedir.endsWith( "/" ) && !dir.startsWith( "/" ) )
198        {
199            path += "/";
200        }
201        path += dir;
202        return path;
203    }
204
205    /**
206     * Put a whole directory content, by transferring a unique zip file and uncompressing it
207     * on the target ssh server with <code>unzip</code> command.
208     */
209    public void putDirectory( Wagon wagon, File sourceDirectory, String destinationDirectory )
210        throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
211    {
212        Repository repository = wagon.getRepository();
213
214        String basedir = repository.getBasedir();
215
216        String destDir = StringUtils.replace( destinationDirectory, "\\", "/" );
217
218        String path = getPath( basedir, destDir );
219        try
220        {
221            if ( repository.getPermissions() != null )
222            {
223                String dirPerms = repository.getPermissions().getDirectoryMode();
224
225                if ( dirPerms != null )
226                {
227                    String umaskCmd = "umask " + PermissionModeUtils.getUserMaskFor( dirPerms );
228                    executor.executeCommand( umaskCmd );
229                }
230            }
231
232            //String mkdirCmd = "mkdir -p " + path;
233            String mkdirCmd = "mkdir -p \"" + path + "\"";
234
235            executor.executeCommand( mkdirCmd );
236        }
237        catch ( CommandExecutionException e )
238        {
239            throw new TransferFailedException( "Error performing commands for file transfer", e );
240        }
241
242        File zipFile;
243        try
244        {
245            zipFile = File.createTempFile( "wagon", ".zip" );
246            zipFile.deleteOnExit();
247
248            List<String> files = FileUtils.getFileNames( sourceDirectory, "**/**", "", false );
249
250            createZip( files, zipFile, sourceDirectory );
251        }
252        catch ( IOException e )
253        {
254            throw new TransferFailedException( "Unable to create ZIP archive of directory", e );
255        }
256
257        wagon.put( zipFile, getPath( destDir, zipFile.getName() ) );
258
259        try
260        {
261            //executor.executeCommand(
262            //    "cd " + path + "; unzip -q -o " + zipFile.getName() + "; rm -f " + zipFile.getName() );
263            executor.executeCommand( "cd \"" + path + "\"; unzip -q -o \"" + zipFile.getName() + "\"; rm -f \"" + zipFile.getName() + "\"" );
264
265            zipFile.delete();
266
267            RepositoryPermissions permissions = repository.getPermissions();
268
269            if ( permissions != null && permissions.getGroup() != null )
270            {
271                //executor.executeCommand( "chgrp -Rf " + permissions.getGroup() + " " + path );
272                executor.executeCommand( "chgrp -Rf " + permissions.getGroup() + " \"" + path + "\"" );
273            }
274
275            if ( permissions != null && permissions.getFileMode() != null )
276            {
277                //executor.executeCommand( "chmod -Rf " + permissions.getFileMode() + " " + path );
278                executor.executeCommand( "chmod -Rf " + permissions.getFileMode() + " \"" + path + "\"" );
279            }
280        }
281        catch ( CommandExecutionException e )
282        {
283            throw new TransferFailedException( "Error performing commands for file transfer", e );
284        }
285    }
286
287    public List<String> getFileList( String destinationDirectory, Repository repository )
288        throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
289    {
290        try
291        {
292            String path = getPath( repository.getBasedir(), destinationDirectory );
293            //Streams streams = executor.executeCommand( "ls -FlA " + path, false );
294            Streams streams = executor.executeCommand( "ls -FlA \"" + path + "\"", false );
295
296            List<String> ret = new LSParser().parseFiles( streams.getOut() );
297            if ( ret == null || ret.isEmpty() )
298            {
299                throw new ResourceDoesNotExistException( "No such file or directory" );
300            }
301            return ret;
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}