/*
 * 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 2021 Tintri by DDN, Inc. All rights reserved.
 */

#include <assert.h>
#include <strings.h>
#include <sys/param.h>
#include <sys/debug.h>

#include <libmlrpc.h>

#ifdef _BIG_ENDIAN
static const int ndr_native_byte_order = NDR_REPLAB_INTG_BIG_ENDIAN;
#else
static const int ndr_native_byte_order = NDR_REPLAB_INTG_LITTLE_ENDIAN;
#endif

static int ndr_decode_hdr_common(ndr_stream_t *, ndr_common_header_t *);
static int ndr_decode_pac_hdr(ndr_stream_t *, ndr_pac_hdr_t *);

/*
 * This is the layout of an RPC PDU, as shown in
 * [MS-RPCE] 2.2.2.13 "Verification Trailer".
 *
 *	+-------------------------------+
 *	|       PDU Header              |
 *	+-------------------------------+ ====
 *	|       Stub Data               |
 *	+-------------------------------+ PDU
 *	|       Stub Padding Octets     |
 *	+-------------------------------+ Body
 *	|       Verification Trailer    |
 *	+-------------------------------+ Here
 *	|       Authentication Padding  |
 *	+-------------------------------+ ====
 *	|       sec_trailer             |
 *	+-------------------------------+
 *	|       Authentication Token    |
 *	+-------------------------------+
 *
 * We don't use the "Verification Trailer" for anything yet.
 * sec_trailer and Authentication Token are for Secure RPC,
 * and are collectively the 'auth_verifier_co' in DCERPC.
 *
 * Each fragment of a multi-fragment response has a unique
 * header and, if authentication was requested, a unique
 * sec_trailer.
 */

static int
ndr_convert_nds_error(ndr_stream_t *nds)
{
	int rc;

	switch (nds->error) {
	case NDR_ERR_MALLOC_FAILED:
		rc = NDR_DRC_FAULT_OUT_OF_MEMORY;
		break;

	case NDR_ERR_SWITCH_VALUE_INVALID:
		rc = NDR_DRC_FAULT_PARAM_0_INVALID;
		break;

	case NDR_ERR_UNDERFLOW:
		rc = NDR_DRC_FAULT_RECEIVED_RUNT;
		break;

	case NDR_ERR_GROW_FAILED:
		rc = NDR_DRC_FAULT_ENCODE_TOO_BIG;
		break;

	default:
		if (nds->m_op == NDR_M_OP_MARSHALL)
			rc = NDR_DRC_FAULT_ENCODE_FAILED;
		else
			rc = NDR_DRC_FAULT_DECODE_FAILED;
		break;
	}

	return (rc);
}

static int
ndr_encode_decode_common(ndr_stream_t *nds, unsigned opnum,
    ndr_typeinfo_t *ti, void *datum)
{
	/*
	 * Perform the (un)marshalling
	 */
	if (ndo_operation(nds, ti, opnum, datum))
		return (NDR_DRC_OK);

	return (ndr_convert_nds_error(nds));
}

static int
ndr_encode_decode_type(ndr_stream_t *nds, ndr_typeinfo_t *ti, void *datum)
{
	/*
	 * Perform the (un)marshalling
	 */
	if (ndo_process(nds, ti, datum))
		return (NDR_DRC_OK);

	return (ndr_convert_nds_error(nds));
}

ndr_buf_t *
ndr_buf_init(ndr_typeinfo_t *ti)
{
	ndr_buf_t		*nbuf;

	if ((nbuf = calloc(1, sizeof (ndr_buf_t))) == NULL)
		return (NULL);

	if ((nbuf->nb_heap = ndr_heap_create()) == NULL) {
		free(nbuf);
		return (NULL);
	}

	nbuf->nb_ti = ti;
	nbuf->nb_magic = NDR_BUF_MAGIC;
	return (nbuf);
}

void
ndr_buf_fini(ndr_buf_t *nbuf)
{
	assert(nbuf->nb_magic == NDR_BUF_MAGIC);

	nds_destruct(&nbuf->nb_nds);
	ndr_heap_destroy(nbuf->nb_heap);
	nbuf->nb_magic = 0;
	free(nbuf);
}

