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"
26 #include "../libcli/ldap/ldap_ndr.h"
29 #define DBGC_CLASS DBGC_IDMAP
31 static struct gc_info
*_gc_server_list
= NULL
;
34 /**********************************************************************
35 *********************************************************************/
37 static struct gc_info
*gc_list_head(void)
39 return _gc_server_list
;
42 /**********************************************************************
43 Checks if either of the domains is a subdomain of the other
44 *********************************************************************/
46 static bool is_subdomain(const char* a
, const char *b
)
49 TALLOC_CTX
*frame
= talloc_stackframe();
61 /* Normalize the case */
63 x
= talloc_strdup(frame
, a
);
64 y
= talloc_strdup(frame
, b
);
75 if (strcmp(x
, y
) == 0) {
80 /* Check for trailing substrings */
83 if (s
&& (strlen(s
) == strlen(y
))) {
89 if (s
&& (strlen(s
) == strlen(x
))) {
95 talloc_destroy(frame
);
100 /**********************************************************************
101 *********************************************************************/
103 NTSTATUS
gc_find_forest_root(struct gc_info
*gc
, const char *domain
)
105 ADS_STRUCT
*ads
= NULL
;
106 ADS_STATUS ads_status
;
107 NTSTATUS nt_status
= NT_STATUS_UNSUCCESSFUL
;
108 struct NETLOGON_SAM_LOGON_RESPONSE_EX cldap_reply
;
109 TALLOC_CTX
*frame
= talloc_stackframe();
111 if (!gc
|| !domain
) {
112 return NT_STATUS_INVALID_PARAMETER
;
115 ZERO_STRUCT(cldap_reply
);
117 ads
= ads_init(domain
, NULL
, NULL
);
118 BAIL_ON_PTR_ERROR(ads
, nt_status
);
120 ads
->auth
.flags
= ADS_AUTH_NO_BIND
;
121 ads_status
= ads_connect(ads
);
122 if (!ADS_ERR_OK(ads_status
)) {
123 DEBUG(4, ("find_forest_root: ads_connect(%s) failed! (%s)\n",
124 domain
, ads_errstr(ads_status
)));
126 nt_status
= ads_ntstatus(ads_status
);
127 BAIL_ON_NTSTATUS_ERROR(nt_status
);
129 if (!ads_cldap_netlogon_5(frame
,
130 ads
->config
.ldap_server_name
,
134 DEBUG(4,("find_forest_root: Failed to get a CLDAP reply from %s!\n",
135 ads
->server
.ldap_server
));
136 nt_status
= NT_STATUS_IO_TIMEOUT
;
137 BAIL_ON_NTSTATUS_ERROR(nt_status
);
140 gc
->forest_name
= talloc_strdup(gc
, cldap_reply
.forest
);
141 BAIL_ON_PTR_ERROR(gc
->forest_name
, nt_status
);
151 /**********************************************************************
152 *********************************************************************/
154 static NTSTATUS
gc_add_forest(const char *domain
)
156 NTSTATUS nt_status
= NT_STATUS_UNSUCCESSFUL
;
157 struct gc_info
*gc
= NULL
;
158 struct gc_info
*find_gc
= NULL
;
160 ADS_STRUCT
*ads
= NULL
;
161 struct likewise_cell
*primary_cell
= NULL
;
163 primary_cell
= cell_list_head();
165 nt_status
= NT_STATUS_INVALID_SERVER_STATE
;
166 BAIL_ON_NTSTATUS_ERROR(nt_status
);
169 /* Check for duplicates based on domain name first as this
170 requires no connection */
172 find_gc
= gc_list_head();
174 if (strequal (find_gc
->forest_name
, domain
))
176 find_gc
= find_gc
->next
;
180 DEBUG(10,("gc_add_forest: %s already in list\n", find_gc
->forest_name
));
184 if ((gc
= TALLOC_ZERO_P(NULL
, struct gc_info
)) == NULL
) {
185 nt_status
= NT_STATUS_NO_MEMORY
;
186 BAIL_ON_NTSTATUS_ERROR(nt_status
);
189 /* Query the rootDSE for the forest root naming conect first.
190 Check that the a GC server for the forest has not already
193 nt_status
= gc_find_forest_root(gc
, domain
);
194 BAIL_ON_NTSTATUS_ERROR(nt_status
);
196 find_gc
= gc_list_head();
198 if (strequal (find_gc
->forest_name
, gc
->forest_name
))
200 find_gc
= find_gc
->next
;
204 DEBUG(10,("gc_add_forest: Forest %s already in list\n",
205 find_gc
->forest_name
));
209 /* Not found, so add it here. Make sure we connect to
210 a DC in _this_ domain and not the forest root. */
212 dn
= ads_build_dn(gc
->forest_name
);
213 BAIL_ON_PTR_ERROR(dn
, nt_status
);
215 gc
->search_base
= talloc_strdup(gc
, dn
);
217 BAIL_ON_PTR_ERROR(gc
->search_base
, nt_status
);
220 /* Can't use cell_connect_dn() here as there is no way to
221 specifiy the LWCELL_FLAG_GC_CELL flag setting for cell_connect() */
223 nt_status
= cell_connect_dn(&gc
->forest_cell
, gc
->search_base
);
224 BAIL_ON_NTSTATUS_ERROR(nt_status
);
227 gc
->forest_cell
= cell_new();
228 BAIL_ON_PTR_ERROR(gc
->forest_cell
, nt_status
);
230 /* Set the DNS domain, dn, etc ... and add it to the list */
232 cell_set_dns_domain(gc
->forest_cell
, gc
->forest_name
);
233 cell_set_dn(gc
->forest_cell
, gc
->search_base
);
234 cell_set_flags(gc
->forest_cell
, LWCELL_FLAG_GC_CELL
);
237 /* It is possible to belong to a non-forest cell and a
238 non-provisioned forest (at our domain levele). In that
239 case, we should just inherit the flags from our primary
240 cell since the GC searches will match our own schema
243 if (strequal(primary_cell
->forest_name
, gc
->forest_name
)
244 || is_subdomain(primary_cell
->dns_domain
, gc
->forest_name
))
246 cell_set_flags(gc
->forest_cell
, cell_flags(primary_cell
));
248 /* outside of our domain */
250 nt_status
= cell_connect(gc
->forest_cell
);
251 BAIL_ON_NTSTATUS_ERROR(nt_status
);
253 nt_status
= cell_lookup_settings(gc
->forest_cell
);
254 BAIL_ON_NTSTATUS_ERROR(nt_status
);
256 /* Drop the connection now that we have the settings */
258 ads
= cell_connection(gc
->forest_cell
);
260 cell_set_connection(gc
->forest_cell
, NULL
);
263 DLIST_ADD_END(_gc_server_list
, gc
, struct gc_info
*);
265 DEBUG(10,("gc_add_forest: Added %s to Global Catalog list of servers\n",
268 nt_status
= NT_STATUS_OK
;
271 if (!NT_STATUS_IS_OK(nt_status
)) {
273 DEBUG(3,("LWI: Failed to add new GC connection for %s (%s)\n",
274 domain
, nt_errstr(nt_status
)));
280 /**********************************************************************
281 *********************************************************************/
283 static void gc_server_list_destroy(void)
285 struct gc_info
*gc
= gc_list_head();
288 struct gc_info
*p
= gc
->next
;
290 cell_destroy(gc
->forest_cell
);
296 _gc_server_list
= NULL
;
301 /**********************************************************************
302 Setup the initial list of forests and initial the forest cell
303 settings for each. FIXME!!!
304 *********************************************************************/
306 NTSTATUS
gc_init_list(void)
308 NTSTATUS nt_status
= NT_STATUS_UNSUCCESSFUL
;
309 struct winbindd_tdc_domain
*domains
= NULL
;
310 size_t num_domains
= 0;
313 if (_gc_server_list
!= NULL
) {
314 gc_server_list_destroy();
317 if (!wcache_tdc_fetch_list(&domains
, &num_domains
)) {
318 nt_status
= NT_STATUS_CANT_ACCESS_DOMAIN_INFO
;
319 BAIL_ON_NTSTATUS_ERROR(nt_status
);
322 /* Find our forest first. Have to try all domains here starting
323 with our own. gc_add_forest() filters duplicates */
325 nt_status
= gc_add_forest(lp_realm());
326 WARN_ON_NTSTATUS_ERROR(nt_status
);
328 for (i
=0; i
<num_domains
; i
++) {
329 uint32_t flags
= (NETR_TRUST_FLAG_IN_FOREST
);
331 /* I think we should be able to break out of loop once
332 we add a GC for our forest and not have to test every one.
333 In fact, this entire loop is probably irrelevant since
334 the GC location code should always find a GC given lp_realm().
335 Will have to spend time testing before making the change.
338 if ((domains
[i
].trust_flags
& flags
) == flags
) {
339 nt_status
= gc_add_forest(domains
[i
].dns_name
);
340 WARN_ON_NTSTATUS_ERROR(nt_status
);
341 /* Don't BAIL here since not every domain may
346 /* Now add trusted forests. gc_add_forest() will filter out
347 duplicates. Check everything with an incoming trust path
348 that is not in our own forest. */
350 for (i
=0; i
<num_domains
; i
++) {
351 uint32_t flags
= domains
[i
].trust_flags
;
352 uint32_t attribs
= domains
[i
].trust_attribs
;
354 /* Skip non_AD domains */
356 if (strlen(domains
[i
].dns_name
) == 0) {
360 /* Only add a GC for a forest outside of our own.
361 Ignore QUARANTINED/EXTERNAL trusts */
363 if ((flags
& NETR_TRUST_FLAG_INBOUND
)
364 && !(flags
& NETR_TRUST_FLAG_IN_FOREST
)
365 && (attribs
& NETR_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
))
367 nt_status
= gc_add_forest(domains
[i
].dns_name
);
368 WARN_ON_NTSTATUS_ERROR(nt_status
);
372 nt_status
= NT_STATUS_OK
;
375 if (!NT_STATUS_IS_OK(nt_status
)) {
376 DEBUG(2,("LWI: Failed to initialized GC list (%s)\n",
377 nt_errstr(nt_status
)));
380 TALLOC_FREE(domains
);
386 /**********************************************************************
387 *********************************************************************/
389 struct gc_info
*gc_search_start(void)
391 NTSTATUS nt_status
= NT_STATUS_OK
;
392 struct gc_info
*gc
= gc_list_head();
395 nt_status
= gc_init_list();
396 BAIL_ON_NTSTATUS_ERROR(nt_status
);
402 if (!NT_STATUS_IS_OK(nt_status
)) {
403 DEBUG(2,("LWI: Failed to initialize GC list (%s)\n",
404 nt_errstr(nt_status
)));
410 /**********************************************************************
411 Search Global Catalog. Always search our own forest. The flags set
412 controls whether or not we search cross forest. Assume that the
413 resulting set is always returned from one GC so that we don't have to
414 both combining the LDAPMessage * results
415 *********************************************************************/
417 NTSTATUS
gc_search_forest(struct gc_info
*gc
,
421 NTSTATUS nt_status
= NT_STATUS_UNSUCCESSFUL
;
422 ADS_STATUS ads_status
= ADS_ERROR_NT(NT_STATUS_UNSUCCESSFUL
);
423 const char *attrs
[] = {"*", NULL
};
424 LDAPMessage
*m
= NULL
;
426 if (!gc
|| !msg
|| !filter
) {
427 nt_status
= NT_STATUS_INVALID_PARAMETER
;
428 BAIL_ON_NTSTATUS_ERROR(nt_status
);
431 /* When you have multiple domain trees in a forest, the
432 GC will search all naming contexts when you send it
433 and empty ("") base search suffix. Tested against
436 ads_status
= cell_do_search(gc
->forest_cell
, "",
437 LDAP_SCOPE_SUBTREE
, filter
, attrs
, &m
);
438 nt_status
= ads_ntstatus(ads_status
);
439 BAIL_ON_NTSTATUS_ERROR(nt_status
);
444 if (!NT_STATUS_IS_OK(nt_status
)) {
445 DEBUG(2,("LWI: Forest wide search %s failed (%s)\n",
446 filter
, nt_errstr(nt_status
)));
452 /**********************************************************************
453 Search all forests via GC and return the results in an array of
454 ADS_STRUCT/LDAPMessage pairs.
455 *********************************************************************/
457 NTSTATUS
gc_search_all_forests(const char *filter
,
458 ADS_STRUCT
***ads_list
,
459 LDAPMessage
***msg_list
,
460 int *num_resp
, uint32_t flags
)
462 NTSTATUS nt_status
= NT_STATUS_UNSUCCESSFUL
;
463 struct gc_info
*gc
= NULL
;
464 uint32_t test_flags
= ADEX_GC_SEARCH_CHECK_UNIQUE
;
470 if ((gc
= gc_search_start()) == NULL
) {
471 nt_status
= NT_STATUS_INVALID_DOMAIN_STATE
;
472 BAIL_ON_NTSTATUS_ERROR(nt_status
);
476 LDAPMessage
*m
= NULL
;
478 nt_status
= gc_search_forest(gc
, &m
, filter
);
479 if (!NT_STATUS_IS_OK(nt_status
)) {
484 nt_status
= add_ads_result_to_array(cell_connection(gc
->forest_cell
),
485 m
, ads_list
, msg_list
,
487 BAIL_ON_NTSTATUS_ERROR(nt_status
);
489 /* If there can only be one match, then we are done */
491 if ((*num_resp
> 0) && ((flags
& test_flags
) == test_flags
)) {
498 if (*num_resp
== 0) {
499 nt_status
= NT_STATUS_OBJECT_NAME_NOT_FOUND
;
500 BAIL_ON_NTSTATUS_ERROR(nt_status
);
503 nt_status
= NT_STATUS_OK
;
509 /**********************************************************************
510 Search all forests via GC and return the results in an array of
511 ADS_STRUCT/LDAPMessage pairs.
512 *********************************************************************/
514 NTSTATUS
gc_search_all_forests_unique(const char *filter
,
518 ADS_STRUCT
**ads_list
= NULL
;
519 LDAPMessage
**msg_list
= NULL
;
521 NTSTATUS nt_status
= NT_STATUS_UNSUCCESSFUL
;
523 nt_status
= gc_search_all_forests(filter
, &ads_list
,
524 &msg_list
, &num_resp
,
525 ADEX_GC_SEARCH_CHECK_UNIQUE
);
526 BAIL_ON_NTSTATUS_ERROR(nt_status
);
528 nt_status
= check_result_unique(ads_list
[0], msg_list
[0]);
529 BAIL_ON_NTSTATUS_ERROR(nt_status
);
535 /* Be care that we don't free the msg result being returned */
537 if (!NT_STATUS_IS_OK(nt_status
)) {
538 free_result_array(ads_list
, msg_list
, num_resp
);
540 talloc_destroy(ads_list
);
541 talloc_destroy(msg_list
);
547 /*********************************************************************
548 ********************************************************************/
550 NTSTATUS
gc_name_to_sid(const char *domain
,
553 enum lsa_SidType
*sid_type
)
555 TALLOC_CTX
*frame
= talloc_stackframe();
557 NTSTATUS nt_status
= NT_STATUS_UNSUCCESSFUL
;
559 ADS_STRUCT
*ads
= NULL
;
560 LDAPMessage
*msg
= NULL
;
561 LDAPMessage
*e
= NULL
;
563 char *dns_domain
= NULL
;
564 ADS_STRUCT
**ads_list
= NULL
;
565 LDAPMessage
**msg_list
= NULL
;
569 /* Strip the "DOMAIN\" prefix if necessary and search for
570 a matching sAMAccountName in the forest */
572 if ((p
= strchr_m( name
, '\\' )) == NULL
)
573 name_user
= talloc_strdup( frame
, name
);
575 name_user
= talloc_strdup( frame
, p
+1 );
576 BAIL_ON_PTR_ERROR(name_user
, nt_status
);
578 name_filter
= talloc_asprintf(frame
, "(sAMAccountName=%s)", name_user
);
579 BAIL_ON_PTR_ERROR(name_filter
, nt_status
);
581 nt_status
= gc_search_all_forests(name_filter
, &ads_list
,
582 &msg_list
, &num_resp
, 0);
583 BAIL_ON_NTSTATUS_ERROR(nt_status
);
585 /* Assume failure until we know otherwise*/
587 nt_status
= NT_STATUS_OBJECT_NAME_NOT_FOUND
;
589 /* Match the domain name from the DN */
591 for (i
=0; i
<num_resp
; i
++) {
595 e
= ads_first_entry(ads
, msg
);
597 struct winbindd_tdc_domain
*domain_rec
;
599 dn
= ads_get_dn(ads
, frame
, e
);
600 BAIL_ON_PTR_ERROR(dn
, nt_status
);
602 dns_domain
= cell_dn_to_dns(dn
);
604 BAIL_ON_PTR_ERROR(dns_domain
, nt_status
);
606 domain_rec
= wcache_tdc_fetch_domain(frame
, dns_domain
);
607 SAFE_FREE(dns_domain
);
609 /* Ignore failures and continue the search */
612 e
= ads_next_entry(ads
, e
);
616 /* Check for a match on the domain name */
618 if (strequal(domain
, domain_rec
->domain_name
)) {
619 if (!ads_pull_sid(ads
, e
, "objectSid", sid
)) {
620 nt_status
= NT_STATUS_INVALID_SID
;
621 BAIL_ON_NTSTATUS_ERROR(nt_status
);
624 talloc_destroy(domain_rec
);
626 nt_status
= get_sid_type(ads
, msg
, sid_type
);
627 BAIL_ON_NTSTATUS_ERROR(nt_status
);
630 nt_status
= NT_STATUS_OK
;
634 /* once more around thew merry-go-round */
636 talloc_destroy(domain_rec
);
637 e
= ads_next_entry(ads
, e
);
642 free_result_array(ads_list
, msg_list
, num_resp
);
643 talloc_destroy(frame
);
648 /********************************************************************
649 Pull an attribute string value
650 *******************************************************************/
652 static NTSTATUS
get_object_account_name(ADS_STRUCT
*ads
,
656 NTSTATUS nt_status
= NT_STATUS_UNSUCCESSFUL
;
657 char *sam_name
= NULL
;
658 struct winbindd_tdc_domain
*domain_rec
= NULL
;
659 char *dns_domain
= NULL
;
661 TALLOC_CTX
*frame
= talloc_stackframe();
664 /* Check parameters */
666 if (!ads
|| !msg
|| !name
) {
667 nt_status
= NT_STATUS_INVALID_PARAMETER
;
668 BAIL_ON_NTSTATUS_ERROR(nt_status
);
671 /* get the name and domain */
673 dn
= ads_get_dn(ads
, frame
, msg
);
674 BAIL_ON_PTR_ERROR(dn
, nt_status
);
676 DEBUG(10,("get_object_account_name: dn = \"%s\"\n", dn
));
678 dns_domain
= cell_dn_to_dns(dn
);
680 BAIL_ON_PTR_ERROR(dns_domain
, nt_status
);
682 domain_rec
= wcache_tdc_fetch_domain(frame
, dns_domain
);
683 SAFE_FREE(dns_domain
);
686 nt_status
= NT_STATUS_TRUSTED_DOMAIN_FAILURE
;
687 BAIL_ON_NTSTATUS_ERROR(nt_status
);
690 sam_name
= ads_pull_string(ads
, frame
, msg
, "sAMAccountName");
691 BAIL_ON_PTR_ERROR(sam_name
, nt_status
);
693 len
= asprintf(name
, "%s\\%s", domain_rec
->domain_name
, sam_name
);
696 BAIL_ON_PTR_ERROR((*name
), nt_status
);
699 nt_status
= NT_STATUS_OK
;
702 talloc_destroy(frame
);
707 /*********************************************************************
708 ********************************************************************/
710 NTSTATUS
gc_sid_to_name(const struct dom_sid
*sid
,
712 enum lsa_SidType
*sid_type
)
714 TALLOC_CTX
*frame
= talloc_stackframe();
715 NTSTATUS nt_status
= NT_STATUS_UNSUCCESSFUL
;
717 ADS_STRUCT
*ads
= NULL
;
718 LDAPMessage
*msg
= NULL
;
723 sid_string
= ldap_encode_ndr_dom_sid(frame
, sid
);
724 BAIL_ON_PTR_ERROR(sid_string
, nt_status
);
726 filter
= talloc_asprintf(frame
, "(objectSid=%s)", sid_string
);
727 TALLOC_FREE(sid_string
);
728 BAIL_ON_PTR_ERROR(filter
, nt_status
);
730 nt_status
= gc_search_all_forests_unique(filter
, &ads
, &msg
);
731 BAIL_ON_NTSTATUS_ERROR(nt_status
);
733 nt_status
= get_object_account_name(ads
, msg
, name
);
734 BAIL_ON_NTSTATUS_ERROR(nt_status
);
736 nt_status
= get_sid_type(ads
, msg
, sid_type
);
737 BAIL_ON_NTSTATUS_ERROR(nt_status
);
740 ads_msgfree(ads
, msg
);
741 talloc_destroy(frame
);
746 /**********************************************************************
747 *********************************************************************/
749 NTSTATUS
add_ads_result_to_array(ADS_STRUCT
*ads
,
751 ADS_STRUCT
***ads_list
,
752 LDAPMessage
***msg_list
,
755 NTSTATUS nt_status
= NT_STATUS_UNSUCCESSFUL
;
756 ADS_STRUCT
**ads_tmp
= NULL
;
757 LDAPMessage
**msg_tmp
= NULL
;
761 nt_status
= NT_STATUS_INVALID_PARAMETER
;
762 BAIL_ON_NTSTATUS_ERROR(nt_status
);
766 /* Don't add a response with no entries */
768 if (ads_count_replies(ads
, msg
) == 0) {
774 ads_tmp
= TALLOC_ARRAY(NULL
, ADS_STRUCT
*, 1);
775 BAIL_ON_PTR_ERROR(ads_tmp
, nt_status
);
777 msg_tmp
= TALLOC_ARRAY(NULL
, LDAPMessage
*, 1);
778 BAIL_ON_PTR_ERROR(msg_tmp
, nt_status
);
780 ads_tmp
= TALLOC_REALLOC_ARRAY(*ads_list
, *ads_list
, ADS_STRUCT
*,
782 BAIL_ON_PTR_ERROR(ads_tmp
, nt_status
);
784 msg_tmp
= TALLOC_REALLOC_ARRAY(*msg_list
, *msg_list
, LDAPMessage
*,
786 BAIL_ON_PTR_ERROR(msg_tmp
, nt_status
);
789 ads_tmp
[count
] = ads
;
790 msg_tmp
[count
] = msg
;
797 nt_status
= NT_STATUS_OK
;
800 if (!NT_STATUS_IS_OK(nt_status
)) {
801 talloc_destroy(ads_tmp
);
802 talloc_destroy(msg_tmp
);
808 /**********************************************************************
809 Frees search results. Do not free the ads_list as these are
810 references back to the GC search structures.
811 *********************************************************************/
813 void free_result_array(ADS_STRUCT
**ads_list
,
814 LDAPMessage
**msg_list
,
819 for (i
=0; i
<num_resp
; i
++) {
820 ads_msgfree(ads_list
[i
], msg_list
[i
]);
823 talloc_destroy(ads_list
);
824 talloc_destroy(msg_list
);
827 /**********************************************************************
828 Check that we have exactly one entry from the search
829 *********************************************************************/
831 NTSTATUS
check_result_unique(ADS_STRUCT
*ads
, LDAPMessage
*msg
)
836 count
= ads_count_replies(ads
, msg
);
839 nt_status
= NT_STATUS_OBJECT_NAME_NOT_FOUND
;
840 BAIL_ON_NTSTATUS_ERROR(nt_status
);
844 nt_status
= NT_STATUS_DUPLICATE_NAME
;
845 BAIL_ON_NTSTATUS_ERROR(nt_status
);
848 nt_status
= NT_STATUS_OK
;