| 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.ByteArrayInputStream; |
| import java.io.File; |
| import java.io.FileNotFoundException; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| 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.KnownHostEntry; |
| 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 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; |
| import com.jcraft.jsch.agentproxy.AgentProxyException; |
| import com.jcraft.jsch.agentproxy.Connector; |
| import com.jcraft.jsch.agentproxy.ConnectorFactory; |
| import com.jcraft.jsch.agentproxy.RemoteIdentityRepository; |
| |
| /** |
| * 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 ByteArrayInputStream( contents.getBytes() ) ); |
| } |
| } |
| 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 ); |
| |
| try |
| { |
| session.connect(); |
| } |
| 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 ); |
| } |
| } |
| |
| if ( getKnownHostsProvider() != null ) |
| { |
| HostKeyRepository hkr = sch.getHostKeyRepository(); |
| |
| HostKey[] hk = hkr.getHostKey( host, null ); |
| try |
| { |
| if ( hk != null ) |
| { |
| for ( HostKey hostKey : hk ) |
| { |
| KnownHostEntry knownHostEntry = new KnownHostEntry( hostKey.getHost(), hostKey.getType(), |
| hostKey.getKey() ); |
| getKnownHostsProvider().addKnownHost( knownHostEntry ); |
| } |
| } |
| } |
| 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; |
| } |
| } |