ctdb-failover: Split statd_callout add-client/del-client
[samba.git] / source4 / ldap_server / ldap_server.c
index 411be294401a67b3b4567783a9fb9faae45d857f..90316fd6b68cd2578cd57e07235775fda0484497 100644 (file)
@@ -1,4 +1,4 @@
-/* 
+/*
    Unix SMB/CIFS implementation.
 
    LDAP server
 #include "../lib/util/dlinklist.h"
 #include "../lib/util/asn1.h"
 #include "ldap_server/ldap_server.h"
-#include "smbd/service_task.h"
-#include "smbd/service_stream.h"
-#include "smbd/service.h"
-#include "smbd/process_model.h"
+#include "samba/service_task.h"
+#include "samba/service_stream.h"
+#include "samba/service.h"
+#include "samba/process_model.h"
 #include "lib/tls/tls.h"
 #include "lib/messaging/irpc.h"
 #include <ldb.h>
 #include "../lib/tsocket/tsocket.h"
 #include "../lib/util/tevent_ntstatus.h"
 #include "../libcli/util/tstream.h"
+#include "libds/common/roles.h"
+#include "lib/util/time.h"
+#include "lib/util/server_id.h"
+#include "lib/util/server_id_db.h"
+#include "lib/messaging/messaging_internal.h"
+
+#undef strcasecmp
 
 static void ldapsrv_terminate_connection_done(struct tevent_req *subreq);
 
@@ -61,9 +68,13 @@ static void ldapsrv_terminate_connection(struct ldapsrv_connection *conn,
                return;
        }
 
+       DLIST_REMOVE(conn->service->connections, conn);
+
        conn->limits.endtime = timeval_current_ofs(0, 500);
 
        tevent_queue_stop(conn->sockets.send_queue);
+       TALLOC_FREE(conn->sockets.read_req);
+       TALLOC_FREE(conn->deferred_expire_disconnect);
        if (conn->active_call) {
                tevent_req_cancel(conn->active_call);
                conn->active_call = NULL;
@@ -99,10 +110,10 @@ static void ldapsrv_terminate_connection_done(struct tevent_req *subreq)
        struct ldapsrv_connection *conn =
                tevent_req_callback_data(subreq,
                struct ldapsrv_connection);
-       int ret;
        int sys_errno;
+       bool ok;
 
-       ret = tstream_disconnect_recv(subreq, &sys_errno);
+       tstream_disconnect_recv(subreq, &sys_errno);
        TALLOC_FREE(subreq);
 
        if (conn->sockets.active == conn->sockets.raw) {
@@ -127,9 +138,15 @@ static void ldapsrv_terminate_connection_done(struct tevent_req *subreq)
                                            conn->limits.reason);
                return;
        }
-       tevent_req_set_endtime(subreq,
-                              conn->connection->event.ctx,
-                              conn->limits.endtime);
+       ok = tevent_req_set_endtime(subreq,
+                                   conn->connection->event.ctx,
+                                   conn->limits.endtime);
+       if (!ok) {
+               TALLOC_FREE(conn->sockets.raw);
+               stream_terminate_connection(conn->connection,
+                                           conn->limits.reason);
+               return;
+       }
        tevent_req_set_callback(subreq, ldapsrv_terminate_connection_done, conn);
 }
 
@@ -166,7 +183,11 @@ static int ldapsrv_load_limits(struct ldapsrv_connection *conn)
        conn->limits.initial_timeout = 120;
        conn->limits.conn_idle_time = 900;
        conn->limits.max_page_size = 1000;
+       conn->limits.max_notifications = 5;
        conn->limits.search_timeout = 120;
+       conn->limits.expire_time = (struct timeval) {
+               .tv_sec = get_time_t_max(),
+       };
 
 
        tmp_ctx = talloc_new(conn);
@@ -218,9 +239,8 @@ static int ldapsrv_load_limits(struct ldapsrv_connection *conn)
                int policy_value, s;
 
                s = sscanf((const char *)el->values[i].data, "%255[^=]=%d", policy_name, &policy_value);
-               if (ret != 2 || policy_value == 0)
+               if (s != 2 || policy_value == 0)
                        continue;
-
                if (strcasecmp("InitRecvTimeout", policy_name) == 0) {
                        conn->limits.initial_timeout = policy_value;
                        continue;
@@ -233,8 +253,14 @@ static int ldapsrv_load_limits(struct ldapsrv_connection *conn)
                        conn->limits.max_page_size = policy_value;
                        continue;
                }
+               if (strcasecmp("MaxNotificationPerConn", policy_name) == 0) {
+                       conn->limits.max_notifications = policy_value;
+                       continue;
+               }
                if (strcasecmp("MaxQueryDuration", policy_name) == 0) {
-                       conn->limits.search_timeout = policy_value;
+                       if (policy_value > 0) {
+                               conn->limits.search_timeout = policy_value;
+                       }
                        continue;
                }
        }
@@ -242,11 +268,23 @@ static int ldapsrv_load_limits(struct ldapsrv_connection *conn)
        return 0;
 
 failed:
-       DEBUG(0, ("Failed to load ldap server query policies\n"));
+       DBG_ERR("Failed to load ldap server query policies\n");
        talloc_free(tmp_ctx);
        return -1;
 }
 
+static int ldapsrv_call_destructor(struct ldapsrv_call *call)
+{
+       if (call->conn == NULL) {
+               return 0;
+       }
+
+       DLIST_REMOVE(call->conn->pending_calls, call);
+
+       call->conn = NULL;
+       return 0;
+}
+
 static struct tevent_req *ldapsrv_process_call_send(TALLOC_CTX *mem_ctx,
                                                    struct tevent_context *ev,
                                                    struct tevent_queue *call_queue,
@@ -262,18 +300,19 @@ static void ldapsrv_accept_tls_done(struct tevent_req *subreq);
 */
 static void ldapsrv_accept(struct stream_connection *c,
                           struct auth_session_info *session_info,
-                          bool is_privileged)
+                          bool is_privileged,
+                          bool is_ldapi)
 {
-       struct ldapsrv_service *ldapsrv_service = 
+       struct ldapsrv_service *ldapsrv_service =
                talloc_get_type(c->private_data, struct ldapsrv_service);
        struct ldapsrv_connection *conn;
        struct cli_credentials *server_credentials;
        struct socket_address *socket_address;
-       NTSTATUS status;
        int port;
        int ret;
        struct tevent_req *subreq;
        struct timeval endtime;
+       char *errstring = NULL;
 
        conn = talloc_zero(c, struct ldapsrv_connection);
        if (!conn) {
@@ -281,8 +320,9 @@ static void ldapsrv_accept(struct stream_connection *c,
                return;
        }
        conn->is_privileged = is_privileged;
+       conn->is_ldapi = is_ldapi;
 
-       conn->sockets.send_queue = tevent_queue_create(conn, "ldapsev send queue");
+       conn->sockets.send_queue = tevent_queue_create(conn, "ldapsrv send queue");
        if (conn->sockets.send_queue == NULL) {
                stream_terminate_connection(c,
                                            "ldapsrv_accept: tevent_queue_create failed");
@@ -300,10 +340,12 @@ static void ldapsrv_accept(struct stream_connection *c,
                return;
        }
        socket_set_flags(c->socket, SOCKET_FLAG_NOCLOSE);
+       /* as server we want to fail early */
+       tstream_bsd_fail_readv_first_error(conn->sockets.raw, true);
 
        conn->connection  = c;
        conn->service     = ldapsrv_service;
-       conn->lp_ctx      = ldapsrv_service->task->lp_ctx;
+       conn->lp_ctx      = ldapsrv_service->lp_ctx;
 
        c->private_data   = conn;
 
@@ -318,34 +360,52 @@ static void ldapsrv_accept(struct stream_connection *c,
                conn->global_catalog = true;
        }
 
-       server_credentials = cli_credentials_init(conn);
+       server_credentials = cli_credentials_init_server(conn, conn->lp_ctx);
        if (!server_credentials) {
                stream_terminate_connection(c, "Failed to init server credentials\n");
                return;
        }
 
-       cli_credentials_set_conf(server_credentials, conn->lp_ctx);
-       status = cli_credentials_set_machine_account(server_credentials, conn->lp_ctx);
-       if (!NT_STATUS_IS_OK(status)) {
-               stream_terminate_connection(c, talloc_asprintf(conn, "Failed to obtain server credentials, perhaps a standalone server?: %s\n", nt_errstr(status)));
-               return;
-       }
        conn->server_credentials = server_credentials;
 
        conn->session_info = session_info;
 
-       if (!NT_STATUS_IS_OK(ldapsrv_backend_Init(conn))) {
-               ldapsrv_terminate_connection(conn, "backend Init failed");
+       conn->sockets.active = conn->sockets.raw;
+
+       if (conn->is_privileged) {
+               conn->require_strong_auth = LDAP_SERVER_REQUIRE_STRONG_AUTH_NO;
+       } else {
+               conn->require_strong_auth = lpcfg_ldap_server_require_strong_auth(conn->lp_ctx);
+       }
+
+       if (conn->require_strong_auth ==
+           LDAP_SERVER_REQUIRE_STRONG_AUTH_ALLOW_SASL_OVER_TLS)
+       {
+               D_ERR("WARNING: You have not configured "
+                     "'ldap server require strong auth = "
+                     "allow_sasl_over_tls'.\n"
+                     "Please change to 'yes' (preferred and default) or "
+                     "'allow_sasl_without_tls_channel_bindings' "
+                     "(if really needed)\n\n");
+       }
+
+       ret = ldapsrv_backend_Init(conn, &errstring);
+       if (ret != LDB_SUCCESS) {
+               char *reason = talloc_asprintf(conn,
+                                              "LDB backend for LDAP Init "
+                                              "failed: %s: %s",
+                                              errstring, ldb_strerror(ret));
+               ldapsrv_terminate_connection(conn, reason);
                return;
        }
 
        /* load limits from the conf partition */
        ldapsrv_load_limits(conn); /* should we fail on error ? */
 
-       /* register the server */       
+       /* register the server */
        irpc_add_name(c->msg_ctx, "ldap_server");
 
-       conn->sockets.active = conn->sockets.raw;
+       DLIST_ADD_END(ldapsrv_service->connections, conn);
 
        if (port != 636 && port != 3269) {
                ldapsrv_call_read_next(conn);
@@ -396,16 +456,26 @@ static void ldapsrv_accept_tls_done(struct tevent_req *subreq)
        }
 
        conn->sockets.active = conn->sockets.tls;
+       conn->referral_scheme = LDAP_REFERRAL_SCHEME_LDAPS;
        ldapsrv_call_read_next(conn);
 }
 
 static void ldapsrv_call_read_done(struct tevent_req *subreq);
+static NTSTATUS ldapsrv_packet_check(
+       struct tstream_context *stream,
+       void *private_data,
+       DATA_BLOB blob,
+       size_t *packet_size);
 
 static bool ldapsrv_call_read_next(struct ldapsrv_connection *conn)
 {
        struct tevent_req *subreq;
 
-       if (timeval_is_zero(&conn->limits.endtime)) {
+       if (conn->pending_calls != NULL) {
+               conn->limits.endtime = timeval_zero();
+
+               ldapsrv_notification_retry_setup(conn->service, false);
+       } else if (timeval_is_zero(&conn->limits.endtime)) {
                conn->limits.endtime =
                        timeval_current_ofs(conn->limits.initial_timeout, 0);
        } else {
@@ -413,8 +483,12 @@ static bool ldapsrv_call_read_next(struct ldapsrv_connection *conn)
                        timeval_current_ofs(conn->limits.conn_idle_time, 0);
        }
 
+       if (conn->sockets.read_req != NULL) {
+               return true;
+       }
+
        /*
-        * The minimun size of a LDAP pdu is 7 bytes
+        * The minimum size of a LDAP pdu is 7 bytes
         *
         * dumpasn1 -hh ldap-unbind-min.dat
         *
@@ -445,21 +519,35 @@ static bool ldapsrv_call_read_next(struct ldapsrv_connection *conn)
                                            conn->connection->event.ctx,
                                            conn->sockets.active,
                                            7, /* initial_read_size */
-                                           ldap_full_packet,
+                                           ldapsrv_packet_check,
                                            conn);
        if (subreq == NULL) {
                ldapsrv_terminate_connection(conn, "ldapsrv_call_read_next: "
                                "no memory for tstream_read_pdu_blob_send");
                return false;
        }
-       tevent_req_set_endtime(subreq,
-                              conn->connection->event.ctx,
-                              conn->limits.endtime);
+       if (!timeval_is_zero(&conn->limits.endtime)) {
+               bool ok;
+               ok = tevent_req_set_endtime(subreq,
+                                           conn->connection->event.ctx,
+                                           conn->limits.endtime);
+               if (!ok) {
+                       ldapsrv_terminate_connection(
+                               conn,
+                               "ldapsrv_call_read_next: "
+                               "no memory for tevent_req_set_endtime");
+                       return false;
+               }
+       }
        tevent_req_set_callback(subreq, ldapsrv_call_read_done, conn);
+       conn->sockets.read_req = subreq;
        return true;
 }
 
 static void ldapsrv_call_process_done(struct tevent_req *subreq);
+static int ldapsrv_check_packet_size(
+       struct ldapsrv_connection *conn,
+       size_t size);
 
 static void ldapsrv_call_read_done(struct tevent_req *subreq)
 {
@@ -470,12 +558,17 @@ static void ldapsrv_call_read_done(struct tevent_req *subreq)
        struct ldapsrv_call *call;
        struct asn1_data *asn1;
        DATA_BLOB blob;
+       int ret = LDAP_SUCCESS;
+       struct ldap_request_limits limits = {0};
+
+       conn->sockets.read_req = NULL;
 
        call = talloc_zero(conn, struct ldapsrv_call);
        if (!call) {
                ldapsrv_terminate_connection(conn, "no memory");
                return;
        }
+       talloc_set_destructor(call, ldapsrv_call_destructor);
 
        call->conn = conn;
 
@@ -497,7 +590,15 @@ static void ldapsrv_call_read_done(struct tevent_req *subreq)
                return;
        }
 
-       asn1 = asn1_init(call);
+       ret = ldapsrv_check_packet_size(conn, blob.length);
+       if (ret != LDAP_SUCCESS) {
+               ldapsrv_terminate_connection(
+                       conn,
+                       "Request packet too large");
+               return;
+       }
+
+       asn1 = asn1_init(call, ASN1_MAX_TREE_DEPTH);
        if (asn1 == NULL) {
                ldapsrv_terminate_connection(conn, "no memory");
                return;
@@ -509,19 +610,22 @@ static void ldapsrv_call_read_done(struct tevent_req *subreq)
                return;
        }
 
-       if (!asn1_load(asn1, blob)) {
-               ldapsrv_terminate_connection(conn, "asn1_load failed");
-               return;
-       }
+       asn1_load_nocopy(asn1, blob.data, blob.length);
 
-       status = ldap_decode(asn1, samba_ldap_control_handlers(),
-                            call->request);
+       limits.max_search_size =
+               lpcfg_ldap_max_search_request_size(conn->lp_ctx);
+       status = ldap_decode(
+               asn1,
+               &limits,
+               samba_ldap_control_handlers(),
+               call->request);
        if (!NT_STATUS_IS_OK(status)) {
                ldapsrv_terminate_connection(conn, nt_errstr(status));
                return;
        }
 
        data_blob_free(&blob);
+       TALLOC_FREE(asn1);
 
 
        /* queue the call in the global queue */
@@ -537,6 +641,8 @@ static void ldapsrv_call_read_done(struct tevent_req *subreq)
        conn->active_call = subreq;
 }
 
+static void ldapsrv_call_wait_done(struct tevent_req *subreq);
+static void ldapsrv_call_writev_start(struct ldapsrv_call *call);
 static void ldapsrv_call_writev_done(struct tevent_req *subreq);
 
 static void ldapsrv_call_process_done(struct tevent_req *subreq)
@@ -546,7 +652,6 @@ static void ldapsrv_call_process_done(struct tevent_req *subreq)
                struct ldapsrv_call);
        struct ldapsrv_connection *conn = call->conn;
        NTSTATUS status;
-       DATA_BLOB blob = data_blob_null;
 
        conn->active_call = NULL;
 
@@ -557,50 +662,149 @@ static void ldapsrv_call_process_done(struct tevent_req *subreq)
                return;
        }
 
