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", "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;
80 pdn = ldb_dn_from_ldb_val(tmp_ctx, s->samdb, &el->values[i]);
83 return WERR_DS_DRA_INTERNAL_ERROR;
85 if (!ldb_dn_validate(pdn)) {
86 return WERR_DS_DRA_INTERNAL_ERROR;
89 p = talloc_zero(s, struct dreplsrv_partition);
90 W_ERROR_HAVE_NO_MEMORY(p);
92 p->dn = talloc_steal(p, pdn);
95 if (strcasecmp(attrs[a], "hasPartialReplicaNCs") == 0) {
96 p->partial_replica = true;
97 } else if (strcasecmp(attrs[a], "msDS-HasFullReplicaNCs") == 0) {
98 p->rodc_replica = true;
101 DLIST_ADD(s->partitions, p);
103 DEBUG(2, ("dreplsrv_partition[%s] loaded\n", ldb_dn_get_linearized(p->dn)));
107 talloc_free(tmp_ctx);
109 status = dreplsrv_refresh_partitions(s);
110 W_ERROR_NOT_OK_RETURN(status);
116 work out the principal to use for DRS replication connections
118 NTSTATUS dreplsrv_get_target_principal(struct dreplsrv_service *s,
120 const struct repsFromTo1 *rft,
121 const char **target_principal)
124 struct ldb_result *res;
125 const char *attrs_server[] = { "dNSHostName", NULL };
126 const char *attrs_ntds[] = { "msDS-HasDomainNCs", "hasMasterNCs", NULL };
128 const char *hostname, *dnsdomain=NULL;
129 struct ldb_dn *ntds_dn, *server_dn;
130 struct ldb_dn *forest_dn, *nc_dn;
132 *target_principal = NULL;
134 tmp_ctx = talloc_new(mem_ctx);
136 /* we need to find their hostname */
137 ret = dsdb_find_dn_by_guid(s->samdb, tmp_ctx, &rft->source_dsa_obj_guid, &ntds_dn);
138 if (ret != LDB_SUCCESS) {
139 talloc_free(tmp_ctx);
140 /* its OK for their NTDSDSA DN not to be in our database */
144 server_dn = ldb_dn_copy(tmp_ctx, ntds_dn);
145 if (server_dn == NULL) {
146 talloc_free(tmp_ctx);
150 /* strip off the NTDS Settings */
151 if (!ldb_dn_remove_child_components(server_dn, 1)) {
152 talloc_free(tmp_ctx);
156 ret = dsdb_search_dn(s->samdb, tmp_ctx, &res, server_dn, attrs_server, 0);
157 if (ret != LDB_SUCCESS) {
158 talloc_free(tmp_ctx);
159 /* its OK for their server DN not to be in our database */
163 forest_dn = ldb_get_root_basedn(s->samdb);
164 if (forest_dn == NULL) {
165 talloc_free(tmp_ctx);
169 hostname = ldb_msg_find_attr_as_string(res->msgs[0], "dNSHostName", NULL);
170 if (hostname != NULL) {
172 if we have the dNSHostName attribute then we can use
173 the GC/hostname/realm SPN. All DCs should have this SPN
175 *target_principal = talloc_asprintf(mem_ctx, "GC/%s/%s",
177 samdb_dn_to_dns_domain(tmp_ctx, forest_dn));
178 talloc_free(tmp_ctx);
183 if we can't find the dNSHostName then we will try for the
184 E3514235-4B06-11D1-AB04-00C04FC2DCD2/${NTDSGUID}/${DNSDOMAIN}
185 SPN. To use that we need the DNS domain name of the target
186 DC. We find that by first looking for the msDS-HasDomainNCs
187 in the NTDSDSA object of the DC, and if we don't find that,
188 then we look for the hasMasterNCs attribute, and eliminate
189 the known schema and configuruation DNs. Despite how
190 bizarre this seems, Hongwei tells us that this is in fact
191 what windows does to find the SPN!!
193 ret = dsdb_search_dn(s->samdb, tmp_ctx, &res, ntds_dn, attrs_ntds, 0);
194 if (ret != LDB_SUCCESS) {
195 talloc_free(tmp_ctx);
199 nc_dn = ldb_msg_find_attr_as_dn(s->samdb, tmp_ctx, res->msgs[0], "msDS-HasDomainNCs");
201 dnsdomain = samdb_dn_to_dns_domain(tmp_ctx, nc_dn);
204 if (dnsdomain == NULL) {
205 struct ldb_message_element *el;
207 el = ldb_msg_find_element(res->msgs[0], "hasMasterNCs");
208 for (i=0; el && i<el->num_values; i++) {
209 nc_dn = ldb_dn_from_ldb_val(tmp_ctx, s->samdb, &el->values[i]);
211 ldb_dn_compare(ldb_get_config_basedn(s->samdb), nc_dn) == 0 ||
212 ldb_dn_compare(ldb_get_schema_basedn(s->samdb), nc_dn) == 0) {
215 /* it must be a domain DN, get the equivalent
217 dnsdomain = samdb_dn_to_dns_domain(tmp_ctx, nc_dn);
222 if (dnsdomain != NULL) {
223 *target_principal = talloc_asprintf(mem_ctx,
224 "E3514235-4B06-11D1-AB04-00C04FC2DCD2/%s/%s",
225 GUID_string(tmp_ctx, &rft->source_dsa_obj_guid),
229 talloc_free(tmp_ctx);
234 WERROR dreplsrv_out_connection_attach(struct dreplsrv_service *s,
235 const struct repsFromTo1 *rft,
236 struct dreplsrv_out_connection **_conn)
238 struct dreplsrv_out_connection *cur, *conn = NULL;
239 const char *hostname;
241 if (!rft->other_info) {
245 if (!rft->other_info->dns_name) {
249 hostname = rft->other_info->dns_name;
251 for (cur = s->connections; cur; cur = cur->next) {
252 if (strcmp(cur->binding->host, hostname) == 0) {
262 conn = talloc_zero(s, struct dreplsrv_out_connection);
263 W_ERROR_HAVE_NO_MEMORY(conn);
267 binding_str = talloc_asprintf(conn, "ncacn_ip_tcp:%s[krb5,seal]",
269 W_ERROR_HAVE_NO_MEMORY(binding_str);
270 nt_status = dcerpc_parse_binding(conn, binding_str, &conn->binding);
271 talloc_free(binding_str);
272 if (!NT_STATUS_IS_OK(nt_status)) {
273 return ntstatus_to_werror(nt_status);
276 /* use the GC principal for DRS replication */
277 nt_status = dreplsrv_get_target_principal(s, conn->binding,
278 rft, &conn->binding->target_principal);
279 if (!NT_STATUS_IS_OK(nt_status)) {
280 return ntstatus_to_werror(nt_status);
283 DLIST_ADD_END(s->connections, conn, struct dreplsrv_out_connection *);
285 DEBUG(4,("dreplsrv_out_connection_attach(%s): create\n", conn->binding->host));
287 DEBUG(4,("dreplsrv_out_connection_attach(%s): attach\n", conn->binding->host));
295 find an existing source dsa in a list
297 static struct dreplsrv_partition_source_dsa *dreplsrv_find_source_dsa(struct dreplsrv_partition_source_dsa *list,
300 struct dreplsrv_partition_source_dsa *s;
301 for (s=list; s; s=s->next) {
302 if (GUID_compare(&s->repsFrom1->source_dsa_obj_guid, guid) == 0) {
311 static WERROR dreplsrv_partition_add_source_dsa(struct dreplsrv_service *s,
312 struct dreplsrv_partition *p,
313 struct dreplsrv_partition_source_dsa **listp,
314 struct dreplsrv_partition_source_dsa *check_list,
315 const struct ldb_val *val)
318 enum ndr_err_code ndr_err;
319 struct dreplsrv_partition_source_dsa *source, *s2;
321 source = talloc_zero(p, struct dreplsrv_partition_source_dsa);
322 W_ERROR_HAVE_NO_MEMORY(source);
324 ndr_err = ndr_pull_struct_blob(val, source,
325 &source->_repsFromBlob,
326 (ndr_pull_flags_fn_t)ndr_pull_repsFromToBlob);
327 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
328 NTSTATUS nt_status = ndr_map_error2ntstatus(ndr_err);
330 return ntstatus_to_werror(nt_status);
332 /* NDR_PRINT_DEBUG(repsFromToBlob, &source->_repsFromBlob); */
333 if (source->_repsFromBlob.version != 1) {
335 return WERR_DS_DRA_INTERNAL_ERROR;
338 source->partition = p;
339 source->repsFrom1 = &source->_repsFromBlob.ctr.ctr1;
341 status = dreplsrv_out_connection_attach(s, source->repsFrom1, &source->conn);
342 W_ERROR_NOT_OK_RETURN(status);
345 dreplsrv_find_source_dsa(check_list, &source->repsFrom1->source_dsa_obj_guid)) {
346 /* its in the check list, don't add it again */
351 /* re-use an existing source if found */
352 for (s2=*listp; s2; s2=s2->next) {
353 if (GUID_compare(&s2->repsFrom1->source_dsa_obj_guid,
354 &source->repsFrom1->source_dsa_obj_guid) == 0) {
355 talloc_free(s2->repsFrom1->other_info);
356 *s2->repsFrom1 = *source->repsFrom1;
357 talloc_steal(s2, s2->repsFrom1->other_info);
363 DLIST_ADD_END(*listp, source, struct dreplsrv_partition_source_dsa *);
367 WERROR dreplsrv_partition_find_for_nc(struct dreplsrv_service *s,
368 struct GUID *nc_guid,
369 struct dom_sid *nc_sid,
370 const char *nc_dn_str,
371 struct dreplsrv_partition **_p)
373 struct dreplsrv_partition *p;
374 bool valid_sid, valid_guid;
375 struct dom_sid null_sid;
376 ZERO_STRUCT(null_sid);
380 valid_sid = nc_sid && !dom_sid_equal(&null_sid, nc_sid);
381 valid_guid = nc_guid && !GUID_all_zero(nc_guid);
383 if (!valid_sid && !valid_guid && !nc_dn_str) {
384 return WERR_DS_DRA_INVALID_PARAMETER;
387 for (p = s->partitions; p; p = p->next) {
388 if ((valid_guid && GUID_equal(&p->nc.guid, nc_guid))
389 || strequal(p->nc.dn, nc_dn_str)
390 || (valid_sid && dom_sid_equal(&p->nc.sid, nc_sid)))
392 /* fill in he right guid and sid if possible */
393 if (nc_guid && !valid_guid) {
394 dsdb_get_extended_dn_guid(p->dn, nc_guid, "GUID");
396 if (nc_sid && !valid_sid) {
397 dsdb_get_extended_dn_sid(p->dn, nc_sid, "SID");
404 return WERR_DS_DRA_BAD_NC;
407 WERROR dreplsrv_partition_source_dsa_by_guid(struct dreplsrv_partition *p,
408 const struct GUID *dsa_guid,
409 struct dreplsrv_partition_source_dsa **_dsa)
411 struct dreplsrv_partition_source_dsa *dsa;
413 SMB_ASSERT(dsa_guid != NULL);
414 SMB_ASSERT(!GUID_all_zero(dsa_guid));
417 for (dsa = p->sources; dsa; dsa = dsa->next) {
418 if (GUID_equal(dsa_guid, &dsa->repsFrom1->source_dsa_obj_guid)) {
424 return WERR_DS_DRA_NO_REPLICA;
427 WERROR dreplsrv_partition_source_dsa_by_dns(const struct dreplsrv_partition *p,
429 struct dreplsrv_partition_source_dsa **_dsa)
431 struct dreplsrv_partition_source_dsa *dsa;
433 SMB_ASSERT(dsa_dns != NULL);
436 for (dsa = p->sources; dsa; dsa = dsa->next) {
437 if (strequal(dsa_dns, dsa->repsFrom1->other_info->dns_name)) {
443 return WERR_DS_DRA_NO_REPLICA;
448 create a temporary dsa structure for a replication. This is needed
449 for the initial replication of a new partition, such as when a new
450 domain NC is created and we are a global catalog server
452 WERROR dreplsrv_partition_source_dsa_temporary(struct dreplsrv_partition *p,
454 const struct GUID *dsa_guid,
455 struct dreplsrv_partition_source_dsa **_dsa)
457 struct dreplsrv_partition_source_dsa *dsa;
460 dsa = talloc_zero(mem_ctx, struct dreplsrv_partition_source_dsa);
461 W_ERROR_HAVE_NO_MEMORY(dsa);
464 dsa->repsFrom1 = &dsa->_repsFromBlob.ctr.ctr1;
465 dsa->repsFrom1->replica_flags = 0;
466 dsa->repsFrom1->source_dsa_obj_guid = *dsa_guid;
468 dsa->repsFrom1->other_info = talloc_zero(dsa, struct repsFromTo1OtherInfo);
469 W_ERROR_HAVE_NO_MEMORY(dsa->repsFrom1->other_info);
471 dsa->repsFrom1->other_info->dns_name = samdb_ntds_msdcs_dns_name(p->service->samdb,
472 dsa->repsFrom1->other_info, dsa_guid);
473 W_ERROR_HAVE_NO_MEMORY(dsa->repsFrom1->other_info->dns_name);
475 werr = dreplsrv_out_connection_attach(p->service, dsa->repsFrom1, &dsa->conn);
476 if (!W_ERROR_IS_OK(werr)) {
477 DEBUG(0,(__location__ ": Failed to attach connection to %s\n",
478 ldb_dn_get_linearized(p->dn)));
489 static WERROR dreplsrv_refresh_partition(struct dreplsrv_service *s,
490 struct dreplsrv_partition *p)
494 struct ldb_message_element *orf_el = NULL;
495 struct ldb_result *r = NULL;
498 TALLOC_CTX *mem_ctx = talloc_new(p);
499 static const char *attrs[] = {
506 DEBUG(4, ("dreplsrv_refresh_partition(%s)\n",
507 ldb_dn_get_linearized(p->dn)));
509 ret = dsdb_search_dn(s->samdb, mem_ctx, &r, p->dn, attrs, DSDB_SEARCH_SHOW_EXTENDED_DN);
510 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
511 /* we haven't replicated the partition yet, but we
512 * can fill in the guid, sid etc from the partition DN */
514 } else if (ret != LDB_SUCCESS) {
515 talloc_free(mem_ctx);
521 talloc_free(discard_const(p->nc.dn));
523 p->nc.dn = ldb_dn_alloc_linearized(p, dn);
524 W_ERROR_HAVE_NO_MEMORY(p->nc.dn);
525 ntstatus = dsdb_get_extended_dn_guid(dn, &p->nc.guid, "GUID");
526 if (!NT_STATUS_IS_OK(ntstatus)) {
527 DEBUG(0,(__location__ ": unable to get GUID for %s: %s\n",
528 p->nc.dn, nt_errstr(ntstatus)));
529 talloc_free(mem_ctx);
530 return WERR_DS_DRA_INTERNAL_ERROR;
532 dsdb_get_extended_dn_sid(dn, &p->nc.sid, "SID");
534 talloc_free(p->uptodatevector.cursors);
535 talloc_free(p->uptodatevector_ex.cursors);
536 ZERO_STRUCT(p->uptodatevector);
537 ZERO_STRUCT(p->uptodatevector_ex);
539 ret = dsdb_load_udv_v2(s->samdb, p->dn, p, &p->uptodatevector.cursors, &p->uptodatevector.count);
540 if (ret != LDB_SUCCESS) {
541 DEBUG(4,(__location__ ": no UDV available for %s\n", ldb_dn_get_linearized(p->dn)));
546 if (r != NULL && (orf_el = ldb_msg_find_element(r->msgs[0], "repsFrom"))) {
547 for (i=0; i < orf_el->num_values; i++) {
548 status = dreplsrv_partition_add_source_dsa(s, p, &p->sources,
549 NULL, &orf_el->values[i]);
550 W_ERROR_NOT_OK_GOTO_DONE(status);
554 if (r != NULL && (orf_el = ldb_msg_find_element(r->msgs[0], "repsTo"))) {
555 for (i=0; i < orf_el->num_values; i++) {
556 status = dreplsrv_partition_add_source_dsa(s, p, &p->notifies,
557 p->sources, &orf_el->values[i]);
558 W_ERROR_NOT_OK_GOTO_DONE(status);
563 talloc_free(mem_ctx);
567 WERROR dreplsrv_refresh_partitions(struct dreplsrv_service *s)
570 struct dreplsrv_partition *p;
572 for (p = s->partitions; p; p = p->next) {
573 status = dreplsrv_refresh_partition(s, p);
574 W_ERROR_NOT_OK_RETURN(status);