Fix include paths to new location of libutil.
[kai/samba-autobuild/.git] / source4 / libcli / ldap / ldap_client.c
index 71b57e116e6359f389a6b36ccdc58c6b84e02a92..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 "system/filesys.h"
-#include "auth/auth.h"
-#include "asn_1.h"
-#include "dlinklist.h"
+#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"
-
-
-
-/****************************************************************************
- Check the timeout. 
-****************************************************************************/
-static BOOL timeout_until(struct timeval *timeout,
-                         const struct timeval *endtime)
-{
-       struct timeval now;
-
-       GetTimeOfDay(&now);
-
-       if ((now.tv_sec > endtime->tv_sec) ||
-           ((now.tv_sec == endtime->tv_sec) &&
-            (now.tv_usec > endtime->tv_usec)))
-               return False;
-
-       timeout->tv_sec = endtime->tv_sec - now.tv_sec;
-       timeout->tv_usec = endtime->tv_usec - now.tv_usec;
-       return True;
-}
-
-
-/****************************************************************************
- Read data from the client, reading exactly N bytes, with timeout. 
-****************************************************************************/
-static ssize_t read_data_until(int fd,char *buffer,size_t N,
-                              const struct timeval *endtime)
+#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)
 {
-       ssize_t ret;
-       size_t total=0;  
-       while (total < N) {
-
-               if (endtime != NULL) {
-                       fd_set r_fds;
-                       struct timeval timeout;
-                       int res;
-
-                       FD_ZERO(&r_fds);
-                       FD_SET(fd, &r_fds);
-
-                       if (!timeout_until(&timeout, endtime))
-                               return -1;
-
-                       res = sys_select(fd+1, &r_fds, NULL, NULL, &timeout);
-                       if (res <= 0)
-                               return -1;
-               }
-
-               ret = sys_read(fd,buffer + total,N - total);
-
-               if (ret == 0) {
-                       DEBUG(10,("read_data: read of %d returned 0. Error = %s\n", (int)(N - total), strerror(errno) ));
-                       return 0;
-               }
+       struct ldap_connection *conn;
 
-               if (ret == -1) {
-                       DEBUG(0,("read_data: read failure for %d. Error = %s\n", (int)(N - total), strerror(errno) ));
-                       return -1;
-               }
-               total += ret;
+       if (ev == NULL) {
+               return NULL;
        }
-       return (ssize_t)total;
-}
-
-
-/****************************************************************************
- Write data to a fd with timeout.
-****************************************************************************/
-static ssize_t write_data_until(int fd,char *buffer,size_t N,
-                               const struct timeval *endtime)
-{
-       size_t total=0;
-       ssize_t ret;
 
-       while (total < N) {
+       conn = talloc_zero(mem_ctx, struct ldap_connection);
+       if (conn == NULL) {
+               return NULL;
+       }
 
-               if (endtime != NULL) {
-                       fd_set w_fds;
-                       struct timeval timeout;
-                       int res;
+       conn->next_messageid  = 1;
+       conn->event.event_ctx = ev;
 
-                       FD_ZERO(&w_fds);
-                       FD_SET(fd, &w_fds);
+       conn->lp_ctx = lp_ctx;
 
-                       if (!timeout_until(&timeout, endtime))
-                               return -1;
+       /* set a reasonable request timeout */
+       conn->timeout = 60;
 
-                       res = sys_select(fd+1, NULL, &w_fds, NULL, &timeout);
-                       if (res <= 0)
-                               return -1;
-               }
-
-               ret = sys_write(fd,buffer + total,N - total);
+       /* explicitly avoid reconnections by default */
+       conn->reconnect.max_retries = 0;
+       
+       return conn;
+}
 
-               if (ret == -1) {
-                       DEBUG(0,("write_data: write failure. Error = %s\n", strerror(errno) ));
-                       return -1;
+/*
+  the connection is dead
+*/
+static void ldap_connection_dead(struct ldap_connection *conn)
+{
+       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);
                }
-               if (ret == 0)
-                       return total;
-
-               total += ret;
        }
-       return (ssize_t)total;
 }
 
