/*
 *  Unix SMB/CIFS implementation.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, see <http://www.gnu.org/licenses/>.
 */

#include "source3/include/includes.h"
#include <spawn.h>
#include "local_np.h"
#include "lib/async_req/async_sock.h"
#include "librpc/gen_ndr/ndr_named_pipe_auth.h"
#include "libcli/named_pipe_auth/npa_tstream.h"
#include "libcli/named_pipe_auth/tstream_u32_read.h"
#include "lib/util/tevent_unix.h"
#include "auth/auth_util.h"
#include "libcli/security/dom_sid.h"
#include "libcli/security/security_token.h"
#include "nsswitch/winbind_client.h"

/**
 * @file local_np.c
 *
 * Connect to a local named pipe by connecting to
 * samba-dcerpcd. Start samba-dcerpcd if it isn't
 * already running.
 */

extern bool override_logfile;

struct np_sock_connect_state {
	struct tevent_context *ev;
	struct samba_sockaddr addr;
	const struct named_pipe_auth_req *npa_req;
	struct named_pipe_auth_rep *npa_rep;

	DATA_BLOB npa_blob;
	struct iovec iov;

	int sock;
	struct tevent_req *subreq;
	struct tstream_context *transport;
	struct tstream_context *npa_stream;
};

static void np_sock_connect_cleanup(
	struct tevent_req *req, enum tevent_req_state req_state);
static void np_sock_connect_before(void *private_data);
static void np_sock_connect_after(void *private_data);
static void np_sock_connect_connected(struct tevent_req *subreq);
static void np_sock_connect_written(struct tevent_req *subreq);
static void np_sock_connect_read_done(struct tevent_req *subreq);

static struct tevent_req *np_sock_connect_send(
	TALLOC_CTX *mem_ctx,
	struct tevent_context *ev,
	const char *sockpath,
	const struct named_pipe_auth_req *npa_req)
{
	struct tevent_req *req = NULL;
	struct np_sock_connect_state *state = NULL;
	size_t len;
	int ret;
	bool ok;

	req = tevent_req_create(mem_ctx, &state, struct np_sock_connect_state);
	if (req == NULL) {
		return NULL;
	}
	state->ev = ev;
	state->npa_req = npa_req;
	state->sock = -1;
	state->addr.u.un.sun_family = AF_UNIX;

	state->npa_rep = talloc_zero(state, struct named_pipe_auth_rep);
	if (tevent_req_nomem(state->npa_rep, req)) {
		return tevent_req_post(req, ev);
	}

	tevent_req_set_cleanup_fn(req, np_sock_connect_cleanup);

	state->addr.sa_socklen = sizeof(struct sockaddr_un);
	len = strlcpy(state->addr.u.un.sun_path,
		      sockpath,
		      sizeof(state->addr.u.un.sun_path));
	if (len >= sizeof(state->addr.u.un.sun_path)) {
		tevent_req_error(req, ENAMETOOLONG);
		return tevent_req_post(req, ev);
	}

	state->sock = socket(AF_UNIX, SOCK_STREAM, 0);
	if (state->sock == -1) {
		tevent_req_error(req, errno);
		return tevent_req_post(req, ev);
	}

	ret = set_blocking(state->sock, true);
	if (ret == -1) {
		tevent_req_error(req, errno);
		return tevent_req_post(req, ev);
	}
	ok = set_close_on_exec(state->sock);
	if (!ok) {
		tevent_req_error(req, errno);
		return tevent_req_post(req, ev);
	}

	state->subreq = async_connect_send(
		state,
		ev,
		state->sock,
		&state->addr.u.sa,
		state->addr.sa_socklen,
		np_sock_connect_before,
		np_sock_connect_after,
		NULL);
	if (tevent_req_nomem(state->subreq, req)) {
		return tevent_req_post(req, ev);
	}
	tevent_req_set_callback(state->subreq, np_sock_connect_connected, req);

	return req;
}

static void np_sock_connect_cleanup(
	struct tevent_req *req, enum tevent_req_state req_state)
{
	struct np_sock_connect_state *state = tevent_req_data(
		req, struct np_sock_connect_state);

	TALLOC_FREE(state->subreq);
	TALLOC_FREE(state->transport);

	if (state->sock != -1) {
		close(state->sock);
		state->sock = -1;
	}
}

static void np_sock_connect_before(void *private_data)
{
	become_root();
}

