root/usr/src/lib/libmlrpc/common/ndr_server.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
 * Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
 * Copyright (c) 2018, Joyent, Inc.
 */

/*
 * Server side RPC handler.
 */

#include <sys/byteorder.h>
#include <sys/uio.h>
#include <errno.h>
#include <synch.h>
#include <stdlib.h>
#include <strings.h>
#include <string.h>
#include <thread.h>

#include <libmlrpc.h>

#define NDR_PIPE_SEND(np, buf, len) \
        ((np)->np_send)((np), (buf), (len))
#define NDR_PIPE_RECV(np, buf, len) \
        ((np)->np_recv)((np), (buf), (len))

static int ndr_svc_process(ndr_xa_t *);
static int ndr_svc_bind(ndr_xa_t *);
static int ndr_svc_request(ndr_xa_t *);
static void ndr_reply_prepare_hdr(ndr_xa_t *);
static int ndr_svc_alter_context(ndr_xa_t *);
static void ndr_reply_fault(ndr_xa_t *, unsigned long);

static int ndr_recv_request(ndr_xa_t *mxa);
static int ndr_recv_frag(ndr_xa_t *mxa);
static int ndr_send_reply(ndr_xa_t *);

static int ndr_pipe_process(ndr_pipe_t *, ndr_xa_t *);

/*
 * External entry point called by smbd.
 */
void
ndr_pipe_worker(ndr_pipe_t *np)
{
        ndr_xa_t        *mxa;
        int rc;

        ndr_svc_binding_pool_init(&np->np_binding, np->np_binding_pool,
            NDR_N_BINDING_POOL);

        if ((mxa = malloc(sizeof (*mxa))) == NULL)
                return;

        do {
                bzero(mxa, sizeof (*mxa));
                rc = ndr_pipe_process(np, mxa);
        } while (rc == 0);

        free(mxa);

        /*
         * Ensure that there are no RPC service policy handles
         * (associated with this fid) left around.
         */
        ndr_hdclose(np);
}

/*
 * Process one server-side RPC request.
 */
static int
ndr_pipe_process(ndr_pipe_t *np, ndr_xa_t *mxa)
{
        ndr_stream_t    *recv_nds;
        ndr_stream_t    *send_nds;
        int             rc = ENOMEM;

        mxa->pipe = np;
        mxa->binding_list = np->np_binding;

        if ((mxa->heap = ndr_heap_create()) == NULL)
                goto out1;

        recv_nds = &mxa->recv_nds;
        rc = nds_initialize(recv_nds, 0, NDR_MODE_CALL_RECV, mxa->heap);
        if (rc != 0)
                goto out2;

        send_nds = &mxa->send_nds;
        rc = nds_initialize(send_nds, 0, NDR_MODE_RETURN_SEND, mxa->heap);
        if (rc != 0)
                goto out3;

        rc = ndr_recv_request(mxa);
        if (rc != 0)
                goto out4;

        (void) ndr_svc_process(mxa);
        (void) ndr_send_reply(mxa);
        rc = 0;

out4:
        nds_destruct(&mxa->send_nds);
out3:
        nds_destruct(&mxa->recv_nds);
out2:
        ndr_heap_destroy(mxa->heap);
out1:
        return (rc);
}

/*
 * Receive an entire RPC request (all fragments)
 * Returns zero or an NDR fault code.
 */
static int
ndr_recv_request(ndr_xa_t *mxa)
{
        ndr_common_header_t     *hdr = &mxa->recv_hdr.common_hdr;
        ndr_stream_t            *nds = &mxa->recv_nds;
        unsigned long           saved_size;
        int                     rc;

        rc = ndr_recv_frag(mxa);
        if (rc != 0)
                return (rc);
        if (!NDR_IS_FIRST_FRAG(hdr->pfc_flags))
                return (NDR_DRC_FAULT_DECODE_FAILED);

        while (!NDR_IS_LAST_FRAG(hdr->pfc_flags)) {
                rc = ndr_recv_frag(mxa);
                if (rc != 0)
                        return (rc);
        }
        nds->pdu_scan_offset = 0;

        /*
         * This whacks nds->pdu_size, so save/restore.
         * It leaves scan_offset after the header.
         */
        saved_size = nds->pdu_size;
        rc = ndr_decode_pdu_hdr(mxa);
        nds->pdu_size = saved_size;

        return (rc);
}

