/** BEGIN COPYRIGHT BLOCK
 *
 * Copyright (C) 2005 Red Hat, Inc.
 * All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * END COPYRIGHT BLOCK **/
/*
 * mod_admserv.c: Provides communication link between Console and Directory.
 *
 * Authors (in alphabetical order)
 * Rob Crittenden
 * Miodrag Kekic
 * Rich Megginson
 * Adam Prishtina
 *
 */

#include <unistd.h>
/* httpd.h defines
   all of these unconditionally - so we undefine
   them here to make the compiler warnings shut up
   hopefully we don't need the real versions
   of these, but then with no warnings the compiler
   will just silently redefine them to the wrong
   ones anyway
   Then undefine them after the include so that
   our own local defines will take effect
*/
#undef PACKAGE_BUGREPORT
#undef PACKAGE_NAME
#undef PACKAGE_STRING
#undef PACKAGE_TARNAME
#undef PACKAGE_VERSION
#include "httpd.h"
#define CORE_PRIVATE 1 /* to pick up ap_die */
#include "http_request.h"
#undef CORE_PRIVATE
#include "apr_strings.h"
#include "apr_lib.h"
#include "apr_optional.h"
#include "apr_fnmatch.h"

#define APR_WANT_STRFUNC
#include "apr_want.h"

#include "ap_config.h"
#include "ap_mpm.h"
#include "apr_sha1.h"
#include "mod_log_config.h"
#include "httpd.h"
#include "http_core.h"
#include "http_config.h"
#include "http_connection.h"
#include "http_protocol.h"
#include "http_log.h"
#undef PACKAGE_BUGREPORT
#undef PACKAGE_NAME
#undef PACKAGE_STRING
#undef PACKAGE_TARNAME
#undef PACKAGE_VERSION

/* need uid and gid of apache process after setuid */
#if !defined(OS2) && !defined(WIN32) && !defined(BEOS) && !defined(NETWARE)
#include "unixd.h"
#define CHANGE_EUID 1
#endif

#include "libadminutil/distadm.h"
#include "libadminutil/admutil.h"
#include "libadminutil/resource.h"
#include "libadminutil/psetc.h"
#include "libadmsslutil/admsslutil.h"
#include "libadmsslutil/psetcssl.h"
#include "libadmsslutil/srvutilssl.h"

#include "nss.h"
#include "ssl.h"
#include "secerr.h"

#include "mod_admserv.h"

#if defined(HPUX) || defined(HPUX10) || defined(HPUX11)
#define SETEUID(id) setresuid((uid_t) -1, id, (uid_t) -1)
#else
#define SETEUID(id) seteuid(id)
#endif

/*
 * These are keys for items we store in r->notes to pass data from one stage
 * in the request to another.  They must be unique.  If necessary, prefix
 * them with admserv_ or something like that if there are namespace conflicts.
 */
#define RQ_NOTES_USERDN "userdn"
#define RQ_NOTES_USERPW "userpw"
#define RQ_NOTES_EXECREF "execRef"
#define RQ_NOTES_EXECREFARGS "execRefArgs"
#define RQ_NOTES_LOGSUPPRESS "logSuppress"
#define RQ_NOTES_SIEPWD "siepwd"
#define RQ_NOTES_COMMAND_NAME "command-name"
#define RQ_NOTES_AUTHZ_REQUIRED "authz-required"
#define RUNTIME_COMMAND_BASE (char*)"commands/"
#define AUTH_URI "/admin-serv/authenticate"
#define MOD_ADMSERV_CONFIG_KEY "mod_admserv"

#define NETSCAPE_ROOT_BASEDN (char*)"o=NetscapeRoot"

/* Globals...hack. */
static char           *configdir    = NULL; /* set by ADMConfigDir in admserv.conf */
static long            cacheLifetime = 0; /* Defaults to 0 (entries immediately expire) */

/* This holds the ldap connection information for the configuration DS e.g.
 * the server with o=NetscapeRoot
 */
static LdapServerData  registryServer;

/* This holds the ldap connection information for the user&group server e.g.
 * the server with dc=mydomain,dc=tld
 */
static LdapServerData  userGroupServer;

/* memory pool for the memory for statics in this module */
static apr_pool_t *module_pool = NULL;

/* List of hosts to allow access from - by default, only localhost */
static char *accessHosts = NULL;
/* List of IP addresses to allow access from - by default, only localhost */
static char *accessAddresses = NULL;

/* This is our Apache module structure */
module AP_MODULE_DECLARE_DATA admserv_module;

static int sync_task_sie_data(const char *name, char *query, void *arg, request_rec *r);
static int change_sie_password(const char *name, char *query, void* arg, request_rec *r);
static int create_auth_users_cache_entry(char *user, char *userDN, const char *userPW, char *ldapURL);

static int admserv_check_user_id(request_rec *r);

/* per-process config structure */
typedef struct {
    int nInitCount;
} admserv_global_config;

/* Per-directory configuration structure */
typedef struct {
   int nescompat;
   int adminsdk;
   char *cgibindir; /* ADMCgiBinDir - the cgi bin directory for this location */
} admserv_config;

/* Per-server config structure */
typedef struct {
    char *configdir; /* directory containing our config files such as adm.conf, local.conf, etc. */
    long cacheLifeTime; /* in seconds - how long to cache auth cred, task access */
    char *versionString; /* returned to client in the Admin-Server header */
    admserv_global_config *gconfig; /* pointer to per-process config */
} admserv_serv_config;

/*
 * Locate our server configuration record for the specified server.
 */
static admserv_serv_config *our_sconfig(const server_rec *s)
{
    return (admserv_serv_config *) ap_get_module_config(s->module_config, &admserv_module);
}

#define NS_ADMIN_ACCESS_HOSTS (char*)"configuration.nsAdminAccessHosts"
#define NS_ADMIN_ACCESS_ADDRESSES (char*)"configuration.nsAdminAccessAddresses"

static HashTable*
HashTableCreate()
{
    HashTable *ht = (HashTable *)apr_palloc(module_pool, sizeof(HashTable));

    if (!ht)
        return NULL;

    ht->table = apr_hash_make(module_pool);

    return ht;
}

static int
HashTableInsert(HashTable *ht, char *name, void *value)
{
    apr_hash_set(ht->table, name, APR_HASH_KEY_STRING, value);

    return TRUE;
}

static void *
HashTableFind(HashTable *ht, const char *name)
{
    return apr_hash_get(ht->table, name, APR_HASH_KEY_STRING);
}

typedef void (HashEnumFunc)(char *, const void *, void *);

static void
HashTableEnumerate(HashTable *ht, HashEnumFunc *fn, void *data)
{
    apr_hash_index_t *hi;
    const void *key;
    void *val;

    for (hi = apr_hash_first(NULL, ht->table); hi;
         hi = apr_hash_next(hi)) {
        apr_hash_this(hi, &key, NULL, &val);
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0 /* status */, NULL,
                     "HashTableEnumerate: Key=%s Val=%s", (char *)key, (char *)val);
        (*fn)((char *)key, val, data);
    }
}


/* Caches for [I]SIE DNs, authenticated users, and available tasks */
static HashTable *servers    = NULL;
static HashTable *auth_users = NULL;
static HashTable *auth_tasks = NULL;
/* Runtime command lookup hashtable */
static HashTable *commands   = NULL;

#define NS_EXEC_REF           (char*)"nsExecRef"
#define NS_EXEC_REF_QUERY     (char*)"(nsExecRef=*)"
#define SERVLET_QUERY         (char*)"(nsExecRef=java:*)"
#define NS_LOG_SUPPRESS       (char*)"nsLogSuppress"
#define LOG_SUPPRESS_ON_VALUE (char*)"true"

static char *searchAttributes[] = { NS_EXEC_REF, NS_LOG_SUPPRESS, NULL };


/* ------------------------------ _uudecode ------------------------------- */

const unsigned char pr2six[256]={
  64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,
  64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,62,64,64,64,63,
  52,53,54,55,56,57,58,59,60,61,64,64,64,64,64,64,64,0,1,2,3,4,5,6,7,8,9,
  10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,64,64,64,64,64,64,26,27,
  28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,
  64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,
  64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,
  64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,
  64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,
  64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,
  64,64,64,64,64,64,64,64,64,64,64,64,64
};

/* returned memory is allocated from given pool */
char *_uudecode(char *bufcoded, apr_pool_t *p)
{
  register char *bufin = bufcoded;
  register unsigned char *bufout;
  register int nprbytes;
  unsigned char *bufplain;
  int nbytesdecoded;

  /* Find the length */
  while(pr2six[(int)*(bufin++)] <= 63);
  nprbytes = bufin - bufcoded - 1;
  nbytesdecoded = ((nprbytes+3)/4) * 3;

  bufout = (unsigned char *) apr_palloc(p, nbytesdecoded + 1);
  bufplain = bufout;

  bufin = bufcoded;

  while (nprbytes > 0) {
    *(bufout++) = (unsigned char)
      (pr2six[(int)(*bufin)] << 2 | pr2six[(int)bufin[1]] >> 4);
    *(bufout++) = (unsigned char)
      (pr2six[(int)bufin[1]] << 4 | pr2six[(int)bufin[2]] >> 2);
    *(bufout++) = (unsigned char)
      (pr2six[(int)bufin[2]] << 6 | pr2six[(int)bufin[3]]);
    bufin += 4;
    nprbytes -= 4;
  }

  if(nprbytes & 03) {
    if(pr2six[(int)bufin[-2]] > 63)
      nbytesdecoded -= 2;
    else
      nbytesdecoded -= 1;
  }
  bufplain[nbytesdecoded] = '\0';

  return (char *)bufplain;
}

enum {
  LDAPU_STR_FILTER_DEFAULT,
  LDAPU_STR_FILTER_USER,
  LDAPU_STR_FILTER_GROUP,
  LDAPU_STR_FILTER_MEMBER,
  LDAPU_STR_FILTER_MEMBER_RECURSE,
  LDAPU_STR_ATTR_USER,
  LDAPU_STR_ATTR_CERT,
  LDAPU_STR_MAX_INDEX
};


static char *ldapu_strings[] = {
  (char*)"objectclass=*",       /* LDAPU_STR_DEFAULT */
  (char*)"uid=%s",          /* LDAPU_STR_FILTER_USER */
  (char*)"(& (cn=%s) (| (objectclass=groupofuniquenames) (objectclass=groupofnames)))", /* LDAPU_STR_FILTER_GROUP */
  (char*)"(| (uniquemember=%s) (member=%s))",   /* LDAPU_STR_FILTER_MEMBER */
  (char*)"(& %s (| (objectclass=groupofuniquenames) (objectclass=groupofnames))", /* LDAPU_STR_FILTER_MEMBER_RECURSE */
  (char*)"uid",         /* LDAPU_STR_ATTR_USER */
  (char*)"userCertificate;binary"   /* LDAPU_STR_ATTR_CERT */
};

/* If we are not interested in the returned attributes, just ask for one
 * attribute in the call to ldap_search.  Also don't ask for the attribute
 * value -- just the attr.
 */
static const char *default_search_attrs[] = { "c" , 0 };
static int default_search_attrsonly = 1;

/*
 * ldapu_find
 *   Description:
 *      Caller should free res if it is not NULL.
 *   Arguments:
 *      ld              Pointer to LDAP (assumes connection has been
 *                      established and the client has called the
 *                      appropriate bind routine)
 *      base            basedn (where to start the search)
 *      scope           scope for the search.  One of
 *                      LDAP_SCOPE_SUBTREE, LDAP_SCOPE_ONELEVEL, and
 *                      LDAP_SCOPE_BASE
 *      filter          LDAP filter
 *      attrs           A NULL-terminated array of strings indicating which
 *                      attributes to return for each matching entry.  Passing
 *                      NULL for this parameter causes all available
 *                      attributes to be retrieved.
 *      attrsonly       A boolean value that should be zero if both attribute
 *                      types and values are to be returned, non-zero if only
 *                      types are wanted.
 *      res             A result parameter which will contain the results of
 *                      the search upon completion of the call.
 *   Return Values:
 *      LDAPU_SUCCESS   if entry is found
 *      LDAPU_FAILED    if entry is not found
 *      <rv>            if error, where <rv> can be passed to
 *                      ldap_err2string to get an error string.
 */
int
ldapu_find (LDAP *ld, const char *base, int scope,
            const char *filter, const char **attrs,
            int attrsonly, LDAPMessage **res)
{
  int retval;
#ifdef USE_THIS_CODE /* ASYNCHRONOUS */
  int msgid;
#endif
  int numEntries;

  *res = 0;

  /* If base is NULL set it to null string */
  if (!base) {
    base = "";
  }
 
  if (!filter || !*filter) {
    filter = ldapu_strings[LDAPU_STR_FILTER_DEFAULT];
  }
 
  retval = ldap_search_ext_s(ld, base, scope, filter, (char **)attrs,
                             attrsonly, NULL, NULL, NULL, -1, res);

  if (retval != LDAP_SUCCESS)
    {
      /* retval = ldap_result2error(ld, *res, 0); */
      return(retval);
    } 

  numEntries = ldap_count_entries(ld, *res);

  if (numEntries == 1) {
    /* success */
    return LDAPU_SUCCESS;
  }
  else if (numEntries == 0) {
    /* not found -- but not an error */
    return LDAPU_FAILED;
  }
  else if (numEntries > 0) {
    /* Found more than one entry! */
    return LDAPU_ERR_MULTIPLE_MATCHES;
  }
  else {
    /* should never get here */
    ldap_msgfree(*res);
    return LDAP_OPERATIONS_ERROR;
  }
}


