/*
 * Copyright (c) 1997 Regents of the University of Michigan.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that this notice is preserved and that due credit is given
 * to the University of Michigan at Ann Arbor. The name of the University
 * may not be used to endorse or promote products derived from this
 * software without specific prior written permission. This software
 * is provided ``as is'' without express or implied warranty.
 *
 * krb4_auth_module: Kerberos v4 authentication,
 *		     AFS filesystem ACL/PTS group authorization.
 *		     Modifications to work with our
 *		     Kerberos browser plug-in.
 *
 * See http://www-personal.umich.edu/~dugsong/kapache/ for more info.
 *
 * Copyright (c) 1997 Dug Song <dugsong@UMICH.EDU>
 * All rights reserved, all wrongs reversed.
 * Disclaimer: i claim dis
 *
 * $Id: mod_auth_krb4.c,v 1.1 1997/06/20 12:57:09 dugsong Exp dugsong $
 */

/* Apache includes. */
#include "httpd.h"
#include "http_config.h"
#include "http_core.h"
#include "http_log.h"
#include "http_protocol.h"
#include "util_md5.h"

#if (MODULE_MAGIC_NUMBER < 19960725)
#error "This module is for Apache 1.2 or greater!"
#endif /* version check */

/* Hacks to build with SSL. ugh. */
#if defined(STRONGHOLD) || defined(APACHE_SSL)
#define SSL_ENABLED
#ifdef APACHE_SSL
#define MD5_CTX APACHE_MD5_CTX
#else
#define MD5_CTX A_MD5_CTX
#endif
#define DES_DEFS
#define _KERBEROS_DES_H
#ifdef AFS
void des_init_random_number_generator(des_cblock *key) {}
void des_pcbc_init() {}
#endif /* AFS */
#endif /* SSL_ENABLED */

/* Kerberos includes. */
#include <krb.h>

#ifdef AFS
/* AFS includes. */
#undef PRIVATE
#undef SHARED
#include <sys/ioccom.h>
#include <afs/param.h>
#include <afs/stds.h>
#include <afs/vice.h>
#include <afs/venus.h>
#include <afs/prs_fs.h>
#include <afs/afsint.h>
#include <afs/cellconfig.h>
#ifdef USE_PTSCD
#include "ptscd.h"
#endif /* USE_PTSCD */
#endif /* AFS */
     
/* Allow plug-in clients. */
#define PLUGIN
#define NETSCAPE_ONLY

#ifdef PLUGIN
/* Plug-in specific defines. */
#define PLUGIN_VERSION	"1.1"
#define MAX_POST_DATA	2048
#define KRB_AUTH_REFRESH 5
#define EMBED_FMT	"<EMBED %s PLUGINSPAGE=\"%s\" PRINCIPAL=\"%s\" URL=\"%s\"%s></EMBED>"
#define EMBED_WIN_FMT	"<EMBED %s HIDDEN=\"true\" PLUGINSPAGE=\"%s\" PRINCIPAL=\"%s\" URL=\"%s\"%s></EMBED>"
#ifdef NETSCAPE_ONLY
#define PLUGIN_MIME	"TYPE=\"application/x-kerberos4-mime\""
#else
#define PLUGIN_MIME	"SRC=\"/dummy.krb4\""
#endif /* NETSCAPE_ONLY */
#define PLUGIN_PAGE	"http://www.umich.edu/~dugsong/kplugin/"
#define PLUGIN_DENIAL_MSG	"<H1>Authorization Required</H1>\n<P>\n \
This server could not verify that you are authorized to access the \n \
document you requested. Either you supplied the wrong credentials, or \n \
your browser doesn't understand how to supply the credentials required. \n \
Please refer to <a href=\"http://www.umich.edu/~dugsong/kplugin/\"> \n \
http://www.umich.edu/~dugsong/kplugin/</a> for information on \n \
how to enable your browser to perform Kerberos authentication.\n<P>\n"

#define PLUGIN_NOSCRIPT_MSG	"<NOSCRIPT>\n<h1>Kerberos v4 authentication \
error</h1>\n<p>\nYou need to have a Javascript-capable browser to use the \
Kerberos plug-in. If you are using Navigator 3.0 or later and you see this \
message, you need to enable JavaScript.<p>\nChoose <b>Options -> Network \
Preferences</b>, choose the <b>Languages</b> tab and click <b>Enable \
JavaScript</b>.\n</NOSCRIPT>\n"
#endif /* PLUGIN */

