r18021: Add ldapi support to our LDAP client. To be used for testing an
[bbaumbach/samba-autobuild/.git] / source4 / libcli / ldap / ldap_client.c
index a8463f78724202289c0c71580ee4190d2370b977..4fa8cc01464fe1121f276f5acfc944f4afcc2ab5 100644 (file)
 */
 
 #include "includes.h"
-#include "asn_1.h"
-#include "dlinklist.h"
+#include "libcli/util/asn_1.h"
+#include "lib/util/dlinklist.h"
 #include "lib/events/events.h"
 #include "lib/socket/socket.h"
-#include "lib/tls/tls.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"
 
 
 /*
@@ -60,10 +64,12 @@ struct ldap_connection *ldap_new_connection(TALLOC_CTX *mem_ctx,
        /* set a reasonable request timeout */
        conn->timeout = 60;
 
+       /* explicitly avoid reconnections by default */
+       conn->reconnect.max_retries = 0;
+       
        return conn;
 }
 
-
 /*
   the connection is dead
 */
@@ -71,6 +77,7 @@ static void ldap_connection_dead(struct ldap_connection *conn)
 {
        struct ldap_request *req;
 
+       /* return an error for any pending request ... */
        while (conn->pending) {
                req = conn->pending;
                DLIST_REMOVE(req->conn->pending, req);
@@ -79,20 +86,28 @@ static void ldap_connection_dead(struct ldap_connection *conn)
                if (req->async.fn) {
                        req->async.fn(req);
                }
-       }       
-
-       while (conn->send_queue) {
-               req = conn->send_queue;
-               DLIST_REMOVE(req->conn->send_queue, req);
-               req->state = LDAP_REQUEST_DONE;
-               req->status = NT_STATUS_UNEXPECTED_NETWORK_ERROR;
-               if (req->async.fn) {
-                       req->async.fn(req);
-               }
        }
 
-       talloc_free(conn->tls);
-       conn->tls = NULL;
+       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 void ldap_reconnect(struct ldap_connection *conn);
+
+/*
+  handle packet errors
+*/
+static void ldap_error_handler(void *private_data, NTSTATUS status)
+{
+       struct ldap_connection *conn = talloc_get_type(private_data, 
+                                                      struct ldap_connection);
+       ldap_connection_dead(conn);
+
+       /* but try to reconnect so that the ldb client can go on */
+       ldap_reconnect(conn);
 }
 
 
@@ -106,6 +121,11 @@ static void ldap_match_message(struct ldap_connection *conn, struct ldap_message
        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));
@@ -143,200 +163,58 @@ static void ldap_match_message(struct ldap_connection *conn, struct ldap_message
        }
 }
 
-/*
-  try and decode/process plain data
-*/
-static void ldap_try_decode_plain(struct ldap_connection *conn)
-{
-       struct asn1_data asn1;
-
-       if (!asn1_load(&asn1, conn->partial)) {
-               ldap_connection_dead(conn);
-               return;
-       }
-
-       /* try and decode - this will fail if we don't have a full packet yet */
-       while (asn1.ofs < asn1.length) {
-               struct ldap_message *msg = talloc(conn, struct ldap_message);
-               off_t saved_ofs = asn1.ofs;
-                       
-               if (msg == NULL) {
-                       ldap_connection_dead(conn);
-                       return;
-               }
-
-               if (ldap_decode(&asn1, msg)) {
-                       ldap_match_message(conn, msg);
-               } else {
-                       asn1.ofs = saved_ofs;
-                       talloc_free(msg);
-                       break;
-               }
-       }
-
-       /* keep any remaining data in conn->partial */
-       data_blob_free(&conn->partial);
-       if (asn1.ofs != asn1.length) {
-               conn->partial = data_blob_talloc(conn, 
-                                                asn1.data + asn1.ofs, 
-                                                asn1.length - asn1.ofs);
-       }
-       asn1_free(&asn1);
-}
 
 /*
-  try and decode/process wrapped data
+  decode/process LDAP data
 */