-       /* build all the replies into a single blob */
-       while (call->replies) {
-               DATA_BLOB b;
-               bool ret;
-
-               if (!ldap_encode(call->replies->msg, samba_ldap_control_handlers(), &b, call)) {
-                       DEBUG(0,("Failed to encode ldap reply of type %d\n",
-                                call->replies->msg->type));
-                       ldapsrv_terminate_connection(conn, "ldap_encode failed");
+       if (call->wait_send != NULL) {
+               subreq = call->wait_send(call,
+                                        conn->connection->event.ctx,
+                                        call->wait_private);
+               if (subreq == NULL) {
+                       ldapsrv_terminate_connection(conn,
+                                       "ldapsrv_call_process_done: "
+                                       "call->wait_send - no memory");
                        return;
                }
+               tevent_req_set_callback(subreq,
+                                       ldapsrv_call_wait_done,
+                                       call);
+               conn->active_call = subreq;
+               return;
+       }
+
+       ldapsrv_call_writev_start(call);
+}
+
+static void ldapsrv_call_wait_done(struct tevent_req *subreq)
+{
+       struct ldapsrv_call *call =
+               tevent_req_callback_data(subreq,
+               struct ldapsrv_call);
+       struct ldapsrv_connection *conn = call->conn;
+       NTSTATUS status;
 
-               ret = data_blob_append(call, &blob, b.data, b.length);
-               data_blob_free(&b);
+       conn->active_call = NULL;
 
-               talloc_set_name_const(blob.data, "Outgoing, encoded LDAP packet");
+       status = call->wait_recv(subreq);
+       TALLOC_FREE(subreq);
+       if (!NT_STATUS_IS_OK(status)) {
+               const char *reason;
 
-               if (!ret) {
-                       ldapsrv_terminate_connection(conn, "data_blob_append failed");
-                       return;
+               reason = talloc_asprintf(call, "ldapsrv_call_wait_done: "
+                                        "call->wait_recv() - %s",
+                                        nt_errstr(status));
+               if (reason == NULL) {
+                       reason = nt_errstr(status);
                }
 
-               DLIST_REMOVE(call->replies, call->replies);
+               ldapsrv_terminate_connection(conn, reason);
+               return;
        }
 
-       if (blob.length == 0) {
-               TALLOC_FREE(call);
+       ldapsrv_call_writev_start(call);
+}
+
+static void ldapsrv_call_writev_start(struct ldapsrv_call *call)
+{
+       struct ldapsrv_connection *conn = call->conn;
+       struct ldapsrv_reply *reply = NULL;
+       struct tevent_req *subreq = NULL;
+       struct timeval endtime;
+       size_t length = 0;
+       size_t i;
+
+       call->iov_count = 0;
+
+       /* build all the replies into an IOV (no copy) */
+       for (reply = call->replies;
+            reply != NULL;
+            reply = reply->next) {
+
+               /* Cap output at 25MB per writev() */
+               if (length > length + reply->blob.length
+                   || length + reply->blob.length > LDAP_SERVER_MAX_CHUNK_SIZE) {
+                       break;
+               }
+
+               /*
+                * Overflow is harmless here, just used below to
+                * decide if to read or write, but checked above anyway
+                */
+               length += reply->blob.length;
+
+               /*
+                * At worst an overflow would mean we send less
+                * replies
+                */
+               call->iov_count++;
+       }
+
+       if (length == 0) {
+               if (!call->notification.busy) {
+                       TALLOC_FREE(call);
+               }
 
                ldapsrv_call_read_next(conn);
                return;
        }
 
-       call->out_iov.iov_base = blob.data;
-       call->out_iov.iov_len = blob.length;
+       /* Cap call->iov_count at IOV_MAX */
+       call->iov_count = MIN(call->iov_count, IOV_MAX);
+
+       call->out_iov = talloc_array(call,
+                                    struct iovec,
+                                    call->iov_count);
+       if (!call->out_iov) {
+               /* This is not ideal */
+               ldapsrv_terminate_connection(conn,
+                                            "failed to allocate "
+                                            "iovec array");
+               return;
+       }
+
+       /* We may have had to cap the number of replies at IOV_MAX */
+       for (i = 0;
+            i < call->iov_count && call->replies != NULL;
+            i++) {
+               reply = call->replies;
+               call->out_iov[i].iov_base = reply->blob.data;
+               call->out_iov[i].iov_len = reply->blob.length;
+
+               /* Keep only the ASN.1 encoded data */
+               talloc_steal(call->out_iov, reply->blob.data);
+
+               DLIST_REMOVE(call->replies, reply);
+               TALLOC_FREE(reply);
+       }
+
+       if (i > call->iov_count) {
+               /* This is not ideal, but also (essentially) impossible */
+               ldapsrv_terminate_connection(conn,
+                                            "call list ended"
+                                            "before iov_count");
+               return;
+       }
 
        subreq = tstream_writev_queue_send(call,
                                           conn->connection->event.ctx,
                                           conn->sockets.active,
                                           conn->sockets.send_queue,
-                                          &call->out_iov, 1);
+                                          call->out_iov, call->iov_count);
        if (subreq == NULL) {
                ldapsrv_terminate_connection(conn, "stream_writev_queue_send failed");
                return;
        }
+       endtime = timeval_current_ofs(conn->limits.conn_idle_time, 0);
+       tevent_req_set_endtime(subreq,
+                              conn->connection->event.ctx,
+                              endtime);
        tevent_req_set_callback(subreq, ldapsrv_call_writev_done, call);
 }
 
@@ -617,6 +821,9 @@ static void ldapsrv_call_writev_done(struct tevent_req *subreq)
 
        rc = tstream_writev_queue_recv(subreq, &sys_errno);
        TALLOC_FREE(subreq);
+
+       /* This releases the ASN.1 encoded packets from memory */
+       TALLOC_FREE(call->out_iov);
        if (rc == -1) {
                const char *reason;
 
@@ -647,7 +854,15 @@ static void ldapsrv_call_writev_done(struct tevent_req *subreq)
                return;
        }
 
-       TALLOC_FREE(call);
+       /* Perhaps still some more to send */
+       if (call->replies != NULL) {
+               ldapsrv_call_writev_start(call);
+               return;
+       }
+
+       if (!call->notification.busy) {
+               TALLOC_FREE(call);
+       }
 
        ldapsrv_call_read_next(conn);
 }
@@ -681,6 +896,112 @@ static void ldapsrv_call_postprocess_done(struct tevent_req *subreq)
        ldapsrv_call_read_next(conn);
 }
 