static void np_sock_connect_after(void *private_data)
{
	unbecome_root();
}

static void np_sock_connect_connected(struct tevent_req *subreq)
{
	struct tevent_req *req = tevent_req_callback_data(
		subreq, struct tevent_req);
	struct np_sock_connect_state *state = tevent_req_data(
		req, struct np_sock_connect_state);
	enum ndr_err_code ndr_err;
	int ret, err;

	SMB_ASSERT(subreq == state->subreq);

	ret = async_connect_recv(subreq, &err);
	TALLOC_FREE(subreq);
	state->subreq = NULL;
	if (ret == -1) {
		DBG_DEBUG("async_connect_recv returned %s\n", strerror(err));
		tevent_req_error(req, err);
		return;
	}

	/*
	 * As a quick workaround for bug 15310 we have done the
	 * connect in blocking mode (see np_sock_connect_send()). The
	 * rest of our code expects a nonblocking socket, activate
	 * this after the connect succeeded.
	 */
	ret = set_blocking(state->sock, false);
	if (ret == -1) {
		tevent_req_error(req, errno);
		return;
	}

	ret = tstream_bsd_existing_socket(
		state, state->sock, &state->transport);
	if (ret == -1) {
		err = errno;
		DBG_DEBUG("tstream_bsd_existing_socket failed: %s\n",
			  strerror(err));
		tevent_req_error(req, err);
		return;
	}
	state->sock = -1;

	ndr_err = ndr_push_struct_blob(
		&state->npa_blob,
		state,
		state->npa_req,
		(ndr_push_flags_fn_t)ndr_push_named_pipe_auth_req);
	if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
		DBG_DEBUG("ndr_push_struct_blob failed: %s\n",
			  ndr_errstr(ndr_err));
		tevent_req_error(req, ndr_map_error2errno(ndr_err));
		return;
	}
	state->iov = (struct iovec) {
		.iov_base = state->npa_blob.data,
		.iov_len = state->npa_blob.length,
	};

	subreq = tstream_writev_send(
		state, state->ev, state->transport, &state->iov, 1);
	if (tevent_req_nomem(subreq, req)) {
		return;
	}
	tevent_req_set_callback(subreq, np_sock_connect_written, req);
}

static void np_sock_connect_written(struct tevent_req *subreq)
{
	struct tevent_req *req = tevent_req_callback_data(
		subreq, struct tevent_req);
	struct np_sock_connect_state *state = tevent_req_data(
		req, struct np_sock_connect_state);
	int ret, err;

	ret = tstream_writev_recv(subreq, &err);
	TALLOC_FREE(subreq);
	if (ret == -1) {
		DBG_DEBUG("tstream_writev_recv returned %s\n", strerror(err));
		tevent_req_error(req, err);
		return;
	}

	subreq = tstream_u32_read_send(
		state, state->ev, 0x00FFFFFF, state->transport);
	if (tevent_req_nomem(subreq, req)) {
		return;
	}
	tevent_req_set_callback(subreq, np_sock_connect_read_done, req);
}

static void np_sock_connect_read_done(struct tevent_req *subreq)
{
	struct tevent_req *req = tevent_req_callback_data(
		subreq, struct tevent_req);
	struct np_sock_connect_state *state = tevent_req_data(
		req, struct np_sock_connect_state);
	DATA_BLOB in;
	int ret;
	enum ndr_err_code ndr_err;

	ret = tstream_u32_read_recv(subreq, state, &in.data, &in.length);
	TALLOC_FREE(subreq);
	if (tevent_req_error(req, ret)) {
		return;
	}

	ndr_err = ndr_pull_struct_blob_all(
		&in,
		state->npa_rep,
		state->npa_rep,
		(ndr_pull_flags_fn_t)ndr_pull_named_pipe_auth_rep);
	if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
		DBG_DEBUG("ndr_pull_named_pipe_auth_rep failed: %s\n",
			  ndr_errstr(ndr_err));
		tevent_req_error(req, ndr_map_error2errno(ndr_err));
		return;
	}
	if (state->npa_rep->level != 8) {
		DBG_DEBUG("npa level = %" PRIu32 ", expected 8\n",
			  state->npa_rep->level);
		tevent_req_error(req, EIO);
		return;
	}

	ret = tstream_npa_existing_stream(state,
					  &state->transport,
					  state->npa_rep->info.info8.file_type,
					  &state->npa_stream);
	if (ret == -1) {
		ret = errno;
		DBG_DEBUG("tstream_npa_existing_stream failed: %s\n",
			  strerror(ret));
		tevent_req_error(req, ret);
		return;
	}

	tevent_req_done(req);
}