/* Configuration record definition. */
typedef struct {
  char *krb4_principal;
  char *krb4_srvtab;
  char *krb4_errdoc;
  int enforce_hmac;
  int allow_caching;
#ifdef PLUGIN
  char *krb4_plugin_version;
#endif /* PLUGIN */
#ifdef SSL_ENABLED
  int requires_ssl;
#endif /* SSL_ENABLED */
} krb4_config_rec;

/* Forward declaration of module, see definition at EOF. */
module krb4_auth_module;

/* Debugging cruft. */
#ifdef KRB_DEBUG
#define D(x) x
#else
#define D(x)
#endif /* KRB_DEBUG */

/* Convert radix-encoded ticket to ktext struct. */
#define GETLONG(l, cp) { \
	register unsigned char *t_cp = (unsigned char*)(cp); \
	(l) = (((unsigned int)t_cp[0]) << 24) \
	    | (((unsigned int)t_cp[1]) << 16) \
	    | (((unsigned int)t_cp[2]) << 8) \
	    | (((unsigned int)t_cp[3])) \
	    ; \
}
int radix_to_tkt(pool *p, const char *buf, KTEXT auth)
{
  extern int pr2six[];
  int len, version;
  char *c;
  
  c = (char *)buf;
  while(pr2six[*(c++)] <= 63);
  len = (char *)c - buf - 1;
  len = ((len+3)/4) * 3;
  if (len < 6) return 1;
  
  c = uudecode(p, buf);
  version = *c; c++; len--;
  GETLONG(auth->length, c); c += 4; len -= 4;
  if (auth->length < 0 || auth->length > len ||
      auth->length > sizeof(auth->dat)) return 1;
  memcpy(&auth->dat, c, auth->length);
  return 0;
}

/* HMAC-MD5 function based on RFC 2104's hmac_md5(). */
char *hmac_md5(pool *p, unsigned char *text, int tlen,
	       unsigned char *key, int klen)
{
  MD5_CTX context; 
  unsigned char k_ipad[65];  /* inner padding - key XORd with ipad */
  unsigned char k_opad[65];  /* outer padding - key XORd with opad */
  unsigned char tk[16], hash[16];
  char *r, result[33];
  int i;

  /* if key is longer than 64 bytes reset it to key=MD5(key) */
  if (klen > 64) {
    MD5_CTX tctx;
    MD5Init(&tctx); MD5Update(&tctx, key, klen); MD5Final(tk, &tctx); 
    key = tk; klen = 16; 
  }
  /* start out by storing key in pads */
  (void) memset(k_ipad, 0, sizeof k_ipad);
  (void) memset(k_opad, 0, sizeof k_opad);
  (void) memcpy(k_ipad, key, klen); (void) memcpy(k_opad, key, klen);
  
  /* XOR key with ipad and opad values */
  for (i=0; i<64; i++) { k_ipad[i] ^= 0x36; k_opad[i] ^= 0x5c; }
  
  /* perform inner MD5 */
  MD5Init(&context);			/* init context for 1st pass */
  MD5Update(&context, k_ipad, 64);	/* start with inner pad */
  MD5Update(&context, text, tlen);	/* or text of datagram */

  MD5Final(hash, &context);		/* finish up 1st pass */

  /* perform outer MD5 */
  MD5Init(&context);			/* init context for 2nd pass */
  MD5Update(&context, k_opad, 64);	/* start with outer pad */
  MD5Update(&context, hash, 16);	/* then results of 1st hash */
  MD5Final(hash, &context);		/* finish up 2nd pass */

  for (i=0, r=result; i<16; i++, r+=2) sprintf(r, "%02x", hash[i]);
  *r = '\0';
  return pstrdup(p, result);
}

/* Verify HMAC-MD5 of URL(+QUERY_STRING, if any). */
int krb4_check_hmac_md5(request_rec *r, const char *hmac, C_Block *session)
{
  char digest[33];
#ifdef PLUGIN
  char *dir = pstrdup(r->pool, r->uri);

  if (dir[strlen(dir)] != '/') *(strrchr(dir, '/')+1) = '\0';
  strncpy(digest, hmac_md5(r->pool, (unsigned char *)dir, strlen(dir),
	          (unsigned char *)session, DES_KEY_SZ), 33);
  D(fprintf(stderr, "check_hmac_md5: %s %s (%s)?\n", dir, digest, hmac));
  if (strncmp(hmac, digest, 32) != 0)
#endif /* PLUGIN */
  strncpy(digest, hmac_md5(r->pool, (unsigned char *)pstrcat(r->pool, r->uri,
		  r->args ? pstrcat(r->pool, "?", r->args, NULL) : "", NULL),
		  strlen(r->uri)+(r->args ? strlen(r->args)+1 : 0),
	          (unsigned char *)session, DES_KEY_SZ), 33);

  return strncmp(hmac, digest, 32);
}