/*
 * Decode an NDR encoded buffer.  The buffer is expected to contain
 * a single fragment packet with a valid PDU header followed by NDR
 * encoded data.  The structure to which result points should be
 * of the appropriate type to hold the decoded output.  For example:
 *
 *	pac_info_t info;
 *
 *	if ((nbuf = ndr_buf_init(&TYPEINFO(ndr_pac)) != NULL) {
 *		rc = ndr_decode_buf(nbuf, opnum, data, datalen, &info);
 *		...
 *		ndr_buf_fini(nbuf);
 *	}
 */
int
ndr_buf_decode(ndr_buf_t *nbuf, unsigned hdr_type, unsigned opnum,
    const char *data, size_t datalen, void *result)
{
	ndr_common_header_t	hdr;
	ndr_pac_hdr_t		pac_hdr;
	unsigned		pdu_size_hint;
	int			rc;

	assert(nbuf->nb_magic == NDR_BUF_MAGIC);
	assert(nbuf->nb_heap != NULL);
	assert(nbuf->nb_ti != NULL);

	if (datalen < NDR_PDU_SIZE_HINT_DEFAULT)
		pdu_size_hint = NDR_PDU_SIZE_HINT_DEFAULT;
	else
		pdu_size_hint = datalen;

	rc = nds_initialize(&nbuf->nb_nds, pdu_size_hint, NDR_MODE_BUF_DECODE,
	    nbuf->nb_heap);
	if (NDR_DRC_IS_FAULT(rc))
		return (rc);

	bcopy(data, nbuf->nb_nds.pdu_base_addr, datalen);
	nbuf->nb_nds.pdu_size = datalen;

	switch (hdr_type) {
	case NDR_PTYPE_COMMON:
		rc = ndr_decode_hdr_common(&nbuf->nb_nds, &hdr);
		if (NDR_DRC_IS_FAULT(rc))
			return (rc);

		if (!NDR_IS_SINGLE_FRAG(hdr.pfc_flags))
			return (NDR_DRC_FAULT_DECODE_FAILED);
		break;

	case NDR_PTYPE_PAC:
		rc = ndr_decode_pac_hdr(&nbuf->nb_nds, &pac_hdr);
		if (NDR_DRC_IS_FAULT(rc))
			return (rc);

		if (pac_hdr.common_hdr.hdrlen != sizeof (ndr_serialtype1_hdr_t))
			return (NDR_DRC_FAULT_DECODE_FAILED);
		break;

	default:
		return (NDR_ERR_UNIMPLEMENTED);
	}

	rc = ndr_encode_decode_common(&nbuf->nb_nds, opnum, nbuf->nb_ti,
	    result);
	return (rc);
}

/*
 * Use the receive stream to unmarshall data (NDR_MODE_CALL_RECV).
 */
int
ndr_decode_call(ndr_xa_t *mxa, void *params)
{
	ndr_stream_t	*nds = &mxa->recv_nds;
	int		rc;

	if (!NDR_MODE_MATCH(nds, NDR_MODE_CALL_RECV))
		return (NDR_DRC_FAULT_MODE_MISMATCH);

	rc = ndr_encode_decode_common(nds, mxa->opnum,
	    mxa->binding->service->interface_ti, params);

	return (rc + NDR_PTYPE_REQUEST);
}

/*
 * Use the send stream to marshall data (NDR_MODE_RETURN_SEND).
 */
int
ndr_encode_return(ndr_xa_t *mxa, void *params)
{
	ndr_stream_t	*nds = &mxa->send_nds;
	int		rc;

	if (!NDR_MODE_MATCH(nds, NDR_MODE_RETURN_SEND))
		return (NDR_DRC_FAULT_MODE_MISMATCH);

	rc = ndr_encode_decode_common(nds, mxa->opnum,
	    mxa->binding->service->interface_ti, params);

	return (rc + NDR_PTYPE_RESPONSE);
}

/*
 * Use the send stream to marshall data (NDR_MODE_CALL_SEND).
 */
