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