diff options
author | Andrew Bartlett <abartlet@samba.org> | 2011-06-24 16:26:23 +1000 |
---|---|---|
committer | Andrew Bartlett <abartlet@samba.org> | 2011-06-24 16:26:23 +1000 |
commit | 6da26870e0ae5acd6ff49a30ec2f6886b44d095e (patch) | |
tree | 850c71039563c16a5d563c47e7ba2ab645baf198 /source4/dsdb | |
parent | 6925a799d04c6fa59dd2ddef1f5510f9bb7d17d1 (diff) | |
parent | 2610c05b5b95cc7036b3d6dfb894c6cfbdb68483 (diff) | |
download | samba-6da26870e0ae5acd6ff49a30ec2f6886b44d095e.tar.gz samba-6da26870e0ae5acd6ff49a30ec2f6886b44d095e.tar.bz2 samba-6da26870e0ae5acd6ff49a30ec2f6886b44d095e.zip |
Merge 2610c05b5b95cc7036b3d6dfb894c6cfbdb68483 as Samba-4.0alpha16
Diffstat (limited to 'source4/dsdb')
27 files changed, 2851 insertions, 152 deletions
diff --git a/source4/dsdb/common/util.c b/source4/dsdb/common/util.c index 0c920d7d85..3fa8f67447 100644 --- a/source4/dsdb/common/util.c +++ b/source4/dsdb/common/util.c @@ -1762,7 +1762,7 @@ const char *samdb_client_site_name(struct ldb_context *ldb, TALLOC_CTX *mem_ctx, allow_list[0] = l_subnet_name; - if (allow_access(mem_ctx, NULL, allow_list, "", ip_address)) { + if (socket_allow_access(mem_ctx, NULL, allow_list, "", ip_address)) { sites_dn = ldb_msg_find_attr_as_dn(ldb, mem_ctx, res->msgs[i], "siteObject"); @@ -3192,7 +3192,7 @@ bool dsdb_dn_is_deleted_val(const struct ldb_val *val) */ bool dsdb_dn_is_upgraded_link_val(struct ldb_val *val) { - return memmem(val->data, val->length, "<RMD_ADDTIME=", 13) != NULL; + return memmem(val->data, val->length, "<RMD_VERSION=", 13) != NULL; } /* diff --git a/source4/dsdb/common/util_samr.c b/source4/dsdb/common/util_samr.c index 7a4f644123..83a8c385af 100644 --- a/source4/dsdb/common/util_samr.c +++ b/source4/dsdb/common/util_samr.c @@ -342,6 +342,11 @@ NTSTATUS dsdb_add_domain_alias(struct ldb_context *ldb, TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); NT_STATUS_HAVE_NO_MEMORY(tmp_ctx); + if (ldb_transaction_start(ldb) != LDB_SUCCESS) { + DEBUG(0, ("Failed to start transaction in dsdb_add_domain_alias(): %s\n", ldb_errstring(ldb))); + return NT_STATUS_INTERNAL_ERROR; + } + /* Check if alias already exists */ name = samdb_search_string(ldb, tmp_ctx, NULL, "sAMAccountName", @@ -350,12 +355,14 @@ NTSTATUS dsdb_add_domain_alias(struct ldb_context *ldb, if (name != NULL) { talloc_free(tmp_ctx); + ldb_transaction_cancel(ldb); return NT_STATUS_ALIAS_EXISTS; } msg = ldb_msg_new(tmp_ctx); if (msg == NULL) { talloc_free(tmp_ctx); + ldb_transaction_cancel(ldb); return NT_STATUS_NO_MEMORY; } @@ -364,6 +371,7 @@ NTSTATUS dsdb_add_domain_alias(struct ldb_context *ldb, ldb_dn_add_child_fmt(msg->dn, "CN=%s,CN=Users", alias_name); if (!msg->dn) { talloc_free(tmp_ctx); + ldb_transaction_cancel(ldb); return NT_STATUS_NO_MEMORY; } @@ -378,15 +386,18 @@ NTSTATUS dsdb_add_domain_alias(struct ldb_context *ldb, break; case LDB_ERR_ENTRY_ALREADY_EXISTS: talloc_free(tmp_ctx); + ldb_transaction_cancel(ldb); return NT_STATUS_ALIAS_EXISTS; case LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS: talloc_free(tmp_ctx); + ldb_transaction_cancel(ldb); return NT_STATUS_ACCESS_DENIED; default: DEBUG(0,("Failed to create alias record %s: %s\n", ldb_dn_get_linearized(msg->dn), ldb_errstring(ldb))); talloc_free(tmp_ctx); + ldb_transaction_cancel(ldb); return NT_STATUS_INTERNAL_DB_CORRUPTION; } @@ -394,10 +405,17 @@ NTSTATUS dsdb_add_domain_alias(struct ldb_context *ldb, alias_sid = samdb_search_dom_sid(ldb, tmp_ctx, msg->dn, "objectSid", NULL); + if (ldb_transaction_commit(ldb) != LDB_SUCCESS) { + DEBUG(0, ("Failed to commit transaction in dsdb_add_domain_alias(): %s\n", + ldb_errstring(ldb))); + return NT_STATUS_INTERNAL_ERROR; + } + *dn = talloc_steal(mem_ctx, msg->dn); *sid = talloc_steal(mem_ctx, alias_sid); talloc_free(tmp_ctx); + return NT_STATUS_OK; } diff --git a/source4/dsdb/dns/dns_update.c b/source4/dsdb/dns/dns_update.c index ede730a8a9..6650534d13 100644 --- a/source4/dsdb/dns/dns_update.c +++ b/source4/dsdb/dns/dns_update.c @@ -79,7 +79,7 @@ static void dnsupdate_rndc_done(struct tevent_req *subreq) ret = samba_runcmd_recv(subreq, &sys_errno); TALLOC_FREE(subreq); if (ret != 0) { - service->confupdate.status = map_nt_error_from_unix(sys_errno); + service->confupdate.status = map_nt_error_from_unix_common(sys_errno); } else { service->confupdate.status = NT_STATUS_OK; } @@ -123,12 +123,12 @@ static void dnsupdate_rebuild(struct dnsupdate_service *service) path = lpcfg_parm_string(service->task->lp_ctx, NULL, "dnsupdate", "path"); if (path == NULL) { - path = private_path(tmp_ctx, service->task->lp_ctx, "named.conf.update"); + path = lpcfg_private_path(tmp_ctx, service->task->lp_ctx, "named.conf.update"); } path_static = lpcfg_parm_string(service->task->lp_ctx, NULL, "dnsupdate", "extra_static_grant_rules"); if (path_static == NULL) { - path_static = private_path(tmp_ctx, service->task->lp_ctx, "named.conf.update.static"); + path_static = lpcfg_private_path(tmp_ctx, service->task->lp_ctx, "named.conf.update.static"); } tmp_path = talloc_asprintf(tmp_ctx, "%s.tmp", path); @@ -242,7 +242,7 @@ static void dnsupdate_nameupdate_done(struct tevent_req *subreq) ret = samba_runcmd_recv(subreq, &sys_errno); TALLOC_FREE(subreq); if (ret != 0) { - service->nameupdate.status = map_nt_error_from_unix(sys_errno); + service->nameupdate.status = map_nt_error_from_unix_common(sys_errno); } else { service->nameupdate.status = NT_STATUS_OK; } @@ -271,7 +271,7 @@ static void dnsupdate_spnupdate_done(struct tevent_req *subreq) ret = samba_runcmd_recv(subreq, &sys_errno); TALLOC_FREE(subreq); if (ret != 0) { - service->nameupdate.status = map_nt_error_from_unix(sys_errno); + service->nameupdate.status = map_nt_error_from_unix_common(sys_errno); } else { service->nameupdate.status = NT_STATUS_OK; } @@ -381,7 +381,7 @@ static void dnsupdate_RODC_callback(struct tevent_req *req) ret = samba_runcmd_recv(req, &sys_errno); talloc_free(req); if (ret != 0) { - st->r->out.result = map_nt_error_from_unix(sys_errno); + st->r->out.result = map_nt_error_from_unix_common(sys_errno); DEBUG(2,(__location__ ": RODC DNS Update failed: %s\n", nt_errstr(st->r->out.result))); } else { st->r->out.result = NT_STATUS_OK; diff --git a/source4/dsdb/pydsdb.c b/source4/dsdb/pydsdb.c index 895bd9a560..5ca6b02608 100644 --- a/source4/dsdb/pydsdb.c +++ b/source4/dsdb/pydsdb.c @@ -331,6 +331,38 @@ static PyObject *py_dsdb_get_attid_from_lDAPDisplayName(PyObject *self, PyObject } /* + return the attribute syntax oid as a string from the attribute name + */ +static PyObject *py_dsdb_get_syntax_oid_from_lDAPDisplayName(PyObject *self, PyObject *args) +{ + PyObject *py_ldb; + struct ldb_context *ldb; + struct dsdb_schema *schema; + const char *ldap_display_name; + const struct dsdb_attribute *attribute; + + if (!PyArg_ParseTuple(args, "Os", &py_ldb, &ldap_display_name)) + return NULL; + + PyErr_LDB_OR_RAISE(py_ldb, ldb); + + schema = dsdb_get_schema(ldb, NULL); + + if (!schema) { + PyErr_SetString(PyExc_RuntimeError, "Failed to find a schema from ldb"); + return NULL; + } + + attribute = dsdb_attribute_by_lDAPDisplayName(schema, ldap_display_name); + if (attribute == NULL) { + PyErr_Format(PyExc_RuntimeError, "Failed to find attribute '%s'", ldap_display_name); + return NULL; + } + + return PyString_FromString(attribute->syntax->ldap_oid); +} + +/* convert a python string to a DRSUAPI drsuapi_DsReplicaAttribute attribute */ static PyObject *py_dsdb_DsReplicaAttribute(PyObject *self, PyObject *args) @@ -423,6 +455,109 @@ static PyObject *py_dsdb_DsReplicaAttribute(PyObject *self, PyObject *args) return ret; } + +/* + normalise a ldb attribute list + */ +static PyObject *py_dsdb_normalise_attributes(PyObject *self, PyObject *args) +{ + PyObject *py_ldb, *el_list, *ret; + struct ldb_context *ldb; + char *ldap_display_name; + const struct dsdb_attribute *a; + struct dsdb_schema *schema; + struct dsdb_syntax_ctx syntax_ctx; + struct ldb_message_element *el; + struct drsuapi_DsReplicaAttribute *attr; + TALLOC_CTX *tmp_ctx; + WERROR werr; + Py_ssize_t i; + + if (!PyArg_ParseTuple(args, "OsO", &py_ldb, &ldap_display_name, &el_list)) { + return NULL; + } + + PyErr_LDB_OR_RAISE(py_ldb, ldb); + + if (!PyList_Check(el_list)) { + PyErr_Format(PyExc_TypeError, "ldif_elements must be a list"); + return NULL; + } + + schema = dsdb_get_schema(ldb, NULL); + if (!schema) { + PyErr_SetString(PyExc_RuntimeError, "Failed to find a schema from ldb"); + return NULL; + } + + a = dsdb_attribute_by_lDAPDisplayName(schema, ldap_display_name); + if (a == NULL) { + PyErr_Format(PyExc_RuntimeError, "Failed to find attribute '%s'", ldap_display_name); + return NULL; + } + + dsdb_syntax_ctx_init(&syntax_ctx, ldb, schema); + syntax_ctx.is_schema_nc = false; + + tmp_ctx = talloc_new(ldb); + if (tmp_ctx == NULL) { + PyErr_NoMemory(); + return NULL; + } + + el = talloc_zero(tmp_ctx, struct ldb_message_element); + if (el == NULL) { + PyErr_NoMemory(); + talloc_free(tmp_ctx); + return NULL; + } + + el->name = ldap_display_name; + el->num_values = PyList_Size(el_list); + + el->values = talloc_array(el, struct ldb_val, el->num_values); + if (el->values == NULL) { + PyErr_NoMemory(); + talloc_free(tmp_ctx); + return NULL; + } + + for (i = 0; i < el->num_values; i++) { + PyObject *item = PyList_GetItem(el_list, i); + if (!PyString_Check(item)) { + PyErr_Format(PyExc_TypeError, "ldif_elements should be strings"); + return NULL; + } + el->values[i].data = (uint8_t *)PyString_AsString(item); + el->values[i].length = PyString_Size(item); + } + + /* first run ldb_to_drsuapi, then convert back again. This has + * the effect of normalising the attributes + */ + + attr = talloc_zero(tmp_ctx, struct drsuapi_DsReplicaAttribute); + if (attr == NULL) { + PyErr_NoMemory(); + talloc_free(tmp_ctx); + return NULL; + } + + werr = a->syntax->ldb_to_drsuapi(&syntax_ctx, a, el, attr, attr); + PyErr_WERROR_IS_ERR_RAISE(werr); + + /* now convert back again */ + werr = a->syntax->drsuapi_to_ldb(&syntax_ctx, a, attr, el, el); + PyErr_WERROR_IS_ERR_RAISE(werr); + + ret = py_return_ndr_struct("ldb", "MessageElement", el, el); + + talloc_free(tmp_ctx); + + return ret; +} + + static PyObject *py_dsdb_set_ntds_invocation_id(PyObject *self, PyObject *args) { PyObject *py_ldb, *py_guid; @@ -699,6 +834,8 @@ static PyMethodDef py_dsdb_methods[] = { METH_VARARGS, NULL }, { "_dsdb_get_attid_from_lDAPDisplayName", (PyCFunction)py_dsdb_get_attid_from_lDAPDisplayName, METH_VARARGS, NULL }, + { "_dsdb_get_syntax_oid_from_lDAPDisplayName", (PyCFunction)py_dsdb_get_syntax_oid_from_lDAPDisplayName, + METH_VARARGS, NULL }, { "_dsdb_set_ntds_invocation_id", (PyCFunction)py_dsdb_set_ntds_invocation_id, METH_VARARGS, NULL }, @@ -723,6 +860,7 @@ static PyMethodDef py_dsdb_methods[] = { NULL }, { "_dsdb_get_partitions_dn", (PyCFunction)py_dsdb_get_partitions_dn, METH_VARARGS, NULL }, { "_dsdb_DsReplicaAttribute", (PyCFunction)py_dsdb_DsReplicaAttribute, METH_VARARGS, NULL }, + { "_dsdb_normalise_attributes", (PyCFunction)py_dsdb_normalise_attributes, METH_VARARGS, NULL }, { NULL } }; @@ -862,4 +1000,10 @@ void initdsdb(void) ADD_DSDB_FLAG(GPO_FLAG_MACHINE_DISABLE); ADD_DSDB_FLAG(GPO_INHERIT); ADD_DSDB_FLAG(GPO_BLOCK_INHERITANCE); + +#define ADD_DSDB_STRING(val) PyModule_AddObject(m, #val, PyString_FromString(val)) + + ADD_DSDB_STRING(DSDB_SYNTAX_BINARY_DN); + ADD_DSDB_STRING(DSDB_SYNTAX_STRING_DN); + ADD_DSDB_STRING(DSDB_SYNTAX_OR_NAME); } diff --git a/source4/dsdb/repl/drepl_fsmo.c b/source4/dsdb/repl/drepl_fsmo.c index f8f4769f1b..db6385315b 100644 --- a/source4/dsdb/repl/drepl_fsmo.c +++ b/source4/dsdb/repl/drepl_fsmo.c @@ -111,7 +111,7 @@ NTSTATUS drepl_take_FSMO_role(struct irpc_message *msg, if (fsmo_master_equal(ntds_dn, role_owner_dn) || (extended_op == DRSUAPI_EXOP_NONE)) { - DEBUG(0,("FSMO role check failed for DN %s and owner %s ", + DEBUG(0,("FSMO role check failed for DN %s and owner %s \n", ldb_dn_get_linearized(fsmo_role_dn), ldb_dn_get_linearized(role_owner_dn))); r->out.result = WERR_OK; diff --git a/source4/dsdb/repl/drepl_ridalloc.c b/source4/dsdb/repl/drepl_ridalloc.c index 48c208c3cf..53b56b63d2 100644 --- a/source4/dsdb/repl/drepl_ridalloc.c +++ b/source4/dsdb/repl/drepl_ridalloc.c @@ -236,7 +236,7 @@ WERROR dreplsrv_ridalloc_check_rid_pool(struct dreplsrv_service *service) /* called by the samldb ldb module to tell us to ask for a new RID pool */ -void dreplsrv_allocate_rid(struct messaging_context *msg, void *private_data, +void dreplsrv_allocate_rid(struct imessaging_context *msg, void *private_data, uint32_t msg_type, struct server_id server_id, DATA_BLOB *data) { diff --git a/source4/dsdb/repl/drepl_service.c b/source4/dsdb/repl/drepl_service.c index 0931a340b1..ec803f6fdb 100644 --- a/source4/dsdb/repl/drepl_service.c +++ b/source4/dsdb/repl/drepl_service.c @@ -497,7 +497,7 @@ static void dreplsrv_task_init(struct task_server *task) IRPC_REGISTER(task->msg_ctx, drsuapi, DRSUAPI_DSREPLICAMOD, dreplsrv_replica_mod, service); IRPC_REGISTER(task->msg_ctx, irpc, DREPL_TAKEFSMOROLE, drepl_take_FSMO_role, service); IRPC_REGISTER(task->msg_ctx, irpc, DREPL_TRIGGER_REPL_SECRET, drepl_trigger_repl_secret, service); - messaging_register(task->msg_ctx, service, MSG_DREPL_ALLOCATE_RID, dreplsrv_allocate_rid); + imessaging_register(task->msg_ctx, service, MSG_DREPL_ALLOCATE_RID, dreplsrv_allocate_rid); } /* diff --git a/source4/dsdb/samdb/ldb_modules/acl_read.c b/source4/dsdb/samdb/ldb_modules/acl_read.c index 181619ab28..35a840e1f4 100644 --- a/source4/dsdb/samdb/ldb_modules/acl_read.c +++ b/source4/dsdb/samdb/ldb_modules/acl_read.c @@ -47,6 +47,7 @@ struct aclread_context { bool sd; bool instance_type; bool object_sid; + bool indirsync; }; struct aclread_private { @@ -158,18 +159,41 @@ static int aclread_callback(struct ldb_request *req, struct ldb_reply *ares) access_mask, attr); - if (ret == LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS) { - /* do not return this entry if attribute is - part of the search filter */ - if (dsdb_attr_in_parse_tree(ac->req->op.search.tree, - msg->elements[i].name)) { - talloc_free(tmp_ctx); - return LDB_SUCCESS; - } - aclread_mark_inaccesslible(&msg->elements[i]); - } else if (ret != LDB_SUCCESS) { - goto fail; - } + /* + * Dirsync control needs the replpropertymetadata attribute + * so return it as it will be removed by the control + * in anycase. + */ + if (ret == LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS) { + if (!ac->indirsync) { + /* do not return this entry if attribute is + part of the search filter */ + if (dsdb_attr_in_parse_tree(ac->req->op.search.tree, + msg->elements[i].name)) { + talloc_free(tmp_ctx); + return LDB_SUCCESS; + } + aclread_mark_inaccesslible(&msg->elements[i]); + } else { + /* + * We are doing dirysnc answers + * and the object shouldn't be returned (normally) + * but we will return it without replPropertyMetaData + * so that the dirysync module will do what is needed + * (remove the object if it is not deleted, or return + * just the objectGUID if it's deleted). + */ + if (dsdb_attr_in_parse_tree(ac->req->op.search.tree, + msg->elements[i].name)) { + ldb_msg_remove_attr(msg, "replPropertyMetaData"); + break; + } else { + aclread_mark_inaccesslible(&msg->elements[i]); + } + } + } else if (ret != LDB_SUCCESS) { + goto fail; + } } for (i=0; i < msg->num_elements; i++) { if (!aclread_is_inaccessible(&msg->elements[i])) { @@ -224,6 +248,7 @@ static int aclread_search(struct ldb_module *module, struct ldb_request *req) struct aclread_context *ac; struct ldb_request *down_req; struct ldb_control *as_system = ldb_request_get_control(req, LDB_CONTROL_AS_SYSTEM_OID); + uint32_t flags = ldb_req_get_custom_flags(req); struct ldb_result *res; struct aclread_private *p; bool is_untrusted = ldb_req_is_untrusted(req); @@ -284,6 +309,11 @@ static int aclread_search(struct ldb_module *module, struct ldb_request *req) ac->module = module; ac->req = req; ac->schema = dsdb_get_schema(ldb, req); + if (flags & DSDB_ACL_CHECKS_DIRSYNC_FLAG) { + ac->indirsync = true; + } else { + ac->indirsync = false; + } if (!ac->schema) { return ldb_operr(ldb); } diff --git a/source4/dsdb/samdb/ldb_modules/dirsync.c b/source4/dsdb/samdb/ldb_modules/dirsync.c new file mode 100644 index 0000000000..64c5047798 --- /dev/null +++ b/source4/dsdb/samdb/ldb_modules/dirsync.c @@ -0,0 +1,1359 @@ +/* + SAMDB control module + + Copyright (C) Matthieu Patou <mat@matws.net> 2011 + + 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 "ldb/include/ldb.h" +#include "ldb/include/ldb_errors.h" +#include "ldb/include/ldb_module.h" +#include "libcli/security/security.h" +#include "librpc/gen_ndr/drsblobs.h" +#include "librpc/gen_ndr/ndr_drsblobs.h" +#include "librpc/ndr/libndr.h" +#include "dsdb/samdb/samdb.h" +#include "util.h" + +#define LDAP_DIRSYNC_OBJECT_SECURITY 0x01 +#define LDAP_DIRSYNC_ANCESTORS_FIRST_ORDER 0x800 +#define LDAP_DIRSYNC_PUBLIC_DATA_ONLY 0x2000 +#define LDAP_DIRSYNC_INCREMENTAL_VALUES 0x80000000 + + +struct dirsync_context { + struct ldb_module *module; + struct ldb_request *req; + + /* + * We keep a track of the number of attributes that we + * add just for the need of the implementation + * it will be usefull to track then entries that needs not to + * be returned because there is no real change + */ + + unsigned int nbDefaultAttrs; + uint64_t highestUSN; + uint64_t fromreqUSN; + uint32_t cursor_size; + bool noextended; + bool linkIncrVal; + bool localonly; + bool partial; + bool assystem; + int functional_level; + const struct GUID *our_invocation_id; + const struct dsdb_schema *schema; + struct ldb_dn *nc_root; + struct drsuapi_DsReplicaCursor *cursors; +}; + + +static int dirsync_filter_entry(struct ldb_request *req, + struct ldb_message *msg, + struct ldb_control **controls, + struct dirsync_context *dsc, + bool referral) +{ + struct ldb_context *ldb; + uint64_t val; + enum ndr_err_code ndr_err; + uint32_t n; + int i; + unsigned int size, j; + uint32_t deletedattr; + struct ldb_val *replMetaData = NULL; + struct replPropertyMetaDataBlob rmd; + const struct dsdb_attribute *attr; + const char **listAttr = NULL; + bool namereturned = false; + bool nameasked = false; + NTSTATUS status; + /* Ajustment for the added attributes, it will reduce the number of + * expected to be here attributes*/ + unsigned int delta = 0; + const char **myaccept = NULL; + const char *emptyaccept[] = { NULL }; + const char *extendedaccept[] = { "GUID", "SID", "WKGUID", NULL }; + const char *rdn = NULL; + struct ldb_message_element *el; + struct ldb_message *newmsg; + bool keep = false; + /* + * Where we asked to do extended dn ? + * if so filter out everything bug GUID, SID, WKGUID, + * if not filter out everything (just keep the dn). + */ + if ( dsc->noextended == true ) { + myaccept = emptyaccept; + } else { + myaccept = extendedaccept; + } + ldb = ldb_module_get_ctx(dsc->module); + + if (msg->num_elements == 0) { + /* + * Entry that we don't really have access to + */ + return LDB_SUCCESS; + } + ldb_dn_extended_filter(msg->dn, myaccept); + + /* + * If the RDN starts with CN then the CN attribute is never returned + */ + rdn = ldb_dn_get_rdn_name(msg->dn); + + deletedattr = 0; + /* + * if objectGUID is asked and we are dealing for the referrals entries and + * the usn searched is 0 then we didn't count the objectGUID as an automatically + * returned attribute, do to so we increament delta. + */ + if (referral == true && + ldb_attr_in_list(req->op.search.attrs, "objectGUID") && + dsc->fromreqUSN == 0) { + delta++; + } + + + /* + * In terms of big O notation this is not the best algorithm, + * but we try our best not to make the worse one. + * We are obliged to run through the n message's elements + * and through the p elements of the replPropertyMetaData. + * + * It turns out that we are crawling twice the message's elements + * the first crawl is to remove the non replicated and generated + * attributes. The second one is to remove attributes that haven't + * a USN > as the requested one. + * + * In the second crawl we are reading the list of elements in the + * replPropertyMetaData for each remaining replicated attribute. + * In order to keep the list small + * + * We have a O(n'*p') complexity, in worse case n' = n and p' = p + * but in most case n' = n/2 (at least half of returned attributes + * are not replicated or generated) and p' is small as we + * list only the attribute that have been modified since last interogation + * + */ + newmsg = talloc_zero(dsc->req, struct ldb_message); + if (newmsg == NULL) { + return ldb_oom(ldb); + } + for (i = msg->num_elements - 1; i >= 0; i--) { + attr = dsdb_attribute_by_lDAPDisplayName(dsc->schema, msg->elements[i].name); + if (ldb_attr_cmp(msg->elements[i].name, "uSNChanged") == 0) { + /* Read the USN it will used at the end of the filtering + * to update the max USN in the cookie if we + * decide to keep this entry + */ + val = strtoull((const char*)msg->elements[i].values[0].data, NULL, 0); + continue; + } + + if (ldb_attr_cmp(msg->elements[i].name, + "replPropertyMetaData") == 0) { + replMetaData = (talloc_steal(dsc, &msg->elements[i].values[0])); + continue; + } + } + + if (replMetaData == NULL) { + bool guidfound = false; + + /* + * We are in the case of deleted object where we don't have the + * right to read it. + */ + if (!ldb_msg_find_attr_as_uint(msg, "isDeleted", 0)) { + /* + * This is not a deleted item and we don't + * have the replPropertyMetaData. + * Do not return it + */ + return LDB_SUCCESS; + } + newmsg->dn = ldb_dn_new(newmsg, ldb, ""); + if (newmsg->dn == NULL) { + return ldb_oom(ldb); + } + + el = ldb_msg_find_element(msg, "objectGUID"); + if ( el != NULL) { + guidfound = true; + } + /* + * We expect to find the GUID in the object, + * if it turns out not to be the case sometime + * well will uncomment the code bellow + */ + SMB_ASSERT(guidfound == true); + /* + if (guidfound == false) { + struct GUID guid; + struct ldb_val *new_val; + DATA_BLOB guid_blob; + + tmp[0] = '\0'; + txt = strrchr(txt, ':'); + if (txt == NULL) { + return ldb_module_done(dsc->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR); + } + txt++; + + status = GUID_from_string(txt, &guid); + if (!NT_STATUS_IS_OK(status)) { + return ldb_module_done(dsc->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR); + } + + status = GUID_to_ndr_blob(&guid, msg, &guid_blob); + if (!NT_STATUS_IS_OK(status)) { + return ldb_module_done(dsc->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR); + } + + new_val = talloc(msg, struct ldb_val); + if (new_val == NULL) { + return ldb_oom(ldb); + } + new_val->data = talloc_steal(new_val, guid_blob.data); + new_val->length = guid_blob.length; + if (ldb_msg_add_value(msg, "objectGUID", new_val, NULL) != 0) { + return ldb_module_done(dsc->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR); + } + } + */ + ldb_msg_add(newmsg, el, LDB_FLAG_MOD_ADD); + talloc_steal(newmsg->elements, el->name); + talloc_steal(newmsg->elements, el->values); + + talloc_free(msg); + return ldb_module_send_entry(dsc->req, msg, controls); + } + + ndr_err = ndr_pull_struct_blob(replMetaData, dsc, &rmd, + (ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaDataBlob); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + ldb_set_errstring(ldb, "Unable to unmarshall replPropertyMetaData"); + return ldb_module_done(dsc->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR); + } + if (ldb_attr_in_list(req->op.search.attrs, "name") || + ldb_attr_in_list(req->op.search.attrs, "*")) { + nameasked = true; + } + + /* + * If we don't have an USN and no updateness array then we skip the + * test phase this is an optimisation for the case when you + * first query the DC without a cookie. + * As this query is most probably the one + * that will return the biggest answer, skipping this part + * will really save time. + */ + if (ldb_dn_compare(dsc->nc_root, msg->dn) == 0) { + /* If we have name then we expect to have parentGUID, + * it will not be the case for the root of the NC + */ + delta++; + } + + if (dsc->fromreqUSN > 0 || dsc->cursors != NULL) { + j = 0; + /* + * Allocate an array of size(replMetaData) of char* + * we know that it will be oversized but it's a short lived element + */ + listAttr = talloc_array(msg, const char*, rmd.ctr.ctr1.count + 1); + if (listAttr == NULL) { + return ldb_oom(ldb); + } + for (n=0; n < rmd.ctr.ctr1.count; n++) { + struct replPropertyMetaData1 *omd = &rmd.ctr.ctr1.array[n]; + if (omd->local_usn > dsc->fromreqUSN) { + const struct dsdb_attribute *a = dsdb_attribute_by_attributeID_id(dsc->schema, + omd->attid); + if (!dsc->localonly) { + struct drsuapi_DsReplicaCursor *tab = dsc->cursors; + uint32_t l; + for (l=0; l < dsc->cursor_size; l++) { + if (GUID_equal(&tab[l].source_dsa_invocation_id, &omd->originating_invocation_id) && + tab[l].highest_usn >= omd->originating_usn) { + /* + * If we have in the uptodateness vector an entry + * with the same invocation id as the originating invocation + * and if the usn in the vector is greater or equal to + * the one in originating_usn, then it means that this entry + * has already been sent (from another DC) to the client + * no need to resend it one more time. + */ + goto skip; + } + } + /* If we are here it's because we have a usn > (max(usn of vectors))*/ + } + if (namereturned == false && + nameasked == true && + ldb_attr_cmp(a->lDAPDisplayName, "name") == 0) { + namereturned = true; + if (ldb_dn_compare(dsc->nc_root, msg->dn) == 0) { + delta++; + } + } + listAttr[j] = a->lDAPDisplayName; + j++; +skip: + continue; + } + } + size = j; + } else { + size = 0; + if (ldb_attr_in_list(req->op.search.attrs, "*") || + ldb_attr_in_list(req->op.search.attrs, "name")) { + namereturned = true; + } + } + + + /* + * Let's loop around the remaining elements + * to see which one are in the listAttr. + * If they are in this array it means that + * their localusn > usn from the request (in the cookie) + * if not we remove the attribute. + */ + for (i = msg->num_elements - 1; i >= 0; i--) { + el = &(msg->elements[i]); + attr = dsdb_attribute_by_lDAPDisplayName(dsc->schema, + el->name); + const char *ldapattrname = el->name; + keep = false; + + if (attr->linkID & 1) { + /* + * Attribute is a backlink so let's remove it + */ + continue; + } + + if (ldb_attr_cmp(msg->elements[i].name, + "replPropertyMetaData") == 0) { + continue; + } + + if ((attr->systemFlags & (DS_FLAG_ATTR_NOT_REPLICATED | DS_FLAG_ATTR_IS_CONSTRUCTED))) { + if (ldb_attr_cmp(attr->lDAPDisplayName, "objectGUID") != 0 && + ldb_attr_cmp(attr->lDAPDisplayName, "parentGUID") != 0) { + /* + * Attribute is constructed or not replicated, let's get rid of it + */ + continue; + } else { + /* Let's keep the attribute that we forced to be added + * even if they are not in the replicationMetaData + * or are just generated + */ + if (namereturned == false && + (ldb_attr_cmp(attr->lDAPDisplayName, "parentGUID") == 0)) { + delta++; + continue; + } + if (ldb_msg_add(newmsg, el, LDB_FLAG_MOD_ADD) != LDB_SUCCESS) { + return ldb_error(ldb, + LDB_ERR_OPERATIONS_ERROR, + "Unable to add attribute"); + } + talloc_steal(newmsg->elements, el->name); + talloc_steal(newmsg->elements, el->values); + continue; + } + } + + if (ldb_attr_cmp(msg->elements[i].name, rdn) == 0) { + /* + * We have an attribute that is the same as the start of the RDN + * (ie. attribute CN with rdn CN=). + */ + continue; + } + + if (ldb_attr_cmp(attr->lDAPDisplayName, "instanceType") == 0) { + if (ldb_msg_add(newmsg, el, LDB_FLAG_MOD_ADD) != LDB_SUCCESS) { + return ldb_error(ldb, + LDB_ERR_OPERATIONS_ERROR, + "Unable to add attribute"); + } + talloc_steal(newmsg->elements, el->name); + talloc_steal(newmsg->elements, el->values); + continue; + } + /* For links, when our functional level > windows 2000 + * we use the RMD_LOCAL_USN information to decide wether + * we return the attribute or not. + * For windows 2000 this information is in the replPropertyMetaData + * so it will be handled like any other replicated attribute + */ + + if (dsc->functional_level > DS_DOMAIN_FUNCTION_2000 && + attr->linkID != 0 ) { + int k; + /* + * Elements for incremental changes on linked attributes + */ + struct ldb_message_element *el_incr_add = NULL; + struct ldb_message_element *el_incr_del = NULL; + /* + * Attribute is a forwardlink so let's remove it + */ + + for (k = el->num_values -1; k >= 0; k--) { + char *dn_ln; + uint32_t flags = 0; + uint32_t tmp_usn = 0; + uint32_t tmp_usn2 = 0; + struct GUID invocation_id = GUID_zero(); + struct dsdb_dn *dn = dsdb_dn_parse(msg, ldb, &el->values[k], attr->syntax->ldap_oid); + if (dn == NULL) { + ldb_set_errstring(ldb, "Cannot parse DN"); + return ldb_module_done(dsc->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR); + } + status = dsdb_get_extended_dn_uint32(dn->dn, &tmp_usn, "RMD_LOCAL_USN"); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(dn); + return ldb_module_done(dsc->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR); + } + status = dsdb_get_extended_dn_guid(dn->dn, &invocation_id, "RMD_INVOCID"); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(dn); + return ldb_module_done(dsc->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR); + } + + status = dsdb_get_extended_dn_uint32(dn->dn, &flags, "RMD_FLAGS"); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(dn); + return ldb_module_done(dsc->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR); + } + + status = dsdb_get_extended_dn_uint32(dn->dn, &tmp_usn2, "RMD_ORIGINATING_USN"); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(dn); + return ldb_module_done(dsc->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR); + } + + ldb_dn_extended_filter(dn->dn, myaccept); + dn_ln = ldb_dn_get_extended_linearized(dn, dn->dn, 1); + if (dn_ln == NULL) + { + talloc_free(dn); + ldb_set_errstring(ldb, "Cannot linearize dn"); + return ldb_module_done(dsc->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR); + } + + talloc_free(el->values[k].data); + el->values[k].data = (uint8_t*)talloc_steal(el->values, dn_ln); + if (el->values[k].data == NULL) { + talloc_free(dn); + return ldb_module_done(dsc->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR); + } + el->values[k].length = strlen(dn_ln); + + + if (tmp_usn > dsc->fromreqUSN) { + if (!dsc->localonly) { + struct drsuapi_DsReplicaCursor *tab = dsc->cursors; + uint32_t l; + + for (l=0; l < dsc->cursor_size; l++) { + if (GUID_equal(&tab[l].source_dsa_invocation_id, &invocation_id) && + tab[l].highest_usn >= tmp_usn2) { + /* + * If we have in the uptodateness vector an entry + * with the same invocation id as the originating invocation + * and if the usn in the vector is greater or equal to + * the one in originating_usn, then it means that this entry + * has already been sent (from another DC) to the client + * no need to resend it one more time. + */ + goto skip_link; + } + } + /* If we are here it's because we have a usn > (max(usn of vectors))*/ + keep = true; + } else { + keep = true; + } + /* If we are here it's because the link is more recent than either any + * originating usn or local usn + */ + + if (dsc->linkIncrVal == true) { + struct ldb_message_element *tmpel; + if (flags & DSDB_RMD_FLAG_DELETED) { + tmpel = el_incr_del; + } else { + tmpel = el_incr_add; + } + + if (tmpel == NULL) { + tmpel = talloc_zero(newmsg, struct ldb_message_element); + if (tmpel == NULL) { + return ldb_oom(ldb); + } + tmpel->values = talloc_array(tmpel, struct ldb_val, 1); + if (tmpel->values == NULL) { + return ldb_oom(ldb); + } + if (flags & DSDB_RMD_FLAG_DELETED) { + tmpel->name = talloc_asprintf(tmpel, + "%s;range=0-0", + el->name); + } + else { + tmpel->name = talloc_asprintf(tmpel, + "%s;range=1-1", + el->name); + } + if (tmpel->name == NULL) { + return ldb_oom(ldb); + } + tmpel->num_values = 1; + } else { + tmpel->num_values += 1; + tmpel->values = talloc_realloc(tmpel, + tmpel->values, + struct ldb_val, + tmpel->num_values); + if (tmpel->values == NULL) { + return ldb_oom(ldb); + } + tmpel = tmpel; + } + tmpel->values[tmpel->num_values -1].data =talloc_steal(tmpel->values, el->values[k].data); + tmpel->values[tmpel->num_values -1].length = el->values[k].length; + + if (flags & DSDB_RMD_FLAG_DELETED) { + el_incr_del = tmpel; + } else { + el_incr_add = tmpel; + } + } + } + + if (dsc->linkIncrVal == false) { + if (flags & DSDB_RMD_FLAG_DELETED) { + if (k < (el->num_values - 1)) { + memmove(el->values + k, + el->values + (k + 1), + ((el->num_values - 1) - k)*sizeof(*el->values)); + } + el->num_values--; + } + } +skip_link: + talloc_free(dn); + + } + if (keep == true) { + if (dsc->linkIncrVal == false) { + if (ldb_msg_add(newmsg, el, LDB_FLAG_MOD_ADD) != LDB_SUCCESS) { + return ldb_error(ldb, + LDB_ERR_OPERATIONS_ERROR, + "Unable to add attribute"); + } + talloc_steal(newmsg->elements, el->name); + talloc_steal(newmsg->elements, el->values); + } else { + if (el_incr_del) { + if (ldb_msg_add(newmsg, el_incr_del, LDB_FLAG_MOD_ADD)) + return ldb_error(ldb, + LDB_ERR_OPERATIONS_ERROR, + "Unable to add attribute"); + } + if (el_incr_add) { + if (ldb_msg_add(newmsg, el_incr_add, LDB_FLAG_MOD_ADD)) + return ldb_error(ldb, + LDB_ERR_OPERATIONS_ERROR, + "Unable to add attribute"); + } + } + } + continue; + } + + if (listAttr) { + for (j=0; j<size; j++) { + /* + * We mark attribute that has already been seen well + * as seen. So that after attribute that are still in + * listAttr are attributes that has been modified after + * the requested USN but not present in the attributes + * returned by the ldb search. + * That is to say attributes that have been removed + */ + if (listAttr[j] && ldb_attr_cmp(listAttr[j], ldapattrname) == 0) { + listAttr[j] = NULL; + keep = true; + continue; + } + } + } else { + keep = true; + } + + if (keep == true) { + if (ldb_msg_add(newmsg, el, LDB_FLAG_MOD_ADD) != LDB_SUCCESS) { + return ldb_error(ldb, + LDB_ERR_OPERATIONS_ERROR, + "Unable to add attribute"); + } + talloc_steal(newmsg->elements, el->name); + talloc_steal(newmsg->elements, el->values); + continue; + } + } + + /* + * Here we run through the list of attributes returned + * in the propertyMetaData. + * Entries of this list have usn > requested_usn, + * entries that are also present in the message have been + * replaced by NULL, so at this moment the list contains + * only elements that have a usn > requested_usn and that + * haven't been seen. It's attributes that were removed. + * We add them to the message like empty elements. + */ + for (j=0; j<size; j++) { + if (listAttr[j] && ( + ldb_attr_in_list(req->op.search.attrs, "*") || + ldb_attr_in_list(req->op.search.attrs, listAttr[j])) && + (ldb_attr_cmp(listAttr[j], rdn) != 0) && + (ldb_attr_cmp(listAttr[j], "instanceType") != 0)) { + ldb_msg_add_empty(newmsg, listAttr[j], LDB_FLAG_MOD_DELETE, NULL); + } + } + talloc_free(listAttr); + + if ((newmsg->num_elements - ( dsc->nbDefaultAttrs - delta)) > 0) { + /* + * After cleaning attributes there is still some attributes that were not added just + * for the purpose of the control (objectGUID, instanceType, ...) + */ + + newmsg->dn = talloc_steal(newmsg, msg->dn); + if (val > dsc->highestUSN) { + dsc->highestUSN = val; + } + talloc_free(msg); + return ldb_module_send_entry(dsc->req, newmsg, controls); + } else { + talloc_free(msg); + return LDB_SUCCESS; + } +} + + +static int dirsync_create_vector(struct ldb_request *req, + struct ldb_reply *ares, + struct dirsync_context *dsc, + struct ldapControlDirSyncCookie *cookie, + struct ldb_context *ldb) +{ + struct ldb_result *resVector; + const char* attrVector[] = {"replUpToDateVector", NULL }; + uint64_t highest_usn; + struct ldb_dn *nc_root; + uint32_t count = 1; + int ret; + struct drsuapi_DsReplicaCursor *tab; + + nc_root = ldb_get_default_basedn(ldb); + ret = ldb_sequence_number(ldb, LDB_SEQ_HIGHEST_SEQ, &highest_usn); + if (ret != LDB_SUCCESS) { + return ldb_error(ldb, LDB_ERR_OPERATIONS_ERROR, "Unable to get highest USN from current NC"); + } + + /* If we have a full answer then the highest USN + * is not the highest USN from the result set but the + * highest of the naming context, unless the sequence is not updated yet. + */ + if (highest_usn > dsc->highestUSN) { + dsc->highestUSN = highest_usn; + } + + + ret = dsdb_module_search_dn(dsc->module, dsc, &resVector, + nc_root, + attrVector, + DSDB_FLAG_NEXT_MODULE, req); + + if (resVector->count != 0) { + DATA_BLOB blob; + uint32_t i; + struct ldb_message_element *el = ldb_msg_find_element(resVector->msgs[0], "replUpToDateVector"); + if (el) { + enum ndr_err_code ndr_err; + struct replUpToDateVectorBlob utd; + blob.data = el->values[0].data; + blob.length = el->values[0].length; + ndr_err = ndr_pull_struct_blob(&blob, dsc, &utd, + (ndr_pull_flags_fn_t)ndr_pull_replUpToDateVectorBlob); + + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return ldb_error(ldb, LDB_ERR_OPERATIONS_ERROR, + "Unable to pull replUpToDateVectorBlob structure"); + } + + + count += utd.ctr.ctr2.count; + tab = talloc_array(cookie, struct drsuapi_DsReplicaCursor, count); + if (tab == NULL) { + return ldb_oom(ldb); + } + for (i=1; i < count; i++) { + memset(&tab[i], 0, sizeof(struct drsuapi_DsReplicaCursor)); + tab[i].highest_usn = utd.ctr.ctr2.cursors[i-1].highest_usn; + tab[i].source_dsa_invocation_id = utd.ctr.ctr2.cursors[i-1].source_dsa_invocation_id; + } + } else { + tab = talloc_array(cookie, struct drsuapi_DsReplicaCursor, count); + if (tab == NULL) { + return ldb_oom(ldb); + } + } + } else { + /* + * No replUpToDateVector ? it happens quite often (1 DC, + * other DCs didn't update ... + */ + tab = talloc_array(cookie, struct drsuapi_DsReplicaCursor, count); + if (tab == NULL) { + return ldb_oom(ldb); + } + } + /* Our vector is always the first */ + tab[0].highest_usn = dsc->highestUSN; + tab[0].source_dsa_invocation_id = *(dsc->our_invocation_id); + + + /* We have to add the updateness vector that we have*/ + /* Version is always 1 in dirsync cookies */ + cookie->blob.extra.uptodateness_vector.version = 1; + cookie->blob.extra.uptodateness_vector.reserved = 0; + cookie->blob.extra.uptodateness_vector.ctr.ctr1.count = count; + cookie->blob.extra.uptodateness_vector.ctr.ctr1.reserved = 0; + cookie->blob.extra.uptodateness_vector.ctr.ctr1.cursors = tab; + + return LDB_SUCCESS; +} + +static int dirsync_search_callback(struct ldb_request *req, struct ldb_reply *ares) +{ + int ret; + struct dirsync_context *dsc; + struct ldb_result *res, *res2; + struct ldb_dirsync_control *control; + struct ldapControlDirSyncCookie *cookie; + struct ldb_context *ldb; + struct ldb_dn *dn; + struct ldb_val *val; + DATA_BLOB *blob; + NTTIME now; + const char *attrs[] = { "objectGUID", NULL }; + enum ndr_err_code ndr_err; + char *tmp; + uint32_t flags; + + dsc = talloc_get_type_abort(req->context, struct dirsync_context); + ldb = ldb_module_get_ctx(dsc->module); + if (!ares) { + return ldb_module_done(dsc->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + return ldb_module_done(dsc->req, ares->controls, + ares->response, ares->error); + } + + switch (ares->type) { + case LDB_REPLY_ENTRY: + return dirsync_filter_entry(req, ares->message, ares->controls, dsc, false); + + case LDB_REPLY_REFERRAL: + /* Skip the ldap(s):// so up to 8 chars, + * we don't care to be precise as the goal is to be in + * the name of DC, then we search the next '/' + * as it will be the last char before the DN of the referal + */ + if (strncmp(ares->referral, "ldap://", 7) == 0) { + tmp = ares->referral + 7; + } else if (strncmp(ares->referral, "ldaps://", 8) == 0) { + tmp = ares->referral + 8; + } else { + return ldb_operr(ldb); + } + + tmp = strchr(tmp, '/'); + tmp++; + + dn = ldb_dn_new(dsc, ldb, tmp); + if (dn == NULL) { + return ldb_oom(ldb); + } + + flags = DSDB_FLAG_NEXT_MODULE | + DSDB_RMD_FLAG_DELETED | + DSDB_SEARCH_SHOW_EXTENDED_DN; + + if (dsc->assystem) { + flags = flags | DSDB_FLAG_AS_SYSTEM; + } + + ret = dsdb_module_search_tree(dsc->module, dsc, &res, + dn, LDB_SCOPE_BASE, + req->op.search.tree, + req->op.search.attrs, + flags, req); + + if (ret != LDB_SUCCESS) { + talloc_free(dn); + return ret; + } + + if (res->count > 1) { + char *ldbmsg = talloc_asprintf(dn, "LDB returned more than result for dn: %s", tmp); + if (ldbmsg) { + ldb_set_errstring(ldb, ldbmsg); + } + talloc_free(dn); + return ldb_module_done(dsc->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR); + } else if (res->count == 0) { + /* if nothing is returned then it means that we don't + * have access to it. + */ + return LDB_SUCCESS; + } + + talloc_free(dn); + /* + * Fetch the objectGUID of the root of current NC + */ + ret = dsdb_module_search_dn(dsc->module, dsc, &res2, + req->op.search.base, + attrs, + DSDB_FLAG_NEXT_MODULE, req); + + if (ret != LDB_SUCCESS) { + return ret; + } + if (res2->msgs[0]->num_elements != 1) { + ldb_set_errstring(ldb, + "More than 1 attribute returned while looking for objectGUID"); + return ldb_module_done(dsc->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR); + } + + val = res2->msgs[0]->elements[0].values; + ret = ldb_msg_add_value(res->msgs[0], "parentGUID", val, NULL); + /* + * It *very* important to steal otherwise as val is in a subcontext + * related to res2, when the value will be one more time stolen + * it's elements[x].values that will be stolen, so it's important to + * recreate the context hierrachy as if it was done from a ldb_request + */ + talloc_steal(res->msgs[0]->elements[0].values, val); + if (ret != LDB_SUCCESS) { + return ret; + } + return dirsync_filter_entry(req, res->msgs[0], res->controls, dsc, true); + + case LDB_REPLY_DONE: + /* + * Let's add our own control + */ + + control = talloc_zero(ares->controls, struct ldb_dirsync_control); + if (control == NULL) { + return ldb_oom(ldb); + } + + /* + * When outputing flags is used to say more results. + * For the moment we didn't honnor the size info */ + + control->flags = 0; + + /* + * max_attribute is unused cf. 3.1.1.3.4.1.3 LDAP_SERVER_DIRSYNC_OID in MS-ADTS + */ + + control->max_attributes = 0; + cookie = talloc_zero(control, struct ldapControlDirSyncCookie); + if (cookie == NULL) { + return ldb_oom(ldb); + } + + if (!dsc->partial) { + ret = dirsync_create_vector(req, ares, dsc, cookie, ldb); + if (ret != LDB_SUCCESS) { + return ldb_module_done(dsc->req, NULL, NULL, ret); + } + } + + unix_to_nt_time(&now, time(NULL)); + cookie->blob.time = now; + cookie->blob.highwatermark.highest_usn = dsc->highestUSN; + cookie->blob.highwatermark.tmp_highest_usn = dsc->highestUSN; + cookie->blob.guid1 = *(dsc->our_invocation_id); + + blob = talloc_zero(control, DATA_BLOB); + if (blob == NULL) { + return ldb_oom(ldb); + } + + ndr_err = ndr_push_struct_blob(blob, blob, cookie, + (ndr_push_flags_fn_t)ndr_push_ldapControlDirSyncCookie); + + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + ldb_set_errstring(ldb, "Can't marshall ldapControlDirSyncCookie struct"); + return ldb_module_done(dsc->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR); + } + control->cookie = (char *)blob->data; + control->cookie_len = blob->length; + ldb_reply_add_control(ares, LDB_CONTROL_DIRSYNC_OID, true, control); + + return ldb_module_done(dsc->req, ares->controls, + ares->response, LDB_SUCCESS); + + } + return LDB_SUCCESS; +} + +static int dirsync_ldb_search(struct ldb_module *module, struct ldb_request *req) +{ + struct ldb_control *control; + struct ldb_result *acl_res; + struct ldb_dirsync_control *dirsync_ctl; + struct ldb_request *down_req; + struct dirsync_context *dsc; + struct ldb_context *ldb; + struct ldb_parse_tree *new_tree = req->op.search.tree; + uint32_t flags = 0; + enum ndr_err_code ndr_err; + DATA_BLOB blob; + const char **attrs; + int ret; + + + if (ldb_dn_is_special(req->op.search.base)) { + return ldb_next_request(module, req); + } + + /* + * check if there's an extended dn control + */ + control = ldb_request_get_control(req, LDB_CONTROL_DIRSYNC_OID); + if (control == NULL) { + /* not found go on */ + return ldb_next_request(module, req); + } + + ldb = ldb_module_get_ctx(module); + /* + * This control must always be critical otherwise we return PROTOCOL error + */ + if (!control->critical) { + return ldb_operr(ldb); + } + + dsc = talloc_zero(req, struct dirsync_context); + if (dsc == NULL) { + return ldb_oom(ldb); + } + dsc->module = module; + dsc->req = req; + dsc->nbDefaultAttrs = 0; + + + dirsync_ctl = talloc_get_type(control->data, struct ldb_dirsync_control); + if (dirsync_ctl == NULL) { + return ldb_error(ldb, LDB_ERR_PROTOCOL_ERROR, "No data in dirsync control"); + } + + ret = dsdb_find_nc_root(ldb, dsc, req->op.search.base, &dsc->nc_root); + if (ret != LDB_SUCCESS) { + return ret; + } + + if (ldb_dn_compare(dsc->nc_root, req->op.search.base) != 0) { + if (dirsync_ctl->flags & LDAP_DIRSYNC_OBJECT_SECURITY) { + return ldb_error(ldb, LDB_ERR_UNWILLING_TO_PERFORM, + "DN is not one of the naming context"); + } + else { + return ldb_error(ldb, LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS, + "dN is not one of the naming context"); + } + } + + if (!(dirsync_ctl->flags & LDAP_DIRSYNC_OBJECT_SECURITY)) { + struct dom_sid *sid; + struct security_descriptor *sd = NULL; + const char *acl_attrs[] = { "nTSecurityDescriptor", "objectSid", NULL }; + /* + * If we don't have the flag and if we have the "replicate directory change" granted + * then we upgrade ourself to system to not be blocked by the acl + */ + /* FIXME we won't check the replicate directory change filtered attribute set + * it should be done so that if attr is not empty then we check that the user + * has also this right + */ + + /* + * First change to system to get the SD of the root of current NC + * if we don't the acl_read will forbid us the right to read it ... + */ + ret = dsdb_module_search_dn(module, dsc, &acl_res, + req->op.search.base, + acl_attrs, + DSDB_FLAG_NEXT_MODULE|DSDB_FLAG_AS_SYSTEM, req); + + if (ret != LDB_SUCCESS) { + return ret; + } + + sid = samdb_result_dom_sid(dsc, acl_res->msgs[0], "objectSid"); + /* sid can be null ... */ + ret = dsdb_get_sd_from_ldb_message(ldb_module_get_ctx(module), acl_res, acl_res->msgs[0], &sd); + + if (ret != LDB_SUCCESS) { + return ret; + } + ret = acl_check_extended_right(dsc, sd, acl_user_token(module), GUID_DRS_GET_CHANGES, SEC_ADS_CONTROL_ACCESS, sid); + + if (ret == LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS) { + return ret; + } + dsc->assystem = true; + ret = ldb_request_add_control(req, LDB_CONTROL_AS_SYSTEM_OID, false, NULL); + + if (ret != LDB_SUCCESS) { + return ret; + } + talloc_free(acl_res); + } else { + flags |= DSDB_ACL_CHECKS_DIRSYNC_FLAG; + + if (ret != LDB_SUCCESS) { + return ret; + } + + } + + dsc->functional_level = dsdb_functional_level(ldb); + + if (req->op.search.attrs) { + attrs = ldb_attr_list_copy(dsc, req->op.search.attrs); + if (attrs == NULL) { + return ldb_oom(ldb); + } + /* + * Check if we have only "dn" as attribute, if so then + * treat as if "*" was requested + */ + if (attrs && attrs[0]) { + if (ldb_attr_cmp(attrs[0], "dn") == 0 && !attrs[1]) { + attrs = talloc_array(dsc, const char*, 2); + if (attrs == NULL) { + return ldb_oom(ldb); + } + attrs[0] = "*"; + attrs[1] = NULL; + } + } + /* + * When returning all the attributes return also the SD as + * Windws do so. + */ + if (ldb_attr_in_list(attrs, "*")) { + struct ldb_sd_flags_control *sdctr = talloc_zero(dsc, struct ldb_sd_flags_control); + sdctr->secinfo_flags = 0; + ret = ldb_request_add_control(req, LDB_CONTROL_SD_FLAGS_OID, false, sdctr); + if (ret != LDB_SUCCESS) { + return ret; + } + attrs = ldb_attr_list_copy_add(dsc, attrs, "parentGUID"); + if (attrs == NULL) { + return ldb_oom(ldb); + } + attrs = ldb_attr_list_copy_add(dsc, attrs, "replPropertyMetaData"); + if (attrs == NULL) { + return ldb_oom(ldb); + } + /* + * When no attributes are asked we in anycase expect at least 3 attributes: + * * instanceType + * * objectGUID + * * parentGUID + */ + + dsc->nbDefaultAttrs = 3; + } else { + /* + * We will need this two attributes in the callback + */ + attrs = ldb_attr_list_copy_add(dsc, attrs, "usnChanged"); + if (attrs == NULL) { + return ldb_operr(ldb); + } + attrs = ldb_attr_list_copy_add(dsc, attrs, "replPropertyMetaData"); + if (attrs == NULL) { + return ldb_operr(ldb); + } + + if (!ldb_attr_in_list(attrs, "instanceType")) { + attrs = ldb_attr_list_copy_add(dsc, attrs, "instanceType"); + if (attrs == NULL) { + return ldb_operr(ldb); + } + dsc->nbDefaultAttrs++; + } + + if (!ldb_attr_in_list(attrs, "objectGUID")) { + attrs = ldb_attr_list_copy_add(dsc, attrs, "objectGUID"); + if (attrs == NULL) { + return ldb_operr(ldb); + } + } + /* + * Always increment the number of asked attributes as we don't care if objectGUID was asked + * or not for counting the number of "real" attributes returned. + */ + dsc->nbDefaultAttrs++; + + if (!ldb_attr_in_list(attrs, "parentGUID")) { + attrs = ldb_attr_list_copy_add(dsc, attrs, "parentGUID"); + if (attrs == NULL) { + return ldb_operr(ldb); + } + } + dsc->nbDefaultAttrs++; + + } + } else { + struct ldb_sd_flags_control *sdctr = talloc_zero(dsc, struct ldb_sd_flags_control); + sdctr->secinfo_flags = 0; + ret = ldb_request_add_control(req, LDB_CONTROL_SD_FLAGS_OID, false, sdctr); + attrs = talloc_array(dsc, const char*, 4); + if (attrs == NULL) { + return ldb_operr(ldb); + } + attrs[0] = "*"; + attrs[1] = "parentGUID"; + attrs[2] = "replPropertyMetaData"; + attrs[3] = NULL; + if (ret != LDB_SUCCESS) { + return ret; + } + /* + * When no attributes are asked we in anycase expect at least 3 attributes: + * * instanceType + * * objectGUID + * * parentGUID + */ + + dsc->nbDefaultAttrs = 3; + } + + if (!ldb_request_get_control(req, LDB_CONTROL_EXTENDED_DN_OID)) { + ret = ldb_request_add_control(req, LDB_CONTROL_EXTENDED_DN_OID, false, NULL); + if (ret != LDB_SUCCESS) { + return ret; + } + dsc->noextended = true; + } + + if (ldb_request_get_control(req, LDB_CONTROL_REVEAL_INTERNALS) == NULL) { + ret = ldb_request_add_control(req, LDB_CONTROL_REVEAL_INTERNALS, false, NULL); + if (ret != LDB_SUCCESS) { + return ret; + } + } + + if (ldb_request_get_control(req, LDB_CONTROL_SHOW_RECYCLED_OID) == NULL) { + ret = ldb_request_add_control(req, LDB_CONTROL_SHOW_RECYCLED_OID, false, NULL); + if (ret != LDB_SUCCESS) { + return ret; + } + } + + if (ldb_request_get_control(req, LDB_CONTROL_SHOW_DELETED_OID) == NULL) { + ret = ldb_request_add_control(req, LDB_CONTROL_SHOW_DELETED_OID, false, NULL); + if (ret != LDB_SUCCESS) { + return ret; + } + } + + if (dirsync_ctl->flags & LDAP_DIRSYNC_INCREMENTAL_VALUES) { + dsc->linkIncrVal = true; + } else { + dsc->linkIncrVal = false; + } + + dsc->our_invocation_id = samdb_ntds_invocation_id(ldb); + if (dsc->our_invocation_id == NULL) { + return ldb_operr(ldb); + } + + if (dirsync_ctl->cookie_len > 0) { + struct ldapControlDirSyncCookie cookie; + + blob.data = (uint8_t *)dirsync_ctl->cookie; + blob.length = dirsync_ctl->cookie_len; + ndr_err = ndr_pull_struct_blob(&blob, dsc, &cookie, + (ndr_pull_flags_fn_t)ndr_pull_ldapControlDirSyncCookie); + + /* If we can't unmarshall the cookie into the correct structure we return + * unsupported critical extension + */ + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return ldb_error(ldb, LDB_ERR_UNSUPPORTED_CRITICAL_EXTENSION, + "Unable to unmarshall cookie as a ldapControlDirSyncCookie structure"); + } + + /* + * Let's search for the max usn withing the cookie + */ + if (GUID_equal(&(cookie.blob.guid1), dsc->our_invocation_id)) { + /* + * Ok, it's our invocation ID so we can treat the demand + * Let's take the highest usn from (tmp)highest_usn + */ + dsc->fromreqUSN = cookie.blob.highwatermark.tmp_highest_usn; + dsc->localonly = true; + + if (cookie.blob.highwatermark.highest_usn > cookie.blob.highwatermark.tmp_highest_usn) { + dsc->fromreqUSN = cookie.blob.highwatermark.highest_usn; + } + } else { + dsc->localonly = false; + } + if (cookie.blob.extra_length > 0 && + cookie.blob.extra.uptodateness_vector.ctr.ctr1.count > 0) { + struct drsuapi_DsReplicaCursor cursor; + uint32_t p; + for (p=0; p < cookie.blob.extra.uptodateness_vector.ctr.ctr1.count; p++) { + cursor = cookie.blob.extra.uptodateness_vector.ctr.ctr1.cursors[p]; + if (GUID_equal( &(cursor.source_dsa_invocation_id), dsc->our_invocation_id)) { + if (cursor.highest_usn > dsc->fromreqUSN) { + dsc->fromreqUSN = cursor.highest_usn; + } + } + } + dsc->cursors = talloc_steal(dsc, + cookie.blob.extra.uptodateness_vector.ctr.ctr1.cursors); + if (dsc->cursors == NULL) { + return ldb_oom(ldb); + } + dsc->cursor_size = p; + } + } + + DEBUG(4, ("Dirsync: searching with min usn > %llu\n", + (long long unsigned int)dsc->fromreqUSN)); + if (dsc->fromreqUSN > 0) { + /* FIXME it would be better to use PRId64 */ + char *expression = talloc_asprintf(dsc, "(&%s(uSNChanged>=%llu))", + ldb_filter_from_tree(dsc, + req->op.search.tree), + (long long unsigned int)(dsc->fromreqUSN + 1)); + + if (expression == NULL) { + return ldb_oom(ldb); + } + new_tree = ldb_parse_tree(req, expression); + if (new_tree == NULL) { + return ldb_error(ldb, LDB_ERR_OPERATIONS_ERROR, + "Problem while parsing tree"); + } + + } + /* + * Remove our control from the list of controls + */ + if (!ldb_save_controls(control, req, NULL)) { + return ldb_operr(ldb); + } + dsc->schema = dsdb_get_schema(ldb, dsc); + /* + * At the begining we make the hypothesis that we will return a complete + * result set + */ + + dsc->partial = false; + + /* + * 3.1.1.3.4.1.3 of MS-ADTS.pdf specify that if the scope is not subtree + * we treat the search as if subtree was specified + */ + + ret = ldb_build_search_req_ex(&down_req, ldb, dsc, + req->op.search.base, + LDB_SCOPE_SUBTREE, + new_tree, + attrs, + req->controls, + dsc, dirsync_search_callback, + req); + ldb_req_set_custom_flags(down_req, flags); + LDB_REQ_SET_LOCATION(down_req); + if (ret != LDB_SUCCESS) { + return ret; + } + /* perform the search */ + return ldb_next_request(module, down_req); +} + +static int dirsync_ldb_init(struct ldb_module *module) +{ + int ret; + + ret = ldb_mod_register_control(module, LDB_CONTROL_DIRSYNC_OID); + if (ret != LDB_SUCCESS) { + ldb_debug(ldb_module_get_ctx(module), LDB_DEBUG_ERROR, + "dirsync: Unable to register control with rootdse!\n"); + return ldb_operr(ldb_module_get_ctx(module)); + } + + return ldb_next_init(module); +} + +static const struct ldb_module_ops ldb_dirsync_ldb_module_ops = { + .name = "dirsync", + .search = dirsync_ldb_search, + .init_context = dirsync_ldb_init, +}; + +/* + initialise the module + */ +_PUBLIC_ int ldb_dirsync_module_init(const char *version) +{ + int ret; + LDB_MODULE_CHECK_VERSION(version); + ret = ldb_register_module(&ldb_dirsync_ldb_module_ops); + return ret; +} diff --git a/source4/dsdb/samdb/ldb_modules/extended_dn_in.c b/source4/dsdb/samdb/ldb_modules/extended_dn_in.c index 3e2004d6f3..9a70d9a3db 100644 --- a/source4/dsdb/samdb/ldb_modules/extended_dn_in.c +++ b/source4/dsdb/samdb/ldb_modules/extended_dn_in.c @@ -103,6 +103,18 @@ static int extended_base_callback(struct ldb_request *req, struct ldb_reply *are switch (ares->type) { case LDB_REPLY_ENTRY: + if (ac->basedn) { + /* we have more than one match! This can + happen as S-1-5-17 appears twice in a + normal provision. We need to return + NO_SUCH_OBJECT */ + const char *str = talloc_asprintf(req, "Duplicate base-DN matches found for '%s'", + ldb_dn_get_extended_linearized(req, ac->req->op.search.base, 1)); + ldb_set_errstring(ldb_module_get_ctx(ac->module), str); + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_NO_SUCH_OBJECT); + } + if (!ac->wellknown_object) { ac->basedn = talloc_steal(ac, ares->message->dn); break; @@ -303,30 +315,33 @@ static int extended_dn_in_fix(struct ldb_module *module, struct ldb_request *req guid_val = ldb_dn_get_extended_component(dn, "GUID"); wkguid_val = ldb_dn_get_extended_component(dn, "WKGUID"); - if (sid_val) { + /* + prioritise the GUID - we have had instances of + duplicate SIDs in the database in the + ForeignSecurityPrinciples due to provision errors + */ + if (guid_val) { all_partitions = true; base_dn = ldb_get_default_basedn(ldb_module_get_ctx(module)); - base_dn_filter = talloc_asprintf(req, "(objectSid=%s)", - ldb_binary_encode(req, *sid_val)); + base_dn_filter = talloc_asprintf(req, "(objectGUID=%s)", + ldb_binary_encode(req, *guid_val)); if (!base_dn_filter) { return ldb_oom(ldb_module_get_ctx(module)); } base_dn_scope = LDB_SCOPE_SUBTREE; base_dn_attrs = no_attr; - } else if (guid_val) { - + } else if (sid_val) { all_partitions = true; base_dn = ldb_get_default_basedn(ldb_module_get_ctx(module)); - base_dn_filter = talloc_asprintf(req, "(objectGUID=%s)", - ldb_binary_encode(req, *guid_val)); + base_dn_filter = talloc_asprintf(req, "(objectSid=%s)", + ldb_binary_encode(req, *sid_val)); if (!base_dn_filter) { return ldb_oom(ldb_module_get_ctx(module)); } base_dn_scope = LDB_SCOPE_SUBTREE; base_dn_attrs = no_attr; - } else if (wkguid_val) { char *wkguid_dup; char *tail_str; diff --git a/source4/dsdb/samdb/ldb_modules/objectclass_attrs.c b/source4/dsdb/samdb/ldb_modules/objectclass_attrs.c index 9df121002f..5639a7a3e3 100644 --- a/source4/dsdb/samdb/ldb_modules/objectclass_attrs.c +++ b/source4/dsdb/samdb/ldb_modules/objectclass_attrs.c @@ -140,7 +140,8 @@ static int attr_handler(struct oc_context *ac) if (!(msg->elements[i].flags & LDB_FLAG_INTERNAL_DISABLE_VALIDATION)) { werr = attr->syntax->validate_ldb(&syntax_ctx, attr, &msg->elements[i]); - if (!W_ERROR_IS_OK(werr)) { + if (!W_ERROR_IS_OK(werr) && + !ldb_request_get_control(ac->req, LDB_CONTROL_RELAX_OID)) { ldb_asprintf_errstring(ldb, "objectclass_attrs: attribute '%s' on entry '%s' contains at least one invalid value!", msg->elements[i].name, ldb_dn_get_linearized(msg->dn)); diff --git a/source4/dsdb/samdb/ldb_modules/proxy.c b/source4/dsdb/samdb/ldb_modules/proxy.c index 6fba24fc2d..5f6e56f9d4 100644 --- a/source4/dsdb/samdb/ldb_modules/proxy.c +++ b/source4/dsdb/samdb/ldb_modules/proxy.c @@ -138,7 +138,7 @@ static int load_proxy_info(struct ldb_module *module) ldb_set_opaque(proxy->upstream, "credentials", creds); ret = ldb_connect(proxy->upstream, url, 0, NULL); - if (ret != 0) { + if (ret != LDB_SUCCESS) { ldb_debug(ldb, LDB_DEBUG_FATAL, "proxy failed to connect to %s\n", url); goto failed; } diff --git a/source4/dsdb/samdb/ldb_modules/repl_meta_data.c b/source4/dsdb/samdb/ldb_modules/repl_meta_data.c index 646abeb771..9d2e5e2ac3 100644 --- a/source4/dsdb/samdb/ldb_modules/repl_meta_data.c +++ b/source4/dsdb/samdb/ldb_modules/repl_meta_data.c @@ -254,7 +254,16 @@ static int replmd_process_backlink(struct ldb_module *module, struct la_backlink msg->elements[0].flags |= LDB_FLAG_INTERNAL_DISABLE_SINGLE_VALUE_CHECK; ret = dsdb_module_modify(module, msg, DSDB_FLAG_NEXT_MODULE, parent); - if (ret != LDB_SUCCESS) { + if (ret == LDB_ERR_NO_SUCH_ATTRIBUTE && !bl->active) { + /* we allow LDB_ERR_NO_SUCH_ATTRIBUTE as success to + cope with possible corruption where the backlink has + already been removed */ + DEBUG(0,("WARNING: backlink from %s already removed from %s - %s\n", + ldb_dn_get_linearized(target_dn), + ldb_dn_get_linearized(source_dn), + ldb_errstring(ldb))); + ret = LDB_SUCCESS; + } else if (ret != LDB_SUCCESS) { ldb_asprintf_errstring(ldb, "Failed to %s backlink from %s to %s - %s", bl->active?"add":"remove", ldb_dn_get_linearized(source_dn), @@ -1634,7 +1643,8 @@ static int replmd_update_la_val(TALLOC_CTX *mem_ctx, struct ldb_val *v, struct d if (old_addtime == NULL) { old_addtime = &tval; } - if (dsdb_dn != old_dsdb_dn) { + if (dsdb_dn != old_dsdb_dn || + ldb_dn_get_extended_component(dn, "RMD_ADDTIME") == NULL) { ret = ldb_dn_set_extended_component(dn, "RMD_ADDTIME", old_addtime); if (ret != LDB_SUCCESS) return ret; } @@ -2488,7 +2498,7 @@ static int replmd_rename_callback(struct ldb_request *req, struct ldb_reply *are if (ret == LDB_ERR_REFERRAL) { struct ldb_dn *olddn = ac->req->op.rename.olddn; struct loadparm_context *lp_ctx; - const char *referral; + char *referral; lp_ctx = talloc_get_type(ldb_get_opaque(ldb, "loadparm"), struct loadparm_context); diff --git a/source4/dsdb/samdb/ldb_modules/ridalloc.c b/source4/dsdb/samdb/ldb_modules/ridalloc.c index 5051919672..28fade11b1 100644 --- a/source4/dsdb/samdb/ldb_modules/ridalloc.c +++ b/source4/dsdb/samdb/ldb_modules/ridalloc.c @@ -66,14 +66,14 @@ */ static void ridalloc_poke_rid_manager(struct ldb_module *module) { - struct messaging_context *msg; + struct imessaging_context *msg; struct server_id *server; struct ldb_context *ldb = ldb_module_get_ctx(module); struct loadparm_context *lp_ctx = (struct loadparm_context *)ldb_get_opaque(ldb, "loadparm"); TALLOC_CTX *tmp_ctx = talloc_new(module); - msg = messaging_client_init(tmp_ctx, lpcfg_messaging_path(tmp_ctx, lp_ctx), + msg = imessaging_client_init(tmp_ctx, lpcfg_imessaging_path(tmp_ctx, lp_ctx), ldb_get_event_context(ldb)); if (!msg) { DEBUG(3,(__location__ ": Failed to create messaging context\n")); @@ -88,7 +88,7 @@ static void ridalloc_poke_rid_manager(struct ldb_module *module) return; } - messaging_send(msg, server[0], MSG_DREPL_ALLOCATE_RID, NULL); + imessaging_send(msg, server[0], MSG_DREPL_ALLOCATE_RID, NULL); /* we don't care if the message got through */ talloc_free(tmp_ctx); diff --git a/source4/dsdb/samdb/ldb_modules/rootdse.c b/source4/dsdb/samdb/ldb_modules/rootdse.c index 0fd65f4795..c584a11b2c 100644 --- a/source4/dsdb/samdb/ldb_modules/rootdse.c +++ b/source4/dsdb/samdb/ldb_modules/rootdse.c @@ -222,11 +222,10 @@ static int rootdse_add_dynamic(struct ldb_module *module, struct ldb_message *ms struct loadparm_context); char *ldap_service_name, *hostname; - hostname = talloc_strdup(msg, lpcfg_netbios_name(lp_ctx)); + hostname = strlower_talloc(msg, lpcfg_netbios_name(lp_ctx)); if (hostname == NULL) { goto failed; } - strlower_m(hostname); ldap_service_name = talloc_asprintf(msg, "%s:%s$@%s", samdb_forest_name(ldb, msg), @@ -613,7 +612,11 @@ static int rootdse_filter_controls(struct ldb_module *module, struct ldb_request continue; } - if (is_registered) { + /* If the control is DIRSYNC control then we keep the critical + * flag as the dirsync module will need to act upon it + */ + if (is_registered && strcmp(req->controls[i]->oid, + LDB_CONTROL_DIRSYNC_OID)!= 0) { req->controls[i]->critical = 0; } } @@ -1195,7 +1198,7 @@ static int rootdse_become_master(struct ldb_module *module, struct ldb_request *req, enum drepl_role_master role) { - struct messaging_context *msg; + struct imessaging_context *msg; struct ldb_context *ldb = ldb_module_get_ctx(module); TALLOC_CTX *tmp_ctx = talloc_new(req); struct loadparm_context *lp_ctx = ldb_get_opaque(ldb, "loadparm"); @@ -1223,10 +1226,10 @@ static int rootdse_become_master(struct ldb_module *module, "RODC cannot become a role master."); } - msg = messaging_client_init(tmp_ctx, lpcfg_messaging_path(tmp_ctx, lp_ctx), + msg = imessaging_client_init(tmp_ctx, lpcfg_imessaging_path(tmp_ctx, lp_ctx), ldb_get_event_context(ldb)); if (!msg) { - ldb_asprintf_errstring(ldb, "Failed to generate client messaging context in %s", lpcfg_messaging_path(tmp_ctx, lp_ctx)); + ldb_asprintf_errstring(ldb, "Failed to generate client messaging context in %s", lpcfg_imessaging_path(tmp_ctx, lp_ctx)); return LDB_ERR_OPERATIONS_ERROR; } irpc_handle = irpc_binding_handle_by_name(tmp_ctx, msg, diff --git a/source4/dsdb/samdb/ldb_modules/samba_dsdb.c b/source4/dsdb/samdb/ldb_modules/samba_dsdb.c index 35b323b72f..e4de1524be 100644 --- a/source4/dsdb/samdb/ldb_modules/samba_dsdb.c +++ b/source4/dsdb/samdb/ldb_modules/samba_dsdb.c @@ -163,6 +163,7 @@ static int samba_dsdb_init(struct ldb_module *module) static const char *modules_list[] = {"resolve_oids", "rootdse", "lazy_commit", + "dirsync", "paged_results", "ranged_results", "anr", diff --git a/source4/dsdb/samdb/ldb_modules/samldb.c b/source4/dsdb/samdb/ldb_modules/samldb.c index 21341850d9..6533d1006b 100644 --- a/source4/dsdb/samdb/ldb_modules/samldb.c +++ b/source4/dsdb/samdb/ldb_modules/samldb.c @@ -3,7 +3,7 @@ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005 Copyright (C) Simo Sorce 2004-2008 - Copyright (C) Matthias Dieter Wallnöfer 2009-2010 + Copyright (C) Matthias Dieter Wallnöfer 2009-2011 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 @@ -790,6 +790,8 @@ static int samldb_schema_info_update(struct samldb_ctx *ac) return LDB_SUCCESS; } +static int samldb_prim_group_tester(struct samldb_ctx *ac, uint32_t rid); + /* * "Objectclass" trigger (MS-SAMR 3.1.1.8.1) * @@ -801,10 +803,9 @@ static int samldb_schema_info_update(struct samldb_ctx *ac) static int samldb_objectclass_trigger(struct samldb_ctx *ac) { struct ldb_context *ldb = ldb_module_get_ctx(ac->module); - struct loadparm_context *lp_ctx = talloc_get_type(ldb_get_opaque(ldb, - "loadparm"), struct loadparm_context); + void *skip_allocate_sids = ldb_get_opaque(ldb, + "skip_allocate_sids"); struct ldb_message_element *el, *el2; - enum sid_generator sid_generator; struct dom_sid *sid; int ret; @@ -830,12 +831,9 @@ static int samldb_objectclass_trigger(struct samldb_ctx *ac) } /* but generate a new SID when we do have an add operations */ - if ((sid == NULL) && (ac->req->operation == LDB_ADD)) { - sid_generator = lpcfg_sid_generator(lp_ctx); - if (sid_generator == SID_GENERATOR_INTERNAL) { - ret = samldb_add_step(ac, samldb_allocate_sid); - if (ret != LDB_SUCCESS) return ret; - } + if ((sid == NULL) && (ac->req->operation == LDB_ADD) && !skip_allocate_sids) { + ret = samldb_add_step(ac, samldb_allocate_sid); + if (ret != LDB_SUCCESS) return ret; } if (strcmp(ac->type, "user") == 0) { @@ -897,6 +895,16 @@ static int samldb_objectclass_trigger(struct samldb_ctx *ac) return LDB_ERR_OTHER; } + /* Workstation and (read-only) DC objects do need objectclass "computer" */ + if ((samdb_find_attribute(ldb, ac->msg, + "objectclass", "computer") == NULL) && + (user_account_control & + (UF_SERVER_TRUST_ACCOUNT | UF_WORKSTATION_TRUST_ACCOUNT))) { + ldb_set_errstring(ldb, + "samldb: Requested account type does need objectclass 'computer'!"); + return LDB_ERR_OBJECT_CLASS_VIOLATION; + } + account_type = ds_uf2atype(user_account_control); if (account_type == 0) { ldb_set_errstring(ldb, "samldb: Unrecognized account type!"); @@ -911,11 +919,20 @@ static int samldb_objectclass_trigger(struct samldb_ctx *ac) el2 = ldb_msg_find_element(ac->msg, "sAMAccountType"); el2->flags = LDB_FLAG_MOD_REPLACE; + /* "isCriticalSystemObject" might be set */ if (user_account_control & (UF_SERVER_TRUST_ACCOUNT | UF_PARTIAL_SECRETS_ACCOUNT)) { - ret = samdb_msg_set_string(ldb, ac->msg, ac->msg, - "isCriticalSystemObject", - "TRUE"); + ret = ldb_msg_add_string(ac->msg, "isCriticalSystemObject", + "TRUE"); + if (ret != LDB_SUCCESS) { + return ret; + } + el2 = ldb_msg_find_element(ac->msg, + "isCriticalSystemObject"); + el2->flags = LDB_FLAG_MOD_REPLACE; + } else if (user_account_control & UF_WORKSTATION_TRUST_ACCOUNT) { + ret = ldb_msg_add_string(ac->msg, "isCriticalSystemObject", + "FALSE"); if (ret != LDB_SUCCESS) { return ret; } @@ -927,6 +944,18 @@ static int samldb_objectclass_trigger(struct samldb_ctx *ac) /* Step 1.4: "userAccountControl" -> "primaryGroupID" mapping */ if (!ldb_msg_find_element(ac->msg, "primaryGroupID")) { uint32_t rid = ds_uf2prim_group_rid(user_account_control); + + /* + * Older AD deployments don't know about the + * RODC group + */ + if (rid == DOMAIN_RID_READONLY_DCS) { + ret = samldb_prim_group_tester(ac, rid); + if (ret != LDB_SUCCESS) { + return ret; + } + } + ret = samdb_msg_add_uint(ldb, ac->msg, ac->msg, "primaryGroupID", rid); if (ret != LDB_SUCCESS) { @@ -1009,26 +1038,14 @@ static int samldb_objectclass_trigger(struct samldb_ctx *ac) * ac->msg contains the "add"/"modify" message */ -static int samldb_prim_group_set(struct samldb_ctx *ac) +static int samldb_prim_group_tester(struct samldb_ctx *ac, uint32_t rid) { struct ldb_context *ldb = ldb_module_get_ctx(ac->module); - uint32_t rid; struct dom_sid *sid; struct ldb_result *res; int ret; const char *noattrs[] = { NULL }; - rid = ldb_msg_find_attr_as_uint(ac->msg, "primaryGroupID", (uint32_t) -1); - if (rid == (uint32_t) -1) { - /* we aren't affected of any primary group set */ - return LDB_SUCCESS; - - } else if (!ldb_request_get_control(ac->req, LDB_CONTROL_RELAX_OID)) { - ldb_set_errstring(ldb, - "The primary group isn't settable on add operations!"); - return LDB_ERR_UNWILLING_TO_PERFORM; - } - sid = dom_sid_add_rid(ac, samdb_domain_sid(ldb), rid); if (sid == NULL) { return ldb_operr(ldb); @@ -1054,6 +1071,25 @@ static int samldb_prim_group_set(struct samldb_ctx *ac) return LDB_SUCCESS; } +static int samldb_prim_group_set(struct samldb_ctx *ac) +{ + struct ldb_context *ldb = ldb_module_get_ctx(ac->module); + uint32_t rid; + + rid = ldb_msg_find_attr_as_uint(ac->msg, "primaryGroupID", (uint32_t) -1); + if (rid == (uint32_t) -1) { + /* we aren't affected of any primary group set */ + return LDB_SUCCESS; + + } else if (!ldb_request_get_control(ac->req, LDB_CONTROL_RELAX_OID)) { + ldb_set_errstring(ldb, + "The primary group isn't settable on add operations!"); + return LDB_ERR_UNWILLING_TO_PERFORM; + } + + return samldb_prim_group_tester(ac, rid); +} + static int samldb_prim_group_change(struct samldb_ctx *ac) { struct ldb_context *ldb = ldb_module_get_ctx(ac->module); @@ -1076,14 +1112,11 @@ static int samldb_prim_group_change(struct samldb_ctx *ac) /* Fetch information from the existing object */ - ret = dsdb_module_search(ac->module, ac, &res, ac->msg->dn, LDB_SCOPE_BASE, attrs, - DSDB_FLAG_NEXT_MODULE, ac->req, NULL); + ret = dsdb_module_search_dn(ac->module, ac, &res, ac->msg->dn, attrs, + DSDB_FLAG_NEXT_MODULE, ac->req); if (ret != LDB_SUCCESS) { return ret; } - if (res->count != 1) { - return ldb_operr(ldb); - } /* Finds out the DN of the old primary group */ @@ -1219,13 +1252,22 @@ static int samldb_prim_group_trigger(struct samldb_ctx *ac) return ret; } + +/** + * This function is called on LDB modify operations. It performs some additions/ + * replaces on the current LDB message when "userAccountControl" changes. + */ static int samldb_user_account_control_change(struct samldb_ctx *ac) { struct ldb_context *ldb = ldb_module_get_ctx(ac->module); - uint32_t user_account_control, account_type; + uint32_t user_account_control, old_user_account_control, account_type; struct ldb_message_element *el; struct ldb_message *tmp_msg; int ret; + struct ldb_result *res; + const char *attrs[] = { "userAccountControl", "objectClass", NULL }; + unsigned int i; + bool is_computer = false; el = dsdb_get_single_valued_attr(ac->msg, "userAccountControl", ac->req->operation); @@ -1253,6 +1295,49 @@ static int samldb_user_account_control_change(struct samldb_ctx *ac) return LDB_ERR_OTHER; } + /* Fetch the old "userAccountControl" and "objectClass" */ + ret = dsdb_module_search_dn(ac->module, ac, &res, ac->msg->dn, attrs, + DSDB_FLAG_NEXT_MODULE, ac->req); + if (ret != LDB_SUCCESS) { + return ret; + } + old_user_account_control = ldb_msg_find_attr_as_uint(res->msgs[0], "userAccountControl", 0); + if (old_user_account_control == 0) { + return ldb_operr(ldb); + } + el = ldb_msg_find_element(res->msgs[0], "objectClass"); + if (el == NULL) { + return ldb_operr(ldb); + } + + /* When we do not have objectclass "computer" we cannot switch to a (read-only) DC */ + for (i = 0; i < el->num_values; i++) { + if (ldb_attr_cmp((char *)el->values[i].data, "computer") == 0) { + is_computer = true; + break; + } + } + if (!is_computer && + (user_account_control & (UF_SERVER_TRUST_ACCOUNT | UF_PARTIAL_SECRETS_ACCOUNT))) { + ldb_set_errstring(ldb, + "samldb: Requested account type does need objectclass 'computer'!"); + return LDB_ERR_UNWILLING_TO_PERFORM; + } + + /* + * The functions "ds_uf2atype" and "ds_uf2prim_group_rid" are used as + * detectors for account type changes. + * So if the account type does change then we need to adjust the + * "sAMAccountType", the "isCriticalSystemObject" and the + * "primaryGroupID" attribute. + */ + if ((ds_uf2atype(user_account_control) + == ds_uf2atype(old_user_account_control)) && + (ds_uf2prim_group_rid(user_account_control) + == ds_uf2prim_group_rid(old_user_account_control))) { + return LDB_SUCCESS; + } + account_type = ds_uf2atype(user_account_control); if (account_type == 0) { ldb_set_errstring(ldb, "samldb: Unrecognized account type!"); @@ -1266,6 +1351,7 @@ static int samldb_user_account_control_change(struct samldb_ctx *ac) el = ldb_msg_find_element(ac->msg, "sAMAccountType"); el->flags = LDB_FLAG_MOD_REPLACE; + /* "isCriticalSystemObject" might be set/changed */ if (user_account_control & (UF_SERVER_TRUST_ACCOUNT | UF_PARTIAL_SECRETS_ACCOUNT)) { ret = ldb_msg_add_string(ac->msg, "isCriticalSystemObject", @@ -1276,10 +1362,28 @@ static int samldb_user_account_control_change(struct samldb_ctx *ac) el = ldb_msg_find_element(ac->msg, "isCriticalSystemObject"); el->flags = LDB_FLAG_MOD_REPLACE; + } else if (user_account_control & UF_WORKSTATION_TRUST_ACCOUNT) { + ret = ldb_msg_add_string(ac->msg, "isCriticalSystemObject", + "FALSE"); + if (ret != LDB_SUCCESS) { + return ret; + } + el = ldb_msg_find_element(ac->msg, + "isCriticalSystemObject"); + el->flags = LDB_FLAG_MOD_REPLACE; } if (!ldb_msg_find_element(ac->msg, "primaryGroupID")) { uint32_t rid = ds_uf2prim_group_rid(user_account_control); + + /* Older AD deployments don't know about the RODC group */ + if (rid == DOMAIN_RID_READONLY_DCS) { + ret = samldb_prim_group_tester(ac, rid); + if (ret != LDB_SUCCESS) { + return ret; + } + } + ret = samdb_msg_add_uint(ldb, ac->msg, ac->msg, "primaryGroupID", rid); if (ret != LDB_SUCCESS) { @@ -1977,7 +2081,7 @@ static int samldb_modify(struct ldb_module *module, struct ldb_request *req) el = ldb_msg_find_element(ac->msg, "primaryGroupID"); if (el != NULL) { - ret = samldb_prim_group_change(ac); + ret = samldb_prim_group_trigger(ac); if (ret != LDB_SUCCESS) { return ret; } diff --git a/source4/dsdb/samdb/ldb_modules/util.c b/source4/dsdb/samdb/ldb_modules/util.c index 49939e2ff4..7dbf233703 100644 --- a/source4/dsdb/samdb/ldb_modules/util.c +++ b/source4/dsdb/samdb/ldb_modules/util.c @@ -109,39 +109,23 @@ int dsdb_module_search_dn(struct ldb_module *module, return ret; } -/* - search for attrs in the modules below - */ -int dsdb_module_search(struct ldb_module *module, +int dsdb_module_search_tree(struct ldb_module *module, TALLOC_CTX *mem_ctx, struct ldb_result **_res, - struct ldb_dn *basedn, enum ldb_scope scope, + struct ldb_dn *basedn, + enum ldb_scope scope, + struct ldb_parse_tree *tree, const char * const *attrs, - int dsdb_flags, - struct ldb_request *parent, - const char *format, ...) _PRINTF_ATTRIBUTE(9, 10) + int dsdb_flags, + struct ldb_request *parent) { int ret; struct ldb_request *req; TALLOC_CTX *tmp_ctx; struct ldb_result *res; - va_list ap; - char *expression; tmp_ctx = talloc_new(mem_ctx); - if (format) { - va_start(ap, format); - expression = talloc_vasprintf(tmp_ctx, format, ap); - va_end(ap); - - if (!expression) { - talloc_free(tmp_ctx); - return ldb_oom(ldb_module_get_ctx(module)); - } - } else { - expression = NULL; - } res = talloc_zero(tmp_ctx, struct ldb_result); if (!res) { @@ -149,10 +133,10 @@ int dsdb_module_search(struct ldb_module *module, return ldb_oom(ldb_module_get_ctx(module)); } - ret = ldb_build_search_req(&req, ldb_module_get_ctx(module), tmp_ctx, + ret = ldb_build_search_req_ex(&req, ldb_module_get_ctx(module), tmp_ctx, basedn, scope, - expression, + tree, attrs, NULL, res, @@ -196,6 +180,61 @@ int dsdb_module_search(struct ldb_module *module, } /* + search for attrs in the modules below + */ +int dsdb_module_search(struct ldb_module *module, + TALLOC_CTX *mem_ctx, + struct ldb_result **_res, + struct ldb_dn *basedn, enum ldb_scope scope, + const char * const *attrs, + int dsdb_flags, + struct ldb_request *parent, + const char *format, ...) _PRINTF_ATTRIBUTE(9, 10) +{ + int ret; + TALLOC_CTX *tmp_ctx; + va_list ap; + char *expression; + struct ldb_parse_tree *tree; + + tmp_ctx = talloc_new(mem_ctx); + + if (format) { + va_start(ap, format); + expression = talloc_vasprintf(tmp_ctx, format, ap); + va_end(ap); + + if (!expression) { + talloc_free(tmp_ctx); + return ldb_oom(ldb_module_get_ctx(module)); + } + } else { + expression = NULL; + } + + tree = ldb_parse_tree(tmp_ctx, expression); + if (tree == NULL) { + talloc_free(tmp_ctx); + ldb_set_errstring(ldb_module_get_ctx(module), + "Unable to parse search expression"); + return LDB_ERR_OPERATIONS_ERROR; + } + + ret = dsdb_module_search_tree(module, + mem_ctx, + _res, + basedn, + scope, + tree, + attrs, + dsdb_flags, + parent); + + talloc_free(tmp_ctx); + return ret; +} + +/* find a DN given a GUID. This searches across all partitions */ int dsdb_module_dn_by_guid(struct ldb_module *module, TALLOC_CTX *mem_ctx, diff --git a/source4/dsdb/samdb/ldb_modules/wscript_build b/source4/dsdb/samdb/ldb_modules/wscript_build index 8ad893c551..eb9c664c71 100644 --- a/source4/dsdb/samdb/ldb_modules/wscript_build +++ b/source4/dsdb/samdb/ldb_modules/wscript_build @@ -390,3 +390,12 @@ bld.SAMBA_MODULE('ldb_simple_dn', internal_module=False, deps='talloc DSDB_MODULE_HELPERS' ) + +bld.SAMBA_MODULE('ldb_dirsync', + source='dirsync.c', + subsystem='ldb', + init_function='ldb_dirsync_module_init', + module_init_name='ldb_init_module', + internal_module=False, + deps='talloc events security samdb DSDB_MODULE_HELPERS DSDB_MODULE_HELPER_SCHEMA' + ) diff --git a/source4/dsdb/samdb/samdb.c b/source4/dsdb/samdb/samdb.c index 29b454467c..d761107b15 100644 --- a/source4/dsdb/samdb/samdb.c +++ b/source4/dsdb/samdb/samdb.c @@ -96,7 +96,7 @@ struct ldb_context *samdb_connect(TALLOC_CTX *mem_ctx, struct tevent_context *ev_ctx, struct loadparm_context *lp_ctx, struct auth_session_info *session_info, - int flags) + unsigned int flags) { struct ldb_context *ldb; struct dsdb_schema *schema; @@ -104,7 +104,7 @@ struct ldb_context *samdb_connect(TALLOC_CTX *mem_ctx, struct cli_credentials *credentials; int ret; - url = lpcfg_sam_url(lp_ctx); + url = "sam.ldb"; credentials = samdb_credentials(lp_ctx); ldb = ldb_wrap_find(url, ev_ctx, lp_ctx, session_info, credentials, flags); diff --git a/source4/dsdb/samdb/samdb.h b/source4/dsdb/samdb/samdb.h index 8efb5e0af2..96f44c356e 100644 --- a/source4/dsdb/samdb/samdb.h +++ b/source4/dsdb/samdb/samdb.h @@ -192,4 +192,5 @@ struct dsdb_fsmo_extended_op { struct GUID destination_dsa_guid; }; +#define DSDB_ACL_CHECKS_DIRSYNC_FLAG 0x1 #endif /* __SAMDB_H__ */ diff --git a/source4/dsdb/schema/schema_convert_to_ol.c b/source4/dsdb/schema/schema_convert_to_ol.c index 77a9b1f291..0e42f20cf0 100644 --- a/source4/dsdb/schema/schema_convert_to_ol.c +++ b/source4/dsdb/schema/schema_convert_to_ol.c @@ -134,6 +134,7 @@ static char *print_schema_recursive(char *append_to_string, struct dsdb_schema * may, NULL); if (schema_entry == NULL) { + talloc_free(mem_ctx); DEBUG(0, ("failed to generate schema description for %s\n", name)); return NULL; } @@ -145,6 +146,10 @@ static char *print_schema_recursive(char *append_to_string, struct dsdb_schema * case TARGET_FEDORA_DS: out = talloc_asprintf_append(out, "objectClasses: %s\n", schema_entry); break; + default: + talloc_free(mem_ctx); + DEBUG(0,(__location__ " Wrong type of target %u!", (unsigned)target)); + return NULL; } talloc_free(mem_ctx); } while (0); @@ -199,6 +204,7 @@ char *dsdb_convert_schema_to_openldap(struct ldb_context *ldb, char *target_str, } else if (strcasecmp(target_str, "fedora-ds") == 0) { target = TARGET_FEDORA_DS; } else { + talloc_free(mem_ctx); DEBUG(0, ("Invalid target type for schema conversion %s\n", target_str)); return NULL; } @@ -263,6 +269,7 @@ char *dsdb_convert_schema_to_openldap(struct ldb_context *ldb, char *target_str, schema = dsdb_get_schema(ldb, mem_ctx); if (!schema) { + talloc_free(mem_ctx); DEBUG(0, ("No schema on ldb to convert!\n")); return NULL; } @@ -274,6 +281,10 @@ char *dsdb_convert_schema_to_openldap(struct ldb_context *ldb, char *target_str, case TARGET_FEDORA_DS: out = talloc_strdup(mem_ctx, "dn: cn=schema\n"); break; + default: + talloc_free(mem_ctx); + DEBUG(0,(__location__ " Wrong type of target %u!", (unsigned)target)); + return NULL; } for (attribute=schema->attributes; attribute; attribute = attribute->next) { @@ -339,6 +350,7 @@ char *dsdb_convert_schema_to_openldap(struct ldb_context *ldb, char *target_str, false, false); if (schema_entry == NULL) { + talloc_free(mem_ctx); DEBUG(0, ("failed to generate attribute description for %s\n", name)); return NULL; } @@ -350,11 +362,18 @@ char *dsdb_convert_schema_to_openldap(struct ldb_context *ldb, char *target_str, case TARGET_FEDORA_DS: out = talloc_asprintf_append(out, "attributeTypes: %s\n", schema_entry); break; + default: + talloc_free(mem_ctx); + DEBUG(0,(__location__ " Wrong type of target %u!", (unsigned)target)); + return NULL; } } out = print_schema_recursive(out, schema, "top", target, attrs_skip, attr_map, oid_map); + talloc_steal(ldb, out); + talloc_free(mem_ctx); + return out; } diff --git a/source4/dsdb/schema/schema_syntax.c b/source4/dsdb/schema/schema_syntax.c index ea582db68b..a93cdfaaa9 100644 --- a/source4/dsdb/schema/schema_syntax.c +++ b/source4/dsdb/schema/schema_syntax.c @@ -1069,8 +1069,14 @@ static WERROR _dsdb_syntax_OID_oid_drsuapi_to_ldb(const struct dsdb_syntax_ctx * struct ldb_message_element *out) { unsigned int i; + const struct dsdb_schema_prefixmap *prefixmap; - SMB_ASSERT(ctx->pfm_remote); + if (ctx->pfm_remote != NULL) { + prefixmap = ctx->pfm_remote; + } else { + prefixmap = ctx->schema->prefixmap; + } + SMB_ASSERT(prefixmap); out->flags = 0; out->name = talloc_strdup(mem_ctx, attr->lDAPDisplayName); @@ -1095,7 +1101,7 @@ static WERROR _dsdb_syntax_OID_oid_drsuapi_to_ldb(const struct dsdb_syntax_ctx * attid = IVAL(in->value_ctr.values[i].blob->data, 0); - status = dsdb_schema_pfm_oid_from_attid(ctx->pfm_remote, attid, + status = dsdb_schema_pfm_oid_from_attid(prefixmap, attid, out->values, &oid); if (!W_ERROR_IS_OK(status)) { DEBUG(0,(__location__ ": Error: Unknown ATTID 0x%08X\n", @@ -1977,20 +1983,21 @@ static WERROR dsdb_syntax_DN_BINARY_drsuapi_to_ldb(const struct dsdb_syntax_ctx W_ERROR_HAVE_NO_MEMORY(dn); } - status = GUID_to_ndr_blob(&id3.guid, tmp_ctx, &guid_blob); - if (!NT_STATUS_IS_OK(status)) { - talloc_free(tmp_ctx); - return ntstatus_to_werror(status); - } + if (!GUID_all_zero(&id3.guid)) { + status = GUID_to_ndr_blob(&id3.guid, tmp_ctx, &guid_blob); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(tmp_ctx); + return ntstatus_to_werror(status); + } - ret = ldb_dn_set_extended_component(dn, "GUID", &guid_blob); - if (ret != LDB_SUCCESS) { - talloc_free(tmp_ctx); - return WERR_FOOBAR; + ret = ldb_dn_set_extended_component(dn, "GUID", &guid_blob); + if (ret != LDB_SUCCESS) { + talloc_free(tmp_ctx); + return WERR_FOOBAR; + } + talloc_free(guid_blob.data); } - talloc_free(guid_blob.data); - if (id3.__ndr_size_sid) { DATA_BLOB sid_blob; ndr_err = ndr_push_struct_blob(&sid_blob, tmp_ctx, &id3.sid, diff --git a/source4/dsdb/tests/python/dirsync.py b/source4/dsdb/tests/python/dirsync.py new file mode 100755 index 0000000000..96c6950841 --- /dev/null +++ b/source4/dsdb/tests/python/dirsync.py @@ -0,0 +1,713 @@ +#!/usr/bin/env python +# +# Unit tests for dirsync control +# Copyright (C) Matthieu Patou <mat@matws.net> 2011 +# +# +# 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/>. + + +import optparse +import sys +sys.path.insert(0, "bin/python") +import samba +samba.ensure_external_module("testtools", "testtools") +samba.ensure_external_module("subunit", "subunit/python") + +import samba.getopt as options +import base64 + +from ldb import LdbError, SCOPE_BASE +from ldb import Message, MessageElement, Dn +from ldb import FLAG_MOD_ADD, FLAG_MOD_DELETE +from samba.dcerpc import security, misc, drsblobs +from samba.ndr import ndr_unpack, ndr_pack + +from samba.auth import system_session +from samba import gensec, sd_utils +from samba.samdb import SamDB +from samba.credentials import Credentials +import samba.tests +from samba.tests import delete_force +from subunit.run import SubunitTestRunner +import unittest + +parser = optparse.OptionParser("dirsync.py [options] <host>") +sambaopts = options.SambaOptions(parser) +parser.add_option_group(sambaopts) +parser.add_option_group(options.VersionOptions(parser)) + +# use command line creds if available +credopts = options.CredentialsOptions(parser) +parser.add_option_group(credopts) +opts, args = parser.parse_args() + +if len(args) < 1: + parser.print_usage() + sys.exit(1) + +host = args[0] +if not "://" in host: + ldaphost = "ldap://%s" % host + ldapshost = "ldaps://%s" % host +else: + ldaphost = host + start = host.rindex("://") + host = host.lstrip(start+3) + +lp = sambaopts.get_loadparm() +creds = credopts.get_credentials(lp) + +# +# Tests start here +# + +class DirsyncBaseTests(samba.tests.TestCase): + + def setUp(self): + super(DirsyncBaseTests, self).setUp() + self.ldb_admin = ldb + self.base_dn = ldb.domain_dn() + self.domain_sid = security.dom_sid(ldb.get_domain_sid()) + self.user_pass = "samba123@AAA" + self.configuration_dn = self.ldb_admin.get_config_basedn().get_linearized() + self.sd_utils = sd_utils.SDUtils(ldb) + #used for anonymous login + print "baseDN: %s" % self.base_dn + + def get_user_dn(self, name): + return "CN=%s,CN=Users,%s" % (name, self.base_dn) + + def get_ldb_connection(self, target_username, target_password): + creds_tmp = Credentials() + creds_tmp.set_username(target_username) + creds_tmp.set_password(target_password) + creds_tmp.set_domain(creds.get_domain()) + creds_tmp.set_realm(creds.get_realm()) + creds_tmp.set_workstation(creds.get_workstation()) + creds_tmp.set_gensec_features(creds_tmp.get_gensec_features() + | gensec.FEATURE_SEAL) + ldb_target = SamDB(url=ldaphost, credentials=creds_tmp, lp=lp) + return ldb_target + + +#tests on ldap add operations +class SimpleDirsyncTests(DirsyncBaseTests): + + def setUp(self): + super(SimpleDirsyncTests, self).setUp() + # Regular user + self.dirsync_user = "test_dirsync_user" + self.simple_user = "test_simple_user" + self.admin_user = "test_admin_user" + self.ouname = None + + self.ldb_admin.newuser(self.dirsync_user, self.user_pass) + self.ldb_admin.newuser(self.simple_user, self.user_pass) + self.ldb_admin.newuser(self.admin_user, self.user_pass) + self.desc_sddl = self.sd_utils.get_sd_as_sddl(self.base_dn) + + user_sid = self.sd_utils.get_object_sid(self.get_user_dn(self.dirsync_user)) + mod = "(A;;CR;1131f6aa-9c07-11d1-f79f-00c04fc2dcd2;;%s)" % str(user_sid) + self.sd_utils.dacl_add_ace(self.base_dn, mod) + + # add admins to the Domain Admins group + self.ldb_admin.add_remove_group_members("Domain Admins", self.admin_user, + add_members_operation=True) + + def tearDown(self): + super(SimpleDirsyncTests, self).tearDown() + delete_force(self.ldb_admin, self.get_user_dn(self.dirsync_user)) + delete_force(self.ldb_admin, self.get_user_dn(self.simple_user)) + delete_force(self.ldb_admin, self.get_user_dn(self.admin_user)) + if self.ouname: + delete_force(self.ldb_admin, self.ouname) + self.sd_utils.modify_sd_on_dn(self.base_dn, self.desc_sddl) + try: + self.ldb_admin.deletegroup("testgroup") + except: + pass + + #def test_dirsync_errors(self): + + + def test_dirsync_supported(self): + """Test the basic of the dirsync is supported""" + self.ldb_dirsync = self.get_ldb_connection(self.dirsync_user, self.user_pass) + self.ldb_simple = self.get_ldb_connection(self.simple_user, self.user_pass) + res = self.ldb_admin.search(self.base_dn, expression="samaccountname=*", controls=["dirsync:1:0:1"]) + res = self.ldb_dirsync.search(self.base_dn, expression="samaccountname=*", controls=["dirsync:1:0:1"]) + try: + self.ldb_simple.search(self.base_dn, + expression="samaccountname=*", + controls=["dirsync:1:0:1"]) + except LdbError,l: + self.assertTrue(str(l).find("LDAP_INSUFFICIENT_ACCESS_RIGHTS") != -1) + + def test_parentGUID_referrals(self): + res2 = self.ldb_admin.search(self.base_dn, scope=SCOPE_BASE, attrs=["objectGUID"]) + + res = self.ldb_admin.search(self.base_dn, + expression="name=Configuration", + controls=["dirsync:1:0:1"]) + self.assertEqual(res2[0].get("objectGUID"), res[0].get("parentGUID")) + + def test_ok_not_rootdc(self): + """Test if it's ok to do dirsync on another NC that is not the root DC""" + try: + res = self.ldb_admin.search("CN=Configuration, %s" % self.base_dn, + expression="samaccountname=*", + controls=["dirsync:1:0:1"]) + except: + self.assertTrue(False) + + def test_dirsync_errors(self): + """Test if dirsync returns the correct LDAP errors in case of pb""" + self.ldb_simple = self.get_ldb_connection(self.simple_user, self.user_pass) + self.ldb_dirsync = self.get_ldb_connection(self.dirsync_user, self.user_pass) + try: + self.ldb_simple.search(self.base_dn, + expression="samaccountname=*", + controls=["dirsync:1:0:1"]) + except LdbError,l: + print l + self.assertTrue(str(l).find("LDAP_INSUFFICIENT_ACCESS_RIGHTS") != -1) + + try: + self.ldb_simple.search("CN=Users,%s" % self.base_dn, + expression="samaccountname=*", + controls=["dirsync:1:0:1"]) + except LdbError,l: + print l + self.assertTrue(str(l).find("LDAP_INSUFFICIENT_ACCESS_RIGHTS") != -1) + + try: + self.ldb_simple.search("CN=Users,%s" % self.base_dn, + expression="samaccountname=*", + controls=["dirsync:1:1:1"]) + except LdbError,l: + print l + self.assertTrue(str(l).find("LDAP_UNWILLING_TO_PERFORM") != -1) + + try: + self.ldb_dirsync.search("CN=Users,%s" % self.base_dn, + expression="samaccountname=*", + controls=["dirsync:1:0:1"]) + except LdbError,l: + print l + self.assertTrue(str(l).find("LDAP_INSUFFICIENT_ACCESS_RIGHTS") != -1) + + try: + self.ldb_admin.search("CN=Users,%s" % self.base_dn, + expression="samaccountname=*", + controls=["dirsync:1:0:1"]) + except LdbError,l: + print l + self.assertTrue(str(l).find("LDAP_INSUFFICIENT_ACCESS_RIGHTS") != -1) + + try: + self.ldb_admin.search("CN=Users,%s" % self.base_dn, + expression="samaccountname=*", + controls=["dirsync:1:1:1"]) + except LdbError,l: + print l + self.assertTrue(str(l).find("LDAP_UNWILLING_TO_PERFORM") != -1) + + + + + def test_dirsync_attributes(self): + """Check behavior with some attributes """ + res = self.ldb_admin.search(self.base_dn, + expression="samaccountname=*", + controls=["dirsync:1:0:1"]) + # Check that nTSecurityDescriptor is returned as it's the case when doing dirsync + self.assertTrue(res.msgs[0].get("ntsecuritydescriptor") != None) + # Check that non replicated attributes are not returned + self.assertTrue(res.msgs[0].get("badPwdCount") == None) + # Check that non forward link are not returned + self.assertTrue(res.msgs[0].get("memberof") == None) + + # Asking for instanceType will return also objectGUID + res = self.ldb_admin.search(self.base_dn, + expression="samaccountname=Administrator", + attrs=["instanceType"], + controls=["dirsync:1:0:1"]) + self.assertTrue(res.msgs[0].get("objectGUID") != None) + self.assertTrue(res.msgs[0].get("instanceType") != None) + + # We don't return an entry if asked for objectGUID + res = self.ldb_admin.search(self.base_dn, + expression="dn=%s" % self.base_dn, + attrs=["objectGUID"], + controls=["dirsync:1:0:1"]) + self.assertEquals(len(res.msgs), 0) + + # a request on the root of a NC didn't return parentGUID + res = self.ldb_admin.search(self.base_dn, + expression="dn=%s" % self.base_dn, + attrs=["name"], + controls=["dirsync:1:0:1"]) + self.assertTrue(res.msgs[0].get("objectGUID") != None) + self.assertTrue(res.msgs[0].get("name") != None) + self.assertTrue(res.msgs[0].get("parentGUID") == None) + self.assertTrue(res.msgs[0].get("instanceType") != None) + + # Asking for name will return also objectGUID and parentGUID + # and instanceType and of course name + res = self.ldb_admin.search(self.base_dn, + expression="samaccountname=Administrator", + attrs=["name"], + controls=["dirsync:1:0:1"]) + self.assertTrue(res.msgs[0].get("objectGUID") != None) + self.assertTrue(res.msgs[0].get("name") != None) + self.assertTrue(res.msgs[0].get("parentGUID") != None) + self.assertTrue(res.msgs[0].get("instanceType") != None) + + # Asking for dn will not return not only DN but more like if attrs=* + # parentGUID should be returned + res = self.ldb_admin.search(self.base_dn, + expression="samaccountname=Administrator", + attrs=["dn"], + controls=["dirsync:1:0:1"]) + count = len(res.msgs[0]) + res2 = self.ldb_admin.search(self.base_dn, + expression="samaccountname=Administrator", + controls=["dirsync:1:0:1"]) + count2 = len(res2.msgs[0]) + self.assertEqual(count, count2) + + # Asking for cn will return nothing on objects that have CN as RDN + res = self.ldb_admin.search(self.base_dn, + expression="samaccountname=Administrator", + attrs=["cn"], + controls=["dirsync:1:0:1"]) + self.assertEqual(len(res.msgs), 0) + # Asking for parentGUID will return nothing too + res = self.ldb_admin.search(self.base_dn, + expression="samaccountname=Administrator", + attrs=["parentGUID"], + controls=["dirsync:1:0:1"]) + self.assertEqual(len(res.msgs), 0) + ouname="OU=testou,%s" % self.base_dn + self.ouname = ouname + self.ldb_admin.create_ou(ouname) + delta = Message() + delta.dn = Dn(self.ldb_admin, str(ouname)) + delta["cn"] = MessageElement("test ou", + FLAG_MOD_ADD, + "cn" ) + self.ldb_admin.modify(delta) + res = self.ldb_admin.search(self.base_dn, + expression="name=testou", + attrs=["cn"], + controls=["dirsync:1:0:1"]) + + self.assertEqual(len(res.msgs), 1) + self.assertEqual(len(res.msgs[0]), 3) + delete_force(self.ldb_admin, ouname) + + def test_dirsync_with_controls(self): + """Check that dirsync return correct informations when dealing with the NC""" + res = self.ldb_admin.search(self.base_dn, + expression="(dn=%s)" % str(self.base_dn), + attrs=["name"], + controls=["dirsync:1:0:10000", "extended_dn:1", "show_deleted:1"]) + + def test_dirsync_basenc(self): + """Check that dirsync return correct informations when dealing with the NC""" + res = self.ldb_admin.search(self.base_dn, + expression="(dn=%s)" % str(self.base_dn), + attrs=["name"], + controls=["dirsync:1:0:10000"]) + self.assertEqual(len(res.msgs), 1) + self.assertEqual(len(res.msgs[0]), 3) + + res = self.ldb_admin.search(self.base_dn, + expression="(dn=%s)" % str(self.base_dn), + attrs=["ntSecurityDescriptor"], + controls=["dirsync:1:0:10000"]) + self.assertEqual(len(res.msgs), 1) + self.assertEqual(len(res.msgs[0]), 3) + + def test_dirsync_othernc(self): + """Check that dirsync return information for entries that are normaly referrals (ie. other NCs)""" + res = self.ldb_admin.search(self.base_dn, + expression="(objectclass=configuration)", + attrs=["name"], + controls=["dirsync:1:0:10000"]) + self.assertEqual(len(res.msgs), 1) + self.assertEqual(len(res.msgs[0]), 4) + + res = self.ldb_admin.search(self.base_dn, + expression="(objectclass=configuration)", + attrs=["ntSecurityDescriptor"], + controls=["dirsync:1:0:10000"]) + self.assertEqual(len(res.msgs), 1) + self.assertEqual(len(res.msgs[0]), 3) + + res = self.ldb_admin.search(self.base_dn, + expression="(objectclass=domaindns)", + attrs=["ntSecurityDescriptor"], + controls=["dirsync:1:0:10000"]) + nb = len(res.msgs) + + # only sub nc returns a result when asked for objectGUID + res = self.ldb_admin.search(self.base_dn, + expression="(objectclass=domaindns)", + attrs=["objectGUID"], + controls=["dirsync:1:0:0"]) + self.assertEqual(len(res.msgs), nb - 1) + if nb > 1: + self.assertTrue(res.msgs[0].get("objectGUID") != None) + else: + res = self.ldb_admin.search(self.base_dn, + expression="(objectclass=configuration)", + attrs=["objectGUID"], + controls=["dirsync:1:0:0"]) + + + def test_dirsync_send_delta(self): + """Check that dirsync return correct delta when sending the last cookie""" + res = self.ldb_admin.search(self.base_dn, + expression="(&(samaccountname=test*)(!(isDeleted=*)))", + controls=["dirsync:1:0:10000"]) + ctl = str(res.controls[0]).split(":") + ctl[1] = "1" + ctl[2] = "0" + ctl[3] = "10000" + control = str(":".join(ctl)) + res = self.ldb_admin.search(self.base_dn, + expression="(&(samaccountname=test*)(!(isDeleted=*)))", + controls=[control]) + self.assertEqual(len(res), 0) + + res = self.ldb_admin.search(self.base_dn, + expression="(&(objectClass=organizationalUnit)(!(isDeleted=*)))", + controls=["dirsync:1:0:100000"]) + + ctl = str(res.controls[0]).split(":") + ctl[1] = "1" + ctl[2] = "0" + ctl[3] = "10000" + control2 = str(":".join(ctl)) + + # Let's create an OU + ouname="OU=testou2,%s" % self.base_dn + self.ouname = ouname + self.ldb_admin.create_ou(ouname) + res = self.ldb_admin.search(self.base_dn, + expression="(&(objectClass=organizationalUnit)(!(isDeleted=*)))", + controls=[control2]) + self.assertEqual(len(res), 1) + ctl = str(res.controls[0]).split(":") + ctl[1] = "1" + ctl[2] = "0" + ctl[3] = "10000" + control3 = str(":".join(ctl)) + + delta = Message() + delta.dn = Dn(self.ldb_admin, str(ouname)) + + delta["cn"] = MessageElement("test ou", + FLAG_MOD_ADD, + "cn" ) + self.ldb_admin.modify(delta) + res = self.ldb_admin.search(self.base_dn, + expression="(&(objectClass=organizationalUnit)(!(isDeleted=*)))", + controls=[control3]) + + self.assertEqual(len(res.msgs), 1) + # 3 attributes: instanceType, cn and objectGUID + self.assertEqual(len(res.msgs[0]), 3) + + delta = Message() + delta.dn = Dn(self.ldb_admin, str(ouname)) + delta["cn"] = MessageElement([], + FLAG_MOD_DELETE, + "cn" ) + self.ldb_admin.modify(delta) + res = self.ldb_admin.search(self.base_dn, + expression="(&(objectClass=organizationalUnit)(!(isDeleted=*)))", + controls=[control3]) + + self.assertEqual(len(res.msgs), 1) + # So we won't have much attribute returned but instanceType and GUID + # are. + # 3 attributes: instanceType and objectGUID and cn but empty + self.assertEqual(len(res.msgs[0]), 3) + ouname = "OU=newouname,%s" % self.base_dn + self.ldb_admin.rename(str(res[0].dn), str(Dn(self.ldb_admin, ouname))) + self.ouname = ouname + ctl = str(res.controls[0]).split(":") + ctl[1] = "1" + ctl[2] = "0" + ctl[3] = "10000" + control4 = str(":".join(ctl)) + res = self.ldb_admin.search(self.base_dn, + expression="(&(objectClass=organizationalUnit)(!(isDeleted=*)))", + controls=[control3]) + + self.assertTrue(res[0].get("parentGUID") != None) + self.assertTrue(res[0].get("name") != None) + delete_force(self.ldb_admin, ouname) + + def test_dirsync_linkedattributes(self): + """Check that dirsync returnd deleted objects too""" + # Let's search for members + self.ldb_simple = self.get_ldb_connection(self.simple_user, self.user_pass) + res = self.ldb_simple.search(self.base_dn, + expression="(name=Administrators)", + controls=["dirsync:1:1:1"]) + + self.assertTrue(len(res[0].get("member")) > 0) + size = len(res[0].get("member")) + + ctl = str(res.controls[0]).split(":") + ctl[1] = "1" + ctl[2] = "1" + ctl[3] = "10000" + control1 = str(":".join(ctl)) + self.ldb_admin.add_remove_group_members("Administrators", self.simple_user, + add_members_operation=True) + + res = self.ldb_simple.search(self.base_dn, + expression="(name=Administrators)", + controls=[control1]) + + self.assertEqual(len(res[0].get("member")), size + 1) + ctl = str(res.controls[0]).split(":") + ctl[1] = "1" + ctl[2] = "1" + ctl[3] = "10000" + control1 = str(":".join(ctl)) + + # remove the user from the group + self.ldb_admin.add_remove_group_members("Administrators", self.simple_user, + add_members_operation=False) + + res = self.ldb_simple.search(self.base_dn, + expression="(name=Administrators)", + controls=[control1]) + + self.assertEqual(len(res[0].get("member")), size ) + + self.ldb_admin.newgroup("testgroup") + self.ldb_admin.add_remove_group_members("testgroup", self.simple_user, + add_members_operation=True) + + res = self.ldb_admin.search(self.base_dn, + expression="(name=testgroup)", + controls=["dirsync:1:0:1"]) + + self.assertEqual(len(res[0].get("member")), 1) + self.assertTrue(res[0].get("member") != "" ) + + ctl = str(res.controls[0]).split(":") + ctl[1] = "1" + ctl[2] = "0" + ctl[3] = "1" + control1 = str(":".join(ctl)) + + # Check that reasking the same question but with an updated cookie + # didn't return any results. + print control1 + res = self.ldb_admin.search(self.base_dn, + expression="(name=testgroup)", + controls=[control1]) + self.assertEqual(len(res), 0) + + ctl = str(res.controls[0]).split(":") + ctl[1] = "1" + ctl[2] = "1" + ctl[3] = "10000" + control1 = str(":".join(ctl)) + + self.ldb_admin.add_remove_group_members("testgroup", self.simple_user, + add_members_operation=False) + + res = self.ldb_admin.search(self.base_dn, + expression="(name=testgroup)", + attrs=["member"], + controls=[control1]) + + self.ldb_admin.deletegroup("testgroup") + self.assertEqual(len(res[0].get("member")), 0) + + + + def test_dirsync_deleted_items(self): + """Check that dirsync returnd deleted objects too""" + # Let's create an OU + ouname="OU=testou3,%s" % self.base_dn + self.ouname = ouname + self.ldb_admin.create_ou(ouname) + res = self.ldb_admin.search(self.base_dn, + expression="(&(objectClass=organizationalUnit)(!(isDeleted=*)))", + controls=["dirsync:1:0:1"]) + guid = None + for e in res: + if str(e["name"]) == "testou3": + guid = str(ndr_unpack(misc.GUID,e.get("objectGUID")[0])) + + ctl = str(res.controls[0]).split(":") + ctl[1] = "1" + ctl[2] = "0" + ctl[3] = "10000" + control1 = str(":".join(ctl)) + + # So now delete the object and check that + # we can see the object but deleted when admin + delete_force(self.ldb_admin, ouname) + + res = self.ldb_admin.search(self.base_dn, + expression="(objectClass=organizationalUnit)", + controls=[control1]) + self.assertEqual(len(res), 1) + guid2 = str(ndr_unpack(misc.GUID,res[0].get("objectGUID")[0])) + self.assertEqual(guid2, guid) + self.assertTrue(res[0].get("isDeleted")) + self.assertTrue(res[0].get("name") != None) + + def test_cookie_from_others(self): + res = self.ldb_admin.search(self.base_dn, + expression="(&(objectClass=organizationalUnit)(!(isDeleted=*)))", + controls=["dirsync:1:0:1"]) + ctl = str(res.controls[0]).split(":") + cookie = ndr_unpack(drsblobs.ldapControlDirSyncCookie, base64.b64decode(str(ctl[4]))) + cookie.blob.guid1 = misc.GUID("128a99bf-abcd-1234-abcd-1fb625e530db") + controls=["dirsync:1:0:0:%s" % base64.b64encode(ndr_pack(cookie))] + res = self.ldb_admin.search(self.base_dn, + expression="(&(objectClass=organizationalUnit)(!(isDeleted=*)))", + controls=controls) + +class ExtendedDirsyncTests(SimpleDirsyncTests): + def test_dirsync_linkedattributes(self): + flag_incr_linked = 2147483648 + self.ldb_simple = self.get_ldb_connection(self.simple_user, self.user_pass) + res = self.ldb_admin.search(self.base_dn, + attrs=["member"], + expression="(name=Administrators)", + controls=["dirsync:1:%d:1" % flag_incr_linked]) + + self.assertTrue(res[0].get("member;range=1-1") != None ) + self.assertTrue(len(res[0].get("member;range=1-1")) > 0) + size = len(res[0].get("member;range=1-1")) + + ctl = str(res.controls[0]).split(":") + ctl[1] = "1" + ctl[2] = "%d" % flag_incr_linked + ctl[3] = "10000" + control1 = str(":".join(ctl)) + self.ldb_admin.add_remove_group_members("Administrators", self.simple_user, + add_members_operation=True) + self.ldb_admin.add_remove_group_members("Administrators", self.dirsync_user, + add_members_operation=True) + + + res = self.ldb_admin.search(self.base_dn, + expression="(name=Administrators)", + controls=[control1]) + + self.assertEqual(len(res[0].get("member;range=1-1")), 2) + ctl = str(res.controls[0]).split(":") + ctl[1] = "1" + ctl[2] = "%d" % flag_incr_linked + ctl[3] = "10000" + control1 = str(":".join(ctl)) + + # remove the user from the group + self.ldb_admin.add_remove_group_members("Administrators", self.simple_user, + add_members_operation=False) + + res = self.ldb_admin.search(self.base_dn, + expression="(name=Administrators)", + controls=[control1]) + + self.assertEqual(res[0].get("member;range=1-1"), None ) + self.assertEqual(len(res[0].get("member;range=0-0")), 1) + + ctl = str(res.controls[0]).split(":") + ctl[1] = "1" + ctl[2] = "%d" % flag_incr_linked + ctl[3] = "10000" + control2 = str(":".join(ctl)) + + self.ldb_admin.add_remove_group_members("Administrators", self.dirsync_user, + add_members_operation=False) + + res = self.ldb_admin.search(self.base_dn, + expression="(name=Administrators)", + controls=[control2]) + + self.assertEqual(res[0].get("member;range=1-1"), None ) + self.assertEqual(len(res[0].get("member;range=0-0")), 1) + + res = self.ldb_admin.search(self.base_dn, + expression="(name=Administrators)", + controls=[control1]) + + self.assertEqual(res[0].get("member;range=1-1"), None ) + self.assertEqual(len(res[0].get("member;range=0-0")), 2) + + def test_dirsync_deleted_items(self): + """Check that dirsync returnd deleted objects too""" + # Let's create an OU + self.ldb_simple = self.get_ldb_connection(self.simple_user, self.user_pass) + ouname="OU=testou3,%s" % self.base_dn + self.ouname = ouname + self.ldb_admin.create_ou(ouname) + + # Specify LDAP_DIRSYNC_OBJECT_SECURITY + res = self.ldb_simple.search(self.base_dn, + expression="(&(objectClass=organizationalUnit)(!(isDeleted=*)))", + controls=["dirsync:1:1:1"]) + + guid = None + for e in res: + if str(e["name"]) == "testou3": + guid = str(ndr_unpack(misc.GUID,e.get("objectGUID")[0])) + + self.assertTrue(guid != None) + ctl = str(res.controls[0]).split(":") + ctl[1] = "1" + ctl[2] = "1" + ctl[3] = "10000" + control1 = str(":".join(ctl)) + + # So now delete the object and check that + # we can see the object but deleted when admin + # we just see the objectGUID when simple user + delete_force(self.ldb_admin, ouname) + + res = self.ldb_simple.search(self.base_dn, + expression="(objectClass=organizationalUnit)", + controls=[control1]) + self.assertEqual(len(res), 1) + guid2 = str(ndr_unpack(misc.GUID,res[0].get("objectGUID")[0])) + self.assertEqual(guid2, guid) + self.assertEqual(str(res[0].dn), "") + + +ldb = SamDB(ldapshost, credentials=creds, session_info=system_session(lp), lp=lp) + +runner = SubunitTestRunner() +rc = 0 +# +if not runner.run(unittest.makeSuite(SimpleDirsyncTests)).wasSuccessful(): + rc = 1 +if not runner.run(unittest.makeSuite(ExtendedDirsyncTests)).wasSuccessful(): + rc = 1 + +sys.exit(rc) diff --git a/source4/dsdb/tests/python/sam.py b/source4/dsdb/tests/python/sam.py index 5f7c90db23..b08fba5a7d 100755 --- a/source4/dsdb/tests/python/sam.py +++ b/source4/dsdb/tests/python/sam.py @@ -36,8 +36,8 @@ from samba.dsdb import (UF_NORMAL_ACCOUNT, UF_ACCOUNTDISABLE, ATYPE_SECURITY_LOCAL_GROUP, ATYPE_DISTRIBUTION_GLOBAL_GROUP, ATYPE_DISTRIBUTION_UNIVERSAL_GROUP, ATYPE_DISTRIBUTION_LOCAL_GROUP, ATYPE_WORKSTATION_TRUST) -from samba.dcerpc.security import (DOMAIN_RID_USERS, DOMAIN_RID_DOMAIN_MEMBERS, - DOMAIN_RID_DCS, DOMAIN_RID_READONLY_DCS) +from samba.dcerpc.security import (DOMAIN_RID_USERS, DOMAIN_RID_ADMINS, + DOMAIN_RID_DOMAIN_MEMBERS, DOMAIN_RID_DCS, DOMAIN_RID_READONLY_DCS) from subunit.run import SubunitTestRunner import unittest @@ -1471,25 +1471,33 @@ class SamTests(unittest.TestCase): self.assertEquals(num, ERR_OTHER) delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn) -# This isn't supported yet in s4 -# try: -# ldb.add({ -# "dn": "cn=ldaptestuser,cn=users," + self.base_dn, -# "objectclass": "user", -# "userAccountControl": str(UF_SERVER_TRUST_ACCOUNT)}) -# self.fail() -# except LdbError, (num, _): -# self.assertEquals(num, ERR_OBJECT_CLASS_VIOLATION) -# delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn) -# -# try: -# ldb.add({ -# "dn": "cn=ldaptestuser,cn=users," + self.base_dn, -# "objectclass": "user", -# "userAccountControl": str(UF_WORKSTATION_TRUST_ACCOUNT)}) -# except LdbError, (num, _): -# self.assertEquals(num, ERR_OBJECT_CLASS_VIOLATION) -# delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn) + try: + ldb.add({ + "dn": "cn=ldaptestuser,cn=users," + self.base_dn, + "objectclass": "user", + "userAccountControl": str(UF_SERVER_TRUST_ACCOUNT)}) + self.fail() + except LdbError, (num, _): + self.assertEquals(num, ERR_OBJECT_CLASS_VIOLATION) + delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn) + + try: + ldb.add({ + "dn": "cn=ldaptestuser,cn=users," + self.base_dn, + "objectclass": "user", + "userAccountControl": str(UF_WORKSTATION_TRUST_ACCOUNT)}) + except LdbError, (num, _): + self.assertEquals(num, ERR_OBJECT_CLASS_VIOLATION) + delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn) + + try: + ldb.add({ + "dn": "cn=ldaptestuser,cn=users," + self.base_dn, + "objectclass": "user", + "userAccountControl": str(UF_WORKSTATION_TRUST_ACCOUNT | UF_PARTIAL_SECRETS_ACCOUNT)}) + except LdbError, (num, _): + self.assertEquals(num, ERR_OBJECT_CLASS_VIOLATION) + delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn) # This isn't supported yet in s4 - needs ACL module adaption # try: @@ -1570,17 +1578,16 @@ class SamTests(unittest.TestCase): except LdbError, (num, _): self.assertEquals(num, ERR_OTHER) -# This isn't supported yet in s4 -# try: -# m = Message() -# m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn) -# m["userAccountControl"] = MessageElement( -# str(UF_SERVER_TRUST_ACCOUNT), -# FLAG_MOD_REPLACE, "userAccountControl") -# ldb.modify(m) -# self.fail() -# except LdbError, (num, _): -# self.assertEquals(num, ERR_UNWILLING_TO_PERFORM) + try: + m = Message() + m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn) + m["userAccountControl"] = MessageElement( + str(UF_SERVER_TRUST_ACCOUNT), + FLAG_MOD_REPLACE, "userAccountControl") + ldb.modify(m) + self.fail() + except LdbError, (num, _): + self.assertEquals(num, ERR_UNWILLING_TO_PERFORM) m = Message() m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn) @@ -1589,6 +1596,17 @@ class SamTests(unittest.TestCase): FLAG_MOD_REPLACE, "userAccountControl") ldb.modify(m) + try: + m = Message() + m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn) + m["userAccountControl"] = MessageElement( + str(UF_WORKSTATION_TRUST_ACCOUNT | UF_PARTIAL_SECRETS_ACCOUNT), + FLAG_MOD_REPLACE, "userAccountControl") + ldb.modify(m) + self.fail() + except LdbError, (num, _): + self.assertEquals(num, ERR_UNWILLING_TO_PERFORM) + res1 = ldb.search("cn=ldaptestuser,cn=users," + self.base_dn, scope=SCOPE_BASE, attrs=["sAMAccountType"]) self.assertTrue(len(res1) == 1) @@ -1866,7 +1884,215 @@ class SamTests(unittest.TestCase): # except LdbError, (num, _): # self.assertEquals(num, ERR_INSUFFICIENT_ACCESS_RIGHTS) + # "primaryGroupID" does not change if account type remains the same + + # For a user account + + ldb.add({ + "dn": "cn=ldaptestuser2,cn=users," + self.base_dn, + "objectclass": "user", + "userAccountControl": str(UF_NORMAL_ACCOUNT | UF_PASSWD_NOTREQD | UF_ACCOUNTDISABLE)}) + + res1 = ldb.search("cn=ldaptestuser2,cn=users," + self.base_dn, + scope=SCOPE_BASE, + attrs=["userAccountControl"]) + self.assertTrue(len(res1) == 1) + self.assertEquals(int(res1[0]["userAccountControl"][0]), + UF_NORMAL_ACCOUNT | UF_PASSWD_NOTREQD | UF_ACCOUNTDISABLE) + + m = Message() + m.dn = Dn(ldb, "<SID=" + ldb.get_domain_sid() + "-" + str(DOMAIN_RID_ADMINS) + ">") + m["member"] = MessageElement( + "cn=ldaptestuser2,cn=users," + self.base_dn, FLAG_MOD_ADD, "member") + ldb.modify(m) + + m = Message() + m.dn = Dn(ldb, "cn=ldaptestuser2,cn=users," + self.base_dn) + m["primaryGroupID"] = MessageElement(str(DOMAIN_RID_ADMINS), + FLAG_MOD_REPLACE, "primaryGroupID") + ldb.modify(m) + + m = Message() + m.dn = Dn(ldb, "cn=ldaptestuser2,cn=users," + self.base_dn) + m["userAccountControl"] = MessageElement( + str(UF_NORMAL_ACCOUNT | UF_PASSWD_NOTREQD), + FLAG_MOD_REPLACE, "userAccountControl") + ldb.modify(m) + + res1 = ldb.search("cn=ldaptestuser2,cn=users," + self.base_dn, + scope=SCOPE_BASE, + attrs=["userAccountControl", "primaryGroupID"]) + self.assertTrue(len(res1) == 1) + self.assertTrue(int(res1[0]["userAccountControl"][0]) & UF_ACCOUNTDISABLE == 0) + self.assertEquals(int(res1[0]["primaryGroupID"][0]), DOMAIN_RID_ADMINS) + + # For a workstation account + + res1 = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn, + scope=SCOPE_BASE, + attrs=["primaryGroupID"]) + self.assertTrue(len(res1) == 1) + self.assertEquals(int(res1[0]["primaryGroupID"][0]), DOMAIN_RID_DOMAIN_MEMBERS) + + m = Message() + m.dn = Dn(ldb, "<SID=" + ldb.get_domain_sid() + "-" + str(DOMAIN_RID_USERS) + ">") + m["member"] = MessageElement( + "cn=ldaptestcomputer,cn=computers," + self.base_dn, FLAG_MOD_ADD, "member") + ldb.modify(m) + + m = Message() + m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn) + m["primaryGroupID"] = MessageElement(str(DOMAIN_RID_USERS), + FLAG_MOD_REPLACE, "primaryGroupID") + ldb.modify(m) + + m = Message() + m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn) + m["userAccountControl"] = MessageElement( + str(UF_WORKSTATION_TRUST_ACCOUNT), + FLAG_MOD_REPLACE, "userAccountControl") + ldb.modify(m) + + res1 = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn, + scope=SCOPE_BASE, + attrs=["primaryGroupID"]) + self.assertTrue(len(res1) == 1) + self.assertEquals(int(res1[0]["primaryGroupID"][0]), DOMAIN_RID_USERS) + delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn) + delete_force(self.ldb, "cn=ldaptestuser2,cn=users," + self.base_dn) + delete_force(self.ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn) + + def test_isCriticalSystemObject(self): + """Test the isCriticalSystemObject behaviour""" + print "Testing isCriticalSystemObject behaviour\n" + + # Add tests + + ldb.add({ + "dn": "cn=ldaptestcomputer,cn=computers," + self.base_dn, + "objectclass": "computer"}) + + res1 = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn, + scope=SCOPE_BASE, + attrs=["isCriticalSystemObject"]) + self.assertTrue(len(res1) == 1) + self.assertTrue("isCriticalSystemObject" not in res1[0]) + + delete_force(self.ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn) + + ldb.add({ + "dn": "cn=ldaptestcomputer,cn=computers," + self.base_dn, + "objectclass": "computer", + "userAccountControl": str(UF_WORKSTATION_TRUST_ACCOUNT)}) + + res1 = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn, + scope=SCOPE_BASE, + attrs=["isCriticalSystemObject"]) + self.assertTrue(len(res1) == 1) + self.assertEquals(res1[0]["isCriticalSystemObject"][0], "FALSE") + + delete_force(self.ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn) + + ldb.add({ + "dn": "cn=ldaptestcomputer,cn=computers," + self.base_dn, + "objectclass": "computer", + "userAccountControl": str(UF_WORKSTATION_TRUST_ACCOUNT | UF_PARTIAL_SECRETS_ACCOUNT)}) + + res1 = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn, + scope=SCOPE_BASE, + attrs=["isCriticalSystemObject"]) + self.assertTrue(len(res1) == 1) + self.assertEquals(res1[0]["isCriticalSystemObject"][0], "TRUE") + + delete_force(self.ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn) + + ldb.add({ + "dn": "cn=ldaptestcomputer,cn=computers," + self.base_dn, + "objectclass": "computer", + "userAccountControl": str(UF_SERVER_TRUST_ACCOUNT)}) + + res1 = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn, + scope=SCOPE_BASE, + attrs=["isCriticalSystemObject"]) + self.assertTrue(len(res1) == 1) + self.assertEquals(res1[0]["isCriticalSystemObject"][0], "TRUE") + + # Modification tests + + m = Message() + m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn) + m["userAccountControl"] = MessageElement(str(UF_NORMAL_ACCOUNT | UF_PASSWD_NOTREQD), + FLAG_MOD_REPLACE, "userAccountControl") + ldb.modify(m) + + res1 = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn, + scope=SCOPE_BASE, + attrs=["isCriticalSystemObject"]) + self.assertTrue(len(res1) == 1) + self.assertEquals(res1[0]["isCriticalSystemObject"][0], "TRUE") + + m = Message() + m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn) + m["userAccountControl"] = MessageElement(str(UF_WORKSTATION_TRUST_ACCOUNT), + FLAG_MOD_REPLACE, "userAccountControl") + ldb.modify(m) + + res1 = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn, + scope=SCOPE_BASE, + attrs=["isCriticalSystemObject"]) + self.assertTrue(len(res1) == 1) + self.assertEquals(res1[0]["isCriticalSystemObject"][0], "FALSE") + + m = Message() + m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn) + m["userAccountControl"] = MessageElement( + str(UF_WORKSTATION_TRUST_ACCOUNT | UF_PARTIAL_SECRETS_ACCOUNT), + FLAG_MOD_REPLACE, "userAccountControl") + ldb.modify(m) + + res1 = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn, + scope=SCOPE_BASE, + attrs=["isCriticalSystemObject"]) + self.assertTrue(len(res1) == 1) + self.assertEquals(res1[0]["isCriticalSystemObject"][0], "TRUE") + + m = Message() + m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn) + m["userAccountControl"] = MessageElement(str(UF_NORMAL_ACCOUNT | UF_PASSWD_NOTREQD), + FLAG_MOD_REPLACE, "userAccountControl") + ldb.modify(m) + + res1 = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn, + scope=SCOPE_BASE, + attrs=["isCriticalSystemObject"]) + self.assertTrue(len(res1) == 1) + self.assertEquals(res1[0]["isCriticalSystemObject"][0], "TRUE") + + m = Message() + m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn) + m["userAccountControl"] = MessageElement(str(UF_SERVER_TRUST_ACCOUNT), + FLAG_MOD_REPLACE, "userAccountControl") + ldb.modify(m) + + res1 = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn, + scope=SCOPE_BASE, + attrs=["isCriticalSystemObject"]) + self.assertTrue(len(res1) == 1) + self.assertEquals(res1[0]["isCriticalSystemObject"][0], "TRUE") + + m = Message() + m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn) + m["userAccountControl"] = MessageElement(str(UF_WORKSTATION_TRUST_ACCOUNT), + FLAG_MOD_REPLACE, "userAccountControl") + ldb.modify(m) + + res1 = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn, + scope=SCOPE_BASE, + attrs=["isCriticalSystemObject"]) + self.assertTrue(len(res1) == 1) + self.assertEquals(res1[0]["isCriticalSystemObject"][0], "FALSE") + delete_force(self.ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn) def test_service_principal_name_updates(self): diff --git a/source4/dsdb/tests/python/token_group.py b/source4/dsdb/tests/python/token_group.py index 62bdbd5ee0..fb7654e7e0 100755 --- a/source4/dsdb/tests/python/token_group.py +++ b/source4/dsdb/tests/python/token_group.py @@ -78,7 +78,7 @@ class TokenTest(samba.tests.TestCase): res = self.ldb.search("", scope=ldb.SCOPE_BASE, attrs=["tokenGroups"]) self.assertEquals(len(res), 1) - print("Geting tokenGroups from rootDSE") + print("Getting tokenGroups from rootDSE") tokengroups = [] for sid in res[0]['tokenGroups']: tokengroups.append(str(ndr_unpack(samba.dcerpc.security.dom_sid, sid))) @@ -93,7 +93,7 @@ class TokenTest(samba.tests.TestCase): self.fail(msg="calculated groups don't match against rootDSE tokenGroups") def test_dn_tokenGroups(self): - print("Geting tokenGroups from user DN") + print("Getting tokenGroups from user DN") res = self.ldb.search(self.user_sid_dn, scope=ldb.SCOPE_BASE, attrs=["tokenGroups"]) self.assertEquals(len(res), 1) diff --git a/source4/dsdb/wscript_build b/source4/dsdb/wscript_build index 9d56e4f150..fe00059c4b 100644 --- a/source4/dsdb/wscript_build +++ b/source4/dsdb/wscript_build @@ -8,7 +8,7 @@ bld.SAMBA_LIBRARY('samdb', autoproto='samdb/samdb_proto.h', public_deps='krb5', vnum='0.0.1', - deps='ndr NDR_DRSUAPI NDR_DRSBLOBS auth_system_session LIBCLI_AUTH ndr SAMDB_SCHEMA ldbsamba samdb-common LIBCLI_DRSUAPI cli-ldap-common samba-util com_err authkrb5 credentials ldbwrap', + deps='ndr NDR_DRSUAPI NDR_DRSBLOBS auth_system_session LIBCLI_AUTH ndr SAMDB_SCHEMA ldbsamba samdb-common LIBCLI_DRSUAPI cli-ldap-common samba-util com_err authkrb5 credentials ldbwrap errors', ) |