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.
24 #include "idmap_adex.h"
25 #include "libads/cldap.h"
28 #define DBGC_CLASS DBGC_IDMAP
30 static struct gc_info
*_gc_server_list
= NULL
;
33 /**********************************************************************
34 *********************************************************************/
36 static struct gc_info
*gc_list_head(void)
38 return _gc_server_list
;
41 /**********************************************************************
42 Checks if either of the domains is a subdomain of the other
43 *********************************************************************/
45 static bool is_subdomain(const char* a
, const char *b
)
48 TALLOC_CTX
*frame
= talloc_stackframe();
60 /* Normalize the case */
62 x
= talloc_strdup(frame
, a
);
63 y
= talloc_strdup(frame
, b
);
74 if (strcmp(x
, y
) == 0) {
79 /* Check for trailing substrings */
82 if (s
&& (strlen(s
) == strlen(y
))) {
88 if (s
&& (strlen(s
) == strlen(x
))) {
94 talloc_destroy(frame
);
99 /**********************************************************************
100 *********************************************************************/
102 NTSTATUS
gc_find_forest_root(struct gc_info
*gc
, const char *domain
)
104 ADS_STRUCT
*ads
= NULL
;
105 ADS_STATUS ads_status
;
106 NTSTATUS nt_status
= NT_STATUS_UNSUCCESSFUL
;
107 struct NETLOGON_SAM_LOGON_RESPONSE_EX cldap_reply
;
108 TALLOC_CTX
*frame
= talloc_stackframe();
110 if (!gc
|| !domain
) {
111 return NT_STATUS_INVALID_PARAMETER
;
114 ZERO_STRUCT(cldap_reply
);
116 ads
= ads_init(domain
, NULL
, NULL
);
117 BAIL_ON_PTR_ERROR(ads
, nt_status
);
119 ads
->auth
.flags
= ADS_AUTH_NO_BIND
;
120 ads_status
= ads_connect(ads
);
121 if (!ADS_ERR_OK(ads_status
)) {
122 DEBUG(4, ("find_forest_root: ads_connect(%s) failed! (%s)\n",
123 domain
, ads_errstr(ads_status
)));
125 nt_status
= ads_ntstatus(ads_status
);
126 BAIL_ON_NTSTATUS_ERROR(nt_status
);
128 if (!ads_cldap_netlogon_5(frame
,
129 ads
->config
.ldap_server_name
,
133 DEBUG(4,("find_forest_root: Failed to get a CLDAP reply from %s!\n",
134 ads
->server
.ldap_server
));
135 nt_status
= NT_STATUS_IO_TIMEOUT
;
136 BAIL_ON_NTSTATUS_ERROR(nt_status
);
139 gc
->forest_name
= talloc_strdup(gc
, cldap_reply
.forest
);
140 BAIL_ON_PTR_ERROR(gc
->forest_name
, nt_status
);
150 /**********************************************************************
151 *********************************************************************/
153 static NTSTATUS
gc_add_forest(const char *domain
)
155 NTSTATUS nt_status
= NT_STATUS_UNSUCCESSFUL
;
156 struct gc_info
*gc
= NULL
;
157 struct gc_info
*find_gc
= NULL
;
159 ADS_STRUCT
*ads
= NULL
;
160 struct likewise_cell
*primary_cell
= NULL
;
162 primary_cell
= cell_list_head();
164 nt_status
= NT_STATUS_INVALID_SERVER_STATE
;
165 BAIL_ON_NTSTATUS_ERROR(nt_status
);
168 /* Check for duplicates based on domain name first as this
169 requires no connection */
171 find_gc
= gc_list_head();
173 if (strequal (find_gc
->forest_name
, domain
))
175 find_gc
= find_gc
->next
;
179 DEBUG(10,("gc_add_forest: %s already in list\n", find_gc
->forest_name
));
183 if ((gc
= TALLOC_ZERO_P(NULL
, struct gc_info
)) == NULL
) {
184 nt_status
= NT_STATUS_NO_MEMORY
;
185 BAIL_ON_NTSTATUS_ERROR(nt_status
);
188 /* Query the rootDSE for the forest root naming conect first.
189 Check that the a GC server for the forest has not already
192 nt_status
= gc_find_forest_root(gc
, domain
);
193 BAIL_ON_NTSTATUS_ERROR(nt_status
);
195 find_gc
= gc_list_head();
197 if (strequal (find_gc
->forest_name
, gc
->forest_name
))
199 find_gc
= find_gc
->next
;
203 DEBUG(10,("gc_add_forest: Forest %s already in list\n",
204 find_gc
->forest_name
));
208 /* Not found, so add it here. Make sure we connect to
209 a DC in _this_ domain and not the forest root. */
211 dn
= ads_build_dn(gc
->forest_name
);
212 BAIL_ON_PTR_ERROR(dn
, nt_status
);
214 gc
->search_base
= talloc_strdup(gc
, dn
);
216 BAIL_ON_PTR_ERROR(gc
->search_base
, nt_status
);
219 /* Can't use cell_connect_dn() here as there is no way to
220 specifiy the LWCELL_FLAG_GC_CELL flag setting for cell_connect() */
222 nt_status
= cell_connect_dn(&gc
->forest_cell
, gc
->search_base
);
223 BAIL_ON_NTSTATUS_ERROR(nt_status
);
226 gc
->forest_cell
= cell_new();
227 BAIL_ON_PTR_ERROR(gc
->forest_cell
, nt_status
);
229 /* Set the DNS domain, dn, etc ... and add it to the list */
231 cell_set_dns_domain(gc
->forest_cell
, gc
->forest_name
);
232 cell_set_dn(gc
->forest_cell
, gc
->search_base
);
233 cell_set_flags(gc
->forest_cell
, LWCELL_FLAG_GC_CELL
);
236 /* It is possible to belong to a non-forest cell and a
237 non-provisioned forest (at our domain levele). In that
238 case, we should just inherit the flags from our primary
239 cell since the GC searches will match our own schema
242 if (strequal(primary_cell
->forest_name
, gc
->forest_name
)
243 || is_subdomain(primary_cell
->dns_domain
, gc
->forest_name
))
245 cell_set_flags(gc
->forest_cell
, cell_flags(primary_cell
));
247 /* outside of our domain */
249 nt_status
= cell_connect(gc
->forest_cell
);
250 BAIL_ON_NTSTATUS_ERROR(nt_status
);
252 nt_status
= cell_lookup_settings(gc
->forest_cell
);
253 BAIL_ON_NTSTATUS_ERROR(nt_status
);
255 /* Drop the connection now that we have the settings */
257 ads
= cell_connection(gc
->forest_cell
);
259 cell_set_connection(gc
->forest_cell
, NULL
);
262 DLIST_ADD_END(_gc_server_list
, gc
, struct gc_info
*);
264 DEBUG(10,("gc_add_forest: Added %s to Global Catalog list of servers\n",
267 nt_status
= NT_STATUS_OK
;
270 if (!NT_STATUS_IS_OK(nt_status
)) {
272 DEBUG(3,("LWI: Failed to add new GC connection for %s (%s)\n",
273 domain
, nt_errstr(nt_status
)));
279 /**********************************************************************
280 *********************************************************************/
282 static void gc_server_list_destroy(void)
284 struct gc_info
*gc
= gc_list_head();
287 struct gc_info
*p
= gc
->next
;
289 cell_destroy(gc
->forest_cell
);
295 _gc_server_list
= NULL
;
300 /**********************************************************************
301 Setup the initial list of forests and initial the forest cell
302 settings for each. FIXME!!!
303 *********************************************************************/
305 NTSTATUS
gc_init_list(void)
307 NTSTATUS nt_status
= NT_STATUS_UNSUCCESSFUL
;
308 struct winbindd_tdc_domain
*domains
= NULL
;
309 size_t num_domains
= 0;
312 if (_gc_server_list
!= NULL
) {
313 gc_server_list_destroy();
316 if (!wcache_tdc_fetch_list(&domains
, &num_domains
)) {
317 nt_status
= NT_STATUS_CANT_ACCESS_DOMAIN_INFO
;
318 BAIL_ON_NTSTATUS_ERROR(nt_status
);
321 /* Find our forest first. Have to try all domains here starting
322 with our own. gc_add_forest() filters duplicates */
324 nt_status
= gc_add_forest(lp_realm());
325 WARN_ON_NTSTATUS_ERROR(nt_status
);
327 for (i
=0; i
<num_domains
; i
++) {
328 uint32_t flags
= (NETR_TRUST_FLAG_IN_FOREST
);
330 /* I think we should be able to break out of loop once
331 we add a GC for our forest and not have to test every one.
332 In fact, this entire loop is probably irrelevant since
333 the GC location code should always find a GC given lp_realm().
334 Will have to spend time testing before making the change.
337 if ((domains
[i
].trust_flags
& flags
) == flags
) {
338 nt_status
= gc_add_forest(domains
[i
].dns_name
);
339 WARN_ON_NTSTATUS_ERROR(nt_status
);
340 /* Don't BAIL here since not every domain may
345 /* Now add trusted forests. gc_add_forest() will filter out
346 duplicates. Check everything with an incoming trust path
347 that is not in our own forest. */
349 for (i
=0; i
<num_domains
; i
++) {
350 uint32_t flags
= domains
[i
].trust_flags
;
351 uint32_t attribs
= domains
[i
].trust_attribs
;
353 /* Skip non_AD domains */
355 if (strlen(domains
[i
].dns_name
) == 0) {
359 /* Only add a GC for a forest outside of our own.
360 Ignore QUARANTINED/EXTERNAL trusts */
362 if ((flags
& NETR_TRUST_FLAG_INBOUND
)
363 && !(flags
& NETR_TRUST_FLAG_IN_FOREST
)
364 && (attribs
& NETR_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
))
366 nt_status
= gc_add_forest(domains
[i
].dns_name
);
367 WARN_ON_NTSTATUS_ERROR(nt_status
);
371 nt_status
= NT_STATUS_OK
;
374 if (!NT_STATUS_IS_OK(nt_status
)) {
375 DEBUG(2,("LWI: Failed to initialized GC list (%s)\n",
376 nt_errstr(nt_status
)));
379 TALLOC_FREE(domains
);
385 /**********************************************************************
386 *********************************************************************/
388 struct gc_info
*gc_search_start(void)
390 NTSTATUS nt_status
= NT_STATUS_OK
;
391 struct gc_info
*gc
= gc_list_head();
394 nt_status
= gc_init_list();
395 BAIL_ON_NTSTATUS_ERROR(nt_status
);
401 if (!NT_STATUS_IS_OK(nt_status
)) {
402 DEBUG(2,("LWI: Failed to initialize GC list (%s)\n",
403 nt_errstr(nt_status
)));
409 /**********************************************************************
410 Search Global Catalog. Always search our own forest. The flags set
411 controls whether or not we search cross forest. Assume that the
412 resulting set is always returned from one GC so that we don't have to
413 both combining the LDAPMessage * results
414 *********************************************************************/
416 NTSTATUS
gc_search_forest(struct gc_info
*gc
,
420 NTSTATUS nt_status
= NT_STATUS_UNSUCCESSFUL
;
421 ADS_STATUS ads_status
= ADS_ERROR_NT(NT_STATUS_UNSUCCESSFUL
);
422 const char *attrs
[] = {"*", NULL
};
423 LDAPMessage
*m
= NULL
;
425 if (!gc
|| !msg
|| !filter
) {
426 nt_status
= NT_STATUS_INVALID_PARAMETER
;
427 BAIL_ON_NTSTATUS_ERROR(nt_status
);
430 /* When you have multiple domain trees in a forest, the
431 GC will search all naming contexts when you send it
432 and empty ("") base search suffix. Tested against
435 ads_status
= cell_do_search(gc
->forest_cell
, "",
436 LDAP_SCOPE_SUBTREE
, filter
, attrs
, &m
);
437 nt_status
= ads_ntstatus(ads_status
);
438 BAIL_ON_NTSTATUS_ERROR(nt_status
);
443 if (!NT_STATUS_IS_OK(nt_status
)) {
444 DEBUG(2,("LWI: Forest wide search %s failed (%s)\n",
445 filter
, nt_errstr(nt_status
)));
451 /**********************************************************************
452 Search all forests via GC and return the results in an array of
453 ADS_STRUCT/LDAPMessage pairs.
454 *********************************************************************/
456 NTSTATUS
gc_search_all_forests(const char *filter
,
457 ADS_STRUCT
***ads_list
,
458 LDAPMessage
***msg_list
,
459 int *num_resp
, uint32_t flags
)
461 NTSTATUS nt_status
= NT_STATUS_UNSUCCESSFUL
;
462 struct gc_info
*gc
= NULL
;
463 uint32_t test_flags
= ADEX_GC_SEARCH_CHECK_UNIQUE
;
469 if ((gc
= gc_search_start()) == NULL
) {
470 nt_status
= NT_STATUS_INVALID_DOMAIN_STATE
;
471 BAIL_ON_NTSTATUS_ERROR(nt_status
);
475 LDAPMessage
*m
= NULL
;
477 nt_status
= gc_search_forest(gc
, &m
, filter
);
478 if (!NT_STATUS_IS_OK(nt_status
)) {
483 nt_status
= add_ads_result_to_array(cell_connection(gc
->forest_cell
),
484 m
, ads_list
, msg_list
,
486 BAIL_ON_NTSTATUS_ERROR(nt_status
);
488 /* If there can only be one match, then we are done */
490 if ((*num_resp
> 0) && ((flags
& test_flags
) == test_flags
)) {
497 if (*num_resp
== 0) {
498 nt_status
= NT_STATUS_OBJECT_NAME_NOT_FOUND
;
499 BAIL_ON_NTSTATUS_ERROR(nt_status
);
502 nt_status
= NT_STATUS_OK
;
508 /**********************************************************************
509 Search all forests via GC and return the results in an array of
510 ADS_STRUCT/LDAPMessage pairs.
511 *********************************************************************/
513 NTSTATUS
gc_search_all_forests_unique(const char *filter
,
517 ADS_STRUCT
**ads_list
= NULL
;
518 LDAPMessage
**msg_list
= NULL
;
520 NTSTATUS nt_status
= NT_STATUS_UNSUCCESSFUL
;
522 nt_status
= gc_search_all_forests(filter
, &ads_list
,
523 &msg_list
, &num_resp
,
524 ADEX_GC_SEARCH_CHECK_UNIQUE
);
525 BAIL_ON_NTSTATUS_ERROR(nt_status
);
527 nt_status
= check_result_unique(ads_list
[0], msg_list
[0]);
528 BAIL_ON_NTSTATUS_ERROR(nt_status
);
534 /* Be care that we don't free the msg result being returned */
536 if (!NT_STATUS_IS_OK(nt_status
)) {
537 free_result_array(ads_list
, msg_list
, num_resp
);
539 talloc_destroy(ads_list
);
540 talloc_destroy(msg_list
);
546 /*********************************************************************
547 ********************************************************************/
549 NTSTATUS
gc_name_to_sid(const char *domain
,
552 enum lsa_SidType
*sid_type
)
554 TALLOC_CTX
*frame
= talloc_stackframe();
556 NTSTATUS nt_status
= NT_STATUS_UNSUCCESSFUL
;
558 ADS_STRUCT
*ads
= NULL
;
559 LDAPMessage
*msg
= NULL
;
560 LDAPMessage
*e
= NULL
;
562 char *dns_domain
= NULL
;
563 ADS_STRUCT
**ads_list
= NULL
;
564 LDAPMessage
**msg_list
= NULL
;
568 /* Strip the "DOMAIN\" prefix if necessary and search for
569 a matching sAMAccountName in the forest */
571 if ((p
= strchr_m( name
, '\\' )) == NULL
)
572 name_user
= talloc_strdup( frame
, name
);
574 name_user
= talloc_strdup( frame
, p
+1 );
575 BAIL_ON_PTR_ERROR(name_user
, nt_status
);
577 name_filter
= talloc_asprintf(frame
, "(sAMAccountName=%s)", name_user
);
578 BAIL_ON_PTR_ERROR(name_filter
, nt_status
);
580 nt_status
= gc_search_all_forests(name_filter
, &ads_list
,
581 &msg_list
, &num_resp
, 0);
582 BAIL_ON_NTSTATUS_ERROR(nt_status
);
584 /* Assume failure until we know otherwise*/
586 nt_status
= NT_STATUS_OBJECT_NAME_NOT_FOUND
;
588 /* Match the domain name from the DN */
590 for (i
=0; i
<num_resp
; i
++) {
594 e
= ads_first_entry(ads
, msg
);
596 struct winbindd_tdc_domain
*domain_rec
;
598 dn
= ads_get_dn(ads
, frame
, e
);
599 BAIL_ON_PTR_ERROR(dn
, nt_status
);
601 dns_domain
= cell_dn_to_dns(dn
);
603 BAIL_ON_PTR_ERROR(dns_domain
, nt_status
);
605 domain_rec
= wcache_tdc_fetch_domain(frame
, dns_domain
);
606 SAFE_FREE(dns_domain
);
608 /* Ignore failures and continue the search */
611 e
= ads_next_entry(ads
, e
);
615 /* Check for a match on the domain name */
617 if (strequal(domain
, domain_rec
->domain_name
)) {
618 if (!ads_pull_sid(ads
, e
, "objectSid", sid
)) {
619 nt_status
= NT_STATUS_INVALID_SID
;
620 BAIL_ON_NTSTATUS_ERROR(nt_status
);
623 talloc_destroy(domain_rec
);
625 nt_status
= get_sid_type(ads
, msg
, sid_type
);
626 BAIL_ON_NTSTATUS_ERROR(nt_status
);
629 nt_status
= NT_STATUS_OK
;
633 /* once more around thew merry-go-round */
635 talloc_destroy(domain_rec
);
636 e
= ads_next_entry(ads
, e
);
641 free_result_array(ads_list
, msg_list
, num_resp
);
642 talloc_destroy(frame
);
647 /********************************************************************
648 Pull an attribute string value
649 *******************************************************************/
651 static NTSTATUS
get_object_account_name(ADS_STRUCT
*ads
,
655 NTSTATUS nt_status
= NT_STATUS_UNSUCCESSFUL
;
656 char *sam_name
= NULL
;
657 struct winbindd_tdc_domain
*domain_rec
= NULL
;
658 char *dns_domain
= NULL
;
660 TALLOC_CTX
*frame
= talloc_stackframe();
663 /* Check parameters */
665 if (!ads
|| !msg
|| !name
) {
666 nt_status
= NT_STATUS_INVALID_PARAMETER
;
667 BAIL_ON_NTSTATUS_ERROR(nt_status
);
670 /* get the name and domain */
672 dn
= ads_get_dn(ads
, frame
, msg
);
673 BAIL_ON_PTR_ERROR(dn
, nt_status
);
675 DEBUG(10,("get_object_account_name: dn = \"%s\"\n", dn
));
677 dns_domain
= cell_dn_to_dns(dn
);
679 BAIL_ON_PTR_ERROR(dns_domain
, nt_status
);
681 domain_rec
= wcache_tdc_fetch_domain(frame
, dns_domain
);
682 SAFE_FREE(dns_domain
);
685 nt_status
= NT_STATUS_TRUSTED_DOMAIN_FAILURE
;
686 BAIL_ON_NTSTATUS_ERROR(nt_status
);
689 sam_name
= ads_pull_string(ads
, frame
, msg
, "sAMAccountName");
690 BAIL_ON_PTR_ERROR(sam_name
, nt_status
);
692 len
= asprintf(name
, "%s\\%s", domain_rec
->domain_name
, sam_name
);
695 BAIL_ON_PTR_ERROR((*name
), nt_status
);
698 nt_status
= NT_STATUS_OK
;
701 talloc_destroy(frame
);
706 /*********************************************************************
707 ********************************************************************/
709 NTSTATUS
gc_sid_to_name(const struct dom_sid
*sid
,
711 enum lsa_SidType
*sid_type
)
713 TALLOC_CTX
*frame
= talloc_stackframe();
714 NTSTATUS nt_status
= NT_STATUS_UNSUCCESSFUL
;
716 ADS_STRUCT
*ads
= NULL
;
717 LDAPMessage
*msg
= NULL
;
722 sid_string
= sid_binstring(frame
, sid
);
723 BAIL_ON_PTR_ERROR(sid_string
, nt_status
);
725 filter
= talloc_asprintf(frame
, "(objectSid=%s)", sid_string
);
726 TALLOC_FREE(sid_string
);
727 BAIL_ON_PTR_ERROR(filter
, nt_status
);
729 nt_status
= gc_search_all_forests_unique(filter
, &ads
, &msg
);
730 BAIL_ON_NTSTATUS_ERROR(nt_status
);
732 nt_status
= get_object_account_name(ads
, msg
, name
);
733 BAIL_ON_NTSTATUS_ERROR(nt_status
);
735 nt_status
= get_sid_type(ads
, msg
, sid_type
);
736 BAIL_ON_NTSTATUS_ERROR(nt_status
);
739 ads_msgfree(ads
, msg
);
740 talloc_destroy(frame
);
745 /**********************************************************************
746 *********************************************************************/
748 NTSTATUS
add_ads_result_to_array(ADS_STRUCT
*ads
,
750 ADS_STRUCT
***ads_list
,
751 LDAPMessage
***msg_list
,
754 NTSTATUS nt_status
= NT_STATUS_UNSUCCESSFUL
;
755 ADS_STRUCT
**ads_tmp
= NULL
;
756 LDAPMessage
**msg_tmp
= NULL
;
760 nt_status
= NT_STATUS_INVALID_PARAMETER
;
761 BAIL_ON_NTSTATUS_ERROR(nt_status
);
765 /* Don't add a response with no entries */
767 if (ads_count_replies(ads
, msg
) == 0) {
773 ads_tmp
= TALLOC_ARRAY(NULL
, ADS_STRUCT
*, 1);
774 BAIL_ON_PTR_ERROR(ads_tmp
, nt_status
);
776 msg_tmp
= TALLOC_ARRAY(NULL
, LDAPMessage
*, 1);
777 BAIL_ON_PTR_ERROR(msg_tmp
, nt_status
);
779 ads_tmp
= TALLOC_REALLOC_ARRAY(*ads_list
, *ads_list
, ADS_STRUCT
*,
781 BAIL_ON_PTR_ERROR(ads_tmp
, nt_status
);
783 msg_tmp
= TALLOC_REALLOC_ARRAY(*msg_list
, *msg_list
, LDAPMessage
*,
785 BAIL_ON_PTR_ERROR(msg_tmp
, nt_status
);
788 ads_tmp
[count
] = ads
;
789 msg_tmp
[count
] = msg
;
796 nt_status
= NT_STATUS_OK
;
799 if (!NT_STATUS_IS_OK(nt_status
)) {
800 talloc_destroy(ads_tmp
);
801 talloc_destroy(msg_tmp
);
807 /**********************************************************************
808 Frees search results. Do not free the ads_list as these are
809 references back to the GC search structures.
810 *********************************************************************/
812 void free_result_array(ADS_STRUCT
**ads_list
,
813 LDAPMessage
**msg_list
,
818 for (i
=0; i
<num_resp
; i
++) {
819 ads_msgfree(ads_list
[i
], msg_list
[i
]);
822 talloc_destroy(ads_list
);
823 talloc_destroy(msg_list
);
826 /**********************************************************************
827 Check that we have exactly one entry from the search
828 *********************************************************************/
830 NTSTATUS
check_result_unique(ADS_STRUCT
*ads
, LDAPMessage
*msg
)
835 count
= ads_count_replies(ads
, msg
);
838 nt_status
= NT_STATUS_OBJECT_NAME_NOT_FOUND
;
839 BAIL_ON_NTSTATUS_ERROR(nt_status
);
843 nt_status
= NT_STATUS_DUPLICATE_NAME
;
844 BAIL_ON_NTSTATUS_ERROR(nt_status
);
847 nt_status
= NT_STATUS_OK
;