/*
 * Read one fragment, leaving the decoded frag header in
 * recv_hdr.common_hdr, and the data in the recv_nds.
 *
 * Returns zero or an NDR fault code.
 *
 * If a first frag, the header is included in the data
 * placed in recv_nds (because it's not fully decoded
 * until later - we only decode the common part here).
 * Additional frags are placed in the recv_nds without
 * the header, so that after the first frag header,
 * the remaining data will be contiguous.  We do this
 * by simply not advancing the offset in recv_nds after
 * reading and decoding these additional fragments, so
 * the payload of such frags will overwrite what was
 * (temporarily) the frag header.
 */
static int
ndr_recv_frag(ndr_xa_t *mxa)
{
        ndr_common_header_t     *hdr = &mxa->recv_hdr.common_hdr;
        ndr_stream_t            *nds = &mxa->recv_nds;
        unsigned char           *data;
        unsigned long           next_offset;
        unsigned long           pay_size;
        int                     rc;

        /* Make room for the frag header. */
        next_offset = nds->pdu_scan_offset + NDR_RSP_HDR_SIZE;
        if (!NDS_GROW_PDU(nds, next_offset, 0))
                return (NDR_DRC_FAULT_OUT_OF_MEMORY);

        /* Read the frag header. */
        data = nds->pdu_base_addr + nds->pdu_scan_offset;
        rc = NDR_PIPE_RECV(mxa->pipe, data, NDR_RSP_HDR_SIZE);
        if (rc != 0)
                return (NDR_DRC_FAULT_RPCHDR_RECEIVED_RUNT);

        /*
         * Decode the frag header, get the length.
         * NB: It uses nds->pdu_scan_offset
         */
        ndr_decode_frag_hdr(nds, hdr);
        ndr_show_hdr(hdr);
        if (hdr->frag_length < NDR_RSP_HDR_SIZE ||
            hdr->frag_length > mxa->pipe->np_max_xmit_frag)
                return (NDR_DRC_FAULT_DECODE_FAILED);

        if (nds->pdu_scan_offset == 0) {
                /* First frag: header stays in the data. */
                nds->pdu_scan_offset = next_offset;
        } /* else overwrite with the payload */

        /* Make room for the payload. */
        pay_size = hdr->frag_length - NDR_RSP_HDR_SIZE;
        next_offset = nds->pdu_scan_offset + pay_size;
        if (!NDS_GROW_PDU(nds, next_offset, 0))
                return (NDR_DRC_FAULT_OUT_OF_MEMORY);

        /* Read the payload. */
        data = nds->pdu_base_addr + nds->pdu_scan_offset;
        rc = NDR_PIPE_RECV(mxa->pipe, data, pay_size);
        if (rc != 0)
                return (NDR_DRC_FAULT_RPCHDR_RECEIVED_RUNT);
        nds->pdu_scan_offset = next_offset;

        return (NDR_DRC_OK);
}

/*
 * This is the entry point for all server-side RPC processing.
 * It is assumed that the PDU has already been received.
 */
static int
ndr_svc_process(ndr_xa_t *mxa)
{
        int                     rc;

        (void) ndr_reply_prepare_hdr(mxa);

        switch (mxa->ptype) {
        case NDR_PTYPE_BIND:
                rc = ndr_svc_bind(mxa);
                break;

        case NDR_PTYPE_REQUEST:
                rc = ndr_svc_request(mxa);
                break;

        case NDR_PTYPE_ALTER_CONTEXT:
                rc = ndr_svc_alter_context(mxa);
                break;

        default:
                rc = NDR_DRC_FAULT_RPCHDR_PTYPE_INVALID;
                break;
        }

        if (NDR_DRC_IS_FAULT(rc))
                ndr_reply_fault(mxa, rc);

        return (rc);
}

/*
 * Multiple p_cont_elem[]s, multiple transfer_syntaxes[] and multiple
 * p_results[] not supported.
 */
