diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 17:20:00 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 17:20:00 +0000 |
commit | 8daa83a594a2e98f39d764422bfbdbc62c9efd44 (patch) | |
tree | 4099e8021376c7d8c05bdf8503093d80e9c7bad0 /source4/ldap_server | |
parent | Initial commit. (diff) | |
download | samba-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.c | 1637 | ||||
-rw-r--r-- | source4/ldap_server/ldap_bind.c | 783 | ||||
-rw-r--r-- | source4/ldap_server/ldap_extended.c | 263 | ||||
-rw-r--r-- | source4/ldap_server/ldap_server.c | 1694 | ||||
-rw-r--r-- | source4/ldap_server/ldap_server.h | 133 | ||||
-rw-r--r-- | source4/ldap_server/wscript_build | 13 |
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() + ) + |