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 work out the principal to use for DRS replication connections
131 NTSTATUS dreplsrv_get_target_principal(struct dreplsrv_service *s,
133 const struct repsFromTo1 *rft,
134 const char **target_principal)
137 struct ldb_result *res;
138 const char *attrs_server[] = { "dNSHostName", NULL };
139 const char *attrs_ntds[] = { "msDS-HasDomainNCs", "hasMasterNCs", NULL };
141 const char *hostname, *dnsdomain=NULL;
142 struct ldb_dn *ntds_dn, *server_dn;
143 struct ldb_dn *forest_dn, *nc_dn;
145 *target_principal = NULL;
147 tmp_ctx = talloc_new(mem_ctx);
149 /* we need to find their hostname */
150 ret = dsdb_find_dn_by_guid(s->samdb, tmp_ctx, &rft->source_dsa_obj_guid, &ntds_dn);
151 if (ret != LDB_SUCCESS) {
152 talloc_free(tmp_ctx);
153 /* its OK for their NTDSDSA DN not to be in our database */
157 server_dn = ldb_dn_copy(tmp_ctx, ntds_dn);
158 if (server_dn == NULL) {
159 talloc_free(tmp_ctx);
163 /* strip off the NTDS Settings */
164 if (!ldb_dn_remove_child_components(server_dn, 1)) {
165 talloc_free(tmp_ctx);
169 ret = dsdb_search_dn(s->samdb, tmp_ctx, &res, server_dn, attrs_server, 0);
170 if (ret != LDB_SUCCESS) {
171 talloc_free(tmp_ctx);
172 /* its OK for their server DN not to be in our database */
176 forest_dn = ldb_get_root_basedn(s->samdb);
177 if (forest_dn == NULL) {
178 talloc_free(tmp_ctx);
182 hostname = ldb_msg_find_attr_as_string(res->msgs[0], "dNSHostName", NULL);
183 if (hostname != NULL) {
185 if we have the dNSHostName attribute then we can use
186 the GC/hostname/realm SPN. All DCs should have this SPN
188 *target_principal = talloc_asprintf(mem_ctx, "GC/%s/%s",
190 samdb_dn_to_dns_domain(tmp_ctx, forest_dn));
191 talloc_free(tmp_ctx);
196 if we can't find the dNSHostName then we will try for the
197 E3514235-4B06-11D1-AB04-00C04FC2DCD2/${NTDSGUID}/${DNSDOMAIN}
198 SPN. To use that we need the DNS domain name of the target
199 DC. We find that by first looking for the msDS-HasDomainNCs
200 in the NTDSDSA object of the DC, and if we don't find that,
201 then we look for the hasMasterNCs attribute, and eliminate
202 the known schema and configuruation DNs. Despite how
203 bizarre this seems, Hongwei tells us that this is in fact
204 what windows does to find the SPN!!
206 ret = dsdb_search_dn(s->samdb, tmp_ctx, &res, ntds_dn, attrs_ntds, 0);
207 if (ret != LDB_SUCCESS) {
208 talloc_free(tmp_ctx);
212 nc_dn = ldb_msg_find_attr_as_dn(s->samdb, tmp_ctx, res->msgs[0], "msDS-HasDomainNCs");
214 dnsdomain = samdb_dn_to_dns_domain(tmp_ctx, nc_dn);
217 if (dnsdomain == NULL) {
218 struct ldb_message_element *el;
220 el = ldb_msg_find_element(res->msgs[0], "hasMasterNCs");
221 for (i=0; el && i<el->num_values; i++) {
222 nc_dn = ldb_dn_from_ldb_val(tmp_ctx, s->samdb, &el->values[i]);
224 ldb_dn_compare(ldb_get_config_basedn(s->samdb), nc_dn) == 0 ||
225 ldb_dn_compare(ldb_get_schema_basedn(s->samdb), nc_dn) == 0) {
228 /* it must be a domain DN, get the equivalent
230 dnsdomain = samdb_dn_to_dns_domain(tmp_ctx, nc_dn);
235 if (dnsdomain != NULL) {
236 *target_principal = talloc_asprintf(mem_ctx,
237 "E3514235-4B06-11D1-AB04-00C04FC2DCD2/%s/%s",
238 GUID_string(tmp_ctx, &rft->source_dsa_obj_guid),
242 talloc_free(tmp_ctx);
247 WERROR dreplsrv_out_connection_attach(struct dreplsrv_service *s,
248 const struct repsFromTo1 *rft,
249 struct dreplsrv_out_connection **_conn)
251 struct dreplsrv_out_connection *cur, *conn = NULL;
252 const char *hostname;
254 if (!rft->other_info) {
258 if (!rft->other_info->dns_name) {
262 hostname = rft->other_info->dns_name;
264 for (cur = s->connections; cur; cur = cur->next) {
265 if (strcmp(cur->binding->host, hostname) == 0) {
275 conn = talloc_zero(s, struct dreplsrv_out_connection);
276 W_ERROR_HAVE_NO_MEMORY(conn);
280 binding_str = talloc_asprintf(conn, "ncacn_ip_tcp:%s[krb5,seal]",
282 W_ERROR_HAVE_NO_MEMORY(binding_str);
283 nt_status = dcerpc_parse_binding(conn, binding_str, &conn->binding);
284 talloc_free(binding_str);
285 if (!NT_STATUS_IS_OK(nt_status)) {
286 return ntstatus_to_werror(nt_status);
289 /* use the GC principal for DRS replication */
290 nt_status = dreplsrv_get_target_principal(s, conn->binding,
291 rft, &conn->binding->target_principal);
292 if (!NT_STATUS_IS_OK(nt_status)) {
293 return ntstatus_to_werror(nt_status);
296 DLIST_ADD_END(s->connections, conn, struct dreplsrv_out_connection *);
298 DEBUG(4,("dreplsrv_out_connection_attach(%s): create\n", conn->binding->host));
300 DEBUG(4,("dreplsrv_out_connection_attach(%s): attach\n", conn->binding->host));
308 find an existing source dsa in a list
310 static struct dreplsrv_partition_source_dsa *dreplsrv_find_source_dsa(struct dreplsrv_partition_source_dsa *list,
313 struct dreplsrv_partition_source_dsa *s;
314 for (s=list; s; s=s->next) {
315 if (GUID_compare(&s->repsFrom1->source_dsa_obj_guid, guid) == 0) {
324 static WERROR dreplsrv_partition_add_source_dsa(struct dreplsrv_service *s,
325 struct dreplsrv_partition *p,
326 struct dreplsrv_partition_source_dsa **listp,
327 struct dreplsrv_partition_source_dsa *check_list,
328 const struct ldb_val *val)
331 enum ndr_err_code ndr_err;
332 struct dreplsrv_partition_source_dsa *source, *s2;
334 source = talloc_zero(p, struct dreplsrv_partition_source_dsa);
335 W_ERROR_HAVE_NO_MEMORY(source);
337 ndr_err = ndr_pull_struct_blob(val, source,
338 &source->_repsFromBlob,
339 (ndr_pull_flags_fn_t)ndr_pull_repsFromToBlob);
340 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
341 NTSTATUS nt_status = ndr_map_error2ntstatus(ndr_err);
343 return ntstatus_to_werror(nt_status);
345 /* NDR_PRINT_DEBUG(repsFromToBlob, &source->_repsFromBlob); */
346 if (source->_repsFromBlob.version != 1) {
348 return WERR_DS_DRA_INTERNAL_ERROR;
351 source->partition = p;
352 source->repsFrom1 = &source->_repsFromBlob.ctr.ctr1;
354 status = dreplsrv_out_connection_attach(s, source->repsFrom1, &source->conn);
355 W_ERROR_NOT_OK_RETURN(status);
358 dreplsrv_find_source_dsa(check_list, &source->repsFrom1->source_dsa_obj_guid)) {
359 /* its in the check list, don't add it again */
364 /* re-use an existing source if found */
365 for (s2=*listp; s2; s2=s2->next) {
366 if (GUID_compare(&s2->repsFrom1->source_dsa_obj_guid,
367 &source->repsFrom1->source_dsa_obj_guid) == 0) {
368 talloc_free(s2->repsFrom1->other_info);
369 *s2->repsFrom1 = *source->repsFrom1;
370 talloc_steal(s2, s2->repsFrom1->other_info);
376 DLIST_ADD_END(*listp, source, struct dreplsrv_partition_source_dsa *);
380 WERROR dreplsrv_partition_find_for_nc(struct dreplsrv_service *s,
381 struct GUID *nc_guid,
382 struct dom_sid *nc_sid,
383 const char *nc_dn_str,
384 struct dreplsrv_partition **_p)
386 struct dreplsrv_partition *p;
387 bool valid_sid, valid_guid;
388 struct dom_sid null_sid;
389 ZERO_STRUCT(null_sid);
393 valid_sid = nc_sid && !dom_sid_equal(&null_sid, nc_sid);
394 valid_guid = nc_guid && !GUID_all_zero(nc_guid);
396 if (!valid_sid && !valid_guid && !nc_dn_str) {
397 return WERR_DS_DRA_INVALID_PARAMETER;
400 for (p = s->partitions; p; p = p->next) {
401 if ((valid_guid && GUID_equal(&p->nc.guid, nc_guid))
402 || strequal(p->nc.dn, nc_dn_str)
403 || (valid_sid && dom_sid_equal(&p->nc.sid, nc_sid)))
405 /* fill in he right guid and sid if possible */
406 if (nc_guid && !valid_guid) {
407 dsdb_get_extended_dn_guid(p->dn, nc_guid, "GUID");
409 if (nc_sid && !valid_sid) {
410 dsdb_get_extended_dn_sid(p->dn, nc_sid, "SID");
417 return WERR_DS_DRA_BAD_NC;
420 WERROR dreplsrv_partition_source_dsa_by_guid(struct dreplsrv_partition *p,
421 const struct GUID *dsa_guid,
422 struct dreplsrv_partition_source_dsa **_dsa)
424 struct dreplsrv_partition_source_dsa *dsa;
426 SMB_ASSERT(dsa_guid != NULL);
427 SMB_ASSERT(!GUID_all_zero(dsa_guid));
430 for (dsa = p->sources; dsa; dsa = dsa->next) {
431 if (GUID_equal(dsa_guid, &dsa->repsFrom1->source_dsa_obj_guid)) {
437 return WERR_DS_DRA_NO_REPLICA;
440 WERROR dreplsrv_partition_source_dsa_by_dns(const struct dreplsrv_partition *p,
442 struct dreplsrv_partition_source_dsa **_dsa)
444 struct dreplsrv_partition_source_dsa *dsa;
446 SMB_ASSERT(dsa_dns != NULL);
449 for (dsa = p->sources; dsa; dsa = dsa->next) {
450 if (strequal(dsa_dns, dsa->repsFrom1->other_info->dns_name)) {
456 return WERR_DS_DRA_NO_REPLICA;
461 create a temporary dsa structure for a replication. This is needed
462 for the initial replication of a new partition, such as when a new
463 domain NC is created and we are a global catalog server
465 WERROR dreplsrv_partition_source_dsa_temporary(struct dreplsrv_partition *p,
467 const struct GUID *dsa_guid,
468 struct dreplsrv_partition_source_dsa **_dsa)
470 struct dreplsrv_partition_source_dsa *dsa;
473 dsa = talloc_zero(mem_ctx, struct dreplsrv_partition_source_dsa);
474 W_ERROR_HAVE_NO_MEMORY(dsa);
477 dsa->repsFrom1 = &dsa->_repsFromBlob.ctr.ctr1;
478 dsa->repsFrom1->replica_flags = 0;
479 dsa->repsFrom1->source_dsa_obj_guid = *dsa_guid;
481 dsa->repsFrom1->other_info = talloc_zero(dsa, struct repsFromTo1OtherInfo);
482 W_ERROR_HAVE_NO_MEMORY(dsa->repsFrom1->other_info);
484 dsa->repsFrom1->other_info->dns_name = samdb_ntds_msdcs_dns_name(p->service->samdb,
485 dsa->repsFrom1->other_info, dsa_guid);
486 W_ERROR_HAVE_NO_MEMORY(dsa->repsFrom1->other_info->dns_name);
488 werr = dreplsrv_out_connection_attach(p->service, dsa->repsFrom1, &dsa->conn);
489 if (!W_ERROR_IS_OK(werr)) {
490 DEBUG(0,(__location__ ": Failed to attach connection to %s\n",
491 ldb_dn_get_linearized(p->dn)));
502 static WERROR dreplsrv_refresh_partition(struct dreplsrv_service *s,
503 struct dreplsrv_partition *p)
507 struct ldb_message_element *orf_el = NULL;
508 struct ldb_result *r = NULL;
511 TALLOC_CTX *mem_ctx = talloc_new(p);
512 static const char *attrs[] = {
519 DEBUG(4, ("dreplsrv_refresh_partition(%s)\n",
520 ldb_dn_get_linearized(p->dn)));
522 ret = dsdb_search_dn(s->samdb, mem_ctx, &r, p->dn, attrs, DSDB_SEARCH_SHOW_EXTENDED_DN);
523 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
524 /* we haven't replicated the partition yet, but we
525 * can fill in the guid, sid etc from the partition DN */
527 } else if (ret != LDB_SUCCESS) {
528 talloc_free(mem_ctx);
534 talloc_free(discard_const(p->nc.dn));
536 p->nc.dn = ldb_dn_alloc_linearized(p, dn);
537 W_ERROR_HAVE_NO_MEMORY(p->nc.dn);
538 ntstatus = dsdb_get_extended_dn_guid(dn, &p->nc.guid, "GUID");
539 if (!NT_STATUS_IS_OK(ntstatus)) {
540 DEBUG(0,(__location__ ": unable to get GUID for %s: %s\n",
541 p->nc.dn, nt_errstr(ntstatus)));
542 talloc_free(mem_ctx);
543 return WERR_DS_DRA_INTERNAL_ERROR;
545 dsdb_get_extended_dn_sid(dn, &p->nc.sid, "SID");
547 talloc_free(p->uptodatevector.cursors);
548 talloc_free(p->uptodatevector_ex.cursors);
549 ZERO_STRUCT(p->uptodatevector);
550 ZERO_STRUCT(p->uptodatevector_ex);
552 ret = dsdb_load_udv_v2(s->samdb, p->dn, p, &p->uptodatevector.cursors, &p->uptodatevector.count);
553 if (ret != LDB_SUCCESS) {
554 DEBUG(4,(__location__ ": no UDV available for %s\n", ldb_dn_get_linearized(p->dn)));
559 if (r != NULL && (orf_el = ldb_msg_find_element(r->msgs[0], "repsFrom"))) {
560 for (i=0; i < orf_el->num_values; i++) {
561 status = dreplsrv_partition_add_source_dsa(s, p, &p->sources,
562 NULL, &orf_el->values[i]);
563 W_ERROR_NOT_OK_GOTO_DONE(status);
567 if (r != NULL && (orf_el = ldb_msg_find_element(r->msgs[0], "repsTo"))) {
568 for (i=0; i < orf_el->num_values; i++) {
569 status = dreplsrv_partition_add_source_dsa(s, p, &p->notifies,
570 p->sources, &orf_el->values[i]);
571 W_ERROR_NOT_OK_GOTO_DONE(status);
576 talloc_free(mem_ctx);
580 WERROR dreplsrv_refresh_partitions(struct dreplsrv_service *s)
583 struct dreplsrv_partition *p;
585 for (p = s->partitions; p; p = p->next) {
586 status = dreplsrv_refresh_partition(s, p);
587 W_ERROR_NOT_OK_RETURN(status);