s3: smbd: Make extract_snapshot_token() a wrapper for extract_snapshot_token_internal().
[Samba.git] / source4 / dsdb / repl / drepl_partitions.c
blobcc32d5904da8e59decad3d51111c8da288d962f0
1 /*
2 Unix SMB/CIFS Implementation.
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/>.
22 #include "includes.h"
23 #include "dsdb/samdb/samdb.h"
24 #include "auth/auth.h"
25 #include "samba/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"
37 #undef DBGC_CLASS
38 #define DBGC_CLASS DBGC_DRS_REPL
40 #undef strcasecmp
43 load the partitions list based on replicated NC attributes in our
44 NTDSDSA object
46 WERROR dreplsrv_load_partitions(struct dreplsrv_service *s)
48 WERROR status;
49 static const char *attrs[] = { "hasMasterNCs", "msDS-hasMasterNCs", "hasPartialReplicaNCs", "msDS-HasFullReplicaNCs", NULL };
50 unsigned int a;
51 int ret;
52 TALLOC_CTX *tmp_ctx;
53 struct ldb_result *res;
54 struct ldb_message_element *el;
55 struct ldb_dn *ntds_dn;
57 tmp_ctx = talloc_new(s);
58 W_ERROR_HAVE_NO_MEMORY(tmp_ctx);
60 ntds_dn = samdb_ntds_settings_dn(s->samdb, tmp_ctx);
61 if (!ntds_dn) {
62 DEBUG(1,(__location__ ": Unable to find ntds_dn: %s\n", ldb_errstring(s->samdb)));
63 talloc_free(tmp_ctx);
64 return WERR_DS_DRA_INTERNAL_ERROR;
67 ret = dsdb_search_dn(s->samdb, tmp_ctx, &res, ntds_dn, attrs, DSDB_SEARCH_SHOW_EXTENDED_DN);
68 if (ret != LDB_SUCCESS) {
69 DEBUG(1,("Searching for hasMasterNCs in NTDS DN failed: %s\n", ldb_errstring(s->samdb)));
70 talloc_free(tmp_ctx);
71 return WERR_DS_DRA_INTERNAL_ERROR;
74 for (a=0; attrs[a]; a++) {
75 int i;
77 el = ldb_msg_find_element(res->msgs[0], attrs[a]);
78 if (el == NULL) {
79 continue;
81 for (i=0; i<el->num_values; i++) {
82 struct ldb_dn *pdn;
83 struct dreplsrv_partition *p, *tp;
84 bool found;
86 pdn = ldb_dn_from_ldb_val(tmp_ctx, s->samdb, &el->values[i]);
87 if (pdn == NULL) {
88 talloc_free(tmp_ctx);
89 return WERR_DS_DRA_INTERNAL_ERROR;
91 if (!ldb_dn_validate(pdn)) {
92 return WERR_DS_DRA_INTERNAL_ERROR;
95 p = talloc_zero(s, struct dreplsrv_partition);
96 W_ERROR_HAVE_NO_MEMORY(p);
98 p->dn = talloc_steal(p, pdn);
99 p->service = s;
101 if (strcasecmp(attrs[a], "hasPartialReplicaNCs") == 0) {
102 p->partial_replica = true;
103 } else if (strcasecmp(attrs[a], "msDS-HasFullReplicaNCs") == 0) {
104 p->rodc_replica = true;
107 /* Do not add partitions more than once */
108 found = false;
109 for (tp = s->partitions; tp; tp = tp->next) {
110 if (ldb_dn_compare(tp->dn, p->dn) == 0) {
111 found = true;
112 break;
115 if (found) {
116 talloc_free(p);
117 continue;
120 DLIST_ADD(s->partitions, p);
121 DEBUG(2, ("dreplsrv_partition[%s] loaded\n", ldb_dn_get_linearized(p->dn)));
125 talloc_free(tmp_ctx);
127 status = dreplsrv_refresh_partitions(s);
128 W_ERROR_NOT_OK_RETURN(status);
130 return WERR_OK;
134 Check if particular SPN exists for an account
136 static bool dreplsrv_spn_exists(struct ldb_context *samdb, struct ldb_dn *account_dn,
137 const char *principal_name)
139 TALLOC_CTX *tmp_ctx;
140 const char *attrs_empty[] = { NULL };
141 int ret;
142 struct ldb_result *res;
144 tmp_ctx = talloc_new(samdb);
146 ret = dsdb_search(samdb, tmp_ctx, &res, account_dn, LDB_SCOPE_BASE, attrs_empty,
147 0, "servicePrincipalName=%s",
148 ldb_binary_encode_string(tmp_ctx, principal_name));
149 if (ret != LDB_SUCCESS || res->count != 1) {
150 talloc_free(tmp_ctx);
151 return false;
154 talloc_free(tmp_ctx);
155 return true;
159 work out the principal to use for DRS replication connections
161 static NTSTATUS dreplsrv_get_target_principal(struct dreplsrv_service *s,
162 TALLOC_CTX *mem_ctx,
163 const struct repsFromTo1 *rft,
164 char **target_principal)
166 TALLOC_CTX *tmp_ctx;
167 struct ldb_result *res;
168 const char *attrs_server[] = { "dNSHostName", "serverReference", NULL };
169 const char *attrs_ntds[] = { "msDS-HasDomainNCs", "hasMasterNCs", NULL };
170 int ret;
171 const char *hostname, *dnsdomain=NULL;
172 struct ldb_dn *ntds_dn, *server_dn, *computer_dn;
173 struct ldb_dn *forest_dn, *nc_dn;
175 *target_principal = NULL;
177 tmp_ctx = talloc_new(mem_ctx);
179 /* we need to find their hostname */
180 ret = dsdb_find_dn_by_guid(s->samdb, tmp_ctx, &rft->source_dsa_obj_guid, 0, &ntds_dn);
181 if (ret != LDB_SUCCESS) {
182 talloc_free(tmp_ctx);
183 /* its OK for their NTDSDSA DN not to be in our database */
184 return NT_STATUS_OK;
187 server_dn = ldb_dn_copy(tmp_ctx, ntds_dn);
188 if (server_dn == NULL) {
189 talloc_free(tmp_ctx);
190 return NT_STATUS_OK;
193 /* strip off the NTDS Settings */
194 if (!ldb_dn_remove_child_components(server_dn, 1)) {
195 talloc_free(tmp_ctx);
196 return NT_STATUS_OK;
199 ret = dsdb_search_dn(s->samdb, tmp_ctx, &res, server_dn, attrs_server, 0);
200 if (ret != LDB_SUCCESS) {
201 talloc_free(tmp_ctx);
202 /* its OK for their server DN not to be in our database */
203 return NT_STATUS_OK;
206 forest_dn = ldb_get_root_basedn(s->samdb);
207 if (forest_dn == NULL) {
208 talloc_free(tmp_ctx);
209 return NT_STATUS_OK;
212 hostname = ldb_msg_find_attr_as_string(res->msgs[0], "dNSHostName", NULL);
213 computer_dn = ldb_msg_find_attr_as_dn(s->samdb, tmp_ctx, res->msgs[0], "serverReference");
214 if (hostname != NULL && computer_dn != NULL) {
215 char *local_principal;
218 if we have the dNSHostName attribute then we can use
219 the GC/hostname/realm SPN. All DCs should have this SPN
221 Windows DC may set up it's dNSHostName before setting up
222 GC/xx/xx SPN. So make sure it exists, before using it.
224 local_principal = talloc_asprintf(mem_ctx, "GC/%s/%s",
225 hostname,
226 samdb_dn_to_dns_domain(tmp_ctx, forest_dn));
227 if (dreplsrv_spn_exists(s->samdb, computer_dn, local_principal)) {
228 *target_principal = local_principal;
229 talloc_free(tmp_ctx);
230 return NT_STATUS_OK;
233 talloc_free(local_principal);
237 if we can't find the dNSHostName then we will try for the
238 E3514235-4B06-11D1-AB04-00C04FC2DCD2/${NTDSGUID}/${DNSDOMAIN}
239 SPN. To use that we need the DNS domain name of the target
240 DC. We find that by first looking for the msDS-HasDomainNCs
241 in the NTDSDSA object of the DC, and if we don't find that,
242 then we look for the hasMasterNCs attribute, and eliminate
243 the known schema and configuruation DNs. Despite how
244 bizarre this seems, Hongwei tells us that this is in fact
245 what windows does to find the SPN!!
247 ret = dsdb_search_dn(s->samdb, tmp_ctx, &res, ntds_dn, attrs_ntds, 0);
248 if (ret != LDB_SUCCESS) {
249 talloc_free(tmp_ctx);
250 return NT_STATUS_OK;
253 nc_dn = ldb_msg_find_attr_as_dn(s->samdb, tmp_ctx, res->msgs[0], "msDS-HasDomainNCs");
254 if (nc_dn != NULL) {
255 dnsdomain = samdb_dn_to_dns_domain(tmp_ctx, nc_dn);
258 if (dnsdomain == NULL) {
259 struct ldb_message_element *el;
260 int i;
261 el = ldb_msg_find_element(res->msgs[0], "hasMasterNCs");
262 for (i=0; el && i<el->num_values; i++) {
263 nc_dn = ldb_dn_from_ldb_val(tmp_ctx, s->samdb, &el->values[i]);
264 if (nc_dn == NULL ||
265 ldb_dn_compare(ldb_get_config_basedn(s->samdb), nc_dn) == 0 ||
266 ldb_dn_compare(ldb_get_schema_basedn(s->samdb), nc_dn) == 0) {
267 continue;
269 /* it must be a domain DN, get the equivalent
270 DNS domain name */
271 dnsdomain = samdb_dn_to_dns_domain(tmp_ctx, nc_dn);
272 break;
276 if (dnsdomain != NULL) {
277 *target_principal = talloc_asprintf(mem_ctx,
278 "E3514235-4B06-11D1-AB04-00C04FC2DCD2/%s/%s@%s",
279 GUID_string(tmp_ctx, &rft->source_dsa_obj_guid),
280 dnsdomain, dnsdomain);
283 talloc_free(tmp_ctx);
284 return NT_STATUS_OK;
288 WERROR dreplsrv_out_connection_attach(struct dreplsrv_service *s,
289 const struct repsFromTo1 *rft,
290 struct dreplsrv_out_connection **_conn)
292 struct dreplsrv_out_connection *cur, *conn = NULL;
293 const char *hostname;
295 if (!rft->other_info) {
296 return WERR_FOOBAR;
299 if (!rft->other_info->dns_name) {
300 return WERR_FOOBAR;
303 hostname = rft->other_info->dns_name;
305 for (cur = s->connections; cur; cur = cur->next) {
306 const char *host;
308 host = dcerpc_binding_get_string_option(cur->binding, "host");
309 if (host == NULL) {
310 continue;
313 if (strcmp(host, hostname) == 0) {
314 conn = cur;
315 break;
319 if (!conn) {
320 NTSTATUS nt_status;
321 char *binding_str;
322 char *target_principal = NULL;
324 conn = talloc_zero(s, struct dreplsrv_out_connection);
325 W_ERROR_HAVE_NO_MEMORY(conn);
327 conn->service = s;
329 binding_str = talloc_asprintf(conn, "ncacn_ip_tcp:%s[krb5,seal]",
330 hostname);
331 W_ERROR_HAVE_NO_MEMORY(binding_str);
332 nt_status = dcerpc_parse_binding(conn, binding_str, &conn->binding);
333 talloc_free(binding_str);
334 if (!NT_STATUS_IS_OK(nt_status)) {
335 return ntstatus_to_werror(nt_status);
338 /* use the GC principal for DRS replication */
339 nt_status = dreplsrv_get_target_principal(s, conn->binding,
340 rft, &target_principal);
341 if (!NT_STATUS_IS_OK(nt_status)) {
342 return ntstatus_to_werror(nt_status);
345 nt_status = dcerpc_binding_set_string_option(conn->binding,
346 "target_principal",
347 target_principal);
348 TALLOC_FREE(target_principal);
349 if (!NT_STATUS_IS_OK(nt_status)) {
350 return ntstatus_to_werror(nt_status);
353 DLIST_ADD_END(s->connections, conn);
355 DEBUG(4,("dreplsrv_out_connection_attach(%s): create\n", hostname));
356 } else {
357 DEBUG(4,("dreplsrv_out_connection_attach(%s): attach\n", hostname));
360 *_conn = conn;
361 return WERR_OK;
365 find an existing source dsa in a list
367 static struct dreplsrv_partition_source_dsa *dreplsrv_find_source_dsa(struct dreplsrv_partition_source_dsa *list,
368 struct GUID *guid)
370 struct dreplsrv_partition_source_dsa *s;
371 for (s=list; s; s=s->next) {
372 if (GUID_equal(&s->repsFrom1->source_dsa_obj_guid, guid)) {
373 return s;
376 return NULL;
381 static WERROR dreplsrv_partition_add_source_dsa(struct dreplsrv_service *s,
382 struct dreplsrv_partition *p,
383 struct dreplsrv_partition_source_dsa **listp,
384 struct dreplsrv_partition_source_dsa *check_list,
385 const struct ldb_val *val)
387 WERROR status;
388 enum ndr_err_code ndr_err;
389 struct dreplsrv_partition_source_dsa *source, *s2;
391 source = talloc_zero(p, struct dreplsrv_partition_source_dsa);
392 W_ERROR_HAVE_NO_MEMORY(source);
394 ndr_err = ndr_pull_struct_blob(val, source,
395 &source->_repsFromBlob,
396 (ndr_pull_flags_fn_t)ndr_pull_repsFromToBlob);
397 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
398 NTSTATUS nt_status = ndr_map_error2ntstatus(ndr_err);
399 talloc_free(source);
400 return ntstatus_to_werror(nt_status);
402 /* NDR_PRINT_DEBUG(repsFromToBlob, &source->_repsFromBlob); */
403 if (source->_repsFromBlob.version != 1) {
404 talloc_free(source);
405 return WERR_DS_DRA_INTERNAL_ERROR;
408 source->partition = p;
409 source->repsFrom1 = &source->_repsFromBlob.ctr.ctr1;
411 status = dreplsrv_out_connection_attach(s, source->repsFrom1, &source->conn);
412 W_ERROR_NOT_OK_RETURN(status);
414 if (check_list &&
415 dreplsrv_find_source_dsa(check_list, &source->repsFrom1->source_dsa_obj_guid)) {
416 /* its in the check list, don't add it again */
417 talloc_free(source);
418 return WERR_OK;
421 /* re-use an existing source if found */
422 for (s2=*listp; s2; s2=s2->next) {
423 if (GUID_equal(&s2->repsFrom1->source_dsa_obj_guid,
424 &source->repsFrom1->source_dsa_obj_guid)) {
425 talloc_free(s2->repsFrom1->other_info);
426 *s2->repsFrom1 = *source->repsFrom1;
427 talloc_steal(s2, s2->repsFrom1->other_info);
428 talloc_free(source);
429 return WERR_OK;
433 DLIST_ADD_END(*listp, source);
434 return WERR_OK;
438 * Find a partition when given a NC
439 * If the NC can't be found it will return BAD_NC
440 * Initial checks for invalid parameters have to be done beforehand
442 WERROR dreplsrv_partition_find_for_nc(struct dreplsrv_service *s,
443 struct GUID *nc_guid,
444 struct dom_sid *nc_sid,
445 const char *nc_dn_str,
446 struct dreplsrv_partition **_p)
448 struct dreplsrv_partition *p;
449 bool valid_sid, valid_guid;
451 SMB_ASSERT(_p);
453 valid_sid = nc_sid && !is_null_sid(nc_sid);
454 valid_guid = nc_guid && !GUID_all_zero(nc_guid);
456 if (!valid_sid && !valid_guid && (!nc_dn_str)) {
457 return WERR_DS_DRA_BAD_NC;
460 for (p = s->partitions; p; p = p->next) {
461 if ((valid_guid && GUID_equal(&p->nc.guid, nc_guid))
462 || strequal(p->nc.dn, nc_dn_str)
463 || (valid_sid && dom_sid_equal(&p->nc.sid, nc_sid)))
465 /* fill in he right guid and sid if possible */
466 if (nc_guid && !valid_guid) {
467 dsdb_get_extended_dn_guid(p->dn, nc_guid, "GUID");
469 if (nc_sid && !valid_sid) {
470 dsdb_get_extended_dn_sid(p->dn, nc_sid, "SID");
472 *_p = p;
473 return WERR_OK;
477 return WERR_DS_DRA_BAD_NC;
480 WERROR dreplsrv_partition_source_dsa_by_guid(struct dreplsrv_partition *p,
481 const struct GUID *dsa_guid,
482 struct dreplsrv_partition_source_dsa **_dsa)
484 struct dreplsrv_partition_source_dsa *dsa;
486 SMB_ASSERT(dsa_guid != NULL);
487 SMB_ASSERT(!GUID_all_zero(dsa_guid));
488 SMB_ASSERT(_dsa);
490 for (dsa = p->sources; dsa; dsa = dsa->next) {
491 if (GUID_equal(dsa_guid, &dsa->repsFrom1->source_dsa_obj_guid)) {
492 *_dsa = dsa;
493 return WERR_OK;
497 return WERR_DS_DRA_NO_REPLICA;
500 WERROR dreplsrv_partition_source_dsa_by_dns(const struct dreplsrv_partition *p,
501 const char *dsa_dns,
502 struct dreplsrv_partition_source_dsa **_dsa)
504 struct dreplsrv_partition_source_dsa *dsa;
506 SMB_ASSERT(dsa_dns != NULL);
507 SMB_ASSERT(_dsa);
509 for (dsa = p->sources; dsa; dsa = dsa->next) {
510 if (strequal(dsa_dns, dsa->repsFrom1->other_info->dns_name)) {
511 *_dsa = dsa;
512 return WERR_OK;
516 return WERR_DS_DRA_NO_REPLICA;
521 create a temporary dsa structure for a replication. This is needed
522 for the initial replication of a new partition, such as when a new
523 domain NC is created and we are a global catalog server
525 WERROR dreplsrv_partition_source_dsa_temporary(struct dreplsrv_partition *p,
526 TALLOC_CTX *mem_ctx,
527 const struct GUID *dsa_guid,
528 struct dreplsrv_partition_source_dsa **_dsa)
530 struct dreplsrv_partition_source_dsa *dsa;
531 WERROR werr;
533 dsa = talloc_zero(mem_ctx, struct dreplsrv_partition_source_dsa);
534 W_ERROR_HAVE_NO_MEMORY(dsa);
536 dsa->partition = p;
537 dsa->repsFrom1 = &dsa->_repsFromBlob.ctr.ctr1;
538 dsa->repsFrom1->replica_flags = 0;
539 dsa->repsFrom1->source_dsa_obj_guid = *dsa_guid;
541 dsa->repsFrom1->other_info = talloc_zero(dsa, struct repsFromTo1OtherInfo);
542 W_ERROR_HAVE_NO_MEMORY(dsa->repsFrom1->other_info);
544 dsa->repsFrom1->other_info->dns_name = samdb_ntds_msdcs_dns_name(p->service->samdb,
545 dsa->repsFrom1->other_info, dsa_guid);
546 W_ERROR_HAVE_NO_MEMORY(dsa->repsFrom1->other_info->dns_name);
548 werr = dreplsrv_out_connection_attach(p->service, dsa->repsFrom1, &dsa->conn);
549 if (!W_ERROR_IS_OK(werr)) {
550 DEBUG(0,(__location__ ": Failed to attach connection to %s\n",
551 ldb_dn_get_linearized(p->dn)));
552 talloc_free(dsa);
553 return werr;
556 *_dsa = dsa;
558 return WERR_OK;
562 static WERROR dreplsrv_refresh_partition(struct dreplsrv_service *s,
563 struct dreplsrv_partition *p)
565 WERROR status;
566 NTSTATUS ntstatus;
567 struct ldb_message_element *orf_el = NULL;
568 struct ldb_result *r = NULL;
569 unsigned int i;
570 int ret;
571 TALLOC_CTX *mem_ctx = talloc_new(p);
572 static const char *attrs[] = {
573 "repsFrom",
574 "repsTo",
575 NULL
577 struct ldb_dn *dn;
579 DEBUG(4, ("dreplsrv_refresh_partition(%s)\n",
580 ldb_dn_get_linearized(p->dn)));
582 ret = dsdb_search_dn(s->samdb, mem_ctx, &r, p->dn, attrs, DSDB_SEARCH_SHOW_EXTENDED_DN);
583 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
584 /* we haven't replicated the partition yet, but we
585 * can fill in the guid, sid etc from the partition DN */
586 dn = p->dn;
587 } else if (ret != LDB_SUCCESS) {
588 talloc_free(mem_ctx);
589 return WERR_FOOBAR;
590 } else {
591 dn = r->msgs[0]->dn;
594 talloc_free(discard_const(p->nc.dn));
595 ZERO_STRUCT(p->nc);
596 p->nc.dn = ldb_dn_alloc_linearized(p, dn);
597 W_ERROR_HAVE_NO_MEMORY(p->nc.dn);
598 ntstatus = dsdb_get_extended_dn_guid(dn, &p->nc.guid, "GUID");
599 if (!NT_STATUS_IS_OK(ntstatus)) {
600 DEBUG(0,(__location__ ": unable to get GUID for %s: %s\n",
601 p->nc.dn, nt_errstr(ntstatus)));
602 talloc_free(mem_ctx);
603 return WERR_DS_DRA_INTERNAL_ERROR;
605 dsdb_get_extended_dn_sid(dn, &p->nc.sid, "SID");
607 talloc_free(p->uptodatevector.cursors);
608 talloc_free(p->uptodatevector_ex.cursors);
609 ZERO_STRUCT(p->uptodatevector);
610 ZERO_STRUCT(p->uptodatevector_ex);
612 ret = dsdb_load_udv_v2(s->samdb, p->dn, p, &p->uptodatevector.cursors, &p->uptodatevector.count);
613 if (ret != LDB_SUCCESS) {
614 DEBUG(4,(__location__ ": no UDV available for %s\n", ldb_dn_get_linearized(p->dn)));
617 status = WERR_OK;
619 if (r != NULL && (orf_el = ldb_msg_find_element(r->msgs[0], "repsFrom"))) {
620 for (i=0; i < orf_el->num_values; i++) {
621 status = dreplsrv_partition_add_source_dsa(s, p, &p->sources,
622 NULL, &orf_el->values[i]);
623 W_ERROR_NOT_OK_GOTO_DONE(status);
627 if (r != NULL && (orf_el = ldb_msg_find_element(r->msgs[0], "repsTo"))) {
628 for (i=0; i < orf_el->num_values; i++) {
629 status = dreplsrv_partition_add_source_dsa(s, p, &p->notifies,
630 p->sources, &orf_el->values[i]);
631 W_ERROR_NOT_OK_GOTO_DONE(status);
635 done:
636 talloc_free(mem_ctx);
637 return status;
640 WERROR dreplsrv_refresh_partitions(struct dreplsrv_service *s)
642 WERROR status;
643 struct dreplsrv_partition *p;
645 for (p = s->partitions; p; p = p->next) {
646 status = dreplsrv_refresh_partition(s, p);
647 W_ERROR_NOT_OK_RETURN(status);
650 return WERR_OK;