1
0
mirror of https://github.com/samba-team/samba.git synced 2024-12-24 21:34:56 +03:00
samba-mirror/source3/rpc_server/rpc_server.c
Samuel Cabrero 9bdf3ccde6 s3:rpc_server: Switch to core dcerpc server loop
This commit finally switches the RPC server implementation.

At the same we have to do other related changes to keep code compiling
and test environments running.

First avoid moving the session_info into the allocated pipes_struct memory
context as it is owned now by the core RPC server, and the s3compat pidl
compiler will update the pipes_struct session_info before dispatching
the call with dcesrv_call->auth_state->session_info.

Also, fix a segfault in the endpoint mapper daemon when it tries to delete
the endpoints previously registered over a NCALRPC connection.

If we have:

rpc_server : epmapper = external
rpc_server : lsarpc = external
rpc_daemon : epmd = fork
rpc_daemon : lsasd = fork

The sequence is:

* The endpoint mapper starts (start_epmd in source3/smbd/server.c)
* The lsarpc daemon starts (start_lsasd in source3/smbd/server.c)
  * The lsarpc daemon creates the sockets and registers its endpoints
    (rpc_ep_register in source3/rpc_server/lsasd.c)
  * The endpoint registration code opens a NCALRPC connection to the
    endpoint mapper daemon (ep_register in source3/librpc/rpc/dcerpc_ep.c)
    and keeps it open to re-register if the endpoint mapper daemon dies
    (rpc_ep_register_loop in source3/rpc_server/rpc_ep_register.c)
* When the endpoint mapper daemon accepts a NCALRPC connection it sets a
  termination function (srv_epmapper_delete_endpoints)
* Suppose the lsarpc daemon exits. The NCALRPC connection termination
  function is called.
* The termination function tries to delete all endpoints registered by that
  connection by calling _epm_Delete
* _epm_Delete calls is_privileged_pipe which access to
  pipes_struct->session_info.

As the call to _epm_Delete occurs outside of the PIDL generated code,
the pipes_stuct->session_info is NULL. This commit also sets
pipes_struct->session_info from the dcerpc_connection before calling
_epm_Delete. As the core rpc server supports security context multiplexing we
need to pass the dcesrv_connection to the termination function and let the
implementation pick a auth context. In the case of the endpoint mapper
the termination function has to pick one of type NCALRPC_AS_SYSTEM to
check if the connection is privileged and delete the endpoints
registered by the connection being closed.

Finally, the samba.tests.dcerpc.raw_protocol testsuite passes against
the ad_member environment.

Signed-off-by: Samuel Cabrero <scabrero@samba.org>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
2020-03-20 15:36:35 +00:00

1485 lines
39 KiB
C