static int
ndr_svc_bind(ndr_xa_t *mxa)
{
        ndr_p_cont_list_t       *cont_list;
        ndr_p_result_list_t     *result_list;
        ndr_p_result_t          *result;
        unsigned                p_cont_id;
        ndr_binding_t           *mbind;
        ndr_uuid_t              *as_uuid;
        ndr_uuid_t              *ts_uuid;
        int                     as_vers;
        int                     ts_vers;
        ndr_service_t           *msvc;
        int                     rc;
        ndr_port_any_t          *sec_addr;

        /* acquire targets */
        cont_list = &mxa->recv_hdr.bind_hdr.p_context_elem;
        result_list = &mxa->send_hdr.bind_ack_hdr.p_result_list;
        result = &result_list->p_results[0];

        /*
         * Set up temporary secondary address port.
         * We will correct this later (below).
         */
        sec_addr = &mxa->send_hdr.bind_ack_hdr.sec_addr;
        sec_addr->length = 13;
        (void) strcpy((char *)sec_addr->port_spec, "\\PIPE\\ntsvcs");

        result_list->n_results = 1;
        result_list->reserved = 0;
        result_list->reserved2 = 0;
        result->result = NDR_PCDR_ACCEPTANCE;
        result->reason = 0;
        bzero(&result->transfer_syntax, sizeof (result->transfer_syntax));

        /* sanity check */
        if (cont_list->n_context_elem != 1 ||
            cont_list->p_cont_elem[0].n_transfer_syn != 1) {
                ndo_trace("ndr_svc_bind: warning: multiple p_cont_elem");
        }

        p_cont_id = cont_list->p_cont_elem[0].p_cont_id;

        if ((mbind = ndr_svc_find_binding(mxa, p_cont_id)) != NULL) {
                /*
                 * Duplicate presentation context id.
                 */
                ndo_trace("ndr_svc_bind: duplicate binding");
                return (NDR_DRC_FAULT_BIND_PCONT_BUSY);
        }

        if ((mbind = ndr_svc_new_binding(mxa)) == NULL) {
                /*
                 * No free binding slot
                 */
                result->result = NDR_PCDR_PROVIDER_REJECTION;
                result->reason = NDR_PPR_LOCAL_LIMIT_EXCEEDED;
                ndo_trace("ndr_svc_bind: no resources");
                return (NDR_DRC_OK);
        }

        as_uuid = &cont_list->p_cont_elem[0].abstract_syntax.if_uuid;
        as_vers = cont_list->p_cont_elem[0].abstract_syntax.if_version;

        ts_uuid = &cont_list->p_cont_elem[0].transfer_syntaxes[0].if_uuid;
        ts_vers = cont_list->p_cont_elem[0].transfer_syntaxes[0].if_version;

        msvc = ndr_svc_lookup_uuid(as_uuid, as_vers, ts_uuid, ts_vers);
        if (msvc == NULL) {
                result->result = NDR_PCDR_PROVIDER_REJECTION;
                result->reason = NDR_PPR_ABSTRACT_SYNTAX_NOT_SUPPORTED;
                return (NDR_DRC_OK);
        }

        /*
         * We can now use the correct secondary address port.
         */
        sec_addr = &mxa->send_hdr.bind_ack_hdr.sec_addr;
        sec_addr->length = strlen(msvc->sec_addr_port) + 1;
        (void) strlcpy((char *)sec_addr->port_spec, msvc->sec_addr_port,
            NDR_PORT_ANY_MAX_PORT_SPEC);

        mbind->p_cont_id = p_cont_id;
        mbind->which_side = NDR_BIND_SIDE_SERVER;
        /* mbind->context set by app */
        mbind->service = msvc;
        mbind->instance_specific = 0;

        mxa->binding = mbind;

        if (msvc->bind_req) {
                /*
                 * Call the service-specific bind() handler.  If
                 * this fails, we shouild send a specific error
                 * on the bind ack.
                 */
                rc = (msvc->bind_req)(mxa);
                if (NDR_DRC_IS_FAULT(rc)) {
                        mbind->service = 0;     /* free binding slot */
                        mbind->which_side = 0;
                        mbind->p_cont_id = 0;
                        mbind->instance_specific = 0;
                        return (rc);
                }
        }

        result->transfer_syntax =
            cont_list->p_cont_elem[0].transfer_syntaxes[0];

        return (NDR_DRC_BINDING_MADE);
}

