mirror of
https://github.com/samba-team/samba.git
synced 2025-01-25 06:04:04 +03:00
03f79a3bd7
Craft core structures to dispatch local calls in the same way as remote ones, removing the special handling in the autogenerated code. This is also necessary to drop s3 rpc handles implementation. Signed-off-by: Samuel Cabrero <scabrero@samba.org> Reviewed-by: Andrew Bartlett <abartlet@samba.org> Autobuild-User(master): Andrew Bartlett <abartlet@samba.org> Autobuild-Date(master): Wed Apr 8 22:23:05 UTC 2020 on sn-devel-184
623 lines
16 KiB
C
623 lines
16 KiB
C
/*
|
|
Unix SMB/CIFS implementation.
|
|
|
|
Provide parent->child communication based on NDR marshalling
|
|
|
|
Copyright (C) Volker Lendecke 2009
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/*
|
|
* This file implements an RPC between winbind parent and child processes,
|
|
* leveraging the autogenerated marshalling routines for MSRPC. This is not
|
|
* MSRPC, as it does not go through the whole DCERPC fragmentation, we just
|
|
* leverage much the same infrastructure we already have for it.
|
|
*/
|
|
|
|
#include "includes.h"
|
|
#include "winbindd/winbindd.h"
|
|
#include "winbindd/winbindd_proto.h"
|
|
#include "ntdomain.h"
|
|
#include "librpc/rpc/dcesrv_core.h"
|
|
#include "librpc/gen_ndr/ndr_winbind.h"
|
|
#include "rpc_server/rpc_config.h"
|
|
#include "rpc_server/rpc_server.h"
|
|
#include "rpc_dce.h"
|
|
|
|
struct wbint_bh_state {
|
|
struct winbindd_domain *domain;
|
|
struct winbindd_child *child;
|
|
};
|
|
|
|
static bool wbint_bh_is_connected(struct dcerpc_binding_handle *h)
|
|
{
|
|
struct wbint_bh_state *hs = dcerpc_binding_handle_data(h,
|
|
struct wbint_bh_state);
|
|
|
|
if ((hs->domain == NULL) && (hs->child == NULL)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static uint32_t wbint_bh_set_timeout(struct dcerpc_binding_handle *h,
|
|
uint32_t timeout)
|
|
{
|
|
/* TODO: implement timeouts */
|
|
return UINT32_MAX;
|
|
}
|
|
|
|
struct wbint_bh_raw_call_state {
|
|
struct winbindd_domain *domain;
|
|
uint32_t opnum;
|
|
DATA_BLOB in_data;
|
|
struct winbindd_request request;
|
|
struct winbindd_response *response;
|
|
DATA_BLOB out_data;
|
|
};
|
|
|
|
static void wbint_bh_raw_call_child_done(struct tevent_req *subreq);
|
|
static void wbint_bh_raw_call_domain_done(struct tevent_req *subreq);
|
|
|
|
static struct tevent_req *wbint_bh_raw_call_send(TALLOC_CTX *mem_ctx,
|
|
struct tevent_context *ev,
|
|
struct dcerpc_binding_handle *h,
|
|
const struct GUID *object,
|
|
uint32_t opnum,
|
|
uint32_t in_flags,
|
|
const uint8_t *in_data,
|
|
size_t in_length)
|
|
{
|
|
struct wbint_bh_state *hs =
|
|
dcerpc_binding_handle_data(h,
|
|
struct wbint_bh_state);
|
|
struct tevent_req *req;
|
|
struct wbint_bh_raw_call_state *state;
|
|
bool ok;
|
|
struct tevent_req *subreq;
|
|
|
|
req = tevent_req_create(mem_ctx, &state,
|
|
struct wbint_bh_raw_call_state);
|
|
if (req == NULL) {
|
|
return NULL;
|
|
}
|
|
state->domain = hs->domain;
|
|
state->opnum = opnum;
|
|
state->in_data.data = discard_const_p(uint8_t, in_data);
|
|
state->in_data.length = in_length;
|
|
|
|
ok = wbint_bh_is_connected(h);
|
|
if (!ok) {
|
|
tevent_req_nterror(req, NT_STATUS_CONNECTION_DISCONNECTED);
|
|
return tevent_req_post(req, ev);
|
|
}
|
|
|
|
if ((state->domain != NULL)
|
|
&& wcache_fetch_ndr(state, state->domain, state->opnum,
|
|
&state->in_data, &state->out_data)) {
|
|
DBG_DEBUG("Got opnum %"PRIu32" for domain %s from cache\n",
|
|
state->opnum, state->domain->name);
|
|
tevent_req_done(req);
|
|
return tevent_req_post(req, ev);
|
|
}
|
|
|
|
state->request.cmd = WINBINDD_DUAL_NDRCMD;
|
|
state->request.data.ndrcmd = state->opnum;
|
|
state->request.extra_data.data = (char *)state->in_data.data;
|
|
state->request.extra_len = state->in_data.length;
|
|
|
|
if (hs->child != NULL) {
|
|
subreq = wb_child_request_send(state, ev, hs->child,
|
|
&state->request);
|
|
if (tevent_req_nomem(subreq, req)) {
|
|
return tevent_req_post(req, ev);
|
|
}
|
|
tevent_req_set_callback(
|
|
subreq, wbint_bh_raw_call_child_done, req);
|
|
return req;
|
|
}
|
|
|
|
subreq = wb_domain_request_send(state, ev, hs->domain,
|
|
&state->request);
|
|
if (tevent_req_nomem(subreq, req)) {
|
|
return tevent_req_post(req, ev);
|
|
}
|
|
tevent_req_set_callback(subreq, wbint_bh_raw_call_domain_done, req);
|
|
|
|
return req;
|
|
}
|
|
|
|
static void wbint_bh_raw_call_child_done(struct tevent_req *subreq)
|
|
{
|
|
struct tevent_req *req =
|
|
tevent_req_callback_data(subreq,
|
|
struct tevent_req);
|
|
struct wbint_bh_raw_call_state *state =
|
|
tevent_req_data(req,
|
|
struct wbint_bh_raw_call_state);
|
|
int ret, err;
|
|
|
|
ret = wb_child_request_recv(subreq, state, &state->response, &err);
|
|
TALLOC_FREE(subreq);
|
|
if (ret == -1) {
|
|
NTSTATUS status = map_nt_error_from_unix(err);
|
|
tevent_req_nterror(req, status);
|
|
return;
|
|
}
|
|
|
|
state->out_data = data_blob_talloc(state,
|
|
state->response->extra_data.data,
|
|
state->response->length - sizeof(struct winbindd_response));
|
|
if (state->response->extra_data.data && !state->out_data.data) {
|
|
tevent_req_oom(req);
|
|
return;
|
|
}
|
|
|
|
if (state->domain != NULL) {
|
|
wcache_store_ndr(state->domain, state->opnum,
|
|
&state->in_data, &state->out_data);
|
|
}
|
|
|
|
tevent_req_done(req);
|
|
}
|
|
|
|
static void wbint_bh_raw_call_domain_done(struct tevent_req *subreq)
|
|
{
|
|
struct tevent_req *req =
|
|
tevent_req_callback_data(subreq,
|
|
struct tevent_req);
|
|
struct wbint_bh_raw_call_state *state =
|
|
tevent_req_data(req,
|
|
struct wbint_bh_raw_call_state);
|
|
int ret, err;
|
|
|
|
ret = wb_domain_request_recv(subreq, state, &state->response, &err);
|
|
TALLOC_FREE(subreq);
|
|
if (ret == -1) {
|
|
NTSTATUS status = map_nt_error_from_unix(err);
|
|
tevent_req_nterror(req, status);
|
|
return;
|
|
}
|
|
|
|
state->out_data = data_blob_talloc(state,
|
|
state->response->extra_data.data,
|
|
state->response->length - sizeof(struct winbindd_response));
|
|
if (state->response->extra_data.data && !state->out_data.data) {
|
|
tevent_req_oom(req);
|
|
return;
|
|
}
|
|
|
|
if (state->domain != NULL) {
|
|
wcache_store_ndr(state->domain, state->opnum,
|
|
&state->in_data, &state->out_data);
|
|
}
|
|
|
|
tevent_req_done(req);
|
|
}
|
|
|
|
static NTSTATUS wbint_bh_raw_call_recv(struct tevent_req *req,
|
|
TALLOC_CTX *mem_ctx,
|
|
uint8_t **out_data,
|
|
size_t *out_length,
|
|
uint32_t *out_flags)
|
|
{
|
|
struct wbint_bh_raw_call_state *state =
|
|
tevent_req_data(req,
|
|
struct wbint_bh_raw_call_state);
|
|
NTSTATUS status;
|
|
|
|
if (tevent_req_is_nterror(req, &status)) {
|
|
tevent_req_received(req);
|
|
return status;
|
|
}
|
|
|
|
*out_data = talloc_move(mem_ctx, &state->out_data.data);
|
|
*out_length = state->out_data.length;
|
|
*out_flags = 0;
|
|
tevent_req_received(req);
|
|
return NT_STATUS_OK;
|
|
}
|
|
|
|
struct wbint_bh_disconnect_state {
|
|
uint8_t _dummy;
|
|
};
|
|
|
|
static struct tevent_req *wbint_bh_disconnect_send(TALLOC_CTX *mem_ctx,
|
|
struct tevent_context *ev,
|
|
struct dcerpc_binding_handle *h)
|
|
{
|
|
struct wbint_bh_state *hs = dcerpc_binding_handle_data(h,
|
|
struct wbint_bh_state);
|
|
struct tevent_req *req;
|
|
struct wbint_bh_disconnect_state *state;
|
|
bool ok;
|
|
|
|
req = tevent_req_create(mem_ctx, &state,
|
|
struct wbint_bh_disconnect_state);
|
|
if (req == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
ok = wbint_bh_is_connected(h);
|
|
if (!ok) {
|
|
tevent_req_nterror(req, NT_STATUS_CONNECTION_DISCONNECTED);
|
|
return tevent_req_post(req, ev);
|
|
}
|
|
|
|
/*
|
|
* TODO: do a real async disconnect ...
|
|
*/
|
|
hs->domain = NULL;
|
|
hs->child = NULL;
|
|
|
|
tevent_req_done(req);
|
|
return tevent_req_post(req, ev);
|
|
}
|
|
|
|
static NTSTATUS wbint_bh_disconnect_recv(struct tevent_req *req)
|
|
{
|
|
NTSTATUS status;
|
|
|
|
if (tevent_req_is_nterror(req, &status)) {
|
|
tevent_req_received(req);
|
|
return status;
|
|
}
|
|
|
|
tevent_req_received(req);
|
|
return NT_STATUS_OK;
|
|
}
|
|
|
|
static bool wbint_bh_ref_alloc(struct dcerpc_binding_handle *h)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
static void wbint_bh_do_ndr_print(struct dcerpc_binding_handle *h,
|
|
int ndr_flags,
|
|
const void *_struct_ptr,
|
|
const struct ndr_interface_call *call)
|
|
{
|
|
void *struct_ptr = discard_const(_struct_ptr);
|
|
|
|
if (DEBUGLEVEL < 10) {
|
|
return;
|
|
}
|
|
|
|
if (ndr_flags & NDR_IN) {
|
|
ndr_print_function_debug(call->ndr_print,
|
|
call->name,
|
|
ndr_flags,
|
|
struct_ptr);
|
|
}
|
|
if (ndr_flags & NDR_OUT) {
|
|
ndr_print_function_debug(call->ndr_print,
|
|
call->name,
|
|
ndr_flags,
|
|
struct_ptr);
|
|
}
|
|
}
|
|
|
|
static const struct dcerpc_binding_handle_ops wbint_bh_ops = {
|
|
.name = "wbint",
|
|
.is_connected = wbint_bh_is_connected,
|
|
.set_timeout = wbint_bh_set_timeout,
|
|
.raw_call_send = wbint_bh_raw_call_send,
|
|
.raw_call_recv = wbint_bh_raw_call_recv,
|
|
.disconnect_send = wbint_bh_disconnect_send,
|
|
.disconnect_recv = wbint_bh_disconnect_recv,
|
|
|
|
.ref_alloc = wbint_bh_ref_alloc,
|
|
.do_ndr_print = wbint_bh_do_ndr_print,
|
|
};
|
|
|
|
static NTSTATUS make_internal_ncacn_conn(TALLOC_CTX *mem_ctx,
|
|
const struct ndr_interface_table *table,
|
|
struct dcerpc_ncacn_conn **_out)
|
|
{
|
|
struct dcerpc_ncacn_conn *ncacn_conn = NULL;
|
|
NTSTATUS status;
|
|
|
|
ncacn_conn = talloc_zero(mem_ctx, struct dcerpc_ncacn_conn);
|
|
if (ncacn_conn == NULL) {
|
|
return NT_STATUS_NO_MEMORY;
|
|
}
|
|
|
|
ncacn_conn->p = talloc_zero(ncacn_conn, struct pipes_struct);
|
|
if (ncacn_conn->p == NULL) {
|
|
status = NT_STATUS_NO_MEMORY;
|
|
goto fail;
|
|
}
|
|
ncacn_conn->p->mem_ctx = mem_ctx;
|
|
|
|
*_out = ncacn_conn;
|
|
|
|
return NT_STATUS_OK;
|
|
|
|
fail:
|
|
talloc_free(ncacn_conn);
|
|
return status;
|
|
}
|
|
|
|
static NTSTATUS find_ncalrpc_default_endpoint(struct dcesrv_context *dce_ctx,
|
|
struct dcesrv_endpoint **ep)
|
|
{
|
|
TALLOC_CTX *tmp_ctx = NULL;
|
|
struct dcerpc_binding *binding = NULL;
|
|
NTSTATUS status;
|
|
|
|
tmp_ctx = talloc_new(dce_ctx);
|
|
if (tmp_ctx == NULL) {
|
|
return NT_STATUS_NO_MEMORY;
|
|
}
|
|
|
|
/*
|
|
* Some services use a rpcint binding handle in their initialization,
|
|
* before the server is fully initialized. Search the NCALRPC endpoint
|
|
* with and without endpoint
|
|
*/
|
|
status = dcerpc_parse_binding(tmp_ctx, "ncalrpc:", &binding);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
goto out;
|
|
}
|
|
|
|
status = dcesrv_find_endpoint(dce_ctx, binding, ep);
|
|
if (NT_STATUS_IS_OK(status)) {
|
|
goto out;
|
|
}
|
|
|
|
status = dcerpc_parse_binding(tmp_ctx, "ncalrpc:[DEFAULT]", &binding);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
goto out;
|
|
}
|
|
|
|
status = dcesrv_find_endpoint(dce_ctx, binding, ep);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
goto out;
|
|
}
|
|
|
|
out:
|
|
talloc_free(tmp_ctx);
|
|
return status;
|
|
}
|
|
|
|
static NTSTATUS make_internal_dcesrv_connection(TALLOC_CTX *mem_ctx,
|
|
const struct ndr_interface_table *ndr_table,
|
|
struct dcerpc_ncacn_conn *ncacn_conn,
|
|
struct dcesrv_connection **_out)
|
|
{
|
|
struct dcesrv_connection *conn = NULL;
|
|
struct dcesrv_connection_context *context = NULL;
|
|
struct dcesrv_endpoint *endpoint = NULL;
|
|
NTSTATUS status;
|
|
|
|
conn = talloc_zero(mem_ctx, struct dcesrv_connection);
|
|
if (conn == NULL) {
|
|
return NT_STATUS_NO_MEMORY;
|
|
}
|
|
conn->dce_ctx = global_dcesrv_context();
|
|
conn->preferred_transfer = &ndr_transfer_syntax_ndr;
|
|
conn->transport.private_data = ncacn_conn;
|
|
|
|
status = find_ncalrpc_default_endpoint(conn->dce_ctx, &endpoint);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
goto fail;
|
|
}
|
|
conn->endpoint = endpoint;
|
|
|
|
conn->default_auth_state = talloc_zero(conn, struct dcesrv_auth);
|
|
if (conn->default_auth_state == NULL) {
|
|
status = NT_STATUS_NO_MEMORY;
|
|
goto fail;
|
|
}
|
|
conn->default_auth_state->session_info = ncacn_conn->session_info;
|
|
conn->default_auth_state->auth_finished = true;
|
|
|
|
context = talloc_zero(conn, struct dcesrv_connection_context);
|
|
if (context == NULL) {
|
|
status = NT_STATUS_NO_MEMORY;
|
|
goto fail;
|
|
}
|
|
context->conn = conn;
|
|
context->context_id = 0;
|
|
context->transfer_syntax = *(conn->preferred_transfer);
|
|
context->iface = find_interface_by_uuid(conn->endpoint,
|
|
&ndr_table->syntax_id.uuid,
|
|
ndr_table->syntax_id.if_version);
|
|
if (context->iface == NULL) {
|
|
status = NT_STATUS_RPC_INTERFACE_NOT_FOUND;
|
|
goto fail;
|
|
}
|
|
|
|
DLIST_ADD(conn->contexts, context);
|
|
|
|
*_out = conn;
|
|
|
|
return NT_STATUS_OK;
|
|
fail:
|
|
talloc_free(conn);
|
|
return status;
|
|
}
|
|
|
|
/* initialise a wbint binding handle */
|
|
struct dcerpc_binding_handle *wbint_binding_handle(TALLOC_CTX *mem_ctx,
|
|
struct winbindd_domain *domain,
|
|
struct winbindd_child *child)
|
|
{
|
|
struct dcerpc_binding_handle *h;
|
|
struct wbint_bh_state *hs;
|
|
|
|
h = dcerpc_binding_handle_create(mem_ctx,
|
|
&wbint_bh_ops,
|
|
NULL,
|
|
&ndr_table_winbind,
|
|
&hs,
|
|
struct wbint_bh_state,
|
|
__location__);
|
|
if (h == NULL) {
|
|
return NULL;
|
|
}
|
|
hs->domain = domain;
|
|
hs->child = child;
|
|
|
|
return h;
|
|
}
|
|
|
|
static NTSTATUS rpcint_dispatch(struct dcesrv_call_state *call)
|
|
{
|
|
NTSTATUS status;
|
|
struct ndr_pull *pull = NULL;
|
|
struct ndr_push *push = NULL;
|
|
struct data_blob_list_item *rep = NULL;
|
|
|
|
pull = ndr_pull_init_blob(&call->pkt.u.request.stub_and_verifier,
|
|
call);
|
|
if (pull == NULL) {
|
|
return NT_STATUS_NO_MEMORY;
|
|
}
|
|
|
|
pull->flags |= LIBNDR_FLAG_REF_ALLOC;
|
|
|
|
call->ndr_pull = pull;
|
|
|
|
/* unravel the NDR for the packet */
|
|
status = call->context->iface->ndr_pull(call, call, pull, &call->r);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
DBG_ERR("DCE/RPC fault in call %s:%02X - %s\n",
|
|
call->context->iface->name,
|
|
call->pkt.u.request.opnum,
|
|
dcerpc_errstr(call, call->fault_code));
|
|
return status;
|
|
}
|
|
|
|
status = call->context->iface->local(call, call, call->r);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
DBG_ERR("DCE/RPC fault in call %s:%02X - %s\n",
|
|
call->context->iface->name,
|
|
call->pkt.u.request.opnum,
|
|
dcerpc_errstr(call, call->fault_code));
|
|
return status;
|
|
}
|
|
|
|
push = ndr_push_init_ctx(call);
|
|
if (push == NULL) {
|
|
return NT_STATUS_NO_MEMORY;
|
|
}
|
|
|
|
push->ptr_count = call->ndr_pull->ptr_count;
|
|
|
|
status = call->context->iface->ndr_push(call, call, push, call->r);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
DBG_ERR("DCE/RPC fault in call %s:%02X - %s\n",
|
|
call->context->iface->name,
|
|
call->pkt.u.request.opnum,
|
|
dcerpc_errstr(call, call->fault_code));
|
|
return status;
|
|
}
|
|
|
|
rep = talloc_zero(call, struct data_blob_list_item);
|
|
if (rep == NULL) {
|
|
return NT_STATUS_NO_MEMORY;
|
|
}
|
|
|
|
rep->blob = ndr_push_blob(push);
|
|
DLIST_ADD_END(call->replies, rep);
|
|
|
|
return NT_STATUS_OK;
|
|
}
|
|
|
|
enum winbindd_result winbindd_dual_ndrcmd(struct winbindd_domain *domain,
|
|
struct winbindd_cli_state *state)
|
|
{
|
|
struct dcerpc_ncacn_conn *ncacn_conn = NULL;
|
|
struct dcesrv_connection *dcesrv_conn = NULL;
|
|
struct dcesrv_call_state *dcesrv_call = NULL;
|
|
struct data_blob_list_item *rep = NULL;
|
|
uint32_t opnum = state->request->data.ndrcmd;
|
|
TALLOC_CTX *mem_ctx;
|
|
NTSTATUS status;
|
|
|
|
DBG_DEBUG("Running command %s (domain '%s')\n",
|
|
ndr_table_winbind.calls[opnum].name,
|
|
domain ? domain->name : "(null)");
|
|
|
|
mem_ctx = talloc_stackframe();
|
|
if (mem_ctx == NULL) {
|
|
DBG_ERR("No memory");
|
|
return WINBINDD_ERROR;
|
|
}
|
|
|
|
status = make_internal_ncacn_conn(mem_ctx,
|
|
&ndr_table_winbind,
|
|
&ncacn_conn);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
goto out;
|
|
}
|
|
|
|
status = make_internal_dcesrv_connection(ncacn_conn,
|
|
&ndr_table_winbind,
|
|
ncacn_conn,
|
|
&dcesrv_conn);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
goto out;
|
|
}
|
|
|
|
dcesrv_call = talloc_zero(dcesrv_conn, struct dcesrv_call_state);
|
|
if (dcesrv_call == NULL) {
|
|
status = NT_STATUS_NO_MEMORY;
|
|
goto out;
|
|
}
|
|
|
|
dcesrv_call->conn = dcesrv_conn;
|
|
dcesrv_call->context = dcesrv_conn->contexts;
|
|
dcesrv_call->auth_state = dcesrv_conn->default_auth_state;
|
|
|
|
ZERO_STRUCT(dcesrv_call->pkt);
|
|
dcesrv_call->pkt.u.bind.assoc_group_id = 0;
|
|
status = dcesrv_call->conn->dce_ctx->callbacks.assoc_group.find(
|
|
dcesrv_call);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
goto out;
|
|
}
|
|
|
|
ZERO_STRUCT(dcesrv_call->pkt);
|
|
dcesrv_call->pkt.u.request.opnum = opnum;
|
|
dcesrv_call->pkt.u.request.context_id = 0;
|
|
dcesrv_call->pkt.u.request.stub_and_verifier =
|
|
data_blob_const(state->request->extra_data.data,
|
|
state->request->extra_len);
|
|
|
|
status = rpcint_dispatch(dcesrv_call);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
goto out;
|
|
}
|
|
|
|
rep = dcesrv_call->replies;
|
|
DLIST_REMOVE(dcesrv_call->replies, rep);
|
|
|
|
state->response->extra_data.data = talloc_steal(state->mem_ctx,
|
|
rep->blob.data);
|
|
state->response->length += rep->blob.length;
|
|
|
|
talloc_free(rep);
|
|
|
|
out:
|
|
talloc_free(mem_ctx);
|
|
if (NT_STATUS_IS_OK(status)) {
|
|
return WINBINDD_OK;
|
|
}
|
|
return WINBINDD_ERROR;
|
|
}
|