2 Unix SMB/CIFS mplementation.
3 DSDB replication service
5 Copyright (C) Stefan Metzmacher 2007
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 3 of the License, or
10 (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program. If not, see <http://www.gnu.org/licenses/>.
23 #include "dsdb/samdb/samdb.h"
24 #include "auth/auth.h"
25 #include "smbd/service.h"
26 #include "lib/events/events.h"
27 #include "dsdb/repl/drepl_service.h"
28 #include <ldb_errors.h>
29 #include "../lib/util/dlinklist.h"
30 #include "librpc/gen_ndr/ndr_misc.h"
31 #include "librpc/gen_ndr/ndr_drsuapi.h"
32 #include "librpc/gen_ndr/ndr_drsblobs.h"
33 #include "libcli/security/security.h"
34 #include "param/param.h"
35 #include "dsdb/common/util.h"
38 load the partitions list based on replicated NC attributes in our
41 WERROR dreplsrv_load_partitions(struct dreplsrv_service *s)
44 static const char *attrs[] = { "hasMasterNCs", "msDs-hasMasterNCs", "hasPartialReplicaNCs", "msDS-HasFullReplicaNCs", NULL };
48 struct ldb_result *res;
49 struct ldb_message_element *el;
50 struct ldb_dn *ntds_dn;
52 tmp_ctx = talloc_new(s);
53 W_ERROR_HAVE_NO_MEMORY(tmp_ctx);
55 ntds_dn = samdb_ntds_settings_dn(s->samdb);
57 DEBUG(1,(__location__ ": Unable to find ntds_dn: %s\n", ldb_errstring(s->samdb)));
59 return WERR_DS_DRA_INTERNAL_ERROR;
62 ret = dsdb_search_dn(s->samdb, tmp_ctx, &res, ntds_dn, attrs, DSDB_SEARCH_SHOW_EXTENDED_DN);
63 if (ret != LDB_SUCCESS) {
64 DEBUG(1,("Searching for hasMasterNCs in NTDS DN failed: %s\n", ldb_errstring(s->samdb)));
66 return WERR_DS_DRA_INTERNAL_ERROR;
69 for (a=0; attrs[a]; a++) {
72 el = ldb_msg_find_element(res->msgs[0], attrs[a]);
76 for (i=0; i<el->num_values; i++) {
78 struct dreplsrv_partition *p, *tp;
81 pdn = ldb_dn_from_ldb_val(tmp_ctx, s->samdb, &el->values[i]);
84 return WERR_DS_DRA_INTERNAL_ERROR;
86 if (!ldb_dn_validate(pdn)) {
87 return WERR_DS_DRA_INTERNAL_ERROR;
90 p = talloc_zero(s, struct dreplsrv_partition);
91 W_ERROR_HAVE_NO_MEMORY(p);
93 p->dn = talloc_steal(p, pdn);
96 if (strcasecmp(attrs[a], "hasPartialReplicaNCs") == 0) {
97 p->partial_replica = true;
98 } else if (strcasecmp(attrs[a], "msDS-HasFullReplicaNCs") == 0) {
99 p->rodc_replica = true;
102 /* Do not add partitions more than once */
104 for (tp = s->partitions; tp; tp = tp->next) {
105 if (ldb_dn_compare(tp->dn, p->dn) == 0) {
115 DLIST_ADD(s->partitions, p);
116 DEBUG(2, ("dreplsrv_partition[%s] loaded\n", ldb_dn_get_linearized(p->dn)));
120 talloc_free(tmp_ctx);
122 status = dreplsrv_refresh_partitions(s);
123 W_ERROR_NOT_OK_RETURN(status);
129 Check if particular SPN exists for an account
131 static bool dreplsrv_spn_exists(struct ldb_context *samdb, struct ldb_dn *ntds_dn,
132 const char *principal_name)
135 const char *attrs[] = { "serverReference", NULL };
136 const char *attrs_empty[] = { NULL };
138 struct ldb_result *res;
139 struct ldb_dn *account_dn;
141 tmp_ctx = talloc_new(samdb);
143 ret = dsdb_search_dn(samdb, tmp_ctx, &res, ntds_dn, attrs, 0);
144 if (ret != LDB_SUCCESS) {
145 talloc_free(tmp_ctx);
149 account_dn = ldb_msg_find_attr_as_dn(samdb, tmp_ctx, res->msgs[0], "serverReference");
150 if (account_dn == NULL) {
151 talloc_free(tmp_ctx);
157 ret = dsdb_search(samdb, tmp_ctx, &res, account_dn, LDB_SCOPE_BASE, attrs_empty,
158 0, "servicePrincipalName=%s",
159 ldb_binary_encode_string(tmp_ctx, principal_name));
160 if (ret != LDB_SUCCESS || res->count != 1) {
161 talloc_free(tmp_ctx);
165 talloc_free(tmp_ctx);
170 work out the principal to use for DRS replication connections
172 NTSTATUS dreplsrv_get_target_principal(struct dreplsrv_service *s,
174 const struct repsFromTo1 *rft,
175 const char **target_principal)
178 struct ldb_result *res;
179 const char *attrs_server[] = { "dNSHostName", NULL };
180 const char *attrs_ntds[] = { "msDS-HasDomainNCs", "hasMasterNCs", NULL };
182 const char *hostname, *dnsdomain=NULL;
183 struct ldb_dn *ntds_dn, *server_dn;
184 struct ldb_dn *forest_dn, *nc_dn;
186 *target_principal = NULL;
188 tmp_ctx = talloc_new(mem_ctx);
190 /* we need to find their hostname */
191 ret = dsdb_find_dn_by_guid(s->samdb, tmp_ctx, &rft->source_dsa_obj_guid, &ntds_dn);
192 if (ret != LDB_SUCCESS) {
193 talloc_free(tmp_ctx);
194 /* its OK for their NTDSDSA DN not to be in our database */
198 server_dn = ldb_dn_copy(tmp_ctx, ntds_dn);
199 if (server_dn == NULL) {
200 talloc_free(tmp_ctx);
204 /* strip off the NTDS Settings */
205 if (!ldb_dn_remove_child_components(server_dn, 1)) {
206 talloc_free(tmp_ctx);
210 ret = dsdb_search_dn(s->samdb, tmp_ctx, &res, server_dn, attrs_server, 0);
211 if (ret != LDB_SUCCESS) {
212 talloc_free(tmp_ctx);
213 /* its OK for their server DN not to be in our database */
217 forest_dn = ldb_get_root_basedn(s->samdb);
218 if (forest_dn == NULL) {
219 talloc_free(tmp_ctx);
223 hostname = ldb_msg_find_attr_as_string(res->msgs[0], "dNSHostName", NULL);
224 if (hostname != NULL) {
225 char *local_principal;
228 if we have the dNSHostName attribute then we can use
229 the GC/hostname/realm SPN. All DCs should have this SPN
231 Windows DC may set up it's dNSHostName before setting up
232 GC/xx/xx SPN. So make sure it exists, before using it.
234 local_principal = talloc_asprintf(mem_ctx, "GC/%s/%s",
236 samdb_dn_to_dns_domain(tmp_ctx, forest_dn));
237 if (dreplsrv_spn_exists(s->samdb, ntds_dn, local_principal)) {
238 *target_principal = local_principal;
239 talloc_free(tmp_ctx);
243 talloc_free(local_principal);
247 if we can't find the dNSHostName then we will try for the
248 E3514235-4B06-11D1-AB04-00C04FC2DCD2/${NTDSGUID}/${DNSDOMAIN}
249 SPN. To use that we need the DNS domain name of the target
250 DC. We find that by first looking for the msDS-HasDomainNCs
251 in the NTDSDSA object of the DC, and if we don't find that,
252 then we look for the hasMasterNCs attribute, and eliminate
253 the known schema and configuruation DNs. Despite how
254 bizarre this seems, Hongwei tells us that this is in fact
255 what windows does to find the SPN!!
257 ret = dsdb_search_dn(s->samdb, tmp_ctx, &res, ntds_dn, attrs_ntds, 0);
258 if (ret != LDB_SUCCESS) {
259 talloc_free(tmp_ctx);
263 nc_dn = ldb_msg_find_attr_as_dn(s->samdb, tmp_ctx, res->msgs[0], "msDS-HasDomainNCs");
265 dnsdomain = samdb_dn_to_dns_domain(tmp_ctx, nc_dn);
268 if (dnsdomain == NULL) {
269 struct ldb_message_element *el;
271 el = ldb_msg_find_element(res->msgs[0], "hasMasterNCs");
272 for (i=0; el && i<el->num_values; i++) {
273 nc_dn = ldb_dn_from_ldb_val(tmp_ctx, s->samdb, &el->values[i]);
275 ldb_dn_compare(ldb_get_config_basedn(s->samdb), nc_dn) == 0 ||
276 ldb_dn_compare(ldb_get_schema_basedn(s->samdb), nc_dn) == 0) {
279 /* it must be a domain DN, get the equivalent
281 dnsdomain = samdb_dn_to_dns_domain(tmp_ctx, nc_dn);
286 if (dnsdomain != NULL) {
287 *target_principal = talloc_asprintf(mem_ctx,
288 "E3514235-4B06-11D1-AB04-00C04FC2DCD2/%s/%s",
289 GUID_string(tmp_ctx, &rft->source_dsa_obj_guid),
293 talloc_free(tmp_ctx);
298 WERROR dreplsrv_out_connection_attach(struct dreplsrv_service *s,
299 const struct repsFromTo1 *rft,
300 struct dreplsrv_out_connection **_conn)
302 struct dreplsrv_out_connection *cur, *conn = NULL;
303 const char *hostname;
305 if (!rft->other_info) {
309 if (!rft->other_info->dns_name) {
313 hostname = rft->other_info->dns_name;
315 for (cur = s->connections; cur; cur = cur->next) {
316 if (strcmp(cur->binding->host, hostname) == 0) {
326 conn = talloc_zero(s, struct dreplsrv_out_connection);
327 W_ERROR_HAVE_NO_MEMORY(conn);
331 binding_str = talloc_asprintf(conn, "ncacn_ip_tcp:%s[krb5,seal]",
333 W_ERROR_HAVE_NO_MEMORY(binding_str);
334 nt_status = dcerpc_parse_binding(conn, binding_str, &conn->binding);
335 talloc_free(binding_str);
336 if (!NT_STATUS_IS_OK(nt_status)) {
337 return ntstatus_to_werror(nt_status);
340 /* use the GC principal for DRS replication */
341 nt_status = dreplsrv_get_target_principal(s, conn->binding,
342 rft, &conn->binding->target_principal);
343 if (!NT_STATUS_IS_OK(nt_status)) {
344 return ntstatus_to_werror(nt_status);
347 DLIST_ADD_END(s->connections, conn, struct dreplsrv_out_connection *);
349 DEBUG(4,("dreplsrv_out_connection_attach(%s): create\n", conn->binding->host));
351 DEBUG(4,("dreplsrv_out_connection_attach(%s): attach\n", conn->binding->host));
359 find an existing source dsa in a list
361 static struct dreplsrv_partition_source_dsa *dreplsrv_find_source_dsa(struct dreplsrv_partition_source_dsa *list,
364 struct dreplsrv_partition_source_dsa *s;
365 for (s=list; s; s=s->next) {
366 if (GUID_compare(&s->repsFrom1->source_dsa_obj_guid, guid) == 0) {
375 static WERROR dreplsrv_partition_add_source_dsa(struct dreplsrv_service *s,
376 struct dreplsrv_partition *p,
377 struct dreplsrv_partition_source_dsa **listp,
378 struct dreplsrv_partition_source_dsa *check_list,
379 struct dreplsrv_partition_source_dsa **oldlist,
380 const struct ldb_val *val)
383 enum ndr_err_code ndr_err;
384 struct dreplsrv_partition_source_dsa *source, *s2;
386 source = talloc_zero(p, struct dreplsrv_partition_source_dsa);
387 W_ERROR_HAVE_NO_MEMORY(source);
389 ndr_err = ndr_pull_struct_blob(val, source,
390 &source->_repsFromBlob,
391 (ndr_pull_flags_fn_t)ndr_pull_repsFromToBlob);
392 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
393 NTSTATUS nt_status = ndr_map_error2ntstatus(ndr_err);
395 return ntstatus_to_werror(nt_status);
397 /* NDR_PRINT_DEBUG(repsFromToBlob, &source->_repsFromBlob); */
398 if (source->_repsFromBlob.version != 1) {
400 return WERR_DS_DRA_INTERNAL_ERROR;
403 source->partition = p;
404 source->repsFrom1 = &source->_repsFromBlob.ctr.ctr1;
406 status = dreplsrv_out_connection_attach(s, source->repsFrom1, &source->conn);
407 W_ERROR_NOT_OK_RETURN(status);
410 dreplsrv_find_source_dsa(check_list, &source->repsFrom1->source_dsa_obj_guid)) {
411 /* its in the check list, don't add it again */
416 /* re-use an existing source if found */
417 for (s2=*oldlist; s2; s2=s2->next) {
418 if (GUID_compare(&s2->repsFrom1->source_dsa_obj_guid,
419 &source->repsFrom1->source_dsa_obj_guid) == 0) {
420 talloc_free(s2->repsFrom1->other_info);
421 *s2->repsFrom1 = *source->repsFrom1;
422 talloc_steal(s2, s2->repsFrom1->other_info);
425 DLIST_REMOVE(*oldlist, s2);
430 DLIST_ADD_END(*listp, source, struct dreplsrv_partition_source_dsa *);
434 WERROR dreplsrv_partition_find_for_nc(struct dreplsrv_service *s,
435 struct GUID *nc_guid,
436 struct dom_sid *nc_sid,
437 const char *nc_dn_str,
438 struct dreplsrv_partition **_p)
440 struct dreplsrv_partition *p;
441 bool valid_sid, valid_guid;
442 struct dom_sid null_sid;
443 ZERO_STRUCT(null_sid);
447 valid_sid = nc_sid && !dom_sid_equal(&null_sid, nc_sid);
448 valid_guid = nc_guid && !GUID_all_zero(nc_guid);
450 if (!valid_sid && !valid_guid && !nc_dn_str) {
451 return WERR_DS_DRA_INVALID_PARAMETER;
454 for (p = s->partitions; p; p = p->next) {
455 if ((valid_guid && GUID_equal(&p->nc.guid, nc_guid))
456 || strequal(p->nc.dn, nc_dn_str)
457 || (valid_sid && dom_sid_equal(&p->nc.sid, nc_sid)))
459 /* fill in he right guid and sid if possible */
460 if (nc_guid && !valid_guid) {
461 dsdb_get_extended_dn_guid(p->dn, nc_guid, "GUID");
463 if (nc_sid && !valid_sid) {
464 dsdb_get_extended_dn_sid(p->dn, nc_sid, "SID");
471 return WERR_DS_DRA_BAD_NC;
474 WERROR dreplsrv_partition_source_dsa_by_guid(struct dreplsrv_partition *p,
475 const struct GUID *dsa_guid,
476 struct dreplsrv_partition_source_dsa **_dsa)
478 struct dreplsrv_partition_source_dsa *dsa;
480 SMB_ASSERT(dsa_guid != NULL);
481 SMB_ASSERT(!GUID_all_zero(dsa_guid));
484 for (dsa = p->sources; dsa; dsa = dsa->next) {
485 if (GUID_equal(dsa_guid, &dsa->repsFrom1->source_dsa_obj_guid)) {
491 return WERR_DS_DRA_NO_REPLICA;
494 WERROR dreplsrv_partition_source_dsa_by_dns(const struct dreplsrv_partition *p,
496 struct dreplsrv_partition_source_dsa **_dsa)
498 struct dreplsrv_partition_source_dsa *dsa;
500 SMB_ASSERT(dsa_dns != NULL);
503 for (dsa = p->sources; dsa; dsa = dsa->next) {
504 if (strequal(dsa_dns, dsa->repsFrom1->other_info->dns_name)) {
510 return WERR_DS_DRA_NO_REPLICA;
515 create a temporary dsa structure for a replication. This is needed
516 for the initial replication of a new partition, such as when a new
517 domain NC is created and we are a global catalog server
519 WERROR dreplsrv_partition_source_dsa_temporary(struct dreplsrv_partition *p,
521 const struct GUID *dsa_guid,
522 struct dreplsrv_partition_source_dsa **_dsa)
524 struct dreplsrv_partition_source_dsa *dsa;
527 dsa = talloc_zero(mem_ctx, struct dreplsrv_partition_source_dsa);
528 W_ERROR_HAVE_NO_MEMORY(dsa);
531 dsa->repsFrom1 = &dsa->_repsFromBlob.ctr.ctr1;
532 dsa->repsFrom1->replica_flags = 0;
533 dsa->repsFrom1->source_dsa_obj_guid = *dsa_guid;
535 dsa->repsFrom1->other_info = talloc_zero(dsa, struct repsFromTo1OtherInfo);
536 W_ERROR_HAVE_NO_MEMORY(dsa->repsFrom1->other_info);
538 dsa->repsFrom1->other_info->dns_name = samdb_ntds_msdcs_dns_name(p->service->samdb,
539 dsa->repsFrom1->other_info, dsa_guid);
540 W_ERROR_HAVE_NO_MEMORY(dsa->repsFrom1->other_info->dns_name);
542 werr = dreplsrv_out_connection_attach(p->service, dsa->repsFrom1, &dsa->conn);
543 if (!W_ERROR_IS_OK(werr)) {
544 DEBUG(0,(__location__ ": Failed to attach connection to %s\n",
545 ldb_dn_get_linearized(p->dn)));
556 static WERROR dreplsrv_refresh_partition(struct dreplsrv_service *s,
557 struct dreplsrv_partition *p)
561 struct ldb_message_element *orf_el = NULL;
562 struct ldb_result *r = NULL;
565 TALLOC_CTX *mem_ctx = talloc_new(p);
566 static const char *attrs[] = {
572 struct dreplsrv_partition_source_dsa *src, *oldsources, *oldnotifies;
574 DEBUG(4, ("dreplsrv_refresh_partition(%s)\n",
575 ldb_dn_get_linearized(p->dn)));
577 ret = dsdb_search_dn(s->samdb, mem_ctx, &r, p->dn, attrs, DSDB_SEARCH_SHOW_EXTENDED_DN);
578 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
579 /* we haven't replicated the partition yet, but we
580 * can fill in the guid, sid etc from the partition DN */
582 } else if (ret != LDB_SUCCESS) {
583 talloc_free(mem_ctx);
589 talloc_free(discard_const(p->nc.dn));
591 p->nc.dn = ldb_dn_alloc_linearized(p, dn);
592 W_ERROR_HAVE_NO_MEMORY(p->nc.dn);
593 ntstatus = dsdb_get_extended_dn_guid(dn, &p->nc.guid, "GUID");
594 if (!NT_STATUS_IS_OK(ntstatus)) {
595 DEBUG(0,(__location__ ": unable to get GUID for %s: %s\n",
596 p->nc.dn, nt_errstr(ntstatus)));
597 talloc_free(mem_ctx);
598 return WERR_DS_DRA_INTERNAL_ERROR;
600 dsdb_get_extended_dn_sid(dn, &p->nc.sid, "SID");
602 talloc_free(p->uptodatevector.cursors);
603 talloc_free(p->uptodatevector_ex.cursors);
604 ZERO_STRUCT(p->uptodatevector);
605 ZERO_STRUCT(p->uptodatevector_ex);
607 ret = dsdb_load_udv_v2(s->samdb, p->dn, p, &p->uptodatevector.cursors, &p->uptodatevector.count);
608 if (ret != LDB_SUCCESS) {
609 DEBUG(4,(__location__ ": no UDV available for %s\n", ldb_dn_get_linearized(p->dn)));
614 oldsources = p->sources;
616 if (r != NULL && (orf_el = ldb_msg_find_element(r->msgs[0], "repsFrom"))) {
617 for (i=0; i < orf_el->num_values; i++) {
618 status = dreplsrv_partition_add_source_dsa(s, p, &p->sources,
621 W_ERROR_NOT_OK_GOTO_DONE(status);
624 if (r != NULL && p->sources) {
625 DEBUG(0, ("repsFrom do not exists or is empty\n"));
629 oldnotifies = p->notifies;
631 if (r != NULL && (orf_el = ldb_msg_find_element(r->msgs[0], "repsTo"))) {
632 for (i=0; i < orf_el->num_values; i++) {
633 status = dreplsrv_partition_add_source_dsa(s, p, &p->notifies,
637 W_ERROR_NOT_OK_GOTO_DONE(status);
644 struct dreplsrv_partition_source_dsa *tmp = src->next;
654 struct dreplsrv_partition_source_dsa *tmp = src->next;
661 talloc_free(mem_ctx);
665 WERROR dreplsrv_refresh_partitions(struct dreplsrv_service *s)
668 struct dreplsrv_partition *p;
670 for (p = s->partitions; p; p = p->next) {
671 status = dreplsrv_refresh_partition(s, p);
672 W_ERROR_NOT_OK_RETURN(status);