int
ndr_encode_call(ndr_xa_t *mxa, void *params)
{
	ndr_stream_t	*nds = &mxa->send_nds;
	int		rc;

	if (!NDR_MODE_MATCH(nds, NDR_MODE_CALL_SEND))
		return (NDR_DRC_FAULT_MODE_MISMATCH);

	rc = ndr_encode_decode_common(nds, mxa->opnum,
	    mxa->binding->service->interface_ti, params);

	return (rc + NDR_PTYPE_REQUEST);
}

/*
 * Use the receive stream to unmarshall data (NDR_MODE_RETURN_RECV).
 */
int
ndr_decode_return(ndr_xa_t *mxa, void *params)
{
	ndr_stream_t	*nds = &mxa->recv_nds;
	int		rc;

	if (!NDR_MODE_MATCH(nds, NDR_MODE_RETURN_RECV))
		return (NDR_DRC_FAULT_MODE_MISMATCH);

	rc = ndr_encode_decode_common(nds, mxa->opnum,
	    mxa->binding->service->interface_ti, params);

	return (rc + NDR_PTYPE_RESPONSE);
}

int
ndr_decode_pdu_hdr(ndr_xa_t *mxa)
{
	ndr_common_header_t	*hdr = &mxa->recv_hdr.common_hdr;
	ndr_stream_t		*nds = &mxa->recv_nds;
	int			rc;
	ulong_t			saved_offset;

	saved_offset = nds->pdu_scan_offset;
	rc = ndr_decode_hdr_common(nds, hdr);
	if (NDR_DRC_IS_FAULT(rc))
		return (rc);

	/*
	 * Verify the protocol version.
	 */
	if ((hdr->rpc_vers != 5) || (hdr->rpc_vers_minor != 0))
		return (NDR_DRC_FAULT_RPCHDR_DECODE_FAILED);

	mxa->ptype = hdr->ptype;
	/* pdu_scan_offset now points to (this fragment's) stub data */
	nds->pdu_body_offset = nds->pdu_scan_offset;
	nds->pdu_hdr_size = nds->pdu_scan_offset - saved_offset;
	nds->pdu_body_size = hdr->frag_length - hdr->auth_length -
	    nds->pdu_hdr_size -
	    ((hdr->auth_length != 0) ? SEC_TRAILER_SIZE : 0);

	if (hdr->auth_length != 0 && hdr->auth_length >
	    (hdr->frag_length - nds->pdu_hdr_size - SEC_TRAILER_SIZE))
		return (NDR_DRC_FAULT_RECEIVED_MALFORMED);
	return (NDR_DRC_OK);
}

static int
ndr_decode_hdr_common(ndr_stream_t *nds, ndr_common_header_t *hdr)
{
	int			ptype;
	int			rc;
	int			charset;
	int			byte_order;
	ulong_t			saved_offset;

	if (nds->m_op != NDR_M_OP_UNMARSHALL)
		return (NDR_DRC_FAULT_RPCHDR_MODE_MISMATCH);

	/*
	 * All PDU headers are at least this big
	 */
	saved_offset = nds->pdu_scan_offset;
	if ((nds->pdu_size - saved_offset) < sizeof (ndr_common_header_t))
		return (NDR_DRC_FAULT_RPCHDR_RECEIVED_RUNT);

	/*
	 * Peek at the first eight bytes to figure out what we're doing.
	 */
	rc = NDS_GET_PDU(nds, 0, 8, (char *)hdr, 0, 0);
	if (!rc)
		return (NDR_DRC_FAULT_RPCHDR_DECODE_FAILED);

	/*
	 * Check for ASCII as the character set.  This is an ASCII
	 * versus EBCDIC option and has nothing to do with Unicode.
	 */
	charset = hdr->packed_drep.intg_char_rep & NDR_REPLAB_CHAR_MASK;
	if (charset != NDR_REPLAB_CHAR_ASCII)
		return (NDR_DRC_FAULT_RPCHDR_DECODE_FAILED);

	/*
	 * Set the byte swap flag if the PDU byte-order
	 * is different from the local byte-order.
	 */
	byte_order = hdr->packed_drep.intg_char_rep & NDR_REPLAB_INTG_MASK;
	nds->swap = (byte_order != ndr_native_byte_order) ? 1 : 0;

	ptype = hdr->ptype;
	if (ptype == NDR_PTYPE_REQUEST &&
	    (hdr->pfc_flags & NDR_PFC_OBJECT_UUID) != 0) {
		ptype = NDR_PTYPE_REQUEST_WITH;	/* fake for sizing */
	}

	rc = ndr_encode_decode_common(nds, ptype, &TYPEINFO(ndr_hdr), hdr);

	if (hdr->frag_length > (nds->pdu_size - saved_offset))
		rc = NDR_DRC_FAULT_RECEIVED_MALFORMED;
	return (NDR_DRC_PTYPE_RPCHDR(rc));
}

