2 * idmap_adex: Global Catalog search interface
4 * Copyright (C) Gerald (Jerry) Carter 2007-2008
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22 #include "idmap_adex.h"
25 #define DBGC_CLASS DBGC_IDMAP
27 static struct gc_info *_gc_server_list = NULL;
30 /**********************************************************************
31 *********************************************************************/
33 static struct gc_info *gc_list_head(void)
35 return _gc_server_list;
38 /**********************************************************************
39 Checks if either of the domains is a subdomain of the other
40 *********************************************************************/
42 static bool is_subdomain(const char* a, const char *b)
45 TALLOC_CTX *frame = talloc_stackframe();
57 /* Normalize the case */
59 x = talloc_strdup(frame, a);
60 y = talloc_strdup(frame, b);
71 if (strcmp(x, y) == 0) {
76 /* Check for trailing substrings */
79 if (s && (strlen(s) == strlen(y))) {
85 if (s && (strlen(s) == strlen(x))) {
91 talloc_destroy(frame);
96 /**********************************************************************
97 *********************************************************************/
99 NTSTATUS gc_find_forest_root(struct gc_info *gc, const char *domain)
101 ADS_STRUCT *ads = NULL;
102 ADS_STATUS ads_status;
103 NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
104 struct nbt_cldap_netlogon_5 cldap_reply;
105 TALLOC_CTX *frame = talloc_stackframe();
107 if (!gc || !domain) {
108 return NT_STATUS_INVALID_PARAMETER;
111 ZERO_STRUCT(cldap_reply);
113 ads = ads_init(domain, NULL, NULL);
114 BAIL_ON_PTR_ERROR(ads, nt_status);
116 ads->auth.flags = ADS_AUTH_NO_BIND;
117 ads_status = ads_connect(ads);
118 if (!ADS_ERR_OK(ads_status)) {
119 DEBUG(4, ("find_forest_root: ads_connect(%s) failed! (%s)\n",
120 domain, ads_errstr(ads_status)));
122 nt_status = ads_ntstatus(ads_status);
123 BAIL_ON_NTSTATUS_ERROR(nt_status);
125 if (!ads_cldap_netlogon_5(frame,
126 ads->config.ldap_server_name,
130 DEBUG(4,("find_forest_root: Failed to get a CLDAP reply from %s!\n",
131 ads->server.ldap_server));
132 nt_status = NT_STATUS_IO_TIMEOUT;
133 BAIL_ON_NTSTATUS_ERROR(nt_status);
136 gc->forest_name = talloc_strdup(gc, cldap_reply.forest);
137 BAIL_ON_PTR_ERROR(gc->forest_name, nt_status);
147 /**********************************************************************
148 *********************************************************************/
150 static NTSTATUS gc_add_forest(const char *domain)
152 NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
153 struct gc_info *gc = NULL;
154 struct gc_info *find_gc = NULL;
156 ADS_STRUCT *ads = NULL;
157 struct likewise_cell *primary_cell = NULL;
159 primary_cell = cell_list_head();
161 nt_status = NT_STATUS_INVALID_SERVER_STATE;
162 BAIL_ON_NTSTATUS_ERROR(nt_status);
165 /* Check for duplicates based on domain name first as this
166 requires no connection */
168 find_gc = gc_list_head();
170 if (strequal (find_gc->forest_name, domain))
172 find_gc = find_gc->next;
176 DEBUG(10,("gc_add_forest: %s already in list\n", find_gc->forest_name));
180 if ((gc = TALLOC_ZERO_P(NULL, struct gc_info)) == NULL) {
181 nt_status = NT_STATUS_NO_MEMORY;
182 BAIL_ON_NTSTATUS_ERROR(nt_status);
185 /* Query the rootDSE for the forest root naming conect first.
186 Check that the a GC server for the forest has not already
189 nt_status = gc_find_forest_root(gc, domain);
190 BAIL_ON_NTSTATUS_ERROR(nt_status);
192 find_gc = gc_list_head();
194 if (strequal (find_gc->forest_name, gc->forest_name))
196 find_gc = find_gc->next;
200 DEBUG(10,("gc_add_forest: Forest %s already in list\n",
201 find_gc->forest_name));
205 /* Not found, so add it here. Make sure we connect to
206 a DC in _this_ domain and not the forest root. */
208 dn = ads_build_dn(gc->forest_name);
209 BAIL_ON_PTR_ERROR(dn, nt_status);
211 gc->search_base = talloc_strdup(gc, dn);
213 BAIL_ON_PTR_ERROR(gc->search_base, nt_status);
216 /* Can't use cell_connect_dn() here as there is no way to
217 specifiy the LWCELL_FLAG_GC_CELL flag setting for cell_connect() */
219 nt_status = cell_connect_dn(&gc->forest_cell, gc->search_base);
220 BAIL_ON_NTSTATUS_ERROR(nt_status);
223 gc->forest_cell = cell_new();
224 BAIL_ON_PTR_ERROR(gc->forest_cell, nt_status);
226 /* Set the DNS domain, dn, etc ... and add it to the list */
228 cell_set_dns_domain(gc->forest_cell, gc->forest_name);
229 cell_set_dn(gc->forest_cell, gc->search_base);
230 cell_set_flags(gc->forest_cell, LWCELL_FLAG_GC_CELL);
233 /* It is possible to belong to a non-forest cell and a
234 non-provisioned forest (at our domain levele). In that
235 case, we should just inherit the flags from our primary
236 cell since the GC searches will match our own schema
239 if (strequal(primary_cell->forest_name, gc->forest_name)
240 || is_subdomain(primary_cell->dns_domain, gc->forest_name))
242 cell_set_flags(gc->forest_cell, cell_flags(primary_cell));
244 /* outside of our domain */
246 nt_status = cell_connect(gc->forest_cell);
247 BAIL_ON_NTSTATUS_ERROR(nt_status);
249 nt_status = cell_lookup_settings(gc->forest_cell);
250 BAIL_ON_NTSTATUS_ERROR(nt_status);
252 /* Drop the connection now that we have the settings */
254 ads = cell_connection(gc->forest_cell);
256 cell_set_connection(gc->forest_cell, NULL);
259 DLIST_ADD_END(_gc_server_list, gc, struct gc_info*);
261 DEBUG(10,("gc_add_forest: Added %s to Global Catalog list of servers\n",
264 nt_status = NT_STATUS_OK;
267 if (!NT_STATUS_IS_OK(nt_status)) {
269 DEBUG(3,("LWI: Failed to add new GC connection for %s (%s)\n",
270 domain, nt_errstr(nt_status)));
276 /**********************************************************************
277 *********************************************************************/
279 static void gc_server_list_destroy(void)
281 struct gc_info *gc = gc_list_head();
284 struct gc_info *p = gc->next;
286 cell_destroy(gc->forest_cell);
292 _gc_server_list = NULL;
297 /**********************************************************************
298 Setup the initial list of forests and initial the forest cell
299 settings for each. FIXME!!!
300 *********************************************************************/
302 NTSTATUS gc_init_list(void)
304 NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
305 struct winbindd_tdc_domain *domains = NULL;
306 size_t num_domains = 0;
309 if (_gc_server_list != NULL) {
310 gc_server_list_destroy();
313 if (!wcache_tdc_fetch_list(&domains, &num_domains)) {
314 nt_status = NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
315 BAIL_ON_NTSTATUS_ERROR(nt_status);
318 /* Find our forest first. Have to try all domains here starting
319 with our own. gc_add_forest() filters duplicates */
321 nt_status = gc_add_forest(lp_realm());
322 WARN_ON_NTSTATUS_ERROR(nt_status);
324 for (i=0; i<num_domains; i++) {
325 uint32_t flags = (NETR_TRUST_FLAG_IN_FOREST);
327 /* I think we should be able to break out of loop once
328 we add a GC for our forest and not have to test every one.
329 In fact, this entire loop is probably irrelevant since
330 the GC location code should always find a GC given lp_realm().
331 Will have to spend time testing before making the change.
334 if ((domains[i].trust_flags & flags) == flags) {
335 nt_status = gc_add_forest(domains[i].dns_name);
336 WARN_ON_NTSTATUS_ERROR(nt_status);
337 /* Don't BAIL here since not every domain may
342 /* Now add trusted forests. gc_add_forest() will filter out
343 duplicates. Check everything with an incoming trust path
344 that is not in our own forest. */
346 for (i=0; i<num_domains; i++) {
347 uint32_t flags = domains[i].trust_flags;
348 uint32_t attribs = domains[i].trust_attribs;
350 /* Skip non_AD domains */
352 if (strlen(domains[i].dns_name) == 0) {
356 /* Only add a GC for a forest outside of our own.
357 Ignore QUARANTINED/EXTERNAL trusts */
359 if ((flags & NETR_TRUST_FLAG_INBOUND)
360 && !(flags & NETR_TRUST_FLAG_IN_FOREST)
361 && (attribs & NETR_TRUST_ATTRIBUTE_FOREST_TRANSITIVE))
363 nt_status = gc_add_forest(domains[i].dns_name);
364 WARN_ON_NTSTATUS_ERROR(nt_status);
368 nt_status = NT_STATUS_OK;
371 if (!NT_STATUS_IS_OK(nt_status)) {
372 DEBUG(2,("LWI: Failed to initialized GC list (%s)\n",
373 nt_errstr(nt_status)));
376 TALLOC_FREE(domains);
382 /**********************************************************************
383 *********************************************************************/
385 struct gc_info *gc_search_start(void)
387 NTSTATUS nt_status = NT_STATUS_OK;
388 struct gc_info *gc = gc_list_head();
391 nt_status = gc_init_list();
392 BAIL_ON_NTSTATUS_ERROR(nt_status);
398 if (!NT_STATUS_IS_OK(nt_status)) {
399 DEBUG(2,("LWI: Failed to initialize GC list (%s)\n",
400 nt_errstr(nt_status)));
406 /**********************************************************************
407 Search Global Catalog. Always search our own forest. The flags set
408 controls whether or not we search cross forest. Assume that the
409 resulting set is always returned from one GC so that we don't have to
410 both combining the LDAPMessage * results
411 *********************************************************************/
413 NTSTATUS gc_search_forest(struct gc_info *gc,
417 NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
418 ADS_STATUS ads_status = ADS_ERROR_NT(NT_STATUS_UNSUCCESSFUL);
419 const char *attrs[] = {"*", NULL};
420 LDAPMessage *m = NULL;
422 if (!gc || !msg || !filter) {
423 nt_status = NT_STATUS_INVALID_PARAMETER;
424 BAIL_ON_NTSTATUS_ERROR(nt_status);
427 /* When you have multiple domain trees in a forest, the
428 GC will search all naming contexts when you send it
429 and empty ("") base search suffix. Tested against
432 ads_status = cell_do_search(gc->forest_cell, "",
433 LDAP_SCOPE_SUBTREE, filter, attrs, &m);
434 nt_status = ads_ntstatus(ads_status);
435 BAIL_ON_NTSTATUS_ERROR(nt_status);
440 if (!NT_STATUS_IS_OK(nt_status)) {
441 DEBUG(2,("LWI: Forest wide search %s failed (%s)\n",
442 filter, nt_errstr(nt_status)));
448 /**********************************************************************
449 Search all forests via GC and return the results in an array of
450 ADS_STRUCT/LDAPMessage pairs.
451 *********************************************************************/
453 NTSTATUS gc_search_all_forests(const char *filter,
454 ADS_STRUCT ***ads_list,
455 LDAPMessage ***msg_list,
456 int *num_resp, uint32_t flags)
458 NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
459 struct gc_info *gc = NULL;
460 uint32_t test_flags = ADEX_GC_SEARCH_CHECK_UNIQUE;
466 if ((gc = gc_search_start()) == NULL) {
467 nt_status = NT_STATUS_INVALID_DOMAIN_STATE;
468 BAIL_ON_NTSTATUS_ERROR(nt_status);
472 LDAPMessage *m = NULL;
474 nt_status = gc_search_forest(gc, &m, filter);
475 if (!NT_STATUS_IS_OK(nt_status)) {
480 nt_status = add_ads_result_to_array(cell_connection(gc->forest_cell),
481 m, ads_list, msg_list,
483 BAIL_ON_NTSTATUS_ERROR(nt_status);
485 /* If there can only be one match, then we are done */
487 if ((*num_resp > 0) && ((flags & test_flags) == test_flags)) {
494 if (*num_resp == 0) {
495 nt_status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
496 BAIL_ON_NTSTATUS_ERROR(nt_status);
499 nt_status = NT_STATUS_OK;
505 /**********************************************************************
506 Search all forests via GC and return the results in an array of
507 ADS_STRUCT/LDAPMessage pairs.
508 *********************************************************************/
510 NTSTATUS gc_search_all_forests_unique(const char *filter,
514 ADS_STRUCT **ads_list = NULL;
515 LDAPMessage **msg_list = NULL;
517 NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
519 nt_status = gc_search_all_forests(filter, &ads_list,
520 &msg_list, &num_resp,
521 ADEX_GC_SEARCH_CHECK_UNIQUE);
522 BAIL_ON_NTSTATUS_ERROR(nt_status);
524 nt_status = check_result_unique(ads_list[0], msg_list[0]);
525 BAIL_ON_NTSTATUS_ERROR(nt_status);
531 /* Be care that we don't free the msg result being returned */
533 if (!NT_STATUS_IS_OK(nt_status)) {
534 free_result_array(ads_list, msg_list, num_resp);
536 talloc_destroy(ads_list);
537 talloc_destroy(msg_list);
543 /*********************************************************************
544 ********************************************************************/
546 NTSTATUS gc_name_to_sid(const char *domain,
549 enum lsa_SidType *sid_type)
551 TALLOC_CTX *frame = talloc_stackframe();
553 NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
555 ADS_STRUCT *ads = NULL;
556 LDAPMessage *msg = NULL;
557 LDAPMessage *e = NULL;
559 char *dns_domain = NULL;
560 ADS_STRUCT **ads_list = NULL;
561 LDAPMessage **msg_list = NULL;
565 /* Strip the "DOMAIN\" prefix if necessary and search for
566 a matching sAMAccountName in the forest */
568 if ((p = strchr_m( name, '\\' )) == NULL)
569 name_user = talloc_strdup( frame, name );
571 name_user = talloc_strdup( frame, p+1 );
572 BAIL_ON_PTR_ERROR(name_user, nt_status);
574 name_filter = talloc_asprintf(frame, "(sAMAccountName=%s)", name_user);
575 BAIL_ON_PTR_ERROR(name_filter, nt_status);
577 nt_status = gc_search_all_forests(name_filter, &ads_list,
578 &msg_list, &num_resp, 0);
579 BAIL_ON_NTSTATUS_ERROR(nt_status);
581 /* Assume failure until we know otherwise*/
583 nt_status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
585 /* Match the domain name from the DN */
587 for (i=0; i<num_resp; i++) {
591 e = ads_first_entry(ads, msg);
593 struct winbindd_tdc_domain *domain_rec;
595 dn = ads_get_dn(ads, e);
596 BAIL_ON_PTR_ERROR(dn, nt_status);
598 dns_domain = cell_dn_to_dns(dn);
600 BAIL_ON_PTR_ERROR(dns_domain, nt_status);
602 domain_rec = wcache_tdc_fetch_domain(frame, dns_domain);
603 SAFE_FREE(dns_domain);
605 /* Ignore failures and continue the search */
608 e = ads_next_entry(ads, e);
612 /* Check for a match on the domain name */
614 if (strequal(domain, domain_rec->domain_name)) {
615 if (!ads_pull_sid(ads, e, "objectSid", sid)) {
616 nt_status = NT_STATUS_INVALID_SID;
617 BAIL_ON_NTSTATUS_ERROR(nt_status);
620 talloc_destroy(domain_rec);
622 nt_status = get_sid_type(ads, msg, sid_type);
623 BAIL_ON_NTSTATUS_ERROR(nt_status);
626 nt_status = NT_STATUS_OK;
630 /* once more around thew merry-go-round */
632 talloc_destroy(domain_rec);
633 e = ads_next_entry(ads, e);
638 free_result_array(ads_list, msg_list, num_resp);
639 talloc_destroy(frame);
644 /********************************************************************
645 Pull an attribute string value
646 *******************************************************************/
648 static NTSTATUS get_object_account_name(ADS_STRUCT *ads,
652 NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
653 char *sam_name = NULL;
654 struct winbindd_tdc_domain *domain_rec = NULL;
655 char *dns_domain = NULL;
657 TALLOC_CTX *frame = talloc_stackframe();
660 /* Check parameters */
662 if (!ads || !msg || !name) {
663 nt_status = NT_STATUS_INVALID_PARAMETER;
664 BAIL_ON_NTSTATUS_ERROR(nt_status);
667 /* get the name and domain */
669 dn = ads_get_dn(ads, msg);
670 BAIL_ON_PTR_ERROR(dn, nt_status);
672 DEBUG(10,("get_object_account_name: dn = \"%s\"\n", dn));
674 dns_domain = cell_dn_to_dns(dn);
676 BAIL_ON_PTR_ERROR(dns_domain, nt_status);
678 domain_rec = wcache_tdc_fetch_domain(frame, dns_domain);
679 SAFE_FREE(dns_domain);
682 nt_status = NT_STATUS_TRUSTED_DOMAIN_FAILURE;
683 BAIL_ON_NTSTATUS_ERROR(nt_status);
686 sam_name = ads_pull_string(ads, frame, msg, "sAMAccountName");
687 BAIL_ON_PTR_ERROR(sam_name, nt_status);
689 len = asprintf(name, "%s\\%s", domain_rec->domain_name, sam_name);
692 BAIL_ON_PTR_ERROR((*name), nt_status);
695 nt_status = NT_STATUS_OK;
698 talloc_destroy(frame);
703 /*********************************************************************
704 ********************************************************************/
706 NTSTATUS gc_sid_to_name(const DOM_SID *sid,
708 enum lsa_SidType *sid_type)
710 TALLOC_CTX *frame = talloc_stackframe();
711 NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
713 ADS_STRUCT *ads = NULL;
714 LDAPMessage *msg = NULL;
719 sid_string = sid_binstring(sid);
720 BAIL_ON_PTR_ERROR(sid_string, nt_status);
722 filter = talloc_asprintf(frame, "(objectSid=%s)", sid_string);
723 SAFE_FREE(sid_string);
724 BAIL_ON_PTR_ERROR(filter, nt_status);
726 nt_status = gc_search_all_forests_unique(filter, &ads, &msg);
727 BAIL_ON_NTSTATUS_ERROR(nt_status);
729 nt_status = get_object_account_name(ads, msg, name);
730 BAIL_ON_NTSTATUS_ERROR(nt_status);
732 nt_status = get_sid_type(ads, msg, sid_type);
733 BAIL_ON_NTSTATUS_ERROR(nt_status);
736 ads_msgfree(ads, msg);
737 talloc_destroy(frame);
742 /**********************************************************************
743 *********************************************************************/
745 NTSTATUS add_ads_result_to_array(ADS_STRUCT *ads,
747 ADS_STRUCT ***ads_list,
748 LDAPMessage ***msg_list,
751 NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
752 ADS_STRUCT **ads_tmp = NULL;
753 LDAPMessage **msg_tmp = NULL;
757 nt_status = NT_STATUS_INVALID_PARAMETER;
758 BAIL_ON_NTSTATUS_ERROR(nt_status);
762 /* Don't add a response with no entries */
764 if (ads_count_replies(ads, msg) == 0) {
770 ads_tmp = TALLOC_ARRAY(NULL, ADS_STRUCT*, 1);
771 BAIL_ON_PTR_ERROR(ads_tmp, nt_status);
773 msg_tmp = TALLOC_ARRAY(NULL, LDAPMessage*, 1);
774 BAIL_ON_PTR_ERROR(msg_tmp, nt_status);
776 ads_tmp = TALLOC_REALLOC_ARRAY(*ads_list, *ads_list, ADS_STRUCT*,
778 BAIL_ON_PTR_ERROR(ads_tmp, nt_status);
780 msg_tmp = TALLOC_REALLOC_ARRAY(*msg_list, *msg_list, LDAPMessage*,
782 BAIL_ON_PTR_ERROR(msg_tmp, nt_status);
785 ads_tmp[count] = ads;
786 msg_tmp[count] = msg;
793 nt_status = NT_STATUS_OK;
796 if (!NT_STATUS_IS_OK(nt_status)) {
797 talloc_destroy(ads_tmp);
798 talloc_destroy(msg_tmp);
804 /**********************************************************************
805 Frees search results. Do not free the ads_list as these are
806 references back to the GC search structures.
807 *********************************************************************/
809 void free_result_array(ADS_STRUCT **ads_list,
810 LDAPMessage **msg_list,
815 for (i=0; i<num_resp; i++) {
816 ads_msgfree(ads_list[i], msg_list[i]);
819 talloc_destroy(ads_list);
820 talloc_destroy(msg_list);
823 /**********************************************************************
824 Check that we have exactly one entry from the search
825 *********************************************************************/
827 NTSTATUS check_result_unique(ADS_STRUCT *ads, LDAPMessage *msg)
832 count = ads_count_replies(ads, msg);
835 nt_status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
836 BAIL_ON_NTSTATUS_ERROR(nt_status);
840 nt_status = NT_STATUS_DUPLICATE_NAME;
841 BAIL_ON_NTSTATUS_ERROR(nt_status);
844 nt_status = NT_STATUS_OK;