/*
Unix SMB/Netbios implementation.
Generic infrstructure for RPC Daemons
Copyright (C) Simo Sorce 2010
Copyright (C) Andrew Bartlett 2011
Copyright (C) Andreas Schneider 2011
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 "includes.h"
#include "librpc/rpc/dcesrv_core.h"
#include "rpc_server/rpc_pipes.h"
#include "rpc_server/rpc_server.h"
#include "rpc_server/rpc_config.h"
#include "rpc_dce.h"
#include "librpc/gen_ndr/netlogon.h"
#include "librpc/gen_ndr/auth.h"
#include "lib/tsocket/tsocket.h"
#include "libcli/named_pipe_auth/npa_tstream.h"
#include "../auth/auth_sam_reply.h"
#include "auth.h"
#include "rpc_server/rpc_ncacn_np.h"
#include "rpc_server/srv_pipe_hnd.h"
#include "rpc_server/srv_pipe.h"
#include "librpc/gen_ndr/ndr_dcerpc.h"
#undef DBGC_CLASS
#define DBGC_CLASS DBGC_RPC_SRV
/* Creates a pipes_struct and initializes it with the information
* sent from the client */
int make_server_pipes_struct(TALLOC_CTX *mem_ctx,
struct messaging_context *msg_ctx,
const char *pipe_name,
enum dcerpc_transport_t transport,
const struct tsocket_address *remote_address,
const struct tsocket_address *local_address,
struct pipes_struct **_p,
int *perrno)
{
struct pipes_struct *p;
int ret;
ret = make_base_pipes_struct(mem_ctx, msg_ctx, pipe_name,
transport, RPC_LITTLE_ENDIAN,
remote_address, local_address, &p);
if (ret) {
*perrno = ret;
return -1;
}
*_p = p;
return 0;
}
/* Start listening on the appropriate unix socket and setup all is needed to
* dispatch requests to the pipes rpc implementation */
struct dcerpc_ncacn_listen_state {
int fd;
struct tevent_context *ev_ctx;
struct messaging_context *msg_ctx;
struct dcesrv_context *dce_ctx;
struct dcesrv_endpoint *endpoint;
dcerpc_ncacn_termination_fn termination_fn;
void *termination_data;
};
static void dcesrv_ncacn_np_listener(struct tevent_context *ev,
struct tevent_fd *fde,
uint16_t flags,
void *private_data);
NTSTATUS dcesrv_create_ncacn_np_socket(struct dcesrv_endpoint *e, int *out_fd)
{
char *np_dir = NULL;
int fd = -1;
NTSTATUS status;
const char *endpoint;
char *endpoint_normalized = NULL;
char *p = NULL;
endpoint = dcerpc_binding_get_string_option(e->ep_description,
"endpoint");
if (endpoint == NULL) {
DBG_ERR("Endpoint mandatory for named pipes\n");
return NT_STATUS_INVALID_PARAMETER;
}
/* The endpoint string from IDL can be mixed uppercase and case is
* normalized by smbd on connection */
endpoint_normalized = strlower_talloc(talloc_tos(), endpoint);
if (endpoint_normalized == NULL) {
return NT_STATUS_NO_MEMORY;
}
/* The endpoint string from IDL can be prefixed by \pipe\ */
p = endpoint_normalized;
if (strncmp(p, "\\pipe\\", 6) == 0) {
p += 6;
}
/*
* As lp_ncalrpc_dir() should have 0755, but
* lp_ncalrpc_dir()/np should have 0700, we need to
* create lp_ncalrpc_dir() first.
*/
if (!directory_create_or_exist(lp_ncalrpc_dir(), 0755)) {
status = map_nt_error_from_unix_common(errno);
DBG_ERR("Failed to create pipe directory %s - %s\n",
lp_ncalrpc_dir(), strerror(errno));
goto out;
}
np_dir = talloc_asprintf(talloc_tos(), "%s/np", lp_ncalrpc_dir());
if (!np_dir) {
status = NT_STATUS_NO_MEMORY;
DBG_ERR("Out of memory\n");
goto out;
}
if (!directory_create_or_exist_strict(np_dir, geteuid(), 0700)) {
status = map_nt_error_from_unix_common(errno);
DBG_ERR("Failed to create pipe directory %s - %s\n",
np_dir, strerror(errno));
goto out;
}
fd = create_pipe_sock(np_dir, p, 0700);
if (fd == -1) {
status = map_nt_error_from_unix_common(errno);
DBG_ERR("Failed to create ncacn_np socket! '%s/%s': %s\n",
np_dir, p, strerror(errno));
goto out;
}
DBG_DEBUG("Opened pipe socket fd %d for %s\n", fd, p);
*out_fd = fd;
status = NT_STATUS_OK;
out:
TALLOC_FREE(endpoint_normalized);
TALLOC_FREE(np_dir);
return status;
}
NTSTATUS dcesrv_setup_ncacn_np_socket(struct tevent_context *ev_ctx,
struct messaging_context *msg_ctx,
struct dcesrv_context *dce_ctx,
struct dcesrv_endpoint *e,
dcerpc_ncacn_termination_fn term_fn,
void *term_data)
{
struct dcerpc_ncacn_listen_state *state;
struct tevent_fd *fde;
int rc;
NTSTATUS status;
const char *endpoint = NULL;
endpoint = dcerpc_binding_get_string_option(e->ep_description,
"endpoint");
if (endpoint == NULL) {
DBG_ERR("Endpoint mandatory for named pipes\n");
return NT_STATUS_INVALID_PARAMETER;
}
/* Alloc in endpoint context. If the endpoint is freed (for example
* when forked daemons reinit the dcesrv_context, the tevent_fd
* listener will be stopped and the socket closed */
state = talloc_zero(e, struct dcerpc_ncacn_listen_state);
if (state == NULL) {
DBG_ERR("Out of memory\n");
return NT_STATUS_NO_MEMORY;
}
state->fd = -1;
state->ev_ctx = ev_ctx;
state->msg_ctx = msg_ctx;
state->endpoint = e;
state->dce_ctx = talloc_reference(state, dce_ctx);
state->termination_fn = term_fn;
state->termination_data = term_data;
status = dcesrv_create_ncacn_np_socket(e, &state->fd);
if (!NT_STATUS_IS_OK(status)) {
goto out;
}
rc = listen(state->fd, 5);
if (rc < 0) {
status = map_nt_error_from_unix_common(errno);
DBG_ERR("Failed to listen on ncacn_np socket %s: %s\n",
endpoint, strerror(errno));
goto out;
}
DBG_DEBUG("Opened pipe socket fd %d for %s\n",
state->fd, endpoint);
errno = 0;
fde = tevent_add_fd(ev_ctx,
state, state->fd, TEVENT_FD_READ,
dcesrv_ncacn_np_listener, state);
if (fde == NULL) {
if (errno == 0) {
errno = ENOMEM;
}
status = map_nt_error_from_unix_common(errno);
DBG_ERR("Failed to add event handler for ncacn_np: %s\n",
strerror(errno));
goto out;
}
tevent_fd_set_auto_close(fde);
return NT_STATUS_OK;
out:
if (state->fd != -1) {
close(state->fd);
}
TALLOC_FREE(state);
return status;
}
static void dcesrv_ncacn_np_listener(struct tevent_context *ev,
struct tevent_fd *fde,
uint16_t flags,
void *private_data)
{
struct dcerpc_ncacn_listen_state *state =
talloc_get_type_abort(private_data,
struct dcerpc_ncacn_listen_state);
struct samba_sockaddr addr = {
.sa_socklen = sizeof(struct sockaddr_un),
};
int sd = -1;
const char *endpoint = NULL;
/* TODO: should we have a limit to the number of clients ? */
sd = accept(state->fd, &addr.u.sa, &addr.sa_socklen);
if (sd == -1) {
if (errno != EINTR) {
DEBUG(6, ("Failed to get a valid socket [%s]\n",
strerror(errno)));
}
return;
}
smb_set_close_on_exec(sd);
endpoint = dcerpc_binding_get_string_option(
state->endpoint->ep_description, "endpoint");
if (endpoint == NULL) {
DBG_ERR("Failed to get endpoint from binding description\n");
close(sd);
return;
}
DBG_DEBUG("Accepted ncacn_np socket %s (fd: %d)\n",
addr.u.un.sun_path, sd);
dcerpc_ncacn_accept(state->ev_ctx,
state->msg_ctx,
state->dce_ctx,
state->endpoint,
NULL, /* remote client address */
NULL, /* local server address */
sd,
state->termination_fn,
state->termination_data);
}
/********************************************************************
* Start listening on the tcp/ip socket
********************************************************************/
static void dcesrv_ncacn_ip_tcp_listener(struct tevent_context *ev,
struct tevent_fd *fde,
uint16_t flags,
void *private_data);
NTSTATUS dcesrv_create_ncacn_ip_tcp_socket(const struct sockaddr_storage *ifss,
uint16_t *port,
int *out_fd)
{
int fd = -1;
if (*port == 0) {
uint16_t i;
for (i = lp_rpc_low_port(); i <= lp_rpc_high_port(); i++) {
fd = open_socket_in(SOCK_STREAM,
i,
0,
ifss,
false);
if (fd >= 0) {
*port = i;
break;
}
}
} else {
fd = open_socket_in(SOCK_STREAM,
*port,
0,
ifss,
true);
}
if (fd == -1) {
DBG_ERR("Failed to create socket on port %u!\n", *port);
return NT_STATUS_UNSUCCESSFUL;
}
DBG_DEBUG("Opened ncacn_ip_tcp socket fd %d for port %u\n", fd, *port);
*out_fd = fd;
return NT_STATUS_OK;
}
NTSTATUS dcesrv_setup_ncacn_ip_tcp_socket(struct tevent_context *ev_ctx,
struct messaging_context *msg_ctx,
struct dcesrv_context *dce_ctx,
struct dcesrv_endpoint *e,
const struct sockaddr_storage *ifss,
dcerpc_ncacn_termination_fn term_fn,
void *term_data)
{
struct dcerpc_ncacn_listen_state *state = NULL;
struct tevent_fd *fde = NULL;
const char *endpoint = NULL;
uint16_t port = 0;
char port_str[6];
int rc;
NTSTATUS status;
endpoint = dcerpc_binding_get_string_option(e->ep_description,
"endpoint");
if (endpoint != NULL) {
port = atoi(endpoint);
}
/* Alloc in endpoint context. If the endpoint is freed (for example
* when forked daemons reinit the dcesrv_context, the tevent_fd
* listener will be stopped and the socket closed */
state = talloc_zero(e, struct dcerpc_ncacn_listen_state);
if (state == NULL) {
DBG_ERR("Out of memory\n");
return NT_STATUS_NO_MEMORY;
}
state->fd = -1;
state->ev_ctx = ev_ctx;
state->msg_ctx = msg_ctx;
state->endpoint = e;
state->dce_ctx = talloc_reference(state, dce_ctx);
state->termination_fn = term_fn;
state->termination_data = term_data;
status = dcesrv_create_ncacn_ip_tcp_socket(ifss, &port, &state->fd);
if (!NT_STATUS_IS_OK(status)) {
goto out;
}
/* ready to listen */
set_socket_options(state->fd, "SO_KEEPALIVE");
set_socket_options(state->fd, lp_socket_options());
/* Set server socket to non-blocking for the accept. */
set_blocking(state->fd, false);
rc = listen(state->fd, SMBD_LISTEN_BACKLOG);
if (rc == -1) {
status = map_nt_error_from_unix_common(errno);
DBG_ERR("Failed to listen on ncacn_ip_tcp socket: %s\n",
strerror(errno));
goto out;
}
DBG_DEBUG("Opened socket fd %d for port %u\n",
state->fd, port);
errno = 0;
fde = tevent_add_fd(state->ev_ctx,
state,
state->fd,
TEVENT_FD_READ,
dcesrv_ncacn_ip_tcp_listener,
state);
if (fde == NULL) {
if (errno == 0) {
errno = ENOMEM;
}
status = map_nt_error_from_unix_common(errno);
DBG_ERR("Failed to add event handler for ncacn_ip_tcp: %s\n",
strerror(errno));
goto out;
}
tevent_fd_set_auto_close(fde);
/* Set the port in the endpoint */
snprintf(port_str, sizeof(port_str), "%u", port);
status = dcerpc_binding_set_string_option(e->ep_description,
"endpoint", port_str);
if (!NT_STATUS_IS_OK(status)) {
DBG_ERR("Failed to set binding endpoint '%s': %s\n",
port_str, nt_errstr(status));
goto out;
}
return NT_STATUS_OK;
out:
if (state->fd != -1) {
close(state->fd);
}
TALLOC_FREE(state);
return status;
}
static void dcesrv_ncacn_ip_tcp_listener(struct tevent_context *ev,
struct tevent_fd *fde,
uint16_t flags,
void *private_data)
{
struct dcerpc_ncacn_listen_state *state =
talloc_get_type_abort(private_data,
struct dcerpc_ncacn_listen_state);
struct tsocket_address *cli_addr = NULL;
struct tsocket_address *srv_addr = NULL;
struct samba_sockaddr addr = {
.sa_socklen = sizeof(struct sockaddr_storage),
};
int s = -1;
int rc;
s = accept(state->fd, &addr.u.sa, &addr.sa_socklen);
if (s == -1) {
if (errno != EINTR) {
DBG_ERR("Failed to accept: %s\n", strerror(errno));
}
return;
}
smb_set_close_on_exec(s);
rc = tsocket_address_bsd_from_samba_sockaddr(state, &addr, &cli_addr);
if (rc < 0) {
close(s);
return;
}
rc = getsockname(s, &addr.u.sa, &addr.sa_socklen);
if (rc < 0) {
close(s);
return;
}
rc = tsocket_address_bsd_from_samba_sockaddr(state, &addr, &srv_addr);
if (rc < 0) {
close(s);
return;
}
DBG_DEBUG("Accepted ncacn_ip_tcp socket %d\n", s);
dcerpc_ncacn_accept(state->ev_ctx,
state->msg_ctx,
state->dce_ctx,
state->endpoint,
cli_addr,
srv_addr,
s,
state->termination_fn,
state->termination_data);
}
/********************************************************************
* Start listening on the ncalrpc socket
********************************************************************/
static void dcesrv_ncalrpc_listener(struct tevent_context *ev,
struct tevent_fd *fde,
uint16_t flags,
void *private_data);
NTSTATUS dcesrv_create_ncalrpc_socket(struct dcesrv_endpoint *e, int *out_fd)
{
int fd = -1;
const char *endpoint = NULL;
NTSTATUS status;
endpoint = dcerpc_binding_get_string_option(e->ep_description,
"endpoint");
if (endpoint == NULL) {
/*
* No identifier specified: use DEFAULT or SMBD.
*
* When role is AD DC we run two rpc server instances, the one
* started by 'samba' and the one embedded in 'smbd'.
* Avoid listening in DEFAULT socket for NCALRPC as both
* servers will race to accept connections. In this case smbd
* will listen in SMBD socket and rpcint binding handle
* implementation will pick the right socket to use.
*
* TODO: DO NOT hardcode this value anywhere else. Rather,
* specify no endpoint and let the epmapper worry about it.
*/
if (lp_server_role() == ROLE_ACTIVE_DIRECTORY_DC) {
endpoint = "SMBD";
} else {
endpoint = "DEFAULT";
}
status = dcerpc_binding_set_string_option(e->ep_description,
"endpoint",
endpoint);
if (!NT_STATUS_IS_OK(status)) {
DBG_ERR("Failed to set ncalrpc 'endpoint' binding "
"string option to '%s': %s\n",
endpoint, nt_errstr(status));
return status;
}
}
if (!directory_create_or_exist(lp_ncalrpc_dir(), 0755)) {
status = map_nt_error_from_unix_common(errno);
DBG_ERR("Failed to create ncalrpc directory '%s': %s\n",
lp_ncalrpc_dir(), strerror(errno));
goto out;
}
fd = create_pipe_sock(lp_ncalrpc_dir(), endpoint, 0755);
if (fd == -1) {
status = map_nt_error_from_unix_common(errno);
DBG_ERR("Failed to create ncalrpc socket '%s/%s': %s\n",
lp_ncalrpc_dir(), endpoint, strerror(errno));
goto out;
}
DBG_DEBUG("Opened ncalrpc socket fd '%d' for '%s/%s'\n",
fd, lp_ncalrpc_dir(), endpoint);
*out_fd = fd;
return NT_STATUS_OK;
out:
return status;
}
NTSTATUS dcesrv_setup_ncalrpc_socket(struct tevent_context *ev_ctx,
struct messaging_context *msg_ctx,
struct dcesrv_context *dce_ctx,
struct dcesrv_endpoint *e,
dcerpc_ncacn_termination_fn term_fn,
void *termination_data)
{
struct dcerpc_ncacn_listen_state *state;
struct tevent_fd *fde;
int rc;
NTSTATUS status;
/* Alloc in endpoint context. If the endpoint is freed (for example
* when forked daemons reinit the dcesrv_context, the tevent_fd
* listener will be stopped and the socket closed */
state = talloc_zero(e, struct dcerpc_ncacn_listen_state);
if (state == NULL) {
DBG_ERR("Out of memory\n");
return NT_STATUS_NO_MEMORY;
}
state->fd = -1;
state->ev_ctx = ev_ctx;
state->msg_ctx = msg_ctx;
state->dce_ctx = talloc_reference(state, dce_ctx);
state->endpoint = e;
state->termination_fn = term_fn;
state->termination_data = termination_data;
status = dcesrv_create_ncalrpc_socket(e, &state->fd);
if (!NT_STATUS_IS_OK(status)) {
DBG_ERR("Failed to create ncalrpc socket: %s\n",
nt_errstr(status));
goto out;
}
rc = listen(state->fd, 5);
if (rc < 0) {
const char *endpoint = dcerpc_binding_get_string_option(
e->ep_description, "endpoint");
status = map_nt_error_from_unix_common(errno);
DBG_ERR("Failed to listen on ncalrpc socket %s: %s\n",
endpoint, strerror(errno));
goto out;
}
/* Set server socket to non-blocking for the accept. */
set_blocking(state->fd, false);
errno = 0;
fde = tevent_add_fd(state->ev_ctx,
state,
state->fd,
TEVENT_FD_READ,
dcesrv_ncalrpc_listener,
state);
if (fde == NULL) {
if (errno == 0) {
errno = ENOMEM;
}
status = map_nt_error_from_unix_common(errno);
DBG_ERR("Failed to add event handler for ncalrpc: %s\n",
strerror(errno));
goto out;
}
tevent_fd_set_auto_close(fde);
return NT_STATUS_OK;
out:
if (state->fd != -1) {
close(state->fd);
}
TALLOC_FREE(state);
return status;
}
static void dcesrv_ncalrpc_listener(struct tevent_context *ev,
struct tevent_fd *fde,
uint16_t flags,
void *private_data)
{
struct dcerpc_ncacn_listen_state *state =
talloc_get_type_abort(private_data,
struct dcerpc_ncacn_listen_state);
struct tsocket_address *cli_addr = NULL, *srv_addr = NULL;
struct samba_sockaddr addr = {
.sa_socklen = sizeof(struct sockaddr_un),
};
struct samba_sockaddr addr_server = {
.sa_socklen = sizeof(struct sockaddr_un),
};
int sd = -1;
int rc;
const char *endpoint = NULL;
sd = accept(state->fd, &addr.u.sa, &addr.sa_socklen);
if (sd == -1) {
if (errno != EINTR) {
DBG_ERR("Failed to accept: %s\n", strerror(errno));
}
return;
}
smb_set_close_on_exec(sd);
rc = tsocket_address_bsd_from_samba_sockaddr(state, &addr, &cli_addr);
if (rc < 0) {
close(sd);
return;
}
rc = getsockname(sd, &addr_server.u.sa, &addr_server.sa_socklen);
if (rc < 0) {
close(sd);
return;
}
rc = tsocket_address_bsd_from_samba_sockaddr(state,
&addr_server,
&srv_addr);
if (rc < 0) {
close(sd);
return;
}
endpoint = dcerpc_binding_get_string_option(
state->endpoint->ep_description, "endpoint");
if (endpoint == NULL) {
DBG_ERR("Failed to get endpoint from binding description\n");
close(sd);
return;
}
DBG_DEBUG("Accepted ncalrpc socket %s (fd: %d)\n",
addr.u.un.sun_path, sd);
dcerpc_ncacn_accept(state->ev_ctx,
state->msg_ctx,
state->dce_ctx,
state->endpoint,
cli_addr, srv_addr, sd,
state->termination_fn,
state->termination_data);
}
static int dcesrv_connection_destructor(struct dcesrv_connection *conn)
{
struct dcerpc_ncacn_conn *ncacn_conn = talloc_get_type_abort(
conn->transport.private_data,
struct dcerpc_ncacn_conn);
if (ncacn_conn->termination_fn != NULL) {
ncacn_conn->termination_fn(conn, ncacn_conn->termination_data);
}
return 0;
}
NTSTATUS dcerpc_ncacn_conn_init(TALLOC_CTX *mem_ctx,
struct tevent_context *ev_ctx,
struct messaging_context *msg_ctx,
struct dcesrv_context *dce_ctx,
struct dcesrv_endpoint *endpoint,
dcerpc_ncacn_termination_fn term_fn,
void *termination_data,
struct dcerpc_ncacn_conn **out)
{
struct dcerpc_ncacn_conn *ncacn_conn = NULL;
ncacn_conn = talloc_zero(mem_ctx, struct dcerpc_ncacn_conn);
if (ncacn_conn == NULL) {
return NT_STATUS_NO_MEMORY;
}
ncacn_conn->ev_ctx = ev_ctx;
ncacn_conn->msg_ctx = msg_ctx;
ncacn_conn->dce_ctx = dce_ctx;
ncacn_conn->endpoint = endpoint;
ncacn_conn->sock = -1;
ncacn_conn->termination_fn = term_fn;
ncacn_conn->termination_data = termination_data;
*out = ncacn_conn;
return NT_STATUS_OK;
}
static void dcerpc_ncacn_packet_done(struct tevent_req *subreq);
static void dcesrv_ncacn_np_accept_done(struct tevent_req *subreq);
static void dcesrv_ncacn_accept_step2(struct dcerpc_ncacn_conn *ncacn_conn);
static void ncacn_terminate_connection(struct dcerpc_ncacn_conn *conn,
const char *reason);
void dcerpc_ncacn_accept(struct tevent_context *ev_ctx,
struct messaging_context *msg_ctx,
struct dcesrv_context *dce_ctx,
struct dcesrv_endpoint *e,
struct tsocket_address *cli_addr,
struct tsocket_address *srv_addr,
int s,
dcerpc_ncacn_termination_fn termination_fn,
void *termination_data)
{
enum dcerpc_transport_t transport =
dcerpc_binding_get_transport(e->ep_description);
struct dcerpc_ncacn_conn *ncacn_conn;
NTSTATUS status;
int rc;
DBG_DEBUG("dcerpc_ncacn_accept\n");
status = dcerpc_ncacn_conn_init(ev_ctx,
ev_ctx,
msg_ctx,
dce_ctx,
e,
termination_fn,
termination_data,
&ncacn_conn);
if (!NT_STATUS_IS_OK(status)) {
DBG_ERR("Failed to initialize dcerpc_ncacn_connection: %s\n",
nt_errstr(status));
close(s);
return;
}
ncacn_conn->sock = s;
if (cli_addr != NULL) {
ncacn_conn->remote_client_addr = talloc_move(ncacn_conn, &cli_addr);
if (tsocket_address_is_inet(ncacn_conn->remote_client_addr, "ip")) {
ncacn_conn->remote_client_name =
tsocket_address_inet_addr_string(ncacn_conn->remote_client_addr,
ncacn_conn);
} else {
ncacn_conn->remote_client_name =
tsocket_address_unix_path(ncacn_conn->remote_client_addr,
ncacn_conn);
}
if (ncacn_conn->remote_client_name == NULL) {
DBG_ERR("Out of memory obtaining remote socket address as a string!\n");
ncacn_terminate_connection(ncacn_conn, "No memory");
close(s);
return;
}
}
if (srv_addr != NULL) {
ncacn_conn->local_server_addr = talloc_move(ncacn_conn, &srv_addr);
if (tsocket_address_is_inet(ncacn_conn->local_server_addr, "ip")) {
ncacn_conn->local_server_name =
tsocket_address_inet_addr_string(ncacn_conn->local_server_addr,
ncacn_conn);
} else {
ncacn_conn->local_server_name =
tsocket_address_unix_path(ncacn_conn->local_server_addr,
ncacn_conn);
}
if (ncacn_conn->local_server_name == NULL) {
DBG_ERR("No memory\n");
ncacn_terminate_connection(ncacn_conn, "No memory");
close(s);
return;
}
}
rc = set_blocking(s, false);
if (rc < 0) {
DBG_WARNING("Failed to set dcerpc socket to non-blocking\n");
ncacn_terminate_connection(ncacn_conn, strerror(errno));
close(s);
return;
}
/*
* As soon as we have tstream_bsd_existing_socket set up it will
* take care of closing the socket.
*/
rc = tstream_bsd_existing_socket(ncacn_conn, s, &ncacn_conn->tstream);
if (rc < 0) {
DBG_WARNING("Failed to create tstream socket for dcerpc\n");
ncacn_terminate_connection(ncacn_conn, "No memory");
close(s);
return;
}
if (transport == NCACN_NP) {
struct tevent_req *subreq = NULL;
uint64_t allocation_size = 4096;
uint16_t device_state = 0xff | 0x0400 | 0x0100;
uint16_t file_type = FILE_TYPE_MESSAGE_MODE_PIPE;
subreq = tstream_npa_accept_existing_send(ncacn_conn,
ncacn_conn->ev_ctx,
ncacn_conn->tstream,
file_type,
device_state,
allocation_size);
if (subreq == NULL) {
ncacn_terminate_connection(ncacn_conn, "No memory");
return;
}
tevent_req_set_callback(subreq, dcesrv_ncacn_np_accept_done,
ncacn_conn);
return;
}
dcesrv_ncacn_accept_step2(ncacn_conn);
}
static void dcesrv_ncacn_np_accept_done(struct tevent_req *subreq)
{
struct auth_session_info_transport *session_info_transport = NULL;
struct dcerpc_ncacn_conn *ncacn_conn = NULL;
int error;
int ret;
ncacn_conn = tevent_req_callback_data(subreq,
struct dcerpc_ncacn_conn);
ret = tstream_npa_accept_existing_recv(subreq, &error, ncacn_conn,
&ncacn_conn->tstream,
&ncacn_conn->remote_client_addr,
&ncacn_conn->remote_client_name,
&ncacn_conn->local_server_addr,
&ncacn_conn->local_server_name,
&session_info_transport);
ncacn_conn->session_info = talloc_move(ncacn_conn,
&session_info_transport->session_info);
TALLOC_FREE(subreq);
if (ret != 0) {
DBG_ERR("Failed to accept named pipe connection: %s\n",
strerror(error));
ncacn_terminate_connection(ncacn_conn, strerror(errno));
return;
}
dcesrv_ncacn_accept_step2(ncacn_conn);
}
static void dcesrv_ncacn_accept_step2(struct dcerpc_ncacn_conn *ncacn_conn)
{
char *pipe_name = NULL;
uid_t uid;
gid_t gid;
int rc;
int sys_errno;
enum dcerpc_transport_t transport = dcerpc_binding_get_transport(
ncacn_conn->endpoint->ep_description);
const char *endpoint = dcerpc_binding_get_string_option(
ncacn_conn->endpoint->ep_description, "endpoint");
struct dcesrv_connection *dcesrv_conn = NULL;
NTSTATUS status;
switch (transport) {
case NCACN_IP_TCP:
pipe_name = tsocket_address_string(ncacn_conn->remote_client_addr,
ncacn_conn);
if (pipe_name == NULL) {
DBG_ERR("No memory\n");
ncacn_terminate_connection(ncacn_conn, "No memory");
return;
}
break;
case NCALRPC:
rc = getpeereid(ncacn_conn->sock, &uid, &gid);
if (rc < 0) {
DEBUG(2, ("Failed to get ncalrpc connecting "
"uid - %s!\n", strerror(errno)));
} else {
if (uid == sec_initial_uid()) {
TALLOC_FREE(ncacn_conn->remote_client_addr);
rc = tsocket_address_unix_from_path(ncacn_conn,
AS_SYSTEM_MAGIC_PATH_TOKEN,
&ncacn_conn->remote_client_addr);
if (rc < 0) {
DBG_ERR("No memory\n");
ncacn_terminate_connection(ncacn_conn, "No memory");
return;
}
TALLOC_FREE(ncacn_conn->remote_client_name);
ncacn_conn->remote_client_name
= tsocket_address_unix_path(ncacn_conn->remote_client_addr,
ncacn_conn);
if (ncacn_conn->remote_client_name == NULL) {
DBG_ERR("No memory\n");
ncacn_terminate_connection(ncacn_conn, "No memory");
return;
}
}
}
FALL_THROUGH;
case NCACN_NP:
pipe_name = talloc_strdup(ncacn_conn, endpoint);
if (pipe_name == NULL) {
DBG_ERR("No memory\n");
ncacn_terminate_connection(ncacn_conn, "No memory");
return;
}
break;
default:
DBG_ERR("unknown dcerpc transport: %u!\n", transport);
ncacn_terminate_connection(ncacn_conn,
"Unknown DCE/RPC transport");
return;
}
if (ncacn_conn->session_info == NULL) {
status = make_session_info_anonymous(ncacn_conn,
&ncacn_conn->session_info);
if (!NT_STATUS_IS_OK(status)) {
DBG_ERR("Failed to create anonymous session info: "
"%s\n", nt_errstr(status));
ncacn_terminate_connection(ncacn_conn,
nt_errstr(status));
return;
}
}
rc = make_server_pipes_struct(ncacn_conn,
ncacn_conn->msg_ctx,
pipe_name,
transport,
ncacn_conn->remote_client_addr,
ncacn_conn->local_server_addr,
&ncacn_conn->p,
&sys_errno);
if (rc < 0) {
DBG_ERR("Failed to create pipe struct: %s",
strerror(sys_errno));
ncacn_terminate_connection(ncacn_conn, strerror(sys_errno));
return;
}
/*
* This fills in dcesrv_conn->endpoint with the endpoint
* associated with the socket. From this point on we know
* which (group of) services we are handling, but not the
* specific interface.
*/
status = dcesrv_endpoint_connect(ncacn_conn->dce_ctx,
ncacn_conn,
ncacn_conn->endpoint,
ncacn_conn->session_info,
ncacn_conn->ev_ctx,
DCESRV_CALL_STATE_FLAG_MAY_ASYNC,
&dcesrv_conn);
if (!NT_STATUS_IS_OK(status)) {
DBG_ERR("Failed to connect to endpoint: %s\n",
nt_errstr(status));
ncacn_terminate_connection(ncacn_conn, nt_errstr(status));
return;
}
talloc_set_destructor(dcesrv_conn, dcesrv_connection_destructor);
dcesrv_conn->transport.private_data = ncacn_conn;
dcesrv_conn->transport.report_output_data =
dcesrv_sock_report_output_data;
dcesrv_conn->transport.terminate_connection =
dcesrv_transport_terminate_connection;
dcesrv_conn->send_queue = tevent_queue_create(dcesrv_conn,
"dcesrv send queue");
if (dcesrv_conn->send_queue == NULL) {
status = NT_STATUS_NO_MEMORY;
DBG_ERR("Failed to create send queue: %s\n",
nt_errstr(status));
ncacn_terminate_connection(ncacn_conn, nt_errstr(status));
return;
}
dcesrv_conn->stream = talloc_move(dcesrv_conn, &ncacn_conn->tstream);
dcesrv_conn->local_address = ncacn_conn->local_server_addr;
dcesrv_conn->remote_address = ncacn_conn->remote_client_addr;
status = dcesrv_connection_loop_start(dcesrv_conn);
if (!NT_STATUS_IS_OK(status)) {
DBG_ERR("Failed to start dcesrv_connection loop: %s\n",
nt_errstr(status));
ncacn_terminate_connection(ncacn_conn, nt_errstr(status));
}
DBG_DEBUG("dcerpc_ncacn_accept done\n");
return;
}
void dcerpc_ncacn_packet_process(struct tevent_req *subreq)
{
struct dcerpc_ncacn_conn *ncacn_conn =
tevent_req_callback_data(subreq, struct dcerpc_ncacn_conn);
struct _output_data *out = &ncacn_conn->p->out_data;
DATA_BLOB recv_buffer = data_blob_null;
struct ncacn_packet *pkt;
uint32_t to_send;
size_t i;
NTSTATUS status;
bool ok;
enum dcerpc_transport_t transport = dcerpc_binding_get_transport(
ncacn_conn->endpoint->ep_description);
status = dcerpc_read_ncacn_packet_recv(subreq, ncacn_conn, &pkt, &recv_buffer);
TALLOC_FREE(subreq);
if (!NT_STATUS_IS_OK(status)) {
goto fail;
}
/* dcerpc_read_ncacn_packet_recv() returns a full PDU */
ncacn_conn->p->in_data.pdu_needed_len = 0;
ncacn_conn->p->in_data.pdu = recv_buffer;
if (dcerpc_get_endian_flag(&recv_buffer) & DCERPC_DREP_LE) {
ncacn_conn->p->endian = RPC_LITTLE_ENDIAN;
} else {
ncacn_conn->p->endian = RPC_BIG_ENDIAN;
}
DEBUG(10, ("PDU is in %s Endian format!\n",
ncacn_conn->p->endian ? "Big" : "Little"));
if (DEBUGLEVEL >= 10) {
NDR_PRINT_DEBUG(ncacn_packet, pkt);
}
process_complete_pdu(ncacn_conn->p, pkt);
/* reset pipe state and free PDU */
ncacn_conn->p->in_data.pdu.length = 0;
talloc_free(recv_buffer.data);
talloc_free(pkt);
/*
* This is needed because of the way DCERPC binds work in the RPC
* marshalling code
*/
to_send = out->frag.length - out->current_pdu_sent;
if (to_send > 0) {
DEBUG(10, ("Current_pdu_len = %u, "
"current_pdu_sent = %u "
"Returning %u bytes\n",
(unsigned int)out->frag.length,
(unsigned int)out->current_pdu_sent,
(unsigned int)to_send));
ncacn_conn->iov = talloc_zero(ncacn_conn, struct iovec);
if (ncacn_conn->iov == NULL) {
status = NT_STATUS_NO_MEMORY;
DEBUG(3, ("Out of memory!\n"));
goto fail;
}
ncacn_conn->count = 1;
ncacn_conn->iov[0].iov_base = out->frag.data
+ out->current_pdu_sent;
ncacn_conn->iov[0].iov_len = to_send;
out->current_pdu_sent += to_send;
}
/*
* This condition is false for bind packets, or when we haven't yet got
* a full request, and need to wait for more data from the client
*/
while (out->data_sent_length < out->rdata.length) {
ok = create_next_pdu(ncacn_conn->p);
if (!ok) {
DEBUG(3, ("Failed to create next PDU!\n"));
status = NT_STATUS_UNEXPECTED_IO_ERROR;
goto fail;
}
ncacn_conn->iov = talloc_realloc(ncacn_conn,
ncacn_conn->iov,
struct iovec,
ncacn_conn->count + 1);
if (ncacn_conn->iov == NULL) {
DEBUG(3, ("Out of memory!\n"));
status = NT_STATUS_NO_MEMORY;
goto fail;
}
ncacn_conn->iov[ncacn_conn->count].iov_base = out->frag.data;
ncacn_conn->iov[ncacn_conn->count].iov_len = out->frag.length;
DEBUG(10, ("PDU number: %d, PDU Length: %u\n",
(unsigned int) ncacn_conn->count,
(unsigned int) ncacn_conn->iov[ncacn_conn->count].iov_len));
dump_data(11, (const uint8_t *) ncacn_conn->iov[ncacn_conn->count].iov_base,
ncacn_conn->iov[ncacn_conn->count].iov_len);
ncacn_conn->count++;
}
/*
* We still don't have a complete request, go back and wait for more
* data.
*/
if (ncacn_conn->count == 0) {
/* Wait for the next packet */
subreq = dcerpc_read_ncacn_packet_send(ncacn_conn,
ncacn_conn->ev_ctx,
ncacn_conn->tstream);
if (subreq == NULL) {
DEBUG(2, ("Failed to start receiving packets\n"));
status = NT_STATUS_NO_MEMORY;
goto fail;
}
tevent_req_set_callback(subreq, dcerpc_ncacn_packet_process, ncacn_conn);
return;
}
switch (transport) {
case NCACN_NP:
/* If sending packets over named pipe proxy we need to send
* each fragment on its own to be a message
*/
DBG_DEBUG("Sending %u fragments in a total of %u bytes\n",
(unsigned int)ncacn_conn->count,
(unsigned int)ncacn_conn->p->out_data.data_sent_length);
for (i = 0; i < ncacn_conn->count; i++) {
DBG_DEBUG("Sending PDU number: %d, PDU Length: %u\n",
(unsigned int)i,
(unsigned int)ncacn_conn->iov[i].iov_len);
dump_data(11, (const uint8_t *)ncacn_conn->iov[i].iov_base,
ncacn_conn->iov[i].iov_len);
subreq = tstream_writev_queue_send(ncacn_conn,
ncacn_conn->ev_ctx,
ncacn_conn->tstream,
ncacn_conn->send_queue,
(ncacn_conn->iov + i),
1);
if (subreq == NULL) {
DBG_ERR("Failed to send packet\n");
status = NT_STATUS_NO_MEMORY;
goto fail;
}
tevent_req_set_callback(subreq,
dcerpc_ncacn_packet_done,
ncacn_conn);
}
break;
default:
DBG_DEBUG("Sending a total of %u bytes\n",
(unsigned int)ncacn_conn->p->out_data.data_sent_length);
subreq = tstream_writev_queue_send(ncacn_conn,
ncacn_conn->ev_ctx,
ncacn_conn->tstream,
ncacn_conn->send_queue,
ncacn_conn->iov,
ncacn_conn->count);
if (subreq == NULL) {
DBG_ERR("Failed to send packet\n");
status = NT_STATUS_NO_MEMORY;
goto fail;
}
tevent_req_set_callback(subreq,
dcerpc_ncacn_packet_done,
ncacn_conn);
break;
}
return;
fail:
DEBUG(3, ("Terminating client(%s) connection! - '%s'\n",
ncacn_conn->remote_client_name, nt_errstr(status)));
/* Terminate client connection */
talloc_free(ncacn_conn);
return;
}
static void dcerpc_ncacn_packet_done(struct tevent_req *subreq)
{
struct dcerpc_ncacn_conn *ncacn_conn =
tevent_req_callback_data(subreq, struct dcerpc_ncacn_conn);
NTSTATUS status = NT_STATUS_OK;
int sys_errno;
int rc;
enum dcerpc_transport_t transport = dcerpc_binding_get_transport(
ncacn_conn->endpoint->ep_description);
rc = tstream_writev_queue_recv(subreq, &sys_errno);
TALLOC_FREE(subreq);
if (rc < 0) {
DEBUG(2, ("Writev failed!\n"));
status = map_nt_error_from_unix(sys_errno);
goto fail;
}
if (transport == NCACN_NP &&
tevent_queue_length(ncacn_conn->send_queue) > 0) {
/* More fragments to send before reading a new packet */
return;
}
if (ncacn_conn->p->fault_state != 0) {
DEBUG(2, ("Disconnect after fault\n"));
sys_errno = EINVAL;
goto fail;
}
/* clear out any data that may have been left around */
ncacn_conn->count = 0;
TALLOC_FREE(ncacn_conn->iov);
data_blob_free(&ncacn_conn->p->in_data.data);
data_blob_free(&ncacn_conn->p->out_data.frag);
data_blob_free(&ncacn_conn->p->out_data.rdata);
talloc_free_children(ncacn_conn->p->mem_ctx);
/* Wait for the next packet */
subreq = dcerpc_read_ncacn_packet_send(ncacn_conn,
ncacn_conn->ev_ctx,
ncacn_conn->tstream);
if (subreq == NULL) {
DEBUG(2, ("Failed to start receiving packets\n"));
status = NT_STATUS_NO_MEMORY;
goto fail;
}
tevent_req_set_callback(subreq, dcerpc_ncacn_packet_process, ncacn_conn);
return;
fail:
DEBUG(3, ("Terminating client(%s) connection! - '%s'\n",
ncacn_conn->remote_client_name, nt_errstr(status)));
/* Terminate client connection */
talloc_free(ncacn_conn);
return;
}
NTSTATUS dcesrv_auth_gensec_prepare(TALLOC_CTX *mem_ctx,
struct dcesrv_call_state *call,
struct gensec_security **out)
{
struct gensec_security *gensec = NULL;
NTSTATUS status;
if (out == NULL) {
return NT_STATUS_INVALID_PARAMETER;
}
status = auth_generic_prepare(mem_ctx,
call->conn->remote_address,
call->conn->local_address,
"DCE/RPC",
&gensec);
if (!NT_STATUS_IS_OK(status)) {
DBG_ERR("Failed to prepare gensec: %s\n", nt_errstr(status));
return status;
}
*out = gensec;
return NT_STATUS_OK;
}
void dcesrv_log_successful_authz(struct dcesrv_call_state *call)
{
TALLOC_CTX *frame = talloc_stackframe();
struct auth4_context *auth4_context = NULL;
struct dcesrv_auth *auth = call->auth_state;
enum dcerpc_transport_t transport = dcerpc_binding_get_transport(
call->conn->endpoint->ep_description);
const char *auth_type = derpc_transport_string_by_transport(transport);
const char *transport_protection = AUTHZ_TRANSPORT_PROTECTION_NONE;
NTSTATUS status;
if (frame == NULL) {
DBG_ERR("No memory");
return;
}
if (transport == NCACN_NP) {
transport_protection = AUTHZ_TRANSPORT_PROTECTION_SMB;
}
become_root();
status = make_auth4_context(frame, &auth4_context);
unbecome_root();
if (!NT_STATUS_IS_OK(status)) {
DBG_ERR("Unable to make auth context for authz log.\n");
TALLOC_FREE(frame);
return;
}
/*
* Log the authorization to this RPC interface. This
* covered ncacn_np pass-through auth, and anonymous
* DCE/RPC (eg epmapper, netlogon etc)
*/
log_successful_authz_event(auth4_context->msg_ctx,
auth4_context->lp_ctx,
call->conn->remote_address,
call->conn->local_address,
"DCE/RPC",
auth_type,
transport_protection,
auth->session_info);
auth->auth_audited = true;
TALLOC_FREE(frame);
}
static NTSTATUS dcesrv_assoc_group_new(struct dcesrv_call_state *call,
uint32_t assoc_group_id)
{
struct dcesrv_connection *conn = call->conn;
struct dcesrv_context *dce_ctx = conn->dce_ctx;
const struct dcesrv_endpoint *endpoint = conn->endpoint;
enum dcerpc_transport_t transport =
dcerpc_binding_get_transport(endpoint->ep_description);
struct dcesrv_assoc_group *assoc_group = NULL;
assoc_group = talloc_zero(conn, struct dcesrv_assoc_group);
if (assoc_group == NULL) {
return NT_STATUS_NO_MEMORY;
}
assoc_group->transport = transport;
assoc_group->id = assoc_group_id;
assoc_group->dce_ctx = dce_ctx;
call->conn->assoc_group = assoc_group;
return NT_STATUS_OK;
}
NTSTATUS dcesrv_assoc_group_find(struct dcesrv_call_state *call)
{
uint32_t assoc_group_id = call->pkt.u.bind.assoc_group_id;
/* If not requested by client create a new association group */
if (assoc_group_id == 0) {
assoc_group_id = 0x53F0;
}
return dcesrv_assoc_group_new(call, assoc_group_id);
}
void dcesrv_transport_terminate_connection(struct dcesrv_connection *dce_conn,
const char *reason)
{
struct dcerpc_ncacn_conn *ncacn_conn = talloc_get_type_abort(
dce_conn->transport.private_data,
struct dcerpc_ncacn_conn);
ncacn_terminate_connection(ncacn_conn, reason);
}
static void ncacn_terminate_connection(struct dcerpc_ncacn_conn *conn,
const char *reason)
{
if (reason == NULL) {
reason = "Unknown reason";
}
DBG_NOTICE("Terminating connection - '%s'\n", reason);
talloc_free(conn);
}
NTSTATUS dcesrv_endpoint_by_ncacn_np_name(struct dcesrv_context *dce_ctx,
const char *pipe_name,
struct dcesrv_endpoint **out)
{
struct dcesrv_endpoint *e = NULL;
for (e = dce_ctx->endpoint_list; e; e = e->next) {
enum dcerpc_transport_t transport =
dcerpc_binding_get_transport(e->ep_description);
const char *endpoint = NULL;
if (transport != NCACN_NP) {
continue;
}
endpoint = dcerpc_binding_get_string_option(e->ep_description,
"endpoint");
if (endpoint == NULL) {
continue;
}
if (strncmp(endpoint, "\\pipe\\", 6) == 0) {
endpoint += 6;
}
if (strequal(endpoint, pipe_name)) {
*out = e;
return NT_STATUS_OK;
}
}
return NT_STATUS_OBJECT_NAME_NOT_FOUND;
}
struct pipes_struct *dcesrv_get_pipes_struct(struct dcesrv_connection *conn)
{
struct dcerpc_ncacn_conn *ncacn_conn = talloc_get_type_abort(
conn->transport.private_data,
struct dcerpc_ncacn_conn);
return ncacn_conn->p;
}
/* vim: set ts=8 sw=8 noet cindent syntax=c.doxygen: */