+static void ldap_reconnect(struct ldap_connection *conn);
 
-
-static BOOL read_one_uint8(int sock, uint8_t *result, struct asn1_data *data,
-                          const struct timeval *endtime)
+/*
+  handle packet errors
+*/
+static void ldap_error_handler(void *private_data, NTSTATUS status)
 {
-       if (read_data_until(sock, result, 1, endtime) != 1)
-               return False;
+       struct ldap_connection *conn = talloc_get_type(private_data, 
+                                                      struct ldap_connection);
+       ldap_connection_dead(conn);
 
-       return asn1_write(data, result, 1);
+       /* but try to reconnect so that the ldb client can go on */
+       ldap_reconnect(conn);
 }
 
-/* Read a complete ASN sequence (ie LDAP result) from a socket */
-static BOOL asn1_read_sequence_until(int sock, struct asn1_data *data,
-                                    const struct timeval *endtime)
-{
-       uint8_t b;
-       size_t len;
-       char *buf;
-
-       ZERO_STRUCTP(data);
 
-       if (!read_one_uint8(sock, &b, data, endtime))
-               return False;
+/*
+  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 ldap_request *req;
+       int i;
 
-       if (b != 0x30) {
-               data->has_error = True;
-               return False;
+       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;
        }
 
-       if (!read_one_uint8(sock, &b, data, endtime))
-               return False;
-
-       if (b & 0x80) {
-               int n = b & 0x7f;
-               if (!read_one_uint8(sock, &b, data, endtime))
-                       return False;
-               len = b;
-               while (n > 1) {
-                       if (!read_one_uint8(sock, &b, data, endtime))
-                               return False;
-                       len = (len<<8) | b;
-                       n--;
+       /* 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;
                }
-       } else {
-               len = b;
        }
 
-       buf = talloc_size(NULL, len);
-       if (buf == NULL)
-               return False;
-
-       if (read_data_until(sock, buf, len, endtime) != len)
-               return False;
+       /* 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;
+       }
 
-       if (!asn1_write(data, buf, len))
-               return False;
+       req->replies[req->num_replies] = talloc_steal(req->replies, msg);
+       req->num_replies++;
 
-       talloc_free(buf);
+       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);
+       }
 
-       data->ofs = 0;
-       
-       return True;
+       if (req->async.fn) {
+               req->async.fn(req);
+       }
 }
 
 
-
-/****************************************************************************
-  create an outgoing socket. timeout is in milliseconds.
-  **************************************************************************/
-static int open_socket_out(int type, struct ipv4_addr *addr, int port, int timeout)
+/*
+  decode/process LDAP data
+*/
+static NTSTATUS ldap_recv_handler(void *private_data, DATA_BLOB blob)
 {
-       struct sockaddr_in sock_out;
-       int res,ret;
-       int connect_loop = 250; /* 250 milliseconds */
-       int loops = (timeout) / connect_loop;
-
-       /* create a socket to write to */
-       res = socket(PF_INET, type, 0);
-       if (res == -1) 
-       { DEBUG(0,("socket error\n")); return -1; }
-       
-       if (type != SOCK_STREAM) return(res);
-       
-       memset((char *)&sock_out,'\0',sizeof(sock_out));
-       sock_out.sin_addr.s_addr = addr->addr;
-       
-       sock_out.sin_port = htons( port );
-       sock_out.sin_family = PF_INET;
-       
-       /* set it non-blocking */
-       set_blocking(res,False);
-       
-       DEBUG(3,("Connecting to %s at port %d\n", sys_inet_ntoa(*addr),port));
-       
-       /* and connect it to the destination */
-connect_again:
-       ret = connect(res,(struct sockaddr *)&sock_out,sizeof(sock_out));
-       
-       /* Some systems return EAGAIN when they mean EINPROGRESS */
-       if (ret < 0 && (errno == EINPROGRESS || errno == EALREADY ||
-                       errno == EAGAIN) && loops--) {
-               msleep(connect_loop);
-               goto connect_again;
-       }
-       
-       if (ret < 0 && (errno == EINPROGRESS || errno == EALREADY ||
-                       errno == EAGAIN)) {
-               DEBUG(1,("timeout connecting to %s:%d\n", sys_inet_ntoa(*addr),port));
-               close(res);
-               return -1;
-       }
-       
-#ifdef EISCONN
-       if (ret < 0 && errno == EISCONN) {
-               errno = 0;
-               ret = 0;
+       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);
+
+       if (asn1 == NULL || msg == NULL) {
+               return NT_STATUS_LDAP(LDAP_PROTOCOL_ERROR);
        }
-#endif
-       
-       if (ret < 0) {
-               DEBUG(2,("error connecting to %s:%d (%s)\n",
-                        sys_inet_ntoa(*addr),port,strerror(errno)));
-               close(res);
-               return -1;
+
+       if (!asn1_load(asn1, blob)) {
+               talloc_free(msg);
+               talloc_free(asn1);
+               return NT_STATUS_LDAP(LDAP_PROTOCOL_ERROR);
        }
        
-       /* set it blocking again */
-       set_blocking(res,True);
-       
-       return res;
-}
-
-#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)
-{
-       struct ldap_message *res;
-
-       res = new_ldap_message(conn);
-       if (!res) {
-               return NULL;
+       status = ldap_decode(asn1, msg);
+       if (!NT_STATUS_IS_OK(status)) {
+               asn1_free(asn1);
+               return status;
        }
 
-       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;
+       ldap_match_message(conn, msg);
 
-       return res;
+       data_blob_free(&blob);
+       asn1_free(asn1);
+       return NT_STATUS_OK;
 }
-#endif
 
-static struct ldap_message *new_ldap_simple_bind_msg(struct ldap_connection *conn, const char *dn, const char *pw)
+/* Handle read events, from the GENSEC socket callback, or real events */
+void ldap_read_io_handler(void *private_data, uint16_t flags) 
 {
-       struct ldap_message *res;
-
-       res = new_ldap_message(conn);
-       if (!res) {
-               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);
-
-       return res;
+       struct ldap_connection *conn = talloc_get_type(private_data, 
+                                                      struct ldap_connection);
+       packet_recv(conn->packet);
 }
 
-static struct ldap_message *new_ldap_sasl_bind_msg(struct ldap_connection *conn, const char *sasl_mechanism, DATA_BLOB *secblob)
+/*
+  handle ldap socket events
+*/
+static void ldap_io_handler(struct event_context *ev, struct fd_event *fde, 
+                           uint16_t flags, void *private_data)
 {
-       struct ldap_message *res;
-
-       res = new_ldap_message(conn);
-       if (!res) {
-               return NULL;
+       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;
        }
-
-       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;
-}
-
-static struct ldap_connection *new_ldap_connection(TALLOC_CTX *mem_ctx)
-{
-       struct ldap_connection *result;
-
-       result = talloc(mem_ctx, struct ldap_connection);
-
-       if (!result) {
-               return NULL;
+       if (flags & EVENT_FD_READ) {
+               ldap_read_io_handler(private_data, flags);
        }
-
-       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;
-
-       return result;
 }
 
-struct ldap_connection *ldap_connect(TALLOC_CTX *mem_ctx, const char *url)
+/*
+  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 hostent *hp;
-       struct ipv4_addr ip;
-       struct ldap_connection *conn;
-       BOOL ret;
-
-       conn = new_ldap_connection(mem_ctx);
-       if (!conn) {
-               return NULL;
-       }
+       int tmp_port = 0;
+       char protocol[11];
+       char tmp_host[1025];
+       int ret;
 
-       ret = ldap_parse_basic_url(conn->mem_ctx, url, &conn->host,
-                                 &conn->port, &conn->ldaps);
-       if (!ret) {
-               talloc_free(conn);
-               return NULL;
+       /* 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;
        }
 
-       hp = sys_gethostbyname(conn->host);
-       if (!hp || !hp->h_addr) {
-               talloc_free(conn);
-               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;
        }
 
-       memcpy((char *)&ip, (char *)hp->h_addr, 4);
+       if (tmp_port != 0)
+               *port = tmp_port;
 
-       conn->sock = open_socket_out(SOCK_STREAM, &ip, conn->port, LDAP_CONNECTION_TIMEOUT);
-       if (conn->sock < 0) {
-               talloc_free(conn);
-               return NULL;
-       }
+       *host = talloc_strdup(mem_ctx, tmp_host);
+       NT_STATUS_HAVE_NO_MEMORY(*host);
 
-       return conn;
+       return NT_STATUS_OK;
 }
 
-struct ldap_message *new_ldap_message(TALLOC_CTX *mem_ctx)
-{
-       struct ldap_message *result;
-
-       result = talloc(mem_ctx, struct ldap_message);
-
-       if (!result) {
-               return NULL;
-       }
+/*
+  connect to a ldap server
+*/
 
-       result->mem_ctx = result;
+struct ldap_connect_state {
+       struct composite_context *ctx;
+       struct ldap_connection *conn;
+};
 
-       return result;
-}
+static void ldap_connect_recv_unix_conn(struct composite_context *ctx);
+static void ldap_connect_recv_tcp_conn(struct composite_context *ctx);
 
-BOOL ldap_send_msg(struct ldap_connection *conn, struct ldap_message *msg,
-                  const struct timeval *endtime)
+_PUBLIC_ struct composite_context *ldap_connect_send(struct ldap_connection *conn,
+                                           const char *url)
 {
-       DATA_BLOB request;
-       BOOL result;
-       struct ldap_queue_entry *entry;
+       struct composite_context *result, *ctx;
+       struct ldap_connect_state *state;
+       char protocol[11];
+       int ret;
 
-       msg->messageid = conn->next_msgid++;
+       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;
 
-       if (!ldap_encode(msg, &request))
-               return False;
+       state = talloc(result, struct ldap_connect_state);
+       if (state == NULL) goto failed;
+       state->ctx = result;
+       result->private_data = state;
 
-       result = (write_data_until(conn->sock, request.data, request.length,
-                                  endtime) == request.length);
+       state->conn = conn;
 
-       data_blob_free(&request);
+       if (conn->reconnect.url == NULL) {
+               conn->reconnect.url = talloc_strdup(conn, url);
+               if (conn->reconnect.url == NULL) goto failed;
+       }
 
-       if (!result)
-               return result;
+       /* Paranoia check */
+       SMB_ASSERT(sizeof(protocol)>10);
 
-       /* abandon and unbind don't expect results */
+       ret = sscanf(url, "%10[^:]://", protocol);
+       if (ret < 1) {
+               return NULL;
+       }
 
-       if ((msg->type == LDAP_TAG_AbandonRequest) ||
-           (msg->type == LDAP_TAG_UnbindRequest))
-               return True;
+       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;
+               }
 
-       entry = malloc_p(struct ldap_queue_entry);
+               /* 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 (entry == NULL)
-               return False;
+               rfc1738_unescape(path);
+       
+               unix_addr = socket_address_from_strings(conn, conn->sock->backend_name, 
+                                                       path, 0);
+               if (!unix_addr) {
+                       return NULL;
+               }
 
-       entry->msgid = msg->messageid;
-       entry->msg = NULL;
-       DLIST_ADD(conn->outstanding, entry);
+               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;
+               }
+               
+               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;
 
-       return True;
+               ctx->async.fn = ldap_connect_recv_tcp_conn;
+               ctx->async.private_data = state;
+               return result;
+       }
+ failed:
+       talloc_free(result);
+       return NULL;
 }
 
-BOOL ldap_receive_msg(struct ldap_connection *conn, struct ldap_message *msg,
-                     const struct timeval *endtime)
+static void ldap_connect_got_sock(struct composite_context *ctx, 
+                                 struct ldap_connection *conn) 
 {
-        struct asn1_data data;
-        BOOL result;
-
-        if (!asn1_read_sequence_until(conn->sock, &data, endtime))
-                return False;
+       /* 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;
+       }
 
-        result = ldap_decode(&data, msg);
+       socket_set_flags(conn->sock, SOCKET_FLAG_NOCLOSE);
 
-        asn1_free(&data);
-        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));
 
-static struct ldap_message *recv_from_queue(struct ldap_connection *conn,
-                                           int msgid)
-{
-       struct ldap_queue_entry *e;
+               if (!cafile || !*cafile) {
+                       talloc_free(conn->sock);
+                       return;
+               }
 
-       for (e = conn->outstanding; e != NULL; e = e->next) {
+               tls_socket = tls_init_client(conn->sock, conn->event.fde, cafile);
+               talloc_free(cafile);
 
-               if (e->msgid == msgid) {
-                       struct ldap_message *result = e->msg;
-                       DLIST_REMOVE(conn->outstanding, e);
-                       SAFE_FREE(e);
-                       return result;
+               if (tls_socket == NULL) {
+                       talloc_free(conn->sock);
+                       return;
                }
-       }
-
-       return NULL;
-}
 
-static void add_search_entry(struct ldap_connection *conn,
-                            struct ldap_message *msg)
-{
-       struct ldap_queue_entry *e = malloc_p(struct ldap_queue_entry);
+               /* 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);
+       }
 
-       if (e == NULL)
+       conn->packet = packet_init(conn);
+       if (conn->packet == NULL) {
+               talloc_free(conn->sock);
                return;
+       }
 
-       e->msg = msg;
-       DLIST_ADD_END(conn->search_entries, e, struct ldap_queue_entry *);
-       return;
+       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); */
+
+       composite_done(ctx);
 }
 
-static void fill_outstanding_request(struct ldap_connection *conn,
-                                    struct ldap_message *msg)
+static void ldap_connect_recv_tcp_conn(struct composite_context *ctx)
 {
-       struct ldap_queue_entry *e;
-
-       for (e = conn->outstanding; e != NULL; e = e->next) {
-               if (e->msgid == msg->messageid) {
-                       e->msg = msg;
-                       return;
-               }
+       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)) {
+               composite_error(state->ctx, status);
+               return;
        }
 
-       /* This reply has not been expected, destroy the incoming msg */
-       talloc_free(msg);
-       return;
+       ldap_connect_got_sock(state->ctx, conn);
 }
 
-struct ldap_message *ldap_receive(struct ldap_connection *conn, int msgid,
-                                 const struct timeval *endtime)
+static void ldap_connect_recv_unix_conn(struct composite_context *ctx)
 {
-       struct ldap_message *result = recv_from_queue(conn, msgid);
-
-       if (result != NULL)
-               return result;
-
-       while (True) {
-               struct asn1_data data;
-               BOOL res;
-
-               result = new_ldap_message(conn);
-
-               if (!asn1_read_sequence_until(conn->sock, &data, endtime))
-                       return NULL;
+       struct ldap_connect_state *state =
+               talloc_get_type(ctx->async.private_data,
+                               struct ldap_connect_state);
+       struct ldap_connection *conn = state->conn;
 
-               res = ldap_decode(&data, result);
-               asn1_free(&data);
+       NTSTATUS status = socket_connect_recv(ctx);
 
-               if (!res)
-                       return NULL;
-
-               if (result->messageid == msgid)
-                       return result;
-
-               if (result->type == LDAP_TAG_SearchResultEntry) {
-                       add_search_entry(conn, result);
-               } else {
-                       fill_outstanding_request(conn, result);
-               }
+       if (!NT_STATUS_IS_OK(state->ctx->status)) {
+               composite_error(state->ctx, status);
+               return;
        }
 
-       return NULL;
+       ldap_connect_got_sock(state->ctx, conn);
 }
 
-struct ldap_message *ldap_transaction(struct ldap_connection *conn,
-                                     struct ldap_message *request)
+_PUBLIC_ NTSTATUS ldap_connect_recv(struct composite_context *ctx)
 {
-       if (!ldap_send_msg(conn, request, NULL))
-               return False;
-
-       return ldap_receive(conn, request->messageid, NULL);
+       NTSTATUS status = composite_wait(ctx);
+       talloc_free(ctx);
+       return status;
 }
 
-int ldap_bind_simple(struct ldap_connection *conn, const char *userdn, const char *password)
+_PUBLIC_ NTSTATUS ldap_connect(struct ldap_connection *conn, const char *url)
 {
-       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 = "";
-               }
-       }
-
-       if (password) {
-               pw = password;
-       } else {
-               if (conn->simple_pw) {
-                       pw = conn->simple_pw;
-               } else {
-                       pw = "";
-               }
-       }
+       struct composite_context *ctx = ldap_connect_send(conn, url);
+       return ldap_connect_recv(ctx);
+}
 