/*
 * ldapu_find_uid_attrs
 *   Description:
 *      Maps the given uid to a user dn.  Caller should free res if it is not
 *      NULL.  Accepts the attrs & attrsonly args.
 *   Arguments:
 *      ld              Pointer to LDAP (assumes connection has been
 *                      established and the client has called the
 *                      appropriate bind routine)
 *      uid             User's name
 *      base            basedn (where to start the search)
 *      attrs           list of attributes to retrieve
 *      attrsonly       flag indicating if attr values are to be retrieved
 *      res             A result parameter which will contain the results of
 *                      the search upon completion of the call.
 *   Return Values: 
 *      LDAPU_SUCCESS   if entry is found
 *      LDAPU_FAILED    if entry is not found
 *      <rv>            if error, where <rv> can be passed to
 *                      ldap_err2string to get an error string.
 */
int
ldapu_find_uid_attrs (LDAP *ld, const char *uid, const char *base,
                      const char **attrs, int attrsonly,
                      LDAPMessage **res)
{
  int           scope = LDAP_SCOPE_SUBTREE;
  char  filter[ BUFSIZ ];
  int           retval;
  int size;

  size = sizeof(ldapu_strings[LDAPU_STR_FILTER_USER]) + strlen(uid);

  if (size < sizeof(filter)) {
    /* setup filter as (uid=<uid>) */
    sprintf(filter, ldapu_strings[LDAPU_STR_FILTER_USER], uid);
    retval = ldapu_find(ld, base, scope, filter, attrs, attrsonly, res);
  } else {
    char * scratch = (char *) malloc(size);
    if (scratch) {
      sprintf(scratch, ldapu_strings[LDAPU_STR_FILTER_USER], uid);
      retval = ldapu_find(ld, base, scope, scratch, attrs, attrsonly, res);
      free(scratch);
    } else {
      retval = LDAPU_ERR_OUT_OF_MEMORY;
    }
  }

  return retval;
}

/*
 * ldapu_find_userdn
 *   Description:
 *      Maps the given uid to a user dn.  Caller should free dn if it is not
 *      NULL. 
 *   Arguments:
 *      ld              Pointer to LDAP (assumes connection has been
 *                      established and the client has called the
 *                      appropriate bind routine)
 *      uid             User's name
 *      base            basedn (where to start the search) 
 *      dn              user dn
 *   Return Values:
 *      LDAPU_SUCCESS   if entry is found
 *      LDAPU_FAILED    if entry is not found
 *      <rv>            if error, where <rv> can be passed to
 *                      ldap_err2string to get an error string.
 */
int
ldapu_find_userdn (LDAP *ld, const char *uid, const char *base,
                   char **dn)
{
  LDAPMessage *res = 0;
  int           retval;

  retval = ldapu_find_uid_attrs(ld, uid, base, default_search_attrs,
                                default_search_attrsonly, &res);

  if (retval == LDAPU_SUCCESS) {
    LDAPMessage *entry;

    entry = ldap_first_entry(ld, res);
    *dn = ldap_get_dn(ld, entry);
  }
  else {
    *dn = 0;
  }

  if (res) ldap_msgfree(res);

  return retval;
}

static void
closeLDAPConnection(LDAP *server)
{
    ldap_unbind_ext(server, NULL, NULL);
}

static LDAP *
openLDAPConnection(LdapServerData *data)
{
    LDAP *server;

    if (!(server = util_ldap_init(data->securitydir, NULL,
                                  data->host, data->port, data->secure, 1, NULL))) {
        ap_log_error(APLOG_MARK, APLOG_CRIT, 0 /* status */, NULL,
                     "openLDAPConnection(): util_ldap_init failed for ldap%s://%s:%d",
                     data->secure ? "s" : "",
                     data->host, data->port);
        return NULL;
    }

    return (server);
}

#if defined(USE_OPENLDAP)
static int
admserv_ldap_rebind_proc(
	LDAP *ld, LDAP_CONST char *url,
	ber_tag_t request, ber_int_t msgid,
	void *arg)
{
  RebindData *data = (RebindData*)arg;

  return util_ldap_bind(ld, data->user, data->pw, LDAP_SASL_SIMPLE, NULL, NULL, NULL, NULL);
}
#else /* NOT USE_OPENLDAP */
static int
admserv_ldap_rebind_proc(LDAP *ld, char **who, char **pw, int *auth, int freeit, void *arg)
{
  RebindData *data = (RebindData*)arg;

  if (!freeit) {
      *who  = data->user;
      *pw   = data->pw;
      *auth = LDAP_AUTH_SIMPLE;
  }

  return LDAP_SUCCESS;
}
#endif

static void
setLDAPRebindProc(LDAP *server, const char *user, const char *pw)
{
  RebindData *data = (RebindData*)apr_palloc(module_pool, sizeof(RebindData));

  if (user) data->user = apr_pstrdup(module_pool, user);
  if (pw) data->pw   = apr_pstrdup(module_pool, pw);

  ldap_set_rebind_proc(server, admserv_ldap_rebind_proc, (void *)data);
}

static apr_status_t close_pipe(void *thefd)
{
    int fd = (int)((intptr_t)thefd);
    int rc;

    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0 /* status */, NULL,
                 "close_pipe(): closing pipe %d errno %d", fd, errno);
    errno = 0;

    rc = close(fd);

    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0 /* status */, NULL,
                 "close_pipe(): closed pipe rc = %d errno = %d",
                 rc, errno);

    return rc;
}

static int password_pipe(request_rec *r)
    {
    apr_table_t *e = r->subprocess_env;
    int fd, wfd;
    apr_size_t  nbytes;
    apr_file_t *readp = NULL;
    apr_file_t *writep = NULL;
    const char *user, *pass, *auth, *userDN, *siepwd;
    char *ans;
    apr_status_t rv;

    user = r->user;
    pass = apr_table_get(r->notes, RQ_NOTES_USERPW);
    userDN = apr_table_get(r->notes, RQ_NOTES_USERDN);
    auth = apr_table_get(r->headers_in, "authorization");
    siepwd = ADM_NO_VALUE_STRING;

    if (!user) user = ADM_NO_VALUE_STRING;
    if (!pass) pass = ADM_NO_VALUE_STRING;
    if (!auth) auth = ADM_NO_VALUE_STRING;
    if (!userDN) userDN = ADM_NO_VALUE_STRING;

    ans = (char*)apr_palloc(r->pool, strlen(user) + strlen(ADM_USER_STRING) +
                        strlen(pass) + strlen(ADM_PASS_STRING) +
                        strlen(auth) + strlen(ADM_AUTH_STRING) +
                        strlen(userDN) + strlen(ADM_USERDN_STRING) +
                        strlen(ADM_SIEPWD_STRING) +
                        strlen(siepwd) + 16);

    sprintf(ans, "%s%s\n%s%s\n%s%s\n%s%s\n%s%s\n", ADM_USER_STRING, user,
            ADM_PASS_STRING, pass,
            ADM_AUTH_STRING, auth,
            ADM_USERDN_STRING, userDN,
            ADM_SIEPWD_STRING,
            siepwd);

    rv = apr_file_pipe_create(&readp, &writep, r->pool);

    if (rv != APR_SUCCESS) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                      "mod_admserv: Unable to create pipe");
        return HTTP_INTERNAL_SERVER_ERROR;
    }

    /* Get the low-level file descriptor */
    apr_os_file_get(&fd, readp);
    apr_os_file_get(&wfd, writep);

    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL,
                 "password_pipe(): created pipe read %d write %d", fd, wfd);

    /* Register a cleanup callback so this gets closed at the end of the
       request. */
    apr_pool_cleanup_register(r->pool, (void *)((intptr_t)fd), close_pipe,
                                      apr_pool_cleanup_null);

    /* Send this to the client so they know what fd to read from */
    apr_table_setn(e, "PASSWORD_PIPE",  apr_itoa(r->pool, fd));

    nbytes = strlen(ans);
    apr_file_write(writep, ans, &nbytes);

    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL,
                 "password_pipe(): wrote %d bytes", (int)nbytes);

    /* Close the writing side, we don't need this any more */
    apr_file_close(writep);

    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL,
                 "password_pipe(): closed write descriptor");

    return 0;
}

static int
admserv_ldap_auth_userdn_password(LDAP *server, 
                  const char *userdn, 
                  const char *pw, 
                  int *pw_expiring)
{
    LDAPControl **ctrls = NULL;
    int ldapError = LDAP_SUCCESS;

    *pw_expiring = -1;

    setLDAPRebindProc(server, userdn, pw);

    /* DT 9/18/98 - Fix for bind problem. Previously, if pw == null,
     * then the bind does not occur (connection is still anonymous),
     * but ldap_simple_bind() returns LDAP_SUCCESS. We want the
     * bind to fail if pw == null, or the pw does not match.
     */
    if (pw == NULL)
        pw = (char*)"";

    /* bind user asynchronously */ 
    ldapError = util_ldap_bind(server, userdn, pw,
                               LDAP_SASL_SIMPLE, NULL, &ctrls, NULL, NULL);

    if (ldapError) {
        ap_log_error(APLOG_MARK, APLOG_ERR, 0 /* status */, NULL,
                     "Could not bind as [%s]: ldap error %d: %s",
                     userdn, ldapError, ldap_err2string(ldapError));
        return ldapError;
    }

    if (ctrls) {
        int i; 
        for (i = 0; ctrls[i] != NULL; ++i) {
            if(!(strcmp(ctrls[i]->ldctl_oid, LDAP_CONTROL_PWEXPIRED))) {
                *pw_expiring = 0;
                ap_log_error(APLOG_MARK, APLOG_WARNING, 0 /* status */, NULL,
                             "The password for user DN [%s] has expired - please reset it",
                             userdn);
            } 
            else if(!(strcmp(ctrls[i]->ldctl_oid, LDAP_CONTROL_PWEXPIRING))) {
                /* "The password is expiring in n seconds" */ 
                if ((ctrls[i]->ldctl_value.bv_val != NULL ) && 
                    (ctrls[i]->ldctl_value.bv_len > 0)) {
                    *pw_expiring = atoi(ctrls[i]->ldctl_value.bv_val); 
                    ap_log_error(APLOG_MARK, APLOG_WARNING, 0 /* status */, NULL,
                                 "The password for user DN [%s] will expire in %d seconds",
                                 userdn, *pw_expiring);
                } 
            } 
        }
        ldap_controls_free(ctrls); 
    }

    return ldapError;
}

static int
admserv_ldap_auth_server(LDAP *server, LdapServerData *data)
{
    int ignored;
    if (!(data->bindDN) && !(data->bindPW))
        return LDAPU_SUCCESS;

    return admserv_ldap_auth_userdn_password(server, data->bindDN, data->bindPW, &ignored);
}

/* The returned string is allocated from a pool in APR.  Attempting to free this string
 * will result in a crash.  APR will deal with the cleanup itself when it cleans up the
 * entire pool. */
static char *
formLdapURL(LdapServerData *data, apr_pool_t *p)
{
    return apr_psprintf(p, "%s%s:%d/%s", (data->secure?LDAPS_BASE_PREFIX:LDAP_BASE_PREFIX),
                        data->host, data->port, data->baseDN);
}

static int
extractLdapError(const server_rec *s, const char *url)
{
    ap_log_error(APLOG_MARK, APLOG_ERR, 0 /* status */, s,
                 "extractLdapServerData(): the LDAP url [%s] is invalid",
                 url ? url : "(null)");
    return FALSE;
}

static int
extractLdapServerData(LdapServerData *data, char *ldapURL, const server_rec *s)
{
    LDAPURLDesc    *ldapInfo = NULL;
    int secure;

    if (!ldapURL)
        return extractLdapError(s, NULL);

    if (util_ldap_url_parse(ldapURL, &ldapInfo, 0, &secure)) {
        return extractLdapError(s, NULL);
    }

    data->secure = secure;
    data->port = ldapInfo->lud_port;
    if (!data->port) {
        if (data->secure) {
            data->port = LDAPS_PORT;
        } else {
            data->port = LDAP_PORT;
        }
    }
    data->host = apr_pstrdup(module_pool, ldapInfo->lud_host);
    data->baseDN = apr_pstrdup(module_pool, ldapInfo->lud_dn);

    /* Currently unused */
    data->bindDN = NULL;
    data->bindPW = NULL;

    ldap_free_urldesc(ldapInfo);

    return TRUE;
}

