/*
 * Daniel Kouril <kouril@ics.muni.cz>
 * http://meta.cesnet.cz/software/heimdal/negotiate.en.html
*/

/* ====================================================================
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2000 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 acknowledgment:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Apache" 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 name, without prior written
 *    permission of the Apache Software Foundation.
 *
 * 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/>.
 *
 * Portions of this software are based upon public domain software
 * originally written at the National Center for Supercomputing Applications,
 * University of Illinois, Urbana-Champaign.
 */
/* 
 * This module implements kerberos5 authentication. Both native kerberos 
 * (via GSS API) and password-based methods are supported. Hacked browser is 
 * needed if you want to use native kerberos5 protocol (a patch for the Mozilla
 * browser can be found at http://meta.cesnet.cz/software/heimdal/)
 */

#include "ap_compat.h"
#include "apr_lib.h"

#define APR_WANT_STRFUNC
#include "apr_want.h"
#include "apr_strings.h"

#include "httpd.h"
#include "http_config.h"
#include "http_core.h"
#include "http_log.h"
#include "http_main.h"
#include "http_protocol.h"
#include "http_request.h"
#include "http_connection.h"

#include <gssapi.h>

#include <krb5.h>
#include <kafs.h>

module AP_MODULE_DECLARE_DATA gss_krb5_auth_module;

extern krb5_keytab gssapi_krb5_keytab;

typedef struct {
  /* Directives for the GSSAPI part */
  char  *gss_krb5_keytab;  
  int    gss_save_creds;
  char  *gss_krb5_cells;
  int    use_gss_auth;
  char  *gss_krb5_realms;

  /* Directives for the password verification part */
  char *krb5_auth_realm;
  char *krb5_keytab;
  int krb5_save_creds;
  int krb5_forwardable;
  char *krb5_cells;
} gss_krb5_config_rec;

static const char *
krb5_save_cells(cmd_parms *, gss_krb5_config_rec *, char *);

static const char *
gss_save_cells(cmd_parms *, gss_krb5_config_rec *, char *);

static const char *
gss_save_realms(cmd_parms *, gss_krb5_config_rec *, char *);

static const char *
krb5_save_realms(cmd_parms *, gss_krb5_config_rec *, char *);

static const command_rec gss_krb5_cmds[] =
{
  /* Directives for the GSSAPI part */ 
  AP_INIT_TAKE1("GssKrb5Keytab", ap_set_file_slot,
    (void*)APR_OFFSETOF(gss_krb5_config_rec, gss_krb5_keytab),
    OR_AUTHCFG, "Kerberos v5 keytab." ),

  AP_INIT_FLAG("GssKrb5SaveCredentials", ap_set_flag_slot,
    (void*)APR_OFFSETOF(gss_krb5_config_rec, gss_save_creds),
    OR_AUTHCFG, "Save the v5 credential cache?" ),

  AP_INIT_RAW_ARGS("GssKrb5AFSCells", gss_save_cells, NULL,
     OR_AUTHCFG, "Create pag and AFS tokens for cells" ),

  AP_INIT_FLAG("GssSaveCredentials", ap_set_flag_slot,
    (void*)APR_OFFSETOF(gss_krb5_config_rec, gss_save_creds),
    OR_AUTHCFG, "Save the v5 credential cache?" ),

  AP_INIT_FLAG("GssAuth", ap_set_flag_slot,
     (void*)APR_OFFSETOF(gss_krb5_config_rec, use_gss_auth),
     OR_AUTHCFG, "Whether to use GSS-API authentication" ),

  AP_INIT_RAW_ARGS("GssKrb5AuthRealms", gss_save_realms, NULL,
     OR_AUTHCFG, "Kerberos realm(s) in which to authenticate users" ),

  /* Directives for the password verification part */
  AP_INIT_RAW_ARGS("KrbAuthRealm", krb5_save_realms, NULL,
    OR_AUTHCFG, "Kerberos realm in which to authenticate users." ),

   AP_INIT_TAKE1("Krb5Keytab", ap_set_file_slot,
    (void*)APR_OFFSETOF(gss_krb5_config_rec, krb5_keytab),
    OR_AUTHCFG, "Kerberos v5 keytab." ),

   AP_INIT_FLAG("Krb5SaveCredentials", ap_set_flag_slot,
    (void*)APR_OFFSETOF(gss_krb5_config_rec, krb5_save_creds),
    OR_AUTHCFG, "Save the v5 credential cache?" ),

   AP_INIT_FLAG("Krb5Forwardable", ap_set_flag_slot,
    (void*)APR_OFFSETOF(gss_krb5_config_rec, krb5_forwardable),
    OR_AUTHCFG, "Set tickets to be forwardable?" ),

   AP_INIT_RAW_ARGS("Krb5AFSCells", krb5_save_cells, NULL,
    OR_AUTHCFG, "Create pag and AFS tokens for cells" ),   

  { NULL }
};

