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
, tmp_ctx
);
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 const struct ldb_val
*val
)
382 enum ndr_err_code ndr_err
;
383 struct dreplsrv_partition_source_dsa
*source
, *s2
;
385 source
= talloc_zero(p
, struct dreplsrv_partition_source_dsa
);
386 W_ERROR_HAVE_NO_MEMORY(source
);
388 ndr_err
= ndr_pull_struct_blob(val
, source
,
389 &source
->_repsFromBlob
,
390 (ndr_pull_flags_fn_t
)ndr_pull_repsFromToBlob
);
391 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err
)) {
392 NTSTATUS nt_status
= ndr_map_error2ntstatus(ndr_err
);
394 return ntstatus_to_werror(nt_status
);
396 /* NDR_PRINT_DEBUG(repsFromToBlob, &source->_repsFromBlob); */
397 if (source
->_repsFromBlob
.version
!= 1) {
399 return WERR_DS_DRA_INTERNAL_ERROR
;
402 source
->partition
= p
;
403 source
->repsFrom1
= &source
->_repsFromBlob
.ctr
.ctr1
;
405 status
= dreplsrv_out_connection_attach(s
, source
->repsFrom1
, &source
->conn
);
406 W_ERROR_NOT_OK_RETURN(status
);
409 dreplsrv_find_source_dsa(check_list
, &source
->repsFrom1
->source_dsa_obj_guid
)) {
410 /* its in the check list, don't add it again */
415 /* re-use an existing source if found */
416 for (s2
=*listp
; s2
; s2
=s2
->next
) {
417 if (GUID_compare(&s2
->repsFrom1
->source_dsa_obj_guid
,
418 &source
->repsFrom1
->source_dsa_obj_guid
) == 0) {
419 talloc_free(s2
->repsFrom1
->other_info
);
420 *s2
->repsFrom1
= *source
->repsFrom1
;
421 talloc_steal(s2
, s2
->repsFrom1
->other_info
);
427 DLIST_ADD_END(*listp
, source
, struct dreplsrv_partition_source_dsa
*);
431 WERROR
dreplsrv_partition_find_for_nc(struct dreplsrv_service
*s
,
432 struct GUID
*nc_guid
,
433 struct dom_sid
*nc_sid
,
434 const char *nc_dn_str
,
435 struct dreplsrv_partition
**_p
)
437 struct dreplsrv_partition
*p
;
438 bool valid_sid
, valid_guid
;
439 struct dom_sid null_sid
;
440 ZERO_STRUCT(null_sid
);
444 valid_sid
= nc_sid
&& !dom_sid_equal(&null_sid
, nc_sid
);
445 valid_guid
= nc_guid
&& !GUID_all_zero(nc_guid
);
447 if (!valid_sid
&& !valid_guid
&& !nc_dn_str
) {
448 return WERR_DS_DRA_INVALID_PARAMETER
;
451 for (p
= s
->partitions
; p
; p
= p
->next
) {
452 if ((valid_guid
&& GUID_equal(&p
->nc
.guid
, nc_guid
))
453 || strequal(p
->nc
.dn
, nc_dn_str
)
454 || (valid_sid
&& dom_sid_equal(&p
->nc
.sid
, nc_sid
)))
456 /* fill in he right guid and sid if possible */
457 if (nc_guid
&& !valid_guid
) {
458 dsdb_get_extended_dn_guid(p
->dn
, nc_guid
, "GUID");
460 if (nc_sid
&& !valid_sid
) {
461 dsdb_get_extended_dn_sid(p
->dn
, nc_sid
, "SID");
468 return WERR_DS_DRA_BAD_NC
;
471 WERROR
dreplsrv_partition_source_dsa_by_guid(struct dreplsrv_partition
*p
,
472 const struct GUID
*dsa_guid
,
473 struct dreplsrv_partition_source_dsa
**_dsa
)
475 struct dreplsrv_partition_source_dsa
*dsa
;
477 SMB_ASSERT(dsa_guid
!= NULL
);
478 SMB_ASSERT(!GUID_all_zero(dsa_guid
));
481 for (dsa
= p
->sources
; dsa
; dsa
= dsa
->next
) {
482 if (GUID_equal(dsa_guid
, &dsa
->repsFrom1
->source_dsa_obj_guid
)) {
488 return WERR_DS_DRA_NO_REPLICA
;
491 WERROR
dreplsrv_partition_source_dsa_by_dns(const struct dreplsrv_partition
*p
,
493 struct dreplsrv_partition_source_dsa
**_dsa
)
495 struct dreplsrv_partition_source_dsa
*dsa
;
497 SMB_ASSERT(dsa_dns
!= NULL
);
500 for (dsa
= p
->sources
; dsa
; dsa
= dsa
->next
) {
501 if (strequal(dsa_dns
, dsa
->repsFrom1
->other_info
->dns_name
)) {
507 return WERR_DS_DRA_NO_REPLICA
;
512 create a temporary dsa structure for a replication. This is needed
513 for the initial replication of a new partition, such as when a new
514 domain NC is created and we are a global catalog server
516 WERROR
dreplsrv_partition_source_dsa_temporary(struct dreplsrv_partition
*p
,
518 const struct GUID
*dsa_guid
,
519 struct dreplsrv_partition_source_dsa
**_dsa
)
521 struct dreplsrv_partition_source_dsa
*dsa
;
524 dsa
= talloc_zero(mem_ctx
, struct dreplsrv_partition_source_dsa
);
525 W_ERROR_HAVE_NO_MEMORY(dsa
);
528 dsa
->repsFrom1
= &dsa
->_repsFromBlob
.ctr
.ctr1
;
529 dsa
->repsFrom1
->replica_flags
= 0;
530 dsa
->repsFrom1
->source_dsa_obj_guid
= *dsa_guid
;
532 dsa
->repsFrom1
->other_info
= talloc_zero(dsa
, struct repsFromTo1OtherInfo
);
533 W_ERROR_HAVE_NO_MEMORY(dsa
->repsFrom1
->other_info
);
535 dsa
->repsFrom1
->other_info
->dns_name
= samdb_ntds_msdcs_dns_name(p
->service
->samdb
,
536 dsa
->repsFrom1
->other_info
, dsa_guid
);
537 W_ERROR_HAVE_NO_MEMORY(dsa
->repsFrom1
->other_info
->dns_name
);
539 werr
= dreplsrv_out_connection_attach(p
->service
, dsa
->repsFrom1
, &dsa
->conn
);
540 if (!W_ERROR_IS_OK(werr
)) {
541 DEBUG(0,(__location__
": Failed to attach connection to %s\n",
542 ldb_dn_get_linearized(p
->dn
)));
553 static WERROR
dreplsrv_refresh_partition(struct dreplsrv_service
*s
,
554 struct dreplsrv_partition
*p
)
558 struct ldb_message_element
*orf_el
= NULL
;
559 struct ldb_result
*r
= NULL
;
562 TALLOC_CTX
*mem_ctx
= talloc_new(p
);
563 static const char *attrs
[] = {
570 DEBUG(4, ("dreplsrv_refresh_partition(%s)\n",
571 ldb_dn_get_linearized(p
->dn
)));
573 ret
= dsdb_search_dn(s
->samdb
, mem_ctx
, &r
, p
->dn
, attrs
, DSDB_SEARCH_SHOW_EXTENDED_DN
);
574 if (ret
== LDB_ERR_NO_SUCH_OBJECT
) {
575 /* we haven't replicated the partition yet, but we
576 * can fill in the guid, sid etc from the partition DN */
578 } else if (ret
!= LDB_SUCCESS
) {
579 talloc_free(mem_ctx
);
585 talloc_free(discard_const(p
->nc
.dn
));
587 p
->nc
.dn
= ldb_dn_alloc_linearized(p
, dn
);
588 W_ERROR_HAVE_NO_MEMORY(p
->nc
.dn
);
589 ntstatus
= dsdb_get_extended_dn_guid(dn
, &p
->nc
.guid
, "GUID");
590 if (!NT_STATUS_IS_OK(ntstatus
)) {
591 DEBUG(0,(__location__
": unable to get GUID for %s: %s\n",
592 p
->nc
.dn
, nt_errstr(ntstatus
)));
593 talloc_free(mem_ctx
);
594 return WERR_DS_DRA_INTERNAL_ERROR
;
596 dsdb_get_extended_dn_sid(dn
, &p
->nc
.sid
, "SID");
598 talloc_free(p
->uptodatevector
.cursors
);
599 talloc_free(p
->uptodatevector_ex
.cursors
);
600 ZERO_STRUCT(p
->uptodatevector
);
601 ZERO_STRUCT(p
->uptodatevector_ex
);
603 ret
= dsdb_load_udv_v2(s
->samdb
, p
->dn
, p
, &p
->uptodatevector
.cursors
, &p
->uptodatevector
.count
);
604 if (ret
!= LDB_SUCCESS
) {
605 DEBUG(4,(__location__
": no UDV available for %s\n", ldb_dn_get_linearized(p
->dn
)));
610 if (r
!= NULL
&& (orf_el
= ldb_msg_find_element(r
->msgs
[0], "repsFrom"))) {
611 for (i
=0; i
< orf_el
->num_values
; i
++) {
612 status
= dreplsrv_partition_add_source_dsa(s
, p
, &p
->sources
,
613 NULL
, &orf_el
->values
[i
]);
614 W_ERROR_NOT_OK_GOTO_DONE(status
);
618 if (r
!= NULL
&& (orf_el
= ldb_msg_find_element(r
->msgs
[0], "repsTo"))) {
619 for (i
=0; i
< orf_el
->num_values
; i
++) {
620 status
= dreplsrv_partition_add_source_dsa(s
, p
, &p
->notifies
,
621 p
->sources
, &orf_el
->values
[i
]);
622 W_ERROR_NOT_OK_GOTO_DONE(status
);
627 talloc_free(mem_ctx
);
631 WERROR
dreplsrv_refresh_partitions(struct dreplsrv_service
*s
)
634 struct dreplsrv_partition
*p
;
636 for (p
= s
->partitions
; p
; p
= p
->next
) {
637 status
= dreplsrv_refresh_partition(s
, p
);
638 W_ERROR_NOT_OK_RETURN(status
);