/* Set up to give authentication required response. */
int krb4_auth_required (request_rec *r, char *reason)
{
  D(fprintf(stderr, "auth_required: %s\n", reason));
#ifdef PLUGIN
  if (table_get(r->subprocess_env, "use_kerberos_plugin")) {
    table_set(r->notes, "krb4err", reason);
    r->handler = "krb4-error-handler";
    return OK;
  }
#endif /* PLUGIN */
  table_set(r->err_headers_out, "WWW-Authenticate",
	    pstrcat(r->pool, "KerberosV4 realm=\"",
		    table_get(r->notes, "krb4principal"), "\"", NULL));
  return AUTH_REQUIRED;
}

#ifdef PLUGIN
/* Generate refreshed cookie for client. */
void krb4_make_cookie (request_rec *r, const char *ticket,
		       const char *checksum, const time_t expires,
		       const char *version)
{
  char cookie[1024];
  time_t when = expires + KRB_AUTH_REFRESH;

  ap_snprintf(cookie, 1024, "KerberosV4=\"ticket=\"%s\"checksum=\"%s\""
	      "expires=\"%d\"version=\"%s\"\"; expires=%s; path=/",
	      ticket, checksum, when, version, gm_timestr_822(r->pool, when));
  table_set(r->headers_out, "Set-Cookie", cookie);
}
#endif /* PLUGIN */

/* Authentication translation and verification. */
int krb4_check_auth(request_rec *r)
{
  KTEXT_ST ktext;
  AUTH_DAT adat = { 0 };
  char *type, *auth_line, *tmp, *ticket, *checksum, *expires, *version;
  char service[ANAME_SZ], instance[INST_SZ], realm[REALM_SZ];	
  krb4_config_rec *sec = (krb4_config_rec *)
    get_module_config(r->per_dir_config, &krb4_auth_module);
  int rc;
  
  D(fprintf(stderr, "*****\ncheck_auth: trying to access %s\n", r->uri));

  /* Check auth type. */
  if (!(type = auth_type(r)) || strncasecmp(type, "KerberosV4", 10))
    return DECLINED;

  /* Tell the client not to cache our responses. */
  if (!sec->allow_caching) {
    table_set(r->headers_out, "Cache-Control", "no-cache");
    table_set(r->headers_out, "Pragma", "no-cache");
  }
  /* Remember configured principal for this request. */
  table_set(r->notes, "krb4principal", sec->krb4_principal);

#ifdef SSL_ENABLED
  /* Check if we require SSL. */
  if (sec->requires_ssl && (r->connection->client->ssl == NULL)) {
    r->connection->user = ""; /* We want the client to see the error. */
    return krb4_auth_required(r, "authentication refused for non-SSL "
			      "encrypted connection");
  }
#endif /* SSL_ENABLED */
  /* Check for auth response in Authorization: header. */
  if ((auth_line = table_get(r->headers_in, "Authorization")) == NULL) {
#ifdef PLUGIN
    /* Check for auth response in cookie. */
    auth_line = table_get(r->headers_in, "Cookie");
#endif /* PLUGIN */
  }
  /* Make sure client specified right authentication method. */
  if (!auth_line || strncasecmp(auth_line, "KerberosV4", 10) != 0) {
    return krb4_auth_required(r, "no credentials found");
  }
  /* Get base64-encoded ticket and HMAC from auth line. */
  tmp = pstrdup(r->pool, auth_line);
  strtok(strstr(tmp, "ticket=\""), "\"");
  ticket = strtok(NULL, "\"");
  strtok(strstr(tmp, "checksum=\""), "\"");
  checksum = strtok(NULL, "\"");
#ifdef PLUGIN
  strtok(strstr(tmp, "expires=\""), "\"");
  expires = strtok(NULL, "\"");
  strtok(strstr(tmp, "version=\""), "\"");
  version = strtok(NULL, "\"");

  /* Make sure the client's plug-in version is current, if server cares. */
  if (sec->krb4_plugin_version &&
      ((version && (atof(version) < atof(sec->krb4_plugin_version))) ||
       !version)) {
    r->connection->user = ""; /* We want the client to see the error. */
    return krb4_auth_required(r, "plug-in version too old - please upgrade!");
  }
#endif /* PLUGIN */
  
  /* Make sure we got ticket and checksum. */
  if (!ticket || !checksum)
    return krb4_auth_required(r, "no ticket or checksum");
  
  /* Decode base-64 encoded ticket. */
  if (radix_to_tkt(r->pool, ticket, &ktext) != 0)
    return krb4_auth_required(r, "decoding of ticket failed");

  /* Get our Kerberos principal. */
  *service = '\0'; *instance = '\0'; *realm = '\0';
  if (kname_parse(service, instance, realm, sec->krb4_principal) != KSUCCESS)
    return SERVER_ERROR;

  /* Verify authenticator. */
  if ((rc = krb_rd_req(&ktext, service, instance, 0L, &adat, sec->krb4_srvtab))
      != KSUCCESS) {
    r->connection->user = ""; /* We want the client to see the error. */
    log_reason (krb_err_txt[rc], r->uri, r);
    return krb4_auth_required(r, krb_err_txt[rc]);
  }
  /* Verify HMAC. */
  if (sec->enforce_hmac) {
    if (krb4_check_hmac_md5(r, checksum, &adat.session) != 0)
      return krb4_auth_required(r, "hmac-md5 incorrect!");
  }
  /* Everything checked out. */
#ifdef PLUGIN
  if (expires) krb4_make_cookie(r, ticket, checksum, atol(expires), version);
#endif /* PLUGIN */
  r->connection->user = pstrdup(r->pool, adat.pname);
  r->connection->auth_type = "KerberosV4";
  return OK;
}

