Files
2019-02-16 00:16:52 +01:00

1360 lines
42 KiB
C

/*
Unix SMB/CIFS implementation.
Main DCOM functionality
Copyright (C) 2004 Jelmer Vernooij <jelmer@samba.org>
Copyright (C) 2006 Andrzej Hajda <andrzej.hajda@wp.pl>
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 2 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, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "includes.h"
#include "system/filesys.h"
#include "librpc/gen_ndr/epmapper.h"
#include "librpc/gen_ndr/ndr_remact_c.h"
#include "librpc/gen_ndr/com_dcom.h"
#include "librpc/gen_ndr/dcom.h"
#include "librpc/gen_ndr/ndr_oxidresolver.h"
#include "librpc/gen_ndr/ndr_oxidresolver_c.h"
#include "librpc/gen_ndr/ndr_remact.h"
#include "librpc/gen_ndr/ndr_remact_c.h"
#include "lib/com/dcom/dcom.h"
#include "librpc/rpc/dcerpc_table.h"
#include "lib/util/dlinklist.h"
#include "auth/credentials/credentials.h"
#include "libcli/composite/composite.h"
#include <ctype.h>
#define DCOM_NEGOTIATED_PROTOCOLS { EPM_PROTOCOL_TCP, EPM_PROTOCOL_SMB, EPM_PROTOCOL_NCALRPC }
/*
* Structure used to keep track of the state of an asynchronous DCOM object
* activation request.
*/
struct dcom_activation_state
{
struct composite_context *parent_ctx; /* parent composite context */
struct com_context *com_ctx; /* the COM context */
struct GUID clsid; /* the requested class ID */
struct GUID iid; /* the requester interface ID */
int num_ifaces; /* the requested # of interfaces */
const char *server; /* the server binding string */
struct dcerpc_binding *binding; /* the resolved server binding */
struct COMVERSION negotiated_version; /* the COM version negotiated */
struct IUnknown **ip; /* receives requested interfaces */
WERROR coresult; /* receives the COM result */
};
static NTSTATUS dcerpc_binding_from_STRINGBINDING(TALLOC_CTX *mem_ctx,
struct dcerpc_binding **b_out, struct STRINGBINDING *bd)
{
char *host, *endpoint;
struct dcerpc_binding *b;
b = talloc_zero(mem_ctx, struct dcerpc_binding);
if (!b)
{
return NT_STATUS_NO_MEMORY;
}
b->transport = dcerpc_transport_by_endpoint_protocol(bd->wTowerId);
if (b->transport == -1)
{
DEBUG(1, ("Can't find transport match endpoint protocol %d\n", bd->wTowerId));
talloc_free(b);
return NT_STATUS_NOT_SUPPORTED;
}
host = talloc_strdup(b, bd->NetworkAddr);
endpoint = strchr(host, '[');
if (endpoint)
{
*endpoint = '\0';
endpoint++;
endpoint[strlen(endpoint) - 1] = '\0';
}
b->host = host;
b->endpoint = talloc_strdup(b, endpoint);
*b_out = b;
return NT_STATUS_OK;
}
struct cli_credentials *dcom_get_server_credentials(struct com_context *ctx,
const char *server)
{
struct dcom_server_credentials *c;
struct cli_credentials *d;
d = NULL;
for (c = ctx->dcom->credentials; c; c = c->next)
{
if (c->server == NULL)
{
d = c->credentials;
continue;
}
if (server && !strcmp(c->server, server))
return c->credentials;
}
return d;
}
void dcom_set_server_credentials(struct com_context *ctx, const char *server,
struct cli_credentials *credentials)
{
struct dcom_server_credentials *c;
for (c = ctx->dcom->credentials; c; c = c->next)
{
if ((server == NULL && c->server == NULL) || (server && c->server
&& !strcmp(c->server, server)))
{
if (c->credentials && c->credentials != credentials)
{
talloc_unlink(c, c->credentials);
c->credentials = credentials;
if (talloc_find_parent_bytype(c->credentials, struct dcom_server_credentials))
(void) talloc_reference(c, c->credentials);
else
talloc_steal(c, c->credentials);
}
return;
}
}
c = talloc(ctx->event_ctx, struct dcom_server_credentials);
c->server = talloc_strdup(c, server);
c->credentials = credentials;
if (talloc_find_parent_bytype(c->credentials, struct dcom_server_credentials))
(void) talloc_reference(c, c->credentials);
else
talloc_steal(c, c->credentials);
DLIST_ADD(ctx->dcom->credentials, c);
}
void dcom_update_credentials_for_aliases(struct com_context *ctx,
const char *server, struct DUALSTRINGARRAY *pds)
{
struct cli_credentials *cc;
struct dcerpc_binding *b;
uint32_t i;
NTSTATUS status;
cc = dcom_get_server_credentials(ctx, server);
for (i = 0; pds->stringbindings[i]; ++i)
{
if (pds->stringbindings[i]->wTowerId != EPM_PROTOCOL_TCP)
continue;
status = dcerpc_binding_from_STRINGBINDING(ctx, &b,
pds->stringbindings[i]);
if (!NT_STATUS_IS_OK(status))
continue;
dcom_set_server_credentials(ctx, b->host, cc);
talloc_free(b);
}
}
struct dcom_client_context *dcom_client_init(struct com_context *ctx,
struct cli_credentials *credentials)
{
ctx->dcom = talloc_zero(ctx, struct dcom_client_context);
if (!credentials)
{
credentials = cli_credentials_init(ctx);
cli_credentials_set_conf(credentials);
cli_credentials_parse_string(credentials, "%", CRED_SPECIFIED);
}
dcom_set_server_credentials(ctx, NULL, credentials);
return ctx->dcom;
}
static void dcom_update_server(struct dcom_object_exporter *m,
const char *server)
{
char *c = strchr(server, '[');
if (m->host)
talloc_free(m->host);
m->host = c ? talloc_strndup(m, server, c - server)
: talloc_strdup(m, server);
}
/*
* Complete a remote activation request by receiving the final results and
* updating the updating the local object exporter as needed.
*/
static void remote_activation_complete(struct rpc_request *rpc_req)
{
struct composite_context *c = NULL;
struct RemoteActivation* r = NULL;
struct dcom_activation_state *s = NULL;
struct dcom_object_exporter *m = NULL;
struct IUnknown *ru_template = NULL;
WERROR result;
int i;
/* retrieve the parent composite context */
c = talloc_get_type(rpc_req->async.private, struct composite_context);
if (!NT_STATUS_IS_OK(rpc_req->status))
{
composite_error(c, rpc_req->status);
return;
}
/* retrieve pointers to the RemoteActivation output and our state data */
r = talloc_get_type(rpc_req->ndr.struct_ptr, struct RemoteActivation);
s = talloc_get_type(c->private_data, struct dcom_activation_state);
/* receive the results of the RPC request and display if requested */
c->status = dcerpc_ndr_request_recv(rpc_req);
if (!composite_is_ok(c)) return;
if (DEBUGLVL(9))
NDR_PRINT_OUT_DEBUG(RemoteActivation, r);
/* determine if the remote activation request was successful */
result = *(r->out.hr);
if (!W_ERROR_IS_OK(result))
{
composite_error(c, werror_to_ntstatus(result));
return;
}
m = object_exporter_update_oxid(s->com_ctx,
*(r->out.pOxid), *(r->out.pdsaOxidBindings));
/* build the return interface pointers from the results. */
s->ip = talloc_array(c, struct IUnknown *, s->num_ifaces);
if (composite_nomem(s->ip, c)) return;
for (i = 0; i < s->num_ifaces; i++)
{
s->ip[i] = NULL;
/*
* determine if each individual interface was successfully returned; in
* some cases the overall request may be successful but certain
* interfaces may not be available.
*/
if (W_ERROR_IS_OK(r->out.results[i]))
{
NTSTATUS status = dcom_IUnknown_from_OBJREF(s->com_ctx,
&(s->ip[i]), &(r->out.ifaces[i]->obj));
if (!NT_STATUS_IS_OK(status))
{
r->out.results[i] = ntstatus_to_werror(status);
}
else if (!ru_template)
{
ru_template = s->ip[i];
}
}
}
/*
* do something with the object exporter?! this whole section of code
* desperately needs a rewrite and clarity.
*/
/* TODO:avg check when exactly oxid should be updated,its lifetime etc */
if (m->rem_unknown && memcmp(
&m->rem_unknown->obj.u_objref.u_standard.std.ipid,
r->out.ipidRemUnknown, sizeof(*(r->out.ipidRemUnknown))))
{
talloc_free(m->rem_unknown);
m->rem_unknown = NULL;
}
if (!m->rem_unknown)
{
if (!ru_template)
{
DEBUG(1,("dcom_activate: Cannot Create IRemUnknown - template interface not available\n"));
result = WERR_GENERAL_FAILURE;
// TODO: notice how this failure doesn't get sent back to the caller?
}
m->rem_unknown = talloc_zero(m, struct IRemUnknown);
memcpy(m->rem_unknown, ru_template, sizeof(struct IUnknown));
GUID_from_string(COM_IREMUNKNOWN_UUID, &m->rem_unknown->obj.iid);
m->rem_unknown->obj.u_objref.u_standard.std.ipid
= *(r->out.ipidRemUnknown);
m->rem_unknown->vtable
= (struct IRemUnknown_vtable *) dcom_proxy_vtable_by_iid(
&m->rem_unknown->obj.iid);
/* TODO:avg copy stringbindigs?? */
}
dcom_update_credentials_for_aliases(s->com_ctx, s->server,
*(r->out.pdsaOxidBindings));
dcom_update_server(m, s->server);
composite_done(c);
}
/*
* Continue the remote activation request following a connect by issuing the
* RemoteActivation call after all the preliminary negotiation has been
* completed.
*/
static void remote_activation_continue(struct composite_context *ctx)
{
static uint16_t protseq[] = DCOM_NEGOTIATED_PROTOCOLS;
struct composite_context *c = NULL;
struct dcom_activation_state *s = NULL;
struct dcerpc_pipe *p = NULL;
struct RemoteActivation *r = NULL;
struct rpc_request *rpc_req = NULL;
/* retrieve the parent composite context */
c = talloc_get_type(ctx->async.private_data, struct composite_context);
if (!composite_is_ok(c)) return;
/* retrieve the activation state data */
s = talloc_get_type(c->private_data, struct dcom_activation_state);
/*
* complete the pipe connect and receive a pointer to the dcerpc_pipe
* structure
*/
c->status = dcerpc_pipe_connect_b_recv(ctx, c, &p);
if (!composite_is_ok(c)) return;
/*
* Prepare arguments for the RemoteActivation call. Refer to 3.1.4.1.1.2
* "Issuing the Activation Request" in [MS-DCOM] for more information.
*/
r = talloc_zero(c, struct RemoteActivation);
if (composite_nomem(r, c)) return;
r->in.this.version = s->negotiated_version;
r->in.this.cid = GUID_random(); /* generate a new causality identifier */
r->in.ClientImpLevel = RPC_C_IMP_LEVEL_IDENTIFY;
r->in.Clsid = s->clsid;
r->in.Mode = 0; /* we do not require an interface to the class factory */
r->in.protseq = protseq;
r->in.num_protseqs = ARRAY_SIZE(protseq);
r->in.Interfaces = s->num_ifaces;
r->in.pIIDs = &s->iid;
r->out.that = talloc_zero(r, struct ORPCTHAT);
if (composite_nomem(r->out.that, c)) return;
r->out.pOxid = talloc_zero(r, uint64_t);
if (composite_nomem(r->out.pOxid, c)) return;
r->out.pdsaOxidBindings = talloc_zero(r, struct DUALSTRINGARRAY *);
if (composite_nomem(r->out.pdsaOxidBindings, c)) return;
r->out.ipidRemUnknown = talloc_zero(r, struct GUID);
if (composite_nomem(r->out.ipidRemUnknown, c)) return;
r->out.AuthnHint = talloc_zero(r, uint32_t);
if (composite_nomem(r->out.AuthnHint, c)) return;
r->out.ServerVersion = talloc_zero(r, struct COMVERSION);
if (composite_nomem(r->out.ServerVersion, c)) return;
r->out.hr = talloc_zero(r, WERROR);
if (composite_nomem(r->out.hr, c)) return;
r->out.ifaces = talloc_array(r, struct MInterfacePointer *, s->num_ifaces);
if (composite_nomem(r->out.ifaces, c)) return;
r->out.results = &s->coresult;
/*
* Send the asynchronous RemoteActivation request and setup the
* RPC continuation callback.
*/
rpc_req = dcerpc_RemoteActivation_send(p, c, r);
if (composite_nomem(rpc_req, c)) return;
composite_continue_rpc(c, rpc_req, remote_activation_complete, c);
}
/**
* Continue the RPC IOXIDResolver:ServerAlive call by receiving the
* response and processing the results.
*/
static void determine_rpc_binding_continue2(struct rpc_request *rpc_ctx)
{
struct composite_context *c = NULL;
struct dcom_activation_state *s = NULL;
struct ServerAlive* r = NULL;
struct cli_credentials *creds = NULL;
struct composite_context *pipe_conn_req = NULL;
NTSTATUS status;
/* retrieve the parent composite context */
c = talloc_get_type(rpc_ctx->async.private, struct composite_context);
/* retrieve a pointer to the results structure and our state data */
s = talloc_get_type(c->private_data, struct dcom_activation_state);
r = talloc_get_type(rpc_ctx->ndr.struct_ptr, struct ServerAlive);
/* retrieve the results of the RPC request */
c->status = dcerpc_ndr_request_recv(rpc_ctx);
DEBUG(3, ("dcerpc_ndr_request_recv returned %s\n", nt_errstr(c->status)));
if (!composite_is_ok(c)) return;
status = werror_to_ntstatus(r->out.result);
DEBUG(3, ("IObjectExporter::ServerAlive returned %s\n", nt_errstr(status)));
if (!W_ERROR_IS_OK(r->out.result))
{
composite_error(c, status);
}
if (!composite_is_ok(c)) return;
// lowest common denominator here; ideally we'd just call ServerAlive2
// and do better negotiation on our end...
s->negotiated_version.MajorVersion = COM_MAJOR_VERSION;
s->negotiated_version.MinorVersion = COM_MINOR_VERSION;
/*
* Proceed with the activating request using the same binding that was
* successful for the ServerAlive call and try to retrieve a pipe to
* the IRemoteActivation interface.
*/
creds = dcom_get_server_credentials(s->com_ctx, s->server);
pipe_conn_req = dcerpc_pipe_connect_b_send(c,
s->binding, &dcerpc_table_IRemoteActivation, creds, c->event_ctx);
composite_continue(c, pipe_conn_req, remote_activation_continue, c);
}
/*
* Continue the RPC connect after a successful socket open to the server by
* receiving the results and then attempting to send a
* IOXIDResolver::ServerAlive call if the connect was successful.
*/
static void determine_rpc_binding_continue(struct composite_context *ctx)
{
struct composite_context *c = NULL;
struct dcerpc_pipe* p = NULL;
struct ServerAlive* r = NULL;
struct rpc_request* rpc_ctx = NULL;
/* retrieve the parent composite context */
c = talloc_get_type(ctx->async.private_data, struct composite_context);
/*
* complete the pipe connect and receive a pointer to the dcerpc_pipe
* structure. a failure here is most likely that no RPC server is listening
* on the specified host & port.
*/
c->status = dcerpc_pipe_connect_b_recv(ctx, c, &p);
if (!composite_is_ok(c)) return;
/* prepare arguments for the ServerAlive call and send it off */
r = talloc_zero(c, struct ServerAlive);
if (composite_nomem(r, c)) return;
rpc_ctx = dcerpc_ServerAlive_send(p, c, r);
if (composite_nomem(rpc_ctx, c)) return;
composite_continue_rpc(c, rpc_ctx, determine_rpc_binding_continue2, c);
}
/*
* 3.1.4.1.1.1 Determining RPC Binding Information for Activation
* The client MUST call the IObjectExporter::ServerAlive2 method using the first
* RPC protocol sequence listed in section 3.2.2.3, unless the client COMVERSION
* is less than 5.6, in which case the client MUST call the
* IObjectExporter::ServerAlive method. The client MUST specify the RPC endpoint
* information to contain the remote server name on which the application wants
* to activate the object and the well-known endpoint of the object resolver.
*
* In our case, we're just assuming the lowest common denominator and acting
* like client version 5.1, so we just call ServerAlive instead of ServerAlive2.
*/
static struct composite_context *dcom_determine_rpc_binding(
struct composite_context *parent_ctx, const char* server,
struct dcom_activation_state *activation_state, void *private_data)
{
struct composite_context *c = NULL;
struct cli_credentials* creds = NULL;
struct composite_context *pipe_conn_req = NULL;
NTSTATUS status;
/* composite context allocation and setup */
c = composite_create(parent_ctx, parent_ctx->event_ctx);
if (c == NULL) return NULL;
c->private_data = activation_state;
/*
* determine if the caller specified a server binding, and if not use
* the default
*/
status = dcerpc_parse_binding(c, server, &activation_state->binding);
if (!NT_STATUS_IS_OK(status))
{
/* build a binding string using NCACN_IP_TCP */
char *bindstr = talloc_asprintf(c, "ncacn_ip_tcp:%s", server);
if (composite_nomem(bindstr, c)) return c;
status = dcerpc_parse_binding(c, bindstr, &activation_state->binding);
if (!NT_STATUS_IS_OK(status))
{
DEBUG(0, ("Failed to parse dcerpc binding '%s'\n", bindstr));
composite_error(c, status);
return c;
}
}
else if (activation_state->binding->transport != NCACN_IP_TCP)
{
DEBUG(0, ("Invalid transport specified, only NCACN_IP_TCP "
"supported for DCOM object activation\n"));
composite_error(c, NT_STATUS_RPC_PROTSEQ_NOT_SUPPORTED);
return c;
}
DEBUG(3, ("Using binding %s\n", dcerpc_binding_string(c,
activation_state->binding)));
/*
* create an empty set of credentials since an
* IOXIDResolver:ServerAlive call should never have any authentication
* specified
*/
creds = talloc_zero(c, struct cli_credentials);
if (composite_nomem(creds, c)) return c;
creds->username = "";
creds->password = "";
/*
* start connecting to a rpc pipe after binding structure
* is established
*/
pipe_conn_req = dcerpc_pipe_connect_b_send(c,
activation_state->binding, &dcerpc_table_IOXIDResolver, creds,
parent_ctx->event_ctx);
if (composite_nomem(pipe_conn_req, c)) return c;
composite_continue(c, pipe_conn_req, determine_rpc_binding_continue, c);
return c;
}
/*
* Complete an activation request and continue the composite chain.
*/
static void complete_activation(struct composite_context *ctx)
{
struct composite_context *c = NULL;
struct dcom_activation_state *s = NULL;
c = talloc_get_type(ctx->async.private_data, struct composite_context);
c->status = ctx->status;
if (!composite_is_ok(c)) return;
s = talloc_get_type(ctx->private_data, struct dcom_activation_state);
DEBUG(3, ("Negotiated COM version: %d.%d using binding %s\n",
s->negotiated_version.MajorVersion,
s->negotiated_version.MinorVersion,
dcerpc_binding_string(c, s->binding)));
composite_done(c);
}
/*
* Begin an asynchronous DCOM object activation request.
*
* parent_ctx the parent memory context for the request
* clsid the class ID requested
* server the binding string or server name
* num_ifaces the number of interfaces requested
* iid the interface ID requested
* com_ctx the COM context to use
*/
struct composite_context *dcom_activate_send(TALLOC_CTX *parent_ctx,
const struct GUID *clsid, const char *server, int num_ifaces,
const struct GUID *iid, struct com_context *com_ctx)
{
struct composite_context *c = NULL;
struct dcom_activation_state *s = NULL;
struct composite_context *binding_ctx = NULL;
/* composite context allocation and setup */
c = composite_create(parent_ctx, com_ctx->event_ctx);
if (c == NULL) return NULL;
s = talloc_zero(c, struct dcom_activation_state);
if (composite_nomem(s, c)) return c;
c->private_data = s;
s->parent_ctx = parent_ctx;
s->com_ctx = com_ctx;
s->server = server;
s->clsid = *clsid;
s->iid = *iid;
s->num_ifaces = num_ifaces;
/*
* Begin the DCOM object activation by first attempting to determine the
* correct RPC binding to use and what COM version should be used. This
* requires sending a non-authenticated IOXIDResolver:ServerAlive RPC call
* to the requested server and handling the results. See section 3.1.4.1.1
* of [MS-DCOM] for further information.
*/
binding_ctx = dcom_determine_rpc_binding(c, server, s, c);
if (composite_nomem(binding_ctx, c)) return c;
composite_continue(c, binding_ctx, complete_activation, c);
return c;
}
/*
* Complete an asynchronous DCOM activation request by receiving the results
* and providing the IUknown interface pointers back to the caller.
*/
NTSTATUS dcom_activate_recv(struct composite_context *c,
TALLOC_CTX *parent_ctx, struct IUnknown ***interfaces)
{
NTSTATUS status = composite_wait(c);
if (NT_STATUS_IS_OK(status))
{
struct dcom_activation_state *s = talloc_get_type(c->private_data,
struct dcom_activation_state);
talloc_steal(parent_ctx, s->ip);
*interfaces = s->ip;
status = werror_to_ntstatus(s->coresult);
}
talloc_free(c);
return status;
}
/*
* Synchronous DCOM activation request.
*/
NTSTATUS dcom_activate(struct com_context *com_ctx, TALLOC_CTX *parent_ctx,
const char* server, struct GUID *clsid, struct GUID *iid,
int num_ifaces, struct IUnknown ***interfaces)
{
struct composite_context *c = dcom_activate_send(parent_ctx, clsid, server,
num_ifaces, iid, com_ctx);
return dcom_activate_recv(c, parent_ctx, interfaces);
}
struct dcom_object_exporter *object_exporter_by_oxid(struct com_context *ctx,
uint64_t oxid)
{
struct dcom_object_exporter *ox;
for (ox = ctx->dcom->object_exporters; ox; ox = ox->next)
{
if (ox->oxid == oxid)
{
return ox;
}
}
return NULL;
}
struct dcom_object_exporter *object_exporter_update_oxid(
struct com_context *ctx, uint64_t oxid,
struct DUALSTRINGARRAY *bindings)
{
struct dcom_object_exporter *ox;
ox = object_exporter_by_oxid(ctx, oxid);
if (!ox)
{
ox = talloc_zero(ctx, struct dcom_object_exporter);
DLIST_ADD(ctx->dcom->object_exporters, ox);
ox->oxid = oxid;
}
else
{
talloc_free(ox->bindings);
}
ox->bindings = bindings;
talloc_steal(ox, bindings);
return ox;
}
struct query_interface_state
{
struct dcom_object_exporter *ox;
struct REMQIRESULT *rqir;
uint16_t cIids;
struct GUID *iids;
struct IUnknown **ips;
WERROR result;
};
struct dcom_object_exporter *object_exporter_by_ip(struct com_context *ctx,
struct IUnknown *ip)
{
return object_exporter_by_oxid(ctx, ip->obj.u_objref.u_standard.std.oxid);
}
static void query_interface_continue(struct composite_context *ctx)
{
struct composite_context *c = NULL;
struct query_interface_state *s = NULL;
WERROR result;
/* retrieve the parent composite context */
c = talloc_get_type(ctx->async.private_data, struct composite_context);
s = talloc_get_type(c->private_data, struct query_interface_state);
result = IRemUnknown_RemQueryInterface_recv(ctx, &s->rqir);
if (W_ERROR_IS_OK(result))
{
int i;
struct IUnknown ru;
NTSTATUS status;
s->ips = talloc_array(c, struct IUnknown *, s->cIids);
if (composite_nomem(s->ips, c)) return;
ru = *(struct IUnknown *) s->ox->rem_unknown;
for (i = 0; i < s->cIids; i++)
{
s->ips[i] = NULL;
if (W_ERROR_IS_OK(s->rqir[i].hResult))
{
ru.obj.iid = s->iids[i];
ru.obj.u_objref.u_standard.std = s->rqir[i].std;
status = dcom_IUnknown_from_OBJREF(s->ox->rem_unknown->ctx,
&(s->ips[i]), &ru.obj);
if (!NT_STATUS_IS_OK(status))
{
/*
* TODO: the old code used an array of status result values
* separately from the overall status. Do we really need
* such a distinction?
*/
result = ntstatus_to_werror(status);
}
}
}
}
s->result = result;
c->status = werror_to_ntstatus(result);
composite_done(c);
}
NTSTATUS dcom_query_interface_recv(struct composite_context *c,
TALLOC_CTX *parent_ctx, struct IUnknown ***interfaces)
{
NTSTATUS status = composite_wait(c);
if (NT_STATUS_IS_OK(status))
{
struct query_interface_state *s = NULL;
s = talloc_get_type(c->private_data, struct query_interface_state);
talloc_steal(parent_ctx, s->ips);
*interfaces = s->ips;
status = werror_to_ntstatus(s->result);
}
talloc_free(c);
return status;
}
struct composite_context *dcom_query_interface_send(struct IUnknown *d,
TALLOC_CTX *parent_ctx, uint32_t cRefs, uint16_t cIids,
struct GUID *iids)
{
struct composite_context *c;
struct composite_context *new_ctx;
struct query_interface_state *s;
c = composite_create(parent_ctx, d->ctx->event_ctx);
if (c == NULL) return NULL;
s = talloc_zero(c, struct query_interface_state);
if (composite_nomem(s, c)) return c;
c->private_data = s;
s->ox = object_exporter_by_ip(d->ctx, d);
s->cIids = cIids;
s->iids = talloc_memdup(s, iids, sizeof(struct GUID) * cIids);
if (composite_nomem(s->iids, c)) return c;
new_ctx = IRemUnknown_RemQueryInterface_send(s->ox->rem_unknown, c,
&IUnknown_ipid(d), cRefs, cIids, s->iids);
if (composite_nomem(new_ctx, c)) return c;
composite_continue(c, new_ctx, query_interface_continue, c);
return c;
}
NTSTATUS dcom_query_interface(struct IUnknown *d, TALLOC_CTX *parent_ctx,
uint32_t cRefs, uint16_t cIids, struct GUID *iids,
struct IUnknown ***interfaces)
{
struct composite_context *c = dcom_query_interface_send(d, parent_ctx,
cRefs, cIids, iids);
return dcom_query_interface_recv(c, parent_ctx, interfaces);
}
/*
* Returns true if the provided string is an numeric IP address binding string.
*/
static int is_ip_binding(const char* s)
{
while (*s && (*s != '['))
{
if (isdigit(*s) || *s == '.')
++s;
else
return 0;
}
return 1;
}
/*
* Attempt to find a STRINGBINDING that is similar to the specified host name.
*/
static int find_similar_binding(struct STRINGBINDING **sb, const char *host)
{
const size_t host_len = strlen(host);
int i;
for (i = 0; sb[i] != NULL; ++i)
{
/* only look @ TCP/IP bindings */
if (sb[i]->wTowerId == EPM_PROTOCOL_TCP)
{
const char *s = strchr(sb[i]->NetworkAddr, '[');
if (s != NULL)
{
/*
* see if the hostnames match, but use the shorter of the two
* names in case the user provided a FQDN but the STRINGBINDING
* is only the hostname (the common case)
*/
const size_t addr_len = s - sb[i]->NetworkAddr;
const size_t n = (addr_len < host_len) ? addr_len : host_len;
if (strncasecmp(host, sb[i]->NetworkAddr, n) == 0) break;
}
}
}
return i;
}
NTSTATUS dcom_OBJREF_from_IUnknown(struct OBJREF *o, struct IUnknown *p)
{
/* FIXME: Cache generated objref objects? */
ZERO_STRUCTP(o);
if (!p)
{
o->signature = OBJREF_SIGNATURE;
o->flags = OBJREF_NULL;
}
else
{
*o = p->obj;
switch (o->flags)
{
case OBJREF_CUSTOM:
{
marshal_fn marshal;
marshal = dcom_marshal_by_clsid(&o->u_objref.u_custom.clsid);
if (marshal)
{
return marshal(p, o);
}
else
{
return NT_STATUS_NOT_SUPPORTED;
}
}
}
}
return NT_STATUS_OK;
}
NTSTATUS dcom_IUnknown_from_OBJREF(struct com_context *ctx,
struct IUnknown **_p, struct OBJREF *o)
{
struct IUnknown *p;
struct dcom_object_exporter *ox;
unmarshal_fn unmarshal;
switch (o->flags)
{
case OBJREF_NULL:
*_p = NULL;
return NT_STATUS_OK;
case OBJREF_STANDARD:
p = talloc_zero(ctx, struct IUnknown);
p->ctx = ctx;
p->obj = *o;
p->vtable = dcom_proxy_vtable_by_iid(&o->iid);
if (!p->vtable)
{
DEBUG(0, ("Unable to find proxy class for interface with IID %s\n", GUID_string(ctx, &o->iid)));
return NT_STATUS_NOT_SUPPORTED;
}
p->vtable->Release_send = dcom_release_send;
ox = object_exporter_by_oxid(ctx, o->u_objref.u_standard.std.oxid);
/* FIXME: Add object to list of objects to ping */
*_p = p;
return NT_STATUS_OK;
case OBJREF_HANDLER:
p = talloc_zero(ctx, struct IUnknown);
p->ctx = ctx;
p->obj = *o;
ox = object_exporter_by_oxid(ctx, o->u_objref.u_handler.std.oxid);
/* FIXME: Add object to list of objects to ping */
/*FIXME p->vtable = dcom_vtable_by_clsid(&o->u_objref.u_handler.clsid);*/
/* FIXME: Do the custom unmarshaling call */
*_p = p;
return NT_STATUS_NOT_SUPPORTED;
case OBJREF_CUSTOM:
p = talloc_zero(ctx, struct IUnknown);
p->ctx = ctx;
p->vtable = NULL;
p->obj = *o;
unmarshal = dcom_unmarshal_by_clsid(&o->u_objref.u_custom.clsid);
*_p = p;
if (unmarshal)
{
return unmarshal(o, _p);
}
else
{
return NT_STATUS_NOT_SUPPORTED;
}
}
return NT_STATUS_NOT_SUPPORTED;
}
uint64_t dcom_get_current_oxid(void)
{
return getpid();
}
/*
* State structure used for asynchronous dcom_get_pipe calls.
*/
struct dcom_get_pipe_state
{
struct IUnknown *iface; /* the requested interface */
struct dcom_object_exporter *ox; /* the object exporter for it */
struct dcerpc_pipe *p; /* the final pipe */
int similar_binding_index;
int current_binding_index;
int current_binding_offset;
};
/*
* Complete the alter_context PDU request for an existing pipe so it can be
* used again for the requested interface.
*/
static void reuse_existing_pipe_continue(struct composite_context *ctx)
{
struct composite_context *c = NULL;
c = talloc_get_type(ctx->async.private_data, struct composite_context);
/* nothing to do here except pass along the status of the alter context */
c->status = dcerpc_alter_context_recv(ctx);
if (!composite_is_ok(c)) return;
composite_done(c);
}
/*
* Reuse an existing pipe already on the object exporter. If the pipe isn't
* presently bound to the GUID for the requested interface then an alter_context
* PDU request is sent off, otherwise the pipe is used as-is.
*/
static void reuse_existing_pipe(struct composite_context *c)
{
struct composite_context *new_ctx = NULL;
struct dcom_get_pipe_state *s = NULL;
struct dcerpc_pipe *p = NULL;
s = talloc_get_type(c->private_data, struct dcom_get_pipe_state);
p = s->p = s->ox->pipe;
if (!GUID_equal(&p->syntax.uuid, &s->iface->vtable->iid))
{
s->ox->pipe->syntax.uuid = s->iface->vtable->iid;
new_ctx = dcerpc_alter_context_send(p, c,
&idl_iface_by_uuid(&s->iface->vtable->iid)->syntax_id,
&p->transfer_syntax);
if (composite_nomem(new_ctx, c)) return;
composite_continue(c, new_ctx, reuse_existing_pipe_continue, c);
}
else
{
c->status = NT_STATUS_OK;
composite_done(c);
}
}
/* forward reference */
static void try_next_binding(struct composite_context *c,
struct dcom_get_pipe_state *s);
/*
* Continues a new pipe binding request by determining if the specific binding
* attempt was successful.
*/
static void bind_new_pipe_continue(struct composite_context *ctx)
{
struct composite_context *c = NULL;
struct dcom_get_pipe_state *s = NULL;
struct dcerpc_pipe *p = NULL;
const struct STRINGBINDING *sb = NULL;
NTSTATUS status;
c = talloc_get_type(ctx->async.private_data, struct composite_context);
s = talloc_get_type(c->private_data, struct dcom_get_pipe_state);
sb = s->ox->bindings->stringbindings[s->current_binding_offset];
status = dcerpc_pipe_connect_b_recv(ctx, c, &p);
if (!NT_STATUS_IS_OK(status))
{
DEBUG(9, ("Unable to bind to %s: %s\n", sb->NetworkAddr,
nt_errstr(status)));
try_next_binding(c, s);
}
else
{
DEBUG(9, ("bind_new_pipe_continue: successfully bound to %s\n",
dcerpc_binding_string(c, p->binding)));
s->p = p;
composite_done(c);
}
}
/*
* Try to bind to the next available STRINGBINDING until we finally run out of
* them.
*/
static void try_next_binding(struct composite_context *c,
struct dcom_get_pipe_state *s)
{
struct STRINGBINDING **bindings = s->ox->bindings->stringbindings;
struct STRINGBINDING *sb = NULL;
int try_index = -1;
/* repeat our loop until we reach the end OR we find a binding to try */
while (bindings[s->current_binding_index++] != NULL)
{
/* wrap the offset around if we've reached the end of the bindings */
if (bindings[++s->current_binding_offset] == NULL)
s->current_binding_offset = 0;
sb = bindings[s->current_binding_offset];
/* let's try the similar binding as soon as we see it */
if (s->current_binding_offset == s->similar_binding_index)
{
try_index = s->current_binding_offset;
break;
}
/*
* otherwise, if we've got a non-TCP/IP binding let's just skip it
*/
if (sb->wTowerId != EPM_PROTOCOL_TCP)
{
DEBUG(3, ("dcom_get_pipe: Skipping binding %s\n", sb->NetworkAddr));
continue;
}
/* hey, a binding to try! */
try_index = s->current_binding_offset;
break;
}
if (try_index != -1)
{
struct composite_context *new_ctx = NULL;
struct dcerpc_binding *binding = NULL;
NTSTATUS status;
DEBUG(9, ("dcom_get_pipe: Trying binding %s\n", sb->NetworkAddr));
status = dcerpc_binding_from_STRINGBINDING(s->iface->ctx, &binding, sb);
if (!NT_STATUS_IS_OK(status))
{
DEBUG(1, ("Error parsing string binding %s: %s\n", sb->NetworkAddr,
nt_errstr(status)));
try_next_binding(c, s);
}
else
{
binding->flags |= DCERPC_AUTH_NTLM | DCERPC_SIGN;
if (DEBUGLVL(9)) binding->flags |= DCERPC_DEBUG_PRINT_BOTH;
new_ctx = dcerpc_pipe_connect_b_send(c, binding,
idl_iface_by_uuid(&s->iface->obj.iid),
dcom_get_server_credentials(s->iface->ctx, binding->host),
s->iface->ctx->event_ctx);
if (!composite_nomem(new_ctx, c))
composite_continue(c, new_ctx, bind_new_pipe_continue, c);
}
}
else
{
/* no more bindings left and we never connected, so error time */
/* NOTE: this should effectively never happen */
composite_error(c, NT_STATUS_INVALID_ADDRESS);
}
}
/*
* Allocate a new DCE/RPC pipe by finding the best binding that matches our
* previous connection attempts.
*/
static void bind_new_pipe(struct composite_context *c)
{
struct dcom_get_pipe_state *s = NULL;
struct dcerpc_binding *binding = NULL;
const char *host = NULL;
int similar_index = -1;
NTSTATUS status;
s = talloc_get_type(c->private_data, struct dcom_get_pipe_state);
/*
* First, try and find a similar STRINGBINDING by comparing the specified
* hostname with that the server has provided us. We parse the hostname as
* a binding string first to make sure we have a consistent hostname format
* without any binding prefix.
*/
host = s->ox->host;
status = dcerpc_parse_binding(c, host, &binding);
if (NT_STATUS_IS_OK(status))
{
host = talloc_strdup(c, binding->host);
talloc_free(binding);
}
similar_index = find_similar_binding(s->ox->bindings->stringbindings, host);
DEBUG(1, (__location__": dcom_get_pipe: host=%s, similar=%s\n", host,
s->ox->bindings->stringbindings[similar_index] ? s->ox->bindings->stringbindings[similar_index]->NetworkAddr : "None"));
/*
* Second, set the initial values for the binding offset and index and begin
* the search for a binding that answers. The current_binding_index
* represents an index from 0 until we hit the maximum string binding, but
* the current_binding_offset starts at the similar_index first and then
* wraps as needed. This approach gives us one pass through the
* stringbindings but starts where we think is best.
*/
s->similar_binding_index = similar_index;
s->current_binding_offset = similar_index - 1;
s->current_binding_index = 0;
try_next_binding(c, s);
}
/*
* Asynchronously request a DCERPC pipe for the specified interface pointer.
*/
struct composite_context *dcom_get_pipe_send(struct IUnknown *d,
TALLOC_CTX *parent_ctx)
{
struct composite_context *c = NULL;
struct dcom_get_pipe_state *s = NULL;
/* create a new composite to use for this call sequence */
c = composite_create(0, d->ctx->event_ctx);
if (c == NULL) return NULL;
s = talloc_zero(c, struct dcom_get_pipe_state);
if (composite_nomem(s, c)) return c;
c->private_data = s;
s->iface = d;
/* get the local object exporter for this IUnknown */
s->ox = object_exporter_by_oxid(s->iface->ctx,
s->iface->obj.u_objref.u_standard.std.oxid);
if (s->ox == NULL)
{
DEBUG(0, ("dcom_get_pipe: OXID not found\n"));
composite_error(c, NT_STATUS_NOT_SUPPORTED);
return c;
}
/* release the existing pipe if it is invalid */
if (s->ox->pipe != NULL && s->ox->pipe->last_fault_code)
{
DEBUG(1, ("dcom_get_pipe: pipe's last_fault_code was %08x, freeing\n",
s->ox->pipe->last_fault_code));
talloc_free(s->ox->pipe);
s->ox->pipe = NULL;
}
/* if the object exporter has a valid pipe then reuse it, otherwise... */
if (s->ox->pipe != NULL)
{
reuse_existing_pipe(c);
}
else
{
bind_new_pipe(c);
}
return c;
}
/*
* Receive the results of an asynchronous request to get a DCERPC pipe.
*/
NTSTATUS dcom_get_pipe_recv(struct composite_context *c,
TALLOC_CTX *parent_ctx, struct dcerpc_pipe **pp)
{
struct dcom_get_pipe_state *s = NULL;
NTSTATUS status;
*pp = NULL;
status = composite_wait(c);
if (NT_STATUS_IS_OK(status))
{
s = talloc_get_type(c->private_data, struct dcom_get_pipe_state);
/*
* let the caller own it, but also let the object exporter own a
* reference as well
*/
talloc_steal(parent_ctx, s->p);
*pp = s->p;
talloc_reference(s->ox, s->p);
s->ox->pipe = s->p;
}
talloc_free(c);
return status;
}
/* FIXME:avg put IUnknown_Release_out into header */
struct IUnknown_Release_out
{
uint32_t result;
};
void dcom_release_continue(struct composite_context *cr)
{
struct composite_context *c;
struct IUnknown *d;
struct IUnknown_Release_out *out;
WERROR r;
c = talloc_get_type(cr->async.private_data, struct composite_context);
d = c->private_data;
r = IRemUnknown_RemRelease_recv(cr);
talloc_free(d);
out = talloc_zero(c, struct IUnknown_Release_out);
out->result = W_ERROR_V(r);
c->private_data = out;
composite_done(c);
}
struct composite_context *dcom_release_send(struct IUnknown *d,
TALLOC_CTX *mem_ctx)
{
struct composite_context *c, *cr;
struct REMINTERFACEREF iref;
struct dcom_object_exporter *ox;
c = composite_create(d->ctx, d->ctx->event_ctx);
if (c == NULL)
return NULL;
c->private_data = d;
ox = object_exporter_by_ip(d->ctx, d);
iref.ipid = IUnknown_ipid(d);
iref.cPublicRefs = 5;
iref.cPrivateRefs = 0;
cr = IRemUnknown_RemRelease_send(ox->rem_unknown, mem_ctx, 1, &iref);
composite_continue(c, cr, dcom_release_continue, c);
return c;
}
uint32_t dcom_release_recv(struct composite_context *c)
{
NTSTATUS status;
WERROR r;
status = composite_wait(c);
if (!NT_STATUS_IS_OK(status))
r = ntstatus_to_werror(status);
else
W_ERROR_V(r) = ((struct IUnknown_Release_out *) c->private_data)->result;
talloc_free(c);
return W_ERROR_IS_OK(r) ? 0 : W_ERROR_V(r);
}
uint32_t dcom_release(void *interface, TALLOC_CTX *mem_ctx)
{
struct composite_context *c;
c = dcom_release_send(interface, mem_ctx);
return dcom_release_recv(c);
}
void dcom_proxy_async_call_recv_pipe_send_rpc(struct composite_context *c_pipe)
{
struct composite_context *c;
struct dcom_proxy_async_call_state *s;
struct dcerpc_pipe *p;
struct rpc_request *req;
NTSTATUS status;
c = c_pipe->async.private_data;
s = talloc_get_type(c->private_data, struct dcom_proxy_async_call_state);
status = dcom_get_pipe_recv(c_pipe, c, &p);
if (!NT_STATUS_IS_OK(status))
{
composite_error(c, NT_STATUS_RPC_NT_CALL_FAILED);
return;
}
req = dcerpc_ndr_request_send(p, &s->d->obj.u_objref.u_standard.std.ipid,
s->table, s->opnum, s, s->r);
composite_continue_rpc(c, req, s->continuation, c);
}