static int
sslinit(AdmldapInfo info, const char *configdir)
{
    if (!NSS_IsInitialized()) {
	ap_log_error(APLOG_MARK, APLOG_DEBUG, 0 /* status */, NULL,
	             "sslinit: doing NSS initialization");
        /* mod_nss is used when we are a TLS/SSL server - mod_nss starts up before we do
           and will set up all of the TLS/SSL stuff */
        /* if we are acting as simply a TLS/SSL client to the directory server, 
           we still have to perform our own TLS/SSL client init */
        if (ADMSSL_Init(info, (char *)configdir, 0)) {
            ap_log_error(APLOG_MARK, APLOG_CRIT, 0 /* status */, NULL,
                         "sslinit: NSS is required to use LDAPS, but security initialization failed [%d:%s].  Cannot start server",
                         PR_GetError(), SSL_Strerror(PR_GetError()));
            exit(1);
        }
    } else {
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0 /* status */, NULL,
                     "sslinit: mod_nss has been started and initialized");
    }

    return 1;
}

static int
buildUGInfo(char** errorInfo, const request_rec *r) {
    AdmldapInfo info = NULL;
    server_rec *s = r->server;
    int  error = 0;
    char *userGroupLdapURL = NULL;
    char *userGroupBindDN = NULL;
    char *userGroupBindPW = NULL;
    char *dirInfoRef = NULL;
    int retval = FALSE;
    char *siedn = NULL;

    *errorInfo = (char*)"";

    /* Check whether data is available already */
    if (userGroupServer.host) return TRUE;

    if (!configdir) {
        *errorInfo = (char*)"NULL config dir";
		goto done;
    }

    if (!(info = admldapBuildInfoOnly(configdir, &error))) {
        ap_log_error(APLOG_MARK, APLOG_CRIT, 0 /* status */, s,
                     "buildUGInfo(): unable to create AdmldapInfo (error code = %d)",
                     error);
        goto done;
    }

    /* Temporarily override the siedn.  This needs to be
     * done to get a valid LDAP handle.
     */
    siedn = admldapGetSIEDN(info);

    admldapSetSIEDN(info, apr_table_get(r->notes, RQ_NOTES_USERDN));
    admSetCachedSIEPWD(apr_table_get(r->notes, RQ_NOTES_USERPW));


    if (admldapGetSecurity(info)) {
        sslinit(info, configdir);
    }

    if (!admldapBuildInfoSSL(info, &error)) {
        char *host = admldapGetHost(info);
        ap_log_error(APLOG_MARK, APLOG_CRIT, 0 /* status */, s,
                     "buildUGInfo(): unable to initialize TLS connection to LDAP host %s port %d: %d",
                     host, admldapGetPort(info), error);
        PL_strfree(host);
        goto done;
    }

    /* We need to reset the siedn before we call
     * admldapGetLocalUserDirectory below.
     */
    admldapSetSIEDN(info, siedn);

    userGroupServer.host   = NULL;
    userGroupServer.port   = 0;
    userGroupServer.secure = 0;
    userGroupServer.baseDN = NULL;
    userGroupServer.admservSieDN = NULL;
    userGroupServer.securitydir = admldapGetSecurityDir(info);

    if (NULL == admldapGetLDAPHndl(info)) {
        /* LDAP is not available; gather info from the cache */
        userGroupLdapURL = admldapGetDirectoryURL(info);
        userGroupBindDN = admldapGetSIEDN(info);
        userGroupBindPW = admldapGetSIEPWD(info);
        if (NULL == userGroupBindPW) {
            ADM_GetCurrentPassword(&error, &userGroupBindPW);
        }
    } else {
        admldapGetLocalUserDirectory(info, 
                                     &userGroupLdapURL, 
                                     &userGroupBindDN, 
                                     &userGroupBindPW, 
                                     &dirInfoRef, 
                                     &error);
    
        if (error != UG_OP_OK) {
            *errorInfo = (char*)"unable to set User/Group baseDN";
            goto done;
        }
    }

    if (!extractLdapServerData(&userGroupServer, userGroupLdapURL, s)) {
        *errorInfo = (char*)"unable to extract User/Group LDAP info";
        goto done;
    }
    userGroupServer.bindDN = userGroupBindDN ? apr_pstrdup(module_pool, userGroupBindDN) : NULL;
    userGroupServer.bindPW = userGroupBindPW ? apr_pstrdup(module_pool, userGroupBindPW) : NULL;
    retval = TRUE; /* made it here, so success */

done:
    PL_strfree(siedn);
    PL_strfree(userGroupLdapURL);
    PL_strfree(userGroupBindDN);
    if (userGroupBindPW) {
        memset(userGroupBindPW, 0, strlen(userGroupBindPW));
        PL_strfree(userGroupBindPW);
    }
    PL_strfree(dirInfoRef);
    destroyAdmldap(info);

    return retval;
}

static int
task_register_server(char *serverid, char *sieDN)
{
    HashTableInsert(servers, apr_pstrdup(module_pool, serverid),
                    apr_pstrdup(module_pool, sieDN));
    return TRUE;
}

static int
admserv_runtime_command_init()
{
    commands = HashTableCreate();
  
    return (TRUE);
}

static int
admserv_register_runtime_command(char *name, RuntimeCommandFn fn, void *arg)
{
    RuntimeCommandRecord *rcr;
  
    if (HashTableFind(commands, name)) {
        ap_log_error(APLOG_MARK, APLOG_CRIT, 0 /* status */, NULL,
                     "admserv_register_runtime_command(): attempt to register duplicate command (%s)",
                     name);
        return FALSE;
    }

    rcr = (RuntimeCommandRecord*)apr_palloc(module_pool, sizeof(RuntimeCommandRecord));
    rcr->fn  = fn;
    rcr->arg = arg;
    HashTableInsert(commands, apr_pstrdup(module_pool, name), rcr);

    return TRUE;
}

static int
admserv_runtime_command_exec(const char *name, char *query, request_rec *r)
{
    RuntimeCommandRecord *rcr = (RuntimeCommandRecord *)HashTableFind(commands, name);

    return (rcr && rcr->fn(name, query, rcr->arg, r));
}

/*
 * Ugh - recursion
 */
static int
reverse_uri(char **storage, char *limit, char *taskuri)
{
    char *p = strchr(taskuri, '/');

    if (p) {
        *(p++) = '\0';
        if (!reverse_uri(storage, limit, p))
            return FALSE;
    }

    /* magic number 4 = "cn=" and "," */
    if ((*storage + strlen(taskuri)+4) > limit) {
        ap_log_error(APLOG_MARK, APLOG_CRIT, 0 /* status */, NULL,
                     "reverse_uri():taskDN exceeds LINE_LENGTH, taskDN truncated for uri [%s]",
                     taskuri);
        return FALSE;
    }

    sprintf(*storage, "cn=%s, ", taskuri);
    *storage += strlen(*storage);
    return TRUE;
}

static int
build_full_DN(char **storage, char *limit, char *taskuri, char *sieDN)
{
    if (!reverse_uri(storage, limit, taskuri))
        return FALSE;

    if ((*storage + strlen(sieDN)) > limit) {
        ap_log_error(APLOG_MARK, APLOG_CRIT, 0 /* status */, NULL,
                     "build_full_DN():taskDN exceeds LINE_LENGTH, taskDN truncated for uri [%s]",
                     taskuri);
        return FALSE;
    }

    sprintf(*storage, "%s", sieDN);
    return TRUE;
}

static char*
adm_normalize_dn(char* origDN, char* newDN)
{
    char *orig = origDN, *dest = newDN;

    if (!newDN) return NULL;

    while (*orig != '\0') {
        if (*orig == ',') {
            *dest++ = *orig++;
            while (*orig == ' ') orig++;
        }
        else *dest++ = *orig++;
    }
    *dest = '\0';
    return newDN;
}

static void
convert_to_lower_case(char *s)
{
    register char *p = s;

    while (*p) {
        if ((((unsigned char)*p)<0x80)&&(isupper(*p)))
            *p = _tolower(*p);
        p++;
    }
}

static int
admserv_error(request_rec *r, int code, char *reason)
{
    apr_table_setn(r->notes, "error-notes", reason);
    ap_die(code, r);
    return DONE;
}

static int
admserv_error_std(request_rec *r, char *reason)
{
    return admserv_error(r, HTTP_INTERNAL_SERVER_ERROR, reason);
}

static int
check_auth_tasks_cache(char *dn, const char *userdn, request_rec *r, long now, int send_response)
{
    TaskCacheEntry *cache_entry;
    char            normEntryDN[1024];
    long            createTime;
    char *msg;

    adm_normalize_dn(dn, normEntryDN);

    /* Server is down. Attempt to resolve task entry from auth_tasks cache. */
    cache_entry = (TaskCacheEntry*)HashTableFind(auth_tasks, normEntryDN);
    if (!cache_entry) {
        msg = apr_psprintf(r->pool, "check_auth_tasks_cache: task entry [%s] not cached",
                           normEntryDN);
        goto bad;
    }
    if (!(createTime = (long)HashTableFind(cache_entry->auth_userDNs, userdn))) {
        msg = apr_psprintf(r->pool,
                           "check_auth_tasks_cache: found task [%s] but user [%s] is not authorized",
                           dn, userdn);
        goto bad;
    }

    if ((now - createTime) > cacheLifetime) {
        msg = apr_psprintf(r->pool, "check_auth_tasks_cache: task [%s] user [%s] entry has expired %ld",
                           dn, userdn, (now - createTime));
        goto bad;
    }

    apr_table_set(r->notes, RQ_NOTES_EXECREF, cache_entry->execRef); /* apr_table_set makes copy of args */
    if (cache_entry->execRefArgs)
        apr_table_set(r->notes, RQ_NOTES_EXECREFARGS, cache_entry->execRefArgs); /* apr_table_set makes copy of args */
    if (cache_entry->logSuppress)
        apr_table_setn(r->notes, RQ_NOTES_LOGSUPPRESS, "true"); /* apr_table_setn does not copy */

    return OK;
bad:
    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "%s", msg);
    if (send_response) {
        return admserv_error_std(r, msg);
    }

    return DONE;
}


static int
sync_task_sie_data(const char *name, char *query, void *arg, request_rec *r)
{
    AttrNameList serverlist = NULL;
    AttributeList installlist = NULL;
    AdmldapInfo ldapInfo = NULL;
    int errorCode;
    PsetHndl tmp;
    int servercnt, i;
    UserCacheEntry *cache_entry = NULL;
    char *siedn = NULL;
    const char *userdn = apr_table_get(r->notes, RQ_NOTES_USERDN);
    const char *passwd = apr_table_get(r->notes, RQ_NOTES_USERPW);

    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL,
                 "sync_task_sie_data: getting ldap info for [%s]",
                 configdir);
    ldapInfo = admldapBuildInfo(configdir, &errorCode);

    if (!ldapInfo) {
        ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
                     "sync_task_sie_data: Could not build ldap info for config in [%s]: %d",
                     configdir, errorCode);
        return FALSE;
    }

    siedn = admldapGetSIEDN(ldapInfo);
    task_register_server(ADMIN_SERVER_ID, siedn);

    /* HACK HACK HACK */
    /* getServerDNListSSL uses the siedn as the binddn - so we temporarily
       replace the siedn with the userdn - fortunately it doesn't use the
       siedn as the SIE DN */
    admldapSetSIEDN(ldapInfo, userdn);
    if (NULL == passwd) { /* use the passwd in cache if possible */
        cache_entry = (UserCacheEntry*)HashTableFind(auth_users, userdn);
        if (cache_entry) {
            passwd = cache_entry->userPW;
        }
    }
    admSetCachedSIEPWD(passwd);

    serverlist = getServerDNListSSL(ldapInfo);
    /* HACK HACK HACK - reset after getServerDNListSSL */
    admldapSetSIEDN(ldapInfo, siedn);

    servercnt=0;
    if (serverlist) {
        while (serverlist[servercnt]) servercnt++;
    }
    if (servercnt) {
        for (i=0; i < servercnt; i++) {
            /* Create Pset for each individual server */
            char *host = admldapGetHost(ldapInfo);
            tmp = psetRealCreateSSL(ldapInfo, host,
                                    admldapGetPort(ldapInfo),
                                    admldapGetSecurity(ldapInfo),
                                    serverlist[i],
                                    (char *)userdn,
                                    (char *)passwd,
                                    NULL,
                                    &errorCode);
            PL_strfree(host);
            if (tmp) {
#          define SERVER_ID_ATTRIBUTE (char*)"nsServerID"

                int errorcode;
                char*  serverid = psetGetAttrSingleValue(tmp, SERVER_ID_ATTRIBUTE, &errorcode);

                psetDelete(tmp);
                tmp = NULL;

                if (!serverid) {
                    ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL,
                                 "sync_task_sie_data: Unable to find serverid for dn=\"%s\" (error code = %d)",
                                 serverlist[i], errorcode);
                    continue;
                }

                task_register_server(serverid, serverlist[i]);
                ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL,
                             "sync_task_sie_data: registered server [%s] dn [%s]",
                             serverid, serverlist[i]);
                PL_strfree(serverid);
            }
            else {
                ap_log_error(APLOG_MARK, APLOG_CRIT, 0, NULL,
                             "AdmInit: Failed to create psetHandle for %s (error code = %d)",
                             serverlist[i], errorCode);
            }
        }
        deleteAttrNameList(serverlist);
    } else {
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL,
                     "sync_task_sie_data: no servers found");
    }

    /* DT 02/02/98
     * Register installed product tasks
     */
    /* HACK HACK HACK */
    /* getInstalledServerDNListSSL uses the siedn as the binddn - so we temporarily
       replace the siedn with the userdn - fortunately it doesn't use the
       siedn as the SIE DN */
    admldapSetSIEDN(ldapInfo, userdn);
    if ((installlist = getInstalledServerDNListSSL(ldapInfo))) {
        int ii = 0;
        while (installlist[ii]) {
            char *productID = installlist[ii]->attrName;
            char *productDN = installlist[ii]->attrVal[0];
            task_register_server(productID, productDN);
            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL,
                         "sync_task_sie_data: registered product [%s] dn [%s]",
                         productID, productDN);
            ii++;
        }
        deleteAttributeList(installlist);
    }
    /* HACK HACK HACK - reset after getInstalledServerDNListSSL */
    admldapSetSIEDN(ldapInfo, siedn);
    PL_strfree(siedn);

    destroyAdmldap(ldapInfo);

    return TRUE;
}

