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

cvs commit: jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/util MD5Encoder.java

remm        00/05/30 18:33:32

  Modified:    proposals/catalina/src/share/org/apache/tomcat Realm.java
               proposals/catalina/src/share/org/apache/tomcat/connector/http
                        HttpResponseImpl.java
               proposals/catalina/src/share/org/apache/tomcat/core
                        StandardWrapperValve.java
               proposals/catalina/src/share/org/apache/tomcat/realm
                        JDBCRealm.java MemoryRealm.java
               proposals/catalina/src/share/org/apache/tomcat/security
                        HttpBasicValve.java
  Added:       proposals/catalina/src/share/org/apache/tomcat/realm
                        RealmBase.java
               proposals/catalina/src/share/org/apache/tomcat/security
                        HttpDigestValve.java
               proposals/catalina/src/share/org/apache/tomcat/util
                        MD5Encoder.java
  Log:
  - Enables DIGEST authentication. To use it, assign the DigestValve
  to a Context. Many thanks to Carson McDonald for his help.
  
  - Slightly redesigned Realm. An abstract base realm class is introduced, which
  takes over the burden of MD5 digest calculations. Neither the clear text
  password nor the authorization digest are exposed.
  
  - The DIGEST authentication implementation is preliminary. Features are missing
  and it is not very secure, but at least it's working (tested with IE). There
  are a lot of commented debug statements in the code, which I'll remove later. I
  need them right now to tweak the implementation.
  
  - Fixed issues related to chunking and the HTTP/1.1 connector. It should now
  work ok. Tested with IE 5, Mozilla (the latest nightly build) and Opera 4 on
  static pages, servlets and JSP pages under Win2k.
  
  - To use the updated HTTP/1.1 connector instead of the test one, change this
  line in server.xml :
    <Connector className="org.apache.tomcat.connector.test.HttpConnector"
  to
    <Connector className="org.apache.tomcat.connector.http.HttpConnector"
  
  - Doesn't include support for some HTTP/1.1 features like resuming or If
  headers yet (coming soon).
  
  Revision  Changes    Path
  1.3       +23 -4     jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/Realm.java
  
  Index: Realm.java
  ===================================================================
  RCS file: /home/cvs/jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/Realm.java,v
  retrieving revision 1.2
  retrieving revision 1.3
  diff -u -r1.2 -r1.3
  --- Realm.java	2000/01/29 07:46:32	1.2
  +++ Realm.java	2000/05/31 01:33:29	1.3
  @@ -1,7 +1,7 @@
   /*
  - * $Header: /home/cvs/jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/Realm.java,v 1.2 2000/01/29 07:46:32 craigmcc Exp $
  - * $Revision: 1.2 $
  - * $Date: 2000/01/29 07:46:32 $
  + * $Header: /home/cvs/jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/Realm.java,v 1.3 2000/05/31 01:33:29 remm Exp $
  + * $Revision: 1.3 $
  + * $Date: 2000/05/31 01:33:29 $
    *
    * ====================================================================
    *
  @@ -77,7 +77,7 @@
    * Container.
    *
    * @author Craig R. McClanahan
  - * @version $Revision: 1.2 $ $Date: 2000/01/29 07:46:32 $
  + * @version $Revision: 1.3 $ $Date: 2000/05/31 01:33:29 $
    */
   
   public interface Realm {
  @@ -139,6 +139,25 @@
        *  authenticating this username
        */
       public Principal authenticate(String username, byte[] credentials);
  +
  +
  +    /**
  +     * Return the Principal associated with the specified username, which
  +     * matches the digest calculated using the given parameters using the 
  +     * method described in RFC 2069; otherwise return <code>null</code>.
  +     * 
  +     * @param username Username of the Principal to look up
  +     * @param digest Digest which has been submitted by the client
  +     * @param nonce Unique (or supposedly unique) token which has been used
  +     * for this request
  +     * @param realm Realm name
  +     * @param md5a2 Second MD5 digest used to calculate the digest : 
  +     * MD5(Method + ":" + uri)
  +     */
  +    public Principal authenticate(String username, String digest,
  +                                  String nonce, String nc, String cnonce,
  +                                  String qop, String realm,
  +                                  String md5a2);
   
   
       /**
  
  
  
  1.5       +63 -4     jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/connector/http/HttpResponseImpl.java
  
  Index: HttpResponseImpl.java
  ===================================================================
  RCS file: /home/cvs/jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/connector/http/HttpResponseImpl.java,v
  retrieving revision 1.4
  retrieving revision 1.5
  diff -u -r1.4 -r1.5
  --- HttpResponseImpl.java	2000/05/23 06:05:53	1.4
  +++ HttpResponseImpl.java	2000/05/31 01:33:30	1.5
  @@ -1,7 +1,7 @@
   /*
  - * $Header: /home/cvs/jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/connector/http/HttpResponseImpl.java,v 1.4 2000/05/23 06:05:53 remm Exp $
  - * $Revision: 1.4 $
  - * $Date: 2000/05/23 06:05:53 $
  + * $Header: /home/cvs/jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/connector/http/HttpResponseImpl.java,v 1.5 2000/05/31 01:33:30 remm Exp $
  + * $Revision: 1.5 $
  + * $Date: 2000/05/31 01:33:30 $
    *
    * ====================================================================
    *
  @@ -66,6 +66,8 @@
   
   
   import java.io.IOException;
  +import java.io.PrintWriter;
  +import java.io.OutputStream;
   import javax.servlet.ServletOutputStream;
   import org.apache.tomcat.connector.HttpResponseBase;
   
  @@ -74,7 +76,7 @@
    * Implementation of <b>HttpResponse</b> specific to the HTTP connector.
    *
    * @author Craig R. McClanahan
  - * @version $Revision: 1.4 $ $Date: 2000/05/23 06:05:53 $
  + * @version $Revision: 1.5 $ $Date: 2000/05/31 01:33:30 $
    */
   
   final class HttpResponseImpl
  @@ -139,6 +141,63 @@
   
   	super.recycle();
           allowChunking = false;
  +
  +    }
  +
  +
  +    /**
  +     * Return a PrintWriter that can be used to render error messages,
  +     * regardless of whether a stream or writer has already been acquired.
  +     */
  +    public PrintWriter getReporter() {
  +
  +        try {
  +            return (new PrintWriter(getOutputStream()));
  +        } catch (IOException e) {
  +            return (new PrintWriter(output));
  +        }
  +
  +    }
  +
  +
  +    /**
  +     * Send an error response with the specified status and message.
  +     *
  +     * @param status HTTP status code to send
  +     * @param message Corresponding message to send
  +     *
  +     * @exception IllegalStateException if this response has
  +     *  already been committed
  +     * @exception IOException if an input/output error occurs
  +     */
  +    public void sendError(int status, String message) throws IOException {
  +
  +        setAllowChunking(false);
  +        addHeader("Connection", "close");
  +        super.sendError(status, message);
  +
  +    }
  +
  +
  +    /**
  +     * Clear any content written to the buffer.  In addition, all cookies
  +     * and headers are cleared, and the status is reset.
  +     *
  +     * @exception IllegalStateException if this response has already
  +     *  been committed
  +     */
  +    public void reset() {
  +
  +        // Saving important HTTP/1.1 specific headers
  +        String connectionValue = 
  +            (String) getHeader("Connection");
  +        String transferEncodingValue = 
  +            (String) getHeader("Transfer-Encoding");
  +	super.reset();
  +        if (connectionValue != null)
  +            addHeader("Connection", connectionValue);
  +        if (transferEncodingValue != null)
  +            addHeader("Transfer-Encoding", transferEncodingValue);
   
       }
   
  
  
  
  1.11      +9 -6      jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/core/StandardWrapperValve.java
  
  Index: StandardWrapperValve.java
  ===================================================================
  RCS file: /home/cvs/jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/core/StandardWrapperValve.java,v
  retrieving revision 1.10
  retrieving revision 1.11
  diff -u -r1.10 -r1.11
  --- StandardWrapperValve.java	2000/05/30 21:29:48	1.10
  +++ StandardWrapperValve.java	2000/05/31 01:33:30	1.11
  @@ -1,7 +1,7 @@
   /*
  - * $Header: /home/cvs/jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/core/StandardWrapperValve.java,v 1.10 2000/05/30 21:29:48 craigmcc Exp $
  - * $Revision: 1.10 $
  - * $Date: 2000/05/30 21:29:48 $
  + * $Header: /home/cvs/jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/core/StandardWrapperValve.java,v 1.11 2000/05/31 01:33:30 remm Exp $
  + * $Revision: 1.11 $
  + * $Date: 2000/05/31 01:33:30 $
    *
    * ====================================================================
    *
  @@ -91,7 +91,7 @@
    * <code>StandardWrapper</code> container implementation.
    *
    * @author Craig R. McClanahan
  - * @version $Revision: 1.10 $ $Date: 2000/05/30 21:29:48 $
  + * @version $Revision: 1.11 $ $Date: 2000/05/31 01:33:30 $
    */
   
   final class StandardWrapperValve
  @@ -273,12 +273,12 @@
   	    } catch (Throwable e) {
   		;
   	    }
  +	    PrintWriter writer = response.getReporter();
   	    try {
   		response.getResponse().flushBuffer();
   	    } catch (IOException e) {
   		;
   	    }
  -	    PrintWriter writer = response.getReporter();
   	    writer.println("<html>");
   	    writer.println("<head>");
   	    writer.println("<title>" +
  @@ -391,6 +391,8 @@
   	    message = "";
   	if (status == HttpServletResponse.SC_OK)
   	    return;
  +        if (status < 300)
  +            return;
   
   	// Do nothing if there is no report for the specified status code
   	String report = null;
  @@ -412,12 +414,12 @@
   	// Render an HTML status report page (FIXME - per web.xml descriptor)
   	try {
   	    hres.setContentType("text/html");
  +	    PrintWriter writer = response.getReporter();
   	    try {
   		hres.flushBuffer();
   	    } catch (IOException e) {
   		;
   	    }
  -	    PrintWriter writer = response.getReporter();
   	    writer.println("<html>");
   	    writer.println("<head>");
   	    writer.println("<title>" +
  @@ -437,6 +439,7 @@
   	} catch (IllegalStateException e) {
   	    ;
   	}
  +        
   
       }
   
  
  
  
  1.2       +23 -1     jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/realm/JDBCRealm.java
  
  Index: JDBCRealm.java
  ===================================================================
  RCS file: /home/cvs/jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/realm/JDBCRealm.java,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- JDBCRealm.java	2000/05/09 22:30:08	1.1
  +++ JDBCRealm.java	2000/05/31 01:33:31	1.2
  @@ -97,7 +97,10 @@
    * @author Carson McDonald
    */
   
  -public final class JDBCRealm implements Lifecycle, Realm {
  +public final class JDBCRealm 
  +    extends RealmBase {
  +
  +
       // ----------------------------------------------------- Instance Variables
   
   
  @@ -473,6 +476,25 @@
   
   
       // -------------------------------------------------------- Package Methods
  +
  +
  +    // ------------------------------------------------------ Protected Methods
  +
  +
  +    /**
  +     * Return the password associated with the given principal's user name.
  +     */
  +    protected String getPassword(String username) {
  +        return (null);
  +    }
  +
  +
  +    /**
  +     * Return the Principal associated with the given user name.
  +     */
  +    protected Principal getPrincipal(String username) {
  +	return (null);
  +    }
   
   
       // -------------------------------------------------------- Private Methods
  
  
  
  1.6       +30 -5     jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/realm/MemoryRealm.java
  
  Index: MemoryRealm.java
  ===================================================================
  RCS file: /home/cvs/jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/realm/MemoryRealm.java,v
  retrieving revision 1.5
  retrieving revision 1.6
  diff -u -r1.5 -r1.6
  --- MemoryRealm.java	2000/05/05 22:45:38	1.5
  +++ MemoryRealm.java	2000/05/31 01:33:31	1.6
  @@ -1,7 +1,7 @@
   /*
  - * $Header: /home/cvs/jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/realm/MemoryRealm.java,v 1.5 2000/05/05 22:45:38 craigmcc Exp $
  - * $Revision: 1.5 $
  - * $Date: 2000/05/05 22:45:38 $
  + * $Header: /home/cvs/jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/realm/MemoryRealm.java,v 1.6 2000/05/31 01:33:31 remm Exp $
  + * $Revision: 1.6 $
  + * $Date: 2000/05/31 01:33:31 $
    *
    * ====================================================================
    *
  @@ -93,11 +93,11 @@
    * location) are identical to those currently supported by Tomcat 3.X.
    *
    * @author Craig R. McClanahan
  - * @version $Revision: 1.5 $ $Date: 2000/05/05 22:45:38 $
  + * @version $Revision: 1.6 $ $Date: 2000/05/31 01:33:31 $
    */
   
   public final class MemoryRealm
  -    implements Lifecycle, Realm {
  +    extends RealmBase {
   
   
       // ----------------------------------------------------- Instance Variables
  @@ -370,6 +370,31 @@
   	    roles = roles.substring(comma + 1);
   	}
   
  +    }
  +
  +
  +    // ------------------------------------------------------ Protected Methods
  +
  +
  +    /**
  +     * Return the password associated with the given principal's user name.
  +     */
  +    protected String getPassword(String username) {
  +	MemoryRealmPrincipal principal =
  +	    (MemoryRealmPrincipal) principals.get(username);
  +	if (principal != null) {
  +	    return (principal.getPassword());
  +	} else {
  +	    return (null);
  +	}
  +    }
  +
  +
  +    /**
  +     * Return the Principal associated with the given user name.
  +     */
  +    protected Principal getPrincipal(String username) {
  +	return (Principal) principals.get(username);
       }
   
   
  
  
  
  1.1                  jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/realm/RealmBase.java
  
  Index: RealmBase.java
  ===================================================================
  /*
   * $Header: /home/cvs/jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/realm/RealmBase.java,v 1.1 2000/05/31 01:33:31 remm Exp $
   * $Revision: 1.1 $
   * $Date: 2000/05/31 01:33:31 $
   *
   * ====================================================================
   *
   * 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.realm;
  
  
  import java.beans.PropertyChangeListener;
  import java.beans.PropertyChangeSupport;
  import java.security.Principal;
  import java.security.MessageDigest;
  import java.security.NoSuchAlgorithmException;
  import java.io.File;
  import java.util.Enumeration;
  import java.util.Hashtable;
  import java.util.Vector;
  import org.apache.tomcat.Container;
  import org.apache.tomcat.Lifecycle;
  import org.apache.tomcat.LifecycleEvent;
  import org.apache.tomcat.LifecycleException;
  import org.apache.tomcat.LifecycleListener;
  import org.apache.tomcat.Logger;
  import org.apache.tomcat.Realm;
  import org.apache.tomcat.util.LifecycleSupport;
  import org.apache.tomcat.util.StringManager;
  import org.apache.tomcat.util.MD5Encoder;
  import org.apache.tomcat.util.xml.SaxContext;
  import org.apache.tomcat.util.xml.XmlAction;
  import org.apache.tomcat.util.xml.XmlMapper;
  import org.xml.sax.AttributeList;
  
  
  /**
   * Simple implementation of <b>Realm</b> that reads an XML file to configure
   * the valid users, passwords, and roles.  The file format (and default file
   * location) are identical to those currently supported by Tomcat 3.X.
   *
   * @author Craig R. McClanahan
   * @version $Revision: 1.1 $ $Date: 2000/05/31 01:33:31 $
   */
  
  public abstract class RealmBase
      implements Lifecycle, Realm {
  
  
      // ----------------------------------------------------- Instance Variables
  
  
      /**
       * The Container with which this Realm is associated.
       */
      protected Container container = null;
  
  
      /**
       * The debugging detail level for this component.
       */
      protected int debug = 0;
  
  
      /**
       * Descriptive information about this Realm implementation.
       */
      protected static final String info =
  	"org.apache.tomcat.realm.RealmBase/1.0";
  
  
      /**
       * The lifecycle event support for this component.
       */
      protected LifecycleSupport lifecycle = new LifecycleSupport(this);
  
  
      /**
       * The string manager for this package.
       */
      protected static StringManager sm =
  	StringManager.getManager(Constants.Package);
  
  
      /**
       * Has this component been started?
       */
      protected boolean started = false;
  
  
      /**
       * The property change support for this component.
       */
      protected PropertyChangeSupport support = new PropertyChangeSupport(this);
  
  
      /**
       * MD5 message digest provider.
       */
      protected static MessageDigest md5Helper;
  
  
      /**
       * The MD5 helper object for this class.
       */
      protected static final MD5Encoder md5Encoder = new MD5Encoder();
  
  
      // ------------------------------------------------------------- Properties
  
  
      /**
       * Return the Container with which this Realm has been associated.
       */
      public Container getContainer() {
  
  	return (container);
  
      }
  
  
      /**
       * Set the Container with which this Realm has been associated.
       *
       * @param container The associated Container
       */
      public void setContainer(Container container) {
  
  	Container oldContainer = this.container;
  	this.container = container;
  	support.firePropertyChange("container", oldContainer, this.container);
  
      }
  
  
      /**
       * Return the debugging detail level for this component.
       */
      public int getDebug() {
  
  	return (this.debug);
  
      }
  
  
      /**
       * Set the debugging detail level for this component.
       *
       * @param debug The new debugging detail level
       */
      public void setDebug(int debug) {
  
  	this.debug = debug;
  
      }
  
  
      /**
       * Return descriptive information about this Realm implementation and
       * the corresponding version number, in the format
       * <code>&lt;description&gt;/&lt;version&gt;</code>.
       */
      public String getInfo() {
  
  	return (info);
  
      }
  
  
      // --------------------------------------------------------- Public Methods
  
  
      /**
       * Add a property change listener to this component.
       *
       * @param listener The listener to add
       */
      public void addPropertyChangeListener(PropertyChangeListener listener) {
  
  	support.addPropertyChangeListener(listener);
  
      }
  
  
      /**
       * Return the Principal associated with the specified username and
       * credentials, if there is one; otherwise return <code>null</code>.
       *
       * @param username Username of the Principal to look up
       * @param credentials Password or other credentials to use in
       *  authenticating this username
       */
      public Principal authenticate(String username, byte[] credentials) {
  
  	return (authenticate(username, credentials.toString()));
  
      }
  
  
      /**
       * Return the Principal associated with the specified username, which
       * matches the digest calculated using the given parameters using the 
       * method described in RFC 2069; otherwise return <code>null</code>.
       * 
       * @param username Username of the Principal to look up
       * @param clientDigest Digest which has been submitted by the client
       * @param nOnce Unique (or supposedly unique) token which has been used
       * for this request
       * @param realm Realm name
       * @param md5a2 Second MD5 digest used to calculate the digest : 
       * MD5(Method + ":" + uri)
       */
      public Principal authenticate(String username, String clientDigest,
                                    String nOnce, String nc, String cnonce,
                                    String qop, String realm,
                                    String md5a2) {
          
          /*
            System.out.println("Digest : " + clientDigest);
            
            System.out.println("************ Digest info");
            System.out.println("Username:" + username);
            System.out.println("ClientSigest:" + clientDigest);
            System.out.println("nOnce:" + nOnce);
            System.out.println("nc:" + nc);
            System.out.println("cnonce:" + cnonce);
            System.out.println("qop:" + qop);
            System.out.println("realm:" + realm);
            System.out.println("md5a2:" + md5a2);
          */
          
  
          String md5a1 = getDigest(username, realm);
          if (md5a1 == null)
              return null;
          String serverDigestValue = md5a1 + ":" + nOnce + ":" + nc + ":" 
              + cnonce + ":" + qop + ":" + md5a2;
          String serverDigest = 
              md5Encoder.encode(md5Helper.digest(serverDigestValue.getBytes()));
          //System.out.println("Server digest : " + serverDigest);
          
          if (serverDigest.equals(clientDigest))
              return getPrincipal(username);
          else
              return null;
      }
  
  
  
      /**
       * Remove a property change listener from this component.
       *
       * @param listener The listener to remove
       */
      public void removePropertyChangeListener(PropertyChangeListener listener) {
  
  	support.removePropertyChangeListener(listener);
  
      }
  
  
      // ------------------------------------------------------ Lifecycle Methods
  
  
      /**
       * Add a lifecycle event listener to this component.
       *
       * @param listener The listener to add
       */
      public void addLifecycleListener(LifecycleListener listener) {
  
  	lifecycle.addLifecycleListener(listener);
  
      }
  
  
      /**
       * Remove a lifecycle event listener from this component.
       *
       * @param listener The listener to remove
       */
      public void removeLifecycleListener(LifecycleListener listener) {
  
  	lifecycle.removeLifecycleListener(listener);
  
      }
  
  
      // ------------------------------------------------------ Protected Methods
  
  
      /**
       * Return the password associated with the given principal's user name.
       */
      protected abstract String getPassword(String username);
  
  
      /**
       * Return the digest associated with given principal's user name.
       */
      protected String getDigest(String username, String realmName) {
          if (md5Helper == null) {
              try {
                  md5Helper = MessageDigest.getInstance("MD5");
              } catch (NoSuchAlgorithmException e) {
                  e.printStackTrace();
                  throw new IllegalStateException();
              }
          }
          String digestValue = username + ":" + realmName + ":" 
              + getPassword(username);
          byte[] digest = 
              md5Helper.digest(digestValue.getBytes());
          return md5Encoder.encode(digest);
      }
  
  
      /**
       * Return the Principal associated with the given user name.
       */
      protected abstract Principal getPrincipal(String username);
  
  
  }
  
  
  
  1.4       +7 -5      jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/security/HttpBasicValve.java
  
  Index: HttpBasicValve.java
  ===================================================================
  RCS file: /home/cvs/jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/security/HttpBasicValve.java,v
  retrieving revision 1.3
  retrieving revision 1.4
  diff -u -r1.3 -r1.4
  --- HttpBasicValve.java	2000/05/08 17:42:17	1.3
  +++ HttpBasicValve.java	2000/05/31 01:33:32	1.4
  @@ -1,7 +1,7 @@
   /*
  - * $Header: /home/cvs/jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/security/HttpBasicValve.java,v 1.3 2000/05/08 17:42:17 craigmcc Exp $
  - * $Revision: 1.3 $
  - * $Date: 2000/05/08 17:42:17 $
  + * $Header: /home/cvs/jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/security/HttpBasicValve.java,v 1.4 2000/05/31 01:33:32 remm Exp $
  + * $Revision: 1.4 $
  + * $Date: 2000/05/31 01:33:32 $
    *
    * ====================================================================
    *
  @@ -83,7 +83,7 @@
    * and Digest Access Authentication."
    *
    * @author Craig R. McClanahan
  - * @version $Revision: 1.3 $ $Date: 2000/05/08 17:42:17 $
  + * @version $Revision: 1.4 $ $Date: 2000/05/31 01:33:32 $
    */
   
   public final class HttpBasicValve
  @@ -165,7 +165,9 @@
   	String realmName = config.getRealmName();
   	if (realmName == null)
   	    realmName = hreq.getServerName() + ":" + hreq.getServerPort();
  -	hres.setHeader("WWW-Authenticate", "Basic realm=\"" + realmName + "\"");
  +	hres.setHeader("WWW-Authenticate", 
  +                       "Basic realm=\"" + realmName + "\"");
  +        hres.setContentLength(0);
   	hres.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
   	hres.flushBuffer();
   	return (false);
  
  
  
  1.1                  jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/security/HttpDigestValve.java
  
  Index: HttpDigestValve.java
  ===================================================================
  /*
   * $Header: /home/cvs/jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/security/HttpDigestValve.java,v 1.1 2000/05/31 01:33:32 remm Exp $
   * $Revision: 1.1 $
   * $Date: 2000/05/31 01:33:32 $
   *
   * ====================================================================
   *
   * 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;
  
  
  import java.io.IOException;
  import java.security.Principal;
  import java.security.MessageDigest;
  import java.security.NoSuchAlgorithmException;
  import java.util.Hashtable;
  import java.util.StringTokenizer;
  import javax.servlet.http.HttpServletRequest;
  import javax.servlet.http.HttpServletResponse;
  import org.apache.tomcat.HttpRequest;
  import org.apache.tomcat.HttpResponse;
  import org.apache.tomcat.Realm;
  import org.apache.tomcat.deploy.LoginConfig;
  import org.apache.tomcat.util.MD5Encoder;
  
  
  
  /**
   * An <b>Authenticator</b> and <b>Valve</b> implementation of HTTP DIGEST
   * Authentication (see RFC 2069).
   * 
   * @author Craig R. McClanahan
   * @author Remy Maucherat
   * @version $Revision: 1.1 $ $Date: 2000/05/31 01:33:32 $
   */
  
  public final class HttpDigestValve
      extends HttpSecurityBase {
  
  
      // -------------------------------------------------------------- Constants
  
  
      /**
       * Indicates that no once tokens are used only once.
       */
      private static final int USE_ONCE = 1;
  
  
      /**
       * Indicates that no once tokens are used only once.
       */
      private static final int USE_NEVER_EXPIRES = Integer.MAX_VALUE;
  
  
      /**
       * Indicates that no once tokens are used only once.
       */
      private static final int TIMEOUT_INFINITE = Integer.MAX_VALUE;
  
  
      /**
       * The MD5 helper object for this class.
       */
      private static final MD5Encoder md5Encoder = new MD5Encoder();
  
  
      /**
       * Descriptive information about this implementation.
       */
      private static final String info =
  	"org.apache.tomcat.security.HttpDigestValve/1.0";
  
  
      // ----------------------------------------------------------- Constructors
  
  
      public HttpDigestValve() {
          super();
          try {
              if (md5Helper == null)
                  md5Helper = MessageDigest.getInstance("MD5");
          } catch (NoSuchAlgorithmException e) {
              e.printStackTrace();
              throw new IllegalStateException();
          }
      }
  
  
      // ----------------------------------------------------- Instance Variables
  
  
      /**
       * MD5 message digest provider.
       */
      private static MessageDigest md5Helper;
  
  
      /**
       * No once hashtable.
       */
      private Hashtable nOnceTokens = new Hashtable();
  
  
      /**
       * No once expiration (in millisecond). A shorter amount would mean a 
       * better security level (since the token is generated more often), but at
       * the expense of a bigger server overhead.
       */
      private long nOnceTimeout = TIMEOUT_INFINITE;
  
  
      /**
       * No once expiration after a specified number of uses. A lower number
       * would produce more overhead, since a token would have to be generated
       * more often, but would be more secure.
       */
      private int nOnceUses = USE_ONCE;
  
  
      /**
       * Private key.
       */
      private String key = "Catalina";
  
  
      // ------------------------------------------------------------- Properties
  
  
      /**
       * Return descriptive information about this Valve implementation.
       */
      public String getInfo() {
  
  	return (this.info);
  
      }
  
  
      // --------------------------------------------------------- Public Methods
  
  
      /**
       * Authenticate the user making this request, based on the specified
       * login configuration.  Return <code>true</code> if any specified
       * constraint has been satisfied, or <code>false</code> if we have
       * created a response challenge already.
       *
       * @param request Request we are processing
       * @param response Response we are creating
       * @param login Login configuration describing how authentication
       *              should be performed
       *
       * @exception IOException if an input/output error occurs
       */
      public boolean authenticate(HttpRequest request,
  				HttpResponse response,
  				LoginConfig config)
  	throws IOException {
  
  	// Have we already authenticated someone?
  	Principal principal =
  	    ((HttpServletRequest) request.getRequest()).getUserPrincipal();
  	if (principal != null)
  	    return (true);
  
  	// Validate any credentials already included with this request
  	HttpServletRequest hreq =
  	    (HttpServletRequest) request.getRequest();
  	HttpServletResponse hres =
  	    (HttpServletResponse) response.getResponse();
  	String authorization = request.getAuthorization();
  	if (authorization != null) {
  	    principal = findPrincipal(hreq, authorization, context.getRealm());
  	    if (principal != null) {
  	        request.setAuthType("DIGEST");
  		request.setUserPrincipal(principal);
  		return (true);
  	    }
  	}
  
  	// Send an "unauthorized" response and an appropriate challenge
          
          // Next, generate a nOnce token (that is a token which is supposed
          // to be unique).
          String nOnce = generateNOnce(hreq);
          
  	setAuthenticateHeader(hreq, hres, config, nOnce);
  	hres.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
  	hres.flushBuffer();
  	return (false);
  
      }
  
  
      // -------------------------------------------------------- Private Methods
  
  
      /**
       * Parse the specified authorization credentials, and return the
       * associated Principal that these credentials authenticate (if any)
       * from the specified Realm.  If there is no such Principal, return
       * <code>null</code>.
       *
       * @param request HTTP servlet request
       * @param authorization Authorization credentials from this request
       * @param login Login configuration describing how authentication
       *              should be performed
       * @param realm Realm used to authenticate Principals
       */
      private static Principal findPrincipal(HttpServletRequest request, 
                                             String authorization, Realm realm) {
  
          //System.out.println("Authorization token : " + authorization);
  	// Validate the authorization credentials format
  	if (authorization == null)
  	    return (null);
  	if (!authorization.startsWith("Digest "))
  	    return (null);
  	authorization = authorization.substring(7).trim();
          
          
          StringTokenizer commaTokenizer = 
              new StringTokenizer(authorization, ",");
          
          String userName = null;
          String realmName = null;
          String nOnce = null;
          String nc = null;
          String cnonce = null;
          String qop = null;
          String uri = null;
          String response = null;
          String opaque = null;
          String method = request.getMethod();
          
          while (commaTokenizer.hasMoreTokens()) {
              String currentToken = commaTokenizer.nextToken();
              int equalSign = currentToken.indexOf('=');
              if (equalSign < 0)
                  return null;
              String currentTokenName = 
                  currentToken.substring(0, equalSign).trim();
              String currentTokenValue = 
                  currentToken.substring(equalSign + 1).trim();
              if ("username".equals(currentTokenName))
                  userName = removeQuotes(currentTokenValue);
              if ("realm".equals(currentTokenName))
                  realmName = removeQuotes(currentTokenValue);
              if ("nonce".equals(currentTokenName))
                  nOnce = removeQuotes(currentTokenValue);
              if ("nc".equals(currentTokenName))
                  nc = currentTokenValue;
              if ("cnonce".equals(currentTokenName))
                  cnonce = removeQuotes(currentTokenValue);
              if ("qop".equals(currentTokenName))
                  qop = removeQuotes(currentTokenValue);
              if ("uri".equals(currentTokenName))
                  uri = removeQuotes(currentTokenValue);
              if ("response".equals(currentTokenName))
                  response = removeQuotes(currentTokenValue);
          }
          
          if ( (userName == null) || (realmName == null) || (nOnce == null)
               || (uri == null) || (response == null) )
              return null;
          
          // Second MD5 digest used to calculate the digest : 
          // MD5(Method + ":" + uri)
          String a2 = method + ":" + uri;
          //System.out.println("A2:" + a2);
          
          String md5a2 = md5Encoder.encode(md5Helper.digest(a2.getBytes()));
          
          return (realm.authenticate(userName, response, nOnce, nc, cnonce, qop,
                                     realmName, md5a2));
          
      }
  
  
      /**
       * Removes the quotes on a string.
       */
      private static String removeQuotes(String quotedString) {
          if (quotedString.length() > 2) {
              return quotedString.substring(1, quotedString.length() - 1);
          } else {
              return new String();
          }
      }
  
  
      /**
       * Generate a unique token. The token is generated according to the 
       * following pattern. NOnceToken = Base64 ( MD5 ( client-IP ":" 
       * time-stamp ":" private-key ) ).
       * 
       * @param request HTTP Servlet request
       */
      private String generateNOnce(HttpServletRequest request) {
          long currentTime = System.currentTimeMillis();
          
          String nOnceValue = request.getRemoteAddr() + ":" + 
              currentTime + ":" + key;
          
          byte[] buffer = md5Helper.digest(nOnceValue.getBytes());
          nOnceValue = md5Encoder.encode(buffer);
          
          // Updating the value in the no once hashtable
          nOnceTokens.put(nOnceValue, new Long(currentTime + nOnceTimeout));
          
          return nOnceValue;
      }
  
  
      /**
       * Generates the WWW-Authenticate header.
       * <p>
       * The header MUST follow this template :
       * <pre>
       *      WWW-Authenticate    = "WWW-Authenticate" ":" "Digest"
       *                            digest-challenge
       * 
       *      digest-challenge    = 1#( realm | [ domain ] | nOnce |
       *                  [ digest-opaque ] |[ stale ] | [ algorithm ] )
       * 
       *      realm               = "realm" "=" realm-value
       *      realm-value         = quoted-string
       *      domain              = "domain" "=" <"> 1#URI <">
       *      nonce               = "nonce" "=" nonce-value
       *      nonce-value         = quoted-string
       *      opaque              = "opaque" "=" quoted-string
       *      stale               = "stale" "=" ( "true" | "false" )
       *      algorithm           = "algorithm" "=" ( "MD5" | token )
       * </pre>
       * 
       * @param request HTTP Servlet request
       * @param resonse HTTP Servlet response
       * @param login Login configuration describing how authentication
       *              should be performed
       * @param nOnce nonce token
       */
      private void setAuthenticateHeader(HttpServletRequest request,
                                         HttpServletResponse response,
                                         LoginConfig config,
                                         String nOnce) {
          
          // Get the realm name
  	String realmName = config.getRealmName();
  	if (realmName == null)
  	    realmName = request.getServerName() + ":" 
                  + request.getServerPort();
          
          byte[] buffer = md5Helper.digest(nOnce.getBytes());
          
          String authenticateHeader = "Digest realm=\"" + realmName + "\", "
              +  "qop=\"auth\", nonce=\"" + nOnce + "\", " + "opaque=\"" 
              + md5Encoder.encode(buffer) + "\"";
          // System.out.println("Authenticate header value : " 
          //                   + authenticateHeader);
          response.setHeader("WWW-Authenticate", authenticateHeader);
          response.setContentLength(0);
          
      }
  
  
  }
  
  
  
  1.1                  jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/util/MD5Encoder.java
  
  Index: MD5Encoder.java
  ===================================================================
  /*
   * $Header: /home/cvs/jakarta-tomcat/proposals/catalina/src/share/org/apache/tomcat/util/MD5Encoder.java,v 1.1 2000/05/31 01:33:32 remm Exp $
   * $Revision: 1.1 $
   * $Date: 2000/05/31 01:33:32 $
   *
   * ====================================================================
   *
   * 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;
  
  
  /**
   * Encode an MD5 digest into a String.
   * <p>
   * The 128 bit MD5 hash is converted into a 32 character long String.
   * Each character of the String is the hexadecimal representation of 4 bits
   * of the digest.
   *
   * @author Remy Maucherat
   * @version $Revision: 1.1 $ $Date: 2000/05/31 01:33:32 $
   */
  
  public final class MD5Encoder {
  
  
      // ----------------------------------------------------- Instance Variables
  
  
      private static final char[] hexadecimal = 
      {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 
       'a', 'b', 'c', 'd', 'e', 'f'};
  
  
      // --------------------------------------------------------- Public Methods
  
  
      /**
       * Encodes the 128 bit (16 bytes) MD5 into a 32 character String.
       *
       * @param binaryData Array containing the digest
       * @return Encoded MD5, or null if encoding failed
       */
      public String encode( byte[] binaryData ) {
          
          if (binaryData.length != 16)
              return null;
  
          char[] buffer = new char[32];
          
          for (int i=0; i<16; i++) {
              int low = (int) (binaryData[i] & 0x0f);
              int high = (int) ((binaryData[i] & 0xf0) >> 4);
              buffer[i*2] = hexadecimal[high];
              buffer[i*2 + 1] = hexadecimal[low];
          }
  
          return new String(buffer);
  
      }
  
  
  }