Coverage Report - org.apache.maven.wagon.providers.ssh.AbstractSshWagon
 
Classes in this File Line Coverage Branch Coverage Complexity
AbstractSshWagon
0% 
0% 
4,1
 
 1  
 package org.apache.maven.wagon.providers.ssh;
 2  
 
 3  
 /*
 4  
  * Copyright 2001-2006 The Apache Software Foundation.
 5  
  *
 6  
  * Licensed under the Apache License, Version 2.0 (the "License");
 7  
  * you may not use this file except in compliance with the License.
 8  
  * You may obtain a copy of the License at
 9  
  *
 10  
  *      http://www.apache.org/licenses/LICENSE-2.0
 11  
  *
 12  
  * Unless required by applicable law or agreed to in writing, software
 13  
  * distributed under the License is distributed on an "AS IS" BASIS,
 14  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 15  
  * See the License for the specific language governing permissions and
 16  
  * limitations under the License.
 17  
  */
 18  
 
 19  
 import com.jcraft.jsch.ChannelExec;
 20  
 import com.jcraft.jsch.JSch;
 21  
 import com.jcraft.jsch.JSchException;
 22  
 import com.jcraft.jsch.Proxy;
 23  
 import com.jcraft.jsch.ProxyHTTP;
 24  
 import com.jcraft.jsch.ProxySOCKS5;
 25  
 import com.jcraft.jsch.Session;
 26  
 import com.jcraft.jsch.UIKeyboardInteractive;
 27  
 import com.jcraft.jsch.UserInfo;
 28  
 import org.apache.maven.wagon.AbstractWagon;
 29  
 import org.apache.maven.wagon.CommandExecutionException;
 30  
 import org.apache.maven.wagon.CommandExecutor;
 31  
 import org.apache.maven.wagon.PermissionModeUtils;
 32  
 import org.apache.maven.wagon.ResourceDoesNotExistException;
 33  
 import org.apache.maven.wagon.TransferFailedException;
 34  
 import org.apache.maven.wagon.WagonConstants;
 35  
 import org.apache.maven.wagon.authentication.AuthenticationException;
 36  
 import org.apache.maven.wagon.authentication.AuthenticationInfo;
 37  
 import org.apache.maven.wagon.authorization.AuthorizationException;
 38  
 import org.apache.maven.wagon.events.TransferEvent;
 39  
 import org.apache.maven.wagon.providers.ssh.interactive.InteractiveUserInfo;
 40  
 import org.apache.maven.wagon.providers.ssh.interactive.NullInteractiveUserInfo;
 41  
 import org.apache.maven.wagon.providers.ssh.interactive.UserInfoUIKeyboardInteractiveProxy;
 42  
 import org.apache.maven.wagon.providers.ssh.knownhost.KnownHostsProvider;
 43  
 import org.apache.maven.wagon.repository.RepositoryPermissions;
 44  
 import org.apache.maven.wagon.resource.Resource;
 45  
 import org.codehaus.plexus.util.FileUtils;
 46  
 import org.codehaus.plexus.util.IOUtil;
 47  
 import org.codehaus.plexus.util.StringUtils;
 48  
 
 49  
 import java.io.BufferedReader;
 50  
 import java.io.File;
 51  
 import java.io.IOException;
 52  
 import java.io.InputStream;
 53  
 import java.io.InputStreamReader;
 54  
 import java.io.OutputStream;
 55  
 import java.util.ArrayList;
 56  
 import java.util.List;
 57  
 import java.util.Properties;
 58  
 
 59  
 /**
 60  
  * Common SSH operations.
 61  
  *
 62  
  * @author <a href="mailto:brett@apache.org">Brett Porter</a>
 63  
  * @version $Id: AbstractSshWagon.java 396016 2006-04-21 21:53:52Z carlos $
 64  
  * @todo cache pass[words|phases]
 65  
  */
 66  0
 public abstract class AbstractSshWagon
 67  
     extends AbstractWagon
 68  
     implements CommandExecutor
 69  
 {
 70  
     public static final int DEFAULT_SSH_PORT = 22;
 71  
 
 72  
     public static final int SOCKS5_PROXY_PORT = 1080;
 73  
 
 74  
     protected Session session;
 75  
 
 76  
     public static final String EXEC_CHANNEL = "exec";
 77  
 
 78  
     private static final int LINE_BUFFER_SIZE = 8192;
 79  
 
 80  
     private static final byte LF = '\n';
 81  
 
 82  
     private KnownHostsProvider knownHostsProvider;
 83  
 
 84  
     private InteractiveUserInfo interactiveUserInfo;
 85  
 
 86  
     private UIKeyboardInteractive uIKeyboardInteractive;
 87  
 
 88  
     public void openConnection()
 89  
         throws AuthenticationException
 90  
     {
 91  0
         if ( authenticationInfo == null )
 92  
         {
 93  0
             authenticationInfo = new AuthenticationInfo();
 94  
         }
 95  
 
 96  0
         if ( authenticationInfo.getUserName() == null )
 97  
         {
 98  0
             authenticationInfo.setUserName( System.getProperty( "user.name" ) );
 99  
         }
 100  
 
 101  0
         JSch sch = new JSch();
 102  
 
 103  0
         int port = getRepository().getPort();
 104  
 
 105  0
         if ( port == WagonConstants.UNKNOWN_PORT )
 106  
         {
 107  0
             port = DEFAULT_SSH_PORT;
 108  
         }
 109  
 
 110  0
         String host = getRepository().getHost();
 111  
 
 112  
         try
 113  
         {
 114  0
             session = sch.getSession( authenticationInfo.getUserName(), host, port );
 115  
         }
 116  0
         catch ( JSchException e )
 117  
         {
 118  0
             fireSessionError( e );
 119  
 
 120  0
             throw new AuthenticationException( "Cannot connect. Reason: " + e.getMessage(), e );
 121  0
         }
 122  
 
 123  
         // If user don't define a password, he want to use a private key
 124  0
         if ( authenticationInfo.getPassword() == null )
 125  
         {
 126  
             File privateKey;
 127  
 
 128  0
             if ( authenticationInfo.getPrivateKey() != null )
 129  
             {
 130  0
                 privateKey = new File( authenticationInfo.getPrivateKey() );
 131  0
             }
 132  
             else
 133  
             {
 134  0
                 privateKey = findPrivateKey();
 135  
             }
 136  
 
 137  0
             if ( privateKey.exists() )
 138  
             {
 139  0
                 if ( authenticationInfo.getPassphrase() == null )
 140  
                 {
 141  0
                     authenticationInfo.setPassphrase( "" );
 142  
                 }
 143  
 
 144  0
                 fireSessionDebug( "Using private key: " + privateKey );
 145  
 
 146  
                 try
 147  
                 {
 148  0
                     sch.addIdentity( privateKey.getAbsolutePath(), authenticationInfo.getPassphrase() );
 149  
                 }
 150  0
                 catch ( JSchException e )
 151  
                 {
 152  0
                     fireSessionError( e );
 153  
 
 154  0
                     throw new AuthenticationException( "Cannot connect. Reason: " + e.getMessage(), e );
 155  0
                 }
 156  
             }
 157  
         }
 158  
 
 159  0
         if ( proxyInfo != null && proxyInfo.getHost() != null )
 160  
         {
 161  
             Proxy proxy;
 162  
 
 163  0
             int proxyPort = proxyInfo.getPort();
 164  
 
 165  
             // HACK: if port == 1080 we will use SOCKS5 Proxy, otherwise will use HTTP Proxy
 166  0
             if ( proxyPort == SOCKS5_PROXY_PORT )
 167  
             {
 168  0
                 proxy = new ProxySOCKS5( proxyInfo.getHost() );
 169  0
                 ( (ProxySOCKS5) proxy ).setUserPasswd( proxyInfo.getUserName(), proxyInfo.getPassword() );
 170  0
             }
 171  
             else
 172  
             {
 173  0
                 proxy = new ProxyHTTP( proxyInfo.getHost(), proxyPort );
 174  0
                 ( (ProxyHTTP) proxy ).setUserPasswd( proxyInfo.getUserName(), proxyInfo.getPassword() );
 175  
             }
 176  
 
 177  0
             session.setProxy( proxy );
 178  0
         }
 179  
         else
 180  
         {
 181  0
             session.setProxy( null );
 182  
         }
 183  
 
 184  0
         Properties config = new Properties();
 185  0
         config.setProperty( "BatchMode", interactive ? "no" : "yes" );
 186  
 
 187  0
         if ( !interactive )
 188  
         {
 189  0
             interactiveUserInfo = new NullInteractiveUserInfo();
 190  0
             uIKeyboardInteractive = null;
 191  
         }
 192  
 
 193  
         // username and password will be given via UserInfo interface.
 194  0
         UserInfo ui = new WagonUserInfo( authenticationInfo, interactiveUserInfo );
 195  
 
 196  0
         if ( uIKeyboardInteractive != null )
 197  
         {
 198  0
             ui = new UserInfoUIKeyboardInteractiveProxy( ui, uIKeyboardInteractive );
 199  
         }
 200  
 
 201  0
         if ( knownHostsProvider != null )
 202  
         {
 203  
             try
 204  
             {
 205  0
                 knownHostsProvider.addConfiguration( config );
 206  0
                 knownHostsProvider.addKnownHosts( sch, ui );
 207  
             }
 208  0
             catch ( JSchException e )
 209  
             {
 210  0
                 fireSessionError( e );
 211  
                 // continue without known_hosts
 212  0
             }
 213  
         }
 214  
 
 215  0
         session.setConfig( config );
 216  
 
 217  0
         session.setUserInfo( ui );
 218  
 
 219  
         try
 220  
         {
 221  0
             session.connect();
 222  
 
 223  0
             if ( knownHostsProvider != null )
 224  
             {
 225  0
                 knownHostsProvider.storeKnownHosts( sch );
 226  
             }
 227  
         }
 228  0
         catch ( JSchException e )
 229  
         {
 230  0
             fireSessionError( e );
 231  
 
 232  0
             throw new AuthenticationException( "Cannot connect. Reason: " + e.getMessage(), e );
 233  0
         }
 234  0
     }
 235  
 
 236  
     private File findPrivateKey()
 237  
     {
 238  0
         String privateKeyDirectory = System.getProperty( "wagon.privateKeyDirectory" );
 239  
 
 240  0
         if ( privateKeyDirectory == null )
 241  
         {
 242  0
             privateKeyDirectory = System.getProperty( "user.home" );
 243  
         }
 244  
 
 245  0
         File privateKey = new File( privateKeyDirectory, ".ssh/id_dsa" );
 246  
 
 247  0
         if ( !privateKey.exists() )
 248  
         {
 249  0
             privateKey = new File( privateKeyDirectory, ".ssh/id_rsa" );
 250  
         }
 251  
 
 252  0
         return privateKey;
 253  
     }
 254  
 
 255  
     public void executeCommand( String command )
 256  
         throws CommandExecutionException
 257  
     {
 258  0
         ChannelExec channel = null;
 259  
 
 260  0
         InputStream in = null;
 261  0
         InputStream err = null;
 262  0
         OutputStream out = null;
 263  
         try
 264  
         {
 265  0
             fireTransferDebug( "Executing command: " + command );
 266  
 
 267  0
             channel = (ChannelExec) session.openChannel( EXEC_CHANNEL );
 268  
 
 269  0
             channel.setCommand( command + "\n" );
 270  
 
 271  0
             out = channel.getOutputStream();
 272  
 
 273  0
             in = channel.getInputStream();
 274  
 
 275  0
             err = channel.getErrStream();
 276  
 
 277  0
             channel.connect();
 278  
 
 279  0
             BufferedReader r = new BufferedReader( new InputStreamReader( err ) );
 280  
 
 281  0
             List output = null;
 282  
 
 283  
             while ( true )
 284  
             {
 285  0
                 String line = r.readLine();
 286  0
                 if ( line == null )
 287  
                 {
 288  0
                     break;
 289  
                 }
 290  
 
 291  0
                 if ( output == null )
 292  
                 {
 293  0
                     output = new ArrayList();
 294  
                 }
 295  
 
 296  
                 // TODO: I think we need to deal with exit codes instead, but IIRC there are some cases of errors that don't have exit codes
 297  
                 // ignore this error. TODO: output a warning
 298  0
                 if ( !line.startsWith( "Could not chdir to home directory" ) && !line.endsWith( "ttyname: Operation not supported" ) )
 299  
                 {
 300  0
                     output.add( line );
 301  
                 }
 302  0
             }
 303  
             
 304  
             // drain the output stream.
 305  
             // TODO: we'll save this for the 1.0-alpha-8 line, so we can test it more. the -q arg in the
 306  
             // unzip command should keep us until then...
 307  
 //            int avail = in.available();
 308  
 //            byte[] trashcan = new byte[1024];
 309  
 //            
 310  
 //            while( ( avail = in.available() ) > 0 )
 311  
 //            {
 312  
 //                in.read( trashcan, 0, avail );
 313  
 //            }
 314  
 
 315  0
             if ( output != null && !output.isEmpty() )
 316  
             {
 317  0
                 throw new CommandExecutionException(
 318  
                     "Exit code: " + channel.getExitStatus() + " - " + StringUtils.join( output.iterator(), "\n" ) );
 319  
             }
 320  
         }
 321  0
         catch ( JSchException e )
 322  
         {
 323  0
             throw new CommandExecutionException( "Cannot execute remote command: " + command, e );
 324  
         }
 325  0
         catch ( IOException e )
 326  
         {
 327  0
             throw new CommandExecutionException( "Cannot execute remote command: " + command, e );
 328  
         }
 329  
         finally
 330  
         {
 331  0
             IOUtil.close( out );
 332  0
             IOUtil.close( in );
 333  0
             IOUtil.close( err );
 334  0
             if ( channel != null )
 335  
             {
 336  0
                 channel.disconnect();
 337  0
             }
 338  0
         }
 339  0
     }
 340  
 
 341  
     protected String readLine( InputStream in )
 342  
         throws IOException
 343  
     {
 344  0
         StringBuffer sb = new StringBuffer();
 345  
 
 346  
         while ( true )
 347  
         {
 348  0
             if ( sb.length() > LINE_BUFFER_SIZE )
 349  
             {
 350  0
                 throw new IOException( "Remote server sent a too long line" );
 351  
             }
 352  
 
 353  0
             int c = in.read();
 354  
 
 355  0
             if ( c < 0 )
 356  
             {
 357  0
                 throw new IOException( "Remote connection terminated unexpectedly." );
 358  
             }
 359  
 
 360  0
             if ( c == LF )
 361  
             {
 362  0
                 break;
 363  
             }
 364  
 
 365  0
             sb.append( (char) c );
 366  0
         }
 367  0
         return sb.toString();
 368  
     }
 369  
 
 370  
     protected static void sendEom( OutputStream out )
 371  
         throws IOException
 372  
     {
 373  0
         out.write( 0 );
 374  
 
 375  0
         out.flush();
 376  0
     }
 377  
 
 378  
     public void closeConnection()
 379  
     {
 380  0
         if ( session != null )
 381  
         {
 382  0
             session.disconnect();
 383  0
             session = null;
 384  
         }
 385  0
     }
 386  
 
 387  
     protected void handleGetException( Resource resource, Exception e, File destination )
 388  
         throws TransferFailedException, ResourceDoesNotExistException
 389  
     {
 390  0
         fireTransferError( resource, e, TransferEvent.REQUEST_GET );
 391  
 
 392  0
         if ( destination.exists() )
 393  
         {
 394  0
             boolean deleted = destination.delete();
 395  
 
 396  0
             if ( !deleted )
 397  
             {
 398  0
                 destination.deleteOnExit();
 399  
             }
 400  
         }
 401  
 
 402  0
         String msg = "Error occured while downloading '" + resource + "' from the remote repository:" + getRepository();
 403  
 
 404  0
         throw new TransferFailedException( msg, e );
 405  
     }
 406  
 
 407  0
     private static class WagonUserInfo
 408  
         implements UserInfo
 409  
     {
 410  
         private final InteractiveUserInfo userInfo;
 411  
 
 412  
         private String password;
 413  
 
 414  
         private String passphrase;
 415  
 
 416  
         WagonUserInfo( AuthenticationInfo authInfo, InteractiveUserInfo userInfo )
 417  0
         {
 418  0
             this.userInfo = userInfo;
 419  
 
 420  0
             this.password = authInfo.getPassword();
 421  
 
 422  0
             this.passphrase = authInfo.getPassphrase();
 423  0
         }
 424  
 
 425  
         public String getPassphrase()
 426  
         {
 427  0
             return passphrase;
 428  
         }
 429  
 
 430  
         public String getPassword()
 431  
         {
 432  0
             return password;
 433  
         }
 434  
 
 435  
         public boolean promptPassphrase( String arg0 )
 436  
         {
 437  0
             if ( passphrase == null && userInfo != null )
 438  
             {
 439  0
                 passphrase = userInfo.promptPassphrase( arg0 );
 440  
             }
 441  0
             return passphrase != null;
 442  
         }
 443  
 
 444  
         public boolean promptPassword( String arg0 )
 445  
         {
 446  0
             if ( password == null && userInfo != null )
 447  
             {
 448  0
                 password = userInfo.promptPassword( arg0 );
 449  
             }
 450  0
             return password != null;
 451  
         }
 452  
 
 453  
         public boolean promptYesNo( String arg0 )
 454  
         {
 455  0
             if ( userInfo != null )
 456  
             {
 457  0
                 return userInfo.promptYesNo( arg0 );
 458  
             }
 459  
             else
 460  
             {
 461  0
                 return false;
 462  
             }
 463  
         }
 464  
 
 465  
         public void showMessage( String message )
 466  
         {
 467  0
             if ( userInfo != null )
 468  
             {
 469  0
                 userInfo.showMessage( message );
 470  
             }
 471  0
         }
 472  
     }
 473  
 
 474  
     public final KnownHostsProvider getKnownHostsProvider()
 475  
     {
 476  0
         return knownHostsProvider;
 477  
     }
 478  
 
 479  
     public final void setKnownHostsProvider( KnownHostsProvider knownHostsProvider )
 480  
     {
 481  0
         if ( knownHostsProvider == null )
 482  
         {
 483  0
             throw new IllegalArgumentException( "knownHostsProvider can't be null" );
 484  
         }
 485  0
         this.knownHostsProvider = knownHostsProvider;
 486  0
     }
 487  
 
 488  
     public InteractiveUserInfo getInteractiveUserInfo()
 489  
     {
 490  0
         return interactiveUserInfo;
 491  
     }
 492  
 
 493  
     public void setInteractiveUserInfo( InteractiveUserInfo interactiveUserInfo )
 494  
     {
 495  0
         if ( interactiveUserInfo == null )
 496  
         {
 497  0
             throw new IllegalArgumentException( "interactiveUserInfo can't be null" );
 498  
         }
 499  0
         this.interactiveUserInfo = interactiveUserInfo;
 500  0
     }
 501  
 
 502  
     public void putDirectory( File sourceDirectory, String destinationDirectory )
 503  
         throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
 504  
     {
 505  0
         String basedir = getRepository().getBasedir();
 506  
 
 507  0
         destinationDirectory = StringUtils.replace( destinationDirectory, "\\", "/" );
 508  
 
 509  0
         String path = getPath( basedir, destinationDirectory );
 510  
         try
 511  
         {
 512  0
             if ( getRepository().getPermissions() != null )
 513  
             {
 514  0
                 String dirPerms = getRepository().getPermissions().getDirectoryMode();
 515  
 
 516  0
                 if ( dirPerms != null )
 517  
                 {
 518  0
                     String umaskCmd = "umask " + PermissionModeUtils.getUserMaskFor( dirPerms );
 519  0
                     executeCommand( umaskCmd );
 520  
                 }
 521  
             }
 522  
 
 523  0
             String mkdirCmd = "mkdir -p " + path;
 524  
 
 525  0
             executeCommand( mkdirCmd );
 526  
         }
 527  0
         catch ( CommandExecutionException e )
 528  
         {
 529  0
             throw new TransferFailedException( "Error performing commands for file transfer", e );
 530  0
         }
 531  
 
 532  
         File zipFile;
 533  
         try
 534  
         {
 535  0
             zipFile = File.createTempFile( "wagon", ".zip" );
 536  0
             zipFile.deleteOnExit();
 537  
 
 538  0
             List files = FileUtils.getFileNames( sourceDirectory, "**/**", "", false );
 539  
 
 540  0
             createZip( files, zipFile, sourceDirectory );
 541  
         }
 542  0
         catch ( IOException e )
 543  
         {
 544  0
             throw new TransferFailedException( "Unable to create ZIP archive of directory", e );
 545  0
         }
 546  
 
 547  0
         put( zipFile, getPath( destinationDirectory, zipFile.getName() ) );
 548  
 
 549  
         try
 550  
         {
 551  0
             executeCommand( "cd " + path + "; unzip -q -o " + zipFile.getName() + "; rm -f " + zipFile.getName() );
 552  
 
 553  0
             zipFile.delete();
 554  
 
 555  0
             RepositoryPermissions permissions = getRepository().getPermissions();
 556  
 
 557  0
             if ( permissions != null && permissions.getGroup() != null )
 558  
             {
 559  0
                 executeCommand( "chgrp -Rf " + permissions.getGroup() + " " + path );
 560  
             }
 561  
 
 562  0
             if ( permissions != null && permissions.getFileMode() != null )
 563  
             {
 564  0
                 executeCommand( "chmod -Rf " + permissions.getFileMode() + " " + path );
 565  
             }
 566  
         }
 567  0
         catch ( CommandExecutionException e )
 568  
         {
 569  0
             throw new TransferFailedException( "Error performing commands for file transfer", e );
 570  0
         }
 571  0
     }
 572  
 
 573  
     public boolean supportsDirectoryCopy()
 574  
     {
 575  0
         return true;
 576  
     }
 577  
 }