blob: 9ae7625fb54172fa6c51d866465576f340b034d4 [file] [log] [blame]
package org.apache.maven.wagon.providers.ssh.jsch;
/*
* 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 java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.List;
import java.util.Properties;
import org.apache.maven.wagon.CommandExecutionException;
import org.apache.maven.wagon.CommandExecutor;
import org.apache.maven.wagon.ResourceDoesNotExistException;
import org.apache.maven.wagon.StreamWagon;
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.CommandExecutorStreamProcessor;
import org.apache.maven.wagon.providers.ssh.ScpHelper;
import org.apache.maven.wagon.providers.ssh.SshWagon;
import org.apache.maven.wagon.providers.ssh.interactive.InteractiveUserInfo;
import org.apache.maven.wagon.providers.ssh.interactive.NullInteractiveUserInfo;
import org.apache.maven.wagon.providers.ssh.jsch.interactive.UserInfoUIKeyboardInteractiveProxy;
import org.apache.maven.wagon.providers.ssh.knownhost.KnownHostChangedException;
import org.apache.maven.wagon.providers.ssh.knownhost.KnownHostsProvider;
import org.apache.maven.wagon.providers.ssh.knownhost.UnknownHostException;
import org.apache.maven.wagon.proxy.ProxyInfo;
import org.apache.maven.wagon.resource.Resource;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.StringInputStream;
import com.jcraft.jsch.agentproxy.AgentProxyException;
import com.jcraft.jsch.agentproxy.Connector;
import com.jcraft.jsch.agentproxy.ConnectorFactory;
import com.jcraft.jsch.agentproxy.RemoteIdentityRepository;
import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.HostKey;
import com.jcraft.jsch.HostKeyRepository;
import com.jcraft.jsch.IdentityRepository;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Proxy;
import com.jcraft.jsch.ProxyHTTP;
import com.jcraft.jsch.ProxySOCKS5;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.UIKeyboardInteractive;
import com.jcraft.jsch.UserInfo;
/**
* AbstractJschWagon
*/
public abstract class AbstractJschWagon
extends StreamWagon
implements SshWagon, CommandExecutor
{
protected ScpHelper sshTool = new ScpHelper( this );
protected Session session;
/**
* @plexus.requirement role-hint="file"
*/
private volatile KnownHostsProvider knownHostsProvider;
/**
* @plexus.requirement
*/
private volatile InteractiveUserInfo interactiveUserInfo;
/**
* @plexus.requirement
*/
private volatile UIKeyboardInteractive uIKeyboardInteractive;
private static final int SOCKS5_PROXY_PORT = 1080;
protected static final String EXEC_CHANNEL = "exec";
public void openConnectionInternal()
throws AuthenticationException
{
if ( authenticationInfo == null )
{
authenticationInfo = new AuthenticationInfo();
}
if ( !interactive )
{
uIKeyboardInteractive = null;
setInteractiveUserInfo( new NullInteractiveUserInfo() );
}
JSch sch = new JSch();
File privateKey;
try
{
privateKey = ScpHelper.getPrivateKey( authenticationInfo );
}
catch ( FileNotFoundException e )
{
throw new AuthenticationException( e.getMessage() );
}
//can only pick one method of authentication
if ( privateKey != null && privateKey.exists() )
{
fireSessionDebug( "Using private key: " + privateKey );
try
{
sch.addIdentity( privateKey.getAbsolutePath(), authenticationInfo.getPassphrase() );
}
catch ( JSchException e )
{
throw new AuthenticationException( "Cannot connect. Reason: " + e.getMessage(), e );
}
}
else
{
try
{
Connector connector = ConnectorFactory.getDefault().createConnector();
if ( connector != null )
{
IdentityRepository repo = new RemoteIdentityRepository( connector );
sch.setIdentityRepository( repo );
}
}
catch ( AgentProxyException e )
{
fireSessionDebug( "Unable to connect to agent: " + e.toString() );
}
}
String host = getRepository().getHost();
int port =
repository.getPort() == WagonConstants.UNKNOWN_PORT ? ScpHelper.DEFAULT_SSH_PORT : repository.getPort();
try
{
String userName = authenticationInfo.getUserName();
if ( userName == null )
{
userName = System.getProperty( "user.name" );
}
session = sch.getSession( userName, host, port );
session.setTimeout( getTimeout() );
}
catch ( JSchException e )
{
throw new AuthenticationException( "Cannot connect. Reason: " + e.getMessage(), e );
}
Proxy proxy = null;
ProxyInfo proxyInfo = getProxyInfo( ProxyInfo.PROXY_SOCKS5, getRepository().getHost() );
if ( proxyInfo != null && proxyInfo.getHost() != null )
{
proxy = new ProxySOCKS5( proxyInfo.getHost(), proxyInfo.getPort() );
( (ProxySOCKS5) proxy ).setUserPasswd( proxyInfo.getUserName(), proxyInfo.getPassword() );
}
else
{
proxyInfo = getProxyInfo( ProxyInfo.PROXY_HTTP, getRepository().getHost() );
if ( proxyInfo != null && proxyInfo.getHost() != null )
{
proxy = new ProxyHTTP( proxyInfo.getHost(), proxyInfo.getPort() );
( (ProxyHTTP) proxy ).setUserPasswd( proxyInfo.getUserName(), proxyInfo.getPassword() );
}
else
{
// Backwards compatibility
proxyInfo = getProxyInfo( getRepository().getProtocol(), getRepository().getHost() );
if ( proxyInfo != null && proxyInfo.getHost() != null )
{
// if port == 1080 we will use SOCKS5 Proxy, otherwise will use HTTP Proxy
if ( proxyInfo.getPort() == SOCKS5_PROXY_PORT )
{
proxy = new ProxySOCKS5( proxyInfo.getHost(), proxyInfo.getPort() );
( (ProxySOCKS5) proxy ).setUserPasswd( proxyInfo.getUserName(), proxyInfo.getPassword() );
}
else
{
proxy = new ProxyHTTP( proxyInfo.getHost(), proxyInfo.getPort() );
( (ProxyHTTP) proxy ).setUserPasswd( proxyInfo.getUserName(), proxyInfo.getPassword() );
}
}
}
}
session.setProxy( proxy );
// username and password will be given via UserInfo interface.
UserInfo ui = new WagonUserInfo( authenticationInfo, getInteractiveUserInfo() );
if ( uIKeyboardInteractive != null )
{
ui = new UserInfoUIKeyboardInteractiveProxy( ui, uIKeyboardInteractive );
}
Properties config = new Properties();
if ( getKnownHostsProvider() != null )
{
try
{
String contents = getKnownHostsProvider().getContents();
if ( contents != null )
{
sch.setKnownHosts( new StringInputStream( contents ) );
}
}
catch ( JSchException e )
{
// continue without known_hosts
}
config.setProperty( "StrictHostKeyChecking", getKnownHostsProvider().getHostKeyChecking() );
}
if ( authenticationInfo.getPassword() != null )
{
config.setProperty( "PreferredAuthentications", "gssapi-with-mic,publickey,password,keyboard-interactive" );
}
config.setProperty( "BatchMode", interactive ? "no" : "yes" );
session.setConfig( config );
session.setUserInfo( ui );
StringWriter stringWriter = new StringWriter();
try
{
session.connect();
if ( getKnownHostsProvider() != null )
{
PrintWriter w = new PrintWriter( stringWriter );
HostKeyRepository hkr = sch.getHostKeyRepository();
HostKey[] keys = hkr.getHostKey();
for ( int i = 0; keys != null && i < keys.length; i++ )
{
HostKey key = keys[i];
w.println( key.getHost() + " " + key.getType() + " " + key.getKey() );
}
}
}
catch ( JSchException e )
{
if ( e.getMessage().startsWith( "UnknownHostKey:" ) || e.getMessage().startsWith( "reject HostKey:" ) )
{
throw new UnknownHostException( host, e );
}
else if ( e.getMessage().contains( "HostKey has been changed" ) )
{
throw new KnownHostChangedException( host, e );
}
else
{
throw new AuthenticationException( "Cannot connect. Reason: " + e.getMessage(), e );
}
}
try
{
getKnownHostsProvider().storeKnownHosts( stringWriter.toString() );
}
catch ( IOException e )
{
closeConnection();
throw new AuthenticationException(
"Connection aborted - failed to write to known_hosts. Reason: " + e.getMessage(), e );
}
}
public void closeConnection()
{
if ( session != null )
{
session.disconnect();
session = null;
}
}
public Streams executeCommand( String command, boolean ignoreFailures )
throws CommandExecutionException
{
ChannelExec channel = null;
BufferedReader stdoutReader = null;
BufferedReader stderrReader = null;
try
{
channel = (ChannelExec) session.openChannel( EXEC_CHANNEL );
channel.setCommand( command + "\n" );
InputStream stdout = channel.getInputStream();
InputStream stderr = channel.getErrStream();
channel.connect();
stdoutReader = new BufferedReader( new InputStreamReader( stdout ) );
stderrReader = new BufferedReader( new InputStreamReader( stderr ) );
Streams streams = CommandExecutorStreamProcessor.processStreams( stderrReader, stdoutReader );
if ( streams.getErr().length() > 0 && !ignoreFailures )
{
int exitCode = channel.getExitStatus();
throw new CommandExecutionException( "Exit code: " + exitCode + " - " + streams.getErr() );
}
return streams;
}
catch ( IOException e )
{
throw new CommandExecutionException( "Cannot execute remote command: " + command, e );
}
catch ( JSchException e )
{
throw new CommandExecutionException( "Cannot execute remote command: " + command, e );
}
finally
{
IOUtil.close( stdoutReader );
IOUtil.close( stderrReader );
if ( channel != null )
{
channel.disconnect();
}
}
}
protected void handleGetException( Resource resource, Exception e )
throws TransferFailedException
{
fireTransferError( resource, e, TransferEvent.REQUEST_GET );
String msg =
"Error occurred while downloading '" + resource + "' from the remote repository:" + getRepository() + ": "
+ e.getMessage();
throw new TransferFailedException( msg, e );
}
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 void executeCommand( String command )
throws CommandExecutionException
{
fireTransferDebug( "Executing command: " + command );
executeCommand( command, false );
}
public InteractiveUserInfo getInteractiveUserInfo()
{
return this.interactiveUserInfo;
}
public KnownHostsProvider getKnownHostsProvider()
{
return this.knownHostsProvider;
}
public void setInteractiveUserInfo( InteractiveUserInfo interactiveUserInfo )
{
this.interactiveUserInfo = interactiveUserInfo;
}
public void setKnownHostsProvider( KnownHostsProvider knownHostsProvider )
{
this.knownHostsProvider = knownHostsProvider;
}
public void setUIKeyboardInteractive( UIKeyboardInteractive uIKeyboardInteractive )
{
this.uIKeyboardInteractive = uIKeyboardInteractive;
}
}