/*
 * ndr_svc_alter_context
 *
 * The alter context request is used to request additional presentation
 * context for another interface and/or version.  It is very similar to
 * a bind request.
 */
static int
ndr_svc_alter_context(ndr_xa_t *mxa)
{
        ndr_p_result_list_t *result_list;
        ndr_p_result_t *result;
        ndr_p_cont_list_t *cont_list;
        ndr_binding_t *mbind;
        ndr_service_t *msvc;
        unsigned p_cont_id;
        ndr_uuid_t *as_uuid;
        ndr_uuid_t *ts_uuid;
        int as_vers;
        int ts_vers;
        ndr_port_any_t *sec_addr;

        result_list = &mxa->send_hdr.alter_context_rsp_hdr.p_result_list;
        result_list->n_results = 1;
        result_list->reserved = 0;
        result_list->reserved2 = 0;

        result = &result_list->p_results[0];
        result->result = NDR_PCDR_ACCEPTANCE;
        result->reason = 0;
        bzero(&result->transfer_syntax, sizeof (result->transfer_syntax));

        cont_list = &mxa->recv_hdr.alter_context_hdr.p_context_elem;
        p_cont_id = cont_list->p_cont_elem[0].p_cont_id;

        if (ndr_svc_find_binding(mxa, p_cont_id) != NULL)
                return (NDR_DRC_FAULT_BIND_PCONT_BUSY);

        if ((mbind = ndr_svc_new_binding(mxa)) == NULL) {
                result->result = NDR_PCDR_PROVIDER_REJECTION;
                result->reason = NDR_PPR_LOCAL_LIMIT_EXCEEDED;
                return (NDR_DRC_OK);
        }

        as_uuid = &cont_list->p_cont_elem[0].abstract_syntax.if_uuid;
        as_vers = cont_list->p_cont_elem[0].abstract_syntax.if_version;

        ts_uuid = &cont_list->p_cont_elem[0].transfer_syntaxes[0].if_uuid;
        ts_vers = cont_list->p_cont_elem[0].transfer_syntaxes[0].if_version;

        msvc = ndr_svc_lookup_uuid(as_uuid, as_vers, ts_uuid, ts_vers);
        if (msvc == NULL) {
                result->result = NDR_PCDR_PROVIDER_REJECTION;
                result->reason = NDR_PPR_ABSTRACT_SYNTAX_NOT_SUPPORTED;
                return (NDR_DRC_OK);
        }

        mbind->p_cont_id = p_cont_id;
        mbind->which_side = NDR_BIND_SIDE_SERVER;
        /* mbind->context set by app */
        mbind->service = msvc;
        mbind->instance_specific = 0;
        mxa->binding = mbind;

        sec_addr = &mxa->send_hdr.alter_context_rsp_hdr.sec_addr;
        sec_addr->length = 0;
        bzero(sec_addr->port_spec, NDR_PORT_ANY_MAX_PORT_SPEC);

        result->transfer_syntax =
            cont_list->p_cont_elem[0].transfer_syntaxes[0];

        return (NDR_DRC_BINDING_MADE);
}

static int
ndr_svc_request(ndr_xa_t *mxa)
{
        ndr_binding_t   *mbind;
        ndr_service_t   *msvc;
        unsigned        p_cont_id;
        int             rc;

        mxa->opnum = mxa->recv_hdr.request_hdr.opnum;
        p_cont_id = mxa->recv_hdr.request_hdr.p_cont_id;

        if ((mbind = ndr_svc_find_binding(mxa, p_cont_id)) == NULL)
                return (NDR_DRC_FAULT_REQUEST_PCONT_INVALID);

        mxa->binding = mbind;
        msvc = mbind->service;

        /*
         * Make room for the response hdr.
         */
        mxa->send_nds.pdu_scan_offset = NDR_RSP_HDR_SIZE;

        if (msvc->call_stub)
                rc = (*msvc->call_stub)(mxa);
        else
                rc = ndr_generic_call_stub(mxa);

        if (NDR_DRC_IS_FAULT(rc)) {
                ndo_printf(0, 0, "%s[0x%02x]: 0x%04x",
                    msvc->name, mxa->opnum, rc);
        }

        return (rc);
}