#ifdef PLUGIN
/* Get POST data to return to client. */
char *get_post_data(request_rec *r)
{
  char *pdata = NULL;
  int len = atoi(table_get(r->headers_in, "Content-length"));

  if (setup_client_block(r, REQUEST_CHUNKED_ERROR) == OK &&
      should_client_block(r) >=0 && /* bogus - an bug in Apache 1.2.0? */
      len < MAX_POST_DATA && (pdata = pcalloc(r->pool, len+1)) != NULL &&
      get_client_block(r, pdata, len) != -1)
    D(fprintf(stderr, "post data is %s, len %d\n", pdata, len));

  return pdata ? pdata : "";
}

/* Error response handler. */
int krb4_error_handler(request_rec *r)
{
  FILE *f;
  struct stat st;
  char challenge[BUFSIZ], *post = NULL;
  char *user = r->connection->user;
  krb4_config_rec *sec = (krb4_config_rec *)
    get_module_config(r->per_dir_config, &krb4_auth_module);
  
  /* Return error code and auth challenge. */
  r->status = 400; r->content_type = "text/html";

  /* Construct challenge. Trap for bogus Windows Netscape bug. */
  if (r->method_number == M_POST)
    post = pstrcat(r->pool, " POST=\"", get_post_data(r), "\"", NULL);

  ap_snprintf(challenge, sizeof(challenge),
	      table_get(r->subprocess_env, "windows_netscape_bogosity") ?
	      EMBED_WIN_FMT : EMBED_FMT, PLUGIN_MIME, PLUGIN_PAGE,
	      sec->krb4_principal, construct_url(r->pool, r->args ?
	      pstrcat(r->pool, r->uri, "?", r->args, NULL) : r->uri,
#ifdef APACHE_SSL
	      r),
#else
	      r->server),
#endif /* APACHE_SSL */
	      post ? post : "", NULL);

  soft_timeout("send", r); send_http_header(r);

  if (!user) rputs("<NOEMBED>\n", r);
  if (sec->krb4_errdoc && stat(sec->krb4_errdoc, &st) == 0 &&
      set_content_length (r, st.st_size) == 0 &&
      (f = fopen(sec->krb4_errdoc, "r")) != NULL)
    send_fd (f, r);
  else
    rputs(PLUGIN_DENIAL_MSG, r);
  rputs(table_get(r->notes, "krb4err"), r);
  if (!user) rputs(pstrcat(r->pool,"</NOEMBED>\n",challenge, NULL), r);
  rputs(PLUGIN_NOSCRIPT_MSG, r);
  
  return OK;
}
#endif /* PLUGIN */

#ifdef AFS
/* AFS ACL authorization routines that ought to be in another file. */

#define MAX_ACLENTRIES 20
#define MAX_ACLNAME 100
#define MAX_ACLSIZE 2048