+static void ldapsrv_notification_retry_done(struct tevent_req *subreq);
+
+void ldapsrv_notification_retry_setup(struct ldapsrv_service *service, bool force)
+{
+       struct ldapsrv_connection *conn = NULL;
+       struct timeval retry;
+       size_t num_pending = 0;
+       size_t num_active = 0;
+
+       if (force) {
+               TALLOC_FREE(service->notification.retry);
+               service->notification.generation += 1;
+       }
+
+       if (service->notification.retry != NULL) {
+               return;
+       }
+
+       for (conn = service->connections; conn != NULL; conn = conn->next) {
+               if (conn->pending_calls == NULL) {
+                       continue;
+               }
+
+               num_pending += 1;
+
+               if (conn->pending_calls->notification.generation !=
+                   service->notification.generation)
+               {
+                       num_active += 1;
+               }
+       }
+
+       if (num_pending == 0) {
+               return;
+       }
+
+       if (num_active != 0) {
+               retry = timeval_current_ofs(0, 100);
+       } else {
+               retry = timeval_current_ofs(5, 0);
+       }
+
+       service->notification.retry = tevent_wakeup_send(service,
+                                                        service->current_ev,
+                                                        retry);
+       if (service->notification.retry == NULL) {
+               /* retry later */
+               return;
+       }
+
+       tevent_req_set_callback(service->notification.retry,
+                               ldapsrv_notification_retry_done,
+                               service);
+}
+
+static void ldapsrv_notification_retry_done(struct tevent_req *subreq)
+{
+       struct ldapsrv_service *service =
+               tevent_req_callback_data(subreq,
+               struct ldapsrv_service);
+       struct ldapsrv_connection *conn = NULL;
+       struct ldapsrv_connection *conn_next = NULL;
+       bool ok;
+
+       service->notification.retry = NULL;
+
+       ok = tevent_wakeup_recv(subreq);
+       TALLOC_FREE(subreq);
+       if (!ok) {
+               /* ignore */
+       }
+
+       for (conn = service->connections; conn != NULL; conn = conn_next) {
+               struct ldapsrv_call *call = conn->pending_calls;
+
+               conn_next = conn->next;
+
+               if (conn->pending_calls == NULL) {
+                       continue;
+               }
+
+               if (conn->active_call != NULL) {
+                       continue;
+               }
+
+               DLIST_DEMOTE(conn->pending_calls, call);
+               call->notification.generation =
+                               service->notification.generation;
+
+               /* queue the call in the global queue */
+               subreq = ldapsrv_process_call_send(call,
+                                                  conn->connection->event.ctx,
+                                                  conn->service->call_queue,
+                                                  call);
+               if (subreq == NULL) {
+                       ldapsrv_terminate_connection(conn,
+                                       "ldapsrv_process_call_send failed");
+                       continue;
+               }
+               tevent_req_set_callback(subreq, ldapsrv_call_process_done, call);
+               conn->active_call = subreq;
+       }
+
+       ldapsrv_notification_retry_setup(service, false);
+}
+
 struct ldapsrv_process_call_state {
        struct ldapsrv_call *call;
 };
