Fix include paths to new location of libutil.
[kai/samba-autobuild/.git] / source4 / libcli / ldap / ldap_client.c
index 9ca9e4b5c4255a84ff87522bd55af7bdeaf2d22a..a59356761b440658d1506b34388b0f69c48c351f 100644 (file)
@@ -9,7 +9,7 @@
     
    This program 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
+   the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.
    
    This program is distributed in the hope that it will be useful,
    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., 675 Mass Ave, Cambridge, MA 02139, USA.
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
    
 */
 
 #include "includes.h"
-#include "system/network.h"
-#include "auth/auth.h"
-#include "asn_1.h"
-#include "dlinklist.h"
-
-#if 0
-static struct ldap_message *new_ldap_search_message(struct ldap_connection *conn,
-                                            const char *base,
-                                            enum ldap_scope scope,
-                                            char *filter,
-                                            int num_attributes,
-                                            const char **attributes)
+#include "../lib/util/asn1.h"
+#include "../lib/util/dlinklist.h"
+#include "lib/events/events.h"
+#include "lib/socket/socket.h"
+#include "libcli/ldap/ldap.h"
+#include "libcli/ldap/ldap_proto.h"
+#include "libcli/ldap/ldap_client.h"
+#include "libcli/composite/composite.h"
+#include "lib/stream/packet.h"
+#include "lib/tls/tls.h"
+#include "auth/gensec/gensec.h"
+#include "system/time.h"
+#include "param/param.h"
+#include "libcli/resolve/resolve.h"
+
+/**
+  create a new ldap_connection stucture. The event context is optional
+*/
+_PUBLIC_ struct ldap_connection *ldap4_new_connection(TALLOC_CTX *mem_ctx, 
+                                            struct loadparm_context *lp_ctx,
+                                            struct event_context *ev)
 {
-       struct ldap_message *res;
+       struct ldap_connection *conn;
 
-       res = new_ldap_message(conn);
-       if (!res) {
+       if (ev == NULL) {
                return NULL;
        }
 
-       res->type = LDAP_TAG_SearchRequest;
-       res->r.SearchRequest.basedn = base;
-       res->r.SearchRequest.scope = scope;
-       res->r.SearchRequest.deref = LDAP_DEREFERENCE_NEVER;
-       res->r.SearchRequest.timelimit = 0;
-       res->r.SearchRequest.sizelimit = 0;
-       res->r.SearchRequest.attributesonly = False;
-       res->r.SearchRequest.filter = filter;
-       res->r.SearchRequest.num_attributes = num_attributes;
-       res->r.SearchRequest.attributes = attributes;
-
-       return res;
-}
-#endif
-
-static struct ldap_message *new_ldap_simple_bind_msg(struct ldap_connection *conn, const char *dn, const char *pw)
-{
-       struct ldap_message *res;
-
-       res = new_ldap_message(conn);
-       if (!res) {
+       conn = talloc_zero(mem_ctx, struct ldap_connection);
+       if (conn == NULL) {
                return NULL;
        }
 
-       res->type = LDAP_TAG_BindRequest;
-       res->r.BindRequest.version = 3;
-       res->r.BindRequest.dn = talloc_strdup(res->mem_ctx, dn);
-       res->r.BindRequest.mechanism = LDAP_AUTH_MECH_SIMPLE;
-       res->r.BindRequest.creds.password = talloc_strdup(res->mem_ctx, pw);
+       conn->next_messageid  = 1;
+       conn->event.event_ctx = ev;
 
-       return res;
-}
-
-static struct ldap_message *new_ldap_sasl_bind_msg(struct ldap_connection *conn, const char *sasl_mechanism, DATA_BLOB *secblob)
-{
-       struct ldap_message *res;
+       conn->lp_ctx = lp_ctx;
 
-       res = new_ldap_message(conn);
-       if (!res) {
-               return NULL;
-       }
+       /* set a reasonable request timeout */
+       conn->timeout = 60;
 
-       res->type = LDAP_TAG_BindRequest;
-       res->r.BindRequest.version = 3;
-       res->r.BindRequest.dn = "";
-       res->r.BindRequest.mechanism = LDAP_AUTH_MECH_SASL;
-       res->r.BindRequest.creds.SASL.mechanism = talloc_strdup(res->mem_ctx, sasl_mechanism);
-       res->r.BindRequest.creds.SASL.secblob = *secblob;
-
-       return res;
+       /* explicitly avoid reconnections by default */
+       conn->reconnect.max_retries = 0;
+       
+       return conn;
 }
 
-static struct ldap_connection *new_ldap_connection(TALLOC_CTX *mem_ctx)
+/*
+  the connection is dead
+*/
+static void ldap_connection_dead(struct ldap_connection *conn)
 {
-       struct ldap_connection *result;
-
-       result = talloc_p(mem_ctx, struct ldap_connection);
-
-       if (!result) {
-               return NULL;
+       struct ldap_request *req;
+
+       talloc_free(conn->sock);  /* this will also free event.fde */
+       talloc_free(conn->packet);
+       conn->sock = NULL;
+       conn->event.fde = NULL;
+       conn->packet = NULL;
+
+       /* return an error for any pending request ... */
+       while (conn->pending) {
+               req = conn->pending;
+               DLIST_REMOVE(req->conn->pending, req);
+               req->state = LDAP_REQUEST_DONE;
+               req->status = NT_STATUS_UNEXPECTED_NETWORK_ERROR;
+               if (req->async.fn) {
+                       req->async.fn(req);
+               }
        }
+}
 
-       result->mem_ctx = result;
-       result->next_msgid = 1;
-       result->outstanding = NULL;
-       result->searchid = 0;
-       result->search_entries = NULL;
-       result->auth_dn = NULL;
-       result->simple_pw = NULL;
-       result->gensec = NULL;
+static void ldap_reconnect(struct ldap_connection *conn);
+
+/*
+  handle packet errors
+*/
+static void ldap_error_handler(void *private_data, NTSTATUS status)
+{
+       struct ldap_connection *conn = talloc_get_type(private_data, 
+                                                      struct ldap_connection);
+       ldap_connection_dead(conn);
 
-       return result;
+       /* but try to reconnect so that the ldb client can go on */
+       ldap_reconnect(conn);
 }
 
-struct ldap_connection *ldap_connect(TALLOC_CTX *mem_ctx, const char *url)
+
+/*
+  match up with a pending message, adding to the replies list
+*/
+static void ldap_match_message(struct ldap_connection *conn, struct ldap_message *msg)
 {
-       struct hostent *hp;
-       struct ipv4_addr ip;
-       struct ldap_connection *conn;
-       BOOL ret;
+       struct ldap_request *req;
+       int i;
 
-       conn = new_ldap_connection(mem_ctx);
-       if (!conn) {
-               return NULL;
+       for (req=conn->pending; req; req=req->next) {
+               if (req->messageid == msg->messageid) break;
+       }
+       /* match a zero message id to the last request sent.
+          It seems that servers send 0 if unable to parse */
+       if (req == NULL && msg->messageid == 0) {
+               req = conn->pending;
+       }
+       if (req == NULL) {
+               DEBUG(0,("ldap: no matching message id for %u\n",
+                        msg->messageid));
+               talloc_free(msg);
+               return;
        }
 
-       ret = ldap_parse_basic_url(conn->mem_ctx, url, &conn->host,
-                                 &conn->port, &conn->ldaps);
-       if (!ret) {
-               talloc_free(conn);
-               return NULL;
+       /* Check for undecoded critical extensions */
+       for (i=0; msg->controls && msg->controls[i]; i++) {
+               if (!msg->controls_decoded[i] && 
+                   msg->controls[i]->critical) {
+                       req->status = NT_STATUS_LDAP(LDAP_UNAVAILABLE_CRITICAL_EXTENSION);
+                       req->state = LDAP_REQUEST_DONE;
+                       DLIST_REMOVE(conn->pending, req);
+                       if (req->async.fn) {
+                               req->async.fn(req);
+                       }
+                       return;
+               }
        }
 
-       hp = sys_gethostbyname(conn->host);
-       if (!hp || !hp->h_addr) {
-               talloc_free(conn);
-               return NULL;
+       /* add to the list of replies received */
+       talloc_steal(req, msg);
+       req->replies = talloc_realloc(req, req->replies, 
+                                     struct ldap_message *, req->num_replies+1);
+       if (req->replies == NULL) {
+               req->status = NT_STATUS_NO_MEMORY;
+               req->state = LDAP_REQUEST_DONE;
+               DLIST_REMOVE(conn->pending, req);
+               if (req->async.fn) {
+                       req->async.fn(req);
+               }
+               return;
        }
 
-       putip((char *)&ip, (char *)hp->h_addr);
+       req->replies[req->num_replies] = talloc_steal(req->replies, msg);
+       req->num_replies++;
 
-       conn->sock = open_socket_out(SOCK_STREAM, &ip, conn->port, LDAP_CONNECTION_TIMEOUT);
-       if (conn->sock < 0) {
-               talloc_free(conn);
-               return NULL;
+       if (msg->type != LDAP_TAG_SearchResultEntry &&
+           msg->type != LDAP_TAG_SearchResultReference) {
+               /* currently only search results expect multiple
+                  replies */
+               req->state = LDAP_REQUEST_DONE;
+               DLIST_REMOVE(conn->pending, req);
        }
 
-       return conn;
+       if (req->async.fn) {
+               req->async.fn(req);
+       }
 }
 
-struct ldap_message *new_ldap_message(TALLOC_CTX *mem_ctx)
+
+/*
+  decode/process LDAP data
+*/
+static NTSTATUS ldap_recv_handler(void *private_data, DATA_BLOB blob)
 {
-       struct ldap_message *result;
+       NTSTATUS status;
+       struct ldap_connection *conn = talloc_get_type(private_data, 
+                                                      struct ldap_connection);
+       struct ldap_message *msg = talloc(conn, struct ldap_message);
+       struct asn1_data *asn1 = asn1_init(conn);
 
-       result = talloc_p(mem_ctx, struct ldap_message);
+       if (asn1 == NULL || msg == NULL) {
+               return NT_STATUS_LDAP(LDAP_PROTOCOL_ERROR);
+       }
 
-       if (!result) {
-               return NULL;
+       if (!asn1_load(asn1, blob)) {
+               talloc_free(msg);
+               talloc_free(asn1);
+               return NT_STATUS_LDAP(LDAP_PROTOCOL_ERROR);
+       }
+       
+       status = ldap_decode(asn1, msg);
+       if (!NT_STATUS_IS_OK(status)) {
+               asn1_free(asn1);
+               return status;
        }
 
-       result->mem_ctx = result;
+       ldap_match_message(conn, msg);
 
-       return result;
+       data_blob_free(&blob);
+       asn1_free(asn1);
+       return NT_STATUS_OK;
 }
 
-BOOL ldap_send_msg(struct ldap_connection *conn, struct ldap_message *msg,
-                  const struct timeval *endtime)
+/* Handle read events, from the GENSEC socket callback, or real events */
+void ldap_read_io_handler(void *private_data, uint16_t flags) 
 {
-       DATA_BLOB request;
-       BOOL result;
-       struct ldap_queue_entry *entry;
-
-       msg->messageid = conn->next_msgid++;
-
-       if (!ldap_encode(msg, &request))
-               return False;
-
-       result = (write_data_until(conn->sock, request.data, request.length,
-                                  endtime) == request.length);
-
-       data_blob_free(&request);
-
-       if (!result)
-               return result;
-
-       /* abandon and unbind don't expect results */
-
-       if ((msg->type == LDAP_TAG_AbandonRequest) ||
-           (msg->type == LDAP_TAG_UnbindRequest))
-               return True;
-
-       entry = malloc_p(struct ldap_queue_entry);
-
-       if (entry == NULL)
-               return False;
-
-       entry->msgid = msg->messageid;
-       entry->msg = NULL;
-       DLIST_ADD(conn->outstanding, entry);
-
-       return True;
+       struct ldap_connection *conn = talloc_get_type(private_data, 
+                                                      struct ldap_connection);
+       packet_recv(conn->packet);
 }
 
-BOOL ldap_receive_msg(struct ldap_connection *conn, struct ldap_message *msg,
-                     const struct timeval *endtime)
+/*
+  handle ldap socket events
+*/
+static void ldap_io_handler(struct event_context *ev, struct fd_event *fde, 
+                           uint16_t flags, void *private_data)
 {
-        struct asn1_data data;
-        BOOL result;
-
-        if (!asn1_read_sequence_until(conn->sock, &data, endtime))
-                return False;
-
-        result = ldap_decode(&data, msg);
-
-        asn1_free(&data);
-        return result;
+       struct ldap_connection *conn = talloc_get_type(private_data, 
+                                                      struct ldap_connection);
+       if (flags & EVENT_FD_WRITE) {
+               packet_queue_run(conn->packet);
+               if (!tls_enabled(conn->sock)) return;
+       }
+       if (flags & EVENT_FD_READ) {
+               ldap_read_io_handler(private_data, flags);
+       }
 }
 
-static struct ldap_message *recv_from_queue(struct ldap_connection *conn,
-                                           int msgid)
+/*
+  parse a ldap URL
+*/
+static NTSTATUS ldap_parse_basic_url(TALLOC_CTX *mem_ctx, const char *url,
+                                    char **host, uint16_t *port, bool *ldaps)
 {
-       struct ldap_queue_entry *e;
+       int tmp_port = 0;
+       char protocol[11];
+       char tmp_host[1025];
+       int ret;
 
-       for (e = conn->outstanding; e != NULL; e = e->next) {
+       /* Paranoia check */
+       SMB_ASSERT(sizeof(protocol)>10 && sizeof(tmp_host)>254);
+               
+       ret = sscanf(url, "%10[^:]://%254[^:/]:%d", protocol, tmp_host, &tmp_port);
+       if (ret < 2) {
+               return NT_STATUS_INVALID_PARAMETER;
+       }
 
-               if (e->msgid == msgid) {
-                       struct ldap_message *result = e->msg;
-                       DLIST_REMOVE(conn->outstanding, e);
-                       SAFE_FREE(e);
-                       return result;
-               }
+       if (strequal(protocol, "ldap")) {
+               *port = 389;
+               *ldaps = false;
+       } else if (strequal(protocol, "ldaps")) {
+               *port = 636;
+               *ldaps = true;
+       } else {
+               DEBUG(0, ("unrecognised ldap protocol (%s)!\n", protocol));
+               return NT_STATUS_PROTOCOL_UNREACHABLE;
        }
 
-       return NULL;
+       if (tmp_port != 0)
+               *port = tmp_port;
+
+       *host = talloc_strdup(mem_ctx, tmp_host);
+       NT_STATUS_HAVE_NO_MEMORY(*host);
+
+       return NT_STATUS_OK;
 }
 
-static void add_search_entry(struct ldap_connection *conn,
-                            struct ldap_message *msg)
-{
-       struct ldap_queue_entry *e = malloc_p(struct ldap_queue_entry);
+/*
+  connect to a ldap server
+*/
 
-       if (e == NULL)
-               return;
+struct ldap_connect_state {
+       struct composite_context *ctx;
+       struct ldap_connection *conn;
+};
 
-       e->msg = msg;
-       DLIST_ADD_END(conn->search_entries, e, struct ldap_queue_entry *);
-       return;
-}
+static void ldap_connect_recv_unix_conn(struct composite_context *ctx);
+static void ldap_connect_recv_tcp_conn(struct composite_context *ctx);
 
-static void fill_outstanding_request(struct ldap_connection *conn,
-                                    struct ldap_message *msg)
+_PUBLIC_ struct composite_context *ldap_connect_send(struct ldap_connection *conn,
+                                           const char *url)
 {
-       struct ldap_queue_entry *e;
+       struct composite_context *result, *ctx;
+       struct ldap_connect_state *state;
+       char protocol[11];
+       int ret;
 
-       for (e = conn->outstanding; e != NULL; e = e->next) {
-               if (e->msgid == msg->messageid) {
-                       e->msg = msg;
-                       return;
-               }
-       }
+       result = talloc_zero(conn, struct composite_context);
+       if (result == NULL) goto failed;
+       result->state = COMPOSITE_STATE_IN_PROGRESS;
+       result->async.fn = NULL;
+       result->event_ctx = conn->event.event_ctx;
 
-       /* This reply has not been expected, destroy the incoming msg */
-       talloc_free(msg);
-       return;
-}
+       state = talloc(result, struct ldap_connect_state);
+       if (state == NULL) goto failed;
+       state->ctx = result;
+       result->private_data = state;
 
-struct ldap_message *ldap_receive(struct ldap_connection *conn, int msgid,
-                                 const struct timeval *endtime)
-{
-       struct ldap_message *result = recv_from_queue(conn, msgid);
+       state->conn = conn;
 
-       if (result != NULL)
-               return result;
+       if (conn->reconnect.url == NULL) {
+               conn->reconnect.url = talloc_strdup(conn, url);
+               if (conn->reconnect.url == NULL) goto failed;
+       }
 
-       while (True) {
-               struct asn1_data data;
-               BOOL res;
+       /* Paranoia check */
+       SMB_ASSERT(sizeof(protocol)>10);
 
-               result = new_ldap_message(conn);
+       ret = sscanf(url, "%10[^:]://", protocol);
+       if (ret < 1) {
+               return NULL;
+       }
 
-               if (!asn1_read_sequence_until(conn->sock, &data, endtime))
+       if (strequal(protocol, "ldapi")) {
+               struct socket_address *unix_addr;
+               char path[1025];
+       
+               NTSTATUS status = socket_create("unix", SOCKET_TYPE_STREAM, &conn->sock, 0);
+               if (!NT_STATUS_IS_OK(status)) {
                        return NULL;
+               }
+               talloc_steal(conn, conn->sock);
+               SMB_ASSERT(sizeof(protocol)>10);
+               SMB_ASSERT(sizeof(path)>1024);
+       
+               /* LDAPI connections are to localhost, so give the local host name as the target for gensec */
+               conn->host = talloc_asprintf(conn, "%s.%s", lp_netbios_name(conn->lp_ctx),  lp_realm(conn->lp_ctx));
+               if (composite_nomem(conn->host, state->ctx)) {
+                       return result;
+               }
 
-               res = ldap_decode(&data, result);
-               asn1_free(&data);
+               /* The %c specifier doesn't null terminate :-( */
+               ZERO_STRUCT(path);
+               ret = sscanf(url, "%10[^:]://%1025c", protocol, path);
+               if (ret < 2) {
+                       composite_error(state->ctx, NT_STATUS_INVALID_PARAMETER);
+                       return result;
+               }
 
-               if (!res)
+               rfc1738_unescape(path);
+       
+               unix_addr = socket_address_from_strings(conn, conn->sock->backend_name, 
+                                                       path, 0);
+               if (!unix_addr) {
                        return NULL;
+               }
 
-               if (result->messageid == msgid)
+               ctx = socket_connect_send(conn->sock, NULL, unix_addr, 
+                                         0, lp_resolve_context(conn->lp_ctx), conn->event.event_ctx);
+               ctx->async.fn = ldap_connect_recv_unix_conn;
+               ctx->async.private_data = state;
+               return result;
+       } else {
+               NTSTATUS status = ldap_parse_basic_url(conn, url, &conn->host,
+                                                         &conn->port, &conn->ldaps);
+               if (!NT_STATUS_IS_OK(state->ctx->status)) {
+                       composite_error(state->ctx, status);
                        return result;
-
-               if (result->type == LDAP_TAG_SearchResultEntry) {
-                       add_search_entry(conn, result);
-               } else {
-                       fill_outstanding_request(conn, result);
                }
-       }
+               
+               ctx = socket_connect_multi_send(state, conn->host, 1, &conn->port,
+                                               lp_resolve_context(conn->lp_ctx), conn->event.event_ctx);
+               if (ctx == NULL) goto failed;
 
+               ctx->async.fn = ldap_connect_recv_tcp_conn;
+               ctx->async.private_data = state;
+               return result;
+       }
+ failed:
+       talloc_free(result);
        return NULL;
 }
 
-struct ldap_message *ldap_transaction(struct ldap_connection *conn,
-                                     struct ldap_message *request)
+static void ldap_connect_got_sock(struct composite_context *ctx, 
+                                 struct ldap_connection *conn) 
 {
-       if (!ldap_send_msg(conn, request, NULL))
-               return False;
-
-       return ldap_receive(conn, request->messageid, NULL);
-}
+       /* setup a handler for events on this socket */
+       conn->event.fde = event_add_fd(conn->event.event_ctx, conn->sock, 
+                                      socket_get_fd(conn->sock), 
+                                      EVENT_FD_READ | EVENT_FD_AUTOCLOSE, ldap_io_handler, conn);
+       if (conn->event.fde == NULL) {
+               composite_error(ctx, NT_STATUS_INTERNAL_ERROR);
+               return;
+       }
 
-int ldap_bind_simple(struct ldap_connection *conn, const char *userdn, const char *password)
-{
-       struct ldap_message *response;
-       struct ldap_message *msg;
-       const char *dn, *pw;
-       int result = LDAP_OTHER;
+       socket_set_flags(conn->sock, SOCKET_FLAG_NOCLOSE);
 
-       if (conn == NULL)
-               return result;
+       talloc_steal(conn, conn->sock);
+       if (conn->ldaps) {
+               struct socket_context *tls_socket;
+               struct socket_context *tmp_socket;
+               char *cafile = private_path(conn->sock, conn->lp_ctx, lp_tls_cafile(conn->lp_ctx));
 
-       if (userdn) {
-               dn = userdn;
-       } else {
-               if (conn->auth_dn) {
-                       dn = conn->auth_dn;
-               } else {
-                       dn = "";
+               if (!cafile || !*cafile) {
+                       talloc_free(conn->sock);
+                       return;
                }
-       }
 
-       if (password) {
-               pw = password;
-       } else {
-               if (conn->simple_pw) {
-                       pw = conn->simple_pw;
-               } else {
-                       pw = "";
+               tls_socket = tls_init_client(conn->sock, conn->event.fde, cafile);
+               talloc_free(cafile);
+
+               if (tls_socket == NULL) {
+                       talloc_free(conn->sock);
+                       return;
                }
-       }
 
-       msg =  new_ldap_simple_bind_msg(conn, dn, pw);
-       if (!msg)
-               return result;
+               /* the original socket, must become a child of the tls socket */
+               tmp_socket = conn->sock;
+               conn->sock = talloc_steal(conn, tls_socket);
+               talloc_steal(conn->sock, tmp_socket);
+       }
 
-       response = ldap_transaction(conn, msg);
-       if (!response) {
-               talloc_free(msg);
-               return result;
+       conn->packet = packet_init(conn);
+       if (conn->packet == NULL) {
+               talloc_free(conn->sock);
+               return;
        }
-               
-       result = response->r.BindResponse.response.resultcode;
 
-       talloc_free(msg);
-       talloc_free(response);
+       packet_set_private(conn->packet, conn);
+       packet_set_socket(conn->packet, conn->sock);
+       packet_set_callback(conn->packet, ldap_recv_handler);
+       packet_set_full_request(conn->packet, ldap_full_packet);
+       packet_set_error_handler(conn->packet, ldap_error_handler);
+       packet_set_event_context(conn->packet, conn->event.event_ctx);
+       packet_set_fde(conn->packet, conn->event.fde);
+/*     packet_set_serialise(conn->packet); */
 
-       return result;
+       composite_done(ctx);
 }
 
-int ldap_bind_sasl(struct ldap_connection *conn, const char *username, const char *domain, const char *password)
+static void ldap_connect_recv_tcp_conn(struct composite_context *ctx)
 {
-       NTSTATUS status;
-       TALLOC_CTX *mem_ctx = NULL;
-       struct ldap_message *response;
-       struct ldap_message *msg;
-       DATA_BLOB input = data_blob(NULL, 0);
-       DATA_BLOB output = data_blob(NULL, 0);
-       int result = LDAP_OTHER;
-
-       if (conn == NULL)
-               return result;
-
-       status = gensec_client_start(conn, &conn->gensec);
+       struct ldap_connect_state *state =
+               talloc_get_type(ctx->async.private_data,
+                               struct ldap_connect_state);
+       struct ldap_connection *conn = state->conn;
+       uint16_t port;
+       NTSTATUS status = socket_connect_multi_recv(ctx, state, &conn->sock,
+                                                      &port);
        if (!NT_STATUS_IS_OK(status)) {
-               DEBUG(0, ("Failed to start GENSEC engine (%s)\n", nt_errstr(status)));
-               return result;
-       }
-
-       gensec_want_feature(conn->gensec, GENSEC_FEATURE_SIGN | GENSEC_FEATURE_SEAL);
-
-       status = gensec_set_domain(conn->gensec, domain);
-       if (!NT_STATUS_IS_OK(status)) {
-               DEBUG(1, ("Failed to start set GENSEC client domain to %s: %s\n", 
-                         domain, nt_errstr(status)));
-               goto done;
+               composite_error(state->ctx, status);
+               return;
        }
 
-       status = gensec_set_username(conn->gensec, username);
-       if (!NT_STATUS_IS_OK(status)) {
-               DEBUG(1, ("Failed to start set GENSEC client username to %s: %s\n", 
-                         username, nt_errstr(status)));
-               goto done;
-       }
+       ldap_connect_got_sock(state->ctx, conn);
+}
 
-       status = gensec_set_password(conn->gensec, password);
-       if (!NT_STATUS_IS_OK(status)) {
-               DEBUG(1, ("Failed to start set GENSEC client password: %s\n", 
-                         nt_errstr(status)));
-               goto done;
-       }
+static void ldap_connect_recv_unix_conn(struct composite_context *ctx)
+{
+       struct ldap_connect_state *state =
+               talloc_get_type(ctx->async.private_data,
+                               struct ldap_connect_state);
+       struct ldap_connection *conn = state->conn;
 
-       status = gensec_set_target_hostname(conn->gensec, conn->host);
-       if (!NT_STATUS_IS_OK(status)) {
-               DEBUG(1, ("Failed to start set GENSEC target hostname: %s\n", 
-                         nt_errstr(status)));
-               goto done;
-       }
+       NTSTATUS status = socket_connect_recv(ctx);
 
-       status = gensec_set_target_service(conn->gensec, "ldap");
-       if (!NT_STATUS_IS_OK(status)) {
-               DEBUG(1, ("Failed to start set GENSEC target service: %s\n", 
-                         nt_errstr(status)));
-               goto done;
+       if (!NT_STATUS_IS_OK(state->ctx->status)) {
+               composite_error(state->ctx, status);
+               return;
        }
 
-       status = gensec_start_mech_by_sasl_name(conn->gensec, "GSS-SPNEGO");
-       if (!NT_STATUS_IS_OK(status)) {
-               DEBUG(1, ("Failed to start set GENSEC client SPNEGO mechanism: %s\n",
-                         nt_errstr(status)));
-               goto done;
-       }
+       ldap_connect_got_sock(state->ctx, conn);
+}
 
-       mem_ctx = talloc_init("ldap_bind_sasl");
-       if (!mem_ctx)
-               goto done;
+_PUBLIC_ NTSTATUS ldap_connect_recv(struct composite_context *ctx)
+{
+       NTSTATUS status = composite_wait(ctx);
+       talloc_free(ctx);
+       return status;
+}
 
-       status = gensec_update(conn->gensec, mem_ctx,
-                              input,
-                              &output);
+_PUBLIC_ NTSTATUS ldap_connect(struct ldap_connection *conn, const char *url)
+{
+       struct composite_context *ctx = ldap_connect_send(conn, url);
+       return ldap_connect_recv(ctx);
+}
 
-       while(1) {
-               if (NT_STATUS_IS_OK(status) && output.length == 0) {
-                       break;
-               }
-               if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED) && !NT_STATUS_IS_OK(status)) {
-                       break;
-               }
+/* set reconnect parameters */
 
-               msg =  new_ldap_sasl_bind_msg(conn, "GSS-SPNEGO", &output);
-               if (!msg)
-                       goto done;
+_PUBLIC_ void ldap_set_reconn_params(struct ldap_connection *conn, int max_retries)
+{
+       if (conn) {
+               conn->reconnect.max_retries = max_retries;
+               conn->reconnect.retries = 0;
+               conn->reconnect.previous = time(NULL);
+       }
+}
 
-               response = ldap_transaction(conn, msg);
-               talloc_free(msg);
+/* Actually this function is NOT ASYNC safe, FIXME? */
+static void ldap_reconnect(struct ldap_connection *conn)
+{
+       NTSTATUS status;
+       time_t now = time(NULL);
 
-               if (!response) {
-                       goto done;
-               }
+       /* do we have set up reconnect ? */
+       if (conn->reconnect.max_retries == 0) return;
 
-               result = response->r.BindResponse.response.resultcode;
+       /* is the retry time expired ? */
+       if (now > conn->reconnect.previous + 30) {
+               conn->reconnect.retries = 0;
+               conn->reconnect.previous = now;
+       }
 
-               if (result != LDAP_SUCCESS && result != LDAP_SASL_BIND_IN_PROGRESS) {
-                       break;
-               }
+       /* are we reconnectind too often and too fast? */
+       if (conn->reconnect.retries > conn->reconnect.max_retries) return;
 
-               if (!NT_STATUS_IS_OK(status)) {
-                       status = gensec_update(conn->gensec, mem_ctx,
-                                              response->r.BindResponse.SASL.secblob,
-                                              &output);
-               } else {
-                       output.length = 0;
-               }
+       /* keep track of the number of reconnections */
+       conn->reconnect.retries++;
 
-               talloc_free(response);
+       /* reconnect */
+       status = ldap_connect(conn, conn->reconnect.url);
+       if ( ! NT_STATUS_IS_OK(status)) {
+               return;
        }
 
-done:
-       if (mem_ctx)
-               talloc_destroy(mem_ctx);
-
-       return result;
+       /* rebind */
+       status = ldap_rebind(conn);
+       if ( ! NT_STATUS_IS_OK(status)) {
+               ldap_connection_dead(conn);
+       }
 }
 
-struct ldap_connection *ldap_setup_connection(TALLOC_CTX *mem_ctx, const char *url, 
-                                               const char *userdn, const char *password)
+/* destroy an open ldap request */
+static int ldap_request_destructor(struct ldap_request *req)
 {
-       struct ldap_connection *conn;
-       int result;
-
-       conn =ldap_connect(mem_ctx, url);
-       if (!conn) {
-               return NULL;
-       }
-
-       result = ldap_bind_simple(conn, userdn, password);
-       if (result != LDAP_SUCCESS) {
-               talloc_free(conn);
-               return NULL;
+       if (req->state == LDAP_REQUEST_PENDING) {
+               DLIST_REMOVE(req->conn->pending, req);
        }
-
-       return conn;
+       return 0;
 }
 
-struct ldap_connection *ldap_setup_connection_with_sasl(TALLOC_CTX *mem_ctx, const char *url,
-                                                       const char *username, const char *domain, const char *password)
+/*
+  called on timeout of a ldap request
+*/
+static void ldap_request_timeout(struct event_context *ev, struct timed_event *te, 
+                                     struct timeval t, void *private_data)
 {
-       struct ldap_connection *conn;
-       int result;
-
-       conn =ldap_connect(mem_ctx, url);
-       if (!conn) {
-               return NULL;
+       struct ldap_request *req = talloc_get_type(private_data, struct ldap_request);
+       req->status = NT_STATUS_IO_TIMEOUT;
+       if (req->state == LDAP_REQUEST_PENDING) {
+               DLIST_REMOVE(req->conn->pending, req);
        }
-
-       result = ldap_bind_sasl(conn, username, domain, password);
-       if (result != LDAP_SUCCESS) {
-               talloc_free(conn);
-               return NULL;
+       req->state = LDAP_REQUEST_DONE;
+       if (req->async.fn) {
+               req->async.fn(req);
        }
-
-       return conn;
 }
 
-BOOL ldap_abandon_message(struct ldap_connection *conn, int msgid,
-                                const struct timeval *endtime)
-{
-       struct ldap_message *msg = new_ldap_message(conn);
-       BOOL result;
-
-       if (msg == NULL)
-               return False;
 
-       msg->type = LDAP_TAG_AbandonRequest;
-       msg->r.AbandonRequest.messageid = msgid;
-
-       result = ldap_send_msg(conn, msg, endtime);
-       talloc_free(msg);
-       return result;
-}
-
-BOOL ldap_setsearchent(struct ldap_connection *conn, struct ldap_message *msg,
-                      const struct timeval *endtime)
+/*
+  called on completion of a one-way ldap request
+*/
+static void ldap_request_complete(struct event_context *ev, struct timed_event *te, 
+                                 struct timeval t, void *private_data)
 {
-       if ((conn->searchid != 0) &&
-           (!ldap_abandon_message(conn, conn->searchid, endtime)))
-               return False;
-
-       conn->searchid = conn->next_msgid;
-       return ldap_send_msg(conn, msg, endtime);
+       struct ldap_request *req = talloc_get_type(private_data, struct ldap_request);
+       if (req->async.fn) {
+               req->async.fn(req);
+       }
 }
 
-struct ldap_message *ldap_getsearchent(struct ldap_connection *conn,
-                                      const struct timeval *endtime)
+/*
+  send a ldap message - async interface
+*/
+_PUBLIC_ struct ldap_request *ldap_request_send(struct ldap_connection *conn,
+                                      struct ldap_message *msg)
 {
-       struct ldap_message *result;
+       struct ldap_request *req;
+       NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
 
-       if (conn->search_entries != NULL) {
-               struct ldap_queue_entry *e = conn->search_entries;
+       req = talloc_zero(conn, struct ldap_request);
+       if (req == NULL) return NULL;
 
-               result = e->msg;
-               DLIST_REMOVE(conn->search_entries, e);
-               SAFE_FREE(e);
-               return result;
+       if (conn->sock == NULL) {
+               status = NT_STATUS_INVALID_CONNECTION;
+               goto failed;
        }
 
-       result = ldap_receive(conn, conn->searchid, endtime);
-       if (!result) {
-               return NULL;
+       req->state       = LDAP_REQUEST_SEND;
+       req->conn        = conn;
+       req->messageid   = conn->next_messageid++;
+       if (conn->next_messageid == 0) {
+               conn->next_messageid = 1;
        }
-
-       if (result->type == LDAP_TAG_SearchResultEntry)
-               return result;
-
-       if (result->type == LDAP_TAG_SearchResultDone) {
-               /* TODO: Handle Paged Results */
-               talloc_free(result);
-               return NULL;
+       req->type        = msg->type;
+       if (req->messageid == -1) {
+               goto failed;
        }
 
-       /* TODO: Handle Search References here */
-       return NULL;
-}
-
-void ldap_endsearchent(struct ldap_connection *conn,
-                      const struct timeval *endtime)
-{
-       struct ldap_queue_entry *e;
+       talloc_set_destructor(req, ldap_request_destructor);
 
-       e = conn->search_entries;
+       msg->messageid = req->messageid;
 
-       while (e != NULL) {
-               struct ldap_queue_entry *next = e->next;
-               DLIST_REMOVE(conn->search_entries, e);
-               SAFE_FREE(e);
-               e = next;
+       if (!ldap_encode(msg, &req->data, req)) {
+               status = NT_STATUS_INTERNAL_ERROR;
+               goto failed;            
        }
-}
 
-struct ldap_message *ldap_searchone(struct ldap_connection *conn,
-                                   struct ldap_message *msg,
-                                   const struct timeval *endtime)
-{
-       struct ldap_message *res1, *res2 = NULL;
-       if (!ldap_setsearchent(conn, msg, endtime))
-               return NULL;
+       status = packet_send(conn->packet, req->data);
+       if (!NT_STATUS_IS_OK(status)) {
+               goto failed;
+       }
 
-       res1 = ldap_getsearchent(conn, endtime);
+       /* some requests don't expect a reply, so don't add those to the
+          pending queue */
+       if (req->type == LDAP_TAG_AbandonRequest ||
+           req->type == LDAP_TAG_UnbindRequest) {
+               req->status = NT_STATUS_OK;
+               req->state = LDAP_REQUEST_DONE;
+               /* we can't call the async callback now, as it isn't setup, so
+                  call it as next event */
+               event_add_timed(conn->event.event_ctx, req, timeval_zero(),
+                               ldap_request_complete, req);
+               return req;
+       }
 
-       if (res1 != NULL)
-               res2 = ldap_getsearchent(conn, endtime);
+       req->state = LDAP_REQUEST_PENDING;
+       DLIST_ADD(conn->pending, req);
 
-       ldap_endsearchent(conn, endtime);
+       /* put a timeout on the request */
+       req->time_event = event_add_timed(conn->event.event_ctx, req, 
+                                         timeval_current_ofs(conn->timeout, 0),
+                                         ldap_request_timeout, req);
 
-       if (res1 == NULL)
-               return NULL;
+       return req;
 
-       if (res2 != NULL) {
-               /* More than one entry */
-               talloc_free(res1);
-               talloc_free(res2);
-               return NULL;
-       }
+failed:
+       req->status = status;
+       req->state = LDAP_REQUEST_ERROR;
+       event_add_timed(conn->event.event_ctx, req, timeval_zero(),
+                       ldap_request_complete, req);
 
-       return res1;
+       return req;
 }
 
-BOOL ldap_find_single_value(struct ldap_message *msg, const char *attr,
-                           DATA_BLOB *value)
-{
-       int i;
-       struct ldap_SearchResEntry *r = &msg->r.SearchResultEntry;
-
-       if (msg->type != LDAP_TAG_SearchResultEntry)
-               return False;
-
-       for (i=0; i<r->num_attributes; i++) {
-               if (strequal(attr, r->attributes[i].name)) {
-                       if (r->attributes[i].num_values != 1)
-                               return False;
 
-                       *value = r->attributes[i].values[0];
-                       return True;
+/*
+  wait for a request to complete
+  note that this does not destroy the request
+*/
+_PUBLIC_ NTSTATUS ldap_request_wait(struct ldap_request *req)
+{
+       while (req->state < LDAP_REQUEST_DONE) {
+               if (event_loop_once(req->conn->event.event_ctx) != 0) {
+                       req->status = NT_STATUS_UNEXPECTED_NETWORK_ERROR;
+                       break;
                }
        }
-       return False;
+       return req->status;
 }
 
-BOOL ldap_find_single_string(struct ldap_message *msg, const char *attr,
-                            TALLOC_CTX *mem_ctx, char **value)
+
+/*
+  a mapping of ldap response code to strings
+*/
+static const struct {
+       enum ldap_result_code code;
+       const char *str;
+} ldap_code_map[] = {
+#define _LDAP_MAP_CODE(c) { c, #c }
+       _LDAP_MAP_CODE(LDAP_SUCCESS),
+       _LDAP_MAP_CODE(LDAP_OPERATIONS_ERROR),
+       _LDAP_MAP_CODE(LDAP_PROTOCOL_ERROR),
+       _LDAP_MAP_CODE(LDAP_TIME_LIMIT_EXCEEDED),
+       _LDAP_MAP_CODE(LDAP_SIZE_LIMIT_EXCEEDED),
+       _LDAP_MAP_CODE(LDAP_COMPARE_FALSE),
+       _LDAP_MAP_CODE(LDAP_COMPARE_TRUE),
+       _LDAP_MAP_CODE(LDAP_AUTH_METHOD_NOT_SUPPORTED),
+       _LDAP_MAP_CODE(LDAP_STRONG_AUTH_REQUIRED),
+       _LDAP_MAP_CODE(LDAP_REFERRAL),
+       _LDAP_MAP_CODE(LDAP_ADMIN_LIMIT_EXCEEDED),
+       _LDAP_MAP_CODE(LDAP_UNAVAILABLE_CRITICAL_EXTENSION),
+       _LDAP_MAP_CODE(LDAP_CONFIDENTIALITY_REQUIRED),
+       _LDAP_MAP_CODE(LDAP_SASL_BIND_IN_PROGRESS),
+       _LDAP_MAP_CODE(LDAP_NO_SUCH_ATTRIBUTE),
+       _LDAP_MAP_CODE(LDAP_UNDEFINED_ATTRIBUTE_TYPE),
+       _LDAP_MAP_CODE(LDAP_INAPPROPRIATE_MATCHING),
+       _LDAP_MAP_CODE(LDAP_CONSTRAINT_VIOLATION),
+       _LDAP_MAP_CODE(LDAP_ATTRIBUTE_OR_VALUE_EXISTS),
+       _LDAP_MAP_CODE(LDAP_INVALID_ATTRIBUTE_SYNTAX),
+       _LDAP_MAP_CODE(LDAP_NO_SUCH_OBJECT),
+       _LDAP_MAP_CODE(LDAP_ALIAS_PROBLEM),
+       _LDAP_MAP_CODE(LDAP_INVALID_DN_SYNTAX),
+       _LDAP_MAP_CODE(LDAP_ALIAS_DEREFERENCING_PROBLEM),
+       _LDAP_MAP_CODE(LDAP_INAPPROPRIATE_AUTHENTICATION),
+       _LDAP_MAP_CODE(LDAP_INVALID_CREDENTIALS),
+       _LDAP_MAP_CODE(LDAP_INSUFFICIENT_ACCESS_RIGHTS),
+       _LDAP_MAP_CODE(LDAP_BUSY),
+       _LDAP_MAP_CODE(LDAP_UNAVAILABLE),
+       _LDAP_MAP_CODE(LDAP_UNWILLING_TO_PERFORM),
+       _LDAP_MAP_CODE(LDAP_LOOP_DETECT),
+       _LDAP_MAP_CODE(LDAP_NAMING_VIOLATION),
+       _LDAP_MAP_CODE(LDAP_OBJECT_CLASS_VIOLATION),
+       _LDAP_MAP_CODE(LDAP_NOT_ALLOWED_ON_NON_LEAF),
+       _LDAP_MAP_CODE(LDAP_NOT_ALLOWED_ON_RDN),
+       _LDAP_MAP_CODE(LDAP_ENTRY_ALREADY_EXISTS),
+       _LDAP_MAP_CODE(LDAP_OBJECT_CLASS_MODS_PROHIBITED),
+       _LDAP_MAP_CODE(LDAP_AFFECTS_MULTIPLE_DSAS),
+       _LDAP_MAP_CODE(LDAP_OTHER)
+};
+
+/*
+  used to setup the status code from a ldap response
+*/
+_PUBLIC_ NTSTATUS ldap_check_response(struct ldap_connection *conn, struct ldap_Result *r)
 {
-       DATA_BLOB blob;
+       int i;
+       const char *codename = "unknown";
 
-       if (!ldap_find_single_value(msg, attr, &blob))
-               return False;
+       if (r->resultcode == LDAP_SUCCESS) {
+               return NT_STATUS_OK;
+       }
 
-       *value = talloc(mem_ctx, blob.length+1);
+       if (conn->last_error) {
+               talloc_free(conn->last_error);
+       }
 
-       if (*value == NULL)
-               return False;
+       for (i=0;i<ARRAY_SIZE(ldap_code_map);i++) {
+               if (r->resultcode == ldap_code_map[i].code) {
+                       codename = ldap_code_map[i].str;
+                       break;
+               }
+       }
 
-       memcpy(*value, blob.data, blob.length);
-       (*value)[blob.length] = '\0';
-       return True;
+       conn->last_error = talloc_asprintf(conn, "LDAP error %u %s - %s <%s> <%s>", 
+                                          r->resultcode,
+                                          codename,
+                                          r->dn?r->dn:"(NULL)", 
+                                          r->errormessage?r->errormessage:"", 
+                                          r->referral?r->referral:"");
+       
+       return NT_STATUS_LDAP(r->resultcode);
 }
 
-BOOL ldap_find_single_int(struct ldap_message *msg, const char *attr,
-                         int *value)
+/*
+  return error string representing the last error
+*/
+_PUBLIC_ const char *ldap_errstr(struct ldap_connection *conn, 
+                       TALLOC_CTX *mem_ctx, 
+                       NTSTATUS status)
 {
-       DATA_BLOB blob;
-       char *val;
-       int errno_save;
-       BOOL res;
-
-       if (!ldap_find_single_value(msg, attr, &blob))
-               return False;
+       if (NT_STATUS_IS_LDAP(status) && conn->last_error != NULL) {
+               return talloc_strdup(mem_ctx, conn->last_error);
+       }
+       return talloc_asprintf(mem_ctx, "LDAP client internal error: %s", nt_errstr(status));
+}
 
-       val = malloc(blob.length+1);
-       if (val == NULL)
-               return False;
 
-       memcpy(val, blob.data, blob.length);
-       val[blob.length] = '\0';
+/*
+  return the Nth result message, waiting if necessary
+*/
+_PUBLIC_ NTSTATUS ldap_result_n(struct ldap_request *req, int n, struct ldap_message **msg)
+{
+       *msg = NULL;
 
-       errno_save = errno;
-       errno = 0;
+       NT_STATUS_HAVE_NO_MEMORY(req);
 
-       *value = strtol(val, NULL, 10);
+       while (req->state < LDAP_REQUEST_DONE && n >= req->num_replies) {
+               if (event_loop_once(req->conn->event.event_ctx) != 0) {
+                       return NT_STATUS_UNEXPECTED_NETWORK_ERROR;
+               }
+       }
 
-       res = (errno == 0);
+       if (n < req->num_replies) {
+               *msg = req->replies[n];
+               return NT_STATUS_OK;
+       }
 
-       free(val);
-       errno = errno_save;
+       if (!NT_STATUS_IS_OK(req->status)) {
+               return req->status;
+       }
 
-       return res;
+       return NT_STATUS_NO_MORE_ENTRIES;
 }
 
-int ldap_error(struct ldap_connection *conn)
+
+/*
+  return a single result message, checking if it is of the expected LDAP type
+*/
+_PUBLIC_ NTSTATUS ldap_result_one(struct ldap_request *req, struct ldap_message **msg, int type)
 {
-       return 0;
+       NTSTATUS status;
+       status = ldap_result_n(req, 0, msg);
+       if (!NT_STATUS_IS_OK(status)) {
+               return status;
+       }
+       if ((*msg)->type != type) {
+               *msg = NULL;
+               return NT_STATUS_UNEXPECTED_NETWORK_ERROR;
+       }
+       return status;
 }
 
-NTSTATUS ldap2nterror(int ldaperror)
+/*
+  a simple ldap transaction, for single result requests that only need a status code
+  this relies on single valued requests having the response type == request type + 1
+*/
+_PUBLIC_ NTSTATUS ldap_transaction(struct ldap_connection *conn, struct ldap_message *msg)
 {
-       return NT_STATUS_OK;
+       struct ldap_request *req = ldap_request_send(conn, msg);
+       struct ldap_message *res;
+       NTSTATUS status;
+       status = ldap_result_n(req, 0, &res);
+       if (!NT_STATUS_IS_OK(status)) {
+               talloc_free(req);
+               return status;
+       }
+       if (res->type != msg->type + 1) {
+               talloc_free(req);
+               return NT_STATUS_LDAP(LDAP_PROTOCOL_ERROR);
+       }
+       status = ldap_check_response(conn, &res->r.GeneralResult);
+       talloc_free(req);
+       return status;
 }