text: /**
   * Copyright (C) 2011-2013 Valery Komarov <komarov@valerka.net>
   * Copyright (C) 2013 Jiri Hruska <jirka@fud.cz>
   * Copyright (C) 2015-2016 Victor Hahn Castell <victor.hahn@flexoptix.net>
   * All rights reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in the
   *    documentation and/or other materials provided with the distribution.
   *
   * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
   * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
   * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
   * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   */
  
  #include <ngx_config.h>
  #include <ngx_core.h>
  #include <ngx_http.h>
  #include <ngx_md5.h>
  #include <ldap.h>
  #include <openssl/opensslv.h>
  
  // used for manual warnings
  #define XSTR(x) STR(x)
  #define STR(x) #x
  // make sure manual warnings don't get escalated to errors
  #ifdef __clang__
  #pragma clang diagnostic warning "-W#warnings"
  #else
  #ifdef __GNUC__
  #pragma GCC diagnostic warning "-Wcpp"
  #endif
  #endif
  // TODO: do the same stuff for MSVC and/or other compilers
  
  
  #ifndef LDAP_PROTO_EXT
  /* Some OpenLDAP headers are accidentally missing ldap_init_fd() etc. */
  
  #define LDAP_PROTO_TCP 1 /* ldap://  */
  #define LDAP_PROTO_UDP 2 /* reserved */
  #define LDAP_PROTO_IPC 3 /* ldapi:// */
  #define LDAP_PROTO_EXT 4 /* user-defined socket/sockbuf */
  
  extern int ldap_init_fd(ber_socket_t fd, int proto, const char *url, LDAP **ld);
  #endif
  
  #define OUTCOME_ERROR          -1 /* Some error occured in the process */
  #define OUTCOME_DENY            0
  #define OUTCOME_ALLOW           1
  #define OUTCOME_CACHED_DENY     2 /* Cached results */
  #define OUTCOME_CACHED_ALLOW    3
  #define OUTCOME_UNCERTAIN       4 /* Not yet decided */
  
  #define NGX_HTTP_AUTH_LDAP_MAX_SERVERS_SIZE 7
  
  
  typedef struct {
      LDAPURLDesc *ludpp;
      ngx_str_t url;
      ngx_url_t parsed_url;
      ngx_str_t alias;
  
      ngx_str_t bind_dn;
      ngx_str_t bind_dn_passwd;
  
      ngx_str_t group_attribute;
      ngx_flag_t group_attribute_dn;
  
      ngx_flag_t ssl_check_cert;
      ngx_str_t ssl_ca_dir;
      ngx_str_t ssl_ca_file;
  
      ngx_array_t *require_group;     /* array of ngx_http_complex_value_t */
      ngx_array_t *require_user;      /* array of ngx_http_complex_value_t */
      ngx_flag_t require_valid_user;
      ngx_http_complex_value_t require_valid_user_dn;
      ngx_flag_t satisfy_all;
      ngx_flag_t referral;
  
      ngx_uint_t connections;
      ngx_uint_t max_down_retries;
      ngx_uint_t max_down_retries_count;
      ngx_msec_t connect_timeout;
      ngx_msec_t reconnect_timeout;
      ngx_msec_t bind_timeout;
      ngx_msec_t request_timeout;
      ngx_queue_t free_connections;
      ngx_queue_t waiting_requests;
  } ngx_http_auth_ldap_server_t;
  
  typedef struct {
      ngx_array_t *servers;        /* array of ngx_http_auth_ldap_server_t */
      ngx_flag_t cache_enabled;
      ngx_msec_t cache_expiration_time;
      size_t cache_size;
      ngx_int_t servers_size;
  #if (NGX_OPENSSL)
      ngx_ssl_t ssl;
  #endif
  } ngx_http_auth_ldap_main_conf_t;
  
  typedef struct {
      ngx_str_t realm;
      ngx_array_t *servers;       /* array of ngx_http_auth_ldap_server_t* */
  } ngx_http_auth_ldap_loc_conf_t;
  
  typedef struct {
      uint32_t small_hash;     /* murmur2 hash of username ^ &server       */
      uint32_t outcome;        /* OUTCOME_DENY or OUTCOME_ALLOW            */
      ngx_msec_t time;         /* ngx_current_msec when created            */
      u_char big_hash[16];     /* md5 hash of (username, server, password) */
  } ngx_http_auth_ldap_cache_elt_t;
  
  typedef struct {
      ngx_http_auth_ldap_cache_elt_t *buckets;
      ngx_uint_t num_buckets;
      ngx_uint_t elts_per_bucket;
      ngx_msec_t expiration_time;
  } ngx_http_auth_ldap_cache_t;
  
  typedef enum {
      PHASE_START,
      PHASE_SEARCH,
      PHASE_CHECK_USER,
      PHASE_CHECK_GROUP,
      PHASE_CHECK_BIND,
      PHASE_REBIND,
      PHASE_NEXT
  } ngx_http_auth_ldap_request_phase_t;
  
  typedef struct {
      ngx_http_request_t *r;
      ngx_uint_t server_index;
      ngx_http_auth_ldap_server_t *server;
      ngx_http_auth_ldap_request_phase_t phase;
      unsigned int iteration;
      int outcome;
  
      struct ngx_http_auth_ldap_connection *c;
      ngx_queue_t queue;
      int replied;
      int error_code;
      ngx_str_t error_msg;
      ngx_str_t dn;
      ngx_str_t user_dn;
      ngx_str_t group_dn;
  
      ngx_http_auth_ldap_cache_elt_t *cache_bucket;
      u_char cache_big_hash[16];
      uint32_t cache_small_hash;
  } ngx_http_auth_ldap_ctx_t;
  
  typedef enum {
      STATE_DISCONNECTED,
      STATE_INITIAL_BINDING,
      STATE_CONNECTING,
      STATE_READY,
      STATE_BINDING,
      STATE_SEARCHING,
      STATE_COMPARING
  } ngx_http_auth_ldap_connection_state_t;
  
  typedef struct ngx_http_auth_ldap_connection {
      ngx_log_t *log;
      ngx_http_auth_ldap_server_t *server;
      ngx_peer_connection_t conn;
      ngx_event_t reconnect_event;
  
  #if (NGX_OPENSSL)
      ngx_pool_t *pool;
      ngx_ssl_t *ssl;
  #endif
  
      ngx_queue_t queue;
      ngx_http_auth_ldap_ctx_t *rctx;
  
      LDAP* ld;
      ngx_http_auth_ldap_connection_state_t state;
      int msgid;
  } ngx_http_auth_ldap_connection_t;
  
  static char * ngx_http_auth_ldap_ldap_server_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
  static char * ngx_http_auth_ldap_ldap_server(ngx_conf_t *cf, ngx_command_t *dummy, void *conf);
  static char * ngx_http_auth_ldap(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
  static char * ngx_http_auth_ldap_servers(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
  static char * ngx_http_auth_ldap_parse_url(ngx_conf_t *cf, ngx_http_auth_ldap_server_t *server);
  static char * ngx_http_auth_ldap_parse_require(ngx_conf_t *cf, ngx_http_auth_ldap_server_t *server);
  static char * ngx_http_auth_ldap_parse_satisfy(ngx_conf_t *cf, ngx_http_auth_ldap_server_t *server);
  static char * ngx_http_auth_ldap_parse_referral(ngx_conf_t *cf, ngx_http_auth_ldap_server_t *server);
  static void * ngx_http_auth_ldap_create_main_conf(ngx_conf_t *cf);
  static char * ngx_http_auth_ldap_init_main_conf(ngx_conf_t *cf, void *parent);
  static void * ngx_http_auth_ldap_create_loc_conf(ngx_conf_t *);
  static char * ngx_http_auth_ldap_merge_loc_conf(ngx_conf_t *, void *, void *);
  static ngx_int_t ngx_http_auth_ldap_init_worker(ngx_cycle_t *cycle);
  static ngx_int_t ngx_http_auth_ldap_init(ngx_conf_t *cf);
  static ngx_int_t ngx_http_auth_ldap_init_cache(ngx_cycle_t *cycle);
  static void ngx_http_auth_ldap_close_connection(ngx_http_auth_ldap_connection_t *c);
  static void ngx_http_auth_ldap_read_handler(ngx_event_t *rev);
  static void ngx_http_auth_ldap_reconnect_handler(ngx_event_t *);
  static ngx_int_t ngx_http_auth_ldap_init_connections(ngx_cycle_t *cycle);
  static ngx_int_t ngx_http_auth_ldap_handler(ngx_http_request_t *r);
  static ngx_int_t ngx_http_auth_ldap_authenticate(ngx_http_request_t *r, ngx_http_auth_ldap_ctx_t *ctx,
          ngx_http_auth_ldap_loc_conf_t *conf);
  static ngx_int_t ngx_http_auth_ldap_search(ngx_http_request_t *r, ngx_http_auth_ldap_ctx_t *ctx);
  static ngx_int_t ngx_http_auth_ldap_check_user(ngx_http_request_t *r, ngx_http_auth_ldap_ctx_t *ctx);
  static ngx_int_t ngx_http_auth_ldap_check_group(ngx_http_request_t *r, ngx_http_auth_ldap_ctx_t *ctx);
  static ngx_int_t ngx_http_auth_ldap_check_bind(ngx_http_request_t *r, ngx_http_auth_ldap_ctx_t *ctx);
  static ngx_int_t ngx_http_auth_ldap_recover_bind(ngx_http_request_t *r, ngx_http_auth_ldap_ctx_t *ctx);
  #if (NGX_OPENSSL)
  static ngx_int_t ngx_http_auth_ldap_restore_handlers(ngx_connection_t *conn);
  #endif
  
  ngx_http_auth_ldap_cache_t ngx_http_auth_ldap_cache;
  
  static ngx_command_t ngx_http_auth_ldap_commands[] = {
      {
          ngx_string("ldap_server"),
          NGX_HTTP_MAIN_CONF | NGX_CONF_BLOCK | NGX_CONF_TAKE1,
          ngx_http_auth_ldap_ldap_server_block,
          NGX_HTTP_MAIN_CONF_OFFSET,
          0,
          NULL
      },
      {
          ngx_string("auth_ldap_cache_enabled"),
          NGX_HTTP_MAIN_CONF | NGX_CONF_TAKE1,
          ngx_conf_set_flag_slot,
          NGX_HTTP_MAIN_CONF_OFFSET,
          offsetof(ngx_http_auth_ldap_main_conf_t, cache_enabled),
          NULL
      },
      {
          ngx_string("auth_ldap_cache_expiration_time"),
          NGX_HTTP_MAIN_CONF | NGX_CONF_TAKE1,
          ngx_conf_set_msec_slot,
          NGX_HTTP_MAIN_CONF_OFFSET,
          offsetof(ngx_http_auth_ldap_main_conf_t, cache_expiration_time),
          NULL
      },
      {
          ngx_string("auth_ldap_cache_size"),
          NGX_HTTP_MAIN_CONF | NGX_CONF_TAKE1,
          ngx_conf_set_size_slot,
          NGX_HTTP_MAIN_CONF_OFFSET,
          offsetof(ngx_http_auth_ldap_main_conf_t, cache_size),
          NULL
      },
      {
          ngx_string("auth_ldap_servers_size"),
          NGX_HTTP_MAIN_CONF | NGX_CONF_TAKE1,
          ngx_conf_set_num_slot,
          NGX_HTTP_MAIN_CONF_OFFSET,
          offsetof(ngx_http_auth_ldap_main_conf_t, servers_size),
          NULL
      },
      {
          ngx_string("auth_ldap"),
          NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_HTTP_LMT_CONF | NGX_CONF_TAKE1,
          ngx_http_auth_ldap,
          NGX_HTTP_LOC_CONF_OFFSET,
          0,
          NULL
      },
      {
          ngx_string("auth_ldap_servers"),
          NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_HTTP_LMT_CONF | NGX_CONF_ANY,
          ngx_http_auth_ldap_servers,
          NGX_HTTP_LOC_CONF_OFFSET,
          0,
          NULL
      },
      ngx_null_command
  };
  
  static ngx_http_module_t ngx_http_auth_ldap_module_ctx = {
      NULL,                                /* preconfiguration */
      ngx_http_auth_ldap_init,             /* postconfiguration */
      ngx_http_auth_ldap_create_main_conf, /* create main configuration */
      ngx_http_auth_ldap_init_main_conf,   /* init main configuration */
      NULL,                                /* create server configuration */
      NULL,                                /* merge server configuration */
      ngx_http_auth_ldap_create_loc_conf,  /* create location configuration */
      ngx_http_auth_ldap_merge_loc_conf    /* merge location configuration */
  };
  
  ngx_module_t ngx_http_auth_ldap_module = {
      NGX_MODULE_V1,
      &ngx_http_auth_ldap_module_ctx,      /* module context */
      ngx_http_auth_ldap_commands,         /* module directives */
      NGX_HTTP_MODULE,                     /* module type */
      NULL,                                /* init master */
      NULL,                                /* init module */
      ngx_http_auth_ldap_init_worker,      /* init process */
      NULL,                                /* init thread */
      NULL,                                /* exit thread */
      NULL,                                /* exit process */
      NULL,                                /* exit master */
      NGX_MODULE_V1_PADDING
  };
  
  
  /*** Configuration and initialization ***/
  
  /**
   * Reads ldap_server block and sets ngx_http_auth_ldap_ldap_server as a handler of each conf value
   */
  static char *
  ngx_http_auth_ldap_ldap_server_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
  {
      char                           *rv;
      ngx_str_t                      *value, name;
      ngx_conf_t                     save;
      ngx_http_auth_ldap_server_t    *server;
      ngx_http_auth_ldap_main_conf_t *cnf = conf;
  
      value = cf->args->elts;
  
      name = value[1];
  
      if (ngx_strlen(name.data) == 0) {
          ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "http_auth_ldap: Missing server name in ldap_server");
          return NGX_CONF_ERROR;
      }
  
      if (cnf->servers == NULL) {
          if (cnf->servers_size == NGX_CONF_UNSET) {
              cnf->servers_size = NGX_HTTP_AUTH_LDAP_MAX_SERVERS_SIZE;
          }
          cnf->servers = ngx_array_create(cf->pool, cnf->servers_size, sizeof(ngx_http_auth_ldap_server_t));
          if (cnf->servers == NULL) {
              return NGX_CONF_ERROR;
          }
      }
  
      server = ngx_array_push(cnf->servers);
      if (server == NULL) {
          return NGX_CONF_ERROR;
      }
  
      ngx_memzero(server, sizeof(*server));
      server->connect_timeout = 10000;
      server->reconnect_timeout = 10000;
      server->bind_timeout = 5000;
      server->request_timeout = 10000;
      server->alias = name;
      server->referral = 1;
  
      save = *cf;
      cf->handler = ngx_http_auth_ldap_ldap_server;
      cf->handler_conf = conf;
      rv = ngx_conf_parse(cf, NULL);
      *cf = save;
  
      if (rv != NGX_CONF_OK) {
          return rv;
      }
  
      return NGX_CONF_OK;
  }
  
  #define CONF_MSEC_VALUE(cf,value,server,x) \
  if (ngx_strcmp(value[0].data, #x) == 0) { \
          ngx_msec_t _i = ngx_parse_time(&value[1], 0); \
          if (_i == (ngx_msec_t) NGX_ERROR || _i == 0) { \
              ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "http_auth_ldap: '" #x "' value has to be a valid time unit greater than 0"); \
              return NGX_CONF_ERROR; \
          } \
          server->x = _i; \
      }
  /**
   * Called for every variable inside ldap_server block
   */
  static char *
  ngx_http_auth_ldap_ldap_server(ngx_conf_t *cf, ngx_command_t *dummy, void *conf)
  {
      char                           *rv;
      ngx_str_t                      *value;
      ngx_http_auth_ldap_server_t    *server;
      ngx_http_auth_ldap_main_conf_t *cnf = conf;
      ngx_int_t                      i;
  
      /* It should be safe to just use latest server from array */
      server = ((ngx_http_auth_ldap_server_t *) cnf->servers->elts + (cnf->servers->nelts - 1));
  
      value = cf->args->elts;
  
      /* TODO: Add more validation */
      if (ngx_strcmp(value[0].data, "url") == 0) {
          return ngx_http_auth_ldap_parse_url(cf, server);
      } else if (ngx_strcmp(value[0].data, "binddn") == 0) {
          server->bind_dn = value[1];
      } else if (ngx_strcmp(value[0].data, "binddn_passwd") == 0) {
          server->bind_dn_passwd = value[1];
      } else if (ngx_strcmp(value[0].data, "group_attribute") == 0) {
          server->group_attribute = value[1];
      } else if (ngx_strcmp(value[0].data, "group_attribute_is_dn") == 0 && ngx_strcmp(value[1].data, "on") == 0) {
          server->group_attribute_dn = 1;
      } else if (ngx_strcmp(value[0].data, "require") == 0) {
          return ngx_http_auth_ldap_parse_require(cf, server);
      } else if (ngx_strcmp(value[0].data, "satisfy") == 0) {
          return ngx_http_auth_ldap_parse_satisfy(cf, server);
      } else if (ngx_strcmp(value[0].data, "referral") == 0) {
          return ngx_http_auth_ldap_parse_referral(cf, server);
      } else if (ngx_strcmp(value[0].data, "max_down_retries") == 0) {
          i = ngx_atoi(value[1].data, value[1].len);
          if (i == NGX_ERROR) {
              ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "http_auth_ldap: 'max_down_retries' value must be an integer: using default of 0");
              i = 0;
          }
          server->max_down_retries = i;
      } else if (ngx_strcmp(value[0].data, "connections") == 0) {
          i = ngx_atoi(value[1].data, value[1].len);
          if (i == NGX_ERROR || i == 0) {
              ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "http_auth_ldap: 'connections' value has to be a number greater than 0");
              return NGX_CONF_ERROR;
          }
          server->connections = i;
      } else if (ngx_strcmp(value[0].data, "ssl_check_cert") == 0  && ngx_strcmp(value[1].data, "on") == 0) {
        #if OPENSSL_VERSION_NUMBER >= 0x10002000
        server->ssl_check_cert = 1;
        #else
        #warning "http_auth_ldap: Compiling with OpenSSL < 1.0.2, certificate verification will be unavailable. OPENSSL_VERSION_NUMBER == " XSTR(OPENSSL_VERSION_NUMBER)
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
          "http_auth_ldap: 'ssl_cert_check': cannot verify remote certificate's domain name because "
          "your version of OpenSSL is too old. "
          "Please install OpenSSL >= 1.02 and recompile nginx.");
        #endif
      } else if (ngx_strcmp(value[0].data, "ssl_ca_dir") == 0) {
        server->ssl_ca_dir = value[1];
      } else if (ngx_strcmp(value[0].data, "ssl_ca_file") == 0) {
        server->ssl_ca_file = value[1];
      }
      else CONF_MSEC_VALUE(cf,value,server,connect_timeout)
      else CONF_MSEC_VALUE(cf,value,server,reconnect_timeout)
      else CONF_MSEC_VALUE(cf,value,server,bind_timeout)
      else CONF_MSEC_VALUE(cf,value,server,request_timeout)
      else if (ngx_strcmp(value[0].data, "include") == 0) {
          return ngx_conf_include(cf, dummy, conf);
      }
  
      rv = NGX_CONF_OK;
  
      return rv;
  }
  
  /**
   * Parse auth_ldap directive
   */
  static char *
  ngx_http_auth_ldap(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
  {
      ngx_str_t *value = cf->args->elts;
      ngx_http_auth_ldap_loc_conf_t *cnf = conf;
      u_char *p;
  
      if (ngx_strcmp(value[1].data, "off") == 0) {
          ngx_str_set(&cnf->realm, "");
          return NGX_CONF_OK;
      }
  
      cnf->realm.len = sizeof("Basic realm=\"") - 1 + value[1].len + 1;
      cnf->realm.data = ngx_pcalloc(cf->pool, cnf->realm.len);
      if (cnf->realm.data == NULL) {
          return NGX_CONF_ERROR;
      }
  
      p = ngx_cpymem(cnf->realm.data, "Basic realm=\"", sizeof("Basic realm=\"") - 1);
      p = ngx_cpymem(p, value[1].data, value[1].len);
      *p = '"';
  
      return NGX_CONF_OK;
  }
  
  /**
   * Parse auth_ldap_servers directive
   */
  static char *
  ngx_http_auth_ldap_servers(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
  {
      ngx_http_auth_ldap_loc_conf_t *cnf;
      ngx_http_auth_ldap_main_conf_t *mconf;
      ngx_http_auth_ldap_server_t *server, *s, **target;
      ngx_str_t *value;
      ngx_uint_t i, j;
  
      cnf = conf;
      mconf = ngx_http_conf_get_module_main_conf(cf, ngx_http_auth_ldap_module);
  
      for (i = 1; i < cf->args->nelts; i++) {
          value = &((ngx_str_t *) cf->args->elts)[i];
          server = NULL;
  
  	if (mconf->servers == NULL) {
              ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "http_auth_ldap: Using \"auth_ldap_servers\" when no \"ldap_server\" has been previously defined"
  						     " (make sure that \"auth_ldap_servers\" goes after \"ldap_server\"s in your configuration file)", value);
              return NGX_CONF_ERROR;
  	}
  
          for (j = 0; j < mconf->servers->nelts; j++) {
              s = &((ngx_http_auth_ldap_server_t *) mconf->servers->elts)[j];
              if (s->alias.len == value->len && ngx_memcmp(s->alias.data, value->data, s->alias.len) == 0) {
                  server = s;
                  break;
              }
          }
  
          if (server == NULL) {
              ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "http_auth_ldap: Server \"%V\" has not been defined", value);
              return NGX_CONF_ERROR;
          }
  
  
          if (cnf->servers == NGX_CONF_UNSET_PTR) {
              cnf->servers = ngx_array_create(cf->pool, 4, sizeof(ngx_http_auth_ldap_server_t *));
              if (cnf->servers == NULL) {
                  return NGX_CONF_ERROR;
              }
          }
  
          target = (ngx_http_auth_ldap_server_t **) ngx_array_push(cnf->servers);
          if (target == NULL) {
              return NGX_CONF_ERROR;
          }
  
          *target = server;
      }
  
      return NGX_CONF_OK;
  }
  
  /**
   * Parse URL conf parameter
   */
  static char *
  ngx_http_auth_ldap_parse_url(ngx_conf_t *cf, ngx_http_auth_ldap_server_t *server)
  {
      ngx_str_t *value;
      u_char *p;
  
      value = cf->args->elts;
  
      int rc = ldap_url_parse((const char *) value[1].data, &server->ludpp);
      if (rc != LDAP_SUCCESS) {
          switch (rc) {
              case LDAP_URL_ERR_MEM:
                  ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "http_auth_ldap: Cannot allocate memory space.");
                  break;
  
              case LDAP_URL_ERR_PARAM:
                  ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "http_auth_ldap: Invalid parameter.");
                  break;
  
              case LDAP_URL_ERR_BADSCHEME:
                  ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "http_auth_ldap: URL doesnt begin with \"ldap[s]://\".");
                  break;
  
              case LDAP_URL_ERR_BADENCLOSURE:
                  ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "http_auth_ldap: URL is missing trailing \">\".");
                  break;
  
              case LDAP_URL_ERR_BADURL:
                  ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "http_auth_ldap: Invalid URL.");
                  break;
  
              case LDAP_URL_ERR_BADHOST:
                  ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "http_auth_ldap: Host port is invalid.");
                  break;
  
              case LDAP_URL_ERR_BADATTRS:
                  ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "http_auth_ldap: Invalid or missing attributes.");
                  break;
  
              case LDAP_URL_ERR_BADSCOPE:
                  ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "http_auth_ldap: Invalid or missing scope string.");
                  break;
  
              case LDAP_URL_ERR_BADFILTER:
                  ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "http_auth_ldap: Invalid or missing filter.");
                  break;
  
              case LDAP_URL_ERR_BADEXTS:
                  ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "http_auth_ldap: Invalid or missing extensions.");
                  break;
          }
          return NGX_CONF_ERROR;
      }
  
      if (server->ludpp->lud_attrs == NULL) {
          ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "http_auth_ldap: No user attribute specified in auth_ldap_url.");
          return NGX_CONF_ERROR;
      }
  
      server->url.data = ngx_palloc(cf->pool, ngx_strlen(server->ludpp->lud_scheme) + sizeof("://") - 1 +
          ngx_strlen(server->ludpp->lud_host) + sizeof(":65535"));
      p = ngx_sprintf(server->url.data, "%s://%s:%d%Z", server->ludpp->lud_scheme, server->ludpp->lud_host,
          server->ludpp->lud_port);
      server->url.len = p - server->url.data - 1;
  
      ngx_memzero(&server->parsed_url, sizeof(ngx_url_t));
      server->parsed_url.url.data = (u_char *) server->ludpp->lud_host;
      server->parsed_url.url.len = ngx_strlen(server->ludpp->lud_host);
      server->parsed_url.default_port = server->ludpp->lud_port;
      if (ngx_parse_url(cf->pool, &server->parsed_url) != NGX_OK) {
          if (server->parsed_url.err) {
              ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "http_auth_ldap: %s in LDAP hostname \"%V\"",
                  server->parsed_url.err, &server->parsed_url.url);
          }
          return NGX_CONF_ERROR;
      }
  
      if (ngx_strcmp(server->ludpp->lud_scheme, "ldap") == 0) {
          return NGX_CONF_OK;
  #if (NGX_OPENSSL)
      } else if (ngx_strcmp(server->ludpp->lud_scheme, "ldaps") == 0) {
          ngx_http_auth_ldap_main_conf_t *halmcf =
              ngx_http_conf_get_module_main_conf(cf, ngx_http_auth_ldap_module);
          ngx_uint_t protos = NGX_SSL_SSLv2 | NGX_SSL_SSLv3 |
              NGX_SSL_TLSv1 | NGX_SSL_TLSv1_1 | NGX_SSL_TLSv1_2;
          if (halmcf->ssl.ctx == NULL && ngx_ssl_create(&halmcf->ssl, protos, halmcf) != NGX_OK) {
              return NGX_CONF_ERROR;
          }
          return NGX_CONF_OK;
  #endif
      } else {
          ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "http_auth_ldap: Protocol \"%s://\" is not supported.",
              server->ludpp->lud_scheme);
          return NGX_CONF_ERROR;
      }
  }
  
  /**
   * Parse "require" conf parameter
   */
  static char *
  ngx_http_auth_ldap_parse_require(ngx_conf_t *cf, ngx_http_auth_ldap_server_t *server)
  {
      ngx_str_t *value;
      ngx_http_complex_value_t* target = NULL;
      ngx_http_compile_complex_value_t ccv;
  
      value = cf->args->elts;
      ngx_conf_log_error(NGX_LOG_NOTICE, cf, 0, "http_auth_ldap: parse_require");
  
      if (ngx_strcmp(value[1].data, "valid_user") == 0) {
          server->require_valid_user = 1;
          if (cf->args->nelts < 3) {
              return NGX_CONF_OK;
          }
          if (server->require_valid_user_dn.value.data != NULL) {
              return "is duplicate";
          }
          target = &server->require_valid_user_dn;
      } else if (ngx_strcmp(value[1].data, "user") == 0) {
          if (server->require_user == NULL) {
              server->require_user = ngx_array_create(cf->pool, 4, sizeof(ngx_http_complex_value_t));
              if (server->require_user == NULL) {
                  return NGX_CONF_ERROR;
              }
          }
          target = ngx_array_push(server->require_user);
      } else if (ngx_strcmp(value[1].data, "group") == 0) {
          ngx_conf_log_error(NGX_LOG_NOTICE, cf, 0, "http_auth_ldap: Setting group");
          if (server->require_group == NULL) {
              server->require_group = ngx_array_create(cf->pool, 4, sizeof(ngx_http_complex_value_t));
              if (server->require_group == NULL) {
                  return NGX_CONF_ERROR;
              }
          }
          target = ngx_array_push(server->require_group);
      }
  
      if (target == NULL) {
         return NGX_CONF_ERROR;
      }
  
      ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
      ccv.cf = cf;
      ccv.value = &value[2];
      ccv.complex_value = target;
      if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
          return NGX_CONF_ERROR;
      }
  
      return NGX_CONF_OK;
  }
  
  /**
   * Parse "satisfy" conf parameter
   */
  static char *
  ngx_http_auth_ldap_parse_satisfy(ngx_conf_t *cf, ngx_http_auth_ldap_server_t *server)
  {
      ngx_str_t *value;
      value = cf->args->elts;
  
      if (ngx_strcmp(value[1].data, "all") == 0) {
          ngx_conf_log_error(NGX_LOG_NOTICE, cf, 0, "http_auth_ldap: Setting satisfy all");
          server->satisfy_all = 1;
          return NGX_CONF_OK;
      }
  
      if (ngx_strcmp(value[1].data, "any") == 0) {
          server->satisfy_all = 0;
          return NGX_CONF_OK;
      }
  
      ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "http_auth_ldap: Incorrect value for auth_ldap_satisfy");
      return NGX_CONF_ERROR;
  }
  
  /**
   * Parse "referral" conf parameter
   */
  static char *
  ngx_http_auth_ldap_parse_referral(ngx_conf_t *cf, ngx_http_auth_ldap_server_t *server)
  {
      ngx_str_t *value;
      value = cf->args->elts;
  
      if (ngx_strcmp(value[1].data, "on") == 0) {
          server->referral = 1;
          return NGX_CONF_OK;
      }
  
      if (ngx_strcmp(value[1].data, "off") == 0) {
          server->referral = 0;
          return NGX_CONF_OK;
      }
  
      ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "http_auth_ldap: Incorrect value for referral");
      return NGX_CONF_ERROR;
  }
  
  /**
   * Create main config which will store ldap_servers array
   */
  static void *
  ngx_http_auth_ldap_create_main_conf(ngx_conf_t *cf)
  {
      ngx_http_auth_ldap_main_conf_t *conf;
  
      conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_auth_ldap_main_conf_t));
      if (conf == NULL) {
          return NULL;
      }
  
      conf->cache_enabled = NGX_CONF_UNSET;
      conf->cache_expiration_time = NGX_CONF_UNSET_MSEC;
      conf->cache_size = NGX_CONF_UNSET_SIZE;
      conf->servers_size = NGX_CONF_UNSET;
  
      return conf;
  }
  
  static char *
  ngx_http_auth_ldap_init_main_conf(ngx_conf_t *cf, void *parent)
  {
      ngx_http_auth_ldap_main_conf_t *conf = parent;
  
      if (conf->cache_enabled == NGX_CONF_UNSET) {
          conf->cache_enabled = 0;
      }
      if (conf->cache_enabled == 0) {
          return NGX_CONF_OK;
      }
  
      if (conf->cache_size == NGX_CONF_UNSET_SIZE) {
          conf->cache_size = 100;
      }
      if (conf->cache_size < 100) {
          ngx_conf_log_error(NGX_LOG_ERR, cf, 0, "http_auth_ldap: auth_ldap_cache_size cannot be smaller than 100 entries.");
          return NGX_CONF_ERROR;
      }
  
      if (conf->cache_expiration_time == NGX_CONF_UNSET_MSEC) {
          conf->cache_expiration_time = 10000;
      }
      if (conf->cache_expiration_time < 1000) {
          ngx_conf_log_error(NGX_LOG_ERR, cf, 0, "http_auth_ldap: auth_ldap_cache_expiration_time cannot be smaller than 1000 ms.");
          return NGX_CONF_ERROR;
      }
  
      return NGX_CONF_OK;
  }
  
  /**
   * Create location conf
   */
  static void *
  ngx_http_auth_ldap_create_loc_conf(ngx_conf_t *cf)
  {
      ngx_http_auth_ldap_loc_conf_t *conf;
      conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_auth_ldap_loc_conf_t));
      if (conf == NULL) {
          return NULL;
      }
      conf->servers = NGX_CONF_UNSET_PTR;
  
      return conf;
  }
  
  /**
   * Merge location conf
   */
  static char *
  ngx_http_auth_ldap_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
  {
      ngx_http_auth_ldap_loc_conf_t *prev = parent;
      ngx_http_auth_ldap_loc_conf_t *conf = child;
  
      if (conf->realm.data == NULL) {
          conf->realm = prev->realm;
      }
      ngx_conf_merge_ptr_value(conf->servers, prev->servers, NULL);
  
      return NGX_CONF_OK;
  }
  
  static ngx_int_t
  ngx_http_auth_ldap_init_worker(ngx_cycle_t *cycle)
  {
      ngx_int_t rc;
  
      if (ngx_process != NGX_PROCESS_SINGLE && ngx_process != NGX_PROCESS_WORKER) {
          return NGX_OK;
      }
  
      rc = ngx_http_auth_ldap_init_cache(cycle);
      if (rc != NGX_OK) {
          return rc;
      }
  
      rc = ngx_http_auth_ldap_init_connections(cycle);
      if (rc != NGX_OK) {
          return rc;
      }
  
      return NGX_OK;
  }
  
  /**
   * Init module and add ldap auth handler to NGX_HTTP_ACCESS_PHASE
   */
  static ngx_int_t
  ngx_http_auth_ldap_init(ngx_conf_t *cf)
  {
      ngx_http_handler_pt *h;
      ngx_http_core_main_conf_t *cmcf;
  
      cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
  
      h = ngx_array_push(&cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers);
      if (h == NULL) {
          return NGX_ERROR;
      }
  
      *h = ngx_http_auth_ldap_handler;
      return NGX_OK;
  }
  
  
  /*** Authentication cache ***/
  
  static ngx_int_t
  ngx_http_auth_ldap_init_cache(ngx_cycle_t *cycle)
  {
      ngx_http_auth_ldap_main_conf_t *conf;
      ngx_uint_t want, count, i;
      ngx_http_auth_ldap_cache_t *cache;
      static const uint16_t primes[] = {
          13, 53, 101, 151, 199, 263, 317, 383, 443, 503,
          577, 641, 701, 769, 839, 911, 983, 1049, 1109
      };
  
      conf = (ngx_http_auth_ldap_main_conf_t *) ngx_http_cycle_get_module_main_conf(cycle, ngx_http_auth_ldap_module);
      if (conf == NULL || !conf->cache_enabled) {
          return NGX_OK;
      }
  
      want = (conf->cache_size + 7) / 8;
      count = 0;
      for (i = 0; i < sizeof(primes)/sizeof(primes[0]); i++) {
          count = primes[i];
          if (count >= want) {
              break;
          }
      }
  
      cache = &ngx_http_auth_ldap_cache;
      cache->expiration_time = conf->cache_expiration_time;
      cache->num_buckets = count;
      cache->elts_per_bucket = 8;
  
      ngx_log_debug1(NGX_LOG_DEBUG_HTTP, cycle->log, 0, "http_auth_ldap: Allocating %ud bytes of LDAP cache.",
          cache->num_buckets * cache->elts_per_bucket * sizeof(ngx_http_auth_ldap_cache_elt_t));
  
      cache->buckets = (ngx_http_auth_ldap_cache_elt_t *) ngx_calloc(count * 8 * sizeof(ngx_http_auth_ldap_cache_elt_t), cycle->log);
      if (cache->buckets == NULL) {
          ngx_log_error(NGX_LOG_ERR, cycle->log, 0, "http_auth_ldap: Unable to allocate memory for LDAP cache.");
          return NGX_ERROR;
      }
  
      return NGX_OK;
  }
  
  static ngx_int_t
  ngx_http_auth_ldap_check_cache(ngx_http_request_t *r, ngx_http_auth_ldap_ctx_t *ctx,
      ngx_http_auth_ldap_cache_t *cache, ngx_http_auth_ldap_server_t *server)
  {
      ngx_http_auth_ldap_cache_elt_t *elt;
      ngx_md5_t md5ctx;
      ngx_msec_t time_limit;
      ngx_uint_t i;
  
      ctx->cache_small_hash = ngx_murmur_hash2(r->headers_in.user.data, r->headers_in.user.len) ^ (uint32_t) (ngx_uint_t) server;
  
      ngx_md5_init(&md5ctx);
      ngx_md5_update(&md5ctx, r->headers_in.user.data, r->headers_in.user.len);
      ngx_md5_update(&md5ctx, server, offsetof(ngx_http_auth_ldap_server_t, free_connections));
      ngx_md5_update(&md5ctx, r->headers_in.passwd.data, r->headers_in.passwd.len);
      ngx_md5_final(ctx->cache_big_hash, &md5ctx);
  
      ctx->cache_bucket = &cache->buckets[ctx->cache_small_hash % cache->num_buckets];
  
      elt = ctx->cache_bucket;
      time_limit = ngx_current_msec - cache->expiration_time;
      for (i = 0; i < cache->elts_per_bucket; i++, elt++) {
          if (elt->small_hash == ctx->cache_small_hash &&
                  elt->time > time_limit &&
                  memcmp(elt->big_hash, ctx->cache_big_hash, 16) == 0) {
              return elt->outcome;
          }
      }
  
      return -1;
  }
  
  static void
  ngx_http_auth_ldap_update_cache(ngx_http_auth_ldap_ctx_t *ctx,
          ngx_http_auth_ldap_cache_t *cache, ngx_flag_t outcome)
  {
      ngx_http_auth_ldap_cache_elt_t *elt, *oldest_elt;
      ngx_uint_t i;
  
      elt = ctx->cache_bucket;
      oldest_elt = elt;
      for (i = 1; i < cache->elts_per_bucket; i++, elt++) {
          if (elt->time < oldest_elt->time) {
              oldest_elt = elt;
          }
      }
  
      oldest_elt->time = ngx_current_msec;
      oldest_elt->outcome = outcome;
      oldest_elt->small_hash = ctx->cache_small_hash;
      ngx_memcpy(oldest_elt->big_hash, ctx->cache_big_hash, 16);
  }
  
  
  /*** OpenLDAP SockBuf implementation over nginx socket functions ***/
  
  static int
  ngx_http_auth_ldap_sb_setup(Sockbuf_IO_Desc *sbiod, void *arg)
  {
      sbiod->sbiod_pvt = arg;
      return 0;
  }
  
  static int
  ngx_http_auth_ldap_sb_remove(Sockbuf_IO_Desc *sbiod)
  {
      ngx_http_auth_ldap_connection_t *c = (ngx_http_auth_ldap_connection_t *)sbiod->sbiod_pvt;
  
      ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "ngx_http_auth_ldap_sb_remove()");
      (void)c; /* 'c' would be left unused on debug builds */
  
      sbiod->sbiod_pvt = NULL;
      return 0;
  }
  
  static int
  ngx_http_auth_ldap_sb_close(Sockbuf_IO_Desc *sbiod)
  {
      ngx_http_auth_ldap_connection_t *c = (ngx_http_auth_ldap_connection_t *)sbiod->sbiod_pvt;
  
      ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "ngx_http_auth_ldap_sb_close()");
  
      if (!c->conn.connection->read->error && !c->conn.connection->read->eof) {
          if (ngx_shutdown_socket(c->conn.connection->fd, SHUT_RDWR) == -1) {
              ngx_connection_error(c->conn.connection, ngx_socket_errno, ngx_shutdown_socket_n " failed");
              ngx_http_auth_ldap_close_connection(c);
              return -1;
          }
      }
  
      return 0;
  }
  
  static int
  ngx_http_auth_ldap_sb_ctrl(Sockbuf_IO_Desc *sbiod, int opt, void *arg)
  {
      ngx_http_auth_ldap_connection_t *c = (ngx_http_auth_ldap_connection_t *)sbiod->sbiod_pvt;
  
      ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "ngx_http_auth_ldap_sb_ctrl(opt=%d)", opt);
  
      switch (opt) {
          case LBER_SB_OPT_DATA_READY:
              if (c->conn.connection->read->ready) {
                  return 1;
              }
              return 0;
      }
  
      return 0;
  }
  
  static ber_slen_t
  ngx_http_auth_ldap_sb_read(Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len)
  {
      ngx_http_auth_ldap_connection_t *c = (ngx_http_auth_ldap_connection_t *)sbiod->sbiod_pvt;
      ber_slen_t ret;
  
      ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "ngx_http_auth_ldap_sb_read(len=%d)", len);
  
      ret = c->conn.connection->recv(c->conn.connection, buf, len);
      if (ret < 0) {
          errno = (ret == NGX_AGAIN) ? NGX_EAGAIN : NGX_ECONNRESET;
          return -1;
      }
  
      return ret;
  }
  
  static ber_slen_t
  ngx_http_auth_ldap_sb_write(Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len)
  {
      ngx_http_auth_ldap_connection_t *c = (ngx_http_auth_ldap_connection_t *)sbiod->sbiod_pvt;
      ber_slen_t ret;
  
      ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "ngx_http_auth_ldap_sb_write(len=%d)", len);
  
      ret = c->conn.connection->send(c->conn.connection, buf, len);
      if (ret < 0) {
          errno = (ret == NGX_AGAIN) ? NGX_EAGAIN : NGX_ECONNRESET;
          return 0;
      }
  
      return ret;
  }
  
  static Sockbuf_IO ngx_http_auth_ldap_sbio =
  {
      ngx_http_auth_ldap_sb_setup,
      ngx_http_auth_ldap_sb_remove,
      ngx_http_auth_ldap_sb_ctrl,
      ngx_http_auth_ldap_sb_read,
      ngx_http_auth_ldap_sb_write,
      ngx_http_auth_ldap_sb_close
  };
  
  
  /*** Asynchronous LDAP connection handling ***/
  
  static void
  ngx_http_auth_ldap_close_connection(ngx_http_auth_ldap_connection_t *c)
  {
      ngx_queue_t *q;
  
      if (c->ld) {
          ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http_auth_ldap: Unbinding from the server \"%V\")",
              &c->server->url);
          ldap_unbind_ext(c->ld, NULL, NULL);
          /* Unbind is always synchronous, even though the function name does not end with an '_s'. */
          c->ld = NULL;
      }
  
      if (c->conn.connection) {
          ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http_auth_ldap: Closing connection (fd=%d)",
              c->conn.connection->fd);
  
  #if (NGX_OPENSSL)
          if (c->conn.connection->ssl) {
              c->conn.connection->ssl->no_wait_shutdown = 1;
              (void) ngx_ssl_shutdown(c->conn.connection);
          }
  #endif
  
          ngx_close_connection(c->conn.connection);
          c->conn.connection = NULL;
      }
  
      q = ngx_queue_head(&c->server->free_connections);
      while (q != ngx_queue_sentinel(&c->server->free_connections)) {
          if (q == &c->queue) {
              ngx_queue_remove(q);
              break;
          }
          q = ngx_queue_next(q);
      }
  
      c->rctx = NULL;
      if (c->state != STATE_DISCONNECTED) {
          c->state = STATE_DISCONNECTED;
          ngx_add_timer(&c->reconnect_event, c->server->reconnect_timeout);
          ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http_auth_ldap: Connection scheduled for reconnection in %d ms", c->server->reconnect_timeout);
      }
  }
  
  static void
  ngx_http_auth_ldap_wake_request(ngx_http_request_t *r)
  {
      ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http_auth_ldap: Waking authentication request \"%V\"",
          &r->request_line);
      ngx_http_core_run_phases(r);
  }
  
  static int
  ngx_http_auth_ldap_get_connection(ngx_http_auth_ldap_ctx_t *ctx)
  {
      ngx_http_auth_ldap_server_t *server;
      ngx_queue_t *q;
      ngx_http_auth_ldap_connection_t *c;
  
      /*
       * If we already have a connection, just say we got them one.
       */
      if (ctx->c != NULL)
          return 1;
  
      server = ctx->server;
  
      ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->r->connection->log, 0, "http_auth_ldap: Wants a free connection to \"%V\"",
          &server->alias);
  
      if (!ngx_queue_empty(&server->free_connections)) {
          q = ngx_queue_last(&server->free_connections);
          ngx_queue_remove(q);
          c = ngx_queue_data(q, ngx_http_auth_ldap_connection_t, queue);
          c->rctx = ctx;
          ctx->c = c;
          ctx->replied = 0;
          return 1;
      }
  
      q = ngx_queue_next(&server->waiting_requests);
      while (q != ngx_queue_sentinel(&server->waiting_requests)) {
          if (q == &ctx->queue) {
              ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ctx->r->connection->log, 0, "http_auth_ldap: Tried to insert a same request");
              return 0;
          }
          q = ngx_queue_next(q);
      }
      ngx_queue_insert_head(&server->waiting_requests, &ctx->queue);
      ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ctx->r->connection->log, 0, "http_auth_ldap: No connection available at the moment, waiting...");
      return 0;
  }
  
  static void
  ngx_http_auth_ldap_return_connection(ngx_http_auth_ldap_connection_t *c)
  {
      ngx_queue_t *q;
  
      ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http_auth_ldap: Marking the connection to \"%V\" as free",
          &c->server->alias);
  
      if (c->rctx != NULL) {
          c->rctx->c = NULL;
          c->rctx = NULL;
          c->msgid = -1;
          c->state = STATE_READY;
      }
  
      ngx_queue_insert_head(&c->server->free_connections, &c->queue);
      if (!ngx_queue_empty(&c->server->waiting_requests)) {
          q = ngx_queue_last(&c->server->waiting_requests);
          ngx_queue_remove(q);
          ngx_http_auth_ldap_wake_request((ngx_queue_data(q, ngx_http_auth_ldap_ctx_t, queue))->r);
      }
  }
  
  static void
  ngx_http_auth_ldap_reply_connection(ngx_http_auth_ldap_connection_t *c, int error_code, char* error_msg)
  {
      ngx_http_auth_ldap_ctx_t *ctx = c->rctx;
  
      ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http_auth_ldap: LDAP request to \"%V\" has finished",
          &c->server->alias);
  
      ctx->replied = 1;
      ctx->error_code = error_code;
      if (error_msg) {
          ctx->error_msg.len = ngx_strlen(error_msg);
          ctx->error_msg.data = ngx_palloc(ctx->r->pool, ctx->error_msg.len);
          ngx_memcpy(ctx->error_msg.data, error_msg, ctx->error_msg.len);
      } else {
          ctx->error_msg.len = 0;
          ctx->error_msg.data = NULL;
      }
  
      ngx_http_auth_ldap_wake_request(ctx->r);
  }
  
  static void
  ngx_http_auth_ldap_dummy_write_handler(ngx_event_t *wev)
  {
      ngx_log_debug0(NGX_LOG_DEBUG_HTTP, wev->log, 0, "http_auth_ldap: Dummy write handler");
  
      if (ngx_handle_write_event(wev, 0) != NGX_OK) {
          ngx_http_auth_ldap_close_connection(((ngx_connection_t *) wev->data)->data);
      }
  }
  
  
  #if (NGX_OPENSSL)
  /* Make sure the event handlers are activated. */
  static ngx_int_t
  ngx_http_auth_ldap_restore_handlers(ngx_connection_t *conn)
  {
      ngx_int_t rc;
  
      ngx_log_debug2(NGX_LOG_DEBUG_HTTP, conn->log, 0, "http_auth_ldap: Restoring event handlers. read=%d write=%d", conn->read->active, conn->write->active);
  
      if (!conn->read->active) {
          rc = ngx_add_event(conn->read, NGX_READ_EVENT, 0);
          if (rc != NGX_OK) {
              return rc;
          }
      }
  
      if (!conn->write->active &&
          (conn->write->handler != ngx_http_auth_ldap_dummy_write_handler)) {
          rc = ngx_add_event(conn->write, NGX_WRITE_EVENT, 0);
          if (rc != NGX_OK) {
              return rc;
          }
      }
  
      return NGX_OK;
  }
  #endif
  
  static void
  ngx_http_auth_ldap_connection_established(ngx_http_auth_ldap_connection_t *c)
  {
      ngx_connection_t *conn;
      Sockbuf *sb;
      ngx_int_t rc;
      struct berval cred;
  
      conn = c->conn.connection;
      ngx_del_timer(conn->read);
      conn->write->handler = ngx_http_auth_ldap_dummy_write_handler;
  
  
      /* Initialize OpenLDAP on the connection */
  
      ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http_auth_ldap: Initializing connection using URL \"%V\"", &c->server->url);
  
      rc = ldap_init_fd(c->conn.connection->fd, LDAP_PROTO_EXT, (const char *) c->server->url.data, &c->ld);
      if (rc != LDAP_SUCCESS) {
          ngx_log_error(NGX_LOG_ERR, c->log, errno, "http_auth_ldap: ldap_init_fd() failed (%d: %s)", rc, ldap_err2string(rc));
          ngx_http_auth_ldap_close_connection(c);
          return;
      }
  
      if (c->server->referral == 0) {
          rc = ldap_set_option(c->ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF);
          if (rc != LDAP_OPT_SUCCESS) {
              ngx_log_error(NGX_LOG_ERR, c->log, 0, "http_auth_ldap: ldap_set_option() failed (%d: %s)", rc, ldap_err2string(rc));
              ngx_http_auth_ldap_close_connection(c);
              return;
          }
      }
  
      rc = ldap_get_option(c->ld, LDAP_OPT_SOCKBUF, (void *) &sb);
      if (rc != LDAP_OPT_SUCCESS) {
          ngx_log_error(NGX_LOG_ERR, c->log, 0, "http_auth_ldap: ldap_get_option() failed (%d: %s)", rc, ldap_err2string(rc));
          ngx_http_auth_ldap_close_connection(c);
          return;
      }
  
      ber_sockbuf_add_io(sb, &ngx_http_auth_ldap_sbio, LBER_SBIOD_LEVEL_PROVIDER, (void *) c);
  
      ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http_auth_ldap: Connection initialized");
  
  
      /* Perform initial bind to the server */
  
      cred.bv_val = (char *) c->server->bind_dn_passwd.data;
      cred.bv_len = c->server->bind_dn_passwd.len;
      rc = ldap_sasl_bind(c->ld, (const char *) c->server->bind_dn.data, LDAP_SASL_SIMPLE, &cred, NULL, NULL, &c->msgid);
      if (rc != LDAP_SUCCESS) {
          ngx_log_error(NGX_LOG_ERR, c->log, 0, "http_auth_ldap: ldap_sasl_bind() failed (%d: %s)",
              rc, ldap_err2string(rc));
          ngx_http_auth_ldap_close_connection(c);
          return;
      }
      ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http_auth_ldap: ldap_sasl_bind() -> msgid=%d", c->msgid);
  
      c->state = STATE_INITIAL_BINDING;
      ngx_add_timer(c->conn.connection->read, c->server->bind_timeout);
      ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http_auth_ldap: bind_timeout=%d", c->server->bind_timeout);
  }
  
  #if (NGX_OPENSSL)
  static void
  ngx_http_auth_ldap_ssl_handshake_handler(ngx_connection_t *conn, ngx_flag_t validate)
  {
      ngx_http_auth_ldap_connection_t *c;
      c = conn->data;
  
      if (conn->ssl->handshaked) {
          #if OPENSSL_VERSION_NUMBER >= 0x10002000
          if (validate) { // verify remote certificate if requested
            X509 *cert = SSL_get_peer_certificate(conn->ssl->connection);
            long chain_verified = SSL_get_verify_result(conn->ssl->connection);
  
            int addr_verified;
            char *hostname = c->server->ludpp->lud_host;
            addr_verified = X509_check_host(cert, hostname, 0, 0, 0);
  
            if (!addr_verified) { // domain not in cert? try IP
              size_t len; // get IP length
              if (conn->sockaddr->sa_family == 4) len = 4;
              else if (conn->sockaddr->sa_family == 6) len = 16;
              else { // very unlikely indeed
                ngx_http_auth_ldap_close_connection(c);
                return;
              }
              addr_verified = X509_check_ip(cert, (const unsigned char*)conn->sockaddr->sa_data, len, 0);
            }
  
            // Find anything fishy?
            if ( !(cert && addr_verified && chain_verified == X509_V_OK) ) {
              if (!addr_verified) {
                ngx_log_error(NGX_LOG_ERR, c->log, 0,
                  "http_auth_ldap: Remote side presented invalid SSL certificate: "
                  "does not match address (neither server's domain nor IP in certificate's CN or SAN)");
                  fprintf(stderr, "DEBUG: SSL cert domain mismatch\n"); fflush(stderr);
              } else {
                ngx_log_error(NGX_LOG_ERR, c->log, 0,
                  "http_auth_ldap: Remote side presented invalid SSL certificate: error %l, %s",
                  chain_verified, X509_verify_cert_error_string(chain_verified));
              }
              ngx_http_auth_ldap_close_connection(c);
              return;
            }
          }
          #endif
  
          // handshaked validation successful -- or not required in the first place
          conn->read->handler = &ngx_http_auth_ldap_read_handler;
          ngx_http_auth_ldap_restore_handlers(conn);
          ngx_http_auth_ldap_connection_established(c);
          return;
      }
      else { // handshake failed
        ngx_log_error(NGX_LOG_ERR, c->log, 0, "http_auth_ldap: SSL handshake failed");
        ngx_http_auth_ldap_close_connection(c);
      }
  }
  
  static void
  ngx_http_auth_ldap_ssl_handshake_validating_handler(ngx_connection_t *conn)
  { ngx_http_auth_ldap_ssl_handshake_handler(conn, 1); }
  
  static void
  ngx_http_auth_ldap_ssl_handshake_non_validating_handler(ngx_connection_t *conn)
  { ngx_http_auth_ldap_ssl_handshake_handler(conn, 0); }
  
  typedef void (*ngx_http_auth_ldap_ssl_callback)(ngx_connection_t *conn);
  
  static void
  ngx_http_auth_ldap_ssl_handshake(ngx_http_auth_ldap_connection_t *c)
  {
      ngx_int_t rc;
  
      c->conn.connection->pool = c->pool;
      rc = ngx_ssl_create_connection(c->ssl, c->conn.connection, NGX_SSL_BUFFER | NGX_SSL_CLIENT);
      if (rc != NGX_OK) {
          ngx_log_error(NGX_LOG_ERR, c->log, 0, "http_auth_ldap: SSL initialization failed");
          ngx_http_auth_ldap_close_connection(c);
          return;
      }
  
      c->log->action = "SSL handshaking to LDAP server";
      ngx_connection_t *transport = c->conn.connection;
  
      ngx_http_auth_ldap_ssl_callback callback;
      if (c->server->ssl_check_cert) {
        // load CA certificates: custom ones if specified, default ones instead
        if (c->server->ssl_ca_file.data || c->server->ssl_ca_dir.data) {
  #if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
          int setcode = SSL_CTX_load_verify_locations(transport->ssl->session_ctx,
            (char*)(c->server->ssl_ca_file.data), (char*)(c->server->ssl_ca_dir.data));
  #else
          int setcode = SSL_CTX_load_verify_locations(transport->ssl->connection->ctx,
            (char*)(c->server->ssl_ca_file.data), (char*)(c->server->ssl_ca_dir.data));
  #endif
          if (setcode != 1) {
            unsigned long error_code = ERR_get_error();
            char *error_msg = ERR_error_string(error_code, NULL);
            ngx_log_error(NGX_LOG_ERR, c->log, 0,
              "http_auth_ldap: SSL initialization failed. Could not set custom CA certificate location. "
              "Error: %lu, %s", error_code, error_msg);
          }
        }
  #if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
        int setcode = SSL_CTX_set_default_verify_paths(transport->ssl->session_ctx);
  #else
        int setcode = SSL_CTX_set_default_verify_paths(transport->ssl->connection->ctx);
  #endif
        if (setcode != 1) {
          unsigned long error_code = ERR_get_error();
          char *error_msg = ERR_error_string(error_code, NULL);
          ngx_log_error(NGX_LOG_ERR, c->log, 0,
            "http_auth_ldap: SSL initialization failed. Could not use default CA certificate location. "
            "Error: %lu, %s", error_code, error_msg);
        }
  
        // use validating version of next function
        callback = &ngx_http_auth_ldap_ssl_handshake_validating_handler;
      } else {
        // use non-validating version of next function
        callback = &ngx_http_auth_ldap_ssl_handshake_non_validating_handler;
      }
  
      rc = ngx_ssl_handshake(transport);
      if (rc == NGX_AGAIN) {
          transport->ssl->handler = callback;
          return;
      }
  
      (*callback)(transport);
      return;
  }
  #endif
  
  static void
  ngx_http_auth_ldap_connect_handler(ngx_event_t *wev)
  {
      ngx_connection_t *conn;
      ngx_http_auth_ldap_connection_t *c;
      int keepalive;
  
      ngx_log_debug0(NGX_LOG_DEBUG_HTTP, wev->log, 0, "http_auth_ldap: Connect handler");
  
      conn = wev->data;
      c = conn->data;
  
      if (ngx_handle_write_event(wev, 0) != NGX_OK) {
          ngx_http_auth_ldap_close_connection(c);
          return;
      }
  
      keepalive = 1;
      if (setsockopt(conn->fd, SOL_SOCKET, SO_KEEPALIVE, (const void *) &keepalive, sizeof(int)) == -1)
      {
          ngx_log_error(NGX_LOG_ALERT, c->log, ngx_socket_errno, "http_auth_ldap: setsockopt(SO_KEEPALIVE) failed");
      }
  
  #if (NGX_OPENSSL)
      if (ngx_strcmp(c->server->ludpp->lud_scheme, "ldaps") == 0) {
          ngx_http_auth_ldap_ssl_handshake(c);
          return;
      }
  #endif
  
      ngx_http_auth_ldap_connection_established(c);
  }
  
  static void
  ngx_http_auth_ldap_read_handler(ngx_event_t *rev)
  {
      ngx_connection_t *conn;
      ngx_http_auth_ldap_connection_t *c;
      ngx_int_t rc;
      struct timeval timeout = {0, 0};
      LDAPMessage *result;
      int error_code;
      char *error_msg;
      char *dn;
  
      ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "http_auth_ldap: Read handler");
  
      conn = rev->data;
      c = conn->data;
  
      if (c->ld == NULL) {
          ngx_log_error(NGX_LOG_ERR, c->log, 0, "http_auth_ldap: Could not connect");
          ngx_http_auth_ldap_close_connection(c);
          return;
      }
  
      if (rev->timedout) {
          ngx_log_error(NGX_LOG_ERR, c->log, NGX_ETIMEDOUT, "http_auth_ldap: Request timed out (state=%d)", c->state);
          conn->timedout = 1;
          ngx_http_auth_ldap_close_connection(c);
          return;
      }
  
      c->log->action = "reading response from LDAP";
  
      for (;;) {
          rc = ldap_result(c->ld, LDAP_RES_ANY, 0, &timeout, &result);
          if (rc < 0) {
              ngx_log_error(NGX_LOG_ERR, c->log, 0, "http_auth_ldap: ldap_result() failed (%d: %s)",
                  rc, ldap_err2string(rc));
              ngx_http_auth_ldap_close_connection(c);
  
              // if LDAP_SERVER_DOWN (usually timeouts or server disconnects)
              if (rc == LDAP_SERVER_DOWN && \
                  c->server->max_down_retries_count < c->server->max_down_retries) {
                  /**
                      update counter (this is always reset in
                      ngx_http_auth_ldap_connect() for a successful ldap
                      connection
                  **/
                  c->server->max_down_retries_count++;
                  ngx_log_error(NGX_LOG_ERR, c->log, 0, "http_auth_ldap: LDAP_SERVER_DOWN: retry count: %d",
                      c->server->max_down_retries_count);
                  c->state = STATE_DISCONNECTED;
                  // immediate reconnect synchronously, this schedules another
                  // timer call to this read handler again
                  ngx_http_auth_ldap_reconnect_handler(rev);
                  return;
              }
  
              return;
          }
          if (rc == 0) {
              ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http_auth_ldap: ldap_result() -> rc=0");
              break;
          }
          ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, "http_auth_ldap: ldap_result() -> rc=%d, msgid=%d, msgtype=%d",
              rc, ldap_msgid(result), ldap_msgtype(result));
  
          if (ldap_msgid(result) != c->msgid) {
              ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http_auth_ldap: Message with unknown ID received, ignoring.");
              ldap_msgfree(result);
              continue;
          }
  
          rc = ldap_parse_result(c->ld, result, &error_code, NULL, &error_msg, NULL, NULL, 0);
          if (rc == LDAP_NO_RESULTS_RETURNED) {
              error_code = LDAP_NO_RESULTS_RETURNED;
              error_msg = NULL;
          } else if (rc != LDAP_SUCCESS) {
              ngx_log_error(NGX_LOG_ERR, c->log, 0, "http_auth_ldap: ldap_parse_result() failed (%d: %s)",
                  rc, ldap_err2string(rc));
              ldap_msgfree(result);
              ngx_http_auth_ldap_close_connection(c);
              return;
          }
  
          switch (c->state) {
              case STATE_INITIAL_BINDING:
                  if (ldap_msgtype(result) != LDAP_RES_BIND) {
                      break;
                  }
                  ngx_del_timer(conn->read);
                  if (error_code == LDAP_SUCCESS) {
                      ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http_auth_ldap: Initial bind successful");
                      c->state = STATE_READY;
                      ngx_http_auth_ldap_return_connection(c);
                  } else {
                      ngx_log_error(NGX_LOG_ERR, c->log, 0, "http_auth_ldap: Initial bind failed (%d: %s [%s])",
                          error_code, ldap_err2string(error_code), error_msg ? error_msg : "-");
                      ldap_memfree(error_msg);
                      ldap_msgfree(result);
                      ngx_http_auth_ldap_close_connection(c);
                      return;
                  }
                  break;
  
              case STATE_BINDING:
                  if (ldap_msgtype(result) != LDAP_RES_BIND) {
                      break;
                  }
                  ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, "http_auth_ldap: Received bind response (%d: %s [%s])",
                      error_code, ldap_err2string(error_code), error_msg ? error_msg : "-");
                  ngx_http_auth_ldap_reply_connection(c, error_code, error_msg);
                  break;
  
              case STATE_SEARCHING:
                  if (ldap_msgtype(result) == LDAP_RES_SEARCH_ENTRY) {
                      ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http_auth_ldap: Received a search entry");
                      if (c->rctx->dn.data == NULL) {
                          dn = ldap_get_dn(c->ld, result);
                          if (dn != NULL) {
                              ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http_auth_ldap: Found entry with DN \"%s\"", dn);
                              c->rctx->dn.len = ngx_strlen(dn);
                              c->rctx->dn.data = (u_char *) ngx_palloc(c->rctx->r->pool, c->rctx->dn.len + 1);
                              ngx_memcpy(c->rctx->dn.data, dn, c->rctx->dn.len + 1);
                              ldap_memfree(dn);
                          }
                      }
                  } else if (ldap_msgtype(result) == LDAP_RES_SEARCH_RESULT) {
                      ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, "http_auth_ldap: Received search result (%d: %s [%s])",
                          error_code, ldap_err2string(error_code), error_msg ? error_msg : "-");
                      ngx_http_auth_ldap_reply_connection(c, error_code, error_msg);
                  }
                  break;
  
              case STATE_COMPARING:
                  if (ldap_msgtype(result) != LDAP_RES_COMPARE) {
                      break;
                  }
                  ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, "http_auth_ldap: Received comparison result (%d: %s [%s])",
                      error_code, ldap_err2string(error_code), error_msg ? error_msg : "-");
                  ngx_http_auth_ldap_reply_connection(c, error_code, error_msg);
                  break;
  
              default:
                  break;
          }
  
          ldap_memfree(error_msg);
          ldap_msgfree(result);
      }
  
      if (ngx_handle_read_event(rev, 0) != NGX_OK) {
          ngx_http_auth_ldap_close_connection(c);
          return;
      }
  }
  
  static void
  ngx_http_auth_ldap_connect(ngx_http_auth_ldap_connection_t *c)
  {
      ngx_peer_connection_t *pconn;
      ngx_connection_t *conn;
      ngx_addr_t *addr;
      ngx_int_t rc;
  
      addr = &c->server->parsed_url.addrs[ngx_random() % c->server->parsed_url.naddrs];
  
      ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http_auth_ldap: Connecting to LDAP server \"%V\".",
          &addr->name);
  
      pconn = &c->conn;
      pconn->sockaddr = addr->sockaddr;
      pconn->socklen = addr->socklen;
      pconn->name = &addr->name;
      pconn->get = ngx_event_get_peer;
      pconn->log = c->log;
      pconn->log_error = NGX_ERROR_ERR;
  
      rc = ngx_event_connect_peer(pconn);
      ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http_auth_ldap: ngx_event_connect_peer() -> %d.", rc);
      if (rc == NGX_ERROR || rc == NGX_BUSY || rc == NGX_DECLINED) {
          ngx_log_error(NGX_LOG_ERR, c->log, 0, "http_auth_ldap: Unable to connect to LDAP server \"%V\".",
              &addr->name);
          ngx_add_timer(&c->reconnect_event, c->server->reconnect_timeout);
          return;
      }
  
      conn = pconn->connection;
      conn->data = c;
  #if (NGX_OPENSSL)
      conn->pool = c->pool;
  #endif
      conn->write->handler = ngx_http_auth_ldap_connect_handler;
      conn->read->handler = ngx_http_auth_ldap_read_handler;
      ngx_add_timer(conn->read, c->server->connect_timeout);
      ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http_auth_ldap: connect_timeout=%d.", c->server->connect_timeout);
  
      c->server->max_down_retries_count = 0;   /* reset retries count */
      c->state = STATE_CONNECTING;
  }
  
  static void
  ngx_http_auth_ldap_connection_cleanup(void *data)
  {
      ngx_http_auth_ldap_close_connection((ngx_http_auth_ldap_connection_t *) data);
  }
  
  static void
  ngx_http_auth_ldap_reconnect_handler(ngx_event_t *ev)
  {
      ngx_connection_t *conn = ev->data;
      ngx_http_auth_ldap_connection_t *c = conn->data;
      ngx_http_auth_ldap_connect(c);
  }
  
  static ngx_int_t
  ngx_http_auth_ldap_init_connections(ngx_cycle_t *cycle)
  {
      ngx_http_auth_ldap_connection_t *c;
      ngx_http_auth_ldap_main_conf_t *halmcf;
      ngx_http_auth_ldap_server_t *server;
      ngx_pool_cleanup_t *cleanup;
      ngx_connection_t *dummy_conn;
      ngx_uint_t i, j;
      int option;
  
      halmcf = ngx_http_cycle_get_module_main_conf(cycle, ngx_http_auth_ldap_module);
      if (halmcf == NULL || halmcf->servers == NULL) {
  	return NGX_OK;
      }
  
      option = LDAP_VERSION3;
      ldap_set_option(NULL, LDAP_OPT_PROTOCOL_VERSION, &option);
  
      for (i = 0; i < halmcf->servers->nelts; i++) {
          server = &((ngx_http_auth_ldap_server_t *) halmcf->servers->elts)[i];
          ngx_queue_init(&server->free_connections);
          ngx_queue_init(&server->waiting_requests);
          if (server->connections <= 1) {
              server->connections = 1;
          }
  
          for (j = 0; j < server->connections; j++) {
              c = ngx_pcalloc(cycle->pool, sizeof(ngx_http_auth_ldap_connection_t));
              cleanup = ngx_pool_cleanup_add(cycle->pool, 0);
              dummy_conn = ngx_pcalloc(cycle->pool, sizeof(ngx_connection_t));
              if (c == NULL || cleanup == NULL || dummy_conn == NULL) {
                  return NGX_ERROR;
              }
  
              cleanup->handler = &ngx_http_auth_ldap_connection_cleanup;
              cleanup->data = c;
  
              c->log = cycle->log;
              c->server = server;
              c->state = STATE_DISCONNECTED;
  
              /* Various debug logging around timer management assume that the field
                 'data' in ngx_event_t is a pointer to ngx_connection_t, therefore we
                 have a dummy such structure around so that it does not crash etc. */
              dummy_conn->data = c;
              c->reconnect_event.log = c->log;
              c->reconnect_event.data = dummy_conn;
              c->reconnect_event.handler = ngx_http_auth_ldap_reconnect_handler;
  
  #if (NGX_OPENSSL)
              c->pool = cycle->pool;
              c->ssl = &halmcf->ssl;
  #endif
  
              ngx_http_auth_ldap_connect(c);
          }
      }
  
      return NGX_OK;
  }
  
  
  
  /*** Per-request authentication processing ***/
  
  /**
   * Respond with "403 Forbidden" and add correct headers
   */
  static ngx_int_t
  ngx_http_auth_ldap_set_realm(ngx_http_request_t *r, ngx_str_t *realm)
  {
      r->headers_out.www_authenticate = ngx_list_push(&r->headers_out.headers);
      if (r->headers_out.www_authenticate == NULL) {
          return NGX_HTTP_INTERNAL_SERVER_ERROR;
      }
  
      r->headers_out.www_authenticate->hash = 1;
      r->headers_out.www_authenticate->key.len = sizeof("WWW-Authenticate") - 1;
      r->headers_out.www_authenticate->key.data = (u_char *) "WWW-Authenticate";
      r->headers_out.www_authenticate->value = *realm;
  
      return NGX_HTTP_UNAUTHORIZED;
  }
  
  /**
   * LDAP Authentication handler
   */
  static ngx_int_t
  ngx_http_auth_ldap_handler(ngx_http_request_t *r)
  {
      ngx_http_auth_ldap_loc_conf_t *alcf;
      ngx_http_auth_ldap_ctx_t *ctx;
      int rc;
  
      alcf = ngx_http_get_module_loc_conf(r, ngx_http_auth_ldap_module);
      if (alcf->realm.len == 0) {
          return NGX_DECLINED;
      }
  
      if (alcf->servers == NULL || alcf->servers->nelts == 0) {
          /* No LDAP servers for the location. */
          ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "http_auth_ldap: \"auth_ldap\" requires "
                        "one or more \"auth_ldap_servers\" in the same location");
          return ngx_http_auth_ldap_set_realm(r, &alcf->realm);
      }
  
      ctx = ngx_http_get_module_ctx(r, ngx_http_auth_ldap_module);
      if (ctx == NULL) {
          rc = ngx_http_auth_basic_user(r);
          if (rc == NGX_DECLINED) {
              return ngx_http_auth_ldap_set_realm(r, &alcf->realm);
          }
          if (rc == NGX_ERROR) {
              return NGX_HTTP_INTERNAL_SERVER_ERROR;
          }
  
          ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http_auth_ldap: Username is \"%V\"",
              &r->headers_in.user);
          if (r->headers_in.passwd.len == 0)
          {
              ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http_auth_ldap: Password is empty");
              return ngx_http_auth_ldap_set_realm(r, &alcf->realm);
          }
  
          ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_auth_ldap_ctx_t));
          if (ctx == NULL) {
              return NGX_HTTP_INTERNAL_SERVER_ERROR;
          }
          ctx->r = r;
          /* Other fields have been initialized to zero/NULL */
          ngx_http_set_ctx(r, ctx, ngx_http_auth_ldap_module);
      }
  
      return ngx_http_auth_ldap_authenticate(r, ctx, alcf);
  }
  
  /**
   * Iteratively handle all phases of the authentication process, might be called many times
   */
  static ngx_int_t
  ngx_http_auth_ldap_authenticate(ngx_http_request_t *r, ngx_http_auth_ldap_ctx_t *ctx,
          ngx_http_auth_ldap_loc_conf_t *conf)
  {
      ngx_int_t rc;
  
      if (r->connection->write->timedout) {
          ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "http_auth_ldap: Authentication timed out");
          if (ctx->c != NULL) {
              ngx_http_auth_ldap_return_connection(ctx->c);
          }
  
          // Remove ctx from waiting_requests queue if it was added.
          if (ngx_queue_next(&ctx->queue)) {
              ngx_queue_remove(&ctx->queue);
          }
  
          return NGX_ERROR;
      }
  
      /*
       * If we are not starting up a request (ctx->phase != PHASE_START) and we actually already
       * sent a request (ctx->iteration > 0) and didn't receive a reply yet (!ctx->replied) we
       * ask to be called again at a later time when we hopefully have received a reply.
       *
       * It is quite possible that we reach this if while not having sent a request yet (ctx->iteration == 0) -
       * this happens when we are trying to get an LDAP connection but all of them are busy right now.
       */
      if (ctx->iteration > 0 && !ctx->replied && ctx->phase != PHASE_START) {
          ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http_auth_ldap: The LDAP operation did not finish yet");
          return NGX_AGAIN;
      }
  
      for (;;) {
          ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http_auth_ldap: Authentication loop (phase=%d, iteration=%d)",
              ctx->phase, ctx->iteration);
  
          switch (ctx->phase) {
              case PHASE_START:
                  ctx->server = ((ngx_http_auth_ldap_server_t **) conf->servers->elts)[ctx->server_index];
                  ctx->outcome = OUTCOME_UNCERTAIN;
  
                  ngx_add_timer(r->connection->write, ctx->server->request_timeout);
                  ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http_auth_ldap: request_timeout=%d",ctx->server->request_timeout);
  
  
                  /* Check cache if enabled */
                  if (ngx_http_auth_ldap_cache.buckets != NULL) {
                      rc = ngx_http_auth_ldap_check_cache(r, ctx, &ngx_http_auth_ldap_cache, ctx->server);
                      ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http_auth_ldap: Using cached outcome %d", rc);
                      if (rc == OUTCOME_DENY || rc == OUTCOME_ALLOW) {
                          ctx->outcome = (rc == OUTCOME_DENY ? OUTCOME_CACHED_DENY : OUTCOME_CACHED_ALLOW);
                          ctx->phase = PHASE_NEXT;
                          break;
                      }
                  }
  
                  if (ctx->server->require_valid_user_dn.value.data != NULL) {
                      /* Construct user DN */
                      if (ngx_http_complex_value(r, &ctx->server->require_valid_user_dn, &ctx->user_dn) != NGX_OK) {
                          ngx_del_timer(r->connection->write);
                          return NGX_ERROR;
                      }
                      ctx->phase = PHASE_CHECK_USER;
                      break;
                  }
  
                  ctx->phase = PHASE_SEARCH;
                  ctx->iteration = 0;
                  break;
  
              case PHASE_SEARCH:
                  /* Search the directory to retrieve full user DN */
                  rc = ngx_http_auth_ldap_search(r, ctx);
                  if (rc == NGX_AGAIN) {
                      /* LDAP operation in progress, wait for the results */
                      return NGX_AGAIN;
                  }
                  if (rc != NGX_OK) {
                      /* Search failed, try next server */
                      ctx->phase = PHASE_NEXT;
                      break;
                  }
  
                  /* User DN has been found, check user next */
                  ctx->phase = PHASE_CHECK_USER;
                  break;
  
              case PHASE_CHECK_USER:
                  ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http_auth_ldap: User DN is \"%V\"",
                      &ctx->user_dn);
  
                  if (ctx->server->require_user != NULL) {
                      rc = ngx_http_auth_ldap_check_user(r, ctx);
                      if (rc != NGX_OK) {
                          ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http_auth_ldap: Not ok", &ctx->user_dn);
                          /* User check failed, try next server */
                          ctx->phase = PHASE_NEXT;
                          break;
                      }
                  }
  
                  /* User not yet fully authenticated, check group next */
                  if ((ctx->outcome == OUTCOME_UNCERTAIN) &&
                      (ctx->server->require_group != NULL)) {
                          ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http_auth_ldap: Moving to group check", &ctx->user_dn);
                          ctx->phase = PHASE_CHECK_GROUP;
                          ctx->iteration = 0;
                          break;
                  }
  
                  /* No groups to validate, try binding next */
                  ctx->phase = PHASE_CHECK_BIND;
                  ctx->iteration = 0;
                  break;
  
              case PHASE_CHECK_GROUP:
                  ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "Checking group", &ctx->user_dn);
                  rc = ngx_http_auth_ldap_check_group(r, ctx);
                  if (rc == NGX_AGAIN) {
                      /* LDAP operation in progress, wait for the results */
                      return NGX_AGAIN;
                  }
                  if (rc != NGX_OK) {
                      /* Group check failed, try next server */
                      ctx->phase = PHASE_NEXT;
                      break;
                  }
  
                  /* Groups validated, try binding next */
                  ctx->phase = PHASE_CHECK_BIND;
                  ctx->iteration = 0;
                  break;
  
              case PHASE_CHECK_BIND:
  
                  if (ctx->outcome == OUTCOME_UNCERTAIN) {
                      /* If we're still uncertain when satisfy is 'any' and there
                       * is at least one require user/group rule, it means no
                       * rule has matched.
                       */
                      if ((ctx->server->satisfy_all == 0) && (
                              (ctx->server->require_user != NULL) ||
                              (ctx->server->require_group != NULL))){
                          ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http_auth_ldap: no requirement satisfied");
                          ctx->outcome = OUTCOME_DENY;
                          ctx->phase = PHASE_NEXT;
                          /*rc = NGX_DECLINED;*/
                          break;
                      } else {
                          /* So far so good */
                          ctx->outcome = OUTCOME_ALLOW;
                      }
                  }
  
                  /* Initiate bind using the found DN and request password */
                  rc = ngx_http_auth_ldap_check_bind(r, ctx);
                  if (rc == NGX_AGAIN) {
                      /* LDAP operation in progress, wait for the result */
                      return NGX_AGAIN;
                  }
  
                  /* All steps done, finish the processing */
                  ctx->phase = PHASE_REBIND;
                  ctx->iteration = 0;
                  break;
  
              case PHASE_REBIND:
                  /* Initiate bind using the found DN and request password */
                  rc = ngx_http_auth_ldap_recover_bind(r, ctx);
                  if (rc == NGX_AGAIN) {
                      /* LDAP operation in progress, wait for the result */
                      return NGX_AGAIN;
                  }
  
                  /* All steps done, finish the processing */
                  ctx->phase = PHASE_NEXT;
                  break;
  
              case PHASE_NEXT:
                  if (r->connection->write->timer_set) {
                      ngx_del_timer(r->connection->write);
                  }
  
                  if (ctx->c != NULL) {
                      ngx_http_auth_ldap_return_connection(ctx->c);
                  }
  
                  if (ngx_http_auth_ldap_cache.buckets != NULL &&
                      (ctx->outcome == OUTCOME_DENY || ctx->outcome == OUTCOME_ALLOW)) {
                      ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http_auth_ldap: Caching outcome %d", ctx->outcome);
                      ngx_http_auth_ldap_update_cache(ctx, &ngx_http_auth_ldap_cache, ctx->outcome);
                  }
  
                  if (ctx->outcome == OUTCOME_ALLOW || ctx->outcome == OUTCOME_CACHED_ALLOW) {
                      return NGX_OK;
                  }
  
                  ctx->server_index++;
                  if (ctx->server_index >= conf->servers->nelts) {
                      return ngx_http_auth_ldap_set_realm(r, &conf->realm);
                  }
  
                  ctx->phase = PHASE_START;
                  break;
          }
      }
  }
  
  static ngx_int_t
  ngx_http_auth_ldap_search(ngx_http_request_t *r, ngx_http_auth_ldap_ctx_t *ctx)
  {
      LDAPURLDesc *ludpp;
      u_char *filter;
      char *attrs[2];
      ngx_int_t rc;
  
      /* On the first call, initiate the LDAP search operation */
      if (ctx->iteration == 0) {
          if (!ngx_http_auth_ldap_get_connection(ctx)) {
              return NGX_AGAIN;
          }
  
          ludpp = ctx->server->ludpp;
          filter = ngx_pcalloc(
              r->pool,
              (ludpp->lud_filter != NULL ? ngx_strlen(ludpp->lud_filter) : ngx_strlen("(objectClass=*)")) +
              ngx_strlen("(&(=))") + ngx_strlen(ludpp->lud_attrs[0]) + r->headers_in.user.len + 1);
          ngx_sprintf(filter, "(&%s(%s=%V))%Z",
                  ludpp->lud_filter != NULL ? ludpp->lud_filter : "(objectClass=*)",
                  ludpp->lud_attrs[0], &r->headers_in.user);
          ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http_auth_ldap: Search filter is \"%s\"",
              (const char *) filter);
  
          attrs[0] = LDAP_NO_ATTRS;
          attrs[1] = NULL;
  
          rc = ldap_search_ext(ctx->c->ld, ludpp->lud_dn, ludpp->lud_scope, (const char *) filter, attrs, 0, NULL, NULL, NULL, 0, &ctx->c->msgid);
          if (rc != LDAP_SUCCESS) {
              ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "http_auth_ldap: ldap_search_ext() failed (%d, %s)",
                  rc, ldap_err2string(rc));
              ngx_http_auth_ldap_return_connection(ctx->c);
              return NGX_ERROR;
          }
  
          ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http_auth_ldap: ldap_search_ext() -> msgid=%d",
              ctx->c->msgid);
          ctx->c->state = STATE_SEARCHING;
          ctx->iteration++;
          return NGX_AGAIN;
      }
  
      /* On the second call, handle the search results */
      if (ctx->error_code != LDAP_SUCCESS) {
          ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "http_auth_ldap: ldap_search_ext() request failed (%d: %s)",
              ctx->error_code, ldap_err2string(ctx->error_code));
          return NGX_ERROR;
      }
  
      if (ctx->dn.data == NULL) {
          ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "http_auth_ldap: Could not find user DN");
          return NGX_ERROR;
      } else {
          ctx->user_dn.len = ngx_strlen(ctx->dn.data);
          ctx->user_dn.data = (u_char *) ngx_palloc(ctx->r->pool, ctx->user_dn.len + 1);
          ngx_memcpy(ctx->user_dn.data, ctx->dn.data, ctx->user_dn.len + 1);
          ctx->dn.data = NULL;
          ctx->dn.len = 0;
      }
  
      return NGX_OK;
  }
  
  static ngx_int_t
  ngx_http_auth_ldap_check_user(ngx_http_request_t *r, ngx_http_auth_ldap_ctx_t *ctx)
  {
      ngx_http_complex_value_t *values;
      ngx_uint_t i;
  
      values = ctx->server->require_user->elts;
      for (i = 0; i < ctx->server->require_user->nelts; i++) {
          ngx_str_t val;
          if (ngx_http_complex_value(r, &values[i], &val) != NGX_OK) {
              ctx->outcome = OUTCOME_ERROR;
              return NGX_ERROR;
          }
  
          ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http_auth_ldap: Comparing user DN with \"%V\"", &val);
          if (val.len == ctx->user_dn.len && ngx_memcmp(val.data, ctx->user_dn.data, val.len) == 0) {
              if (ctx->server->satisfy_all == 0) {
                  ctx->outcome = OUTCOME_ALLOW;
                  return NGX_OK;
              }
          } else {
              if (ctx->server->satisfy_all == 1) {
                  ctx->outcome = OUTCOME_DENY;
                  return NGX_DECLINED;
              }
          }
      }
  
      return NGX_OK;
  }
  
  static ngx_int_t
  ngx_http_auth_ldap_check_group(ngx_http_request_t *r, ngx_http_auth_ldap_ctx_t *ctx)
  {
      ngx_http_complex_value_t *values;
      ngx_int_t rc;
      u_char *filter;
      char *user_val;
      char *attrs[2];
      size_t for_filter;
  
      /* Handle result of the search started during previous call */
      if (ctx->iteration > 0) {
          ctx->group_dn.data = ctx->dn.data;
          if (ctx->group_dn.data != NULL) {
              if (ctx->error_code == LDAP_SUCCESS) {
                  if (ctx->server->satisfy_all == 0) {
                      ctx->outcome = OUTCOME_ALLOW;
                      return NGX_OK;
                  }
              } else if (ctx->error_code == LDAP_NO_RESULTS_RETURNED) {
                  ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "http_auth_ldap: ldap_search_ext() request failed (%d: %s)",
                                ctx->error_code, ldap_err2string(ctx->error_code));
                  if (ctx->server->satisfy_all == 1) {
                      ctx->outcome = OUTCOME_DENY;
                      return NGX_DECLINED;
                  }
              } else {
                  ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "http_auth_ldap: ldap_search_ext() request failed (%d: %s)",
                      ctx->error_code, ldap_err2string(ctx->error_code));
                  return NGX_ERROR;
              }
          }
      }
  
      /* Check next group */
      if (ctx->iteration >= ctx->server->require_group->nelts) {
          /* No more groups */
          return NGX_OK;
      }
  
      if (!ngx_http_auth_ldap_get_connection(ctx)) {
          return NGX_AGAIN;
      }
  
      ngx_str_t val;
      values = ctx->server->require_group->elts;
      if (ngx_http_complex_value(r, &values[ctx->iteration], &val) != NGX_OK) {
          ctx->outcome = OUTCOME_ERROR;
          ngx_http_auth_ldap_return_connection(ctx->c);
          return NGX_ERROR;
      }
  
      char *gr;
      char *cn_gr;
      char *tail_gr;
      gr = ngx_pcalloc(r->pool,val.len+1);
      ngx_memcpy(gr, val.data, val.len);
      gr[val.len] = '\0';
      tail_gr = ngx_strchr(gr, ',');
      if (tail_gr == NULL) {
          ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "http_auth_ldap: Incorrect group DN: \"%s\"", gr);
          ctx->outcome = OUTCOME_ERROR;
          ngx_http_auth_ldap_return_connection(ctx->c);
          return NGX_ERROR;
      }
      *tail_gr = '\0';
      tail_gr++;
      cn_gr = gr;
  
      if (ctx->server->group_attribute_dn == 1) {
          user_val = ngx_pcalloc(
              r->pool,
              ctx->user_dn.len + 1);
          ngx_memcpy(user_val, ctx->user_dn.data, ctx->user_dn.len);
          user_val[ctx->user_dn.len] = '\0';
      } else {
          user_val = ngx_pcalloc(
              r->pool,
              r->headers_in.user.len + 1);
          ngx_memcpy(user_val, r->headers_in.user.data, r->headers_in.user.len);
          user_val[r->headers_in.user.len] = '\0';
      }
  
      ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http_auth_ldap: Search user in group \"%V\"", &val);
  
      if (ctx->server->group_attribute.data == NULL) {
          ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http_auth_ldap: group_attribute.data is \"%V\" so calling a failure here", ctx->server->group_attribute.data);
          rc = !LDAP_SUCCESS;
      } else {
          for_filter = ngx_strlen(cn_gr) + ctx->server->group_attribute.len + ngx_strlen((const char *) user_val) + ngx_strlen("(&()(=))") + 1;
          filter = ngx_pcalloc(
              r->pool,
              for_filter);
          ngx_sprintf(filter, "(&(%s)(%s=%s))", cn_gr, ctx->server->group_attribute.data, user_val);
          ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http_auth_ldap: Search group filter is \"%s\"", (const char *) filter);
          attrs[0] = LDAP_NO_ATTRS;
          attrs[1] = NULL;
          rc = ldap_search_ext(ctx->c->ld, tail_gr, LDAP_SCOPE_ONELEVEL, (const char *) filter, attrs, 0, NULL, NULL, NULL, 0, &ctx->c->msgid);
      }
      if (rc != LDAP_SUCCESS) {
          ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "http_auth_ldap: ldap_search_ext() failed (%d: %s)",
              rc, ldap_err2string(rc));
          ctx->outcome = OUTCOME_ERROR;
          ngx_http_auth_ldap_return_connection(ctx->c);
          return NGX_ERROR;
      }
  
      ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http_auth_ldap: ldap_search_ext() -> msgid=%d",
          ctx->c->msgid);
      ctx->c->state = STATE_SEARCHING;
      ctx->iteration++;
      return NGX_AGAIN;
  }
  
  /**
   * Initiate and handle a bind operation using the authentication parameters
   */
  static ngx_int_t
  ngx_http_auth_ldap_check_bind(ngx_http_request_t *r, ngx_http_auth_ldap_ctx_t *ctx)
  {
      struct berval cred;
      ngx_int_t rc;
  
      /* On the first call, initiate the bind LDAP operation */
      if (ctx->iteration == 0) {
          if (!ngx_http_auth_ldap_get_connection(ctx)) {
              return NGX_AGAIN;
          }
  
          cred.bv_val = (char *) r->headers_in.passwd.data;
          cred.bv_len = r->headers_in.passwd.len;
          rc = ldap_sasl_bind(ctx->c->ld, (const char *) ctx->user_dn.data, LDAP_SASL_SIMPLE, &cred, NULL, NULL, &ctx->c->msgid);
          if (rc != LDAP_SUCCESS) {
              ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "http_auth_ldap: ldap_sasl_bind() failed (%d: %s)",
                  rc, ldap_err2string(rc));
              ctx->outcome = OUTCOME_ERROR;
              ngx_http_auth_ldap_return_connection(ctx->c);
              return NGX_ERROR;
          }
  
          ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http_auth_ldap: ldap_sasl_bind() -> msgid=%d",
              ctx->c->msgid);
          ctx->c->state = STATE_BINDING;
          ctx->iteration++;
  
          return NGX_AGAIN;
      }
  
      /* On the second call, process the operation result */
      if (ctx->error_code != LDAP_SUCCESS) {
          ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "http_auth_ldap: User bind failed (%d: %s)",
              ctx->error_code, ldap_err2string(ctx->error_code));
          ctx->outcome = OUTCOME_DENY;
      } else {
          ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http_auth_ldap: User bind successful");
          ctx->outcome = OUTCOME_ALLOW;
      }
      return NGX_OK;
  }
  
  static ngx_int_t
  ngx_http_auth_ldap_recover_bind(ngx_http_request_t *r, ngx_http_auth_ldap_ctx_t *ctx)
  {
      struct berval cred;
      ngx_int_t rc;
  
      /* On the first call, initiate the bind LDAP operation */
      if (ctx->iteration == 0) {
          if (!ngx_http_auth_ldap_get_connection(ctx)) {
              return NGX_AGAIN;
          }
  
          ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http_auth_ldap: Rebinding to binddn");
          cred.bv_val = (char *) ctx->server->bind_dn_passwd.data;
          cred.bv_len = ctx->server->bind_dn_passwd.len;
          rc = ldap_sasl_bind(ctx->c->ld, (const char *) ctx->server->bind_dn.data, LDAP_SASL_SIMPLE, &cred, NULL, NULL, &ctx->c->msgid);
          if (rc != LDAP_SUCCESS) {
              ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "http_auth_ldap: ldap_sasl_bind() failed (%d: %s)",
                  rc, ldap_err2string(rc));
              ctx->outcome = OUTCOME_ERROR;
              ngx_http_auth_ldap_return_connection(ctx->c);
              return NGX_ERROR;
          }
  
          ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http_auth_ldap: ldap_sasl_bind() -> msgid=%d",
              ctx->c->msgid);
          ctx->c->state = STATE_BINDING;
          ctx->iteration++;
          return NGX_AGAIN;
      }
  
      /* On the second call, process the operation result */
      if (ctx->error_code != LDAP_SUCCESS) {
          ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "http_auth_ldap: binddn bind failed (%d: %s)",
              ctx->error_code, ldap_err2string(ctx->error_code));
      } else {
          ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http_auth_ldap: binddn bind successful");
      }
      return NGX_OK;
  }
  