You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by co...@locus.apache.org on 2000/05/03 00:39:59 UTC

cvs commit: jakarta-tomcat/src/share/org/apache/tomcat/util SecurityTools.java

costin      00/05/02 15:39:59

  Modified:    src/etc  server.xml
               src/share/org/apache/tomcat/core Container.java Context.java
                        ContextManager.java Request.java RequestImpl.java
               src/share/org/apache/tomcat/request AccessInterceptor.java
                        SecurityCheck.java SessionInterceptor.java
                        SimpleMapper1.java
  Added:       src/share/org/apache/tomcat/util SecurityTools.java
  Log:
  - All functionality in SimpleMapper is now re-implemented in a clean way
  SimpleMapper will be removed after the new code is tested.
  
  - The access control is now cleaner and closer to the spec. We do check
  the method, and it's easy to extend. I think it's too early to try to
  optimize this code, probably it's more important to integrate with apache
  and other web servers.
  
  - Start working on SecurityCheck - it will be replaced with independent
  modules, better design, etc.
  
  - added the request notes to the interface. The notes are faster attributes
  ( indexed array instead of hashtable), and we need to keep internal attribs
  separate from servlet-visible attributes.
  
  Revision  Changes    Path
  1.15      +4 -1      jakarta-tomcat/src/etc/server.xml
  
  Index: server.xml
  ===================================================================
  RCS file: /home/cvs/jakarta-tomcat/src/etc/server.xml,v
  retrieving revision 1.14
  retrieving revision 1.15
  diff -u -r1.14 -r1.15
  --- server.xml	2000/05/01 23:19:57	1.14
  +++ server.xml	2000/05/02 22:39:56	1.15
  @@ -56,8 +56,11 @@
             -->
           <RequestInterceptor className="org.apache.tomcat.request.SimpleMapper1" debug="0" />
   
  +        <!-- Check if the request requires an authenticated role  -->
  +        <RequestInterceptor className="org.apache.tomcat.request.AccessInterceptor" debug="0" />
  +
           <!-- Check permissions -->
  -        <RequestInterceptor className="org.apache.tomcat.request.SecurityCheck" />
  +        <RequestInterceptor className="org.apache.tomcat.request.SecurityCheck" debug="0" />
   
           <!-- Set extra headers - like Date: and ServletEngine, plus version 0 cookies
                Most of this is not needed, but it was allways here. There are better 
  
  
  
  1.19      +16 -0     jakarta-tomcat/src/share/org/apache/tomcat/core/Container.java
  
  Index: Container.java
  ===================================================================
  RCS file: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/core/Container.java,v
  retrieving revision 1.18
  retrieving revision 1.19
  diff -u -r1.18 -r1.19
  --- Container.java	2000/04/27 21:40:53	1.18
  +++ Container.java	2000/05/02 22:39:56	1.19
  @@ -138,6 +138,8 @@
       /** Security constraints associated with this Container
        */
       String roles[]=null;
  +
  +    String methods[]=null;
       
       public Container() {
       }
  @@ -253,6 +255,20 @@
   	this.vhosts=vhosts;
       }
       
  +    /** If not null, this container can only be accessed by users
  +     *  in roles.
  +     */
  +    public String []getMethods() {
  +	return methods;
  +    }
  +
  +    /** If not null, this container can only be accessed by users
  +	in roles.
  +    */
  +    public void setMethods( String m[] ) {
  +	this.methods=m;
  +    }
  +
       // -------------------- Mapping RHS --------------------
       
       /** The handler for this container
  
  
  
  1.84      +1 -0      jakarta-tomcat/src/share/org/apache/tomcat/core/Context.java
  
  Index: Context.java
  ===================================================================
  RCS file: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/core/Context.java,v
  retrieving revision 1.83
  retrieving revision 1.84
  diff -u -r1.83 -r1.84
  --- Context.java	2000/05/01 23:23:47	1.83
  +++ Context.java	2000/05/02 22:39:56	1.84
  @@ -536,6 +536,7 @@
   	    ct.setTransport( transport );
   	    ct.setRoles( roles );
   	    ct.setPath( path[i] );
  +	    ct.setMethods( methods );
   
   	    // XXX check if exists, merge if true.
   	    constraints.put( path[i], ct );
  
  
  
  1.77      +4 -1      jakarta-tomcat/src/share/org/apache/tomcat/core/ContextManager.java
  
  Index: ContextManager.java
  ===================================================================
  RCS file: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/core/ContextManager.java,v
  retrieving revision 1.76
  retrieving revision 1.77
  diff -u -r1.76 -r1.77
  --- ContextManager.java	2000/05/01 23:25:44	1.76
  +++ ContextManager.java	2000/05/02 22:39:56	1.77
  @@ -633,7 +633,10 @@
       int authorize( Request req, Response res ) {
   	for( int i=0; i< requestInterceptors.size(); i++ ) {
   	    int err = ((RequestInterceptor)requestInterceptors.elementAt(i)).authorize( req, res );
  -	    if ( err != 0 ) return err;
  +	    if ( err != 0 ) {
  +		if( debug>0) log( "Authorize rezult " + err );
  +		return err;
  +	    }
   	}
   	return 0;
       }
  
  
  
  1.33      +17 -1     jakarta-tomcat/src/share/org/apache/tomcat/core/Request.java
  
  Index: Request.java
  ===================================================================
  RCS file: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/core/Request.java,v
  retrieving revision 1.32
  retrieving revision 1.33
  diff -u -r1.32 -r1.33
  --- Request.java	2000/04/21 20:45:03	1.32
  +++ Request.java	2000/05/02 22:39:57	1.33
  @@ -191,6 +191,16 @@
   
       boolean isSecure() ;
   
  +    // XXX It may be usefull to add few more fields - right now
  +    // tomcat use notes, after everything is stable we can
  +    // use typed methods
  +    
  +    /** This request has an auth constraint and requires one of
  +     *  the roles.
  +     */
  +    // public String []getRequiredRoles();
  +    // public void setRequiredRoles(String []);
  +
   
       // -------------------- Session --------------------
       // Will be set by session interceptors
  @@ -298,6 +308,12 @@
        */
       public void setMappedPath( String m ) ;
   
  -
  +    /** Add a per/request internal attribute.
  +     *  We keep internal attributes in a separate space to prevent
  +     *  servlets from accessing them. We also use indexed access for
  +     *  speed ( as oposed to hashtable lookups ). Get an Id from ContextManager.
  +     */
  +    public void setNote( int pos, Object value );
   
  +    public Object getNote( int pos );
   }
  
  
  
  1.31      +4 -4      jakarta-tomcat/src/share/org/apache/tomcat/core/RequestImpl.java
  
  Index: RequestImpl.java
  ===================================================================
  RCS file: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/core/RequestImpl.java,v
  retrieving revision 1.30
  retrieving revision 1.31
  diff -u -r1.30 -r1.31
  --- RequestImpl.java	2000/04/25 17:54:14	1.30
  +++ RequestImpl.java	2000/05/02 22:39:57	1.31
  @@ -1,7 +1,7 @@
   /*
  - * $Header: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/core/RequestImpl.java,v 1.30 2000/04/25 17:54:14 costin Exp $
  - * $Revision: 1.30 $
  - * $Date: 2000/04/25 17:54:14 $
  + * $Header: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/core/RequestImpl.java,v 1.31 2000/05/02 22:39:57 costin Exp $
  + * $Revision: 1.31 $
  + * $Date: 2000/05/02 22:39:57 $
    *
    * ====================================================================
    *
  @@ -206,7 +206,7 @@
   	    return serverName;
   	}
   	// default to localhost - and warn
  -	System.out.println("No server name, defaulting to localhost");
  +	//	System.out.println("No server name, defaulting to localhost");
   	serverName="localhost";
   	return serverName;
       }
  
  
  
  1.2       +89 -40    jakarta-tomcat/src/share/org/apache/tomcat/request/AccessInterceptor.java
  
  Index: AccessInterceptor.java
  ===================================================================
  RCS file: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/request/AccessInterceptor.java,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- AccessInterceptor.java	2000/05/01 23:15:16	1.1
  +++ AccessInterceptor.java	2000/05/02 22:39:58	1.2
  @@ -88,6 +88,9 @@
   
       // Security mapping note
       int secMapNote;
  +
  +    // Required roles attribute
  +    int reqRolesNote;
       
       public AccessInterceptor() {
       }
  @@ -112,11 +115,13 @@
        *  dynamic add/remove for this interceptor. 
        */
       public void setContextManager( ContextManager cm ) {
  -	this.cm=cm;
  +	super.setContextManager( cm );
   	
  +	this.cm=cm;
   	// set-up a per/container note for maps
   	try {
   	    secMapNote = cm.getNoteId( ContextManager.CONTAINER_NOTE, "map.security");
  +	    reqRolesNote = cm.getNoteId( ContextManager.REQUEST_NOTE, "required.roles");
   	} catch( TomcatException ex ) {
   	    ex.printStackTrace();
   	    throw new RuntimeException( "Invalid state ");
  @@ -127,9 +132,8 @@
        */
       public void addContext( ContextManager cm, Context ctx ) throws TomcatException
       {
  -	Hashtable sec=new Hashtable();
   	Container ct=ctx.getContainer();
  -	ct.setNote( secMapNote, sec );
  +	ct.setNote( secMapNote, new SecurityConstraints() );
       }
   
       /** Called when a context is removed from a CM - we must ask the mapper to
  @@ -141,33 +145,27 @@
       }
       
   
  +    // XXX not implemented - will deal with that after everything else works.
  +    public void removeContainer( Container ct )
  +	throws TomcatException
  +    {
  +    }
  +
       /**
        */
       public void addContainer( Container ct )
   	throws TomcatException
       {
   	Context ctx=ct.getContext();
  -	String path=ct.getPath();
  -	String ctxP=ctx.getPath();
  -
  -	if( ct.getRoles() != null ) {
  -	    return; // XXX - right now we add either security or handler,
  -	    // later we can use a more general/efficient aproach
  -	}
  +	Container ctxCt=ctx.getContainer();
  +	SecurityConstraints ctxSecurityC=(SecurityConstraints)ctxCt.getNote( secMapNote );
   	
  -	if(ct.getHandler() == null ) {
  -	    // it was only a security map
  -	    return;
  +	if( ct.getRoles()!=null || ct.getTransport()!=null ) {
  +	    log( "ACCESS: Adding " + ctx.getHost() + " " + ctx.getPath() + " " + ct.getPath() );
  +	    ctxSecurityC.addContainer( ct );
   	}
       }
   
  -    // XXX not implemented - will deal with that after everything else works.
  -    public void removeContainer( Container ct )
  -	throws TomcatException
  -    {
  -    }
  -
  -
       /* -------------------- Request mapping -------------------- */
   
       /** Check if this request requires auth, and if so check the roles.
  @@ -177,33 +175,84 @@
       public int authorize( Request req, Response response )
       {
   	Context ctx=req.getContext();
  -
  -	// first we check if this request _requires_ access control
  +	SecurityConstraints ctxSec=(SecurityConstraints)ctx.getContainer().getNote( secMapNote );
  +	if( ctxSec.patterns==0 ) return 0; // fast exit
  +	
  +	String reqURI = req.getRequestURI();
  +	String ctxPath= ctx.getPath();
  +	String path=reqURI.substring( ctxPath.length());
  +	String method=req.getMethod();
   
  -	// this could be optimized and part of the contextMap, to
  -	// avoid double parsing and lookup - but in production mode
  -	// both methods will be no-ops anyway ( the server has already done
  -	// it ) - and in standalone mode it's not the biggest problem and
  -	// we can optimize it later if needed.
  -	
  -	String roles[]=req.getContainer().getRoles();
  -	if( roles==null ) {
  -	    return 0;
  +	log( "ACCESS: checking " + path );
  +	
  +	for( int i=0; i< ctxSec.patterns ; i++ ) {
  +	    Container ct=ctxSec.securityPatterns[i];
  +	    if( match( ct, path, method ) ) {
  +		log( "ACCESS: matched " + ct.getPath() + " " + ct.getMethods() + " " +
  +		     ct.getTransport() + " " + ct.getRoles());
  +		String roles[]=ct.getRoles();
  +		String transport=ct.getTransport();
  +
  +		if( transport != null && (
  +					  "INTEGRAL".equals( transport ) ||
  +					  "CONFIDENTAIL".equals( transport ))) {
  +		    // check if SSL is used
  +		    log( "ACCESS: SSL required " + req );
  +		}
  +
  +		// roles will be checked by a different interceptor
  +		req.setNote( reqRolesNote, roles );
  +	    }
   	}
  -
    	return 0;
       }
   
  +    /** Find if a pattern is matched by a container
  +     */
  +    boolean match( Container ct, String path, String method ) {
  +	String ctPath=ct.getPath();
  +	int ctPathL=ctPath.length();
  +	String ctMethods[]=ct.getMethods();
  +	
  +	if( ctMethods != null && ctMethods.length > 0 ) {
  +	    boolean ok=false;
  +	    for( int i=0; i< ctMethods.length; i++ ) {
  +		if( method.equals( ctMethods[i] ) ) {
  +		    ok=true;
  +		    break;
  +		}
  +	    }
  +	    if( ! ok ) return false; // no method matched
  +	}
  +
  +	// either method is any or we matched the method
  +	
  +	switch( ct.getMapType() ) {
  +	case Container.PREFIX_MAP:
  +	    return path.startsWith( ctPath.substring(0, ctPathL - 2  ));
  +	case Container.EXTENSION_MAP:
  +	    return ctPath.substring( 1 ).equals( URLUtil.getExtension( path ));
  +	case Container.PATH_MAP:
  +	    return path.equals( ctPath );
  +	}
  +	return false;
  +    }
       // -------------------- Implementation methods --------------------
  -    
  -    
   }
   
  -class ResourceCollectionPattern {
  -    String methods[];
  -    String prefixPatterns[];
  -    String extPatterns[];
  -    String exactPatterns[];
  +class SecurityConstraints {
  +    Container []securityPatterns;
  +    int patterns=0;
  +    // implement re-sizeable array later
  +    static final int MAX_CONSTRAINTS=30;
  +
  +    public SecurityConstraints() {
  +	securityPatterns=new Container[MAX_CONSTRAINTS];
  +    }
   
  -    
  +    // It's called in a single thread anyway
  +    public synchronized void addContainer(Container ct) {
  +	securityPatterns[ patterns ]= ct;
  +	patterns++;
  +    }
   }
  
  
  
  1.15      +45 -9     jakarta-tomcat/src/share/org/apache/tomcat/request/SecurityCheck.java
  
  Index: SecurityCheck.java
  ===================================================================
  RCS file: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/request/SecurityCheck.java,v
  retrieving revision 1.14
  retrieving revision 1.15
  diff -u -r1.14 -r1.15
  --- SecurityCheck.java	2000/04/07 22:59:01	1.14
  +++ SecurityCheck.java	2000/05/02 22:39:58	1.15
  @@ -81,10 +81,35 @@
   public class SecurityCheck extends  BaseInterceptor {
       MemoryRealm memoryRealm;
       int debug=0;
  +    int reqRolesNote;
  +    ContextManager cm;
       
       public SecurityCheck() {
       }
  -	
  +
  +    void log( String s ) {
  +	cm.log("SecurityCheck: " + s );
  +    }
  +
  +    /** Set the context manager. To keep it simple we don't support
  +     *  dynamic add/remove for this interceptor. 
  +     */
  +    public void setContextManager( ContextManager cm ) {
  +	super.setContextManager( cm );
  +
  +	this.cm=cm;
  +	// set-up a per/container note for maps
  +	try {
  +	    // XXX make the name a "global" static - after everything is stable!
  +	    reqRolesNote = cm.getNoteId( ContextManager.REQUEST_NOTE, "required.roles");
  +	} catch( TomcatException ex ) {
  +	    ex.printStackTrace();
  +	    throw new RuntimeException( "Invalid state ");
  +	}
  +    }
  +
  +
  +    
       public void contextInit( Context ctx)
   	throws TomcatException
       {
  @@ -139,7 +164,7 @@
   	if( req.getRemoteUser() != null) return 0; // already authenticated
   
   	String authMethod=ctx.getAuthMethod();
  -	//	if( ctx.getDebug() > 0 ) ctx.log( "Auth: " + authMethod );
  +	//	if( debug > 0 ) ctx.log( "Auth: " + authMethod );
   	if( authMethod==null || "BASIC".equals(authMethod) ) {
   	    String authorization = req.getHeader("Authorization");
   	    // XXX we may have multiple headers ?
  @@ -147,14 +172,14 @@
   		authorization = authorization.substring(6).trim();
   		String unencoded=base64Decode( authorization );
   		int colon = unencoded.indexOf(':');
  -		if( ctx.getDebug() > 0 ) ctx.log( "BASIC auth " + authorization + " " + unencoded );
  +		if( debug > 0 ) ctx.log( "BASIC auth " + authorization + " " + unencoded );
   		if (colon < 0)
   		    return 0;
   		String username = unencoded.substring(0, colon);
   		String password = unencoded.substring(colon + 1);
   		if( checkPassword( username, password ) ) {
   		    req.setRemoteUser( username );
  -		    if( ctx.getDebug() > 0 ) ctx.log( "BASIC Auth:  " + username );
  +		    if( debug > 0 ) ctx.log( "BASIC Auth:  " + username );
   		} else {
   		    // wrong password
   		    errorPage( req, response );
  @@ -181,7 +206,7 @@
   		// it wasn't authenticated, maybe it's a new login 
   		String username=(String)session.getAttribute("j_username");
   		String password=(String)session.getAttribute("j_password");
  -		if( ctx.getDebug() > 0 ) ctx.log( "Form Auth:  " + username + " " + password);
  +		if( debug > 0 ) ctx.log( "Form Auth:  " + username + " " + password);
   		if( username!=null && checkPassword( username, password ) ) {
   		    req.setRemoteUser( username );
   		    Credential c=new Credential();
  @@ -208,6 +233,7 @@
       public int authorize( Request req, Response response )
       {
   	Context ctx=req.getContext();
  +	log( "Authorizing " + req );
   	// Set  default RequestSecurityProvider if not set
   	if (ctx.getRequestSecurityProvider() == null) {
   	    Hashtable roles = memoryRealm.getRoles();
  @@ -216,13 +242,23 @@
   	    ctx.setRequestSecurityProvider(rsp);
   	}
   
  +	// XXX 3.1 compatibility, will go away
   	String roles[]=req.getContainer().getRoles();
   	if( roles==null ) {
  +	    roles=(String[]) req.getNote( reqRolesNote );
  +	}
  +	
  +	if( roles==null ) {
  +	    log( "No roles " + req );
   	    return 0;
   	}
  -
  +	
  +	for( int i=0; i< roles.length; i++ ) {
  +	    log( "Require " + roles[i]);
  +	}
  +	
   	String user=req.getRemoteUser();
  -	if( ctx.getDebug() > 0 ) ctx.log( "Controled access for " + user + " " + req + " " + req.getContainer() );
  +	if( debug > 0 ) log( "Controled access for " + user + " " + req + " " + req.getContainer() );
   	if( user!=null ) {
   	    for( int i=0; i< roles.length; i++ ) {
   		if( userInRole( user, roles[i] ) )
  @@ -230,7 +266,7 @@
   	    }
   	}
   
  -	if( ctx.getDebug() > 0 ) ctx.log( "Unauthorized " + user + " " + req.getContainer());
  +	if( debug > 0 ) log( "Unauthorized " + user + " " + req.getContainer());
    	return HttpServletResponse.SC_UNAUTHORIZED;
   	// XXX check transport
       }
  @@ -313,7 +349,7 @@
       }
       
       public void addUser(String name, String pass, String groups ) {
  -	if( debug > 0 )  ctx.log( "Add user " + name + " " + pass + " " + groups );
  +	if( ctx.getDebug() > 0 )  ctx.log( "Add user " + name + " " + pass + " " + groups );
   	passwords.put( name, pass );
   	groups += ",";
   	while (true) {
  
  
  
  1.17      +1 -1      jakarta-tomcat/src/share/org/apache/tomcat/request/SessionInterceptor.java
  
  Index: SessionInterceptor.java
  ===================================================================
  RCS file: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/request/SessionInterceptor.java,v
  retrieving revision 1.16
  retrieving revision 1.17
  diff -u -r1.16 -r1.17
  --- SessionInterceptor.java	2000/05/01 23:15:16	1.16
  +++ SessionInterceptor.java	2000/05/02 22:39:58	1.17
  @@ -233,7 +233,7 @@
       	cookie.setMaxAge(-1);
           cookie.setPath(sessionPath);
       	cookie.setVersion(1);
  -	
  +
   	response.addHeader( CookieTools.getCookieHeaderName(cookie),
   			    CookieTools.getCookieHeaderValue(cookie));
       	cookie.setVersion(0);
  
  
  
  1.5       +1 -1      jakarta-tomcat/src/share/org/apache/tomcat/request/SimpleMapper1.java
  
  Index: SimpleMapper1.java
  ===================================================================
  RCS file: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/request/SimpleMapper1.java,v
  retrieving revision 1.4
  retrieving revision 1.5
  diff -u -r1.4 -r1.5
  --- SimpleMapper1.java	2000/05/01 23:15:16	1.4
  +++ SimpleMapper1.java	2000/05/02 22:39:58	1.5
  @@ -179,7 +179,7 @@
   	String path=ct.getPath();
   	String ctxP=ctx.getPath();
   
  -	if(ct.getHandler() == null ) {
  +	if(ct.getRoles() != null || ct.getTransport() != null ) {
   	    // it was only a security map, no handler defined
   	    return;
   	}
  
  
  
  1.1                  jakarta-tomcat/src/share/org/apache/tomcat/util/SecurityTools.java
  
  Index: SecurityTools.java
  ===================================================================
  /*
   * ====================================================================
   *
   * 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.util;
  
  import org.apache.tomcat.core.*;
  import org.apache.tomcat.util.*;
  import org.apache.tomcat.util.xml.*;
  import java.io.*;
  import java.net.*;
  import java.util.*;
  import javax.servlet.http.*;
  import org.xml.sax.*;
  
  /**
   *  Various tools used to implement security.
   * 
   */
  public class SecurityTools {
      
      static int base64[]= {
  	    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
  	    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
  	    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63,
  	    52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64,
  	    64,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
  	    15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64,
  	    64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
  	    41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64,
  	    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
  	    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
  	    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
  	    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
  	    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
  	    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
  	    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
  	    64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64
      };
      
      public static String base64Decode( String orig ) {
  	char chars[]=orig.toCharArray();
  	StringBuffer sb=new StringBuffer();
  	int i=0;
  
  	int shift = 0;   // # of excess bits stored in accum
  	int acc = 0;
  	
  	for (i=0; i<chars.length; i++) {
  	    int v = base64[ chars[i] & 0xFF ];
  	    
  	    if ( v >= 64 ) {
  		if( chars[i] != '=' )
  		    System.out.println("Wrong char in base64: " + chars[i]);
  	    } else {
  		acc= ( acc << 6 ) | v;
  		shift += 6;
  		if ( shift >= 8 ) {
  		    shift -= 8;
  		    sb.append( (char) ((acc >> shift) & 0xff));
  		}
  	    }
  	}
  	return sb.toString();
      }