/*
 * The transaction and the two nds streams use the same heap, which
 * should already exist at this point.  The heap will also be available
 * to the stub.
 */
int
ndr_generic_call_stub(ndr_xa_t *mxa)
{
        ndr_binding_t           *mbind = mxa->binding;
        ndr_service_t           *msvc = mbind->service;
        ndr_typeinfo_t          *intf_ti = msvc->interface_ti;
        ndr_stub_table_t        *ste;
        int                     opnum = mxa->opnum;
        unsigned                p_len = intf_ti->c_size_fixed_part;
        char                    *param;
        int                     rc;

        if (mxa->heap == NULL) {
                ndo_printf(0, 0, "%s[0x%02x]: no heap", msvc->name, opnum);
                return (NDR_DRC_FAULT_OUT_OF_MEMORY);
        }

        if ((ste = ndr_svc_find_stub(msvc, opnum)) == NULL) {
                ndo_printf(0, 0, "%s[0x%02x]: invalid opnum",
                    msvc->name, opnum);
                return (NDR_DRC_FAULT_REQUEST_OPNUM_INVALID);
        }

        if ((param = ndr_heap_malloc(mxa->heap, p_len)) == NULL)
                return (NDR_DRC_FAULT_OUT_OF_MEMORY);

        bzero(param, p_len);

        rc = ndr_decode_call(mxa, param);
        if (!NDR_DRC_IS_OK(rc))
                return (rc);

        rc = (*ste->func)(param, mxa);
        if (rc == NDR_DRC_OK)
                rc = ndr_encode_return(mxa, param);

        return (rc);
}

/*
 * We can perform some initial setup of the response header here.
 * We also need to cache some of the information from the bind
 * negotiation for use during subsequent RPC calls.
 */
static void
ndr_reply_prepare_hdr(ndr_xa_t *mxa)
{
        ndr_common_header_t *rhdr = &mxa->recv_hdr.common_hdr;
        ndr_common_header_t *hdr = &mxa->send_hdr.common_hdr;

        hdr->rpc_vers = 5;
        hdr->rpc_vers_minor = 0;
        hdr->pfc_flags = NDR_PFC_FIRST_FRAG + NDR_PFC_LAST_FRAG;
        hdr->packed_drep = rhdr->packed_drep;
        hdr->frag_length = 0;
        hdr->auth_length = 0;
        hdr->call_id = rhdr->call_id;
#ifdef _BIG_ENDIAN
        hdr->packed_drep.intg_char_rep = NDR_REPLAB_CHAR_ASCII
            | NDR_REPLAB_INTG_BIG_ENDIAN;
#else
        hdr->packed_drep.intg_char_rep = NDR_REPLAB_CHAR_ASCII
            | NDR_REPLAB_INTG_LITTLE_ENDIAN;
#endif

        switch (mxa->ptype) {
        case NDR_PTYPE_BIND:
                /*
                 * Compute the maximum fragment sizes for xmit/recv
                 * and store in the pipe endpoint.  Note "xmit" is
                 * client-to-server; "recv" is server-to-client.
                 */
                if (mxa->pipe->np_max_xmit_frag >
                    mxa->recv_hdr.bind_hdr.max_xmit_frag)
                        mxa->pipe->np_max_xmit_frag =
                            mxa->recv_hdr.bind_hdr.max_xmit_frag;
                if (mxa->pipe->np_max_recv_frag >
                    mxa->recv_hdr.bind_hdr.max_recv_frag)
                        mxa->pipe->np_max_recv_frag =
                            mxa->recv_hdr.bind_hdr.max_recv_frag;

                hdr->ptype = NDR_PTYPE_BIND_ACK;
                mxa->send_hdr.bind_ack_hdr.max_xmit_frag =
                    mxa->pipe->np_max_xmit_frag;
                mxa->send_hdr.bind_ack_hdr.max_recv_frag =
                    mxa->pipe->np_max_recv_frag;

                /*
                 * We're supposed to assign a unique "assoc group"
                 * (identifies this connection for the client).
                 * Using the pipe address is adequate.
                 */
                mxa->send_hdr.bind_ack_hdr.assoc_group_id =
                    mxa->recv_hdr.bind_hdr.assoc_group_id;
                if (mxa->send_hdr.bind_ack_hdr.assoc_group_id == 0)
                        mxa->send_hdr.bind_ack_hdr.assoc_group_id =
                            (DWORD)(uintptr_t)mxa->pipe;

                break;

        case NDR_PTYPE_REQUEST:
                hdr->ptype = NDR_PTYPE_RESPONSE;
                /* mxa->send_hdr.response_hdr.alloc_hint */
                mxa->send_hdr.response_hdr.p_cont_id =
                    mxa->recv_hdr.request_hdr.p_cont_id;
                mxa->send_hdr.response_hdr.cancel_count = 0;
                mxa->send_hdr.response_hdr.reserved = 0;
                break;

        case NDR_PTYPE_ALTER_CONTEXT:
                hdr->ptype = NDR_PTYPE_ALTER_CONTEXT_RESP;
                /*
                 * The max_xmit_frag, max_recv_frag and assoc_group_id are
                 * ignored by the client but it's useful to fill them in.
                 */
                mxa->send_hdr.alter_context_rsp_hdr.max_xmit_frag =
                    mxa->recv_hdr.alter_context_hdr.max_xmit_frag;
                mxa->send_hdr.alter_context_rsp_hdr.max_recv_frag =
                    mxa->recv_hdr.alter_context_hdr.max_recv_frag;
                mxa->send_hdr.alter_context_rsp_hdr.assoc_group_id =
                    mxa->recv_hdr.alter_context_hdr.assoc_group_id;
                break;

        default:
                hdr->ptype = 0xFF;
        }
}