static const char *
gss_save_cells(cmd_parms *cmd, gss_krb5_config_rec *sec, char *arg)
{
   sec->gss_krb5_cells = apr_pstrdup(cmd->pool,arg);
   return NULL;
}

static const char *
krb5_save_cells(cmd_parms *cmd, gss_krb5_config_rec *sec, char *arg)
{
   sec->krb5_cells = apr_pstrdup(cmd->pool,arg);
   return NULL;
}

static const char *
gss_save_realms(cmd_parms *cmd, gss_krb5_config_rec *sec, char *arg)
{
   sec->gss_krb5_realms = apr_pstrdup(cmd->pool,arg);
   return NULL;
}

static const char *
krb5_save_realms(cmd_parms *cmd, gss_krb5_config_rec *sec, char *arg)
{
   sec->krb5_auth_realm = apr_pstrdup(cmd->pool,arg);
   return NULL;
}

static apr_status_t
krb5_cache_cleanup(void *data)
{
   krb5_context context;
   krb5_ccache  cache;
   krb5_error_code problem;
   char *cache_name = (char *) data;

   problem = krb5_init_context(&context);
   if (problem) {
      ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, "krb5_init_context() failed");
      return APR_EGENERAL;
   }

   problem = krb5_cc_resolve(context, cache_name, &cache);
   if (problem) {
      ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, 
                   "krb5_cc_resolve() failed (%s: %s)",
	           cache_name, krb5_get_err_text(context, problem)); 
      return APR_EGENERAL;
   }

   krb5_cc_destroy(context, cache);
   krb5_free_context(context);

   return APR_SUCCESS;
}

static apr_status_t krb5_dummy_cleanup(void *data) { return APR_SUCCESS; }

static apr_status_t krb5_unlog_cleanup(void *data)
{
   if (k_hasafs())
      k_unlog();
   return APR_SUCCESS;
}

static void
note_auth_failure(request_rec *r, const gss_krb5_config_rec *conf)
{
   const char *auth_type = NULL;
   const char *auth_name = NULL;

   /* get the type specified in .htaccess */
   auth_type = ap_auth_type(r);

   /* get the user realm specified in .htaccess */
   auth_name = ap_auth_name(r);

   /* XXX should the WWW-Authenticate header be cleared first? */
   if (conf->use_gss_auth)
      ap_table_add(r->err_headers_out, "WWW-Authenticate", "GSS-Negotiate ");
   if (auth_type && strncasecmp(auth_type, "KerberosV5", 10) == 0)
      ap_table_add(r->err_headers_out, "WWW-Authenticate",
	           ap_pstrcat(r->pool, "Basic realm=\"", auth_name, "\"", NULL));
}