static int update_admpwd(char *admroot, char *newuid, char *newpw);

/* 
 * Miodrag (06-15-98)
 * The following metthod is called from the runtime command 
 * "change_sie_password" after the sie password is changed
 * 
 * Return value: if successful, 1; otherwise, 0 is returned.
 */
static int
task_update_registry_server_bindpw(char *uid, char *password,
                                   const char* bindpw, request_rec *r)
{
   LDAP        *ld = NULL;
   AdmldapInfo ldapInfo;
   int         ldapError;
   char        *ldapURL = NULL;
   const char  *userDN = NULL;
   char        *adminDN = NULL;
   LDAPMod     mod, *mods[2];
   char        *vals[2];
   int         rval = 0;
   int         error;

   /* We need ldapInfo to fetch the adminDN */
   ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL,
              "task_update_registry_server_bindpw: getting ldap info for [%s]",
              configdir);
   ldapInfo = admldapBuildInfo(configdir, &error);

   if (!ldapInfo) {
      ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
                  "task_update_registry_server_bindpw: Could not build ldap info for config in [%s]: %d",
                  configdir, error);
      goto bailout;
   }

   /* get the admin user DN */
   adminDN = admldapGetUserDN(ldapInfo, NULL);

   if (!adminDN) {
      ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
         "failed to retreive admin user DN");
      goto bailout;
   }

   /* update password for adminDN */
   if (!(ld = openLDAPConnection(&registryServer))) {
      ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
         "task_update_registry_server_bindpw(): cannot connect to the Configuration Directory Server");
      goto bailout;
   }

   userDN = apr_table_get(r->notes, RQ_NOTES_USERDN);

   /* authenticate to LDAP server */
   if (LDAP_SUCCESS != (ldapError = util_ldap_bind(ld, userDN, bindpw, LDAP_SASL_SIMPLE, NULL, NULL, NULL, NULL))) {
      switch (ldapError) {
      case LDAP_INAPPROPRIATE_AUTH:
      case LDAP_INVALID_CREDENTIALS:
      case LDAP_INSUFFICIENT_ACCESS:
         /* authenticate failed: Should not continue */
         ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
            "task_update_registry_server_bindpw(): failed to authenticate as %s: %s",
            userDN, ldap_err2string(ldapError));
         goto bailout;
      case LDAP_NO_SUCH_OBJECT:
      case LDAP_ALIAS_PROBLEM:
      case LDAP_INVALID_DN_SYNTAX:
         ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
            "task_update_registry_server_bindpw(): bad userdn %s: %s",
            userDN, ldap_err2string(ldapError));
         goto bailout;
      default:
         ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
            "task_update_registry_server_bindpw(): ldap error %s",
            ldap_err2string(ldapError));
         goto bailout;
      }
   }

   mod.mod_op = LDAP_MOD_REPLACE;
   mod.mod_type = "userPassword";
   vals[0] = password;
   vals[1] = NULL;
   mod.mod_values = vals;
   mods[0] = &mod;
   mods[1] = NULL;
   if (LDAP_SUCCESS != (ldapError = ldap_modify_ext_s(ld, adminDN, mods, NULL, NULL))) {
      ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
         "task_update_registry_server_bindpw(): ldap_modify for %s failed: %s",
         adminDN, ldap_err2string(ldapError));
      goto bailout;
   }

   /* we don't want to free this since APR allocates it from a pool.  APR will take care of it */
   ldapURL = formLdapURL(&registryServer, r->pool);

   /* update the auth_users cache */
   create_auth_users_cache_entry(uid, adminDN, password, ldapURL);

   /* We only want to reset the pwd in the request if RQ_NOTES_USERDN is the admin user */
    if(strcasecmp(adminDN, userDN) == 0) {
        apr_table_set(r->notes, RQ_NOTES_USERPW, password);
    }

   rval = 1;

bailout:
   if (ld) {
      closeLDAPConnection(ld);
   }
   if (ldapInfo) {
      destroyAdmldap(ldapInfo);
   }
   return rval;
}

/*
 * Modify SIE password 
 * query points to new password
 */
static int
change_sie_password(const char *name, char *query, void* arg, request_rec *r)
{
    FILE       *f;
    char       *uid=NULL , *col=NULL, *origpw_hash=NULL;
    char       *newpw = NULL;
    char       filename[BIG_LINE];
    char       inbuf[BIG_LINE];
    char       outbuf[64];  /* needs at least 36 bytes */
    char       *bindpw = (char *)apr_table_get(r->notes, RQ_NOTES_USERPW);
    int        admpwd_done = 0;

    apr_snprintf(filename, sizeof(filename), "%s/admpw", configdir);

    if (query==NULL) {
        ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r,
                      "Wrong usage, password param missing");
        return 0;
    }

    if ((f=fopen(filename, "r"))==NULL) {
        ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r,
                      "Can not open admpw file [%s]", filename);
        return 0;
    }
    if (fgets(inbuf, sizeof(inbuf), f) == NULL) {
        fclose(f);
        ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r,
                      "Can not read admpw file [%s]", filename);
        return 0;
    }
    fclose(f);

    col = strchr(inbuf, ':');
    if (col == NULL) {
        ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r,
                      "admpw file [%s] is corrupted", filename);
        return 0;
    }
    
    uid = inbuf; *col=0; origpw_hash=col+1;
    
    /* newpw is escaped; it needs to be unescaped. */
    newpw = strdup(query);
    if (NULL == newpw) {
        ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
           "failed to allocate memory for password");
        goto recover;
    }
    ap_unescape_url(newpw);
    apr_sha1_base64(newpw, strlen(newpw), outbuf);
    if (!update_admpwd(configdir, uid, outbuf)) {
        ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
           "failed to update admpw");
        goto recover;
    }
    admpwd_done = 1;

    if (!task_update_registry_server_bindpw(uid, newpw, bindpw, r)) {
        ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
            "failed to update server bindpw");
        goto recover;
    }

    return 1;

recover:
    if (newpw) {
        free(newpw);
    }
    if (admpwd_done) {
        update_admpwd(configdir, uid, origpw_hash);
    }
    return 0;
}

/*
 * Modify admpw.
 */
static int
update_admpwd(char *admroot, char *newuid, char *newpw)
{
    FILE *f;
    int cnt;
    char filename[BIG_LINE];
    char outbuf[BIG_LINE];

    apr_snprintf(filename, sizeof(filename), "%s/admpw", admroot);

    f = fopen(filename, "w");
    if (f==NULL) {
        ap_log_error(APLOG_MARK, APLOG_CRIT, 0, NULL,
                     "Can not open admpw file [%s] for writing", filename);
        return 0;
    }

    apr_snprintf(outbuf, sizeof(outbuf), "%s:%s", newuid, newpw);

    cnt = fprintf(f,"%s", outbuf);

    if (cnt != (int)strlen(outbuf)) {
        ap_log_error(APLOG_MARK, APLOG_CRIT, 0, NULL,
                     "Failed to write to admpw file [%s]", filename);
        fclose(f);
        return 0;
    }
    
    fclose(f);
    return 1;
}

static void
populate_tasks_from_server(char *serverid, const void *sieDN,  void *userdata)
{
    PopulateTasksData *data   = (PopulateTasksData *)userdata;
    LDAP              *server = data->server;
    LDAPMessage       *result, *e;
    int                ldapError;
    char              normDN[1024];
    int tries = 0;

    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL,
                 "populate_tasks_from_server(): getting tasks for server [%s] siedn [%s]",
                 serverid, (char *)sieDN);

    if(data->server == NULL) {
        if(!(server = openLDAPConnection(&registryServer))) {
            ap_log_error(APLOG_MARK, APLOG_CRIT, 0, NULL,
                         "populate_tasks_from_server(): Unable to open LDAPConnection to [%s:%d]",
                         registryServer.host, registryServer.port);
            return;
        }
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL,
                     "populate_tasks_from_server(): Opened new LDAPConnection to [%s:%d]",
                     registryServer.host, registryServer.port);
        data->server = server;
    }

    tries = 0;
    do {
        ldapError = ldap_search_ext_s(server, (char *)sieDN, LDAP_SCOPE_SUBTREE, NS_EXEC_REF_QUERY,
                                      searchAttributes, 0, NULL, NULL, NULL, -1, &result);
        if(ldapError != LDAP_SERVER_DOWN && ldapError != LDAP_CONNECT_ERROR)
            break;
     
        closeLDAPConnection(server);
        if(!(server = openLDAPConnection(&registryServer))) {
            ap_log_error(APLOG_MARK, APLOG_CRIT, 0, NULL,
                         "populate_tasks_from_server(): Unable to open LDAPConnection to [%s:%d]",
                         registryServer.host, registryServer.port);
            return;
        }
        data->server = server;
    } while (server != NULL && ++tries < 2);

    if (ldapError != LDAPU_SUCCESS) {
        ap_log_error(APLOG_MARK, APLOG_CRIT, 0, NULL,
                     "populate_tasks_from_server(): Unable to search [%s] for LDAPConnection [%s:%d]",
                     (char *)sieDN, registryServer.host, registryServer.port);
        return;
    }
   
    for (e = ldap_first_entry(server, result) ; e != NULL ;
         e = ldap_next_entry(data->server, e)) {
        char *dn = NULL;
        char *execRefArgs = NULL;
        struct berval **vals, **vals2;
        TaskCacheEntry *cache_entry;

        dn = ldap_get_dn(server, e);

        if (!dn) continue;

        vals = ldap_get_values_len(server, e, NS_EXEC_REF);

        if (!vals) {
            ldap_memfree(dn);
            continue;
        }

        vals2 = ldap_get_values_len(server, e, NS_LOG_SUPPRESS);

        if (vals && vals[0] && ((execRefArgs = PL_strnchr(vals[0]->bv_val, '?', vals[0]->bv_len)) != NULL))
            *(execRefArgs++) = '\0';

        adm_normalize_dn(dn, normDN);
        convert_to_lower_case(normDN);

        if (!(cache_entry = (TaskCacheEntry *)HashTableFind(auth_tasks, normDN))) {
            cache_entry = (TaskCacheEntry*)apr_pcalloc(module_pool, sizeof(TaskCacheEntry));
            cache_entry->auth_userDNs = HashTableCreate();
            HashTableInsert(auth_tasks, apr_pstrdup(module_pool, normDN), cache_entry);
        } else {
/* these are pool allocated now - no freeing - I don't expect this will be a problem
   since the tasks probably won't change at all
            free(cache_entry->execRef);
            if (cache_entry->execRefArgs)
                free(cache_entry->execRefArgs);
*/
        }

        if (vals && vals[0]) {
            cache_entry->execRef     = apr_pstrndup(module_pool, vals[0]->bv_val, vals[0]->bv_len);
        } else {
            cache_entry->execRef = NULL;
        }
        cache_entry->execRefArgs = execRefArgs ? apr_pstrdup(module_pool, execRefArgs) : NULL;
        cache_entry->logSuppress = (vals2 && vals[0] && !strncasecmp(vals2[0]->bv_val, LOG_SUPPRESS_ON_VALUE, vals2[0]->bv_len));
        HashTableInsert(cache_entry->auth_userDNs, apr_pstrdup(module_pool, data->userDN),
                        (char*)(data->now));

        
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL,
                     "populate_tasks_from_server(): Added task entry [%s:%s:%s] for user [%s]",
                     normDN, cache_entry->execRef,
                     cache_entry->execRefArgs ? cache_entry->execRefArgs : "",
                     data->userDN);

        ldap_value_free_len(vals);
        if (vals2)
            ldap_value_free_len(vals2);

        ldap_memfree(dn);
    }

    ldap_msgfree(result);
}


static void
populate_task_cache_entries(const char *userDN, LDAP *server)
{
    PopulateTasksData data;

    data.server = server;
    data.userDN = (char*)userDN;
    data.now    = time(0);

    HashTableEnumerate(servers, populate_tasks_from_server, &data);
}

