]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
Breakout libldap functions into a utility library
authorArran Cudbard-Bell <a.cudbardb@freeradius.org>
Thu, 6 Apr 2017 21:40:44 +0000 (17:40 -0400)
committerArran Cudbard-Bell <a.cudbardb@freeradius.org>
Thu, 6 Apr 2017 21:40:44 +0000 (17:40 -0400)
19 files changed:
src/modules/rlm_ldap/.gitignore
src/modules/rlm_ldap/all.mk.in
src/modules/rlm_ldap/attrmap.c
src/modules/rlm_ldap/clients.c
src/modules/rlm_ldap/configure
src/modules/rlm_ldap/configure.ac
src/modules/rlm_ldap/conn.c [new file with mode: 0644]
src/modules/rlm_ldap/control.c
src/modules/rlm_ldap/directory.c
src/modules/rlm_ldap/groups.c
src/modules/rlm_ldap/ldap.c
src/modules/rlm_ldap/libfreeradius-ldap.mk.in [new file with mode: 0644]
src/modules/rlm_ldap/libldap.h [new file with mode: 0644]
src/modules/rlm_ldap/rlm_ldap.c
src/modules/rlm_ldap/rlm_ldap.h
src/modules/rlm_ldap/rlm_ldap.mk [new file with mode: 0644]
src/modules/rlm_ldap/sasl.c
src/modules/rlm_ldap/user.c [new file with mode: 0644]
src/modules/rlm_ldap/util.c [new file with mode: 0644]

index 01a5daa3cc56f4a9ae0c2cfd7e0cbd567ff7e4c0..8e5e021c7f839a432492e9ffdfbad627d555da37 100644 (file)
@@ -1 +1,2 @@
 all.mk
+libfreeradius-ldap.mk
index 75bd1d819b444e1e2a5d6da1ca40155f3fb24367..fa7a27fa46c1c2fd6088e8c13bb5fcb459552e25 100644 (file)
@@ -1,10 +1 @@
-TARGETNAME     := @targetname@
-
-ifneq "$(TARGETNAME)" ""
-TARGET         := $(TARGETNAME).a
-endif
-
-SOURCES                := $(TARGETNAME).c attrmap.c ldap.c clients.c groups.c edir.c control.c directory.c @SASL@
-
-SRC_CFLAGS     := @mod_cflags@
-TGT_LDLIBS     := @mod_ldflags@
+SUBMAKEFILES := libfreeradius-ldap.mk rlm_ldap.mk
index c9332af9e8cec3207a9f720d93a7d77f8c67aa15..45bd39489957279f1f33c8e3f06fac313397f1b6 100644 (file)
@@ -38,7 +38,7 @@
  */
 int rlm_ldap_map_getvalue(TALLOC_CTX *ctx, VALUE_PAIR **out, REQUEST *request, vp_map_t const *map, void *uctx)
 {
-       rlm_ldap_result_t *self = uctx;
+       fr_ldap_result_t *self = uctx;
        VALUE_PAIR *head = NULL, *vp;
        vp_cursor_t cursor;
        int i;
@@ -309,7 +309,7 @@ int rlm_ldap_map_do(const rlm_ldap_t *inst, REQUEST *request, LDAP *handle,
        unsigned int            total = 0;
        int                     applied = 0;    /* How many maps have been applied to the current request */
 
-       rlm_ldap_result_t       result;
+       fr_ldap_result_t        result;
        char const              *name;
 
        for (map = expanded->maps; map != NULL; map = map->next) {
@@ -366,7 +366,7 @@ int rlm_ldap_map_do(const rlm_ldap_t *inst, REQUEST *request, LDAP *handle,
                        vp_map_t *attr;
                        char *value;
 
-                       value = rlm_ldap_berval_to_string(request, values[i]);
+                       value = fr_ldap_berval_to_string(request, values[i]);
                        RDEBUG3("Parsing attribute string '%s'", value);
                        if (map_afrom_attr_str(request, &attr, value,
                                               REQUEST_CURRENT, PAIR_LIST_REPLY,
index 123dd2a517dad3c563cbd68c527e145bfb0d7172..3b91f56baeba4cf0605006338ff8595e5a86befb 100644 (file)
@@ -86,7 +86,7 @@ static int _get_client_value(char **out, CONF_PAIR const *cp, void *data)
                return 0;
        }
 
-       *out = rlm_ldap_berval_to_string(NULL, values[0]);
+       *out = fr_ldap_berval_to_string(NULL, values[0]);
        ldap_value_free_len(values);
 
        if (!*out) return -1;
@@ -127,7 +127,7 @@ int rlm_ldap_client_load(rlm_ldap_t const *inst, CONF_SECTION *tmpl, CONF_SECTIO
        count++;
 
        /*
-        *      Create an array of LDAP attributes to feed to rlm_ldap_search.
+        *      Create an array of LDAP attributes to feed to fr_ldap_search.
         */
        attrs = talloc_array(inst, char const *, count);
        if (rlm_ldap_client_get_attrs(attrs, &idx, map) < 0) {
@@ -145,9 +145,11 @@ int rlm_ldap_client_load(rlm_ldap_t const *inst, CONF_SECTION *tmpl, CONF_SECTIO
         *      Perform all searches as the admin user.
         */
        if (conn->rebound) {
-               status = rlm_ldap_bind(inst, NULL, &conn,
-                                      conn->pool_inst->admin_identity, conn->pool_inst->admin_password,
-                                      &(conn->pool_inst->admin_sasl), true, NULL, NULL, NULL);
+               status = fr_ldap_bind(NULL, &conn,
+                                     conn->config->admin_identity, conn->config->admin_password,
+                                     &(conn->config->admin_sasl),
+                                     NULL,
+                                     NULL, NULL);
                if (status != LDAP_PROC_SUCCESS) {
                        ret = -1;
                        goto finish;
@@ -158,8 +160,8 @@ int rlm_ldap_client_load(rlm_ldap_t const *inst, CONF_SECTION *tmpl, CONF_SECTIO
                conn->rebound = false;
        }
 
-       status = rlm_ldap_search(&result, inst, NULL, &conn, inst->clientobj_base_dn, inst->clientobj_scope,
-                                inst->clientobj_filter, attrs, NULL, NULL);
+       status = fr_ldap_search(&result, NULL, &conn, inst->clientobj_base_dn, inst->clientobj_scope,
+                               inst->clientobj_filter, attrs, NULL, NULL);
        switch (status) {
        case LDAP_PROC_SUCCESS:
                break;
@@ -203,12 +205,12 @@ int rlm_ldap_client_load(rlm_ldap_t const *inst, CONF_SECTION *tmpl, CONF_SECTIO
 
                        goto finish;
                }
-               rlm_ldap_normalise_dn(dn, dn);
+               fr_ldap_util_normalise_dn(dn, dn);
 
                cp = cf_pair_find(map, "identifier");
                if (cp) {
                        values = ldap_get_values_len(conn->handle, entry, cf_pair_value(cp));
-                       if (values) id = rlm_ldap_berval_to_string(NULL, values[0]);
+                       if (values) id = fr_ldap_berval_to_string(NULL, values[0]);
                        ldap_value_free_len(values);
                }
 
index bba0a878fae422c32a7f994674885e63c212fe88..acb62af22bcc808111d6b2ddc104dc19de05907c 100755 (executable)
@@ -615,7 +615,6 @@ infodir
 docdir
 oldincludedir
 includedir
-runstatedir
 localstatedir
 sharedstatedir
 sysconfdir
@@ -687,7 +686,6 @@ datadir='${datarootdir}'
 sysconfdir='${prefix}/etc'
 sharedstatedir='${prefix}/com'
 localstatedir='${prefix}/var'
-runstatedir='${localstatedir}/run'
 includedir='${prefix}/include'
 oldincludedir='/usr/include'
 docdir='${datarootdir}/doc/${PACKAGE}'
@@ -940,15 +938,6 @@ do
   | -silent | --silent | --silen | --sile | --sil)
     silent=yes ;;
 
-  -runstatedir | --runstatedir | --runstatedi | --runstated \
-  | --runstate | --runstat | --runsta | --runst | --runs \
-  | --run | --ru | --r)
-    ac_prev=runstatedir ;;
-  -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \
-  | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \
-  | --run=* | --ru=* | --r=*)
-    runstatedir=$ac_optarg ;;
-
   -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb)
     ac_prev=sbindir ;;
   -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \
@@ -1086,7 +1075,7 @@ fi
 for ac_var in  exec_prefix prefix bindir sbindir libexecdir datarootdir \
                datadir sysconfdir sharedstatedir localstatedir includedir \
                oldincludedir docdir infodir htmldir dvidir pdfdir psdir \
-               libdir localedir mandir runstatedir
+               libdir localedir mandir
 do
   eval ac_val=\$$ac_var
   # Remove trailing slashes.
@@ -1239,7 +1228,6 @@ Fine tuning of the installation directories:
   --sysconfdir=DIR        read-only single-machine data [PREFIX/etc]
   --sharedstatedir=DIR    modifiable architecture-independent data [PREFIX/com]
   --localstatedir=DIR     modifiable single-machine data [PREFIX/var]
-  --runstatedir=DIR       modifiable per-process data [LOCALSTATEDIR/run]
   --libdir=DIR            object code libraries [EPREFIX/lib]
   --includedir=DIR        C header files [PREFIX/include]
   --oldincludedir=DIR     C header files for non-gcc [/usr/include]
@@ -1913,7 +1901,7 @@ fail=
 SMART_LIBS=
 SMART_CLFAGS=
 SASL=
-if test x$with_rlm_ldap != xno; then
+if test x$with_libfreeradius-ldap != xno; then
 
                                ac_ext=c
 ac_cpp='$CPP $CPPFLAGS'
@@ -3462,20 +3450,20 @@ $as_echo "#define WITH_SASL 1" >>confdefs.h
          fi
        fi
 
-       targetname=rlm_ldap
+       targetname=libfreeradius-ldap
 else
        targetname=
-       echo \*\*\* module rlm_ldap is disabled.
+       echo \*\*\* module libfreeradius-ldap is disabled.
 fi
 
 if test x"$fail" != x""; then
        if test x"${enable_strict_dependencies}" = x"yes"; then
-               as_fn_error $? "set --without-rlm_ldap to disable it explicitly." "$LINENO" 5
+               as_fn_error $? "set --without-libfreeradius-ldap to disable it explicitly." "$LINENO" 5
        else
-               { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: silently not building rlm_ldap." >&5
-$as_echo "$as_me: WARNING: silently not building rlm_ldap." >&2;}
-               { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: FAILURE: rlm_ldap requires: $fail." >&5
-$as_echo "$as_me: WARNING: FAILURE: rlm_ldap requires: $fail." >&2;}
+               { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: silently not building libfreeradius-ldap." >&5
+$as_echo "$as_me: WARNING: silently not building libfreeradius-ldap." >&2;}
+               { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: FAILURE: libfreeradius-ldap requires: $fail." >&5
+$as_echo "$as_me: WARNING: FAILURE: libfreeradius-ldap requires: $fail." >&2;}
                if test x"$headersuggestion" != x; then
                        { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $headersuggestion" >&5
 $as_echo "$as_me: WARNING: $headersuggestion" >&2;}
@@ -3507,10 +3495,11 @@ mod_cflags="$SMART_CPPFLAGS"
 ac_config_headers="$ac_config_headers config.h"
 
 
+
 unset ac_cv_env_LIBS_set
 unset ac_cv_env_LIBS_value
 
-ac_config_files="$ac_config_files all.mk"
+ac_config_files="$ac_config_files libfreeradius-ldap.mk"
 
 cat >confcache <<\_ACEOF
 # This file is a shell script that caches the results of configure
@@ -4202,7 +4191,7 @@ for ac_config_target in $ac_config_targets
 do
   case $ac_config_target in
     "config.h") CONFIG_HEADERS="$CONFIG_HEADERS config.h" ;;
-    "all.mk") CONFIG_FILES="$CONFIG_FILES all.mk" ;;
+    "libfreeradius-ldap.mk") CONFIG_FILES="$CONFIG_FILES libfreeradius-ldap.mk" ;;
 
   *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;;
   esac
@@ -4785,3 +4774,1279 @@ $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;}
 fi
 
 
+
+ac_config_files="$ac_config_files all.mk"
+
+cat >confcache <<\_ACEOF
+# This file is a shell script that caches the results of configure
+# tests run on this system so they can be shared between configure
+# scripts and configure runs, see configure's option --config-cache.
+# It is not useful on other systems.  If it contains results you don't
+# want to keep, you may remove or edit it.
+#
+# config.status only pays attention to the cache file if you give it
+# the --recheck option to rerun configure.
+#
+# `ac_cv_env_foo' variables (set or unset) will be overridden when
+# loading this file, other *unset* `ac_cv_foo' will be assigned the
+# following values.
+
+_ACEOF
+
+# The following way of writing the cache mishandles newlines in values,
+# but we know of no workaround that is simple, portable, and efficient.
+# So, we kill variables containing newlines.
+# Ultrix sh set writes to stderr and can't be redirected directly,
+# and sets the high bit in the cache file unless we assign to the vars.
+(
+  for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do
+    eval ac_val=\$$ac_var
+    case $ac_val in #(
+    *${as_nl}*)
+      case $ac_var in #(
+      *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5
+$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;;
+      esac
+      case $ac_var in #(
+      _ | IFS | as_nl) ;; #(
+      BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #(
+      *) { eval $ac_var=; unset $ac_var;} ;;
+      esac ;;
+    esac
+  done
+
+  (set) 2>&1 |
+    case $as_nl`(ac_space=' '; set) 2>&1` in #(
+    *${as_nl}ac_space=\ *)
+      # `set' does not quote correctly, so add quotes: double-quote
+      # substitution turns \\\\ into \\, and sed turns \\ into \.
+      sed -n \
+       "s/'/'\\\\''/g;
+         s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p"
+      ;; #(
+    *)
+      # `set' quotes correctly as required by POSIX, so do not add quotes.
+      sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p"
+      ;;
+    esac |
+    sort
+) |
+  sed '
+     /^ac_cv_env_/b end
+     t clear
+     :clear
+     s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/
+     t end
+     s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/
+     :end' >>confcache
+if diff "$cache_file" confcache >/dev/null 2>&1; then :; else
+  if test -w "$cache_file"; then
+    if test "x$cache_file" != "x/dev/null"; then
+      { $as_echo "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5
+$as_echo "$as_me: updating cache $cache_file" >&6;}
+      if test ! -f "$cache_file" || test -h "$cache_file"; then
+       cat confcache >"$cache_file"
+      else
+        case $cache_file in #(
+        */* | ?:*)
+         mv -f confcache "$cache_file"$$ &&
+         mv -f "$cache_file"$$ "$cache_file" ;; #(
+        *)
+         mv -f confcache "$cache_file" ;;
+       esac
+      fi
+    fi
+  else
+    { $as_echo "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5
+$as_echo "$as_me: not updating unwritable cache $cache_file" >&6;}
+  fi
+fi
+rm -f confcache
+
+test "x$prefix" = xNONE && prefix=$ac_default_prefix
+# Let make expand exec_prefix.
+test "x$exec_prefix" = xNONE && exec_prefix='${prefix}'
+
+DEFS=-DHAVE_CONFIG_H
+
+ac_libobjs=
+ac_ltlibobjs=
+U=
+for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue
+  # 1. Remove the extension, and $U if already installed.
+  ac_script='s/\$U\././;s/\.o$//;s/\.obj$//'
+  ac_i=`$as_echo "$ac_i" | sed "$ac_script"`
+  # 2. Prepend LIBOBJDIR.  When used with automake>=1.10 LIBOBJDIR
+  #    will be set to the directory where LIBOBJS objects are built.
+  as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext"
+  as_fn_append ac_ltlibobjs " \${LIBOBJDIR}$ac_i"'$U.lo'
+done
+LIBOBJS=$ac_libobjs
+
+LTLIBOBJS=$ac_ltlibobjs
+
+
+
+: "${CONFIG_STATUS=./config.status}"
+ac_write_fail=0
+ac_clean_files_save=$ac_clean_files
+ac_clean_files="$ac_clean_files $CONFIG_STATUS"
+{ $as_echo "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5
+$as_echo "$as_me: creating $CONFIG_STATUS" >&6;}
+as_write_fail=0
+cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1
+#! $SHELL
+# Generated by $as_me.
+# Run this file to recreate the current configuration.
+# Compiler output produced by configure, useful for debugging
+# configure, is in config.log if it exists.
+
+debug=false
+ac_cs_recheck=false
+ac_cs_silent=false
+
+SHELL=\${CONFIG_SHELL-$SHELL}
+export SHELL
+_ASEOF
+cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1
+## -------------------- ##
+## M4sh Initialization. ##
+## -------------------- ##
+
+# Be more Bourne compatible
+DUALCASE=1; export DUALCASE # for MKS sh
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then :
+  emulate sh
+  NULLCMD=:
+  # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which
+  # is contrary to our usage.  Disable this feature.
+  alias -g '${1+"$@"}'='"$@"'
+  setopt NO_GLOB_SUBST
+else
+  case `(set -o) 2>/dev/null` in #(
+  *posix*) :
+    set -o posix ;; #(
+  *) :
+     ;;
+esac
+fi
+
+
+as_nl='
+'
+export as_nl
+# Printing a long string crashes Solaris 7 /usr/bin/printf.
+as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'
+as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo
+as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo
+# Prefer a ksh shell builtin over an external printf program on Solaris,
+# but without wasting forks for bash or zsh.
+if test -z "$BASH_VERSION$ZSH_VERSION" \
+    && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then
+  as_echo='print -r --'
+  as_echo_n='print -rn --'
+elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then
+  as_echo='printf %s\n'
+  as_echo_n='printf %s'
+else
+  if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then
+    as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"'
+    as_echo_n='/usr/ucb/echo -n'
+  else
+    as_echo_body='eval expr "X$1" : "X\\(.*\\)"'
+    as_echo_n_body='eval
+      arg=$1;
+      case $arg in #(
+      *"$as_nl"*)
+       expr "X$arg" : "X\\(.*\\)$as_nl";
+       arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;;
+      esac;
+      expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl"
+    '
+    export as_echo_n_body
+    as_echo_n='sh -c $as_echo_n_body as_echo'
+  fi
+  export as_echo_body
+  as_echo='sh -c $as_echo_body as_echo'
+fi
+
+# The user is always right.
+if test "${PATH_SEPARATOR+set}" != set; then
+  PATH_SEPARATOR=:
+  (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && {
+    (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 ||
+      PATH_SEPARATOR=';'
+  }
+fi
+
+
+# IFS
+# We need space, tab and new line, in precisely that order.  Quoting is
+# there to prevent editors from complaining about space-tab.
+# (If _AS_PATH_WALK were called with IFS unset, it would disable word
+# splitting by setting IFS to empty value.)
+IFS=" ""       $as_nl"
+
+# Find who we are.  Look in the path if we contain no directory separator.
+as_myself=
+case $0 in #((
+  *[\\/]* ) as_myself=$0 ;;
+  *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break
+  done
+IFS=$as_save_IFS
+
+     ;;
+esac
+# We did not find ourselves, most probably we were run as `sh COMMAND'
+# in which case we are not to be found in the path.
+if test "x$as_myself" = x; then
+  as_myself=$0
+fi
+if test ! -f "$as_myself"; then
+  $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2
+  exit 1
+fi
+
+# Unset variables that we do not need and which cause bugs (e.g. in
+# pre-3.0 UWIN ksh).  But do not cause bugs in bash 2.01; the "|| exit 1"
+# suppresses any "Segmentation fault" message there.  '((' could
+# trigger a bug in pdksh 5.2.14.
+for as_var in BASH_ENV ENV MAIL MAILPATH
+do eval test x\${$as_var+set} = xset \
+  && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || :
+done
+PS1='$ '
+PS2='> '
+PS4='+ '
+
+# NLS nuisances.
+LC_ALL=C
+export LC_ALL
+LANGUAGE=C
+export LANGUAGE
+
+# CDPATH.
+(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
+
+
+# as_fn_error STATUS ERROR [LINENO LOG_FD]
+# ----------------------------------------
+# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are
+# provided, also output the error to LOG_FD, referencing LINENO. Then exit the
+# script with STATUS, using 1 if that was 0.
+as_fn_error ()
+{
+  as_status=$1; test $as_status -eq 0 && as_status=1
+  if test "$4"; then
+    as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+    $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4
+  fi
+  $as_echo "$as_me: error: $2" >&2
+  as_fn_exit $as_status
+} # as_fn_error
+
+
+# as_fn_set_status STATUS
+# -----------------------
+# Set $? to STATUS, without forking.
+as_fn_set_status ()
+{
+  return $1
+} # as_fn_set_status
+
+# as_fn_exit STATUS
+# -----------------
+# Exit the shell with STATUS, even in a "trap 0" or "set -e" context.
+as_fn_exit ()
+{
+  set +e
+  as_fn_set_status $1
+  exit $1
+} # as_fn_exit
+
+# as_fn_unset VAR
+# ---------------
+# Portably unset VAR.
+as_fn_unset ()
+{
+  { eval $1=; unset $1;}
+}
+as_unset=as_fn_unset
+# as_fn_append VAR VALUE
+# ----------------------
+# Append the text in VALUE to the end of the definition contained in VAR. Take
+# advantage of any shell optimizations that allow amortized linear growth over
+# repeated appends, instead of the typical quadratic growth present in naive
+# implementations.
+if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then :
+  eval 'as_fn_append ()
+  {
+    eval $1+=\$2
+  }'
+else
+  as_fn_append ()
+  {
+    eval $1=\$$1\$2
+  }
+fi # as_fn_append
+
+# as_fn_arith ARG...
+# ------------------
+# Perform arithmetic evaluation on the ARGs, and store the result in the
+# global $as_val. Take advantage of shells that can avoid forks. The arguments
+# must be portable across $(()) and expr.
+if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then :
+  eval 'as_fn_arith ()
+  {
+    as_val=$(( $* ))
+  }'
+else
+  as_fn_arith ()
+  {
+    as_val=`expr "$@" || test $? -eq 1`
+  }
+fi # as_fn_arith
+
+
+if expr a : '\(a\)' >/dev/null 2>&1 &&
+   test "X`expr 00001 : '.*\(...\)'`" = X001; then
+  as_expr=expr
+else
+  as_expr=false
+fi
+
+if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then
+  as_basename=basename
+else
+  as_basename=false
+fi
+
+if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then
+  as_dirname=dirname
+else
+  as_dirname=false
+fi
+
+as_me=`$as_basename -- "$0" ||
+$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \
+        X"$0" : 'X\(//\)$' \| \
+        X"$0" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X/"$0" |
+    sed '/^.*\/\([^/][^/]*\)\/*$/{
+           s//\1/
+           q
+         }
+         /^X\/\(\/\/\)$/{
+           s//\1/
+           q
+         }
+         /^X\/\(\/\).*/{
+           s//\1/
+           q
+         }
+         s/.*/./; q'`
+
+# Avoid depending upon Character Ranges.
+as_cr_letters='abcdefghijklmnopqrstuvwxyz'
+as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+as_cr_Letters=$as_cr_letters$as_cr_LETTERS
+as_cr_digits='0123456789'
+as_cr_alnum=$as_cr_Letters$as_cr_digits
+
+ECHO_C= ECHO_N= ECHO_T=
+case `echo -n x` in #(((((
+-n*)
+  case `echo 'xy\c'` in
+  *c*) ECHO_T='        ';;     # ECHO_T is single tab character.
+  xy)  ECHO_C='\c';;
+  *)   echo `echo ksh88 bug on AIX 6.1` > /dev/null
+       ECHO_T='        ';;
+  esac;;
+*)
+  ECHO_N='-n';;
+esac
+
+rm -f conf$$ conf$$.exe conf$$.file
+if test -d conf$$.dir; then
+  rm -f conf$$.dir/conf$$.file
+else
+  rm -f conf$$.dir
+  mkdir conf$$.dir 2>/dev/null
+fi
+if (echo >conf$$.file) 2>/dev/null; then
+  if ln -s conf$$.file conf$$ 2>/dev/null; then
+    as_ln_s='ln -s'
+    # ... but there are two gotchas:
+    # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail.
+    # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable.
+    # In both cases, we have to default to `cp -pR'.
+    ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe ||
+      as_ln_s='cp -pR'
+  elif ln conf$$.file conf$$ 2>/dev/null; then
+    as_ln_s=ln
+  else
+    as_ln_s='cp -pR'
+  fi
+else
+  as_ln_s='cp -pR'
+fi
+rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file
+rmdir conf$$.dir 2>/dev/null
+
+
+# as_fn_mkdir_p
+# -------------
+# Create "$as_dir" as a directory, including parents if necessary.
+as_fn_mkdir_p ()
+{
+
+  case $as_dir in #(
+  -*) as_dir=./$as_dir;;
+  esac
+  test -d "$as_dir" || eval $as_mkdir_p || {
+    as_dirs=
+    while :; do
+      case $as_dir in #(
+      *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'(
+      *) as_qdir=$as_dir;;
+      esac
+      as_dirs="'$as_qdir' $as_dirs"
+      as_dir=`$as_dirname -- "$as_dir" ||
+$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+        X"$as_dir" : 'X\(//\)[^/]' \| \
+        X"$as_dir" : 'X\(//\)$' \| \
+        X"$as_dir" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$as_dir" |
+    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+           s//\1/
+           q
+         }
+         /^X\(\/\/\)[^/].*/{
+           s//\1/
+           q
+         }
+         /^X\(\/\/\)$/{
+           s//\1/
+           q
+         }
+         /^X\(\/\).*/{
+           s//\1/
+           q
+         }
+         s/.*/./; q'`
+      test -d "$as_dir" && break
+    done
+    test -z "$as_dirs" || eval "mkdir $as_dirs"
+  } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir"
+
+
+} # as_fn_mkdir_p
+if mkdir -p . 2>/dev/null; then
+  as_mkdir_p='mkdir -p "$as_dir"'
+else
+  test -d ./-p && rmdir ./-p
+  as_mkdir_p=false
+fi
+
+
+# as_fn_executable_p FILE
+# -----------------------
+# Test if FILE is an executable regular file.
+as_fn_executable_p ()
+{
+  test -f "$1" && test -x "$1"
+} # as_fn_executable_p
+as_test_x='test -x'
+as_executable_p=as_fn_executable_p
+
+# Sed expression to map a string onto a valid CPP name.
+as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'"
+
+# Sed expression to map a string onto a valid variable name.
+as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'"
+
+
+exec 6>&1
+## ----------------------------------- ##
+## Main body of $CONFIG_STATUS script. ##
+## ----------------------------------- ##
+_ASEOF
+test $as_write_fail = 0 && chmod +x $CONFIG_STATUS || ac_write_fail=1
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+# Save the log message, to keep $0 and so on meaningful, and to
+# report actual input values of CONFIG_FILES etc. instead of their
+# values after options handling.
+ac_log="
+This file was extended by $as_me, which was
+generated by GNU Autoconf 2.69.  Invocation command line was
+
+  CONFIG_FILES    = $CONFIG_FILES
+  CONFIG_HEADERS  = $CONFIG_HEADERS
+  CONFIG_LINKS    = $CONFIG_LINKS
+  CONFIG_COMMANDS = $CONFIG_COMMANDS
+  $ $0 $@
+
+on `(hostname || uname -n) 2>/dev/null | sed 1q`
+"
+
+_ACEOF
+
+case $ac_config_files in *"
+"*) set x $ac_config_files; shift; ac_config_files=$*;;
+esac
+
+case $ac_config_headers in *"
+"*) set x $ac_config_headers; shift; ac_config_headers=$*;;
+esac
+
+
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+# Files that config.status was made for.
+config_files="$ac_config_files"
+config_headers="$ac_config_headers"
+
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+ac_cs_usage="\
+\`$as_me' instantiates files and other configuration actions
+from templates according to the current configuration.  Unless the files
+and actions are specified as TAGs, all are instantiated by default.
+
+Usage: $0 [OPTION]... [TAG]...
+
+  -h, --help       print this help, then exit
+  -V, --version    print version number and configuration settings, then exit
+      --config     print configuration, then exit
+  -q, --quiet, --silent
+                   do not print progress messages
+  -d, --debug      don't remove temporary files
+      --recheck    update $as_me by reconfiguring in the same conditions
+      --file=FILE[:TEMPLATE]
+                   instantiate the configuration file FILE
+      --header=FILE[:TEMPLATE]
+                   instantiate the configuration header FILE
+
+Configuration files:
+$config_files
+
+Configuration headers:
+$config_headers
+
+Report bugs to the package provider."
+
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
+ac_cs_version="\\
+config.status
+configured by $0, generated by GNU Autoconf 2.69,
+  with options \\"\$ac_cs_config\\"
+
+Copyright (C) 2012 Free Software Foundation, Inc.
+This config.status script is free software; the Free Software Foundation
+gives unlimited permission to copy, distribute and modify it."
+
+ac_pwd='$ac_pwd'
+srcdir='$srcdir'
+test -n "\$AWK" || AWK=awk
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+# The default lists apply if the user does not specify any file.
+ac_need_defaults=:
+while test $# != 0
+do
+  case $1 in
+  --*=?*)
+    ac_option=`expr "X$1" : 'X\([^=]*\)='`
+    ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'`
+    ac_shift=:
+    ;;
+  --*=)
+    ac_option=`expr "X$1" : 'X\([^=]*\)='`
+    ac_optarg=
+    ac_shift=:
+    ;;
+  *)
+    ac_option=$1
+    ac_optarg=$2
+    ac_shift=shift
+    ;;
+  esac
+
+  case $ac_option in
+  # Handling of the options.
+  -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r)
+    ac_cs_recheck=: ;;
+  --version | --versio | --versi | --vers | --ver | --ve | --v | -V )
+    $as_echo "$ac_cs_version"; exit ;;
+  --config | --confi | --conf | --con | --co | --c )
+    $as_echo "$ac_cs_config"; exit ;;
+  --debug | --debu | --deb | --de | --d | -d )
+    debug=: ;;
+  --file | --fil | --fi | --f )
+    $ac_shift
+    case $ac_optarg in
+    *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;;
+    '') as_fn_error $? "missing file argument" ;;
+    esac
+    as_fn_append CONFIG_FILES " '$ac_optarg'"
+    ac_need_defaults=false;;
+  --header | --heade | --head | --hea )
+    $ac_shift
+    case $ac_optarg in
+    *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;;
+    esac
+    as_fn_append CONFIG_HEADERS " '$ac_optarg'"
+    ac_need_defaults=false;;
+  --he | --h)
+    # Conflict between --help and --header
+    as_fn_error $? "ambiguous option: \`$1'
+Try \`$0 --help' for more information.";;
+  --help | --hel | -h )
+    $as_echo "$ac_cs_usage"; exit ;;
+  -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+  | -silent | --silent | --silen | --sile | --sil | --si | --s)
+    ac_cs_silent=: ;;
+
+  # This is an error.
+  -*) as_fn_error $? "unrecognized option: \`$1'
+Try \`$0 --help' for more information." ;;
+
+  *) as_fn_append ac_config_targets " $1"
+     ac_need_defaults=false ;;
+
+  esac
+  shift
+done
+
+ac_configure_extra_args=
+
+if $ac_cs_silent; then
+  exec 6>/dev/null
+  ac_configure_extra_args="$ac_configure_extra_args --silent"
+fi
+
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+if \$ac_cs_recheck; then
+  set X $SHELL '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion
+  shift
+  \$as_echo "running CONFIG_SHELL=$SHELL \$*" >&6
+  CONFIG_SHELL='$SHELL'
+  export CONFIG_SHELL
+  exec "\$@"
+fi
+
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+exec 5>>config.log
+{
+  echo
+  sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX
+## Running $as_me. ##
+_ASBOX
+  $as_echo "$ac_log"
+} >&5
+
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+
+# Handling of arguments.
+for ac_config_target in $ac_config_targets
+do
+  case $ac_config_target in
+    "config.h") CONFIG_HEADERS="$CONFIG_HEADERS config.h" ;;
+    "libfreeradius-ldap.mk") CONFIG_FILES="$CONFIG_FILES libfreeradius-ldap.mk" ;;
+    "all.mk") CONFIG_FILES="$CONFIG_FILES all.mk" ;;
+
+  *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;;
+  esac
+done
+
+
+# If the user did not use the arguments to specify the items to instantiate,
+# then the envvar interface is used.  Set only those that are not.
+# We use the long form for the default assignment because of an extremely
+# bizarre bug on SunOS 4.1.3.
+if $ac_need_defaults; then
+  test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files
+  test "${CONFIG_HEADERS+set}" = set || CONFIG_HEADERS=$config_headers
+fi
+
+# Have a temporary directory for convenience.  Make it in the build tree
+# simply because there is no reason against having it here, and in addition,
+# creating and moving files from /tmp can sometimes cause problems.
+# Hook for its removal unless debugging.
+# Note that there is a small window in which the directory will not be cleaned:
+# after its creation but before its name has been assigned to `$tmp'.
+$debug ||
+{
+  tmp= ac_tmp=
+  trap 'exit_status=$?
+  : "${ac_tmp:=$tmp}"
+  { test ! -d "$ac_tmp" || rm -fr "$ac_tmp"; } && exit $exit_status
+' 0
+  trap 'as_fn_exit 1' 1 2 13 15
+}
+# Create a (secure) tmp directory for tmp files.
+
+{
+  tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` &&
+  test -d "$tmp"
+}  ||
+{
+  tmp=./conf$$-$RANDOM
+  (umask 077 && mkdir "$tmp")
+} || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5
+ac_tmp=$tmp
+
+# Set up the scripts for CONFIG_FILES section.
+# No need to generate them if there are no CONFIG_FILES.
+# This happens for instance with `./config.status config.h'.
+if test -n "$CONFIG_FILES"; then
+
+
+ac_cr=`echo X | tr X '\015'`
+# On cygwin, bash can eat \r inside `` if the user requested igncr.
+# But we know of no other shell where ac_cr would be empty at this
+# point, so we can use a bashism as a fallback.
+if test "x$ac_cr" = x; then
+  eval ac_cr=\$\'\\r\'
+fi
+ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' </dev/null 2>/dev/null`
+if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then
+  ac_cs_awk_cr='\\r'
+else
+  ac_cs_awk_cr=$ac_cr
+fi
+
+echo 'BEGIN {' >"$ac_tmp/subs1.awk" &&
+_ACEOF
+
+
+{
+  echo "cat >conf$$subs.awk <<_ACEOF" &&
+  echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' &&
+  echo "_ACEOF"
+} >conf$$subs.sh ||
+  as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5
+ac_delim_num=`echo "$ac_subst_vars" | grep -c '^'`
+ac_delim='%!_!# '
+for ac_last_try in false false false false false :; do
+  . ./conf$$subs.sh ||
+    as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5
+
+  ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | grep -c X`
+  if test $ac_delim_n = $ac_delim_num; then
+    break
+  elif $ac_last_try; then
+    as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5
+  else
+    ac_delim="$ac_delim!$ac_delim _$ac_delim!! "
+  fi
+done
+rm -f conf$$subs.sh
+
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK &&
+_ACEOF
+sed -n '
+h
+s/^/S["/; s/!.*/"]=/
+p
+g
+s/^[^!]*!//
+:repl
+t repl
+s/'"$ac_delim"'$//
+t delim
+:nl
+h
+s/\(.\{148\}\)..*/\1/
+t more1
+s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/
+p
+n
+b repl
+:more1
+s/["\\]/\\&/g; s/^/"/; s/$/"\\/
+p
+g
+s/.\{148\}//
+t nl
+:delim
+h
+s/\(.\{148\}\)..*/\1/
+t more2
+s/["\\]/\\&/g; s/^/"/; s/$/"/
+p
+b
+:more2
+s/["\\]/\\&/g; s/^/"/; s/$/"\\/
+p
+g
+s/.\{148\}//
+t delim
+' <conf$$subs.awk | sed '
+/^[^""]/{
+  N
+  s/\n//
+}
+' >>$CONFIG_STATUS || ac_write_fail=1
+rm -f conf$$subs.awk
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+_ACAWK
+cat >>"\$ac_tmp/subs1.awk" <<_ACAWK &&
+  for (key in S) S_is_set[key] = 1
+  FS = "\a"
+
+}
+{
+  line = $ 0
+  nfields = split(line, field, "@")
+  substed = 0
+  len = length(field[1])
+  for (i = 2; i < nfields; i++) {
+    key = field[i]
+    keylen = length(key)
+    if (S_is_set[key]) {
+      value = S[key]
+      line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3)
+      len += length(value) + length(field[++i])
+      substed = 1
+    } else
+      len += 1 + keylen
+  }
+
+  print line
+}
+
+_ACAWK
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then
+  sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g"
+else
+  cat
+fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \
+  || as_fn_error $? "could not setup config files machinery" "$LINENO" 5
+_ACEOF
+
+# VPATH may cause trouble with some makes, so we remove sole $(srcdir),
+# ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and
+# trailing colons and then remove the whole line if VPATH becomes empty
+# (actually we leave an empty line to preserve line numbers).
+if test "x$srcdir" = x.; then
+  ac_vpsub='/^[         ]*VPATH[        ]*=[    ]*/{
+h
+s///
+s/^/:/
+s/[     ]*$/:/
+s/:\$(srcdir):/:/g
+s/:\${srcdir}:/:/g
+s/:@srcdir@:/:/g
+s/^:*//
+s/:*$//
+x
+s/\(=[  ]*\).*/\1/
+G
+s/\n//
+s/^[^=]*=[      ]*$//
+}'
+fi
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+fi # test -n "$CONFIG_FILES"
+
+# Set up the scripts for CONFIG_HEADERS section.
+# No need to generate them if there are no CONFIG_HEADERS.
+# This happens for instance with `./config.status Makefile'.
+if test -n "$CONFIG_HEADERS"; then
+cat >"$ac_tmp/defines.awk" <<\_ACAWK ||
+BEGIN {
+_ACEOF
+
+# Transform confdefs.h into an awk script `defines.awk', embedded as
+# here-document in config.status, that substitutes the proper values into
+# config.h.in to produce config.h.
+
+# Create a delimiter string that does not exist in confdefs.h, to ease
+# handling of long lines.
+ac_delim='%!_!# '
+for ac_last_try in false false :; do
+  ac_tt=`sed -n "/$ac_delim/p" confdefs.h`
+  if test -z "$ac_tt"; then
+    break
+  elif $ac_last_try; then
+    as_fn_error $? "could not make $CONFIG_HEADERS" "$LINENO" 5
+  else
+    ac_delim="$ac_delim!$ac_delim _$ac_delim!! "
+  fi
+done
+
+# For the awk script, D is an array of macro values keyed by name,
+# likewise P contains macro parameters if any.  Preserve backslash
+# newline sequences.
+
+ac_word_re=[_$as_cr_Letters][_$as_cr_alnum]*
+sed -n '
+s/.\{148\}/&'"$ac_delim"'/g
+t rset
+:rset
+s/^[    ]*#[    ]*define[       ][      ]*/ /
+t def
+d
+:def
+s/\\$//
+t bsnl
+s/["\\]/\\&/g
+s/^ \('"$ac_word_re"'\)\(([^()]*)\)[    ]*\(.*\)/P["\1"]="\2"\
+D["\1"]=" \3"/p
+s/^ \('"$ac_word_re"'\)[        ]*\(.*\)/D["\1"]=" \2"/p
+d
+:bsnl
+s/["\\]/\\&/g
+s/^ \('"$ac_word_re"'\)\(([^()]*)\)[    ]*\(.*\)/P["\1"]="\2"\
+D["\1"]=" \3\\\\\\n"\\/p
+t cont
+s/^ \('"$ac_word_re"'\)[        ]*\(.*\)/D["\1"]=" \2\\\\\\n"\\/p
+t cont
+d
+:cont
+n
+s/.\{148\}/&'"$ac_delim"'/g
+t clear
+:clear
+s/\\$//
+t bsnlc
+s/["\\]/\\&/g; s/^/"/; s/$/"/p
+d
+:bsnlc
+s/["\\]/\\&/g; s/^/"/; s/$/\\\\\\n"\\/p
+b cont
+' <confdefs.h | sed '
+s/'"$ac_delim"'/"\\\
+"/g' >>$CONFIG_STATUS || ac_write_fail=1
+
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+  for (key in D) D_is_set[key] = 1
+  FS = "\a"
+}
+/^[\t ]*#[\t ]*(define|undef)[\t ]+$ac_word_re([\t (]|\$)/ {
+  line = \$ 0
+  split(line, arg, " ")
+  if (arg[1] == "#") {
+    defundef = arg[2]
+    mac1 = arg[3]
+  } else {
+    defundef = substr(arg[1], 2)
+    mac1 = arg[2]
+  }
+  split(mac1, mac2, "(") #)
+  macro = mac2[1]
+  prefix = substr(line, 1, index(line, defundef) - 1)
+  if (D_is_set[macro]) {
+    # Preserve the white space surrounding the "#".
+    print prefix "define", macro P[macro] D[macro]
+    next
+  } else {
+    # Replace #undef with comments.  This is necessary, for example,
+    # in the case of _POSIX_SOURCE, which is predefined and required
+    # on some systems where configure will not decide to define it.
+    if (defundef == "undef") {
+      print "/*", prefix defundef, macro, "*/"
+      next
+    }
+  }
+}
+{ print }
+_ACAWK
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+  as_fn_error $? "could not setup config headers machinery" "$LINENO" 5
+fi # test -n "$CONFIG_HEADERS"
+
+
+eval set X "  :F $CONFIG_FILES  :H $CONFIG_HEADERS    "
+shift
+for ac_tag
+do
+  case $ac_tag in
+  :[FHLC]) ac_mode=$ac_tag; continue;;
+  esac
+  case $ac_mode$ac_tag in
+  :[FHL]*:*);;
+  :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5;;
+  :[FH]-) ac_tag=-:-;;
+  :[FH]*) ac_tag=$ac_tag:$ac_tag.in;;
+  esac
+  ac_save_IFS=$IFS
+  IFS=:
+  set x $ac_tag
+  IFS=$ac_save_IFS
+  shift
+  ac_file=$1
+  shift
+
+  case $ac_mode in
+  :L) ac_source=$1;;
+  :[FH])
+    ac_file_inputs=
+    for ac_f
+    do
+      case $ac_f in
+      -) ac_f="$ac_tmp/stdin";;
+      *) # Look for the file first in the build tree, then in the source tree
+        # (if the path is not absolute).  The absolute path cannot be DOS-style,
+        # because $ac_f cannot contain `:'.
+        test -f "$ac_f" ||
+          case $ac_f in
+          [\\/$]*) false;;
+          *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";;
+          esac ||
+          as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;;
+      esac
+      case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac
+      as_fn_append ac_file_inputs " '$ac_f'"
+    done
+
+    # Let's still pretend it is `configure' which instantiates (i.e., don't
+    # use $as_me), people would be surprised to read:
+    #    /* config.h.  Generated by config.status.  */
+    configure_input='Generated from '`
+         $as_echo "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g'
+       `' by configure.'
+    if test x"$ac_file" != x-; then
+      configure_input="$ac_file.  $configure_input"
+      { $as_echo "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5
+$as_echo "$as_me: creating $ac_file" >&6;}
+    fi
+    # Neutralize special characters interpreted by sed in replacement strings.
+    case $configure_input in #(
+    *\&* | *\|* | *\\* )
+       ac_sed_conf_input=`$as_echo "$configure_input" |
+       sed 's/[\\\\&|]/\\\\&/g'`;; #(
+    *) ac_sed_conf_input=$configure_input;;
+    esac
+
+    case $ac_tag in
+    *:-:* | *:-) cat >"$ac_tmp/stdin" \
+      || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;;
+    esac
+    ;;
+  esac
+
+  ac_dir=`$as_dirname -- "$ac_file" ||
+$as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+        X"$ac_file" : 'X\(//\)[^/]' \| \
+        X"$ac_file" : 'X\(//\)$' \| \
+        X"$ac_file" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$ac_file" |
+    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+           s//\1/
+           q
+         }
+         /^X\(\/\/\)[^/].*/{
+           s//\1/
+           q
+         }
+         /^X\(\/\/\)$/{
+           s//\1/
+           q
+         }
+         /^X\(\/\).*/{
+           s//\1/
+           q
+         }
+         s/.*/./; q'`
+  as_dir="$ac_dir"; as_fn_mkdir_p
+  ac_builddir=.
+
+case "$ac_dir" in
+.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;;
+*)
+  ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'`
+  # A ".." for each directory in $ac_dir_suffix.
+  ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'`
+  case $ac_top_builddir_sub in
+  "") ac_top_builddir_sub=. ac_top_build_prefix= ;;
+  *)  ac_top_build_prefix=$ac_top_builddir_sub/ ;;
+  esac ;;
+esac
+ac_abs_top_builddir=$ac_pwd
+ac_abs_builddir=$ac_pwd$ac_dir_suffix
+# for backward compatibility:
+ac_top_builddir=$ac_top_build_prefix
+
+case $srcdir in
+  .)  # We are building in place.
+    ac_srcdir=.
+    ac_top_srcdir=$ac_top_builddir_sub
+    ac_abs_top_srcdir=$ac_pwd ;;
+  [\\/]* | ?:[\\/]* )  # Absolute name.
+    ac_srcdir=$srcdir$ac_dir_suffix;
+    ac_top_srcdir=$srcdir
+    ac_abs_top_srcdir=$srcdir ;;
+  *) # Relative name.
+    ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix
+    ac_top_srcdir=$ac_top_build_prefix$srcdir
+    ac_abs_top_srcdir=$ac_pwd/$srcdir ;;
+esac
+ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix
+
+
+  case $ac_mode in
+  :F)
+  #
+  # CONFIG_FILE
+  #
+
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+# If the template does not know about datarootdir, expand it.
+# FIXME: This hack should be removed a few years after 2.60.
+ac_datarootdir_hack=; ac_datarootdir_seen=
+ac_sed_dataroot='
+/datarootdir/ {
+  p
+  q
+}
+/@datadir@/p
+/@docdir@/p
+/@infodir@/p
+/@localedir@/p
+/@mandir@/p'
+case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in
+*datarootdir*) ac_datarootdir_seen=yes;;
+*@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*)
+  { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5
+$as_echo "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;}
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+  ac_datarootdir_hack='
+  s&@datadir@&$datadir&g
+  s&@docdir@&$docdir&g
+  s&@infodir@&$infodir&g
+  s&@localedir@&$localedir&g
+  s&@mandir@&$mandir&g
+  s&\\\${datarootdir}&$datarootdir&g' ;;
+esac
+_ACEOF
+
+# Neutralize VPATH when `$srcdir' = `.'.
+# Shell code in configure.ac might set extrasub.
+# FIXME: do we really want to maintain this feature?
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+ac_sed_extra="$ac_vpsub
+$extrasub
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+:t
+/@[a-zA-Z_][a-zA-Z_0-9]*@/!b
+s|@configure_input@|$ac_sed_conf_input|;t t
+s&@top_builddir@&$ac_top_builddir_sub&;t t
+s&@top_build_prefix@&$ac_top_build_prefix&;t t
+s&@srcdir@&$ac_srcdir&;t t
+s&@abs_srcdir@&$ac_abs_srcdir&;t t
+s&@top_srcdir@&$ac_top_srcdir&;t t
+s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t
+s&@builddir@&$ac_builddir&;t t
+s&@abs_builddir@&$ac_abs_builddir&;t t
+s&@abs_top_builddir@&$ac_abs_top_builddir&;t t
+$ac_datarootdir_hack
+"
+eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \
+  >$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5
+
+test -z "$ac_datarootdir_hack$ac_datarootdir_seen" &&
+  { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } &&
+  { ac_out=`sed -n '/^[         ]*datarootdir[  ]*:*=/p' \
+      "$ac_tmp/out"`; test -z "$ac_out"; } &&
+  { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir'
+which seems to be undefined.  Please make sure it is defined" >&5
+$as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir'
+which seems to be undefined.  Please make sure it is defined" >&2;}
+
+  rm -f "$ac_tmp/stdin"
+  case $ac_file in
+  -) cat "$ac_tmp/out" && rm -f "$ac_tmp/out";;
+  *) rm -f "$ac_file" && mv "$ac_tmp/out" "$ac_file";;
+  esac \
+  || as_fn_error $? "could not create $ac_file" "$LINENO" 5
+ ;;
+  :H)
+  #
+  # CONFIG_HEADER
+  #
+  if test x"$ac_file" != x-; then
+    {
+      $as_echo "/* $configure_input  */" \
+      && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs"
+    } >"$ac_tmp/config.h" \
+      || as_fn_error $? "could not create $ac_file" "$LINENO" 5
+    if diff "$ac_file" "$ac_tmp/config.h" >/dev/null 2>&1; then
+      { $as_echo "$as_me:${as_lineno-$LINENO}: $ac_file is unchanged" >&5
+$as_echo "$as_me: $ac_file is unchanged" >&6;}
+    else
+      rm -f "$ac_file"
+      mv "$ac_tmp/config.h" "$ac_file" \
+       || as_fn_error $? "could not create $ac_file" "$LINENO" 5
+    fi
+  else
+    $as_echo "/* $configure_input  */" \
+      && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs" \
+      || as_fn_error $? "could not create -" "$LINENO" 5
+  fi
+ ;;
+
+
+  esac
+
+done # for ac_tag
+
+
+as_fn_exit 0
+_ACEOF
+ac_clean_files=$ac_clean_files_save
+
+test $ac_write_fail = 0 ||
+  as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5
+
+
+# configure is writing to config.log, and then calls config.status.
+# config.status does its own redirection, appending to config.log.
+# Unfortunately, on DOS this fails, as config.log is still kept open
+# by configure, so config.status won't be able to write to it; its
+# output is simply discarded.  So we exec the FD to /dev/null,
+# effectively closing config.log, so it can be properly (re)opened and
+# appended to by config.status.  When coming back to configure, we
+# need to make the FD available again.
+if test "$no_create" != yes; then
+  ac_cs_success=:
+  ac_config_status_args=
+  test "$silent" = yes &&
+    ac_config_status_args="$ac_config_status_args --quiet"
+  exec 5>/dev/null
+  $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false
+  exec 5>>config.log
+  # Use ||, not &&, to avoid exiting from the if with $? = 1, which
+  # would make configure fail if this is the last instruction.
+  $ac_cs_success || as_fn_exit 1
+fi
+if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5
+$as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;}
+fi
+
index 107eaba8a8212f8163c26de35a0c6f8dc69ae156..35c0e43894f2776ac15c612b8972697bfc7e679a 100644 (file)
@@ -1,7 +1,7 @@
 AC_PREREQ([2.53])
 AC_INIT(rlm_ldap.c)
 AC_REVISION($Revision$)
