r26192: Handle, test and implement the style of extended_dn requiest that MMC uses.
[ab/samba.git/.git] / source4 / libcli / ldap / ldap_client.c
index 88c84d880ba3300b103a9fc2aae3346150efba63..41e9c3719610135d0c15768435fd5fcd6c7a2a5b 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 "libcli/util/asn_1.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_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"
+
+
+/*
+  create a new ldap_connection stucture. The event context is optional
+*/
+struct ldap_connection *ldap4_new_connection(TALLOC_CTX *mem_ctx, 
+                                           struct event_context *ev)
 {
-       struct ldap_message *res;
+       struct ldap_connection *conn;
 
-       res = new_ldap_message(conn);
-       if (!res) {
+       conn = talloc_zero(mem_ctx, struct ldap_connection);
+       if (conn == 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) {
-               return NULL;
+       if (ev == NULL) {
+               ev = event_context_init(conn);
+               if (ev == NULL) {
+                       talloc_free(conn);
+                       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;
+       /* set a reasonable request timeout */
+       conn->timeout = 60;
+
+       /* explicitly avoid reconnections by default */
+       conn->reconnect.max_retries = 0;
+       
+       return conn;
 }
 
-static struct ldap_message *new_ldap_sasl_bind_msg(struct ldap_connection *conn, const char *sasl_mechanism, DATA_BLOB *secblob)
+/*
+  the connection is dead
+*/
+static void ldap_connection_dead(struct ldap_connection *conn)
 {
-       struct ldap_message *res;
-
-       res = new_ldap_message(conn);
-       if (!res) {
-               return NULL;
+       struct ldap_request *req;
+
+       /* 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);
+               }
        }
 
-       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;
+       talloc_free(conn->sock);  /* this will also free event.fde */
+       talloc_free(conn->packet);
+       conn->sock = NULL;
+       conn->event.fde = NULL;
+       conn->packet = NULL;
 }
 
-static struct ldap_connection *new_ldap_connection(TALLOC_CTX *mem_ctx)
-{
-       struct ldap_connection *result;
-
-       result = talloc_p(mem_ctx, struct ldap_connection);
-
-       if (!result) {
-               return NULL;
-       }
+static void ldap_reconnect(struct ldap_connection *conn);
 
-       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;
+/*
+  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) {
-
-               if (e->msgid == msgid) {
-                       struct ldap_message *result = e->msg;
-                       DLIST_REMOVE(conn->outstanding, e);
-                       SAFE_FREE(e);
-                       return result;
-               }
+       /* 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;
        }
 
-       return NULL;
-}
+       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;
+       }
 
-static void add_search_entry(struct ldap_connection *conn,
-                            struct ldap_message *msg)
-{
-       struct ldap_queue_entry *e = malloc_p(struct ldap_queue_entry);
+       if (tmp_port != 0)
+               *port = tmp_port;
 
-       if (e == NULL)
-               return;
+       *host = talloc_strdup(mem_ctx, tmp_host);
+       NT_STATUS_HAVE_NO_MEMORY(*host);
 
-       e->msg = msg;
-       DLIST_ADD_END(conn->search_entries, e, struct ldap_queue_entry *);
-       return;
+       return NT_STATUS_OK;
 }
 
-static void fill_outstanding_request(struct ldap_connection *conn,
-                                    struct ldap_message *msg)
-{
-       struct ldap_queue_entry *e;
+/*
+  connect to a ldap server
+*/
 
-       for (e = conn->outstanding; e != NULL; e = e->next) {
-               if (e->msgid == msg->messageid) {
-                       e->msg = msg;
-                       return;
-               }
-       }
+struct ldap_connect_state {
+       struct composite_context *ctx;
+       struct ldap_connection *conn;
+};
 
-       /* This reply has not been expected, destroy the incoming msg */
-       talloc_free(msg);
-       return;
-}
+static void ldap_connect_recv_unix_conn(struct composite_context *ctx);
+static void ldap_connect_recv_tcp_conn(struct composite_context *ctx);
 
-struct ldap_message *ldap_receive(struct ldap_connection *conn, int msgid,
-                                 const struct timeval *endtime)
+struct composite_context *ldap_connect_send(struct ldap_connection *conn,
+                                           const char *url)
 {
-       struct ldap_message *result = recv_from_queue(conn, msgid);
+       struct composite_context *result, *ctx;
+       struct ldap_connect_state *state;
+       char protocol[11];
+       int ret;
 
-       if (result != NULL)
-               return result;
+       result = talloc_zero(NULL, 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;
 
-       while (True) {
-               struct asn1_data data;
-               BOOL res;
+       state = talloc(result, struct ldap_connect_state);
+       if (state == NULL) goto failed;
+       state->ctx = result;
+       result->private_data = state;
 
-               result = new_ldap_message(conn);
+       state->conn = conn;
 
-               if (!asn1_read_sequence_until(conn->sock, &data, endtime))
-                       return NULL;
+       if (conn->reconnect.url == NULL) {
+               conn->reconnect.url = talloc_strdup(conn, url);
+               if (conn->reconnect.url == NULL) goto failed;
+       }
 
-               res = ldap_decode(&data, result);
-               asn1_free(&data);
+       /* Paranoia check */
+       SMB_ASSERT(sizeof(protocol)>10);
 
-               if (!res)
-                       return NULL;
+       ret = sscanf(url, "%10[^:]://", protocol);
+       if (ret < 1) {
+               return NULL;
+       }
 
-               if (result->messageid == msgid)
+       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);
+       
+               /* 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 (result->type == LDAP_TAG_SearchResultEntry) {
-                       add_search_entry(conn, result);
-               } else {
-                       fill_outstanding_request(conn, result);
                }
-       }
-
-       return NULL;
-}
-
-struct ldap_message *ldap_transaction(struct ldap_connection *conn,
-                                     struct ldap_message *request)
-{
-       if (!ldap_send_msg(conn, request, NULL))
-               return False;
-
-       return ldap_receive(conn, request->messageid, NULL);
-}
-
-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;
 
-       if (conn == NULL)
-               return result;
-
-       if (userdn) {
-               dn = userdn;
-       } else {
-               if (conn->auth_dn) {
-                       dn = conn->auth_dn;
-               } else {
-                       dn = "";
+               rfc1738_unescape(path);
+       
+               unix_addr = socket_address_from_strings(conn, conn->sock->backend_name, 
+                                                       path, 0);
+               if (!unix_addr) {
+                       return NULL;
                }
-       }
 
-       if (password) {
-               pw = password;
+               ctx = socket_connect_send(conn->sock, NULL, unix_addr, 
+                                         0, conn->event.event_ctx);
+               ctx->async.fn = ldap_connect_recv_unix_conn;
+               ctx->async.private_data = state;
+               return result;
        } else {
-               if (conn->simple_pw) {
-                       pw = conn->simple_pw;
-               } else {
-                       pw = "";
+               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;
                }
-       }
-
-       msg =  new_ldap_simple_bind_msg(conn, dn, pw);
-       if (!msg)
-               return result;
+               
+               ctx = socket_connect_multi_send(state, conn->host, 1, &conn->port,
+                                               conn->event.event_ctx);
+               if (ctx == NULL) goto failed;
 
-       response = ldap_transaction(conn, msg);
-       if (!response) {
-               talloc_free(msg);
+               ctx->async.fn = ldap_connect_recv_tcp_conn;
+               ctx->async.private_data = state;
                return result;
        }
-               
-       result = response->r.BindResponse.response.resultcode;
-
-       talloc_free(msg);
-       talloc_free(response);
-
-       return result;
+ failed:
+       talloc_free(result);
+       return NULL;
 }
 
-int ldap_bind_sasl(struct ldap_connection *conn, const char *username, const char *domain, const char *password)
+static void ldap_connect_got_sock(struct composite_context *ctx, struct ldap_connection *conn) 
 {
-       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);
-       if (!NT_STATUS_IS_OK(status)) {
-               DEBUG(0, ("Failed to start GENSEC engine (%s)\n", nt_errstr(status)));
-               return result;
+       /* 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;
        }
 
-       gensec_want_feature(conn->gensec, GENSEC_WANT_SIGN | GENSEC_WANT_SEAL);
+       socket_set_flags(conn->sock, SOCKET_FLAG_NOCLOSE);
 
-       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;
+       talloc_steal(conn, conn->sock);
+       if (conn->ldaps) {
+               struct socket_context *tls_socket = tls_init_client(conn->sock, conn->event.fde);
+               if (tls_socket == NULL) {
+                       talloc_free(conn->sock);
+                       return;
+               }
+               talloc_unlink(conn, conn->sock);
+               conn->sock = tls_socket;
+               talloc_steal(conn, conn->sock);
        }
 
-       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;
+       conn->packet = packet_init(conn);
+       if (conn->packet == NULL) {
+               talloc_free(conn->sock);
+               return;
        }
 
-       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;
-       }
+       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);
 
-       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;
-       }
+       composite_done(ctx);
+}
 
-       status = gensec_start_mech_by_sasl_name(conn->gensec, "GSS-SPNEGO");
+static void ldap_connect_recv_tcp_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;
+       uint16_t port;
+       NTSTATUS status = socket_connect_multi_recv(ctx, state, &conn->sock,
+                                                      &port);
        if (!NT_STATUS_IS_OK(status)) {
-               DEBUG(1, ("Failed to start set GENSEC client SPNEGO mechanism: %s\n",
-                         nt_errstr(status)));
-               goto done;
+               composite_error(state->ctx, status);
+               return;
        }
 
-       mem_ctx = talloc_init("ldap_bind_sasl");
-       if (!mem_ctx)
-               goto done;
-
-       status = gensec_update(conn->gensec, mem_ctx,
-                              input,
-                              &output);
-
-       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;
-               }
-
-               msg =  new_ldap_sasl_bind_msg(conn, "GSS-SPNEGO", &output);
-               if (!msg)
-                       goto done;
-
-               response = ldap_transaction(conn, msg);
-               talloc_free(msg);
-
-               if (!response) {
-                       goto done;
-               }
-
-               result = response->r.BindResponse.response.resultcode;
+       ldap_connect_got_sock(state->ctx, conn);
+}
 
-               if (result != LDAP_SUCCESS && result != LDAP_SASL_BIND_IN_PROGRESS) {
-                       break;
-               }
+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_update(conn->gensec, mem_ctx,
-                                      response->r.BindResponse.SASL.secblob,
-                                      &output);
+       NTSTATUS status = socket_connect_recv(ctx);
 
-               talloc_free(response);
+       if (!NT_STATUS_IS_OK(state->ctx->status)) {
+               composite_error(state->ctx, status);
+               return;
        }
 
-done:
-       if (mem_ctx)
-               talloc_destroy(mem_ctx);
+       ldap_connect_got_sock(state->ctx, conn);
+}
 
-       return result;
+_PUBLIC_ NTSTATUS ldap_connect_recv(struct composite_context *ctx)
+{
+       NTSTATUS status = composite_wait(ctx);
+       talloc_free(ctx);
+       return status;
 }
 
-struct ldap_connection *ldap_setup_connection(TALLOC_CTX *mem_ctx, const char *url, 
-                                               const char *userdn, const char *password)
+NTSTATUS ldap_connect(struct ldap_connection *conn, const char *url)
 {
-       struct ldap_connection *conn;
-       int result;
+       struct composite_context *ctx = ldap_connect_send(conn, url);
+       return ldap_connect_recv(ctx);
+}
 
-       conn =ldap_connect(mem_ctx, url);
-       if (!conn) {
-               return NULL;
-       }
+/* set reconnect parameters */
 
-       result = ldap_bind_simple(conn, userdn, password);
-       if (result != LDAP_SUCCESS) {
-               talloc_free(conn);
-               return NULL;
+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);
        }
-
-       return conn;
 }
 
-struct ldap_connection *ldap_setup_connection_with_sasl(TALLOC_CTX *mem_ctx, const char *url,
-                                                       const char *username, const char *domain, const char *password)
+/* Actually this function is NOT ASYNC safe, FIXME? */
+static void ldap_reconnect(struct ldap_connection *conn)
 {
-       struct ldap_connection *conn;
-       int result;
+       NTSTATUS status;
+       time_t now = time(NULL);
 
-       conn =ldap_connect(mem_ctx, url);
-       if (!conn) {
-               return NULL;
-       }
+       /* do we have set up reconnect ? */
+       if (conn->reconnect.max_retries == 0) return;
 
-       result = ldap_bind_sasl(conn, username, domain, password);
-       if (result != LDAP_SUCCESS) {
-               talloc_free(conn);
-               return NULL;
+       /* is the retry time expired ? */
+       if (now > conn->reconnect.previous + 30) {
+               conn->reconnect.retries = 0;
+               conn->reconnect.previous = now;
        }
 
-       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;
+       /* are we reconnectind too often and too fast? */
+       if (conn->reconnect.retries > conn->reconnect.max_retries) return;
 
-       if (msg == NULL)
-               return False;
+       /* keep track of the number of reconnections */
+       conn->reconnect.retries++;
 
-       msg->type = LDAP_TAG_AbandonRequest;
-       msg->r.AbandonRequest.messageid = msgid;
+       /* reconnect */
+       status = ldap_connect(conn, conn->reconnect.url);
+       if ( ! NT_STATUS_IS_OK(status)) {
+               return;
+       }
 
-       result = ldap_send_msg(conn, msg, endtime);
-       talloc_free(msg);
-       return result;
+       /* rebind */
+       status = ldap_rebind(conn);
+       if ( ! NT_STATUS_IS_OK(status)) {
+               ldap_connection_dead(conn);
+       }
 }
 
-BOOL ldap_setsearchent(struct ldap_connection *conn, struct ldap_message *msg,
-                      const struct timeval *endtime)
+/* destroy an open ldap request */
+static int ldap_request_destructor(struct ldap_request *req)
 {
-       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);
+       if (req->state == LDAP_REQUEST_PENDING) {
+               DLIST_REMOVE(req->conn->pending, req);
+       }
+       return 0;
 }
 
-struct ldap_message *ldap_getsearchent(struct ldap_connection *conn,
-                                      const struct timeval *endtime)
+/*
+  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_message *result;
-
-       if (conn->search_entries != NULL) {
-               struct ldap_queue_entry *e = conn->search_entries;
-
-               result = e->msg;
-               DLIST_REMOVE(conn->search_entries, e);
-               SAFE_FREE(e);
-               return result;
+       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_receive(conn, conn->searchid, endtime);
-       if (!result) {
-               return NULL;
+       req->state = LDAP_REQUEST_DONE;
+       if (req->async.fn) {
+               req->async.fn(req);
        }
+}
 
-       if (result->type == LDAP_TAG_SearchResultEntry)
-               return result;
 
-       if (result->type == LDAP_TAG_SearchResultDone) {
-               /* TODO: Handle Paged Results */
-               talloc_free(result);
-               return NULL;
+/*
+  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)
+{
+       struct ldap_request *req = talloc_get_type(private_data, struct ldap_request);
+       if (req->async.fn) {
+               req->async.fn(req);
        }
-
-       /* TODO: Handle Search References here */
-       return NULL;
 }
 
-void ldap_endsearchent(struct ldap_connection *conn,
-                      const struct timeval *endtime)
+/*
+  send a ldap message - async interface
+*/
+struct ldap_request *ldap_request_send(struct ldap_connection *conn,
+                                      struct ldap_message *msg)
 {
-       struct ldap_queue_entry *e;
+       struct ldap_request *req;
+       NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
 
-       e = conn->search_entries;
+       req = talloc_zero(conn, struct ldap_request);
+       if (req == NULL) return NULL;
 
-       while (e != NULL) {
-               struct ldap_queue_entry *next = e->next;
-               DLIST_REMOVE(conn->search_entries, e);
-               SAFE_FREE(e);
-               e = next;
+       if (conn->sock == NULL) {
+               status = NT_STATUS_INVALID_CONNECTION;
+               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;
+       req->state       = LDAP_REQUEST_SEND;
+       req->conn        = conn;
+       req->messageid   = conn->next_messageid++;
+       if (conn->next_messageid == 0) {
+               conn->next_messageid = 1;
+       }
+       req->type        = msg->type;
+       if (req->messageid == -1) {
+               goto failed;
+       }
 
-       res1 = ldap_getsearchent(conn, endtime);
+       talloc_set_destructor(req, ldap_request_destructor);
 
-       if (res1 != NULL)
-               res2 = ldap_getsearchent(conn, endtime);
+       msg->messageid = req->messageid;
 
-       ldap_endsearchent(conn, endtime);
+       if (!ldap_encode(msg, &req->data, req)) {
+               status = NT_STATUS_INTERNAL_ERROR;
+               goto failed;            
+       }
 
-       if (res1 == NULL)
-               return NULL;
+       status = packet_send(conn->packet, req->data);
+       if (!NT_STATUS_IS_OK(status)) {
+               goto failed;
+       }
 
-       if (res2 != NULL) {
-               /* More than one entry */
-               talloc_free(res1);
-               talloc_free(res2);
-               return NULL;
+       /* 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;
        }
 
-       return res1;
-}
+       req->state = LDAP_REQUEST_PENDING;
+       DLIST_ADD(conn->pending, 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;
+       /* 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 (msg->type != LDAP_TAG_SearchResultEntry)
-               return False;
+       return req;
 
-       for (i=0; i<r->num_attributes; i++) {
-               if (strequal(attr, r->attributes[i].name)) {
-                       if (r->attributes[i].num_values != 1)
-                               return False;
+failed:
+       req->status = status;
+       req->state = LDAP_REQUEST_ERROR;
+       event_add_timed(conn->event.event_ctx, req, timeval_zero(),
+                       ldap_request_complete, req);
 
-                       *value = r->attributes[i].values[0];
-                       return True;
+       return req;
+}
+
+
+/*
+  wait for a request to complete
+  note that this does not destroy the request
+*/
+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
+*/
+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
+*/
+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
+*/
+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
+*/
+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
+*/
+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;
 }