static int
ndr_decode_pac_hdr(ndr_stream_t *nds, ndr_pac_hdr_t *hdr)
{
	int	rc;

	if (nds->m_op != NDR_M_OP_UNMARSHALL)
		return (NDR_DRC_FAULT_RPCHDR_MODE_MISMATCH);

	/*
	 * All PDU headers are at least this big
	 */
	if ((nds->pdu_size - nds->pdu_scan_offset) < sizeof (ndr_pac_hdr_t))
		return (NDR_DRC_FAULT_RPCHDR_RECEIVED_RUNT);

	/*
	 * Peek at the first eight bytes to figure out what we're doing.
	 */
	rc = NDS_GET_PDU(nds, 0, 8, (char *)hdr, 0, 0);
	if (!rc)
		return (NDR_DRC_FAULT_RPCHDR_DECODE_FAILED);

	/* Must be set to 1 to indicate type serialization version 1. */
	if (hdr->common_hdr.version != 1)
		return (NDR_DRC_FAULT_RPCHDR_DECODE_FAILED);

	/*
	 * Set the byte swap flag if the PDU byte-order
	 * is different from the local byte-order.
	 */
	nds->swap =
	    (hdr->common_hdr.endianness != ndr_native_byte_order) ? 1 : 0;

	rc = ndr_encode_decode_common(nds, NDR_PTYPE_PAC,
	    &TYPEINFO(ndr_hdr), hdr);

	return (NDR_DRC_PTYPE_RPCHDR(rc));
}

CTASSERT(sizeof (ndr_common_header_t) >= NDR_CMN_HDR_SIZE);

/*
 * Decode an RPC fragment header.  Use ndr_decode_pdu_hdr() to process
 * the first fragment header then this function to process additional
 * fragment headers.
 */
void
ndr_decode_frag_hdr(ndr_stream_t *nds, ndr_common_header_t *hdr)
{
	ndr_common_header_t *tmp;
	uint8_t *pdu;
	int byte_order;

	pdu = (uint8_t *)nds->pdu_base_offset + nds->pdu_scan_offset;
	bcopy(pdu, hdr, NDR_CMN_HDR_SIZE);

	/*
	 * Swap non-byte fields if the PDU byte-order
	 * is different from the local byte-order.
	 */
	byte_order = hdr->packed_drep.intg_char_rep & NDR_REPLAB_INTG_MASK;

	if (byte_order != ndr_native_byte_order) {
		/*LINTED E_BAD_PTR_CAST_ALIGN*/
		tmp = (ndr_common_header_t *)pdu;

		nds_bswap(&tmp->frag_length, &hdr->frag_length,
		    sizeof (WORD));
		nds_bswap(&tmp->auth_length, &hdr->auth_length,
		    sizeof (WORD));
		nds_bswap(&tmp->call_id, &hdr->call_id, sizeof (DWORD));
	}

	/* pdu_scan_offset points to byte 0 of this fragment */
	nds->pdu_hdr_size = NDR_RSP_HDR_SIZE;
	nds->pdu_body_offset = nds->pdu_scan_offset + nds->pdu_hdr_size;
	nds->pdu_body_size = hdr->frag_length - hdr->auth_length -
	    nds->pdu_hdr_size -
	    ((hdr->auth_length != 0) ? SEC_TRAILER_SIZE : 0);
}

/*
 * Remove an RPC fragment header from the received data stream.
 *
 * NDR stream on entry:
 *
 *                |<--- frag --->|
 * +-----+--------+-----+--------+-----+---------+-----+
 * | hdr |  data  | hdr |  data  | hdr |  data   | ... |
 * +-----+--------+-----+--------+-----+---------+-----+
 *                 <----
 *
 * NDR stream on return:
 *
 * +-----+----------------+-----+---------+-----+
 * | hdr |       data     | hdr |  data   | ... |
 * +-----+----------------+-----+---------+-----+
 */