static char *
get_gss_error(apr_pool_t *p, OM_uint32 error_status, char *prefix)
{
   OM_uint32 maj_stat, min_stat;
   OM_uint32 msg_ctx = 0;
   gss_buffer_desc status_string;
   char buf[1024];
   size_t len;

   snprintf(buf, sizeof(buf), "%s: ", prefix);
   len = strlen(buf);
   do {
      maj_stat = gss_display_status (&min_stat,
	                             error_status,
				     GSS_C_MECH_CODE,
				     GSS_C_NO_OID,
				     &msg_ctx,
				     &status_string);
      if (sizeof(buf) > len + status_string.length + 1) {
         sprintf(buf, "%s:", (char*) status_string.value);
         len += status_string.length;
      }
      gss_release_buffer(&min_stat, &status_string);
   } while (!GSS_ERROR(maj_stat) && msg_ctx != 0);

   return (apr_pstrdup(p, buf));
}

static void *
gss_krb5_dir_create_config(apr_pool_t *p, char *dir)
{
  gss_krb5_config_rec *rec;

  rec = (gss_krb5_config_rec *) ap_pcalloc(p, sizeof(gss_krb5_config_rec));
  return rec;
}

static void
do_afs_log(krb5_context krb_ctx,
           request_rec *r,
	   krb5_ccache ccache,
	   char *afs_cells)
{
   if (afs_cells && k_hasafs()) {
      char *cells = apr_pstrdup(r->pool, afs_cells), *next;

      k_setpag();
      for (next=strtok(cells," \t"); next; next = strtok(NULL," \t"))
	  krb5_afslog(krb_ctx, ccache, next, NULL);
      ap_register_cleanup(r->pool, NULL, 
	                  krb5_unlog_cleanup, krb5_dummy_cleanup);
   }
}
static int
store_krb5_creds(krb5_context krb_ctx,
                 request_rec *r,
                 gss_krb5_config_rec *conf,
                 krb5_ccache delegated_cred)
{
   krb5_error_code problem;
   char *cache_name = NULL;
   krb5_ccache ccache = NULL;
   krb5_principal principal = NULL;

   if (delegated_cred == NULL)
      return OK; /* XXX */

   problem = krb5_cc_gen_new(krb_ctx, &krb5_fcc_ops, &ccache);
   if (problem) {
      ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, 0, r,
	            "krb5_cc_gen_new() failed (%s)",
		    krb5_get_err_text(krb_ctx, problem));
      goto end;
   }

   problem = krb5_cc_get_principal(krb_ctx, delegated_cred, &principal);
   if (problem) {
      ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, 0, r,
	            "krb5_cc_get_principal() failed (%s)",
		    krb5_get_err_text(krb_ctx, problem));
      goto end;
   }

   problem = krb5_cc_initialize(krb_ctx, ccache, principal);
   if (problem) {
       ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, 0, r,
	             "krb5_cc_initialize() failed (%s)",
		     krb5_get_err_text(krb_ctx, problem));
       goto end;
   }

   problem = krb5_cc_copy_cache(krb_ctx, delegated_cred, ccache);
   if (problem) {
      ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, 0, r,
	            "krb5_cc_copy_cache() failed (%s)",
		    krb5_get_err_text(krb_ctx, problem));
      goto end;
   }

   cache_name = apr_pstrdup(r->pool, krb5_cc_get_name(krb_ctx, ccache));
   ap_table_setn(r->subprocess_env, "KRB5CCNAME", cache_name);
   ap_register_cleanup(r->pool, cache_name, 
	               krb5_cache_cleanup, krb5_dummy_cleanup);
     
   krb5_cc_close(krb_ctx, ccache);

end:
   if (principal)
      krb5_free_principal(krb_ctx, principal);
   if (ccache && problem)
      krb5_cc_destroy(krb_ctx, ccache);

   return (problem ? HTTP_INTERNAL_SERVER_ERROR : OK);
}

