/* 
   Copyright © 1998, 1999 Enbridge Pipelines Inc. 
   Copyright © 1999-2001 Dave Carrigan
   All rights reserved.

   This module is free software; you can redistribute it and/or modify
   it under the same terms as Apache itself. This module is
   distributed in the hope that it will be useful, but WITHOUT ANY
   WARRANTY; without even the implied warranty of MERCHANTABILITY or
   FITNESS FOR A PARTICULAR PURPOSE. The copyright holder of this
   module can not be held liable for any general, special, incidental
   or consequential damages arising out of the use of the module.

   $Id: auth_ldap_config.c,v 1.14 2001/07/04 14:40:00 dave Exp $

*/

#include "auth_ldap.h"

extern module MODULE_VAR_EXPORT auth_ldap_module;

void *
create_auth_ldap_dir_config(pool *p, char *d)
{
  auth_ldap_config_rec *sec = 
    (auth_ldap_config_rec *)ap_pcalloc(p, sizeof(auth_ldap_config_rec));

  sec->auth_authoritative = 1;
  sec->enabled = 1;
  sec->groupattr = ap_make_array(p, GROUPATTR_MAX_ELTS, 
				 sizeof(struct groupattr_entry));

  sec->have_ldap_url = 0;
  sec->ldc = NULL;
  sec->host = NULL;
  sec->url = "";
  sec->binddn = NULL;
  sec->bindpw = NULL;
  sec->deref = always;
  sec->group_attrib_is_dn = 1;

#ifdef AUTH_LDAP_FRONTPAGE_HACK
  sec->frontpage_hack = 0;
#endif
#ifdef HAVE_TLS
  sec->starttls = 0;
#endif

  sec->dn = "";
  sec->user_is_dn = 0;
  sec->compare_dn_on_server = 0;

  return sec;
}

/* 
   Use the ldap url parsing routines to break up the ldap url into
   host and port, and find/make a pointer to a LDAPconnection struct
*/
const char *
parse_auth_ldap_url(cmd_parms *cmd, auth_ldap_config_rec *sec, char *url)
{
  int result;
  LDAPURLDesc *urld;

  ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 
	       cmd->server, "version %s: Trying to parse an url `%s'", 
	       auth_ldap_version, url);

  result = ldap_url_parse(url, &(urld));
  if (result != LDAP_SUCCESS) {
    switch (result) {
    case LDAP_URL_ERR_NOTLDAP:
      return "LDAP URL does not begin with ldap://";
    case LDAP_URL_ERR_NODN:
      return "LDAP URL does not have a DN";
    case LDAP_URL_ERR_BADSCOPE:
      return "LDAP URL has an invalid scope";
    case LDAP_URL_ERR_MEM:
      return "Out of memory parsing LDAP URL";
    default:
      return "Could not parse LDAP URL";
    }
  }
  sec->url = ap_pstrdup(cmd->pool, url);

  ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 
	       cmd->server, "Url parse: Host: %s", urld->lud_host);
  ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 
	       cmd->server, "Url parse: Port: %d", urld->lud_port);
  ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 
	       cmd->server, "Url parse: DN: %s", urld->lud_dn);
  ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 
	       cmd->server, "Url parse: Attrib: %s", urld->lud_attrs? urld->lud_attrs[0] : "(null)");
  ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 
	       cmd->server, "Url parse: Scope: %s", 
	       (urld->lud_scope == LDAP_SCOPE_SUBTREE? "subtree" : 
		urld->lud_scope == LDAP_SCOPE_BASE? "base" : 
		urld->lud_scope == LDAP_SCOPE_ONELEVEL? "onelevel" : "unknown"));
  ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 
	       cmd->server, "Url parse: Filter: %s", urld->lud_filter);

  /* Set all the values, or at least some sane defaults */
  if (sec->host) {
    char *p = ap_palloc(cmd->pool, strlen(sec->host) + strlen(urld->lud_host) + 2);
    strcpy(p, urld->lud_host);
    strcat(p, " ");
    strcat(p, sec->host);
    sec->host = p;
  } else {
    sec->host = urld->lud_host? ap_pstrdup(cmd->pool, urld->lud_host) : "localhost";
  }
  sec->basedn = urld->lud_dn? ap_pstrdup(cmd->pool, urld->lud_dn) : "";
  if (urld->lud_attrs && urld->lud_attrs[0]) {
    sec->attribute = ap_pstrdup(cmd->pool, urld->lud_attrs[0]);
  } else {
    sec->attribute = "uid";
  }

  sec->scope = urld->lud_scope == LDAP_SCOPE_ONELEVEL?
    LDAP_SCOPE_ONELEVEL : LDAP_SCOPE_SUBTREE;

  if (urld->lud_filter) {
    if (urld->lud_filter[0] == '(') {
      /* 
	 Get rid of the surrounding parens; later on when generating the
	 filter, they'll be put back.
      */
      sec->filter = ap_pstrdup(cmd->pool, urld->lud_filter+1);
      sec->filter[strlen(sec->filter)-1] = '\0';
    } else {
      sec->filter = ap_pstrdup(cmd->pool, urld->lud_filter);
    }
  } else {
    sec->filter = "objectclass=*";
  }
  if (strncmp(url, "ldaps", 5) == 0) {
    ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 
		 cmd->server, "{%d} requesting secure LDAP", (int)getpid());
#ifdef WITH_SSL
    sec->port = urld->lud_port? urld->lud_port : LDAPS_PORT;
    sec->secure = 1;
#else
    return "Secure LDAP (ldaps://) not supported. Rebuild auth_ldap";
#endif
  } else {
    ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 
		 cmd->server, "{%d} not requesting secure LDAP", (int)getpid());
