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