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