mirror of
https://github.com/samba-team/samba.git
synced 2024-12-31 17:18:04 +03:00
af06d73a75
In samba3 it is possible to run some services externally, for example: rpc_daemon:lsasd = fork rpc_server:netlogon = disabled rpc_server:samr = external rpc_server:lsarpc = external The external services running in separate processes have to use its own dedicated ncalrpc endpoint, otherwise will race with main smbd serving the embedded services to accept connections on ncalrpc default socket. If the connection ends in an external process and the client tries to bind to an interface not registered there (like winreg for example) the bind will fail. Signed-off-by: Samuel Cabrero <scabrero@samba.org> Reviewed-by: Volker Lendecke <vl@samba.org> Autobuild-User(master): Volker Lendecke <vl@samba.org> Autobuild-Date(master): Tue Sep 21 11:00:01 UTC 2021 on sn-devel-184
572 lines
14 KiB
C
572 lines
14 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:[WINBIND]", &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_syntax_id(
|
|
conn->endpoint, &ndr_table->syntax_id);
|
|
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;
|
|
}
|
|
|
|
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;
|
|
struct dcesrv_context_callbacks *cb = 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;
|
|
|
|
cb = dcesrv_call->conn->dce_ctx->callbacks;
|
|
status = cb->assoc_group.find(
|
|
dcesrv_call, cb->assoc_group.private_data);
|
|
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 = dcesrv_call_dispatch_local(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;
|
|
}
|