View Javadoc
1   package org.apache.maven.scm.provider.cvslib.cvsjava.util;
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 org.apache.maven.scm.log.ScmLogger;
23  import org.codehaus.plexus.util.StringUtils;
24  import org.codehaus.plexus.util.cli.CommandLineUtils;
25  import org.netbeans.lib.cvsclient.CVSRoot;
26  import org.netbeans.lib.cvsclient.Client;
27  import org.netbeans.lib.cvsclient.admin.StandardAdminHandler;
28  import org.netbeans.lib.cvsclient.command.Command;
29  import org.netbeans.lib.cvsclient.command.CommandAbortedException;
30  import org.netbeans.lib.cvsclient.command.CommandException;
31  import org.netbeans.lib.cvsclient.command.GlobalOptions;
32  import org.netbeans.lib.cvsclient.commandLine.CommandFactory;
33  import org.netbeans.lib.cvsclient.commandLine.GetOpt;
34  import org.netbeans.lib.cvsclient.connection.AbstractConnection;
35  import org.netbeans.lib.cvsclient.connection.AuthenticationException;
36  import org.netbeans.lib.cvsclient.connection.Connection;
37  import org.netbeans.lib.cvsclient.connection.ConnectionFactory;
38  import org.netbeans.lib.cvsclient.connection.PServerConnection;
39  import org.netbeans.lib.cvsclient.connection.StandardScrambler;
40  import org.netbeans.lib.cvsclient.event.CVSListener;
41  
42  import java.io.BufferedReader;
43  import java.io.File;
44  import java.io.FileReader;
45  import java.io.IOException;
46  
47  /**
48   * A Cvs connection that simulates a command line interface.
49   *
50   * @author <a href="mailto:epugh@upstate.com">Eric Pugh</a>
51   *
52   */
53  public class CvsConnection
54  {
55  
56      /**
57       * The path to the repository on the server
58       */
59      @SuppressWarnings( "unused" )
60      private String repository;
61  
62      /**
63       * The local path to use to perform operations (the top level)
64       */
65      private String localPath;
66  
67      /**
68       * The connection to the server
69       */
70      private Connection connection;
71  
72      /**
73       * The client that manages interactions with the server
74       */
75      private Client client;
76  
77      /**
78       * The global options being used. GlobalOptions are only global for a
79       * particular command.
80       */
81      private GlobalOptions globalOptions;
82  
83      private CvsConnection()
84      {
85      }
86  
87      /**
88       * Execute a configured CVS command
89       *
90       * @param command the command to execute
91       * @throws CommandException if there is an error running the command
92       */
93      public boolean executeCommand( Command command )
94          throws CommandException, AuthenticationException
95      {
96          return client.executeCommand( command, globalOptions );
97      }
98  
99      public void setRepository( String repository )
100     {
101         this.repository = repository;
102     }
103 
104     public void setLocalPath( String localPath )
105     {
106         this.localPath = localPath;
107     }
108 
109     public void setGlobalOptions( GlobalOptions globalOptions )
110     {
111         this.globalOptions = globalOptions;
112     }
113 
114     /**
115      * Creates the connection and the client and connects.
116      */
117     private void connect( CVSRoot root, String password )
118         throws AuthenticationException, CommandAbortedException
119     {
120         if ( CVSRoot.METHOD_EXT.equals( root.getMethod() ) )
121         {
122             String cvsRsh = System.getProperty( "maven.scm.cvs.java.cvs_rsh" );
123             if ( cvsRsh == null )
124             {
125                 try
126                 {
127                     cvsRsh = CommandLineUtils.getSystemEnvVars().getProperty( "CVS_RSH" );
128                 }
129                 catch ( IOException e )
130                 {
131                     // we assume searching env var can't fail
132                 }
133             }
134 
135             if ( cvsRsh != null )
136             {
137                 if ( cvsRsh.indexOf( ' ' ) < 0 )
138                 {
139                     //cvs_rsh should be 'rsh' or 'ssh'
140                     //Complete the command to use
141                     String username = root.getUserName();
142                     if ( username == null )
143                     {
144                         username = System.getProperty( "user.name" );
145                     }
146 
147                     cvsRsh += " " + username + "@" + root.getHostName() + " cvs server";
148                 }
149 
150                 AbstractConnection conn = new org.netbeans.lib.cvsclient.connection.ExtConnection( cvsRsh );
151                 conn.setRepository( root.getRepository() );
152                 connection = conn;
153             }
154             else
155             {
156                 connection = new ExtConnection( root );
157             }
158         }
159         else
160         {
161             connection = ConnectionFactory.getConnection( root );
162             if ( CVSRoot.METHOD_PSERVER.equals( root.getMethod() ) )
163             {
164                 ( (PServerConnection) connection ).setEncodedPassword( password );
165             }
166         }
167         connection.open();
168 
169         client = new Client( connection, new StandardAdminHandler() );
170         client.setLocalPath( localPath );
171     }
172 
173     private void disconnect()
174     {
175         if ( connection != null && connection.isOpen() )
176         {
177             try
178             {
179                 connection.close();
180             }
181             catch ( IOException e )
182             {
183                 //ignore
184             }
185         }
186     }
187 
188     private void addListener( CVSListener listener )
189     {
190         if ( client != null )
191         {
192             // add a listener to the client
193             client.getEventManager().addCVSListener( listener );
194         }
195     }
196 
197     /**
198      * Obtain the CVS root, either from the -D option cvs.root or from the CVS
199      * directory
200      *
201      * @return the CVSRoot string
202      */
203     private static String getCVSRoot( String workingDir )
204     {
205         String root = null;
206         BufferedReader r = null;
207         if ( workingDir == null )
208         {
209             workingDir = System.getProperty( "user.dir" );
210         }
211         try
212         {
213             File f = new File( workingDir );
214             File rootFile = new File( f, "CVS/Root" );
215             if ( rootFile.exists() )
216             {
217                 r = new BufferedReader( new FileReader( rootFile ) );
218                 root = r.readLine();
219             }
220         }
221         catch ( IOException e )
222         {
223             // ignore
224         }
225         finally
226         {
227             try
228             {
229                 if ( r != null )
230                 {
231                     r.close();
232                 }
233             }
234             catch ( IOException e )
235             {
236                 System.err.println( "Warning: could not close CVS/Root file!" );
237             }
238         }
239         if ( root == null )
240         {
241             root = System.getProperty( "cvs.root" );
242         }
243         return root;
244     }
245 
246     /**
247      * Process global options passed into the application
248      *
249      * @param args          the argument list, complete
250      * @param globalOptions the global options structure that will be passed to
251      *                      the command
252      */
253     private static int processGlobalOptions( String[] args, GlobalOptions globalOptions )
254     {
255         final String getOptString = globalOptions.getOptString();
256         GetOpt go = new GetOpt( args, getOptString );
257         int ch;
258         while ( ( ch = go.getopt() ) != GetOpt.optEOF )
259         {
260             //System.out.println("Global option '"+((char) ch)+"',
261             // '"+go.optArgGet()+"'");
262             String arg = go.optArgGet();
263             boolean success = globalOptions.setCVSCommand( (char) ch, arg );
264             if ( !success )
265             {
266                 throw new IllegalArgumentException( "Failed to set CVS Command: -" + ch + " = " + arg );
267             }
268         }
269 
270         return go.optIndexGet();
271     }
272 
273     /**
274      * Lookup the password in the .cvspass file. This file is looked for in the
275      * user.home directory if the option cvs.passfile is not set
276      *
277      * @param cvsRoot the CVS root for which the password is being searched
278      * @return the password, scrambled
279      */
280     private static String lookupPassword( String cvsRoot, ScmLogger logger )
281     {
282         File passFile = new File( System.getProperty( "cygwin.user.home", System.getProperty( "user.home" ) ) + File
283             .separatorChar + ".cvspass" );
284 
285         BufferedReader reader = null;
286         String password = null;
287 
288         try
289         {
290             reader = new BufferedReader( new FileReader( passFile ) );
291             password = processCvspass( cvsRoot, reader );
292         }
293         catch ( IOException e )
294         {
295             if ( logger.isWarnEnabled() )
296             {
297                 logger.warn( "Could not read password for '" + cvsRoot + "' from '" + passFile + "'", e );
298             }
299             return null;
300         }
301         finally
302         {
303             if ( reader != null )
304             {
305                 try
306                 {
307                     reader.close();
308                 }
309                 catch ( IOException e )
310                 {
311                     if ( logger.isErrorEnabled() )
312                     {
313                         logger.error( "Warning: could not close password file." );
314                     }
315                 }
316             }
317         }
318         if ( password == null )
319         {
320             if ( logger.isErrorEnabled() )
321             {
322                 logger.error( "Didn't find password for CVSROOT '" + cvsRoot + "'." );
323             }
324         }
325         return password;
326     }
327 
328     /**
329      * Read in a list of return delimited lines from .cvspass and retreive
330      * the password.  Return null if the cvsRoot can't be found.
331      *
332      * @param cvsRoot the CVS root for which the password is being searched
333      * @param reader  A buffered reader of lines of cvspass information
334      * @return The password, or null if it can't be found.
335      * @throws IOException
336      */
337     static String processCvspass( String cvsRoot, BufferedReader reader )
338         throws IOException
339     {
340         String line;
341         String password = null;
342         while ( ( line = reader.readLine() ) != null )
343         {
344             if ( line.startsWith( "/" ) )
345             {
346                 String[] cvspass = StringUtils.split( line, " " );
347                 String cvspassRoot = cvspass[1];
348                 if ( compareCvsRoot( cvsRoot, cvspassRoot ) )
349                 {
350                     int index = line.indexOf( cvspassRoot ) + cvspassRoot.length() + 1;
351                     password = line.substring( index );
352                     break;
353                 }
354             }
355             else if ( line.startsWith( cvsRoot ) )
356             {
357                 password = line.substring( cvsRoot.length() + 1 );
358                 break;
359             }
360         }
361         return password;
362     }
363 
364     static boolean compareCvsRoot( String cvsRoot, String target )
365     {
366         String s1 = completeCvsRootPort( cvsRoot );
367         String s2 = completeCvsRootPort( target );
368         return s1 != null && s1.equals( s2 );
369 
370     }
371 
372     private static String completeCvsRootPort( String cvsRoot )
373     {
374         String result = cvsRoot;
375         int idx = cvsRoot.indexOf( ':' );
376         for ( int i = 0; i < 2 && idx != -1; i++ )
377         {
378             idx = cvsRoot.indexOf( ':', idx + 1 );
379         }
380         if ( idx != -1 && cvsRoot.charAt( idx + 1 ) == '/' )
381         {
382             StringBuilder sb = new StringBuilder();
383             sb.append( cvsRoot.substring( 0, idx + 1 ) );
384             sb.append( "2401" );
385             sb.append( cvsRoot.substring( idx + 1 ) );
386             result = sb.toString();
387         }
388         return result;
389 
390     }
391 
392     /**
393      * Process the CVS command passed in args[] array with all necessary
394      * options. The only difference from main() method is, that this method
395      * does not exit the JVM and provides command output.
396      *
397      * @param args The command with options
398      */
399     public static boolean processCommand( String[] args, String localPath, CVSListener listener, ScmLogger logger )
400         throws Exception
401     {
402         // Set up the CVSRoot. Note that it might still be null after this
403         // call if the user has decided to set it with the -d command line
404         // global option
405         GlobalOptions globalOptions = new GlobalOptions();
406         globalOptions.setCVSRoot( getCVSRoot( localPath ) );
407 
408         // Set up any global options specified. These occur before the
409         // name of the command to run
410         int commandIndex;
411 
412         try
413         {
414             commandIndex = processGlobalOptions( args, globalOptions );
415         }
416         catch ( IllegalArgumentException e )
417         {
418             if ( logger.isErrorEnabled() )
419             {
420                 logger.error( "Invalid argument: " + e );
421             }
422             return false;
423         }
424 
425         // if we don't have a CVS root by now, the user has messed up
426         if ( globalOptions.getCVSRoot() == null )
427         {
428             if ( logger.isErrorEnabled() )
429             {
430                 logger.error( "No CVS root is set. Check your <repository> information in the POM." );
431             }
432             return false;
433         }
434 
435         // parse the CVS root into its constituent parts
436         CVSRoot root;
437         final String cvsRoot = globalOptions.getCVSRoot();
438         try
439         {
440             root = CVSRoot.parse( cvsRoot );
441         }
442         catch ( IllegalArgumentException e )
443         {
444             if ( logger.isErrorEnabled() )
445             {
446                 logger.error( "Incorrect format for CVSRoot: " + cvsRoot + "\nThe correct format is: "
447                     + "[:method:][[user][:password]@][hostname:[port]]/path/to/repository"
448                     + "\nwhere \"method\" is pserver." );
449             }
450             return false;
451         }
452 
453         final String command = args[commandIndex];
454 
455         // this is not login, but a 'real' cvs command, so construct it,
456         // set the options, and then connect to the server and execute it
457 
458         Command c;
459         try
460         {
461             c = CommandFactory.getDefault().createCommand( command, args, ++commandIndex, globalOptions, localPath );
462         }
463         catch ( IllegalArgumentException e )
464         {
465             if ( logger.isErrorEnabled() )
466             {
467                 logger.error( "Illegal argument: " + e.getMessage() );
468             }
469             return false;
470         }
471 
472         String password = null;
473 
474         if ( CVSRoot.METHOD_PSERVER.equals( root.getMethod() ) )
475         {
476             password = root.getPassword();
477             if ( password != null )
478             {
479                 password = StandardScrambler.getInstance().scramble( password );
480             }
481             else
482             {
483                 password = lookupPassword( cvsRoot, logger );
484                 if ( password == null )
485                 {
486                     password = StandardScrambler.getInstance().scramble( "" );
487                     // an empty password
488                 }
489             }
490         }
491         CvsConnection cvsCommand = new CvsConnection();
492         cvsCommand.setGlobalOptions( globalOptions );
493         cvsCommand.setRepository( root.getRepository() );
494         // the local path is just the path where we executed the
495         // command. This is the case for command-line CVS but not
496         // usually for GUI front-ends
497         cvsCommand.setLocalPath( localPath );
498 
499         cvsCommand.connect( root, password );
500         cvsCommand.addListener( listener );
501         if ( logger.isDebugEnabled() )
502         {
503             logger.debug( "Executing CVS command: " + c.getCVSCommand() );
504         }
505         boolean result = cvsCommand.executeCommand( c );
506         cvsCommand.disconnect();
507         return result;
508     }
509 }