static int
admserv_check_authz(request_rec *r)
{
    int             ldapError;
    char            entryDN[LINE_LENGTH];
    char           *p;
    char           *siedn; /* this is looked up from the serverid, which is
                              extracted from the uri */
    LDAP           *server;
    const char     *userdn;
    const char     *pw;
    char           *serverid;
    char           *storage = entryDN;
    char           *uri;
    char           *saveduri;
    long            now;
    int tries = 0;
    admserv_config *cf = ap_get_module_config(r->per_dir_config,
                                              &admserv_module);

    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
                  "admserv_check_authz: request for uri [%s]", r->uri);

    /* for some reason, we get sub requests for our tasks which we can ignore */
    if (r->main) {
        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
                      "admserv_check_authz: skipping sub request [%s] - filename [%s] user [%s]",
                      r->uri, r->main->filename ? r->main->filename : "(null)",
                      r->main->user ? r->main->user : "(null)");
        return DECLINED;
    }

    uri = apr_pstrdup(r->pool, r->uri); /* might need unparsed_uri here? */

    if (!(p = strchr(uri+1, '/'))) {
        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
                      "admserv_check_authz(): Skipping invalid URI [%s]", uri);
        return DECLINED;
    }

    serverid = uri+1;
    uri      = p+1;
    /* need a copy because build_full_DN modifies the uri argument */
    saveduri = apr_pstrdup(r->pool, uri);
    *p       = '\0';

    if (!(siedn = (char*)HashTableFind(servers, serverid))) {
        /* DT 4/6/98 -- If we're seeing a serverid for the first time, then we try to do
         *              a resync to pull in new data for the serverid. If it still fails,
         *              then the client is out of luck. */
      
        admserv_runtime_command_exec(RUNTIME_RESYNC_COMMAND, r->args, r);
      
        if (!(siedn = (char*)HashTableFind(servers, serverid))) {
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                          "admserv_check_authz(): unable to find registered server (%s)",
                          serverid);
            return admserv_error(r, HTTP_BAD_REQUEST, "server not registered"); /*i18n*/
        }
    }

    if (!STRNCASECMP(r->uri, AUTH_URI, strlen(AUTH_URI))) {
        /* already authenticated - no remapping required - no task authz required */
        ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r,
                      "admserv_check_authz(): passing [%s] to the userauth handler",
                      r->uri);
        return OK;
    }

    if (!build_full_DN(&storage, entryDN+LINE_LENGTH, uri, siedn)) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                      "admserv_check_authz(): unable to build DN from URL - bad URL [%s]",
                      uri);
        return admserv_error_std(r, "server not registered"); /*i18n*/
    }

    convert_to_lower_case(entryDN);

    userdn = apr_table_get(r->notes, RQ_NOTES_USERDN);
    pw = apr_table_get(r->notes, RQ_NOTES_USERPW);

    /*
     * if we got here, we either have a userdn - user was authenticated via LDAP
     * or r->user is set and was therefore authenticated via mod_auth - if both
     * LDAP and mod_auth auth fail, we should not be here - not an authenticated user
     */
    if (!userdn && r->user) {
        int retval = check_auth_tasks_cache(entryDN, LOCAL_SUPER_NAME, r, 0, 1 /* send_response */);
        if (retval != OK) {
            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
                          "admserv_check_authz: task [%s] not cached for local superuser",
                          entryDN);
            return retval;
        }
        goto found;
    }

    if (!userdn || !pw) {
        ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r,
                      "admserv_check_authz(): invalid userdn/pw");
        return admserv_error_std(r, "invalid user/password"); /*i18n*/
    }

    /* DT 3/10/98
     * Turn on the task cache for evals, and not just for DS failover.
     */

    now = time(0);

    if (check_auth_tasks_cache(entryDN, userdn, r, now, 0 /* no response */) == OK) {
        goto found;
    }

    /* Cache lookup failed or the entry expired. Try the DS. */

    if (!(server = openLDAPConnection(&registryServer))) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                      "admserv_check_authz(): unable to open LDAP connection to %s:%d",
                      registryServer.host, registryServer.port);
        return admserv_error_std(r, "unable to open LDAPConnection"); /*i18n*/
    }

    tries = 0;
    do {
        /*
         * Bug fix #340829 - do NOT bind as the local admin here, bind as user DN.
         */
        int ignored;
        ldapError = admserv_ldap_auth_userdn_password(server, userdn, pw, &ignored);
        if(ldapError != LDAP_SERVER_DOWN && ldapError != LDAP_CONNECT_ERROR)
            break;
     
        closeLDAPConnection(server);
        if(!(server = openLDAPConnection(&registryServer))) {
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                          "admserv_check_authz(): unable to LDAP BIND as [%s] to %s:%d",
                          userdn, registryServer.host, registryServer.port);
            return admserv_error_std(r, "unable to open LDAPConnection"); /*i18n*/
        }
    } while (server != NULL && ++tries < 2);

    if(ldapError != LDAPU_SUCCESS) {
        closeLDAPConnection(server);

        if ((ldapError == LDAP_CONNECT_ERROR) || (ldapError == LDAP_SERVER_DOWN)) {
            int retval = check_auth_tasks_cache(entryDN, userdn, r, 0, 1 /* send response */); /* Try the cache, ignoring expiration */
            if (retval != OK) {
                ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
                              "admserv_check_authz: could not find cached task [%s] for [%s]",
                              entryDN, userdn);
                return retval;
            }
            goto found;
        }

        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                      "admserv_check_authz(): got LDAP error %d talking to %s:%d - possible user [%s] not authorized",
                      ldapError, registryServer.host, registryServer.port, userdn);
        return admserv_error(r, HTTP_UNAUTHORIZED, "invalid user credentials"); /*i18n*/
    }

    /* Attempt to read the entry data -- involves DS eval of entry ACI for userDN.
     * If we are able to read it, then access is granted.
     */
    populate_task_cache_entries(userdn, server);
    closeLDAPConnection(server);

    if (check_auth_tasks_cache(entryDN, userdn, r, now, 0 /* no response */) == OK) {
        goto found;
    }

    ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r,
                  "admserv_check_authz(): Task [%s] not found for user [%s] - either"
                  " the task was not registered or the user was not authorized",
                  entryDN, userdn);
    return admserv_error(r, HTTP_UNAUTHORIZED, "task not found or unauthorized"); /*i18n*/

    /* This is remapping - converting the requested URI to the actual CGI filename
       We can't do this in a translate_name hook because that happens _before_ authenticating
       the user, and we can't get the task information from the ds until after
       the user has been authenticated.  So, we do the remapping here
    */
found:
    /* MK 8/1/00
     * Treat StartConfigDS as a special task. This will be the case if the user was
     * authenticated as the local superuser (admpw) and the server-id is "admin-serv"
     */
#   define STARTDS_IDENTIFIER "tasks/operation/StartConfigDS"
#   define STARTDS_CGI        "start_config_ds"
    if (!STRNCASECMP(saveduri, STARTDS_IDENTIFIER, strlen(STARTDS_IDENTIFIER)) &&
        !STRCMP(serverid, "admin-serv") &&  
        /* is local superuser */
        !userdn && r->user) {
        apr_status_t status;

        if (!cf->cgibindir) {
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                          "admserv_check_authz(): No ADMCgiBinDir was specified for location [%s]",
                          r->filename);
            return admserv_error_std(r, apr_psprintf(r->pool,
                                                     "No ADMCgiBinDir specified for location [%s]",
                                                     r->filename));
        }

        r->filename = apr_psprintf(r->pool, "%s%c%s",
                                   cf->cgibindir, FILE_PATHSEP, STARTDS_CGI);

        ap_set_content_type(r, CGI_MAGIC_TYPE);
 
        /* Make sure it's not a directory, Mike's way. This is also required 
         * for when we end up in the CGI. mod_cgi assumes that the stat has
         * already been done. */
        status = apr_stat(&r->finfo, r->filename, APR_FINFO_NORM, r->pool);
        if (status == APR_SUCCESS) {
            if (r->finfo.filetype == APR_DIR) {
                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                              "admserv_check_authz(): start config ds CGI is a directory [%s]",
                              r->filename);
                return admserv_error_std(r, apr_psprintf(r->pool,
                                                         "Invalid URL [%s] is a directory",
                                                         r->filename));
            }
        }

        /* Show this in the error log*/
        ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r,
                      "admserv_check_authz(): StartConfigDS requested by the local superuser");

        return OK;
    }
 
    /* DT 3/15/98 Runtime command support. */
    if (!STRNCASECMP(saveduri, RUNTIME_COMMAND_BASE, strlen(RUNTIME_COMMAND_BASE))) {
        /* subdir is allocated from r->pool */
        char *name = saveduri + strlen(RUNTIME_COMMAND_BASE);
        apr_table_set(r->notes, RQ_NOTES_COMMAND_NAME, name);
        ap_set_content_type(r, RUNTIME_COMMAND_CONTENT_TYPE);
        r->filename = apr_pstrdup(r->pool, "/commands");
        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
                      "admserv_check_authz: mapped uri [%s] to command [%s]",
                      r->uri, name);
        return OK;
    } else {
        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
                      "admserv_check_authz: uri [%s] did not begin with [%s] - not a command",
                      saveduri, RUNTIME_COMMAND_BASE);
    }

    {
        apr_status_t status;
        /* these are set in check_auth_tasks_cache if the task was cached */
        const char *execRef = apr_table_get(r->notes, RQ_NOTES_EXECREF);
        const char *execRefArgs = apr_table_get(r->notes, RQ_NOTES_EXECREFARGS);

        if (!cf->cgibindir) {
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                          "admserv_check_authz(): No ADMCgiBinDir was specified for location [%s]",
                          r->filename);
            return admserv_error_std(r, apr_psprintf(r->pool,
                                                     "No ADMCgiBinDir specified for location [%s]",
                                                     r->filename));
        }

        /* set cgibindir in the Directory/Location section for this server */
        r->filename = apr_psprintf(r->pool, "%s%c%s",
                                   cf->cgibindir, FILE_PATHSEP, execRef);

        /* This is so it will get evaulated as a CGI */
        ap_set_content_type(r, CGI_MAGIC_TYPE);
    
        /* Make sure it's not a directory, Mike's way. This is also required 
         * for when we end up in the CGI. mod_cgi assumes that the stat has
         * already been done. */
        status = apr_stat(&r->finfo, r->filename, APR_FINFO_NORM, r->pool);
        if (status == APR_SUCCESS) {
            if (r->finfo.filetype == APR_DIR) {
                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                              "admserv_check_authz(): CGI is a directory [%s]",
                              r->filename);
                return admserv_error_std(r, apr_psprintf(r->pool,
                                                         "Invalid URL [%s] is a directory",
                                                         r->filename));
            }
        }

        /* Append execRefArgs to query, if applicable. */
        if (execRefArgs != NULL) {
            if (r->args != NULL) {
                char *new_query = apr_psprintf(r->pool, "%s&%s", execRefArgs, r->args);
                r->args = new_query;
            } else {
                r->args = apr_pstrdup(r->pool, execRefArgs);
            }
        }
    }

    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
                  "admserv_check_authz: execute CGI [%s] args [%s]",
                  r->filename, r->args);

    return OK; /* FIXME, does this matter? */
}

static int
check_auth_users_cache(char *user, const char *pw, request_rec *r, long now)
{
  /* Attempt to authenticate using auth_users cache */

  UserCacheEntry *cache_entry = NULL;
    
  if (!auth_users) {
      ap_log_error(APLOG_MARK, APLOG_CRIT, 0 /* status */, NULL,
          "[%d] auth_users doesn't exist", getpid());
      return DECLINED; /* Failed. */
  }

  cache_entry = (UserCacheEntry*)HashTableFind(auth_users, user);

  if (cache_entry) {
      ap_log_error(APLOG_MARK, APLOG_DEBUG, 0 /* status */, NULL,
                   "[%d] Found cache entry for user [%s][%s] pw is %s, cached is %s, now is %ld, cached is %ld",
                   getpid(), user, cache_entry->userDN, pw, cache_entry->userPW, now, cache_entry->createTime);
  } else {
      ap_log_error(APLOG_MARK, APLOG_DEBUG, 0 /* status */, NULL,
                   "[%d] cache entry not found for user [%s]",
                   getpid(), user);
  }


  if (!cache_entry || STRCMP(cache_entry->userPW, pw) ||
      ((now - cache_entry->createTime) > cacheLifetime)) {
      ap_log_error(APLOG_MARK, APLOG_DEBUG, 0 /* status */, NULL,
                   "[%d] user [%s] not cached - reason %s",
                   getpid(), user,
                   !cache_entry ? "user not in cache" :
                   (STRCMP(cache_entry->userPW, pw) ? "password changed" : "cache entry expired"));
      return DECLINED; /* Failed. */
  }

  apr_table_set(r->notes, RQ_NOTES_USERDN, cache_entry->userDN);
  apr_table_set(r->notes, RQ_NOTES_USERPW, pw);
/* FIXME
  if (!strchr(user, '='))
    pblock_nvinsert("auth-user", user, rq->vars);
  pblock_nvinsert(AUTHENTICATION_LDAP_URL, cache_entry->ldapURL, rq->vars);
*/

  return OK;
}

