/*
* RPC host
*
* Implements samba-dcerpcd service.
*
* 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 .
*/
/*
* This binary has two usage modes:
*
* In the normal case when invoked from smbd or winbind it is given a
* directory to scan via --libexec-rpcds and will invoke on demand any
* binaries it finds there starting with rpcd_ when a named pipe
* connection is requested.
*
* In the second mode it can be started explicitly from system startup
* scripts.
*
* When Samba is set up as an Active Directory Domain Controller the
* normal samba binary overrides and provides DCERPC services, whilst
* allowing samba-dcerpcd to provide the services that smbd used to
* provide in that set-up, such as SRVSVC.
*
* The second mode can also be useful for use outside of the Samba framework,
* for example, use with the Linux kernel SMB2 server ksmbd. In this mode
* it behaves like inetd and listens on sockets on behalf of RPC server
* implementations.
*/
#include "replace.h"
#include
#include "lib/cmdline/cmdline.h"
#include "lib/cmdline/closefrom_except.h"
#include "source3/include/includes.h"
#include "source3/include/auth.h"
#include "rpc_sock_helper.h"
#include "messages.h"
#include "lib/util_file.h"
#include "lib/util/tevent_unix.h"
#include "lib/util/tevent_ntstatus.h"
#include "lib/util/smb_strtox.h"
#include "lib/util/debug.h"
#include "lib/util/server_id.h"
#include "lib/util/util_tdb.h"
#include "lib/tdb_wrap/tdb_wrap.h"
#include "lib/async_req/async_sock.h"
#include "librpc/rpc/dcerpc_util.h"
#include "lib/tsocket/tsocket.h"
#include "libcli/named_pipe_auth/npa_tstream.h"
#include "librpc/gen_ndr/ndr_rpc_host.h"
#include "source3/param/loadparm.h"
#include "source3/lib/global_contexts.h"
#include "lib/util/strv.h"
#include "lib/util/pidfile.h"
#include "source3/rpc_client/cli_pipe.h"
#include "librpc/gen_ndr/ndr_epmapper.h"
#include "librpc/gen_ndr/ndr_epmapper_c.h"
#include "nsswitch/winbind_client.h"
#include "libcli/security/dom_sid.h"
#include "libcli/security/security_token.h"
extern bool override_logfile;
struct rpc_server;
struct rpc_work_process;
/*
* samba-dcerpcd state to keep track of rpcd_* servers.
*/
struct rpc_host {
struct messaging_context *msg_ctx;
struct rpc_server **servers;
struct tdb_wrap *epmdb;
int worker_stdin[2];
bool np_helper;
/*
* If we're started with --np-helper but nobody contacts us,
* we need to exit after a while. This will be deleted once
* the first real client connects and our self-exit mechanism
* when we don't have any worker processes left kicks in.
*/
struct tevent_timer *np_helper_shutdown;
};
/*
* Map a RPC interface to a name. Used when filling the endpoint
* mapper database
*/
struct rpc_host_iface_name {
struct ndr_syntax_id iface;
char *name;
};
/*
* rpc_host representation for listening sockets. ncacn_ip_tcp might
* listen on multiple explicit IPs, all with the same port.
*/
struct rpc_host_endpoint {
struct rpc_server *server;
struct dcerpc_binding *binding;
struct ndr_syntax_id *interfaces;
int *fds;
size_t num_fds;
};
/*
* Staging area until we sent the socket plus bind to the helper
*/
struct rpc_host_pending_client {
struct rpc_host_pending_client *prev, *next;
/*
* Pointer for the destructor to remove us from the list of
* pending clients
*/
struct rpc_server *server;
/*
* Waiter for client exit before a helper accepted the request
*/
struct tevent_req *hangup_wait;
/*
* Info to pick the worker
*/
struct ncacn_packet *bind_pkt;
/*
* This is what we send down to the worker
*/
int sock;
struct rpc_host_client *client;
};
/*
* Representation of one worker process. For each rpcd_* executable
* there will be more of than one of these.
*/
struct rpc_work_process {
pid_t pid;
/*
* !available means:
*
* Worker forked but did not send its initial status yet (not
* yet initialized)
*
* Worker died, but we did not receive SIGCHLD yet. We noticed
* it because we couldn't send it a message.
*/
bool available;
/*
* Incremented by us when sending a client, decremented by
* MSG_RPC_HOST_WORKER_STATUS sent by workers whenever a
* client exits.
*/
uint32_t num_clients;
/*
* Send SHUTDOWN to an idle child after a while
*/
struct tevent_timer *exit_timer;
};
/*
* State for a set of running instances of an rpcd_* server executable
*/
struct rpc_server {
struct rpc_host *host;
/*
* Index into the rpc_host_state->servers array
*/
uint32_t server_index;
const char *rpc_server_exe;
struct rpc_host_endpoint **endpoints;
struct rpc_host_iface_name *iface_names;
size_t max_workers;
size_t idle_seconds;
/*
* "workers" can be larger than "max_workers": Internal
* connections require an idle worker to avoid deadlocks
* between RPC servers: netlogon requires samr, everybody
* requires winreg. And if a deep call in netlogon asks for a
* samr connection, this must never end up in the same
* process. named_pipe_auth_req_info7->need_idle_server is set
* in those cases.
*/
struct rpc_work_process *workers;
struct rpc_host_pending_client *pending_clients;
};
struct rpc_server_get_endpoints_state {
char **argl;
char *ncalrpc_endpoint;
enum dcerpc_transport_t only_transport;
struct rpc_host_iface_name *iface_names;
struct rpc_host_endpoint **endpoints;
unsigned long num_workers;
unsigned long idle_seconds;
};
static void rpc_server_get_endpoints_done(struct tevent_req *subreq);
/**
* @brief Query interfaces from an rpcd helper
*
* Spawn a rpcd helper, ask it for the interfaces it serves via
* --list-interfaces, parse the output
*
* @param[in] mem_ctx Memory context for the tevent_req
* @param[in] ev Event context to run this on
* @param[in] rpc_server_exe Binary to ask with --list-interfaces
* @param[in] only_transport Filter out anything but this
* @return The tevent_req representing this process
*/
static struct tevent_req *rpc_server_get_endpoints_send(
TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
const char *rpc_server_exe,
enum dcerpc_transport_t only_transport)
{
struct tevent_req *req = NULL, *subreq = NULL;
struct rpc_server_get_endpoints_state *state = NULL;
const char *progname = NULL;
req = tevent_req_create(
mem_ctx, &state, struct rpc_server_get_endpoints_state);
if (req == NULL) {
return NULL;
}
state->only_transport = only_transport;
progname = strrchr(rpc_server_exe, '/');
if (progname != NULL) {
progname += 1;
} else {
progname = rpc_server_exe;
}
state->ncalrpc_endpoint = talloc_strdup(state, progname);
if (tevent_req_nomem(state->ncalrpc_endpoint, req)) {
return tevent_req_post(req, ev);
}
state->argl = talloc_array(state, char *, 4);
if (tevent_req_nomem(state->argl, req)) {
return tevent_req_post(req, ev);
}
state->argl = str_list_make_empty(state);
str_list_add_printf(&state->argl, "%s", rpc_server_exe);
str_list_add_printf(&state->argl, "--list-interfaces");
str_list_add_printf(
&state->argl, "--configfile=%s", get_dyn_CONFIGFILE());
if (tevent_req_nomem(state->argl, req)) {
return tevent_req_post(req, ev);
}
subreq = file_ploadv_send(state, ev, state->argl, 65536);
if (tevent_req_nomem(subreq, req)) {
return tevent_req_post(req, ev);
}
tevent_req_set_callback(subreq, rpc_server_get_endpoints_done, req);
return req;
}
/*
* Parse a line of format
*
* 338cd001-2244-31f1-aaaa-900038001003/0x00000001 winreg
*
* and add it to the "piface_names" array.
*/
static struct rpc_host_iface_name *rpc_exe_parse_iface_line(
TALLOC_CTX *mem_ctx,
struct rpc_host_iface_name **piface_names,
const char *line)
{
struct rpc_host_iface_name *iface_names = *piface_names;
struct rpc_host_iface_name *tmp = NULL, *result = NULL;
size_t i, num_ifaces = talloc_array_length(iface_names);
struct ndr_syntax_id iface;
char *name = NULL;
bool ok;
ok = ndr_syntax_id_from_string(line, &iface);
if (!ok) {
DBG_WARNING("ndr_syntax_id_from_string() failed for: [%s]\n",
line);
return NULL;
}
name = strchr(line, ' ');
if (name == NULL) {
return NULL;
}
name += 1;
for (i=0; iiface, &iface)) {
return result;
}
}
if (num_ifaces + 1 < num_ifaces) {
return NULL;
}
name = talloc_strdup(mem_ctx, name);
if (name == NULL) {
return NULL;
}
tmp = talloc_realloc(
mem_ctx,
iface_names,
struct rpc_host_iface_name,
num_ifaces + 1);
if (tmp == NULL) {
TALLOC_FREE(name);
return NULL;
}
iface_names = tmp;
result = &iface_names[num_ifaces];
*result = (struct rpc_host_iface_name) {
.iface = iface,
.name = talloc_move(iface_names, &name),
};
*piface_names = iface_names;
return result;
}
static struct rpc_host_iface_name *rpc_host_iface_names_find(
struct rpc_host_iface_name *iface_names,
const struct ndr_syntax_id *iface)
{
size_t i, num_iface_names = talloc_array_length(iface_names);
for (i=0; iiface)) {
return iface_name;
}
}
return NULL;
}
static bool dcerpc_binding_same_endpoint(
const struct dcerpc_binding *b1, const struct dcerpc_binding *b2)
{
enum dcerpc_transport_t t1 = dcerpc_binding_get_transport(b1);
enum dcerpc_transport_t t2 = dcerpc_binding_get_transport(b2);
const char *e1 = NULL, *e2 = NULL;
int cmp;
if (t1 != t2) {
return false;
}
e1 = dcerpc_binding_get_string_option(b1, "endpoint");
e2 = dcerpc_binding_get_string_option(b2, "endpoint");
if ((e1 == NULL) && (e2 == NULL)) {
return true;
}
if ((e1 == NULL) || (e2 == NULL)) {
return false;
}
cmp = strcmp(e1, e2);
return (cmp == 0);
}
/**
* @brief Filter whether we want to serve an endpoint
*
* samba-dcerpcd might want to serve all endpoints a rpcd reported to
* us via --list-interfaces.
*
* In member mode, we only serve named pipes. Indicated by NCACN_NP
* passed in via "only_transport".
*
* @param[in] binding Which binding is in question?
* @param[in] only_transport Exclusive transport to serve
* @return Do we want to serve "binding" from samba-dcerpcd?
*/
static bool rpc_host_serve_endpoint(
struct dcerpc_binding *binding,
enum dcerpc_transport_t only_transport)
{
enum dcerpc_transport_t transport =
dcerpc_binding_get_transport(binding);
if (only_transport == NCA_UNKNOWN) {
/* no filter around */
return true;
}
if (transport != only_transport) {
/* filter out */
return false;
}
return true;
}
static struct rpc_host_endpoint *rpc_host_endpoint_find(
struct rpc_server_get_endpoints_state *state,
const char *binding_string)
{
size_t i, num_endpoints = talloc_array_length(state->endpoints);
struct rpc_host_endpoint **tmp = NULL, *ep = NULL;
enum dcerpc_transport_t transport;
NTSTATUS status;
bool serve_this;
ep = talloc_zero(state, struct rpc_host_endpoint);
if (ep == NULL) {
goto fail;
}
status = dcerpc_parse_binding(ep, binding_string, &ep->binding);
if (!NT_STATUS_IS_OK(status)) {
DBG_DEBUG("dcerpc_parse_binding(%s) failed: %s\n",
binding_string,
nt_errstr(status));
goto fail;
}
serve_this = rpc_host_serve_endpoint(
ep->binding, state->only_transport);
if (!serve_this) {
goto fail;
}
transport = dcerpc_binding_get_transport(ep->binding);
if (transport == NCALRPC) {
const char *ncalrpc_sock = dcerpc_binding_get_string_option(
ep->binding, "endpoint");
if (ncalrpc_sock == NULL) {
/*
* generic ncalrpc:, set program-specific
* socket name. epmapper will redirect clients
* properly.
*/
status = dcerpc_binding_set_string_option(
ep->binding,
"endpoint",
state->ncalrpc_endpoint);
if (!NT_STATUS_IS_OK(status)) {
DBG_DEBUG("dcerpc_binding_set_string_option "
"failed: %s\n",
nt_errstr(status));
goto fail;
}
}
}
for (i=0; ibinding, state->endpoints[i]->binding);
if (ok) {
TALLOC_FREE(ep);
return state->endpoints[i];
}
}
if (num_endpoints + 1 < num_endpoints) {
goto fail;
}
tmp = talloc_realloc(
state,
state->endpoints,
struct rpc_host_endpoint *,
num_endpoints + 1);
if (tmp == NULL) {
goto fail;
}
state->endpoints = tmp;
state->endpoints[num_endpoints] = talloc_move(state->endpoints, &ep);
return state->endpoints[num_endpoints];
fail:
TALLOC_FREE(ep);
return NULL;
}
static bool ndr_interfaces_add_unique(
TALLOC_CTX *mem_ctx,
struct ndr_syntax_id **pifaces,
const struct ndr_syntax_id *iface)
{
struct ndr_syntax_id *ifaces = *pifaces;
size_t i, num_ifaces = talloc_array_length(ifaces);
for (i=0; inum_workers = smb_strtoul(
lines[0], NULL, 10, &ret, SMB_STR_FULL_STR_CONV);
if (ret != 0) {
DBG_DEBUG("Could not parse num_workers(%s): %s\n",
lines[0],
strerror(ret));
tevent_req_error(req, ret);
return;
}
state->idle_seconds = smb_strtoul(
lines[1], NULL, 10, &ret, SMB_STR_FULL_STR_CONV);
if (ret != 0) {
DBG_DEBUG("Could not parse idle_seconds (%s): %s\n",
lines[1],
strerror(ret));
tevent_req_error(req, ret);
return;
}
DBG_DEBUG("num_workers=%lu, idle_seconds=%lu for %s\n",
state->num_workers,
state->idle_seconds,
state->argl[0]);
for (i=2; iiface_names, line);
if (iface == NULL) {
DBG_WARNING(
"rpc_exe_parse_iface_line failed "
"for: [%s] from %s\n",
line,
state->argl[0]);
tevent_req_oom(req);
return;
}
continue;
}
if (iface == NULL) {
DBG_DEBUG("Interface GUID line missing\n");
tevent_req_error(req, EINVAL);
return;
}
endpoint = rpc_host_endpoint_find(state, line+1);
if (endpoint == NULL) {
DBG_DEBUG("rpc_host_endpoint_find for %s failed\n",
line+1);
continue;
}
ok = ndr_interfaces_add_unique(
endpoint,
&endpoint->interfaces,
&iface->iface);
if (!ok) {
DBG_DEBUG("ndr_interfaces_add_unique failed\n");
tevent_req_oom(req);
return;
}
}
tevent_req_done(req);
}
/**
* @brief Receive output from --list-interfaces
*
* @param[in] req The async req that just finished
* @param[in] mem_ctx Where to put the output on
* @param[out] endpoints The endpoints to be listened on
* @param[out] iface_names Annotation for epm_Lookup's epm_entry_t
* @return 0/errno
*/
static int rpc_server_get_endpoints_recv(
struct tevent_req *req,
TALLOC_CTX *mem_ctx,
struct rpc_host_endpoint ***endpoints,
struct rpc_host_iface_name **iface_names,
size_t *num_workers,
size_t *idle_seconds)
{
struct rpc_server_get_endpoints_state *state = tevent_req_data(
req, struct rpc_server_get_endpoints_state);
int err;
if (tevent_req_is_unix_error(req, &err)) {
tevent_req_received(req);
return err;
}
*endpoints = talloc_move(mem_ctx, &state->endpoints);
*iface_names = talloc_move(mem_ctx, &state->iface_names);
*num_workers = state->num_workers;
*idle_seconds = state->idle_seconds;
tevent_req_received(req);
return 0;
}
/*
* For NCACN_NP we get the named pipe auth info from smbd, if a client
* comes in via TCP or NCALPRC we need to invent it ourselves with
* anonymous session info.
*/
static NTSTATUS rpc_host_generate_npa_info7_from_sock(
TALLOC_CTX *mem_ctx,
enum dcerpc_transport_t transport,
int sock,
const struct samba_sockaddr *peer_addr,
struct named_pipe_auth_req_info7 **pinfo7)
{
struct named_pipe_auth_req_info7 *info7 = NULL;
struct samba_sockaddr local_addr = {
.sa_socklen = sizeof(struct sockaddr_storage),
};
struct tsocket_address *taddr = NULL;
char *remote_client_name = NULL;
char *remote_client_addr = NULL;
char *local_server_name = NULL;
char *local_server_addr = NULL;
char *(*tsocket_address_to_name_fn)(
const struct tsocket_address *addr,
TALLOC_CTX *mem_ctx) = NULL;
NTSTATUS status = NT_STATUS_NO_MEMORY;
int ret;
/*
* For NCACN_NP we get the npa info from smbd
*/
SMB_ASSERT((transport == NCACN_IP_TCP) || (transport == NCALRPC));
tsocket_address_to_name_fn = (transport == NCACN_IP_TCP) ?
tsocket_address_inet_addr_string : tsocket_address_unix_path;
info7 = talloc_zero(mem_ctx, struct named_pipe_auth_req_info7);
if (info7 == NULL) {
goto fail;
}
info7->session_info =
talloc_zero(info7, struct auth_session_info_transport);
if (info7->session_info == NULL) {
goto fail;
}
status = make_session_info_anonymous(
info7->session_info,
&info7->session_info->session_info);
if (!NT_STATUS_IS_OK(status)) {
DBG_DEBUG("make_session_info_anonymous failed: %s\n",
nt_errstr(status));
goto fail;
}
ret = tsocket_address_bsd_from_samba_sockaddr(info7,
peer_addr,
&taddr);
if (ret == -1) {
status = map_nt_error_from_unix(errno);
DBG_DEBUG("tsocket_address_bsd_from_samba_sockaddr failed: "
"%s\n",
strerror(errno));
goto fail;
}
remote_client_addr = tsocket_address_to_name_fn(taddr, info7);
if (remote_client_addr == NULL) {
DBG_DEBUG("tsocket_address_to_name_fn failed\n");
goto nomem;
}
TALLOC_FREE(taddr);
remote_client_name = talloc_strdup(info7, remote_client_addr);
if (remote_client_name == NULL) {
DBG_DEBUG("talloc_strdup failed\n");
goto nomem;
}
if (transport == NCACN_IP_TCP) {
bool ok = samba_sockaddr_get_port(peer_addr,
&info7->remote_client_port);
if (!ok) {
DBG_DEBUG("samba_sockaddr_get_port failed\n");
status = NT_STATUS_INVALID_PARAMETER;
goto fail;
}
}
ret = getsockname(sock, &local_addr.u.sa, &local_addr.sa_socklen);
if (ret == -1) {
status = map_nt_error_from_unix(errno);
DBG_DEBUG("getsockname failed: %s\n", strerror(errno));
goto fail;
}
ret = tsocket_address_bsd_from_samba_sockaddr(info7,
&local_addr,
&taddr);
if (ret == -1) {
status = map_nt_error_from_unix(errno);
DBG_DEBUG("tsocket_address_bsd_from_samba_sockaddr failed: "
"%s\n",
strerror(errno));
goto fail;
}
local_server_addr = tsocket_address_to_name_fn(taddr, info7);
if (local_server_addr == NULL) {
DBG_DEBUG("tsocket_address_to_name_fn failed\n");
goto nomem;
}
TALLOC_FREE(taddr);
local_server_name = talloc_strdup(info7, local_server_addr);
if (local_server_name == NULL) {
DBG_DEBUG("talloc_strdup failed\n");
goto nomem;
}
if (transport == NCACN_IP_TCP) {
bool ok = samba_sockaddr_get_port(&local_addr,
&info7->local_server_port);
if (!ok) {
DBG_DEBUG("samba_sockaddr_get_port failed\n");
status = NT_STATUS_INVALID_PARAMETER;
goto fail;
}
}
if (transport == NCALRPC) {
uid_t uid;
gid_t gid;
ret = getpeereid(sock, &uid, &gid);
if (ret < 0) {
status = map_nt_error_from_unix(errno);
DBG_DEBUG("getpeereid failed: %s\n", strerror(errno));
goto fail;
}
if (uid == sec_initial_uid()) {
/*
* Indicate "root" to gensec
*/
TALLOC_FREE(remote_client_addr);
TALLOC_FREE(remote_client_name);
ret = tsocket_address_unix_from_path(
info7,
AS_SYSTEM_MAGIC_PATH_TOKEN,
&taddr);
if (ret == -1) {
DBG_DEBUG("tsocket_address_unix_from_path "
"failed\n");
goto nomem;
}
remote_client_addr =
tsocket_address_unix_path(taddr, info7);
if (remote_client_addr == NULL) {
DBG_DEBUG("tsocket_address_unix_path "
"failed\n");
goto nomem;
}
remote_client_name =
talloc_strdup(info7, remote_client_addr);
if (remote_client_name == NULL) {
DBG_DEBUG("talloc_strdup failed\n");
goto nomem;
}
}
}
info7->remote_client_addr = remote_client_addr;
info7->remote_client_name = remote_client_name;
info7->local_server_addr = local_server_addr;
info7->local_server_name = local_server_name;
*pinfo7 = info7;
return NT_STATUS_OK;
nomem:
status = NT_STATUS_NO_MEMORY;
fail:
TALLOC_FREE(info7);
return status;
}
struct rpc_host_bind_read_state {
struct tevent_context *ev;
int sock;
struct tstream_context *plain;
struct tstream_context *npa_stream;
struct ncacn_packet *pkt;
struct rpc_host_client *client;
};
static void rpc_host_bind_read_cleanup(
struct tevent_req *req, enum tevent_req_state req_state);
static void rpc_host_bind_read_got_npa(struct tevent_req *subreq);
static void rpc_host_bind_read_got_bind(struct tevent_req *subreq);
/*
* Wait for a bind packet from a client.
*/
static struct tevent_req *rpc_host_bind_read_send(
TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
enum dcerpc_transport_t transport,
int *psock,
const struct samba_sockaddr *peer_addr)
{
struct tevent_req *req = NULL, *subreq = NULL;
struct rpc_host_bind_read_state *state = NULL;
int rc, sock_dup;
NTSTATUS status;
req = tevent_req_create(
mem_ctx, &state, struct rpc_host_bind_read_state);
if (req == NULL) {
return NULL;
}
state->ev = ev;
state->sock = *psock;
*psock = -1;
tevent_req_set_cleanup_fn(req, rpc_host_bind_read_cleanup);
state->client = talloc_zero(state, struct rpc_host_client);
if (tevent_req_nomem(state->client, req)) {
return tevent_req_post(req, ev);
}
/*
* Dup the socket to read the first RPC packet:
* tstream_bsd_existing_socket() takes ownership with
* autoclose, but we need to send "sock" down to our worker
* process later.
*/
sock_dup = dup(state->sock);
if (sock_dup == -1) {
tevent_req_error(req, errno);
return tevent_req_post(req, ev);
}
rc = tstream_bsd_existing_socket(state, sock_dup, &state->plain);
if (rc == -1) {
DBG_DEBUG("tstream_bsd_existing_socket failed: %s\n",
strerror(errno));
tevent_req_error(req, errno);
close(sock_dup);
return tevent_req_post(req, ev);
}
if (transport == NCACN_NP) {
subreq = tstream_npa_accept_existing_send(
state,
ev,
state->plain,
FILE_TYPE_MESSAGE_MODE_PIPE,
0xff | 0x0400 | 0x0100,
4096);
if (tevent_req_nomem(subreq, req)) {
return tevent_req_post(req, ev);
}
tevent_req_set_callback(
subreq, rpc_host_bind_read_got_npa, req);
return req;
}
status = rpc_host_generate_npa_info7_from_sock(
state->client,
transport,
state->sock,
peer_addr,
&state->client->npa_info7);
if (!NT_STATUS_IS_OK(status)) {
tevent_req_oom(req);
return tevent_req_post(req, ev);
}
subreq = dcerpc_read_ncacn_packet_send(state, ev, state->plain);
if (tevent_req_nomem(subreq, req)) {
return tevent_req_post(req, ev);
}
tevent_req_set_callback(subreq, rpc_host_bind_read_got_bind, req);
return req;
}
static void rpc_host_bind_read_cleanup(
struct tevent_req *req, enum tevent_req_state req_state)
{
struct rpc_host_bind_read_state *state = tevent_req_data(
req, struct rpc_host_bind_read_state);
if ((req_state == TEVENT_REQ_RECEIVED) && (state->sock != -1)) {
close(state->sock);
state->sock = -1;
}
}
static void rpc_host_bind_read_got_npa(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(
subreq, struct tevent_req);
struct rpc_host_bind_read_state *state = tevent_req_data(
req, struct rpc_host_bind_read_state);
struct named_pipe_auth_req_info7 *info7 = NULL;
int ret, err;
ret = tstream_npa_accept_existing_recv(subreq,
&err,
state,
&state->npa_stream,
&info7,
NULL, /* transport */
NULL, /* remote_client_addr */
NULL, /* remote_client_name */
NULL, /* local_server_addr */
NULL, /* local_server_name */
NULL); /* session_info */
if (ret == -1) {
tevent_req_error(req, err);
return;
}
state->client->npa_info7 = talloc_move(state->client, &info7);
subreq = dcerpc_read_ncacn_packet_send(
state, state->ev, state->npa_stream);
if (tevent_req_nomem(subreq, req)) {
return;
}
tevent_req_set_callback(subreq, rpc_host_bind_read_got_bind, req);
}
static void rpc_host_bind_read_got_bind(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(
subreq, struct tevent_req);
struct rpc_host_bind_read_state *state = tevent_req_data(
req, struct rpc_host_bind_read_state);
struct ncacn_packet *pkt = NULL;
NTSTATUS status;
status = dcerpc_read_ncacn_packet_recv(
subreq,
state->client,
&pkt,
&state->client->bind_packet);
TALLOC_FREE(subreq);
if (!NT_STATUS_IS_OK(status)) {
DBG_DEBUG("dcerpc_read_ncacn_packet_recv failed: %s\n",
nt_errstr(status));
tevent_req_error(req, EINVAL); /* TODO */
return;
}
state->pkt = talloc_move(state, &pkt);
tevent_req_done(req);
}
static int rpc_host_bind_read_recv(
struct tevent_req *req,
TALLOC_CTX *mem_ctx,
int *sock,
struct rpc_host_client **client,
struct ncacn_packet **bind_pkt)
{
struct rpc_host_bind_read_state *state = tevent_req_data(
req, struct rpc_host_bind_read_state);
int err;
if (tevent_req_is_unix_error(req, &err)) {
tevent_req_received(req);
return err;
}
*sock = state->sock;
state->sock = -1;
*client = talloc_move(mem_ctx, &state->client);
*bind_pkt = talloc_move(mem_ctx, &state->pkt);
tevent_req_received(req);
return 0;
}
/*
* Start the given rpcd_* binary.
*/
static int rpc_host_exec_worker(struct rpc_server *server, size_t idx)
{
struct rpc_work_process *worker = &server->workers[idx];
char **argv = NULL;
int ret = ENOMEM;
argv = str_list_make_empty(server);
str_list_add_printf(
&argv, "%s", server->rpc_server_exe);
str_list_add_printf(
&argv, "--configfile=%s", get_dyn_CONFIGFILE());
str_list_add_printf(
&argv, "--worker-group=%"PRIu32, server->server_index);
str_list_add_printf(
&argv, "--worker-index=%zu", idx);
str_list_add_printf(
&argv, "--debuglevel=%d", debuglevel_get_class(DBGC_RPC_SRV));
if (!is_default_dyn_LOGFILEBASE()) {
str_list_add_printf(
&argv, "--log-basename=%s", get_dyn_LOGFILEBASE());
}
if (argv == NULL) {
ret = ENOMEM;
goto fail;
}
worker->pid = fork();
if (worker->pid == -1) {
ret = errno;
goto fail;
}
if (worker->pid == 0) {
/* Child. */
close(server->host->worker_stdin[1]);
ret = dup2(server->host->worker_stdin[0], 0);
if (ret != 0) {
exit(1);
}
execv(argv[0], argv);
_exit(1);
}
DBG_DEBUG("Creating worker %s for index %zu: pid=%d\n",
server->rpc_server_exe,
idx,
(int)worker->pid);
ret = 0;
fail:
TALLOC_FREE(argv);
return ret;
}
/*
* Find an rpcd_* worker for an external client, respect server->max_workers
*/
static struct rpc_work_process *rpc_host_find_worker(struct rpc_server *server)
{
struct rpc_work_process *worker = NULL;
size_t i;
size_t empty_slot = SIZE_MAX;
uint32_t min_clients = UINT32_MAX;
size_t min_worker = server->max_workers;
for (i=0; imax_workers; i++) {
worker = &server->workers[i];
if (worker->pid == -1) {
empty_slot = MIN(empty_slot, i);
continue;
}
if (!worker->available) {
continue;
}
if (worker->num_clients < min_clients) {
min_clients = worker->num_clients;
min_worker = i;
}
}
if (min_clients == 0) {
return &server->workers[min_worker];
}
if (empty_slot < SIZE_MAX) {
int ret = rpc_host_exec_worker(server, empty_slot);
if (ret != 0) {
DBG_WARNING("Could not fork worker: %s\n",
strerror(ret));
}
return NULL;
}
if (min_worker < server->max_workers) {
return &server->workers[min_worker];
}
return NULL;
}
/*
* Find an rpcd_* worker for an internal connection, possibly go beyond
* server->max_workers
*/
static struct rpc_work_process *rpc_host_find_idle_worker(
struct rpc_server *server)
{
struct rpc_work_process *worker = NULL, *tmp = NULL;
size_t i, num_workers = talloc_array_length(server->workers);
size_t empty_slot = SIZE_MAX;
int ret;
for (i=server->max_workers; iworkers[i];
if (worker->pid == -1) {
empty_slot = MIN(empty_slot, i);
continue;
}
if (!worker->available) {
continue;
}
if (worker->num_clients == 0) {
return &server->workers[i];
}
}
if (empty_slot < SIZE_MAX) {
ret = rpc_host_exec_worker(server, empty_slot);
if (ret != 0) {
DBG_WARNING("Could not fork worker: %s\n",
strerror(ret));
}
return NULL;
}
/*
* All workers are busy. We need to expand the number of
* workers because we were asked for an idle worker.
*/
if (num_workers+1 < num_workers) {
return NULL;
}
tmp = talloc_realloc(
server,
server->workers,
struct rpc_work_process,
num_workers+1);
if (tmp == NULL) {
return NULL;
}
server->workers = tmp;
server->workers[num_workers] = (struct rpc_work_process) { .pid=-1, };
ret = rpc_host_exec_worker(server, num_workers);
if (ret != 0) {
DBG_WARNING("Could not exec worker: %s\n", strerror(ret));
}
return NULL;
}
/*
* Find an rpcd_* process to talk to. Start a new one if necessary.
*/
static void rpc_host_distribute_clients(struct rpc_server *server)
{
struct rpc_work_process *worker = NULL;
struct rpc_host_pending_client *pending_client = NULL;
uint32_t assoc_group_id;
DATA_BLOB blob;
struct iovec iov;
enum ndr_err_code ndr_err;
NTSTATUS status;
again:
pending_client = server->pending_clients;
if (pending_client == NULL) {
DBG_DEBUG("No pending clients\n");
return;
}
assoc_group_id = pending_client->bind_pkt->u.bind.assoc_group_id;
if (assoc_group_id != 0) {
size_t num_workers = talloc_array_length(server->workers);
uint8_t worker_index = assoc_group_id >> 24;
if (worker_index >= num_workers) {
DBG_DEBUG("Invalid assoc group id %"PRIu32"\n",
assoc_group_id);
goto done;
}
worker = &server->workers[worker_index];
if ((worker->pid == -1) || !worker->available) {
DBG_DEBUG("Requested worker index %"PRIu8": "
"pid=%d, available=%d",
worker_index,
(int)worker->pid,
(int)worker->available);
/*
* Pick a random one for a proper bind nack
*/
worker = rpc_host_find_worker(server);
}
} else {
struct auth_session_info_transport *session_info =
pending_client->client->npa_info7->session_info;
uint32_t flags = 0;
bool found;
found = security_token_find_npa_flags(
session_info->session_info->security_token,
&flags);
/* fresh assoc group requested */
if (found & (flags & SAMBA_NPA_FLAGS_NEED_IDLE)) {
worker = rpc_host_find_idle_worker(server);
} else {
worker = rpc_host_find_worker(server);
}
}
if (worker == NULL) {
DBG_DEBUG("No worker found\n");
return;
}
DLIST_REMOVE(server->pending_clients, pending_client);
ndr_err = ndr_push_struct_blob(
&blob,
pending_client,
pending_client->client,
(ndr_push_flags_fn_t)ndr_push_rpc_host_client);
if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
DBG_WARNING("ndr_push_rpc_host_client failed: %s\n",
ndr_errstr(ndr_err));
goto done;
}
DBG_INFO("Sending new client %s to %d with %"PRIu32" clients\n",
server->rpc_server_exe,
worker->pid,
worker->num_clients);
iov = (struct iovec) {
.iov_base = blob.data, .iov_len = blob.length,
};
status = messaging_send_iov(
server->host->msg_ctx,
pid_to_procid(worker->pid),
MSG_RPC_HOST_NEW_CLIENT,
&iov,
1,
&pending_client->sock,
1);
if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
DBG_DEBUG("worker %d died, sigchld not yet received?\n",
worker->pid);
DLIST_ADD(server->pending_clients, pending_client);
worker->available = false;
goto again;
}
if (!NT_STATUS_IS_OK(status)) {
DBG_DEBUG("messaging_send_iov failed: %s\n",
nt_errstr(status));
goto done;
}
worker->num_clients += 1;
TALLOC_FREE(worker->exit_timer);
TALLOC_FREE(server->host->np_helper_shutdown);
done:
TALLOC_FREE(pending_client);
}
static int rpc_host_pending_client_destructor(
struct rpc_host_pending_client *p)
{
TALLOC_FREE(p->hangup_wait);
if (p->sock != -1) {
close(p->sock);
p->sock = -1;
}
DLIST_REMOVE(p->server->pending_clients, p);
return 0;
}
/*
* Exception condition handler before rpcd_* worker
* is handling the socket. Either the client exited or
* sent unexpected data after the initial bind.
*/
static void rpc_host_client_exited(struct tevent_req *subreq)
{
struct rpc_host_pending_client *pending = tevent_req_callback_data(
subreq, struct rpc_host_pending_client);
bool ok;
int err;
ok = wait_for_read_recv(subreq, &err);
TALLOC_FREE(subreq);
pending->hangup_wait = NULL;
if (ok) {
DBG_DEBUG("client on sock %d sent data\n", pending->sock);
} else {
DBG_DEBUG("client exited with %s\n", strerror(err));
}
TALLOC_FREE(pending);
}
struct rpc_iface_binding_map {
struct ndr_syntax_id iface;
char *bindings;
};
static bool rpc_iface_binding_map_add_endpoint(
TALLOC_CTX *mem_ctx,
const struct rpc_host_endpoint *ep,
struct rpc_host_iface_name *iface_names,
struct rpc_iface_binding_map **pmaps)
{
const struct ndr_syntax_id mgmt_iface = {
{0xafa8bd80,
0x7d8a,
0x11c9,
{0xbe,0xf4},
{0x08,0x00,0x2b,0x10,0x29,0x89}
},
1.0};
struct rpc_iface_binding_map *maps = *pmaps;
size_t i, num_ifaces = talloc_array_length(ep->interfaces);
char *binding_string = NULL;
bool ok = false;
binding_string = dcerpc_binding_string(mem_ctx, ep->binding);
if (binding_string == NULL) {
return false;
}
for (i=0; iinterfaces[i];
size_t j, num_maps = talloc_array_length(maps);
struct rpc_iface_binding_map *map = NULL;
char *p = NULL;
if (ndr_syntax_id_equal(iface, &mgmt_iface)) {
/*
* mgmt is offered everywhere, don't put it
* into epmdb.tdb.
*/
continue;
}
for (j=0; jiface, iface)) {
break;
}
}
if (j == num_maps) {
struct rpc_iface_binding_map *tmp = NULL;
struct rpc_host_iface_name *iface_name = NULL;
iface_name = rpc_host_iface_names_find(
iface_names, iface);
if (iface_name == NULL) {
goto fail;
}
tmp = talloc_realloc(
mem_ctx,
maps,
struct rpc_iface_binding_map,
num_maps+1);
if (tmp == NULL) {
goto fail;
}
maps = tmp;
map = &maps[num_maps];
*map = (struct rpc_iface_binding_map) {
.iface = *iface,
.bindings = talloc_move(
maps, &iface_name->name),
};
}
p = strv_find(map->bindings, binding_string);
if (p == NULL) {
int ret = strv_add(
maps, &map->bindings, binding_string);
if (ret != 0) {
goto fail;
}
}
}
ok = true;
fail:
*pmaps = maps;
return ok;
}
static bool rpc_iface_binding_map_add_endpoints(
TALLOC_CTX *mem_ctx,
struct rpc_host_endpoint **endpoints,
struct rpc_host_iface_name *iface_names,
struct rpc_iface_binding_map **pbinding_maps)
{
size_t i, num_endpoints = talloc_array_length(endpoints);
for (i=0; iiface, &buf);
TDB_DATA value = {
.dptr = (uint8_t *)map->bindings,
.dsize = talloc_array_length(map->bindings),
};
int rc;
rc = tdb_store(
db->tdb, string_term_tdb_data(keystr), value, 0);
if (rc == -1) {
DBG_DEBUG("tdb_store() failed: %s\n",
tdb_errorstr(db->tdb));
goto fail;
}
}
ret = true;
fail:
TALLOC_FREE(maps);
return ret;
}
struct rpc_server_setup_state {
struct rpc_server *server;
};
static void rpc_server_setup_got_endpoints(struct tevent_req *subreq);
/*
* Async initialize state for all possible rpcd_* servers.
* Note this does not start them.
*/
static struct tevent_req *rpc_server_setup_send(
TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct rpc_host *host,
const char *rpc_server_exe)
{
struct tevent_req *req = NULL, *subreq = NULL;
struct rpc_server_setup_state *state = NULL;
struct rpc_server *server = NULL;
req = tevent_req_create(
mem_ctx, &state, struct rpc_server_setup_state);
if (req == NULL) {
return NULL;
}
state->server = talloc_zero(state, struct rpc_server);
if (tevent_req_nomem(state->server, req)) {
return tevent_req_post(req, ev);
}
server = state->server;
*server = (struct rpc_server) {
.host = host,
.server_index = UINT32_MAX,
.rpc_server_exe = talloc_strdup(server, rpc_server_exe),
};
if (tevent_req_nomem(server->rpc_server_exe, req)) {
return tevent_req_post(req, ev);
}
subreq = rpc_server_get_endpoints_send(
state,
ev,
rpc_server_exe,
host->np_helper ? NCACN_NP : NCA_UNKNOWN);
if (tevent_req_nomem(subreq, req)) {
return tevent_req_post(req, ev);
}
tevent_req_set_callback(subreq, rpc_server_setup_got_endpoints, req);
return req;
}
static void rpc_server_setup_got_endpoints(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(
subreq, struct tevent_req);
struct rpc_server_setup_state *state = tevent_req_data(
req, struct rpc_server_setup_state);
struct rpc_server *server = state->server;
int ret;
size_t i, num_endpoints;
bool ok;
ret = rpc_server_get_endpoints_recv(
subreq,
server,
&server->endpoints,
&server->iface_names,
&server->max_workers,
&server->idle_seconds);
TALLOC_FREE(subreq);
if (ret != 0) {
tevent_req_nterror(req, map_nt_error_from_unix(ret));
return;
}
server->workers = talloc_array(
server, struct rpc_work_process, server->max_workers);
if (tevent_req_nomem(server->workers, req)) {
return;
}
for (i=0; imax_workers; i++) {
/* mark as not yet created */
server->workers[i] = (struct rpc_work_process) { .pid=-1, };
}
num_endpoints = talloc_array_length(server->endpoints);
for (i=0; iendpoints[i];
NTSTATUS status;
size_t j;
e->server = server;
status = dcesrv_create_binding_sockets(
e->binding, e, &e->num_fds, &e->fds);
if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED)) {
continue;
}
if (tevent_req_nterror(req, status)) {
DBG_DEBUG("dcesrv_create_binding_sockets failed: %s\n",
nt_errstr(status));
return;
}
for (j=0; jnum_fds; j++) {
ret = listen(e->fds[j], 256);
if (ret == -1) {
tevent_req_nterror(
req, map_nt_error_from_unix(errno));
return;
}
}
}
ok = rpc_host_fill_epm_db(
server->host->epmdb, server->endpoints, server->iface_names);
if (!ok) {
DBG_DEBUG("rpc_host_fill_epm_db failed\n");
}
tevent_req_done(req);
}
static NTSTATUS rpc_server_setup_recv(
struct tevent_req *req, TALLOC_CTX *mem_ctx, struct rpc_server **server)
{
struct rpc_server_setup_state *state = tevent_req_data(
req, struct rpc_server_setup_state);
NTSTATUS status;
if (tevent_req_is_nterror(req, &status)) {
tevent_req_received(req);
return status;
}
*server = talloc_move(mem_ctx, &state->server);
tevent_req_received(req);
return NT_STATUS_OK;
}
/*
* rpcd_* died. Called from SIGCHLD handler.
*/
static void rpc_worker_exited(struct rpc_host *host, pid_t pid)
{
size_t i, num_servers = talloc_array_length(host->servers);
struct rpc_work_process *worker = NULL;
bool found_pid = false;
bool have_active_worker = false;
for (i=0; iservers[i];
size_t j, num_workers;
if (server == NULL) {
/* SIGCHLD for --list-interfaces run */
continue;
}
num_workers = talloc_array_length(server->workers);
for (j=0; jworkers[j];
if (worker->pid == pid) {
found_pid = true;
worker->pid = -1;
worker->available = false;
}
if (worker->pid != -1) {
have_active_worker = true;
}
}
}
if (!found_pid) {
DBG_WARNING("No worker with PID %d\n", (int)pid);
return;
}
if (!have_active_worker && host->np_helper) {
/*
* We have nothing left to do as an np_helper.
* Terminate ourselves (samba-dcerpcd). We will
* be restarted on demand anyway.
*/
DBG_DEBUG("Exiting idle np helper\n");
exit(0);
}
}
/*
* rpcd_* died.
*/
static void rpc_host_sigchld(
struct tevent_context *ev,
struct tevent_signal *se,
int signum,
int count,
void *siginfo,
void *private_data)
{
struct rpc_host *state = talloc_get_type_abort(
private_data, struct rpc_host);
pid_t pid;
int wstatus;
while ((pid = waitpid(-1, &wstatus, WNOHANG)) > 0) {
DBG_DEBUG("pid=%d, wstatus=%d\n", (int)pid, wstatus);
rpc_worker_exited(state, pid);
}
}
/*
* Idle timer fired for a rcpd_* worker. Ask it to terminate.
*/
static void rpc_host_exit_worker(
struct tevent_context *ev,
struct tevent_timer *te,
struct timeval current_time,
void *private_data)
{
struct rpc_server *server = talloc_get_type_abort(
private_data, struct rpc_server);
size_t i, num_workers = talloc_array_length(server->workers);
/*
* Scan for the right worker. We don't have too many of those,
* and maintaining an index would be more data structure effort.
*/
for (i=0; iworkers[i];
NTSTATUS status;
if (w->exit_timer != te) {
continue;
}
w->exit_timer = NULL;
SMB_ASSERT(w->num_clients == 0);
status = messaging_send(
server->host->msg_ctx,
pid_to_procid(w->pid),
MSG_SHUTDOWN,
NULL);
if (!NT_STATUS_IS_OK(status)) {
DBG_DEBUG("Could not send SHUTDOWN msg: %s\n",
nt_errstr(status));
}
w->available = false;
break;
}
}
/*
* rcpd_* worker replied with its status.
*/
static void rpc_host_child_status_recv(
struct messaging_context *msg,
void *private_data,
uint32_t msg_type,
struct server_id server_id,
DATA_BLOB *data)
{
struct rpc_host *host = talloc_get_type_abort(
private_data, struct rpc_host);
size_t num_servers = talloc_array_length(host->servers);
struct rpc_server *server = NULL;
size_t num_workers;
pid_t src_pid = procid_to_pid(&server_id);
struct rpc_work_process *worker = NULL;
struct rpc_worker_status status_message;
enum ndr_err_code ndr_err;
ndr_err = ndr_pull_struct_blob_all_noalloc(
data,
&status_message,
(ndr_pull_flags_fn_t)ndr_pull_rpc_worker_status);
if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
struct server_id_buf buf;
DBG_WARNING("Got invalid message from pid %s\n",
server_id_str_buf(server_id, &buf));
return;
}
if (DEBUGLEVEL >= 10) {
NDR_PRINT_DEBUG(rpc_worker_status, &status_message);
}
if (status_message.server_index >= num_servers) {
DBG_WARNING("Got invalid server_index=%"PRIu32", "
"num_servers=%zu\n",
status_message.server_index,
num_servers);
return;
}
server = host->servers[status_message.server_index];
num_workers = talloc_array_length(server->workers);
if (status_message.worker_index >= num_workers) {
DBG_WARNING("Got invalid worker_index=%"PRIu32", "
"num_workers=%zu\n",
status_message.worker_index,
num_workers);
return;
}
worker = &server->workers[status_message.worker_index];
if (src_pid != worker->pid) {
DBG_WARNING("Got idx=%"PRIu32" from %d, expected %d\n",
status_message.worker_index,
(int)src_pid,
worker->pid);
return;
}
worker->available = true;
worker->num_clients = status_message.num_clients;
if (worker->num_clients != 0) {
TALLOC_FREE(worker->exit_timer);
} else {
worker->exit_timer = tevent_add_timer(
messaging_tevent_context(msg),
server->workers,
tevent_timeval_current_ofs(server->idle_seconds, 0),
rpc_host_exit_worker,
server);
/* No NULL check, it's not fatal if this does not work */
}
rpc_host_distribute_clients(server);
}
/*
* samba-dcerpcd has been asked to shutdown.
* Mark the initial tevent_req as done so we
* exit the event loop.
*/
static void rpc_host_msg_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);
}
/*
* Only match directory entries starting in rpcd_
*/
static int rpcd_filter(const struct dirent *d)
{
int match = fnmatch("rpcd_*", d->d_name, 0);
return (match == 0) ? 1 : 0;
}
/*
* Scan the given libexecdir for rpcd_* services
* and return them as a strv list.
*/
static int rpc_host_list_servers(
const char *libexecdir, TALLOC_CTX *mem_ctx, char **pservers)
{
char *servers = NULL;
struct dirent **namelist = NULL;
int i, num_servers;
int ret = ENOMEM;
num_servers = scandir(libexecdir, &namelist, rpcd_filter, alphasort);
if (num_servers == -1) {
DBG_DEBUG("scandir failed: %s\n", strerror(errno));
return errno;
}
for (i=0; id_name);
if (exe == NULL) {
goto fail;
}
ret = strv_add(mem_ctx, &servers, exe);
TALLOC_FREE(exe);
if (ret != 0) {
goto fail;
}
}
fail:
for (i=0; iev = ev;
state->endpoint = endpoint;
for (i=0; inum_fds; i++) {
struct tevent_req *subreq = NULL;
subreq = accept_send(state, ev, endpoint->fds[i]);
if (tevent_req_nomem(subreq, req)) {
return tevent_req_post(req, ev);
}
tevent_req_set_callback(
subreq, rpc_host_endpoint_accept_accepted, req);
}
return req;
}
/*
* Accept a DCERPC connection from a client.
*/
static void rpc_host_endpoint_accept_accepted(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(
subreq, struct tevent_req);
struct rpc_host_endpoint_accept_state *state = tevent_req_data(
req, struct rpc_host_endpoint_accept_state);
struct rpc_host_endpoint *endpoint = state->endpoint;
int sock, listen_sock, err;
struct samba_sockaddr peer_addr;
sock = accept_recv(subreq, &listen_sock, &peer_addr, &err);
TALLOC_FREE(subreq);
if (sock == -1) {
/* What to do here? Just ignore the error and retry? */
DBG_DEBUG("accept_recv failed: %s\n", strerror(err));
tevent_req_error(req, err);
return;
}
subreq = accept_send(state, state->ev, listen_sock);
if (tevent_req_nomem(subreq, req)) {
close(sock);
sock = -1;
return;
}
tevent_req_set_callback(
subreq, rpc_host_endpoint_accept_accepted, req);
subreq = rpc_host_bind_read_send(
state,
state->ev,
dcerpc_binding_get_transport(endpoint->binding),
&sock,
&peer_addr);
if (tevent_req_nomem(subreq, req)) {
return;
}
tevent_req_set_callback(
subreq, rpc_host_endpoint_accept_got_bind, req);
}
/*
* Client sent us a DCERPC bind packet.
*/
static void rpc_host_endpoint_accept_got_bind(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(
subreq, struct tevent_req);
struct rpc_host_endpoint_accept_state *state = tevent_req_data(
req, struct rpc_host_endpoint_accept_state);
struct rpc_host_endpoint *endpoint = state->endpoint;
struct rpc_server *server = endpoint->server;
struct rpc_host_pending_client *pending = NULL;
struct rpc_host_client *client = NULL;
struct ncacn_packet *bind_pkt = NULL;
int ret;
int sock=-1;
ret = rpc_host_bind_read_recv(
subreq, state, &sock, &client, &bind_pkt);
TALLOC_FREE(subreq);
if (ret != 0) {
DBG_DEBUG("rpc_host_bind_read_recv returned %s\n",
strerror(ret));
goto fail;
}
client->binding = dcerpc_binding_string(client, endpoint->binding);
if (client->binding == NULL) {
DBG_WARNING("dcerpc_binding_string failed, dropping client\n");
goto fail;
}
pending = talloc_zero(server, struct rpc_host_pending_client);
if (pending == NULL) {
DBG_WARNING("talloc failed, dropping client\n");
goto fail;
}
pending->server = server;
pending->sock = sock;
pending->bind_pkt = talloc_move(pending, &bind_pkt);
pending->client = talloc_move(pending, &client);
talloc_set_destructor(pending, rpc_host_pending_client_destructor);
sock = -1;
pending->hangup_wait = wait_for_read_send(
pending, state->ev, pending->sock, true);
if (pending->hangup_wait == NULL) {
DBG_WARNING("wait_for_read_send failed, dropping client\n");
TALLOC_FREE(pending);
return;
}
tevent_req_set_callback(
pending->hangup_wait, rpc_host_client_exited, pending);
DLIST_ADD_END(server->pending_clients, pending);
rpc_host_distribute_clients(server);
return;
fail:
TALLOC_FREE(client);
if (sock != -1) {
close(sock);
}
}
static int rpc_host_endpoint_accept_recv(
struct tevent_req *req, struct rpc_host_endpoint **ep)
{
struct rpc_host_endpoint_accept_state *state = tevent_req_data(
req, struct rpc_host_endpoint_accept_state);
*ep = state->endpoint;
return tevent_req_simple_recv_unix(req);
}
/*
* Full state for samba-dcerpcd. Everything else
* is hung off this.
*/
struct rpc_host_state {
struct tevent_context *ev;
struct rpc_host *host;
bool is_ready;
const char *daemon_ready_progname;
struct tevent_immediate *ready_signal_immediate;
int *ready_signal_fds;
size_t num_servers;
size_t num_prepared;
};
/*
* Tell whoever invoked samba-dcerpcd we're ready to
* serve.
*/
static void rpc_host_report_readiness(
struct tevent_context *ev,
struct tevent_immediate *im,
void *private_data)
{
struct rpc_host_state *state = talloc_get_type_abort(
private_data, struct rpc_host_state);
int i, num_fds = talloc_array_length(state->ready_signal_fds);
if (!state->is_ready) {
DBG_DEBUG("Not yet ready\n");
return;
}
for (i=0; iready_signal_fds[i],
(void *)&byte,
sizeof(byte));
} while ((nwritten == -1) && (errno == EINTR));
close(state->ready_signal_fds[i]);
}
TALLOC_FREE(state->ready_signal_fds);
}
/*
* Respond to a "are you ready" message.
*/
static bool rpc_host_ready_signal_filter(
struct messaging_rec *rec, void *private_data)
{
struct rpc_host_state *state = talloc_get_type_abort(
private_data, struct rpc_host_state);
size_t num_fds = talloc_array_length(state->ready_signal_fds);
int *tmp = NULL;
if (rec->msg_type != MSG_DAEMON_READY_FD) {
return false;
}
if (rec->num_fds != 1) {
DBG_DEBUG("Got %"PRIu8" fds\n", rec->num_fds);
return false;
}
if (num_fds + 1 < num_fds) {
return false;
}
tmp = talloc_realloc(state, state->ready_signal_fds, int, num_fds+1);
if (tmp == NULL) {
return false;
}
state->ready_signal_fds = tmp;
state->ready_signal_fds[num_fds] = rec->fds[0];
rec->fds[0] = -1;
tevent_schedule_immediate(
state->ready_signal_immediate,
state->ev,
rpc_host_report_readiness,
state);
return false;
}
/*
* Respond to a "what is your status" message.
*/
static bool rpc_host_dump_status_filter(
struct messaging_rec *rec, void *private_data)
{
struct rpc_host_state *state = talloc_get_type_abort(
private_data, struct rpc_host_state);
struct rpc_host *host = state->host;
struct rpc_server **servers = host->servers;
size_t i, num_servers = talloc_array_length(servers);
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 (i=0; iworkers);
size_t active_workers = 0;
for (j=0; jworkers[j].pid != -1) {
active_workers += 1;
}
}
fprintf(f,
"%s: active_workers=%zu\n",
server->rpc_server_exe,
active_workers);
for (j=0; jworkers[j];
if (w->pid == (pid_t)-1) {
continue;
}
fprintf(f,
" worker[%zu]: pid=%d, num_clients=%"PRIu32"\n",
j,
(int)w->pid,
w->num_clients);
}
}
fclose(f);
return false;
}
static void rpc_host_server_setup_done(struct tevent_req *subreq);
static void rpc_host_endpoint_failed(struct tevent_req *subreq);
/*
* Async startup for samba-dcerpcd.
*/
static struct tevent_req *rpc_host_send(
TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct messaging_context *msg_ctx,
char *servers,
int ready_signal_fd,
const char *daemon_ready_progname,
bool is_np_helper)
{
struct tevent_req *req = NULL, *subreq = NULL;
struct rpc_host_state *state = NULL;
struct rpc_host *host = NULL;
struct tevent_signal *se = NULL;
char *epmdb_path = NULL;
char *exe = NULL;
size_t i, num_servers = strv_count(servers);
NTSTATUS status;
int ret;
req = tevent_req_create(req, &state, struct rpc_host_state);
if (req == NULL) {
return NULL;
}
state->ev = ev;
state->daemon_ready_progname = daemon_ready_progname;
state->ready_signal_immediate = tevent_create_immediate(state);
if (tevent_req_nomem(state->ready_signal_immediate, req)) {
return tevent_req_post(req, ev);
}
if (ready_signal_fd != -1) {
state->ready_signal_fds = talloc_array(state, int, 1);
if (tevent_req_nomem(state->ready_signal_fds, req)) {
return tevent_req_post(req, ev);
}
state->ready_signal_fds[0] = ready_signal_fd;
}
state->host = talloc_zero(state, struct rpc_host);
if (tevent_req_nomem(state->host, req)) {
return tevent_req_post(req, ev);
}
host = state->host;
host->msg_ctx = msg_ctx;
host->np_helper = is_np_helper;
ret = pipe(host->worker_stdin);
if (ret == -1) {
tevent_req_nterror(req, map_nt_error_from_unix(errno));
return tevent_req_post(req, ev);
}
host->servers = talloc_zero_array(
host, struct rpc_server *, num_servers);
if (tevent_req_nomem(host->servers, req)) {
return tevent_req_post(req, ev);
}
se = tevent_add_signal(ev, state, SIGCHLD, 0, rpc_host_sigchld, host);
if (tevent_req_nomem(se, req)) {
return tevent_req_post(req, ev);
}
BlockSignals(false, SIGCHLD);
status = messaging_register(
msg_ctx,
host,
MSG_RPC_WORKER_STATUS,
rpc_host_child_status_recv);
if (tevent_req_nterror(req, status)) {
return tevent_req_post(req, ev);
}
status = messaging_register(
msg_ctx, req, MSG_SHUTDOWN, rpc_host_msg_shutdown);
if (tevent_req_nterror(req, status)) {
return tevent_req_post(req, ev);
}
subreq = messaging_filtered_read_send(
state, ev, msg_ctx, rpc_host_ready_signal_filter, state);
if (tevent_req_nomem(subreq, req)) {
return tevent_req_post(req, ev);
}
subreq = messaging_filtered_read_send(
state, ev, msg_ctx, rpc_host_dump_status_filter, state);
if (tevent_req_nomem(subreq, req)) {
return tevent_req_post(req, ev);
}
epmdb_path = lock_path(state, "epmdb.tdb");
if (tevent_req_nomem(epmdb_path, req)) {
return tevent_req_post(req, ev);
}
host->epmdb = tdb_wrap_open(
host,
epmdb_path,
0,
TDB_CLEAR_IF_FIRST|TDB_INCOMPATIBLE_HASH,
O_RDWR|O_CREAT,
0644);
if (host->epmdb == NULL) {
DBG_DEBUG("tdb_wrap_open(%s) failed: %s\n",
epmdb_path,
strerror(errno));
tevent_req_nterror(req, map_nt_error_from_unix(errno));
return tevent_req_post(req, ev);
}
TALLOC_FREE(epmdb_path);
for (exe = strv_next(servers, exe), i = 0;
exe != NULL;
exe = strv_next(servers, exe), i++) {
DBG_DEBUG("server_setup for %s index %zu\n", exe, i);
subreq = rpc_server_setup_send(
state,
ev,
host,
exe);
if (tevent_req_nomem(subreq, req)) {
return tevent_req_post(req, ev);
}
tevent_req_set_callback(
subreq, rpc_host_server_setup_done, req);
}
return req;
}
/*
* Timer function called after we were initialized but no one
* connected. Shutdown.
*/
static void rpc_host_shutdown(
struct tevent_context *ev,
struct tevent_timer *te,
struct timeval current_time,
void *private_data)
{
struct tevent_req *req = talloc_get_type_abort(
private_data, struct tevent_req);
DBG_DEBUG("Nobody connected -- shutting down\n");
tevent_req_done(req);
}
static void rpc_host_server_setup_done(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(
subreq, struct tevent_req);
struct rpc_host_state *state = tevent_req_data(
req, struct rpc_host_state);
struct rpc_server *server = NULL;
struct rpc_host *host = state->host;
size_t i, num_servers = talloc_array_length(host->servers);
NTSTATUS status;
status = rpc_server_setup_recv(subreq, host, &server);
TALLOC_FREE(subreq);
if (!NT_STATUS_IS_OK(status)) {
DBG_DEBUG("rpc_server_setup_recv returned %s, ignoring\n",
nt_errstr(status));
host->servers = talloc_realloc(
host,
host->servers,
struct rpc_server *,
num_servers-1);
return;
}
server->server_index = state->num_prepared;
host->servers[state->num_prepared] = server;
state->num_prepared += 1;
if (state->num_prepared < num_servers) {
return;
}
for (i=0; iservers[i];
num_endpoints = talloc_array_length(server->endpoints);
for (j=0; jev, server->endpoints[j]);
if (tevent_req_nomem(subreq, req)) {
return;
}
tevent_req_set_callback(
subreq, rpc_host_endpoint_failed, req);
}
}
state->is_ready = true;
if (state->daemon_ready_progname != NULL) {
daemon_ready(state->daemon_ready_progname);
}
if (host->np_helper) {
/*
* If we're started as an np helper, and no one talks to
* us within 10 seconds, just shut ourselves down.
*/
host->np_helper_shutdown = tevent_add_timer(
state->ev,
state,
timeval_current_ofs(10, 0),
rpc_host_shutdown,
req);
if (tevent_req_nomem(host->np_helper_shutdown, req)) {
return;
}
}
tevent_schedule_immediate(
state->ready_signal_immediate,
state->ev,
rpc_host_report_readiness,
state);
}
/*
* Log accept fail on an endpoint.
*/
static void rpc_host_endpoint_failed(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(
subreq, struct tevent_req);
struct rpc_host_state *state = tevent_req_data(
req, struct rpc_host_state);
struct rpc_host_endpoint *endpoint = NULL;
char *binding_string = NULL;
int ret;
ret = rpc_host_endpoint_accept_recv(subreq, &endpoint);
TALLOC_FREE(subreq);
binding_string = dcerpc_binding_string(state, endpoint->binding);
DBG_DEBUG("rpc_host_endpoint_accept_recv for %s returned %s\n",
binding_string,
strerror(ret));
TALLOC_FREE(binding_string);
}
static NTSTATUS rpc_host_recv(struct tevent_req *req)
{
return tevent_req_simple_recv_ntstatus(req);
}
static int rpc_host_pidfile_create(
struct messaging_context *msg_ctx,
const char *progname,
int ready_signal_fd)
{
const char *piddir = lp_pid_directory();
size_t len = strlen(piddir) + strlen(progname) + 6;
char pidFile[len];
pid_t existing_pid;
int fd, ret;
snprintf(pidFile,
sizeof(pidFile),
"%s/%s.pid",
piddir, progname);
ret = pidfile_path_create(pidFile, &fd, &existing_pid);
if (ret == 0) {
/* leak fd */
return 0;
}
if (ret != EAGAIN) {
DBG_DEBUG("pidfile_path_create() failed: %s\n",
strerror(ret));
return ret;
}
DBG_DEBUG("%s pid %d exists\n", progname, (int)existing_pid);
if (ready_signal_fd != -1) {
NTSTATUS status = messaging_send_iov(
msg_ctx,
pid_to_procid(existing_pid),
MSG_DAEMON_READY_FD,
NULL,
0,
&ready_signal_fd,
1);
if (!NT_STATUS_IS_OK(status)) {
DBG_DEBUG("Could not send ready_signal_fd: %s\n",
nt_errstr(status));
}
}
return EAGAIN;
}
static void samba_dcerpcd_stdin_handler(
struct tevent_context *ev,
struct tevent_fd *fde,
uint16_t flags,
void *private_data)
{
struct tevent_req *req = talloc_get_type_abort(
private_data, struct tevent_req);
char c;
if (read(0, &c, 1) != 1) {
/* we have reached EOF on stdin, which means the
parent has exited. Shutdown the server */
tevent_req_done(req);
}
}
/*
* samba-dcerpcd microservice startup !
*/
int main(int argc, const char *argv[])
{
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 messaging_context *msg_ctx = NULL;
struct tevent_req *req = NULL;
char *servers = NULL;
const char *arg = NULL;
size_t num_servers;
poptContext pc;
int ret, err;
NTSTATUS status;
bool log_stdout;
bool ok;
int libexec_rpcds = 0;
int np_helper = 0;
int ready_signal_fd = -1;
struct samba_cmdline_daemon_cfg *cmdline_daemon_cfg = NULL;
struct poptOption long_options[] = {
POPT_AUTOHELP
{
.longName = "libexec-rpcds",
.argInfo = POPT_ARG_NONE,
.arg = &libexec_rpcds,
.descrip = "Use all rpcds in libexec",
},
{
.longName = "ready-signal-fd",
.argInfo = POPT_ARG_INT,
.arg = &ready_signal_fd,
.descrip = "fd to close when initialized",
},
{
.longName = "np-helper",
.argInfo = POPT_ARG_NONE,
.arg = &np_helper,
.descrip = "Internal named pipe server",
},
POPT_COMMON_SAMBA
POPT_COMMON_DAEMON
POPT_COMMON_VERSION
POPT_TABLEEND
};
{
const char *fd_params[] = { "ready-signal-fd", };
closefrom_except_fd_params(
3, ARRAY_SIZE(fd_params), fd_params, argc, argv);
}
talloc_enable_null_tracking();
frame = talloc_stackframe();
umask(0);
sec_init();
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(getprogname(),
argc,
argv,
long_options,
0);
if (pc == NULL) {
DBG_ERR("Failed to setup popt context!\n");
TALLOC_FREE(frame);
exit(1);
}
poptSetOtherOptionHelp(
pc, "[OPTIONS] [SERVICE_1 SERVICE_2 .. SERVICE_N]");
ret = poptGetNextOpt(pc);
if (ret != -1) {
if (ret >= 0) {
fprintf(stderr,
"\nGot unexpected option %d\n",
ret);
} else if (ret == POPT_ERROR_BADOPT) {
fprintf(stderr,
"\nInvalid option %s: %s\n\n",
poptBadOption(pc, 0),
poptStrerror(ret));
} else {
fprintf(stderr,
"\npoptGetNextOpt returned %s\n",
poptStrerror(ret));
}
poptFreeContext(pc);
TALLOC_FREE(frame);
exit(1);
}
while ((arg = poptGetArg(pc)) != NULL) {
ret = strv_add(frame, &servers, arg);
if (ret != 0) {
DBG_ERR("strv_add() failed\n");
poptFreeContext(pc);
TALLOC_FREE(frame);
exit(1);
}
}
log_stdout = (debug_get_log_type() == DEBUG_STDOUT);
if (log_stdout) {
setup_logging(progname, DEBUG_STDOUT);
} else {
setup_logging(progname, DEBUG_FILE);
}
/*
* If "rpc start on demand helpers = true" in smb.conf we must
* not start as standalone, only on demand from
* local_np_connect() functions. Log an error message telling
* the admin how to fix and then exit.
*/
if (lp_rpc_start_on_demand_helpers() && np_helper == 0) {
DBG_ERR("Cannot start in standalone mode if smb.conf "
"[global] setting "
"\"rpc start on demand helpers = true\" - "
"exiting\n");
TALLOC_FREE(frame);
exit(1);
}
if (libexec_rpcds != 0) {
ret = rpc_host_list_servers(
dyn_SAMBA_LIBEXECDIR, frame, &servers);
if (ret != 0) {
DBG_ERR("Could not list libexec: %s\n",
strerror(ret));
poptFreeContext(pc);
TALLOC_FREE(frame);
exit(1);
}
}
num_servers = strv_count(servers);
if (num_servers == 0) {
poptPrintUsage(pc, stderr, 0);
poptFreeContext(pc);
TALLOC_FREE(frame);
exit(1);
}
poptFreeContext(pc);
cmdline_daemon_cfg = samba_cmdline_get_daemon_cfg();
if (log_stdout && cmdline_daemon_cfg->fork) {
DBG_ERR("Can't log to stdout unless in foreground\n");
TALLOC_FREE(frame);
exit(1);
}
msg_ctx = global_messaging_context();
if (msg_ctx == NULL) {
DBG_ERR("messaging_init() failed\n");
TALLOC_FREE(frame);
exit(1);
}
ev_ctx = messaging_tevent_context(msg_ctx);
if (cmdline_daemon_cfg->fork) {
become_daemon(
true,
cmdline_daemon_cfg->no_process_group,
log_stdout);
status = reinit_after_fork(msg_ctx, ev_ctx, false, NULL);
if (!NT_STATUS_IS_OK(status)) {
exit_daemon("reinit_after_fork() failed",
map_errno_from_nt_status(status));
}
} else {
DBG_DEBUG("Calling daemon_status\n");
daemon_status(progname, "Starting process ... ");
}
BlockSignals(true, SIGPIPE);
dump_core_setup(progname, lp_logfile(frame, lp_sub));
DEBUG(0, ("%s version %s started.\n",
progname,
samba_version_string()));
DEBUGADD(0,("%s\n", COPYRIGHT_STARTUP_MESSAGE));
reopen_logs();
(void)winbind_off();
ok = init_guest_session_info(frame);
(void)winbind_on();
if (!ok) {
DBG_ERR("init_guest_session_info failed\n");
global_messaging_context_free();
TALLOC_FREE(frame);
exit(1);
}
ret = rpc_host_pidfile_create(msg_ctx, progname, ready_signal_fd);
if (ret != 0) {
DBG_DEBUG("rpc_host_pidfile_create failed: %s\n",
strerror(ret));
global_messaging_context_free();
TALLOC_FREE(frame);
exit(1);
}
req = rpc_host_send(
ev_ctx,
ev_ctx,
msg_ctx,
servers,
ready_signal_fd,
cmdline_daemon_cfg->fork ? NULL : progname,
np_helper != 0);
if (req == NULL) {
DBG_ERR("rpc_host_send failed\n");
global_messaging_context_free();
TALLOC_FREE(frame);
exit(1);
}
if (!cmdline_daemon_cfg->fork) {
struct stat st;
if (fstat(0, &st) != 0) {
DBG_DEBUG("fstat(0) failed: %s\n",
strerror(errno));
global_messaging_context_free();
TALLOC_FREE(frame);
exit(1);
}
if (S_ISFIFO(st.st_mode) || S_ISSOCK(st.st_mode)) {
tevent_add_fd(
ev_ctx,
ev_ctx,
0,
TEVENT_FD_READ,
samba_dcerpcd_stdin_handler,
req);
}
}
ok = tevent_req_poll_unix(req, ev_ctx, &err);
if (!ok) {
DBG_ERR("tevent_req_poll_unix failed: %s\n",
strerror(err));
global_messaging_context_free();
TALLOC_FREE(frame);
exit(1);
}
status = rpc_host_recv(req);
if (!NT_STATUS_IS_OK(status)) {
DBG_ERR("rpc_host_recv returned %s\n", nt_errstr(status));
global_messaging_context_free();
TALLOC_FREE(frame);
exit(1);
}
TALLOC_FREE(frame);
return 0;
}