s4-rodc: ensure we load replicated partitions for RODCs
[mat/samba.git] / source4 / dsdb / repl / drepl_partitions.c
1 /* 
2    Unix SMB/CIFS mplementation.
3    DSDB replication service
4    
5    Copyright (C) Stefan Metzmacher 2007
6     
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.
11    
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.
16    
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/>.
19    
20 */
21
22 #include "includes.h"
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"
36
37 /*
38   load the partitions list based on replicated NC attributes in our
39   NTDSDSA object
40  */
41 WERROR dreplsrv_load_partitions(struct dreplsrv_service *s)
42 {
43         WERROR status;
44         static const char *attrs[] = { "hasMasterNCs", "hasPartialReplicaNCs", "msDS-HasFullReplicaNCs", NULL };
45         unsigned int a;
46         int ret;
47         TALLOC_CTX *tmp_ctx;
48         struct ldb_result *res;
49         struct ldb_message_element *el;
50         struct ldb_dn *ntds_dn;
51
52         tmp_ctx = talloc_new(s);
53         W_ERROR_HAVE_NO_MEMORY(tmp_ctx);
54
55         ntds_dn = samdb_ntds_settings_dn(s->samdb);
56         if (!ntds_dn) {
57                 DEBUG(1,(__location__ ": Unable to find ntds_dn: %s\n", ldb_errstring(s->samdb)));
58                 talloc_free(tmp_ctx);
59                 return WERR_DS_DRA_INTERNAL_ERROR;
60         }
61
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)));
65                 talloc_free(tmp_ctx);
66                 return WERR_DS_DRA_INTERNAL_ERROR;
67         }
68
69         for (a=0; attrs[a]; a++) {
70                 int i;
71
72                 el = ldb_msg_find_element(res->msgs[0], attrs[a]);
73                 if (el == NULL) {
74                         continue;
75                 }
76                 for (i=0; i<el->num_values; i++) {
77                         struct ldb_dn *pdn;
78                         struct dreplsrv_partition *p;
79
80                         pdn = ldb_dn_from_ldb_val(tmp_ctx, s->samdb, &el->values[i]);
81                         if (pdn == NULL) {
82                                 talloc_free(tmp_ctx);
83                                 return WERR_DS_DRA_INTERNAL_ERROR;
84                         }
85                         if (!ldb_dn_validate(pdn)) {
86                                 return WERR_DS_DRA_INTERNAL_ERROR;
87                         }
88
89                         p = talloc_zero(s, struct dreplsrv_partition);
90                         W_ERROR_HAVE_NO_MEMORY(p);
91
92                         p->dn = talloc_steal(p, pdn);
93                         p->service = s;
94
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;
99                         }
100
101                         DLIST_ADD(s->partitions, p);
102
103                         DEBUG(2, ("dreplsrv_partition[%s] loaded\n", ldb_dn_get_linearized(p->dn)));
104                 }
105         }
106
107         talloc_free(tmp_ctx);
108
109         status = dreplsrv_refresh_partitions(s);
110         W_ERROR_NOT_OK_RETURN(status);
111
112         return WERR_OK;
113 }
114
115 /*
116   work out the principal to use for DRS replication connections
117  */
118 NTSTATUS dreplsrv_get_target_principal(struct dreplsrv_service *s,
119                                        TALLOC_CTX *mem_ctx,
120                                        const struct repsFromTo1 *rft,
121                                        const char **target_principal)
122 {
123         TALLOC_CTX *tmp_ctx;
124         struct ldb_result *res;
125         const char *attrs_server[] = { "dNSHostName", NULL };
126         const char *attrs_ntds[] = { "msDS-HasDomainNCs", "hasMasterNCs", NULL };
127         int ret;
128         const char *hostname, *dnsdomain=NULL;
129         struct ldb_dn *ntds_dn, *server_dn;
130         struct ldb_dn *forest_dn, *nc_dn;
131
132         *target_principal = NULL;
133
134         tmp_ctx = talloc_new(mem_ctx);
135
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 */
141                 return NT_STATUS_OK;
142         }
143
144         server_dn = ldb_dn_copy(tmp_ctx, ntds_dn);
145         if (server_dn == NULL) {
146                 talloc_free(tmp_ctx);
147                 return NT_STATUS_OK;
148         }
149
150         /* strip off the NTDS Settings */
151         if (!ldb_dn_remove_child_components(server_dn, 1)) {
152                 talloc_free(tmp_ctx);
153                 return NT_STATUS_OK;
154         }
155
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 */
160                 return NT_STATUS_OK;
161         }
162
163         forest_dn = ldb_get_root_basedn(s->samdb);
164         if (forest_dn == NULL) {
165                 talloc_free(tmp_ctx);
166                 return NT_STATUS_OK;
167         }
168
169         hostname = ldb_msg_find_attr_as_string(res->msgs[0], "dNSHostName", NULL);
170         if (hostname != NULL) {
171                 /*
172                   if we have the dNSHostName attribute then we can use
173                   the GC/hostname/realm SPN. All DCs should have this SPN
174                  */
175                 *target_principal = talloc_asprintf(mem_ctx, "GC/%s/%s",
176                                                     hostname,
177                                                     samdb_dn_to_dns_domain(tmp_ctx, forest_dn));
178                 talloc_free(tmp_ctx);
179                 return NT_STATUS_OK;
180         }
181
182         /*
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!!
192         */
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);
196                 return NT_STATUS_OK;
197         }
198
199         nc_dn = ldb_msg_find_attr_as_dn(s->samdb, tmp_ctx, res->msgs[0], "msDS-HasDomainNCs");
200         if (nc_dn != NULL) {
201                 dnsdomain = samdb_dn_to_dns_domain(tmp_ctx, nc_dn);
202         }
203
204         if (dnsdomain == NULL) {
205                 struct ldb_message_element *el;
206                 int i;
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]);
210                         if (nc_dn == NULL ||
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) {
213                                 continue;
214                         }
215                         /* it must be a domain DN, get the equivalent
216                            DNS domain name */
217                         dnsdomain = samdb_dn_to_dns_domain(tmp_ctx, nc_dn);
218                         break;
219                 }
220         }
221
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),
226                                                     dnsdomain);
227         }
228
229         talloc_free(tmp_ctx);
230         return NT_STATUS_OK;
231 }
232
233
234 WERROR dreplsrv_out_connection_attach(struct dreplsrv_service *s,
235                                       const struct repsFromTo1 *rft,
236                                       struct dreplsrv_out_connection **_conn)
237 {
238         struct dreplsrv_out_connection *cur, *conn = NULL;
239         const char *hostname;
240
241         if (!rft->other_info) {
242                 return WERR_FOOBAR;
243         }
244
245         if (!rft->other_info->dns_name) {
246                 return WERR_FOOBAR;
247         }
248
249         hostname = rft->other_info->dns_name;
250
251         for (cur = s->connections; cur; cur = cur->next) {              
252                 if (strcmp(cur->binding->host, hostname) == 0) {
253                         conn = cur;
254                         break;
255                 }
256         }
257
258         if (!conn) {
259                 NTSTATUS nt_status;
260                 char *binding_str;
261
262                 conn = talloc_zero(s, struct dreplsrv_out_connection);
263                 W_ERROR_HAVE_NO_MEMORY(conn);
264
265                 conn->service   = s;
266
267                 binding_str = talloc_asprintf(conn, "ncacn_ip_tcp:%s[krb5,seal]",
268                                               hostname);
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);
274                 }
275
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);
281                 }
282
283                 DLIST_ADD_END(s->connections, conn, struct dreplsrv_out_connection *);
284
285                 DEBUG(4,("dreplsrv_out_connection_attach(%s): create\n", conn->binding->host));
286         } else {
287                 DEBUG(4,("dreplsrv_out_connection_attach(%s): attach\n", conn->binding->host));
288         }
289
290         *_conn = conn;
291         return WERR_OK;
292 }
293
294 /*
295   find an existing source dsa in a list
296  */
297 static struct dreplsrv_partition_source_dsa *dreplsrv_find_source_dsa(struct dreplsrv_partition_source_dsa *list,
298                                                                       struct GUID *guid)
299 {
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) {
303                         return s;
304                 }
305         }
306         return NULL;    
307 }
308
309
310
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)
316 {
317         WERROR status;
318         enum ndr_err_code ndr_err;
319         struct dreplsrv_partition_source_dsa *source, *s2;
320
321         source = talloc_zero(p, struct dreplsrv_partition_source_dsa);
322         W_ERROR_HAVE_NO_MEMORY(source);
323
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);
329                 talloc_free(source);
330                 return ntstatus_to_werror(nt_status);
331         }
332         /* NDR_PRINT_DEBUG(repsFromToBlob, &source->_repsFromBlob); */
333         if (source->_repsFromBlob.version != 1) {
334                 talloc_free(source);
335                 return WERR_DS_DRA_INTERNAL_ERROR;
336         }
337
338         source->partition       = p;
339         source->repsFrom1       = &source->_repsFromBlob.ctr.ctr1;
340
341         status = dreplsrv_out_connection_attach(s, source->repsFrom1, &source->conn);
342         W_ERROR_NOT_OK_RETURN(status);
343
344         if (check_list && 
345             dreplsrv_find_source_dsa(check_list, &source->repsFrom1->source_dsa_obj_guid)) {
346                 /* its in the check list, don't add it again */
347                 talloc_free(source);
348                 return WERR_OK;
349         }
350
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);
358                         talloc_free(source);
359                         return WERR_OK;
360                 }
361         }
362
363         DLIST_ADD_END(*listp, source, struct dreplsrv_partition_source_dsa *);
364         return WERR_OK;
365 }
366
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)
372 {
373         struct dreplsrv_partition *p;
374         bool valid_sid, valid_guid;
375         struct dom_sid null_sid;
376         ZERO_STRUCT(null_sid);
377
378         SMB_ASSERT(_p);
379
380         valid_sid  = nc_sid && !dom_sid_equal(&null_sid, nc_sid);
381         valid_guid = nc_guid && !GUID_all_zero(nc_guid);
382
383         if (!valid_sid && !valid_guid && !nc_dn_str) {
384                 return WERR_DS_DRA_INVALID_PARAMETER;
385         }
386
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)))
391                 {
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");
395                         }
396                         if (nc_sid && !valid_sid) {
397                                 dsdb_get_extended_dn_sid(p->dn, nc_sid, "SID");
398                         }
399                         *_p = p;
400                         return WERR_OK;
401                 }
402         }
403
404         return WERR_DS_DRA_BAD_NC;
405 }
406
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)
410 {
411         struct dreplsrv_partition_source_dsa *dsa;
412
413         SMB_ASSERT(dsa_guid != NULL);
414         SMB_ASSERT(!GUID_all_zero(dsa_guid));
415         SMB_ASSERT(_dsa);
416
417         for (dsa = p->sources; dsa; dsa = dsa->next) {
418                 if (GUID_equal(dsa_guid, &dsa->repsFrom1->source_dsa_obj_guid)) {
419                         *_dsa = dsa;
420                         return WERR_OK;
421                 }
422         }
423
424         return WERR_DS_DRA_NO_REPLICA;
425 }
426
427 WERROR dreplsrv_partition_source_dsa_by_dns(const struct dreplsrv_partition *p,
428                                             const char *dsa_dns,
429                                             struct dreplsrv_partition_source_dsa **_dsa)
430 {
431         struct dreplsrv_partition_source_dsa *dsa;
432
433         SMB_ASSERT(dsa_dns != NULL);
434         SMB_ASSERT(_dsa);
435
436         for (dsa = p->sources; dsa; dsa = dsa->next) {
437                 if (strequal(dsa_dns, dsa->repsFrom1->other_info->dns_name)) {
438                         *_dsa = dsa;
439                         return WERR_OK;
440                 }
441         }
442
443         return WERR_DS_DRA_NO_REPLICA;
444 }
445
446
447 /*
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
451  */
452 WERROR dreplsrv_partition_source_dsa_temporary(struct dreplsrv_partition *p,
453                                                TALLOC_CTX *mem_ctx,
454                                                const struct GUID *dsa_guid,
455                                                struct dreplsrv_partition_source_dsa **_dsa)
456 {
457         struct dreplsrv_partition_source_dsa *dsa;
458         WERROR werr;
459
460         dsa = talloc_zero(mem_ctx, struct dreplsrv_partition_source_dsa);
461         W_ERROR_HAVE_NO_MEMORY(dsa);
462
463         dsa->partition = p;
464         dsa->repsFrom1 = &dsa->_repsFromBlob.ctr.ctr1;
465         dsa->repsFrom1->replica_flags = 0;
466         dsa->repsFrom1->source_dsa_obj_guid = *dsa_guid;
467
468         dsa->repsFrom1->other_info = talloc_zero(dsa, struct repsFromTo1OtherInfo);
469         W_ERROR_HAVE_NO_MEMORY(dsa->repsFrom1->other_info);
470
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);
474
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)));
479                 talloc_free(dsa);
480                 return werr;
481         }
482
483         *_dsa = dsa;
484
485         return WERR_OK;
486 }
487
488
489 static WERROR dreplsrv_refresh_partition(struct dreplsrv_service *s,
490                                          struct dreplsrv_partition *p)
491 {
492         WERROR status;
493         NTSTATUS ntstatus;
494         struct ldb_message_element *orf_el = NULL;
495         struct ldb_result *r = NULL;
496         unsigned int i;
497         int ret;
498         TALLOC_CTX *mem_ctx = talloc_new(p);
499         static const char *attrs[] = {
500                 "repsFrom",
501                 "repsTo",
502                 NULL
503         };
504         struct ldb_dn *dn;
505
506         DEBUG(4, ("dreplsrv_refresh_partition(%s)\n",
507                 ldb_dn_get_linearized(p->dn)));
508
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 */
513                 dn = p->dn;
514         } else if (ret != LDB_SUCCESS) {
515                 talloc_free(mem_ctx);
516                 return WERR_FOOBAR;
517         } else {
518                 dn = r->msgs[0]->dn;
519         }
520         
521         talloc_free(discard_const(p->nc.dn));
522         ZERO_STRUCT(p->nc);
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;
531         }
532         dsdb_get_extended_dn_sid(dn, &p->nc.sid, "SID");
533
534         talloc_free(p->uptodatevector.cursors);
535         talloc_free(p->uptodatevector_ex.cursors);
536         ZERO_STRUCT(p->uptodatevector);
537         ZERO_STRUCT(p->uptodatevector_ex);
538
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)));
542         }
543
544         status = WERR_OK;
545
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);
551                 }
552         }
553
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);
559                 }
560         }
561
562 done:
563         talloc_free(mem_ctx);
564         return status;
565 }
566
567 WERROR dreplsrv_refresh_partitions(struct dreplsrv_service *s)
568 {
569         WERROR status;
570         struct dreplsrv_partition *p;
571
572         for (p = s->partitions; p; p = p->next) {
573                 status = dreplsrv_refresh_partition(s, p);
574                 W_ERROR_NOT_OK_RETURN(status);
575         }
576
577         return WERR_OK;
578 }