You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by cr...@hyperreal.org on 1999/10/24 00:30:22 UTC

cvs commit: jakarta-tomcat/src/share/org/apache/tomcat/security/file FileRealmTool.java Constants.java FileRealmConnector.java FileRealmDatabase.java FileRealmUser.java LocalStrings.properties tomcat-users.xml

craigmcc    99/10/23 15:30:22

  Modified:    src/share/org/apache/tomcat/security/file Constants.java
                        FileRealmConnector.java FileRealmDatabase.java
                        FileRealmUser.java LocalStrings.properties
                        tomcat-users.xml
  Added:       src/share/org/apache/tomcat/security/file FileRealmTool.java
  Log:
  Flesh out a usable implementation of RealmConnector that stores its
  database information in an XML file.
  
  org.apache.tomcat.security.file.FileRealmTool is a command line
  application that can be used to manipulate the contexts of an XML
  database, in preparation for use by Tomcat.  For example, you might
  add a new user, and put them in a specified group, like this:
  
  	java org.apache.tomcat.security.file.FileRealmTool \
  	  mydatabase.xml \
  	  -addUser hans mypassword \
  	  -addMember committer hans \
  	  -addMember pmc hans
  
  See the JavaDoc comments on FileRealmTool for all the possible commands.
  
  Revision  Changes    Path
  1.2       +13 -4     jakarta-tomcat/src/share/org/apache/tomcat/security/file/Constants.java
  
  Index: Constants.java
  ===================================================================
  RCS file: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/security/file/Constants.java,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- Constants.java	1999/10/18 03:34:03	1.1
  +++ Constants.java	1999/10/23 22:30:16	1.2
  @@ -1,7 +1,7 @@
   /*
  - * $Header: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/security/file/Constants.java,v 1.1 1999/10/18 03:34:03 craigmcc Exp $
  - * $Revision: 1.1 $
  - * $Date: 1999/10/18 03:34:03 $
  + * $Header: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/security/file/Constants.java,v 1.2 1999/10/23 22:30:16 craigmcc Exp $
  + * $Revision: 1.2 $
  + * $Date: 1999/10/23 22:30:16 $
    *
    * ====================================================================
    *
  @@ -78,7 +78,7 @@
       /**
        * The name of this Java package.
        */
  -    public static final String Package = "org.apache.tomcat.file.security";
  +    public static final String Package = "org.apache.tomcat.security.file";
   
   
       /**
  @@ -101,6 +101,15 @@
       public static class Attribute {
   	public static final String NAME = "name";
   	public static final String PASSWORD = "password";
  +    }
  +
  +
  +    /**
  +     * Initialization parameters from the associated ServletContext.
  +     */
  +    public static class Parameter {
  +	public static final String DATABASE =
  +	    Constants.Package + ".database";
       }
   
   
  
  
  
  1.2       +76 -18    jakarta-tomcat/src/share/org/apache/tomcat/security/file/FileRealmConnector.java
  
  Index: FileRealmConnector.java
  ===================================================================
  RCS file: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/security/file/FileRealmConnector.java,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- FileRealmConnector.java	1999/10/18 03:34:04	1.1
  +++ FileRealmConnector.java	1999/10/23 22:30:17	1.2
  @@ -1,7 +1,7 @@
   /*
  - * $Header: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/security/file/FileRealmConnector.java,v 1.1 1999/10/18 03:34:04 craigmcc Exp $
  - * $Revision: 1.1 $
  - * $Date: 1999/10/18 03:34:04 $
  + * $Header: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/security/file/FileRealmConnector.java,v 1.2 1999/10/23 22:30:17 craigmcc Exp $
  + * $Revision: 1.2 $
  + * $Date: 1999/10/23 22:30:17 $
    *
    * ====================================================================
    *
  @@ -64,12 +64,18 @@
   
   package org.apache.tomcat.security.file;
   
  -
  +import java.io.BufferedInputStream;
  +import java.io.FileInputStream;
  +import java.io.FileNotFoundException;
  +import java.io.InputStream;
  +import java.io.IOException;
   import java.security.Principal;
   import java.util.Enumeration;
   import org.apache.tomcat.core.Context;
   import org.apache.tomcat.security.RealmConnector;
  -
  +import org.apache.tomcat.util.StringManager;
  +import org.xml.sax.SAXException;
  +import org.xml.sax.SAXParseException;
   
   /**
    * Implementation of <code>RealmConnector</code> that uses a
  @@ -77,7 +83,7 @@
    * XML-stored database of users and their associated roles.
    *
    * @author Craig R. McClanahan
  - * @version $Revision: 1.1 $ $Date: 1999/10/18 03:34:04 $
  + * @version $Revision: 1.2 $ $Date: 1999/10/23 22:30:17 $
    */
   
   public final class FileRealmConnector
  @@ -97,6 +103,13 @@
   
   
       /**
  +     * The internationalized string constants for this package.
  +     */
  +    private StringManager sm =
  +	StringManager.getManager(Constants.Package);
  +
  +
  +    /**
        * No-arguments constructor so that an instance of this class can be
        * instantiated dynamically.
        */
  @@ -118,11 +131,9 @@
        */
       public Principal authenticate(String username, String credentials) {
   
  -	if (context == null) {
  -	    // XXX Internationalization
  -	    throw new IllegalStateException("FileRealmConnector:  " +
  -					    "Not started");
  -	}
  +	if (context == null)
  +	    throw new IllegalStateException(
  +                sm.getString("file.authenticate.notstarted"));
   
   	FileRealmUser user = database.getUser(username);
   	if (user == null)
  @@ -150,11 +161,9 @@
        */
       public boolean hasRole(Principal principal, String role) {
   
  -	if (context == null) {
  -	    // XXX Internationalization
  -	    throw new IllegalStateException("FileRealmConnector:  " +
  -					    "Not started");
  -	}
  +	if (context == null)
  +	    throw new IllegalStateException(
  +                sm.getString("file.hasRole.notstarted"));
   
   	FileRealmUser user = database.getUser(principal.getName());
   	if (user == null)
  @@ -178,13 +187,62 @@
        *
        * @param context The Context with which this RealmConnector is associated
        *
  -     * XXX Should we support some formal exception to report initialization
  -     * problems?
  +     * @exception IllegalArgumentException if the underlying database
  +     *  file cannot be successfully loaded
        */
       public void start(Context context) {
   
  +	// Open an input stream to the specified database file
  +	String filename =
  +	    context.getInitParameter(Constants.Parameter.DATABASE);
  +	if (filename == null)
  +	    throw new IllegalArgumentException(
  +	        sm.getString("file.start.missing",
  +			     Constants.Parameter.DATABASE));
  +	InputStream stream = null;
  +	try {
  +	    stream = new BufferedInputStream(new FileInputStream(filename));
  +	} catch (FileNotFoundException e) {
  +	    throw new IllegalArgumentException(
  +		sm.getString("file.start.open", filename));
  +	}
  +
  +	// Configure a local database object based on this file
  +	try {
  +	    this.database = new FileRealmDatabase(stream);
  +	} catch (IOException e) {
  +	    try {
  +		stream.close();
  +	    } catch (IOException f) {
  +		;
  +	    }
  +	    throw new IllegalArgumentException(
  +		sm.getString("file.start.read", filename) + ": " + e);
  +	} catch (SAXParseException e) {
  +	    try {
  +		stream.close();
  +	    } catch (IOException f) {
  +		;
  +	    }
  +	    throw new IllegalArgumentException(
  +		sm.getString("file.start.parse", filename) + ": " + e);
  +	} catch (SAXException e) {
  +	    try {
  +		stream.close();
  +	    } catch (IOException f) {
  +		;
  +	    }
  +	    throw new IllegalArgumentException(
  +		sm.getString("file.start.process", filename) + ": " + e);
  +	}
  +	try {
  +	    stream.close();
  +	} catch (IOException e) {
  +	    ;
  +	}
  +
  +	// Store a local reference to our associated context
   	this.context = context;
  -	// XXX create and initialize the database object
   
       }
   
  
  
  
  1.3       +66 -9     jakarta-tomcat/src/share/org/apache/tomcat/security/file/FileRealmDatabase.java
  
  Index: FileRealmDatabase.java
  ===================================================================
  RCS file: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/security/file/FileRealmDatabase.java,v
  retrieving revision 1.2
  retrieving revision 1.3
  diff -u -r1.2 -r1.3
  --- FileRealmDatabase.java	1999/10/22 08:14:16	1.2
  +++ FileRealmDatabase.java	1999/10/23 22:30:17	1.3
  @@ -1,7 +1,7 @@
   /*
  - * $Header: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/security/file/FileRealmDatabase.java,v 1.2 1999/10/22 08:14:16 craigmcc Exp $
  - * $Revision: 1.2 $
  - * $Date: 1999/10/22 08:14:16 $
  + * $Header: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/security/file/FileRealmDatabase.java,v 1.3 1999/10/23 22:30:17 craigmcc Exp $
  + * $Revision: 1.3 $
  + * $Date: 1999/10/23 22:30:17 $
    *
    * ====================================================================
    *
  @@ -64,6 +64,7 @@
   
   package org.apache.tomcat.security.file;
   
  +import java.io.ByteArrayOutputStream;
   import java.io.InputStream;
   import java.io.IOException;
   import java.io.OutputStream;
  @@ -83,7 +84,7 @@
    * <code>tomcat-users.dtd</code> file in this directory.
    *
    * @author Craig R. McClanahan
  - * @version $Revision: 1.2 $ $Date: 1999/10/22 08:14:16 $
  + * @version $Revision: 1.3 $ $Date: 1999/10/23 22:30:17 $
    */
   
   public final class FileRealmDatabase {
  @@ -173,6 +174,41 @@
       }
   
   
  +    /** [Private] Convert the specified byte array representation of an
  +     * encrypted password into the corresponding hexadecimal digit
  +     * representation.
  +     *
  +     * @param bytes Byte array representation
  +     */
  +    private String convert(byte bytes[]) {
  +
  +	StringBuffer sb = new StringBuffer(bytes.length * 2);
  +	for (int i = 0; i < bytes.length; i++) {
  +	    sb.append(convertDigit((int) (bytes[i] >> 4)));
  +	    sb.append(convertDigit((int) (bytes[i] & 0x0f)));
  +	}
  +	return (sb.toString());
  +
  +    }
  +
  +
  +    /**
  +     * [Private] Convert the specified value (0 .. 15) to the corresponding
  +     * hexadecimal digit.
  +     *
  +     * @param value Value to be converted
  +     */
  +    private char convertDigit(int value) {
  +
  +	value &= 0x0f;
  +	if (value >= 10)
  +	    return ((char) (value - 10 + 'a'));
  +	else
  +	    return ((char) (value + '0'));
  +
  +    }
  +
  +
       /**
        * [Private] Convert the hexadecimal digit representation of an encrypted
        * password into the corresponding byte array representation.
  @@ -181,8 +217,28 @@
        */
       private byte[] convert(String digits) {
   
  -	// XXX Any good hex->byte converters lying around?
  -	return (new byte[0]);
  +	ByteArrayOutputStream baos = new ByteArrayOutputStream();
  +	for (int i = 0; i < digits.length(); i += 2) {
  +	    char c1 = digits.charAt(i);
  +	    char c2 = '0';
  +	    if (i+1 < digits.length())
  +		c2 = digits.charAt(i+1);
  +	    byte b = 0;
  +	    if ((c1 >= '0') && (c1 <= '9'))
  +		b += ((c1 - '0') * 16);
  +	    else if ((c1 >= 'a') && (c1 <= 'f'))
  +		b += ((c1 - 'a' + 10) * 16);
  +	    else if ((c1 >= 'A') && (c1 <= 'F'))
  +		b += ((c1 - 'A' + 10) * 16);
  +	    if ((c2 >= '0') && (c2 <= '9'))
  +		b += (c2 - '0');
  +	    else if ((c2 >= 'a') && (c2 <= 'f'))
  +		b += (c2 - 'a' + 10);
  +	    else if ((c2 >= 'A') && (c2 <= 'F'))
  +		b += (c2 - 'A' + 10);
  +	    baos.write(b);
  +	}
  +	return (baos.toByteArray());
   
       }
   
  @@ -505,14 +561,15 @@
   	while (users.hasMoreElements()) {
   	    FileRealmUser user = (FileRealmUser) users.nextElement();
   	    writer.println("  <user name=\"" + user.getName() +
  -			   "\" password=\"" + user.getPassword() + "\" />");
  +			   "\" password=\"" + convert(user.getPassword()) +
  +			   "\" />");
   	}
   
   	// Render group elements for all defined groups
   	Enumeration groups = getGroups();
   	while (groups.hasMoreElements()) {
   	    FileRealmGroup group = (FileRealmGroup) groups.nextElement();
  -	    writer.println("  <group name=\"" + group.getName() + "\" />");
  +	    writer.println("  <group name=\"" + group.getName() + "\">");
   	    users = group.getUsers();
   	    while (users.hasMoreElements()) {
   		FileRealmUser user = (FileRealmUser) users.nextElement();
  @@ -526,7 +583,7 @@
   	Enumeration roles = getRoles();
   	while (roles.hasMoreElements()) {
   	    String role = (String) roles.nextElement();
  -	    writer.println("  <role name=\"" + role + "\" />");
  +	    writer.println("  <role name=\"" + role + "\">");
   	    users = getUsers();
   	    while (users.hasMoreElements()) {
   		FileRealmUser user = (FileRealmUser) users.nextElement();
  
  
  
  1.2       +15 -3     jakarta-tomcat/src/share/org/apache/tomcat/security/file/FileRealmUser.java
  
  Index: FileRealmUser.java
  ===================================================================
  RCS file: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/security/file/FileRealmUser.java,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- FileRealmUser.java	1999/10/18 03:34:05	1.1
  +++ FileRealmUser.java	1999/10/23 22:30:19	1.2
  @@ -1,7 +1,7 @@
   /*
  - * $Header: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/security/file/FileRealmUser.java,v 1.1 1999/10/18 03:34:05 craigmcc Exp $
  - * $Revision: 1.1 $
  - * $Date: 1999/10/18 03:34:05 $
  + * $Header: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/security/file/FileRealmUser.java,v 1.2 1999/10/23 22:30:19 craigmcc Exp $
  + * $Revision: 1.2 $
  + * $Date: 1999/10/23 22:30:19 $
    *
    * ====================================================================
    *
  @@ -87,7 +87,7 @@
    * </ul>
    *
    * @author Craig R. McClanahan
  - * @version $Revision: 1.1 $ $Date: 1999/10/18 03:34:05 $
  + * @version $Revision: 1.2 $ $Date: 1999/10/23 22:30:19 $
    */
   
   final class FileRealmUser
  @@ -283,6 +283,18 @@
       public Enumeration getRoles() {
   
   	return (roles.keys());
  +
  +    }
  +
  +
  +    /**
  +     * Is this user a member of the specified group?
  +     *
  +     * @param group The group to be tested
  +     */
  +    public boolean hasGroup(FileRealmGroup group) {
  +
  +	return (groups.get(group.getName()) != null);
   
       }
   
  
  
  
  1.2       +26 -3     jakarta-tomcat/src/share/org/apache/tomcat/security/file/LocalStrings.properties
  
  Index: LocalStrings.properties
  ===================================================================
  RCS file: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/security/file/LocalStrings.properties,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- LocalStrings.properties	1999/10/22 08:14:19	1.1
  +++ LocalStrings.properties	1999/10/23 22:30:19	1.2
  @@ -1,7 +1,30 @@
  -# $Id: LocalStrings.properties,v 1.1 1999/10/22 08:14:19 craigmcc Exp $
  +# $Id: LocalStrings.properties,v 1.2 1999/10/23 22:30:19 craigmcc Exp $
   #
   # Localized strings for package org.apache.tomcat.security.file
   # This is the default locale and is en_US
   
  -file.createGroup.exists=createGroup: Group $0 already exists
  -file.createUser.exists=createUser: User $0 already exists
  +file.authenticate.notstarted=authenticate: Connector not started
  +file.createGroup.exists=createGroup: Group {0} already exists
  +file.createUser.exists=createUser: User {0} already exists
  +file.hasRole.notstarted=hasRole: Connector not started
  +file.start.missing=start: Missing initialization parameter {0}
  +file.start.open=start: Cannot open {0}
  +file.start.parse=start: Cannot parse {0}
  +file.start.process=start: Canont process {0}
  +file.start.read=start: Cannot read {0}
  +tool.close.open=close: Cannot open {0}
  +tool.close.write=close: Cannot write {0}
  +tool.missing.group=process: Missing group name
  +tool.missing.password=process: Missing user password
  +tool.missing.principal=process: Missing group or user name
  +tool.missing.role=process: Missing role name
  +tool.missing.user=process: Missing user name
  +tool.open.missing=open: Missing database filename
  +tool.open.open=open: Cannot open {0}
  +tool.open.parse=open: Cannot parse {0}
  +tool.open.process=open: Cannot process {0}
  +tool.open.read=open: Cannot read {0}
  +tool.process.command=process: Unknown command {0}
  +tool.unknown.group=process: Unknown group {0}
  +tool.missing.principal=process: Unknown group or user {0}
  +tool.unknown.user=process: Unknown user {0}
  
  
  
  1.2       +17 -20    jakarta-tomcat/src/share/org/apache/tomcat/security/file/tomcat-users.xml
  
  Index: tomcat-users.xml
  ===================================================================
  RCS file: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/security/file/tomcat-users.xml,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- tomcat-users.xml	1999/10/18 03:34:06	1.1
  +++ tomcat-users.xml	1999/10/23 22:30:20	1.2
  @@ -1,28 +1,25 @@
   <tomcat-users>
  -
  -  <user name="duncan" password="32ba13f902749387"/>
  -  <user name="craigmcc" password="234c199203df9448"/>
  -  <user name="costin" password="659a033904033938"/>
  -  <user name="brianb" password="a8c88c8a02192903"/>
  -
  -  <group name="pmc">
  -    <user-member name="duncan"/>
  -    <user-member name="craigmcc"/>
  -  </group>
  -
  +  <user name="costin" password="5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8" />
  +  <user name="craigmcc" password="5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8" />
  +  <user name="brianb" password="5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8" />
  +  <user name="duncan" password="5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8" />
     <group name="committer">
  -    <user-member name="duncan"/>
  -    <user-member name="craigmcc"/>
  -    <user-member name="costin"/>
  +    <user-member name="costin" />
  +    <user-member name="craigmcc" />
  +    <user-member name="duncan" />
  +  </group>
  +  <group name="pmc">
  +    <user-member name="craigmcc" />
  +    <user-member name="duncan" />
     </group>
  -
     <role name="project-coordinator">
  -    <group-member name="pmc"/>
  -    <user-member name="brianb"/>
  +    <user-member name="brianb" />
  +    <group-member name="pmc" />
     </role>
  -
     <role name="developer">
  -    <anyone/>
  +    <user-member name="costin" />
  +    <user-member name="craigmcc" />
  +    <user-member name="brianb" />
  +    <user-member name="duncan" />
     </role>
  -
   </tomcat-users>
  
  
  
  1.1                  jakarta-tomcat/src/share/org/apache/tomcat/security/file/FileRealmTool.java
  
  Index: FileRealmTool.java
  ===================================================================
  /*
   * $Header: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/security/file/FileRealmTool.java,v 1.1 1999/10/23 22:30:18 craigmcc Exp $
   * $Revision: 1.1 $
   * $Date: 1999/10/23 22:30:18 $
   *
   * ====================================================================
   *
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 1999 The Apache Software Foundation.  All rights 
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer. 
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution, if
   *    any, must include the following acknowlegement:  
   *       "This product includes software developed by the 
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowlegement may appear in the software itself,
   *    if and wherever such third-party acknowlegements normally appear.
   *
   * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
   *    Foundation" must not be used to endorse or promote products derived
   *    from this software without prior written permission. For written 
   *    permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache"
   *    nor may "Apache" appear in their names without prior written
   *    permission of the Apache Group.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   *
   * [Additional notices, if required by prior licensing conditions]
   *
   */ 
  
  
  package org.apache.tomcat.security.file;
  
  import java.io.BufferedInputStream;
  import java.io.BufferedOutputStream;
  import java.io.FileInputStream;
  import java.io.FileNotFoundException;
  import java.io.FileOutputStream;
  import java.io.InputStream;
  import java.io.IOException;
  import java.io.OutputStream;
  import java.util.Enumeration;
  import org.apache.tomcat.util.StringManager;
  import org.xml.sax.SAXException;
  import org.xml.sax.SAXParseException;
  
  
  /**
   * Command-line tool to manipulate database files containing FileRealmDatabase
   * information.  Usage:
   * <pre>
   *	java org.apache.tomcat.security.file.FileRealmTool {path} {commands}
   * </pre>
   * where <code>{path}</code> is the pathname to a local file containing the
   * database information, and <code>{commands}</code> is a series of zero or
   * more commands (each of which including command parameters) from the
   * following list:
   * <ul>
   * <li><b>-addGroup {group}</b> - Add a new group with the specified name
   * <li><b>-addMember {group} {user}</b> - Make {user} a member of {group}
   * <li><b>-addRole {group/user} {role}</b> - Add an explicit assignment of
   *     {role} to the specified {group} or {user}.
   * <li><b>-addUser {user} {password}</b> - Add a new user with the specified
   *     username and (cleartext) password.
   * <li><b>-dropGroup {group}</b> - Drop this group and all its associated
   *     memberships and roles
   * <li><b>-dropMember {group} {user}</b> - Make {user} no longer a member
   *     of {group}
   * <li><b>-dropRole {group/user} {role}</b> - Drop this explicitly assigned
   *     {role} from the specified {group} or {user}.
   * <li><b>-dropUser {user}</b> - Drop this user and all its associated
   *     memberships and roles
   * <li><b>-list</b> - List the users, groups, and roles in this database
   * </ul>
   * <p>
   * To initialize a new database file for use by this tool, create a text file
   * (whose name ends in ".xml") with the following contents:
   * <pre>
   *	<tomcat-users>
   *	</tomcat-users>
   * </pre>
   *
   * @author Craig R. McClanahan
   * @version $Revision: 1.1 $ $Date: 1999/10/23 22:30:18 $
   */
  
  public final class FileRealmTool {
  
  
      /**
       * The command line arguments we are processing
       */
      private static String args[] = null;
  
  
      /**
       * The subscript of the next argument to be returned by getArg().
       */
      private static int current = 0;
  
  
      /**
       * The database we are processing.
       */
      private static FileRealmDatabase database = null;
  
  
      /**
       * The name of the database file we are processing.
       */
      private static String filename = null;
  
  
      /**
       * Has the cached database information been modified?
       */
      private static boolean modified = false;
  
  
      /**
       * The internationalized string constants for this package.
       */
      private static StringManager sm =
      	StringManager.getManager(Constants.Package);
  
  
      /**
       * The application main program.
       */
      public static void main(String arguments[]) {
  
  	args = arguments;
  	open();
  	process();
  	close();
  
      }
  
  
      /**
       * Add a new group, if it is not already present.
       */
      private static void addGroup() {
  
  	String name = getArg();
  	if (name.length() < 1) {
  	    System.err.println(sm.getString("tool.missing.group"));
  	    System.exit(2);
  	}
  
  	FileRealmGroup group = database.getGroup(name);
  	if (group != null)
  	    return;
  	group = database.createGroup(name);
  	modified = true;
  
      }
  
  
      /**
       * Add a new group membership, if not already present.
       */
      private static void addMember() {
  
  	String groupName = getArg();
  	if (groupName.length() < 1) {
  	    System.err.println(sm.getString("tool.missing.group"));
  	    System.exit(2);
  	}
  	FileRealmGroup group = database.getGroup(groupName);
  	if (group == null) {
  	    System.err.println(sm.getString("tool.unknown.group", groupName));
  	    System.exit(2);
  	}
  
  	String userName = getArg();
  	if (userName.length() < 1) {
  	    System.err.println(sm.getString("tool.missing.user"));
  	    System.exit(2);
  	}
  	FileRealmUser user = database.getUser(userName);
  	if (user == null) {
  	    System.err.println(sm.getString("tool.unknown.user", userName));
  	    System.exit(2);
  	}
  
  	if (!user.hasGroup(group)) {
  	    user.add(group);
  	    modified = true;
  	}
  
      }
  
  
      /**
       * Add a new assigned role to a group or user, if not already present.
       */
      private static void addRole() {
  
  	String principalName = getArg();
  	if (principalName.length() < 1) {
  	    System.err.println(sm.getString("tool.missing.principal"));
  	    System.exit(2);
  	}
  	FileRealmGroup group = database.getGroup(principalName);
  	FileRealmUser user = database.getUser(principalName);
  	if ((group == null) && (user == null)) {
  	    System.err.println(sm.getString("tool.unknown.principal",
  					    principalName));
  	    System.exit(2);
  	}
  
  	String role = getArg();
  	if (role.length() < 1) {
  	    System.err.println(sm.getString("tool.missing.role"));
  	    System.exit(2);
  	}
  
  	if (group != null) {
  	    if (!group.hasRole(role)) {
  		group.add(role);
  		modified = true;
  	    }
  	}
  
  	if (user != null) {
  	    if (!user.hasRole(role)) {
  		user.add(role);
  		modified = true;
  	    }
  	}
  
      }
  
  
      /**
       * Add a new user, if it is not already present.  If the user is already
       * present, update the password to the specified value.
       */
      private static void addUser() {
  
  	String name = getArg();
  	if (name.length() < 1) {
  	    System.err.println(sm.getString("tool.missing.user"));
  	    System.exit(2);
  	}
  	String password = getArg();
  	if (password.length() < 1) {
  	    System.err.println(sm.getString("tool.missing.password"));
  	    System.exit(2);
  	}
  
  	FileRealmUser user = database.getUser(name);
  	if (user != null)
  	    user.setPassword(password);
  	else
  	    user = database.createUser(name, password);
  	modified = true;
  
      }
  
  
      /**
       * Close the database, flushing any changes first.
       */
      private static void close() {
  
  	if (!modified)
  	    return;
  
  	OutputStream stream = null;
  	try {
  	    stream = new BufferedOutputStream(new FileOutputStream(filename));
  	} catch (IOException e) {
  	    System.err.println(sm.getString("tool.close.open", filename) +
  			       ": " + e);
  	    e.printStackTrace();
  	    System.exit(1);
  	}
  
  	try {
  	    database.write(stream);
  	} catch (IOException e) {
  	    System.err.println(sm.getString("tool.close.write", filename) +
  			       ": " + e);
  	    e.printStackTrace();
  	    System.exit(1);
  	}
  
  	try {
  	    stream.close();
  	} catch (IOException e) {
  	    ;
  	}
  
      }
  
  
      /** [Private] Convert the specified byte array representation of an
       * encrypted password into the corresponding hexadecimal digit
       * representation.
       *
       * @param bytes Byte array representation
       */
      private static String convert(byte bytes[]) {
  
  	StringBuffer sb = new StringBuffer(bytes.length * 2);
  	for (int i = 0; i < bytes.length; i++) {
  	    sb.append(convertDigit((int) (bytes[i] >> 4)));
  	    sb.append(convertDigit((int) (bytes[i] & 0x0f)));
  	}
  	return (sb.toString());
  
      }
  
  
      /**
       * [Private] Convert the specified value (0 .. 15) to the corresponding
       * hexadecimal digit.
       *
       * @param value Value to be converted
       */
      private static char convertDigit(int value) {
  
  	value &= 0x0f;
  	if (value >= 10)
  	    return ((char) (value - 10 + 'a'));
  	else
  	    return ((char) (value + '0'));
  
      }
  
  
      /**
       * Drop an existing group, if it is already present.
       */
      private static void dropGroup() {
  
  	String name = getArg();
  	if (name.length() < 1) {
  	    System.err.println(sm.getString("tool.missing.group"));
  	    System.exit(2);
  	}
  
  	FileRealmGroup group = database.getGroup(name);
  	if (group == null) {
  	    System.err.println(sm.getString("tool.unknown.group"));
  	    System.exit(2);
  	}
  	database.remove(group);
  	modified = true;
  
      }
  
  
      /**
       * Drop an existing group membership.
       */
      private static void dropMember() {
  
  	String groupName = getArg();
  	if (groupName.length() < 1) {
  	    System.err.println(sm.getString("tool.missing.group"));
  	    System.exit(2);
  	}
  	FileRealmGroup group = database.getGroup(groupName);
  	if (group == null) {
  	    System.err.println(sm.getString("tool.unknown.group", groupName));
  	    System.exit(2);
  	}
  
  	String userName = getArg();
  	if (userName.length() < 1) {
  	    System.err.println(sm.getString("tool.missing.user"));
  	    System.exit(2);
  	}
  	FileRealmUser user = database.getUser(userName);
  	if (user == null) {
  	    System.err.println(sm.getString("tool.unknown.user", userName));
  	    System.exit(2);
  	}
  
  	if (user.hasGroup(group)) {
  	    user.remove(group);
  	    modified = true;
  	}
  
      }
  
  
      /**
       * Drop an existing assigned role for a group or user.
       */
      private static void dropRole() {
  
  	String principalName = getArg();
  	if (principalName.length() < 1) {
  	    System.err.println(sm.getString("tool.missing.principal"));
  	    System.exit(2);
  	}
  	FileRealmGroup group = database.getGroup(principalName);
  	FileRealmUser user = database.getUser(principalName);
  	if ((group == null) && (user == null)) {
  	    System.err.println(sm.getString("tool.unknown.principal",
  					    principalName));
  	    System.exit(2);
  	}
  
  	String role = getArg();
  	if (role.length() < 1) {
  	    System.err.println(sm.getString("tool.missing.role"));
  	    System.exit(2);
  	}
  
  	if (group != null) {
  	    if (group.hasRole(role)) {
  		group.remove(role);
  		modified = true;
  	    }
  	}
  
  	if (user != null) {
  	    if (user.hasRole(role)) {
  		user.remove(role);
  		modified = true;
  	    }
  	}
  
      }
  
  
      /**
       * Drop an existing user, if it is already present.
       */
      private static void dropUser() {
  
  	String name = getArg();
  	if (name.length() < 1) {
  	    System.err.println(sm.getString("tool.missing.user"));
  	    System.exit(2);
  	}
  
  	FileRealmUser user = database.getUser(name);
  	if (user == null) {
  	    System.err.println(sm.getString("tool.unknown.user"));
  	    System.exit(2);
  	}
  	database.remove(user);
  	modified = true;
  
      }
  
  
      /**
       * Return the next command line argument, if there is one.  Otherwise,
       * return a zero-length string.
       */
      private static String getArg() {
  
  	if (current >= args.length)
  	    return ("");
  	else
  	    return (args[current++]);
  
      }
  
  
      /**
       * List the users and groups, and their associated roles, in this database.
       */
      private static void list() {
  
  	Enumeration users = database.getUsers();
  	while (users.hasMoreElements()) {
  	    FileRealmUser user = (FileRealmUser) users.nextElement();
  	    System.out.print("User '" + user.getName() +
  			     "', password='" + convert(user.getPassword()) +
  			     "'");
  	    Enumeration groups = user.getGroups();
  	    while (groups.hasMoreElements()) {
  		FileRealmGroup group = (FileRealmGroup) groups.nextElement();
  		System.out.print(", group='" + group.getName() + "'");
  	    }
  	    Enumeration roles = user.getRoles();
  	    while (roles.hasMoreElements()) {
  		String role = (String) roles.nextElement();
  		System.out.print(", role='" + role + "'");
  	    }
  	    System.out.println();
  	}
  
  	Enumeration groups = database.getGroups();
  	while (groups.hasMoreElements()) {
  	    FileRealmGroup group = (FileRealmGroup) groups.nextElement();
  	    System.out.print("Group '" + group.getName() + "'");
  	    Enumeration roles = group.getRoles();
  	    while (roles.hasMoreElements()) {
  		String role = (String) roles.nextElement();
  		System.out.print(", role='" + role + "'");
  	    }
  	    System.out.println();
  	}
  
  	System.out.println("===============================================");
  
      }
  
  
      /**
       * Open the database file specified by the next command line argument.
       */
      private static void open() {
  
  	filename = getArg();
  	if (filename.length() < 1) {
  	    System.err.println(sm.getString("tool.open.missing"));
  	    System.exit(1);
  	}
  
  	InputStream stream = null;
  	try {
  	    stream = new BufferedInputStream(new FileInputStream(filename));
  	} catch (FileNotFoundException e) {
  	    System.err.println(sm.getString("tool.open.open", filename) +
  			       ": " + e);
  	    e.printStackTrace();
  	    System.exit(1);
  	}
  
  	try {
  	    database = new FileRealmDatabase(stream);
  	} catch (IOException e) {
  	    System.err.println(sm.getString("tool.open.read", filename) +
  			       ": " + e);
  	    e.printStackTrace();
  	    System.exit(1);
  	} catch (SAXParseException e) {
  	    System.err.println(sm.getString("tool.open.parse", filename) +
  			       ": " + e);
  	    e.printStackTrace();
  	    System.exit(1);
  	} catch (SAXException e) {
  	    System.err.println(sm.getString("tool.open.process", filename) +
  			       ": " + e);
  	    e.printStackTrace();
  	    System.exit(1);
  	}
  	try {
  	    stream.close();
  	} catch (IOException e) {
  	    ;
  	}
  
      }
  
  
      /**
       * Process all valid command from the command line arguments.
       */
      private static void process() {
  
  	while (true) {
  	    String command = getArg();
  	    if (command.length() < 1)
  		break;
  	    else if (command.equals("-addGroup"))
  		addGroup();
  	    else if (command.equals("-addMember"))
  		addMember();
  	    else if (command.equals("-addRole"))
  		addRole();
  	    else if (command.equals("-addUser"))
  		addUser();
  	    else if (command.equals("-dropGroup"))
  		dropGroup();
  	    else if (command.equals("-dropMember"))
  		dropMember();
  	    else if (command.equals("-dropRole"))
  		dropRole();
  	    else if (command.equals("-dropUser"))
  		dropUser();
  	    else if (command.equals("-list"))
  		list();
  	    else {
  		System.err.println(sm.getString("tool.process.command",
  						command));
  	        modified = false;
  		System.exit(2);
  	    }
  	}
  
      }
  
  
  }