@@ -715,16 +1036,62 @@ static struct tevent_req *ldapsrv_process_call_send(TALLOC_CTX *mem_ctx,
        return req;
 }
 
+static void ldapsrv_disconnect_ticket_expired(struct tevent_req *subreq);
+
 static void ldapsrv_process_call_trigger(struct tevent_req *req,
                                         void *private_data)
 {
        struct ldapsrv_process_call_state *state =
                tevent_req_data(req,
                struct ldapsrv_process_call_state);
+       struct ldapsrv_connection *conn = state->call->conn;
        NTSTATUS status;
 
+       if (conn->deferred_expire_disconnect != NULL) {
+               /*
+                * Just drop this on the floor
+                */
+               tevent_req_done(req);
+               return;
+       }
+
        /* make the call */
        status = ldapsrv_do_call(state->call);
+
+       if (NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_SESSION_EXPIRED)) {
+               /*
+                * For testing purposes, defer the TCP disconnect
+                * after having sent the msgid 0
+                * 1.3.6.1.4.1.1466.20036 exop response. LDAP clients
+                * should not wait for the TCP connection to close but
+                * handle this packet equivalent to a TCP
+                * disconnect. This delay enables testing both cases
+                * in LDAP client libraries.
+                */
+
+               int defer_msec = lpcfg_parm_int(
+                       conn->lp_ctx,
+                       NULL,
+                       "ldap_server",
+                       "delay_expire_disconnect",
+                       0);
+
+               conn->deferred_expire_disconnect = tevent_wakeup_send(
+                       conn,
+                       conn->connection->event.ctx,
+                       timeval_current_ofs_msec(defer_msec));
+               if (tevent_req_nomem(conn->deferred_expire_disconnect, req)) {
+                       return;
+               }
+               tevent_req_set_callback(
+                       conn->deferred_expire_disconnect,
+                       ldapsrv_disconnect_ticket_expired,
+                       conn);
+
+               tevent_req_done(req);
+               return;
+       }
+
        if (!NT_STATUS_IS_OK(status)) {
                tevent_req_nterror(req, status);
                return;
@@ -733,6 +1100,21 @@ static void ldapsrv_process_call_trigger(struct tevent_req *req,
        tevent_req_done(req);
 }
 
+static void ldapsrv_disconnect_ticket_expired(struct tevent_req *subreq)
+{
+       struct ldapsrv_connection *conn = tevent_req_callback_data(
+               subreq, struct ldapsrv_connection);
+       bool ok;
+
+       ok = tevent_wakeup_recv(subreq);
+       TALLOC_FREE(subreq);
+       if (!ok) {
+               DBG_WARNING("tevent_wakeup_recv failed\n");
+       }
+       conn->deferred_expire_disconnect = NULL;
+       ldapsrv_terminate_connection(conn, "network session expired");
+}
+
 static NTSTATUS ldapsrv_process_call_recv(struct tevent_req *req)
 {
        NTSTATUS status;
@@ -754,13 +1136,13 @@ static void ldapsrv_accept_nonpriv(struct stream_connection *c)
        NTSTATUS status;
 
        status = auth_anonymous_session_info(
-               c, ldapsrv_service->task->lp_ctx, &session_info);
+               c, ldapsrv_service->lp_ctx, &session_info);
        if (!NT_STATUS_IS_OK(status)) {
                stream_terminate_connection(c, "failed to setup anonymous "
                                            "session info");
                return;
        }
-       ldapsrv_accept(c, session_info, false);
+       ldapsrv_accept(c, session_info, false, false);
 }
 
 static const struct stream_server_ops ldap_stream_nonpriv_ops = {
@@ -770,30 +1152,54 @@ static const struct stream_server_ops ldap_stream_nonpriv_ops = {
        .send_handler           = ldapsrv_send,
 };
 
+static void ldapsrv_accept_nonpriv_ldapi(struct stream_connection *c)
+{
+       struct ldapsrv_service *ldapsrv_service = talloc_get_type_abort(
+               c->private_data, struct ldapsrv_service);
+       struct auth_session_info *session_info;
+       NTSTATUS status;
+
+       status = auth_anonymous_session_info(
+               c, ldapsrv_service->lp_ctx, &session_info);
+       if (!NT_STATUS_IS_OK(status)) {
+               stream_terminate_connection(c, "failed to setup anonymous "
+                                           "session info");
+               return;
+       }
+       ldapsrv_accept(c, session_info, false, true);
+}
+
+static const struct stream_server_ops ldapi_stream_nonpriv_ops = {
+       .name                   = "ldap",
+       .accept_connection      = ldapsrv_accept_nonpriv_ldapi,
+       .recv_handler           = ldapsrv_recv,
+       .send_handler           = ldapsrv_send,
+};
+
 /* The feature removed behind an #ifdef until we can do it properly
  * with an EXTERNAL bind. */
 
 #define WITH_LDAPI_PRIV_SOCKET
 
 #ifdef WITH_LDAPI_PRIV_SOCKET
-static void ldapsrv_accept_priv(struct stream_connection *c)
+static void ldapsrv_accept_priv_ldapi(struct stream_connection *c)
 {
        struct ldapsrv_service *ldapsrv_service = talloc_get_type_abort(
                c->private_data, struct ldapsrv_service);
        struct auth_session_info *session_info;
 
-       session_info = system_session(ldapsrv_service->task->lp_ctx);
+       session_info = system_session(ldapsrv_service->lp_ctx);
        if (!session_info) {
                stream_terminate_connection(c, "failed to setup system "
                                            "session info");
                return;
        }
-       ldapsrv_accept(c, session_info, true);
+       ldapsrv_accept(c, session_info, true, true);
 }
 
-static const struct stream_server_ops ldap_stream_priv_ops = {
+static const struct stream_server_ops ldapi_stream_priv_ops = {
        .name                   = "ldap",
-       .accept_connection      = ldapsrv_accept_priv,
+       .accept_connection      = ldapsrv_accept_priv_ldapi,
        .recv_handler           = ldapsrv_recv,
        .send_handler           = ldapsrv_send,
 };
@@ -817,10 +1223,10 @@ static NTSTATUS add_socket(struct task_server *task,
                                     model_ops, &ldap_stream_nonpriv_ops,
                                     "ip", address, &port,
                                     lpcfg_socket_options(lp_ctx),
-                                    ldap_service);
+                                    ldap_service, task->process_context);
        if (!NT_STATUS_IS_OK(status)) {
-               DEBUG(0,("ldapsrv failed to bind to %s:%u - %s\n",
-                        address, port, nt_errstr(status)));
+               DBG_ERR("ldapsrv failed to bind to %s:%u - %s\n",
+                       address, port, nt_errstr(status));
                return status;
        }
 
@@ -832,17 +1238,22 @@ static NTSTATUS add_socket(struct task_server *task,
                                             &ldap_stream_nonpriv_ops,
                                             "ip", address, &port,
                                             lpcfg_socket_options(lp_ctx),
-                                            ldap_service);
+                                            ldap_service,
+                                            task->process_context);
                if (!NT_STATUS_IS_OK(status)) {
-                       DEBUG(0,("ldapsrv failed to bind to %s:%u - %s\n",
-                                address, port, nt_errstr(status)));
+                       DBG_ERR("ldapsrv failed to bind to %s:%u - %s\n",
+                               address, port, nt_errstr(status));
                        return status;
                }
        }
 
        /* Load LDAP database, but only to read our settings */
