s4-drepl: Search for application partitions in addition to main ones
[amitay/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", "msDs-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, *tp;
79                         bool found;
80
81                         pdn = ldb_dn_from_ldb_val(tmp_ctx, s->samdb, &el->values[i]);
82                         if (pdn == NULL) {
83                                 talloc_free(tmp_ctx);
84                                 return WERR_DS_DRA_INTERNAL_ERROR;
85                         }
86                         if (!ldb_dn_validate(pdn)) {
87                                 return WERR_DS_DRA_INTERNAL_ERROR;
88                         }
89
90                         p = talloc_zero(s, struct dreplsrv_partition);
91                         W_ERROR_HAVE_NO_MEMORY(p);
92
93                         p->dn = talloc_steal(p, pdn);
94                         p->service = s;
95
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;
100                         }
101
102                         /* Do not add partitions more than once */
103                         found = false;
104                         for (tp = s->partitions; tp; tp = tp->next) {
105                                 if (ldb_dn_compare(tp->dn, p->dn) == 0) {
106                                         found = true;
107                                         break;
108                                 }
109                         }
110                         if (found) {
111                                 talloc_free(p);
112                                 continue;
113                         }
114
115                         DLIST_ADD(s->partitions, p);
116                         DEBUG(2, ("dreplsrv_partition[%s] loaded\n", ldb_dn_get_linearized(p->dn)));
117                 }
118         }
119
120         talloc_free(tmp_ctx);
121
122         status = dreplsrv_refresh_partitions(s);
123         W_ERROR_NOT_OK_RETURN(status);
124
125         return WERR_OK;
126 }
127
128 /*
129   work out the principal to use for DRS replication connections
130  */
131 NTSTATUS dreplsrv_get_target_principal(struct dreplsrv_service *s,
132                                        TALLOC_CTX *mem_ctx,
133                                        const struct repsFromTo1 *rft,
134                                        const char **target_principal)
135 {
136         TALLOC_CTX *tmp_ctx;
137         struct ldb_result *res;
138         const char *attrs_server[] = { "dNSHostName", NULL };
139         const char *attrs_ntds[] = { "msDS-HasDomainNCs", "hasMasterNCs", NULL };
140         int ret;
141         const char *hostname, *dnsdomain=NULL;
142         struct ldb_dn *ntds_dn, *server_dn;
143         struct ldb_dn *forest_dn, *nc_dn;
144
145         *target_principal = NULL;
146
147         tmp_ctx = talloc_new(mem_ctx);
148
149         /* we need to find their hostname */
150         ret = dsdb_find_dn_by_guid(s->samdb, tmp_ctx, &rft->source_dsa_obj_guid, &ntds_dn);
151         if (ret != LDB_SUCCESS) {
152                 talloc_free(tmp_ctx);
153                 /* its OK for their NTDSDSA DN not to be in our database */
154                 return NT_STATUS_OK;
155         }
156
157         server_dn = ldb_dn_copy(tmp_ctx, ntds_dn);
158         if (server_dn == NULL) {
159                 talloc_free(tmp_ctx);
160                 return NT_STATUS_OK;
161         }
162
163         /* strip off the NTDS Settings */
164         if (!ldb_dn_remove_child_components(server_dn, 1)) {
165                 talloc_free(tmp_ctx);
166                 return NT_STATUS_OK;
167         }
168
169         ret = dsdb_search_dn(s->samdb, tmp_ctx, &res, server_dn, attrs_server, 0);
170         if (ret != LDB_SUCCESS) {
171                 talloc_free(tmp_ctx);
172                 /* its OK for their server DN not to be in our database */
173                 return NT_STATUS_OK;
174         }
175
176         forest_dn = ldb_get_root_basedn(s->samdb);
177         if (forest_dn == NULL) {
178                 talloc_free(tmp_ctx);
179                 return NT_STATUS_OK;
180         }
181
182         hostname = ldb_msg_find_attr_as_string(res->msgs[0], "dNSHostName", NULL);
183         if (hostname != NULL) {
184                 /*
185                   if we have the dNSHostName attribute then we can use
186                   the GC/hostname/realm SPN. All DCs should have this SPN
187                  */
188                 *target_principal = talloc_asprintf(mem_ctx, "GC/%s/%s",
189                                                     hostname,
190                                                     samdb_dn_to_dns_domain(tmp_ctx, forest_dn));
191                 talloc_free(tmp_ctx);
192                 return NT_STATUS_OK;
193         }
194
195         /*
196            if we can't find the dNSHostName then we will try for the
197            E3514235-4B06-11D1-AB04-00C04FC2DCD2/${NTDSGUID}/${DNSDOMAIN}
198            SPN. To use that we need the DNS domain name of the target
199            DC. We find that by first looking for the msDS-HasDomainNCs
200            in the NTDSDSA object of the DC, and if we don't find that,
201            then we look for the hasMasterNCs attribute, and eliminate
202            the known schema and configuruation DNs. Despite how
203            bizarre this seems, Hongwei tells us that this is in fact
204            what windows does to find the SPN!!
205         */
206         ret = dsdb_search_dn(s->samdb, tmp_ctx, &res, ntds_dn, attrs_ntds, 0);
207         if (ret != LDB_SUCCESS) {
208                 talloc_free(tmp_ctx);
209                 return NT_STATUS_OK;
210         }
211
212         nc_dn = ldb_msg_find_attr_as_dn(s->samdb, tmp_ctx, res->msgs[0], "msDS-HasDomainNCs");
213         if (nc_dn != NULL) {
214                 dnsdomain = samdb_dn_to_dns_domain(tmp_ctx, nc_dn);
215         }
216
217         if (dnsdomain == NULL) {
218                 struct ldb_message_element *el;
219                 int i;
220                 el = ldb_msg_find_element(res->msgs[0], "hasMasterNCs");
221                 for (i=0; el && i<el->num_values; i++) {
222                         nc_dn = ldb_dn_from_ldb_val(tmp_ctx, s->samdb, &el->values[i]);
223                         if (nc_dn == NULL ||
224                             ldb_dn_compare(ldb_get_config_basedn(s->samdb), nc_dn) == 0 ||
225                             ldb_dn_compare(ldb_get_schema_basedn(s->samdb), nc_dn) == 0) {
226                                 continue;
227                         }
228                         /* it must be a domain DN, get the equivalent
229                            DNS domain name */
230                         dnsdomain = samdb_dn_to_dns_domain(tmp_ctx, nc_dn);
231                         break;
232                 }
233         }
234
235         if (dnsdomain != NULL) {
236                 *target_principal = talloc_asprintf(mem_ctx,
237                                                     "E3514235-4B06-11D1-AB04-00C04FC2DCD2/%s/%s",
238                                                     GUID_string(tmp_ctx, &rft->source_dsa_obj_guid),
239                                                     dnsdomain);
240         }
241
242         talloc_free(tmp_ctx);
243         return NT_STATUS_OK;
244 }
245
246
247 WERROR dreplsrv_out_connection_attach(struct dreplsrv_service *s,
248                                       const struct repsFromTo1 *rft,
249                                       struct dreplsrv_out_connection **_conn)
250 {
251         struct dreplsrv_out_connection *cur, *conn = NULL;
252         const char *hostname;
253
254         if (!rft->other_info) {
255                 return WERR_FOOBAR;
256         }
257
258         if (!rft->other_info->dns_name) {
259                 return WERR_FOOBAR;
260         }
261
262         hostname = rft->other_info->dns_name;
263
264         for (cur = s->connections; cur; cur = cur->next) {              
265                 if (strcmp(cur->binding->host, hostname) == 0) {
266                         conn = cur;
267                         break;
268                 }
269         }
270
271         if (!conn) {
272                 NTSTATUS nt_status;
273                 char *binding_str;
274
275                 conn = talloc_zero(s, struct dreplsrv_out_connection);
276                 W_ERROR_HAVE_NO_MEMORY(conn);
277
278                 conn->service   = s;
279
280                 binding_str = talloc_asprintf(conn, "ncacn_ip_tcp:%s[krb5,seal]",
281                                               hostname);
282                 W_ERROR_HAVE_NO_MEMORY(binding_str);
283                 nt_status = dcerpc_parse_binding(conn, binding_str, &conn->binding);
284                 talloc_free(binding_str);
285                 if (!NT_STATUS_IS_OK(nt_status)) {
286                         return ntstatus_to_werror(nt_status);
287                 }
288
289                 /* use the GC principal for DRS replication */
290                 nt_status = dreplsrv_get_target_principal(s, conn->binding,
291                                                           rft, &conn->binding->target_principal);
292                 if (!NT_STATUS_IS_OK(nt_status)) {
293                         return ntstatus_to_werror(nt_status);
294                 }
295
296                 DLIST_ADD_END(s->connections, conn, struct dreplsrv_out_connection *);
297
298                 DEBUG(4,("dreplsrv_out_connection_attach(%s): create\n", conn->binding->host));
299         } else {
300                 DEBUG(4,("dreplsrv_out_connection_attach(%s): attach\n", conn->binding->host));
301         }
302
303         *_conn = conn;
304         return WERR_OK;
305 }
306
307 /*
308   find an existing source dsa in a list
309  */
310 static struct dreplsrv_partition_source_dsa *dreplsrv_find_source_dsa(struct dreplsrv_partition_source_dsa *list,
311                                                                       struct GUID *guid)
312 {
313         struct dreplsrv_partition_source_dsa *s;
314         for (s=list; s; s=s->next) {
315                 if (GUID_compare(&s->repsFrom1->source_dsa_obj_guid, guid) == 0) {
316                         return s;
317                 }
318         }
319         return NULL;    
320 }
321
322
323
324 static WERROR dreplsrv_partition_add_source_dsa(struct dreplsrv_service *s,
325                                                 struct dreplsrv_partition *p,
326                                                 struct dreplsrv_partition_source_dsa **listp,
327                                                 struct dreplsrv_partition_source_dsa *check_list,
328                                                 const struct ldb_val *val)
329 {
330         WERROR status;
331         enum ndr_err_code ndr_err;
332         struct dreplsrv_partition_source_dsa *source, *s2;
333
334         source = talloc_zero(p, struct dreplsrv_partition_source_dsa);
335         W_ERROR_HAVE_NO_MEMORY(source);
336
337         ndr_err = ndr_pull_struct_blob(val, source, 
338                                        &source->_repsFromBlob,
339                                        (ndr_pull_flags_fn_t)ndr_pull_repsFromToBlob);
340         if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
341                 NTSTATUS nt_status = ndr_map_error2ntstatus(ndr_err);
342                 talloc_free(source);
343                 return ntstatus_to_werror(nt_status);
344         }
345         /* NDR_PRINT_DEBUG(repsFromToBlob, &source->_repsFromBlob); */
346         if (source->_repsFromBlob.version != 1) {
347                 talloc_free(source);
348                 return WERR_DS_DRA_INTERNAL_ERROR;
349         }
350
351         source->partition       = p;
352         source->repsFrom1       = &source->_repsFromBlob.ctr.ctr1;
353
354         status = dreplsrv_out_connection_attach(s, source->repsFrom1, &source->conn);
355         W_ERROR_NOT_OK_RETURN(status);
356
357         if (check_list && 
358             dreplsrv_find_source_dsa(check_list, &source->repsFrom1->source_dsa_obj_guid)) {
359                 /* its in the check list, don't add it again */
360                 talloc_free(source);
361                 return WERR_OK;
362         }
363
364         /* re-use an existing source if found */
365         for (s2=*listp; s2; s2=s2->next) {
366                 if (GUID_compare(&s2->repsFrom1->source_dsa_obj_guid, 
367                                  &source->repsFrom1->source_dsa_obj_guid) == 0) {
368                         talloc_free(s2->repsFrom1->other_info);
369                         *s2->repsFrom1 = *source->repsFrom1;
370                         talloc_steal(s2, s2->repsFrom1->other_info);
371                         talloc_free(source);
372                         return WERR_OK;
373                 }
374         }
375
376         DLIST_ADD_END(*listp, source, struct dreplsrv_partition_source_dsa *);
377         return WERR_OK;
378 }
379
380 WERROR dreplsrv_partition_find_for_nc(struct dreplsrv_service *s,
381                                       struct GUID *nc_guid,
382                                       struct dom_sid *nc_sid,
383                                       const char *nc_dn_str,
384                                       struct dreplsrv_partition **_p)
385 {
386         struct dreplsrv_partition *p;
387         bool valid_sid, valid_guid;
388         struct dom_sid null_sid;
389         ZERO_STRUCT(null_sid);
390
391         SMB_ASSERT(_p);
392
393         valid_sid  = nc_sid && !dom_sid_equal(&null_sid, nc_sid);
394         valid_guid = nc_guid && !GUID_all_zero(nc_guid);
395
396         if (!valid_sid && !valid_guid && !nc_dn_str) {
397                 return WERR_DS_DRA_INVALID_PARAMETER;
398         }
399
400         for (p = s->partitions; p; p = p->next) {
401                 if ((valid_guid && GUID_equal(&p->nc.guid, nc_guid))
402                     || strequal(p->nc.dn, nc_dn_str)
403                     || (valid_sid && dom_sid_equal(&p->nc.sid, nc_sid)))
404                 {
405                         /* fill in he right guid and sid if possible */
406                         if (nc_guid && !valid_guid) {
407                                 dsdb_get_extended_dn_guid(p->dn, nc_guid, "GUID");
408                         }
409                         if (nc_sid && !valid_sid) {
410                                 dsdb_get_extended_dn_sid(p->dn, nc_sid, "SID");
411                         }
412                         *_p = p;
413                         return WERR_OK;
414                 }
415         }
416
417         return WERR_DS_DRA_BAD_NC;
418 }
419
420 WERROR dreplsrv_partition_source_dsa_by_guid(struct dreplsrv_partition *p,
421                                              const struct GUID *dsa_guid,
422                                              struct dreplsrv_partition_source_dsa **_dsa)
423 {
424         struct dreplsrv_partition_source_dsa *dsa;
425
426         SMB_ASSERT(dsa_guid != NULL);
427         SMB_ASSERT(!GUID_all_zero(dsa_guid));
428         SMB_ASSERT(_dsa);
429
430         for (dsa = p->sources; dsa; dsa = dsa->next) {
431                 if (GUID_equal(dsa_guid, &dsa->repsFrom1->source_dsa_obj_guid)) {
432                         *_dsa = dsa;
433                         return WERR_OK;
434                 }
435         }
436
437         return WERR_DS_DRA_NO_REPLICA;
438 }
439
440 WERROR dreplsrv_partition_source_dsa_by_dns(const struct dreplsrv_partition *p,
441                                             const char *dsa_dns,
442                                             struct dreplsrv_partition_source_dsa **_dsa)
443 {
444         struct dreplsrv_partition_source_dsa *dsa;
445
446         SMB_ASSERT(dsa_dns != NULL);
447         SMB_ASSERT(_dsa);
448
449         for (dsa = p->sources; dsa; dsa = dsa->next) {
450                 if (strequal(dsa_dns, dsa->repsFrom1->other_info->dns_name)) {
451                         *_dsa = dsa;
452                         return WERR_OK;
453                 }
454         }
455
456         return WERR_DS_DRA_NO_REPLICA;
457 }
458
459
460 /*
461   create a temporary dsa structure for a replication. This is needed
462   for the initial replication of a new partition, such as when a new
463   domain NC is created and we are a global catalog server
464  */
465 WERROR dreplsrv_partition_source_dsa_temporary(struct dreplsrv_partition *p,
466                                                TALLOC_CTX *mem_ctx,
467                                                const struct GUID *dsa_guid,
468                                                struct dreplsrv_partition_source_dsa **_dsa)
469 {
470         struct dreplsrv_partition_source_dsa *dsa;
471         WERROR werr;
472
473         dsa = talloc_zero(mem_ctx, struct dreplsrv_partition_source_dsa);
474         W_ERROR_HAVE_NO_MEMORY(dsa);
475
476         dsa->partition = p;
477         dsa->repsFrom1 = &dsa->_repsFromBlob.ctr.ctr1;
478         dsa->repsFrom1->replica_flags = 0;
479         dsa->repsFrom1->source_dsa_obj_guid = *dsa_guid;
480
481         dsa->repsFrom1->other_info = talloc_zero(dsa, struct repsFromTo1OtherInfo);
482         W_ERROR_HAVE_NO_MEMORY(dsa->repsFrom1->other_info);
483
484         dsa->repsFrom1->other_info->dns_name = samdb_ntds_msdcs_dns_name(p->service->samdb,
485                                                                          dsa->repsFrom1->other_info, dsa_guid);
486         W_ERROR_HAVE_NO_MEMORY(dsa->repsFrom1->other_info->dns_name);
487
488         werr = dreplsrv_out_connection_attach(p->service, dsa->repsFrom1, &dsa->conn);
489         if (!W_ERROR_IS_OK(werr)) {
490                 DEBUG(0,(__location__ ": Failed to attach connection to %s\n",
491                          ldb_dn_get_linearized(p->dn)));
492                 talloc_free(dsa);
493                 return werr;
494         }
495
496         *_dsa = dsa;
497
498         return WERR_OK;
499 }
500
501
502 static WERROR dreplsrv_refresh_partition(struct dreplsrv_service *s,
503                                          struct dreplsrv_partition *p)
504 {
505         WERROR status;
506         NTSTATUS ntstatus;
507         struct ldb_message_element *orf_el = NULL;
508         struct ldb_result *r = NULL;
509         unsigned int i;
510         int ret;
511         TALLOC_CTX *mem_ctx = talloc_new(p);
512         static const char *attrs[] = {
513                 "repsFrom",
514                 "repsTo",
515                 NULL
516         };
517         struct ldb_dn *dn;
518
519         DEBUG(4, ("dreplsrv_refresh_partition(%s)\n",
520                 ldb_dn_get_linearized(p->dn)));
521
522         ret = dsdb_search_dn(s->samdb, mem_ctx, &r, p->dn, attrs, DSDB_SEARCH_SHOW_EXTENDED_DN);
523         if (ret == LDB_ERR_NO_SUCH_OBJECT) {
524                 /* we haven't replicated the partition yet, but we
525                  * can fill in the guid, sid etc from the partition DN */
526                 dn = p->dn;
527         } else if (ret != LDB_SUCCESS) {
528                 talloc_free(mem_ctx);
529                 return WERR_FOOBAR;
530         } else {
531                 dn = r->msgs[0]->dn;
532         }
533         
534         talloc_free(discard_const(p->nc.dn));
535         ZERO_STRUCT(p->nc);
536         p->nc.dn        = ldb_dn_alloc_linearized(p, dn);
537         W_ERROR_HAVE_NO_MEMORY(p->nc.dn);
538         ntstatus = dsdb_get_extended_dn_guid(dn, &p->nc.guid, "GUID");
539         if (!NT_STATUS_IS_OK(ntstatus)) {
540                 DEBUG(0,(__location__ ": unable to get GUID for %s: %s\n",
541                          p->nc.dn, nt_errstr(ntstatus)));
542                 talloc_free(mem_ctx);
543                 return WERR_DS_DRA_INTERNAL_ERROR;
544         }
545         dsdb_get_extended_dn_sid(dn, &p->nc.sid, "SID");
546
547         talloc_free(p->uptodatevector.cursors);
548         talloc_free(p->uptodatevector_ex.cursors);
549         ZERO_STRUCT(p->uptodatevector);
550         ZERO_STRUCT(p->uptodatevector_ex);
551
552         ret = dsdb_load_udv_v2(s->samdb, p->dn, p, &p->uptodatevector.cursors, &p->uptodatevector.count);
553         if (ret != LDB_SUCCESS) {
554                 DEBUG(4,(__location__ ": no UDV available for %s\n", ldb_dn_get_linearized(p->dn)));
555         }
556
557         status = WERR_OK;
558
559         if (r != NULL && (orf_el = ldb_msg_find_element(r->msgs[0], "repsFrom"))) {
560                 for (i=0; i < orf_el->num_values; i++) {
561                         status = dreplsrv_partition_add_source_dsa(s, p, &p->sources, 
562                                                                    NULL, &orf_el->values[i]);
563                         W_ERROR_NOT_OK_GOTO_DONE(status);
564                 }
565         }
566
567         if (r != NULL && (orf_el = ldb_msg_find_element(r->msgs[0], "repsTo"))) {
568                 for (i=0; i < orf_el->num_values; i++) {
569                         status = dreplsrv_partition_add_source_dsa(s, p, &p->notifies, 
570                                                                    p->sources, &orf_el->values[i]);
571                         W_ERROR_NOT_OK_GOTO_DONE(status);
572                 }
573         }
574
575 done:
576         talloc_free(mem_ctx);
577         return status;
578 }
579
580 WERROR dreplsrv_refresh_partitions(struct dreplsrv_service *s)
581 {
582         WERROR status;
583         struct dreplsrv_partition *p;
584
585         for (p = s->partitions; p; p = p->next) {
586                 status = dreplsrv_refresh_partition(s, p);
587                 W_ERROR_NOT_OK_RETURN(status);
588         }
589
590         return WERR_OK;
591 }