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
*account_dn
,
132 const char *principal_name
)
135 const char *attrs_empty
[] = { NULL
};
137 struct ldb_result
*res
;
139 tmp_ctx
= talloc_new(samdb
);
141 ret
= dsdb_search(samdb
, tmp_ctx
, &res
, account_dn
, LDB_SCOPE_BASE
, attrs_empty
,
142 0, "servicePrincipalName=%s",
143 ldb_binary_encode_string(tmp_ctx
, principal_name
));
144 if (ret
!= LDB_SUCCESS
|| res
->count
!= 1) {
145 talloc_free(tmp_ctx
);
149 talloc_free(tmp_ctx
);
154 work out the principal to use for DRS replication connections
156 NTSTATUS
dreplsrv_get_target_principal(struct dreplsrv_service
*s
,
158 const struct repsFromTo1
*rft
,
159 const char **target_principal
)
162 struct ldb_result
*res
;
163 const char *attrs_server
[] = { "dNSHostName", "serverReference", NULL
};
164 const char *attrs_ntds
[] = { "msDS-HasDomainNCs", "hasMasterNCs", NULL
};
166 const char *hostname
, *dnsdomain
=NULL
;
167 struct ldb_dn
*ntds_dn
, *server_dn
, *computer_dn
;
168 struct ldb_dn
*forest_dn
, *nc_dn
;
170 *target_principal
= NULL
;
172 tmp_ctx
= talloc_new(mem_ctx
);
174 /* we need to find their hostname */
175 ret
= dsdb_find_dn_by_guid(s
->samdb
, tmp_ctx
, &rft
->source_dsa_obj_guid
, &ntds_dn
);
176 if (ret
!= LDB_SUCCESS
) {
177 talloc_free(tmp_ctx
);
178 /* its OK for their NTDSDSA DN not to be in our database */
182 server_dn
= ldb_dn_copy(tmp_ctx
, ntds_dn
);
183 if (server_dn
== NULL
) {
184 talloc_free(tmp_ctx
);
188 /* strip off the NTDS Settings */
189 if (!ldb_dn_remove_child_components(server_dn
, 1)) {
190 talloc_free(tmp_ctx
);
194 ret
= dsdb_search_dn(s
->samdb
, tmp_ctx
, &res
, server_dn
, attrs_server
, 0);
195 if (ret
!= LDB_SUCCESS
) {
196 talloc_free(tmp_ctx
);
197 /* its OK for their server DN not to be in our database */
201 forest_dn
= ldb_get_root_basedn(s
->samdb
);
202 if (forest_dn
== NULL
) {
203 talloc_free(tmp_ctx
);
207 hostname
= ldb_msg_find_attr_as_string(res
->msgs
[0], "dNSHostName", NULL
);
208 computer_dn
= ldb_msg_find_attr_as_dn(s
->samdb
, tmp_ctx
, res
->msgs
[0], "serverReference");
209 if (hostname
!= NULL
&& computer_dn
!= NULL
) {
210 char *local_principal
;
213 if we have the dNSHostName attribute then we can use
214 the GC/hostname/realm SPN. All DCs should have this SPN
216 Windows DC may set up it's dNSHostName before setting up
217 GC/xx/xx SPN. So make sure it exists, before using it.
219 local_principal
= talloc_asprintf(mem_ctx
, "GC/%s/%s",
221 samdb_dn_to_dns_domain(tmp_ctx
, forest_dn
));
222 if (dreplsrv_spn_exists(s
->samdb
, computer_dn
, local_principal
)) {
223 *target_principal
= local_principal
;
224 talloc_free(tmp_ctx
);
228 talloc_free(local_principal
);
232 if we can't find the dNSHostName then we will try for the
233 E3514235-4B06-11D1-AB04-00C04FC2DCD2/${NTDSGUID}/${DNSDOMAIN}
234 SPN. To use that we need the DNS domain name of the target
235 DC. We find that by first looking for the msDS-HasDomainNCs
236 in the NTDSDSA object of the DC, and if we don't find that,
237 then we look for the hasMasterNCs attribute, and eliminate
238 the known schema and configuruation DNs. Despite how
239 bizarre this seems, Hongwei tells us that this is in fact
240 what windows does to find the SPN!!
242 ret
= dsdb_search_dn(s
->samdb
, tmp_ctx
, &res
, ntds_dn
, attrs_ntds
, 0);
243 if (ret
!= LDB_SUCCESS
) {
244 talloc_free(tmp_ctx
);
248 nc_dn
= ldb_msg_find_attr_as_dn(s
->samdb
, tmp_ctx
, res
->msgs
[0], "msDS-HasDomainNCs");
250 dnsdomain
= samdb_dn_to_dns_domain(tmp_ctx
, nc_dn
);
253 if (dnsdomain
== NULL
) {
254 struct ldb_message_element
*el
;
256 el
= ldb_msg_find_element(res
->msgs
[0], "hasMasterNCs");
257 for (i
=0; el
&& i
<el
->num_values
; i
++) {
258 nc_dn
= ldb_dn_from_ldb_val(tmp_ctx
, s
->samdb
, &el
->values
[i
]);
260 ldb_dn_compare(ldb_get_config_basedn(s
->samdb
), nc_dn
) == 0 ||
261 ldb_dn_compare(ldb_get_schema_basedn(s
->samdb
), nc_dn
) == 0) {
264 /* it must be a domain DN, get the equivalent
266 dnsdomain
= samdb_dn_to_dns_domain(tmp_ctx
, nc_dn
);
271 if (dnsdomain
!= NULL
) {
272 *target_principal
= talloc_asprintf(mem_ctx
,
273 "E3514235-4B06-11D1-AB04-00C04FC2DCD2/%s/%s",
274 GUID_string(tmp_ctx
, &rft
->source_dsa_obj_guid
),
278 talloc_free(tmp_ctx
);
283 WERROR
dreplsrv_out_connection_attach(struct dreplsrv_service
*s
,
284 const struct repsFromTo1
*rft
,
285 struct dreplsrv_out_connection
**_conn
)
287 struct dreplsrv_out_connection
*cur
, *conn
= NULL
;
288 const char *hostname
;
290 if (!rft
->other_info
) {
294 if (!rft
->other_info
->dns_name
) {
298 hostname
= rft
->other_info
->dns_name
;
300 for (cur
= s
->connections
; cur
; cur
= cur
->next
) {
301 if (strcmp(cur
->binding
->host
, hostname
) == 0) {
311 conn
= talloc_zero(s
, struct dreplsrv_out_connection
);
312 W_ERROR_HAVE_NO_MEMORY(conn
);
316 binding_str
= talloc_asprintf(conn
, "ncacn_ip_tcp:%s[krb5,seal]",
318 W_ERROR_HAVE_NO_MEMORY(binding_str
);
319 nt_status
= dcerpc_parse_binding(conn
, binding_str
, &conn
->binding
);
320 talloc_free(binding_str
);
321 if (!NT_STATUS_IS_OK(nt_status
)) {
322 return ntstatus_to_werror(nt_status
);
325 /* use the GC principal for DRS replication */
326 nt_status
= dreplsrv_get_target_principal(s
, conn
->binding
,
327 rft
, &conn
->binding
->target_principal
);
328 if (!NT_STATUS_IS_OK(nt_status
)) {
329 return ntstatus_to_werror(nt_status
);
332 DLIST_ADD_END(s
->connections
, conn
, struct dreplsrv_out_connection
*);
334 DEBUG(4,("dreplsrv_out_connection_attach(%s): create\n", conn
->binding
->host
));
336 DEBUG(4,("dreplsrv_out_connection_attach(%s): attach\n", conn
->binding
->host
));
344 find an existing source dsa in a list
346 static struct dreplsrv_partition_source_dsa
*dreplsrv_find_source_dsa(struct dreplsrv_partition_source_dsa
*list
,
349 struct dreplsrv_partition_source_dsa
*s
;
350 for (s
=list
; s
; s
=s
->next
) {
351 if (GUID_compare(&s
->repsFrom1
->source_dsa_obj_guid
, guid
) == 0) {
360 static WERROR
dreplsrv_partition_add_source_dsa(struct dreplsrv_service
*s
,
361 struct dreplsrv_partition
*p
,
362 struct dreplsrv_partition_source_dsa
**listp
,
363 struct dreplsrv_partition_source_dsa
*check_list
,
364 const struct ldb_val
*val
)
367 enum ndr_err_code ndr_err
;
368 struct dreplsrv_partition_source_dsa
*source
, *s2
;
370 source
= talloc_zero(p
, struct dreplsrv_partition_source_dsa
);
371 W_ERROR_HAVE_NO_MEMORY(source
);
373 ndr_err
= ndr_pull_struct_blob(val
, source
,
374 &source
->_repsFromBlob
,
375 (ndr_pull_flags_fn_t
)ndr_pull_repsFromToBlob
);
376 if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err
)) {
377 NTSTATUS nt_status
= ndr_map_error2ntstatus(ndr_err
);
379 return ntstatus_to_werror(nt_status
);
381 /* NDR_PRINT_DEBUG(repsFromToBlob, &source->_repsFromBlob); */
382 if (source
->_repsFromBlob
.version
!= 1) {
384 return WERR_DS_DRA_INTERNAL_ERROR
;
387 source
->partition
= p
;
388 source
->repsFrom1
= &source
->_repsFromBlob
.ctr
.ctr1
;
390 status
= dreplsrv_out_connection_attach(s
, source
->repsFrom1
, &source
->conn
);
391 W_ERROR_NOT_OK_RETURN(status
);
394 dreplsrv_find_source_dsa(check_list
, &source
->repsFrom1
->source_dsa_obj_guid
)) {
395 /* its in the check list, don't add it again */
400 /* re-use an existing source if found */
401 for (s2
=*listp
; s2
; s2
=s2
->next
) {
402 if (GUID_compare(&s2
->repsFrom1
->source_dsa_obj_guid
,
403 &source
->repsFrom1
->source_dsa_obj_guid
) == 0) {
404 talloc_free(s2
->repsFrom1
->other_info
);
405 *s2
->repsFrom1
= *source
->repsFrom1
;
406 talloc_steal(s2
, s2
->repsFrom1
->other_info
);
412 DLIST_ADD_END(*listp
, source
, struct dreplsrv_partition_source_dsa
*);
417 * Find a partition when given a NC
418 * If the NC can't be found it will return BAD_NC
419 * Initial checks for invalid parameters have to be done beforehand
421 WERROR
dreplsrv_partition_find_for_nc(struct dreplsrv_service
*s
,
422 struct GUID
*nc_guid
,
423 struct dom_sid
*nc_sid
,
424 const char *nc_dn_str
,
425 struct dreplsrv_partition
**_p
)
427 struct dreplsrv_partition
*p
;
428 bool valid_sid
, valid_guid
;
429 struct dom_sid null_sid
;
430 ZERO_STRUCT(null_sid
);
434 valid_sid
= nc_sid
&& !dom_sid_equal(&null_sid
, nc_sid
);
435 valid_guid
= nc_guid
&& !GUID_all_zero(nc_guid
);
437 if (!valid_sid
&& !valid_guid
&& (!nc_dn_str
)) {
438 return WERR_DS_DRA_BAD_NC
;
441 for (p
= s
->partitions
; p
; p
= p
->next
) {
442 if ((valid_guid
&& GUID_equal(&p
->nc
.guid
, nc_guid
))
443 || strequal(p
->nc
.dn
, nc_dn_str
)
444 || (valid_sid
&& dom_sid_equal(&p
->nc
.sid
, nc_sid
)))
446 /* fill in he right guid and sid if possible */
447 if (nc_guid
&& !valid_guid
) {
448 dsdb_get_extended_dn_guid(p
->dn
, nc_guid
, "GUID");
450 if (nc_sid
&& !valid_sid
) {
451 dsdb_get_extended_dn_sid(p
->dn
, nc_sid
, "SID");
458 return WERR_DS_DRA_BAD_NC
;
461 WERROR
dreplsrv_partition_source_dsa_by_guid(struct dreplsrv_partition
*p
,
462 const struct GUID
*dsa_guid
,
463 struct dreplsrv_partition_source_dsa
**_dsa
)
465 struct dreplsrv_partition_source_dsa
*dsa
;
467 SMB_ASSERT(dsa_guid
!= NULL
);
468 SMB_ASSERT(!GUID_all_zero(dsa_guid
));
471 for (dsa
= p
->sources
; dsa
; dsa
= dsa
->next
) {
472 if (GUID_equal(dsa_guid
, &dsa
->repsFrom1
->source_dsa_obj_guid
)) {
478 return WERR_DS_DRA_NO_REPLICA
;
481 WERROR
dreplsrv_partition_source_dsa_by_dns(const struct dreplsrv_partition
*p
,
483 struct dreplsrv_partition_source_dsa
**_dsa
)
485 struct dreplsrv_partition_source_dsa
*dsa
;
487 SMB_ASSERT(dsa_dns
!= NULL
);
490 for (dsa
= p
->sources
; dsa
; dsa
= dsa
->next
) {
491 if (strequal(dsa_dns
, dsa
->repsFrom1
->other_info
->dns_name
)) {
497 return WERR_DS_DRA_NO_REPLICA
;
502 create a temporary dsa structure for a replication. This is needed
503 for the initial replication of a new partition, such as when a new
504 domain NC is created and we are a global catalog server
506 WERROR
dreplsrv_partition_source_dsa_temporary(struct dreplsrv_partition
*p
,
508 const struct GUID
*dsa_guid
,
509 struct dreplsrv_partition_source_dsa
**_dsa
)
511 struct dreplsrv_partition_source_dsa
*dsa
;
514 dsa
= talloc_zero(mem_ctx
, struct dreplsrv_partition_source_dsa
);
515 W_ERROR_HAVE_NO_MEMORY(dsa
);
518 dsa
->repsFrom1
= &dsa
->_repsFromBlob
.ctr
.ctr1
;
519 dsa
->repsFrom1
->replica_flags
= 0;
520 dsa
->repsFrom1
->source_dsa_obj_guid
= *dsa_guid
;
522 dsa
->repsFrom1
->other_info
= talloc_zero(dsa
, struct repsFromTo1OtherInfo
);
523 W_ERROR_HAVE_NO_MEMORY(dsa
->repsFrom1
->other_info
);
525 dsa
->repsFrom1
->other_info
->dns_name
= samdb_ntds_msdcs_dns_name(p
->service
->samdb
,
526 dsa
->repsFrom1
->other_info
, dsa_guid
);
527 W_ERROR_HAVE_NO_MEMORY(dsa
->repsFrom1
->other_info
->dns_name
);
529 werr
= dreplsrv_out_connection_attach(p
->service
, dsa
->repsFrom1
, &dsa
->conn
);
530 if (!W_ERROR_IS_OK(werr
)) {
531 DEBUG(0,(__location__
": Failed to attach connection to %s\n",
532 ldb_dn_get_linearized(p
->dn
)));
543 static WERROR
dreplsrv_refresh_partition(struct dreplsrv_service
*s
,
544 struct dreplsrv_partition
*p
)
548 struct ldb_message_element
*orf_el
= NULL
;
549 struct ldb_result
*r
= NULL
;
552 TALLOC_CTX
*mem_ctx
= talloc_new(p
);
553 static const char *attrs
[] = {
560 DEBUG(4, ("dreplsrv_refresh_partition(%s)\n",
561 ldb_dn_get_linearized(p
->dn
)));
563 ret
= dsdb_search_dn(s
->samdb
, mem_ctx
, &r
, p
->dn
, attrs
, DSDB_SEARCH_SHOW_EXTENDED_DN
);
564 if (ret
== LDB_ERR_NO_SUCH_OBJECT
) {
565 /* we haven't replicated the partition yet, but we
566 * can fill in the guid, sid etc from the partition DN */
568 } else if (ret
!= LDB_SUCCESS
) {
569 talloc_free(mem_ctx
);
575 talloc_free(discard_const(p
->nc
.dn
));
577 p
->nc
.dn
= ldb_dn_alloc_linearized(p
, dn
);
578 W_ERROR_HAVE_NO_MEMORY(p
->nc
.dn
);
579 ntstatus
= dsdb_get_extended_dn_guid(dn
, &p
->nc
.guid
, "GUID");
580 if (!NT_STATUS_IS_OK(ntstatus
)) {
581 DEBUG(0,(__location__
": unable to get GUID for %s: %s\n",
582 p
->nc
.dn
, nt_errstr(ntstatus
)));
583 talloc_free(mem_ctx
);
584 return WERR_DS_DRA_INTERNAL_ERROR
;
586 dsdb_get_extended_dn_sid(dn
, &p
->nc
.sid
, "SID");
588 talloc_free(p
->uptodatevector
.cursors
);
589 talloc_free(p
->uptodatevector_ex
.cursors
);
590 ZERO_STRUCT(p
->uptodatevector
);
591 ZERO_STRUCT(p
->uptodatevector_ex
);
593 ret
= dsdb_load_udv_v2(s
->samdb
, p
->dn
, p
, &p
->uptodatevector
.cursors
, &p
->uptodatevector
.count
);
594 if (ret
!= LDB_SUCCESS
) {
595 DEBUG(4,(__location__
": no UDV available for %s\n", ldb_dn_get_linearized(p
->dn
)));
600 if (r
!= NULL
&& (orf_el
= ldb_msg_find_element(r
->msgs
[0], "repsFrom"))) {
601 for (i
=0; i
< orf_el
->num_values
; i
++) {
602 status
= dreplsrv_partition_add_source_dsa(s
, p
, &p
->sources
,
603 NULL
, &orf_el
->values
[i
]);
604 W_ERROR_NOT_OK_GOTO_DONE(status
);
608 if (r
!= NULL
&& (orf_el
= ldb_msg_find_element(r
->msgs
[0], "repsTo"))) {
609 for (i
=0; i
< orf_el
->num_values
; i
++) {
610 status
= dreplsrv_partition_add_source_dsa(s
, p
, &p
->notifies
,
611 p
->sources
, &orf_el
->values
[i
]);
612 W_ERROR_NOT_OK_GOTO_DONE(status
);
617 talloc_free(mem_ctx
);
621 WERROR
dreplsrv_refresh_partitions(struct dreplsrv_service
*s
)
624 struct dreplsrv_partition
*p
;
626 for (p
= s
->partitions
; p
; p
= p
->next
) {
627 status
= dreplsrv_refresh_partition(s
, p
);
628 W_ERROR_NOT_OK_RETURN(status
);