-static void ldap_try_decode_wrapped(struct ldap_connection *conn)
+static NTSTATUS ldap_recv_handler(void *private_data, DATA_BLOB blob)
 {
-       uint32_t len;
-
-       /* keep decoding while we have a full wrapped packet */
-       while (conn->partial.length >= 4 &&
-              (len=RIVAL(conn->partial.data, 0)) <= conn->partial.length-4) {
-               DATA_BLOB wrapped, unwrapped;
-               struct asn1_data asn1;
-               struct ldap_message *msg = talloc(conn, struct ldap_message);
-               NTSTATUS status;
-
-               if (msg == NULL) {
-                       ldap_connection_dead(conn);
-                       return;
-               }
-
-               wrapped.data   = conn->partial.data+4;
-               wrapped.length = len;
-
-               status = gensec_unwrap(conn->gensec, msg, &wrapped, &unwrapped);
-               if (!NT_STATUS_IS_OK(status)) {
-                       ldap_connection_dead(conn);
-                       return;
-               }
-
-               if (!asn1_load(&asn1, unwrapped)) {
-                       ldap_connection_dead(conn);
-                       return;
-               }
+       struct asn1_data asn1;
+       struct ldap_connection *conn = talloc_get_type(private_data, 
+                                                      struct ldap_connection);
+       struct ldap_message *msg = talloc(conn, struct ldap_message);
 
-               while (ldap_decode(&asn1, msg)) {
-                       ldap_match_message(conn, msg);
-                       msg = talloc(conn, struct ldap_message);
-               }
-               
-               talloc_free(msg);
-               asn1_free(&asn1);
-
-               if (conn->partial.length == len + 4) {
-                       data_blob_free(&conn->partial);
-               } else {
-                       memmove(conn->partial.data, conn->partial.data+len+4,
-                               conn->partial.length - (len+4));
-                       conn->partial.length -= len + 4;
-               }
+       if (msg == NULL) {
+               return NT_STATUS_LDAP(LDAP_PROTOCOL_ERROR);
        }
-}
-
 
-/*
-  handle ldap recv events
-*/
-static void ldap_recv_handler(struct ldap_connection *conn)
-{
-       NTSTATUS status;
-       size_t npending=0, nread;
-
-       /* work out how much data is pending */
-       status = tls_socket_pending(conn->tls, &npending);
-       if (!NT_STATUS_IS_OK(status) || npending == 0) {
-               ldap_connection_dead(conn);
-               return;
+       if (!asn1_load(&asn1, blob)) {
+               return NT_STATUS_LDAP(LDAP_PROTOCOL_ERROR);
        }
-
-       conn->partial.data = talloc_realloc_size(conn, conn->partial.data, 
-                                                conn->partial.length + npending);
-       if (conn->partial.data == NULL) {
-               ldap_connection_dead(conn);
-               return;
+       
+       if (!ldap_decode(&asn1, msg)) {
+               return NT_STATUS_LDAP(LDAP_PROTOCOL_ERROR);
        }
 
-       /* receive the pending data */
-       status = tls_socket_recv(conn->tls, conn->partial.data + conn->partial.length,
-                                npending, &nread);
-       if (NT_STATUS_EQUAL(status, STATUS_MORE_ENTRIES)) {
-               return;
-       }
-       if (!NT_STATUS_IS_OK(status)) {
-               ldap_connection_dead(conn);
-               return;
-       }
-       conn->partial.length += nread;
+       ldap_match_message(conn, msg);
 
-       /* see if we can decode what we have */
-       if (conn->enable_wrap) {
-               ldap_try_decode_wrapped(conn);
-       } else {
-               ldap_try_decode_plain(conn);
-       }
+       data_blob_free(&blob);
+       asn1_free(&asn1);
+       return NT_STATUS_OK;
 }
 
-
-/*
-  handle ldap send events
-*/
-static void ldap_send_handler(struct ldap_connection *conn)
+/* Handle read events, from the GENSEC socket callback, or real events */
+void ldap_read_io_handler(void *private_data, uint16_t flags) 
 {
-       while (conn->send_queue) {
-               struct ldap_request *req = conn->send_queue;
-               size_t nsent;
-               NTSTATUS status;
-
-               status = tls_socket_send(conn->tls, &req->data, &nsent);
-               if (NT_STATUS_EQUAL(status, STATUS_MORE_ENTRIES)) {
-                       break;
-               }
-               if (!NT_STATUS_IS_OK(status)) {
-                       ldap_connection_dead(conn);
-                       return;
-               }
-
-               req->data.data += nsent;
-               req->data.length -= nsent;
-               if (req->data.length == 0) {
-                       req->state = LDAP_REQUEST_PENDING;
-                       DLIST_REMOVE(conn->send_queue, req);
-
-                       /* some types of requests don't expect a reply */
-                       if (req->type == LDAP_TAG_AbandonRequest ||
-                           req->type == LDAP_TAG_UnbindRequest) {
-                               req->status = NT_STATUS_OK;
-                               req->state = LDAP_REQUEST_DONE;
-                               if (req->async.fn) {
-                                       req->async.fn(req);
-                               }
-                       } else {
-                               DLIST_ADD(conn->pending, req);
-                       }
-               }
-       }
-       if (conn->send_queue == NULL) {
-               EVENT_FD_NOT_WRITEABLE(conn->event.fde);
-       }
+       struct ldap_connection *conn = talloc_get_type(private_data, 
+                                                      struct ldap_connection);
+       packet_recv(conn->packet);
 }
 
-
 /*
   handle ldap socket events
 */
 static void ldap_io_handler(struct event_context *ev, struct fd_event *fde, 
-                           uint16_t flags, void *private)
+                           uint16_t flags, void *private_data)
 {
-       struct ldap_connection *conn = talloc_get_type(private, struct ldap_connection);
+       struct ldap_connection *conn = talloc_get_type(private_data, 
+                                                      struct ldap_connection);
        if (flags & EVENT_FD_WRITE) {
-               ldap_send_handler(conn);
-               if (conn->tls == NULL) return;
+               packet_queue_run(conn->packet);
+               if (!tls_enabled(conn->sock)) return;
        }
        if (flags & EVENT_FD_READ) {
-               ldap_recv_handler(conn);
+               ldap_read_io_handler(private_data, flags);
        }
 }
 
@@ -348,19 +226,13 @@ static NTSTATUS ldap_parse_basic_url(TALLOC_CTX *mem_ctx, const char *url,
 {
        int tmp_port = 0;
        char protocol[11];
-       char tmp_host[255];
-       const char *p = url;
+       char tmp_host[1025];
        int ret;
 
-       /* skip leading "URL:" (if any) */
-       if (strncasecmp(p, "URL:", 4) == 0) {
-               p += 4;
-       }
-
        /* Paranoia check */
        SMB_ASSERT(sizeof(protocol)>10 && sizeof(tmp_host)>254);
                
-       ret = sscanf(p, "%10[^:]://%254[^:/]:%d", protocol, tmp_host, &tmp_port);
+       ret = sscanf(url, "%10[^:]://%254[^:/]:%d", protocol, tmp_host, &tmp_port);
        if (ret < 2) {
                return NT_STATUS_INVALID_PARAMETER;
        }
@@ -388,54 +260,237 @@ static NTSTATUS ldap_parse_basic_url(TALLOC_CTX *mem_ctx, const char *url,
 /*
   connect to a ldap server
 */
-NTSTATUS ldap_connect(struct ldap_connection *conn, const char *url)
+
+struct ldap_connect_state {
+       struct composite_context *ctx;
+       struct ldap_connection *conn;
+};
+
+static void ldap_connect_recv_unix_conn(struct composite_context *ctx);
+static void ldap_connect_recv_tcp_conn(struct composite_context *ctx);
+
+struct composite_context *ldap_connect_send(struct ldap_connection *conn,
+                                           const char *url)
 {
-       NTSTATUS status;
+       struct composite_context *result, *ctx;
+       struct ldap_connect_state *state;
+       char protocol[11];
+       int ret;
 
-       status = ldap_parse_basic_url(conn, url, &conn->host,
-                                     &conn->port, &conn->ldaps);
-       NT_STATUS_NOT_OK_RETURN(status);
+       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;
 
-       status = socket_create("ipv4", SOCKET_TYPE_STREAM, &conn->sock, 0);
-       NT_STATUS_NOT_OK_RETURN(status);
+       state = talloc(result, struct ldap_connect_state);
+       if (state == NULL) goto failed;
+       state->ctx = result;
+       result->private_data = state;
 
-       talloc_steal(conn, conn->sock);
+       state->conn = conn;
 
-       /* connect in a event friendly way */
-       status = socket_connect_ev(conn->sock, NULL, 0, conn->host, conn->port, 0, 
-                                  conn->event.event_ctx);
-       if (!NT_STATUS_IS_OK(status)) {
-               talloc_free(conn->sock);
-               return status;
+       if (conn->reconnect.url == NULL) {
+               conn->reconnect.url = talloc_strdup(conn, url);
+               if (conn->reconnect.url == NULL) goto failed;
+       }
+
+       /* Paranoia check */
+       SMB_ASSERT(sizeof(protocol)>10);
+
+       ret = sscanf(url, "%10[^:]://", protocol);
+       if (ret < 1) {
+               return NULL;
        }
 
+       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;
+               }
+               SMB_ASSERT(sizeof(protocol)>10);
+               SMB_ASSERT(sizeof(path)>1024);
+       
+               ret = sscanf(url, "%10[^:]://%1025c", protocol, path);
+               if (ret < 2) {
+                       composite_error(state->ctx, NT_STATUS_INVALID_PARAMETER);
+                       return result;
+               }
+
+               rfc1738_unescape(path);
+       
+               unix_addr = socket_address_from_strings(conn, conn->sock->backend_name, 
+                                                       path, 0);
+               if (!unix_addr) {
+                       return NULL;
+               }
+
+               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 {
+               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,
+                                               conn->event.event_ctx);
+               if (ctx == NULL) goto failed;
+
+               ctx->async.fn = ldap_connect_recv_tcp_conn;
+               ctx->async.private_data = state;
+               return result;
+       }
+ failed:
+       talloc_free(result);
+       return NULL;
+}
+
+static void ldap_connect_got_sock(struct composite_context *ctx, struct ldap_connection *conn) 
+{
        /* 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, ldap_io_handler, conn);
        if (conn->event.fde == NULL) {
-               talloc_free(conn->sock);
-               return NT_STATUS_INTERNAL_ERROR;
+               composite_error(ctx, NT_STATUS_INTERNAL_ERROR);
+               return;
        }
 
-       conn->tls = tls_init_client(conn->sock, conn->event.fde, conn->ldaps);
-       if (conn->tls == NULL) {
+       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);
+       }
+
+       conn->packet = packet_init(conn);
+       if (conn->packet == NULL) {
                talloc_free(conn->sock);
-               return NT_STATUS_INTERNAL_ERROR;
+               return;
        }
-       talloc_steal(conn, conn->tls);
-       talloc_steal(conn->tls, conn->sock);
 
-       return NT_STATUS_OK;
+       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);
 }
 
-/* destroy an open ldap request */
-static int ldap_request_destructor(void *ptr)
+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(state->ctx->status)) {
+               composite_error(state->ctx, status);
+               return;
+       }
+
+       ldap_connect_got_sock(state->ctx, conn);
+}
+
+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;
+
+       NTSTATUS status = socket_connect_recv(ctx);
+
+       if (!NT_STATUS_IS_OK(state->ctx->status)) {
+               composite_error(state->ctx, status);
+               return;
+       }
+
+       ldap_connect_got_sock(state->ctx, conn);
+}
+
+NTSTATUS ldap_connect_recv(struct composite_context *ctx)
+{
+       NTSTATUS status = composite_wait(ctx);
+       talloc_free(ctx);
+       return status;
+}
+
+NTSTATUS ldap_connect(struct ldap_connection *conn, const char *url)
+{
+       struct composite_context *ctx = ldap_connect_send(conn, url);
+       return ldap_connect_recv(ctx);
+}
+
+/* set reconnect parameters */
+
+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);
+       }
+}
+
+/* Actually this function is NOT ASYNC safe, FIXME? */
+static void ldap_reconnect(struct ldap_connection *conn)
 {
-       struct ldap_request *req = talloc_get_type(ptr, struct ldap_request);
-       if (req->state == LDAP_REQUEST_SEND) {
-               DLIST_REMOVE(req->conn->send_queue, req);
+       NTSTATUS status;
+       time_t now = time(NULL);
+
+       /* do we have set up reconnect ? */
+       if (conn->reconnect.max_retries == 0) return;
+
+       /* is the retry time expired ? */
+       if (now > conn->reconnect.previous + 30) {
+               conn->reconnect.retries = 0;
+               conn->reconnect.previous = now;
+       }
+
+       /* are we reconnectind too often and too fast? */
+       if (conn->reconnect.retries > conn->reconnect.max_retries) return;
+
+       /* keep track of the number of reconnections */
+       conn->reconnect.retries++;
+
+       /* reconnect */
+       status = ldap_connect(conn, conn->reconnect.url);
+       if ( ! NT_STATUS_IS_OK(status)) {
+               return;
        }
+
+       /* rebind */
+       status = ldap_rebind(conn);
+       if ( ! NT_STATUS_IS_OK(status)) {
+               ldap_connection_dead(conn);
+       }
+}
+
+/* destroy an open ldap request */
+static int ldap_request_destructor(struct ldap_request *req)
+{
        if (req->state == LDAP_REQUEST_PENDING) {
                DLIST_REMOVE(req->conn->pending, req);
        }
@@ -446,13 +501,10 @@ static int ldap_request_destructor(void *ptr)
   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)
+                                     struct timeval t, void *private_data)
 {
-       struct ldap_request *req = talloc_get_type(private, struct ldap_request);
+       struct ldap_request *req = talloc_get_type(private_data, struct ldap_request);
        req->status = NT_STATUS_IO_TIMEOUT;
-       if (req->state == LDAP_REQUEST_SEND) {
-               DLIST_REMOVE(req->conn->send_queue, req);
-       }
        if (req->state == LDAP_REQUEST_PENDING) {
                DLIST_REMOVE(req->conn->pending, req);
        }
@@ -462,6 +514,19 @@ static void ldap_request_timeout(struct event_context *ev, struct timed_event *t
        }
 }
 
