/* * 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_associations; uint32_t num_connections; /* * 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_info8->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; } /* * We need to limit the number of workers in order * to put the worker index into a 16-bit space, * in order to use a 16-bit association group space * per worker. */ if (state->num_workers > 65536) { state->num_workers = 65536; } 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_info8_from_sock( TALLOC_CTX *mem_ctx, enum dcerpc_transport_t transport, int sock, const struct samba_sockaddr *peer_addr, struct named_pipe_auth_req_info8 **pinfo8) { struct named_pipe_auth_req_info8 *info8 = 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; info8 = talloc_zero(mem_ctx, struct named_pipe_auth_req_info8); if (info8 == NULL) { goto fail; } info8->session_info = talloc_zero(info8, struct auth_session_info_transport); if (info8->session_info == NULL) { goto fail; } status = make_session_info_anonymous( info8->session_info, &info8->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(info8, 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, info8); if (remote_client_addr == NULL) { DBG_DEBUG("tsocket_address_to_name_fn failed\n"); goto nomem; } TALLOC_FREE(taddr); remote_client_name = talloc_strdup(info8, 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, &info8->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(info8, &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, info8); if (local_server_addr == NULL) { DBG_DEBUG("tsocket_address_to_name_fn failed\n"); goto nomem; } TALLOC_FREE(taddr); local_server_name = talloc_strdup(info8, 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, &info8->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( info8, 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, info8); if (remote_client_addr == NULL) { DBG_DEBUG("tsocket_address_unix_path " "failed\n"); goto nomem; } remote_client_name = talloc_strdup(info8, remote_client_addr); if (remote_client_name == NULL) { DBG_DEBUG("talloc_strdup failed\n"); goto nomem; } } } info8->remote_client_addr = remote_client_addr; info8->remote_client_name = remote_client_name; info8->local_server_addr = local_server_addr; info8->local_server_name = local_server_name; *pinfo8 = info8; return NT_STATUS_OK; nomem: status = NT_STATUS_NO_MEMORY; fail: TALLOC_FREE(info8); 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); } /* as server we want to fail early */ tstream_bsd_fail_readv_first_error(state->plain, true); 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_info8_from_sock( state->client, transport, state->sock, peer_addr, &state->client->npa_info8); 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_info8 *info8 = NULL; int ret, err; ret = tstream_npa_accept_existing_recv(subreq, &err, state, &state->npa_stream, &info8, 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_info8 = talloc_move(state->client, &info8); 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; struct rpc_work_process *perfect_worker = NULL; struct rpc_work_process *best_worker = NULL; size_t empty_slot = SIZE_MAX; size_t i; 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_associations == 0) { /* * We have an idle worker... */ perfect_worker = worker; break; } if (best_worker == NULL) { /* * It's busy, but the best so far... */ best_worker = worker; continue; } if (worker->num_associations < best_worker->num_associations) { /* * It's also busy, but has less association groups * (logical clients) */ best_worker = worker; continue; } if (worker->num_associations > best_worker->num_associations) { /* * It's not better */ continue; } /* * Ok, with the same number of association groups * we pick the one with the lowest number of connections */ if (worker->num_connections < best_worker->num_connections) { best_worker = worker; continue; } } if (perfect_worker != NULL) { return perfect_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 (best_worker != NULL) { return best_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_associations == 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 >= UINT16_MAX) { /* * The worker index would not fit into 16-bits */ 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; const char *client_type = NULL; 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); uint16_t worker_index = assoc_group_id >> 16; client_type = "associated"; 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 %"PRIu16": " "pid=%d, available=%d\n", worker_index, (int)worker->pid, (int)worker->available); /* * Pick a random one for a proper bind nack */ client_type = "associated+lost"; worker = rpc_host_find_worker(server); } } else { struct auth_session_info_transport *session_info = pending_client->client->npa_info8->session_info; uint32_t flags = 0; bool found; client_type = "new"; 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)) { client_type = "new+exclusive"; worker = rpc_host_find_idle_worker(server); } else { client_type = "new"; worker = rpc_host_find_worker(server); } } if (worker == NULL) { DBG_DEBUG("No worker found for %s client\n", client_type); 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 %s client %s to %d with " "%"PRIu32" associations and %"PRIu32" connections\n", client_type, server->rpc_server_exe, worker->pid, worker->num_associations, worker->num_connections); 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; } if (assoc_group_id == 0) { worker->num_associations += 1; } worker->num_connections += 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_associations == 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_associations = status_message.num_association_groups; worker->num_connections = status_message.num_connections; if (worker->num_associations != 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); size_t 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_associations=%"PRIu32", num_connections=%"PRIu32"\n", j, (int)w->pid, w->num_associations, w->num_connections); } } 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); 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)); reopen_logs(); DBG_STARTUP_NOTICE("%s version %s started.\n%s\n", progname, samba_version_string(), samba_copyright_string()); (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; }