static int 
authenticate_user_gss(request_rec *r, 
                      gss_krb5_config_rec *conf,
                      const char *auth_line)
{
  OM_uint32 major_status, minor_status, minor_status2;
  gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
  gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
  const char *auth_param = NULL;
  krb5_context krb_ctx = NULL;
  int ret;
  gss_name_t client_name = GSS_C_NO_NAME;
  gss_ctx_id_t gss_context = GSS_C_NO_CONTEXT;
  gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL;
  char *p;

  krb5_init_context(&krb_ctx);
  if (conf->gss_krb5_keytab &&
      krb5_kt_resolve(krb_ctx, conf->gss_krb5_keytab, &gssapi_krb5_keytab)) {
	ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, 0, r,
	             "krb5_kt_resolve() failed");
	ret = HTTP_INTERNAL_SERVER_ERROR;
	goto end;
  }

  /* ap_getword() shifts parameter */
  auth_param = ap_getword_white(r->pool, &auth_line);
  if (auth_param == NULL) {
     ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, 0, r,
	           "No Authorization parameter from client");
     ret = HTTP_UNAUTHORIZED;
     goto end;
  }

  /* XXX use ap_uudecode() */
  input_token.length = ap_base64decode_len(auth_param);
  input_token.value = malloc(input_token.length);
  if (input_token.value == NULL) {
     ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, 0, r,
	   	   "Not enough memory");
     ret = HTTP_INTERNAL_SERVER_ERROR;
     goto end;
  }
  input_token.length = ap_base64decode(input_token.value, auth_param);

  major_status = gss_accept_sec_context(&minor_status,
	                                &gss_context,
					GSS_C_NO_CREDENTIAL,
					&input_token,
					GSS_C_NO_CHANNEL_BINDINGS,
					&client_name,
					NULL,
					&output_token,
					NULL,
					NULL,
					&delegated_cred);
  if (output_token.length) {
     char *token = NULL;
     
     /* XXX use ap_uuencode() */
     token = malloc(ap_base64encode_len(output_token.length));
     if (token == NULL) {
	ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, 0, r,
	             "Not enough memory");
        ret = HTTP_INTERNAL_SERVER_ERROR;
	gss_release_buffer(&minor_status2, &output_token);
	goto end;
     }
     ap_base64encode(token, output_token.value, output_token.length);
     ap_table_set(r->err_headers_out, "WWW-Authenticate",
	          ap_pstrcat(r->pool, "GSS-Negotiate ", token, NULL));
     free(token);
     gss_release_buffer(&minor_status2, &output_token);
  }

  if (GSS_ERROR(major_status)) {
     ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, 0, r,
	           "%s", get_gss_error(r->pool, minor_status,
		                       "gss_accept_sec_context() failed"));
     ret = HTTP_UNAUTHORIZED;
     goto end;
  }

  if (major_status & GSS_S_CONTINUE_NEEDED) {
     ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, 0, r,
	           "only one authentication iteration allowed"); 
     ret = HTTP_UNAUTHORIZED;
     goto end;
  }

  major_status = gss_export_name(&minor_status, client_name, &output_token);
  gss_release_name(&minor_status, &client_name); 
  if (GSS_ERROR(major_status)) {
    ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, 0, r,
	          "%s", get_gss_error(r->pool, minor_status, 
		                      "gss_export_name() failed"));
    ret = HTTP_INTERNAL_SERVER_ERROR;
    goto end;
  }

  /* If the user comes from a realm specified by configuration don't include
      its realm name in the username so that the authorization routine could
      work for both Password-based and Ticket-based authentication. It's
      administrators responsibility to include only such realm that have
      unified principal instances, i.e. if the same principal name occures in
      multiple realms, it must be always assigned to a single user.
  */    
  r->ap_auth_type = "Negotiate";
  r->user = apr_pstrdup(r->pool, output_token.value);
  p = strchr(r->user, '@');
  if (p != NULL) {
     const char *realms = conf->gss_krb5_realms;

     while (realms && *realms) {
	if (strcmp(p+1, ap_getword_white(r->pool, &realms)) == 0) {
	   *p = '\0';
	   break;
	}
     }
  }

  gss_release_buffer(&minor_status, &output_token);

  /* This should be only done if afs token are requested or gss_save creds is 
   * specified */
  /* gss_export_cred() from the GGF GSS Extensions could be used */
  if (delegated_cred != GSS_C_NO_CREDENTIAL &&
      (conf->gss_save_creds || (conf->gss_krb5_cells && k_hasafs()))) {	
     do_afs_log(krb_ctx, r, delegated_cred->ccache, conf->gss_krb5_cells);
     ret = store_krb5_creds(krb_ctx, r, conf, delegated_cred->ccache);
     if (ret)
	goto end;
  }
  ret = OK;