static int
create_auth_users_cache_entry(char *user, char *userDN, const char *userPW, char *ldapURL)
{
  UserCacheEntry *cache_entry;

  ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL, "[%d] Looking for entry %s", getpid(), user);
    
  if (!(cache_entry = (UserCacheEntry *)HashTableFind(auth_users, user)))
  {
      cache_entry = (UserCacheEntry*)apr_palloc(module_pool, sizeof(UserCacheEntry));
      HashTableInsert(auth_users, apr_pstrdup(module_pool, user), cache_entry);
      ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL, "Added new entry [%s][%s] to auth_users cache",
                   user, userDN);
  } else {
      ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL, "freeing existing entry");
/*
      free(cache_entry->userDN);
      free(cache_entry->userPW);
      free(cache_entry->ldapURL);
*/
  }

  cache_entry->userDN     = apr_pstrdup(module_pool, userDN);
  cache_entry->userPW     = apr_pstrdup(module_pool, userPW);
  cache_entry->ldapURL    = apr_pstrdup(module_pool, ldapURL);
  cache_entry->createTime = time(0);

  return 1;
}

/* pattern is (pat1|pat2|...|patN) where patN is a simple apr_fnmatch pattern
   if we get a match, just return immediately with success, otherwise, loop
   through all the patterns and return a failure code if no match
*/
static apr_status_t
admserv_match_list(char *patterns, const char *string, int flags)
{
    apr_status_t rc = APR_SUCCESS;
    char *last = NULL;
    char *pattern = apr_strtok(patterns, "()|", &last);

    while (pattern) {
        rc = apr_fnmatch(pattern, string, flags);
        if (rc == APR_SUCCESS) {
            return rc;
        }
        pattern = apr_strtok(NULL, "()|", &last);
    }

    return rc;
}

/* Check if the caller hostname or ip address is disallowed */
static int 
admserv_host_ip_check(request_rec *r)
{
#ifdef WITH_APACHE24
    char * clientIP = r->connection->client_ip;
#else
    char * clientIP = r->connection->remote_ip;
#endif
    char *msg;

    if (clientIP) {
    } else {
        ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r,
                      "admserv_host_ip_check: Unauthorized and unknown host, connection rejected");
    
        return admserv_error(r, HTTP_UNAUTHORIZED, "Unauthorized Host!");
    }

    if (accessHosts && *accessHosts) {
        /* flags for accessHosts matching - want . to match . (not "any char") and
           do case insensitive matching */
        int matchflags = APR_FNM_PERIOD|APR_FNM_CASE_BLIND;
        const char *maxdns = ap_get_remote_host(r->connection, r->per_dir_config,
                                                REMOTE_HOST, NULL);
        if (maxdns) {
            apr_status_t rc = admserv_match_list(apr_pstrdup(r->pool, accessHosts), maxdns, matchflags);
            if (rc != APR_SUCCESS) {
            } else {
                return DECLINED;
            }
        } else {
            PRNetAddr addr;
            ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r,
                          "admserv_host_ip_check: ap_get_remote_host could not resolve %s", clientIP);
            if (PR_SUCCESS == PR_StringToNetAddr(clientIP, &addr)) {
                char buf[PR_NETDB_BUF_SIZE];
                PRHostEnt hEntry;
                if (PR_SUCCESS == PR_GetHostByAddr(&addr, buf, sizeof(buf), &hEntry)) {
                    if (APR_SUCCESS != admserv_match_list(apr_pstrdup(r->pool, accessHosts),
                                                          hEntry.h_name, matchflags)) {
                        char ** x;
                        ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r,
                                      "admserv_host_ip_check: host [%s] did not match pattern [%s] -"
                                      "will scan aliases", hEntry.h_name, accessHosts);
                        for (x = hEntry.h_aliases; x && *x; x++) {
                            if (APR_SUCCESS != admserv_match_list(apr_pstrdup(r->pool, accessHosts),
                                                                  *x, matchflags)) {
                                ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r,
                                              "admserv_host_ip_check: host alias [%s] did not match pattern [%s]",
                                              *x, accessHosts);
                            } else {
                                return DECLINED;
                            }
                        }
                    } else {
                        return DECLINED;
                    }
                } else {
                    ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
                                  "admserv_host_ip_check: failed to get host by ip addr [%s] - "
                                  "check your host and DNS configuration", clientIP);
                }
            } else {
                ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
                              "admserv_host_ip_check: failed to convert addr [%s] to netaddr", clientIP);
            }
        }
    }

    if (accessAddresses && *accessAddresses) {
        int matchflags = APR_FNM_PERIOD;
        apr_status_t rc = admserv_match_list(apr_pstrdup(r->pool, accessAddresses), clientIP, matchflags);
        if (rc != APR_SUCCESS) {
        } else {
            return DECLINED;
        }
    }

    msg = apr_psprintf(r->pool, "admserv_host_ip_check: Unauthorized host ip=%s, connection rejected",
                       clientIP);
    ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r, "%s", msg);
  
    return admserv_error(r, HTTP_UNAUTHORIZED, msg);
}

static int 
host_ip_init(apr_pool_t *p, apr_pool_t *plog,
             apr_pool_t *ptemp,
             server_rec *base_server)
{
    int error;
#ifdef CHANGE_EUID
    int reseteuid = 0;
#endif /* CHANGE_EUID */
    AdmldapInfo info;
    PsetHndl       pset;
    char *val;
    char msgbuf[BUFSIZ];

    info = admldapBuildInfo(configdir, &error);
  
    if (info) {
    } else {
        ap_log_error(APLOG_MARK, APLOG_CRIT, 0, base_server,
                     "host_ip_init(): unable to create AdmldapInfo (error code = %d)",
                     error);
        return DONE;
    }
  
    if (admldapGetSecurity(info)) {
        sslinit(info, configdir);
        if (admldapBuildInfoSSL(info, &error)) {
            if (error != ADMUTIL_OP_OK) {
        	ap_log_error(APLOG_MARK, APLOG_INFO, 0, base_server,
                             "host_ip_init(): problem creating secure AdmldapInfo (error code = %d)",
                             error);
            }
        } else {
            ap_log_error(APLOG_MARK, APLOG_CRIT, 0, base_server,
                         "host_ip_init(): unable to create secure AdmldapInfo (error code = %d)",
                         error);
            destroyAdmldap(info);
            return DONE;
        }
    } else {
	ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, base_server,
	             "host_ip_init(): secure connection not enabled, skipping sslinit");

    }

    destroyAdmldap(info);
#ifdef CHANGE_EUID
    /* make sure pset creates the cache file owned by the server uid, not root */
    if (geteuid() == 0) {
#ifdef WITH_APACHE24
        SETEUID(ap_unixd_config.user_id);
#else
        SETEUID(unixd_config.user_id);
#endif
        reseteuid = 1;
    }
#endif /* CHANGE_EUID */

    pset = psetCreateSSL((char*)"admin-serv", 
                         configdir,
                         NULL,
                         NULL,
                         &error);

#ifdef CHANGE_EUID
    if (reseteuid) {
        SETEUID(0);
    }
#endif /* CHANGE_EUID */

    if (pset) {
    } else {
        ap_log_error(APLOG_MARK, APLOG_CRIT, 0, base_server,
                     "host_ip_init(): PSET failure: Failed to create PSET handle (pset error = %s)",
                     psetErrorString(error, NULL, msgbuf, sizeof(msgbuf), NULL));
        return DONE;
    }

    val = psetGetAttrSingleValue(pset, NS_ADMIN_ACCESS_HOSTS, &error);
    if (val) {
    } else {
        ap_log_error(APLOG_MARK, APLOG_CRIT, 0, base_server,
                     "host_ip_init(): PSET failure: Could not retrieve access hosts attribute (pset error = %s)",
                     psetErrorString(error, NULL, msgbuf, sizeof(msgbuf), NULL));
        psetDelete(pset);
        return DONE;
    }

    accessHosts = apr_pstrdup(module_pool, val);
    PL_strfree(val);

    val = psetGetAttrSingleValue(pset, NS_ADMIN_ACCESS_ADDRESSES, &error);
    psetDelete(pset);
    pset = NULL;
    if(val) {
    } else {
        ap_log_error(APLOG_MARK, APLOG_CRIT, 0, base_server,
                     "host_ip_init(): PSET failure: Could not retrieve access addresses attribute (pset error = %s)",
                     psetErrorString(error, NULL, msgbuf, sizeof(msgbuf), NULL));
        return DONE;
    }
    accessAddresses = apr_pstrdup(module_pool, val);
    PL_strfree(val);

    if (accessHosts[0] || accessAddresses[0]) {
    } else {
        /* the configuration hasn't specified if anyone can connect.
         * That is a contradiction as an Admin Server that can't
         * let anyone connect is pointless! Consequently, I have
         * added code here to scan through the local.conf file
         * and extract the serverHostName and add it to the allowed
         * hosts file. rwagner
         */
        char const myAttr[] = "serverHostName";
        char line[BIG_LINE];
        char filename[BIG_LINE];
        FILE *f;
    
        apr_snprintf(filename, sizeof(filename),
                     "%s%clocal.conf", configdir, FILE_PATHSEP);
        if((f = fopen(filename, "r" )) != NULL) {
            while(!feof(f)) {
                fgets(line, sizeof(line), f);
    
                if (STRNCASECMP(line, myAttr, sizeof(myAttr) - 1) == 0) {
                    char * c;
                    for (c = &line[sizeof(myAttr) - 1]; *c && *c != ':'; c++);
                    if (*c == ':') {
                        for (c++; *c && *c == ' '; c++);
                        if (*c) {
                            char * x;
                            for (x = c; *x; x++) {
                                if (*x < ' ') {
                                    *x = '\0';
                                    break;
                                }
                            }
                            accessHosts = apr_pstrdup(module_pool, c);
                        }
                    }
                }
            }
            fclose(f);
      
            if (accessHosts[0]) {
                char buf[PR_NETDB_BUF_SIZE];
                PRHostEnt hEntry;

                ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, base_server,
                             "host_ip_init(): no hosts allowed or ip addresses allowed "
                             "specified. Allowing %s nonetheless.",
                             accessHosts);
                if (PR_SUCCESS == PR_GetHostByName(accessHosts, buf, sizeof(buf), &hEntry)) {
                    PRNetAddr addr;
                    if (PR_EnumerateHostEnt(0, &hEntry, 0, &addr) > 0) {
                        if (PR_SUCCESS == PR_NetAddrToString(&addr, buf, sizeof(buf))) {
                            accessAddresses = apr_pstrdup(module_pool, buf);
                        }
                    }
                }
            } else {
                ap_log_error(APLOG_MARK, APLOG_WARNING, 0, base_server,
                             "host_ip_init(): could not locate %s in file %s",
                             myAttr, filename);
            }
        }
    }
  
    if (accessHosts[0] || accessAddresses[0]) {
        if (accessHosts[0]) {
            ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, base_server,
                         "Access Host filter is: %s",
                         accessHosts);
        }
        if (accessAddresses[0]) {
            ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, base_server,
                         "Access Address filter is: %s",
                         accessAddresses);
        }
    } else {
        ap_log_error(APLOG_MARK, APLOG_CRIT, 0, base_server,
                     "host_ip_init(): the given configuration denies all hosts, thus nothing can connect!");
        return DONE;
    }
  
    return OK;
}

/*
 * NSS caches SSL client session information - this cache must be cleared, otherwise
 * NSS_Shutdown will give an error.  mod_nss also does this (along with the NSS_Shutdown)
 * It is ok to call SSL_ClearSessionCache multiple times.
 * The actual NSS_Shutdown is done in mod_nss.  Note that we cannot call NSS_Shutdown
 * here - if NSS_Shutdown fails because mod_nss still has server caches referenced,
 * NSS will be left in a bad state - it won't really be shutdown because of the outstanding
 * references, but NSS_IsInitialized will return false, and NSS_Initialize will fail.
 * So we must be careful here to just release any references we have.
 * The assumption here is that mod_nss is loaded before mod_admserv (which will usually
 * happen since it is listed first in the httpd.conf) - but note that module unload
 * happens in _reverse_ order - so mod_admserv_unload will be called _before_ the
 * mod_nss unload function.  If this ever changes, we will need to figure out some other
 * way to ensure that NSS_Shutdown is only ever called once, and only after all caches
 * and other resources have been released.
 */
static
apr_status_t mod_admserv_unload(void *data)
{
    if (NSS_IsInitialized()) {
        SSL_ClearSessionCache();
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL,
                     "mod_admserv_unload: cleared SSL session cache");
    } else {
	ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL,
	             "mod_admserv_unload: cannot clear cache - NSS not initialized");
    }
    return OK;
}