static int np_sock_connect_recv(
	struct tevent_req *req,
	TALLOC_CTX *mem_ctx,
	struct tstream_context **stream)
{
	struct np_sock_connect_state *state = tevent_req_data(
		req, struct np_sock_connect_state);
	int err;

	if (tevent_req_is_unix_error(req, &err)) {
		tevent_req_received(req);
		return err;
	}
	*stream = talloc_move(mem_ctx, &state->npa_stream);
	tevent_req_received(req);
	return 0;
}

struct start_rpc_host_state {
	int ready_fd;
	struct tevent_req *read_ready_req;
};

static void start_rpc_host_cleanup(
	struct tevent_req *req, enum tevent_req_state req_state);
static void start_rpc_host_ready(struct tevent_req *subreq);

/*
 * Start samba-dcerpcd and wait for it to report ready.
 */
static struct tevent_req *start_rpc_host_send(
	TALLOC_CTX *mem_ctx, struct tevent_context *ev)
{
	struct tevent_req *req = NULL, *subreq = NULL;
	struct start_rpc_host_state *state = NULL;
	int ret;
	int ready_fds[2] = { -1, -1 };
	char **argv = NULL;
	pid_t pid;
	bool ok;

	req = tevent_req_create(
		mem_ctx, &state, struct start_rpc_host_state);
	if (req == NULL) {
		return NULL;
	}

	ret = pipe(ready_fds);
	if (ret == -1) {
		ret = errno;
		DBG_DEBUG("pipe() failed: %s\n", strerror(ret));
		goto fail;
	}

	ok = smb_set_close_on_exec(ready_fds[0]);
	if (!ok) {
		ret = errno;
		DBG_DEBUG("smb_set_close_on_exec failed: %s\n",
			  strerror(ret));
		goto fail;
	}

	argv = str_list_make_empty(mem_ctx);
	str_list_add_printf(
		&argv, "%s/samba-dcerpcd", get_dyn_SAMBA_LIBEXECDIR());
	if (!is_default_dyn_CONFIGFILE()) {
		str_list_add_printf(
			&argv, "--configfile=%s", get_dyn_CONFIGFILE());
	}
	str_list_add_printf(&argv, "--libexec-rpcds");
	str_list_add_printf(&argv, "--ready-signal-fd=%d", ready_fds[1]);
	str_list_add_printf(&argv, "--np-helper");
	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) {
		errno = ENOMEM;
		goto fail;
	}

	become_root();
	ret = posix_spawn(&pid, argv[0], NULL, NULL, argv, environ);
	unbecome_root();
	if (ret != 0) {
		DBG_DEBUG("posix_spawn() failed: %s\n", strerror(ret));
		goto fail;
	}

	state->ready_fd = ready_fds[0];
	ready_fds[0] = -1;
	tevent_req_set_cleanup_fn(req, start_rpc_host_cleanup);

	close(ready_fds[1]);
	ready_fds[1] = -1;

	subreq = read_packet_send(state, ev, state->ready_fd, 1, NULL, NULL);
	if (tevent_req_nomem(subreq, req)) {
		return tevent_req_post(req, ev);
	}
	tevent_req_set_callback(subreq, start_rpc_host_ready, req);
	return req;

fail:
	if (ready_fds[0] != -1) {
		close(ready_fds[0]);
		ready_fds[0] = -1;
	}
	if (ready_fds[1] != -1) {
		close(ready_fds[1]);
		ready_fds[1] = -1;
	}
	tevent_req_error(req, ret);
	return tevent_req_post(req, ev);
}

static void start_rpc_host_cleanup(
	struct tevent_req *req, enum tevent_req_state req_state)
{
	struct start_rpc_host_state *state = tevent_req_data(
		req, struct start_rpc_host_state);

	if (state->ready_fd != -1) {
		close(state->ready_fd);
		state->ready_fd = -1;
	}
}

static void start_rpc_host_ready(struct tevent_req *subreq)
{
	struct tevent_req *req = tevent_req_callback_data(
		subreq, struct tevent_req);
	struct start_rpc_host_state *state = tevent_req_data(
		req, struct start_rpc_host_state);
	uint8_t *buf;
	int err;
	ssize_t nread;

	nread = read_packet_recv(subreq, state, &buf, &err);
	TALLOC_FREE(subreq);
	if (nread == -1) {
		tevent_req_error(req, err);
		return;
	}

	close(state->ready_fd);
	state->ready_fd = -1;

	tevent_req_done(req);
}