/*
 * Signal an RPC fault. The stream is reset and we overwrite whatever
 * was in the response header with the fault information.
 */
static void
ndr_reply_fault(ndr_xa_t *mxa, unsigned long drc)
{
        ndr_common_header_t *rhdr = &mxa->recv_hdr.common_hdr;
        ndr_common_header_t *hdr = &mxa->send_hdr.common_hdr;
        ndr_stream_t *nds = &mxa->send_nds;
        unsigned long fault_status;

        (void) NDS_RESET(nds);

        hdr->rpc_vers = 5;
        hdr->rpc_vers_minor = 0;
        hdr->pfc_flags = NDR_PFC_FIRST_FRAG + NDR_PFC_LAST_FRAG;
        hdr->packed_drep = rhdr->packed_drep;
        hdr->frag_length = sizeof (mxa->send_hdr.fault_hdr);
        hdr->auth_length = 0;
        hdr->call_id = rhdr->call_id;
#ifdef _BIG_ENDIAN
        hdr->packed_drep.intg_char_rep = NDR_REPLAB_CHAR_ASCII
            | NDR_REPLAB_INTG_BIG_ENDIAN;
#else
        hdr->packed_drep.intg_char_rep = NDR_REPLAB_CHAR_ASCII
            | NDR_REPLAB_INTG_LITTLE_ENDIAN;
#endif

        switch (drc & NDR_DRC_MASK_SPECIFIER) {
        case NDR_DRC_FAULT_OUT_OF_MEMORY:
        case NDR_DRC_FAULT_ENCODE_TOO_BIG:
                fault_status = NDR_FAULT_NCA_OUT_ARGS_TOO_BIG;
                break;

        case NDR_DRC_FAULT_REQUEST_PCONT_INVALID:
                fault_status = NDR_FAULT_NCA_INVALID_PRES_CONTEXT_ID;
                break;

        case NDR_DRC_FAULT_REQUEST_OPNUM_INVALID:
                fault_status = NDR_FAULT_NCA_OP_RNG_ERROR;
                break;

        case NDR_DRC_FAULT_DECODE_FAILED:
        case NDR_DRC_FAULT_ENCODE_FAILED:
                fault_status = NDR_FAULT_NCA_PROTO_ERROR;
                break;

        default:
                fault_status = NDR_FAULT_NCA_UNSPEC_REJECT;
                break;
        }

        mxa->send_hdr.fault_hdr.common_hdr.ptype = NDR_PTYPE_FAULT;
        mxa->send_hdr.fault_hdr.status = fault_status;
        mxa->send_hdr.response_hdr.alloc_hint = hdr->frag_length;
}