-AC_DEFUN(modname,[rlm_ldap])
+AC_DEFUN(modname,[libfreeradius-ldap])
 
 fail=
 SMART_LIBS=
@@ -161,4 +161,10 @@ AC_SUBST(mod_cflags)
 AC_SUBST(SASL)
 AC_SUBST(targetname)
 AC_CONFIG_HEADER(config.h)
+
+AC_OUTPUT(libfreeradius-ldap.mk)
+
+dnl # This is needed so that all.mk is not created unless configure is run
+dnl # so that it doesn't attempt to include libfreeradius-redis.mk which
+dnl # is also dependent on configure being run.
 AC_OUTPUT(all.mk)
diff --git a/src/modules/rlm_ldap/conn.c b/src/modules/rlm_ldap/conn.c
new file mode 100644 (file)
index 0000000..86b2abc
--- /dev/null
@@ -0,0 +1,150 @@
+/*
+ *   This program is is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or (at
+ *   your option) any later version.
+ *
+ *   This program 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.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * $Id$
+ * @file rlm_ldap.c
+ * @brief Connection wrappers
+ *
+ * @author Arran Cudbard-Bell <a.cudbardb@freeradius.org>
+ *
+ * @copyright 2017 The FreeRADIUS Server Project.
+ */
+RCSID("$Id$")
+
+#define LOG_PREFIX "%s - "
+#define LOG_PREFIX_ARGS handle_config->name
+
+#include <freeradius-devel/rad_assert.h>
+
+#include "rlm_ldap.h"
+
+/** Gets an LDAP socket from the connection pool
+ *
+ * Retrieve a socket from the connection pool, or NULL on error (of if no sockets are available).
+ *
+ * @param inst rlm_ldap configuration.
+ * @param request Current request (may be NULL).
+ */
+ldap_handle_t *mod_conn_get(rlm_ldap_t const *inst, REQUEST *request)
+{
+       ldap_handle_t *conn;
+
+       conn = fr_connection_get(inst->pool, request);
+
+       rad_assert(!conn || conn->config);
+
+#ifdef LDAP_CONTROL_X_SESSION_TRACKING
+       /*
+        *      Add optional session tracking controls,
+        *      that contain values of some attributes
+        *      in the request.
+        */
+       if ((conn != NULL) && (request != NULL) && inst->session_tracking) {
+               if (rlm_ldap_control_add_session_tracking(conn, request) < 0) {
+                       fr_connection_release(inst->pool, request, conn);
+                       return NULL;
+               }
+       }
+#endif
+       return conn;
+}
+
+/** Releases an LDAP socket back to the connection pool
+ *
+ * If the socket was rebound chasing a referral onto another server then we destroy it.
+ * If the socket was rebound to another user on the same server, we let the next caller rebind it.
+ *
+ * @param inst rlm_ldap configuration.
+ * @param request The current request.
+ * @param conn to release.
+ */
+void mod_conn_release(rlm_ldap_t const *inst, REQUEST *request, ldap_handle_t *conn)
+{
+       /*
+        *      Could have already been free'd due to a previous error.
+        */
+       if (!conn) return;
+
+       /*
+        *      Clear any client/server controls associated with the connection.
+        */
+       fr_ldap_control_clear(conn);
+
+       /*
+        *      We chased a referral to another server.
+        *
+        *      This connection is no longer part of the pool which is
+        *      connected to and bound to the configured server.
+        *      Close it.
+        *
+        *      Note that we do NOT close it if it was bound to another user.
+        *      Instead, we let the next caller do the rebind.
+        */
+       if (conn->referred) {
+               fr_connection_close(inst->pool, request, conn);
+               return;
+       }
+
+       fr_connection_release(inst->pool, request, conn);
+       return;
+}
+
+/** Create and return a new connection
+ *
+ * Create a new ldap connection and allocate memory for a new rlm_handle_t
+ */
+void *mod_conn_create(TALLOC_CTX *ctx, void *instance, struct timeval const *timeout)
+{
+       ldap_rcode_t                    status;
+       ldap_handle_t                   *conn;
+       ldap_handle_config_t const      *handle_config = instance;      /* Not talloced */
+
+       conn = fr_ldap_conn_alloc(ctx, handle_config);
+       if (!conn) return NULL;
+
+       fr_ldap_conn_timeout_set(conn, timeout);
+       if (handle_config->start_tls) {
+               if (ldap_start_tls_s(conn->handle, NULL, NULL) != LDAP_SUCCESS) {
+                       int ldap_errno;
+
+                       ldap_get_option(conn->handle, LDAP_OPT_ERROR_NUMBER, &ldap_errno);
+
+                       ERROR("Could not start TLS: %s", ldap_err2string(ldap_errno));
+
+               error:
+                       talloc_free(conn);
+
+                       return NULL;
+               }
+       }
+
+       status = fr_ldap_bind(NULL,
+                             &conn,
+                             conn->config->admin_identity, conn->config->admin_password,
+                             &(conn->config->admin_sasl),
+                             timeout,
+                             NULL, NULL);
+       if (status != LDAP_PROC_SUCCESS) goto error;
+       fr_ldap_conn_timeout_reset(conn);
+
+       /*
+        *      Only error out on memory allocation errors
+        */
+       if (fr_ldap_directory_alloc(conn, &conn->directory, &conn) < 0) goto error;
+
+       return conn;
+}
index 8b88578ff75d5db698b545a8ba3565b1dcafec1e..e6b73f87ea314bc3c9b865b058f528c54f229795 100644 (file)
 
 /**
  * $Id$
- * @file src/modules/rlm_ldap/control.c
+ * @file src/modules/fr_ldap/control.c
  * @brief Functions for managing server/client side sort controls.
  *
  * @author Arran Cudbard-Bell <a.cudbardb@freeradius.org>
  * @copyright 2015 Arran Cudbard-Bell <a.cudbardb@freeradius.org>
  */
-#define LOG_PREFIX "rlm_ldap (%s) - "
+#define LOG_PREFIX "fr_ldap (%s) - "
 #define LOG_PREFIX_ARGS inst->name
 
 #include "rlm_ldap.h"
  * because they're overriden in their entirety if any call specific
  * controls are specified.
  *
- * @param[out] serverctrls_out Where to write serverctrls.
- * @param[out] clientctrls_out Where to write clientctrls.
- * @param[in] serverctrls_len length of serverctrls array.
- * @param[in] clientctrls_len length of clientctrls array.
- * @param[in] conn to get controls from.
- * @param[in] serverctrls_in from arguments.
- * @param[in] clientctrls_in from_arguments.
+ * @param[out] serverctrls_out Where to write serverctrls.
+ * @param[out] clientctrls_out Where to write clientctrls.
+ * @param[in] serverctrls_len  length of serverctrls array.
+ * @param[in] clientctrls_len  length of clientctrls array.
+ * @param[in] conn             to get controls from.
+ * @param[in] serverctrls_in   from arguments.
+ * @param[in] clientctrls_in   from_arguments.
  */