static int start_rpc_host_recv(struct tevent_req *req)
{
	return tevent_req_simple_recv_unix(req);
}

struct local_np_connect_state {
	struct tevent_context *ev;
	const char *socketpath;
	struct named_pipe_auth_req *npa_req;
	struct tstream_context *npa_stream;
};

static void local_np_connect_connected(struct tevent_req *subreq);
static void local_np_connect_started(struct tevent_req *subreq);
static void local_np_connect_retried(struct tevent_req *subreq);

/**
 * @brief Async connect to a local named pipe RPC interface
 *
 * Start "samba-dcerpcd" on demand if it does not exist
 *
 * @param[in] mem_ctx  The memory context to use.
 * @param[in] ev       The tevent context to use.
 *
 * @param[in] pipename The raw pipename to connect to without path
 * @param[in] remote_client_name The client name to transmit
 * @param[in] remote_client_addr The client addr to transmit
 * @param[in] local_server_name The server name to transmit
 * @param[in] local_server_addr The server addr to transmit
 * @param[in] session_info The authorization info to use
 * @param[in] need_idle_server Does this need to be an exclusive server?
 * @return The tevent_req that was started
 */

struct tevent_req *local_np_connect_send(
	TALLOC_CTX *mem_ctx,
	struct tevent_context *ev,
	const char *pipename,
	enum dcerpc_transport_t transport,
	const char *remote_client_name,
	const struct tsocket_address *remote_client_addr,
	const char *local_server_name,
	const struct tsocket_address *local_server_addr,
	const struct auth_session_info *session_info,
	bool need_idle_server)
{
	struct tevent_req *req = NULL, *subreq = NULL;
	struct local_np_connect_state *state = NULL;
	struct named_pipe_auth_req_info8 *i8 = NULL;
	const char *socket_dir = NULL;
	char *lower_case_pipename = NULL;
	struct dom_sid npa_sid = global_sid_Samba_NPA_Flags;
	uint32_t npa_flags = 0;
	struct security_token *token = NULL;
	NTSTATUS status;
	size_t num_npa_sids;
	bool ok;

	req = tevent_req_create(
		mem_ctx, &state, struct local_np_connect_state);
	if (req == NULL) {
		return NULL;
	}
	state->ev = ev;

	num_npa_sids =
		security_token_count_flag_sids(session_info->security_token,
					       &npa_sid,
					       1,
					       NULL);
	if (num_npa_sids != 0) {
		DBG_ERR("ERROR: %zu NPA Flags SIDs have already been "
			"detected in the security token!\n",
			num_npa_sids);
		tevent_req_error(req, EACCES);
		return tevent_req_post(req, ev);
	}

	socket_dir = lp_parm_const_string(
		GLOBAL_SECTION_SNUM, "external_rpc_pipe", "socket_dir",
		lp_ncalrpc_dir());
	if (socket_dir == NULL) {
		DBG_DEBUG("external_rpc_pipe:socket_dir not set\n");
		tevent_req_error(req, EINVAL);
		return tevent_req_post(req, ev);
	}

	lower_case_pipename = strlower_talloc(state, pipename);
	if (tevent_req_nomem(lower_case_pipename, req)) {
		return tevent_req_post(req, ev);
	}

	/*
	 * Ensure we cannot process a path that exits
	 * the socket_dir.
	 */
	if (ISDOTDOT(lower_case_pipename) ||
	    (strchr(lower_case_pipename, '/')!=NULL))
	{
		DBG_DEBUG("attempt to connect to invalid pipe pathname %s\n",
			lower_case_pipename);
		tevent_req_error(req, ENOENT);
		return tevent_req_post(req, ev);
	}

	state->socketpath = talloc_asprintf(
		state, "%s/np/%s", socket_dir, lower_case_pipename);
	if (tevent_req_nomem(state->socketpath, req)) {
		return tevent_req_post(req, ev);
	}
	TALLOC_FREE(lower_case_pipename);

	state->npa_req = talloc_zero(state, struct named_pipe_auth_req);
	if (tevent_req_nomem(state->npa_req, req)) {
		return tevent_req_post(req, ev);
	}
	state->npa_req->level = 8;

	i8 = &state->npa_req->info.info8;

	i8->transport = transport;

	/* we don't have "int" in IDL, make sure we don't overflow */
	SMB_ASSERT(i8->transport == transport);

	if (remote_client_name == NULL) {
		remote_client_name = get_myname(state->npa_req);
		if (remote_client_name == NULL) {
			tevent_req_error(req, errno);
			return tevent_req_post(req, ev);
		}
	}
	i8->remote_client_name = remote_client_name;

	if (remote_client_addr == NULL) {
		struct tsocket_address *addr = NULL;
		int ret = tsocket_address_inet_from_strings(
			state->npa_req, "ip", NULL, 0, &addr);
		if (ret != 0) {
			tevent_req_error(req, errno);
			return tevent_req_post(req, ev);
		}
		remote_client_addr = addr;
	}
	i8->remote_client_addr =
		tsocket_address_inet_addr_string(remote_client_addr,
						 state->npa_req);
	if (i8->remote_client_addr == NULL) {
		tevent_req_error(req, errno);
		return tevent_req_post(req, ev);
	}
	i8->remote_client_port = tsocket_address_inet_port(remote_client_addr);

	if (local_server_name == NULL) {
		local_server_name = remote_client_name;
	}
	i8->local_server_name = local_server_name;

	if (local_server_addr == NULL) {
		struct tsocket_address *addr = NULL;
		int ret = tsocket_address_inet_from_strings(
			state->npa_req, "ip", NULL, 0, &addr);
		if (ret != 0) {
			tevent_req_error(req, errno);
			return tevent_req_post(req, ev);
		}
		local_server_addr = addr;
	}
	i8->local_server_addr =
		tsocket_address_inet_addr_string(local_server_addr,
						 state->npa_req);
	if (i8->local_server_addr == NULL) {
		tevent_req_error(req, errno);
		return tevent_req_post(req, ev);
	}
	i8->local_server_port = tsocket_address_inet_port(local_server_addr);

	i8->session_info = talloc_zero(state->npa_req,
				       struct auth_session_info_transport);
	if (tevent_req_nomem(i8->session_info, req)) {
		return tevent_req_post(req, ev);
	}

	i8->session_info->session_info =
		copy_session_info(i8->session_info, session_info);
	if (tevent_req_nomem(i8->session_info->session_info, req)) {
		return tevent_req_post(req, ev);
	}

	if (need_idle_server) {
		npa_flags |= SAMBA_NPA_FLAGS_NEED_IDLE;
	}

	ok = winbind_env_set();
	if (ok) {
		npa_flags |= SAMBA_NPA_FLAGS_WINBIND_OFF;
	}

	ok = sid_append_rid(&npa_sid, npa_flags);
	if (!ok) {
		tevent_req_error(req, EINVAL);
		return tevent_req_post(req, ev);
	}

	token = i8->session_info->session_info->security_token;

	status = add_sid_to_array_unique(token,
					 &npa_sid,
					 &token->sids,
					 &token->num_sids);
	if (!NT_STATUS_IS_OK(status)) {
		tevent_req_oom(req);
		return tevent_req_post(req, ev);
	}

	subreq = np_sock_connect_send(
		state, state->ev, state->socketpath, state->npa_req);
	if (tevent_req_nomem(subreq, req)) {
		return tevent_req_post(req, ev);
	}
	tevent_req_set_callback(subreq, local_np_connect_connected, req);

	return req;
}