void
ndr_remove_frag_hdr(ndr_stream_t *nds)
{
	char	*hdr;
	char	*data;
	int	nbytes;

	hdr = (char *)nds->pdu_base_offset + nds->pdu_scan_offset;
	data = hdr + NDR_RSP_HDR_SIZE;
	nbytes = nds->pdu_size - nds->pdu_scan_offset - NDR_RSP_HDR_SIZE;

	/*
	 * Move all of the data after the header back to where the header began.
	 */
	memmove(hdr, data, nbytes);
	nds->pdu_size -= NDR_RSP_HDR_SIZE;
}

void
ndr_show_hdr(ndr_common_header_t *hdr)
{
	char	*fragtype;

	if (hdr == NULL) {
		ndo_printf(NULL, NULL, "ndr hdr: <null>");
		return;
	}

	if (NDR_IS_SINGLE_FRAG(hdr->pfc_flags))
		fragtype = "single";
	else if (NDR_IS_FIRST_FRAG(hdr->pfc_flags))
		fragtype = "first";
	else if (NDR_IS_LAST_FRAG(hdr->pfc_flags))
		fragtype = "last";
	else
		fragtype = "intermediate";

	ndo_printf(NULL, NULL,
	    "ndr hdr: %d.%d ptype=%d, %s frag (flags=0x%08x) len=%d "
	    "auth_len=%d",
	    hdr->rpc_vers, hdr->rpc_vers_minor, hdr->ptype,
	    fragtype, hdr->pfc_flags, hdr->frag_length, hdr->auth_length);
}

void
ndr_show_auth(ndr_sec_t *auth)
{
	if (auth == NULL) {
		ndo_printf(NULL, NULL, "ndr auth: <null>");
		return;
	}

	ndo_printf(NULL, NULL,
	    "ndr auth: type=0x%x, level=0x%x, pad_len=%d, ctx_id=%d",
	    auth->auth_type, auth->auth_level, auth->auth_pad_len,
	    auth->auth_context_id);
}

int
ndr_encode_pdu_hdr(ndr_xa_t *mxa)
{
	ndr_common_header_t	*hdr = &mxa->send_hdr.common_hdr;
	ndr_stream_t		*nds = &mxa->send_nds;
	int			ptype;
	int			rc;

	if (nds->m_op != NDR_M_OP_MARSHALL)
		return (NDR_DRC_FAULT_RPCHDR_MODE_MISMATCH);

	ptype = hdr->ptype;
	if (ptype == NDR_PTYPE_REQUEST &&
	    (hdr->pfc_flags & NDR_PFC_OBJECT_UUID) != 0) {
		ptype = NDR_PTYPE_REQUEST_WITH;	/* fake for sizing */
	}

	rc = ndr_encode_decode_common(nds, ptype, &TYPEINFO(ndr_hdr), hdr);

	return (NDR_DRC_PTYPE_RPCHDR(rc));
}

/*
 * This is a hand-coded derivative of the automatically generated
 * (un)marshalling routine for bind_ack headers. bind_ack headers
 * have an interior conformant array, which is inconsistent with
 * IDL/NDR rules.
 */
extern struct ndr_typeinfo ndt__uchar;
extern struct ndr_typeinfo ndt__ushort;
extern struct ndr_typeinfo ndt__ulong;

int ndr__ndr_bind_ack_hdr(ndr_ref_t *encl_ref);
ndr_typeinfo_t ndt__ndr_bind_ack_hdr = {
    1,		/* NDR version */
    3,		/* alignment */
    NDR_F_STRUCT,	/* flags */
    ndr__ndr_bind_ack_hdr,	/* ndr_func */
    68,		/* pdu_size_fixed_part */
    0,		/* pdu_size_variable_part */
    68,		/* c_size_fixed_part */
    0,		/* c_size_variable_part */
};

/*
 * [_no_reorder]
 */