static int
do_admserv_post_config(apr_pool_t *p, apr_pool_t *plog,
                       apr_pool_t *ptemp,
                       server_rec *base_server)
{
    AdmldapInfo info;
    int   error;
    LDAP *server;
    int tries = 0;
    admserv_serv_config *srv_cfg = NULL;

    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, base_server,
                 "Entering do_admserv_post_config - pid is [%d]",
                 getpid());

    /* if configdir was not set in the config, get from the environment */
    srv_cfg = our_sconfig(base_server);
    srv_cfg->gconfig->nInitCount++;

    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, base_server,
                 "Entering do_admserv_post_config - init count is [%d]",
                 srv_cfg->gconfig->nInitCount);

    servers    = HashTableCreate();
    auth_users = HashTableCreate();
    auth_tasks = HashTableCreate();

    /* 
     * Let us cleanup on restarts and exists
     */
    apr_pool_cleanup_register(p, base_server,
                              mod_admserv_unload,
                              apr_pool_cleanup_null);

    /* if configdir was not set in the config, get from the environment */
    srv_cfg = our_sconfig(base_server);
    configdir = srv_cfg->configdir;
    if (!configdir) {
        configdir = getenv("ADMSERV_CONF_DIR"); /* set in admserv.conf to override */
    }

    /* cache entry expiration */
    cacheLifetime = srv_cfg->cacheLifeTime;
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, base_server,
                 "[%d] Cache expiration set to %ld seconds", getpid(), cacheLifetime);

    if (configdir) {
    } else {
        ap_log_error(APLOG_MARK, APLOG_CRIT, 0, base_server, "do_admserv_post_config(): NULL ADMConfigDir");
        return DONE;
    }

    admserv_runtime_command_init();
    admserv_register_runtime_command(RUNTIME_RESYNC_COMMAND, sync_task_sie_data, NULL);
    admserv_register_runtime_command(CHANGE_SIEPWD_COMMAND, change_sie_password, NULL);

    info = admldapBuildInfo(configdir, &error);

    if (info) {
    } else {
        ap_log_error(APLOG_MARK, APLOG_CRIT, 0, base_server, "do_admserv_post_config(): unable to create AdmldapInfo");
        return DONE;
    }
  
    /* Registry DS setup */
    registryServer.host   = admldapGetHost(info);
    registryServer.port   = (admldapGetPort(info) < 0) ? 389 : admldapGetPort(info);
    registryServer.secure = (admldapGetSecurity(info)) ? 1 : 0;
    registryServer.baseDN = admldapGetBaseDN(info);
    registryServer.bindDN = ""; /* deprecated - use user credentials */
    registryServer.bindPW = ""; /* deprecated - use user credentials */
    registryServer.admservSieDN = admldapGetSIEDN(info);
    registryServer.securitydir = admldapGetSecurityDir(info);

    destroyAdmldap(info);
    info = NULL;
  
    /* Initialize the UG host to NULL.  This will cause the
     * UG info to be loaded the first time a user authenticates.
     */
    userGroupServer.host   = NULL;
  
    /* Register the admin server tasks */
    task_register_server(ADMIN_SERVER_ID, registryServer.admservSieDN);

    /* Populate the auth_tasks cache for the Local Admin */

    server = openLDAPConnection(&registryServer);

    if (server) {
    } else {
        ap_log_error(APLOG_MARK, APLOG_WARNING, 0, base_server,
                     "Unable to open initial LDAPConnection to populate LocalAdmin tasks into cache.");
        return OK;
    }

    tries = 0;
    do {
        error = admserv_ldap_auth_server(server, &registryServer);
        if(error != LDAP_SERVER_DOWN && error != LDAP_CONNECT_ERROR)
            break;
     
        closeLDAPConnection(server);
        if(!(server = openLDAPConnection(&registryServer))) {
            ap_log_error(APLOG_MARK, APLOG_WARNING, 0, base_server,
                         "Unable to open LDAPConnection to populate LocalAdmin tasks into cache.");
            return OK;
        }
    } while (server != NULL && ++tries < 2);
   
    {
        /* Always treat StartConfigDS specially.
         * Regardless of the result of opening LDAPConnection, 
         * put StartConfigDs into the cache for bootstrap. 
         * This code allows the Console to launch the Config DS via Admin Server
         * even if
         * 1) the Config DS is not up when the Admin Server is started, or
         * 2) the Config DS is shutdown after the Admin Server is started.
         */
        char startds[LINE_LENGTH];
        char normStartds[LINE_LENGTH];
        char *storage = startds;
        char *uri = apr_pstrdup(module_pool, STARTDS_IDENTIFIER);
        TaskCacheEntry *cache_entry;

        if (!build_full_DN(&storage, startds+LINE_LENGTH, uri,
                           registryServer.admservSieDN)) {
            ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server,
                "do_admserv_post_config: unable to build DN from URL - bad URL [%s]",
                uri?uri:"none");
            return OK;
        }
        convert_to_lower_case(startds);
        adm_normalize_dn(startds, normStartds);
        if (!(cache_entry = (TaskCacheEntry *)HashTableFind(auth_tasks, normStartds))) {
            cache_entry = (TaskCacheEntry*)apr_pcalloc(module_pool, sizeof(TaskCacheEntry));
            cache_entry->auth_userDNs = HashTableCreate();
            HashTableInsert(auth_tasks, apr_pstrdup(module_pool, normStartds), cache_entry);
        }

        cache_entry->execRef     = apr_pstrdup(module_pool, STARTDS_CGI);
        cache_entry->execRefArgs = NULL;
        cache_entry->logSuppress = 0;
        HashTableInsert(cache_entry->auth_userDNs, 
                        apr_pstrdup(module_pool, LOCAL_SUPER_NAME),
                        (char*)time(0));
        
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, base_server,
                     "Added StartConfigDs task entry [%s:%s:%s] for user [%s]",
                     normStartds,
                     cache_entry->execRef ? cache_entry->execRef : "",
                     cache_entry->execRefArgs ? cache_entry->execRefArgs : "",
                     LOCAL_SUPER_NAME);
    }

    if (error != LDAPU_SUCCESS) {
        closeLDAPConnection(server);
        ap_log_error(APLOG_MARK, APLOG_WARNING, 0, base_server,
                     "Unable to bind as LocalAdmin to populate LocalAdmin tasks into cache.");
        return host_ip_init(p, plog, ptemp, base_server);
    }

    populate_task_cache_entries(LOCAL_SUPER_NAME, server);

    closeLDAPConnection(server);

    return host_ip_init(p, plog, ptemp, base_server);
}

/*
 * This is where we do the rest of our initialization, that depends
 * on configuration settings
 */
static int
mod_admserv_post_config(apr_pool_t *p, apr_pool_t *plog,
                        apr_pool_t *ptemp,
                        server_rec *base_server)
{
    int status = OK;
    admserv_serv_config *srv_cfg = NULL;

    srv_cfg = our_sconfig(base_server);

    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, base_server,
                 "Entering mod_admserv_post_config - pid is [%d] init count is [%d]",
                 getpid(), srv_cfg->gconfig->nInitCount);

    /* if configdir was not set in the config, get from the environment */
    if (srv_cfg->gconfig->nInitCount < 1) {
        status = do_admserv_post_config(p, plog, ptemp, base_server);
    } else {
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, base_server,
                     "mod_admserv_post_config - pid is [%d] - post config already done once -"
                     " additional config will be done in init_child",
                     getpid());
    }

    return status;
}

admserv_global_config *admserv_config_global_create(server_rec *s)
{
    apr_pool_t *pool = s->process->pool;
    admserv_global_config *globalc = NULL;
    void *vglobalc = NULL;

    apr_pool_userdata_get(&vglobalc, MOD_ADMSERV_CONFIG_KEY, pool);
    if (vglobalc) {
        return vglobalc; /* reused for lifetime of the server */
    }

    /*
     * allocate an own subpool which survives server restarts
     */
    globalc = (admserv_global_config *)apr_palloc(pool, sizeof(*globalc));

    /*
     * initialize per-module configuration
     */
    globalc->nInitCount = 0;

    apr_pool_userdata_set(globalc, MOD_ADMSERV_CONFIG_KEY,
                          apr_pool_cleanup_null,
                          pool);

    return globalc;
}

/*
 * Create the per-directory structure.
 */
static void * create_config(apr_pool_t *p, char *path)
{
    admserv_config * cf = (admserv_config *) apr_palloc(p, sizeof(admserv_config));
    cf->nescompat = 0;
    cf->adminsdk = 0;
    cf->cgibindir = 0;
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL, "[%d] create_config [0x%p] for %s",
                 getpid(), cf, path ? path : "(null)");

    return (void *) cf;
}

/*
 * Create the per-server structure.
 */
static void * create_server_config(apr_pool_t *p, server_rec *s)
{
    admserv_serv_config * cf = (admserv_serv_config *) apr_pcalloc(p, sizeof(admserv_serv_config));
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL, "[%d] create_server_config [0x%p] for %s",
                 getpid(), cf, s->server_hostname ? s->server_hostname : "(null)");
    cf->gconfig = admserv_config_global_create(s);

    return (void *) cf;
}

static const char * adminsdk(cmd_parms *cmd, void *dconf, int flag)
{
    admserv_config *cf = (admserv_config *)dconf;

    cf->adminsdk = flag;

    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL, "[%d] adminsdk [0x%p] flag %d",
                 getpid(), cf, flag);
    return NULL;
}

static const char * nescompat(cmd_parms *cmd, void *dconf, int flag)
{
    admserv_config *cf = (admserv_config *)dconf;

    cf->nescompat = 1;

    return NULL;
}

static const char * set_config_dir(cmd_parms *cmd, void *dconf, const char *confdir)
{
    if (cmd->path) {
        ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, "The %s config param cannot be specified in a Directory section",
                     cmd->directive->directive);
    } else {
        void *sconf = cmd->server->module_config;
        admserv_serv_config *cf = (admserv_serv_config *)ap_get_module_config(sconf, &admserv_module);

        cf->configdir = apr_pstrdup(cmd->pool, confdir);
    }

    return NULL;
}

static const char * set_cgi_bin_dir(cmd_parms *cmd, void *dconf, const char *cgibindir)
{
    admserv_config *cf = (admserv_config *)dconf;

    cf->cgibindir = apr_pstrdup(cmd->pool, cgibindir);

    return NULL;
}

static const char * set_cache_life_time(cmd_parms *cmd, void *dconf, const char *value)
{
    if (cmd->path) {
        ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, "The %s config param cannot be specified in a Directory section",
                     cmd->directive->directive);
    } else {
        void *sconf = cmd->server->module_config;
        admserv_serv_config *cf = (admserv_serv_config *)ap_get_module_config(sconf, &admserv_module);

        cf->cacheLifeTime = atol(value);
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL,
                     "[%d] Set [0x%p] [%s] to %ld",
                     getpid(), cf, cmd->directive->directive, cf->cacheLifeTime);
    }

    return NULL;
}

static const char * set_version_string(cmd_parms *cmd, void *dconf, const char *value)
{
    if (cmd->path) {
        ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, "The %s config param cannot be specified in a Directory section",
                     cmd->directive->directive);
    } else {
        void *sconf = cmd->server->module_config;
        admserv_serv_config *cf = (admserv_serv_config *)ap_get_module_config(sconf, &admserv_module);

        cf->versionString = apr_pstrdup(cmd->pool, value);
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL,
                     "[%d] Set [0x%p] [%s] to %s",
                     getpid(), cf, cmd->directive->directive, cf->versionString);
    }

    return NULL;
}

static int userauth(request_rec *r)
{
    char *dummy = NULL;

    if (strcmp(r->handler, "user-auth"))
        return DECLINED;

    r->allowed |= (AP_METHOD_BIT << M_GET);
    if (r->method_number != M_GET)
        return DECLINED;

    /* If U/G Info is not available, try to get it */
    if (!(userGroupServer.host)) {
        buildUGInfo(&dummy, r);
    }

    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL, "userauth, bind %s",
                 apr_table_get(r->notes, RQ_NOTES_USERDN));

    ap_set_content_type(r, "text/html");

    ap_rprintf(r, "UserDN: %s\n", apr_table_get(r->notes, RQ_NOTES_USERDN));
    ap_rprintf(r, "UserDirectory: ldap%s://%s:%d/%s\n",
               userGroupServer.secure ? "s" : "",
               userGroupServer.host, userGroupServer.port,
               userGroupServer.baseDN);
    ap_rprintf(r, "ldapHost: %s\n", registryServer.host);
    ap_rprintf(r, "ldapPort: %d\n", registryServer.port);
    ap_rprintf(r, "ldapSecurity: %s\n", (registryServer.secure == 1) ? "on" : "off");
    ap_rprintf(r, "ldapBaseDN: %s\n", registryServer.baseDN);
    ap_rprintf(r, "SIE: %s\n", registryServer.admservSieDN);
    ap_rputs("NMC_Status: 0\n", r);

    return OK;
}

