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.
23 #include "idmap_adex.h"
24 #include "libads/cldap.h"
27 #define DBGC_CLASS DBGC_IDMAP
29 static struct gc_info *_gc_server_list = NULL;
32 /**********************************************************************
33 *********************************************************************/
35 static struct gc_info *gc_list_head(void)
37 return _gc_server_list;
40 /**********************************************************************
41 Checks if either of the domains is a subdomain of the other
42 *********************************************************************/
44 static bool is_subdomain(const char* a, const char *b)
47 TALLOC_CTX *frame = talloc_stackframe();
59 /* Normalize the case */
61 x = talloc_strdup(frame, a);
62 y = talloc_strdup(frame, b);
73 if (strcmp(x, y) == 0) {
78 /* Check for trailing substrings */
81 if (s && (strlen(s) == strlen(y))) {
87 if (s && (strlen(s) == strlen(x))) {
93 talloc_destroy(frame);
98 /**********************************************************************
99 *********************************************************************/
101 NTSTATUS gc_find_forest_root(struct gc_info *gc, const char *domain)
103 ADS_STRUCT *ads = NULL;
104 ADS_STATUS ads_status;
105 NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
106 struct NETLOGON_SAM_LOGON_RESPONSE_EX cldap_reply;
107 TALLOC_CTX *frame = talloc_stackframe();
109 if (!gc || !domain) {
110 return NT_STATUS_INVALID_PARAMETER;
113 ZERO_STRUCT(cldap_reply);
115 ads = ads_init(domain, NULL, NULL);
116 BAIL_ON_PTR_ERROR(ads, nt_status);
118 ads->auth.flags = ADS_AUTH_NO_BIND;
119 ads_status = ads_connect(ads);
120 if (!ADS_ERR_OK(ads_status)) {
121 DEBUG(4, ("find_forest_root: ads_connect(%s) failed! (%s)\n",
122 domain, ads_errstr(ads_status)));
124 nt_status = ads_ntstatus(ads_status);
125 BAIL_ON_NTSTATUS_ERROR(nt_status);
127 if (!ads_cldap_netlogon_5(frame,
128 ads->config.ldap_server_name,
132 DEBUG(4,("find_forest_root: Failed to get a CLDAP reply from %s!\n",
133 ads->server.ldap_server));
134 nt_status = NT_STATUS_IO_TIMEOUT;
135 BAIL_ON_NTSTATUS_ERROR(nt_status);
138 gc->forest_name = talloc_strdup(gc, cldap_reply.forest);
139 BAIL_ON_PTR_ERROR(gc->forest_name, nt_status);
149 /**********************************************************************
150 *********************************************************************/
152 static NTSTATUS gc_add_forest(const char *domain)
154 NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
155 struct gc_info *gc = NULL;
156 struct gc_info *find_gc = NULL;
158 ADS_STRUCT *ads = NULL;
159 struct likewise_cell *primary_cell = NULL;
161 primary_cell = cell_list_head();
163 nt_status = NT_STATUS_INVALID_SERVER_STATE;
164 BAIL_ON_NTSTATUS_ERROR(nt_status);
167 /* Check for duplicates based on domain name first as this
168 requires no connection */
170 find_gc = gc_list_head();
172 if (strequal (find_gc->forest_name, domain))
174 find_gc = find_gc->next;
178 DEBUG(10,("gc_add_forest: %s already in list\n", find_gc->forest_name));
182 if ((gc = TALLOC_ZERO_P(NULL, struct gc_info)) == NULL) {
183 nt_status = NT_STATUS_NO_MEMORY;
184 BAIL_ON_NTSTATUS_ERROR(nt_status);
187 /* Query the rootDSE for the forest root naming conect first.
188 Check that the a GC server for the forest has not already
191 nt_status = gc_find_forest_root(gc, domain);
192 BAIL_ON_NTSTATUS_ERROR(nt_status);
194 find_gc = gc_list_head();
196 if (strequal (find_gc->forest_name, gc->forest_name))
198 find_gc = find_gc->next;
202 DEBUG(10,("gc_add_forest: Forest %s already in list\n",
203 find_gc->forest_name));
207 /* Not found, so add it here. Make sure we connect to
208 a DC in _this_ domain and not the forest root. */
210 dn = ads_build_dn(gc->forest_name);
211 BAIL_ON_PTR_ERROR(dn, nt_status);
213 gc->search_base = talloc_strdup(gc, dn);
215 BAIL_ON_PTR_ERROR(gc->search_base, nt_status);
218 /* Can't use cell_connect_dn() here as there is no way to
219 specifiy the LWCELL_FLAG_GC_CELL flag setting for cell_connect() */
221 nt_status = cell_connect_dn(&gc->forest_cell, gc->search_base);
222 BAIL_ON_NTSTATUS_ERROR(nt_status);
225 gc->forest_cell = cell_new();
226 BAIL_ON_PTR_ERROR(gc->forest_cell, nt_status);
228 /* Set the DNS domain, dn, etc ... and add it to the list */
230 cell_set_dns_domain(gc->forest_cell, gc->forest_name);
231 cell_set_dn(gc->forest_cell, gc->search_base);
232 cell_set_flags(gc->forest_cell, LWCELL_FLAG_GC_CELL);
235 /* It is possible to belong to a non-forest cell and a
236 non-provisioned forest (at our domain levele). In that
237 case, we should just inherit the flags from our primary
238 cell since the GC searches will match our own schema
241 if (strequal(primary_cell->forest_name, gc->forest_name)
242 || is_subdomain(primary_cell->dns_domain, gc->forest_name))
244 cell_set_flags(gc->forest_cell, cell_flags(primary_cell));
246 /* outside of our domain */
248 nt_status = cell_connect(gc->forest_cell);
249 BAIL_ON_NTSTATUS_ERROR(nt_status);
251 nt_status = cell_lookup_settings(gc->forest_cell);
252 BAIL_ON_NTSTATUS_ERROR(nt_status);
254 /* Drop the connection now that we have the settings */
256 ads = cell_connection(gc->forest_cell);
258 cell_set_connection(gc->forest_cell, NULL);
261 DLIST_ADD_END(_gc_server_list, gc, struct gc_info*);
263 DEBUG(10,("gc_add_forest: Added %s to Global Catalog list of servers\n",
266 nt_status = NT_STATUS_OK;
269 if (!NT_STATUS_IS_OK(nt_status)) {
271 DEBUG(3,("LWI: Failed to add new GC connection for %s (%s)\n",
272 domain, nt_errstr(nt_status)));
278 /**********************************************************************
279 *********************************************************************/
281 static void gc_server_list_destroy(void)
283 struct gc_info *gc = gc_list_head();
286 struct gc_info *p = gc->next;
288 cell_destroy(gc->forest_cell);
294 _gc_server_list = NULL;
299 /**********************************************************************
300 Setup the initial list of forests and initial the forest cell
301 settings for each. FIXME!!!
302 *********************************************************************/
304 NTSTATUS gc_init_list(void)
306 NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
307 struct winbindd_tdc_domain *domains = NULL;
308 size_t num_domains = 0;
311 if (_gc_server_list != NULL) {
312 gc_server_list_destroy();
315 if (!wcache_tdc_fetch_list(&domains, &num_domains)) {
316 nt_status = NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
317 BAIL_ON_NTSTATUS_ERROR(nt_status);
320 /* Find our forest first. Have to try all domains here starting
321 with our own. gc_add_forest() filters duplicates */
323 nt_status = gc_add_forest(lp_realm());
324 WARN_ON_NTSTATUS_ERROR(nt_status);
326 for (i=0; i<num_domains; i++) {
327 uint32_t flags = (NETR_TRUST_FLAG_IN_FOREST);
329 /* I think we should be able to break out of loop once
330 we add a GC for our forest and not have to test every one.
331 In fact, this entire loop is probably irrelevant since
332 the GC location code should always find a GC given lp_realm().
333 Will have to spend time testing before making the change.
336 if ((domains[i].trust_flags & flags) == flags) {
337 nt_status = gc_add_forest(domains[i].dns_name);
338 WARN_ON_NTSTATUS_ERROR(nt_status);
339 /* Don't BAIL here since not every domain may
344 /* Now add trusted forests. gc_add_forest() will filter out
345 duplicates. Check everything with an incoming trust path
346 that is not in our own forest. */
348 for (i=0; i<num_domains; i++) {
349 uint32_t flags = domains[i].trust_flags;
350 uint32_t attribs = domains[i].trust_attribs;
352 /* Skip non_AD domains */
354 if (strlen(domains[i].dns_name) == 0) {
358 /* Only add a GC for a forest outside of our own.
359 Ignore QUARANTINED/EXTERNAL trusts */
361 if ((flags & NETR_TRUST_FLAG_INBOUND)
362 && !(flags & NETR_TRUST_FLAG_IN_FOREST)
363 && (attribs & NETR_TRUST_ATTRIBUTE_FOREST_TRANSITIVE))
365 nt_status = gc_add_forest(domains[i].dns_name);
366 WARN_ON_NTSTATUS_ERROR(nt_status);
370 nt_status = NT_STATUS_OK;
373 if (!NT_STATUS_IS_OK(nt_status)) {
374 DEBUG(2,("LWI: Failed to initialized GC list (%s)\n",
375 nt_errstr(nt_status)));
378 TALLOC_FREE(domains);
384 /**********************************************************************
385 *********************************************************************/
387 struct gc_info *gc_search_start(void)
389 NTSTATUS nt_status = NT_STATUS_OK;
390 struct gc_info *gc = gc_list_head();
393 nt_status = gc_init_list();
394 BAIL_ON_NTSTATUS_ERROR(nt_status);
400 if (!NT_STATUS_IS_OK(nt_status)) {
401 DEBUG(2,("LWI: Failed to initialize GC list (%s)\n",
402 nt_errstr(nt_status)));
408 /**********************************************************************
409 Search Global Catalog. Always search our own forest. The flags set
410 controls whether or not we search cross forest. Assume that the
411 resulting set is always returned from one GC so that we don't have to
412 both combining the LDAPMessage * results
413 *********************************************************************/
415 NTSTATUS gc_search_forest(struct gc_info *gc,
419 NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
420 ADS_STATUS ads_status = ADS_ERROR_NT(NT_STATUS_UNSUCCESSFUL);
421 const char *attrs[] = {"*", NULL};
422 LDAPMessage *m = NULL;
424 if (!gc || !msg || !filter) {
425 nt_status = NT_STATUS_INVALID_PARAMETER;
426 BAIL_ON_NTSTATUS_ERROR(nt_status);
429 /* When you have multiple domain trees in a forest, the
430 GC will search all naming contexts when you send it
431 and empty ("") base search suffix. Tested against
434 ads_status = cell_do_search(gc->forest_cell, "",
435 LDAP_SCOPE_SUBTREE, filter, attrs, &m);
436 nt_status = ads_ntstatus(ads_status);
437 BAIL_ON_NTSTATUS_ERROR(nt_status);
442 if (!NT_STATUS_IS_OK(nt_status)) {
443 DEBUG(2,("LWI: Forest wide search %s failed (%s)\n",
444 filter, nt_errstr(nt_status)));
450 /**********************************************************************
451 Search all forests via GC and return the results in an array of
452 ADS_STRUCT/LDAPMessage pairs.
453 *********************************************************************/
455 NTSTATUS gc_search_all_forests(const char *filter,
456 ADS_STRUCT ***ads_list,
457 LDAPMessage ***msg_list,
458 int *num_resp, uint32_t flags)
460 NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
461 struct gc_info *gc = NULL;
462 uint32_t test_flags = ADEX_GC_SEARCH_CHECK_UNIQUE;
468 if ((gc = gc_search_start()) == NULL) {
469 nt_status = NT_STATUS_INVALID_DOMAIN_STATE;
470 BAIL_ON_NTSTATUS_ERROR(nt_status);
474 LDAPMessage *m = NULL;
476 nt_status = gc_search_forest(gc, &m, filter);
477 if (!NT_STATUS_IS_OK(nt_status)) {
482 nt_status = add_ads_result_to_array(cell_connection(gc->forest_cell),
483 m, ads_list, msg_list,
485 BAIL_ON_NTSTATUS_ERROR(nt_status);
487 /* If there can only be one match, then we are done */
489 if ((*num_resp > 0) && ((flags & test_flags) == test_flags)) {
496 if (*num_resp == 0) {
497 nt_status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
498 BAIL_ON_NTSTATUS_ERROR(nt_status);
501 nt_status = NT_STATUS_OK;
507 /**********************************************************************
508 Search all forests via GC and return the results in an array of
509 ADS_STRUCT/LDAPMessage pairs.
510 *********************************************************************/
512 NTSTATUS gc_search_all_forests_unique(const char *filter,
516 ADS_STRUCT **ads_list = NULL;
517 LDAPMessage **msg_list = NULL;
519 NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
521 nt_status = gc_search_all_forests(filter, &ads_list,
522 &msg_list, &num_resp,
523 ADEX_GC_SEARCH_CHECK_UNIQUE);
524 BAIL_ON_NTSTATUS_ERROR(nt_status);
526 nt_status = check_result_unique(ads_list[0], msg_list[0]);
527 BAIL_ON_NTSTATUS_ERROR(nt_status);
533 /* Be care that we don't free the msg result being returned */
535 if (!NT_STATUS_IS_OK(nt_status)) {
536 free_result_array(ads_list, msg_list, num_resp);
538 talloc_destroy(ads_list);
539 talloc_destroy(msg_list);
545 /*********************************************************************
546 ********************************************************************/
548 NTSTATUS gc_name_to_sid(const char *domain,
551 enum lsa_SidType *sid_type)
553 TALLOC_CTX *frame = talloc_stackframe();
555 NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
557 ADS_STRUCT *ads = NULL;
558 LDAPMessage *msg = NULL;
559 LDAPMessage *e = NULL;
561 char *dns_domain = NULL;
562 ADS_STRUCT **ads_list = NULL;
563 LDAPMessage **msg_list = NULL;
567 /* Strip the "DOMAIN\" prefix if necessary and search for
568 a matching sAMAccountName in the forest */
570 if ((p = strchr_m( name, '\\' )) == NULL)
571 name_user = talloc_strdup( frame, name );
573 name_user = talloc_strdup( frame, p+1 );
574 BAIL_ON_PTR_ERROR(name_user, nt_status);
576 name_filter = talloc_asprintf(frame, "(sAMAccountName=%s)", name_user);
577 BAIL_ON_PTR_ERROR(name_filter, nt_status);
579 nt_status = gc_search_all_forests(name_filter, &ads_list,
580 &msg_list, &num_resp, 0);
581 BAIL_ON_NTSTATUS_ERROR(nt_status);
583 /* Assume failure until we know otherwise*/
585 nt_status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
587 /* Match the domain name from the DN */
589 for (i=0; i<num_resp; i++) {
593 e = ads_first_entry(ads, msg);
595 struct winbindd_tdc_domain *domain_rec;
597 dn = ads_get_dn(ads, frame, e);
598 BAIL_ON_PTR_ERROR(dn, nt_status);
600 dns_domain = cell_dn_to_dns(dn);
602 BAIL_ON_PTR_ERROR(dns_domain, nt_status);
604 domain_rec = wcache_tdc_fetch_domain(frame, dns_domain);
605 SAFE_FREE(dns_domain);
607 /* Ignore failures and continue the search */
610 e = ads_next_entry(ads, e);
614 /* Check for a match on the domain name */
616 if (strequal(domain, domain_rec->domain_name)) {
617 if (!ads_pull_sid(ads, e, "objectSid", sid)) {
618 nt_status = NT_STATUS_INVALID_SID;
619 BAIL_ON_NTSTATUS_ERROR(nt_status);
622 talloc_destroy(domain_rec);
624 nt_status = get_sid_type(ads, msg, sid_type);
625 BAIL_ON_NTSTATUS_ERROR(nt_status);
628 nt_status = NT_STATUS_OK;
632 /* once more around thew merry-go-round */
634 talloc_destroy(domain_rec);
635 e = ads_next_entry(ads, e);
640 free_result_array(ads_list, msg_list, num_resp);
641 talloc_destroy(frame);
646 /********************************************************************
647 Pull an attribute string value
648 *******************************************************************/
650 static NTSTATUS get_object_account_name(ADS_STRUCT *ads,
654 NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
655 char *sam_name = NULL;
656 struct winbindd_tdc_domain *domain_rec = NULL;
657 char *dns_domain = NULL;
659 TALLOC_CTX *frame = talloc_stackframe();
662 /* Check parameters */
664 if (!ads || !msg || !name) {
665 nt_status = NT_STATUS_INVALID_PARAMETER;
666 BAIL_ON_NTSTATUS_ERROR(nt_status);
669 /* get the name and domain */
671 dn = ads_get_dn(ads, frame, msg);
672 BAIL_ON_PTR_ERROR(dn, nt_status);
674 DEBUG(10,("get_object_account_name: dn = \"%s\"\n", dn));
676 dns_domain = cell_dn_to_dns(dn);
678 BAIL_ON_PTR_ERROR(dns_domain, nt_status);
680 domain_rec = wcache_tdc_fetch_domain(frame, dns_domain);
681 SAFE_FREE(dns_domain);
684 nt_status = NT_STATUS_TRUSTED_DOMAIN_FAILURE;
685 BAIL_ON_NTSTATUS_ERROR(nt_status);
688 sam_name = ads_pull_string(ads, frame, msg, "sAMAccountName");
689 BAIL_ON_PTR_ERROR(sam_name, nt_status);
691 len = asprintf(name, "%s\\%s", domain_rec->domain_name, sam_name);
694 BAIL_ON_PTR_ERROR((*name), nt_status);
697 nt_status = NT_STATUS_OK;
700 talloc_destroy(frame);
705 /*********************************************************************
706 ********************************************************************/
708 NTSTATUS gc_sid_to_name(const struct dom_sid *sid,
710 enum lsa_SidType *sid_type)
712 TALLOC_CTX *frame = talloc_stackframe();
713 NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
715 ADS_STRUCT *ads = NULL;
716 LDAPMessage *msg = NULL;
721 sid_string = sid_binstring(frame, sid);
722 BAIL_ON_PTR_ERROR(sid_string, nt_status);
724 filter = talloc_asprintf(frame, "(objectSid=%s)", sid_string);
725 TALLOC_FREE(sid_string);
726 BAIL_ON_PTR_ERROR(filter, nt_status);
728 nt_status = gc_search_all_forests_unique(filter, &ads, &msg);
729 BAIL_ON_NTSTATUS_ERROR(nt_status);
731 nt_status = get_object_account_name(ads, msg, name);
732 BAIL_ON_NTSTATUS_ERROR(nt_status);
734 nt_status = get_sid_type(ads, msg, sid_type);
735 BAIL_ON_NTSTATUS_ERROR(nt_status);
738 ads_msgfree(ads, msg);
739 talloc_destroy(frame);
744 /**********************************************************************
745 *********************************************************************/
747 NTSTATUS add_ads_result_to_array(ADS_STRUCT *ads,
749 ADS_STRUCT ***ads_list,
750 LDAPMessage ***msg_list,
753 NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
754 ADS_STRUCT **ads_tmp = NULL;
755 LDAPMessage **msg_tmp = NULL;
759 nt_status = NT_STATUS_INVALID_PARAMETER;
760 BAIL_ON_NTSTATUS_ERROR(nt_status);
764 /* Don't add a response with no entries */
766 if (ads_count_replies(ads, msg) == 0) {
772 ads_tmp = TALLOC_ARRAY(NULL, ADS_STRUCT*, 1);
773 BAIL_ON_PTR_ERROR(ads_tmp, nt_status);
775 msg_tmp = TALLOC_ARRAY(NULL, LDAPMessage*, 1);
776 BAIL_ON_PTR_ERROR(msg_tmp, nt_status);
778 ads_tmp = TALLOC_REALLOC_ARRAY(*ads_list, *ads_list, ADS_STRUCT*,
780 BAIL_ON_PTR_ERROR(ads_tmp, nt_status);
782 msg_tmp = TALLOC_REALLOC_ARRAY(*msg_list, *msg_list, LDAPMessage*,
784 BAIL_ON_PTR_ERROR(msg_tmp, nt_status);
787 ads_tmp[count] = ads;
788 msg_tmp[count] = msg;
795 nt_status = NT_STATUS_OK;
798 if (!NT_STATUS_IS_OK(nt_status)) {
799 talloc_destroy(ads_tmp);
800 talloc_destroy(msg_tmp);
806 /**********************************************************************
807 Frees search results. Do not free the ads_list as these are
808 references back to the GC search structures.
809 *********************************************************************/
811 void free_result_array(ADS_STRUCT **ads_list,
812 LDAPMessage **msg_list,
817 for (i=0; i<num_resp; i++) {
818 ads_msgfree(ads_list[i], msg_list[i]);
821 talloc_destroy(ads_list);
822 talloc_destroy(msg_list);
825 /**********************************************************************
826 Check that we have exactly one entry from the search
827 *********************************************************************/
829 NTSTATUS check_result_unique(ADS_STRUCT *ads, LDAPMessage *msg)
834 count = ads_count_replies(ads, msg);
837 nt_status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
838 BAIL_ON_NTSTATUS_ERROR(nt_status);
842 nt_status = NT_STATUS_DUPLICATE_NAME;
843 BAIL_ON_NTSTATUS_ERROR(nt_status);
846 nt_status = NT_STATUS_OK;