end:
  if (delegated_cred)
     gss_release_cred(&minor_status, &delegated_cred);

  if (output_token.length) 
     gss_release_buffer(&minor_status, &output_token);

  if (client_name != GSS_C_NO_NAME)
     gss_release_name(&minor_status, &client_name);

  if (gss_context != GSS_C_NO_CONTEXT)
     gss_delete_sec_context(&minor_status, &gss_context, GSS_C_NO_BUFFER);

  krb5_free_context(krb_ctx);

  if (ret == HTTP_UNAUTHORIZED)
     note_auth_failure(r, conf);

  return ret;
}

static int
authenticate_user_pwd(request_rec *r,
                      gss_krb5_config_rec *conf,
		      const char *auth_line)
{
   const char      *sent_pw = NULL;
   const char      *realms = NULL;
   krb5_context    kcontext;
   krb5_error_code code;
   krb5_principal  princ = NULL;
   char            errstr[512];
   krb5_ccache     ccache = NULL;
   int             ret;

   krb5_init_context(&kcontext);
   
   sent_pw = ap_pbase64decode(r->pool, auth_line);
   r->user = ap_getword (r->pool, &sent_pw, ':');
   r->ap_auth_type = "Basic";
   
   /* do not allow user to override realm setting of server */
   if (strchr(r->user,'@')) {
      ap_log_rerror (APLOG_MARK, APLOG_ERR, 0, r, "specifying realm in user name is prohibited: %s", r->uri);
      return  HTTP_UNAUTHORIZED;
   }

   code = krb5_cc_gen_new(kcontext, &krb5_mcc_ops, &ccache);
   if (code) {
      snprintf(errstr, sizeof(errstr), "krb5_cc_gen_new(): %.100s",
	       krb5_get_err_text(kcontext, code));
      ap_log_rerror (APLOG_MARK, APLOG_ERR, 0, r, errstr);
      ret = HTTP_INTERNAL_SERVER_ERROR;
      goto end;
   }

   if (conf->krb5_keytab)
      kcontext->default_keytab = conf->krb5_keytab;

   realms = conf->krb5_auth_realm;

   do {
      code = 0;
      if (realms)
	 code = krb5_set_default_realm(kcontext,
	                               ap_getword_white(r->pool, &realms));
      if (code)
	 continue;

      code = krb5_parse_name(kcontext, r->user, &princ);
      if (code)
	 continue;

      code = krb5_verify_user(kcontext, princ, ccache, sent_pw, 1, "khttp");
      if (code == 0)
	 break;
   } while (realms && *realms);

   memset((char *)sent_pw, 0x00, strlen(sent_pw));

   if (code) {
      snprintf(errstr, sizeof(errstr), "Verifying krb5 password failed: %s",
               krb5_get_err_text(kcontext, code));
      ap_log_rerror (APLOG_MARK, APLOG_ERR, 0, r, errstr);
      ret = HTTP_UNAUTHORIZED;
      goto end;
   }

   if (conf->krb5_save_creds) {
      do_afs_log(kcontext, r, ccache, conf->krb5_cells);
      ret = store_krb5_creds(kcontext, r, conf, ccache);
      if (ret)
         goto end;
   }

   ret = OK;

end:
   if (princ)
      krb5_free_principal(kcontext, princ);
   if (ccache)
      krb5_cc_destroy(kcontext, ccache);
   krb5_free_context(kcontext);

   return ret;
}