-       ldb = samdb_connect(ldap_service, ldap_service->task->event_ctx, 
-                           lp_ctx, system_session(lp_ctx), 0);
+       ldb = samdb_connect(ldap_service,
+                           ldap_service->current_ev,
+                           lp_ctx,
+                           system_session(lp_ctx),
+                           NULL,
+                           0);
        if (!ldb) {
                return NT_STATUS_INTERNAL_DB_CORRUPTION;
        }
@@ -854,10 +1265,11 @@ static NTSTATUS add_socket(struct task_server *task,
                                             &ldap_stream_nonpriv_ops,
                                             "ip", address, &port,
                                             lpcfg_socket_options(lp_ctx),
-                                            ldap_service);
+                                            ldap_service,
+                                            task->process_context);
                if (!NT_STATUS_IS_OK(status)) {
-                       DEBUG(0,("ldapsrv failed to bind to %s:%u - %s\n",
-                                address, port, nt_errstr(status)));
+                       DBG_ERR("ldapsrv failed to bind to %s:%u - %s\n",
+                               address, port, nt_errstr(status));
                        return status;
                }
                if (tstream_tls_params_enabled(ldap_service->tls_params)) {
@@ -868,10 +1280,11 @@ static NTSTATUS add_socket(struct task_server *task,
                                                     &ldap_stream_nonpriv_ops,
                                                     "ip", address, &port,
                                                     lpcfg_socket_options(lp_ctx),
-                                                    ldap_service);
+                                                    ldap_service,
+                                                    task->process_context);
                        if (!NT_STATUS_IS_OK(status)) {
-                               DEBUG(0,("ldapsrv failed to bind to %s:%u - %s\n",
-                                        address, port, nt_errstr(status)));
+                               DBG_ERR("ldapsrv failed to bind to %s:%u - %s\n",
+                                       address, port, nt_errstr(status));
                                return status;
                        }
                }
