r12867: Remove deleted header.
[tprouty/samba.git] / source4 / winbind / wb_samba3_cmd.c
1 /* 
2    Unix SMB/CIFS implementation.
3    Main winbindd samba3 server routines
4
5    Copyright (C) Stefan Metzmacher      2005
6    Copyright (C) Volker Lendecke        2005
7    Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005
8
9    This program is free software; you can redistribute it and/or modify
10    it under the terms of the GNU General Public License as published by
11    the Free Software Foundation; either version 2 of the License, or
12    (at your option) any later version.
13    
14    This program is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17    GNU General Public License for more details.
18    
19    You should have received a copy of the GNU General Public License
20    along with this program; if not, write to the Free Software
21    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22 */
23
24 #include "includes.h"
25 #include "nsswitch/winbindd_nss.h"
26 #include "winbind/wb_server.h"
27 #include "winbind/wb_async_helpers.h"
28 #include "libcli/composite/composite.h"
29 #include "include/version.h"
30 #include "librpc/gen_ndr/ndr_netlogon.h"
31
32 /* 
33    Send off the reply to an async Samba3 query, handling filling in the PAM, NTSTATUS and string errors.
34 */
35
36 static void wbsrv_samba3_async_auth_epilogue(NTSTATUS status,
37                                              struct wbsrv_samba3_call *s3call)
38 {
39         struct winbindd_response *resp = &s3call->response;
40         if (!NT_STATUS_IS_OK(status)) {
41                 resp->result = WINBINDD_ERROR;
42                 WBSRV_SAMBA3_SET_STRING(resp->data.auth.nt_status_string,
43                                         nt_errstr(status));
44                 WBSRV_SAMBA3_SET_STRING(resp->data.auth.error_string,
45                                         get_friendly_nt_error_msg(status));
46         } else {
47                 resp->result = WINBINDD_OK;
48         }
49
50         resp->data.auth.pam_error = nt_status_to_pam(status);
51         resp->data.auth.nt_status = NT_STATUS_V(status);
52
53         wbsrv_samba3_send_reply(s3call);
54 }
55
56 /* 
57    Send of a generic reply to a Samba3 query
58 */
59
60 static void wbsrv_samba3_async_epilogue(NTSTATUS status,
61                                         struct wbsrv_samba3_call *s3call)
62 {
63         struct winbindd_response *resp = &s3call->response;
64         if (NT_STATUS_IS_OK(status)) {
65                 resp->result = WINBINDD_OK;
66         } else {
67                 resp->result = WINBINDD_ERROR;
68         }
69
70         wbsrv_samba3_send_reply(s3call);
71 }
72
73 /* 
74    Boilerplate commands, simple queries without network traffic 
75 */
76
77 NTSTATUS wbsrv_samba3_interface_version(struct wbsrv_samba3_call *s3call)
78 {
79         s3call->response.result                 = WINBINDD_OK;
80         s3call->response.data.interface_version = WINBIND_INTERFACE_VERSION;
81         return NT_STATUS_OK;
82 }
83
84 NTSTATUS wbsrv_samba3_info(struct wbsrv_samba3_call *s3call)
85 {
86         s3call->response.result                 = WINBINDD_OK;
87         s3call->response.data.info.winbind_separator = *lp_winbind_separator();
88         WBSRV_SAMBA3_SET_STRING(s3call->response.data.info.samba_version,
89                                 SAMBA_VERSION_STRING);
90         return NT_STATUS_OK;
91 }
92
93 NTSTATUS wbsrv_samba3_domain_name(struct wbsrv_samba3_call *s3call)
94 {
95         s3call->response.result                 = WINBINDD_OK;
96         WBSRV_SAMBA3_SET_STRING(s3call->response.data.domain_name,
97                                 lp_workgroup());
98         return NT_STATUS_OK;
99 }
100
101 NTSTATUS wbsrv_samba3_netbios_name(struct wbsrv_samba3_call *s3call)
102 {
103         s3call->response.result                 = WINBINDD_OK;
104         WBSRV_SAMBA3_SET_STRING(s3call->response.data.netbios_name,
105                                 lp_netbios_name());
106         return NT_STATUS_OK;
107 }
108
109 NTSTATUS wbsrv_samba3_priv_pipe_dir(struct wbsrv_samba3_call *s3call)
110 {
111         s3call->response.result                 = WINBINDD_OK;
112         s3call->response.extra_data =
113                 smbd_tmp_path(s3call, WINBINDD_SAMBA3_PRIVILEGED_SOCKET);
114         NT_STATUS_HAVE_NO_MEMORY(s3call->response.extra_data);
115         return NT_STATUS_OK;
116 }
117
118 NTSTATUS wbsrv_samba3_ping(struct wbsrv_samba3_call *s3call)
119 {
120         s3call->response.result                 = WINBINDD_OK;
121         return NT_STATUS_OK;
122 }
123
124 #if 0
125 /* 
126    Validate that we have a working pipe to the domain controller.
127    Return any NT error found in the process
128 */
129
130 static void checkmachacc_recv_creds(struct composite_context *ctx);
131
132 NTSTATUS wbsrv_samba3_check_machacc(struct wbsrv_samba3_call *s3call)
133 {
134         struct composite_context *ctx;
135
136         DEBUG(5, ("wbsrv_samba3_check_machacc called\n"));
137
138         ctx = wb_cmd_checkmachacc_send(s3call->call);
139         NT_STATUS_HAVE_NO_MEMORY(ctx);
140
141         ctx->async.fn = checkmachacc_recv_creds;
142         ctx->async.private_data = s3call;
143         s3call->call->flags |= WBSRV_CALL_FLAGS_REPLY_ASYNC;
144         return NT_STATUS_OK;
145 }
146         
147 static void checkmachacc_recv_creds(struct composite_context *ctx)
148 {
149         struct wbsrv_samba3_call *s3call =
150                 talloc_get_type(ctx->async.private_data,
151                                 struct wbsrv_samba3_call);
152         NTSTATUS status;
153
154         status = wb_cmd_checkmachacc_recv(ctx);
155
156         wbsrv_samba3_async_auth_epilogue(status, s3call);
157 }
158 #endif
159
160 /*
161   Find the name of a suitable domain controller, by query on the
162   netlogon pipe to the DC.  
163 */
164
165 static void getdcname_recv_dc(struct composite_context *ctx);
166
167 NTSTATUS wbsrv_samba3_getdcname(struct wbsrv_samba3_call *s3call)
168 {
169         struct composite_context *ctx;
170         struct wbsrv_service *service =
171                 s3call->wbconn->listen_socket->service;
172
173         DEBUG(5, ("wbsrv_samba3_getdcname called\n"));
174
175         ctx = wb_cmd_getdcname_send(s3call, service,
176                                     s3call->request.domain_name);
177         NT_STATUS_HAVE_NO_MEMORY(ctx);
178
179         ctx->async.fn = getdcname_recv_dc;
180         ctx->async.private_data = s3call;
181         s3call->flags |= WBSRV_CALL_FLAGS_REPLY_ASYNC;
182         return NT_STATUS_OK;
183 }
184
185 static void getdcname_recv_dc(struct composite_context *ctx)
186 {
187         struct wbsrv_samba3_call *s3call =
188                 talloc_get_type(ctx->async.private_data,
189                                 struct wbsrv_samba3_call);
190         const char *dcname;
191         NTSTATUS status;
192
193         status = wb_cmd_getdcname_recv(ctx, s3call, &dcname);
194         if (!NT_STATUS_IS_OK(status)) goto done;
195
196         s3call->response.result = WINBINDD_OK;
197         WBSRV_SAMBA3_SET_STRING(s3call->response.data.dc_name, dcname);
198
199  done:
200         wbsrv_samba3_async_epilogue(status, s3call);
201 }
202
203 /* 
204    Lookup a user's domain groups
205 */
206
207 static void userdomgroups_recv_groups(struct composite_context *ctx);
208
209 NTSTATUS wbsrv_samba3_userdomgroups(struct wbsrv_samba3_call *s3call)
210 {
211         struct composite_context *ctx;
212         struct dom_sid *sid;
213
214         DEBUG(5, ("wbsrv_samba3_userdomgroups called\n"));
215
216         sid = dom_sid_parse_talloc(s3call, s3call->request.data.sid);
217         if (sid == NULL) {
218                 DEBUG(5, ("Could not parse sid %s\n",
219                           s3call->request.data.sid));
220                 return NT_STATUS_NO_MEMORY;
221         }
222
223         ctx = wb_cmd_userdomgroups_send(
224                 s3call, s3call->wbconn->listen_socket->service, sid);
225         NT_STATUS_HAVE_NO_MEMORY(ctx);
226
227         ctx->async.fn = userdomgroups_recv_groups;
228         ctx->async.private_data = s3call;
229         s3call->flags |= WBSRV_CALL_FLAGS_REPLY_ASYNC;
230         return NT_STATUS_OK;
231 }
232
233 static void userdomgroups_recv_groups(struct composite_context *ctx)
234 {
235         struct wbsrv_samba3_call *s3call =
236                 talloc_get_type(ctx->async.private_data,
237                                 struct wbsrv_samba3_call);
238         int i, num_sids;
239         struct dom_sid **sids;
240         char *sids_string;
241         NTSTATUS status;
242
243         status = wb_cmd_userdomgroups_recv(ctx, s3call, &num_sids, &sids);
244         if (!NT_STATUS_IS_OK(status)) goto done;
245
246         sids_string = talloc_strdup(s3call, "");
247         if (sids_string == NULL) {
248                 status = NT_STATUS_NO_MEMORY;
249                 goto done;
250         }
251
252         for (i=0; i<num_sids; i++) {
253                 sids_string = talloc_asprintf_append(
254                         sids_string, "%s\n", dom_sid_string(s3call, sids[i]));
255         }
256
257         if (sids_string == NULL) {
258                 status = NT_STATUS_NO_MEMORY;
259                 goto done;
260         }
261
262         s3call->response.result = WINBINDD_OK;
263         s3call->response.extra_data = sids_string;
264         s3call->response.length += strlen(sids_string)+1;
265         s3call->response.data.num_entries = num_sids;
266
267  done:
268         wbsrv_samba3_async_epilogue(status, s3call);
269 }
270
271 /* 
272    Lookup the list of SIDs for a user 
273 */
274 static void usersids_recv_sids(struct composite_context *ctx);
275
276 NTSTATUS wbsrv_samba3_usersids(struct wbsrv_samba3_call *s3call)
277 {
278         struct composite_context *ctx;
279         struct dom_sid *sid;
280
281         DEBUG(5, ("wbsrv_samba3_usersids called\n"));
282
283         sid = dom_sid_parse_talloc(s3call, s3call->request.data.sid);
284         if (sid == NULL) {
285                 DEBUG(5, ("Could not parse sid %s\n",
286                           s3call->request.data.sid));
287                 return NT_STATUS_NO_MEMORY;
288         }
289
290         ctx = wb_cmd_usersids_send(
291                 s3call, s3call->wbconn->listen_socket->service, sid);
292         NT_STATUS_HAVE_NO_MEMORY(ctx);
293
294         ctx->async.fn = usersids_recv_sids;
295         ctx->async.private_data = s3call;
296         s3call->flags |= WBSRV_CALL_FLAGS_REPLY_ASYNC;
297         return NT_STATUS_OK;
298 }
299
300 static void usersids_recv_sids(struct composite_context *ctx)
301 {
302         struct wbsrv_samba3_call *s3call =
303                 talloc_get_type(ctx->async.private_data,
304                                 struct wbsrv_samba3_call);
305         int i, num_sids;
306         struct dom_sid **sids;
307         char *sids_string;
308         NTSTATUS status;
309
310         status = wb_cmd_usersids_recv(ctx, s3call, &num_sids, &sids);
311         if (!NT_STATUS_IS_OK(status)) goto done;
312
313         sids_string = talloc_strdup(s3call, "");
314         if (sids_string == NULL) {
315                 status = NT_STATUS_NO_MEMORY;
316                 goto done;
317         }
318
319         for (i=0; i<num_sids; i++) {
320                 sids_string = talloc_asprintf_append(
321                         sids_string, "%s\n", dom_sid_string(s3call, sids[i]));
322                 if (sids_string == NULL) {
323                         status = NT_STATUS_NO_MEMORY;
324                         goto done;
325                 }
326         }
327
328         s3call->response.result = WINBINDD_OK;
329         s3call->response.extra_data = sids_string;
330         s3call->response.length += strlen(sids_string);
331         s3call->response.data.num_entries = num_sids;
332
333         /* Hmmmm. Nasty protocol -- who invented the zeros between the
334          * SIDs? Hmmm. Could have been me -- vl */
335
336         while (*sids_string != '\0') {
337                 if ((*sids_string) == '\n') {
338                         *sids_string = '\0';
339                 }
340                 sids_string += 1;
341         }
342
343  done:
344         wbsrv_samba3_async_epilogue(status, s3call);
345 }
346
347 /* 
348    Lookup a DOMAIN\\user style name, and return a SID
349 */
350
351 static void lookupname_recv_sid(struct composite_context *ctx);
352
353 NTSTATUS wbsrv_samba3_lookupname(struct wbsrv_samba3_call *s3call)
354 {
355         struct composite_context *ctx;
356         struct wbsrv_service *service =
357                 s3call->wbconn->listen_socket->service;
358
359         DEBUG(5, ("wbsrv_samba3_lookupname called\n"));
360
361         ctx = wb_cmd_lookupname_send(s3call, service,
362                                      s3call->request.data.name.dom_name,
363                                      s3call->request.data.name.name);
364         NT_STATUS_HAVE_NO_MEMORY(ctx);
365
366         /* setup the callbacks */
367         ctx->async.fn = lookupname_recv_sid;
368         ctx->async.private_data = s3call;
369         s3call->flags |= WBSRV_CALL_FLAGS_REPLY_ASYNC;
370         return NT_STATUS_OK;
371 }
372
373 static void lookupname_recv_sid(struct composite_context *ctx)
374 {
375         struct wbsrv_samba3_call *s3call =
376                 talloc_get_type(ctx->async.private_data,
377                                 struct wbsrv_samba3_call);
378         struct wb_sid_object *sid;
379         NTSTATUS status;
380
381         status = wb_cmd_lookupname_recv(ctx, s3call, &sid);
382         if (!NT_STATUS_IS_OK(status)) goto done;
383
384         s3call->response.result = WINBINDD_OK;
385         s3call->response.data.sid.type = sid->type;
386         WBSRV_SAMBA3_SET_STRING(s3call->response.data.sid.sid,
387                                 dom_sid_string(s3call, sid->sid));
388
389  done:
390         wbsrv_samba3_async_epilogue(status, s3call);
391 }
392
393 /* 
394    Lookup a SID, and return a DOMAIN\\user style name
395 */
396
397 static void lookupsid_recv_name(struct composite_context *ctx);
398
399 NTSTATUS wbsrv_samba3_lookupsid(struct wbsrv_samba3_call *s3call)
400 {
401         struct composite_context *ctx;
402         struct wbsrv_service *service =
403                 s3call->wbconn->listen_socket->service;
404         struct dom_sid *sid;
405
406         DEBUG(5, ("wbsrv_samba3_lookupsid called\n"));
407
408         sid = dom_sid_parse_talloc(s3call, s3call->request.data.sid);
409         if (sid == NULL) {
410                 DEBUG(5, ("Could not parse sid %s\n",
411                           s3call->request.data.sid));
412                 return NT_STATUS_NO_MEMORY;
413         }
414
415         ctx = wb_cmd_lookupsid_send(s3call, service, sid);
416         NT_STATUS_HAVE_NO_MEMORY(ctx);
417
418         /* setup the callbacks */
419         ctx->async.fn = lookupsid_recv_name;
420         ctx->async.private_data = s3call;
421         s3call->flags |= WBSRV_CALL_FLAGS_REPLY_ASYNC;
422         return NT_STATUS_OK;
423 }
424
425 static void lookupsid_recv_name(struct composite_context *ctx)
426 {
427         struct wbsrv_samba3_call *s3call =
428                 talloc_get_type(ctx->async.private_data,
429                                 struct wbsrv_samba3_call);
430         struct wb_sid_object *sid;
431         NTSTATUS status;
432
433         status = wb_cmd_lookupsid_recv(ctx, s3call, &sid);
434         if (!NT_STATUS_IS_OK(status)) goto done;
435
436         s3call->response.result = WINBINDD_OK;
437         s3call->response.data.name.type = sid->type;
438         WBSRV_SAMBA3_SET_STRING(s3call->response.data.name.dom_name,
439                                 sid->domain);
440         WBSRV_SAMBA3_SET_STRING(s3call->response.data.name.name, sid->name);
441
442  done:
443         wbsrv_samba3_async_epilogue(status, s3call);
444 }
445
446 /*
447   Challenge-response authentication.  This interface is used by
448   ntlm_auth and the smbd auth subsystem to pass NTLM authentication
449   requests along a common pipe to the domain controller.  
450
451   The return value (in the async reply) may include the 'info3'
452   (effectivly most things you would want to know about the user), or
453   the NT and LM session keys seperated.
454 */
455
456 static void pam_auth_crap_recv(struct composite_context *ctx);
457
458 NTSTATUS wbsrv_samba3_pam_auth_crap(struct wbsrv_samba3_call *s3call)
459 {
460         struct composite_context *ctx;
461         struct wbsrv_service *service =
462                 s3call->wbconn->listen_socket->service;
463         DATA_BLOB chal, nt_resp, lm_resp;
464
465         DEBUG(5, ("wbsrv_samba3_pam_auth_crap called\n"));
466
467         chal.data       = s3call->request.data.auth_crap.chal;
468         chal.length     = sizeof(s3call->request.data.auth_crap.chal);
469         nt_resp.data    = (uint8_t *)s3call->request.data.auth_crap.nt_resp;
470         nt_resp.length  = s3call->request.data.auth_crap.nt_resp_len;
471         lm_resp.data    = (uint8_t *)s3call->request.data.auth_crap.lm_resp;
472         lm_resp.length  = s3call->request.data.auth_crap.lm_resp_len;
473
474         ctx = wb_cmd_pam_auth_crap_send(
475                 s3call, service,
476                 s3call->request.data.auth_crap.logon_parameters,
477                 s3call->request.data.auth_crap.domain,
478                 s3call->request.data.auth_crap.user,
479                 s3call->request.data.auth_crap.workstation,
480                 chal, nt_resp, lm_resp);
481         NT_STATUS_HAVE_NO_MEMORY(ctx);
482
483         ctx->async.fn = pam_auth_crap_recv;
484         ctx->async.private_data = s3call;
485         s3call->flags |= WBSRV_CALL_FLAGS_REPLY_ASYNC;
486         return NT_STATUS_OK;
487 }
488
489 static void pam_auth_crap_recv(struct composite_context *ctx)
490 {
491         struct wbsrv_samba3_call *s3call =
492                 talloc_get_type(ctx->async.private_data,
493                                 struct wbsrv_samba3_call);
494         NTSTATUS status;
495         DATA_BLOB info3;
496         struct netr_UserSessionKey user_session_key;
497         struct netr_LMSessionKey lm_key;
498         char *unix_username;
499         
500         status = wb_cmd_pam_auth_crap_recv(ctx, s3call, &info3,
501                                            &user_session_key, &lm_key, &unix_username);
502         if (!NT_STATUS_IS_OK(status)) goto done;
503
504         if (s3call->request.flags & WBFLAG_PAM_USER_SESSION_KEY) {
505                 memcpy(s3call->response.data.auth.user_session_key, 
506                        &user_session_key.key,
507                        sizeof(s3call->response.data.auth.user_session_key));
508         }
509
510         if (s3call->request.flags & WBFLAG_PAM_INFO3_NDR) {
511                 s3call->response.extra_data = info3.data;
512                 s3call->response.length += info3.length;
513         }
514
515         if (s3call->request.flags & WBFLAG_PAM_LMKEY) {
516                 memcpy(s3call->response.data.auth.first_8_lm_hash, 
517                        lm_key.key,
518                        sizeof(s3call->response.data.auth.first_8_lm_hash));
519         }
520         
521         if (s3call->request.flags & WBFLAG_PAM_UNIX_NAME) {
522                 s3call->response.extra_data = unix_username;
523                 s3call->response.length += strlen(unix_username)+1;
524         }
525
526  done:
527         wbsrv_samba3_async_auth_epilogue(status, s3call);
528 }
529
530 /* Helper function: Split a domain\\user string into it's parts,
531  * because the client supplies it as one string */
532
533 static BOOL samba3_parse_domuser(TALLOC_CTX *mem_ctx, const char *domuser,
534                                  char **domain, char **user)
535 {
536         char *p = strchr(domuser, *lp_winbind_separator());
537
538         if (p == NULL) {
539                 *domain = talloc_strdup(mem_ctx, lp_workgroup());
540         } else {
541                 *domain = talloc_strndup(mem_ctx, domuser,
542                                          PTR_DIFF(p, domuser));
543                 domuser = p+1;
544         }
545
546         *user = talloc_strdup(mem_ctx, domuser);
547
548         return ((*domain != NULL) && (*user != NULL));
549 }
550
551 /* Plaintext authentication 
552    
553    This interface is used by ntlm_auth in it's 'basic' authentication
554    mode, as well as by pam_winbind to authenticate users where we are
555    given a plaintext password.
556 */
557
558 static void pam_auth_recv(struct composite_context *ctx);
559
560 NTSTATUS wbsrv_samba3_pam_auth(struct wbsrv_samba3_call *s3call)
561 {
562         struct composite_context *ctx;
563         struct wbsrv_service *service =
564                 s3call->wbconn->listen_socket->service;
565         char *user, *domain;
566
567         if (!samba3_parse_domuser(s3call, 
568                                  s3call->request.data.auth.user,
569                                  &domain, &user)) {
570                 return NT_STATUS_NO_SUCH_USER;
571         }
572
573         ctx = wb_cmd_pam_auth_send(s3call, service, domain, user,
574                                    s3call->request.data.auth.pass);
575         NT_STATUS_HAVE_NO_MEMORY(ctx);
576
577         ctx->async.fn = pam_auth_recv;
578         ctx->async.private_data = s3call;
579         s3call->flags |= WBSRV_CALL_FLAGS_REPLY_ASYNC;
580         return NT_STATUS_OK;
581 }
582
583 static void pam_auth_recv(struct composite_context *ctx)
584 {
585         struct wbsrv_samba3_call *s3call =
586                 talloc_get_type(ctx->async.private_data,
587                                 struct wbsrv_samba3_call);
588         NTSTATUS status;
589
590         status = wb_cmd_pam_auth_recv(ctx);
591
592         if (!NT_STATUS_IS_OK(status)) goto done;
593
594  done:
595         wbsrv_samba3_async_auth_epilogue(status, s3call);
596 }
597
598 /* 
599    List trusted domains
600 */
601
602 static void list_trustdom_recv_doms(struct composite_context *ctx);
603
604 NTSTATUS wbsrv_samba3_list_trustdom(struct wbsrv_samba3_call *s3call)
605 {
606         struct composite_context *ctx;
607         struct wbsrv_service *service =
608                 s3call->wbconn->listen_socket->service;
609
610         DEBUG(5, ("wbsrv_samba3_list_trustdom called\n"));
611
612         ctx = wb_cmd_list_trustdoms_send(s3call, service);
613         NT_STATUS_HAVE_NO_MEMORY(ctx);
614
615         ctx->async.fn = list_trustdom_recv_doms;
616         ctx->async.private_data = s3call;
617         s3call->flags |= WBSRV_CALL_FLAGS_REPLY_ASYNC;
618         return NT_STATUS_OK;
619 }
620
621 static void list_trustdom_recv_doms(struct composite_context *ctx)
622 {
623         struct wbsrv_samba3_call *s3call =
624                 talloc_get_type(ctx->async.private_data,
625                                 struct wbsrv_samba3_call);
626         int i, num_domains;
627         struct wb_dom_info **domains;
628         NTSTATUS status;
629         char *result;
630
631         status = wb_cmd_list_trustdoms_recv(ctx, s3call, &num_domains,
632                                             &domains);
633         if (!NT_STATUS_IS_OK(status)) goto done;
634
635         result = talloc_strdup(s3call, "");
636         if (result == NULL) {
637                 status = NT_STATUS_NO_MEMORY;
638                 goto done;
639         }
640
641         for (i=0; i<num_domains; i++) {
642                 result = talloc_asprintf_append(
643                         result, "%s\\%s\\%s",
644                         domains[i]->name, domains[i]->name,
645                         dom_sid_string(s3call, domains[i]->sid));
646         }
647
648         if (result == NULL) {
649                 status = NT_STATUS_NO_MEMORY;
650                 goto done;
651         }
652
653         s3call->response.result = WINBINDD_OK;
654         if (num_domains > 0) {
655                 s3call->response.extra_data = result;
656                 s3call->response.length += strlen(result)+1;
657         }
658
659  done:
660         wbsrv_samba3_async_epilogue(status, s3call);
661 }