int
ndr__ndr_bind_ack_hdr(ndr_ref_t *encl_ref)
{
	ndr_stream_t		*nds = encl_ref->stream;
	struct ndr_bind_ack_hdr	*val = /*LINTED E_BAD_PTR_CAST_ALIGN*/
	    (struct ndr_bind_ack_hdr *)encl_ref->datum;
	ndr_ref_t		myref;
	unsigned long		offset;

	bzero(&myref, sizeof (myref));
	myref.enclosing = encl_ref;
	myref.stream = encl_ref->stream;
	myref.packed_alignment = 0;

	/* do all members in order */
	NDR_MEMBER(_ndr_common_header, common_hdr, 0UL);
	NDR_MEMBER(_ushort, max_xmit_frag, 16UL);
	NDR_MEMBER(_ushort, max_recv_frag, 18UL);
	NDR_MEMBER(_ulong, assoc_group_id, 20UL);

	/* port any is the conformant culprit */
	offset = 24UL;

	switch (nds->m_op) {
	case NDR_M_OP_MARSHALL:
		val->sec_addr.length =
		    strlen((char *)val->sec_addr.port_spec) + 1;
		break;

	case NDR_M_OP_UNMARSHALL:
		break;

	default:
		NDR_SET_ERROR(encl_ref, NDR_ERR_M_OP_INVALID);
		return (0);
	}

	NDR_MEMBER(_ushort, sec_addr.length, offset);
	NDR_MEMBER_ARR_WITH_DIMENSION(_uchar, sec_addr.port_spec,
	    offset+2UL, val->sec_addr.length);

	offset += 2;
	offset += val->sec_addr.length;
	offset += NDR_ALIGN4(offset);

	NDR_MEMBER(_ndr_p_result_list, p_result_list, offset);
	return (1);
}

/*
 * Assume a single presentation context element in the result list.
 */
unsigned
ndr_bind_ack_hdr_size(ndr_xa_t *mxa)
{
	ndr_bind_ack_hdr_t *bahdr = &mxa->send_hdr.bind_ack_hdr;
	unsigned	offset;
	unsigned	length;

	/* port any is the conformant culprit */
	offset = 24UL;

	length = strlen((char *)bahdr->sec_addr.port_spec) + 1;

	offset += 2;
	offset += length;
	offset += NDR_ALIGN4(offset);
	offset += sizeof (ndr_p_result_list_t);
	return (offset);
}

/*
 * This is a hand-coded derivative of the automatically generated
 * (un)marshalling routine for alter_context_rsp headers.
 * Alter context response headers have an interior conformant array,
 * which is inconsistent with IDL/NDR rules.
 */
int ndr__ndr_alter_context_rsp_hdr(ndr_ref_t *encl_ref);
ndr_typeinfo_t ndt__ndr_alter_context_rsp_hdr = {
    1,			/* NDR version */
    3,			/* alignment */
    NDR_F_STRUCT,	/* flags */
    ndr__ndr_alter_context_rsp_hdr,	/* ndr_func */
    56,			/* pdu_size_fixed_part */
    0,			/* pdu_size_variable_part */
    56,			/* c_size_fixed_part */
    0,			/* c_size_variable_part */
};

/*
 * [_no_reorder]
 */
int
ndr__ndr_alter_context_rsp_hdr(ndr_ref_t *encl_ref)
{
	ndr_stream_t		*nds = encl_ref->stream;
	ndr_alter_context_rsp_hdr_t *val = /*LINTED E_BAD_PTR_CAST_ALIGN*/
	    (ndr_alter_context_rsp_hdr_t *)encl_ref->datum;
	ndr_ref_t		myref;
	unsigned long		offset;

	bzero(&myref, sizeof (myref));
	myref.enclosing = encl_ref;
	myref.stream = encl_ref->stream;
	myref.packed_alignment = 0;

	/* do all members in order */
	NDR_MEMBER(_ndr_common_header, common_hdr, 0UL);
	NDR_MEMBER(_ushort, max_xmit_frag, 16UL);
	NDR_MEMBER(_ushort, max_recv_frag, 18UL);
	NDR_MEMBER(_ulong, assoc_group_id, 20UL);

	offset = 24UL;	/* offset of sec_addr */

	switch (nds->m_op) {
	case NDR_M_OP_MARSHALL:
		val->sec_addr.length = 0;
		break;

	case NDR_M_OP_UNMARSHALL:
		break;

	default:
		NDR_SET_ERROR(encl_ref, NDR_ERR_M_OP_INVALID);
		return (0);
	}

	NDR_MEMBER(_ushort, sec_addr.length, offset);
	NDR_MEMBER_ARR_WITH_DIMENSION(_uchar, sec_addr.port_spec,
	    offset+2UL, val->sec_addr.length);

	offset += 2;	/* sizeof (sec_addr.length) */
	offset += NDR_ALIGN4(offset);

	NDR_MEMBER(_ndr_p_result_list, p_result_list, offset);
	return (1);
}