@@ -884,67 +1297,175 @@ static NTSTATUS add_socket(struct task_server *task,
        return NT_STATUS_OK;
 }
 
+static void ldap_reload_certs(struct imessaging_context *msg_ctx,
+                             void *private_data,
+                             uint32_t msg_type,
+                             struct server_id server_id,
+                             size_t num_fds,
+                             int *fds,
+                             DATA_BLOB *data)
+{
+       TALLOC_CTX *frame = talloc_stackframe();
+       struct ldapsrv_service *ldap_service =
+               talloc_get_type_abort(private_data,
+               struct ldapsrv_service);
+       int default_children;
+       int num_children;
+       int i;
+       bool ok;
+       struct server_id ldap_master_id;
+       NTSTATUS status;
+       struct tstream_tls_params *new_tls_params = NULL;
+
+       SMB_ASSERT(msg_ctx == ldap_service->current_msg);
+
+       /* reload certificates */
+       status = tstream_tls_params_server(ldap_service,
+                                          ldap_service->dns_host_name,
+                                          lpcfg_tls_enabled(ldap_service->lp_ctx),
+                                          lpcfg_tls_keyfile(frame, ldap_service->lp_ctx),
+                                          lpcfg_tls_certfile(frame, ldap_service->lp_ctx),
+                                          lpcfg_tls_cafile(frame, ldap_service->lp_ctx),
+                                          lpcfg_tls_crlfile(frame, ldap_service->lp_ctx),
+                                          lpcfg_tls_dhpfile(frame, ldap_service->lp_ctx),
+                                          lpcfg_tls_priority(ldap_service->lp_ctx),
+                                          &new_tls_params);
+       if (!NT_STATUS_IS_OK(status)) {
+               DBG_ERR("ldapsrv failed tstream_tls_params_server - %s\n",
+                       nt_errstr(status));
+               TALLOC_FREE(frame);
+               return;
+       }
+
+       TALLOC_FREE(ldap_service->tls_params);
+       ldap_service->tls_params = new_tls_params;
+
+       if (getpid() != ldap_service->parent_pid) {
+               /*
+                * If we are not the master process we are done
+                */
+               TALLOC_FREE(frame);
+               return;
+       }
+
+       /*
+        * Check we're running under the prefork model,
+        * by checking if the prefork-master-ldap name
+        * was registered
+        */
+       ok = server_id_db_lookup_one(msg_ctx->names, "prefork-master-ldap", &ldap_master_id);
+       if (!ok) {
+               /*
+                * We are done if another process model is in use.
+                */
+               TALLOC_FREE(frame);
+               return;
+       }
+
+       /*
+        * Now we loop over all possible prefork workers
+        * in order to notify them about the reload
+        */
+       default_children = lpcfg_prefork_children(ldap_service->lp_ctx);
+       num_children = lpcfg_parm_int(ldap_service->lp_ctx,
+                                     NULL, "prefork children", "ldap",
+                                     default_children);
+       for (i = 0; i < num_children; i++) {
+               char child_name[64] = { 0, };
+               struct server_id ldap_worker_id;
+
+               snprintf(child_name, sizeof(child_name), "prefork-worker-ldap-%d", i);
+               ok = server_id_db_lookup_one(msg_ctx->names, child_name, &ldap_worker_id);
+               if (!ok) {
+                       DBG_ERR("server_id_db_lookup_one(%s) - failed\n",
+                               child_name);
+                       continue;
+               }
+
+               status = imessaging_send(msg_ctx, ldap_worker_id,
+                                        MSG_RELOAD_TLS_CERTIFICATES, NULL);
+               if (!NT_STATUS_IS_OK(status)) {
+                       struct server_id_buf id_buf;
+                       DBG_ERR("ldapsrv failed imessaging_send(%s, %s) - %s\n",
+                               child_name,
+                               server_id_str_buf(ldap_worker_id, &id_buf),
+                               nt_errstr(status));
+                       continue;
+               }
+       }
+
+       TALLOC_FREE(frame);
+}
+
 /*
   open the ldap server sockets
 */
