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/>.
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"
38 #define DBGC_CLASS DBGC_DRS_REPL
43 load the partitions list based on replicated NC attributes in our
46 WERROR
dreplsrv_load_partitions(struct dreplsrv_service
*s
)
49 static const char *attrs
[] = { "hasMasterNCs", "msDS-hasMasterNCs", "hasPartialReplicaNCs", "msDS-HasFullReplicaNCs", NULL
};
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
);
62 DEBUG(1,(__location__
": Unable to find ntds_dn: %s\n", ldb_errstring(s
->samdb
)));
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
)));
71 return WERR_DS_DRA_INTERNAL_ERROR
;
74 for (a
=0; attrs
[a
]; a
++) {
77 el
= ldb_msg_find_element(res
->msgs
[0], attrs
[a
]);
81 for (i
=0; i
<el
->num_values
; i
++) {
83 struct dreplsrv_partition
*p
, *tp
;
86 pdn
= ldb_dn_from_ldb_val(tmp_ctx
, s
->samdb
, &el
->values
[i
]);
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
);
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 */
109 for (tp
= s
->partitions
; tp
; tp
= tp
->next
) {
110 if (ldb_dn_compare(tp
->dn
, p
->dn
) == 0) {
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
);
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
)
140 const char *attrs_empty
[] = { NULL
};
142 struct ldb_result
*res
;
143 const char *principal_name_encoded
= NULL
;
145 tmp_ctx
= talloc_new(samdb
);
146 if (tmp_ctx
== NULL
) {
150 principal_name_encoded
= ldb_binary_encode_string(tmp_ctx
, principal_name
);
151 if (principal_name_encoded
== NULL
) {
152 talloc_free(tmp_ctx
);
156 ret
= dsdb_search(samdb
, tmp_ctx
, &res
, account_dn
, LDB_SCOPE_BASE
, attrs_empty
,
157 0, "servicePrincipalName=%s",
158 principal_name_encoded
);
159 if (ret
!= LDB_SUCCESS
|| res
->count
!= 1) {
160 talloc_free(tmp_ctx
);
164 talloc_free(tmp_ctx
);
169 work out the principal to use for DRS replication connections
171 static NTSTATUS
dreplsrv_get_target_principal(struct dreplsrv_service
*s
,
173 const struct repsFromTo1
*rft
,
174 char **target_principal
)
177 struct ldb_result
*res
;
178 const char *attrs_server
[] = { "dNSHostName", "serverReference", NULL
};
179 const char *attrs_ntds
[] = { "msDS-HasDomainNCs", "hasMasterNCs", NULL
};
181 const char *hostname
, *dnsdomain
=NULL
;
182 struct ldb_dn
*ntds_dn
, *server_dn
, *computer_dn
;
183 struct ldb_dn
*forest_dn
, *nc_dn
;
185 *target_principal
= NULL
;
187 tmp_ctx
= talloc_new(mem_ctx
);
189 /* we need to find their hostname */
190 ret
= dsdb_find_dn_by_guid(s
->samdb
, tmp_ctx
, &rft
->source_dsa_obj_guid
, 0, &ntds_dn
);
191 if (ret
!= LDB_SUCCESS
) {
192 talloc_free(tmp_ctx
);
193 /* its OK for their NTDSDSA DN not to be in our database */
197 server_dn
= ldb_dn_copy(tmp_ctx
, ntds_dn
);
198 if (server_dn
== NULL
) {
199 talloc_free(tmp_ctx
);
203 /* strip off the NTDS Settings */
204 if (!ldb_dn_remove_child_components(server_dn
, 1)) {
205 talloc_free(tmp_ctx
);
209 ret
= dsdb_search_dn(s
->samdb
, tmp_ctx
, &res
, server_dn
, attrs_server
, 0);
210 if (ret
!= LDB_SUCCESS
) {
211 talloc_free(tmp_ctx
);
212 /* its OK for their server DN not to be in our database */
216 forest_dn
= ldb_get_root_basedn(s
->samdb
);
217 if (forest_dn
== NULL
) {
218 talloc_free(tmp_ctx
);
222 hostname
= ldb_msg_find_attr_as_string(res
->msgs
[0], "dNSHostName", NULL
);
223 computer_dn
= ldb_msg_find_attr_as_dn(s
->samdb
, tmp_ctx
, res
->msgs
[0], "serverReference");
224 if (hostname
!= NULL
&& computer_dn
!= 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 (local_principal
== NULL
) {
238 talloc_free(tmp_ctx
);
239 return NT_STATUS_NO_MEMORY
;
241 if (dreplsrv_spn_exists(s
->samdb
, computer_dn
, local_principal
)) {
242 *target_principal
= local_principal
;
243 talloc_free(tmp_ctx
);
247 talloc_free(local_principal
);
251 if we can't find the dNSHostName then we will try for the
252 E3514235-4B06-11D1-AB04-00C04FC2DCD2/${NTDSGUID}/${DNSDOMAIN}
253 SPN. To use that we need the DNS domain name of the target
254 DC. We find that by first looking for the msDS-HasDomainNCs
255 in the NTDSDSA object of the DC, and if we don't find that,
256 then we look for the hasMasterNCs attribute, and eliminate
257 the known schema and configuration DNs. Despite how
258 bizarre this seems, Hongwei tells us that this is in fact
259 what windows does to find the SPN!!
261 ret
= dsdb_search_dn(s
->samdb
, tmp_ctx
, &res
, ntds_dn
, attrs_ntds
, 0);
262 if (ret
!= LDB_SUCCESS
) {
263 talloc_free(tmp_ctx
);
267 nc_dn
= ldb_msg_find_attr_as_dn(s
->samdb
, tmp_ctx
, res
->msgs
[0], "msDS-HasDomainNCs");
269 dnsdomain
= samdb_dn_to_dns_domain(tmp_ctx
, nc_dn
);
272 if (dnsdomain
== NULL
) {
273 struct ldb_message_element
*el
;
275 el
= ldb_msg_find_element(res
->msgs
[0], "hasMasterNCs");
276 for (i
=0; el
&& i
<el
->num_values
; i
++) {
277 nc_dn
= ldb_dn_from_ldb_val(tmp_ctx
, s
->samdb
, &el
->values
[i
]);
279 ldb_dn_compare(ldb_get_config_basedn(s
->samdb
), nc_dn
) == 0 ||
280 ldb_dn_compare(ldb_get_schema_basedn(s
->samdb
), nc_dn
) == 0) {
283 /* it must be a domain DN, get the equivalent
285 dnsdomain
= samdb_dn_to_dns_domain(tmp_ctx
, nc_dn
);
290 if (dnsdomain
!= NULL
) {
291 *target_principal
= talloc_asprintf(mem_ctx
,
292 "E3514235-4B06-11D1-AB04-00C04FC2DCD2/%s/%s@%s",
293 GUID_string(tmp_ctx
, &rft
->source_dsa_obj_guid
),
294 dnsdomain
, dnsdomain
);
297 talloc_free(tmp_ctx
);
302 WERROR
dreplsrv_out_connection_attach(struct dreplsrv_service
*s
,
303 const struct repsFromTo1
*rft
,
304 struct dreplsrv_out_connection
**_conn
)
306 struct dreplsrv_out_connection
*cur
, *conn
= NULL
;
307 const char *hostname
;
309 if (!rft
->other_info
) {
313 if (!rft
->other_info
->dns_name
) {
317 hostname
= rft
->other_info
->dns_name
;
319 for (cur
= s
->connections
; cur
; cur
= cur
->next
) {
322 host
= dcerpc_binding_get_string_option(cur
->binding
, "host");
327 if (strcmp(host
, hostname
) == 0) {
336 char *target_principal
= NULL
;
338 conn
= talloc_zero(s
, struct dreplsrv_out_connection
);
339 W_ERROR_HAVE_NO_MEMORY(conn
);
343 binding_str
= talloc_asprintf(conn
, "ncacn_ip_tcp:%s[krb5,seal]",
345 W_ERROR_HAVE_NO_MEMORY(binding_str
);
346 nt_status
= dcerpc_parse_binding(conn
, binding_str
, &conn
->binding
);
347 talloc_free(binding_str
);
348 if (!NT_STATUS_IS_OK(nt_status
)) {
349 return ntstatus_to_werror(nt_status
);
352 /* use the GC principal for DRS replication */
353 nt_status
= dreplsrv_get_target_principal(s
, conn
->binding
,
354 rft
, &target_principal
);
355 if (!NT_STATUS_IS_OK(nt_status
)) {
356 return ntstatus_to_werror(nt_status
);
359 nt_status
= dcerpc_binding_set_string_option(conn
->binding
,
362 TALLOC_FREE(target_principal
);
363 if (!NT_STATUS_IS_OK(nt_status
)) {
364 return ntstatus_to_werror(nt_status
);
367 DLIST_ADD_END(s
->connections
, conn
);
369 DEBUG(4,("dreplsrv_out_connection_attach(%s): create\n", hostname
));
371 DEBUG(4,("dreplsrv_out_connection_attach(%s): attach\n", hostname
));
379 find an existing source dsa in a list
381 static struct dreplsrv_partition_source_dsa
*dreplsrv_find_source_dsa(struct dreplsrv_partition_source_dsa
*list
,
384 struct dreplsrv_partition_source_dsa
*s
;
385 for (s
=list
; s
; s
=s
->next
) {
386 if (GUID_equal(&s
->repsFrom1
->source_dsa_obj_guid
, guid
)) {
395 static WERROR
dreplsrv_partition_add_source_dsa(struct dreplsrv_service
*s
,
396 struct dreplsrv_partition
*p
,
397 struct dreplsrv_partition_source_dsa
**listp
,
398 struct dreplsrv_partition_source_dsa
*check_list
,
399 const struct ldb_val
*val
)
402 enum ndr_err_code ndr_err
;
403 struct dreplsrv_partition_source_dsa
*source
, *s2
;
405 source
= talloc_zero(p
, struct dreplsrv_partition_source_dsa
);
406 W_ERROR_HAVE_NO_MEMORY(source
);
408 ndr_err
= ndr_pull_struct_blob(val
, source
,
409 &source
->_repsFromBlob
,
410 (ndr_pull_flags_fn_t
)ndr_pull_repsFromToBlob
);
411 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err
)) {
412 NTSTATUS nt_status
= ndr_map_error2ntstatus(ndr_err
);
414 return ntstatus_to_werror(nt_status
);
416 /* NDR_PRINT_DEBUG(repsFromToBlob, &source->_repsFromBlob); */
417 if (source
->_repsFromBlob
.version
!= 1) {
419 return WERR_DS_DRA_INTERNAL_ERROR
;
422 source
->partition
= p
;
423 source
->repsFrom1
= &source
->_repsFromBlob
.ctr
.ctr1
;
425 status
= dreplsrv_out_connection_attach(s
, source
->repsFrom1
, &source
->conn
);
426 W_ERROR_NOT_OK_RETURN(status
);
429 dreplsrv_find_source_dsa(check_list
, &source
->repsFrom1
->source_dsa_obj_guid
)) {
430 /* its in the check list, don't add it again */
435 /* re-use an existing source if found */
436 for (s2
=*listp
; s2
; s2
=s2
->next
) {
437 if (GUID_equal(&s2
->repsFrom1
->source_dsa_obj_guid
,
438 &source
->repsFrom1
->source_dsa_obj_guid
)) {
439 talloc_free(s2
->repsFrom1
->other_info
);
440 *s2
->repsFrom1
= *source
->repsFrom1
;
441 talloc_steal(s2
, s2
->repsFrom1
->other_info
);
447 DLIST_ADD_END(*listp
, source
);
452 * Find a partition when given a NC
453 * If the NC can't be found it will return BAD_NC
454 * Initial checks for invalid parameters have to be done beforehand
456 WERROR
dreplsrv_partition_find_for_nc(struct dreplsrv_service
*s
,
457 struct GUID
*nc_guid
,
458 struct dom_sid
*nc_sid
,
459 const char *nc_dn_str
,
460 struct dreplsrv_partition
**_p
)
462 struct dreplsrv_partition
*p
;
463 bool valid_sid
, valid_guid
;
467 valid_sid
= nc_sid
&& !is_null_sid(nc_sid
);
468 valid_guid
= nc_guid
&& !GUID_all_zero(nc_guid
);
470 if (!valid_sid
&& !valid_guid
&& (!nc_dn_str
)) {
471 return WERR_DS_DRA_BAD_NC
;
474 for (p
= s
->partitions
; p
; p
= p
->next
) {
475 if ((valid_guid
&& GUID_equal(&p
->nc
.guid
, nc_guid
))
476 || strequal(p
->nc
.dn
, nc_dn_str
)
477 || (valid_sid
&& dom_sid_equal(&p
->nc
.sid
, nc_sid
)))
479 /* fill in the right guid and sid if possible */
480 if (nc_guid
&& !valid_guid
) {
481 dsdb_get_extended_dn_guid(p
->dn
, nc_guid
, "GUID");
483 if (nc_sid
&& !valid_sid
) {
484 dsdb_get_extended_dn_sid(p
->dn
, nc_sid
, "SID");
491 return WERR_DS_DRA_BAD_NC
;
494 WERROR
dreplsrv_partition_source_dsa_by_guid(struct dreplsrv_partition
*p
,
495 const struct GUID
*dsa_guid
,
496 struct dreplsrv_partition_source_dsa
**_dsa
)
498 struct dreplsrv_partition_source_dsa
*dsa
;
500 SMB_ASSERT(dsa_guid
!= NULL
);
501 SMB_ASSERT(!GUID_all_zero(dsa_guid
));
504 for (dsa
= p
->sources
; dsa
; dsa
= dsa
->next
) {
505 if (GUID_equal(dsa_guid
, &dsa
->repsFrom1
->source_dsa_obj_guid
)) {
511 return WERR_DS_DRA_NO_REPLICA
;
514 WERROR
dreplsrv_partition_source_dsa_by_dns(const struct dreplsrv_partition
*p
,
516 struct dreplsrv_partition_source_dsa
**_dsa
)
518 struct dreplsrv_partition_source_dsa
*dsa
;
520 SMB_ASSERT(dsa_dns
!= NULL
);
523 for (dsa
= p
->sources
; dsa
; dsa
= dsa
->next
) {
524 if (strequal(dsa_dns
, dsa
->repsFrom1
->other_info
->dns_name
)) {
530 return WERR_DS_DRA_NO_REPLICA
;
535 create a temporary dsa structure for a replication. This is needed
536 for the initial replication of a new partition, such as when a new
537 domain NC is created and we are a global catalog server
539 WERROR
dreplsrv_partition_source_dsa_temporary(struct dreplsrv_partition
*p
,
541 const struct GUID
*dsa_guid
,
542 struct dreplsrv_partition_source_dsa
**_dsa
)
544 struct dreplsrv_partition_source_dsa
*dsa
;
547 dsa
= talloc_zero(mem_ctx
, struct dreplsrv_partition_source_dsa
);
548 W_ERROR_HAVE_NO_MEMORY(dsa
);
551 dsa
->repsFrom1
= &dsa
->_repsFromBlob
.ctr
.ctr1
;
552 dsa
->repsFrom1
->replica_flags
= 0;
553 dsa
->repsFrom1
->source_dsa_obj_guid
= *dsa_guid
;
555 dsa
->repsFrom1
->other_info
= talloc_zero(dsa
, struct repsFromTo1OtherInfo
);
556 W_ERROR_HAVE_NO_MEMORY(dsa
->repsFrom1
->other_info
);
558 dsa
->repsFrom1
->other_info
->dns_name
= samdb_ntds_msdcs_dns_name(p
->service
->samdb
,
559 dsa
->repsFrom1
->other_info
, dsa_guid
);
560 W_ERROR_HAVE_NO_MEMORY(dsa
->repsFrom1
->other_info
->dns_name
);
562 werr
= dreplsrv_out_connection_attach(p
->service
, dsa
->repsFrom1
, &dsa
->conn
);
563 if (!W_ERROR_IS_OK(werr
)) {
564 DEBUG(0,(__location__
": Failed to attach connection to %s\n",
565 ldb_dn_get_linearized(p
->dn
)));
576 static WERROR
dreplsrv_refresh_partition(struct dreplsrv_service
*s
,
577 struct dreplsrv_partition
*p
)
581 struct ldb_message_element
*orf_el
= NULL
;
582 struct ldb_result
*r
= NULL
;
585 TALLOC_CTX
*mem_ctx
= talloc_new(p
);
586 static const char *attrs
[] = {
593 DEBUG(4, ("dreplsrv_refresh_partition(%s)\n",
594 ldb_dn_get_linearized(p
->dn
)));
596 ret
= dsdb_search_dn(s
->samdb
, mem_ctx
, &r
, p
->dn
, attrs
, DSDB_SEARCH_SHOW_EXTENDED_DN
);
597 if (ret
== LDB_ERR_NO_SUCH_OBJECT
) {
598 /* we haven't replicated the partition yet, but we
599 * can fill in the guid, sid etc from the partition DN */
601 } else if (ret
!= LDB_SUCCESS
) {
602 talloc_free(mem_ctx
);
608 talloc_free(discard_const(p
->nc
.dn
));
610 p
->nc
.dn
= ldb_dn_alloc_linearized(p
, dn
);
611 W_ERROR_HAVE_NO_MEMORY(p
->nc
.dn
);
612 ntstatus
= dsdb_get_extended_dn_guid(dn
, &p
->nc
.guid
, "GUID");
613 if (!NT_STATUS_IS_OK(ntstatus
)) {
614 DEBUG(0,(__location__
": unable to get GUID for %s: %s\n",
615 p
->nc
.dn
, nt_errstr(ntstatus
)));
616 talloc_free(mem_ctx
);
617 return WERR_DS_DRA_INTERNAL_ERROR
;
619 dsdb_get_extended_dn_sid(dn
, &p
->nc
.sid
, "SID");
621 talloc_free(p
->uptodatevector
.cursors
);
622 talloc_free(p
->uptodatevector_ex
.cursors
);
623 ZERO_STRUCT(p
->uptodatevector
);
624 ZERO_STRUCT(p
->uptodatevector_ex
);
626 ret
= dsdb_load_udv_v2(s
->samdb
, p
->dn
, p
, &p
->uptodatevector
.cursors
, &p
->uptodatevector
.count
);
627 if (ret
!= LDB_SUCCESS
) {
628 DEBUG(4,(__location__
": no UDV available for %s\n", ldb_dn_get_linearized(p
->dn
)));
633 if (r
!= NULL
&& (orf_el
= ldb_msg_find_element(r
->msgs
[0], "repsFrom"))) {
634 for (i
=0; i
< orf_el
->num_values
; i
++) {
635 status
= dreplsrv_partition_add_source_dsa(s
, p
, &p
->sources
,
636 NULL
, &orf_el
->values
[i
]);
637 W_ERROR_NOT_OK_GOTO_DONE(status
);
641 if (r
!= NULL
&& (orf_el
= ldb_msg_find_element(r
->msgs
[0], "repsTo"))) {
642 for (i
=0; i
< orf_el
->num_values
; i
++) {
643 status
= dreplsrv_partition_add_source_dsa(s
, p
, &p
->notifies
,
644 p
->sources
, &orf_el
->values
[i
]);
645 W_ERROR_NOT_OK_GOTO_DONE(status
);
650 talloc_free(mem_ctx
);
654 WERROR
dreplsrv_refresh_partitions(struct dreplsrv_service
*s
)
657 struct dreplsrv_partition
*p
;
659 for (p
= s
->partitions
; p
; p
= p
->next
) {
660 status
= dreplsrv_refresh_partition(s
, p
);
661 W_ERROR_NOT_OK_RETURN(status
);