blob: b2b665a2d660ec85bebdea9ecdb0deac3b5316bb [file] [log] [blame]
package org.apache.maven.wagon.providers.ssh.external;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import org.apache.maven.wagon.AbstractWagon;
import org.apache.maven.wagon.CommandExecutionException;
import org.apache.maven.wagon.CommandExecutor;
import org.apache.maven.wagon.PathUtils;
import org.apache.maven.wagon.PermissionModeUtils;
import org.apache.maven.wagon.ResourceDoesNotExistException;
import org.apache.maven.wagon.Streams;
import org.apache.maven.wagon.TransferFailedException;
import org.apache.maven.wagon.WagonConstants;
import org.apache.maven.wagon.authentication.AuthenticationException;
import org.apache.maven.wagon.authentication.AuthenticationInfo;
import org.apache.maven.wagon.authorization.AuthorizationException;
import org.apache.maven.wagon.events.TransferEvent;
import org.apache.maven.wagon.providers.ssh.ScpHelper;
import org.apache.maven.wagon.repository.RepositoryPermissions;
import org.apache.maven.wagon.resource.Resource;
import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.util.cli.CommandLineException;
import org.codehaus.plexus.util.cli.CommandLineUtils;
import org.codehaus.plexus.util.cli.Commandline;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.List;
import java.util.Locale;
/**
* SCP deployer using "external" scp program. To allow for
* ssh-agent type behavior, until we can construct a Java SSH Agent and interface for JSch.
*
* @author <a href="mailto:brett@apache.org">Brett Porter</a>
* @todo [BP] add compression flag
* @plexus.component role="org.apache.maven.wagon.Wagon"
* role-hint="scpexe"
* instantiation-strategy="per-lookup"
*/
public class ScpExternalWagon
extends AbstractWagon
implements CommandExecutor
{
/**
* The external SCP command to use - default is <code>scp</code>.
*
* @component.configuration default="scp"
*/
private String scpExecutable = "scp";
/**
* The external SSH command to use - default is <code>ssh</code>.
*
* @component.configuration default="ssh"
*/
private String sshExecutable = "ssh";
/**
* Arguments to pass to the SCP command.
*
* @component.configuration
*/
private String scpArgs;
/**
* Arguments to pass to the SSH command.
*
* @component.configuration
*/
private String sshArgs;
private ScpHelper sshTool = new ScpHelper( this );
private static final int SSH_FATAL_EXIT_CODE = 255;
// ----------------------------------------------------------------------
//
// ----------------------------------------------------------------------
protected void openConnectionInternal()
throws AuthenticationException
{
if ( authenticationInfo == null )
{
authenticationInfo = new AuthenticationInfo();
}
}
public void closeConnection()
{
// nothing to disconnect
}
public boolean getIfNewer( String resourceName, File destination, long timestamp )
throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
{
fireSessionDebug( "getIfNewer in SCP wagon is not supported - performing an unconditional get" );
get( resourceName, destination );
return true;
}
/**
* @return The hostname of the remote server prefixed with the username, which comes either from the repository URL
* or from the authenticationInfo.
*/
private String buildRemoteHost()
{
String username = this.getRepository().getUsername();
if ( username == null )
{
username = authenticationInfo.getUserName();
}
if ( username == null )
{
return getRepository().getHost();
}
else
{
return username + "@" + getRepository().getHost();
}
}
public void executeCommand( String command )
throws CommandExecutionException
{
fireTransferDebug( "Executing command: " + command );
executeCommand( command, false );
}
public Streams executeCommand( String command, boolean ignoreFailures )
throws CommandExecutionException
{
boolean putty = isPuTTY();
File privateKey;
try
{
privateKey = ScpHelper.getPrivateKey( authenticationInfo );
}
catch ( FileNotFoundException e )
{
throw new CommandExecutionException( e.getMessage(), e );
}
Commandline cl = createBaseCommandLine( putty, sshExecutable, privateKey );
int port =
repository.getPort() == WagonConstants.UNKNOWN_PORT ? ScpHelper.DEFAULT_SSH_PORT : repository.getPort();
if ( port != ScpHelper.DEFAULT_SSH_PORT )
{
if ( putty )
{
cl.createArg().setLine( "-P " + port );
}
else
{
cl.createArg().setLine( "-p " + port );
}
}
if ( sshArgs != null )
{
cl.createArg().setLine( sshArgs );
}
String remoteHost = this.buildRemoteHost();
cl.createArg().setValue( remoteHost );
cl.createArg().setValue( command );
fireSessionDebug( "Executing command: " + cl.toString() );
try
{
CommandLineUtils.StringStreamConsumer out = new CommandLineUtils.StringStreamConsumer();
CommandLineUtils.StringStreamConsumer err = new CommandLineUtils.StringStreamConsumer();
int exitCode = CommandLineUtils.executeCommandLine( cl, out, err );
Streams streams = new Streams();
streams.setOut( out.getOutput() );
streams.setErr( err.getOutput() );
fireSessionDebug( streams.getOut() );
fireSessionDebug( streams.getErr() );
if ( exitCode != 0 )
{
if ( !ignoreFailures || exitCode == SSH_FATAL_EXIT_CODE )
{
throw new CommandExecutionException( "Exit code " + exitCode + " - " + err.getOutput() );
}
}
return streams;
}
catch ( CommandLineException e )
{
throw new CommandExecutionException( "Error executing command line", e );
}
}
protected boolean isPuTTY()
{
String exe = sshExecutable.toLowerCase( Locale.ENGLISH );
return exe.contains( "plink" ) || exe.contains( "klink" );
}
private Commandline createBaseCommandLine( boolean putty, String executable, File privateKey )
{
Commandline cl = new Commandline();
cl.setExecutable( executable );
if ( privateKey != null )
{
cl.createArg().setValue( "-i" );
cl.createArg().setFile( privateKey );
}
String password = authenticationInfo.getPassword();
if ( putty && password != null )
{
cl.createArg().setValue( "-pw" );
cl.createArg().setValue( password );
}
// should check interactive flag, but scpexe never works in interactive mode right now due to i/o streams
if ( putty )
{
cl.createArg().setValue( "-batch" );
}
else
{
cl.createArg().setValue( "-o" );
cl.createArg().setValue( "BatchMode yes" );
}
return cl;
}
private void executeScpCommand( Resource resource, File localFile, boolean put )
throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
{
boolean putty = isPuTTYSCP();
File privateKey;
try
{
privateKey = ScpHelper.getPrivateKey( authenticationInfo );
}
catch ( FileNotFoundException e )
{
fireSessionConnectionRefused();
throw new AuthorizationException( e.getMessage() );
}
Commandline cl = createBaseCommandLine( putty, scpExecutable, privateKey );
cl.setWorkingDirectory( localFile.getParentFile().getAbsolutePath() );
int port =
repository.getPort() == WagonConstants.UNKNOWN_PORT ? ScpHelper.DEFAULT_SSH_PORT : repository.getPort();
if ( port != ScpHelper.DEFAULT_SSH_PORT )
{
cl.createArg().setLine( "-P " + port );
}
if ( scpArgs != null )
{
cl.createArg().setLine( scpArgs );
}
String resourceName = normalizeResource( resource );
String remoteFile = getRepository().getBasedir() + "/" + resourceName;
remoteFile = StringUtils.replace( remoteFile, " ", "\\ " );
String qualifiedRemoteFile = this.buildRemoteHost() + ":" + remoteFile;
if ( put )
{
cl.createArg().setValue( localFile.getName() );
cl.createArg().setValue( qualifiedRemoteFile );
}
else
{
cl.createArg().setValue( qualifiedRemoteFile );
cl.createArg().setValue( localFile.getName() );
}
fireSessionDebug( "Executing command: " + cl.toString() );
try
{
CommandLineUtils.StringStreamConsumer err = new CommandLineUtils.StringStreamConsumer();
int exitCode = CommandLineUtils.executeCommandLine( cl, null, err );
if ( exitCode != 0 )
{
if ( !put
&& err.getOutput().trim().toLowerCase( Locale.ENGLISH ).contains( "no such file or directory" ) )
{
throw new ResourceDoesNotExistException( err.getOutput() );
}
else
{
TransferFailedException e =
new TransferFailedException( "Exit code: " + exitCode + " - " + err.getOutput() );
fireTransferError( resource, e, put ? TransferEvent.REQUEST_PUT : TransferEvent.REQUEST_GET );
throw e;
}
}
}
catch ( CommandLineException e )
{
fireTransferError( resource, e, put ? TransferEvent.REQUEST_PUT : TransferEvent.REQUEST_GET );
throw new TransferFailedException( "Error executing command line", e );
}
}
boolean isPuTTYSCP()
{
String exe = scpExecutable.toLowerCase( Locale.ENGLISH );
return exe.contains( "pscp" ) || exe.contains( "kscp" );
}
private String normalizeResource( Resource resource )
{
return StringUtils.replace( resource.getName(), "\\", "/" );
}
public void put( File source, String destination )
throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
{
Resource resource = new Resource( destination );
firePutInitiated( resource, source );
if ( !source.exists() )
{
throw new ResourceDoesNotExistException( "Specified source file does not exist: " + source );
}
String basedir = getRepository().getBasedir();
String resourceName = StringUtils.replace( destination, "\\", "/" );
String dir = PathUtils.dirname( resourceName );
dir = StringUtils.replace( dir, "\\", "/" );
String umaskCmd = null;
if ( getRepository().getPermissions() != null )
{
String dirPerms = getRepository().getPermissions().getDirectoryMode();
if ( dirPerms != null )
{
umaskCmd = "umask " + PermissionModeUtils.getUserMaskFor( dirPerms );
}
}
String mkdirCmd = "mkdir -p " + basedir + "/" + dir + "\n";
if ( umaskCmd != null )
{
mkdirCmd = umaskCmd + "; " + mkdirCmd;
}
try
{
executeCommand( mkdirCmd );
}
catch ( CommandExecutionException e )
{
fireTransferError( resource, e, TransferEvent.REQUEST_PUT );
throw new TransferFailedException( "Error executing command for transfer", e );
}
resource.setContentLength( source.length() );
resource.setLastModified( source.lastModified() );
firePutStarted( resource, source );
executeScpCommand( resource, source, true );
postProcessListeners( resource, source, TransferEvent.REQUEST_PUT );
try
{
RepositoryPermissions permissions = getRepository().getPermissions();
if ( permissions != null && permissions.getGroup() != null )
{
executeCommand( "chgrp -f " + permissions.getGroup() + " " + basedir + "/" + resourceName + "\n",
true );
}
if ( permissions != null && permissions.getFileMode() != null )
{
executeCommand( "chmod -f " + permissions.getFileMode() + " " + basedir + "/" + resourceName + "\n",
true );
}
}
catch ( CommandExecutionException e )
{
fireTransferError( resource, e, TransferEvent.REQUEST_PUT );
throw new TransferFailedException( "Error executing command for transfer", e );
}
firePutCompleted( resource, source );
}
public void get( String resourceName, File destination )
throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
{
String path = StringUtils.replace( resourceName, "\\", "/" );
Resource resource = new Resource( path );
fireGetInitiated( resource, destination );
createParentDirectories( destination );
fireGetStarted( resource, destination );
executeScpCommand( resource, destination, false );
postProcessListeners( resource, destination, TransferEvent.REQUEST_GET );
fireGetCompleted( resource, destination );
}
//
// these parameters are user specific, so should not be read from the repository itself.
// They can be configured by plexus, or directly on the instantiated object.
// Alternatively, we may later accept a generic parameters argument to connect, or some other configure(Properties)
// method on a Wagon.
//
public List<String> getFileList( String destinationDirectory )
throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
{
return sshTool.getFileList( destinationDirectory, repository );
}
public void putDirectory( File sourceDirectory, String destinationDirectory )
throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
{
sshTool.putDirectory( this, sourceDirectory, destinationDirectory );
}
public boolean resourceExists( String resourceName )
throws TransferFailedException, AuthorizationException
{
return sshTool.resourceExists( resourceName, repository );
}
public boolean supportsDirectoryCopy()
{
return true;
}
public String getScpExecutable()
{
return scpExecutable;
}
public void setScpExecutable( String scpExecutable )
{
this.scpExecutable = scpExecutable;
}
public String getSshExecutable()
{
return sshExecutable;
}
public void setSshExecutable( String sshExecutable )
{
this.sshExecutable = sshExecutable;
}
public String getScpArgs()
{
return scpArgs;
}
public void setScpArgs( String scpArgs )
{
this.scpArgs = scpArgs;
}
public String getSshArgs()
{
return sshArgs;
}
public void setSshArgs( String sshArgs )
{
this.sshArgs = sshArgs;
}
}