+
+/*
+  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);
+       }
+}
+
 /*
   send a ldap message - async interface
 */
@@ -469,17 +534,22 @@ struct ldap_request *ldap_request_send(struct ldap_connection *conn,
                                       struct ldap_message *msg)
 {
        struct ldap_request *req;
-
-       if (conn->tls == NULL) {
-               return NULL;
-       }
+       NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
 
        req = talloc_zero(conn, struct ldap_request);
-       if (req == NULL) goto failed;
+       if (req == NULL) return NULL;
+
+       if (conn->sock == NULL) {
+               status = NT_STATUS_INVALID_CONNECTION;
+               goto failed;
+       }
 
        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;
@@ -489,45 +559,45 @@ struct ldap_request *ldap_request_send(struct ldap_connection *conn,
 
        msg->messageid = req->messageid;
 
-       if (!ldap_encode(msg, &req->data)) {
+       if (!ldap_encode(msg, &req->data, req)) {
                goto failed;            
        }
 
-       /* possibly encrypt/sign the request */
-       if (conn->enable_wrap) {
-               DATA_BLOB wrapped;
-               NTSTATUS status;
-
-               status = gensec_wrap(conn->gensec, req, &req->data, &wrapped);
-               if (!NT_STATUS_IS_OK(status)) {
-                       goto failed;
-               }
-               data_blob_free(&req->data);
-               req->data = data_blob_talloc(req, NULL, wrapped.length + 4);
-               if (req->data.data == NULL) {
-                       goto failed;
-               }
-               RSIVAL(req->data.data, 0, wrapped.length);
-               memcpy(req->data.data+4, wrapped.data, wrapped.length);
-               data_blob_free(&wrapped);
+       status = packet_send(conn->packet, req->data);
+       if (!NT_STATUS_IS_OK(status)) {
+               goto failed;
        }
 
-
-       if (conn->send_queue == NULL) {
-               EVENT_FD_WRITEABLE(conn->event.fde);
+       /* 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;
        }
-       DLIST_ADD_END(conn->send_queue, req, struct ldap_request *);
+
+       req->state = LDAP_REQUEST_PENDING;
+       DLIST_ADD(conn->pending, req);
 
        /* put a timeout on the request */
-       event_add_timed(conn->event.event_ctx, req, 
-                       timeval_current_ofs(conn->timeout, 0),
-                       ldap_request_timeout, req);
+       req->time_event = event_add_timed(conn->event.event_ctx, req, 
+                                         timeval_current_ofs(conn->timeout, 0),
+                                         ldap_request_timeout, req);
 
        return req;
 
 failed:
-       talloc_free(req);
-       return NULL;
+       req->status = status;
+       req->state = LDAP_REQUEST_ERROR;
+       event_add_timed(conn->event.event_ctx, req, timeval_zero(),
+                       ldap_request_complete, req);
+
+       return req;
 }
 
 
@@ -537,7 +607,7 @@ failed:
 */
 NTSTATUS ldap_request_wait(struct ldap_request *req)
 {
-       while (req->state != LDAP_REQUEST_DONE) {
+       while (req->state < LDAP_REQUEST_DONE) {
                if (event_loop_once(req->conn->event.event_ctx) != 0) {
                        req->status = NT_STATUS_UNEXPECTED_NETWORK_ERROR;
                        break;
@@ -547,11 +617,63 @@ NTSTATUS ldap_request_wait(struct ldap_request *req)
 }
 
 
+/*
+  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)
 {
+       int i;
+       const char *codename = "unknown";
+
        if (r->resultcode == LDAP_SUCCESS) {
                return NT_STATUS_OK;
        }
@@ -559,8 +681,17 @@ NTSTATUS ldap_check_response(struct ldap_connection *conn, struct ldap_Result *r
        if (conn->last_error) {
                talloc_free(conn->last_error);
        }
-       conn->last_error = talloc_asprintf(conn, "LDAP error %u - %s <%s> <%s>", 
+
+       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;
+               }
+       }
+
+       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:"");
@@ -589,7 +720,7 @@ NTSTATUS ldap_result_n(struct ldap_request *req, int n, struct ldap_message **ms
 
        NT_STATUS_HAVE_NO_MEMORY(req);
 
-       while (req->state != LDAP_REQUEST_DONE && n >= req->num_replies) {
+       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;
                }