typedef struct Acl_entry_type {
  char name[MAX_ACLNAME];
  long rights;
} Acl_entry;
 
typedef struct Acl_type {
  unsigned int num_plus;
  unsigned int num_minus;
  Acl_entry plus[MAX_ACLENTRIES];
  Acl_entry minus[MAX_ACLENTRIES];
} Acl;

int afs_get_acl(Acl *acl, const char *path)
{
  struct ViceIoctl acljunk;
  char *c, tmp[MAX_ACLSIZE];
  int i;
 
  if (strlen(path) > MAXPATHLEN) return -1;

  acljunk.out_size = sizeof(tmp);
  acljunk.in_size = 0;
  acljunk.out = tmp;
  
  if (pioctl(path, VIOCGETAL, &acljunk, 1)) return -1;
 
  c = tmp;
  sscanf(c, "%d", &acl->num_plus); c = strchr(c, '\n'); c++;
  sscanf(c, "%d", &acl->num_minus); c = strchr(c, '\n'); c++;
 
  for (i = 0; i < acl->num_plus; i++) {
    sscanf(c, "%100s %d", acl->plus[i].name, &acl->plus[i].rights);
    c = strchr(c, '\n'); c++;
  }
  for (i = 0; i < acl->num_minus ; i++) {
    sscanf(c, "%100s %d", acl->minus[i].name, &acl->minus[i].rights);
    c = strchr(c, '\n'); c++;
  }
  if ((acl->num_plus + acl->num_minus) > MAX_ACLENTRIES) return -1;
 
  return 0;
}

