s3-idmap: only include idmap headers where needed.
[samba.git] / source3 / winbindd / idmap_adex / gc_util.c
1 /*
2  * idmap_adex: Global Catalog search interface
3  *
4  * Copyright (C) Gerald (Jerry) Carter 2007-2008
5  *
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.
10  *
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.
15  *
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.
19  */
20
21 #include "includes.h"
22 #include "ads.h"
23 #include "idmap.h"
24 #include "idmap_adex.h"
25 #include "libads/cldap.h"
26
27 #undef DBGC_CLASS
28 #define DBGC_CLASS DBGC_IDMAP
29
30 static struct gc_info *_gc_server_list = NULL;
31
32
33 /**********************************************************************
34  *********************************************************************/
35
36 static struct gc_info *gc_list_head(void)
37 {
38         return _gc_server_list;
39 }
40
41 /**********************************************************************
42  Checks if either of the domains is a subdomain of the other
43  *********************************************************************/
44
45 static bool is_subdomain(const char* a, const char *b)
46 {
47         char *s;
48         TALLOC_CTX *frame = talloc_stackframe();
49         char *x, *y;
50         bool ret = false;
51
52         /* Trivial cases */
53
54         if (!a && !b)
55                 return true;
56
57         if (!a || !b)
58                 return false;
59
60         /* Normalize the case */
61
62         x = talloc_strdup(frame, a);
63         y = talloc_strdup(frame, b);
64         if (!x || !y) {
65                 ret = false;
66                 goto done;
67         }
68
69         strupper_m(x);
70         strupper_m(y);
71
72         /* Exact match */
73
74         if (strcmp(x, y) == 0) {
75                 ret = true;
76                 goto done;
77         }
78
79         /* Check for trailing substrings */
80
81         s = strstr_m(x, y);
82         if (s && (strlen(s) == strlen(y))) {
83                 ret = true;
84                 goto done;
85         }
86
87         s = strstr_m(y, x);
88         if (s && (strlen(s) == strlen(x))) {
89                 ret = true;
90                 goto done;
91         }
92
93 done:
94         talloc_destroy(frame);
95
96         return ret;
97 }
98
99 /**********************************************************************
100  *********************************************************************/
101
102  NTSTATUS gc_find_forest_root(struct gc_info *gc, const char *domain)
103 {
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();
109
110         if (!gc || !domain) {
111                 return NT_STATUS_INVALID_PARAMETER;
112         }
113
114         ZERO_STRUCT(cldap_reply);
115
116         ads = ads_init(domain, NULL, NULL);
117         BAIL_ON_PTR_ERROR(ads, nt_status);
118
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)));
124         }
125         nt_status = ads_ntstatus(ads_status);
126         BAIL_ON_NTSTATUS_ERROR(nt_status);
127
128         if (!ads_cldap_netlogon_5(frame,
129                                   ads->config.ldap_server_name,
130                                   ads->config.realm,
131                                   &cldap_reply))
132         {
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);
137         }
138
139         gc->forest_name = talloc_strdup(gc, cldap_reply.forest);
140         BAIL_ON_PTR_ERROR(gc->forest_name, nt_status);
141
142 done:
143         if (ads) {
144                 ads_destroy(&ads);
145         }
146
147         return nt_status;
148 }
149
150 /**********************************************************************
151  *********************************************************************/
152
153 static NTSTATUS gc_add_forest(const char *domain)
154 {
155         NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
156         struct gc_info *gc = NULL;
157         struct gc_info *find_gc = NULL;
158         char *dn;
159         ADS_STRUCT *ads = NULL;
160         struct likewise_cell *primary_cell = NULL;
161
162         primary_cell = cell_list_head();
163         if (!primary_cell) {
164                 nt_status = NT_STATUS_INVALID_SERVER_STATE;
165                 BAIL_ON_NTSTATUS_ERROR(nt_status);
166         }
167
168         /* Check for duplicates based on domain name first as this
169            requires no connection */
170
171         find_gc = gc_list_head();
172         while (find_gc) {
173                 if (strequal (find_gc->forest_name, domain))
174                         break;
175                 find_gc = find_gc->next;
176         }
177
178         if (find_gc) {
179                 DEBUG(10,("gc_add_forest: %s already in list\n", find_gc->forest_name));
180                 return NT_STATUS_OK;
181         }
182
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);
186         }
187
188         /* Query the rootDSE for the forest root naming conect first.
189            Check that the a GC server for the forest has not already
190            been added */
191
192         nt_status = gc_find_forest_root(gc, domain);
193         BAIL_ON_NTSTATUS_ERROR(nt_status);
194
195         find_gc = gc_list_head();
196         while (find_gc) {
197                 if (strequal (find_gc->forest_name, gc->forest_name))
198                         break;
199                 find_gc = find_gc->next;
200         }
201
202         if (find_gc) {
203                 DEBUG(10,("gc_add_forest: Forest %s already in list\n",
204                           find_gc->forest_name));
205                 return NT_STATUS_OK;
206         }
207
208         /* Not found, so add it here.  Make sure we connect to
209            a DC in _this_ domain and not the forest root. */
210
211         dn = ads_build_dn(gc->forest_name);
212         BAIL_ON_PTR_ERROR(dn, nt_status);
213
214         gc->search_base = talloc_strdup(gc, dn);
215         SAFE_FREE(dn);
216         BAIL_ON_PTR_ERROR(gc->search_base, nt_status);
217
218 #if 0
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() */
221
222         nt_status = cell_connect_dn(&gc->forest_cell, gc->search_base);
223         BAIL_ON_NTSTATUS_ERROR(nt_status);
224 #else
225
226         gc->forest_cell = cell_new();
227         BAIL_ON_PTR_ERROR(gc->forest_cell, nt_status);
228
229         /* Set the DNS domain, dn, etc ... and add it to the list */
230
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);
234 #endif
235
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
240            model. */
241
242         if (strequal(primary_cell->forest_name, gc->forest_name)
243             || is_subdomain(primary_cell->dns_domain, gc->forest_name))
244         {
245                 cell_set_flags(gc->forest_cell, cell_flags(primary_cell));
246         } else {
247                 /* outside of our domain */
248
249                 nt_status = cell_connect(gc->forest_cell);
250                 BAIL_ON_NTSTATUS_ERROR(nt_status);
251
252                 nt_status = cell_lookup_settings(gc->forest_cell);
253                 BAIL_ON_NTSTATUS_ERROR(nt_status);
254
255                 /* Drop the connection now that we have the settings */
256
257                 ads = cell_connection(gc->forest_cell);
258                 ads_destroy(&ads);
259                 cell_set_connection(gc->forest_cell, NULL);
260         }
261
262         DLIST_ADD_END(_gc_server_list, gc, struct gc_info*);
263
264         DEBUG(10,("gc_add_forest: Added %s to Global Catalog list of servers\n",
265                   gc->forest_name));
266
267         nt_status = NT_STATUS_OK;
268
269 done:
270         if (!NT_STATUS_IS_OK(nt_status)) {
271                 talloc_destroy(gc);
272                 DEBUG(3,("LWI: Failed to add new GC connection for %s (%s)\n",
273                          domain, nt_errstr(nt_status)));
274         }
275
276         return nt_status;
277 }
278
279 /**********************************************************************
280  *********************************************************************/
281
282 static void gc_server_list_destroy(void)
283 {
284         struct gc_info *gc = gc_list_head();
285
286         while (gc) {
287                 struct gc_info *p = gc->next;
288
289                 cell_destroy(gc->forest_cell);
290                 talloc_destroy(gc);
291
292                 gc = p;
293         }
294
295         _gc_server_list = NULL;
296
297         return;
298 }
299
300 /**********************************************************************
301  Setup the initial list of forests and initial the forest cell
302  settings for each.  FIXME!!!
303  *********************************************************************/
304
305  NTSTATUS gc_init_list(void)
306 {
307         NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
308         struct winbindd_tdc_domain *domains = NULL;
309         size_t num_domains = 0;
310         int i;
311
312         if (_gc_server_list != NULL) {
313                 gc_server_list_destroy();
314         }
315
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);
319         }
320
321         /* Find our forest first.  Have to try all domains here starting
322            with our own.  gc_add_forest() filters duplicates */
323
324         nt_status = gc_add_forest(lp_realm());
325         WARN_ON_NTSTATUS_ERROR(nt_status);
326
327         for (i=0; i<num_domains; i++) {
328                 uint32_t flags = (NETR_TRUST_FLAG_IN_FOREST);
329
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.
335                    --jerry */
336
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
341                            have a GC server */
342                 }
343         }
344
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.  */
348
349         for (i=0; i<num_domains; i++) {
350                 uint32_t flags = domains[i].trust_flags;
351                 uint32_t attribs = domains[i].trust_attribs;
352
353                 /* Skip non_AD domains */
354
355                 if (strlen(domains[i].dns_name) == 0) {
356                         continue;
357                 }
358
359                 /* Only add a GC for a forest outside of our own.
360                    Ignore QUARANTINED/EXTERNAL trusts */
361
362                 if ((flags & NETR_TRUST_FLAG_INBOUND)
363                     && !(flags & NETR_TRUST_FLAG_IN_FOREST)
364                     && (attribs & NETR_TRUST_ATTRIBUTE_FOREST_TRANSITIVE))
365                 {
366                         nt_status = gc_add_forest(domains[i].dns_name);
367                         WARN_ON_NTSTATUS_ERROR(nt_status);
368                 }
369         }
370
371         nt_status = NT_STATUS_OK;
372
373 done:
374         if (!NT_STATUS_IS_OK(nt_status)) {
375                 DEBUG(2,("LWI: Failed to initialized GC list (%s)\n",
376                          nt_errstr(nt_status)));
377         }
378
379         TALLOC_FREE(domains);
380
381         return nt_status;
382 }
383
384
385 /**********************************************************************
386  *********************************************************************/
387
388  struct gc_info *gc_search_start(void)
389 {
390         NTSTATUS nt_status = NT_STATUS_OK;
391         struct gc_info *gc = gc_list_head();
392
393         if (!gc) {
394                 nt_status = gc_init_list();
395                 BAIL_ON_NTSTATUS_ERROR(nt_status);
396
397                 gc = gc_list_head();
398         }
399
400 done:
401         if (!NT_STATUS_IS_OK(nt_status)) {
402                 DEBUG(2,("LWI: Failed to initialize GC list (%s)\n",
403                          nt_errstr(nt_status)));
404         }
405
406         return gc;
407 }
408
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  *********************************************************************/
415
416  NTSTATUS gc_search_forest(struct gc_info *gc,
417                            LDAPMessage **msg,
418                            const char *filter)
419 {
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;
424
425         if (!gc || !msg || !filter) {
426                 nt_status = NT_STATUS_INVALID_PARAMETER;
427                 BAIL_ON_NTSTATUS_ERROR(nt_status);
428         }
429
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
433            Windows 2003.  */
434
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);
439
440         *msg = m;
441
442 done:
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)));
446         }
447
448         return nt_status;
449 }
450
451 /**********************************************************************
452  Search all forests via GC and return the results in an array of
453  ADS_STRUCT/LDAPMessage pairs.
454  *********************************************************************/
455
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)
460 {
461         NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
462         struct gc_info *gc = NULL;
463         uint32_t test_flags = ADEX_GC_SEARCH_CHECK_UNIQUE;
464
465         *ads_list = NULL;
466         *msg_list = NULL;
467         *num_resp = 0;
468
469         if ((gc = gc_search_start()) == NULL) {
470                 nt_status = NT_STATUS_INVALID_DOMAIN_STATE;
471                 BAIL_ON_NTSTATUS_ERROR(nt_status);
472         }
473
474         while (gc) {
475                 LDAPMessage *m = NULL;
476
477                 nt_status = gc_search_forest(gc, &m, filter);
478                 if (!NT_STATUS_IS_OK(nt_status)) {
479                         gc = gc->next;
480                         continue;
481                 }
482
483                 nt_status = add_ads_result_to_array(cell_connection(gc->forest_cell),
484                                                     m, ads_list, msg_list,
485                                                     num_resp);
486                 BAIL_ON_NTSTATUS_ERROR(nt_status);
487
488                 /* If there can only be one match, then we are done */
489
490                 if ((*num_resp > 0) && ((flags & test_flags) == test_flags)) {
491                         break;
492                 }
493
494                 gc = gc->next;
495         }
496
497         if (*num_resp == 0) {
498                 nt_status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
499                 BAIL_ON_NTSTATUS_ERROR(nt_status);
500         }
501
502         nt_status = NT_STATUS_OK;
503
504 done:
505         return nt_status;
506 }
507
508 /**********************************************************************
509  Search all forests via GC and return the results in an array of
510  ADS_STRUCT/LDAPMessage pairs.
511  *********************************************************************/
512
513  NTSTATUS gc_search_all_forests_unique(const char *filter,
514                                        ADS_STRUCT **ads,
515                                        LDAPMessage **msg)
516 {
517         ADS_STRUCT **ads_list = NULL;
518         LDAPMessage **msg_list = NULL;
519         int num_resp;
520         NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
521
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);
526
527         nt_status = check_result_unique(ads_list[0], msg_list[0]);
528         BAIL_ON_NTSTATUS_ERROR(nt_status);
529
530         *ads = ads_list[0];
531         *msg = msg_list[0];
532
533 done:
534         /* Be care that we don't free the msg result being returned */
535
536         if (!NT_STATUS_IS_OK(nt_status)) {
537                 free_result_array(ads_list, msg_list, num_resp);
538         } else {
539                 talloc_destroy(ads_list);
540                 talloc_destroy(msg_list);
541         }
542
543         return nt_status;
544 }
545
546 /*********************************************************************
547  ********************************************************************/
548
549  NTSTATUS gc_name_to_sid(const char *domain,
550                          const char *name,
551                          struct dom_sid *sid,
552                          enum lsa_SidType *sid_type)
553 {
554         TALLOC_CTX *frame = talloc_stackframe();
555         char *p, *name_user;
556         NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
557         char *name_filter;
558         ADS_STRUCT *ads = NULL;
559         LDAPMessage *msg = NULL;
560         LDAPMessage *e = NULL;
561         char *dn = NULL;
562         char *dns_domain = NULL;
563         ADS_STRUCT **ads_list = NULL;
564         LDAPMessage **msg_list = NULL;
565         int num_resp = 0;
566         int i;
567
568         /* Strip the "DOMAIN\" prefix if necessary and search for
569            a matching sAMAccountName in the forest */
570
571         if ((p = strchr_m( name, '\\' )) == NULL)
572                 name_user = talloc_strdup( frame, name );
573         else
574                 name_user = talloc_strdup( frame, p+1 );
575         BAIL_ON_PTR_ERROR(name_user, nt_status);
576
577         name_filter = talloc_asprintf(frame, "(sAMAccountName=%s)", name_user);
578         BAIL_ON_PTR_ERROR(name_filter, nt_status);
579
580         nt_status = gc_search_all_forests(name_filter, &ads_list,
581                                           &msg_list, &num_resp, 0);
582         BAIL_ON_NTSTATUS_ERROR(nt_status);
583
584         /* Assume failure until we know otherwise*/
585
586         nt_status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
587
588         /* Match the domain name from the DN */
589
590         for (i=0; i<num_resp; i++) {
591                 ads = ads_list[i];
592                 msg = msg_list[i];
593
594                 e = ads_first_entry(ads, msg);
595                 while (e) {
596                         struct winbindd_tdc_domain *domain_rec;
597
598                         dn = ads_get_dn(ads, frame, e);
599                         BAIL_ON_PTR_ERROR(dn, nt_status);
600
601                         dns_domain = cell_dn_to_dns(dn);
602                         TALLOC_FREE(dn);
603                         BAIL_ON_PTR_ERROR(dns_domain, nt_status);
604
605                         domain_rec = wcache_tdc_fetch_domain(frame, dns_domain);
606                         SAFE_FREE(dns_domain);
607
608                         /* Ignore failures and continue the search */
609
610                         if (!domain_rec) {
611                                 e = ads_next_entry(ads, e);
612                                 continue;
613                         }
614
615                         /* Check for a match on the domain name */
616
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);
621                                 }
622
623                                 talloc_destroy(domain_rec);
624
625                                 nt_status = get_sid_type(ads, msg, sid_type);
626                                 BAIL_ON_NTSTATUS_ERROR(nt_status);
627
628                                 /* We're done! */
629                                 nt_status = NT_STATUS_OK;
630                                 break;
631                         }
632
633                         /* once more around thew merry-go-round */
634
635                         talloc_destroy(domain_rec);
636                         e = ads_next_entry(ads, e);
637                 }
638         }
639
640 done:
641         free_result_array(ads_list, msg_list, num_resp);
642         talloc_destroy(frame);
643
644         return nt_status;
645 }
646
647 /********************************************************************
648  Pull an attribute string value
649  *******************************************************************/
650
651 static NTSTATUS get_object_account_name(ADS_STRUCT *ads,
652                                         LDAPMessage *msg,
653                                         char **name)
654 {
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;
659         char *dn = NULL;
660         TALLOC_CTX *frame = talloc_stackframe();
661         int len;
662
663         /* Check parameters */
664
665         if (!ads || !msg || !name) {
666                 nt_status = NT_STATUS_INVALID_PARAMETER;
667                 BAIL_ON_NTSTATUS_ERROR(nt_status);
668         }
669
670         /* get the name and domain */
671
672         dn = ads_get_dn(ads, frame, msg);
673         BAIL_ON_PTR_ERROR(dn, nt_status);
674
675         DEBUG(10,("get_object_account_name: dn = \"%s\"\n", dn));
676
677         dns_domain = cell_dn_to_dns(dn);
678         TALLOC_FREE(dn);
679         BAIL_ON_PTR_ERROR(dns_domain, nt_status);
680
681         domain_rec = wcache_tdc_fetch_domain(frame, dns_domain);
682         SAFE_FREE(dns_domain);
683
684         if (!domain_rec) {
685                 nt_status = NT_STATUS_TRUSTED_DOMAIN_FAILURE;
686                 BAIL_ON_NTSTATUS_ERROR(nt_status);
687         }
688
689         sam_name = ads_pull_string(ads, frame, msg, "sAMAccountName");
690         BAIL_ON_PTR_ERROR(sam_name, nt_status);
691
692         len = asprintf(name, "%s\\%s", domain_rec->domain_name, sam_name);
693         if (len == -1) {
694                 *name = NULL;
695                 BAIL_ON_PTR_ERROR((*name), nt_status);
696         }
697
698         nt_status = NT_STATUS_OK;
699
700 done:
701         talloc_destroy(frame);
702
703         return nt_status;
704 }
705
706 /*********************************************************************
707  ********************************************************************/
708
709  NTSTATUS gc_sid_to_name(const struct dom_sid *sid,
710                          char **name,
711                          enum lsa_SidType *sid_type)
712 {
713         TALLOC_CTX *frame = talloc_stackframe();
714         NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
715         char *filter;
716         ADS_STRUCT *ads = NULL;
717         LDAPMessage *msg = NULL;
718         char *sid_string;
719
720         *name = NULL;
721
722         sid_string = sid_binstring(frame, sid);
723         BAIL_ON_PTR_ERROR(sid_string, nt_status);
724
725         filter = talloc_asprintf(frame, "(objectSid=%s)", sid_string);
726         TALLOC_FREE(sid_string);
727         BAIL_ON_PTR_ERROR(filter, nt_status);
728
729         nt_status = gc_search_all_forests_unique(filter, &ads, &msg);
730         BAIL_ON_NTSTATUS_ERROR(nt_status);
731
732         nt_status = get_object_account_name(ads, msg, name);
733         BAIL_ON_NTSTATUS_ERROR(nt_status);
734
735         nt_status = get_sid_type(ads, msg, sid_type);
736         BAIL_ON_NTSTATUS_ERROR(nt_status);
737
738 done:
739         ads_msgfree(ads, msg);
740         talloc_destroy(frame);
741
742         return nt_status;
743 }
744
745 /**********************************************************************
746  *********************************************************************/
747
748  NTSTATUS add_ads_result_to_array(ADS_STRUCT *ads,
749                                   LDAPMessage *msg,
750                                   ADS_STRUCT ***ads_list,
751                                   LDAPMessage ***msg_list,
752                                   int *size)
753 {
754         NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
755         ADS_STRUCT **ads_tmp = NULL;
756         LDAPMessage **msg_tmp = NULL;
757         int count = *size;
758
759         if (!ads || !msg) {
760                 nt_status = NT_STATUS_INVALID_PARAMETER;
761                 BAIL_ON_NTSTATUS_ERROR(nt_status);
762         }
763
764 #if 0
765         /* Don't add a response with no entries */
766
767         if (ads_count_replies(ads, msg) == 0) {
768                 return NT_STATUS_OK;
769         }
770 #endif
771
772         if (count == 0) {
773                 ads_tmp = TALLOC_ARRAY(NULL, ADS_STRUCT*, 1);
774                 BAIL_ON_PTR_ERROR(ads_tmp, nt_status);
775
776                 msg_tmp = TALLOC_ARRAY(NULL, LDAPMessage*, 1);
777                 BAIL_ON_PTR_ERROR(msg_tmp, nt_status);
778         } else {
779                 ads_tmp = TALLOC_REALLOC_ARRAY(*ads_list, *ads_list, ADS_STRUCT*,
780                                                count+1);
781                 BAIL_ON_PTR_ERROR(ads_tmp, nt_status);
782
783                 msg_tmp = TALLOC_REALLOC_ARRAY(*msg_list, *msg_list, LDAPMessage*,
784                                                count+1);
785                 BAIL_ON_PTR_ERROR(msg_tmp, nt_status);
786         }
787
788         ads_tmp[count] = ads;
789         msg_tmp[count] = msg;
790         count++;
791
792         *ads_list = ads_tmp;
793         *msg_list = msg_tmp;
794         *size = count;
795
796         nt_status = NT_STATUS_OK;
797
798 done:
799         if (!NT_STATUS_IS_OK(nt_status)) {
800                 talloc_destroy(ads_tmp);
801                 talloc_destroy(msg_tmp);
802         }
803
804         return nt_status;
805 }
806
807 /**********************************************************************
808  Frees search results.  Do not free the ads_list as these are
809  references back to the GC search structures.
810  *********************************************************************/
811
812  void free_result_array(ADS_STRUCT **ads_list,
813                         LDAPMessage **msg_list,
814                         int num_resp)
815 {
816         int i;
817
818         for (i=0; i<num_resp; i++) {
819                 ads_msgfree(ads_list[i], msg_list[i]);
820         }
821
822         talloc_destroy(ads_list);
823         talloc_destroy(msg_list);
824 }
825
826 /**********************************************************************
827  Check that we have exactly one entry from the search
828  *********************************************************************/
829
830  NTSTATUS check_result_unique(ADS_STRUCT *ads, LDAPMessage *msg)
831 {
832         NTSTATUS nt_status;
833         int count;
834
835         count = ads_count_replies(ads, msg);
836
837         if (count <= 0) {
838                 nt_status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
839                 BAIL_ON_NTSTATUS_ERROR(nt_status);
840         }
841
842         if (count > 1) {
843                 nt_status = NT_STATUS_DUPLICATE_NAME;
844                 BAIL_ON_NTSTATUS_ERROR(nt_status);
845         }
846
847         nt_status = NT_STATUS_OK;
848
849 done:
850         return nt_status;
851 }