- void rlm_ldap_control_merge(LDAPControl *serverctrls_out[],
-                            LDAPControl *clientctrls_out[],
-                            size_t serverctrls_len,
-                            size_t clientctrls_len,
-                            ldap_handle_t *conn,
-                            LDAPControl *serverctrls_in[],
-                            LDAPControl *clientctrls_in[])
+ void fr_ldap_control_merge(LDAPControl *serverctrls_out[],
+                           LDAPControl *clientctrls_out[],
+                           size_t serverctrls_len,
+                           size_t clientctrls_len,
+                           ldap_handle_t *conn,
+                           LDAPControl *serverctrls_in[],
+                           LDAPControl *clientctrls_in[])
 {
        size_t i, num_serverctrls = 0, num_clientctrls = 0;
 
@@ -86,7 +86,7 @@
  *     - 0 on success.
  *     - -1 on failure (exceeded maximum controls).
  */
-int rlm_ldap_control_add_server(ldap_handle_t *conn, LDAPControl *ctrl, bool freeit)
+int fr_ldap_control_add_server(ldap_handle_t *conn, LDAPControl *ctrl, bool freeit)
 {
        if ((size_t)conn->serverctrls_cnt >= ((sizeof(conn->serverctrls) / sizeof(conn->serverctrls[0])) - 1)) {
                return -1;
@@ -110,7 +110,7 @@ int rlm_ldap_control_add_server(ldap_handle_t *conn, LDAPControl *ctrl, bool fre
  *     - 0 on success.
  *     - -1 on failure (exceeded maximum controls).
  */
-int rlm_ldap_control_add_client(ldap_handle_t *conn, LDAPControl *ctrl, bool freeit)
+int fr_ldap_control_add_client(ldap_handle_t *conn, LDAPControl *ctrl, bool freeit)
 {
        if ((size_t)conn->clientctrls_cnt >= ((sizeof(conn->clientctrls) / sizeof(conn->clientctrls[0])) - 1)) {
                return -1;
@@ -127,7 +127,7 @@ int rlm_ldap_control_add_client(ldap_handle_t *conn, LDAPControl *ctrl, bool fre
  *
  * @param conn to clear controls from.
  */
-void rlm_ldap_control_clear(ldap_handle_t *conn)
+void fr_ldap_control_clear(ldap_handle_t *conn)
 {
        int i;
 
@@ -161,7 +161,7 @@ void rlm_ldap_control_clear(ldap_handle_t *conn)
  * @param conn to add controls to.
  * @param request to draw attributes from.
  */
-int rlm_ldap_control_add_session_tracking(ldap_handle_t *conn, REQUEST *request)
+int fr_ldap_control_add_session_tracking(ldap_handle_t *conn, REQUEST *request)
 {
        /*
         *      The OpenLDAP guys didn't declare the formatOID parameter to
@@ -267,15 +267,15 @@ int rlm_ldap_control_add_session_tracking(ldap_handle_t *conn, REQUEST *request)
                goto error;
        }
 
-       if (username_control && (rlm_ldap_control_add_server(conn, username_control, true) < 0)) goto error;
+       if (username_control && (fr_ldap_control_add_server(conn, username_control, true) < 0)) goto error;
 
-       if (acctsessionid_control && (rlm_ldap_control_add_server(conn, acctsessionid_control, true) < 0)) {
+       if (acctsessionid_control && (fr_ldap_control_add_server(conn, acctsessionid_control, true) < 0)) {
                conn->serverctrls_cnt--;
                conn->serverctrls[conn->serverctrls_cnt].control = NULL;
                goto error;
        }
 
-       if (acctmultisessionid_control && (rlm_ldap_control_add_server(conn, acctmultisessionid_control, true) < 0)) {
+       if (acctmultisessionid_control && (fr_ldap_control_add_server(conn, acctmultisessionid_control, true) < 0)) {
                conn->serverctrls_cnt--;
                conn->serverctrls[conn->serverctrls_cnt].control = NULL;
                conn->serverctrls_cnt--;
index 34b0075bacfa6b1321ce7e0df8dbbb1048693ebd..4514b4d2bd61643135d85420b8c62bac7c5c1d10 100644 (file)
  * @copyright 2016 The FreeRADIUS Server Project.
  * @copyright 2016 Arran Cudbard-Bell <a.cudbardb@freeradius.org>
  */
-#define LOG_PREFIX "rlm_ldap (%s) - "
-#define LOG_PREFIX_ARGS inst->name
+#define LOG_PREFIX "%s - "
+#define LOG_PREFIX_ARGS (*pconn)->config->name
 
-#include "rlm_ldap.h"
+#include "libldap.h"
 
 static FR_NAME_NUMBER const ldap_directory_type_table[] = {
        { "Unknown",                    LDAP_DIRECTORY_UNKNOWN  },
@@ -49,14 +49,13 @@ static FR_NAME_NUMBER const ldap_directory_type_table[] = {
  *
  * @param[in] ctx      to allocate ldap_directory_t in.
  * @param[out] out     where to write pointer to new ldap_directory_t struct.
- * @param[in] inst     rlm_ldap configuration.
  * @param[in,out] pconn        connection for querying the directory.
  * @return
  *     - 0 on success.
  *     - 1 if we failed identifying the directory server.
  *     - -1 on error.
  */
-int rlm_ldap_directory_alloc(TALLOC_CTX *ctx, ldap_directory_t **out, rlm_ldap_t const *inst, ldap_handle_t **pconn)
+int fr_ldap_directory_alloc(TALLOC_CTX *ctx, ldap_directory_t **out, ldap_handle_t **pconn)
 {
        static char const       *attrs[] = { "vendorname",
                                             "vendorversion",
@@ -82,7 +81,8 @@ int rlm_ldap_directory_alloc(TALLOC_CTX *ctx, ldap_directory_t **out, rlm_ldap_t
 
        directory->type = LDAP_DIRECTORY_UNKNOWN;
 
-       status = rlm_ldap_search(&result, inst, NULL, pconn, "", LDAP_SCOPE_BASE, "(objectclass=*)", attrs, NULL, NULL);
+       status = fr_ldap_search(&result, NULL, pconn, "", LDAP_SCOPE_BASE, "(objectclass=*)",
+                               attrs, NULL, NULL);
        switch (status) {
        case LDAP_PROC_SUCCESS:
                break;
@@ -115,7 +115,7 @@ int rlm_ldap_directory_alloc(TALLOC_CTX *ctx, ldap_directory_t **out, rlm_ldap_t
 
        values = ldap_get_values_len((*pconn)->handle, entry, "vendorname");
        if (values) {
-               directory->vendor_str = rlm_ldap_berval_to_string(directory, values[0]);
+               directory->vendor_str = fr_ldap_berval_to_string(directory, values[0]);
                INFO("Directory vendor: %s", directory->vendor_str);
 
                ldap_value_free_len(values);
@@ -123,7 +123,7 @@ int rlm_ldap_directory_alloc(TALLOC_CTX *ctx, ldap_directory_t **out, rlm_ldap_t
 
        values = ldap_get_values_len((*pconn)->handle, entry, "vendorversion");
        if (values) {
-               directory->version_str = rlm_ldap_berval_to_string(directory, values[0]);
+               directory->version_str = fr_ldap_berval_to_string(directory, values[0]);
                INFO("Directory version: %s", directory->version_str);
 
                ldap_value_free_len(values);
index 28561b097d2508268822000051d234bf79ab9f63..13a474e6ed56440aad318899c73b8257890c1c56 100644 (file)
@@ -87,7 +87,7 @@ static rlm_rcode_t rlm_ldap_group_name2dn(rlm_ldap_t const *inst, REQUEST *reque
                                 inst->groupobj_filter ? inst->groupobj_filter : "",
                                 names[0] && names[1] ? "(|" : "");
        while (*name) {
-               rlm_ldap_escape_func(request, buffer, sizeof(buffer), *name++, NULL);
+               fr_ldap_escape_func(request, buffer, sizeof(buffer), *name++, NULL);
                filter = talloc_asprintf_append_buffer(filter, "(%s=%s)", inst->groupobj_name_attr, buffer);
 
                name_cnt++;
@@ -97,14 +97,14 @@ static rlm_rcode_t rlm_ldap_group_name2dn(rlm_ldap_t const *inst, REQUEST *reque
                                               names[0] && names[1] ? ")" : "");
 
        if (tmpl_expand(&base_dn, base_dn_buff, sizeof(base_dn_buff), request,
-                       inst->groupobj_base_dn, rlm_ldap_escape_func, NULL) < 0) {
+                       inst->groupobj_base_dn, fr_ldap_escape_func, NULL) < 0) {
                REDEBUG("Failed creating base_dn");
 
                return RLM_MODULE_INVALID;
        }
 
-       status = rlm_ldap_search(&result, inst, request, pconn, base_dn, inst->groupobj_scope,
-                                filter, attrs, NULL, NULL);
+       status = fr_ldap_search(&result, request, pconn, base_dn, inst->groupobj_scope,
+                               filter, attrs, NULL, NULL);
        switch (status) {
        case LDAP_PROC_SUCCESS:
                break;
@@ -156,7 +156,7 @@ static rlm_rcode_t rlm_ldap_group_name2dn(rlm_ldap_t const *inst, REQUEST *reque
                        rcode = RLM_MODULE_FAIL;
                        goto finish;
                }
-               rlm_ldap_normalise_dn(*dn, *dn);
+               fr_ldap_util_normalise_dn(*dn, *dn);
 
                RDEBUG("Got group DN \"%s\"", *dn);
                dn++;
@@ -213,7 +213,7 @@ static rlm_rcode_t rlm_ldap_group_dn2name(rlm_ldap_t const *inst, REQUEST *reque
 
        RDEBUG("Resolving group DN \"%s\" to group name", dn);
 
-       status = rlm_ldap_search(&result, inst, request, pconn, dn, LDAP_SCOPE_BASE, NULL, attrs, NULL, NULL);
+       status = fr_ldap_search(&result, request, pconn, dn, LDAP_SCOPE_BASE, NULL, attrs, NULL, NULL);
        switch (status) {
        case LDAP_PROC_SUCCESS:
                break;
@@ -244,7 +244,7 @@ static rlm_rcode_t rlm_ldap_group_dn2name(rlm_ldap_t const *inst, REQUEST *reque
                goto finish;
        }
 
-       *out = rlm_ldap_berval_to_string(request, values[0]);
+       *out = fr_ldap_berval_to_string(request, values[0]);
        RDEBUG("Group DN \"%s\" resolves to name \"%s\"", dn, *out);
 
 finish:
@@ -259,7 +259,7 @@ finish:
  * @param[in] inst rlm_ldap configuration.
  * @param[in] request Current request.
  * @param[in,out] pconn to use. May change as this function calls functions which auto re-connect.
- * @param[in] entry retrieved by rlm_ldap_find_user or rlm_ldap_search.
+ * @param[in] entry retrieved by rlm_ldap_find_user or fr_ldap_search.
  * @param[in] attr membership attribute to look for in the entry.
  * @return One of the RLM_MODULE_* values.
  */
@@ -314,7 +314,7 @@ rlm_rcode_t rlm_ldap_cacheable_userobj(rlm_ldap_t const *inst, REQUEST *request,
        fr_pair_cursor_init(&groups_cursor, &groups);
 
        for (i = 0; (i < LDAP_MAX_CACHEABLE) && (i < count); i++) {
-               is_dn = rlm_ldap_is_dn(values[i]->bv_val, values[i]->bv_len);
+               is_dn = fr_ldap_util_is_dn(values[i]->bv_val, values[i]->bv_len);
 
                if (inst->cacheable_group_dn) {
                        /*
@@ -329,7 +329,7 @@ rlm_rcode_t rlm_ldap_cacheable_userobj(rlm_ldap_t const *inst, REQUEST *request,
                         *      this to a DN. Store all the group names in an array so we can do one query.
                         */
                        } else {
-                               *name_p++ = rlm_ldap_berval_to_string(value_ctx, values[i]);
+                               *name_p++ = fr_ldap_berval_to_string(value_ctx, values[i]);
                        }
                }
 
@@ -350,7 +350,7 @@ rlm_rcode_t rlm_ldap_cacheable_userobj(rlm_ldap_t const *inst, REQUEST *request,
                        } else {
                                char *dn;
 
-                               dn = rlm_ldap_berval_to_string(value_ctx, values[i]);
+                               dn = fr_ldap_berval_to_string(value_ctx, values[i]);
                                rcode = rlm_ldap_group_dn2name(inst, request, pconn, dn, &name);
                                talloc_free(dn);
                                if (rcode != RLM_MODULE_OK) {
@@ -439,21 +439,21 @@ rlm_rcode_t rlm_ldap_cacheable_groupobj(rlm_ldap_t const *inst, REQUEST *request
                return RLM_MODULE_OK;
        }
 
-       if (rlm_ldap_xlat_filter(request,
+       if (fr_ldap_xlat_filter(request,
                                 filters, sizeof(filters) / sizeof(*filters),
                                 filter, sizeof(filter)) < 0) {
                return RLM_MODULE_INVALID;
        }
 
        if (tmpl_expand(&base_dn, base_dn_buff, sizeof(base_dn_buff), request,
-                       inst->groupobj_base_dn, rlm_ldap_escape_func, NULL) < 0) {
+                       inst->groupobj_base_dn, fr_ldap_escape_func, NULL) < 0) {
                REDEBUG("Failed creating base_dn");
 
                return RLM_MODULE_INVALID;
        }
 
-       status = rlm_ldap_search(&result, inst, request, pconn, base_dn,
-                                inst->groupobj_scope, filter, attrs, NULL, NULL);
+       status = fr_ldap_search(&result, request, pconn, base_dn,
+                               inst->groupobj_scope, filter, attrs, NULL, NULL);
        switch (status) {
        case LDAP_PROC_SUCCESS:
                break;
@@ -485,7 +485,7 @@ rlm_rcode_t rlm_ldap_cacheable_groupobj(rlm_ldap_t const *inst, REQUEST *request
 
                                goto finish;
                        }
-                       rlm_ldap_normalise_dn(dn, dn);
+                       fr_ldap_util_normalise_dn(dn, dn);
 
                        MEM(vp = pair_make_config(inst->cache_da->name, NULL, T_OP_ADD));
                        fr_pair_value_strcpy(vp, dn);
@@ -557,11 +557,11 @@ rlm_rcode_t rlm_ldap_check_groupobj_dynamic(rlm_ldap_t const *inst, REQUEST *req
 
        RDEBUG2("Checking for user in group objects");
 
-       if (rlm_ldap_is_dn(check->vp_strvalue, check->vp_length)) {
+       if (fr_ldap_util_is_dn(check->vp_strvalue, check->vp_length)) {
                char const *filters[] = { inst->groupobj_filter, inst->groupobj_membership_filter };
 
                RINDENT();
-               ret = rlm_ldap_xlat_filter(request,
+               ret = fr_ldap_xlat_filter(request,
                                           filters, sizeof(filters) / sizeof(*filters),
                                           filter, sizeof(filter));
                REXDENT();
@@ -582,7 +582,7 @@ rlm_rcode_t rlm_ldap_check_groupobj_dynamic(rlm_ldap_t const *inst, REQUEST *req
 
                snprintf(name_filter, sizeof(name_filter), "(%s=%s)", inst->groupobj_name_attr, check->vp_strvalue);
                RINDENT();
-               ret = rlm_ldap_xlat_filter(request,
+               ret = fr_ldap_xlat_filter(request,
                                           filters, sizeof(filters) / sizeof(*filters),
                                           filter, sizeof(filter));
                REXDENT();
@@ -594,7 +594,7 @@ rlm_rcode_t rlm_ldap_check_groupobj_dynamic(rlm_ldap_t const *inst, REQUEST *req
                 */
                RINDENT();
                ret = tmpl_expand(&base_dn, base_dn_buff, sizeof(base_dn_buff), request, inst->groupobj_base_dn,
-                                 rlm_ldap_escape_func, NULL);
+                                 fr_ldap_escape_func, NULL);
                REXDENT();
                if (ret < 0) {
                        REDEBUG("Failed creating base_dn");
@@ -604,7 +604,7 @@ rlm_rcode_t rlm_ldap_check_groupobj_dynamic(rlm_ldap_t const *inst, REQUEST *req
        }
 
        RINDENT();
-       status = rlm_ldap_search(NULL, inst, request, pconn, base_dn, inst->groupobj_scope, filter, NULL, NULL, NULL);
+       status = fr_ldap_search(NULL, request, pconn, base_dn, inst->groupobj_scope, filter, NULL, NULL, NULL);
        REXDENT();
        switch (status) {
        case LDAP_PROC_SUCCESS:
@@ -646,7 +646,7 @@ rlm_rcode_t rlm_ldap_check_userobj_dynamic(rlm_ldap_t const *inst, REQUEST *requ
 
        RDEBUG2("Checking user object's %s attributes", inst->userobj_membership_attr);
        RINDENT();
-       status = rlm_ldap_search(&result, inst, request, pconn, dn, LDAP_SCOPE_BASE, NULL, attrs, NULL, NULL);
+       status = fr_ldap_search(&result, request, pconn, dn, LDAP_SCOPE_BASE, NULL, attrs, NULL, NULL);
        REXDENT();
        switch (status) {
        case LDAP_PROC_SUCCESS:
@@ -683,10 +683,10 @@ rlm_rcode_t rlm_ldap_check_userobj_dynamic(rlm_ldap_t const *inst, REQUEST *requ
         *      Loop over the list of groups the user is a member of,
         *      looking for a match.
         */
-       name_is_dn = rlm_ldap_is_dn(check->vp_strvalue, check->vp_length);
+       name_is_dn = fr_ldap_util_is_dn(check->vp_strvalue, check->vp_length);
        count = ldap_count_values_len(values);
        for (i = 0; i < count; i++) {
-               value_is_dn = rlm_ldap_is_dn(values[i]->bv_val, values[i]->bv_len);
+               value_is_dn = fr_ldap_util_is_dn(values[i]->bv_val, values[i]->bv_len);
 
                RDEBUG2("Processing %s value \"%.*s\" as a %s", inst->userobj_membership_attr,
                        (int)values[i]->bv_len, values[i]->bv_val, value_is_dn ? "DN" : "group name");
@@ -769,7 +769,7 @@ rlm_rcode_t rlm_ldap_check_userobj_dynamic(rlm_ldap_t const *inst, REQUEST *requ
                        char *value;
                        bool eq = false;
 
-                       value = rlm_ldap_berval_to_string(request, values[i]);
+                       value = fr_ldap_berval_to_string(request, values[i]);
                        RINDENT();
                        ret = rlm_ldap_group_dn2name(inst, request, pconn, value, &resolved);
                        REXDENT();
index 22a92208b409857ffeed4ecdf530276cffac49b1..8ad5eb79e113a681dc74b3e9f753a2f1f4547656 100644 (file)
 
 #include <freeradius-devel/rad_assert.h>
 
-#include <stdarg.h>
-#include <ctype.h>
-
-#define LOG_PREFIX "rlm_ldap (%s) - "
-#define LOG_PREFIX_ARGS inst->name
+#define LOG_PREFIX "%s - "
+#define LOG_PREFIX_ARGS handle_config->name
 
 #include <freeradius-devel/radiusd.h>
-#include <freeradius-devel/modules.h>
-#include "rlm_ldap.h"
+#include "libldap.h"
+
+LDAP *ldap_global_handle;                      //!< Hack for OpenLDAP libldap global initialisation.
+static int instance_count = 0;
+
+/** Used to set the global log prefix for functions which don't operate on connections
+ *
+ */
+static ldap_handle_config_t ldap_global_handle_config = {
+       .name = "global"
+};
 
-static const char specials[] = ",+\"\\<>;*=()";
-static const char hextab[] = "0123456789abcdef";
+/*
+ *     Some functions may be called with a NULL request structure, this
+ *     simplifies switching certain messages from the request log to
+ *     the main log.
+ */
+#define LDAP_EXTRA_DEBUG() do { if (extra) { if (request) REDEBUG("%s", extra); else ERROR("%s", extra); }} while (0)
 
 FR_NAME_NUMBER const ldap_supported_extensions[] = {
        { "bindname",   LDAP_DEREF_NEVER        },
@@ -52,11 +62,13 @@ FR_NAME_NUMBER const ldap_supported_extensions[] = {
  * There are so many different timers in LDAP it's often hard to debug
  * issues with them, hence the need for this function.
  */
-void rlm_ldap_timeout_debug(rlm_ldap_t const *inst, REQUEST *request, ldap_handle_t const *conn,
-                           struct timeval const *timeout, char const *prefix)
+void fr_ldap_timeout_debug(REQUEST *request, ldap_handle_t const *conn,
+                          struct timeval const *timeout, char const *prefix)
 {
-       struct timeval  *net = NULL, *client = NULL;
-       int             server = 0;
+       struct timeval                  *net = NULL, *client = NULL;
+       int                             server = 0;
+       ldap_handle_config_t const      *handle_config = conn->config;
+
 
 #ifdef LDAP_OPT_NETWORK_TIMEOUT
        if (ldap_get_option(conn->handle, LDAP_OPT_NETWORK_TIMEOUT, &net) != LDAP_OPT_SUCCESS) {
@@ -109,437 +121,12 @@ void rlm_ldap_timeout_debug(rlm_ldap_t const *inst, REQUEST *request, ldap_handl
        free(client);
 }
 
-/** Converts "bad" strings into ones which are safe for LDAP
- *
- * @note RFC 4515 says filter strings can only use the @verbatim \<hex><hex> @endverbatim
- *     format, whereas RFC 4514 indicates that some chars in DNs, may be escaped simply
- *     with a backslash. For simplicity, we always use the hex escape sequences.
- *     In other areas where we're doing DN comparison, the DNs need to be normalised first
- *     so that they both use only hex escape sequences.
- *
- * @note This is a callback for xlat operations.
- *
- * Will escape any characters in input strings that would cause the string to be interpreted
- * as part of a DN and or filter. Escape sequence is @verbatim \<hex><hex> @endverbatim.
- *
- * @param request The current request.
- * @param out Pointer to output buffer.
- * @param outlen Size of the output buffer.
- * @param in Raw unescaped string.
- * @param arg Any additional arguments (unused).
- */
-size_t rlm_ldap_escape_func(UNUSED REQUEST *request, char *out, size_t outlen, char const *in, UNUSED void *arg)
-{
-
-       size_t left = outlen;
-
-       if (*in && ((*in == ' ') || (*in == '#'))) goto encode;
-
-       while (*in) {
-               /*
-                *      Encode unsafe characters.
-                */
-               if (memchr(specials, *in, sizeof(specials) - 1)) {
-               encode:
-                       /*
-                        *      Only 3 or less bytes available.
-                        */
-                       if (left <= 3) break;
-
-                       *out++ = '\\';
-                       *out++ = hextab[(*in >> 4) & 0x0f];
-                       *out++ = hextab[*in & 0x0f];
-                       in++;
-                       left -= 3;
-
-                       continue;
-               }
-
-               if (left <= 1) break;
-
-               /*
-                *      Doesn't need encoding
-                */
-               *out++ = *in++;
-               left--;
-       }
-
-       *out = '\0';
-
-       return outlen - left;
-}
-
-/** Converts escaped DNs and filter strings into normal
- *
- * @note RFC 4515 says filter strings can only use the @verbatim \<hex><hex> @endverbatim
- *     format, whereas RFC 4514 indicates that some chars in DNs, may be escaped simply
- *     with a backslash..
- *
- * Will unescape any special characters in strings, or @verbatim \<hex><hex> @endverbatim
- * sequences.
- *
- * @param request The current request.
- * @param out Pointer to output buffer.
- * @param outlen Size of the output buffer.
- * @param in Escaped string string.
- * @param arg Any additional arguments (unused).
- */
-size_t rlm_ldap_unescape_func(UNUSED REQUEST *request, char *out, size_t outlen, char const *in, UNUSED void *arg)
-{
-       char const *p;
-       char *c1, *c2, c3;
-       size_t  freespace = outlen;
-
-       if (outlen <= 1) return 0;
-
-       p = in;
-       while (*p && (--freespace > 0)) {
-               if (*p != '\\') {
-               next:
-                       *out++ = *p++;
-                       continue;
-               }
-
-               p++;
-
-               /* It's an escaped special, just remove the slash */
-               if (memchr(specials, *in, sizeof(specials) - 1)) {
-                       *out++ = *p++;
-                       continue;
-               }
-
-               /* Is a hex sequence */
-               if (!(c1 = memchr(hextab, tolower(p[0]), 16)) ||
-                   !(c2 = memchr(hextab, tolower(p[1]), 16))) goto next;
-               c3 = ((c1 - hextab) << 4) + (c2 - hextab);
-
-               *out++ = c3;
-               p += 2;
-       }
-
-       *out = '\0';
-
-       return outlen - freespace;
-}
-
-
-/** Check whether a string looks like a DN
- *
- * @param[in] in Str to check.
- * @param[in] inlen Length of string to check.
- * @return
- *     - true if string looks like a DN.
- *     - false if string does not look like DN.
- */
-bool rlm_ldap_is_dn(char const *in, size_t inlen)
-{
-       char const *p;
-
-       char want = '=';
-       bool too_soon = true;
-       int comp = 1;
-
-       for (p = in; inlen > 0; p++, inlen--) {
-               if (p[0] == '\\') {
-                       char c;
-
-                       too_soon = false;
-
-                       /*
-                        *      Invalid escape sequence, not a DN
-                        */
-                       if (inlen < 2) return false;
-
-                       /*
-                        *      Double backslash, consume two chars
-                        */
-                       if (p[1] == '\\') {
-                               inlen--;
-                               p++;
-                               continue;
-                       }
-
-                       /*
-                        *      Special, consume two chars
-                        */
-                       switch (p[1]) {
-                       case ' ':
-                       case '#':
-                       case '=':
-                       case '"':
-                       case '+':
-                       case ',':
-                       case ';':
-                       case '<':
-                       case '>':
-                       case '\'':
-                               inlen -= 1;
-                               p += 1;
-                               continue;
-
-                       default:
-                               break;
-                       }
-
-                       /*
-                        *      Invalid escape sequence, not a DN
-                        */
-                       if (inlen < 3) return false;
-
-                       /*
-                        *      Hex encoding, consume three chars
-                        */
-                       if (fr_hex2bin((uint8_t *) &c, 1, p + 1, 2) == 1) {
-                               inlen -= 2;
-                               p += 2;
-                               continue;
-                       }
-
-                       /*
-                        *      Invalid escape sequence, not a DN
-                        */
-                       return false;
-               }
-
-               switch (*p) {
-               case '=':
-                       if (too_soon || (*p != want)) return false;     /* Too soon after last , or = */
-                       want = ',';
-                       too_soon = true;
-                       break;
-
-               case ',':
-                       if (too_soon || (*p != want)) return false;     /* Too soon after last , or = */
-                       want = '=';
-                       too_soon = true;
-                       comp++;
-                       break;
-
-               default:
-                       too_soon = false;
-                       break;
-               }
-       }
-
-       /*
-        *      If the string ended with , or =, or the number
-        *      of components was less than 2
-        *
-        *      i.e. we don't have <attr>=<val>,<attr>=<val>
-        */
-       if (too_soon || (comp < 2)) return false;
-
-       return true;
-}
-
-/** Convert a berval to a talloced string
- *
- * The ldap_get_values function is deprecated, and ldap_get_values_len
- * does not guarantee the berval buffers it returns are \0 terminated.
- *
- * For some cases this is fine, for others we require a \0 terminated
- * buffer (feeding DNs back into libldap for example).
- *
- * @param ctx to allocate in.
- * @param in Berval to copy.
- * @return \0 terminated buffer containing in->bv_val.
- */
-char *rlm_ldap_berval_to_string(TALLOC_CTX *ctx, struct berval const *in)
-{
-       char *out;
-
-       out = talloc_array(ctx, char, in->bv_len + 1);
-       if (!out) return NULL;
-
-       memcpy(out, in->bv_val, in->bv_len);
-       out[in->bv_len] = '\0';
-
-       return out;
-}
-
-/** Normalise escape sequences in a DN
- *
- * Characters in a DN can either be escaped as
- * @verbatim \<hex><hex> @endverbatim or @verbatim \<special> @endverbatim
- *
- * The LDAP directory chooses how characters are escaped, which can make
- * local comparisons of DNs difficult.
- *
- * Here we search for hex sequences that match special chars, and convert
- * them to the @verbatim \<special> @endverbatim form.
- *
- * @note the resulting output string will only ever be shorter than the
- *       input, so it's fine to use the same buffer for both out and in.
- *
- * @param out Where to write the normalised DN.
- * @param in The input DN.
- * @return The number of bytes written to out.
- */
-size_t rlm_ldap_normalise_dn(char *out, char const *in)
-{
-       char const *p;
-       char *o = out;
-
-       for (p = in; *p != '\0'; p++) {
-               if (p[0] == '\\') {
-                       char c;
-
-                       /*
-                        *      Double backslashes get processed specially
-                        */
-                       if (p[1] == '\\') {
-                               p += 1;
-                               *o++ = p[0];
-                               *o++ = p[1];
-                               continue;
-                       }
-
-                       /*
-                        *      Hex encodings that have an alternative
-                        *      special encoding, get rewritten to the
-                        *      special encoding.
-                        */
-                       if (fr_hex2bin((uint8_t *) &c, 1, p + 1, 2) == 1) {
-                               switch (c) {
-                               case ' ':
-                               case '#':
-                               case '=':
-                               case '"':
-                               case '+':
-                               case ',':
-                               case ';':
-                               case '<':
-                               case '>':
-                               case '\'':
-                                       *o++ = '\\';
-                                       *o++ = c;
-                                       p += 2;
-                                       continue;
-
-                               default:
-                                       break;
-                               }
-                       }
-               }
-               *o++ = *p;
-       }
-       *o = '\0';
-
-       return o - out;
-}
-
-/** Find the place at which the two DN strings diverge
- *
- * Returns the length of the non matching string in full.
- *
- * @param full DN.
- * @param part Partial DN as returned by ldap_parse_result.
- * @return
- *     - Length of the portion of full which wasn't matched
- *     - -1 on failure.
- */
-static size_t rlm_ldap_common_dn(char const *full, char const *part)
-{
-       size_t f_len, p_len, i;
-
-       if (!full) return -1;
-
-       f_len = strlen(full);
-
-       if (!part) return -1;
-
-       p_len = strlen(part);
-       if (!p_len) return f_len;
-
-       if ((f_len < p_len) || !f_len) return -1;
-
-       for (i = 0; i < p_len; i++) if (part[p_len - i] != full[f_len - i]) return -1;
-
-       return f_len - p_len;
-}
-
-/** Combine and expand filters
- *
- * @param request Current request.
- * @param out Where to write the expanded string.
- * @param outlen Length of output buffer.
- * @param sub Array of subfilters (may contain NULLs).
- * @param sublen Number of potential subfilters in array.
- * @return length of expanded data.
- */
-ssize_t rlm_ldap_xlat_filter(REQUEST *request, char const **sub, size_t sublen, char *out, size_t outlen)
-{
-       char buffer[LDAP_MAX_FILTER_STR_LEN + 1];
-       char const *in = NULL;
-       char *p = buffer;
-
-       ssize_t len = 0;
-
-       unsigned int i;
-       int cnt = 0;
-
-       /*
-        *      Figure out how many filter elements we need to integrate
-        */
-       for (i = 0; i < sublen; i++) {
-               if (sub[i] && *sub[i]) {
-                       in = sub[i];
-                       cnt++;
-               }
-       }
-
-       if (!cnt) {
-               out[0] = '\0';
-               return 0;
-       }
-
-       if (cnt > 1) {
-               if (outlen < 3) {
-                       goto oob;
-               }
-
-               p[len++] = '(';
-               p[len++] = '&';
-
-               for (i = 0; i < sublen; i++) {
-                       if (sub[i] && (*sub[i] != '\0')) {
-                               len += strlcpy(p + len, sub[i], outlen - len);
-
-                               if ((size_t) len >= outlen) {
-                                       oob:
-                                       REDEBUG("Out of buffer space creating filter");
-
-                                       return -1;
-                               }
-                       }
-               }
-
-               if ((outlen - len) < 2) {
-                       goto oob;
-               }
-
-               p[len++] = ')';
-               p[len] = '\0';
-
-               in = buffer;
-       }
-
-       len = xlat_eval(out, outlen, request, in, rlm_ldap_escape_func, NULL);
-       if (len < 0) {
-               REDEBUG("Failed creating filter");
-
-               return -1;
-       }
-
-       return len;
-}
-
 /** Return the error string associated with a handle
  *
  * @param conn to retrieve error from.
  * @return error string.
  */
-char const *rlm_ldap_error_str(ldap_handle_t const *conn)
+char const *fr_ldap_error_str(ldap_handle_t const *conn)
 {
        int lib_errno;
        ldap_get_option(conn->handle, LDAP_OPT_ERROR_NUMBER, &lib_errno);
@@ -558,7 +145,6 @@ char const *rlm_ldap_error_str(ldap_handle_t const *conn)
  * Will also produce extended error output including any messages the server
  * sent, and information about partial DN matches.
  *
- * @param[in] inst     of LDAP module.
  * @param[in] conn     Current connection.
  * @param[in] msgid    returned from last operation. May be -1 if no result
  *                     processing is required.
@@ -571,13 +157,12 @@ char const *rlm_ldap_error_str(ldap_handle_t const *conn)
  *                     (faster) or must be freed (with talloc_free).
  * @return One of the LDAP_PROC_* (#ldap_rcode_t) values.
  */
-ldap_rcode_t rlm_ldap_result(rlm_ldap_t const *inst,
-                            ldap_handle_t const *conn,
-                            int msgid,
-                            char const *dn,
-                            struct timeval const *timeout,
-                            LDAPMessage **result,
-                            char const **error, char **extra)
+ldap_rcode_t fr_ldap_result(ldap_handle_t const *conn,
+                           int msgid,
+                           char const *dn,
+                           struct timeval const *timeout,
+                           LDAPMessage **result,
+                           char const **error, char **extra)
 {
        ldap_rcode_t status = LDAP_PROC_SUCCESS;
 
@@ -620,8 +205,7 @@ ldap_rcode_t rlm_ldap_result(rlm_ldap_t const *inst,
        if (msgid < 0) return LDAP_SUCCESS;     /* No msgid and no error, return now */
 
        if (!timeout) {
-               tv.tv_sec = inst->res_timeout;
-               tv.tv_usec = 0;
+               tv = conn->config->res_timeout;
        } else {
                tv = *timeout;
        }
@@ -684,7 +268,7 @@ process_error:
                /*
                 *      Build our own internal diagnostic string
                 */
-               len = rlm_ldap_common_dn(dn, part_dn);
+               len = fr_ldap_common_dn(dn, part_dn);
                if (len < 0) break;
 
                our_err = talloc_typed_asprintf(conn, "Match stopped here: [%.*s]%s", len, dn, part_dn ? part_dn : "");
@@ -707,18 +291,18 @@ process_error:
 
        case LDAP_TIMEOUT:
                *error = "Timed out while waiting for server to respond";
-               goto timeout;
+               status = LDAP_PROC_TIMEOUT;
+               break;
 
        case LDAP_TIMELIMIT_EXCEEDED:
                *error = "Time limit exceeded";
-       timeout:
-               trigger_exec(NULL, inst->cs, "modules.ldap.timeout", true, NULL);
-               /* FALL-THROUGH */
+               status = LDAP_PROC_TIMEOUT;
+               break;
 
        case LDAP_BUSY:
        case LDAP_UNAVAILABLE:
        case LDAP_SERVER_DOWN:
-               status = LDAP_PROC_RETRY;
+               status = LDAP_PROC_BAD_CONN;
                goto error_string;
 
        case LDAP_INVALID_CREDENTIALS:
@@ -728,7 +312,7 @@ process_error:
 
        case LDAP_OPERATIONS_ERROR:
                *error = "Please set 'chase_referrals=yes' and 'rebind=yes'. See the ldap module configuration "
-                        "for details.";
+                        "for details";
 
                /* FALL-THROUGH */
        default:
@@ -802,7 +386,6 @@ process_error:
  *
  * Performs a simple bind to the LDAP directory, and handles any errors that occur.
  *
- * @param[in] inst             rlm_ldap configuration.
  * @param[in] request          Current request, this may be NULL, in which case all
  *                             debug logging is done with radlog.
  * @param[in,out] pconn                to use. May change as this function calls functions
@@ -810,46 +393,39 @@ process_error:
  * @param[in] dn               of the user, may be NULL to bind anonymously.
  * @param[in] password         of the user, may be NULL if no password is specified.
  * @param[in] sasl             mechanism to use for bind, and additional parameters.
- * @param[in] retry            whether we're rebinding an existing connection.
  * @param[in] timeout          Maximum time bind is allowed to take.
  * @param[in] serverctrls      Only used for SASL binds.  May be NULL.
  * @param[in] clientctrls      Search controls for sasl_bind.
  *                             Only used for SASL binds. May be NULL.
  * @return One of the LDAP_PROC_* (#ldap_rcode_t) values.
  */
-ldap_rcode_t rlm_ldap_bind(rlm_ldap_t const *inst,
-                          REQUEST *request,
-                          ldap_handle_t **pconn,
-                          char const *dn, char const *password,
+ldap_rcode_t fr_ldap_bind(REQUEST *request,
+                         ldap_handle_t **pconn,
+                         char const *dn, char const *password,
 #ifdef WITH_SASL
-                          ldap_sasl const *sasl,
+                         ldap_sasl const *sasl,
 #else
-                          NDEBUG_UNUSED ldap_sasl const *sasl,
+                         NDEBUG_UNUSED ldap_sasl const *sasl,
 #endif
-                          bool retry,
-                          struct timeval const *timeout,
-                          LDAPControl **serverctrls, LDAPControl **clientctrls)
+                         struct timeval const *timeout,
+                         LDAPControl **serverctrls, LDAPControl **clientctrls)
 {
-       ldap_rcode_t            status = LDAP_PROC_ERROR;
-       ldap_pool_inst_t const  *pool_inst = (*pconn)->pool_inst;
-
-       int                     msgid = -1;
+       ldap_rcode_t                    status = LDAP_PROC_ERROR;
+       ldap_handle_config_t const      *handle_config = (*pconn)->config;
 
-       char const              *error = NULL;
-       char                    *extra = NULL;
+       int                             msgid = -1;
 
-       int                     i, num;
-       int                     conn_available;
+       char const                      *error = NULL;
+       char                            *extra = NULL;
 
        rad_assert(*pconn && (*pconn)->handle);
-       rad_assert(!retry || inst->pool);
 
 #ifndef WITH_SASL
        rad_assert(!sasl || !sasl->mech);
 #endif
 
        if (DEBUG_ENABLED4 || (request && RDEBUG_ENABLED4)) {
-               rlm_ldap_timeout_debug(inst, request, *pconn, timeout, __FUNCTION__);
+               fr_ldap_timeout_debug(request, *pconn, timeout, __FUNCTION__);
        }
 
        /*
@@ -857,102 +433,57 @@ ldap_rcode_t rlm_ldap_bind(rlm_ldap_t const *inst,
         */
        if (!dn) dn = "";
 
-       /*
-        *      Pool isn't available during module instantiation
-        */
-       conn_available = inst->pool ? fr_connection_pool_state(inst->pool)->num : 0;
-
-       /*
-        *      For sanity, for when no connections are viable,
-        *      and we can't make a new one.
-        */
-       num = retry ? conn_available : 0;
-       for (i = num; i >= 0; i--) {
 #ifdef WITH_SASL
-               if (sasl && sasl->mech) {
-                       status = rlm_ldap_sasl_interactive(inst, request, *pconn, dn, password, sasl,
-                                                          serverctrls, clientctrls, timeout, &error, &extra);
-               } else
+       if (sasl && sasl->mech) {
+               status = fr_ldap_sasl_interactive(request, *pconn, dn, password, sasl,
+                                                 serverctrls, clientctrls, timeout, &error, &extra);
+       } else
 #endif
-               {
-                       int ret;
-                       struct berval cred;
-
-                       if (password) {
-                               memcpy(&cred.bv_val, &password, sizeof(cred.bv_val));
-                               cred.bv_len = talloc_array_length(password) - 1;
-                       } else {
-                               cred.bv_val = NULL;
-                               cred.bv_len = 0;
-                       }
-
-                       /*
-                        *      Yes, confusingly named.  This is the simple version
-                        *      of the SASL bind function that should always be
-                        *      available.
-                        */
-                       ret = ldap_sasl_bind((*pconn)->handle, dn, LDAP_SASL_SIMPLE, &cred,
-                                            serverctrls, clientctrls, &msgid);
-
-                       /* We got a valid message ID */
-                       if ((ret == 0) && (msgid >= 0)) ROPTIONAL(RDEBUG2, DEBUG2, "Waiting for bind result...");
+       {
+               int ret;
+               struct berval cred;
 
-                       status = rlm_ldap_result(inst, *pconn, msgid, dn, timeout, NULL, &error, &extra);
+               if (password) {
+                       memcpy(&cred.bv_val, &password, sizeof(cred.bv_val));
+                       cred.bv_len = talloc_array_length(password) - 1;
+               } else {
+                       cred.bv_val = NULL;
+                       cred.bv_len = 0;
                }
 
-               switch (status) {
-               case LDAP_PROC_SUCCESS:
-                       ROPTIONAL(RDEBUG, DEBUG, "Bind successful");
-                       break;
-
-               case LDAP_PROC_NOT_PERMITTED:
-                       ROPTIONAL(REDEBUG, ERROR, "Bind was not permitted: %s", error);
-                       LDAP_EXTRA_DEBUG();
-
-                       break;
-
-               case LDAP_PROC_REJECT:
-                       ROPTIONAL(REDEBUG, ERROR, "Bind credentials incorrect: %s", error);
-                       LDAP_EXTRA_DEBUG();
-
-                       break;
+               /*
+                *      Yes, confusingly named.  This is the simple version
+                *      of the SASL bind function that should always be
+                *      available.
+                */
+               ret = ldap_sasl_bind((*pconn)->handle, dn, LDAP_SASL_SIMPLE, &cred,
+                                    serverctrls, clientctrls, &msgid);
 
-               case LDAP_PROC_RETRY:
-                       if (retry) {
-                               *pconn = fr_connection_reconnect(inst->pool, request, *pconn);
-                               if (*pconn) {
-                                       ROPTIONAL(RWDEBUG, WARN, "Bind with %s to %s failed: %s. Got new socket, "
-                                                 "retrying...", *dn ? dn : "(anonymous)",
-                                                 pool_inst->server, error);
+               /* We got a valid message ID */
+               if ((ret == 0) && (msgid >= 0)) ROPTIONAL(RDEBUG2, DEBUG2, "Waiting for bind result...");
 
-                                       talloc_free(extra); /* don't leak debug info */
+               status = fr_ldap_result(*pconn, msgid, dn, NULL, NULL, &error, &extra);
+       }
 
-                                       continue;
-                               }
-                       };
-                       status = LDAP_PROC_ERROR;
+       switch (status) {
+       case LDAP_PROC_SUCCESS:
+               ROPTIONAL(RDEBUG, DEBUG, "Bind successful");
+               break;
 
-                       /*
-                        *      Were not allowed to retry, or there are no more
-                        *      sockets, treat this as a hard failure.
-                        */
-                       /* FALL-THROUGH */
-               default:
-                       ROPTIONAL(REDEBUG, ERROR, "Bind with %s to %s failed: %s", *dn ? dn : "(anonymous)",
-                                 pool_inst->server, error);
-                       LDAP_EXTRA_DEBUG();
+       case LDAP_PROC_NOT_PERMITTED:
+               ROPTIONAL(REDEBUG, ERROR, "Bind as \"%s\" to \"%s\" not permitted: %s", *dn ? dn : "(anonymous)",
+                         handle_config->server, error);
+               LDAP_EXTRA_DEBUG();
+               break;
 
-                       break;
-               }
+       default:
+               ROPTIONAL(REDEBUG, ERROR, "Bind as \"%s\" to \"%s\" failed: %s", *dn ? dn : "(anonymous)",
+                         handle_config->server, error);
+               LDAP_EXTRA_DEBUG();
 
                break;
        }
 
-       if (retry && (i < 0)) {
-               ROPTIONAL(REDEBUG, ERROR, "Hit reconnection limit");
-               status = LDAP_PROC_ERROR;
-       }
-
        talloc_free(extra);
 
        return status; /* caller closes the connection */
@@ -962,53 +493,51 @@ ldap_rcode_t rlm_ldap_bind(rlm_ldap_t const *inst,
  *
  * Binds as the administrative user and performs a search, dealing with any errors.
  *
- * @param[out] result Where to store the result. Must be freed with ldap_msgfree if LDAP_PROC_SUCCESS is returned.
- *     May be NULL in which case result will be automatically freed after use.
- * @param[in] inst rlm_ldap configuration.
- * @param[in] request Current request.
- * @param[in,out] pconn to use. May change as this function calls functions which auto re-connect.
- * @param[in] dn to use as base for the search.
- * @param[in] scope to use (LDAP_SCOPE_BASE, LDAP_SCOPE_ONE, LDAP_SCOPE_SUB).
- * @param[in] filter to use, should be pre-escaped.
- * @param[in] attrs to retrieve.
- * @param[in] serverctrls Search controls to pass to the server.  May be NULL.
- * @param[in] clientctrls Search controls for ldap_search.  May be NULL.
+ * @param[out] result          Where to store the result. Must be freed with ldap_msgfree
+ *                             if LDAP_PROC_SUCCESS is returned.
+ *                             May be NULL in which case result will be automatically freed after use.
+ * @param[in] request          Current request.
+ * @param[in,out] pconn                to use. May change as this function calls functions which auto re-connect.
+ * @param[in] dn               to use as base for the search.
+ * @param[in] scope            to use (LDAP_SCOPE_BASE, LDAP_SCOPE_ONE, LDAP_SCOPE_SUB).
+ * @param[in] filter           to use, should be pre-escaped.
+ * @param[in] attrs            to retrieve.
+ * @param[in] serverctrls      Search controls to pass to the server.  May be NULL.
+ * @param[in] clientctrls      Search controls for ldap_search.  May be NULL.
  * @return One of the LDAP_PROC_* (#ldap_rcode_t) values.
  */
-ldap_rcode_t rlm_ldap_search(LDAPMessage **result, rlm_ldap_t const *inst, REQUEST *request,
-                            ldap_handle_t **pconn,
-                            char const *dn, int scope, char const *filter, char const * const *attrs,
-                            LDAPControl **serverctrls, LDAPControl **clientctrls)
+ldap_rcode_t fr_ldap_search(LDAPMessage **result, REQUEST *request,
+                           ldap_handle_t **pconn,
+                           char const *dn, int scope, char const *filter, char const * const *attrs,
+                           LDAPControl **serverctrls, LDAPControl **clientctrls)
 {
-       ldap_rcode_t    status = LDAP_PROC_ERROR;
-       LDAPMessage     *our_result = NULL;
-
-       int             msgid;          // Message id returned by
-                                       // ldap_search_ext.
+       ldap_rcode_t                    status = LDAP_PROC_ERROR;
+       LDAPMessage                     *our_result = NULL;
 
-       int             count = 0;      // Number of results we got.
+       ldap_handle_config_t const      *handle_config = (*pconn)->config;
 
-       struct timeval  tv;             // Holds timeout values.
+       int                             msgid;          // Message id returned by
+                                                       // ldap_search_ext.
 
-       char const      *error = NULL;
-       char            *extra = NULL;
+       int                             count = 0;      // Number of results we got.
 
-       int             i;
+       struct timeval                  tv;             // Holds timeout values.
 
-       int             conn_available;
+       char const                      *error = NULL;
+       char                            *extra = NULL;
 
-       LDAPControl     *our_serverctrls[LDAP_MAX_CONTROLS];
-       LDAPControl     *our_clientctrls[LDAP_MAX_CONTROLS];
+       LDAPControl                     *our_serverctrls[LDAP_MAX_CONTROLS];
+       LDAPControl                     *our_clientctrls[LDAP_MAX_CONTROLS];
 
-       rlm_ldap_control_merge(our_serverctrls, our_clientctrls,
-                              sizeof(our_serverctrls) / sizeof(*our_serverctrls),
-                              sizeof(our_clientctrls) / sizeof(*our_clientctrls),
-                              *pconn, serverctrls, clientctrls);
+       fr_ldap_control_merge(our_serverctrls, our_clientctrls,
+                             sizeof(our_serverctrls) / sizeof(*our_serverctrls),
+                             sizeof(our_clientctrls) / sizeof(*our_clientctrls),
+                             *pconn, serverctrls, clientctrls);
 
        rad_assert(*pconn && (*pconn)->handle);
 
        if (DEBUG_ENABLED4 || (request && RDEBUG_ENABLED4)) {
-               rlm_ldap_timeout_debug(inst, request, *pconn, NULL, __FUNCTION__);
+               fr_ldap_timeout_debug(request, *pconn, NULL, __FUNCTION__);
        }
 
        /*
@@ -1022,12 +551,11 @@ ldap_rcode_t rlm_ldap_search(LDAPMessage **result, rlm_ldap_t const *inst, REQUE
         *      Do all searches as the admin user.
         */
        if ((*pconn)->rebound) {
-               status = rlm_ldap_bind(inst, request, pconn, (*pconn)->pool_inst->admin_identity,
-                                      (*pconn)->pool_inst->admin_password, &(*pconn)->pool_inst->admin_sasl, true,
-                                      NULL, NULL, NULL);
-               if (status != LDAP_PROC_SUCCESS) {
-                       return LDAP_PROC_ERROR;
-               }
+               status = fr_ldap_bind(request, pconn,
+                                     (*pconn)->config->admin_identity, (*pconn)->config->admin_password,
+                                     &(*pconn)->config->admin_sasl, NULL,
+                                     NULL, NULL);
+               if (status != LDAP_PROC_SUCCESS) return LDAP_PROC_ERROR;
 
                rad_assert(*pconn);
 
@@ -1047,71 +575,42 @@ ldap_rcode_t rlm_ldap_search(LDAPMessage **result, rlm_ldap_t const *inst, REQUE
         *      having to pass it explicitly.
         */
        memset(&tv, 0, sizeof(tv));
-       tv.tv_sec = inst->res_timeout;
 
-       /*
-        *      Pool isn't available during module instantiation
-        */
-       conn_available = inst->pool ? fr_connection_pool_state(inst->pool)->num : 0;
+       (void) ldap_search_ext((*pconn)->handle, dn, scope, filter, search_attrs,
+                              0, our_serverctrls, our_clientctrls, NULL, 0, &msgid);
+
+       ROPTIONAL(RDEBUG, DEBUG, "Waiting for search result...");
+       status = fr_ldap_result(*pconn, msgid, dn, NULL, &our_result, &error, &extra);
+       switch (status) {
+       case LDAP_PROC_SUCCESS:
+               break;
 
        /*
-        *      For sanity, for when no connections are viable,
-        *      and we can't make a new one.
+        *      Invalid DN isn't a failure when searching.
+        *      The DN may be xlat expanded so may point directly
+        *      to an LDAP object. If that can't be located, it's
+        *      the same as notfound.
         */
-       for (i = conn_available; i >= 0; i--) {
-               (void) ldap_search_ext((*pconn)->handle, dn, scope, filter, search_attrs,
-                                      0, our_serverctrls, our_clientctrls, &tv, 0, &msgid);
-
-               ROPTIONAL(RDEBUG, DEBUG, "Waiting for search result...");
-               status = rlm_ldap_result(inst, *pconn, msgid, dn, NULL, &our_result, &error, &extra);
-               switch (status) {
-               case LDAP_PROC_SUCCESS:
-                       break;
-
-               /*
-                *      Invalid DN isn't a failure when searching.
-                *      The DN may be xlat expanded so may point directly
-                *      to an LDAP object. If that can't be located, it's
-                *      the same as notfound.
-                */
-               case LDAP_PROC_BAD_DN:
-                       ROPTIONAL(RDEBUG, DEBUG, "%s", error);
-                       if (extra) ROPTIONAL(RDEBUG, DEBUG, "%s", extra);
-                       break;
-
-               case LDAP_PROC_RETRY:
-                       *pconn = fr_connection_reconnect(inst->pool, request, *pconn);
-                       if (*pconn) {
-                               ROPTIONAL(RWDEBUG, WARN, "Search failed: %s. Got new socket, retrying...", error);
-
-                               talloc_free(extra); /* don't leak debug info */
-
-                               continue;
-                       }
-
-                       status = LDAP_PROC_ERROR;
-
-                       /* FALL-THROUGH */
-               default:
-                       ROPTIONAL(REDEBUG, ERROR, "Failed performing search: %s", error);
-                       if (extra) ROPTIONAL(REDEBUG, ERROR, "%s", extra);
-
-                       goto finish;
-               }
-
+       case LDAP_PROC_BAD_DN:
+               ROPTIONAL(RDEBUG, DEBUG, "%s", error);
+               if (extra) ROPTIONAL(RDEBUG, DEBUG, "%s", extra);
                break;
-       }
 
-       if (i < 0) {
-               ROPTIONAL(REDEBUG, ERROR, "Hit reconnection limit");
+       case LDAP_PROC_BAD_CONN:
                status = LDAP_PROC_ERROR;
+               goto finish;
+
+               /* FALL-THROUGH */
+       default:
+               ROPTIONAL(REDEBUG, ERROR, "Failed performing search: %s", error);
+               if (extra) ROPTIONAL(REDEBUG, ERROR, "%s", extra);
 
                goto finish;
        }
 
        count = ldap_count_entries((*pconn)->handle, our_result);
        if (count < 0) {
-               ROPTIONAL(REDEBUG, ERROR, "Error counting results: %s", rlm_ldap_error_str(*pconn));
+               ROPTIONAL(REDEBUG, ERROR, "Error counting results: %s", fr_ldap_error_str(*pconn));
                status = LDAP_PROC_ERROR;
 
                ldap_msgfree(our_result);
@@ -1145,18 +644,17 @@ finish:
  *
  * Binds as the administrative user and attempts to modify an LDAP object.
  *
- * @param[in] inst rlm_ldap configuration.
- * @param[in] request Current request.
- * @param[in,out] pconn to use. May change as this function calls functions which auto re-connect.
- * @param[in] dn of the object to modify.
- * @param[in] mods to make, see 'man ldap_modify' for more information.
- * @param[in] serverctrls Search controls to pass to the server.  May be NULL.
- * @param[in] clientctrls Search controls for ldap_modify.  May be NULL.
+ * @param[in] request          Current request.
+ * @param[in,out] pconn                to use. May change as this function calls functions which auto re-connect.
+ * @param[in] dn               of the object to modify.
+ * @param[in] mods             to make, see 'man ldap_modify' for more information.
+ * @param[in] serverctrls      Search controls to pass to the server.  May be NULL.
+ * @param[in] clientctrls      Search controls for ldap_modify.  May be NULL.
  * @return One of the LDAP_PROC_* (#ldap_rcode_t) values.
  */
-ldap_rcode_t rlm_ldap_modify(rlm_ldap_t const *inst, REQUEST *request, ldap_handle_t **pconn,
-                            char const *dn, LDAPMod *mods[],
-                            LDAPControl **serverctrls, LDAPControl **clientctrls)
+ldap_rcode_t fr_ldap_modify(REQUEST *request, ldap_handle_t **pconn,
+                           char const *dn, LDAPMod *mods[],
+                           LDAPControl **serverctrls, LDAPControl **clientctrls)
 {
        ldap_rcode_t    status = LDAP_PROC_ERROR;
 
@@ -1165,29 +663,26 @@ ldap_rcode_t rlm_ldap_modify(rlm_ldap_t const *inst, REQUEST *request, ldap_hand
        char const      *error = NULL;
        char            *extra = NULL;
 
-       int             i;
-
        LDAPControl     *our_serverctrls[LDAP_MAX_CONTROLS];
        LDAPControl     *our_clientctrls[LDAP_MAX_CONTROLS];
 
-       int             conn_available = 0;
-
-       rlm_ldap_control_merge(our_serverctrls, our_clientctrls,
-                              sizeof(our_serverctrls) / sizeof(*our_serverctrls),
-                              sizeof(our_clientctrls) / sizeof(*our_clientctrls),
-                              *pconn, serverctrls, clientctrls);
+       fr_ldap_control_merge(our_serverctrls, our_clientctrls,
+                             sizeof(our_serverctrls) / sizeof(*our_serverctrls),
+                             sizeof(our_clientctrls) / sizeof(*our_clientctrls),
+                             *pconn, serverctrls, clientctrls);
 
        rad_assert(*pconn && (*pconn)->handle);
 
-       if (RDEBUG_ENABLED4) rlm_ldap_timeout_debug(inst, request, *pconn, NULL, __FUNCTION__);
+       if (RDEBUG_ENABLED4) fr_ldap_timeout_debug(request, *pconn, NULL, __FUNCTION__);
 
        /*
         *      Perform all modifications as the admin user.
         */
        if ((*pconn)->rebound) {
-               status = rlm_ldap_bind(inst, request, pconn, (*pconn)->pool_inst->admin_identity,
-                                      (*pconn)->pool_inst->admin_password, &(*pconn)->pool_inst->admin_sasl, true,
-                                      NULL, NULL, NULL);
+               status = fr_ldap_bind(request, pconn,
+                                     (*pconn)->config->admin_identity, (*pconn)->config->admin_password,
+                                     &(*pconn)->config->admin_sasl,
+                                     NULL, NULL, NULL);
                if (status != LDAP_PROC_SUCCESS) {
                        return LDAP_PROC_ERROR;
                }
@@ -1197,361 +692,30 @@ ldap_rcode_t rlm_ldap_modify(rlm_ldap_t const *inst, REQUEST *request, ldap_hand
                (*pconn)->rebound = false;
        }
 
-       /*
-        *      Pool isn't available during module instantiation
-        */
-       conn_available = inst->pool ? fr_connection_pool_state(inst->pool)->num : 0;
-
-       /*
-        *      For sanity, for when no connections are viable,
-        *      and we can't make a new one.
-        */
-       for (i = conn_available; i >= 0; i--) {
-               RDEBUG2("Modifying object with DN \"%s\"", dn);
-               (void) ldap_modify_ext((*pconn)->handle, dn, mods, our_serverctrls, our_clientctrls, &msgid);
-
-               RDEBUG2("Waiting for modify result...");
-               status = rlm_ldap_result(inst, *pconn, msgid, dn, NULL, NULL, &error, &extra);
-               switch (status) {
-               case LDAP_PROC_SUCCESS:
-                       break;
-
-               case LDAP_PROC_RETRY:
-                       *pconn = fr_connection_reconnect(inst->pool, request, *pconn);
-                       if (*pconn) {
-                               RWDEBUG("Modify failed: %s. Got new socket, retrying...", error);
-
-                               talloc_free(extra); /* don't leak debug info */
-                               continue;
-                       }
-
-                       status = LDAP_PROC_ERROR;
-
-                       /* FALL-THROUGH */
-               default:
-                       REDEBUG("Failed modifying object: %s", error);
-                       REDEBUG("%s", extra);
-
-                       goto finish;
-               }
-
-               break;
-       }
-
-       if (i < 0) {
-               ROPTIONAL(REDEBUG, ERROR, "Hit reconnection limit");
-               status = LDAP_PROC_ERROR;
-       }
-
-finish:
-       talloc_free(extra);
-
-       return status;
-}
-
-/** Retrieve the DN of a user object
- *
- * Retrieves the DN of a user and adds it to the control list as LDAP-UserDN. Will also retrieve any
- * attributes passed and return the result in *result.
- *
- * This potentially allows for all authorization and authentication checks to be performed in one
- * ldap search operation, which is a big bonus given the number of crappy, slow *cough*AD*cough*
- * LDAP directory servers out there.
- *
- * @param[in] inst rlm_ldap configuration.
- * @param[in] request Current request.
- * @param[in,out] pconn to use. May change as this function calls functions which auto re-connect.
- * @param[in] attrs Additional attributes to retrieve, may be NULL.
- * @param[in] force Query even if the User-DN already exists.
- * @param[out] result Where to write the result, may be NULL in which case result is discarded.
- * @param[out] rcode The status of the operation, one of the RLM_MODULE_* codes.
- * @return The user's DN or NULL on error.
- */
-char const *rlm_ldap_find_user(rlm_ldap_t const *inst, REQUEST *request, ldap_handle_t **pconn,
-                              char const *attrs[], bool force, LDAPMessage **result, rlm_rcode_t *rcode)
-{
-       static char const *tmp_attrs[] = { NULL };
-
-       ldap_rcode_t    status;
-       VALUE_PAIR      *vp = NULL;
-       LDAPMessage     *tmp_msg = NULL, *entry = NULL;
-       int             ldap_errno;
-       int             cnt;
-       char            *dn = NULL;
-       char const      *filter = NULL;
-       char            filter_buff[LDAP_MAX_FILTER_STR_LEN];
-       char const      *base_dn;
-       char            base_dn_buff[LDAP_MAX_DN_STR_LEN];
-       LDAPControl     *serverctrls[] = { inst->userobj_sort_ctrl, NULL };
-
-       bool freeit = false;                                    //!< Whether the message should
-                                                               //!< be freed after being processed.
-
-       *rcode = RLM_MODULE_FAIL;
-
-       if (!result) {
-               result = &tmp_msg;
-               freeit = true;
-       }
-       *result = NULL;
-
-       if (!attrs) {
-               memset(&attrs, 0, sizeof(tmp_attrs));
-       }
-
-       /*
-        *      If the caller isn't looking for the result we can just return the current userdn value.
-        */
-       if (!force) {
-               vp = fr_pair_find_by_num(request->control, 0, PW_LDAP_USERDN, TAG_ANY);
-               if (vp) {
-                       RDEBUG("Using user DN from request \"%s\"", vp->vp_strvalue);
-                       *rcode = RLM_MODULE_OK;
-                       return vp->vp_strvalue;
-               }
-       }
-
-       /*
-        *      Perform all searches as the admin user.
-        */
-       if ((*pconn)->rebound) {
-               status = rlm_ldap_bind(inst, request, pconn, (*pconn)->pool_inst->admin_identity,
-                                      (*pconn)->pool_inst->admin_password, &(*pconn)->pool_inst->admin_sasl, true,
-                                      NULL, NULL, NULL);
-               if (status != LDAP_PROC_SUCCESS) {
-                       *rcode = RLM_MODULE_FAIL;
-                       return NULL;
-               }
-
-               rad_assert(*pconn);
-
-               (*pconn)->rebound = false;
-       }
-
-       if (inst->userobj_filter) {
-               if (tmpl_expand(&filter, filter_buff, sizeof(filter_buff), request, inst->userobj_filter,
-                               rlm_ldap_escape_func, NULL) < 0) {
-                       REDEBUG("Unable to create filter");
-                       *rcode = RLM_MODULE_INVALID;
-
-                       return NULL;
-               }
-       }
-
-       if (tmpl_expand(&base_dn, base_dn_buff, sizeof(base_dn_buff), request,
-                       inst->userobj_base_dn, rlm_ldap_escape_func, NULL) < 0) {
-               REDEBUG("Unable to create base_dn");
-               *rcode = RLM_MODULE_INVALID;
-
-               return NULL;
-       }
+       RDEBUG2("Modifying object with DN \"%s\"", dn);
+       (void) ldap_modify_ext((*pconn)->handle, dn, mods, our_serverctrls, our_clientctrls, &msgid);
 
-       status = rlm_ldap_search(result, inst, request, pconn, base_dn,
-                                inst->userobj_scope, filter, attrs, serverctrls, NULL);
+       RDEBUG2("Waiting for modify result...");
+       status = fr_ldap_result(*pconn, msgid, dn, NULL, NULL, &error, &extra);
        switch (status) {
        case LDAP_PROC_SUCCESS:
                break;
 
-       case LDAP_PROC_BAD_DN:
-       case LDAP_PROC_NO_RESULT:
-               *rcode = RLM_MODULE_NOTFOUND;
-               return NULL;
+       case LDAP_PROC_BAD_CONN:
+               break;
 
+               /* FALL-THROUGH */
        default:
-               *rcode = RLM_MODULE_FAIL;
-               return NULL;
-       }
-
-       rad_assert(*pconn);
-
-       /*
-        *      Forbid the use of unsorted search results that
-        *      contain multiple entries, as it's a potential
-        *      security issue, and likely non deterministic.
-        */
-       if (!inst->userobj_sort_ctrl) {
-               cnt = ldap_count_entries((*pconn)->handle, *result);
-               if (cnt > 1) {
-                       REDEBUG("Ambiguous search result, returned %i unsorted entries (should return 1 or 0).  "
-                               "Enable sorting, or specify a more restrictive base_dn, filter or scope", cnt);
-                       REDEBUG("The following entries were returned:");
-                       RINDENT();
-                       for (entry = ldap_first_entry((*pconn)->handle, *result);
-                            entry;
-                            entry = ldap_next_entry((*pconn)->handle, entry)) {
-                               dn = ldap_get_dn((*pconn)->handle, entry);
-                               REDEBUG("%s", dn);
-                               ldap_memfree(dn);
-                       }
-                       REXDENT();
-                       *rcode = RLM_MODULE_INVALID;
-                       goto finish;
-               }
-       }
-
-       entry = ldap_first_entry((*pconn)->handle, *result);
-       if (!entry) {
-               ldap_get_option((*pconn)->handle, LDAP_OPT_RESULT_CODE, &ldap_errno);
-               REDEBUG("Failed retrieving entry: %s",
-                       ldap_err2string(ldap_errno));
-
-               goto finish;
-       }
-
-       dn = ldap_get_dn((*pconn)->handle, entry);
-       if (!dn) {
-               ldap_get_option((*pconn)->handle, LDAP_OPT_RESULT_CODE, &ldap_errno);
-               REDEBUG("Retrieving object DN from entry failed: %s", ldap_err2string(ldap_errno));
+               REDEBUG("Failed modifying object: %s", error);
+               REDEBUG("%s", extra);
 
                goto finish;
        }
-       rlm_ldap_normalise_dn(dn, dn);
-
-       /*
-        *      We can't use fr_pair_make here to copy the value into the
-        *      attribute, as the dn must be copied into the attribute
-        *      verbatim (without de-escaping).
-        *
-        *      Special chars are pre-escaped by libldap, and because
-        *      we pass the string back to libldap we must not alter it.
-        */
-       RDEBUG("User object found at DN \"%s\"", dn);
-       vp = fr_pair_make(request, &request->control, "LDAP-UserDN", NULL, T_OP_EQ);
-       if (vp) {
-               fr_pair_value_strcpy(vp, dn);
-               *rcode = RLM_MODULE_OK;
-       }
-       ldap_memfree(dn);
 
 finish:
-       if ((freeit || (*rcode != RLM_MODULE_OK)) && *result) {
-               ldap_msgfree(*result);
-               *result = NULL;
-       }
-
-       return vp ? vp->vp_strvalue : NULL;
-}
-
-/** Check for presence of access attribute in result
- *
- * @param[in] inst rlm_ldap configuration.
- * @param[in] request Current request.
- * @param[in] conn used to retrieve access attributes.
- * @param[in] entry retrieved by rlm_ldap_find_user or rlm_ldap_search.
- * @return
- *     - #RLM_MODULE_USERLOCK if the user was denied access.
- *     - #RLM_MODULE_OK otherwise.
- */
-rlm_rcode_t rlm_ldap_check_access(rlm_ldap_t const *inst, REQUEST *request,
-                                 ldap_handle_t const *conn, LDAPMessage *entry)
-{
-       rlm_rcode_t rcode = RLM_MODULE_OK;
-       struct berval **values = NULL;
-
-       values = ldap_get_values_len(conn->handle, entry, inst->userobj_access_attr);
-       if (values) {
-               if (inst->access_positive) {
-                       if ((values[0]->bv_len >= 5) && (strncasecmp(values[0]->bv_val, "false", 5) == 0)) {
-                               RDEBUG("\"%s\" attribute exists but is set to 'false' - user locked out",
-                                      inst->userobj_access_attr);
-                               rcode = RLM_MODULE_USERLOCK;
-                       }
-                       /* RLM_MODULE_OK set above... */
-               } else if ((values[0]->bv_len < 5) || (strncasecmp(values[0]->bv_val, "false", 5) != 0)) {
-                       RDEBUG("\"%s\" attribute exists - user locked out", inst->userobj_access_attr);
-                       rcode = RLM_MODULE_USERLOCK;
-               }
-               ldap_value_free_len(values);
-       } else if (inst->access_positive) {
-               RDEBUG("No \"%s\" attribute - user locked out", inst->userobj_access_attr);
-               rcode = RLM_MODULE_USERLOCK;
-       }
-
-       return rcode;
-}
+       talloc_free(extra);
 
-/** Verify we got a password from the search
- *
- * Checks to see if after the LDAP to RADIUS mapping has been completed that a reference password.
- *
- * @param inst rlm_ldap configuration.
- * @param request Current request.
- * @param conn the connection handle
- */
-void rlm_ldap_check_reply(rlm_ldap_t const *inst, REQUEST *request, ldap_handle_t const *conn)
-{
-       /*
-       *       More warning messages for people who can't be bothered to read the documentation.
-       *
-       *       Expect_password is set when we process the mapping, and is only true if there was a mapping between
-       *       an LDAP attribute and a password reference attribute in the control list.
-       */
-       if (!inst->expect_password || (rad_debug_lvl < L_DBG_LVL_2)) return;
-
-       if (!fr_pair_find_by_num(request->control, 0, PW_CLEARTEXT_PASSWORD, TAG_ANY) &&
-           !fr_pair_find_by_num(request->control, 0, PW_NT_PASSWORD, TAG_ANY) &&
-           !fr_pair_find_by_num(request->control, 0, PW_USER_PASSWORD, TAG_ANY) &&
-           !fr_pair_find_by_num(request->control, 0, PW_PASSWORD_WITH_HEADER, TAG_ANY) &&
-           !fr_pair_find_by_num(request->control, 0, PW_CRYPT_PASSWORD, TAG_ANY)) {
-               switch (conn->directory->type) {
-               case LDAP_DIRECTORY_ACTIVE_DIRECTORY:
-                       RWDEBUG("!!! Found map between LDAP attribute and a FreeRADIUS password attribute");
-                       RWDEBUG("!!! Active Directory does not allow passwords to be read via LDAP");
-                       RWDEBUG("!!! Remove the password map and either:");
-                       RWDEBUG("!!!  - Configure authentication via ntlm_auth (mschapv2 only)");
-                       RWDEBUG("!!!  - Configure authentication via wbclient (mschapv2 only)");
-                       RWDEBUG("!!!    that password attribute");
-                       RWDEBUG("!!!  - Bind as the user by listing %s in the authenticate section, and",
-                               inst->name);
-                       RWDEBUG("!!!    setting attribute &control:Auth-Type := '%s' in the authorize section",
-                               inst->name);
-                       RWDEBUG("!!!    (pap only)");
-
-                       break;
-
-               case LDAP_DIRECTORY_EDIRECTORY:
-                       RWDEBUG("!!! Found map between LDAP attribute and a FreeRADIUS password attribute");
-                       RWDEBUG("!!! eDirectory does not allow passwords to be retrieved via LDAP search");
-                       RWDEBUG("!!! Remove the password map and either:");
-                       RWDEBUG("!!!  - Set 'edir = yes' and enable the universal password feature on your ");
-                       RWDEBUG("!!!    eDir server (recommended)");
-                       RWDEBUG("!!!    that password attribute");
-                       RWDEBUG("!!!  - Bind as the user by listing %s in the authenticate section, and",
-                               inst->name);
-                       RWDEBUG("!!!    setting attribute &control:Auth-Type := '%s' in the authorize section",
-                               inst->name);
-                       RWDEBUG("!!!    (pap only)");
-                       break;
-
-               default:
-                       if (!conn->pool_inst->admin_identity) {
-                               RWDEBUG("!!! Found map between LDAP attribute and a FreeRADIUS password attribute");
-                               RWDEBUG("!!! but no password attribute found in search result");
-                               RWDEBUG("!!! Either:");
-                               RWDEBUG("!!!  - Ensure the user object contains a password attribute, and that ");
-                               RWDEBUG("!!!    \"%s\" has permission to read that password attribute (recommended)",
-                                       conn->pool_inst->admin_identity);
-                               RWDEBUG("!!!  - Bind as the user by listing %s in the authenticate section, and",
-                                       inst->name);
-                               RWDEBUG("!!!    setting attribute &control:Auth-Type := '%s' in the authorize section",
-                                       inst->name);
-                               RWDEBUG("!!!    (pap only)");
-                       } else {
-                               RWDEBUG("!!! No \"known good\" password added");
-                               RWDEBUG("!!! but no password attribute found in search result");
-                               RWDEBUG("!!! Either:");
-                               RWDEBUG("!!!  - Ensure the user object contains a password attribute, and that ");
-                               RWDEBUG("!!!    'identity' is set to the DN of an account that has permission to read");
-                               RWDEBUG("!!!    that password attribute");
-                               RWDEBUG("!!!  - Bind as the user by listing %s in the authenticate section, and",
-                                       inst->name);
-                               RWDEBUG("!!!    setting attribute &control:Auth-Type := '%s' in the authorize section",
-                                       inst->name);
-                               RWDEBUG("!!!    (pap only)");
-                       }
-                       break;
-               }
-       }
+       return status;
 }
 
 #if LDAP_SET_REBIND_PROC_ARGS == 3
@@ -1563,15 +727,14 @@ void rlm_ldap_check_reply(rlm_ldap_t const *inst, REQUEST *request, ldap_handle_
  * @param url to bind to.
  * @param request that triggered the rebind.
  * @param msgid that triggered the rebind.
- * @param ctx rlm_ldap configuration.
+ * @param ctx fr_ldap configuration.
  */
-static int rlm_ldap_rebind(LDAP *handle, LDAP_CONST char *url, UNUSED ber_tag_t request, UNUSED ber_int_t msgid,
-                          void *ctx)
+static int fr_ldap_rebind(LDAP *handle, LDAP_CONST char *url,
+                         UNUSED ber_tag_t request, UNUSED ber_int_t msgid, void *ctx)
 {
        ldap_rcode_t                    status;
        ldap_handle_t                   *conn = talloc_get_type_abort(ctx, ldap_handle_t);
-       ldap_pool_inst_t const          *pool_inst = conn->pool_inst;
-       rlm_ldap_t const                *inst = conn->inst;
+       ldap_handle_config_t const      *handle_config = conn->config;
 
        char const                      *admin_identity = NULL;
        char const                      *admin_password = NULL;
@@ -1590,7 +753,7 @@ static int rlm_ldap_rebind(LDAP *handle, LDAP_CONST char *url, UNUSED ber_tag_t
         *      SASL mech is inherited from the module that defined the connection
         *      pool.
         */
-       if (pool_inst->use_referral_credentials) {
+       if (handle_config->use_referral_credentials) {
                LDAPURLDesc     *ldap_url;
                int             ret;
                char            **ext;
@@ -1641,7 +804,7 @@ static int rlm_ldap_rebind(LDAP *handle, LDAP_CONST char *url, UNUSED ber_tag_t
                        default:
                                if (critical) {
                                        ERROR("Failed parsing critical extension \"%s\": "
-                                             "Not supported by rlm_ldap", *ext);
+                                             "Not supported by FreeRADIUS", *ext);
                                        ldap_free_urldesc(ldap_url);
                                        return LDAP_OTHER;
                                }
@@ -1653,12 +816,12 @@ static int rlm_ldap_rebind(LDAP *handle, LDAP_CONST char *url, UNUSED ber_tag_t
        } else
 #  endif
        {
-               admin_identity = pool_inst->admin_identity;
-               admin_password = pool_inst->admin_password;
+               admin_identity = handle_config->admin_identity;
+               admin_password = handle_config->admin_password;
        }
 
-       status = rlm_ldap_bind(inst, NULL, &conn, admin_identity, admin_password,
-                              &conn->pool_inst->admin_sasl, false, NULL, NULL, NULL);
+       status = fr_ldap_bind(NULL, &conn, admin_identity, admin_password,
+                             &conn->config->admin_sasl, NULL, NULL, NULL);
        if (status != LDAP_PROC_SUCCESS) {
                ldap_get_option(handle, LDAP_OPT_ERROR_NUMBER, &ldap_errno);
 
@@ -1669,41 +832,6 @@ static int rlm_ldap_rebind(LDAP *handle, LDAP_CONST char *url, UNUSED ber_tag_t
 }
 #endif
 
-int rlm_ldap_global_init(rlm_ldap_t *inst)
-{
-       int ldap_errno;
-
-       rad_assert(inst); /* clang scan */
-
-#define do_ldap_global_option(_option, _name, _value) \
-       if (ldap_set_option(NULL, _option, _value) != LDAP_OPT_SUCCESS) { \
-               ldap_get_option(NULL, LDAP_OPT_ERROR_NUMBER, &ldap_errno); \
-               ERROR("Failed setting global option %s: %s", _name, \
-                        (ldap_errno != LDAP_SUCCESS) ? ldap_err2string(ldap_errno) : "Unknown error"); \
-               return -1;\
-       }
-
-#define maybe_ldap_global_option(_option, _name, _value) \
-       if (_value) do_ldap_global_option(_option, _name, _value)
-
-#ifdef LDAP_OPT_DEBUG_LEVEL
-       /*
-        *      Can't use do_ldap_global_option
-        */
-       if (inst->ldap_debug) do_ldap_global_option(LDAP_OPT_DEBUG_LEVEL, "ldap_debug", &(inst->ldap_debug));
-#endif
-
-#ifdef LDAP_OPT_X_TLS_RANDOM_FILE
-       /*
-        *      OpenLDAP will error out if we attempt to set
-        *      this on a handle. Presumably it's global in
-        *      OpenSSL too.
-        */
-       maybe_ldap_global_option(LDAP_OPT_X_TLS_RANDOM_FILE, "random_file", inst->tls_random_file);
-#endif
-       return 0;
-}
-
 /** Close and delete a connection
  *
  * Unbinds the LDAP connection, informing the server and freeing any memory, then releases the memory used by the
@@ -1714,20 +842,20 @@ int rlm_ldap_global_init(rlm_ldap_t *inst)
  */
 static int _mod_conn_free(ldap_handle_t *conn)
 {
-       rlm_ldap_t const *inst = conn->inst;
+       ldap_handle_config_t const      *handle_config = conn->config;
 
        rad_assert(conn->handle);
 
-       rlm_ldap_control_clear(conn);
+       fr_ldap_control_clear(conn);
 
 #ifdef HAVE_LDAP_UNBIND_EXT_S
        LDAPControl     *our_serverctrls[LDAP_MAX_CONTROLS];
        LDAPControl     *our_clientctrls[LDAP_MAX_CONTROLS];
 
-       rlm_ldap_control_merge(our_serverctrls, our_clientctrls,
-                              sizeof(our_serverctrls) / sizeof(*our_serverctrls),
-                              sizeof(our_clientctrls) / sizeof(*our_clientctrls),
-                              conn, NULL, NULL);
+       fr_ldap_control_merge(our_serverctrls, our_clientctrls,
+                             sizeof(our_serverctrls) / sizeof(*our_serverctrls),
+                             sizeof(our_clientctrls) / sizeof(*our_clientctrls),
+                             conn, NULL, NULL);
 
        DEBUG3("Closing libldap handle %p", conn->handle);
        ldap_unbind_ext_s(conn->handle, our_serverctrls, our_clientctrls);
@@ -1740,50 +868,51 @@ static int _mod_conn_free(ldap_handle_t *conn)
        return 0;
 }
 
-/** Create and return a new connection
+/** Allocate and configure a new connection
  *
- * Create a new ldap connection and allocate memory for a new rlm_handle_t
+ * Allocates and configures both our ldap handle, and libldap's handle.
+ *
+ * @param[in] ctx              to allocate handle in.
+ * @param[in] handle_config    Connection configuration.
+ * @return
+ *     - A new handle on success.
+ *     - NULL on error.
  */
-void *mod_conn_create(TALLOC_CTX *ctx, void *instance, struct timeval const *timeout)
+ldap_handle_t *fr_ldap_conn_alloc(TALLOC_CTX *ctx, ldap_handle_config_t const *handle_config)
 {
-       ldap_rcode_t status;
+       ldap_handle_t                   *conn;
+       LDAP                            *handle = NULL;
 
-       int ldap_errno, ldap_version;
+       int                             ldap_errno, ldap_version;
 
-       rlm_ldap_t const        *inst = talloc_get_type_abort(instance, rlm_ldap_t);
-       ldap_pool_inst_t const  *pool_inst = &inst->pool_inst;
-       ldap_handle_t           *conn;
-       LDAP                    *handle = NULL;
-
-       DEBUG("Connecting to %s", pool_inst->server);
 #ifdef HAVE_LDAP_INITIALIZE
-       ldap_errno = ldap_initialize(&handle, pool_inst->server);
+       ldap_errno = ldap_initialize(&handle, handle_config->server);
        if (ldap_errno != LDAP_SUCCESS) {
                ERROR("ldap_initialize failed: %s", ldap_err2string(ldap_errno));
                return NULL;
        }
 #else
-       handle = ldap_init(pool_inst->server, pool_inst->port);
+       handle = ldap_init(handle_config->server, handle_config->port);
        if (!handle) {
                ERROR("ldap_init failed");
                return NULL;
        }
 #endif
 
+       DEBUG3("New libldap handle %p", handle);
+
        /*
         *      Allocate memory for the handle.
         */
        conn = talloc_zero(ctx, ldap_handle_t);
        if (!conn) return NULL;
 
-       conn->inst = inst;
-       conn->pool_inst = pool_inst;
+       conn->config = handle_config;
        conn->handle = handle;
        conn->rebound = false;
        conn->referred = false;
-       talloc_set_destructor(conn, _mod_conn_free);
 
-       DEBUG3("New libldap handle %p", conn->handle);
+       talloc_set_destructor(conn, _mod_conn_free);
 
        /*
         *      We now have a connection structure, but no actual connection.
@@ -1794,7 +923,7 @@ void *mod_conn_create(TALLOC_CTX *ctx, void *instance, struct timeval const *tim
        if (ldap_set_option(conn->handle, _option, _value) != LDAP_OPT_SUCCESS) { \
                ldap_get_option(conn->handle, LDAP_OPT_ERROR_NUMBER, &ldap_errno); \
                ERROR("Failed setting connection option %s: %s", _name, \
-                        (ldap_errno != LDAP_SUCCESS) ? ldap_err2string(ldap_errno) : "Unknown error"); \
+                     (ldap_errno != LDAP_SUCCESS) ? ldap_err2string(ldap_errno) : "Unknown error"); \
                goto error;\
        }
 
@@ -1804,20 +933,20 @@ void *mod_conn_create(TALLOC_CTX *ctx, void *instance, struct timeval const *tim
        /*
         *      Leave "dereference" unset to use the OpenLDAP default.
         */
-       if (pool_inst->dereference_str) {
-               do_ldap_option(LDAP_OPT_DEREF, "dereference", &(pool_inst->dereference));
+       if (handle_config->dereference_str) {
+               do_ldap_option(LDAP_OPT_DEREF, "dereference", &(handle_config->dereference));
        }
 
        /*
         *      Leave "chase_referrals" unset to use the OpenLDAP default.
         */
-       if (!pool_inst->chase_referrals_unset) {
-               if (pool_inst->chase_referrals) {
+       if (!handle_config->chase_referrals_unset) {
+               if (handle_config->chase_referrals) {
                        do_ldap_option(LDAP_OPT_REFERRALS, "chase_referrals", LDAP_OPT_ON);
 
-                       if (pool_inst->rebind == true) {
+                       if (handle_config->rebind == true) {
 #if LDAP_SET_REBIND_PROC_ARGS == 3
-                               ldap_set_rebind_proc(conn->handle, rlm_ldap_rebind, conn);
+                               ldap_set_rebind_proc(conn->handle, fr_ldap_rebind, conn);
 #endif
                        }
                } else {
@@ -1827,56 +956,56 @@ void *mod_conn_create(TALLOC_CTX *ctx, void *instance, struct timeval const *tim
 
 #ifdef LDAP_OPT_NETWORK_TIMEOUT
        /*
-        *      A value of zero results in an pool_instant failure.
+        *      A value of zero results in an handle configuration failure.
         *
         *      When most people specify zero they mean infinite.
         *
         *      libldap requires tv_sec to be -1 to mean that.
         */
        {
-               struct timeval ldap_timeout = *timeout;
+               struct timeval ldap_timeout = handle_config->net_timeout;
 
                if ((ldap_timeout.tv_usec == 0) && (ldap_timeout.tv_sec == 0)) ldap_timeout.tv_sec = -1;
 
-               do_ldap_option(LDAP_OPT_NETWORK_TIMEOUT, "pool.connect_timeout", &ldap_timeout);
+               do_ldap_option(LDAP_OPT_NETWORK_TIMEOUT, "net_timeout", &ldap_timeout);
        }
 #endif
 
-       do_ldap_option(LDAP_OPT_TIMELIMIT, "srv_timelimit", &(pool_inst->srv_timelimit));
+       do_ldap_option(LDAP_OPT_TIMELIMIT, "srv_timelimit", &(handle_config->srv_timelimit));
 
        ldap_version = LDAP_VERSION3;
        do_ldap_option(LDAP_OPT_PROTOCOL_VERSION, "ldap_version", &ldap_version);
 
 #ifdef LDAP_OPT_X_KEEPALIVE_IDLE
-       do_ldap_option(LDAP_OPT_X_KEEPALIVE_IDLE, "keepalive_idle", &(pool_inst->keepalive_idle));
+       do_ldap_option(LDAP_OPT_X_KEEPALIVE_IDLE, "keepalive_idle", &(handle_config->keepalive_idle));
 #endif
 
 #ifdef LDAP_OPT_X_KEEPALIVE_PROBES
-       do_ldap_option(LDAP_OPT_X_KEEPALIVE_PROBES, "keepalive_probes", &(pool_inst->keepalive_probes));
+       do_ldap_option(LDAP_OPT_X_KEEPALIVE_PROBES, "keepalive_probes", &(handle_config->keepalive_probes));
 #endif
 
 #ifdef LDAP_OPT_X_KEEPALIVE_INTERVAL
-       do_ldap_option(LDAP_OPT_X_KEEPALIVE_INTERVAL, "keepalive_interval", &(pool_inst->keepalive_interval));
+       do_ldap_option(LDAP_OPT_X_KEEPALIVE_INTERVAL, "keepalive_interval", &(handle_config->keepalive_interval));
 #endif
 
 #ifdef HAVE_LDAP_START_TLS_S
        /*
         *      Set all of the TLS options
         */
-       if (pool_inst->tls_mode) do_ldap_option(LDAP_OPT_X_TLS, "tls_mode", &(pool_inst->tls_mode));
+       if (handle_config->tls_mode) do_ldap_option(LDAP_OPT_X_TLS, "tls_mode", &(handle_config->tls_mode));
 
-       maybe_ldap_option(LDAP_OPT_X_TLS_CACERTFILE, "ca_file", pool_inst->tls_ca_file);
-       maybe_ldap_option(LDAP_OPT_X_TLS_CACERTDIR, "ca_path", pool_inst->tls_ca_path);
+       maybe_ldap_option(LDAP_OPT_X_TLS_CACERTFILE, "ca_file", handle_config->tls_ca_file);
+       maybe_ldap_option(LDAP_OPT_X_TLS_CACERTDIR, "ca_path", handle_config->tls_ca_path);
 
        /*
         *      Set certificate options
         */
-       maybe_ldap_option(LDAP_OPT_X_TLS_CERTFILE, "certificate_file", pool_inst->tls_certificate_file);
-       maybe_ldap_option(LDAP_OPT_X_TLS_KEYFILE, "private_key_file", pool_inst->tls_private_key_file);
+       maybe_ldap_option(LDAP_OPT_X_TLS_CERTFILE, "certificate_file", handle_config->tls_certificate_file);
+       maybe_ldap_option(LDAP_OPT_X_TLS_KEYFILE, "private_key_file", handle_config->tls_private_key_file);
 
 #  ifdef LDAP_OPT_X_TLS_NEVER
-       if (pool_inst->tls_require_cert_str) {
-               do_ldap_option(LDAP_OPT_X_TLS_REQUIRE_CERT, "require_cert", &pool_inst->tls_require_cert);
+       if (handle_config->tls_require_cert_str) {
+               do_ldap_option(LDAP_OPT_X_TLS_REQUIRE_CERT, "require_cert", &handle_config->tls_require_cert);
        }
 #  endif
 
@@ -1892,32 +1021,15 @@ void *mod_conn_create(TALLOC_CTX *ctx, void *instance, struct timeval const *tim
        }
 #  endif
 
-       /*
-        *      And finally start the TLS code.
-        */
-       if (pool_inst->start_tls) {
-               if (pool_inst->port == 636) {
+       if (handle_config->start_tls) {
+               if (handle_config->port == 636) {
                        WARN("Told to Start TLS on LDAPS port this will probably fail, please correct the "
                             "configuration");
                }
-
-               if (ldap_start_tls_s(conn->handle, NULL, NULL) != LDAP_SUCCESS) {
-                       ldap_get_option(conn->handle, LDAP_OPT_ERROR_NUMBER, &ldap_errno);
-
-                       ERROR("Could not start TLS: %s", ldap_err2string(ldap_errno));
-                       goto error;
-               }
        }
 #endif /* HAVE_LDAP_START_TLS_S */
 
-       status = rlm_ldap_bind(inst, NULL, &conn, conn->pool_inst->admin_identity, conn->pool_inst->admin_password,
-                              &(conn->pool_inst->admin_sasl), false, timeout, NULL, NULL);
-       if (status != LDAP_PROC_SUCCESS) goto error;
-
-       /*
-        *      Only error out on memory allocation errors
-        */
-       if (rlm_ldap_directory_alloc(conn, &conn->directory, inst, &conn) < 0) goto error;
+       conn->config = handle_config;
 
        return conn;
 
@@ -1927,74 +1039,194 @@ error:
        return NULL;
 }
 
-/** Gets an LDAP socket from the connection pool
- *
- * Retrieve a socket from the connection pool, or NULL on error (of if no sockets are available).
- *
- * @param inst rlm_ldap configuration.
- * @param request Current request (may be NULL).
- */
-
-ldap_handle_t *mod_conn_get(rlm_ldap_t const *inst, REQUEST *request)
+int fr_ldap_conn_timeout_set(ldap_handle_t const *conn, struct timeval const *timeout)
 {
-       ldap_handle_t *conn;
+       int                             ldap_errno;
+       ldap_handle_config_t const      *handle_config = conn->config;
+
+#ifdef LDAP_OPT_NETWORK_TIMEOUT
+       /*
+        *      A value of zero results in an handle configuration failure.
+        *
+        *      When most people specify zero they mean infinite.
+        *
+        *      libldap requires tv_sec to be -1 to mean that.
+        */
+       {
+               struct timeval ldap_timeout = *timeout;
+
+               if ((ldap_timeout.tv_usec == 0) && (ldap_timeout.tv_sec == 0)) ldap_timeout.tv_sec = -1;
 
-       conn = fr_connection_get(inst->pool, request);
+               do_ldap_option(LDAP_OPT_NETWORK_TIMEOUT, "net_timeout", &ldap_timeout);
+       }
+#endif
+
+       return 0;
 
-       rad_assert(!conn || conn->pool_inst);
+error:
+       return -1;
+}
+
+int fr_ldap_conn_timeout_reset(ldap_handle_t const *conn)
+{
+       int                             ldap_errno;
+       ldap_handle_config_t const      *handle_config = conn->config;
 
-#ifdef LDAP_CONTROL_X_SESSION_TRACKING
+#ifdef LDAP_OPT_NETWORK_TIMEOUT
        /*
-        *      Add optional session tracking controls,
-        *      that contain values of some attributes
-        *      in the request.
+        *      A value of zero results in an handle configuration failure.
+        *
+        *      When most people specify zero they mean infinite.
+        *
+        *      libldap requires tv_sec to be -1 to mean that.
         */
-       if ((conn != NULL) && (request != NULL) && conn->pool_inst->session_tracking) {
-               if (rlm_ldap_control_add_session_tracking(conn, request) < 0) {
-                       fr_connection_release(inst->pool, request, conn);
-                       return NULL;
-               }
+       {
+               struct timeval ldap_timeout = handle_config->net_timeout;
+
+               if ((ldap_timeout.tv_usec == 0) && (ldap_timeout.tv_sec == 0)) ldap_timeout.tv_sec = -1;
+
+               do_ldap_option(LDAP_OPT_NETWORK_TIMEOUT, "net_timeout", &ldap_timeout);
        }
 #endif
-       return conn;
+
+       return 0;
+
+error:
+       return -1;
 }
 
-/** Frees an LDAP socket back to the connection pool
+/** Change settings global to libldap
  *
- * If the socket was rebound chasing a referral onto another server then we destroy it.
- * If the socket was rebound to another user on the same server, we let the next caller rebind it.
+ * May only be called once.  Subsequent calls will be ignored.
  *
- * @param inst rlm_ldap configuration.
- * @param request The current request.
- * @param conn to release.
+ * @param[in] debug_level      to enable in libldap.
+ * @param[in] tls_random_file  Where OpenSSL gets its randomness.
  */
-void mod_conn_release(rlm_ldap_t const *inst, REQUEST *request, ldap_handle_t *conn)
+int fr_ldap_global_config(int debug_level, char const *tls_random_file)
 {
-       /*
-        *      Could have already been free'd due to a previous error.
-        */
-       if (!conn) return;
+       static bool             done_config;
+       ldap_handle_config_t    *handle_config = &ldap_global_handle_config;
+
+       if (done_config) return 0;
+
+#define do_ldap_global_option(_option, _name, _value) \
+       if (ldap_set_option(NULL, _option, _value) != LDAP_OPT_SUCCESS) { \
+               int _ldap_errno; \
+               ldap_get_option(NULL, LDAP_OPT_ERROR_NUMBER, &_ldap_errno); \
+               ERROR("Failed setting global option %s: %s", _name, \
+                        (_ldap_errno != LDAP_SUCCESS) ? ldap_err2string(_ldap_errno) : "Unknown error"); \
+               return -1;\
+       }
+
+#define maybe_ldap_global_option(_option, _name, _value) \
+       if (_value) do_ldap_global_option(_option, _name, _value)
+
+#ifdef LDAP_OPT_DEBUG_LEVEL
+       if (debug_level) do_ldap_global_option(LDAP_OPT_DEBUG_LEVEL, "ldap_debug", &debug_level);
+#else
+       if (debug_level) WARN("ldap_debug not honoured as LDAP_OPT_DEBUG_LEVEL is not available");
+#endif
 
+#ifdef LDAP_OPT_X_TLS_RANDOM_FILE
        /*
-        *      Clear any client/server controls associated with the connection.
+        *      OpenLDAP will error out if we attempt to set
+        *      this on a handle. Presumably it's global in
+        *      OpenSSL too.
         */
-       rlm_ldap_control_clear(conn);
+       maybe_ldap_global_option(LDAP_OPT_X_TLS_RANDOM_FILE, "random_file", tls_random_file);
+#endif
+
+       done_config = true;
+
+       return 0;
+}
+
+/** Initialise libldap and check library versions
+ *
+ * @return
+ *     - 0 on success.
+ *     - -1 on failure.
+ */
+int fr_ldap_global_init(void)
+{
+       int                     ldap_errno;
+       static LDAPAPIInfo      info = { .ldapai_info_version = LDAP_API_INFO_VERSION };        /* static to quiet valgrind about this being uninitialised */
+       ldap_handle_config_t    *handle_config = &ldap_global_handle_config;
+
+       if (instance_count > 0) {
+               instance_count++;
+
+               return 0;
+       }
 
        /*
-        *      We chased a referral to another server.
-        *
-        *      This connection is no longer part of the pool which is
-        *      connected to and bound to the configured server.
-        *      Close it.
+        *      Only needs to be done once, prevents races in environment
+        *      initialisation within libldap.
         *
-        *      Note that we do NOT close it if it was bound to another user.
-        *      Instead, we let the next caller do the rebind.
+        *      See: https://github.com/arr2036/ldapperf/issues/2
         */
-       if (conn->referred) {
-               fr_connection_close(inst->pool, request, conn);
-               return;
+#ifdef HAVE_LDAP_INITIALIZE
+       ldap_initialize(&ldap_global_handle, "");
+#else
+       ldap_global_handle = ldap_init("", 0);
+#endif
+       if (!ldap_global_handle) {
+               ERROR("Failed initialising global LDAP handle");
+               return -1;
        }
 
-       fr_connection_release(inst->pool, request, conn);
-       return;
+       ldap_errno = ldap_get_option(NULL, LDAP_OPT_API_INFO, &info);
+       if (ldap_errno == LDAP_OPT_SUCCESS) {
+               /*
+                *      Don't generate warnings if the compile type vendor name
+                *      is found within the link time vendor name.
+                *
+                *      This allows the server to be built against OpenLDAP but
+                *      run with Symas OpenLDAP.
+                */
+               if (strcasestr(info.ldapai_vendor_name, LDAP_VENDOR_NAME) == NULL) {
+                       WARN("ldap - libldap vendor changed since the server was built");
+                       WARN("ldap - linked: %s, built: %s", info.ldapai_vendor_name, LDAP_VENDOR_NAME);
+               }
+
+               if (info.ldapai_vendor_version < LDAP_VENDOR_VERSION) {
+                       WARN("ldap - libldap older than the version the server was built against");
+                       WARN("ldap - linked: %i, built: %i",
+                            info.ldapai_vendor_version, LDAP_VENDOR_VERSION);
+               }
+
+               INFO("ldap - libldap vendor: %s, version: %i", info.ldapai_vendor_name,
+                    info.ldapai_vendor_version);
+
+               ldap_memfree(info.ldapai_vendor_name);
+               ldap_memfree(info.ldapai_extensions);
+       } else {
+               DEBUG("ldap - Falling back to build time libldap version info.  Query for LDAP_OPT_API_INFO "
+                     "returned: %i", ldap_errno);
+               INFO("ldap - libldap vendor: %s, version: %i.%i.%i", LDAP_VENDOR_NAME,
+                    LDAP_VENDOR_VERSION_MAJOR, LDAP_VENDOR_VERSION_MINOR, LDAP_VENDOR_VERSION_PATCH);
+       }
+
+       instance_count++;
+
+       return 0;
+}
+
+/** Free any global libldap resources
+ *
+ */
+void fr_ldap_global_free(void)
+{
+       if (--instance_count > 0) return;
+
+       /*
+        *      Keeping the dummy ld around for the lifetime
+        *      of the module should always work,
+        *      irrespective of what changes happen in libldap.
+        */
+#ifdef HAVE_LDAP_UNBIND_EXT_S
+       ldap_unbind_ext_s(ldap_global_handle, NULL, NULL);
+#else
+       ldap_unbind_s(ldap_global_handle);
+#endif
 }
diff --git a/src/modules/rlm_ldap/libfreeradius-ldap.mk.in b/src/modules/rlm_ldap/libfreeradius-ldap.mk.in
new file mode 100644 (file)
index 0000000..cf38b7b
--- /dev/null
@@ -0,0 +1,10 @@
+TARGETNAME     := @targetname@
+
+ifneq "$(TARGETNAME)" ""
+TARGET         := $(TARGETNAME).a
+endif
+
+SOURCES                := control.c directory.c edir.c ldap.c @SASL@
+
+SRC_CFLAGS     := @mod_cflags@
+TGT_LDLIBS     := @mod_ldflags@
diff --git a/src/modules/rlm_ldap/libldap.h b/src/modules/rlm_ldap/libldap.h
new file mode 100644 (file)
index 0000000..90bbd93
--- /dev/null
@@ -0,0 +1,421 @@
+/**
+ * $Id$
+ * @file libldap.h
+ * @brief Common utility functions for interacting with LDAP directories
+ *
+ * @note Do not rename to ldap.h.  This causes configure checks to break
+ *     in stupid ways, where the configure script will use the local ldap.h
+ *     file, instead of the one from libldap.
+ *
+ * @author Arran Cudbard-Bell <a.cudbardb@freeradius.org>
+ *
+ * @copyright 2017 The FreeRADIUS Server Project.
+ * @copyright 2017 Arran Cudbard-Bell <a.cudbardb@freeradius.org>
+ */
+#ifndef LIBFREERADIUS_LDAP_H
+#define        LIBFREERADIUS_LDAP_H
+
+#include <freeradius-devel/radiusd.h>
+#include <lber.h>
+#include <ldap.h>
+#include "config.h"
+
+extern LDAP *ldap_global_handle;
+
+/*
+ *     Framework on OSX doesn't export the symbols but leaves
+ *     the macro defined *sigh*.
+ */
+#ifndef HAVE_LDAP_CREATE_SESSION_TRACKING_CONTROL
+#  undef LDAP_CONTROL_X_SESSION_TRACKING
+#endif
+
+/*
+ *     There's a typo in libldap's ldap.h which was fixed by
+ *     Howard Chu in 19aeb1cd. This typo had the function defined
+ *     as ldap_create_session_tracking_control but declared as
+ *     ldap_create_session_tracking.
+ *
+ *     We fix this, by adding the correct declaration here.
+ */
+#ifdef LDAP_CONTROL_X_SESSION_TRACKING
+#  if !defined(HAVE_DECL_LDAP_CREATE_SESSION_TRACKING_CONTROL) || (HAVE_DECL_LDAP_CREATE_SESSION_TRACKING_CONTROL == 0)
+LDAP_F( int )
+ldap_create_session_tracking_control LDAP_P((
+        LDAP            *ld,
+        char            *sessionSourceIp,
+        char            *sessionSourceName,
+        char            *formatOID,
+        struct berval   *sessionTrackingIdentifier,
+        LDAPControl     **ctrlp ));
+#  endif
+#endif
+
+/*
+ *     Ensure the have the ldap_create_sort_keylist()
+ *     function too, else we can't use ldap_create_sort_control()
+ */
+#if !defined(HAVE_LDAP_CREATE_SORT_KEYLIST) || !defined(HAVE_LDAP_FREE_SORT_KEYLIST)
+#  undef HAVE_LDAP_CREATE_SORT_CONTROL
+#endif
+
+/*
+ *     Because the LTB people define LDAP_VENDOR_VERSION_PATCH
+ *     as X, which precludes its use in printf statements *sigh*
+ *
+ *     Identifiers that are not macros, all evaluate to 0,
+ *     which is why this works.
+ */
+#if !defined(LDAP_VENDOR_VERSION_PATCH) || LDAP_VENDOR_VERSION_PATCH == 0
+#  undef LDAP_VENDOR_VERSION_PATCH
+#  define LDAP_VENDOR_VERSION_PATCH 0
+#endif
+
+/*
+ *      For compatibility with other LDAP libraries
+ */
+#if !defined(LDAP_SCOPE_BASE) && defined(LDAP_SCOPE_BASEOBJECT)
+#  define LDAP_SCOPE_BASE LDAP_SCOPE_BASEOBJECT
+#endif
+
+#if !defined(LDAP_SCOPE_ONE) && defined(LDAP_SCOPE_ONELEVEL)
+#  define LDAP_SCOPE_ONE LDAP_SCOPE_ONELEVEL
+#endif
+
+#if !defined(LDAP_SCOPE_SUB) && defined(LDAP_SCOPE_SUBTREE)
+#  define LDAP_SCOPE_SUB LDAP_SCOPE_SUBTREE
+#endif
+
+#if !defined(LDAP_OPT_RESULT_CODE) && defined(LDAP_OPT_ERROR_NUMBER)
+#  define LDAP_OPT_RESULT_CODE LDAP_OPT_ERROR_NUMBER
+#endif
+
+#ifndef LDAP_CONST
+#  define LDAP_CONST
+#endif
+
+#if defined(HAVE_LDAP_URL_PARSE) && defined(HAVE_LDAP_IS_LDAP_URL) && defined(HAVE_LDAP_URL_DESC2STR)
+#  define LDAP_CAN_PARSE_URLS
+#endif
+
+#define LDAP_MAX_CONTROLS              10              //!< Maximum number of client/server controls.
+                                                       //!< Used to allocate static arrays of control pointers.
+#define LDAP_MAX_ATTRMAP               128             //!< Maximum number of mappings between LDAP and
+                                                       //!< FreeRADIUS attributes.
+#define LDAP_MAP_RESERVED              4               //!< Number of additional items to allocate in expanded
+                                                       //!< attribute name arrays. Currently for enable attribute,
+                                                       //!< group membership attribute, valuepair attribute,
+                                                       //!< and profile attribute.
+
+#define LDAP_MAX_CACHEABLE             64              //!< Maximum number of groups we retrieve from the server for
+                                                       //!< a given user. If more than this number are retrieve the
+                                                       //!< module returns invalid.
+
+#define LDAP_MAX_GROUP_NAME_LEN                128             //!< Maximum name of a group name.
+#define LDAP_MAX_ATTR_STR_LEN          256             //!< Maximum length of an xlat expanded LDAP attribute.
+#define LDAP_MAX_FILTER_STR_LEN                1024            //!< Maximum length of an xlat expanded filter.
+#define LDAP_MAX_DN_STR_LEN            1024            //!< Maximum length of an xlat expanded DN.
+
+#define LDAP_VIRTUAL_DN_ATTR           "dn"            //!< 'Virtual' attribute which maps to the DN of the object.
+
+
+typedef enum {
+       LDAP_EXT_UNSUPPORTED,                           //!< Unsupported extension.
+       LDAP_EXT_BINDNAME,                              //!< Specifies the user DN or name for an LDAP bind.
+       LDAP_EXT_BINDPW,                                //!< Specifies the password for an LDAP bind.
+} ldap_supported_extension_t;
+
+typedef struct ldap_sasl {
+       char const      *mech;                          //!< SASL mech(s) to try.
+       char const      *proxy;                         //!< Identity to proxy.
+       char const      *realm;                         //!< Kerberos realm.
+} ldap_sasl;
+
+typedef struct fr_ldap_control {
+       LDAPControl     *control;                       //!< LDAP control.
+       bool            freeit;                         //!< Whether the control should be freed after
+                                                       //!< we've finished using it.
+} fr_ldap_control_t;
+
+typedef enum {
+       LDAP_DIRECTORY_UNKNOWN = 0,                     //!< We can't determine the directory server.
+
+       LDAP_DIRECTORY_ACTIVE_DIRECTORY,                //!< Directory server is Active Directory.
+       LDAP_DIRECTORY_EDIRECTORY,                      //!< Directory server is eDir.
+       LDAP_DIRECTORY_IBM,                             //!< Directory server is IBM.
+       LDAP_DIRECTORY_NETSCAPE,                        //!< Directory server is Netscape.
+       LDAP_DIRECTORY_OPENLDAP,                        //!< Directory server is OpenLDAP.
+       LDAP_DIRECTORY_ORACLE_INTERNET_DIRECTORY,       //!< Directory server is Oracle Internet Directory.
+       LDAP_DIRECTORY_ORACLE_UNIFIED_DIRECTORY,        //!< Directory server is Oracle Unified Directory.
+       LDAP_DIRECTORY_ORACLE_VIRTUAL_DIRECTORY,        //!< Directory server is Oracle Virtual Directory.
+       LDAP_DIRECTORY_SUN_ONE_DIRECTORY,               //!< Directory server is Sun One Directory.
+       LDAP_DIRECTORY_SIEMENS_AG,                      //!< Directory server is Siemens AG.
+       LDAP_DIRECTORY_UNBOUND_ID                       //!< Directory server is Unbound ID
+} ldap_directory_type_t;
+
+typedef struct ldap_directory {
+       char const              *vendor_str;            //!< As returned from the vendorName attribute in the
+                                                       //!< rootDSE.
+       char const              *version_str;           //!< As returned from the vendorVersion attribute in the
+                                                       //!< rootDSE.
+       ldap_directory_type_t   type;                   //!< Cannonical server implementation.
+
+       bool                    cleartext_password;     //!< Whether the server will return the user's plaintext
+                                                       //!< password.
+} ldap_directory_t;
+
+/** Connection configuration
+ *
+ * Must not be passed into functions except via the connection handle
+ * this avoids problems with not using the connection pool configuration.
+ */
+typedef struct {
+       char const      *name;                          //!< Name of the module that created this connection.
+
+       char            *server;                        //!< Initial server to bind to.
+       char const      **server_str;                   //!< Server set in the config.
+
+       uint16_t        port;                           //!< Port to use when binding to the server.
+
+       char const      *admin_identity;                //!< Identity we bind as when we need to query the LDAP
+                                                       //!< directory.
+       char const      *admin_password;                //!< Password used in administrative bind.
+
+       ldap_sasl       admin_sasl;                     //!< SASL parameters used when binding as the admin.
+
+       int             dereference;                    //!< libldap value specifying dereferencing behaviour.
+       char const      *dereference_str;               //!< When to dereference (never, searching, finding, always)
+
+       bool            chase_referrals;                //!< If the LDAP server returns a referral to another server
+                                                       //!< or point in the tree, follow it, establishing new
+                                                       //!< connections and binding where necessary.
+       bool            chase_referrals_unset;          //!< If true, use the OpenLDAP defaults for chase_referrals.
+
+       bool            use_referral_credentials;       //!< If true use credentials from the referral URL.
+
+       bool            rebind;                         //!< Controls whether we set an ldad_rebind_proc function
+                                                       //!< and so determines if we can bind to other servers whilst
+                                                       //!< chasing referrals. If this is false, we will still chase
+                                                       //!< referrals on the same server, but won't bind to other
+                                                       //!< servers.
+
+       /*
+        *      TLS items.
+        */
+       int             tls_mode;
+
+       bool            start_tls;                      //!< Send the Start TLS message to the LDAP directory
+                                                       //!< to start encrypted communications using the standard
+                                                       //!< LDAP port.
+
+       char const      *tls_ca_file;                   //!< Sets the full path to a CA certificate (used to validate
+                                                       //!< the certificate the server presents).
+
+       char const      *tls_ca_path;                   //!< Sets the path to a directory containing CA certificates.
+
+       char const      *tls_certificate_file;          //!< Sets the path to the public certificate file we present
+                                                       //!< to the servers.
+
+       char const      *tls_private_key_file;          //!< Sets the path to the private key for our public
+                                                       //!< certificate.
+
+       char const      *tls_require_cert_str;          //!< Sets requirements for validating the certificate the
+                                                       //!< server presents.
+
+       int             tls_require_cert;               //!< OpenLDAP constant representing the require cert string.
+
+
+       /*
+        *      For keep-alives.
+        */
+#ifdef LDAP_OPT_X_KEEPALIVE_IDLE
+       uint32_t        keepalive_idle;                 //!< Number of seconds a connections needs to remain idle
+                                                       //!< before TCP starts sending keepalive probes.
+#endif
+#ifdef LDAP_OPT_X_KEEPALIVE_PROBES
+       uint32_t        keepalive_probes;               //!< Number of missed timeouts before the connection is
+                                                       //!< dropped.
+#endif
+#ifdef LDAP_OPT_X_KEEPALIVE_INTERVAL
+       uint32_t        keepalive_interval;             //!< Interval between keepalive probes.
+#endif
+
+       /*
+        *      Search timelimits
+        */
+       uint32_t        srv_timelimit;                  //!< How long the server should spent on a single request
+                                                       //!< (also bounded by value on the server).
+
+       struct timeval  net_timeout;                    //!< How long we wait in blocking network calls.
+                                                       //!< We set this in the LDAP API, even though with
+                                                       //!< async calls, we control this using our event loop.
+                                                       //!< This is just in case there are blocking calls which
+                                                       //!< happen internally which we can't work around.
+
+       struct timeval  res_timeout;                    //!< How long we wait for results.
+} ldap_handle_config_t;
+
+/** Tracks the state of a libldap connection handle
+ *
+ */
+typedef struct ldap_handle {
+       LDAP            *handle;                        //!< libldap handle.
+       bool            rebound;                        //!< Whether the connection has been rebound to something
+                                                       //!< other than the admin user.
+       bool            referred;                       //!< Whether the connection is now established a server
+                                                       //!< other than the configured one.
+
+       fr_ldap_control_t serverctrls[LDAP_MAX_CONTROLS + 1];   //!< Server controls to use for all operations with
+                                                               //!< this handle.
+       fr_ldap_control_t clientctrls[LDAP_MAX_CONTROLS + 1];   //!< Client controls to use for all operations with
+                                                               //!< this handle.
+       int             serverctrls_cnt;                //!< Number of server controls associated with the handle.
+       int             clientctrls_cnt;                //!< Number of client controls associated with the handle.
+
+       ldap_directory_t *directory;                    //!< The type of directory we're connected to.
+
+       ldap_handle_config_t const *config;             //!< rlm_ldap connection configuration.
+} ldap_handle_t;
+
+/** Contains a collection of values
+ *
+ */
+typedef struct fr_ldap_result {
+       struct berval   **values;                       //!< libldap struct containing bv_val (char *)
+                                                       //!< and length bv_len.
+       int             count;                          //!< Number of values.
+} fr_ldap_result_t;
+
+/** Codes returned by fr_ldap internal functions
+ *
+ */
+typedef enum {
+       LDAP_PROC_CONTINUE = 1,                         //!< Operation is in progress.
+       LDAP_PROC_SUCCESS = 0,                          //!< Operation was successfull.
+
+       LDAP_PROC_ERROR = -1,                           //!< Unrecoverable library/server error.
+
+       LDAP_PROC_BAD_CONN      = -2,                           //!< Transitory error, caller should retry the operation
+                                                       //!< with a new connection.
+
+       LDAP_PROC_NOT_PERMITTED = -3,                   //!< Operation was not permitted, either current user was
+                                                       //!< locked out in the case of binds, or has insufficient
+                                                       //!< access.
+
+       LDAP_PROC_REJECT = -4,                          //!< Bind failed, user was rejected.
+
+       LDAP_PROC_BAD_DN = -5,                          //!< Specified an invalid object in a bind or search DN.
+
+       LDAP_PROC_NO_RESULT = -6,                       //!< Got no results.
+
+       LDAP_PROC_TIMEOUT = -7                          //!< Operation timed out.
+} ldap_rcode_t;
+
+
+extern FR_NAME_NUMBER const ldap_supported_extensions[];
+
+extern FR_NAME_NUMBER const ldap_scope[];
+extern FR_NAME_NUMBER const ldap_tls_require_cert[];
+
+/*
+ *     ldap.c - Wrappers arounds OpenLDAP functions.
+ */
+void           fr_ldap_timeout_debug(REQUEST *request, ldap_handle_t const *conn,
+                                     struct timeval const *timeout, char const *prefix);
+
+size_t         fr_ldap_escape_func(UNUSED REQUEST *request, char *out, size_t outlen, char const *in, UNUSED void *arg);
+
+size_t         fr_ldap_unescape_func(UNUSED REQUEST *request, char *out, size_t outlen, char const *in, UNUSED void *arg);
+
+ssize_t                fr_ldap_xlat_filter(REQUEST *request, char const **sub, size_t sublen, char *out, size_t outlen);
+
+ldap_rcode_t   fr_ldap_bind(REQUEST *request,
+                            ldap_handle_t **pconn,
+                            char const *dn, char const *password,
+#ifdef WITH_SASL
+                            ldap_sasl const *sasl,
+#else
+                            NDEBUG_UNUSED ldap_sasl const *sasl,
+#endif
+                            struct timeval const *timeout,
+                            LDAPControl **serverctrls, LDAPControl **clientctrls);
+
+char const     *fr_ldap_error_str(ldap_handle_t const *conn);
+
+ldap_rcode_t   fr_ldap_search(LDAPMessage **result, REQUEST *request,
+                              ldap_handle_t **pconn,
+                              char const *dn, int scope, char const *filter, char const * const * attrs,
+                              LDAPControl **serverctrls, LDAPControl **clientctrls);
+
+ldap_rcode_t   fr_ldap_modify(REQUEST *request, ldap_handle_t **pconn,
+                              char const *dn, LDAPMod *mods[],
+                              LDAPControl **serverctrls, LDAPControl **clientctrls);
+
+
+ldap_rcode_t   fr_ldap_result(ldap_handle_t const *conn, int msgid, char const *dn,
+                              struct timeval const *timeout,
+                              LDAPMessage **result, char const **error, char **extra);
+
+ldap_handle_t  *fr_ldap_conn_alloc(TALLOC_CTX *ctx, ldap_handle_config_t const *handle_config);
+
+int            fr_ldap_conn_timeout_set(ldap_handle_t const *conn, struct timeval const *timeout);
+
+int            fr_ldap_conn_timeout_reset(ldap_handle_t const *conn);
+
+int            fr_ldap_global_config(int debug_level, char const *tls_random_file);
+
+int            fr_ldap_global_init(void);
+
+void           fr_ldap_global_free(void);
+
+/*
+ *     control.c - Connection based client/server controls
+ */
+void           fr_ldap_control_merge(LDAPControl *serverctrls_out[],
+                                     LDAPControl *clientctrls_out[],
+                                     size_t serverctrls_len,
+                                     size_t clientctrls_len,
+                                     ldap_handle_t *conn,
+                                     LDAPControl *serverctrls_in[],
+                                     LDAPControl *clientctrls_in[]);
+
+int            fr_ldap_control_add_server(ldap_handle_t *conn, LDAPControl *ctrl, bool freeit);
+
+int            fr_ldap_control_add_client(ldap_handle_t *conn, LDAPControl *ctrl, bool freeit);
+
+void           fr_ldap_control_clear(ldap_handle_t *conn);
+
+int            fr_ldap_control_add_session_tracking(ldap_handle_t *conn, REQUEST *request);
+
+/*
+ *     directory.c - Get directory capabilities from the remote server
+ */
+int            fr_ldap_directory_alloc(TALLOC_CTX *ctx, ldap_directory_t **out, ldap_handle_t **pconn);
+
+
+/*
+ *     sasl.c - SASL bind functions
+ */
+ldap_rcode_t   fr_ldap_sasl_interactive(REQUEST *request,
+                                        ldap_handle_t *pconn, char const *dn,
+                                        char const *password, ldap_sasl const *sasl,
+                                        LDAPControl **serverctrls, LDAPControl **clientctrls,
+                                        struct timeval const *timeout,
+                                        char const **error, char **error_extra);
+
+/*
+ *     uti.c - Utility functions
+ */
+size_t         fr_ldap_common_dn(char const *full, char const *part);
+
+bool           fr_ldap_util_is_dn(char const *in, size_t inlen);
+
+size_t         fr_ldap_util_normalise_dn(char *out, char const *in);
+
+char           *fr_ldap_berval_to_string(TALLOC_CTX *ctx, struct berval const *in);
+
+int            fr_ldap_parse_url_extensions(LDAPControl **sss, REQUEST *request,
+                                            ldap_handle_t *conn, char **extensions);
+
+
+#endif
index ba05c4cab85ae4979a926748bb20c7b6e5d04bd2..b117595bb76a551a4367b72909a681cd6b4e0136 100644 (file)
@@ -31,9 +31,6 @@ RCSID("$Id$")
 
 #include <freeradius-devel/rad_assert.h>
 
-#include <stdarg.h>
-#include <ctype.h>
-
 #include "rlm_ldap.h"
 
 #include <freeradius-devel/map_proc.h>
@@ -73,9 +70,9 @@ static FR_NAME_NUMBER const ldap_dereference[] = {
 };
 
 static CONF_PARSER sasl_mech_dynamic[] = {
-       { FR_CONF_OFFSET("mech", PW_TYPE_TMPL | PW_TYPE_NOT_EMPTY, ldap_sasl_dynamic, mech) },
-       { FR_CONF_OFFSET("proxy", PW_TYPE_TMPL, ldap_sasl_dynamic, proxy) },
-       { FR_CONF_OFFSET("realm", PW_TYPE_TMPL, ldap_sasl_dynamic, realm) },
+       { FR_CONF_OFFSET("mech", PW_TYPE_TMPL | PW_TYPE_NOT_EMPTY, ldap_sasl_dynamic_t, mech) },
+       { FR_CONF_OFFSET("proxy", PW_TYPE_TMPL, ldap_sasl_dynamic_t, proxy) },
+       { FR_CONF_OFFSET("realm", PW_TYPE_TMPL, ldap_sasl_dynamic_t, realm) },
        CONF_PARSER_TERMINATOR
 };
 
@@ -93,20 +90,20 @@ static CONF_PARSER tls_config[] = {
        /*
         *      Deprecated attributes
         */
-       { FR_CONF_OFFSET("ca_file", PW_TYPE_FILE_INPUT, ldap_pool_inst_t, tls_ca_file) },
+       { FR_CONF_OFFSET("ca_file", PW_TYPE_FILE_INPUT, ldap_handle_config_t, tls_ca_file) },
 
-       { FR_CONF_OFFSET("ca_path", PW_TYPE_FILE_INPUT, ldap_pool_inst_t, tls_ca_path) },
+       { FR_CONF_OFFSET("ca_path", PW_TYPE_FILE_INPUT, ldap_handle_config_t, tls_ca_path) },
 
-       { FR_CONF_OFFSET("certificate_file", PW_TYPE_FILE_INPUT, ldap_pool_inst_t, tls_certificate_file) },
+       { FR_CONF_OFFSET("certificate_file", PW_TYPE_FILE_INPUT, ldap_handle_config_t, tls_certificate_file) },
 
-       { FR_CONF_OFFSET("private_key_file", PW_TYPE_FILE_INPUT, ldap_pool_inst_t, tls_private_key_file) },
+       { FR_CONF_OFFSET("private_key_file", PW_TYPE_FILE_INPUT, ldap_handle_config_t, tls_private_key_file) },
 
        /*
         *      LDAP Specific TLS attributes
         */
-       { FR_CONF_OFFSET("start_tls", PW_TYPE_BOOLEAN, ldap_pool_inst_t, start_tls), .dflt = "no" },
+       { FR_CONF_OFFSET("start_tls", PW_TYPE_BOOLEAN, ldap_handle_config_t, start_tls), .dflt = "no" },
 
-       { FR_CONF_OFFSET("require_cert", PW_TYPE_STRING, ldap_pool_inst_t, tls_require_cert_str) },
+       { FR_CONF_OFFSET("require_cert", PW_TYPE_STRING, ldap_handle_config_t, tls_require_cert_str) },
 
        CONF_PARSER_TERMINATOR
 };
@@ -178,41 +175,37 @@ static CONF_PARSER option_config[] = {
        /*
         *      Pool config items
         */
-       { FR_CONF_OFFSET("chase_referrals", PW_TYPE_BOOLEAN, rlm_ldap_t, pool_inst.chase_referrals) },
+       { FR_CONF_OFFSET("chase_referrals", PW_TYPE_BOOLEAN, rlm_ldap_t, handle_config.chase_referrals) },
 
-       { FR_CONF_OFFSET("use_referral_credentials", PW_TYPE_BOOLEAN, rlm_ldap_t, pool_inst.use_referral_credentials), .dflt = "no" },
+       { FR_CONF_OFFSET("use_referral_credentials", PW_TYPE_BOOLEAN, rlm_ldap_t, handle_config.use_referral_credentials), .dflt = "no" },
 
-       { FR_CONF_OFFSET("rebind", PW_TYPE_BOOLEAN, rlm_ldap_t, pool_inst.rebind) },
-
-#ifdef LDAP_CONTROL_X_SESSION_TRACKING
-       { FR_CONF_OFFSET("session_tracking", PW_TYPE_BOOLEAN, rlm_ldap_t, pool_inst.session_tracking), .dflt = "no" },
-#endif
+       { FR_CONF_OFFSET("rebind", PW_TYPE_BOOLEAN, rlm_ldap_t, handle_config.rebind) },
 
 #ifdef LDAP_OPT_NETWORK_TIMEOUT
        /* timeout on network activity */
-       { FR_CONF_DEPRECATED("net_timeout", PW_TYPE_INTEGER, rlm_ldap_t, pool_inst.net_timeout), .dflt = "10" },
+       { FR_CONF_DEPRECATED("net_timeout", PW_TYPE_INTEGER, rlm_ldap_t, handle_config.net_timeout), .dflt = "10" },
 #endif
 
 #ifdef LDAP_OPT_X_KEEPALIVE_IDLE
-       { FR_CONF_OFFSET("idle", PW_TYPE_INTEGER, rlm_ldap_t, pool_inst.keepalive_idle), .dflt = "60" },
+       { FR_CONF_OFFSET("idle", PW_TYPE_INTEGER, rlm_ldap_t, handle_config.keepalive_idle), .dflt = "60" },
 #endif
 #ifdef LDAP_OPT_X_KEEPALIVE_PROBES
-       { FR_CONF_OFFSET("probes", PW_TYPE_INTEGER, rlm_ldap_t, pool_inst.keepalive_probes), .dflt = "3" },
+       { FR_CONF_OFFSET("probes", PW_TYPE_INTEGER, rlm_ldap_t, handle_config.keepalive_probes), .dflt = "3" },
 #endif
 #ifdef LDAP_OPT_X_KEEPALIVE_INTERVAL
-       { FR_CONF_OFFSET("interval", PW_TYPE_INTEGER, rlm_ldap_t, pool_inst.keepalive_interval), .dflt = "30" },
+       { FR_CONF_OFFSET("interval", PW_TYPE_INTEGER, rlm_ldap_t, handle_config.keepalive_interval), .dflt = "30" },
 #endif
 
-       { FR_CONF_OFFSET("dereference", PW_TYPE_STRING, rlm_ldap_t, pool_inst.dereference_str) },
+       { FR_CONF_OFFSET("dereference", PW_TYPE_STRING, rlm_ldap_t, handle_config.dereference_str) },
 
        /* allow server unlimited time for search (server-side limit) */
-       { FR_CONF_OFFSET("srv_timelimit", PW_TYPE_INTEGER, rlm_ldap_t, pool_inst.srv_timelimit), .dflt = "20" },
+       { FR_CONF_OFFSET("srv_timelimit", PW_TYPE_INTEGER, rlm_ldap_t, handle_config.srv_timelimit), .dflt = "20" },
 
        /*
         *      Instance config items
         */
        /* timeout for search results */
-       { FR_CONF_OFFSET("res_timeout", PW_TYPE_INTEGER, rlm_ldap_t, res_timeout), .dflt = "20" },
+       { FR_CONF_OFFSET("res_timeout", PW_TYPE_TIMEVAL, rlm_ldap_t, handle_config.res_timeout), .dflt = "20" },
 
        CONF_PARSER_TERMINATOR
 };
@@ -229,17 +222,21 @@ static const CONF_PARSER module_config[] = {
        /*
         *      Pool config items
         */
-       { FR_CONF_OFFSET("server", PW_TYPE_STRING | PW_TYPE_MULTI, rlm_ldap_t, pool_inst.server_str) }, /* Do not set to required */
+       { FR_CONF_OFFSET("server", PW_TYPE_STRING | PW_TYPE_MULTI, rlm_ldap_t, handle_config.server_str) },     /* Do not set to required */
 
-       { FR_CONF_OFFSET("port", PW_TYPE_SHORT, rlm_ldap_t, pool_inst.port) },
+       { FR_CONF_OFFSET("port", PW_TYPE_SHORT, rlm_ldap_t, handle_config.port) },
 
-       { FR_CONF_OFFSET("identity", PW_TYPE_STRING, rlm_ldap_t, pool_inst.admin_identity) },
-       { FR_CONF_OFFSET("password", PW_TYPE_STRING | PW_TYPE_SECRET, rlm_ldap_t, pool_inst.admin_password) },
+       { FR_CONF_OFFSET("identity", PW_TYPE_STRING, rlm_ldap_t, handle_config.admin_identity) },
+       { FR_CONF_OFFSET("password", PW_TYPE_STRING | PW_TYPE_SECRET, rlm_ldap_t, handle_config.admin_password) },
 
-       { FR_CONF_OFFSET("sasl", PW_TYPE_SUBSECTION, rlm_ldap_t, pool_inst.admin_sasl), .subcs = (void const *) sasl_mech_static },
+       { FR_CONF_OFFSET("sasl", PW_TYPE_SUBSECTION, rlm_ldap_t, handle_config.admin_sasl), .subcs = (void const *) sasl_mech_static },
 
        { FR_CONF_OFFSET("valuepair_attribute", PW_TYPE_STRING, rlm_ldap_t, valuepair_attr) },
 
+#ifdef LDAP_CONTROL_X_SESSION_TRACKING
+       { FR_CONF_OFFSET("session_tracking", PW_TYPE_BOOLEAN, rlm_ldap_t, session_tracking), .dflt = "no" },
+#endif
+
 #ifdef WITH_EDIR
        /* support for eDirectory Universal Password */
        { FR_CONF_OFFSET("edir", PW_TYPE_BOOLEAN, rlm_ldap_t, edir) }, /* NULL defaults to "no" */
@@ -265,98 +262,22 @@ static const CONF_PARSER module_config[] = {
 
        { FR_CONF_POINTER("global", PW_TYPE_SUBSECTION, NULL), .subcs = (void const *) global_config },
 
-       { FR_CONF_OFFSET("tls", PW_TYPE_SUBSECTION, rlm_ldap_t, pool_inst), .subcs = (void const *) tls_config },
+       { FR_CONF_OFFSET("tls", PW_TYPE_SUBSECTION, rlm_ldap_t, handle_config), .subcs = (void const *) tls_config },
        CONF_PARSER_TERMINATOR
 };
 
-
-static LDAP *global_handle;                    //!< Hack for OpenLDAP libldap global initialisation.
-
 static ssize_t ldap_escape_xlat(UNUSED TALLOC_CTX *ctx, char **out, size_t outlen,
                                UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
                                REQUEST *request, char const *fmt)
 {
-       return rlm_ldap_escape_func(request, *out, outlen, fmt, NULL);
+       return fr_ldap_escape_func(request, *out, outlen, fmt, NULL);
 }
 
 static ssize_t ldap_unescape_xlat(UNUSED TALLOC_CTX *ctx, char **out, size_t outlen,
                                  UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
                                  REQUEST *request, char const *fmt)
 {
-       return rlm_ldap_unescape_func(request, *out, outlen, fmt, NULL);
-}
-
-/** Parse a subset (just server side sort for now) of LDAP URL extensions
- *
- * @param[out] sss             Where to write a pointer to the server side sort control
- *                             we created.
- * @param[in] request          The current request.
- * @param[in] conn             Handle to allocate controls under.
- * @param[in] extensions       A NULL terminated array of extensions.
- * @return
- *     - 0 on success.
- *     - -1 on failure.
- */
-static int ldap_parse_url_extensions(LDAPControl **sss, REQUEST *request, ldap_handle_t *conn, char **extensions)
-{
-       int i;
-
-       *sss = NULL;
-
-       if (!extensions) return 0;
-
-       /*
-        *      Parse extensions in the LDAP URL
-        */
-       for (i = 0; extensions[i]; i++) {
-               char *p;
-               bool is_critical = false;
-
-               p = extensions[i];
-               if (*p == '!') {
-                       is_critical = true;
-                       p++;
-               }
-
-#ifdef HAVE_LDAP_CREATE_SORT_CONTROL
-               /*
-                *      Server side sort control
-                */
-               if (strncmp(p, "sss", 3) == 0) {
-                       LDAPSortKey     **keys;
-                       int             ret;
-
-                       p += 3;
-                       p = strchr(p, '=');
-                       if (!p) {
-                               REDEBUG("Server side sort extension must be in the format \"[!]sss=<key>[,key]\"");
-                               return -1;
-                       }
-                       p++;
-
-                       ret = ldap_create_sort_keylist(&keys, p);
-                       if (ret != LDAP_SUCCESS) {
-                               REDEBUG("Invalid server side sort value \"%s\": %s", p, ldap_err2string(ret));
-                               return -1;
-                       }
-
-                       if (*sss) ldap_control_free(*sss);
-
-                       ret = ldap_create_sort_control(conn->handle, keys, is_critical ? 1 : 0, sss);
-                       ldap_free_sort_keylist(keys);
-                       if (ret != LDAP_SUCCESS) {
-                               ERROR("Failed creating server sort control: %s", ldap_err2string(ret));
-                               return -1;
-                       }
-
-                       continue;
-               }
-#endif
-
-               RWDEBUG("URL extension \"%s\" ignored", p);
-       }
-
-       return 0;
+       return fr_ldap_unescape_func(request, *out, outlen, fmt, NULL);
 }
 
 /** Expand an LDAP URL into a query, and return a string result from that query.
@@ -413,10 +334,10 @@ static ssize_t ldap_xlat(UNUSED TALLOC_CTX *ctx, char **out, size_t outlen,
 
        memcpy(&attrs, &ldap_url->lud_attrs, sizeof(attrs));
 
-       if (ldap_parse_url_extensions(&server_ctrls[0], request, conn, ldap_url->lud_exts) < 0) goto free_socket;
+       if (fr_ldap_parse_url_extensions(&server_ctrls[0], request, conn, ldap_url->lud_exts) < 0) goto free_socket;
 
-       status = rlm_ldap_search(&result, inst, request, &conn, ldap_url->lud_dn, ldap_url->lud_scope,
-                                ldap_url->lud_filter, attrs, server_ctrls, NULL);
+       status = fr_ldap_search(&result, request, &conn, ldap_url->lud_dn, ldap_url->lud_scope,
+                               ldap_url->lud_filter, attrs, server_ctrls, NULL);
 
 #ifdef HAVE_LDAP_CREATE_SORT_CONTROL
        if (server_ctrls[0]) ldap_control_free(server_ctrls[0]);
@@ -516,7 +437,7 @@ static rlm_rcode_t mod_map_proc(void *mod_inst, UNUSED void *proc_inst, REQUEST
 
        rlm_ldap_map_exp_t      expanded; /* faster than allocing every time */
 
-       if (tmpl_aexpand(request, &url_str, request, url, rlm_ldap_escape_func, NULL) < 0) {
+       if (tmpl_aexpand(request, &url_str, request, url, fr_ldap_escape_func, NULL) < 0) {
                return RLM_MODULE_FAIL;
        }
 
@@ -541,10 +462,10 @@ static rlm_rcode_t mod_map_proc(void *mod_inst, UNUSED void *proc_inst, REQUEST
        conn = mod_conn_get(inst, request);
        if (!conn) goto free_expanded;
 
-       if (ldap_parse_url_extensions(&server_ctrls[0], request, conn, ldap_url->lud_exts) < 0) goto free_socket;
+       if (fr_ldap_parse_url_extensions(&server_ctrls[0], request, conn, ldap_url->lud_exts) < 0) goto free_socket;
 
-       status = rlm_ldap_search(&result, inst, request, &conn, ldap_url->lud_dn, ldap_url->lud_scope,
-                                ldap_url->lud_filter, expanded.attrs, server_ctrls, NULL);
+       status = fr_ldap_search(&result, request, &conn, ldap_url->lud_dn, ldap_url->lud_scope,
+                               ldap_url->lud_filter, expanded.attrs, server_ctrls, NULL);
 
 #ifdef HAVE_LDAP_CREATE_SORT_CONTROL
        if (server_ctrls[0]) ldap_control_free(server_ctrls[0]);
@@ -583,7 +504,7 @@ static rlm_rcode_t mod_map_proc(void *mod_inst, UNUSED void *proc_inst, REQUEST
                     map != NULL;
                     map = map->next, i++) {
                        int                     ret;
-                       rlm_ldap_result_t       attr;
+                       fr_ldap_result_t        attr;
 
                        attr.values = ldap_get_values_len(conn->handle, entry, expanded.attrs[i]);
                        if (!attr.values) {
@@ -682,12 +603,12 @@ static int rlm_ldap_groupcmp(void *instance, REQUEST *request, UNUSED VALUE_PAIR
        /*
         *      Check if we can do cached membership verification
         */
-       check_is_dn = rlm_ldap_is_dn(check->vp_strvalue, check->vp_length);
+       check_is_dn = fr_ldap_util_is_dn(check->vp_strvalue, check->vp_length);
        if (check_is_dn) {
                char *norm;
 
                MEM(norm = talloc_memdup(check, check->vp_strvalue, talloc_array_length(check->vp_strvalue)));
-               rlm_ldap_normalise_dn(norm, check->vp_strvalue);
+               fr_ldap_util_normalise_dn(norm, check->vp_strvalue);
                fr_pair_value_strsteal(check, norm);
        }
        if ((check_is_dn && inst->cacheable_group_dn) || (!check_is_dn && inst->cacheable_group_name)) {
@@ -825,7 +746,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, UNUSED void
                memset(&sasl, 0, sizeof(sasl));
 
                if (tmpl_expand(&sasl.mech, sasl_mech_buff, sizeof(sasl_mech_buff), request,
-                               inst->user_sasl.mech, rlm_ldap_escape_func, inst) < 0) {
+                               inst->user_sasl.mech, fr_ldap_escape_func, inst) < 0) {
                        REDEBUG("Failed expanding user.sasl.mech: %s", fr_strerror());
                        rcode = RLM_MODULE_FAIL;
                        goto finish;
@@ -833,7 +754,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, UNUSED void
 
                if (inst->user_sasl.proxy) {
                        if (tmpl_expand(&sasl.proxy, sasl_proxy_buff, sizeof(sasl_proxy_buff), request,
-                                       inst->user_sasl.proxy, rlm_ldap_escape_func, inst) < 0) {
+                                       inst->user_sasl.proxy, fr_ldap_escape_func, inst) < 0) {
                                REDEBUG("Failed expanding user.sasl.proxy: %s", fr_strerror());
                                rcode = RLM_MODULE_FAIL;
                                goto finish;
@@ -842,7 +763,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, UNUSED void
 
                if (inst->user_sasl.realm) {
                        if (tmpl_expand(&sasl.realm, sasl_realm_buff, sizeof(sasl_realm_buff), request,
-                                       inst->user_sasl.realm, rlm_ldap_escape_func, inst) < 0) {
+                                       inst->user_sasl.realm, fr_ldap_escape_func, inst) < 0) {
                                REDEBUG("Failed expanding user.sasl.realm: %s", fr_strerror());
                                rcode = RLM_MODULE_FAIL;
                                goto finish;
@@ -862,8 +783,12 @@ static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, UNUSED void
                return rcode;
        }
        conn->rebound = true;
-       status = rlm_ldap_bind(inst, request, &conn, dn, request->password->vp_strvalue,
-                              inst->user_sasl.mech ? &sasl : NULL, true, NULL, NULL, NULL);
+       status = fr_ldap_bind(request,
+                             &conn,
+                             dn, request->password->vp_strvalue,
+                             inst->user_sasl.mech ? &sasl : NULL,
+                             NULL,
+                             NULL, NULL);
        switch (status) {
        case LDAP_PROC_SUCCESS:
                rcode = RLM_MODULE_OK;
@@ -926,14 +851,14 @@ static rlm_rcode_t rlm_ldap_map_profile(rlm_ldap_t const *inst, REQUEST *request
        if (!dn || !*dn) return RLM_MODULE_OK;
 
        if (tmpl_expand(&filter, filter_buff, sizeof(filter_buff), request,
-                       inst->profile_filter, rlm_ldap_escape_func, NULL) < 0) {
+                       inst->profile_filter, fr_ldap_escape_func, NULL) < 0) {
                REDEBUG("Failed creating profile filter");
 
                return RLM_MODULE_INVALID;
        }
 
-       status = rlm_ldap_search(&result, inst, request, pconn, dn,
-                                LDAP_SCOPE_BASE, filter, expanded->attrs, NULL, NULL);
+       status = fr_ldap_search(&result, request, pconn, dn,
+                               LDAP_SCOPE_BASE, filter, expanded->attrs, NULL, NULL);
        switch (status) {
        case LDAP_PROC_SUCCESS:
                break;
@@ -1104,7 +1029,7 @@ static rlm_rcode_t mod_authorize(void *instance, UNUSED void *thread, REQUEST *r
                         *      Bind as the user
                         */
                        conn->rebound = true;
-                       status = rlm_ldap_bind(inst, request, &conn, dn, vp->vp_strvalue, NULL, true, NULL, NULL, NULL);
+                       status = fr_ldap_bind(request, &conn, dn, vp->vp_strvalue, NULL, NULL, NULL, NULL);
                        switch (status) {
                        case LDAP_PROC_SUCCESS:
                                rcode = RLM_MODULE_OK;
@@ -1179,7 +1104,7 @@ skip_edir:
                                rlm_rcode_t ret;
                                char *value;
 
-                               value = rlm_ldap_berval_to_string(request, values[i]);
+                               value = fr_ldap_berval_to_string(request, values[i]);
                                ret = rlm_ldap_map_profile(inst, request, &conn, value, &expanded);
                                talloc_free(value);
                                if (ret == RLM_MODULE_FAIL) {
@@ -1412,7 +1337,7 @@ static rlm_rcode_t user_modify(rlm_ldap_t const *inst, REQUEST *request, ldap_ac
                goto error;
        }
 
-       status = rlm_ldap_modify(inst, request, &conn, dn, modify, NULL, NULL);
+       status = fr_ldap_modify(request, &conn, dn, modify, NULL, NULL);
        switch (status) {
        case LDAP_PROC_SUCCESS:
                break;
@@ -1537,6 +1462,8 @@ static int mod_bootstrap(CONF_SECTION *conf, void *instance)
        inst->name = cf_section_name2(conf);
        if (!inst->name) inst->name = cf_section_name1(conf);
 
+       inst->handle_config.name = talloc_asprintf(inst, "rlm_ldap (%s)", inst->name);
+
        if (inst->group_attribute) {
                group_attribute = inst->group_attribute;
        } else if (cf_section_name2(conf)) {
@@ -1573,7 +1500,7 @@ static int mod_bootstrap(CONF_SECTION *conf, void *instance)
                inst->cache_da = inst->group_da;        /* Default to the group_da */
        }
 
-       xlat_register(inst, inst->name, ldap_xlat, rlm_ldap_escape_func, NULL, 0, XLAT_DEFAULT_BUF_LEN);
+       xlat_register(inst, inst->name, ldap_xlat, fr_ldap_escape_func, NULL, 0, XLAT_DEFAULT_BUF_LEN);
        xlat_register(inst, "ldap_escape", ldap_escape_xlat, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN);
        xlat_register(inst, "ldap_unescape", ldap_unescape_xlat, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN);
        map_proc_register(inst, inst->name, mod_map_proc, ldap_map_verify, 0);
@@ -1602,7 +1529,7 @@ static int mod_instantiate(CONF_SECTION *conf, void *instance)
 
        options = cf_section_sub_find(conf, "options");
        if (!options || !cf_pair_find(options, "chase_referrals")) {
-               inst->pool_inst.chase_referrals_unset = true;    /* use OpenLDAP defaults */
+               inst->handle_config.chase_referrals_unset = true;        /* use OpenLDAP defaults */
        }
 
        /*
@@ -1633,7 +1560,7 @@ static int mod_instantiate(CONF_SECTION *conf, void *instance)
         *      connection pool.
         */
        if (!cf_pair_find(conf, "pool")) {
-               if (!inst->pool_inst.server_str) {
+               if (!inst->handle_config.server_str) {
                        cf_log_err_cs(conf, "Configuration item 'server' must have a value");
                        goto error;
                }
@@ -1646,7 +1573,7 @@ static int mod_instantiate(CONF_SECTION *conf, void *instance)
                goto error;
        }
 
-       if (inst->pool_inst.admin_sasl.mech) {
+       if (inst->handle_config.admin_sasl.mech) {
                cf_log_err_cs(conf, "Configuration item 'sasl.mech' not supported.  "
                              "Linked libldap does not provide ldap_sasl_interactive_bind function");
                goto error;
@@ -1672,8 +1599,8 @@ static int mod_instantiate(CONF_SECTION *conf, void *instance)
        /*
         *      Now iterate over all the 'server' config items
         */
-       for (i = 0; i < talloc_array_length(inst->pool_inst.server_str); i++) {
-               char const *value = inst->pool_inst.server_str[i];
+       for (i = 0; i < talloc_array_length(inst->handle_config.server_str); i++) {
+               char const *value = inst->handle_config.server_str[i];
                size_t j;
 
                /*
@@ -1754,7 +1681,7 @@ static int mod_instantiate(CONF_SECTION *conf, void *instance)
                                 */
                                if (ldap_url->lud_scheme) {
                                        if (strcmp(ldap_url->lud_scheme, "ldaps") == 0) {
-                                               if (inst->pool_inst.start_tls == true) {
+                                               if (inst->handle_config.start_tls == true) {
                                                        cf_log_err_cs(conf, "ldaps:// scheme is not compatible "
                                                                      "with 'start_tls'");
                                                        goto ldap_url_error;
@@ -1770,7 +1697,7 @@ static int mod_instantiate(CONF_SECTION *conf, void *instance)
                                        /*
                                         *      URL port overrides configured port.
                                         */
-                                       ldap_url->lud_port = inst->pool_inst.port;
+                                       ldap_url->lud_port = inst->handle_config.port;
 
                                        /*
                                         *      If there's no URL port, then set it to the default
@@ -1785,7 +1712,7 @@ static int mod_instantiate(CONF_SECTION *conf, void *instance)
                                        cf_log_err_cs(conf, "Failed recombining URL components");
                                        goto ldap_url_error;
                                }
-                               inst->pool_inst.server = talloc_asprintf_append(inst->pool_inst.server, "%s ", url);
+                               inst->handle_config.server = talloc_asprintf_append(inst->handle_config.server, "%s ", url);
                                free(url);
                        }
 #  else
@@ -1807,11 +1734,11 @@ static int mod_instantiate(CONF_SECTION *conf, void *instance)
                         *      port, we use the hard-coded default.
                         */
                        if (set_port_maybe) {
-                               ldap_url->lud_port = inst->pool_inst.port;
+                               ldap_url->lud_port = inst->handle_config.port;
                                if (!ldap_url->lud_port) ldap_url->lud_port = default_port;
                        }
 
-                       inst->pool_inst.server = talloc_asprintf_append(inst->pool_inst.server, "%s:%i ",
+                       inst->handle_config.server = talloc_asprintf_append(inst->handle_config.server, "%s:%i ",
                                                              ldap_url->lud_host ? ldap_url->lud_host : "localhost",
                                                              ldap_url->lud_port);
 #  endif
@@ -1838,7 +1765,7 @@ static int mod_instantiate(CONF_SECTION *conf, void *instance)
                        int     port = 0;
                        size_t  len;
 
-                       port = inst->pool_inst.port;
+                       port = inst->handle_config.port;
 
                        /*
                         *      We don't support URLs if the library didn't provide
@@ -1865,44 +1792,44 @@ static int mod_instantiate(CONF_SECTION *conf, void *instance)
                        }
                        if (port == 0) port = LDAP_PORT;
 
-                       inst->pool_inst.server = talloc_asprintf_append(inst->pool_inst.server, "ldap://%.*s:%i ", (int) len, value, port);
+                       inst->handle_config.server = talloc_asprintf_append(inst->handle_config.server, "ldap://%.*s:%i ", (int) len, value, port);
 #else
                        /*
                         *      ldap_init takes port, which can be overridden by :port so
                         *      we don't need to do any parsing here.
                         */
-                       inst->pool_inst.server = talloc_asprintf_append(inst->pool_inst.server, "%s ", value);
+                       inst->handle_config.server = talloc_asprintf_append(inst->handle_config.server, "%s ", value);
 #endif
                }
        }
 
        /*
-        *      inst->pool_inst.server be unset if connection pool sharing is used.
+        *      inst->handle_config.server be unset if connection pool sharing is used.
         */
-       if (inst->pool_inst.server) {
-               inst->pool_inst.server[talloc_array_length(inst->pool_inst.server) - 2] = '\0';
-               DEBUG4("rlm_ldap (%s) - LDAP server string: %s", inst->name, inst->pool_inst.server);
+       if (inst->handle_config.server) {
+               inst->handle_config.server[talloc_array_length(inst->handle_config.server) - 2] = '\0';
+               DEBUG4("rlm_ldap (%s) - LDAP server string: %s", inst->name, inst->handle_config.server);
        }
 
 #ifdef LDAP_OPT_X_TLS_NEVER
        /*
         *      Workaround for servers which support LDAPS but not START TLS
         */
-       if (inst->pool_inst.port == LDAPS_PORT || inst->pool_inst.tls_mode) {
-               inst->pool_inst.tls_mode = LDAP_OPT_X_TLS_HARD;
+       if (inst->handle_config.port == LDAPS_PORT || inst->handle_config.tls_mode) {
+               inst->handle_config.tls_mode = LDAP_OPT_X_TLS_HARD;
        } else {
-               inst->pool_inst.tls_mode = 0;
+               inst->handle_config.tls_mode = 0;
        }
 #endif
 
        /*
         *      Convert dereference strings to enumerated constants
         */
-       if (inst->pool_inst.dereference_str) {
-               inst->pool_inst.dereference = fr_str2int(ldap_dereference, inst->pool_inst.dereference_str, -1);
-               if (inst->pool_inst.dereference < 0) {
+       if (inst->handle_config.dereference_str) {
+               inst->handle_config.dereference = fr_str2int(ldap_dereference, inst->handle_config.dereference_str, -1);
+               if (inst->handle_config.dereference < 0) {
                        cf_log_err_cs(conf, "Invalid 'dereference' value \"%s\", expected 'never', 'searching', "
-                                     "'finding' or 'always'", inst->pool_inst.dereference_str);
+                                     "'finding' or 'always'", inst->handle_config.dereference_str);
                        goto error;
                }
        }
@@ -1981,7 +1908,7 @@ static int mod_instantiate(CONF_SECTION *conf, void *instance)
                 *      Always set the control as critical, if it's not needed
                 *      the user can comment it out...
                 */
-               ret = ldap_create_sort_control(global_handle, keys, 1, &inst->userobj_sort_ctrl);
+               ret = ldap_create_sort_control(ldap_global_handle, keys, 1, &inst->userobj_sort_ctrl);
                ldap_free_sort_keylist(keys);
                if (ret != LDAP_SUCCESS) {
                        ERROR("Failed creating server sort control: %s", ldap_err2string(ret));
@@ -1990,16 +1917,16 @@ static int mod_instantiate(CONF_SECTION *conf, void *instance)
        }
 #endif
 
-       if (inst->pool_inst.tls_require_cert_str) {
+       if (inst->handle_config.tls_require_cert_str) {
 #ifdef LDAP_OPT_X_TLS_NEVER
                /*
                 *      Convert cert strictness to enumerated constants
                 */
-               inst->pool_inst.tls_require_cert = fr_str2int(ldap_tls_require_cert,
-                                                             inst->pool_inst.tls_require_cert_str, -1);
-               if (inst->pool_inst.tls_require_cert < 0) {
+               inst->handle_config.tls_require_cert = fr_str2int(ldap_tls_require_cert,
+                                                             inst->handle_config.tls_require_cert_str, -1);
+               if (inst->handle_config.tls_require_cert < 0) {
                        cf_log_err_cs(conf, "Invalid 'tls.require_cert' value \"%s\", expected 'never', "
-                                     "'demand', 'allow', 'try' or 'hard'", inst->pool_inst.tls_require_cert_str);
+                                     "'demand', 'allow', 'try' or 'hard'", inst->handle_config.tls_require_cert_str);
                        goto error;
                }
 #else
@@ -2024,12 +1951,13 @@ static int mod_instantiate(CONF_SECTION *conf, void *instance)
        /*
         *      Set global options
         */
-       if (rlm_ldap_global_init(inst) < 0) goto error;
+       if (fr_ldap_global_init() < 0) goto error;
 
        /*
         *      Initialize the socket pool.
         */
-       inst->pool = module_connection_pool_init(inst->cs, inst, mod_conn_create, NULL, NULL, NULL, NULL);
+       inst->pool = module_connection_pool_init(inst->cs, &inst->handle_config,
+                                                mod_conn_create, NULL, NULL, NULL, NULL);
        if (!inst->pool) goto error;
 
        /*
@@ -2059,6 +1987,8 @@ static int mod_instantiate(CONF_SECTION *conf, void *instance)
                }
        }
 
+       fr_ldap_global_config(inst->ldap_debug, inst->tls_random_file);
+
        return 0;
 
 error:
@@ -2067,68 +1997,14 @@ error:
 
 static int mod_load(void)
 {
-       static LDAPAPIInfo info = { .ldapai_info_version = LDAP_API_INFO_VERSION };     /* static to quiet valgrind about this being uninitialised */
-       int ldap_errno;
-
-       /*
-        *      Only needs to be done once, prevents races in environment
-        *      initialisation within libldap.
-        *
-        *      See: https://github.com/arr2036/ldapperf/issues/2
-        */
-#ifdef HAVE_LDAP_INITIALIZE
-       ldap_initialize(&global_handle, "");
-#else
-       global_handle = ldap_init("", 0);
-#endif
-
-       ldap_errno = ldap_get_option(NULL, LDAP_OPT_API_INFO, &info);
-       if (ldap_errno == LDAP_OPT_SUCCESS) {
-               /*
-                *      Don't generate warnings if the compile type vendor name
-                *      is found within the link time vendor name.
-                *
-                *      This allows the server to be built against OpenLDAP but
-                *      run with Symas OpenLDAP.
-                */
-               if (strcasestr(info.ldapai_vendor_name, LDAP_VENDOR_NAME) == NULL) {
-                       WARN("rlm_ldap - libldap vendor changed since the server was built");
-                       WARN("rlm_ldap - linked: %s, built: %s", info.ldapai_vendor_name, LDAP_VENDOR_NAME);
-               }
-
-               if (info.ldapai_vendor_version < LDAP_VENDOR_VERSION) {
-                       WARN("rlm_ldap - libldap older than the version the server was built against");
-                       WARN("rlm_ldap - linked: %i, built: %i",
-                            info.ldapai_vendor_version, LDAP_VENDOR_VERSION);
-               }
-
-               INFO("rlm_ldap - libldap vendor: %s, version: %i", info.ldapai_vendor_name,
-                    info.ldapai_vendor_version);
-
-               ldap_memfree(info.ldapai_vendor_name);
-               ldap_memfree(info.ldapai_extensions);
-       } else {
-               DEBUG("rlm_ldap - Falling back to build time libldap version info.  Query for LDAP_OPT_API_INFO "
-                     "returned: %i", ldap_errno);
-               INFO("rlm_ldap - libldap vendor: %s, version: %i.%i.%i", LDAP_VENDOR_NAME,
-                    LDAP_VENDOR_VERSION_MAJOR, LDAP_VENDOR_VERSION_MINOR, LDAP_VENDOR_VERSION_PATCH);
-       }
+       fr_ldap_global_init();
 
        return 0;
 }
 
 static void mod_unload(void)
 {
-       /*
-        *      Keeping the dummy ld around for the lifetime
-        *      of the module should always work,
-        *      irrespective of what changes happen in libldap.
-        */
-#ifdef HAVE_LDAP_UNBIND_EXT_S
-       ldap_unbind_ext_s(global_handle, NULL, NULL);
-#else
-       ldap_unbind_s(global_handle);
-#endif
+       fr_ldap_global_free();;
 }
 
 /* globally exported name */
index 284c75d053a805ad84f40a350c8ce669068f5792..43c2d0aab4d5deab75202b89d84359b0d0d0f8d3 100644 (file)
 
 #include <freeradius-devel/radiusd.h>
 #include <freeradius-devel/modules.h>
+#include "libldap.h"
 
-/*
- *     We're mostly using the new API now, but ldap_bind
- *     is in the list of deprecated functions, at we may
- *     always need to support that.
- */
-#define LDAP_DEPRECATED 1
-#include <lber.h>
-#include <ldap.h>
-#include "config.h"
-
-/*
- *     Framework on OSX doesn't export the symbols but leaves
- *     the macro defined *sigh*.
- */
-#ifndef HAVE_LDAP_CREATE_SESSION_TRACKING_CONTROL
-#  undef LDAP_CONTROL_X_SESSION_TRACKING
-#endif
-
-/*
- *     There's a typo in libldap's ldap.h which was fixed by
- *     Howard Chu in 19aeb1cd. This typo had the function defined
- *     as ldap_create_session_tracking_control but declared as
- *     ldap_create_session_tracking.
- *
- *     We fix this, by adding the correct declaration here.
- */
-#ifdef LDAP_CONTROL_X_SESSION_TRACKING
-#  if !defined(HAVE_DECL_LDAP_CREATE_SESSION_TRACKING_CONTROL) || (HAVE_DECL_LDAP_CREATE_SESSION_TRACKING_CONTROL == 0)
-LDAP_F( int )
-ldap_create_session_tracking_control LDAP_P((
-        LDAP            *ld,
-        char            *sessionSourceIp,
-        char            *sessionSourceName,
-        char            *formatOID,
-        struct berval   *sessionTrackingIdentifier,
-        LDAPControl     **ctrlp ));
-#  endif
-#endif
-
-/*
- *     Ensure the have the ldap_create_sort_keylist()
- *     function too, else we can't use ldap_create_sort_control()
- */
-#if !defined(HAVE_LDAP_CREATE_SORT_KEYLIST) || !defined(HAVE_LDAP_FREE_SORT_KEYLIST)
-#  undef HAVE_LDAP_CREATE_SORT_CONTROL
-#endif
-
-/*
- *     Because the LTB people define LDAP_VENDOR_VERSION_PATCH
- *     as X, which precludes its use in printf statements *sigh*
- *
- *     Identifiers that are not macros, all evaluate to 0,
- *     which is why this works.
- */
-#if !defined(LDAP_VENDOR_VERSION_PATCH) || LDAP_VENDOR_VERSION_PATCH == 0
-#  undef LDAP_VENDOR_VERSION_PATCH
-#  define LDAP_VENDOR_VERSION_PATCH 0
-#endif
-
-/*
- *      For compatibility with other LDAP libraries
- */
-#if !defined(LDAP_SCOPE_BASE) && defined(LDAP_SCOPE_BASEOBJECT)
-#  define LDAP_SCOPE_BASE LDAP_SCOPE_BASEOBJECT
-#endif
-
-#if !defined(LDAP_SCOPE_ONE) && defined(LDAP_SCOPE_ONELEVEL)
-#  define LDAP_SCOPE_ONE LDAP_SCOPE_ONELEVEL
-#endif
-
-#if !defined(LDAP_SCOPE_SUB) && defined(LDAP_SCOPE_SUBTREE)
-#  define LDAP_SCOPE_SUB LDAP_SCOPE_SUBTREE
-#endif
-
-#if !defined(LDAP_OPT_RESULT_CODE) && defined(LDAP_OPT_ERROR_NUMBER)
-#  define LDAP_OPT_RESULT_CODE LDAP_OPT_ERROR_NUMBER
-#endif
-
-#ifndef LDAP_CONST
-#  define LDAP_CONST
-#endif
-
-#if defined(HAVE_LDAP_URL_PARSE) && defined(HAVE_LDAP_IS_LDAP_URL) && defined(HAVE_LDAP_URL_DESC2STR)
-#  define LDAP_CAN_PARSE_URLS
-#endif
-
-#define LDAP_MAX_CONTROLS              10              //!< Maximum number of client/server controls.
-                                                       //!< Used to allocate static arrays of control pointers.
-#define LDAP_MAX_ATTRMAP               128             //!< Maximum number of mappings between LDAP and
-                                                       //!< FreeRADIUS attributes.
-#define LDAP_MAP_RESERVED              4               //!< Number of additional items to allocate in expanded
-                                                       //!< attribute name arrays. Currently for enable attribute,
-                                                       //!< group membership attribute, valuepair attribute,
-                                                       //!< and profile attribute.
-
-#define LDAP_MAX_CACHEABLE             64              //!< Maximum number of groups we retrieve from the server for
-                                                       //!< a given user. If more than this number are retrieve the
-                                                       //!< module returns invalid.
+typedef struct ldap_inst_s rlm_ldap_t;
 
-#define LDAP_MAX_GROUP_NAME_LEN                128             //!< Maximum name of a group name.
-#define LDAP_MAX_ATTR_STR_LEN          256             //!< Maximum length of an xlat expanded LDAP attribute.
-#define LDAP_MAX_FILTER_STR_LEN                1024            //!< Maximum length of an xlat expanded filter.
-#define LDAP_MAX_DN_STR_LEN            1024            //!< Maximum length of an xlat expanded DN.
-
-#define LDAP_VIRTUAL_DN_ATTR           "dn"            //!< 'Virtual' attribute which maps to the DN of the object.
-
-
-typedef enum {
-       LDAP_EXT_UNSUPPORTED,                           //!< Unsupported extension.
-       LDAP_EXT_BINDNAME,                              //!< Specifies the user DN or name for an LDAP bind.
-       LDAP_EXT_BINDPW,                                //!< Specifies the password for an LDAP bind.
-} ldap_supported_extension;
-
-extern FR_NAME_NUMBER const ldap_supported_extensions[];
-
-typedef struct rlm_ldap_s rlm_ldap_t;
+typedef struct {
+       vp_tmpl_t       *mech;                          //!< SASL mech(s) to try.
+       vp_tmpl_t       *proxy;                         //!< Identity to proxy.
+       vp_tmpl_t       *realm;                         //!< Kerberos realm.
+} ldap_sasl_dynamic_t;
 
 typedef struct ldap_acct_section {
        CONF_SECTION    *cs;                            //!< Section configuration.
@@ -141,141 +33,7 @@ typedef struct ldap_acct_section {
        char const      *reference;                     //!< Configuration reference string.
 } ldap_acct_section_t;
 
-typedef struct ldap_sasl {
-       char const      *mech;                          //!< SASL mech(s) to try.
-       char const      *proxy;                         //!< Identity to proxy.
-       char const      *realm;                         //!< Kerberos realm.
-} ldap_sasl;
-
-typedef struct ldap_sasl_dynamic {
-       vp_tmpl_t       *mech;                          //!< SASL mech(s) to try.
-       vp_tmpl_t       *proxy;                         //!< Identity to proxy.
-       vp_tmpl_t       *realm;                         //!< Kerberos realm.
-} ldap_sasl_dynamic;
-
-typedef struct rlm_ldap_control {
-       LDAPControl     *control;                       //!< LDAP control.
-       bool            freeit;                         //!< Whether the control should be freed after
-                                                       //!< we've finished using it.
-} rlm_ldap_control_t;
-
-
-typedef enum {
-       LDAP_DIRECTORY_UNKNOWN = 0,                     //!< We can't determine the directory server.
-
-       LDAP_DIRECTORY_ACTIVE_DIRECTORY,                //!< Directory server is Active Directory.
-       LDAP_DIRECTORY_EDIRECTORY,                      //!< Directory server is eDir.
-       LDAP_DIRECTORY_IBM,                             //!< Directory server is IBM.
-       LDAP_DIRECTORY_NETSCAPE,                        //!< Directory server is Netscape.
-       LDAP_DIRECTORY_OPENLDAP,                        //!< Directory server is OpenLDAP.
-       LDAP_DIRECTORY_ORACLE_INTERNET_DIRECTORY,       //!< Directory server is Oracle Internet Directory.
-       LDAP_DIRECTORY_ORACLE_UNIFIED_DIRECTORY,        //!< Directory server is Oracle Unified Directory.
-       LDAP_DIRECTORY_ORACLE_VIRTUAL_DIRECTORY,        //!< Directory server is Oracle Virtual Directory.
-       LDAP_DIRECTORY_SUN_ONE_DIRECTORY,               //!< Directory server is Sun One Directory.
-       LDAP_DIRECTORY_SIEMENS_AG,                      //!< Directory server is Siemens AG.
-       LDAP_DIRECTORY_UNBOUND_ID                       //!< Directory server is Unbound ID
-} ldap_directory_type_t;
-
-typedef struct ldap_directory {
-       char const              *vendor_str;            //!< As returned from the vendorName attribute in the
-                                                       //!< rootDSE.
-       char const              *version_str;           //!< As returned from the vendorVersion attribute in the
-                                                       //!< rootDSE.
-       ldap_directory_type_t   type;                   //!< Cannonical server implementation.
-
-       bool                    cleartext_password;     //!< Whether the server will return the user's plaintext
-                                                       //!< password.
-} ldap_directory_t;
-
-/** Pool configuration
- *
- * Must not be passed into functions except via the connection handle
- * this avoids problems with not using the connection pool configuration.
- */
-typedef struct {
-       char            *server;                        //!< Initial server to bind to.
-       char const      **server_str;           //!< Server set in the config.
-
-       uint16_t        port;                           //!< Port to use when binding to the server.
-
-       char const      *admin_identity;                //!< Identity we bind as when we need to query the LDAP
-                                                       //!< directory.
-       char const      *admin_password;                //!< Password used in administrative bind.
-
-       ldap_sasl       admin_sasl;                     //!< SASL parameters used when binding as the admin.
-
-       int             dereference;                    //!< libldap value specifying dereferencing behaviour.
-       char const      *dereference_str;               //!< When to dereference (never, searching, finding, always)
-
-       bool            chase_referrals;                //!< If the LDAP server returns a referral to another server
-                                                       //!< or point in the tree, follow it, establishing new
-                                                       //!< connections and binding where necessary.
-       bool            chase_referrals_unset;          //!< If true, use the OpenLDAP defaults for chase_referrals.
-
-       bool            use_referral_credentials;       //!< If true use credentials from the referral URL.
-
-       bool            rebind;                         //!< Controls whether we set an ldad_rebind_proc function
-                                                       //!< and so determines if we can bind to other servers whilst
-                                                       //!< chasing referrals. If this is false, we will still chase
-                                                       //!< referrals on the same server, but won't bind to other
-                                                       //!< servers.
-
-       /*
-        *      TLS items.
-        */
-       int             tls_mode;
-       bool            start_tls;                      //!< Send the Start TLS message to the LDAP directory
-                                                       //!< to start encrypted communications using the standard
-                                                       //!< LDAP port.
-
-       char const      *tls_ca_file;                   //!< Sets the full path to a CA certificate (used to validate
-                                                       //!< the certificate the server presents).
-
-       char const      *tls_ca_path;                   //!< Sets the path to a directory containing CA certificates.
-
-       char const      *tls_certificate_file;          //!< Sets the path to the public certificate file we present
-                                                       //!< to the servers.
-
-       char const      *tls_private_key_file;          //!< Sets the path to the private key for our public
-                                                       //!< certificate.
-
-       char const      *tls_require_cert_str;          //!< Sets requirements for validating the certificate the
-                                                       //!< server presents.
-
-       int             tls_require_cert;               //!< OpenLDAP constant representing the require cert string.
-
-       /*
-        *      Options
-        */
-#ifdef LDAP_CONTROL_X_SESSION_TRACKING
-       bool            session_tracking;               //!< Whether we add session tracking controls, which help
-                                                       //!< identify the autz or acct session the commands were
-                                                       //!< issued for.
-#endif
-
-       /*
-        *      For keep-alives.
-        */
-#ifdef LDAP_OPT_X_KEEPALIVE_IDLE
-       uint32_t        keepalive_idle;                 //!< Number of seconds a connections needs to remain idle
-                                                       //!< before TCP starts sending keepalive probes.
-#endif
-#ifdef LDAP_OPT_X_KEEPALIVE_PROBES
-       uint32_t        keepalive_probes;               //!< Number of missed timeouts before the connection is
-                                                       //!< dropped.
-#endif
-#ifdef LDAP_OPT_X_KEEPALIVE_INTERVAL
-       uint32_t        keepalive_interval;             //!< Interval between keepalive probes.
-#endif
-
-       /*
-        *      Search timelimits
-        */
-       uint32_t        srv_timelimit;                  //!< How long the server should spent on a single request
-                                                       //!< (also bounded by value on the server).
-} ldap_pool_inst_t;
-
-struct rlm_ldap_s {
+struct ldap_inst_s {
        char const      *name;                          //!< Instance name.
 
        CONF_SECTION    *cs;                            //!< Main configuration section for this instance.
@@ -289,9 +47,13 @@ struct rlm_ldap_s {
        vp_map_t        *user_map;                      //!< Attribute map applied to users and profiles.
 
        /*
-        *      Search time limits
+        *      Options
         */
-       uint32_t        res_timeout;                    //!< How long we wait for a result from the server.
+#ifdef LDAP_CONTROL_X_SESSION_TRACKING
+       bool            session_tracking;               //!< Whether we add session tracking controls, which help
+                                                       //!< identify the autz or acct session the commands were
+                                                       //!< issued for.
+#endif
 
        /*
         *      User object attributes and filters
@@ -309,7 +71,7 @@ struct rlm_ldap_s {
        bool            access_positive;                //!< If true the presence of the attribute will allow access,
                                                        //!< else it will deny access.
 
-       ldap_sasl_dynamic user_sasl;                    //!< SASL parameters used when binding as the user.
+       ldap_sasl_dynamic_t user_sasl;                  //!< SASL parameters used when binding as the user.
 
        char const      *valuepair_attr;                //!< Generic dynamic mapping attribute, contains a RADIUS
                                                        //!< attribute and value.
@@ -387,7 +149,7 @@ struct rlm_ldap_s {
 #endif
 
        fr_connection_pool_t *pool;                     //!< Connection pool instance.
-       ldap_pool_inst_t pool_inst;                     //!< Connection configuration instance.
+       ldap_handle_config_t handle_config;                     //!< Connection configuration instance.
 
        /*
         *      Global config
@@ -398,29 +160,6 @@ struct rlm_ldap_s {
        uint32_t        ldap_debug;                     //!< Debug flag for the SDK.
 };
 
-/** Tracks the state of a libldap connection handle
- *
- */
-typedef struct ldap_handle {
-       LDAP            *handle;                        //!< libldap handle.
-       bool            rebound;                        //!< Whether the connection has been rebound to something
-                                                       //!< other than the admin user.
-       bool            referred;                       //!< Whether the connection is now established a server
-                                                       //!< other than the configured one.
-
-       rlm_ldap_control_t serverctrls[LDAP_MAX_CONTROLS + 1];  //!< Server controls to use for all operations with
-                                                               //!< this handle.
-       rlm_ldap_control_t clientctrls[LDAP_MAX_CONTROLS + 1];  //!< Client controls to use for all operations with
-                                                               //!< this handle.
-       int             serverctrls_cnt;                //!< Number of server controls associated with the handle.
-       int             clientctrls_cnt;                //!< Number of client controls associated with the handle.
-
-       ldap_directory_t *directory;                    //!< The type of directory we're connected to.
-
-       ldap_pool_inst_t const *pool_inst;              //!< rlm_ldap connection configuration.
-       rlm_ldap_t       const *inst;                   //!< rlm_ldap pool inst.
-} ldap_handle_t;
-
 /** Result of expanding the RHS of a set of maps
  *
  * Used to store the array of attributes we'll be querying for.
@@ -433,104 +172,17 @@ typedef struct rlm_ldap_map_exp {
        int             count;                          //!< Index on next free element.
 } rlm_ldap_map_exp_t;
 
-/** Contains a collection of values
- *
- */
-typedef struct rlm_ldap_result {
-       struct berval   **values;                       //!< libldap struct containing bv_val (char *)
-                                                       //!< and length bv_len.
-       int             count;                          //!< Number of values.
-} rlm_ldap_result_t;
-
-/** Codes returned by rlm_ldap internal functions
- *
- */
-typedef enum {
-       LDAP_PROC_CONTINUE = 1,                         //!< Operation is in progress.
-       LDAP_PROC_SUCCESS = 0,                          //!< Operation was successfull.
-
-       LDAP_PROC_ERROR = -1,                           //!< Unrecoverable library/server error.
-
-       LDAP_PROC_RETRY = -2,                           //!< Transitory error, caller should retry the operation
-                                                       //!< with a new connection.
-
-       LDAP_PROC_NOT_PERMITTED = -3,                   //!< Operation was not permitted, either current user was
-                                                       //!< locked out in the case of binds, or has insufficient
-                                                       //!< access.
-
-       LDAP_PROC_REJECT = -4,                          //!< Bind failed, user was rejected.
-
-       LDAP_PROC_BAD_DN = -5,                          //!< Specified an invalid object in a bind or search DN.
-
-       LDAP_PROC_NO_RESULT = -6                        //!< Got no results.
-} ldap_rcode_t;
-
-/*
- *     Some functions may be called with a NULL request structure, this
- *     simplifies switching certain messages from the request log to
- *     the main log.
- */
-#define LDAP_EXTRA_DEBUG() do { if (extra) { if (request) REDEBUG("%s", extra); else ERROR("%s", extra); }} while (0)
-
-extern FR_NAME_NUMBER const ldap_scope[];
-extern FR_NAME_NUMBER const ldap_tls_require_cert[];
-
 /*
- *     ldap.c - Wrappers arounds OpenLDAP functions.
+ *     user.c - User lookup functions
  */
-void   rlm_ldap_timeout_debug(rlm_ldap_t const *inst, REQUEST *request, ldap_handle_t const *conn,
-                              struct timeval const *timeout, char const *prefix);
-
-size_t rlm_ldap_escape_func(UNUSED REQUEST *request, char *out, size_t outlen, char const *in, UNUSED void *arg);
-
-size_t rlm_ldap_unescape_func(UNUSED REQUEST *request, char *out, size_t outlen, char const *in, UNUSED void *arg);
-
-bool rlm_ldap_is_dn(char const *in, size_t inlen);
-
-size_t rlm_ldap_normalise_dn(char *out, char const *in);
-
-ssize_t rlm_ldap_xlat_filter(REQUEST *request, char const **sub, size_t sublen, char *out, size_t outlen);
-
-ldap_rcode_t rlm_ldap_bind(rlm_ldap_t const *inst, REQUEST *request, ldap_handle_t **pconn, char const *dn,
-                          char const *password, ldap_sasl const *sasl, bool retry, struct timeval const *timeout,
-                          LDAPControl **serverctrls, LDAPControl **clientctrls);
-
-char const *rlm_ldap_error_str(ldap_handle_t const *conn);
-
-ldap_rcode_t rlm_ldap_search(LDAPMessage **result, rlm_ldap_t const *inst, REQUEST *request,
-                            ldap_handle_t **pconn,
-                            char const *dn, int scope, char const *filter, char const * const *attrs,
-                            LDAPControl **serverctrls, LDAPControl **clientctrls);
-
-ldap_rcode_t rlm_ldap_modify(rlm_ldap_t const *inst, REQUEST *request, ldap_handle_t **pconn,
-                            char const *dn, LDAPMod *mods[],
-                            LDAPControl **serverctrls, LDAPControl **clientctrls);
-
 char const *rlm_ldap_find_user(rlm_ldap_t const *inst, REQUEST *request, ldap_handle_t **pconn,
                               char const *attrs[], bool force, LDAPMessage **result, rlm_rcode_t *rcode);
 
-rlm_rcode_t rlm_ldap_check_access(rlm_ldap_t const *inst, REQUEST *request, ldap_handle_t const *conn,
-                                 LDAPMessage *entry);
+rlm_rcode_t rlm_ldap_check_access(rlm_ldap_t const *inst, REQUEST *request,
+                                 ldap_handle_t const *conn, LDAPMessage *entry);
 
 void rlm_ldap_check_reply(rlm_ldap_t const *inst, REQUEST *request, ldap_handle_t const *conn);
 
-/*
- *     ldap.c - Callbacks for the connection pool API.
- */
-ldap_rcode_t rlm_ldap_result(rlm_ldap_t const *inst, ldap_handle_t const *conn, int msgid, char const *dn,
-                            struct timeval const *timeout,
-                            LDAPMessage **result, char const **error, char **extra);
-
-char *rlm_ldap_berval_to_string(TALLOC_CTX *ctx, struct berval const *in);
-
-int rlm_ldap_global_init(rlm_ldap_t *inst) CC_HINT(nonnull);
-
-void *mod_conn_create(TALLOC_CTX *ctx, void *instance, struct timeval const *timeout);
-
-ldap_handle_t *mod_conn_get(rlm_ldap_t const *inst, REQUEST *request);
-
-void mod_conn_release(rlm_ldap_t const *inst, REQUEST *request, ldap_handle_t *conn);
-
 /*
  *     groups.c - Group membership functions.
  */
@@ -547,6 +199,15 @@ rlm_rcode_t rlm_ldap_check_userobj_dynamic(rlm_ldap_t const *inst, REQUEST *requ
 
 rlm_rcode_t rlm_ldap_check_cached(rlm_ldap_t const *inst, REQUEST *request, VALUE_PAIR *check);
 
+/*
+ *     conn.c - Connection wrappers.
+ */
+ldap_handle_t  *mod_conn_get(rlm_ldap_t const *inst, REQUEST *request);
+
+void           mod_conn_release(rlm_ldap_t const *inst, REQUEST *request, ldap_handle_t *conn);
+
+void           *mod_conn_create(TALLOC_CTX *ctx, void *instance, struct timeval const *timeout);
+
 /*
  *     attrmap.c - Attribute mapping code.
  */
@@ -564,30 +225,6 @@ int rlm_ldap_map_do(rlm_ldap_t const *inst, REQUEST *request, LDAP *handle,
  */
 int  rlm_ldap_client_load(rlm_ldap_t const *inst, CONF_SECTION *tmpl, CONF_SECTION *cs);
 
-/*
- *     control.c - Connection based client/server controls
- */
-void rlm_ldap_control_merge(LDAPControl *serverctrls_out[],
-                           LDAPControl *clientctrls_out[],
-                           size_t serverctrls_len,
-                           size_t clientctrls_len,
-                           ldap_handle_t *conn,
-                           LDAPControl *serverctrls_in[],
-                           LDAPControl *clientctrls_in[]);
-
-int rlm_ldap_control_add_server(ldap_handle_t *conn, LDAPControl *ctrl, bool freeit);
-
-int rlm_ldap_control_add_client(ldap_handle_t *conn, LDAPControl *ctrl, bool freeit);
-
-void rlm_ldap_control_clear(ldap_handle_t *conn);
-
-int rlm_ldap_control_add_session_tracking(ldap_handle_t *conn, REQUEST *request);
-
-/*
- *     directory.c - Get directory capabilities from the remote server
- */
-int rlm_ldap_directory_alloc(TALLOC_CTX *ctx, ldap_directory_t **out, rlm_ldap_t const *inst, ldap_handle_t **pconn);
-
 /*
  *     edir.c - Magic extensions for Novell
  */
@@ -595,13 +232,4 @@ int nmasldap_get_password(LDAP *ld, char const *dn, char *password, size_t *len)
 
 char const *edir_errstr(int code);
 
-/*
- *     sasl.s - SASL bind functions
- */
-ldap_rcode_t rlm_ldap_sasl_interactive(rlm_ldap_t const *inst, REQUEST *request,
-                                      ldap_handle_t *pconn, char const *dn,
-                                      char const *password, ldap_sasl const *sasl,
-                                      LDAPControl **serverctrls, LDAPControl **clientctrls,
-                                      struct timeval const *timeout,
-                                      char const **error, char **error_extra);
 #endif
diff --git a/src/modules/rlm_ldap/rlm_ldap.mk b/src/modules/rlm_ldap/rlm_ldap.mk
new file mode 100644 (file)
index 0000000..84367d7
--- /dev/null
@@ -0,0 +1,15 @@
+#  This needs to be cleared explicitly, as the libfreeradius-ldap.mk
+#  might not always be available, and the TARGETNAME from the previous
+#  target may stick around.
+TARGETNAME=
+-include $(top_builddir)/src/modules/rlm_ldap/libfreeradius-ldap.mk
+
+ifneq "${TARGETNAME}" ""
+  TARGETNAME   := rlm_ldap
+  TARGET           := $(TARGETNAME).a
+endif
+
+SOURCES                    := $(TARGETNAME).c attrmap.c clients.c groups.c user.c conn.c
+
+SRC_CFLAGS     += -I$(top_builddir)/src/modules/rlm_ldap
+TGT_PREREQS    := libfreeradius-ldap.a
index 00a0156be3aded3babfee9a6cafab21e5c196275..55a3c0c9fbf073f54aadab0c9bd2a382095ca33a 100644 (file)
@@ -36,7 +36,6 @@
  *
  */
 typedef struct rlm_ldap_sasl_ctx {
-       rlm_ldap_t const        *inst;          //!< LDAP instance
        REQUEST                 *request;       //!< The current request.
 
        char const              *identity;      //!< User's DN or identity.
@@ -57,7 +56,6 @@ static int _sasl_interact(UNUSED LDAP *handle, UNUSED unsigned flags, void *ctx,
 {
        rlm_ldap_sasl_ctx_t     *this = ctx;
        REQUEST                 *request = this->request;
-       rlm_ldap_t const        *inst = this->inst;
        sasl_interact_t         *cb = sasl_callbacks;
        sasl_interact_t         *cb_p;
 
@@ -92,7 +90,6 @@ static int _sasl_interact(UNUSED LDAP *handle, UNUSED unsigned flags, void *ctx,
 
 /** Initiate an LDAP interactive bind
  *
- * @param[in] inst rlm_ldap configuration.
  * @param[in] request          Current request, this may be NULL.
  * @param[in] conn             to use. May change as this function calls functions
  *                             which auto re-connect.
@@ -106,7 +103,7 @@ static int _sasl_interact(UNUSED LDAP *handle, UNUSED unsigned flags, void *ctx,
  * @param[out] extra           information about the error.
  * @return One of the LDAP_PROC_* (#ldap_rcode_t) values.
  */
-ldap_rcode_t rlm_ldap_sasl_interactive(rlm_ldap_t const *inst, REQUEST *request,
+ldap_rcode_t rlm_ldap_sasl_interactive(REQUEST *request,
                                       ldap_handle_t *conn, char const *identity,
                                       char const *password, ldap_sasl const *sasl,
                                       LDAPControl **serverctrls, LDAPControl **clientctrls,
@@ -132,7 +129,6 @@ ldap_rcode_t rlm_ldap_sasl_interactive(rlm_ldap_t const *inst, REQUEST *request,
        if (error) *error = NULL;
        if (extra) *extra = NULL;
 
-       sasl_ctx.inst = inst;
        sasl_ctx.request = request;
        sasl_ctx.identity = identity;
        sasl_ctx.password = password;
@@ -155,7 +151,7 @@ ldap_rcode_t rlm_ldap_sasl_interactive(rlm_ldap_t const *inst, REQUEST *request,
                 *      successful without the help of ldap_result.
                 */
                if (ret != LDAP_SASL_BIND_IN_PROGRESS) {
-                       status = rlm_ldap_result(inst, conn, -1, identity, timeout, NULL, error, extra);
+                       status = rlm_ldap_result(conn, -1, identity, timeout, NULL, error, extra);
                        break;          /* Old result gets freed on after exit */
                }
 
@@ -165,7 +161,7 @@ ldap_rcode_t rlm_ldap_sasl_interactive(rlm_ldap_t const *inst, REQUEST *request,
                 *      If LDAP parse result indicates there was an error
                 *      then we're done.
                 */
-               status = rlm_ldap_result(inst, conn, msgid, identity, timeout, &result, error, extra);
+               status = rlm_ldap_result(conn, msgid, identity, timeout, &result, error, extra);
                switch (status) {
                case LDAP_PROC_SUCCESS:         /* ldap_sasl_interactive_bind should have indicated success */
                case LDAP_PROC_CONTINUE:
diff --git a/src/modules/rlm_ldap/user.c b/src/modules/rlm_ldap/user.c
new file mode 100644 (file)
index 0000000..384860a
--- /dev/null
@@ -0,0 +1,338 @@
+/*
+ *   This program is is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or (at
+ *   your option) any later version.
+ *
+ *   This program 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.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * $Id$
+ * @file groups.c
+ * @brief LDAP module group functions.
+ *
+ * @author Arran Cudbard-Bell <a.cudbardb@freeradius.org>
+ *
+ * @copyright 2013 Network RADIUS SARL <info@networkradius.com>
+ * @copyright 2013-2015 The FreeRADIUS Server Project.
+ */
+#include <freeradius-devel/rad_assert.h>
+#include <ctype.h>
+
+#define LOG_PREFIX "rlm_ldap (%s) - "
+#define LOG_PREFIX_ARGS inst->name
+
+#include "rlm_ldap.h"
+
+/** Retrieve the DN of a user object
+ *
+ * Retrieves the DN of a user and adds it to the control list as LDAP-UserDN. Will also retrieve any
+ * attributes passed and return the result in *result.
+ *
+ * This potentially allows for all authorization and authentication checks to be performed in one
+ * ldap search operation, which is a big bonus given the number of crappy, slow *cough*AD*cough*
+ * LDAP directory servers out there.
+ *
+ * @param[in] inst rlm_ldap configuration.
+ * @param[in] request Current request.
+ * @param[in,out] pconn to use. May change as this function calls functions which auto re-connect.
+ * @param[in] attrs Additional attributes to retrieve, may be NULL.
+ * @param[in] force Query even if the User-DN already exists.
+ * @param[out] result Where to write the result, may be NULL in which case result is discarded.
+ * @param[out] rcode The status of the operation, one of the RLM_MODULE_* codes.
+ * @return The user's DN or NULL on error.
+ */
+char const *rlm_ldap_find_user(rlm_ldap_t const *inst, REQUEST *request, ldap_handle_t **pconn,
+                              char const *attrs[], bool force, LDAPMessage **result, rlm_rcode_t *rcode)
+{
+       static char const *tmp_attrs[] = { NULL };
+
+       ldap_rcode_t    status;
+       VALUE_PAIR      *vp = NULL;
+       LDAPMessage     *tmp_msg = NULL, *entry = NULL;
+       int             ldap_errno;
+       int             cnt;
+       char            *dn = NULL;
+       char const      *filter = NULL;
+       char            filter_buff[LDAP_MAX_FILTER_STR_LEN];
+       char const      *base_dn;
+       char            base_dn_buff[LDAP_MAX_DN_STR_LEN];
+       LDAPControl     *serverctrls[] = { inst->userobj_sort_ctrl, NULL };
+
+       bool freeit = false;                                    //!< Whether the message should
+                                                               //!< be freed after being processed.
+
+       *rcode = RLM_MODULE_FAIL;
+
+       if (!result) {
+               result = &tmp_msg;
+               freeit = true;
+       }
+       *result = NULL;
+
+       if (!attrs) {
+               memset(&attrs, 0, sizeof(tmp_attrs));
+       }
+
+       /*
+        *      If the caller isn't looking for the result we can just return the current userdn value.
+        */
+       if (!force) {
+               vp = fr_pair_find_by_num(request->control, 0, PW_LDAP_USERDN, TAG_ANY);
+               if (vp) {
+                       RDEBUG("Using user DN from request \"%s\"", vp->vp_strvalue);
+                       *rcode = RLM_MODULE_OK;
+                       return vp->vp_strvalue;
+               }
+       }
+
+       /*
+        *      Perform all searches as the admin user.
+        */
+       if ((*pconn)->rebound) {
+               status = fr_ldap_bind(request, pconn, (*pconn)->config->admin_identity,
+                                     (*pconn)->config->admin_password, &(*pconn)->config->admin_sasl,
+                                     NULL, NULL, NULL);
+               if (status != LDAP_PROC_SUCCESS) {
+                       *rcode = RLM_MODULE_FAIL;
+                       return NULL;
+               }
+
+               rad_assert(*pconn);
+
+               (*pconn)->rebound = false;
+       }
+
+       if (inst->userobj_filter) {
+               if (tmpl_expand(&filter, filter_buff, sizeof(filter_buff), request, inst->userobj_filter,
+                               fr_ldap_escape_func, NULL) < 0) {
+                       REDEBUG("Unable to create filter");
+                       *rcode = RLM_MODULE_INVALID;
+
+                       return NULL;
+               }
+       }
+
+       if (tmpl_expand(&base_dn, base_dn_buff, sizeof(base_dn_buff), request,
+                       inst->userobj_base_dn, fr_ldap_escape_func, NULL) < 0) {
+               REDEBUG("Unable to create base_dn");
+               *rcode = RLM_MODULE_INVALID;
+
+               return NULL;
+       }
+
+       status = fr_ldap_search(result, request, pconn, base_dn,
+                               inst->userobj_scope, filter, attrs, serverctrls, NULL);
+       switch (status) {
+       case LDAP_PROC_SUCCESS:
+               break;
+
+       case LDAP_PROC_BAD_DN:
+       case LDAP_PROC_NO_RESULT:
+               *rcode = RLM_MODULE_NOTFOUND;
+               return NULL;
+
+       default:
+               *rcode = RLM_MODULE_FAIL;
+               return NULL;
+       }
+
+       rad_assert(*pconn);
+
+       /*
+        *      Forbid the use of unsorted search results that
+        *      contain multiple entries, as it's a potential
+        *      security issue, and likely non deterministic.
+        */
+       if (!inst->userobj_sort_ctrl) {
+               cnt = ldap_count_entries((*pconn)->handle, *result);
+               if (cnt > 1) {
+                       REDEBUG("Ambiguous search result, returned %i unsorted entries (should return 1 or 0).  "
+                               "Enable sorting, or specify a more restrictive base_dn, filter or scope", cnt);
+                       REDEBUG("The following entries were returned:");
+                       RINDENT();
+                       for (entry = ldap_first_entry((*pconn)->handle, *result);
+                            entry;
+                            entry = ldap_next_entry((*pconn)->handle, entry)) {
+                               dn = ldap_get_dn((*pconn)->handle, entry);
+                               REDEBUG("%s", dn);
+                               ldap_memfree(dn);
+                       }
+                       REXDENT();
+                       *rcode = RLM_MODULE_INVALID;
+                       goto finish;
+               }
+       }
+
+       entry = ldap_first_entry((*pconn)->handle, *result);
+       if (!entry) {
+               ldap_get_option((*pconn)->handle, LDAP_OPT_RESULT_CODE, &ldap_errno);
+               REDEBUG("Failed retrieving entry: %s",
+                       ldap_err2string(ldap_errno));
+
+               goto finish;
+       }
+
+       dn = ldap_get_dn((*pconn)->handle, entry);
+       if (!dn) {
+               ldap_get_option((*pconn)->handle, LDAP_OPT_RESULT_CODE, &ldap_errno);
+               REDEBUG("Retrieving object DN from entry failed: %s", ldap_err2string(ldap_errno));
+
+               goto finish;
+       }
+       fr_ldap_util_normalise_dn(dn, dn);
+
+       /*
+        *      We can't use fr_pair_make here to copy the value into the
+        *      attribute, as the dn must be copied into the attribute
+        *      verbatim (without de-escaping).
+        *
+        *      Special chars are pre-escaped by libldap, and because
+        *      we pass the string back to libldap we must not alter it.
+        */
+       RDEBUG("User object found at DN \"%s\"", dn);
+       vp = fr_pair_make(request, &request->control, "LDAP-UserDN", NULL, T_OP_EQ);
+       if (vp) {
+               fr_pair_value_strcpy(vp, dn);
+               *rcode = RLM_MODULE_OK;
+       }
+       ldap_memfree(dn);
+
+finish:
+       if ((freeit || (*rcode != RLM_MODULE_OK)) && *result) {
+               ldap_msgfree(*result);
+               *result = NULL;
+       }
+
+       return vp ? vp->vp_strvalue : NULL;
+}
+
+/** Check for presence of access attribute in result
+ *
+ * @param[in] inst rlm_ldap configuration.
+ * @param[in] request Current request.
+ * @param[in] conn used to retrieve access attributes.
+ * @param[in] entry retrieved by rlm_ldap_find_user or fr_ldap_search.
+ * @return
+ *     - #RLM_MODULE_USERLOCK if the user was denied access.
+ *     - #RLM_MODULE_OK otherwise.
+ */
+rlm_rcode_t rlm_ldap_check_access(rlm_ldap_t const *inst, REQUEST *request,
+                                 ldap_handle_t const *conn, LDAPMessage *entry)
+{
+       rlm_rcode_t rcode = RLM_MODULE_OK;
+       struct berval **values = NULL;
+
+       values = ldap_get_values_len(conn->handle, entry, inst->userobj_access_attr);
+       if (values) {
+               if (inst->access_positive) {
+                       if ((values[0]->bv_len >= 5) && (strncasecmp(values[0]->bv_val, "false", 5) == 0)) {
+                               RDEBUG("\"%s\" attribute exists but is set to 'false' - user locked out",
+                                      inst->userobj_access_attr);
+                               rcode = RLM_MODULE_USERLOCK;
+                       }
+                       /* RLM_MODULE_OK set above... */
+               } else if ((values[0]->bv_len < 5) || (strncasecmp(values[0]->bv_val, "false", 5) != 0)) {
+                       RDEBUG("\"%s\" attribute exists - user locked out", inst->userobj_access_attr);
+                       rcode = RLM_MODULE_USERLOCK;
+               }
+               ldap_value_free_len(values);
+       } else if (inst->access_positive) {
+               RDEBUG("No \"%s\" attribute - user locked out", inst->userobj_access_attr);
+               rcode = RLM_MODULE_USERLOCK;
+       }
+
+       return rcode;
+}
+
+/** Verify we got a password from the search
+ *
+ * Checks to see if after the LDAP to RADIUS mapping has been completed that a reference password.
+ *
+ * @param inst rlm_ldap configuration.
+ * @param request Current request.
+ * @param conn the connection handle
+ */
+void rlm_ldap_check_reply(rlm_ldap_t const *inst, REQUEST *request, ldap_handle_t const *conn)
+{
+       /*
+       *       More warning messages for people who can't be bothered to read the documentation.
+       *
+       *       Expect_password is set when we process the mapping, and is only true if there was a mapping between
+       *       an LDAP attribute and a password reference attribute in the control list.
+       */
+       if (!inst->expect_password || (rad_debug_lvl < L_DBG_LVL_2)) return;
+
+       if (!fr_pair_find_by_num(request->control, 0, PW_CLEARTEXT_PASSWORD, TAG_ANY) &&
+           !fr_pair_find_by_num(request->control, 0, PW_NT_PASSWORD, TAG_ANY) &&
+           !fr_pair_find_by_num(request->control, 0, PW_USER_PASSWORD, TAG_ANY) &&
+           !fr_pair_find_by_num(request->control, 0, PW_PASSWORD_WITH_HEADER, TAG_ANY) &&
+           !fr_pair_find_by_num(request->control, 0, PW_CRYPT_PASSWORD, TAG_ANY)) {
+               switch (conn->directory->type) {
+               case LDAP_DIRECTORY_ACTIVE_DIRECTORY:
+                       RWDEBUG("!!! Found map between LDAP attribute and a FreeRADIUS password attribute");
+                       RWDEBUG("!!! Active Directory does not allow passwords to be read via LDAP");
+                       RWDEBUG("!!! Remove the password map and either:");
+                       RWDEBUG("!!!  - Configure authentication via ntlm_auth (mschapv2 only)");
+                       RWDEBUG("!!!  - Configure authentication via wbclient (mschapv2 only)");
+                       RWDEBUG("!!!    that password attribute");
+                       RWDEBUG("!!!  - Bind as the user by listing %s in the authenticate section, and",
+                               inst->name);
+                       RWDEBUG("!!!    setting attribute &control:Auth-Type := '%s' in the authorize section",
+                               inst->name);
+                       RWDEBUG("!!!    (pap only)");
+
+                       break;
+
+               case LDAP_DIRECTORY_EDIRECTORY:
+                       RWDEBUG("!!! Found map between LDAP attribute and a FreeRADIUS password attribute");
+                       RWDEBUG("!!! eDirectory does not allow passwords to be retrieved via LDAP search");
+                       RWDEBUG("!!! Remove the password map and either:");
+                       RWDEBUG("!!!  - Set 'edir = yes' and enable the universal password feature on your ");
+                       RWDEBUG("!!!    eDir server (recommended)");
+                       RWDEBUG("!!!    that password attribute");
+                       RWDEBUG("!!!  - Bind as the user by listing %s in the authenticate section, and",
+                               inst->name);
+                       RWDEBUG("!!!    setting attribute &control:Auth-Type := '%s' in the authorize section",
+                               inst->name);
+                       RWDEBUG("!!!    (pap only)");
+                       break;
+
+               default:
+                       if (!conn->config->admin_identity) {
+                               RWDEBUG("!!! Found map between LDAP attribute and a FreeRADIUS password attribute");
+                               RWDEBUG("!!! but no password attribute found in search result");
+                               RWDEBUG("!!! Either:");
+                               RWDEBUG("!!!  - Ensure the user object contains a password attribute, and that ");
+                               RWDEBUG("!!!    \"%s\" has permission to read that password attribute (recommended)",
+                                       conn->config->admin_identity);
+                               RWDEBUG("!!!  - Bind as the user by listing %s in the authenticate section, and",
+                                       inst->name);
+                               RWDEBUG("!!!    setting attribute &control:Auth-Type := '%s' in the authorize section",
+                                       inst->name);
+                               RWDEBUG("!!!    (pap only)");
+                       } else {
+                               RWDEBUG("!!! No \"known good\" password added");
+                               RWDEBUG("!!! but no password attribute found in search result");
+                               RWDEBUG("!!! Either:");
+                               RWDEBUG("!!!  - Ensure the user object contains a password attribute, and that ");
+                               RWDEBUG("!!!    'identity' is set to the DN of an account that has permission to read");
+                               RWDEBUG("!!!    that password attribute");
+                               RWDEBUG("!!!  - Bind as the user by listing %s in the authenticate section, and",
+                                       inst->name);
+                               RWDEBUG("!!!    setting attribute &control:Auth-Type := '%s' in the authorize section",
+                                       inst->name);
+                               RWDEBUG("!!!    (pap only)");
+                       }
+                       break;
+               }
+       }
+}
diff --git a/src/modules/rlm_ldap/util.c b/src/modules/rlm_ldap/util.c
new file mode 100644 (file)
index 0000000..ee88b58
--- /dev/null
@@ -0,0 +1,531 @@
+/*
+ *   This program is is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or (at
+ *   your option) any later version.
+ *
+ *   This program 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.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * $Id$
+ * @file util.c
+ * @brief Utility functions to escape and parse DNs
+ *
+ * @author Arran Cudbard-Bell <a.cudbardb@freeradius.org>
+ * @copyright 2017 Arran Cudbard-Bell <a.cudbardb@freeradius.org>
+ * @copyright 2017 The FreeRADIUS Server Project.
+ */
+#include "rlm_ldap.h"
+
+#include <stdarg.h>
+#include <ctype.h>
+
+static const char specials[] = ",+\"\\<>;*=()";
+static const char hextab[] = "0123456789abcdef";
+
+/** Converts "bad" strings into ones which are safe for LDAP
+ *
+ * @note RFC 4515 says filter strings can only use the @verbatim \<hex><hex> @endverbatim
+ *     format, whereas RFC 4514 indicates that some chars in DNs, may be escaped simply
+ *     with a backslash. For simplicity, we always use the hex escape sequences.
+ *     In other areas where we're doing DN comparison, the DNs need to be normalised first
+ *     so that they both use only hex escape sequences.
+ *
+ * @note This is a callback for xlat operations.
+ *
+ * Will escape any characters in input strings that would cause the string to be interpreted
+ * as part of a DN and or filter. Escape sequence is @verbatim \<hex><hex> @endverbatim.
+ *
+ * @param request The current request.
+ * @param out Pointer to output buffer.
+ * @param outlen Size of the output buffer.
+ * @param in Raw unescaped string.
+ * @param arg Any additional arguments (unused).
+ */
+size_t fr_ldap_escape_func(UNUSED REQUEST *request, char *out, size_t outlen, char const *in, UNUSED void *arg)
+{
+
+       size_t left = outlen;
+
+       if (*in && ((*in == ' ') || (*in == '#'))) goto encode;
+
+       while (*in) {
+               /*
+                *      Encode unsafe characters.
+                */
+               if (memchr(specials, *in, sizeof(specials) - 1)) {
+               encode:
+                       /*
+                        *      Only 3 or less bytes available.
+                        */
+                       if (left <= 3) break;
+
+                       *out++ = '\\';
+                       *out++ = hextab[(*in >> 4) & 0x0f];
+                       *out++ = hextab[*in & 0x0f];
+                       in++;
+                       left -= 3;
+
+                       continue;
+               }
+
+               if (left <= 1) break;
+
+               /*
+                *      Doesn't need encoding
+                */
+               *out++ = *in++;
+               left--;
+       }
+
+       *out = '\0';
+
+       return outlen - left;
+}
+
+/** Converts escaped DNs and filter strings into normal
+ *
+ * @note RFC 4515 says filter strings can only use the @verbatim \<hex><hex> @endverbatim
+ *     format, whereas RFC 4514 indicates that some chars in DNs, may be escaped simply
+ *     with a backslash..
+ *
+ * Will unescape any special characters in strings, or @verbatim \<hex><hex> @endverbatim
+ * sequences.
+ *
+ * @param request The current request.
+ * @param out Pointer to output buffer.
+ * @param outlen Size of the output buffer.
+ * @param in Escaped string string.
+ * @param arg Any additional arguments (unused).
+ */
+size_t fr_ldap_unescape_func(UNUSED REQUEST *request, char *out, size_t outlen, char const *in, UNUSED void *arg)
+{
+       char const *p;
+       char *c1, *c2, c3;
+       size_t  freespace = outlen;
+
+       if (outlen <= 1) return 0;
+
+       p = in;
+       while (*p && (--freespace > 0)) {
+               if (*p != '\\') {
+               next:
+                       *out++ = *p++;
+                       continue;
+               }
+
+               p++;
+
+               /* It's an escaped special, just remove the slash */
+               if (memchr(specials, *in, sizeof(specials) - 1)) {
+                       *out++ = *p++;
+                       continue;
+               }
+
+               /* Is a hex sequence */
+               if (!(c1 = memchr(hextab, tolower(p[0]), 16)) ||
+                   !(c2 = memchr(hextab, tolower(p[1]), 16))) goto next;
+               c3 = ((c1 - hextab) << 4) + (c2 - hextab);
+
+               *out++ = c3;
+               p += 2;
+       }
+
+       *out = '\0';
+
+       return outlen - freespace;
+}
+
+
+/** Check whether a string looks like a DN
+ *
+ * @param[in] in Str to check.
+ * @param[in] inlen Length of string to check.
+ * @return
+ *     - true if string looks like a DN.
+ *     - false if string does not look like DN.
+ */
+bool fr_ldap_util_is_dn(char const *in, size_t inlen)
+{
+       char const *p;
+
+       char want = '=';
+       bool too_soon = true;
+       int comp = 1;
+
+       for (p = in; inlen > 0; p++, inlen--) {
+               if (p[0] == '\\') {
+                       char c;
+
+                       too_soon = false;
+
+                       /*
+                        *      Invalid escape sequence, not a DN
+                        */
+                       if (inlen < 2) return false;
+
+                       /*
+                        *      Double backslash, consume two chars
+                        */
+                       if (p[1] == '\\') {
+                               inlen--;
+                               p++;
+                               continue;
+                       }
+
+                       /*
+                        *      Special, consume two chars
+                        */
+                       switch (p[1]) {
+                       case ' ':
+                       case '#':
+                       case '=':
+                       case '"':
+                       case '+':
+                       case ',':
+                       case ';':
+                       case '<':
+                       case '>':
+                       case '\'':
+                               inlen -= 1;
+                               p += 1;
+                               continue;
+
+                       default:
+                               break;
+                       }
+
+                       /*
+                        *      Invalid escape sequence, not a DN
+                        */
+                       if (inlen < 3) return false;
+
+                       /*
+                        *      Hex encoding, consume three chars
+                        */
+                       if (fr_hex2bin((uint8_t *) &c, 1, p + 1, 2) == 1) {
+                               inlen -= 2;
+                               p += 2;
+                               continue;
+                       }
+
+                       /*
+                        *      Invalid escape sequence, not a DN
+                        */
+                       return false;
+               }
+
+               switch (*p) {
+               case '=':
+                       if (too_soon || (*p != want)) return false;     /* Too soon after last , or = */
+                       want = ',';
+                       too_soon = true;
+                       break;
+
+               case ',':
+                       if (too_soon || (*p != want)) return false;     /* Too soon after last , or = */
+                       want = '=';
+                       too_soon = true;
+                       comp++;
+                       break;
+
+               default:
+                       too_soon = false;
+                       break;
+               }
+       }
+
+       /*
+        *      If the string ended with , or =, or the number
+        *      of components was less than 2
+        *
+        *      i.e. we don't have <attr>=<val>,<attr>=<val>
+        */
+       if (too_soon || (comp < 2)) return false;
+
+       return true;
+}
+
+/** Parse a subset (just server side sort for now) of LDAP URL extensions
+ *
+ * @param[out] sss             Where to write a pointer to the server side sort control
+ *                             we created.
+ * @param[in] request          The current request.
+ * @param[in] conn             Handle to allocate controls under.
+ * @param[in] extensions       A NULL terminated array of extensions.
+ * @return
+ *     - 0 on success.
+ *     - -1 on failure.
+ */
+int fr_ldap_parse_url_extensions(LDAPControl **sss, REQUEST *request, ldap_handle_t *conn, char **extensions)
+{
+       int i;
+
+       *sss = NULL;
+
+       if (!extensions) return 0;
+
+       /*
+        *      Parse extensions in the LDAP URL
+        */
+       for (i = 0; extensions[i]; i++) {
+               char *p;
+               bool is_critical = false;
+
+               p = extensions[i];
+               if (*p == '!') {
+                       is_critical = true;
+                       p++;
+               }
+
+#ifdef HAVE_LDAP_CREATE_SORT_CONTROL
+               /*
+                *      Server side sort control
+                */
+               if (strncmp(p, "sss", 3) == 0) {
+                       LDAPSortKey     **keys;
+                       int             ret;
+
+                       p += 3;
+                       p = strchr(p, '=');
+                       if (!p) {
+                               REDEBUG("Server side sort extension must be in the format \"[!]sss=<key>[,key]\"");
+                               return -1;
+                       }
+                       p++;
+
+                       ret = ldap_create_sort_keylist(&keys, p);
+                       if (ret != LDAP_SUCCESS) {
+                               REDEBUG("Invalid server side sort value \"%s\": %s", p, ldap_err2string(ret));
+                               return -1;
+                       }
+
+                       if (*sss) ldap_control_free(*sss);
+
+                       ret = ldap_create_sort_control(conn->handle, keys, is_critical ? 1 : 0, sss);
+                       ldap_free_sort_keylist(keys);
+                       if (ret != LDAP_SUCCESS) {
+                               ERROR("Failed creating server sort control: %s", ldap_err2string(ret));
+                               return -1;
+                       }
+
+                       continue;
+               }
+#endif
+
+               RWDEBUG("URL extension \"%s\" ignored", p);
+       }
+
+       return 0;
+}
+
+
+/** Convert a berval to a talloced string
+ *
+ * The ldap_get_values function is deprecated, and ldap_get_values_len
+ * does not guarantee the berval buffers it returns are \0 terminated.
+ *
+ * For some cases this is fine, for others we require a \0 terminated
+ * buffer (feeding DNs back into libldap for example).
+ *
+ * @param ctx to allocate in.
+ * @param in Berval to copy.
+ * @return \0 terminated buffer containing in->bv_val.
+ */
+char *fr_ldap_berval_to_string(TALLOC_CTX *ctx, struct berval const *in)
+{
+       char *out;
+
+       out = talloc_array(ctx, char, in->bv_len + 1);
+       if (!out) return NULL;
+
+       memcpy(out, in->bv_val, in->bv_len);
+       out[in->bv_len] = '\0';
+
+       return out;
+}
+
+/** Normalise escape sequences in a DN
+ *
+ * Characters in a DN can either be escaped as
+ * @verbatim \<hex><hex> @endverbatim or @verbatim \<special> @endverbatim
+ *
+ * The LDAP directory chooses how characters are escaped, which can make
+ * local comparisons of DNs difficult.
+ *
+ * Here we search for hex sequences that match special chars, and convert
+ * them to the @verbatim \<special> @endverbatim form.
+ *
+ * @note the resulting output string will only ever be shorter than the
+ *       input, so it's fine to use the same buffer for both out and in.
+ *
+ * @param out Where to write the normalised DN.
+ * @param in The input DN.
+ * @return The number of bytes written to out.
+ */
+size_t fr_ldap_util_normalise_dn(char *out, char const *in)
+{
+       char const *p;
+       char *o = out;
+
+       for (p = in; *p != '\0'; p++) {
+               if (p[0] == '\\') {
+                       char c;
+
+                       /*
+                        *      Double backslashes get processed specially
+                        */
+                       if (p[1] == '\\') {
+                               p += 1;
+                               *o++ = p[0];
+                               *o++ = p[1];
+                               continue;
+                       }
+
+                       /*
+                        *      Hex encodings that have an alternative
+                        *      special encoding, get rewritten to the
+                        *      special encoding.
+                        */
+                       if (fr_hex2bin((uint8_t *) &c, 1, p + 1, 2) == 1) {
+                               switch (c) {
+                               case ' ':
+                               case '#':
+                               case '=':
+                               case '"':
+                               case '+':
+                               case ',':
+                               case ';':
+                               case '<':
+                               case '>':
+                               case '\'':
+                                       *o++ = '\\';
+                                       *o++ = c;
+                                       p += 2;
+                                       continue;
+
+                               default:
+                                       break;
+                               }
+                       }
+               }
+               *o++ = *p;
+       }
+       *o = '\0';
+
+       return o - out;
+}
+
+/** Find the place at which the two DN strings diverge
+ *
+ * Returns the length of the non matching string in full.
+ *
+ * @param full DN.
+ * @param part Partial DN as returned by ldap_parse_result.
+ * @return
+ *     - Length of the portion of full which wasn't matched
+ *     - -1 on failure.
+ */
+size_t fr_ldap_common_dn(char const *full, char const *part)
+{
+       size_t f_len, p_len, i;
+
+       if (!full) return -1;
+
+       f_len = strlen(full);
+
+       if (!part) return -1;
+
+       p_len = strlen(part);
+       if (!p_len) return f_len;
+
+       if ((f_len < p_len) || !f_len) return -1;
+
+       for (i = 0; i < p_len; i++) if (part[p_len - i] != full[f_len - i]) return -1;
+
+       return f_len - p_len;
+}
+
+/** Combine and expand filters
+ *
+ * @param request Current request.
+ * @param out Where to write the expanded string.
+ * @param outlen Length of output buffer.
+ * @param sub Array of subfilters (may contain NULLs).
+ * @param sublen Number of potential subfilters in array.
+ * @return length of expanded data.
+ */
+ssize_t fr_ldap_xlat_filter(REQUEST *request, char const **sub, size_t sublen, char *out, size_t outlen)
+{
+       char buffer[LDAP_MAX_FILTER_STR_LEN + 1];
+       char const *in = NULL;
+       char *p = buffer;
+
+       ssize_t len = 0;
+
+       unsigned int i;
+       int cnt = 0;
+
+       /*
+        *      Figure out how many filter elements we need to integrate
+        */
+       for (i = 0; i < sublen; i++) {
+               if (sub[i] && *sub[i]) {
+                       in = sub[i];
+                       cnt++;
+               }
+       }
+
+       if (!cnt) {
+               out[0] = '\0';
+               return 0;
+       }
+
+       if (cnt > 1) {
+               if (outlen < 3) {
+                       goto oob;
+               }
+
+               p[len++] = '(';
+               p[len++] = '&';
+
+               for (i = 0; i < sublen; i++) {
+                       if (sub[i] && (*sub[i] != '\0')) {
+                               len += strlcpy(p + len, sub[i], outlen - len);
+
+                               if ((size_t) len >= outlen) {
+                                       oob:
+                                       REDEBUG("Out of buffer space creating filter");
+
+                                       return -1;
+                               }
+                       }
+               }
+
+               if ((outlen - len) < 2) {
+                       goto oob;
+               }
+
+               p[len++] = ')';
+               p[len] = '\0';
+
+               in = buffer;
+       }
+
+       len = xlat_eval(out, outlen, request, in, fr_ldap_escape_func, NULL);
+       if (len < 0) {
+               REDEBUG("Failed creating filter");
+
+               return -1;
+       }
+
+       return len;
+}