static void local_np_connect_connected(struct tevent_req *subreq)
{
	struct tevent_req *req = tevent_req_callback_data(
		subreq, struct tevent_req);
	struct local_np_connect_state *state = tevent_req_data(
		req, struct local_np_connect_state);
	int ret;

	ret = np_sock_connect_recv(subreq, state, &state->npa_stream);
	TALLOC_FREE(subreq);

	if (ret == 0) {
		tevent_req_done(req);
		return;
	}

	DBG_DEBUG("np_sock_connect failed: %s\n", strerror(ret));

	if (!lp_rpc_start_on_demand_helpers()) {
		/*
		 * samba-dcerpcd should already be started in
		 * daemon/standalone mode when "rpc start on demand
		 * helpers = false". We are prohibited from starting
		 * on demand as a named-pipe helper.
		 */
		DBG_ERR("Can't connect to a running samba-dcerpcd. smb.conf "
			"config prohibits starting as named pipe helper as "
			"the [global] section contains "
			"\"rpc start on demand helpers = false\".\n");
		tevent_req_error(req, ret);
		return;
	}

	/*
	 * samba-dcerpcd isn't running. We need to start it.
	 * Note if it doesn't start we treat this as a fatal
	 * error for connecting to the named pipe and don't
	 * keep trying to restart for this connection.
	 */
	subreq = start_rpc_host_send(state, state->ev);
	if (tevent_req_nomem(subreq, req)) {
		return;
	}
	tevent_req_set_callback(subreq, local_np_connect_started, req);
}

