/* 
   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.h,v 1.15 2001/04/07 20:20:17 dave Exp $

*/

#ifdef __AUTH_LDAP_C__
#define EXTERN
#else
#define EXTERN extern
#endif

#include <httpd.h>
#include <http_config.h>
#include <http_core.h>
#include <http_log.h>
#include <http_main.h>
#include <http_protocol.h>
#include <http_conf_globals.h>
#include <multithread.h>

#include <lber.h>
#include <ldap.h>

#ifndef LDAP_SDK_VERSION
#if defined(WITH_OPENLDAP) && LDAP_VENDOR_VERSION < 20000
#define LDAP_SDK_VERSION 2
#else
#define LDAP_SDK_VERSION 3
#endif
#endif

#if defined(WITH_OPENLDAP) && LDAP_SDK_VERSION > 2
#define HAVE_TLS
#endif

#if defined(WITH_SHARED_LDAP_CACHE) && !defined(EAPI)
#undef WITH_SHARED_LDAP_CACHE
#endif

#ifdef WITH_SSL
#include <ldap_ssl.h>
#endif

#include <stdarg.h>
#include <sys/types.h>
#include <time.h>

#include "auth_ldap_cache_mgr.h"

/* Backwards compatibility for Apache 1.2 */
#if APACHE_RELEASE < 1030000
#define auth_ldap_get_pw get_pw
#define ap_pcalloc pcalloc
#define ap_pstrdup pstrdup
#define ap_get_module_config get_module_config
#define ap_get_basic_auth_pw get_basic_auth_pw
#define ap_set_string_slot set_string_slot
#define ap_set_flag_slot set_flag_slot
#define ap_getword getword
#define ap_note_basic_auth_failure note_basic_auth_failure
#define ap_requires requires
#define ap_getword_conf getword_conf

#define APLOG_MARK __FILE__,__LINE__
#define APLOG_DEBUG 7
#define APLOG_WARNING 4
#define APLOG_ERR 3
#define APLOG_NOERRNO 0
#endif

#ifndef WIN32
#define ALD_MM_FILE_MODE ( S_IRUSR|S_IWUSR )
#else
#define ALD_MM_FILE_MODE ( _S_IREAD|_S_IWRITE )
#endif


/*
 * Maintain a cache of LDAP URLs that the server handles. Each node in
 * the cache contains the search cache for that URL, and a compare cache
 * for the URL. The compare cash is populated when doing require group
 * compares.
 */
typedef struct url_node {
  char *url;
  ald_cache *search_cache;
  ald_cache *compare_cache;
  ald_cache *dn_compare_cache;
} url_node;

/*
 * We cache every successful search and bind operation, using the username 
 * as the key. Each node in the cache contains the returned DN, plus the 
 * password used to bind.
 */
typedef struct search_node {
  char *username;		/* Cache key */
  char *dn;			/* DN returned from search */
  char *bindpw;			/* The most recently used bind password; 
				   NULL if the bind failed */
  time_t lastbind;		/* Time of last successful bind */
} search_node;

/*
 * We cache every successful compare operation, using the DN, attrib, and
 * value as the key. 
 */
typedef struct compare_node {
  char *dn;			/* DN, attrib and value combine to be the key */
  char *attrib;			
  char *value;
  time_t lastcompare;
} compare_node;

/*
 * We cache every successful compare dn operation, using the dn in the require
 * statement and the dn fetched based on the client-provided username.
 */
typedef struct dn_compare_node {
  char *reqdn;			/* The DN in the require dn statement */
  char *dn;			/* The DN found in the search */
} dn_compare_node;

#if LDAP_SDK_VERSION <= 2
int ldap_search_ext_s(LDAP *ldap, char *base, int scope, char *filter,
		      char **attrs, int attrsonly, void *servertrls, void *clientctrls,
		      void *timeout, int sizelimit, LDAPMessage **res);
void ldap_memfree(void *p);

/* The const_cast is used to get around the fact that some of the LDAPv2 prototypes
   have non-const parameters, while the same ones in LDAPv3 are const. If compiling
   with LDAPv2, the const_cast casts away the constness, but won't under LDAPv3 */
#define const_cast(x) ((char *)(x))
#else
#define const_cast(x) (x)
#endif /* LDAP_SDK_VERSION */

