View Javadoc

1   package org.apache.maven.wagon.providers.ssh.jsch;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.BufferedReader;
23  import java.io.File;
24  import java.io.FileNotFoundException;
25  import java.io.IOException;
26  import java.io.InputStream;
27  import java.io.InputStreamReader;
28  import java.io.PrintWriter;
29  import java.io.StringWriter;
30  import java.util.List;
31  import java.util.Properties;
32  
33  import org.apache.maven.wagon.CommandExecutionException;
34  import org.apache.maven.wagon.CommandExecutor;
35  import org.apache.maven.wagon.ResourceDoesNotExistException;
36  import org.apache.maven.wagon.StreamWagon;
37  import org.apache.maven.wagon.Streams;
38  import org.apache.maven.wagon.TransferFailedException;
39  import org.apache.maven.wagon.WagonConstants;
40  import org.apache.maven.wagon.authentication.AuthenticationException;
41  import org.apache.maven.wagon.authentication.AuthenticationInfo;
42  import org.apache.maven.wagon.authorization.AuthorizationException;
43  import org.apache.maven.wagon.events.TransferEvent;
44  import org.apache.maven.wagon.providers.ssh.CommandExecutorStreamProcessor;
45  import org.apache.maven.wagon.providers.ssh.ScpHelper;
46  import org.apache.maven.wagon.providers.ssh.SshWagon;
47  import org.apache.maven.wagon.providers.ssh.interactive.InteractiveUserInfo;
48  import org.apache.maven.wagon.providers.ssh.interactive.NullInteractiveUserInfo;
49  import org.apache.maven.wagon.providers.ssh.jsch.interactive.UserInfoUIKeyboardInteractiveProxy;
50  import org.apache.maven.wagon.providers.ssh.knownhost.KnownHostChangedException;
51  import org.apache.maven.wagon.providers.ssh.knownhost.KnownHostsProvider;
52  import org.apache.maven.wagon.providers.ssh.knownhost.UnknownHostException;
53  import org.apache.maven.wagon.proxy.ProxyInfo;
54  import org.apache.maven.wagon.resource.Resource;
55  import org.codehaus.plexus.util.IOUtil;
56  import org.codehaus.plexus.util.StringInputStream;
57  
58  import com.jcraft.jsch.ChannelExec;
59  import com.jcraft.jsch.HostKey;
60  import com.jcraft.jsch.HostKeyRepository;
61  import com.jcraft.jsch.JSch;
62  import com.jcraft.jsch.JSchException;
63  import com.jcraft.jsch.Proxy;
64  import com.jcraft.jsch.ProxyHTTP;
65  import com.jcraft.jsch.ProxySOCKS5;
66  import com.jcraft.jsch.Session;
67  import com.jcraft.jsch.UIKeyboardInteractive;
68  import com.jcraft.jsch.UserInfo;
69  
70  /**
71   * AbstractJschWagon 
72   *
73   *
74   */
75  public abstract class AbstractJschWagon
76      extends StreamWagon
77      implements SshWagon, CommandExecutor
78  {
79      protected ScpHelper sshTool = new ScpHelper( this );
80      
81      protected Session session;
82      
83      /**
84       * @plexus.requirement role-hint="file"
85       */
86      private volatile KnownHostsProvider knownHostsProvider;
87      
88      /**
89       * @plexus.requirement
90       */
91      private volatile InteractiveUserInfo interactiveUserInfo;
92  
93      /**
94       * @plexus.requirement
95       */
96      private volatile UIKeyboardInteractive uIKeyboardInteractive;
97  
98      private static final int SOCKS5_PROXY_PORT = 1080;
99  
100     protected static final String EXEC_CHANNEL = "exec";
101 
102     public void openConnectionInternal()
103         throws AuthenticationException
104     {
105         if ( authenticationInfo == null )
106         {
107             authenticationInfo = new AuthenticationInfo();
108         }
109 
110         if ( !interactive )
111         {
112             uIKeyboardInteractive = null;
113             setInteractiveUserInfo( new NullInteractiveUserInfo() );
114         }
115 
116         JSch sch = new JSch();
117 
118         File privateKey;
119         try
120         {
121             privateKey = ScpHelper.getPrivateKey( authenticationInfo );
122         }
123         catch ( FileNotFoundException e )
124         {
125             throw new AuthenticationException( e.getMessage() );
126         }
127 
128         if ( privateKey != null && privateKey.exists() )
129         {
130             fireSessionDebug( "Using private key: " + privateKey );
131             try
132             {
133                 sch.addIdentity( privateKey.getAbsolutePath(), authenticationInfo.getPassphrase() );
134             }
135             catch ( JSchException e )
136             {
137                 throw new AuthenticationException( "Cannot connect. Reason: " + e.getMessage(), e );
138             }
139         }
140 
141         String host = getRepository().getHost();
142         int port =
143             repository.getPort() == WagonConstants.UNKNOWN_PORT ? ScpHelper.DEFAULT_SSH_PORT : repository.getPort();
144         try
145         {
146             String userName = authenticationInfo.getUserName();
147             if ( userName == null )
148             {
149                 userName = System.getProperty( "user.name" );
150             }
151             session = sch.getSession( userName, host, port );
152             session.setTimeout( getTimeout() );
153         }
154         catch ( JSchException e )
155         {
156             throw new AuthenticationException( "Cannot connect. Reason: " + e.getMessage(), e );
157         }
158 
159         Proxy proxy = null;
160         ProxyInfo proxyInfo = getProxyInfo( ProxyInfo.PROXY_SOCKS5, getRepository().getHost() );
161         if ( proxyInfo != null && proxyInfo.getHost() != null )
162         {
163             proxy = new ProxySOCKS5( proxyInfo.getHost(), proxyInfo.getPort() );
164             ( (ProxySOCKS5) proxy ).setUserPasswd( proxyInfo.getUserName(), proxyInfo.getPassword() );
165         }
166         else
167         {
168             proxyInfo = getProxyInfo( ProxyInfo.PROXY_HTTP, getRepository().getHost() );
169             if ( proxyInfo != null && proxyInfo.getHost() != null )
170             {
171                 proxy = new ProxyHTTP( proxyInfo.getHost(), proxyInfo.getPort() );
172                 ( (ProxyHTTP) proxy ).setUserPasswd( proxyInfo.getUserName(), proxyInfo.getPassword() );
173             }
174             else
175             {
176                 // Backwards compatibility
177                 proxyInfo = getProxyInfo( getRepository().getProtocol(), getRepository().getHost() );
178                 if ( proxyInfo != null && proxyInfo.getHost() != null )
179                 {
180                     // if port == 1080 we will use SOCKS5 Proxy, otherwise will use HTTP Proxy
181                     if ( proxyInfo.getPort() == SOCKS5_PROXY_PORT )
182                     {
183                         proxy = new ProxySOCKS5( proxyInfo.getHost(), proxyInfo.getPort() );
184                         ( (ProxySOCKS5) proxy ).setUserPasswd( proxyInfo.getUserName(), proxyInfo.getPassword() );
185                     }
186                     else
187                     {
188                         proxy = new ProxyHTTP( proxyInfo.getHost(), proxyInfo.getPort() );
189                         ( (ProxyHTTP) proxy ).setUserPasswd( proxyInfo.getUserName(), proxyInfo.getPassword() );
190                     }
191                 }
192             }
193         }
194         session.setProxy( proxy );
195 
196         // username and password will be given via UserInfo interface.
197         UserInfo ui = new WagonUserInfo( authenticationInfo, getInteractiveUserInfo() );
198 
199         if ( uIKeyboardInteractive != null )
200         {
201             ui = new UserInfoUIKeyboardInteractiveProxy( ui, uIKeyboardInteractive );
202         }
203 
204         Properties config = new Properties();
205         if ( getKnownHostsProvider() != null )
206         {
207             try
208             {
209                 String contents = getKnownHostsProvider().getContents();
210                 if ( contents != null )
211                 {
212                     sch.setKnownHosts( new StringInputStream( contents ) );
213                 }
214             }
215             catch ( JSchException e )
216             {
217                 // continue without known_hosts
218             }
219             config.setProperty( "StrictHostKeyChecking", getKnownHostsProvider().getHostKeyChecking() );
220         }
221 
222         if ( authenticationInfo.getPassword() != null )
223         {
224             config.setProperty( "PreferredAuthentications", "gssapi-with-mic,publickey,password,keyboard-interactive" );
225         }
226         
227         config.setProperty( "BatchMode", interactive ? "no" : "yes" );
228 
229         session.setConfig( config );
230 
231         session.setUserInfo( ui );
232 
233         StringWriter stringWriter = new StringWriter();
234         try
235         {
236             session.connect();
237 
238             if ( getKnownHostsProvider() != null )
239             {
240                 PrintWriter w = new PrintWriter( stringWriter );
241 
242                 HostKeyRepository hkr = sch.getHostKeyRepository();
243                 HostKey[] keys = hkr.getHostKey();
244 
245                 for ( int i = 0; keys != null && i < keys.length; i++ )
246                 {
247                     HostKey key = keys[i];
248                     w.println( key.getHost() + " " + key.getType() + " " + key.getKey() );
249                 }
250             }
251         }
252         catch ( JSchException e )
253         {
254             if ( e.getMessage().startsWith( "UnknownHostKey:" ) || e.getMessage().startsWith( "reject HostKey:" ) )
255             {
256                 throw new UnknownHostException( host, e );
257             }
258             else if ( e.getMessage().indexOf( "HostKey has been changed" ) >= 0 )
259             {
260                 throw new KnownHostChangedException( host, e );
261             }
262             else
263             {
264                 throw new AuthenticationException( "Cannot connect. Reason: " + e.getMessage(), e );
265             }
266         }
267 
268         try
269         {
270             getKnownHostsProvider().storeKnownHosts( stringWriter.toString() );
271         }
272         catch ( IOException e )
273         {
274             closeConnection();
275 
276             throw new AuthenticationException(
277                 "Connection aborted - failed to write to known_hosts. Reason: " + e.getMessage(), e );
278         }
279     }
280 
281     public void closeConnection()
282     {
283         if ( session != null )
284         {
285             session.disconnect();
286             session = null;
287         }
288     }
289 
290     public Streams executeCommand( String command, boolean ignoreFailures )
291         throws CommandExecutionException
292     {
293         ChannelExec channel = null;
294         BufferedReader stdoutReader = null;
295         BufferedReader stderrReader = null;
296         try
297         {
298             channel = (ChannelExec) session.openChannel( EXEC_CHANNEL );
299 
300             channel.setCommand( command + "\n" );
301 
302             InputStream stdout = channel.getInputStream();
303             InputStream stderr = channel.getErrStream();
304 
305             channel.connect();
306 
307             stdoutReader = new BufferedReader( new InputStreamReader( stdout ) );
308             stderrReader = new BufferedReader( new InputStreamReader( stderr ) );
309 
310             Streams streams = CommandExecutorStreamProcessor.processStreams( stderrReader, stdoutReader );
311 
312             if ( streams.getErr().length() > 0 && !ignoreFailures )
313             {
314                 int exitCode = channel.getExitStatus();
315                 throw new CommandExecutionException( "Exit code: " + exitCode + " - " + streams.getErr() );
316             }
317 
318             return streams;
319         }
320         catch ( IOException e )
321         {
322             throw new CommandExecutionException( "Cannot execute remote command: " + command, e );
323         }
324         catch ( JSchException e )
325         {
326             throw new CommandExecutionException( "Cannot execute remote command: " + command, e );
327         }
328         finally
329         {
330             IOUtil.close( stdoutReader );
331             IOUtil.close( stderrReader );
332             if ( channel != null )
333             {
334                 channel.disconnect();
335             }
336         }
337     }
338 
339     protected void handleGetException( Resource resource, Exception e )
340         throws TransferFailedException
341     {
342         fireTransferError( resource, e, TransferEvent.REQUEST_GET );
343 
344         String msg =
345             "Error occurred while downloading '" + resource + "' from the remote repository:" + getRepository() + ": "
346                 + e.getMessage();
347 
348         throw new TransferFailedException( msg, e );
349     }
350 
351     public List<String> getFileList( String destinationDirectory )
352         throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
353     {
354         return sshTool.getFileList( destinationDirectory, repository );
355     }
356 
357     public void putDirectory( File sourceDirectory, String destinationDirectory )
358         throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
359     {
360         sshTool.putDirectory( this, sourceDirectory, destinationDirectory );
361     }
362 
363     public boolean resourceExists( String resourceName )
364         throws TransferFailedException, AuthorizationException
365     {
366         return sshTool.resourceExists( resourceName, repository );
367     }
368 
369     public boolean supportsDirectoryCopy()
370     {
371         return true;
372     }
373 
374     public void executeCommand( String command )
375         throws CommandExecutionException
376     {
377         fireTransferDebug( "Executing command: " + command );
378 
379         executeCommand( command, false );
380     }
381 
382     public InteractiveUserInfo getInteractiveUserInfo()
383     {
384         return this.interactiveUserInfo;
385     }
386 
387     public KnownHostsProvider getKnownHostsProvider()
388     {
389         return this.knownHostsProvider;
390     }
391 
392     public void setInteractiveUserInfo( InteractiveUserInfo interactiveUserInfo )
393     {
394         this.interactiveUserInfo = interactiveUserInfo;
395     }
396 
397     public void setKnownHostsProvider( KnownHostsProvider knownHostsProvider )
398     {
399         this.knownHostsProvider = knownHostsProvider;
400     }
401 }