/*
 * Note that the frag_length for bind ack and alter context is
 * non-standard.
 */
static int
ndr_send_reply(ndr_xa_t *mxa)
{
        ndr_common_header_t *hdr = &mxa->send_hdr.common_hdr;
        ndr_stream_t *nds = &mxa->send_nds;
        uint8_t *pdu_buf;
        unsigned long pdu_size;
        unsigned long frag_size;
        unsigned long pdu_data_size;
        unsigned long frag_data_size;

        frag_size = mxa->pipe->np_max_recv_frag;
        pdu_size = nds->pdu_size;
        pdu_buf = nds->pdu_base_addr;

        if (pdu_size <= frag_size) {
                /*
                 * Single fragment response. The PDU size may be zero
                 * here (i.e. bind or fault response). So don't make
                 * any assumptions about it until after the header is
                 * encoded.
                 */
                switch (hdr->ptype) {
                case NDR_PTYPE_BIND_ACK:
                        hdr->frag_length = ndr_bind_ack_hdr_size(mxa);
                        break;

                case NDR_PTYPE_FAULT:
                        /* already setup */
                        break;

                case NDR_PTYPE_RESPONSE:
                        hdr->frag_length = pdu_size;
                        mxa->send_hdr.response_hdr.alloc_hint =
                            hdr->frag_length;
                        break;

                case NDR_PTYPE_ALTER_CONTEXT_RESP:
                        hdr->frag_length = ndr_alter_context_rsp_hdr_size();
                        break;

                default:
                        hdr->frag_length = pdu_size;
                        break;
                }

                nds->pdu_scan_offset = 0;
                (void) ndr_encode_pdu_hdr(mxa);
                pdu_size = nds->pdu_size;
                (void) NDR_PIPE_SEND(mxa->pipe, pdu_buf, pdu_size);
                return (0);
        }

        /*
         * Multiple fragment response.
         *
         * We need to update the RPC header for every fragment.
         *
         * pdu_data_size:       total data remaining to be handled
         * frag_size:           total fragment size including header
         * frag_data_size:      data in fragment
         *                      (i.e. frag_size - NDR_RSP_HDR_SIZE)
         */
        pdu_data_size = pdu_size - NDR_RSP_HDR_SIZE;
        frag_data_size = frag_size - NDR_RSP_HDR_SIZE;

        /*
         * Send the first frag.
         */
        hdr->pfc_flags = NDR_PFC_FIRST_FRAG;
        hdr->frag_length = frag_size;
        mxa->send_hdr.response_hdr.alloc_hint = pdu_data_size;
        nds->pdu_scan_offset = 0;
        (void) ndr_encode_pdu_hdr(mxa);
        (void) NDR_PIPE_SEND(mxa->pipe, pdu_buf, frag_size);
        pdu_data_size -= frag_data_size;
        pdu_buf += frag_data_size;

        /*
         * Send "middle" (full-sized) fragments...
         */
        hdr->pfc_flags = 0;
        while (pdu_data_size > frag_data_size) {

                hdr->frag_length = frag_size;
                mxa->send_hdr.response_hdr.alloc_hint = pdu_data_size;
                nds->pdu_scan_offset = 0;
                (void) ndr_encode_pdu_hdr(mxa);
                bcopy(nds->pdu_base_addr, pdu_buf, NDR_RSP_HDR_SIZE);
                (void) NDR_PIPE_SEND(mxa->pipe, pdu_buf, frag_size);
                pdu_data_size -= frag_data_size;
                pdu_buf += frag_data_size;
        }

        /*
         * Last frag (pdu_data_size <= frag_data_size)
         */
        hdr->pfc_flags = NDR_PFC_LAST_FRAG;
        frag_size = pdu_data_size + NDR_RSP_HDR_SIZE;
        hdr->frag_length = frag_size;
        mxa->send_hdr.response_hdr.alloc_hint = pdu_data_size;
        nds->pdu_scan_offset = 0;
        (void) ndr_encode_pdu_hdr(mxa);
        bcopy(nds->pdu_base_addr, pdu_buf, NDR_RSP_HDR_SIZE);
        (void) NDR_PIPE_SEND(mxa->pipe, pdu_buf, frag_size);

        return (0);
}