static void local_np_connect_started(struct tevent_req *subreq)
{
	struct tevent_req *req = tevent_req_callback_data(
		subreq, struct tevent_req);
	struct local_np_connect_state *state = tevent_req_data(
		req, struct local_np_connect_state);
	int ret;

	ret = start_rpc_host_recv(subreq);
	TALLOC_FREE(subreq);
	if (tevent_req_error(req, ret)) {
		DBG_DEBUG("start_rpc_host_recv failed: %s\n",
			  strerror(ret));
		return;
	}

	subreq = np_sock_connect_send(
		state, state->ev, state->socketpath, state->npa_req);
	if (tevent_req_nomem(subreq, req)) {
		return;
	}
	tevent_req_set_callback(subreq, local_np_connect_retried, req);
}

static void local_np_connect_retried(struct tevent_req *subreq)
{
	struct tevent_req *req = tevent_req_callback_data(
		subreq, struct tevent_req);
	struct local_np_connect_state *state = tevent_req_data(
		req, struct local_np_connect_state);
	int ret;

	ret = np_sock_connect_recv(subreq, state, &state->npa_stream);
	TALLOC_FREE(subreq);
	if (tevent_req_error(req, ret)) {
		return;
	}
	tevent_req_done(req);
}

/**
 * @brief Receive handle to a local named pipe RPC interface
 *
 * @param[in] req The tevent_req that started the operation
 * @param[in] ev      The tevent context to use.
 * @param[in] mem_ctx The memory context to put pstream on
 * @param[out] pstream The established connection to the RPC server
 *
 * @return 0/errno
 */

int local_np_connect_recv(
	struct tevent_req *req,
	TALLOC_CTX *mem_ctx,
	struct tstream_context **pstream)
{
	struct local_np_connect_state *state = tevent_req_data(
		req, struct local_np_connect_state);
	int err;

	if (tevent_req_is_unix_error(req, &err)) {
		tevent_req_received(req);
		return err;
	}

	*pstream = talloc_move(mem_ctx, &state->npa_stream);
	return 0;
}

/**
 * @brief Sync connect to a local named pipe RPC interface
 *
 * Start "samba-dcerpcd" on demand if it does not exist
 *
 * @param[in] pipename The raw pipename to connect to without path
 * @param[in] remote_client_name The client name to transmit
 * @param[in] remote_client_addr The client addr to transmit
 * @param[in] local_server_name The server name to transmit
 * @param[in] local_server_addr The server addr to transmit
 * @param[in] session_info The authorization info to use
 * @param[in] need_idle_server Does this need to be an exclusive server?
 * @param[in] mem_ctx  The memory context to use.
 * @param[out] pstream The established connection to the RPC server
 * @return 0/errno
 */

int local_np_connect(
	const char *pipename,
	enum dcerpc_transport_t transport,
	const char *remote_client_name,
	const struct tsocket_address *remote_client_addr,
	const char *local_server_name,
	const struct tsocket_address *local_server_addr,
	const struct auth_session_info *session_info,
	bool need_idle_server,
	TALLOC_CTX *mem_ctx,
	struct tstream_context **pstream)
{
	struct tevent_context *ev = NULL;
	struct tevent_req *req = NULL;
	int ret = ENOMEM;

	ev = samba_tevent_context_init(mem_ctx);
	if (ev == NULL) {
		goto fail;
	}
	req = local_np_connect_send(
		ev,
		ev,
		pipename,
		transport,
		remote_client_name,
		remote_client_addr,
		local_server_name,
		local_server_addr,
		session_info,
		need_idle_server);
	if (req == NULL) {
		goto fail;
	}
	if (!tevent_req_poll_unix(req, ev, &ret)) {
		goto fail;
	}
	ret = local_np_connect_recv(req, mem_ctx, pstream);
 fail:
	TALLOC_FREE(req);
	TALLOC_FREE(ev);
	return ret;
}