static int
gss_krb5_authenticate_user(request_rec *r)
{
  gss_krb5_config_rec *conf =
     (gss_krb5_config_rec *) ap_get_module_config(r->per_dir_config,
						  &gss_krb5_auth_module);
  const char *auth_type = NULL;
  const char *auth_line = NULL;
  const char *type = NULL;
  int ret;

  /* get the type specified in .htaccess */
  type = ap_auth_type(r);

  /* Apache doesn't allow multiple values in AuthType within one directory.
   * But since we want to support both the method we added a new configuration
   * option (UseGSS) to turn on GSS authentication. */
  if (!conf->use_gss_auth &&
      (type == NULL || strncasecmp(type, "KerberosV5", 10) != 0)) {
     return DECLINED;
  }

  /* get what the user sent us in the HTTP header */
  auth_line = ap_table_get (r->headers_in, "Authorization");
  if (!auth_line) {
      note_auth_failure(r, conf);
      return HTTP_UNAUTHORIZED;
  }
  auth_type = ap_getword_white(r->pool, &auth_line);

  if (conf->use_gss_auth &&
      strncasecmp(auth_type, "GSS-Negotiate", 13) == 0) {
     ret = authenticate_user_gss(r, conf, auth_line);
  } else
     if (type != NULL && strncasecmp(type, "KerberosV5", 10) == 0 &&
	 strncasecmp(auth_type, "Basic", 5) == 0) {
	ret = authenticate_user_pwd(r, conf, auth_line);
     }
     else {
	ret = HTTP_UNAUTHORIZED;
     }

  if (ret == HTTP_UNAUTHORIZED)
     note_auth_failure(r, conf);

  return ret;
}


static int 
gss_krb5_check_user(request_rec *r)
{
  gss_krb5_config_rec *conf =
     (gss_krb5_config_rec *) ap_get_module_config(r->per_dir_config,
						&gss_krb5_auth_module);
  char *user = r->user;
  int m = r->method_number;
  int method_restricted = 0;
  register int x;
  const char *t, *w;
  const require_line *require;
  const apr_array_header_t *required_users;

  required_users = ap_requires(r);
  if (required_users == NULL)
    return OK;

  require = (require_line *) required_users->elts;

  for (x = 0; x < required_users->nelts; x++) {
     if (! (require[x].method_mask & (1 << m)))
	continue;

     method_restricted = 1;

     t = require[x].requirement;
     w = ap_getword_white(r->pool, &t);
     if (strcmp(w, "valid-user") == 0) {
	return OK;
     }

     if (strcmp(w, "user") == 0) {
	while (t[0] != '\0') {
	   w = ap_getword_conf(r->pool, &t);
           if (strcmp(user, w) == 0) 
              return OK;
	}
     }
  }

  if (! method_restricted)
     return OK;

  ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
	"GSS & Krb5 module: access to %s failed, reason: user %s not allowed access",
	r->uri, user);

  note_auth_failure(r, conf);

  return HTTP_UNAUTHORIZED;
}

static void register_hooks(apr_pool_t * p)
{
  ap_hook_check_user_id(gss_krb5_authenticate_user, NULL, NULL, APR_HOOK_MIDDLE);
  ap_hook_auth_checker(gss_krb5_check_user, NULL, NULL, APR_HOOK_MIDDLE);
}

module AP_MODULE_DECLARE_DATA gss_krb5_auth_module =
{
  STANDARD20_MODULE_STUFF, 
  gss_krb5_dir_create_config, /* dir config creater */
  NULL,                       /* dir merger --- default is to override */
  NULL,                       /* server config */
  NULL,                       /* merge server config */
  gss_krb5_cmds,              /* command table */
  register_hooks              /* register other hooks */
};