-static void ldapsrv_task_init(struct task_server *task)
-{      
+static NTSTATUS ldapsrv_task_init(struct task_server *task)
+{
        char *ldapi_path;
 #ifdef WITH_LDAPI_PRIV_SOCKET
        char *priv_dir;
 #endif
-       const char *dns_host_name;
        struct ldapsrv_service *ldap_service;
        NTSTATUS status;
-       const struct model_ops *model_ops;
 
        switch (lpcfg_server_role(task->lp_ctx)) {
        case ROLE_STANDALONE:
-               task_server_terminate(task, "ldap_server: no LDAP server required in standalone configuration", 
+               task_server_terminate(task, "ldap_server: no LDAP server required in standalone configuration",
                                      false);
-               return;
+               return NT_STATUS_INVALID_DOMAIN_ROLE;
        case ROLE_DOMAIN_MEMBER:
-               task_server_terminate(task, "ldap_server: no LDAP server required in member server configuration", 
+               task_server_terminate(task, "ldap_server: no LDAP server required in member server configuration",
                                      false);
-               return;
-       case ROLE_DOMAIN_CONTROLLER:
+               return NT_STATUS_INVALID_DOMAIN_ROLE;
+       case ROLE_ACTIVE_DIRECTORY_DC:
                /* Yes, we want an LDAP server */
                break;
        }
 
        task_server_set_title(task, "task[ldapsrv]");
 
-       /* run the ldap server as a single process */
-       model_ops = process_model_startup("single");
-       if (!model_ops) goto failed;
-
        ldap_service = talloc_zero(task, struct ldapsrv_service);
-       if (ldap_service == NULL) goto failed;
+       if (ldap_service == NULL) {
+               status = NT_STATUS_NO_MEMORY;
+               goto failed;
+       }
 
-       ldap_service->task = task;
+       ldap_service->lp_ctx = task->lp_ctx;
+       ldap_service->current_ev = task->event_ctx;
+       ldap_service->current_msg = task->msg_ctx;
 
-       dns_host_name = talloc_asprintf(ldap_service, "%s.%s",
+       ldap_service->dns_host_name = talloc_asprintf(ldap_service, "%s.%s",
                                        lpcfg_netbios_name(task->lp_ctx),
                                        lpcfg_dnsdomain(task->lp_ctx));
-       if (dns_host_name == NULL) goto failed;
+       if (ldap_service->dns_host_name == NULL) {
+               status = NT_STATUS_NO_MEMORY;
+               goto failed;
+       }
+
+       ldap_service->parent_pid = getpid();
 
        status = tstream_tls_params_server(ldap_service,
-                                          dns_host_name,
+                                          ldap_service->dns_host_name,
                                           lpcfg_tls_enabled(task->lp_ctx),
                                           lpcfg_tls_keyfile(ldap_service, task->lp_ctx),
                                           lpcfg_tls_certfile(ldap_service, task->lp_ctx),
                                           lpcfg_tls_cafile(ldap_service, task->lp_ctx),
                                           lpcfg_tls_crlfile(ldap_service, task->lp_ctx),
                                           lpcfg_tls_dhpfile(ldap_service, task->lp_ctx),
+                                          lpcfg_tls_priority(task->lp_ctx),
                                           &ldap_service->tls_params);
        if (!NT_STATUS_IS_OK(status)) {
-               DEBUG(0,("ldapsrv failed tstream_tls_patams_server - %s\n",
-                        nt_errstr(status)));
+               DBG_ERR("ldapsrv failed tstream_tls_params_server - %s\n",
+                       nt_errstr(status));
                goto failed;
        }
 
        ldap_service->call_queue = tevent_queue_create(ldap_service, "ldapsrv_call_queue");
-       if (ldap_service->call_queue == NULL) goto failed;
+       if (ldap_service->call_queue == NULL) {
+               status = NT_STATUS_NO_MEMORY;
+               goto failed;
+       }
 
        if (lpcfg_interfaces(task->lp_ctx) && lpcfg_bind_interfaces_only(task->lp_ctx)) {
                struct interface *ifaces;
@@ -954,86 +1475,257 @@ static void ldapsrv_task_init(struct task_server *task)
                load_interface_list(task, task->lp_ctx, &ifaces);
                num_interfaces = iface_list_count(ifaces);
 
-               /* We have been given an interfaces line, and been 
+               /* We have been given an interfaces line, and been
                   told to only bind to those interfaces. Create a
                   socket per interface and bind to only these.
                */
                for(i = 0; i < num_interfaces; i++) {
                        const char *address = iface_list_n_ip(ifaces, i);
-                       status = add_socket(task, task->lp_ctx, model_ops, address, ldap_service);
+                       status = add_socket(task, task->lp_ctx, task->model_ops,
+                                           address, ldap_service);
                        if (!NT_STATUS_IS_OK(status)) goto failed;
                }
        } else {
-               const char **wcard;
-               int i;
-               wcard = iface_list_wildcard(task, task->lp_ctx);
+               char **wcard;
+               size_t i;
+               size_t num_binds = 0;
+               wcard = iface_list_wildcard(task);
                if (wcard == NULL) {
-                       DEBUG(0,("No wildcard addresses available\n"));
+                       DBG_ERR("No wildcard addresses available\n");
+                       status = NT_STATUS_UNSUCCESSFUL;
                        goto failed;
                }
                for (i=0; wcard[i]; i++) {
-                       status = add_socket(task, task->lp_ctx, model_ops, wcard[i], ldap_service);
-                       if (!NT_STATUS_IS_OK(status)) goto failed;
+                       status = add_socket(task, task->lp_ctx, task->model_ops,
+                                           wcard[i], ldap_service);
+                       if (NT_STATUS_IS_OK(status)) {
+                               num_binds++;
+                       }
                }
                talloc_free(wcard);
+               if (num_binds == 0) {
+                       status = NT_STATUS_UNSUCCESSFUL;
+                       goto failed;
+               }
        }
 
        ldapi_path = lpcfg_private_path(ldap_service, task->lp_ctx, "ldapi");
        if (!ldapi_path) {
+               status = NT_STATUS_UNSUCCESSFUL;
                goto failed;
        }
 
        status = stream_setup_socket(task, task->event_ctx, task->lp_ctx,
-                                    model_ops, &ldap_stream_nonpriv_ops,
-                                    "unix", ldapi_path, NULL, 
+                                    task->model_ops, &ldapi_stream_nonpriv_ops,
+                                    "unix", ldapi_path, NULL,
                                     lpcfg_socket_options(task->lp_ctx),
-                                    ldap_service);
+                                    ldap_service, task->process_context);
        talloc_free(ldapi_path);
        if (!NT_STATUS_IS_OK(status)) {
-               DEBUG(0,("ldapsrv failed to bind to %s - %s\n",
-                        ldapi_path, nt_errstr(status)));
+               DBG_ERR("ldapsrv failed to bind to %s - %s\n",
+                       ldapi_path, nt_errstr(status));
        }
 
 #ifdef WITH_LDAPI_PRIV_SOCKET
        priv_dir = lpcfg_private_path(ldap_service, task->lp_ctx, "ldap_priv");
        if (priv_dir == NULL) {
+               status = NT_STATUS_UNSUCCESSFUL;
                goto failed;
        }
        /*
         * Make sure the directory for the privileged ldapi socket exists, and
         * is of the correct permissions
         */
-       if (!directory_create_or_exist(priv_dir, geteuid(), 0750)) {
+       if (!directory_create_or_exist(priv_dir, 0750)) {
                task_server_terminate(task, "Cannot create ldap "
                                      "privileged ldapi directory", true);
-               return;
+               return NT_STATUS_UNSUCCESSFUL;
        }
        ldapi_path = talloc_asprintf(ldap_service, "%s/ldapi", priv_dir);
        talloc_free(priv_dir);
        if (ldapi_path == NULL) {
+               status = NT_STATUS_NO_MEMORY;
                goto failed;
        }
 
        status = stream_setup_socket(task, task->event_ctx, task->lp_ctx,
-                                    model_ops, &ldap_stream_priv_ops,
+                                    task->model_ops, &ldapi_stream_priv_ops,
                                     "unix", ldapi_path, NULL,
                                     lpcfg_socket_options(task->lp_ctx),
-                                    ldap_service);
+                                    ldap_service,
+                                    task->process_context);
        talloc_free(ldapi_path);
        if (!NT_STATUS_IS_OK(status)) {
-               DEBUG(0,("ldapsrv failed to bind to %s - %s\n",
-                        ldapi_path, nt_errstr(status)));
+               DBG_ERR("ldapsrv failed to bind to %s - %s\n",
+                       ldapi_path, nt_errstr(status));
        }
 
 #endif
-       return;
+
+       /* register the server */
+       irpc_add_name(task->msg_ctx, "ldap_server");
+
+       task->private_data = ldap_service;
+
+       return NT_STATUS_OK;
 
 failed:
        task_server_terminate(task, "Failed to startup ldap server task", true);
+       return status;
+}
+
+/*
+ * Open a database to be later used by LDB wrap code (although it should be
+ * plumbed through correctly eventually).
+ */
+static void ldapsrv_post_fork(struct task_server *task, struct process_details *pd)
+{
+       struct ldapsrv_service *ldap_service =
+               talloc_get_type_abort(task->private_data, struct ldapsrv_service);
+
+       /*
+        * As ldapsrv_before_loop() may changed the values for the parent loop
+        * we need to adjust the pointers to the correct value in the child
+        */
+       ldap_service->lp_ctx = task->lp_ctx;
+       ldap_service->current_ev = task->event_ctx;
+       ldap_service->current_msg = task->msg_ctx;
+
+       ldap_service->sam_ctx = samdb_connect(ldap_service,
+                                             ldap_service->current_ev,
+                                             ldap_service->lp_ctx,
+                                             system_session(ldap_service->lp_ctx),
+                                             NULL,
+                                             0);
+       if (ldap_service->sam_ctx == NULL) {
+               task_server_terminate(task, "Cannot open system session LDB",
+                                     true);
+               return;
+       }
 }
 
+static void ldapsrv_before_loop(struct task_server *task)
+{
+       struct ldapsrv_service *ldap_service =
+               talloc_get_type_abort(task->private_data, struct ldapsrv_service);
+       NTSTATUS status;
+
+       if (ldap_service->sam_ctx != NULL) {
+               /*
+                * Make sure the values are still the same
+                * as set in ldapsrv_post_fork()
+                */
+               SMB_ASSERT(task->lp_ctx == ldap_service->lp_ctx);
+               SMB_ASSERT(task->event_ctx == ldap_service->current_ev);
+               SMB_ASSERT(task->msg_ctx == ldap_service->current_msg);
+       } else {
+               /*
+                * We need to adjust the pointers to the correct value
+                * in the parent loop.
+                */
+               ldap_service->lp_ctx = task->lp_ctx;
+               ldap_service->current_ev = task->event_ctx;
+               ldap_service->current_msg = task->msg_ctx;
+       }
+
+       status = imessaging_register(ldap_service->current_msg,
+                                    ldap_service,
+                                    MSG_RELOAD_TLS_CERTIFICATES,
+                                    ldap_reload_certs);
+       if (!NT_STATUS_IS_OK(status)) {
+               task_server_terminate(task, "Cannot register ldap_reload_certs",
+                                     true);
+               return;
+       }
+}
+
+/*
+ * Check the size of an ldap request packet.
+ *
+ * For authenticated connections the maximum packet size is controlled by
+ * the smb.conf parameter "ldap max authenticated request size"
+ *
+ * For anonymous connections the maximum packet size is controlled by
+ * the smb.conf parameter "ldap max anonymous request size"
+ */
+static int ldapsrv_check_packet_size(
+       struct ldapsrv_connection *conn,
+       size_t size)
+{
+       bool is_anonymous = false;
+       size_t max_size = 0;
+
+       max_size = lpcfg_ldap_max_anonymous_request_size(conn->lp_ctx);
+       if (size <= max_size) {
+               return LDAP_SUCCESS;
+       }
+
+       /*
+        * Request is larger than the maximum unauthenticated request size.
+        * As this code is called frequently we avoid calling
+        * security_token_is_anonymous if possible
+        */
+       if (conn->session_info != NULL &&
+               conn->session_info->security_token != NULL) {
+               is_anonymous = security_token_is_anonymous(
+                       conn->session_info->security_token);
+       }
+
+       if (is_anonymous) {
+               DBG_WARNING(
+                       "LDAP request size (%zu) exceeds (%zu)\n",
+                       size,
+                       max_size);
+               return LDAP_UNWILLING_TO_PERFORM;
+       }
+
+       max_size = lpcfg_ldap_max_authenticated_request_size(conn->lp_ctx);
+       if (size > max_size) {
+               DBG_WARNING(
+                       "LDAP request size (%zu) exceeds (%zu)\n",
+                       size,
+                       max_size);
+               return LDAP_UNWILLING_TO_PERFORM;
+       }
+       return LDAP_SUCCESS;
+
+}
+
+/*
+ * Check that the blob contains enough data to be a valid packet
+ * If there is a packet header check the size to ensure that it does not
+ * exceed the maximum sizes.
+ *
+ */
+static NTSTATUS ldapsrv_packet_check(
+       struct tstream_context *stream,
+       void *private_data,
+       DATA_BLOB blob,
+       size_t *packet_size)
+{
+       NTSTATUS ret;
+       struct ldapsrv_connection *conn = private_data;
+       int result = LDB_SUCCESS;
+
+       ret = ldap_full_packet(stream, private_data, blob, packet_size);
+       if (!NT_STATUS_IS_OK(ret)) {
+               return ret;
+       }
+       result = ldapsrv_check_packet_size(conn, *packet_size);
+       if (result != LDAP_SUCCESS) {
+               return NT_STATUS_LDAP(result);
+       }
+       return NT_STATUS_OK;
+}
 
-NTSTATUS server_service_ldap_init(void)
+NTSTATUS server_service_ldap_init(TALLOC_CTX *ctx)
 {
-       return register_server_service("ldap", ldapsrv_task_init);
+       static const struct service_details details = {
+               .inhibit_fork_on_accept = false,
+               .inhibit_pre_fork = false,
+               .task_init = ldapsrv_task_init,
+               .post_fork = ldapsrv_post_fork,
+               .before_loop = ldapsrv_before_loop,
+       };
+       return register_server_service(ctx, "ldap", &details);
 }