-       msg =  new_ldap_simple_bind_msg(conn, dn, pw);
-       if (!msg)
-               return result;
+/* set reconnect parameters */
 
-       response = ldap_transaction(conn, msg);
-       if (!response) {
-               talloc_free(msg);
-               return result;
+_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);
        }
-               
-       result = response->r.BindResponse.response.resultcode;
-
-       talloc_free(msg);
-       talloc_free(response);
-
-       return result;
 }
 
-int ldap_bind_sasl(struct ldap_connection *conn, struct cli_credentials *creds)
+/* Actually this function is NOT ASYNC safe, FIXME? */
+static void ldap_reconnect(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;
-       }
+       time_t now = time(NULL);
 
-       gensec_want_feature(conn->gensec, GENSEC_FEATURE_SIGN | GENSEC_FEATURE_SEAL);
+       /* do we have set up reconnect ? */
+       if (conn->reconnect.max_retries == 0) return;
 
-       status = gensec_set_credentials(conn->gensec, creds);
-       if (!NT_STATUS_IS_OK(status)) {
-               DEBUG(1, ("Failed to start set GENSEC creds: %s\n", 
-                         nt_errstr(status)));
-               goto done;
+       /* is the retry time expired ? */
+       if (now > conn->reconnect.previous + 30) {
+               conn->reconnect.retries = 0;
+               conn->reconnect.previous = now;
        }
 
-       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;
-       }
+       /* are we reconnectind too often and too fast? */
+       if (conn->reconnect.retries > conn->reconnect.max_retries) return;
 
-       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;
-       }
+       /* keep track of the number of reconnections */
+       conn->reconnect.retries++;
 
-       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;
+       /* reconnect */
+       status = ldap_connect(conn, conn->reconnect.url);
+       if ( ! NT_STATUS_IS_OK(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;
-
-               if (result != LDAP_SUCCESS && result != LDAP_SASL_BIND_IN_PROGRESS) {
-                       break;
-               }
-
-               if (!NT_STATUS_IS_OK(status)) {
-                       status = gensec_update(conn->gensec, mem_ctx,
-                                              response->r.BindResponse.SASL.secblob,
-                                              &output);
-               } else {
-                       output.length = 0;
-               }
-
-               talloc_free(response);
+       /* rebind */
+       status = ldap_rebind(conn);
+       if ( ! NT_STATUS_IS_OK(status)) {
+               ldap_connection_dead(conn);
        }
-
-done:
-       if (mem_ctx)
-               talloc_free(mem_ctx);
-
-       return result;
 }
 
-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,
-                                                       struct cli_credentials *creds)
+/*
+  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, creds);
-       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_size(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;
 }