From 8daa83a594a2e98f39d764422bfbdbc62c9efd44 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 19:20:00 +0200 Subject: Adding upstream version 2:4.20.0+dfsg. Signed-off-by: Daniel Baumann --- source3/rpc_server/rpc_worker.c | 1287 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 1287 insertions(+) create mode 100644 source3/rpc_server/rpc_worker.c (limited to 'source3/rpc_server/rpc_worker.c') diff --git a/source3/rpc_server/rpc_worker.c b/source3/rpc_server/rpc_worker.c new file mode 100644 index 0000000..bf9671d --- /dev/null +++ b/source3/rpc_server/rpc_worker.c @@ -0,0 +1,1287 @@ +/* + * Unix SMB/CIFS implementation. + * + * 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 . + */ + +#include "source3/include/includes.h" +#include "lib/cmdline/cmdline.h" +#include "rpc_worker.h" +#include "rpc_config.h" +#include "librpc/rpc/dcesrv_core.h" +#include "librpc/rpc/dcerpc_util.h" +#include "source3/librpc/gen_ndr/ndr_rpc_host.h" +#include "lib/util/debug.h" +#include "lib/util/fault.h" +#include "rpc_server.h" +#include "rpc_pipes.h" +#include "source3/smbd/proto.h" +#include "source3/lib/smbd_shim.h" +#include "source3/lib/global_contexts.h" +#include "source3/lib/util_procid.h" +#include "lib/tsocket/tsocket.h" +#include "libcli/named_pipe_auth/npa_tstream.h" +#include "libcli/smb/smb_constants.h" +#include "lib/param/param.h" +#include "lib/util/idtree_random.h" +#include "lib/util/tevent_unix.h" +#include "lib/async_req/async_sock.h" +#include "lib/util/dlinklist.h" +#include "source3/include/auth.h" +#include "nsswitch/winbind_client.h" +#include "source3/include/messages.h" +#include "libcli/security/security_token.h" +#include "libcli/security/dom_sid.h" +#include "source3/include/proto.h" + +/* + * This is the generic code that becomes the + * template that all rpcd_* instances that + * serve DCERPC can use to provide services to samba-dcerpcd. + * + * The external entry point is: + * rpc_worker_main() which takes an argc/argv list + * and two functions: + * + * get_interfaces() - List all interfaces that this server provides + * get_servers() - Provide the RPC server implementations + * + * Each rpcd_* service needs only to provide + * the implementations of get_interfaces() and get_servers() + * and call rpc_worker_main() from their main() function + * to provide services that can be connected to from samba-dcerpcd. + */ + +struct rpc_worker { + struct dcerpc_ncacn_conn *conns; + struct server_id rpc_host_pid; + struct messaging_context *msg_ctx; + struct dcesrv_context *dce_ctx; + + struct dcesrv_context_callbacks cb; + + struct rpc_worker_status status; + + bool done; +}; + +static void rpc_worker_print_interface( + FILE *f, const struct ndr_interface_table *t) +{ + const struct ndr_interface_string_array *endpoints = t->endpoints; + uint32_t i; + struct ndr_syntax_id_buf id_buf; + + fprintf(f, + "%s %s\n", + ndr_syntax_id_buf_string(&t->syntax_id, &id_buf), + t->name); + + for (i=0; icount; i++) { + fprintf(f, " %s\n", endpoints->names[i]); + } +} + +static NTSTATUS rpc_worker_report_status(struct rpc_worker *worker) +{ + uint8_t buf[16]; + DATA_BLOB blob = { .data = buf, .length = sizeof(buf), }; + enum ndr_err_code ndr_err; + NTSTATUS status; + + worker->status.num_association_groups = worker->dce_ctx->assoc_groups_num; + + if (DEBUGLEVEL >= 10) { + NDR_PRINT_DEBUG(rpc_worker_status, &worker->status); + } + + ndr_err = ndr_push_struct_into_fixed_blob( + &blob, + &worker->status, + (ndr_push_flags_fn_t)ndr_push_rpc_worker_status); + SMB_ASSERT(NDR_ERR_CODE_IS_SUCCESS(ndr_err)); + + status = messaging_send( + worker->msg_ctx, + worker->rpc_host_pid, + MSG_RPC_WORKER_STATUS, + &blob); + return status; +} + +static void rpc_worker_connection_terminated( + struct dcesrv_connection *conn, void *private_data) +{ + struct rpc_worker *worker = talloc_get_type_abort( + private_data, struct rpc_worker); + struct dcerpc_ncacn_conn *ncacn_conn = talloc_get_type_abort( + conn->transport.private_data, struct dcerpc_ncacn_conn); + struct dcerpc_ncacn_conn *w = NULL; + NTSTATUS status; + bool found = false; + + /* + * We need to drop the association group reference + * explicitly here in order to avoid the order given + * by the destructors. rpc_worker_report_status() below, + * expects worker->dce_ctx->assoc_groups_num to be updated + * already. + */ + if (conn->assoc_group != NULL) { + talloc_unlink(conn, conn->assoc_group); + conn->assoc_group = NULL; + } + + SMB_ASSERT(worker->status.num_connections > 0); + + for (w = worker->conns; w != NULL; w = w->next) { + if (w == ncacn_conn) { + found = true; + break; + } + } + SMB_ASSERT(found); + + DLIST_REMOVE(worker->conns, ncacn_conn); + + worker->status.num_connections -= 1; + + status = rpc_worker_report_status(worker); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("rpc_worker_report_status returned %s\n", + nt_errstr(status)); + } +} + +static int dcesrv_connection_destructor(struct dcesrv_connection *conn) +{ + struct dcerpc_ncacn_conn *ncacn_conn = talloc_get_type_abort( + conn->transport.private_data, + struct dcerpc_ncacn_conn); + + if (ncacn_conn->termination_fn != NULL) { + ncacn_conn->termination_fn(conn, ncacn_conn->termination_data); + } + + return 0; +} + +/* + * A new client has been passed to us from samba-dcerpcd. + */ +static void rpc_worker_new_client( + struct rpc_worker *worker, + struct rpc_host_client *client, + int sock) +{ + struct dcesrv_context *dce_ctx = worker->dce_ctx; + struct named_pipe_auth_req_info8 *info8 = client->npa_info8; + struct tsocket_address *remote_client_addr = NULL; + struct tsocket_address *local_server_addr = NULL; + struct dcerpc_binding *b = NULL; + enum dcerpc_transport_t transport; + struct dcesrv_endpoint *ep = NULL; + struct tstream_context *tstream = NULL; + struct dcerpc_ncacn_conn *ncacn_conn = NULL; + struct dcesrv_connection *dcesrv_conn = NULL; + DATA_BLOB buffer = { .data = NULL }; + struct ncacn_packet *pkt = NULL; + struct security_token *token = NULL; + uint32_t npa_flags, state_flags; + bool found_npa_flags; + NTSTATUS status; + int ret; + + DBG_DEBUG("Got new conn sock %d for binding %s\n", + sock, + client->binding); + + status = dcerpc_parse_binding(client, client->binding, &b); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("dcerpc_parse_binding(%s) failed: %s\n", + client->binding, + nt_errstr(status)); + goto fail; + } + transport = dcerpc_binding_get_transport(b); + + status = dcesrv_find_endpoint(dce_ctx, b, &ep); + + if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND) && + ((transport == NCACN_IP_TCP) || (transport == NCALRPC)) && + (dcerpc_binding_get_string_option(b, "endpoint") != NULL)) { + /* + * We have two kinds of servers: Those who explicitly + * bind to a port (e.g. 135 for epmapper) and those + * who just specify a transport. The client specified + * a port (or socket name), but we did not find this + * in the list of servers having specified a + * port. Retry just matching for the transport, + * catching the servers that did not explicitly + * specify a port. + * + * This is not fully correct, what we should do is + * that once the port the server listens on has been + * finalized we should mark this in the server list, + * but for now it works. We don't have the same RPC + * interface listening twice on different ports. + */ + struct dcerpc_binding *b_without_port = dcerpc_binding_dup( + client, b); + if (b_without_port == NULL) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + + status = dcerpc_binding_set_string_option( + b_without_port, "endpoint", NULL); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("Could not delete endpoint: %s\n", + nt_errstr(status)); + TALLOC_FREE(b_without_port); + goto fail; + } + + status = dcesrv_find_endpoint(dce_ctx, b_without_port, &ep); + + TALLOC_FREE(b_without_port); + } + + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("Could not find endpoint for %s: %s\n", + client->binding, + nt_errstr(status)); + goto fail; + } + + ncacn_conn = talloc(dce_ctx, struct dcerpc_ncacn_conn); + if (ncacn_conn == NULL) { + DBG_DEBUG("talloc failed\n"); + goto fail; + } + *ncacn_conn = (struct dcerpc_ncacn_conn) { + .endpoint = ep, + .sock = sock, + .termination_fn = rpc_worker_connection_terminated, + .termination_data = worker, + }; + + if (transport == NCALRPC) { + ret = tsocket_address_unix_from_path(ncacn_conn, + info8->remote_client_addr, + &remote_client_addr); + if (ret == -1) { + DBG_DEBUG("tsocket_address_unix_from_path" + "(%s) failed: %s\n", + info8->remote_client_addr, + strerror(errno)); + goto fail; + } + + ncacn_conn->remote_client_name = + talloc_strdup(ncacn_conn, info8->remote_client_name); + if (ncacn_conn->remote_client_name == NULL) { + DBG_DEBUG("talloc_strdup(%s) failed\n", + info8->remote_client_name); + goto fail; + } + + ret = tsocket_address_unix_from_path(ncacn_conn, + info8->local_server_addr, + &local_server_addr); + if (ret == -1) { + DBG_DEBUG("tsocket_address_unix_from_path" + "(%s) failed: %s\n", + info8->local_server_addr, + strerror(errno)); + goto fail; + } + + ncacn_conn->local_server_name = + talloc_strdup(ncacn_conn, info8->local_server_name); + if (ncacn_conn->local_server_name == NULL) { + DBG_DEBUG("talloc_strdup(%s) failed\n", + info8->local_server_name); + goto fail; + } + } else { + ret = tsocket_address_inet_from_strings( + ncacn_conn, + "ip", + info8->remote_client_addr, + info8->remote_client_port, + &remote_client_addr); + if (ret == -1) { + DBG_DEBUG("tsocket_address_inet_from_strings" + "(%s, %" PRIu16 ") failed: %s\n", + info8->remote_client_addr, + info8->remote_client_port, + strerror(errno)); + goto fail; + } + ncacn_conn->remote_client_name = + talloc_strdup(ncacn_conn, info8->remote_client_name); + if (ncacn_conn->remote_client_name == NULL) { + DBG_DEBUG("talloc_strdup(%s) failed\n", + info8->remote_client_name); + goto fail; + } + + ret = tsocket_address_inet_from_strings( + ncacn_conn, + "ip", + info8->local_server_addr, + info8->local_server_port, + &local_server_addr); + if (ret == -1) { + DBG_DEBUG("tsocket_address_inet_from_strings" + "(%s, %" PRIu16 ") failed: %s\n", + info8->local_server_addr, + info8->local_server_port, + strerror(errno)); + goto fail; + } + ncacn_conn->local_server_name = + talloc_strdup(ncacn_conn, info8->local_server_name); + if (ncacn_conn->local_server_name == NULL) { + DBG_DEBUG("talloc_strdup(%s) failed\n", + info8->local_server_name); + goto fail; + } + } + + if (transport == NCACN_NP) { + ret = tstream_npa_existing_socket( + ncacn_conn, + sock, + FILE_TYPE_MESSAGE_MODE_PIPE, + &tstream); + if (ret == -1) { + DBG_DEBUG("tstream_npa_existing_socket failed: %s\n", + strerror(errno)); + goto fail; + } + + /* + * "transport" so far is implicitly assigned by the + * socket that the client connected to, passed in from + * samba-dcerpcd via the binding. For NCACN_NP (root + * only by unix permissions) we got a + * named_pipe_auth_req_info8 where the transport can + * be overridden. + */ + transport = info8->transport; + } else { + ret = tstream_bsd_existing_socket( + ncacn_conn, sock, &tstream); + if (ret == -1) { + DBG_DEBUG("tstream_bsd_existing_socket failed: %s\n", + strerror(errno)); + goto fail; + } + /* as server we want to fail early */ + tstream_bsd_fail_readv_first_error(tstream, true); + } + sock = -1; + + token = info8->session_info->session_info->security_token; + + if (security_token_is_system(token) && (transport != NCALRPC)) { + DBG_DEBUG("System token only allowed on NCALRPC\n"); + goto fail; + } + + state_flags = DCESRV_CALL_STATE_FLAG_MAY_ASYNC; + + found_npa_flags = security_token_find_npa_flags(token, &npa_flags); + if (found_npa_flags) { + if (npa_flags & SAMBA_NPA_FLAGS_WINBIND_OFF) { + state_flags |= + DCESRV_CALL_STATE_FLAG_WINBIND_OFF; + } + + /* + * Delete the flags so that we don't bail in + * local_np_connect_send() on subsequent + * connects. Once we connect to another RPC service, a + * new flags sid will be added if required. + */ + security_token_del_npa_flags(token); + } + + ncacn_conn->p.msg_ctx = global_messaging_context(); + ncacn_conn->p.transport = transport; + + status = dcesrv_endpoint_connect(dce_ctx, + ncacn_conn, + ep, + info8->session_info->session_info, + global_event_context(), + state_flags, + &dcesrv_conn); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("Failed to connect to endpoint: %s\n", + nt_errstr(status)); + goto fail; + } + + talloc_set_destructor(dcesrv_conn, dcesrv_connection_destructor); + + dcesrv_conn->transport.private_data = ncacn_conn; + dcesrv_conn->transport.report_output_data = + dcesrv_sock_report_output_data; + dcesrv_conn->transport.terminate_connection = + dcesrv_transport_terminate_connection; + + dcesrv_conn->send_queue = tevent_queue_create( + dcesrv_conn, "dcesrv send queue"); + if (dcesrv_conn->send_queue == NULL) { + DBG_DEBUG("tevent_queue_create failed\n"); + goto fail; + } + + dcesrv_conn->stream = talloc_move(dcesrv_conn, &tstream); + dcesrv_conn->local_address = + talloc_move(dcesrv_conn, &local_server_addr); + dcesrv_conn->remote_address = + talloc_move(dcesrv_conn, &remote_client_addr); + + if (client->bind_packet.length == 0) { + DBG_DEBUG("Expected bind packet\n"); + goto fail; + } + + buffer = (DATA_BLOB) { + .data = talloc_move(dcesrv_conn, &client->bind_packet.data), + .length = client->bind_packet.length, + }; + + pkt = talloc(dcesrv_conn, struct ncacn_packet); + if (pkt == NULL) { + DBG_DEBUG("talloc failed\n"); + goto fail; + } + + status = dcerpc_pull_ncacn_packet(pkt, &buffer, pkt); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("dcerpc_pull_ncacn_packet failed: %s\n", + nt_errstr(status)); + goto fail; + } + + TALLOC_FREE(client); + + DLIST_ADD(worker->conns, ncacn_conn); + worker->status.num_connections += 1; + + dcesrv_loop_next_packet(dcesrv_conn, pkt, buffer); + + return; +fail: + TALLOC_FREE(ncacn_conn); + TALLOC_FREE(dcesrv_conn); + TALLOC_FREE(client); + if (sock != -1) { + close(sock); + } + + /* + * Parent thinks it successfully sent us a client. Tell it + * that we declined. + */ + status = rpc_worker_report_status(worker); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("rpc_worker_report_status returned %s\n", + nt_errstr(status)); + } +} + +/* + * New client message processing. + */ +static bool rpc_worker_new_client_filter( + struct messaging_rec *rec, void *private_data) +{ + struct rpc_worker *worker = talloc_get_type_abort( + private_data, struct rpc_worker); + struct dcesrv_context *dce_ctx = worker->dce_ctx; + struct rpc_host_client *client = NULL; + enum ndr_err_code ndr_err; + int sock; + + if (rec->msg_type != MSG_RPC_HOST_NEW_CLIENT) { + return false; + } + + if (rec->num_fds != 1) { + DBG_DEBUG("Got %"PRIu8" fds\n", rec->num_fds); + return false; + } + + client = talloc(dce_ctx, struct rpc_host_client); + if (client == NULL) { + DBG_DEBUG("talloc failed\n"); + return false; + } + + ndr_err = ndr_pull_struct_blob_all( + &rec->buf, + client, + client, + (ndr_pull_flags_fn_t)ndr_pull_rpc_host_client); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + DBG_DEBUG("ndr_pull_rpc_host_client failed: %s\n", + ndr_errstr(ndr_err)); + TALLOC_FREE(client); + return false; + } + + if (DEBUGLEVEL >= 10) { + NDR_PRINT_DEBUG(rpc_host_client, client); + } + + sock = rec->fds[0]; + rec->fds[0] = -1; + + rpc_worker_new_client(worker, client, sock); + + return false; +} + +/* + * Return your status message processing. + */ +static bool rpc_worker_status_filter( + struct messaging_rec *rec, void *private_data) +{ + struct rpc_worker *worker = talloc_get_type_abort( + private_data, struct rpc_worker); + struct dcerpc_ncacn_conn *conn = NULL; + FILE *f = NULL; + int fd; + + if (rec->msg_type != MSG_RPC_DUMP_STATUS) { + return false; + } + + if (rec->num_fds != 1) { + DBG_DEBUG("Got %"PRIu8" fds\n", rec->num_fds); + return false; + } + + fd = dup(rec->fds[0]); + if (fd == -1) { + DBG_DEBUG("dup(%"PRIi64") failed: %s\n", + rec->fds[0], + strerror(errno)); + return false; + } + + f = fdopen(fd, "w"); + if (f == NULL) { + DBG_DEBUG("fdopen failed: %s\n", strerror(errno)); + close(fd); + return false; + } + + for (conn = worker->conns; conn != NULL; conn = conn->next) { + char *endpoint = NULL; + + endpoint = dcerpc_binding_string( + conn, conn->endpoint->ep_description); + + fprintf(f, + "endpoint=%s client=%s server=%s\n", + endpoint ? endpoint : "UNKNOWN", + conn->remote_client_name, + conn->local_server_name); + TALLOC_FREE(endpoint); + } + + fclose(f); + + return false; +} + +/* + take a reference to an existing association group + */ +static struct dcesrv_assoc_group *rpc_worker_assoc_group_reference( + struct dcesrv_connection *conn, + uint32_t id) +{ + const struct dcesrv_endpoint *endpoint = conn->endpoint; + enum dcerpc_transport_t transport = dcerpc_binding_get_transport( + endpoint->ep_description); + struct dcesrv_assoc_group *assoc_group = NULL; + void *id_ptr = NULL; + + /* find an association group given a assoc_group_id */ + id_ptr = idr_find(conn->dce_ctx->assoc_groups_idr, id & UINT16_MAX); + if (id_ptr == NULL) { + DBG_NOTICE("Failed to find assoc_group 0x%08x\n", id); + return NULL; + } + assoc_group = talloc_get_type_abort(id_ptr, struct dcesrv_assoc_group); + + if (assoc_group->transport != transport) { + const char *at = derpc_transport_string_by_transport( + assoc_group->transport); + const char *ct = derpc_transport_string_by_transport( + transport); + + DBG_NOTICE("assoc_group 0x%08x (transport %s) " + "is not available on transport %s\n", + id, at, ct); + return NULL; + } + + /* + * Yes, this is a talloc_reference: The assoc group must be + * removed when all connections go. This should be replaced by + * adding a linked list of dcesrv_connection structs to the + * assoc group. + */ + return talloc_reference(conn, assoc_group); +} + +static int rpc_worker_assoc_group_destructor( + struct dcesrv_assoc_group *assoc_group) +{ + int ret; + + ret = idr_remove( + assoc_group->dce_ctx->assoc_groups_idr, + assoc_group->id & UINT16_MAX); + if (ret != 0) { + DBG_WARNING("Failed to remove assoc_group 0x%08x\n", + assoc_group->id); + } + + SMB_ASSERT(assoc_group->dce_ctx->assoc_groups_num > 0); + assoc_group->dce_ctx->assoc_groups_num -= 1; + return 0; +} + +/* + allocate a new association group + */ +static struct dcesrv_assoc_group *rpc_worker_assoc_group_new( + struct dcesrv_connection *conn, uint16_t worker_index) +{ + struct dcesrv_context *dce_ctx = conn->dce_ctx; + const struct dcesrv_endpoint *endpoint = conn->endpoint; + enum dcerpc_transport_t transport = dcerpc_binding_get_transport( + endpoint->ep_description); + struct dcesrv_assoc_group *assoc_group = NULL; + int id; + + assoc_group = talloc_zero(conn, struct dcesrv_assoc_group); + if (assoc_group == NULL) { + return NULL; + } + + /* + * We use 16-bit to encode the worker index, + * have 16-bits left within the worker to form a + * 32-bit association group id. + */ + id = idr_get_new_random( + dce_ctx->assoc_groups_idr, assoc_group, 1, UINT16_MAX); + if (id == -1) { + talloc_free(assoc_group); + DBG_WARNING("Out of association groups!\n"); + return NULL; + } + assoc_group->id = (((uint32_t)worker_index) << 16) | id; + assoc_group->transport = transport; + assoc_group->dce_ctx = dce_ctx; + + talloc_set_destructor(assoc_group, rpc_worker_assoc_group_destructor); + + SMB_ASSERT(dce_ctx->assoc_groups_num < UINT16_MAX); + dce_ctx->assoc_groups_num += 1; + + return assoc_group; +} + +static NTSTATUS rpc_worker_assoc_group_find( + struct dcesrv_call_state *call, + void *private_data) +{ + struct rpc_worker *w = talloc_get_type_abort( + private_data, struct rpc_worker); + uint32_t assoc_group_id = call->pkt.u.bind.assoc_group_id; + + if (assoc_group_id != 0) { + uint16_t worker_index = (assoc_group_id & 0xffff0000) >> 16; + if (worker_index != w->status.worker_index) { + DBG_DEBUG("Wrong worker id %"PRIu16", " + "expected %"PRIu32"\n", + worker_index, + w->status.worker_index); + return NT_STATUS_NOT_FOUND; + } + call->conn->assoc_group = rpc_worker_assoc_group_reference( + call->conn, assoc_group_id); + } else { + call->conn->assoc_group = rpc_worker_assoc_group_new( + call->conn, w->status.worker_index); + } + + if (call->conn->assoc_group == NULL) { + /* TODO Return correct status */ + return NT_STATUS_UNSUCCESSFUL; + } + + return NT_STATUS_OK; +} + +static struct rpc_worker *rpc_worker_new( + TALLOC_CTX *mem_ctx, + struct messaging_context *msg_ctx) +{ + struct rpc_worker *worker = NULL; + + worker = talloc_zero(mem_ctx, struct rpc_worker); + if (worker == NULL) { + return NULL; + } + + worker->rpc_host_pid = (struct server_id) { .pid = 0 }; + worker->msg_ctx = msg_ctx; + + worker->cb = (struct dcesrv_context_callbacks) { + .log.successful_authz = dcesrv_log_successful_authz, + .auth.gensec_prepare = dcesrv_auth_gensec_prepare, + .auth.become_root = become_root, + .auth.unbecome_root = unbecome_root, + .assoc_group.find = rpc_worker_assoc_group_find, + .assoc_group.private_data = worker, + }; + + worker->dce_ctx = global_dcesrv_context(); + if (worker->dce_ctx == NULL) { + goto fail; + } + dcesrv_context_set_callbacks(worker->dce_ctx, &worker->cb); + + return worker; +fail: + TALLOC_FREE(worker); + return NULL; +} + +static struct dcesrv_context *rpc_worker_dce_ctx(struct rpc_worker *w) +{ + return w->dce_ctx; +} + +struct rpc_worker_state { + struct tevent_context *ev; + struct rpc_worker *w; + struct tevent_req *new_client_req; + struct tevent_req *status_req; + struct tevent_req *finish_req; +}; + +static void rpc_worker_done(struct tevent_req *subreq); +static void rpc_worker_shutdown( + struct messaging_context *msg, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data); + +static struct tevent_req *rpc_worker_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct rpc_worker *w, + pid_t rpc_host_pid, + int server_index, + int worker_index) +{ + struct tevent_req *req = NULL; + struct rpc_worker_state *state = NULL; + NTSTATUS status; + + req = tevent_req_create(mem_ctx, &state, struct rpc_worker_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + state->w = w; + + if ((server_index < 0) || ((unsigned)server_index > UINT32_MAX)) { + DBG_ERR("Invalid server index %d\n", server_index); + tevent_req_error(req, EINVAL); + return tevent_req_post(req, ev); + } + if ((worker_index < 0) || ((unsigned)worker_index > UINT16_MAX)) { + DBG_ERR("Invalid worker index %d\n", worker_index); + tevent_req_error(req, EINVAL); + return tevent_req_post(req, ev); + } + w->rpc_host_pid = pid_to_procid(rpc_host_pid); + + w->status = (struct rpc_worker_status) { + .server_index = server_index, + .worker_index = worker_index, + }; + + /* Wait for new client messages. */ + state->new_client_req = messaging_filtered_read_send( + w, + messaging_tevent_context(w->msg_ctx), + w->msg_ctx, + rpc_worker_new_client_filter, + w); + if (tevent_req_nomem(state->new_client_req, req)) { + return tevent_req_post(req, ev); + } + + /* Wait for report your status messages. */ + state->status_req = messaging_filtered_read_send( + w, + messaging_tevent_context(w->msg_ctx), + w->msg_ctx, + rpc_worker_status_filter, + w); + if (tevent_req_nomem(state->status_req, req)) { + return tevent_req_post(req, ev); + } + + /* Wait for shutdown messages. */ + status = messaging_register( + w->msg_ctx, req, MSG_SHUTDOWN, rpc_worker_shutdown); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("messaging_register failed: %s\n", + nt_errstr(status)); + tevent_req_error(req, map_errno_from_nt_status(status)); + return tevent_req_post(req, ev); + } + + state->finish_req = wait_for_read_send(state, ev, 0, false); + if (tevent_req_nomem(state->finish_req, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(state->finish_req, rpc_worker_done, req); + + rpc_worker_report_status(w); + + return req; +} + +static void rpc_worker_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + int err = 0; + bool ok; + + ok = wait_for_read_recv(subreq, &err); + TALLOC_FREE(subreq); + if (!ok) { + tevent_req_error(req, err); + return; + } + tevent_req_done(req); +} + +static void rpc_worker_shutdown( + struct messaging_context *msg, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data) +{ + struct tevent_req *req = talloc_get_type_abort( + private_data, struct tevent_req); + tevent_req_done(req); +} + +static int rpc_worker_recv(struct tevent_req *req) +{ + return tevent_req_simple_recv_unix(req); +} + +static void sig_term_handler( + struct tevent_context *ev, + struct tevent_signal *se, + int signum, + int count, + void *siginfo, + void *private_data) +{ + exit(0); +} + +static void sig_hup_handler( + struct tevent_context *ev, + struct tevent_signal *se, + int signum, + int count, + void *siginfo, + void *private_data) +{ + change_to_root_user(); + lp_load_with_shares(get_dyn_CONFIGFILE()); +} + +static NTSTATUS register_ep_server( + struct dcesrv_context *dce_ctx, + const struct dcesrv_endpoint_server *ep_server) +{ + NTSTATUS status; + + DBG_DEBUG("Registering server %s\n", ep_server->name); + + status = dcerpc_register_ep_server(ep_server); + if (!NT_STATUS_IS_OK(status) && + !NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_COLLISION)) { + DBG_ERR("Failed to register '%s' endpoint server: %s\n", + ep_server->name, + nt_errstr(status)); + return status; + } + + status = dcesrv_init_ep_server(dce_ctx, ep_server->name); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("dcesrv_init_ep_server(%s) failed: %s\n", + ep_server->name, + nt_errstr(status)); + return status; + } + + return NT_STATUS_OK; +} + +/** + * @brief Main function for RPC server implementations + * + * This function provides all that is necessary to run a RPC server + * inside the samba-dcerpcd framework. Just pass argv and argc on to + * this function. + * + * The get_interfaces() callback provides the information that is + * passed to samba-dcerpcd via --list-interfaces, it should not do any + * real RPC server initialization work. Quickly after this function is + * called by rpc_worker_main, the process exits again. It should + * return the number of interfaces provided. + * + * get_servers() is called when the process is about to do the real + * work. So more heavy-weight initialization should happen here. It + * should return NT_STATUS_OK and the number of server implementations provided. + * + * @param[in] argc argc from main() + * @param[in] argv argv from main() + * @param[in] get_interfaces List all interfaces that this server provides + * @param[in] get_servers Provide the RPC server implementations + * @param[in] private_data Passed to the callback functions + * @return 0 It should never return except on successful process exit + */ + +int rpc_worker_main( + int argc, + const char *argv[], + const char *daemon_config_name, + int num_workers, + int idle_seconds, + size_t (*get_interfaces)( + const struct ndr_interface_table ***ifaces, + void *private_data), + NTSTATUS (*get_servers)( + struct dcesrv_context *dce_ctx, + const struct dcesrv_endpoint_server ***ep_servers, + size_t *num_ep_servers, + void *private_data), + void *private_data) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + const char *progname = getprogname(); + TALLOC_CTX *frame = NULL; + struct tevent_context *ev_ctx = NULL; + struct tevent_req *req = NULL; + struct messaging_context *msg_ctx = NULL; + struct dcesrv_context *dce_ctx = NULL; + struct tevent_signal *se = NULL; + poptContext pc; + int opt; + NTSTATUS status; + int ret; + int worker_group = -1; + int worker_index = -1; + bool log_stdout; + int list_interfaces = 0; + struct rpc_worker *worker = NULL; + const struct dcesrv_endpoint_server **ep_servers; + size_t i, num_servers; + bool ok; + + struct poptOption long_options[] = { + POPT_AUTOHELP + { + .longName = "list-interfaces", + .argInfo = POPT_ARG_NONE, + .arg = &list_interfaces, + .descrip = "List the interfaces provided", + }, + { + .longName = "worker-group", + .argInfo = POPT_ARG_INT, + .arg = &worker_group, + .descrip = "Group index in status message", + }, + { + .longName = "worker-index", + .argInfo = POPT_ARG_INT, + .arg = &worker_index, + .descrip = "Worker index in status message", + }, + POPT_COMMON_SAMBA + POPT_TABLEEND + }; + static const struct smbd_shim smbd_shim_fns = { + .become_authenticated_pipe_user = + smbd_become_authenticated_pipe_user, + .unbecome_authenticated_pipe_user = + smbd_unbecome_authenticated_pipe_user, + .become_root = smbd_become_root, + .unbecome_root = smbd_unbecome_root, + }; + + closefrom(3); + talloc_enable_null_tracking(); + frame = talloc_stackframe(); + umask(0); + smb_init_locale(); + + ok = samba_cmdline_init(frame, + SAMBA_CMDLINE_CONFIG_SERVER, + true /* require_smbconf */); + if (!ok) { + DBG_ERR("Failed to init cmdline parser!\n"); + TALLOC_FREE(frame); + exit(ENOMEM); + } + + pc = samba_popt_get_context(progname, argc, argv, long_options, 0); + if (pc == NULL) { + DBG_ERR("Failed to setup popt context!\n"); + TALLOC_FREE(frame); + exit(1); + } + + while ((opt = poptGetNextOpt(pc)) != -1) { + d_fprintf(stderr, + "\nInvalid option %s: %s\n\n", + poptBadOption(pc, 0), + poptStrerror(opt)); + poptPrintUsage(pc, stderr, 0); + TALLOC_FREE(frame); + exit(1); + }; + poptFreeContext(pc); + + if (list_interfaces != 0) { + const struct ndr_interface_table **ifaces = NULL; + size_t num_ifaces; + + num_workers = lp_parm_int( + -1, daemon_config_name, "num_workers", num_workers); + idle_seconds = lp_parm_int( + -1, daemon_config_name, "idle_seconds", idle_seconds); + + DBG_DEBUG("daemon=%s, num_workers=%d, idle_seconds=%d\n", + daemon_config_name, + num_workers, + idle_seconds); + + fprintf(stdout, "%d\n%d\n", num_workers, idle_seconds); + + num_ifaces = get_interfaces(&ifaces, private_data); + + for (i=0; i