static int
authenticate_user(LdapServerData *data, char *baseDN, char *user, const char *pw, request_rec *r)
{
  LDAP           *server;
  char           *userdn, *ldapURL;
  int             ldapError = LDAP_SUCCESS;
  int             pw_expiring = 0;
  int tries = 0;

  ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r,
                "authenticate_user: begin auth user [%s] pw [%s] in [%s] for [%s:%d]",
                user, pw, baseDN, data->host, data->port);

  if (!(server = openLDAPConnection(data))){
      ap_log_rerror(APLOG_MARK, APLOG_NOTICE|APLOG_NOERRNO, 0, r,
          "unable to open LDAPConnection to server [%s:%d]", data->host, data->port);
      return DECLINED;
  }

  /* The basic auth data may be either uid:pw or userDN:pw. The test for '='
   * is hopefully adequate to detect a DN...
   */
  if (!strchr(user, '=')) {
      /* not a DN, so resolve the DN from the uid */

      tries = 0;
      do {
        /* first try the basedn in the ldap server data, then the basedn param as a fallback */
        ldapError = ldapu_find_userdn(server, user, data->baseDN ? data->baseDN : baseDN, &userdn);
        if(ldapError != LDAP_SERVER_DOWN && ldapError != LDAP_CONNECT_ERROR)
          break;
  
        closeLDAPConnection(server);
        if(!(server = openLDAPConnection(data))) {
          ap_log_rerror(APLOG_MARK, APLOG_NOTICE|APLOG_NOERRNO, 0, r,
                        "unable to find user [%s] in server [%s:%d] under base DN [%s]",
                        user, data->host, data->port, data->baseDN ? data->baseDN : baseDN);
          return DECLINED;
        }
      } while (server != NULL && ++tries < 2);

      if (ldapError != LDAPU_SUCCESS) {
          closeLDAPConnection(server);
  
          if ((ldapError == LDAP_CONNECT_ERROR) || (ldapError == LDAP_SERVER_DOWN))
            return check_auth_users_cache(user, pw, r, 0); /* DS down. Use the cache, ignoring entry expiration. */

          return DECLINED; /* fall back to final check against admpw */
      }
  } else {
      /* it's a DN */

      userdn = user;

      /* strip the leading "ldap:", if present */

      if (!STRNCASECMP(userdn, LDAP_PREFIX, LDAP_PREFIX_LENGTH)) {
          if (strlen(userdn) > LDAP_PREFIX_LENGTH)
            userdn += LDAP_PREFIX_LENGTH;
      }
  }

  tries = 0;
  do {
    ldapError = admserv_ldap_auth_userdn_password(server, userdn, pw, &pw_expiring);
    if(ldapError != LDAP_SERVER_DOWN && ldapError != LDAP_CONNECT_ERROR)
      break;
  
    closeLDAPConnection(server);
    if(!(server = openLDAPConnection(data))) {
      ap_log_rerror(APLOG_MARK, APLOG_NOTICE|APLOG_NOERRNO, 0, r,
                    "unable to bind as [%s] to server [%s:%d]",
                    userdn, data->host, data->port); /*i18n*/
      return DECLINED;
    }
  } while (server != NULL && ++tries < 2);

  if (ldapError != LDAP_SUCCESS) {
      closeLDAPConnection(server);

      if ((ldapError == LDAP_CONNECT_ERROR) || (ldapError == LDAP_SERVER_DOWN))
        return check_auth_users_cache(user, pw, r, 0); /* DS down. Look in the cache, ignoring entry expiration. */

      return DECLINED; /* fall back to final check against admpw */
  }

  closeLDAPConnection(server);

  apr_table_set(r->notes, RQ_NOTES_USERDN, userdn);
  apr_table_set(r->notes, AUTHENTICATION_LDAP_URL, ldapURL = formLdapURL(data, r->pool));
  apr_table_set(r->notes, RQ_NOTES_USERPW, pw);

  create_auth_users_cache_entry(user, userdn, pw, ldapURL);

  return OK;
} 

static int admserv_check_user_id(request_rec *r)
{
    const char *sent_pw;
    int result = 0;
    long now;
    int ret;
    char *dummy = NULL;

    ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, NULL,
       "admserv_check_user_id");

    /* Get the password that the client sent */
    if ((result = ap_get_basic_auth_pw(r, &sent_pw))) {
        ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r,
                      "[%d] auth_ldap authenticate: "
                      "ap_get_basic_auth_pw() returns %d", getpid(), result);
        return result;
    }

    if (r->user == NULL) {
        ap_log_rerror(APLOG_MARK, APLOG_NOTICE|APLOG_NOERRNO, 0, r,
                      "[%d] auth_ldap authenticate: no user specified", getpid());
        return HTTP_UNAUTHORIZED;
    }

    now = time(0);

    ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r,
         "checking user cache for: %s", r->user);
    if (check_auth_users_cache(r->user, sent_pw, r, now) == OK) {
        ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r,
            "user found in cache %s", r->user);
        return OK;
    }
    ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, r,
         "not in cache, trying DS");

    /* Cache lookup failed, or cache entry is expired. Try DS. */

    /* First try o=NetscapeRoot in the registry server */
    ret = authenticate_user(&registryServer, NETSCAPE_ROOT_BASEDN, r->user, sent_pw, r);
    if (ret != DECLINED) {
        return OK;
    }

    /* Then, try the user/group server */

    /* If U/G Info is not available, try to get it */
    if (!(userGroupServer.host)) {
        buildUGInfo(&dummy, r);
    }

    if (userGroupServer.host) {
        ret = authenticate_user(&userGroupServer, NULL, r->user, sent_pw, r);
        if (ret != DECLINED) {
          return OK;
      }
    }

    /* That failed too. The last resort is to fall back to the standard
     * Apache basic-auth using admpw.
     */
    return DECLINED;
}

/*
 * DT 3/15/98
 *
 * We need a way to send commands to a running admin server without simply
 * restarting the server (e.g. to resync to pick up new SIEs). The
 * \admin-serv\commands\* url namespace is to be used for this purpose.
 */
static int
admserv_command_handler(request_rec *r)
{
    const char *name  = apr_table_get(r->notes, RQ_NOTES_COMMAND_NAME);
    char *query = r->args;

    if (!r->content_type ||
        strcmp(r->content_type, RUNTIME_COMMAND_CONTENT_TYPE)) {
        return DECLINED;
    }

    if (name == NULL || query == NULL) {
        return DONE;
    }

    if (admserv_runtime_command_exec(name, query, r)) {
        ap_set_content_length(r, 0);
        r->status = HTTP_OK;
        ap_finalize_request_protocol(r);
        return OK;
    }

    return DONE;
}

static int fixup_adminsdk(request_rec *r)
{
    admserv_config *cf = ap_get_module_config(r->per_dir_config,
                                              &admserv_module);

    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
                 "fixup_adminsdk flag is %d", cf->adminsdk);

    if (!cf->adminsdk)
        return DECLINED;

    r->path_info = '\0';
    password_pipe(r);

    return OK;
}

static int fixup_nescompat(request_rec *r)
{
    apr_table_t *e = r->subprocess_env;
    admserv_config *cf = ap_get_module_config(r->per_dir_config,
                                              &admserv_module);
    if (!cf->nescompat)
        return DECLINED;
                                                                                
    apr_table_setn(e, "SERVER_URL", ap_construct_url(r->pool, "", r));

    return OK;
}

static int fixup_admin_server_header(request_rec *r)
{
    admserv_serv_config *srv_cfg = our_sconfig(r->server);
    const char *string = srv_cfg->versionString;
    /*
     * Insert special "Admin-Server" name/value pair in the server header 
     * so Console knows what type of Admin Server this is.
     * The define is only used for fallback purposes.  The real value should
     * be set in admserv.conf (or httpd.conf)
     */
    if (!string) {
#define ADMSERV_VERSION_STRING "389-Administrator/1.0"
        string = ADMSERV_VERSION_STRING;
    }
    apr_table_setn(r->headers_out, "Admin-Server", string);

    return OK;
}

/*
 * This routine is called before the server processes the configuration
 * files.  There is no return value.  All we do here is set up our
 * static module memory pool and initialize adminsdk.
 */
static int mod_admserv_pre_config(apr_pool_t *pconf, apr_pool_t *plog,
                                  apr_pool_t *ptemp)
{
    int threaded;

    if (module_pool == NULL) {
        apr_pool_create(&module_pool, NULL);
    }

    (void)ADMUTIL_Init();

    ap_mpm_query(AP_MPMQ_IS_THREADED, &threaded);

    if (threaded != 1) {
        ap_log_error(APLOG_MARK, APLOG_NOTICE, 0 /* status */, NULL,
                     "Not using a threaded server.  The Admin Server authorization cache "
                     "will not work correctly.  Console and Admin Server tasks will "
                     "be disabled if the configuration directory server is not available.");
    }

    return OK;
}

static void admserv_init_child(apr_pool_t *p, server_rec *base_server)
{
    admserv_serv_config *srv_cfg = NULL;

    srv_cfg = our_sconfig(base_server);

    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0 /* status */, NULL,
                 "Entering admserv_init_child pid [%d] init count is [%d]",
                 getpid(), srv_cfg->gconfig->nInitCount);

    /* if configdir was not set in the config, get from the environment */
    srv_cfg = our_sconfig(base_server);
    if (srv_cfg->gconfig->nInitCount > 0) {
        do_admserv_post_config(p, NULL, NULL, base_server);
    } else {
        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, base_server,
                     "admserv_init_child - pid is [%d] - config should be done in regular post config",
                     getpid());
    }

    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0 /* status */, NULL,
                 "Leaving admserv_init_child");
    return;
}

/*
  The order in which hooks are called is:
  pre_config
  - command_rec commands, if any
  post_config
  pre_connection
  post_connection
  post_read_request
  translate_name
  access_checker
- until this point, we don't know the user's identity
  check_user_id - only called if Requires was specified
  auth_checker - only called if Requires was specified
  fixups
  type_checker
  - various handlers, if any
*/
static void register_hooks(apr_pool_t *p)
{
    /* Do basic auth after our own auth */
#ifdef AP_SERVER_MAJORVERSION_NUMBER
    /* Apache 2.2+ */
    static const char * const aszPost[] = { "mod_auth_basic.c", NULL };
#else
    /* Apache 2.0 and earlier uses a different module name than 2.2+ */
    static const char * const aszPost[] = { "mod_auth.c", NULL };
#endif
    /* Make sure mod_nss has been configured before us */
    static const char * const aszPre[] = { "mod_nss.c", NULL };

    /* handler for /admin-serv/authenticate requests */
    ap_hook_handler(userauth, NULL, NULL, APR_HOOK_MIDDLE);
    /* handler for /admin-serv/commands */
    ap_hook_handler(admserv_command_handler, NULL, NULL, APR_HOOK_MIDDLE);
    ap_hook_pre_config(mod_admserv_pre_config, aszPre, NULL, APR_HOOK_MIDDLE);
    ap_hook_post_config(mod_admserv_post_config, aszPre, NULL, APR_HOOK_MIDDLE);
    /* called at read_request phase to block clients from disallowed hosts */
    ap_hook_post_read_request(admserv_host_ip_check, NULL, NULL, APR_HOOK_MIDDLE);
    ap_hook_check_user_id(admserv_check_user_id, NULL, aszPost, APR_HOOK_MIDDLE);
    ap_hook_auth_checker(admserv_check_authz, NULL, NULL, APR_HOOK_FIRST);
    ap_hook_fixups(fixup_nescompat, NULL, NULL, APR_HOOK_MIDDLE);
    ap_hook_fixups(fixup_adminsdk, NULL, NULL, APR_HOOK_MIDDLE);
    ap_hook_fixups(fixup_admin_server_header, NULL, NULL, APR_HOOK_MIDDLE);
    /* do per forked child init */
    ap_hook_child_init(admserv_init_child, NULL,NULL, APR_HOOK_MIDDLE);
}

static const command_rec mod_adm_cmds[] =
{
    AP_INIT_FLAG("NESCompatEnv", nescompat, NULL, OR_AUTHCFG,
                 "On or Off to enable or disable (default) NES-compatible environment variables."),
    AP_INIT_FLAG("AdminSDK", adminsdk, NULL, OR_AUTHCFG,
                 "On to interoperate with the AdminSDK properly."),
    AP_INIT_TAKE1("ADMConfigDir", set_config_dir, NULL, RSRC_CONF,
                  "The absolute path of the directory containing our config files."),
    AP_INIT_TAKE1("ADMCgiBinDir", set_cgi_bin_dir, NULL, ACCESS_CONF,
                  "The absolute path of the directory containing the CGI binaries for this location."),
    AP_INIT_TAKE1("ADMCacheLifeTime", set_cache_life_time, NULL, RSRC_CONF,
                 "Number of seconds to cache auth credentials."),
    AP_INIT_TAKE1("ADMServerVersionString", set_version_string, NULL, RSRC_CONF,
                 "The server and version string to be returned to the client in the Admin-Server HTTP header value."),
    { NULL }
};

module AP_MODULE_DECLARE_DATA admserv_module =
{
    STANDARD20_MODULE_STUFF,
    create_config,              /* create per-dir config */
    NULL,                       /* merge per-dir config */
    create_server_config,       /* server config */
    NULL,                       /* merge server config */
    mod_adm_cmds,               /* command apr_table_t */
    register_hooks              /* register hooks */
};
