summaryrefslogtreecommitdiffstats
path: root/source4/ldap_server
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 17:20:00 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 17:20:00 +0000
commit8daa83a594a2e98f39d764422bfbdbc62c9efd44 (patch)
tree4099e8021376c7d8c05bdf8503093d80e9c7bad0 /source4/ldap_server
parentInitial commit. (diff)
downloadsamba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.tar.xz
samba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.zip
Adding upstream version 2:4.20.0+dfsg.upstream/2%4.20.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'source4/ldap_server')
-rw-r--r--source4/ldap_server/ldap_backend.c1637
-rw-r--r--source4/ldap_server/ldap_bind.c783
-rw-r--r--source4/ldap_server/ldap_extended.c263
-rw-r--r--source4/ldap_server/ldap_server.c1694
-rw-r--r--source4/ldap_server/ldap_server.h133
-rw-r--r--source4/ldap_server/wscript_build13
6 files changed, 4523 insertions, 0 deletions
diff --git a/source4/ldap_server/ldap_backend.c b/source4/ldap_server/ldap_backend.c
new file mode 100644
index 0000000..1a90653
--- /dev/null
+++ b/source4/ldap_server/ldap_backend.c
@@ -0,0 +1,1637 @@
+/*
+ Unix SMB/CIFS implementation.
+ LDAP server
+ Copyright (C) Stefan Metzmacher 2004
+ Copyright (C) Matthias Dieter Wallnöfer 2009
+
+ 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 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ 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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "ldap_server/ldap_server.h"
+#include "../lib/util/dlinklist.h"
+#include "auth/credentials/credentials.h"
+#include "auth/gensec/gensec.h"
+#include "auth/gensec/gensec_internal.h" /* TODO: remove this */
+#include "auth/common_auth.h"
+#include "param/param.h"
+#include "samba/service_stream.h"
+#include "dsdb/samdb/samdb.h"
+#include <ldb_errors.h>
+#include <ldb_module.h>
+#include "ldb_wrap.h"
+#include "lib/tsocket/tsocket.h"
+#include "libcli/ldap/ldap_proto.h"
+#include "source4/auth/auth.h"
+
+static int map_ldb_error(TALLOC_CTX *mem_ctx, int ldb_err,
+ const char *add_err_string, const char **errstring)
+{
+ WERROR err;
+
+ /* Certain LDB modules need to return very special WERROR codes. Proof
+ * for them here and if they exist skip the rest of the mapping. */
+ if (add_err_string != NULL) {
+ char *endptr;
+ strtol(add_err_string, &endptr, 16);
+ if (endptr != add_err_string) {
+ *errstring = add_err_string;
+ return ldb_err;
+ }
+ }
+
+ /* Otherwise we calculate here a generic, but appropriate WERROR. */
+
+ switch (ldb_err) {
+ case LDB_SUCCESS:
+ err = WERR_OK;
+ break;
+ case LDB_ERR_OPERATIONS_ERROR:
+ err = WERR_DS_OPERATIONS_ERROR;
+ break;
+ case LDB_ERR_PROTOCOL_ERROR:
+ err = WERR_DS_PROTOCOL_ERROR;
+ break;
+ case LDB_ERR_TIME_LIMIT_EXCEEDED:
+ err = WERR_DS_TIMELIMIT_EXCEEDED;
+ break;
+ case LDB_ERR_SIZE_LIMIT_EXCEEDED:
+ err = WERR_DS_SIZELIMIT_EXCEEDED;
+ break;
+ case LDB_ERR_COMPARE_FALSE:
+ err = WERR_DS_COMPARE_FALSE;
+ break;
+ case LDB_ERR_COMPARE_TRUE:
+ err = WERR_DS_COMPARE_TRUE;
+ break;
+ case LDB_ERR_AUTH_METHOD_NOT_SUPPORTED:
+ err = WERR_DS_AUTH_METHOD_NOT_SUPPORTED;
+ break;
+ case LDB_ERR_STRONG_AUTH_REQUIRED:
+ err = WERR_DS_STRONG_AUTH_REQUIRED;
+ break;
+ case LDB_ERR_REFERRAL:
+ err = WERR_DS_REFERRAL;
+ break;
+ case LDB_ERR_ADMIN_LIMIT_EXCEEDED:
+ err = WERR_DS_ADMIN_LIMIT_EXCEEDED;
+ break;
+ case LDB_ERR_UNSUPPORTED_CRITICAL_EXTENSION:
+ err = WERR_DS_UNAVAILABLE_CRIT_EXTENSION;
+ break;
+ case LDB_ERR_CONFIDENTIALITY_REQUIRED:
+ err = WERR_DS_CONFIDENTIALITY_REQUIRED;
+ break;
+ case LDB_ERR_SASL_BIND_IN_PROGRESS:
+ err = WERR_DS_BUSY;
+ break;
+ case LDB_ERR_NO_SUCH_ATTRIBUTE:
+ err = WERR_DS_NO_ATTRIBUTE_OR_VALUE;
+ break;
+ case LDB_ERR_UNDEFINED_ATTRIBUTE_TYPE:
+ err = WERR_DS_ATTRIBUTE_TYPE_UNDEFINED;
+ break;
+ case LDB_ERR_INAPPROPRIATE_MATCHING:
+ err = WERR_DS_INAPPROPRIATE_MATCHING;
+ break;
+ case LDB_ERR_CONSTRAINT_VIOLATION:
+ err = WERR_DS_CONSTRAINT_VIOLATION;
+ break;
+ case LDB_ERR_ATTRIBUTE_OR_VALUE_EXISTS:
+ err = WERR_DS_ATTRIBUTE_OR_VALUE_EXISTS;
+ break;
+ case LDB_ERR_INVALID_ATTRIBUTE_SYNTAX:
+ err = WERR_DS_INVALID_ATTRIBUTE_SYNTAX;
+ break;
+ case LDB_ERR_NO_SUCH_OBJECT:
+ err = WERR_DS_NO_SUCH_OBJECT;
+ break;
+ case LDB_ERR_ALIAS_PROBLEM:
+ err = WERR_DS_ALIAS_PROBLEM;
+ break;
+ case LDB_ERR_INVALID_DN_SYNTAX:
+ err = WERR_DS_INVALID_DN_SYNTAX;
+ break;
+ case LDB_ERR_ALIAS_DEREFERENCING_PROBLEM:
+ err = WERR_DS_ALIAS_DEREF_PROBLEM;
+ break;
+ case LDB_ERR_INAPPROPRIATE_AUTHENTICATION:
+ err = WERR_DS_INAPPROPRIATE_AUTH;
+ break;
+ case LDB_ERR_INVALID_CREDENTIALS:
+ err = WERR_ACCESS_DENIED;
+ break;
+ case LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS:
+ err = WERR_DS_INSUFF_ACCESS_RIGHTS;
+ break;
+ case LDB_ERR_BUSY:
+ err = WERR_DS_BUSY;
+ break;
+ case LDB_ERR_UNAVAILABLE:
+ err = WERR_DS_UNAVAILABLE;
+ break;
+ case LDB_ERR_UNWILLING_TO_PERFORM:
+ err = WERR_DS_UNWILLING_TO_PERFORM;
+ break;
+ case LDB_ERR_LOOP_DETECT:
+ err = WERR_DS_LOOP_DETECT;
+ break;
+ case LDB_ERR_NAMING_VIOLATION:
+ err = WERR_DS_NAMING_VIOLATION;
+ break;
+ case LDB_ERR_OBJECT_CLASS_VIOLATION:
+ err = WERR_DS_OBJ_CLASS_VIOLATION;
+ break;
+ case LDB_ERR_NOT_ALLOWED_ON_NON_LEAF:
+ err = WERR_DS_CANT_ON_NON_LEAF;
+ break;
+ case LDB_ERR_NOT_ALLOWED_ON_RDN:
+ err = WERR_DS_CANT_ON_RDN;
+ break;
+ case LDB_ERR_ENTRY_ALREADY_EXISTS:
+ err = WERR_DS_OBJ_STRING_NAME_EXISTS;
+ break;
+ case LDB_ERR_OBJECT_CLASS_MODS_PROHIBITED:
+ err = WERR_DS_CANT_MOD_OBJ_CLASS;
+ break;
+ case LDB_ERR_AFFECTS_MULTIPLE_DSAS:
+ err = WERR_DS_AFFECTS_MULTIPLE_DSAS;
+ break;
+ default:
+ err = WERR_DS_GENERIC_ERROR;
+ break;
+ }
+
+ *errstring = talloc_asprintf(mem_ctx, "%08X: %s", W_ERROR_V(err),
+ add_err_string != NULL ? add_err_string : ldb_strerror(ldb_err));
+
+ /* result is 1:1 for now */
+ return ldb_err;
+}
+
+/*
+ connect to the sam database
+*/
+int ldapsrv_backend_Init(struct ldapsrv_connection *conn,
+ char **errstring)
+{
+ bool using_tls = conn->sockets.active == conn->sockets.tls;
+ bool using_seal = conn->gensec != NULL && gensec_have_feature(conn->gensec,
+ GENSEC_FEATURE_SEAL);
+ struct dsdb_encrypted_connection_state *opaque_connection_state = NULL;
+
+ int ret = samdb_connect_url(conn,
+ conn->connection->event.ctx,
+ conn->lp_ctx,
+ conn->session_info,
+ conn->global_catalog ? LDB_FLG_RDONLY : 0,
+ "sam.ldb",
+ conn->connection->remote_address,
+ &conn->ldb,
+ errstring);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /*
+ * We can safely call ldb_set_opaque() on this ldb as we have
+ * set remote_address above which avoids the ldb handle cache
+ */
+ opaque_connection_state = talloc_zero(conn, struct dsdb_encrypted_connection_state);
+ if (opaque_connection_state == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ opaque_connection_state->using_encrypted_connection = using_tls || using_seal;
+ ret = ldb_set_opaque(conn->ldb,
+ DSDB_OPAQUE_ENCRYPTED_CONNECTION_STATE_NAME,
+ opaque_connection_state);
+ if (ret != LDB_SUCCESS) {
+ DBG_ERR("ldb_set_opaque() failed to store our "
+ "encrypted connection state!\n");
+ return ret;
+ }
+
+ if (conn->server_credentials) {
+ struct gensec_security *gensec_security = NULL;
+ const char **sasl_mechs = NULL;
+ NTSTATUS status;
+
+ status = samba_server_gensec_start(conn,
+ conn->connection->event.ctx,
+ conn->connection->msg_ctx,
+ conn->lp_ctx,
+ conn->server_credentials,
+ "ldap",
+ &gensec_security);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("samba_server_gensec_start failed: %s\n",
+ nt_errstr(status));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* ldb can have a different lifetime to conn, so we
+ need to ensure that sasl_mechs lives as long as the
+ ldb does */
+ sasl_mechs = gensec_security_sasl_names(gensec_security,
+ conn->ldb);
+ TALLOC_FREE(gensec_security);
+ if (sasl_mechs == NULL) {
+ DBG_ERR("Failed to get sasl mechs!\n");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ldb_set_opaque(conn->ldb, "supportedSASLMechanisms", sasl_mechs);
+ }
+
+ return LDB_SUCCESS;
+}
+
+struct ldapsrv_reply *ldapsrv_init_reply(struct ldapsrv_call *call, uint8_t type)
+{
+ struct ldapsrv_reply *reply;
+
+ reply = talloc_zero(call, struct ldapsrv_reply);
+ if (!reply) {
+ return NULL;
+ }
+ reply->msg = talloc_zero(reply, struct ldap_message);
+ if (reply->msg == NULL) {
+ talloc_free(reply);
+ return NULL;
+ }
+
+ reply->msg->messageid = call->request->messageid;
+ reply->msg->type = type;
+ reply->msg->controls = NULL;
+
+ return reply;
+}
+
+/*
+ * Encode a reply to an LDAP client as ASN.1, free the original memory
+ */
+static NTSTATUS ldapsrv_encode(TALLOC_CTX *mem_ctx,
+ struct ldapsrv_reply *reply)
+{
+ bool bret = ldap_encode(reply->msg,
+ samba_ldap_control_handlers(),
+ &reply->blob,
+ mem_ctx);
+ if (!bret) {
+ DBG_ERR("Failed to encode ldap reply of type %d: "
+ "ldap_encode() failed\n",
+ reply->msg->type);
+ TALLOC_FREE(reply->msg);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ TALLOC_FREE(reply->msg);
+ talloc_set_name_const(reply->blob.data,
+ "Outgoing, encoded single LDAP reply");
+
+ return NT_STATUS_OK;
+}
+
+/*
+ * Queue a reply (encoding it also), even if it would exceed the
+ * limit. This allows the error packet with LDAP_SIZE_LIMIT_EXCEEDED
+ * to be sent
+ */
+static NTSTATUS ldapsrv_queue_reply_forced(struct ldapsrv_call *call,
+ struct ldapsrv_reply *reply)
+{
+ NTSTATUS status = ldapsrv_encode(call, reply);
+
+ if (NT_STATUS_IS_OK(status)) {
+ DLIST_ADD_END(call->replies, reply);
+ }
+ return status;
+}
+
+/*
+ * Queue a reply (encoding it also) but check we do not send more than
+ * LDAP_SERVER_MAX_REPLY_SIZE of responses as a way to limit the
+ * amount of data a client can make us allocate.
+ */
+NTSTATUS ldapsrv_queue_reply(struct ldapsrv_call *call, struct ldapsrv_reply *reply)
+{
+ NTSTATUS status = ldapsrv_encode(call, reply);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (call->reply_size > call->reply_size + reply->blob.length
+ || call->reply_size + reply->blob.length > LDAP_SERVER_MAX_REPLY_SIZE) {
+ DBG_WARNING("Refusing to queue LDAP search response size "
+ "of more than %zu bytes\n",
+ LDAP_SERVER_MAX_REPLY_SIZE);
+ TALLOC_FREE(reply->blob.data);
+ return NT_STATUS_FILE_TOO_LARGE;
+ }
+
+ call->reply_size += reply->blob.length;
+
+ DLIST_ADD_END(call->replies, reply);
+
+ return status;
+}
+
+static NTSTATUS ldapsrv_unwilling(struct ldapsrv_call *call, int error)
+{
+ struct ldapsrv_reply *reply;
+ struct ldap_ExtendedResponse *r;
+
+ DBG_DEBUG("type[%d] id[%d]\n", call->request->type, call->request->messageid);
+
+ reply = ldapsrv_init_reply(call, LDAP_TAG_ExtendedResponse);
+ if (!reply) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ r = &reply->msg->r.ExtendedResponse;
+ r->response.resultcode = error;
+ r->response.dn = NULL;
+ r->response.errormessage = NULL;
+ r->response.referral = NULL;
+ r->oid = NULL;
+ r->value = NULL;
+
+ ldapsrv_queue_reply(call, reply);
+ return NT_STATUS_OK;
+}
+
+static int ldapsrv_add_with_controls(struct ldapsrv_call *call,
+ const struct ldb_message *message,
+ struct ldb_control **controls,
+ struct ldb_result *res)
+{
+ struct ldb_context *ldb = call->conn->ldb;
+ struct ldb_request *req;
+ int ret;
+
+ ret = ldb_msg_sanity_check(ldb, message);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = ldb_build_add_req(&req, ldb, ldb,
+ message,
+ controls,
+ res,
+ ldb_modify_default_callback,
+ NULL);
+
+ if (ret != LDB_SUCCESS) return ret;
+
+ if (call->conn->global_catalog) {
+ return ldb_error(ldb, LDB_ERR_UNWILLING_TO_PERFORM, "modify forbidden on global catalog port");
+ }
+ ldb_request_add_control(req, DSDB_CONTROL_NO_GLOBAL_CATALOG, false, NULL);
+
+ ret = ldb_transaction_start(ldb);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ if (!call->conn->is_privileged) {
+ ldb_req_mark_untrusted(req);
+ }
+
+ LDB_REQ_SET_LOCATION(req);
+
+ ret = ldb_request(ldb, req);
+ if (ret == LDB_SUCCESS) {
+ ret = ldb_wait(req->handle, LDB_WAIT_ALL);
+ }
+
+ if (ret == LDB_SUCCESS) {
+ ret = ldb_transaction_commit(ldb);
+ }
+ else {
+ ldb_transaction_cancel(ldb);
+ }
+
+ talloc_free(req);
+ return ret;
+}
+
+/* create and execute a modify request */
+static int ldapsrv_mod_with_controls(struct ldapsrv_call *call,
+ const struct ldb_message *message,
+ struct ldb_control **controls,
+ struct ldb_result *res)
+{
+ struct ldb_context *ldb = call->conn->ldb;
+ struct ldb_request *req;
+ int ret;
+
+ ret = ldb_msg_sanity_check(ldb, message);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = ldb_build_mod_req(&req, ldb, ldb,
+ message,
+ controls,
+ res,
+ ldb_modify_default_callback,
+ NULL);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ if (call->conn->global_catalog) {
+ return ldb_error(ldb, LDB_ERR_UNWILLING_TO_PERFORM, "modify forbidden on global catalog port");
+ }
+ ldb_request_add_control(req, DSDB_CONTROL_NO_GLOBAL_CATALOG, false, NULL);
+
+ ret = ldb_transaction_start(ldb);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ if (!call->conn->is_privileged) {
+ ldb_req_mark_untrusted(req);
+ }
+
+ LDB_REQ_SET_LOCATION(req);
+
+ ret = ldb_request(ldb, req);
+ if (ret == LDB_SUCCESS) {
+ ret = ldb_wait(req->handle, LDB_WAIT_ALL);
+ }
+
+ if (ret == LDB_SUCCESS) {
+ ret = ldb_transaction_commit(ldb);
+ }
+ else {
+ ldb_transaction_cancel(ldb);
+ }
+
+ talloc_free(req);
+ return ret;
+}
+
+/* create and execute a delete request */
+static int ldapsrv_del_with_controls(struct ldapsrv_call *call,
+ struct ldb_dn *dn,
+ struct ldb_control **controls,
+ struct ldb_result *res)
+{
+ struct ldb_context *ldb = call->conn->ldb;
+ struct ldb_request *req;
+ int ret;
+
+ ret = ldb_build_del_req(&req, ldb, ldb,
+ dn,
+ controls,
+ res,
+ ldb_modify_default_callback,
+ NULL);
+
+ if (ret != LDB_SUCCESS) return ret;
+
+ if (call->conn->global_catalog) {
+ return ldb_error(ldb, LDB_ERR_UNWILLING_TO_PERFORM, "modify forbidden on global catalog port");
+ }
+ ldb_request_add_control(req, DSDB_CONTROL_NO_GLOBAL_CATALOG, false, NULL);
+
+ ret = ldb_transaction_start(ldb);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ if (!call->conn->is_privileged) {
+ ldb_req_mark_untrusted(req);
+ }
+
+ LDB_REQ_SET_LOCATION(req);
+
+ ret = ldb_request(ldb, req);
+ if (ret == LDB_SUCCESS) {
+ ret = ldb_wait(req->handle, LDB_WAIT_ALL);
+ }
+
+ if (ret == LDB_SUCCESS) {
+ ret = ldb_transaction_commit(ldb);
+ }
+ else {
+ ldb_transaction_cancel(ldb);
+ }
+
+ talloc_free(req);
+ return ret;
+}
+
+static int ldapsrv_rename_with_controls(struct ldapsrv_call *call,
+ struct ldb_dn *olddn,
+ struct ldb_dn *newdn,
+ struct ldb_control **controls,
+ struct ldb_result *res)
+{
+ struct ldb_context *ldb = call->conn->ldb;
+ struct ldb_request *req;
+ int ret;
+
+ ret = ldb_build_rename_req(&req, ldb, ldb,
+ olddn,
+ newdn,
+ controls,
+ res,
+ ldb_modify_default_callback,
+ NULL);
+
+ if (ret != LDB_SUCCESS) return ret;
+
+ if (call->conn->global_catalog) {
+ return ldb_error(ldb, LDB_ERR_UNWILLING_TO_PERFORM, "modify forbidden on global catalog port");
+ }
+ ldb_request_add_control(req, DSDB_CONTROL_NO_GLOBAL_CATALOG, false, NULL);
+
+ ret = ldb_transaction_start(ldb);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ if (!call->conn->is_privileged) {
+ ldb_req_mark_untrusted(req);
+ }
+
+ LDB_REQ_SET_LOCATION(req);
+
+ ret = ldb_request(ldb, req);
+ if (ret == LDB_SUCCESS) {
+ ret = ldb_wait(req->handle, LDB_WAIT_ALL);
+ }
+
+ if (ret == LDB_SUCCESS) {
+ ret = ldb_transaction_commit(ldb);
+ }
+ else {
+ ldb_transaction_cancel(ldb);
+ }
+
+ talloc_free(req);
+ return ret;
+}
+
+
+
+struct ldapsrv_context {
+ struct ldapsrv_call *call;
+ int extended_type;
+ bool attributesonly;
+ struct ldb_control **controls;
+ size_t count; /* For notification only */
+};
+
+static int ldap_server_search_callback(struct ldb_request *req, struct ldb_reply *ares)
+{
+ struct ldapsrv_context *ctx = talloc_get_type(req->context, struct ldapsrv_context);
+ struct ldapsrv_call *call = ctx->call;
+ struct ldb_context *ldb = call->conn->ldb;
+ unsigned int j;
+ struct ldapsrv_reply *ent_r = NULL;
+ struct ldap_SearchResEntry *ent;
+ int ret;
+ NTSTATUS status;
+
+ if (!ares) {
+ return ldb_request_done(req, LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_request_done(req, ares->error);
+ }
+
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ {
+ struct ldb_message *msg = ares->message;
+ ent_r = ldapsrv_init_reply(call, LDAP_TAG_SearchResultEntry);
+ if (ent_r == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ ctx->count++;
+
+ /*
+ * Put the LDAP search response data under ent_r->msg
+ * so we can free that later once encoded
+ */
+ talloc_steal(ent_r->msg, msg);
+
+ ent = &ent_r->msg->r.SearchResultEntry;
+ ent->dn = ldb_dn_get_extended_linearized(ent_r, msg->dn,
+ ctx->extended_type);
+ ent->num_attributes = 0;
+ ent->attributes = NULL;
+ if (msg->num_elements == 0) {
+ goto queue_reply;
+ }
+ ent->num_attributes = msg->num_elements;
+ ent->attributes = talloc_array(ent_r, struct ldb_message_element, ent->num_attributes);
+ if (ent->attributes == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ for (j=0; j < ent->num_attributes; j++) {
+ ent->attributes[j].name = msg->elements[j].name;
+ ent->attributes[j].num_values = 0;
+ ent->attributes[j].values = NULL;
+ if (ctx->attributesonly && (msg->elements[j].num_values == 0)) {
+ continue;
+ }
+ ent->attributes[j].num_values = msg->elements[j].num_values;
+ ent->attributes[j].values = msg->elements[j].values;
+ }
+queue_reply:
+ status = ldapsrv_queue_reply(call, ent_r);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_FILE_TOO_LARGE)) {
+ ret = ldb_request_done(req,
+ LDB_ERR_SIZE_LIMIT_EXCEEDED);
+ ldb_asprintf_errstring(ldb,
+ "LDAP search response size "
+ "limited to %zu bytes\n",
+ LDAP_SERVER_MAX_REPLY_SIZE);
+ } else if (!NT_STATUS_IS_OK(status)) {
+ ret = ldb_request_done(req,
+ ldb_operr(ldb));
+ } else {
+ ret = LDB_SUCCESS;
+ }
+ break;
+ }
+ case LDB_REPLY_REFERRAL:
+ {
+ struct ldap_SearchResRef *ent_ref;
+
+ /*
+ * TODO: This should be handled by the notification
+ * module not here
+ */
+ if (call->notification.busy) {
+ ret = LDB_SUCCESS;
+ break;
+ }
+
+ ent_r = ldapsrv_init_reply(call, LDAP_TAG_SearchResultReference);
+ if (ent_r == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ /*
+ * Put the LDAP referral data under ent_r->msg
+ * so we can free that later once encoded
+ */
+ talloc_steal(ent_r->msg, ares->referral);
+
+ ent_ref = &ent_r->msg->r.SearchResultReference;
+ ent_ref->referral = ares->referral;
+
+ status = ldapsrv_queue_reply(call, ent_r);
+ if (!NT_STATUS_IS_OK(status)) {
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ } else {
+ ret = LDB_SUCCESS;
+ }
+ break;
+ }
+ case LDB_REPLY_DONE:
+ {
+ /*
+ * We don't queue the reply for this one, we let that
+ * happen outside
+ */
+ ctx->controls = talloc_move(ctx, &ares->controls);
+
+ TALLOC_FREE(ares);
+ return ldb_request_done(req, LDB_SUCCESS);
+ }
+ default:
+ /* Doesn't happen */
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ }
+ TALLOC_FREE(ares);
+
+ return ret;
+}
+
+
+static NTSTATUS ldapsrv_SearchRequest(struct ldapsrv_call *call)
+{
+ struct ldap_SearchRequest *req = &call->request->r.SearchRequest;
+ struct ldap_Result *done;
+ struct ldapsrv_reply *done_r;
+ TALLOC_CTX *local_ctx;
+ struct ldapsrv_context *callback_ctx = NULL;
+ struct ldb_context *samdb = talloc_get_type(call->conn->ldb, struct ldb_context);
+ struct ldb_dn *basedn;
+ struct ldb_request *lreq;
+ struct ldb_control *search_control;
+ struct ldb_search_options_control *search_options;
+ struct ldb_control *extended_dn_control;
+ struct ldb_extended_dn_control *extended_dn_decoded = NULL;
+ struct ldb_control *notification_control = NULL;
+ enum ldb_scope scope = LDB_SCOPE_DEFAULT;
+ const char **attrs = NULL;
+ const char *scope_str, *errstr = NULL;
+ int result = -1;
+ int ldb_ret = -1;
+ unsigned int i;
+ int extended_type = 1;
+
+ /*
+ * Warn for searches that are longer than 1/4 of the
+ * search_timeout, being 30sec by default
+ */
+ struct timeval start_time = timeval_current();
+ struct timeval warning_time
+ = timeval_add(&start_time,
+ call->conn->limits.search_timeout / 4,
+ 0);
+
+ local_ctx = talloc_new(call);
+ NT_STATUS_HAVE_NO_MEMORY(local_ctx);
+
+ basedn = ldb_dn_new(local_ctx, samdb, req->basedn);
+ NT_STATUS_HAVE_NO_MEMORY(basedn);
+
+ switch (req->scope) {
+ case LDAP_SEARCH_SCOPE_BASE:
+ scope = LDB_SCOPE_BASE;
+ break;
+ case LDAP_SEARCH_SCOPE_SINGLE:
+ scope = LDB_SCOPE_ONELEVEL;
+ break;
+ case LDAP_SEARCH_SCOPE_SUB:
+ scope = LDB_SCOPE_SUBTREE;
+ break;
+ default:
+ result = LDAP_PROTOCOL_ERROR;
+ map_ldb_error(local_ctx, LDB_ERR_PROTOCOL_ERROR, NULL,
+ &errstr);
+ scope_str = "<Invalid scope>";
+ errstr = talloc_asprintf(local_ctx,
+ "%s. Invalid scope", errstr);
+ goto reply;
+ }
+ scope_str = dsdb_search_scope_as_string(scope);
+
+ DBG_DEBUG("scope: [%s]\n", scope_str);
+
+ if (req->num_attributes >= 1) {
+ attrs = talloc_array(local_ctx, const char *, req->num_attributes+1);
+ NT_STATUS_HAVE_NO_MEMORY(attrs);
+
+ for (i=0; i < req->num_attributes; i++) {
+ DBG_DEBUG("attrs: [%s]\n",req->attributes[i]);
+ attrs[i] = req->attributes[i];
+ }
+ attrs[i] = NULL;
+ }
+
+ DBG_INFO("ldb_request %s dn=%s filter=%s\n",
+ scope_str, req->basedn, ldb_filter_from_tree(call, req->tree));
+
+ callback_ctx = talloc_zero(local_ctx, struct ldapsrv_context);
+ NT_STATUS_HAVE_NO_MEMORY(callback_ctx);
+ callback_ctx->call = call;
+ callback_ctx->extended_type = extended_type;
+ callback_ctx->attributesonly = req->attributesonly;
+
+ ldb_ret = ldb_build_search_req_ex(&lreq, samdb, local_ctx,
+ basedn, scope,
+ req->tree, attrs,
+ call->request->controls,
+ callback_ctx,
+ ldap_server_search_callback,
+ NULL);
+
+ if (ldb_ret != LDB_SUCCESS) {
+ goto reply;
+ }
+
+ if (call->conn->global_catalog) {
+ search_control = ldb_request_get_control(lreq, LDB_CONTROL_SEARCH_OPTIONS_OID);
+
+ search_options = NULL;
+ if (search_control) {
+ search_options = talloc_get_type(search_control->data, struct ldb_search_options_control);
+ search_options->search_options |= LDB_SEARCH_OPTION_PHANTOM_ROOT;
+ } else {
+ search_options = talloc(lreq, struct ldb_search_options_control);
+ NT_STATUS_HAVE_NO_MEMORY(search_options);
+ search_options->search_options = LDB_SEARCH_OPTION_PHANTOM_ROOT;
+ ldb_request_add_control(lreq, LDB_CONTROL_SEARCH_OPTIONS_OID, false, search_options);
+ }
+ } else {
+ ldb_request_add_control(lreq, DSDB_CONTROL_NO_GLOBAL_CATALOG, false, NULL);
+ }
+
+ extended_dn_control = ldb_request_get_control(lreq, LDB_CONTROL_EXTENDED_DN_OID);
+
+ if (extended_dn_control) {
+ if (extended_dn_control->data) {
+ extended_dn_decoded = talloc_get_type(extended_dn_control->data, struct ldb_extended_dn_control);
+ extended_type = extended_dn_decoded->type;
+ } else {
+ extended_type = 0;
+ }
+ callback_ctx->extended_type = extended_type;
+ }
+
+ notification_control = ldb_request_get_control(lreq, LDB_CONTROL_NOTIFICATION_OID);
+ if (notification_control != NULL) {
+ const struct ldapsrv_call *pc = NULL;
+ size_t count = 0;
+
+ for (pc = call->conn->pending_calls; pc != NULL; pc = pc->next) {
+ count += 1;
+ }
+
+ if (count >= call->conn->limits.max_notifications) {
+ DBG_DEBUG("error MaxNotificationPerConn\n");
+ result = map_ldb_error(local_ctx,
+ LDB_ERR_ADMIN_LIMIT_EXCEEDED,
+ "MaxNotificationPerConn reached",
+ &errstr);
+ goto reply;
+ }
+
+ /*
+ * For now we need to do periodic retries on our own.
+ * As the dsdb_notification module will return after each run.
+ */
+ call->notification.busy = true;
+ }
+
+ {
+ const char *scheme = NULL;
+ switch (call->conn->referral_scheme) {
+ case LDAP_REFERRAL_SCHEME_LDAPS:
+ scheme = "ldaps";
+ break;
+ default:
+ scheme = "ldap";
+ }
+ ldb_ret = ldb_set_opaque(
+ samdb,
+ LDAP_REFERRAL_SCHEME_OPAQUE,
+ discard_const_p(char *, scheme));
+ if (ldb_ret != LDB_SUCCESS) {
+ goto reply;
+ }
+ }
+
+ {
+ time_t timeout = call->conn->limits.search_timeout;
+
+ if (timeout == 0
+ || (req->timelimit != 0
+ && req->timelimit < timeout))
+ {
+ timeout = req->timelimit;
+ }
+ ldb_set_timeout(samdb, lreq, timeout);
+ }
+
+ if (!call->conn->is_privileged) {
+ ldb_req_mark_untrusted(lreq);
+ }
+
+ LDB_REQ_SET_LOCATION(lreq);
+
+ ldb_ret = ldb_request(samdb, lreq);
+
+ if (ldb_ret != LDB_SUCCESS) {
+ goto reply;
+ }
+
+ ldb_ret = ldb_wait(lreq->handle, LDB_WAIT_ALL);
+
+ if (ldb_ret == LDB_SUCCESS) {
+ if (call->notification.busy) {
+ /* Move/Add it to the end */
+ DLIST_DEMOTE(call->conn->pending_calls, call);
+ call->notification.generation =
+ call->conn->service->notification.generation;
+
+ if (callback_ctx->count != 0) {
+ call->notification.generation += 1;
+ ldapsrv_notification_retry_setup(call->conn->service,
+ true);
+ }
+
+ talloc_free(local_ctx);
+ return NT_STATUS_OK;
+ }
+ }
+
+reply:
+
+ /*
+ * This looks like duplicated code - because it is - but
+ * otherwise the work in the parameters will be done
+ * regardless, this way the functions only execute when the
+ * log level is set.
+ *
+ * The basedn is re-obtained as a string to escape it
+ */
+ if ((req->timelimit == 0 || call->conn->limits.search_timeout < req->timelimit)
+ && ldb_ret == LDB_ERR_TIME_LIMIT_EXCEEDED) {
+ struct dom_sid_buf sid_buf;
+ DBG_WARNING("MaxQueryDuration(%d) timeout exceeded "
+ "in SearchRequest by %s from %s filter: [%s] "
+ "basedn: [%s] "
+ "scope: [%s]\n",
+ call->conn->limits.search_timeout,
+ dom_sid_str_buf(&call->conn->session_info->security_token->sids[0],
+ &sid_buf),
+ tsocket_address_string(call->conn->connection->remote_address,
+ call),
+ ldb_filter_from_tree(call, req->tree),
+ ldb_dn_get_extended_linearized(call, basedn, 1),
+ scope_str);
+ for (i=0; i < req->num_attributes; i++) {
+ DBG_WARNING("MaxQueryDuration timeout exceeded attrs: [%s]\n",
+ req->attributes[i]);
+ }
+
+ } else if (timeval_expired(&warning_time)) {
+ struct dom_sid_buf sid_buf;
+ DBG_NOTICE("Long LDAP Query: Duration was %.2fs, "
+ "MaxQueryDuration(%d)/4 == %d "
+ "in SearchRequest by %s from %s filter: [%s] "
+ "basedn: [%s] "
+ "scope: [%s] "
+ "result: %s\n",
+ timeval_elapsed(&start_time),
+ call->conn->limits.search_timeout,
+ call->conn->limits.search_timeout / 4,
+ dom_sid_str_buf(&call->conn->session_info->security_token->sids[0],
+ &sid_buf),
+ tsocket_address_string(call->conn->connection->remote_address,
+ call),
+ ldb_filter_from_tree(call, req->tree),
+ ldb_dn_get_extended_linearized(call, basedn, 1),
+ scope_str,
+ ldb_strerror(ldb_ret));
+ for (i=0; i < req->num_attributes; i++) {
+ DBG_NOTICE("Long LDAP Query attrs: [%s]\n",
+ req->attributes[i]);
+ }
+ } else {
+ struct dom_sid_buf sid_buf;
+ DBG_INFO("LDAP Query: Duration was %.2fs, "
+ "SearchRequest by %s from %s filter: [%s] "
+ "basedn: [%s] "
+ "scope: [%s] "
+ "result: %s\n",
+ timeval_elapsed(&start_time),
+ dom_sid_str_buf(&call->conn->session_info->security_token->sids[0],
+ &sid_buf),
+ tsocket_address_string(call->conn->connection->remote_address,
+ call),
+ ldb_filter_from_tree(call, req->tree),
+ ldb_dn_get_extended_linearized(call, basedn, 1),
+ scope_str,
+ ldb_strerror(ldb_ret));
+ }
+
+ DLIST_REMOVE(call->conn->pending_calls, call);
+ call->notification.busy = false;
+
+ done_r = ldapsrv_init_reply(call, LDAP_TAG_SearchResultDone);
+ NT_STATUS_HAVE_NO_MEMORY(done_r);
+
+ done = &done_r->msg->r.SearchResultDone;
+ done->dn = NULL;
+ done->referral = NULL;
+
+ if (result != -1) {
+ } else if (ldb_ret == LDB_SUCCESS) {
+ if (callback_ctx->controls) {
+ done_r->msg->controls = callback_ctx->controls;
+ talloc_steal(done_r->msg, callback_ctx->controls);
+ }
+ result = LDB_SUCCESS;
+ } else {
+ DBG_DEBUG("error\n");
+ result = map_ldb_error(local_ctx, ldb_ret, ldb_errstring(samdb),
+ &errstr);
+ }
+
+ done->resultcode = result;
+ done->errormessage = (errstr?talloc_strdup(done_r, errstr):NULL);
+
+ talloc_free(local_ctx);
+
+ return ldapsrv_queue_reply_forced(call, done_r);
+}
+
+static NTSTATUS ldapsrv_ModifyRequest(struct ldapsrv_call *call)
+{
+ struct ldap_ModifyRequest *req = &call->request->r.ModifyRequest;
+ struct ldap_Result *modify_result;
+ struct ldapsrv_reply *modify_reply;
+ TALLOC_CTX *local_ctx;
+ struct ldb_context *samdb = call->conn->ldb;
+ struct ldb_message *msg = NULL;
+ struct ldb_dn *dn;
+ const char *errstr = NULL;
+ int result = LDAP_SUCCESS;
+ int ldb_ret;
+ unsigned int i,j;
+ struct ldb_result *res = NULL;
+
+ DBG_DEBUG("dn: %s\n", req->dn);
+
+ local_ctx = talloc_named(call, 0, "ModifyRequest local memory context");
+ NT_STATUS_HAVE_NO_MEMORY(local_ctx);
+
+ dn = ldb_dn_new(local_ctx, samdb, req->dn);
+ NT_STATUS_HAVE_NO_MEMORY(dn);
+
+ DBG_DEBUG("dn: [%s]\n", req->dn);
+
+ msg = ldb_msg_new(local_ctx);
+ NT_STATUS_HAVE_NO_MEMORY(msg);
+
+ msg->dn = dn;
+
+ if (req->num_mods > 0) {
+ msg->num_elements = req->num_mods;
+ msg->elements = talloc_array(msg, struct ldb_message_element, req->num_mods);
+ NT_STATUS_HAVE_NO_MEMORY(msg->elements);
+
+ for (i=0; i < msg->num_elements; i++) {
+ msg->elements[i].name = discard_const_p(char, req->mods[i].attrib.name);
+ msg->elements[i].num_values = 0;
+ msg->elements[i].values = NULL;
+
+ switch (req->mods[i].type) {
+ default:
+ result = LDAP_PROTOCOL_ERROR;
+ map_ldb_error(local_ctx,
+ LDB_ERR_PROTOCOL_ERROR, NULL, &errstr);
+ errstr = talloc_asprintf(local_ctx,
+ "%s. Invalid LDAP_MODIFY_* type", errstr);
+ goto reply;
+ case LDAP_MODIFY_ADD:
+ msg->elements[i].flags = LDB_FLAG_MOD_ADD;
+ break;
+ case LDAP_MODIFY_DELETE:
+ msg->elements[i].flags = LDB_FLAG_MOD_DELETE;
+ break;
+ case LDAP_MODIFY_REPLACE:
+ msg->elements[i].flags = LDB_FLAG_MOD_REPLACE;
+ break;
+ }
+
+ msg->elements[i].num_values = req->mods[i].attrib.num_values;
+ if (msg->elements[i].num_values > 0) {
+ msg->elements[i].values = talloc_array(msg->elements, struct ldb_val,
+ msg->elements[i].num_values);
+ NT_STATUS_HAVE_NO_MEMORY(msg->elements[i].values);
+
+ for (j=0; j < msg->elements[i].num_values; j++) {
+ msg->elements[i].values[j].length = req->mods[i].attrib.values[j].length;
+ msg->elements[i].values[j].data = req->mods[i].attrib.values[j].data;
+ }
+ }
+ }
+ }
+
+reply:
+ modify_reply = ldapsrv_init_reply(call, LDAP_TAG_ModifyResponse);
+ NT_STATUS_HAVE_NO_MEMORY(modify_reply);
+
+ if (result == LDAP_SUCCESS) {
+ res = talloc_zero(local_ctx, struct ldb_result);
+ NT_STATUS_HAVE_NO_MEMORY(res);
+ ldb_ret = ldapsrv_mod_with_controls(call, msg, call->request->controls, res);
+ result = map_ldb_error(local_ctx, ldb_ret, ldb_errstring(samdb),
+ &errstr);
+ }
+
+ modify_result = &modify_reply->msg->r.ModifyResponse;
+ modify_result->dn = NULL;
+ if ((res != NULL) && (res->refs != NULL)) {
+ modify_result->resultcode = map_ldb_error(local_ctx,
+ LDB_ERR_REFERRAL,
+ NULL, &errstr);
+ modify_result->errormessage = (errstr?talloc_strdup(modify_reply, errstr):NULL);
+ modify_result->referral = talloc_strdup(call, *res->refs);
+ } else {
+ modify_result->resultcode = result;
+ modify_result->errormessage = (errstr?talloc_strdup(modify_reply, errstr):NULL);
+ modify_result->referral = NULL;
+ }
+ talloc_free(local_ctx);
+
+ return ldapsrv_queue_reply(call, modify_reply);
+
+}
+
+static NTSTATUS ldapsrv_AddRequest(struct ldapsrv_call *call)
+{
+ struct ldap_AddRequest *req = &call->request->r.AddRequest;
+ struct ldap_Result *add_result;
+ struct ldapsrv_reply *add_reply;
+ TALLOC_CTX *local_ctx;
+ struct ldb_context *samdb = call->conn->ldb;
+ struct ldb_message *msg = NULL;
+ struct ldb_dn *dn;
+ const char *errstr = NULL;
+ int result = LDAP_SUCCESS;
+ int ldb_ret;
+ unsigned int i,j;
+ struct ldb_result *res = NULL;
+
+ DBG_DEBUG("dn: %s\n", req->dn);
+
+ local_ctx = talloc_named(call, 0, "AddRequest local memory context");
+ NT_STATUS_HAVE_NO_MEMORY(local_ctx);
+
+ dn = ldb_dn_new(local_ctx, samdb, req->dn);
+ NT_STATUS_HAVE_NO_MEMORY(dn);
+
+ DBG_DEBUG("dn: [%s]\n", req->dn);
+
+ msg = talloc(local_ctx, struct ldb_message);
+ NT_STATUS_HAVE_NO_MEMORY(msg);
+
+ msg->dn = dn;
+ msg->num_elements = 0;
+ msg->elements = NULL;
+
+ if (req->num_attributes > 0) {
+ msg->num_elements = req->num_attributes;
+ msg->elements = talloc_array(msg, struct ldb_message_element, msg->num_elements);
+ NT_STATUS_HAVE_NO_MEMORY(msg->elements);
+
+ for (i=0; i < msg->num_elements; i++) {
+ msg->elements[i].name = discard_const_p(char, req->attributes[i].name);
+ msg->elements[i].flags = 0;
+ msg->elements[i].num_values = 0;
+ msg->elements[i].values = NULL;
+
+ if (req->attributes[i].num_values > 0) {
+ msg->elements[i].num_values = req->attributes[i].num_values;
+ msg->elements[i].values = talloc_array(msg->elements, struct ldb_val,
+ msg->elements[i].num_values);
+ NT_STATUS_HAVE_NO_MEMORY(msg->elements[i].values);
+
+ for (j=0; j < msg->elements[i].num_values; j++) {
+ msg->elements[i].values[j].length = req->attributes[i].values[j].length;
+ msg->elements[i].values[j].data = req->attributes[i].values[j].data;
+ }
+ }
+ }
+ }
+
+ add_reply = ldapsrv_init_reply(call, LDAP_TAG_AddResponse);
+ NT_STATUS_HAVE_NO_MEMORY(add_reply);
+
+ if (result == LDAP_SUCCESS) {
+ res = talloc_zero(local_ctx, struct ldb_result);
+ NT_STATUS_HAVE_NO_MEMORY(res);
+ ldb_ret = ldapsrv_add_with_controls(call, msg, call->request->controls, res);
+ result = map_ldb_error(local_ctx, ldb_ret, ldb_errstring(samdb),
+ &errstr);
+ }
+
+ add_result = &add_reply->msg->r.AddResponse;
+ add_result->dn = NULL;
+ if ((res != NULL) && (res->refs != NULL)) {
+ add_result->resultcode = map_ldb_error(local_ctx,
+ LDB_ERR_REFERRAL, NULL,
+ &errstr);
+ add_result->errormessage = (errstr?talloc_strdup(add_reply,errstr):NULL);
+ add_result->referral = talloc_strdup(call, *res->refs);
+ } else {
+ add_result->resultcode = result;
+ add_result->errormessage = (errstr?talloc_strdup(add_reply,errstr):NULL);
+ add_result->referral = NULL;
+ }
+ talloc_free(local_ctx);
+
+ return ldapsrv_queue_reply(call, add_reply);
+
+}
+
+static NTSTATUS ldapsrv_DelRequest(struct ldapsrv_call *call)
+{
+ struct ldap_DelRequest *req = &call->request->r.DelRequest;
+ struct ldap_Result *del_result;
+ struct ldapsrv_reply *del_reply;
+ TALLOC_CTX *local_ctx;
+ struct ldb_context *samdb = call->conn->ldb;
+ struct ldb_dn *dn;
+ const char *errstr = NULL;
+ int result = LDAP_SUCCESS;
+ int ldb_ret;
+ struct ldb_result *res = NULL;
+
+ DBG_DEBUG("dn: %s\n", req->dn);
+
+ local_ctx = talloc_named(call, 0, "DelRequest local memory context");
+ NT_STATUS_HAVE_NO_MEMORY(local_ctx);
+
+ dn = ldb_dn_new(local_ctx, samdb, req->dn);
+ NT_STATUS_HAVE_NO_MEMORY(dn);
+
+ DBG_DEBUG("dn: [%s]\n", req->dn);
+
+ del_reply = ldapsrv_init_reply(call, LDAP_TAG_DelResponse);
+ NT_STATUS_HAVE_NO_MEMORY(del_reply);
+
+ if (result == LDAP_SUCCESS) {
+ res = talloc_zero(local_ctx, struct ldb_result);
+ NT_STATUS_HAVE_NO_MEMORY(res);
+ ldb_ret = ldapsrv_del_with_controls(call, dn, call->request->controls, res);
+ result = map_ldb_error(local_ctx, ldb_ret, ldb_errstring(samdb),
+ &errstr);
+ }
+
+ del_result = &del_reply->msg->r.DelResponse;
+ del_result->dn = NULL;
+ if ((res != NULL) && (res->refs != NULL)) {
+ del_result->resultcode = map_ldb_error(local_ctx,
+ LDB_ERR_REFERRAL, NULL,
+ &errstr);
+ del_result->errormessage = (errstr?talloc_strdup(del_reply,errstr):NULL);
+ del_result->referral = talloc_strdup(call, *res->refs);
+ } else {
+ del_result->resultcode = result;
+ del_result->errormessage = (errstr?talloc_strdup(del_reply,errstr):NULL);
+ del_result->referral = NULL;
+ }
+
+ talloc_free(local_ctx);
+
+ return ldapsrv_queue_reply(call, del_reply);
+}
+
+static NTSTATUS ldapsrv_ModifyDNRequest(struct ldapsrv_call *call)
+{
+ struct ldap_ModifyDNRequest *req = &call->request->r.ModifyDNRequest;
+ struct ldap_Result *modifydn;
+ struct ldapsrv_reply *modifydn_r;
+ TALLOC_CTX *local_ctx;
+ struct ldb_context *samdb = call->conn->ldb;
+ struct ldb_dn *olddn, *newdn=NULL, *newrdn;
+ struct ldb_dn *parentdn = NULL;
+ const char *errstr = NULL;
+ int result = LDAP_SUCCESS;
+ int ldb_ret;
+ struct ldb_result *res = NULL;
+
+ DBG_DEBUG("dn: %s newrdn: %s\n",
+ req->dn, req->newrdn);
+
+ local_ctx = talloc_named(call, 0, "ModifyDNRequest local memory context");
+ NT_STATUS_HAVE_NO_MEMORY(local_ctx);
+
+ olddn = ldb_dn_new(local_ctx, samdb, req->dn);
+ NT_STATUS_HAVE_NO_MEMORY(olddn);
+
+ newrdn = ldb_dn_new(local_ctx, samdb, req->newrdn);
+ NT_STATUS_HAVE_NO_MEMORY(newrdn);
+
+ DBG_DEBUG("olddn: [%s] newrdn: [%s]\n",
+ req->dn, req->newrdn);
+
+ if (ldb_dn_get_comp_num(newrdn) == 0) {
+ result = LDAP_PROTOCOL_ERROR;
+ map_ldb_error(local_ctx, LDB_ERR_PROTOCOL_ERROR, NULL,
+ &errstr);
+ goto reply;
+ }
+
+ if (ldb_dn_get_comp_num(newrdn) > 1) {
+ result = LDAP_NAMING_VIOLATION;
+ map_ldb_error(local_ctx, LDB_ERR_NAMING_VIOLATION, NULL,
+ &errstr);
+ goto reply;
+ }
+
+ /* we can't handle the rename if we should not remove the old dn */
+ if (!req->deleteolddn) {
+ result = LDAP_UNWILLING_TO_PERFORM;
+ map_ldb_error(local_ctx, LDB_ERR_UNWILLING_TO_PERFORM, NULL,
+ &errstr);
+ errstr = talloc_asprintf(local_ctx,
+ "%s. Old RDN must be deleted", errstr);
+ goto reply;
+ }
+
+ if (req->newsuperior) {
+ DBG_DEBUG("newsuperior: [%s]\n", req->newsuperior);
+ parentdn = ldb_dn_new(local_ctx, samdb, req->newsuperior);
+ }
+
+ if (!parentdn) {
+ parentdn = ldb_dn_get_parent(local_ctx, olddn);
+ }
+ if (!parentdn) {
+ result = LDAP_NO_SUCH_OBJECT;
+ map_ldb_error(local_ctx, LDB_ERR_NO_SUCH_OBJECT, NULL, &errstr);
+ goto reply;
+ }
+
+ if ( ! ldb_dn_add_child(parentdn, newrdn)) {
+ result = LDAP_OTHER;
+ map_ldb_error(local_ctx, LDB_ERR_OTHER, NULL, &errstr);
+ goto reply;
+ }
+ newdn = parentdn;
+
+reply:
+ modifydn_r = ldapsrv_init_reply(call, LDAP_TAG_ModifyDNResponse);
+ NT_STATUS_HAVE_NO_MEMORY(modifydn_r);
+
+ if (result == LDAP_SUCCESS) {
+ res = talloc_zero(local_ctx, struct ldb_result);
+ NT_STATUS_HAVE_NO_MEMORY(res);
+ ldb_ret = ldapsrv_rename_with_controls(call, olddn, newdn, call->request->controls, res);
+ result = map_ldb_error(local_ctx, ldb_ret, ldb_errstring(samdb),
+ &errstr);
+ }
+
+ modifydn = &modifydn_r->msg->r.ModifyDNResponse;
+ modifydn->dn = NULL;
+ if ((res != NULL) && (res->refs != NULL)) {
+ modifydn->resultcode = map_ldb_error(local_ctx,
+ LDB_ERR_REFERRAL, NULL,
+ &errstr);;
+ modifydn->errormessage = (errstr?talloc_strdup(modifydn_r,errstr):NULL);
+ modifydn->referral = talloc_strdup(call, *res->refs);
+ } else {
+ modifydn->resultcode = result;
+ modifydn->errormessage = (errstr?talloc_strdup(modifydn_r,errstr):NULL);
+ modifydn->referral = NULL;
+ }
+
+ talloc_free(local_ctx);
+
+ return ldapsrv_queue_reply(call, modifydn_r);
+}
+
+static NTSTATUS ldapsrv_CompareRequest(struct ldapsrv_call *call)
+{
+ struct ldap_CompareRequest *req = &call->request->r.CompareRequest;
+ struct ldap_Result *compare;
+ struct ldapsrv_reply *compare_r;
+ TALLOC_CTX *local_ctx;
+ struct ldb_context *samdb = call->conn->ldb;
+ struct ldb_result *res = NULL;
+ struct ldb_dn *dn;
+ const char *attrs[1];
+ const char *errstr = NULL;
+ const char *filter = NULL;
+ int result = LDAP_SUCCESS;
+ int ldb_ret;
+
+ DBG_DEBUG("dn: %s\n", req->dn);
+
+ local_ctx = talloc_named(call, 0, "CompareRequest local_memory_context");
+ NT_STATUS_HAVE_NO_MEMORY(local_ctx);
+
+ dn = ldb_dn_new(local_ctx, samdb, req->dn);
+ NT_STATUS_HAVE_NO_MEMORY(dn);
+
+ DBG_DEBUG("dn: [%s]\n", req->dn);
+ filter = talloc_asprintf(local_ctx, "(%s=%*s)", req->attribute,
+ (int)req->value.length, req->value.data);
+ NT_STATUS_HAVE_NO_MEMORY(filter);
+
+ DBG_DEBUG("attribute: [%s]\n", filter);
+
+ attrs[0] = NULL;
+
+ compare_r = ldapsrv_init_reply(call, LDAP_TAG_CompareResponse);
+ NT_STATUS_HAVE_NO_MEMORY(compare_r);
+
+ if (result == LDAP_SUCCESS) {
+ ldb_ret = ldb_search(samdb, local_ctx, &res,
+ dn, LDB_SCOPE_BASE, attrs, "%s", filter);
+ if (ldb_ret != LDB_SUCCESS) {
+ result = map_ldb_error(local_ctx, ldb_ret,
+ ldb_errstring(samdb), &errstr);
+ DBG_DEBUG("error: %s\n", errstr);
+ } else if (res->count == 0) {
+ DBG_DEBUG("didn't match\n");
+ result = LDAP_COMPARE_FALSE;
+ errstr = NULL;
+ } else if (res->count == 1) {
+ DBG_DEBUG("matched\n");
+ result = LDAP_COMPARE_TRUE;
+ errstr = NULL;
+ } else if (res->count > 1) {
+ result = LDAP_OTHER;
+ map_ldb_error(local_ctx, LDB_ERR_OTHER, NULL, &errstr);
+ errstr = talloc_asprintf(local_ctx,
+ "%s. Too many objects match!", errstr);
+ DBG_DEBUG("%u results: %s\n", res->count, errstr);
+ }
+ }
+
+ compare = &compare_r->msg->r.CompareResponse;
+ compare->dn = NULL;
+ compare->resultcode = result;
+ compare->errormessage = (errstr?talloc_strdup(compare_r,errstr):NULL);
+ compare->referral = NULL;
+
+ talloc_free(local_ctx);
+
+ return ldapsrv_queue_reply(call, compare_r);
+}
+
+static NTSTATUS ldapsrv_AbandonRequest(struct ldapsrv_call *call)
+{
+ struct ldap_AbandonRequest *req = &call->request->r.AbandonRequest;
+ struct ldapsrv_call *c = NULL;
+ struct ldapsrv_call *n = NULL;
+
+ DBG_DEBUG("abandoned\n");
+
+ for (c = call->conn->pending_calls; c != NULL; c = n) {
+ n = c->next;
+
+ if (c->request->messageid != req->messageid) {
+ continue;
+ }
+
+ DLIST_REMOVE(call->conn->pending_calls, c);
+ TALLOC_FREE(c);
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS ldapsrv_expired(struct ldapsrv_call *call)
+{
+ struct ldapsrv_reply *reply = NULL;
+ struct ldap_ExtendedResponse *r = NULL;
+
+ DBG_DEBUG("Sending connection expired message\n");
+
+ reply = ldapsrv_init_reply(call, LDAP_TAG_ExtendedResponse);
+ if (reply == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /*
+ * According to RFC4511 section 4.4.1 this has a msgid of 0
+ */
+ reply->msg->messageid = 0;
+
+ r = &reply->msg->r.ExtendedResponse;
+ r->response.resultcode = LDB_ERR_UNAVAILABLE;
+ r->response.errormessage = "The server has timed out this connection";
+ r->oid = "1.3.6.1.4.1.1466.20036"; /* see rfc4511 section 4.4.1 */
+
+ ldapsrv_queue_reply(call, reply);
+ return NT_STATUS_OK;
+}
+
+NTSTATUS ldapsrv_do_call(struct ldapsrv_call *call)
+{
+ unsigned int i;
+ struct ldap_message *msg = call->request;
+ struct ldapsrv_connection *conn = call->conn;
+ NTSTATUS status;
+ bool expired;
+
+ expired = timeval_expired(&conn->limits.expire_time);
+ if (expired) {
+ status = ldapsrv_expired(call);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ return NT_STATUS_NETWORK_SESSION_EXPIRED;
+ }
+
+ /* Check for undecoded critical extensions */
+ for (i=0; msg->controls && msg->controls[i]; i++) {
+ if (!msg->controls_decoded[i] &&
+ msg->controls[i]->critical) {
+ DBG_NOTICE("Critical extension %s is not known to this server\n",
+ msg->controls[i]->oid);
+ return ldapsrv_unwilling(call, LDAP_UNAVAILABLE_CRITICAL_EXTENSION);
+ }
+ }
+
+ if (call->conn->authz_logged == false) {
+ bool log = true;
+
+ /*
+ * We do not want to log anonymous access if the query
+ * is just for the rootDSE, or it is a startTLS or a
+ * Bind.
+ *
+ * A rootDSE search could also be done over
+ * CLDAP anonymously for example, so these don't
+ * really count.
+ * Essentially we want to know about
+ * access beyond that normally done prior to a
+ * bind.
+ */
+
+ switch(call->request->type) {
+ case LDAP_TAG_BindRequest:
+ case LDAP_TAG_UnbindRequest:
+ case LDAP_TAG_AbandonRequest:
+ log = false;
+ break;
+ case LDAP_TAG_ExtendedResponse: {
+ struct ldap_ExtendedRequest *req = &call->request->r.ExtendedRequest;
+ if (strcmp(req->oid, LDB_EXTENDED_START_TLS_OID) == 0) {
+ log = false;
+ }
+ break;
+ }
+ case LDAP_TAG_SearchRequest: {
+ struct ldap_SearchRequest *req = &call->request->r.SearchRequest;
+ if (req->scope == LDAP_SEARCH_SCOPE_BASE) {
+ if (req->basedn[0] == '\0') {
+ log = false;
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ if (log) {
+ const char *transport_protection = AUTHZ_TRANSPORT_PROTECTION_NONE;
+ if (call->conn->sockets.active == call->conn->sockets.tls) {
+ transport_protection = AUTHZ_TRANSPORT_PROTECTION_TLS;
+ }
+
+ log_successful_authz_event(call->conn->connection->msg_ctx,
+ call->conn->connection->lp_ctx,
+ call->conn->connection->remote_address,
+ call->conn->connection->local_address,
+ "LDAP",
+ "no bind",
+ transport_protection,
+ call->conn->session_info,
+ NULL /* client_audit_info */,
+ NULL /* server_audit_info */);
+
+ call->conn->authz_logged = true;
+ }
+ }
+
+ switch(call->request->type) {
+ case LDAP_TAG_BindRequest:
+ return ldapsrv_BindRequest(call);
+ case LDAP_TAG_UnbindRequest:
+ return ldapsrv_UnbindRequest(call);
+ case LDAP_TAG_SearchRequest:
+ return ldapsrv_SearchRequest(call);
+ case LDAP_TAG_ModifyRequest:
+ status = ldapsrv_ModifyRequest(call);
+ break;
+ case LDAP_TAG_AddRequest:
+ status = ldapsrv_AddRequest(call);
+ break;
+ case LDAP_TAG_DelRequest:
+ status = ldapsrv_DelRequest(call);
+ break;
+ case LDAP_TAG_ModifyDNRequest:
+ status = ldapsrv_ModifyDNRequest(call);
+ break;
+ case LDAP_TAG_CompareRequest:
+ return ldapsrv_CompareRequest(call);
+ case LDAP_TAG_AbandonRequest:
+ return ldapsrv_AbandonRequest(call);
+ case LDAP_TAG_ExtendedRequest:
+ status = ldapsrv_ExtendedRequest(call);
+ break;
+ default:
+ return ldapsrv_unwilling(call, LDAP_PROTOCOL_ERROR);
+ }
+
+ if (NT_STATUS_IS_OK(status)) {
+ ldapsrv_notification_retry_setup(call->conn->service, true);
+ }
+
+ return status;
+}
diff --git a/source4/ldap_server/ldap_bind.c b/source4/ldap_server/ldap_bind.c
new file mode 100644
index 0000000..d592d47
--- /dev/null
+++ b/source4/ldap_server/ldap_bind.c
@@ -0,0 +1,783 @@
+/*
+ Unix SMB/CIFS implementation.
+ LDAP server
+ Copyright (C) Stefan Metzmacher 2004
+
+ 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 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ 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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "ldap_server/ldap_server.h"
+#include "auth/auth.h"
+#include "samba/service.h"
+#include <ldb.h>
+#include <ldb_errors.h>
+#include "../lib/util/dlinklist.h"
+#include "dsdb/samdb/samdb.h"
+#include "auth/gensec/gensec.h"
+#include "auth/gensec/gensec_tstream.h"
+#include "param/param.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "lib/util/time_basic.h"
+
+static char *ldapsrv_bind_error_msg(TALLOC_CTX *mem_ctx,
+ HRESULT hresult,
+ uint32_t DSID,
+ NTSTATUS status)
+{
+ WERROR werr;
+ char *msg = NULL;
+
+ status = nt_status_squash(status);
+ werr = ntstatus_to_werror(status);
+
+ /*
+ * There are 4 lower case hex digits following 'v' at the end,
+ * but different Windows Versions return different values:
+ *
+ * Windows 2008R2 uses 'v1db1'
+ * Windows 2012R2 uses 'v2580'
+ *
+ * We just match Windows 2008R2 as that's what was referenced
+ * in https://bugzilla.samba.org/show_bug.cgi?id=9048
+ */
+ msg = talloc_asprintf(mem_ctx, "%08X: LdapErr: DSID-%08X, comment: "
+ "AcceptSecurityContext error, data %x, v1db1",
+ (unsigned)HRES_ERROR_V(hresult),
+ (unsigned)DSID,
+ (unsigned)W_ERROR_V(werr));
+
+ return msg;
+}
+
+struct ldapsrv_bind_wait_context {
+ struct ldapsrv_reply *reply;
+ struct tevent_req *req;
+ NTSTATUS status;
+ bool done;
+};
+
+struct ldapsrv_bind_wait_state {
+ uint8_t dummy;
+};
+
+static struct tevent_req *ldapsrv_bind_wait_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ void *private_data)
+{
+ struct ldapsrv_bind_wait_context *bind_wait =
+ talloc_get_type_abort(private_data,
+ struct ldapsrv_bind_wait_context);
+ struct tevent_req *req;
+ struct ldapsrv_bind_wait_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct ldapsrv_bind_wait_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ bind_wait->req = req;
+
+ tevent_req_defer_callback(req, ev);
+
+ if (!bind_wait->done) {
+ return req;
+ }
+
+ if (tevent_req_nterror(req, bind_wait->status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+}
+
+static NTSTATUS ldapsrv_bind_wait_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+static NTSTATUS ldapsrv_bind_wait_setup(struct ldapsrv_call *call,
+ struct ldapsrv_reply *reply)
+{
+ struct ldapsrv_bind_wait_context *bind_wait = NULL;
+
+ if (call->wait_private != NULL) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ bind_wait = talloc_zero(call, struct ldapsrv_bind_wait_context);
+ if (bind_wait == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ bind_wait->reply = reply;
+
+ call->wait_private = bind_wait;
+ call->wait_send = ldapsrv_bind_wait_send;
+ call->wait_recv = ldapsrv_bind_wait_recv;
+ return NT_STATUS_OK;
+}
+
+static void ldapsrv_bind_wait_finished(struct ldapsrv_call *call,
+ NTSTATUS status)
+{
+ struct ldapsrv_bind_wait_context *bind_wait =
+ talloc_get_type_abort(call->wait_private,
+ struct ldapsrv_bind_wait_context);
+
+ bind_wait->done = true;
+ bind_wait->status = status;
+
+ if (bind_wait->req == NULL) {
+ return;
+ }
+
+ if (tevent_req_nterror(bind_wait->req, status)) {
+ return;
+ }
+
+ tevent_req_done(bind_wait->req);
+}
+
+static void ldapsrv_BindSimple_done(struct tevent_req *subreq);
+
+static NTSTATUS ldapsrv_BindSimple(struct ldapsrv_call *call)
+{
+ struct ldap_BindRequest *req = &call->request->r.BindRequest;
+ struct ldapsrv_reply *reply = NULL;
+ struct ldap_BindResponse *resp = NULL;
+ int result;
+ const char *errstr = NULL;
+ NTSTATUS status;
+ bool using_tls = call->conn->sockets.active == call->conn->sockets.tls;
+ struct tevent_req *subreq = NULL;
+
+ DEBUG(10, ("BindSimple dn: %s\n",req->dn));
+
+ reply = ldapsrv_init_reply(call, LDAP_TAG_BindResponse);
+ if (!reply) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (req->dn != NULL &&
+ strlen(req->dn) != 0 &&
+ call->conn->require_strong_auth > LDAP_SERVER_REQUIRE_STRONG_AUTH_NO &&
+ !using_tls)
+ {
+ status = NT_STATUS_NETWORK_ACCESS_DENIED;
+ result = LDAP_STRONG_AUTH_REQUIRED;
+ errstr = talloc_asprintf(reply,
+ "BindSimple: Transport encryption required.");
+ goto do_reply;
+ }
+
+ subreq = authenticate_ldap_simple_bind_send(call,
+ call->conn->connection->event.ctx,
+ call->conn->connection->msg_ctx,
+ call->conn->lp_ctx,
+ call->conn->connection->remote_address,
+ call->conn->connection->local_address,
+ using_tls,
+ req->dn,
+ req->creds.password);
+ if (subreq == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ tevent_req_set_callback(subreq, ldapsrv_BindSimple_done, call);
+
+ status = ldapsrv_bind_wait_setup(call, reply);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(subreq);
+ return status;
+ }
+
+ /*
+ * The rest will be async.
+ */
+ return NT_STATUS_OK;
+
+do_reply:
+ resp = &reply->msg->r.BindResponse;
+ resp->response.resultcode = result;
+ resp->response.errormessage = errstr;
+ resp->response.dn = NULL;
+ resp->response.referral = NULL;
+ resp->SASL.secblob = NULL;
+
+ ldapsrv_queue_reply(call, reply);
+ return NT_STATUS_OK;
+}
+
+static void ldapsrv_BindSimple_done(struct tevent_req *subreq)
+{
+ struct ldapsrv_call *call =
+ tevent_req_callback_data(subreq,
+ struct ldapsrv_call);
+ struct ldapsrv_bind_wait_context *bind_wait =
+ talloc_get_type_abort(call->wait_private,
+ struct ldapsrv_bind_wait_context);
+ struct ldapsrv_reply *reply = bind_wait->reply;
+ struct auth_session_info *session_info = NULL;
+ NTSTATUS status;
+ struct ldap_BindResponse *resp = NULL;
+ int result;
+ const char *errstr = NULL;
+
+ status = authenticate_ldap_simple_bind_recv(subreq,
+ call,
+ &session_info);
+ if (NT_STATUS_IS_OK(status)) {
+ char *ldb_errstring = NULL;
+ result = LDAP_SUCCESS;
+ errstr = NULL;
+
+ talloc_unlink(call->conn, call->conn->session_info);
+ call->conn->session_info = talloc_steal(call->conn, session_info);
+
+ call->conn->authz_logged = true;
+
+ /* don't leak the old LDB */
+ talloc_unlink(call->conn, call->conn->ldb);
+
+ result = ldapsrv_backend_Init(call->conn, &ldb_errstring);
+
+ if (result != LDB_SUCCESS) {
+ /* Only put the detailed error in DEBUG() */
+ DBG_ERR("ldapsrv_backend_Init failed: %s: %s\n",
+ ldb_errstring, ldb_strerror(result));
+ errstr = talloc_strdup(reply,
+ "Simple Bind: Failed to advise "
+ "ldb new credentials");
+ result = LDB_ERR_OPERATIONS_ERROR;
+ }
+ } else {
+ status = nt_status_squash(status);
+
+ result = LDAP_INVALID_CREDENTIALS;
+ errstr = ldapsrv_bind_error_msg(reply, HRES_SEC_E_INVALID_TOKEN,
+ 0x0C0903A9, status);
+ }
+
+ resp = &reply->msg->r.BindResponse;
+ resp->response.resultcode = result;
+ resp->response.errormessage = errstr;
+ resp->response.dn = NULL;
+ resp->response.referral = NULL;
+ resp->SASL.secblob = NULL;
+
+ ldapsrv_queue_reply(call, reply);
+ ldapsrv_bind_wait_finished(call, NT_STATUS_OK);
+}
+
+struct ldapsrv_sasl_postprocess_context {
+ struct ldapsrv_connection *conn;
+ struct tstream_context *sasl;
+};
+
+struct ldapsrv_sasl_postprocess_state {
+ uint8_t dummy;
+};
+
+static struct tevent_req *ldapsrv_sasl_postprocess_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ void *private_data)
+{
+ struct ldapsrv_sasl_postprocess_context *context =
+ talloc_get_type_abort(private_data,
+ struct ldapsrv_sasl_postprocess_context);
+ struct tevent_req *req;
+ struct ldapsrv_sasl_postprocess_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct ldapsrv_sasl_postprocess_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ TALLOC_FREE(context->conn->sockets.sasl);
+ context->conn->sockets.sasl = talloc_move(context->conn, &context->sasl);
+ context->conn->sockets.active = context->conn->sockets.sasl;
+
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+}
+
+static NTSTATUS ldapsrv_sasl_postprocess_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+static NTSTATUS ldapsrv_setup_gensec(struct ldapsrv_connection *conn,
+ const char *sasl_mech,
+ struct gensec_security **_gensec_security)
+{
+ NTSTATUS status;
+
+ struct gensec_security *gensec_security;
+
+ status = samba_server_gensec_start(conn,
+ conn->connection->event.ctx,
+ conn->connection->msg_ctx,
+ conn->lp_ctx,
+ conn->server_credentials,
+ "ldap",
+ &gensec_security);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = gensec_set_target_service_description(gensec_security,
+ "LDAP");
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = gensec_set_remote_address(gensec_security,
+ conn->connection->remote_address);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = gensec_set_local_address(gensec_security,
+ conn->connection->local_address);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ gensec_want_feature(gensec_security, GENSEC_FEATURE_ASYNC_REPLIES);
+ gensec_want_feature(gensec_security, GENSEC_FEATURE_LDAP_STYLE);
+
+ if (conn->sockets.active == conn->sockets.tls) {
+ gensec_want_feature(gensec_security, GENSEC_FEATURE_LDAPS_TRANSPORT);
+ }
+
+ status = gensec_start_mech_by_sasl_name(gensec_security, sasl_mech);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ *_gensec_security = gensec_security;
+ return status;
+}
+
+static void ldapsrv_BindSASL_done(struct tevent_req *subreq);
+
+static NTSTATUS ldapsrv_BindSASL(struct ldapsrv_call *call)
+{
+ struct ldap_BindRequest *req = &call->request->r.BindRequest;
+ struct ldapsrv_reply *reply;
+ struct ldap_BindResponse *resp;
+ struct ldapsrv_connection *conn;
+ int result = 0;
+ const char *errstr=NULL;
+ NTSTATUS status = NT_STATUS_OK;
+ DATA_BLOB input = data_blob_null;
+ struct tevent_req *subreq = NULL;
+
+ DEBUG(10, ("BindSASL dn: %s\n",req->dn));
+
+ reply = ldapsrv_init_reply(call, LDAP_TAG_BindResponse);
+ if (!reply) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ resp = &reply->msg->r.BindResponse;
+ /* Windows 2000 mmc doesn't like secblob == NULL and reports a decoding error */
+ resp->SASL.secblob = talloc_zero(reply, DATA_BLOB);
+ if (resp->SASL.secblob == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ conn = call->conn;
+
+ /*
+ * TODO: a SASL bind with a different mechanism
+ * should cancel an inprogress SASL bind.
+ * (see RFC 4513)
+ */
+
+ if (!conn->gensec) {
+ status = ldapsrv_setup_gensec(conn, req->creds.SASL.mechanism,
+ &conn->gensec);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("Failed to start GENSEC server for [%s] code: %s\n",
+ ldb_binary_encode_string(call, req->creds.SASL.mechanism),
+ nt_errstr(status)));
+ result = LDAP_OPERATIONS_ERROR;
+ errstr = talloc_asprintf(reply, "SASL: Failed to start authentication system: %s",
+ nt_errstr(status));
+ goto do_reply;
+ }
+ }
+
+ if (req->creds.SASL.secblob) {
+ input = *req->creds.SASL.secblob;
+ }
+
+ subreq = gensec_update_send(call, conn->connection->event.ctx,
+ conn->gensec, input);
+ if (subreq == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ tevent_req_set_callback(subreq, ldapsrv_BindSASL_done, call);
+
+ status = ldapsrv_bind_wait_setup(call, reply);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(subreq);
+ return status;
+ }
+
+ /*
+ * The rest will be async.
+ */
+ return NT_STATUS_OK;
+
+do_reply:
+ if (result != LDAP_SASL_BIND_IN_PROGRESS) {
+ /*
+ * We should destroy the gensec context
+ * when we hit a fatal error.
+ *
+ * Note: conn->gensec is already cleared
+ * for the LDAP_SUCCESS case.
+ */
+ talloc_unlink(conn, conn->gensec);
+ conn->gensec = NULL;
+ }
+
+ resp->response.resultcode = result;
+ resp->response.dn = NULL;
+ resp->response.errormessage = errstr;
+ resp->response.referral = NULL;
+
+ ldapsrv_queue_reply(call, reply);
+ return NT_STATUS_OK;
+}
+
+static void ldapsrv_BindSASL_done(struct tevent_req *subreq)
+{
+ struct ldapsrv_call *call =
+ tevent_req_callback_data(subreq,
+ struct ldapsrv_call);
+ struct ldapsrv_bind_wait_context *bind_wait =
+ talloc_get_type_abort(call->wait_private,
+ struct ldapsrv_bind_wait_context);
+ struct ldap_BindRequest *req = &call->request->r.BindRequest;
+ struct ldapsrv_reply *reply = bind_wait->reply;
+ struct ldap_BindResponse *resp = &reply->msg->r.BindResponse;
+ struct ldapsrv_connection *conn = call->conn;
+ struct auth_session_info *session_info = NULL;
+ struct ldapsrv_sasl_postprocess_context *context = NULL;
+ NTSTATUS status;
+ int result;
+ const char *errstr = NULL;
+ char *ldb_errstring = NULL;
+ DATA_BLOB output = data_blob_null;
+ NTTIME expire_time_nt;
+
+ status = gensec_update_recv(subreq, call, &output);
+ TALLOC_FREE(subreq);
+
+ if (NT_STATUS_EQUAL(NT_STATUS_MORE_PROCESSING_REQUIRED, status)) {
+ *resp->SASL.secblob = output;
+ result = LDAP_SASL_BIND_IN_PROGRESS;
+ errstr = NULL;
+ goto do_reply;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ status = nt_status_squash(status);
+ result = LDAP_INVALID_CREDENTIALS;
+ errstr = ldapsrv_bind_error_msg(reply, HRES_SEC_E_LOGON_DENIED,
+ 0x0C0904DC, status);
+ goto do_reply;
+ }
+
+ if (gensec_have_feature(conn->gensec, GENSEC_FEATURE_SIGN) ||
+ gensec_have_feature(conn->gensec, GENSEC_FEATURE_SEAL)) {
+
+ context = talloc_zero(call, struct ldapsrv_sasl_postprocess_context);
+ if (context == NULL) {
+ ldapsrv_bind_wait_finished(call, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ }
+
+ if (context && conn->sockets.tls) {
+ TALLOC_FREE(context);
+ status = NT_STATUS_NOT_SUPPORTED;
+ result = LDAP_UNWILLING_TO_PERFORM;
+ errstr = talloc_asprintf(reply,
+ "SASL:[%s]: Sign or Seal are not allowed if TLS is used",
+ req->creds.SASL.mechanism);
+ goto do_reply;
+ }
+
+ if (context && conn->sockets.sasl) {
+ TALLOC_FREE(context);
+ status = NT_STATUS_NOT_SUPPORTED;
+ result = LDAP_UNWILLING_TO_PERFORM;
+ errstr = talloc_asprintf(reply,
+ "SASL:[%s]: Sign or Seal are not allowed if SASL encryption has already been set up",
+ req->creds.SASL.mechanism);
+ goto do_reply;
+ }
+
+ if (context == NULL) {
+ switch (call->conn->require_strong_auth) {
+ case LDAP_SERVER_REQUIRE_STRONG_AUTH_NO:
+ break;
+ case LDAP_SERVER_REQUIRE_STRONG_AUTH_ALLOW_SASL_OVER_TLS:
+ if (call->conn->sockets.active == call->conn->sockets.tls) {
+ break;
+ }
+ status = NT_STATUS_NETWORK_ACCESS_DENIED;
+ result = LDAP_STRONG_AUTH_REQUIRED;
+ errstr = talloc_asprintf(reply,
+ "SASL:[%s]: not allowed if TLS is used.",
+ req->creds.SASL.mechanism);
+ goto do_reply;
+
+ case LDAP_SERVER_REQUIRE_STRONG_AUTH_YES:
+ status = NT_STATUS_NETWORK_ACCESS_DENIED;
+ result = LDAP_STRONG_AUTH_REQUIRED;
+ errstr = talloc_asprintf(reply,
+ "SASL:[%s]: Sign or Seal are required.",
+ req->creds.SASL.mechanism);
+ goto do_reply;
+ }
+ }
+
+ if (context != NULL) {
+ context->conn = conn;
+ status = gensec_create_tstream(context,
+ context->conn->gensec,
+ context->conn->sockets.raw,
+ &context->sasl);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = LDAP_OPERATIONS_ERROR;
+ errstr = talloc_asprintf(reply,
+ "SASL:[%s]: Failed to setup SASL socket: %s",
+ req->creds.SASL.mechanism, nt_errstr(status));
+ goto do_reply;
+ }
+ }
+
+ status = gensec_session_info(conn->gensec, call, &session_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = LDAP_OPERATIONS_ERROR;
+ errstr = talloc_asprintf(reply,
+ "SASL:[%s]: Failed to get session info: %s",
+ req->creds.SASL.mechanism, nt_errstr(status));
+ goto do_reply;
+ }
+
+ talloc_unlink(conn, conn->session_info);
+ conn->session_info = talloc_steal(conn, session_info);
+
+ /* don't leak the old LDB */
+ talloc_unlink(conn, conn->ldb);
+
+ call->conn->authz_logged = true;
+
+ result = ldapsrv_backend_Init(call->conn, &ldb_errstring);
+
+ if (result != LDB_SUCCESS) {
+ /* Only put the detailed error in DEBUG() */
+ DBG_ERR("ldapsrv_backend_Init failed: %s: %s\n",
+ ldb_errstring, ldb_strerror(result));
+ errstr = talloc_strdup(reply,
+ "SASL Bind: Failed to advise "
+ "ldb new credentials");
+ result = LDB_ERR_OPERATIONS_ERROR;
+ goto do_reply;
+ }
+
+ expire_time_nt = gensec_expire_time(conn->gensec);
+ if (expire_time_nt != GENSEC_EXPIRE_TIME_INFINITY) {
+ struct timeval_buf buf;
+
+ nttime_to_timeval(&conn->limits.expire_time, expire_time_nt);
+
+ DBG_DEBUG("Setting connection expire_time to %s\n",
+ timeval_str_buf(&conn->limits.expire_time,
+ false,
+ true,
+ &buf));
+ }
+
+ if (context != NULL) {
+ const void *ptr = NULL;
+
+ ptr = talloc_reparent(conn, context->sasl, conn->gensec);
+ if (ptr == NULL) {
+ ldapsrv_bind_wait_finished(call, NT_STATUS_NO_MEMORY);
+ return;
+ }
+
+ call->postprocess_send = ldapsrv_sasl_postprocess_send;
+ call->postprocess_recv = ldapsrv_sasl_postprocess_recv;
+ call->postprocess_private = context;
+ } else {
+ talloc_unlink(conn, conn->gensec);
+ }
+ conn->gensec = NULL;
+
+ *resp->SASL.secblob = output;
+ result = LDAP_SUCCESS;
+ errstr = NULL;
+
+do_reply:
+ if (result != LDAP_SASL_BIND_IN_PROGRESS) {
+ /*
+ * We should destroy the gensec context
+ * when we hit a fatal error.
+ *
+ * Note: conn->gensec is already cleared
+ * for the LDAP_SUCCESS case.
+ */
+ talloc_unlink(conn, conn->gensec);
+ conn->gensec = NULL;
+ }
+
+ resp->response.resultcode = result;
+ resp->response.dn = NULL;
+ resp->response.errormessage = errstr;
+ resp->response.referral = NULL;
+
+ ldapsrv_queue_reply(call, reply);
+ ldapsrv_bind_wait_finished(call, NT_STATUS_OK);
+}
+
+NTSTATUS ldapsrv_BindRequest(struct ldapsrv_call *call)
+{
+ struct ldap_BindRequest *req = &call->request->r.BindRequest;
+ struct ldapsrv_reply *reply;
+ struct ldap_BindResponse *resp;
+
+ if (call->conn->pending_calls != NULL) {
+ reply = ldapsrv_init_reply(call, LDAP_TAG_BindResponse);
+ if (!reply) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ resp = &reply->msg->r.BindResponse;
+ resp->response.resultcode = LDAP_BUSY;
+ resp->response.dn = NULL;
+ resp->response.errormessage = talloc_asprintf(reply, "Pending requests on this LDAP session");
+ resp->response.referral = NULL;
+ resp->SASL.secblob = NULL;
+
+ ldapsrv_queue_reply(call, reply);
+ return NT_STATUS_OK;
+ }
+
+ /*
+ * TODO: a simple bind should cancel an
+ * inprogress SASL bind.
+ * (see RFC 4513)
+ */
+ switch (req->mechanism) {
+ case LDAP_AUTH_MECH_SIMPLE:
+ return ldapsrv_BindSimple(call);
+ case LDAP_AUTH_MECH_SASL:
+ return ldapsrv_BindSASL(call);
+ }
+
+ reply = ldapsrv_init_reply(call, LDAP_TAG_BindResponse);
+ if (!reply) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ resp = &reply->msg->r.BindResponse;
+ resp->response.resultcode = LDAP_AUTH_METHOD_NOT_SUPPORTED;
+ resp->response.dn = NULL;
+ resp->response.errormessage = talloc_asprintf(reply, "Bad AuthenticationChoice [%d]", req->mechanism);
+ resp->response.referral = NULL;
+ resp->SASL.secblob = NULL;
+
+ ldapsrv_queue_reply(call, reply);
+ return NT_STATUS_OK;
+}
+
+struct ldapsrv_unbind_wait_context {
+ uint8_t dummy;
+};
+
+struct ldapsrv_unbind_wait_state {
+ uint8_t dummy;
+};
+
+static struct tevent_req *ldapsrv_unbind_wait_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ void *private_data)
+{
+ struct ldapsrv_unbind_wait_context *unbind_wait =
+ talloc_get_type_abort(private_data,
+ struct ldapsrv_unbind_wait_context);
+ struct tevent_req *req;
+ struct ldapsrv_unbind_wait_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct ldapsrv_unbind_wait_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ (void)unbind_wait;
+
+ tevent_req_nterror(req, NT_STATUS_LOCAL_DISCONNECT);
+ return tevent_req_post(req, ev);
+}
+
+static NTSTATUS ldapsrv_unbind_wait_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+static NTSTATUS ldapsrv_unbind_wait_setup(struct ldapsrv_call *call)
+{
+ struct ldapsrv_unbind_wait_context *unbind_wait = NULL;
+
+ if (call->wait_private != NULL) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ unbind_wait = talloc_zero(call, struct ldapsrv_unbind_wait_context);
+ if (unbind_wait == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ call->wait_private = unbind_wait;
+ call->wait_send = ldapsrv_unbind_wait_send;
+ call->wait_recv = ldapsrv_unbind_wait_recv;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS ldapsrv_UnbindRequest(struct ldapsrv_call *call)
+{
+ struct ldapsrv_call *c = NULL;
+ struct ldapsrv_call *n = NULL;
+
+ DEBUG(10, ("UnbindRequest\n"));
+
+ for (c = call->conn->pending_calls; c != NULL; c = n) {
+ n = c->next;
+
+ DLIST_REMOVE(call->conn->pending_calls, c);
+ TALLOC_FREE(c);
+ }
+
+ return ldapsrv_unbind_wait_setup(call);
+}
diff --git a/source4/ldap_server/ldap_extended.c b/source4/ldap_server/ldap_extended.c
new file mode 100644
index 0000000..a451050
--- /dev/null
+++ b/source4/ldap_server/ldap_extended.c
@@ -0,0 +1,263 @@
+/*
+ Unix SMB/CIFS implementation.
+ LDAP server
+ Copyright (C) Stefan Metzmacher 2004
+
+ 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 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ 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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "ldap_server/ldap_server.h"
+#include "../lib/util/dlinklist.h"
+#include "lib/tls/tls.h"
+#include "samba/service_stream.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "librpc/gen_ndr/auth.h"
+#include "libcli/security/security_token.h"
+
+struct ldapsrv_starttls_postprocess_context {
+ struct ldapsrv_connection *conn;
+};
+
+struct ldapsrv_starttls_postprocess_state {
+ struct ldapsrv_connection *conn;
+};
+
+static void ldapsrv_starttls_postprocess_done(struct tevent_req *subreq);
+
+static struct tevent_req *ldapsrv_starttls_postprocess_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ void *private_data)
+{
+ struct ldapsrv_starttls_postprocess_context *context =
+ talloc_get_type_abort(private_data,
+ struct ldapsrv_starttls_postprocess_context);
+ struct ldapsrv_connection *conn = context->conn;
+ struct tevent_req *req;
+ struct ldapsrv_starttls_postprocess_state *state;
+ struct tevent_req *subreq;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct ldapsrv_starttls_postprocess_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->conn = conn;
+
+ subreq = tstream_tls_accept_send(conn,
+ conn->connection->event.ctx,
+ conn->sockets.raw,
+ conn->service->tls_params);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, ldapsrv_starttls_postprocess_done, req);
+
+ return req;
+}
+
+static void ldapsrv_starttls_postprocess_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct ldapsrv_starttls_postprocess_state *state =
+ tevent_req_data(req,
+ struct ldapsrv_starttls_postprocess_state);
+ struct ldapsrv_connection *conn = state->conn;
+ int ret;
+ int sys_errno;
+
+ ret = tstream_tls_accept_recv(subreq, &sys_errno,
+ conn, &conn->sockets.tls);
+ TALLOC_FREE(subreq);
+ if (ret == -1) {
+ NTSTATUS status = map_nt_error_from_unix_common(sys_errno);
+
+ DEBUG(1,("ldapsrv_starttls_postprocess_done: accept_tls_loop: "
+ "tstream_tls_accept_recv() - %d:%s => %s\n",
+ sys_errno, strerror(sys_errno), nt_errstr(status)));
+
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ conn->sockets.active = conn->sockets.tls;
+
+ tevent_req_done(req);
+}
+
+static NTSTATUS ldapsrv_starttls_postprocess_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+static NTSTATUS ldapsrv_StartTLS(struct ldapsrv_call *call,
+ struct ldapsrv_reply *reply,
+ const char **errstr)
+{
+ struct ldapsrv_starttls_postprocess_context *context;
+
+ (*errstr) = NULL;
+
+ /*
+ * TODO: give LDAP_OPERATIONS_ERROR also when
+ * there's a SASL bind in progress
+ * (see rfc4513 section 3.1.1)
+ */
+ if (call->conn->sockets.tls) {
+ (*errstr) = talloc_asprintf(reply, "START-TLS: TLS is already enabled on this LDAP session");
+ return NT_STATUS_LDAP(LDAP_OPERATIONS_ERROR);
+ }
+
+ if (call->conn->sockets.sasl) {
+ (*errstr) = talloc_asprintf(reply, "START-TLS: SASL is already enabled on this LDAP session");
+ return NT_STATUS_LDAP(LDAP_OPERATIONS_ERROR);
+ }
+
+ if (call->conn->pending_calls != NULL) {
+ (*errstr) = talloc_asprintf(reply, "START-TLS: pending requests on this LDAP session");
+ return NT_STATUS_LDAP(LDAP_BUSY);
+ }
+
+ context = talloc(call, struct ldapsrv_starttls_postprocess_context);
+ NT_STATUS_HAVE_NO_MEMORY(context);
+
+ context->conn = call->conn;
+
+ call->postprocess_send = ldapsrv_starttls_postprocess_send;
+ call->postprocess_recv = ldapsrv_starttls_postprocess_recv;
+ call->postprocess_private = context;
+
+ reply->msg->r.ExtendedResponse.response.resultcode = LDAP_SUCCESS;
+ reply->msg->r.ExtendedResponse.response.errormessage = NULL;
+
+ ldapsrv_queue_reply(call, reply);
+ return NT_STATUS_OK;
+}
+
+struct ldapsrv_extended_operation {
+ const char *oid;
+ NTSTATUS (*fn)(struct ldapsrv_call *call, struct ldapsrv_reply *reply, const char **errorstr);
+};
+
+static NTSTATUS ldapsrv_whoami(struct ldapsrv_call *call,
+ struct ldapsrv_reply *reply,
+ const char **errstr)
+{
+ struct ldapsrv_connection *conn = call->conn;
+ struct auth_session_info *session_info = conn->session_info;
+ struct ldap_ExtendedResponse *ext_resp =
+ &reply->msg->r.ExtendedResponse;
+
+ *errstr = NULL;
+
+ if (!security_token_is_anonymous(session_info->security_token)) {
+ struct auth_user_info *uinfo = session_info->info;
+ DATA_BLOB *value = talloc_zero(call, DATA_BLOB);
+
+ if (value == NULL) {
+ goto nomem;
+ }
+
+ value->data = (uint8_t *)talloc_asprintf(value,
+ "u:%s\\%s",
+ uinfo->domain_name,
+ uinfo->account_name);
+ if (value->data == NULL) {
+ goto nomem;
+ }
+ value->length = talloc_get_size(value->data) - 1;
+
+ ext_resp->value = value;
+ }
+
+ ext_resp->response.resultcode = LDAP_SUCCESS;
+ ext_resp->response.errormessage = NULL;
+
+ ldapsrv_queue_reply(call, reply);
+
+ return NT_STATUS_OK;
+nomem:
+ return NT_STATUS_LDAP(LDAP_OPERATIONS_ERROR);
+}
+
+
+static struct ldapsrv_extended_operation extended_ops[] = {
+ {
+ .oid = LDB_EXTENDED_START_TLS_OID,
+ .fn = ldapsrv_StartTLS,
+ },{
+ .oid = LDB_EXTENDED_WHOAMI_OID,
+ .fn = ldapsrv_whoami,
+ },
+ {
+ .oid = NULL,
+ .fn = NULL,
+ }
+};
+
+NTSTATUS ldapsrv_ExtendedRequest(struct ldapsrv_call *call)
+{
+ struct ldap_ExtendedRequest *req = &call->request->r.ExtendedRequest;
+ struct ldapsrv_reply *reply;
+ int result = LDAP_PROTOCOL_ERROR;
+ const char *error_str = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+ unsigned int i;
+
+ DEBUG(10, ("Extended\n"));
+
+ reply = ldapsrv_init_reply(call, LDAP_TAG_ExtendedResponse);
+ NT_STATUS_HAVE_NO_MEMORY(reply);
+
+ ZERO_STRUCT(reply->msg->r);
+ reply->msg->r.ExtendedResponse.oid = talloc_steal(reply, req->oid);
+ reply->msg->r.ExtendedResponse.response.resultcode = LDAP_PROTOCOL_ERROR;
+ reply->msg->r.ExtendedResponse.response.errormessage = NULL;
+
+ for (i=0; extended_ops[i].oid; i++) {
+ if (strcmp(extended_ops[i].oid,req->oid) != 0) continue;
+
+ /*
+ * if the backend function returns an error we
+ * need to send the reply otherwise the reply is already
+ * sent and we need to return directly
+ */
+ status = extended_ops[i].fn(call, reply, &error_str);
+ if (NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (NT_STATUS_IS_LDAP(status)) {
+ result = NT_STATUS_LDAP_CODE(status);
+ } else {
+ result = LDAP_OPERATIONS_ERROR;
+ error_str = talloc_asprintf(reply, "Extended Operation(%s) failed: %s",
+ req->oid, nt_errstr(status));
+ }
+ }
+ /* if we haven't found the oid, then status is still NT_STATUS_OK */
+ if (NT_STATUS_IS_OK(status)) {
+ error_str = talloc_asprintf(reply, "Extended Operation(%s) not supported",
+ req->oid);
+ }
+
+ reply->msg->r.ExtendedResponse.response.resultcode = result;
+ reply->msg->r.ExtendedResponse.response.errormessage = error_str;
+
+ ldapsrv_queue_reply(call, reply);
+ return NT_STATUS_OK;
+}
diff --git a/source4/ldap_server/ldap_server.c b/source4/ldap_server/ldap_server.c
new file mode 100644
index 0000000..9c34c3e
--- /dev/null
+++ b/source4/ldap_server/ldap_server.c
@@ -0,0 +1,1694 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ LDAP server
+
+ Copyright (C) Andrew Tridgell 2005
+ Copyright (C) Volker Lendecke 2004
+ Copyright (C) Stefan Metzmacher 2004
+
+ 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 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ 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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/network.h"
+#include "lib/events/events.h"
+#include "auth/auth.h"
+#include "auth/credentials/credentials.h"
+#include "librpc/gen_ndr/ndr_samr.h"
+#include "../lib/util/dlinklist.h"
+#include "../lib/util/asn1.h"
+#include "ldap_server/ldap_server.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 <ldb_errors.h>
+#include "libcli/ldap/ldap_proto.h"
+#include "system/network.h"
+#include "lib/socket/netif.h"
+#include "dsdb/samdb/samdb.h"
+#include "param/param.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);
+
+/*
+ close the socket and shutdown a server_context
+*/
+static void ldapsrv_terminate_connection(struct ldapsrv_connection *conn,
+ const char *reason)
+{
+ struct tevent_req *subreq;
+
+ if (conn->limits.reason) {
+ 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;
+ }
+
+ conn->limits.reason = talloc_strdup(conn, reason);
+ if (conn->limits.reason == NULL) {
+ TALLOC_FREE(conn->sockets.tls);
+ TALLOC_FREE(conn->sockets.sasl);
+ TALLOC_FREE(conn->sockets.raw);
+ stream_terminate_connection(conn->connection, reason);
+ return;
+ }
+
+ subreq = tstream_disconnect_send(conn,
+ conn->connection->event.ctx,
+ conn->sockets.active);
+ if (subreq == NULL) {
+ TALLOC_FREE(conn->sockets.tls);
+ TALLOC_FREE(conn->sockets.sasl);
+ TALLOC_FREE(conn->sockets.raw);
+ stream_terminate_connection(conn->connection, reason);
+ return;
+ }
+ tevent_req_set_endtime(subreq,
+ conn->connection->event.ctx,
+ conn->limits.endtime);
+ tevent_req_set_callback(subreq, ldapsrv_terminate_connection_done, conn);
+}
+
+static void ldapsrv_terminate_connection_done(struct tevent_req *subreq)
+{
+ struct ldapsrv_connection *conn =
+ tevent_req_callback_data(subreq,
+ struct ldapsrv_connection);
+ int sys_errno;
+ bool ok;
+
+ tstream_disconnect_recv(subreq, &sys_errno);
+ TALLOC_FREE(subreq);
+
+ if (conn->sockets.active == conn->sockets.raw) {
+ TALLOC_FREE(conn->sockets.tls);
+ TALLOC_FREE(conn->sockets.sasl);
+ TALLOC_FREE(conn->sockets.raw);
+ stream_terminate_connection(conn->connection,
+ conn->limits.reason);
+ return;
+ }
+
+ TALLOC_FREE(conn->sockets.tls);
+ TALLOC_FREE(conn->sockets.sasl);
+ conn->sockets.active = conn->sockets.raw;
+
+ subreq = tstream_disconnect_send(conn,
+ conn->connection->event.ctx,
+ conn->sockets.active);
+ if (subreq == NULL) {
+ TALLOC_FREE(conn->sockets.raw);
+ stream_terminate_connection(conn->connection,
+ conn->limits.reason);
+ return;
+ }
+ 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);
+}
+
+/*
+ called when a LDAP socket becomes readable
+*/
+void ldapsrv_recv(struct stream_connection *c, uint16_t flags)
+{
+ smb_panic(__location__);
+}
+
+/*
+ called when a LDAP socket becomes writable
+*/
+static void ldapsrv_send(struct stream_connection *c, uint16_t flags)
+{
+ smb_panic(__location__);
+}
+
+static int ldapsrv_load_limits(struct ldapsrv_connection *conn)
+{
+ TALLOC_CTX *tmp_ctx;
+ const char *attrs[] = { "configurationNamingContext", NULL };
+ const char *attrs2[] = { "lDAPAdminLimits", NULL };
+ struct ldb_message_element *el;
+ struct ldb_result *res = NULL;
+ struct ldb_dn *basedn;
+ struct ldb_dn *conf_dn;
+ struct ldb_dn *policy_dn;
+ unsigned int i;
+ int ret;
+
+ /* set defaults limits in case of failure */
+ 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);
+ if (tmp_ctx == NULL) {
+ return -1;
+ }
+
+ basedn = ldb_dn_new(tmp_ctx, conn->ldb, NULL);
+ if (basedn == NULL) {
+ goto failed;
+ }
+
+ ret = ldb_search(conn->ldb, tmp_ctx, &res, basedn, LDB_SCOPE_BASE, attrs, NULL);
+ if (ret != LDB_SUCCESS) {
+ goto failed;
+ }
+
+ if (res->count != 1) {
+ goto failed;
+ }
+
+ conf_dn = ldb_msg_find_attr_as_dn(conn->ldb, tmp_ctx, res->msgs[0], "configurationNamingContext");
+ if (conf_dn == NULL) {
+ goto failed;
+ }
+
+ policy_dn = ldb_dn_copy(tmp_ctx, conf_dn);
+ ldb_dn_add_child_fmt(policy_dn, "CN=Default Query Policy,CN=Query-Policies,CN=Directory Service,CN=Windows NT,CN=Services");
+ if (policy_dn == NULL) {
+ goto failed;
+ }
+
+ ret = ldb_search(conn->ldb, tmp_ctx, &res, policy_dn, LDB_SCOPE_BASE, attrs2, NULL);
+ if (ret != LDB_SUCCESS) {
+ goto failed;
+ }
+
+ if (res->count != 1) {
+ goto failed;
+ }
+
+ el = ldb_msg_find_element(res->msgs[0], "lDAPAdminLimits");
+ if (el == NULL) {
+ goto failed;
+ }
+
+ for (i = 0; i < el->num_values; i++) {
+ char policy_name[256];
+ int policy_value, s;
+
+ s = sscanf((const char *)el->values[i].data, "%255[^=]=%d", policy_name, &policy_value);
+ if (s != 2 || policy_value == 0)
+ continue;
+ if (strcasecmp("InitRecvTimeout", policy_name) == 0) {
+ conn->limits.initial_timeout = policy_value;
+ continue;
+ }
+ if (strcasecmp("MaxConnIdleTime", policy_name) == 0) {
+ conn->limits.conn_idle_time = policy_value;
+ continue;
+ }
+ if (strcasecmp("MaxPageSize", policy_name) == 0) {
+ 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) {
+ if (policy_value > 0) {
+ conn->limits.search_timeout = policy_value;
+ }
+ continue;
+ }
+ }
+
+ return 0;
+
+failed:
+ 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,
+ struct ldapsrv_call *call);
+static NTSTATUS ldapsrv_process_call_recv(struct tevent_req *req);
+
+static bool ldapsrv_call_read_next(struct ldapsrv_connection *conn);
+static void ldapsrv_accept_tls_done(struct tevent_req *subreq);
+
+/*
+ initialise a server_context from a open socket and register a event handler
+ for reading from that socket
+*/
+static void ldapsrv_accept(struct stream_connection *c,
+ struct auth_session_info *session_info,
+ bool is_privileged)
+{
+ 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;
+ int port;
+ int ret;
+ struct tevent_req *subreq;
+ struct timeval endtime;
+ char *errstring = NULL;
+
+ conn = talloc_zero(c, struct ldapsrv_connection);
+ if (!conn) {
+ stream_terminate_connection(c, "ldapsrv_accept: out of memory");
+ return;
+ }
+ conn->is_privileged = is_privileged;
+
+ conn->sockets.send_queue = tevent_queue_create(conn, "ldapsev send queue");
+ if (conn->sockets.send_queue == NULL) {
+ stream_terminate_connection(c,
+ "ldapsrv_accept: tevent_queue_create failed");
+ return;
+ }
+
+ TALLOC_FREE(c->event.fde);
+
+ ret = tstream_bsd_existing_socket(conn,
+ socket_get_fd(c->socket),
+ &conn->sockets.raw);
+ if (ret == -1) {
+ stream_terminate_connection(c,
+ "ldapsrv_accept: out of memory");
+ 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->lp_ctx;
+
+ c->private_data = conn;
+
+ socket_address = socket_get_my_addr(c->socket, conn);
+ if (!socket_address) {
+ ldapsrv_terminate_connection(conn, "ldapsrv_accept: failed to obtain local socket address!");
+ return;
+ }
+ port = socket_address->port;
+ talloc_free(socket_address);
+ if (port == 3268 || port == 3269) /* Global catalog */ {
+ conn->global_catalog = true;
+ }
+
+ server_credentials = cli_credentials_init_server(conn, conn->lp_ctx);
+ if (!server_credentials) {
+ stream_terminate_connection(c, "Failed to init server credentials\n");
+ return;
+ }
+
+ conn->server_credentials = server_credentials;
+
+ conn->session_info = session_info;
+
+ 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);
+ }
+
+ 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 */
+ irpc_add_name(c->msg_ctx, "ldap_server");
+
+ DLIST_ADD_END(ldapsrv_service->connections, conn);
+
+ if (port != 636 && port != 3269) {
+ ldapsrv_call_read_next(conn);
+ return;
+ }
+
+ endtime = timeval_current_ofs(conn->limits.conn_idle_time, 0);
+
+ subreq = tstream_tls_accept_send(conn,
+ conn->connection->event.ctx,
+ conn->sockets.raw,
+ conn->service->tls_params);
+ if (subreq == NULL) {
+ ldapsrv_terminate_connection(conn, "ldapsrv_accept: "
+ "no memory for tstream_tls_accept_send");
+ return;
+ }
+ tevent_req_set_endtime(subreq,
+ conn->connection->event.ctx,
+ endtime);
+ tevent_req_set_callback(subreq, ldapsrv_accept_tls_done, conn);
+}
+
+static void ldapsrv_accept_tls_done(struct tevent_req *subreq)
+{
+ struct ldapsrv_connection *conn =
+ tevent_req_callback_data(subreq,
+ struct ldapsrv_connection);
+ int ret;
+ int sys_errno;
+
+ ret = tstream_tls_accept_recv(subreq, &sys_errno,
+ conn, &conn->sockets.tls);
+ TALLOC_FREE(subreq);
+ if (ret == -1) {
+ const char *reason;
+
+ reason = talloc_asprintf(conn, "ldapsrv_accept_tls_loop: "
+ "tstream_tls_accept_recv() - %d:%s",
+ sys_errno, strerror(sys_errno));
+ if (!reason) {
+ reason = "ldapsrv_accept_tls_loop: "
+ "tstream_tls_accept_recv() - failed";
+ }
+
+ ldapsrv_terminate_connection(conn, reason);
+ return;
+ }
+
+ 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 (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 {
+ conn->limits.endtime =
+ timeval_current_ofs(conn->limits.conn_idle_time, 0);
+ }
+
+ if (conn->sockets.read_req != NULL) {
+ return true;
+ }
+
+ /*
+ * The minimum size of a LDAP pdu is 7 bytes
+ *
+ * dumpasn1 -hh ldap-unbind-min.dat
+ *
+ * <30 05 02 01 09 42 00>
+ * 0 5: SEQUENCE {
+ * <02 01 09>
+ * 2 1: INTEGER 9
+ * <42 00>
+ * 5 0: [APPLICATION 2]
+ * : Error: Object has zero length.
+ * : }
+ *
+ * dumpasn1 -hh ldap-unbind-windows.dat
+ *
+ * <30 84 00 00 00 05 02 01 09 42 00>
+ * 0 5: SEQUENCE {
+ * <02 01 09>
+ * 6 1: INTEGER 9
+ * <42 00>
+ * 9 0: [APPLICATION 2]
+ * : Error: Object has zero length.
+ * : }
+ *
+ * This means using an initial read size
+ * of 7 is ok.
+ */
+ subreq = tstream_read_pdu_blob_send(conn,
+ conn->connection->event.ctx,
+ conn->sockets.active,
+ 7, /* initial_read_size */
+ 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;
+ }
+ 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)
+{
+ struct ldapsrv_connection *conn =
+ tevent_req_callback_data(subreq,
+ struct ldapsrv_connection);
+ NTSTATUS status;
+ 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;
+
+ status = tstream_read_pdu_blob_recv(subreq,
+ call,
+ &blob);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ const char *reason;
+
+ reason = talloc_asprintf(call, "ldapsrv_call_loop: "
+ "tstream_read_pdu_blob_recv() - %s",
+ nt_errstr(status));
+ if (!reason) {
+ reason = nt_errstr(status);
+ }
+
+ ldapsrv_terminate_connection(conn, reason);
+ return;
+ }
+
+ 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;
+ }
+
+ call->request = talloc(call, struct ldap_message);
+ if (call->request == NULL) {
+ ldapsrv_terminate_connection(conn, "no memory");
+ return;
+ }
+
+ asn1_load_nocopy(asn1, blob.data, blob.length);
+
+ 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 */
+ 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");
+ return;
+ }
+ tevent_req_set_callback(subreq, ldapsrv_call_process_done, call);
+ 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)
+{
+ struct ldapsrv_call *call =
+ tevent_req_callback_data(subreq,
+ struct ldapsrv_call);
+ struct ldapsrv_connection *conn = call->conn;
+ NTSTATUS status;
+
+ conn->active_call = NULL;
+
+ status = ldapsrv_process_call_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ ldapsrv_terminate_connection(conn, nt_errstr(status));
+ return;
+ }
+
+ 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;
+
+ conn->active_call = NULL;
+
+ status = call->wait_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ const char *reason;
+
+ reason = talloc_asprintf(call, "ldapsrv_call_wait_done: "
+ "call->wait_recv() - %s",
+ nt_errstr(status));
+ if (reason == NULL) {
+ reason = nt_errstr(status);
+ }
+
+ ldapsrv_terminate_connection(conn, reason);
+ return;
+ }
+
+ 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;
+ }
+
+ /* 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, 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);
+}
+
+static void ldapsrv_call_postprocess_done(struct tevent_req *subreq);
+
+static void ldapsrv_call_writev_done(struct tevent_req *subreq)
+{
+ struct ldapsrv_call *call =
+ tevent_req_callback_data(subreq,
+ struct ldapsrv_call);
+ struct ldapsrv_connection *conn = call->conn;
+ int sys_errno;
+ int rc;
+
+ 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;
+
+ reason = talloc_asprintf(call, "ldapsrv_call_writev_done: "
+ "tstream_writev_queue_recv() - %d:%s",
+ sys_errno, strerror(sys_errno));
+ if (reason == NULL) {
+ reason = "ldapsrv_call_writev_done: "
+ "tstream_writev_queue_recv() failed";
+ }
+
+ ldapsrv_terminate_connection(conn, reason);
+ return;
+ }
+
+ if (call->postprocess_send) {
+ subreq = call->postprocess_send(call,
+ conn->connection->event.ctx,
+ call->postprocess_private);
+ if (subreq == NULL) {
+ ldapsrv_terminate_connection(conn, "ldapsrv_call_writev_done: "
+ "call->postprocess_send - no memory");
+ return;
+ }
+ tevent_req_set_callback(subreq,
+ ldapsrv_call_postprocess_done,
+ call);
+ return;
+ }
+
+ /* 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);
+}
+
+static void ldapsrv_call_postprocess_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;
+
+ status = call->postprocess_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ const char *reason;
+
+ reason = talloc_asprintf(call, "ldapsrv_call_postprocess_done: "
+ "call->postprocess_recv() - %s",
+ nt_errstr(status));
+ if (reason == NULL) {
+ reason = nt_errstr(status);
+ }
+
+ ldapsrv_terminate_connection(conn, reason);
+ return;
+ }
+
+ TALLOC_FREE(call);
+
+ 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;
+};
+
+static void ldapsrv_process_call_trigger(struct tevent_req *req,
+ void *private_data);
+
+static struct tevent_req *ldapsrv_process_call_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct tevent_queue *call_queue,
+ struct ldapsrv_call *call)
+{
+ struct tevent_req *req;
+ struct ldapsrv_process_call_state *state;
+ bool ok;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct ldapsrv_process_call_state);
+ if (req == NULL) {
+ return req;
+ }
+
+ state->call = call;
+
+ ok = tevent_queue_add(call_queue, ev, req,
+ ldapsrv_process_call_trigger, NULL);
+ if (!ok) {
+ tevent_req_oom(req);
+ return tevent_req_post(req, ev);
+ }
+
+ 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;
+ }
+
+ 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;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+static void ldapsrv_accept_nonpriv(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);
+}
+
+static const struct stream_server_ops ldap_stream_nonpriv_ops = {
+ .name = "ldap",
+ .accept_connection = ldapsrv_accept_nonpriv,
+ .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)
+{
+ 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->lp_ctx);
+ if (!session_info) {
+ stream_terminate_connection(c, "failed to setup system "
+ "session info");
+ return;
+ }
+ ldapsrv_accept(c, session_info, true);
+}
+
+static const struct stream_server_ops ldap_stream_priv_ops = {
+ .name = "ldap",
+ .accept_connection = ldapsrv_accept_priv,
+ .recv_handler = ldapsrv_recv,
+ .send_handler = ldapsrv_send,
+};
+
+#endif
+
+
+/*
+ add a socket address to the list of events, one event per port
+*/
+static NTSTATUS add_socket(struct task_server *task,
+ struct loadparm_context *lp_ctx,
+ const struct model_ops *model_ops,
+ const char *address, struct ldapsrv_service *ldap_service)
+{
+ uint16_t port = 389;
+ NTSTATUS status;
+ struct ldb_context *ldb;
+
+ status = stream_setup_socket(task, task->event_ctx, lp_ctx,
+ model_ops, &ldap_stream_nonpriv_ops,
+ "ip", address, &port,
+ lpcfg_socket_options(lp_ctx),
+ ldap_service, task->process_context);
+ if (!NT_STATUS_IS_OK(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)) {
+ /* add ldaps server */
+ port = 636;
+ status = stream_setup_socket(task, task->event_ctx, lp_ctx,
+ model_ops,
+ &ldap_stream_nonpriv_ops,
+ "ip", address, &port,
+ lpcfg_socket_options(lp_ctx),
+ ldap_service,
+ task->process_context);
+ if (!NT_STATUS_IS_OK(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->current_ev,
+ lp_ctx,
+ system_session(lp_ctx),
+ NULL,
+ 0);
+ if (!ldb) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ if (samdb_is_gc(ldb)) {
+ port = 3268;
+ status = stream_setup_socket(task, task->event_ctx, lp_ctx,
+ model_ops,
+ &ldap_stream_nonpriv_ops,
+ "ip", address, &port,
+ lpcfg_socket_options(lp_ctx),
+ ldap_service,
+ task->process_context);
+ if (!NT_STATUS_IS_OK(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)) {
+ /* add ldaps server for the global catalog */
+ port = 3269;
+ status = stream_setup_socket(task, task->event_ctx, lp_ctx,
+ model_ops,
+ &ldap_stream_nonpriv_ops,
+ "ip", address, &port,
+ lpcfg_socket_options(lp_ctx),
+ ldap_service,
+ task->process_context);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("ldapsrv failed to bind to %s:%u - %s\n",
+ address, port, nt_errstr(status));
+ return status;
+ }
+ }
+ }
+
+ /* And once we are bound, free the temporary ldb, it will
+ * connect again on each incoming LDAP connection */
+ talloc_unlink(ldap_service, ldb);
+
+ 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 NTSTATUS ldapsrv_task_init(struct task_server *task)
+{
+ char *ldapi_path;
+#ifdef WITH_LDAPI_PRIV_SOCKET
+ char *priv_dir;
+#endif
+ struct ldapsrv_service *ldap_service;
+ NTSTATUS status;
+
+ switch (lpcfg_server_role(task->lp_ctx)) {
+ case ROLE_STANDALONE:
+ task_server_terminate(task, "ldap_server: no LDAP server required in standalone configuration",
+ false);
+ return NT_STATUS_INVALID_DOMAIN_ROLE;
+ case ROLE_DOMAIN_MEMBER:
+ task_server_terminate(task, "ldap_server: no LDAP server required in member server configuration",
+ false);
+ 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]");
+
+ ldap_service = talloc_zero(task, struct ldapsrv_service);
+ if (ldap_service == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto failed;
+ }
+
+ ldap_service->lp_ctx = task->lp_ctx;
+ ldap_service->current_ev = task->event_ctx;
+ ldap_service->current_msg = task->msg_ctx;
+
+ ldap_service->dns_host_name = talloc_asprintf(ldap_service, "%s.%s",
+ lpcfg_netbios_name(task->lp_ctx),
+ lpcfg_dnsdomain(task->lp_ctx));
+ 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,
+ 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)) {
+ 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) {
+ status = NT_STATUS_NO_MEMORY;
+ goto failed;
+ }
+
+ if (lpcfg_interfaces(task->lp_ctx) && lpcfg_bind_interfaces_only(task->lp_ctx)) {
+ struct interface *ifaces;
+ int num_interfaces;
+ int i;
+
+ load_interface_list(task, task->lp_ctx, &ifaces);
+ num_interfaces = iface_list_count(ifaces);
+
+ /* 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, task->model_ops,
+ address, ldap_service);
+ if (!NT_STATUS_IS_OK(status)) goto failed;
+ }
+ } else {
+ char **wcard;
+ size_t i;
+ size_t num_binds = 0;
+ wcard = iface_list_wildcard(task);
+ if (wcard == NULL) {
+ 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, 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,
+ task->model_ops, &ldap_stream_nonpriv_ops,
+ "unix", ldapi_path, NULL,
+ lpcfg_socket_options(task->lp_ctx),
+ ldap_service, task->process_context);
+ talloc_free(ldapi_path);
+ if (!NT_STATUS_IS_OK(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, 0750)) {
+ task_server_terminate(task, "Cannot create ldap "
+ "privileged ldapi directory", true);
+ 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,
+ task->model_ops, &ldap_stream_priv_ops,
+ "unix", ldapi_path, NULL,
+ lpcfg_socket_options(task->lp_ctx),
+ ldap_service,
+ task->process_context);
+ talloc_free(ldapi_path);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("ldapsrv failed to bind to %s - %s\n",
+ ldapi_path, nt_errstr(status));
+ }
+
+#endif
+
+ /* 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(TALLOC_CTX *ctx)
+{
+ 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);
+}
diff --git a/source4/ldap_server/ldap_server.h b/source4/ldap_server/ldap_server.h
new file mode 100644
index 0000000..a56aa8f
--- /dev/null
+++ b/source4/ldap_server/ldap_server.h
@@ -0,0 +1,133 @@
+/*
+ Unix SMB/CIFS implementation.
+ LDAP server
+ Copyright (C) Volker Lendecke 2004
+ Copyright (C) Stefan Metzmacher 2004
+
+ 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 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ 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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "libcli/ldap/libcli_ldap.h"
+#include "lib/socket/socket.h"
+#include "lib/stream/packet.h"
+#include "system/network.h"
+#include "lib/param/loadparm.h"
+
+enum ldap_server_referral_scheme {
+ LDAP_REFERRAL_SCHEME_LDAP,
+ LDAP_REFERRAL_SCHEME_LDAPS
+};
+
+struct ldapsrv_connection {
+ struct ldapsrv_connection *next, *prev;
+ struct loadparm_context *lp_ctx;
+ struct stream_connection *connection;
+ struct gensec_security *gensec;
+ struct auth_session_info *session_info;
+ struct ldapsrv_service *service;
+ struct cli_credentials *server_credentials;
+ struct ldb_context *ldb;
+
+ struct {
+ struct tevent_queue *send_queue;
+ struct tevent_req *read_req;
+ struct tstream_context *raw;
+ struct tstream_context *tls;
+ struct tstream_context *sasl;
+ struct tstream_context *active;
+ } sockets;
+
+ bool global_catalog;
+ bool is_privileged;
+ enum ldap_server_require_strong_auth require_strong_auth;
+ bool authz_logged;
+ enum ldap_server_referral_scheme referral_scheme;
+
+ struct {
+ int initial_timeout;
+ int conn_idle_time;
+ int max_page_size;
+ int max_notifications;
+ int search_timeout;
+ struct timeval endtime;
+ struct timeval expire_time; /* Krb5 ticket expiry */
+ const char *reason;
+ } limits;
+
+ struct tevent_req *active_call;
+ struct tevent_req *deferred_expire_disconnect;
+
+ struct ldapsrv_call *pending_calls;
+};
+
+struct ldapsrv_call {
+ struct ldapsrv_call *prev, *next;
+ struct ldapsrv_connection *conn;
+ struct ldap_message *request;
+ struct ldapsrv_reply {
+ struct ldapsrv_reply *prev, *next;
+ struct ldap_message *msg;
+ DATA_BLOB blob;
+ } *replies;
+ struct iovec *out_iov;
+ size_t iov_count;
+ size_t reply_size;
+
+ struct tevent_req *(*wait_send)(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ void *private_data);
+ NTSTATUS (*wait_recv)(struct tevent_req *req);
+ void *wait_private;
+
+ struct tevent_req *(*postprocess_send)(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ void *private_data);
+ NTSTATUS (*postprocess_recv)(struct tevent_req *req);
+ void *postprocess_private;
+
+ struct {
+ bool busy;
+ uint64_t generation;
+ } notification;
+};
+
+/*
+ * This matches the previous implicit size limit via talloc's maximum
+ * allocation size
+ */
+#define LDAP_SERVER_MAX_REPLY_SIZE ((size_t)(256 * 1024 * 1024))
+
+/*
+ * Start writing to the network before we hit this size
+ */
+#define LDAP_SERVER_MAX_CHUNK_SIZE ((size_t)(25 * 1024 * 1024))
+
+struct ldapsrv_service {
+ const char *dns_host_name;
+ pid_t parent_pid;
+ struct tstream_tls_params *tls_params;
+ struct tevent_queue *call_queue;
+ struct ldapsrv_connection *connections;
+ struct {
+ uint64_t generation;
+ struct tevent_req *retry;
+ } notification;
+
+ struct loadparm_context *lp_ctx;
+ struct tevent_context *current_ev;
+ struct imessaging_context *current_msg;
+ struct ldb_context *sam_ctx;
+};
+
+#include "ldap_server/proto.h"
diff --git a/source4/ldap_server/wscript_build b/source4/ldap_server/wscript_build
new file mode 100644
index 0000000..881cc89
--- /dev/null
+++ b/source4/ldap_server/wscript_build
@@ -0,0 +1,13 @@
+#!/usr/bin/env python
+
+
+bld.SAMBA_MODULE('service_ldap',
+ source='ldap_server.c ldap_backend.c ldap_bind.c ldap_extended.c',
+ autoproto='proto.h',
+ subsystem='service',
+ init_function='server_service_ldap_init',
+ deps='samba-credentials cli-ldap samdb process_model gensec samba-hostconfig samba_server_gensec common_auth',
+ internal_module=False,
+ enabled=bld.AD_DC_BUILD_IS_ENABLED()
+ )
+