/* 
 * We want the ldap connections to be long-lived; we'll need one
 * connection for each host/port combination. We keep these in a
 * global linked list, so they're around for as long as the apache
 * process is around.
 *
 * Note that the URLs ldap://ldap and ldap://ldap.airius.com should
 * represent the same connection, but will result in different
 * connections entries in the cache. Caveat emptor.
 */

/* 
 * Values in this enum help us keep track of how this connection is
 * currently bound to the server 
 */
typedef enum {
  bind_none,			/* Not bound */
  bind_system,			/* Bound using config bind creds */
  bind_user			/* Bound using user-supplied creds */
} bindings;

/* Define some errors that are mysteriously gone from OpenLDAP 2.x */
#ifndef LDAP_URL_ERR_NOTLDAP
#if defined(WITH_OPENLDAP) && LDAP_VENDOR_VERSION >= 20000
#define LDAP_URL_ERR_NOTLDAP LDAP_URL_ERR_BADSCHEME
#endif
#endif

#ifndef LDAP_URL_ERR_NODN
#if defined(WITH_OPENLDAP) && LDAP_VENDOR_VERSION >= 20000
#define LDAP_URL_ERR_NODN LDAP_URL_ERR_BADURL
#endif
#endif

struct LDAPconnection {
  LDAP *ldap;
  mutex *mtx;
  char *bounddn;
  char *host;
  int port;
  bindings boundas;
#ifdef HAVE_TLS	
  int withtls;
#endif
  struct LDAPconnection *next;
};

/* auth_ldap global configuration */ 
typedef struct {
  long search_cache_ttl;	/* TTL for search cache */
  long search_cache_size;	/* Size of search cache */
  long compare_cache_ttl;	/* TTL for compare cache */
  long compare_cache_size;	/* Size of compare cache */

  mutex *mtx;
  struct LDAPconnection *ldapconnections;
#ifdef WITH_SSL
  int have_certdb;
#endif
} auth_ldap_server_conf;

#define GROUPATTR_MAX_ELTS 10

/* Values that the deref member can have */
typedef enum {
  never=LDAP_DEREF_NEVER, 
  searching=LDAP_DEREF_SEARCHING, 
  finding=LDAP_DEREF_FINDING, 
  always=LDAP_DEREF_ALWAYS
} deref_options;

/* The actual LDAP auth struct. */
typedef struct {
  int auth_authoritative;	/* Is this auth method the one and only? */
  int enabled;			/* Is auth_ldap enabled in this directory? */

  /* These parameters are all derived from the AuthLDAPURL directive */
  char *url;			/* String representation of the URL */
  char *host;			/* Name of the LDAP server (or space separated list) */
  int port;			/* Port of the LDAP server */
  char *basedn;			/* Base DN to do all searches from */
  char *attribute;		/* Attribute to search for */
  int scope;			/* Scope of the search */
  char *filter;			/* Filter to further limit the search  */
  deref_options deref;		/* how to handle alias dereferening */

#ifdef AUTH_LDAP_FRONTPAGE_HACK
  int frontpage_hack;		/* Hack for frontpage support */
#endif

  char *binddn;			/* DN to bind to server (can be NULL) */
  char *bindpw;			/* Password to bind to server (can be NULL) */

  char *dn;			/* The saved dn from a successful search */
  char *user;			/* The username provided by the client */
  int user_is_dn;		/* If true, connection->user is DN instead of userid */
  int compare_dn_on_server;	/* If true, will use server to do DN compare */

  int have_ldap_url;		/* Set if we have found an LDAP url */
 
  array_header *groupattr;	/* List of Group attributes */
  int group_attrib_is_dn;	/* If true, the group attribute is the DN, otherwise, 
				   it's the exact string passed by the HTTP client */
 
  struct LDAPconnection *ldc;	/* Pointer to an LDAP connection in the linked list */
#ifdef WITH_SSL
  int secure;			/* True if use SSL connection */
#endif

#ifdef HAVE_TLS
  int starttls;                 /* True if StartTLS */
#endif
    
} auth_ldap_config_rec;

struct groupattr_entry {
  char *name;
};
 