int user_in_acl(char *user, Acl_entry *acl_list, int num_entries)
{
  int i, flag;
  
  if (pr_Initialize(0, AFSCONF_CLIENTNAME, 0)) return 0;
  for (i = 0; i < num_entries; i++) {
#ifdef USE_PTSCD
    if ((pts_check(user, acl_list[i].name) ||
#else
    if (((!pr_IsAMemberOf(user, acl_list[i].name, &flag) && flag) ||
#endif /* USE_PTSCD */
         !strncmp(acl_list[i].name, "system:anyuser", 14) ||
         !strncmp(acl_list[i].name, "system:authuser", 15) ||
         !strncmp(acl_list[i].name, user, strlen(user))) &&
        (acl_list[i].rights & PRSFS_READ) &&
        (acl_list[i].rights & PRSFS_LOOKUP))
      return 1;
  }
  return 0;
}
#endif /* AFS */

/* Authorization checking. */
int krb4_check_path (request_rec *r)
{
  char *w, *user = r->connection->user;
  const char *t;
  int i, method_restricted = 0;    
  array_header *reqs_arr = requires(r);
  require_line *reqs;
#ifdef AFS
  Acl acl;
#endif /* AFS */
  
  /* This function should only be used by our module. */
  if (!(t = auth_type(r)) || strncasecmp(t, "KerberosV4", 10)) return DECLINED;

  /* If previous authentication step failed, fall through to custom handler. */
  if (table_get(r->notes, "krb4err")) return OK;
  D(fprintf(stderr, "check_path: %s authorized?\n", user));

  /* If there is no "require" directive, any user is accepted. */
  if (!reqs_arr) return OK;
  
  /* Check the authenticated username against the require lines. */
  reqs = (require_line *)reqs_arr->elts;
  for (i = 0; i < reqs_arr->nelts; i++) {
    if (!(reqs[i].method_mask & (1 << r->method_number))) continue;
    method_restricted = 1;
    t = reqs[i].requirement; w = getword(r->pool, &t, ' ');
    if (strncmp(w, "valid-user", 10) == 0) return OK;
    else if (strncmp(w, "user", 4) == 0)
      while (t[0]) {
	w = getword_conf(r->pool, &t);
	if (strcmp(w, r->connection->user) == 0) return OK;
      }
#ifdef AFS
    else if (strncmp(w, "group", 5) == 0) {
#ifndef USE_PTSCD      
      /* If pts server initialization fails, deny access. */
      if (pr_Initialize(1, AFSCONF_CLIENTNAME, 0)) break;
#endif /* USE_PTSCD */
      while (t[0]) {
	w = getword_conf(r->pool, &t);
#ifdef USE_PTSCD
	if (pts_check(user, w) ||
#else
	if ((!pr_IsAMemberOf(user, w, &i) && i) ||
#endif /* USE_PTSCD */
	    strncasecmp(w, "system:anyuser", 14) == 0 ||
	    strncasecmp(user, w, strlen(w)) == 0)
	  return OK;
      }
    }
#endif /* AFS */
#ifdef AFS
    else if (strncmp(w, "afs-acl-check", 13) == 0 &&
	     afs_get_acl(&acl, r->filename) == 0) {
      if (user_in_acl(user, acl.plus, acl.num_plus) &&
	  !user_in_acl(user, acl.minus, acl.num_minus)) {
	return OK;
      }
    }
#endif /* AFS */
    else
      return DECLINED;
  }
  if (!method_restricted) return OK;

  /* Authorization checks failed, note and fall thru to handler. */
  krb4_auth_required(r, pstrcat(r->pool, "user ", user,
		     " is not allowed to access this URL", NULL));
  return OK;
}

/* Set up directory configuration. */
void *create_krb4_config_rec(pool *p, char *d)
{
  return pcalloc (p, sizeof(krb4_config_rec));
}

/* Merge directory configuration. */
void *merge_krb4_config_rec(pool *p, void *basev, void *addv)
{
  krb4_config_rec *new = (krb4_config_rec *)addv,
    *base = (krb4_config_rec *)basev;
  new->krb4_principal = base->krb4_principal;
  new->krb4_srvtab = base->krb4_srvtab;
  return new;
}
  
/* Our custom response handler. */
static handler_rec krb4_handlers[] = {
  { "krb4-error-handler", krb4_error_handler },
  { NULL }
};

/* Map the valid commands to the config record. */
command_rec krb4_auth_cmds[] = {
  { "KerberosV4Principal", set_string_slot,
      (void *)XtOffsetOf(krb4_config_rec, krb4_principal),
      ACCESS_CONF|RSRC_CONF, TAKE1,
      "The Kerberos V4 principal to use." },
  { "KerberosV4Srvtab", set_string_slot,
      (void *)XtOffsetOf(krb4_config_rec, krb4_srvtab),
      ACCESS_CONF|RSRC_CONF, TAKE1,
      "The Kerberos V4 srvtab file to use." },
  { "KerberosV4ErrorDoc", set_string_slot,
      (void *)XtOffsetOf(krb4_config_rec, krb4_errdoc),
      ACCESS_CONF|RSRC_CONF|OR_AUTHCFG, TAKE1,
      "Full path to custom ErrorDocument to send on auth failure." },
  { "KerberosV4EnforceHMAC", set_flag_slot,
      (void *)XtOffsetOf(krb4_config_rec, enforce_hmac),
      ACCESS_CONF|RSRC_CONF|OR_AUTHCFG, FLAG,
      "Whether HMAC checking on every request should be On or Off" },
  { "AllowClientCaching", set_flag_slot,
      (void *)XtOffsetOf(krb4_config_rec, allow_caching),
      ACCESS_CONF|RSRC_CONF|OR_AUTHCFG, FLAG,
      "Whether to allow client caching of protected documents." },
#ifdef PLUGIN
  { "KerberosV4PluginVersion", set_string_slot,
      (void *)XtOffsetOf(krb4_config_rec, krb4_plugin_version),
      ACCESS_CONF|RSRC_CONF|OR_AUTHCFG, TAKE1,
      "The oldest client plug-in version # to accept." },
#endif /* PLUGIN */
#ifdef SSL_ENABLED
  { "KerberosV4RequiresSSL", set_flag_slot,
      (void *)XtOffsetOf(krb4_config_rec, requires_ssl),
      ACCESS_CONF|RSRC_CONF|OR_AUTHCFG, FLAG,
      "Whether restricting Kerberos v4 auth to only SSL-encrypted " \
      "connections should be On or Off" },
#endif /* SSL_ENABLED */
  { NULL }
};

/* Register the functions in the correct places. */
module krb4_auth_module = {
  STANDARD_MODULE_STUFF,
  NULL,				/* initializer */
  create_krb4_config_rec,       /* dir config creater */
  merge_krb4_config_rec,	/* dir merger */
  NULL,				/* server config */
  NULL,				/* merge server config */
  krb4_auth_cmds,		/* command table */
  krb4_handlers,		/* handlers */
  NULL,				/* filename translation */
  krb4_check_auth,		/* check_user_id */
  krb4_check_path,		/* check auth */
  NULL,				/* check access */
  NULL,				/* type_checker */
  NULL,				/* fixups */
  NULL				/* logger */
};

/* 5000. */