/*
 * Assume a single presentation context element in the result list.
 */
unsigned
ndr_alter_context_rsp_hdr_size(void)
{
	unsigned	offset;

	offset = 24UL;	/* offset of sec_addr */
	offset += 2;	/* sizeof (sec_addr.length) */
	offset += NDR_ALIGN4(offset);
	offset += sizeof (ndr_p_result_list_t);
	return (offset);
}

/*
 * This is a hand-coded (un)marshalling routine for auth_verifier_co
 * (aka ndr_sec_t).
 *
 * We need to pretend this structure isn't variably sized, until ndrgen
 * has been modified to support variable-sized arrays.
 * Here, we only account for the fixed-size members (8 bytes), plus
 * a pointer for the C structure.
 *
 * We then convert between a pointer to the auth token (auth_value,
 * allocated here during unmarshall) and a flat, 'fixed'-sized array.
 */

int ndr__auth_verifier_co(ndr_ref_t *encl_ref);
ndr_typeinfo_t ndt__auth_verifier_co = {
    1,		/* NDR version */
    3,		/* alignment */
    NDR_F_STRUCT,	/* flags */
    ndr__auth_verifier_co,	/* ndr_func */
    8,		/* pdu_size_fixed_part */
    0,		/* pdu_size_variable_part */
    8 + sizeof (void *),	/* c_size_fixed_part */
    0,		/* c_size_variable_part */
};

/*
 * [_no_reorder]
 */
int
ndr__auth_verifier_co(ndr_ref_t *encl_ref)
{
	ndr_stream_t		*nds = encl_ref->stream;
	ndr_xa_t		*mxa = /*LINTED E_BAD_PTR_CAST_ALIGN*/
	    (ndr_xa_t *)encl_ref->datum;
	ndr_common_header_t	*hdr;
	ndr_ref_t		myref;
	ndr_sec_t		*val;

	/*
	 * Assumes scan_offset points to the end of PDU body.
	 * (That's base + frag_len - auth_len - SEC_TRAILER_SIZE)
	 *
	 * At some point, NDRGEN could use struct initializers instead of
	 * bzero() + initialization.
	 */
	bzero(&myref, sizeof (myref));
	myref.enclosing = encl_ref;
	myref.stream = encl_ref->stream;

	switch (nds->m_op) {
	case NDR_M_OP_MARSHALL:
		val = &mxa->send_auth;
		hdr = &mxa->send_hdr.common_hdr;
		break;

	case NDR_M_OP_UNMARSHALL:
		val = &mxa->recv_auth;
		hdr = &mxa->recv_hdr.common_hdr;
		val->auth_value = (uchar_t *)NDS_MALLOC(nds, hdr->auth_length,
		    encl_ref);
		break;

	default:
		NDR_SET_ERROR(encl_ref, NDR_ERR_M_OP_INVALID);
		return (0);
	}

	/*
	 * ndr_topmost() can't account for auth_length (pdu_scan/end_offset).
	 * This would only matter if any of this struct's members
	 * are treated as 'outer' constructs, but they aren't.
	 */
	encl_ref->pdu_end_offset += hdr->auth_length;
	nds->pdu_scan_offset += hdr->auth_length;

	NDR_MEMBER(_uchar, auth_type, 0UL);
	NDR_MEMBER(_uchar, auth_level, 1UL);
	NDR_MEMBER(_uchar, auth_pad_len, 2UL);
	NDR_MEMBER(_uchar, auth_rsvd, 3UL);
	NDR_MEMBER(_ulong, auth_context_id, 4UL);

	NDR_MEMBER_PTR_WITH_DIMENSION(_uchar, auth_value, 8UL,
	    hdr->auth_length);

	return (1);
}