/*

On NT, ap_release_mutex returns the result of ReleaseMutex. On Unix,
ap_release_mutex is a macro that evaluates to MULTI_OK (0). However, a
successful result of ReleaseMutex is not MULTI_OK.

Thus, we can't do 

  if (ap_release_mutex(m) != MULTI_OK) { 
    ... warn ... 
  } 

because it will do exactly the opposite of what we want. However, if we do

  ap_release_mutex(m);

then we get a bunch of ugly errors about 'statement with no effect' under Unix. 

So, I define my own release_mutex function.

*/
 
#ifdef MULTITHREAD
#define GETMUTEX(mtx) \
  if (ap_acquire_mutex(mtx) != MULTI_OK) \
    ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, \
		  "Could not acquire connection mutex. Expect deadlocks.")
#define RELMUTEX(m) ap_release_mutex(m)
#else
#define GETMUTEX(mtx)
#define RELMUTEX(m)
#endif

#ifdef WITH_SHARED_LDAP_CACHE
EXTERN AP_MM *auth_ldap_mm;
#endif
EXTERN ald_cache *auth_ldap_cache;

extern void auth_ldap_init_module(server_rec *s, pool *p);
extern void *create_auth_ldap_dir_config(pool *p, char *d);
extern const char *parse_auth_ldap_url(cmd_parms *cmd, auth_ldap_config_rec *sec, 
 			       char *url);
extern const char *auth_ldap_add_group_attribute(cmd_parms *cmd, 
						 auth_ldap_config_rec *sec, 
						 char *arg);
extern const char *auth_ldap_set_cache_ttl(cmd_parms *cmd, void *dummy, char *ttl);
extern const char *auth_ldap_set_cache_size(cmd_parms *cmd, void *dummy, char *size);
extern const char *auth_ldap_set_opcache_ttl(cmd_parms *cmd, void *dummy, char *size);
extern const char *auth_ldap_set_opcache_size(cmd_parms *cmd, void *dummy, char *size);
extern const char *auth_ldap_set_compare_flag(cmd_parms *cmd, void *dummy, char *size);
#ifdef WITH_SSL
extern const char *auth_ldap_set_certdbpath(cmd_parms *cmd, void *dummy, char *path);
#endif
extern const char *auth_ldap_add_redundant(cmd_parms *cmd, 
					   auth_ldap_config_rec *sec, char *f);
extern const char *auth_ldap_set_deref(cmd_parms *cmd, 
				       auth_ldap_config_rec *sec, char *f);
extern void *create_auth_ldap_config(pool *p, server_rec *s);
extern int auth_ldap_display_info(request_rec *r);
extern const char *auth_ldap_version;

int auth_ldap_authbind(const char *, request_rec *, url_node *);
int auth_ldap_comparedn(const char *, const char *, request_rec *, url_node *);
int auth_ldap_compare(const char *, const char *, const char *, 
		      request_rec *, ald_cache *);
int auth_ldap_connect_to_server(request_rec *r);
void auth_ldap_free_connection(request_rec *r, int log);
void auth_ldap_log_reason(request_rec *r, const char *fmt, ...);

unsigned long auth_ldap_search_node_hash(void *);
int auth_ldap_search_node_compare(void *, void *);
void * auth_ldap_search_node_copy(void *);
void auth_ldap_search_node_free(void *);
unsigned long auth_ldap_compare_node_hash(void *);
int auth_ldap_compare_node_compare(void *, void *);
void * auth_ldap_compare_node_copy(void *);
void auth_ldap_compare_node_free(void *);
unsigned long auth_ldap_group_compare_node_hash(void *);
int auth_ldap_group_compare_node_compare(void *, void *);
void * auth_ldap_group_compare_node_copy(void *);
void auth_ldap_group_compare_node_free(void *);
unsigned long auth_ldap_dn_compare_node_hash(void *);
int auth_ldap_dn_compare_node_compare(void *, void *);
void * auth_ldap_dn_compare_node_copy(void *);
void auth_ldap_dn_compare_node_free(void *);
unsigned long auth_ldap_url_node_hash(void *n);
int auth_ldap_url_node_compare(void *a, void *b);
void * auth_ldap_url_node_copy(void *c);
void auth_ldap_url_node_free(void *n);
