/*
 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);
}