int
ndr_encode_pdu_auth(ndr_xa_t *mxa)
{
	ndr_common_header_t	*hdr = &mxa->send_hdr.common_hdr;
	ndr_stream_t		*nds = &mxa->send_nds;
	int			rc;
	ulong_t			want_size;

	if (nds->m_op != NDR_M_OP_MARSHALL)
		return (NDR_DRC_FAULT_MODE_MISMATCH);

	if (hdr->auth_length == 0)
		return (NDR_DRC_OK);

	want_size = nds->pdu_scan_offset + hdr->auth_length + SEC_TRAILER_SIZE;

	/*
	 * Make sure we have space for the sec trailer - the marshaller
	 * doesn't know how large the auth token is.
	 * Note: ndr_add_auth_token() has already added padding.
	 *
	 * NDS_GROW_PDU will adjust pdu_size for us.
	 */
	if (nds->pdu_max_size < want_size) {
		if (NDS_GROW_PDU(nds, want_size, NULL) == 0)
			return (NDR_DRC_FAULT_ENCODE_TOO_BIG);
	} else {
		nds->pdu_size = want_size;
	}
	rc = ndr_encode_decode_type(nds, &TYPEINFO(auth_verifier_co),
	    mxa);

	return (rc);
}

int
ndr_decode_pdu_auth(ndr_xa_t *mxa)
{
	ndr_common_header_t	*hdr = &mxa->recv_hdr.common_hdr;
	ndr_stream_t		*nds = &mxa->recv_nds;
	ndr_sec_t		*auth = &mxa->recv_auth;
	int			rc;
	ulong_t			saved_offset;
	size_t			auth_size;

	if (nds->m_op != NDR_M_OP_UNMARSHALL)
		return (NDR_DRC_FAULT_MODE_MISMATCH);

	mxa->recv_auth.auth_pad_len = 0;
	if (hdr->auth_length == 0)
		return (NDR_DRC_OK);

	/*
	 * Save the current offset, and skip to the sec_trailer.
	 * That's located after the (fragment of) stub data and the auth
	 * pad bytes (collectively the 'PDU Body').
	 */
	saved_offset = nds->pdu_scan_offset;
	nds->pdu_scan_offset = nds->pdu_body_offset + nds->pdu_body_size;

	/* auth_length is all of the data after the sec_trailer */
	if (hdr->auth_length >
	    (nds->pdu_size - nds->pdu_scan_offset - SEC_TRAILER_SIZE)) {
		nds->pdu_scan_offset = saved_offset;
		return (NDR_DRC_FAULT_RECEIVED_MALFORMED);
	}

	rc = ndr_encode_decode_type(nds, &TYPEINFO(auth_verifier_co),
	    mxa);

	/*
	 * Reset the scan_offset for call decode processing.
	 * If we were successful, remove the sec trailer and padding
	 * from size accounting.
	 */
	if (auth->auth_pad_len > nds->pdu_body_size)
		rc = NDR_DRC_FAULT_RECEIVED_MALFORMED;
	else if (rc == NDR_DRC_OK) {
		auth_size = hdr->auth_length + SEC_TRAILER_SIZE +
		    auth->auth_pad_len;

		/*
		 * After the authenticator has been decoded,
		 * pdu_scan_offset points to just after the auth token,
		 * which is the end of the fragment.
		 *
		 * If there's no data after the authenticator, then we
		 * just remove the authenticator from size accounting.
		 * Otherwise, need to memmove() all of that data back to after
		 * the stub data. The data we move starts at the beginning of
		 * the next fragment.
		 */
		if (nds->pdu_size > nds->pdu_scan_offset) {
			uchar_t *next_frag_ptr = nds->pdu_base_addr +
			    nds->pdu_scan_offset;

			memmove(next_frag_ptr - auth_size, next_frag_ptr,
			    nds->pdu_size - nds->pdu_scan_offset);
		}

		nds->pdu_size -= auth_size;
	}
	nds->pdu_scan_offset = saved_offset;
	return (rc);
}