#ifdef WITH_SSL
    sec->secure = 0;
#endif
    sec->port = urld->lud_port? urld->lud_port : LDAP_PORT;
  }

  sec->have_ldap_url = 1;
  ldap_free_urldesc(urld);
  return NULL;
}

const char *
auth_ldap_set_compare_flag(cmd_parms *cmd, void *dummy, char *arg)
{
  ap_log_error(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 
	       cmd->server, "Ignoring deprecated AuthLDAPCacheCompareOps directive.");
  return NULL;
}

const char *
auth_ldap_set_deref(cmd_parms *cmd, auth_ldap_config_rec *sec, char *arg)
{
  if (strcmp(arg, "never") == 0 || strcasecmp(arg, "off") == 0) {
    sec->deref = never;
  } else if (strcmp(arg, "searching") == 0) {
    sec->deref = searching;
  } else if (strcmp(arg, "finding") == 0) {
    sec->deref = finding;
  } else if (strcmp(arg, "always") == 0 || strcasecmp(arg, "on") == 0) {
    sec->deref = always;
  } else {
    return "Unrecognized value for AuthLDAPAliasDereference directive";
  }
  return NULL;
}

const char *
auth_ldap_add_group_attribute(cmd_parms *cmd, auth_ldap_config_rec *sec, char *arg)
{
  struct groupattr_entry *new;

  if (sec->groupattr->nelts > GROUPATTR_MAX_ELTS)
    return "Too many AuthLDAPGroupAttribute directives";

  new = ap_push_array(sec->groupattr);
  new->name = ap_pstrdup(cmd->pool, arg);
  
  return NULL;
}

const char *
auth_ldap_set_cache_ttl(cmd_parms *cmd, void *dummy, char *ttl)
{
  auth_ldap_server_conf *c = 
    (auth_ldap_server_conf *)ap_get_module_config(cmd->server->module_config, 
						  &auth_ldap_module);
  c->search_cache_ttl = atol(ttl);
  return NULL;
}

const char *
auth_ldap_set_cache_size(cmd_parms *cmd, void *dummy, char *size)
{
  auth_ldap_server_conf *c = 
    (auth_ldap_server_conf *)ap_get_module_config(cmd->server->module_config, 
						  &auth_ldap_module);
  c->search_cache_size = atol(size);
  if (c->search_cache_size < 0)
    c->search_cache_size = 0;
  return NULL;
}

const char *
auth_ldap_set_opcache_ttl(cmd_parms *cmd, void *dummy, char *ttl)
{
  auth_ldap_server_conf *c = 
    (auth_ldap_server_conf *)ap_get_module_config(cmd->server->module_config, 
						  &auth_ldap_module);
  c->compare_cache_ttl = atol(ttl);
  return NULL;
}

const char *
auth_ldap_set_opcache_size(cmd_parms *cmd, void *dummy, char *size)
{
  auth_ldap_server_conf *c = 
    (auth_ldap_server_conf *)ap_get_module_config(cmd->server->module_config, 
						  &auth_ldap_module);
  c->compare_cache_size = atol(size);
  if (c->compare_cache_size < 0)
    c->compare_cache_size = 0;
  return NULL;
}

#ifdef WITH_SSL
const char *
auth_ldap_set_certdbpath(cmd_parms *cmd, void *dummy, char *path)
{
  auth_ldap_server_conf *c = 
    (auth_ldap_server_conf *)ap_get_module_config(cmd->server->module_config, 
						  &auth_ldap_module);
  c->have_certdb = 1;
  if (ldapssl_client_init(path, NULL) != 0)
    return "Could not initialize SSL client";
  else
    return NULL;
}
#endif

void
auth_ldap_module_kill(void *data)
{
#ifdef WITH_SHARED_LDAP_CACHE
  if (auth_ldap_mm != NULL) {
    ap_mm_destroy(auth_ldap_mm);
    auth_ldap_mm = NULL;
  }
#endif
}

void
auth_ldap_child_kill(void *data)
{
  /* Nothing to do */
}

void
auth_ldap_init_module(server_rec *s, pool *p)
{
  ap_register_cleanup(p, s, auth_ldap_module_kill, auth_ldap_child_kill);
  
#ifdef WITH_SHARED_LDAP_CACHE
  if (ap_mm_useable()) {
    extern AP_MM *auth_ldap_mm;
    ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO,
		 s, "Trying to enable shared cache.");
    auth_ldap_mm = ap_mm_create(0, "/tmp/auth_ldap_cache");
    if (auth_ldap_mm != NULL) {
      ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO,
		   s, "Successfully enabled shared cache.");
      ap_mm_permission(auth_ldap_mm, ALD_MM_FILE_MODE, ap_user_id, -1);
    } else {
      ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO,
		   s, "Shared cache allocation failed.");
    }
  } else {
    ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 
		 s, "MM is unavailable; not enabling shared cache.");
  }
#endif
  auth_ldap_cache = ald_create_cache(50,
				     auth_ldap_url_node_hash,
				     auth_ldap_url_node_compare,
				     auth_ldap_url_node_copy,
				     auth_ldap_url_node_free);
}

void *
create_auth_ldap_config(pool *p, server_rec *s)
{
  auth_ldap_server_conf *c = 
    (auth_ldap_server_conf *)ap_pcalloc(p, sizeof(auth_ldap_server_conf));

  c->search_cache_ttl = 600;
  c->search_cache_size = 1024;
  c->compare_cache_ttl = 600;
  c->compare_cache_size = 1024;
  c->mtx = ap_create_mutex(NULL);
  c->ldapconnections = NULL;
#ifdef WITH_SSL
  c->have_certdb = 0;
#endif

  return c;
}

