/*
 * Copyright (c) 2009 Secure Endpoints Inc.
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use, copy,
 * modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include <afsconfig.h>
#include <afs/param.h>
#include <roken.h>

#include <malloc.h>
#include "ms-wkssvc.h"
#include "ms-srvsvc.h"
#include "afsd.h"
#include "cm_btree.h"
#include "cm_nls.h"
#include "msrpc.h"

static void
MSRPC_AllocBuffer(msrpc_buffer * buf, unsigned int size);

static void
MSRPC_ReallocBuffer(msrpc_buffer * buf, unsigned int size);

static void
MSRPC_FreeBuffer(msrpc_buffer * buf);

static void
MSRPC_SetCmUser(cm_user_t * userp);

/*! \brief Interface list

   This is the list of interfaces that are exported through this RPC
   module.  The *_ifspec variable is defined the <interface>_s.c file
   that is generated by MIDL.  We have to use an extra level of
   indirection because the actual RPC_SERVER_INTERFACE structure is
   not exported in the generated code.
 */
RPC_SERVER_INTERFACE **_Interfaces[] = {
    (RPC_SERVER_INTERFACE **) &wkssvc_v1_0_s_ifspec,
    (RPC_SERVER_INTERFACE **) &srvsvc_v3_0_s_ifspec,
};

const int n_Interfaces = sizeof(_Interfaces)/sizeof(_Interfaces[0]);

/* Serialization  */

#define RPC_ALIGNMENT 8
#define ALIGNTO8(x) (((((x) - 1)/8) + 1) * 8)

typedef struct fcursor {
    BYTE * pos;
    BYTE * end;
} fcursor;

#define INITC(pfc, buf, len) (pfc)->pos = (BYTE *)(buf); (pfc)->end = (pfc)->pos + (len)

#define SPC_LEFT(pfc) ((unsigned int)((pfc)->end - (pfc)->pos))
#define IS_SPC(pfc, n) (SPC_LEFT(pfc) >= (n))

__inline BYTE READB(fcursor * pfc) {
    BYTE b = *pfc->pos;
    pfc->pos++;
    return b;
}

#define READW(pfc) (((WORD)READB(pfc)) | (((WORD)READB(pfc)) << 8))
#define READDW(pfc) (((DWORD)READW(pfc)) | (((DWORD)READW(pfc)) << 16))
#define O_READS(pfc, s) do { memcpy(s, (pfc)->pos, sizeof(*s)); (pfc)->pos += sizeof(*s); } while (0)
#define O_READA(pfc, a) do { memcpy(&a, (pfc)->pos, sizeof(a)); (pfc)->pos += sizeof(a); } while (0)
#define O_READSZ(pfc, s, sz) do { memcpy(s, (pfc)->pos, sz); (pfc)->pos += sz; } while (0)
#define O_RALIGN4(pfc) do { while(((size_t) (pfc)->pos) & 0x3) READB(pfc); } while (0)
#define O_RALIGN8(pfc) do { while(((size_t) (pfc)->pos) & 0x7) READB(pfc); } while (0)

__inline void WRITEB(fcursor * pfc, BYTE b) {
    *pfc->pos = b;
    pfc->pos++;
}

#define WRITEW(pfc, w) (WRITEB(pfc, ((w) & 0xff)), WRITEB(pfc, (((w) >> 8) & 0xff)))
#define WRITEDW(pfc, dw) (WRITEW(pfc, ((dw) & 0xffff)), WRITEW(pfc, (((dw) >> 16) & 0xffff)))
#define O_WRITES(pfc, s) do { memcpy((pfc)->pos, s, sizeof(*s)); (pfc)->pos += sizeof(*s); } while (0)
#define O_WRITEA(pfc, a) do { memcpy((pfc)->pos, &a, sizeof(a)); (pfc)->pos += sizeof(a); } while (0)
#define O_WRITESZ(pfc, s, sz) do { memcpy((pfc)->pos, s, sz); (pfc)->pos += sz; } while (0)
#define O_WALIGN4(pfc) do { while(((size_t) (pfc)->pos) & 0x3) WRITEB(pfc, 0); } while (0)
#define O_WALIGN8(pfc) do { while(((size_t) (pfc)->pos) & 0x7) WRITEB(pfc, 0); } while (0)

#define iread_u_int8(pfc)  READB(pfc)
#define iread_byte(pfc) READB(pfc)
#define iread_u_int16(pfc) READW(pfc)
#define iread_u_int32(pfc) READDW(pfc)

#define write_u_int8(pfc, i) WRITEB(pfc, i)
#define write_byte(pfc, i) WRITEB(pfc, i)
#define write_u_int16(pfc, i) WRITEW(pfc, i)
#define write_u_int32(pfc, i) WRITEDW(pfc, i)

/*! \brief Supported RPC protocol version numbers */
RPC_VERSION msrpc_supported_protocol_versions[] = {
    { 5, 0 },			/* 5.0 */
    { 5, 1 }			/* 5.1 */
};

const int n_msrpc_supported_protocol_versions = sizeof(msrpc_supported_protocol_versions)/sizeof(msrpc_supported_protocol_versions[0]);

#define IS_PROTOCOL_VER_SUPPORTED(_maj, _min) ((_maj) == 5 && ((_min) == 0 || (_min) == 1))

/*! \brief Number of bytes of envelope overhead for an RPC response PDU */
#define RPC_RESPONSE_ENVELOPE_OVERHEAD 24

static int
read_E_CommonHeader(fcursor *pc, E_CommonHeader * pr)
{
    if (IS_SPC(pc, 16)) {

	pr->rpc_vers       = iread_u_int8(pc);
	pr->rpc_vers_minor = iread_u_int8(pc);
	pr->PTYPE	   = iread_u_int8(pc);
	pr->pfc_flags      = iread_u_int8(pc);
	O_READA(pc, pr->packed_drep);
	pr->frag_length    = iread_u_int16(pc);
	pr->auth_length    = iread_u_int16(pc);
	pr->call_id	   = iread_u_int32(pc);

	return 0;

    } else {
	return CM_ERROR_BADFORMAT;
    }
}

static int
write_E_CommonHeader(fcursor * pc, msrpc_conn * conn,
		     msrpc_call * call, PDU_TYPE pdu_type)
{
    INITC(pc, call->out.buf_data, call->out.buf_alloc);

    call->out.pdu_type = pdu_type;

    if (IS_SPC(pc, 16)) {
	write_u_int8(pc, conn->rpc_vers);	/* protocol version */
	write_u_int8(pc, conn->rpc_vers_minor);	/* minor version */
	write_u_int8(pc, pdu_type);
	write_u_int8(pc, PFC_FIRST_FRAG|PFC_LAST_FRAG);

	write_byte(pc, 0x10);	/* Data representation */
	write_byte(pc, 0);
	write_byte(pc, 0);
	write_byte(pc, 0);

	write_u_int16(pc, 0);	/* Fragment ength, to be filled in
				   later */
	write_u_int16(pc, 0);	/* Length of auth data. Always 0 since
				   we don't do authentication for RPC
				   calls.*/
	write_u_int32(pc, call->call_id);

	return 0;
    } else {
	return CM_ERROR_BUFFERTOOSMALL;
    }
}

#define USE_CURRENT_BUFFER_LENGTH (-1)
static int
closeMessage(fcursor * pc, msrpc_call * call, ptrdiff_t len)
{

    unsigned short slen;

    if (len == USE_CURRENT_BUFFER_LENGTH)
	len = pc->pos - call->out.buf_data;

    if (len >= USHRT_MAX)
        return CM_ERROR_TOOBIG;
    slen = (unsigned short)len;

    pc->pos = &call->out.buf_data[8];
    write_u_int16(pc, slen);
    call->out.buf_length = slen;

    return 0;
}

static int
read_UUID(fcursor * pc,
	  UUID * uuid)
{
    if (IS_SPC(pc, sizeof(*uuid))) {
	O_READS(pc, uuid);
	return 0;
    } else {
	return CM_ERROR_BADFORMAT;
    }
}

static int
write_UUID(fcursor * pc,
	   const UUID * uuid)
{
    if (IS_SPC(pc, sizeof(*uuid))) {
	O_WRITES(pc, uuid);
	return 0;
    } else {
	return CM_ERROR_BUFFERTOOSMALL;
    }
}

static int
read_RPC_VERSION(fcursor * pc,
		 RPC_VERSION * ver)
{
    if (IS_SPC(pc, 4)) {
	ver->MajorVersion = iread_u_int16(pc);
	ver->MinorVersion = iread_u_int16(pc);
	return 0;
    } else {
	return CM_ERROR_BADFORMAT;
    }
}

static int
write_RPC_VERSION(fcursor * pc,
		  const RPC_VERSION * ver)
{
    if (IS_SPC(pc, 4)) {
	write_u_int16(pc, ver->MajorVersion);
	write_u_int16(pc, ver->MinorVersion);
	return 0;
    } else {
	return CM_ERROR_BUFFERTOOSMALL;
    }
}

static int
read_RPC_SYNTAX_IDENTIFIER(fcursor * pc,
			   RPC_SYNTAX_IDENTIFIER * psi)
{
    int code;

    code = read_UUID(pc, &psi->SyntaxGUID);
    if (code)
	return code;
    code = read_RPC_VERSION(pc, &psi->SyntaxVersion);
    return code;
}

static int
write_RPC_SYNTAX_IDENTIFIER(fcursor * pc,
			    const RPC_SYNTAX_IDENTIFIER * psi)
{
    int code;

    code = write_UUID(pc, &psi->SyntaxGUID);
    if (code)
	return code;
    code = write_RPC_VERSION(pc, &psi->SyntaxVersion);
    return code;
}

static int
is_equal_RPC_SYNTAX_IDENTIFIER(const RPC_SYNTAX_IDENTIFIER * s1,
			       const RPC_SYNTAX_IDENTIFIER * s2)
{
    return
	IsEqualGUID(&s1->SyntaxGUID, &s2->SyntaxGUID) &&
	s1->SyntaxVersion.MajorVersion == s2->SyntaxVersion.MajorVersion &&
	s2->SyntaxVersion.MinorVersion == s2->SyntaxVersion.MinorVersion;
}

#define FAULT_PDU_PACKET_SIZE 32

/*! \brief Write a fault PDU as the output of this call */
static int
write_fault_PDU(msrpc_conn * conn, msrpc_call * call,
		u_int32 status, u_int8 flags)
{
    fcursor o;

    MSRPC_AllocBuffer(&call->out, FAULT_PDU_PACKET_SIZE);

    write_E_CommonHeader(&o, conn, call, PDU_TYPE_FAULT);

    write_u_int32(&o, 16);	/* allocation hint */
    write_u_int16(&o, call->context_id); /* presentation context ID */

    write_u_int8(&o, 0);	/* cancel count */
    write_u_int8(&o, 0);	/* reserved */

    write_u_int32(&o, status);	/* status code */

    O_WALIGN8(&o);		/* alignment */

    closeMessage(&o, call, USE_CURRENT_BUFFER_LENGTH);

    if (flags) {
	call->out.buf_data[3] |= flags;
    }

    call->status = MSRPC_CALL_DISPATCHED;

    return 0;
}

#define BIND_NAK_PDU_PACKET_SIZE 18

/*! \brief Write a bind_nak PDU as the output of this call */
static int
write_bind_nak_PDU(msrpc_conn * conn, msrpc_call * call,
		   BIND_REJECT_REASON reject_reason)
{
    fcursor o;
    unsigned buf_size = BIND_NAK_PDU_PACKET_SIZE;

    if (reject_reason == BIND_REJ_PROTOCOL_VERSION_NOT_SUPPORTED)
	buf_size += n_msrpc_supported_protocol_versions * 2 + 1;

    MSRPC_AllocBuffer(&call->out, buf_size);

    write_E_CommonHeader(&o, conn, call, PDU_TYPE_BIND_NAK);

    write_u_int16(&o, reject_reason);

    if (reject_reason == BIND_REJ_PROTOCOL_VERSION_NOT_SUPPORTED) {
	int i;

	write_u_int8(&o, n_msrpc_supported_protocol_versions);

	for (i=0; i < n_msrpc_supported_protocol_versions; i++) {
	    write_u_int8(&o, (u_int8) msrpc_supported_protocol_versions[i].MajorVersion);
	    write_u_int8(&o, (u_int8) msrpc_supported_protocol_versions[i].MinorVersion);
	}
    }

    closeMessage(&o, call, USE_CURRENT_BUFFER_LENGTH);

    call->status = MSRPC_CALL_DISPATCHED;

    return 0;
}

static int
handle_ConnBind(msrpc_conn * conn, msrpc_call * call, fcursor * in, fcursor * out)
{
    int code = 0;

    if (!IS_SPC(in, 12))
	return CM_ERROR_BADFORMAT;

    conn->max_xmit_frag = iread_u_int16(in);
    conn->max_recv_frag = iread_u_int16(in);
    conn->assoc_group_id = iread_u_int32(in);

    conn->rpc_vers = call->in_header->rpc_vers;
    conn->rpc_vers_minor = call->in_header->rpc_vers_minor;

    if (conn->assoc_group_id == 0)
	conn->assoc_group_id = 1;

    write_E_CommonHeader(out, conn, call, PDU_TYPE_BIND_ACK);

    write_u_int16(out, conn->max_xmit_frag);
    write_u_int16(out, conn->max_recv_frag);
    write_u_int32(out, conn->assoc_group_id);

    if (conn->secondary_address) {
	int len = (int)strlen(conn->secondary_address) + 1;

	write_u_int16(out, len);
	O_WRITESZ(out, conn->secondary_address, len);
    } else {
	write_u_int16(out, 0);
    }

    O_WALIGN4(out);

    /* presentation context negotiation */
    {
	int n_context_elem;
	int i;

	n_context_elem = iread_u_int8(in);
	iread_u_int8(in);	/* skip over reserved */
	iread_u_int16(in);	/* skip over reserved */

	write_u_int8(out, n_context_elem);
	write_u_int8(out, 0);	/* pad */
	write_u_int16(out, 0);	/* pad */

	for (i=0; i < n_context_elem; i++) {
	    int n_transfer_syn;
	    RPC_SYNTAX_IDENTIFIER abstract_syntax;
	    RPC_SYNTAX_IDENTIFIER transfer_syntax;
	    RPC_SERVER_INTERFACE *interf = NULL;
	    int j, k;

	    if (!IS_SPC(in, 4 + 16))
		return CM_ERROR_BADFORMAT;

	    call->context_id = iread_u_int16(in);
	    n_transfer_syn = iread_u_int8(in);
	    iread_u_int8(in);	/* skip */

	    code = read_RPC_SYNTAX_IDENTIFIER(in, &abstract_syntax);
	    if (code)
		return code;

	    for (j=0; j < n_Interfaces; j++) {
		interf = *_Interfaces[j];

		if (is_equal_RPC_SYNTAX_IDENTIFIER(&abstract_syntax, &interf->InterfaceId))
		    break;
	    }

	    if (j < n_Interfaces) {
		for (k=0; k < n_transfer_syn; k++) {
		    code = read_RPC_SYNTAX_IDENTIFIER(in, &transfer_syntax);
		    if (code)
			return code;

		    if (is_equal_RPC_SYNTAX_IDENTIFIER(&transfer_syntax, &interf->TransferSyntax))
			break;
		}
	    }

	    if (j < n_Interfaces && k < n_transfer_syn) {
		/* accepted */
		write_u_int16(out, 0);
		write_u_int16(out, 0);
		write_RPC_SYNTAX_IDENTIFIER(out, &transfer_syntax);

		conn->interface = interf;
	    } else {
		int reason;

		if (j >= n_Interfaces)
		    reason = 1;	/* abstract syntax not supported */
		else
		    reason = 2;	       /* transfer syntax not supported */
		write_u_int16(out, 2); /* provider rejection */
		write_u_int16(out, reason);
		ZeroMemory(&transfer_syntax, sizeof(transfer_syntax));
		write_RPC_SYNTAX_IDENTIFIER(out, &transfer_syntax);
	    }
	}
    }

    return closeMessage(out, call, USE_CURRENT_BUFFER_LENGTH);
}

static int
handle_ConnRequest(msrpc_conn * conn, msrpc_call * call, fcursor * in, fcursor * out)
{
    int code = 0;

    int alloc_hint;
    unsigned int op_num;
    RPC_STATUS _Status = RPC_S_OK;

    int len;

    if (!IS_SPC(in, 8))
	return CM_ERROR_BADFORMAT;

    alloc_hint = iread_u_int32(in);
    call->context_id = iread_u_int16(in);
    op_num = iread_u_int16(in);

    if (call->in_header->pfc_flags & PFC_OBJECT_UUID) {
	UUID uuid;

	code = read_UUID(in, &uuid);
	if (code)
	    return code;
    }

    O_RALIGN8(in);

    if (conn->interface == NULL) {
	/* if there was no interface selected, we can't go ahead with
	   this request. */
	return write_fault_PDU(conn, call, RPC_S_NO_BINDINGS, PFC_DID_NOT_EXECUTE);
    }

    len = SPC_LEFT(in);

    if (op_num < 0 || op_num >= conn->interface->DispatchTable->DispatchTableCount ||
	conn->interface->DispatchTable->DispatchTable[op_num] == NULL) {

	return write_fault_PDU(conn, call, RPC_S_PROCNUM_OUT_OF_RANGE, PFC_DID_NOT_EXECUTE);
    }

    call->msg.BufferLength = len;
    call->msg.ProcNum = op_num;
    call->msg.TransferSyntax = &conn->interface->TransferSyntax;
    call->msg.RpcInterfaceInformation = conn->interface;

    _Status = I_RpcGetBuffer(&call->msg);
    if ( _Status || call->msg.Buffer == NULL) {
	return write_fault_PDU(conn, call, RPC_S_SERVER_OUT_OF_MEMORY, PFC_DID_NOT_EXECUTE);
    }

    memcpy(call->msg.Buffer, in->pos, len);

    MSRPC_SetCmUser(call->cm_userp);

    RpcTryExcept
	{
	    (*conn->interface->DispatchTable->DispatchTable[op_num])(&call->msg);
	}
    RpcExcept ( 1 )
	{
	    code = CM_ERROR_UNKNOWN;
	}
    RpcEndExcept;

    MSRPC_SetCmUser(NULL);

    if (code == 0) {
	call->out.pdu_type = PDU_TYPE_RESPONSE;

	MSRPC_AllocBuffer(&call->out, call->msg.BufferLength);
	call->out.buf_length = call->msg.BufferLength;
	memcpy(call->out.buf_data, call->msg.Buffer, call->out.buf_length);
    } else {
	code = write_fault_PDU(conn, call, RPC_S_CALL_FAILED, 0);
    }

    return code;
}

static int
merge_fragment_Bind(msrpc_call * call, E_CommonHeader * ph,
			msrpc_call * ncall, E_CommonHeader *pnh)
{
    if (ph->auth_length + (unsigned int) pnh->auth_length > _UI16_MAX ||
	ph->frag_length + (unsigned int) pnh->auth_length > _UI16_MAX ||
	pnh->auth_length + 16 > pnh->frag_length)
	return CM_ERROR_TOOBIG;

    MSRPC_ReallocBuffer(&call->in, call->in.buf_length + pnh->auth_length);
    if (call->in.buf_data == NULL)
	return CM_ERROR_TOOBIG;

    memcpy(call->in.buf_data + ph->frag_length,
	   ncall->in.buf_data + (pnh->frag_length - pnh->auth_length),
	   pnh->auth_length);

    call->in.buf_length += pnh->auth_length;
    ph->frag_length += pnh->auth_length;
    ph->auth_length += pnh->auth_length;

    if ((pnh->pfc_flags & PFC_LAST_FRAG) == PFC_LAST_FRAG) {
	ph->pfc_flags |= PFC_LAST_FRAG;
    }

    return 0;
}

static int
merge_fragment_Request(msrpc_call * call, E_CommonHeader * ph,
			   msrpc_call * ncall, E_CommonHeader * pnh)
{
    int new_auth_length = pnh->auth_length;
    int new_stub_length = (int)pnh->frag_length - (pnh->auth_length + 24);

    if (pnh->pfc_flags & PFC_OBJECT_UUID)
	new_stub_length -= 16;

    if (ph->auth_length + new_auth_length > _UI16_MAX ||
	ph->frag_length + new_stub_length + new_auth_length > _UI16_MAX ||
	pnh->auth_length + 24 > pnh->frag_length)
	return CM_ERROR_TOOBIG;

    MSRPC_ReallocBuffer(&call->in, call->in.buf_length + new_auth_length + new_stub_length);
    if (call->in.buf_data == NULL)
	return CM_ERROR_TOOBIG;

    if(ph->auth_length)
	memmove(call->in.buf_data + (ph->frag_length - ph->auth_length),
		call->in.buf_data + (ph->frag_length - ph->auth_length) + new_stub_length,
		ph->auth_length);

    memcpy(call->in.buf_data + (ph->frag_length - ph->auth_length),
	   ncall->in.buf_data + (pnh->frag_length - (new_auth_length + new_stub_length)),
	   new_stub_length);

    memcpy(call->in.buf_data + ph->frag_length + new_stub_length,
	   ncall->in.buf_data + (pnh->frag_length - pnh->auth_length),
	   pnh->auth_length);

    call->in.buf_length += new_auth_length + new_stub_length;
    ph->frag_length += new_auth_length + new_stub_length;
    ph->auth_length += new_auth_length;

    if ((pnh->pfc_flags & PFC_LAST_FRAG) == PFC_LAST_FRAG) {
	ph->pfc_flags |= PFC_LAST_FRAG;
    }

    return 0;
}

static int
assemble_message(msrpc_conn * conn)
{
    int code = 0;
    msrpc_call * call;
    msrpc_call * ncall;
    E_CommonHeader h;
    fcursor c;

    call = conn->head;

    if (call == NULL)
	return CM_ERROR_INVAL;

    INITC(&c, call->in.buf_data, call->in.buf_length);

    code = read_E_CommonHeader(&c, &h);
    if (code)
	return code;

    if ((h.pfc_flags & (PFC_FIRST_FRAG | PFC_LAST_FRAG)) ==
	(PFC_FIRST_FRAG | PFC_LAST_FRAG))
	return 0;

    if ((h.pfc_flags & PFC_FIRST_FRAG) != PFC_FIRST_FRAG)
	return CM_ERROR_INVAL;

    for (ncall = call->next; ncall; ncall = ncall->next) {
	fcursor nc;
	E_CommonHeader nh;

	INITC(&nc, ncall->in.buf_data, ncall->in.buf_length);

	code = read_E_CommonHeader(&nc, &nh);

	if (nh.call_id != h.call_id)
	    continue;

	if ((nh.pfc_flags & PFC_FIRST_FRAG) != 0)
	    continue;

	switch(h.PTYPE) {
	case PDU_TYPE_BIND:
	case PDU_TYPE_ALTER_CONTEXT:
	    code = merge_fragment_Bind(call, &h, ncall, &nh);
	    break;

	case PDU_TYPE_REQUEST:
	    code = merge_fragment_Request(call, &h, ncall, &nh);
	    break;

	default:
	    code = CM_ERROR_INVAL;
	}

	ncall->status = MSRPC_CALL_SKIP;

	if (nh.pfc_flags & PFC_LAST_FRAG) {
	    break;
	}
    }

    c.pos = &call->in.buf_data[3];
    write_u_int8(&c, h.pfc_flags);
    write_u_int16(&c, h.frag_length);
    write_u_int16(&c, h.auth_length);

    return code;
}

static int
dispatch_call(msrpc_conn * conn)
{
    int code = 0;
    fcursor c;
    fcursor o;
    E_CommonHeader h;
    msrpc_call * call;

    call = conn->head;

    if (call == NULL ||
	call->status != MSRPC_CALL_MSGRECEIVED)
	return CM_ERROR_INVAL;

    MSRPC_AllocBuffer(&call->out, DEF_RPC_MSG_SIZE);

 retry_buffer:

    INITC(&c, call->in.buf_data, call->in.buf_length);

    code = read_E_CommonHeader(&c, &h);
    if (code)
	return code;

    if (!IS_PROTOCOL_VER_SUPPORTED(h.rpc_vers, h.rpc_vers_minor)) {
	if (h.PTYPE == PDU_TYPE_BIND) {
	    return write_bind_nak_PDU(conn, call, BIND_REJ_PROTOCOL_VERSION_NOT_SUPPORTED);
	} else {
	    /* we shouldn't get here, but just in case */
	    return write_fault_PDU(conn, call, RPC_S_INVALID_ARG, PFC_DID_NOT_EXECUTE);
	}
    }

    if (h.packed_drep[0] != 0x10 ||
	h.packed_drep[1] != 0 ||
	h.packed_drep[2] != 0 ||
	h.packed_drep[3] != 0) {

	if (h.PTYPE == PDU_TYPE_BIND) {
	    return write_bind_nak_PDU(conn, call, BIND_REJ_REASON_NOT_SPECIFIED);
	} else {
	    return write_fault_PDU(conn, call, RPC_S_INVALID_ARG, PFC_DID_NOT_EXECUTE);
	}
    }

    if (h.frag_length > call->in.buf_length) {
	return CM_ERROR_INVAL;
    }

    if ((h.pfc_flags & (PFC_FIRST_FRAG | PFC_LAST_FRAG)) !=
	(PFC_FIRST_FRAG | PFC_LAST_FRAG)) {
	code = assemble_message(conn);
	if (code == 0)
	    goto retry_buffer;
	else
	    return write_fault_PDU(conn, call, RPC_S_INVALID_ARG, PFC_DID_NOT_EXECUTE);
    }

    if (h.auth_length != 0) {
	return write_fault_PDU(conn, call, RPC_S_BINDING_HAS_NO_AUTH, PFC_DID_NOT_EXECUTE);
    }

    call->call_id = h.call_id;

    call->in.pdu_type = h.PTYPE;

    INITC(&o, call->out.buf_data, call->out.buf_alloc);

    call->in_header = &h;

    code = CM_ERROR_INVAL;

    switch (h.PTYPE) {
    case PDU_TYPE_REQUEST:
	code = handle_ConnRequest(conn, call, &c, &o);
	break;

    case PDU_TYPE_BIND:
	code = handle_ConnBind(conn, call, &c, &o);
	break;

    case PDU_TYPE_ALTER_CONTEXT:
	code = write_fault_PDU(conn, call, RPC_S_CALL_FAILED, PFC_DID_NOT_EXECUTE);
	break;

    case PDU_TYPE_SHUTDOWN:
	code = write_fault_PDU(conn, call, RPC_S_CALL_FAILED, PFC_DID_NOT_EXECUTE);
	break;

    case PDU_TYPE_CO_CANCEL:
	code = write_fault_PDU(conn, call, RPC_S_CALL_FAILED, PFC_DID_NOT_EXECUTE);
	break;

    case PDU_TYPE_ORPHANED:
	code = write_fault_PDU(conn, call, RPC_S_CALL_FAILED, PFC_DID_NOT_EXECUTE);
	break;

    default:
	code = write_fault_PDU(conn, call, RPC_S_CALL_FAILED, PFC_DID_NOT_EXECUTE);
    }

    call->in_header = NULL;

    call->status = MSRPC_CALL_DISPATCHED;

    return code;
}

static void
MSRPC_AllocBuffer(msrpc_buffer * buf, unsigned int size)
{
    if (buf->buf_alloc < size) {
	if (buf->buf_data)
	    _aligned_free(buf->buf_data);

	buf->buf_alloc = ALIGNTO8(size);
	buf->buf_data = _aligned_malloc(buf->buf_alloc, RPC_ALIGNMENT);
	buf->buf_length = 0;
	buf->buf_pos = 0;
    }
}

static void
MSRPC_FreeBuffer(msrpc_buffer * buf)
{
    if (buf->buf_data) {
	_aligned_free(buf->buf_data);
	buf->buf_data = NULL;
	buf->buf_alloc = 0;
	buf->buf_length = 0;
	buf->buf_pos = 0;
    }
}

static void
MSRPC_ReallocBuffer(msrpc_buffer * buf, unsigned int size)
{
    if (buf->buf_alloc < size) {
	BYTE * new_buf;

	buf->buf_alloc = ALIGNTO8(size);
	new_buf = _aligned_realloc(buf->buf_data, buf->buf_alloc, RPC_ALIGNMENT);
	if (new_buf != NULL) {
	    buf->buf_data = new_buf;
	} else {
	    MSRPC_FreeBuffer(buf);
	}
    }
}

static msrpc_call *
MSRPC_NewCall(void)
{
    msrpc_call * c;

    c = calloc(sizeof(*c), 1);

    c->msg.Handle = NULL;
    c->msg.DataRepresentation = 0x00000010;
    c->msg.Buffer = NULL;
    c->msg.BufferLength = 0;
    c->msg.TransferSyntax = NULL;

    return c;
}

static int
MSRPC_PutCall(msrpc_conn * conn, msrpc_call * pcall)
{
    if (conn->tail) {
	conn->tail->next = pcall;
	pcall->prev = conn->tail;
	conn->tail = pcall;
	pcall->next = NULL;
    } else {
	conn->head = conn->tail = pcall;
	pcall->next = pcall->prev = NULL;
    }
    return 0;
}

static int
MSRPC_DequeueCall(msrpc_conn * conn, msrpc_call * call)
{
    if (call->prev)
	call->prev->next = call->next;
    if (call->next)
	call->next->prev = call->prev;

    if (conn->head  && conn->head == call)
	conn->head = call->next;

    if (conn->tail && conn->tail == call)
	conn->tail = call->prev;

    call->next = call->prev = NULL;

    return 0;
}

static int
MSRPC_GetCall(msrpc_conn * conn, msrpc_call ** ppcall)
{
    if (conn->head) {
	*ppcall = conn->head;
	conn->head = conn->head->next;
	if (conn->head)
	    conn->head->prev = NULL;
	(*ppcall)->next = (*ppcall)->prev = NULL;
	if (conn->tail == *ppcall)
	    conn->tail = NULL;
	return 0;
    } else {
	*ppcall = NULL;
	return 1;
    }
}

static int
MSRPC_FreeCall(msrpc_call * call)
{
    I_RpcFreeBuffer(&call->msg);
    MSRPC_FreeBuffer(&call->in);
    MSRPC_FreeBuffer(&call->out);

    if (call->cm_userp) {
	cm_ReleaseUser(call->cm_userp);
	call->cm_userp = NULL;
    }

    ZeroMemory(call, sizeof(*call));
    free (call);
    return 0;
}

/*! \brief Initialize a msrpc_conn

   \param [out] conn Connection to initialize

   \param [in] secondary_address An optional secondary address for the
       query.  Typically for RPCs to wellknown endpoints such as
       SRVSVC or WKSSVC, this is a named pipe address (e.g.:
       \PIPE\srvsvc ).

   \return Always returns 0.
 */
int
MSRPC_InitConn(msrpc_conn * conn, const char * secondary_address)
{
    ZeroMemory(conn, sizeof(*conn));
    if (secondary_address) {
	conn->secondary_address = strdup(secondary_address);
    }
    return 0;
}

/*! \brief Free a msrcp_conn structure contents

   This doesn't free the msrpc_conn object, but frees the contents.
 */
int
MSRPC_FreeConn(msrpc_conn * conn)
{
    msrpc_call * call = NULL;

    while(!MSRPC_GetCall(conn, &call)) {
	MSRPC_FreeCall(call);
    }

    if (conn->secondary_address)
	free(conn->secondary_address);

    ZeroMemory(conn, sizeof(*conn));

    return 0;
}

/*! \brief Write an RPC message into the RPC module

   This function should be used by message mode RPC transports to
   submit a single message.  if successful, the message will be queued
   for dispatch.  The RPC will not be executed until
   MSRPC_PrepareRead() or MSRPC_ReadMessageLength() or
   MSRPC_ReadMessage() is called.
 */
int
MSRPC_WriteMessage(msrpc_conn * conn, BYTE * buffer, unsigned int len,
                   cm_user_t * userp)
{
    msrpc_call * call;

    if (len == 0)
	return CM_ERROR_INVAL;

    if (len > MAX_RPC_MSG_SIZE)
	return CM_ERROR_BUFFERTOOSMALL;

    call = MSRPC_NewCall();
    if (!call)
        return ENOMEM;

    MSRPC_AllocBuffer(&call->in, len);
    memcpy(call->in.buf_data, buffer, len);
    call->in.buf_pos = len;
    call->in.buf_length = call->in.buf_pos;
    call->status = MSRPC_CALL_MSGRECEIVED;

    if (userp) {
	call->cm_userp = userp;
	cm_HoldUser(userp);
    }

    MSRPC_PutCall(conn, call);

    return 0;
}

/*! \brief Prepare to read the next message from the RPC output queue

   Each RPC message received is queued until the response is required.
   MSRPC_PrepareRead() will, if necessary, dispatch the RPC request
   and prepare the output buffer to be sent to the caller.
 */
int
MSRPC_PrepareRead(msrpc_conn * conn)
{
    msrpc_call * call;
    int code = 0;

    while (conn->head) {
	call = conn->head;

	if (call->status == MSRPC_CALL_SKIP ||
	    call->status == MSRPC_CALL_MSGSENT ||
	    (call->status == MSRPC_CALL_DISPATCHED &&
	     call->out.buf_length == 0)) {

	    MSRPC_DequeueCall(conn, call);
	    MSRPC_FreeCall(call);
	    continue;
	}

	if (call->status == MSRPC_CALL_MSGRECEIVED) {
	    code = dispatch_call(conn);

	    /* If the dispatch failed, then we discard the message.
	       If the error was handled and a response packet was
	       created, dispatch_call() would return 0. */

	    if (code) {
		MSRPC_DequeueCall(conn, call);
		MSRPC_FreeCall(call);
	    }
	    continue;
	}

	break;			/* the status wasn't SKIP or
				   MSGRECEIVED, or we successfully
				   dispatched the message. */
    }

    if (conn->head == NULL)
	return CM_ERROR_BADFD;

    if (conn->head->status == MSRPC_CALL_DISPATCHED ||
	conn->head->status == MSRPC_CALL_MSGSENDING)
	return 0;
    else
	return CM_ERROR_BADFD;
}

/*! \brief Return the size of the buffer for the next RPC message

   Given the maximum size of the buffer, returns the actual size of
   the data that will be copied.  If the return value is 0, there is
   no data to be sent to the caller either because there was no RPC
   message queued or the queued message could not be dispatched.
 */
int
MSRPC_ReadMessageLength(msrpc_conn * conn, unsigned int max_len)
{
    int code;
    msrpc_call *call;

    max_len = min(max_len, conn->max_recv_frag);

    code = MSRPC_PrepareRead(conn);
    if (code)
	return 0;

    call = conn->head;

    if (call &&
	(call->status == MSRPC_CALL_DISPATCHED ||
	 call->status == MSRPC_CALL_MSGSENDING)) {

	switch(call->out.pdu_type) {
	case PDU_TYPE_RESPONSE:
	    {
		/* This calculation should mirror what
		   MSRPC_ReadMessage() does below. */

		int fragment_size = conn->max_recv_frag - RPC_RESPONSE_ENVELOPE_OVERHEAD;
		unsigned int len;

		len = min(max_len, conn->max_recv_frag);

		if (call->out.buf_pos % fragment_size == 0) {
		    if (len < RPC_RESPONSE_ENVELOPE_OVERHEAD)
			return 0;

		    len = min(len, RPC_RESPONSE_ENVELOPE_OVERHEAD + call->out.buf_length - call->out.buf_pos);
		} else {
		    unsigned int to_copy;

		    to_copy = min(fragment_size - (call->out.buf_pos % fragment_size),
				  call->out.buf_length - call->out.buf_pos);
		    len = min(to_copy, len);
		}

		return len;
	    }

	case PDU_TYPE_BIND_ACK:
	case PDU_TYPE_FAULT:
	case PDU_TYPE_BIND_NAK:
	    {
		int len = min(call->out.buf_length - call->out.buf_pos, max_len);

		return len;
	    }
	}

	return 0;
    } else {
	return 0;
    }
}

/*! \brief Read the next message from the RPC queue

   Copies the next chunk of data from the response buffer to the
   buffer indicated by \a buffer and \a len.

   \retval 0 The data was copied to the buffer.  There is either no
       more data to send or the existence of more data is implicit in
       the data already sent. I.e. fragment flags were set
       appropriately to indicate more data and we are at a fragment
       boundary.

   \retval CM_ERROR_RPC_MOREDATA The data was copied to the buffer and
       there is more data to be sent.

   \retval Else something went wrong.  The data was not copied to the
       buffer successfully.
 */
int
MSRPC_ReadMessage(msrpc_conn * conn, BYTE *buffer, unsigned int len)
{
    int code;
    msrpc_call * call;

    code = MSRPC_PrepareRead(conn);
    if (code)
	return code;

    if (conn->head &&
	(conn->head->status == MSRPC_CALL_DISPATCHED ||
	 conn->head->status == MSRPC_CALL_MSGSENDING)) {

	call = conn->head;

	switch(call->out.pdu_type) {
	case PDU_TYPE_RESPONSE:
	    {
		BYTE * o_buffer = call->out.buf_data;
		unsigned int o_alloc = call->out.buf_alloc;
		unsigned int o_pos = call->out.buf_pos;
		unsigned int o_len = call->out.buf_length;
		unsigned int start_pos = o_pos;
		unsigned int fragment_size;
		fcursor o;

		/* If call->out.buf_pos falls on a fragment boundary:

		     Assume that we are starting a new fragment and deal accordingly.

		   Else

		     Assume we are continuing a fragment.
		 */

		fragment_size = conn->max_recv_frag - RPC_RESPONSE_ENVELOPE_OVERHEAD;

		/* Don't send more than the max_recv_frag */
		len = min(len, conn->max_recv_frag);

		/* Switch the supplied user buffer with the output
		   buffer */
		call->out.buf_data = buffer;
		call->out.buf_length = 0;
		call->out.buf_alloc = len;
		call->out.buf_pos = 0;

		if (o_pos % fragment_size == 0) {

		    /* New fragment:

		       We need to write the packet headers and
		       fragment bits here.  The length of the buffer
		       should be at least big enough to hold the
		       entire envelope.  We don't handle the case were
		       the envelope itself is too big.

		     */

		    if (len < RPC_RESPONSE_ENVELOPE_OVERHEAD) {
			return CM_ERROR_INVAL;
		    }

		    code = write_E_CommonHeader(&o, conn, call, PDU_TYPE_RESPONSE);
		    if (code)
			return code;

		    write_u_int32(&o, o_len); /* allocation hint */
		    write_u_int16(&o, call->context_id); /* presentation context ID */
		    write_u_int8(&o, 0);		 /* cancel count */
		    write_u_int8(&o, 0);		 /* reserved */

		    {
			int to_copy = min(SPC_LEFT(&o), o_len - o_pos);
			int rc;

			O_WRITESZ(&o, (o_buffer + o_pos), to_copy);

			/* the fragment size of the message is the minimum of:

			   - max_recv_frag for the connection
			   - envelope size + (full message size - start offset)

			 */
			rc = closeMessage(&o, call,
					  min(conn->max_recv_frag,
					      RPC_RESPONSE_ENVELOPE_OVERHEAD + o_len - start_pos));
			if (rc)
			    return rc;

			o_pos += to_copy;

			/* If there is more data to be sent and we are
			   not at a fragment boundary after the last
			   send, we return CM_ERROR_RPC_MOREDATA.
			   Having more fragments to send is not a case
			   we indicate via a return value. */

			if (o_pos < o_len &&
			    o_pos < start_pos + fragment_size)
			    code = CM_ERROR_RPC_MOREDATA;
		    }

		    if (start_pos + fragment_size < o_len)
			buffer[3] &= ~PFC_LAST_FRAG;

		    if (start_pos != 0)
			buffer[3] &= ~PFC_FIRST_FRAG;

		} else {

		    /* Continuation:

		       The packet header should have already been sent
		       in a previous block.

		     */
		    unsigned int to_copy;

		    INITC(&o, call->out.buf_data, call->out.buf_alloc);

		    to_copy = min(fragment_size - (o_pos % fragment_size),
				  o_len - o_pos);
		    to_copy = min(to_copy, SPC_LEFT(&o));

		    O_WRITESZ(&o, (o_buffer + o_pos), to_copy);

		    o_pos += to_copy;

		    if (o_pos < o_len && (o_pos % fragment_size) != 0)
			code = CM_ERROR_RPC_MOREDATA;
		}

		call->out.buf_data = o_buffer;
		call->out.buf_alloc = o_alloc;
		call->out.buf_pos = o_pos;
		call->out.buf_length = o_len;

		if (call->out.buf_pos < call->out.buf_length) {
		    call->status = MSRPC_CALL_MSGSENDING;
		} else {
		    call->status = MSRPC_CALL_MSGSENT;
		    MSRPC_DequeueCall(conn, call);
		    MSRPC_FreeCall(call);
		}
	    }
	    return code;

	case PDU_TYPE_BIND_ACK:
	case PDU_TYPE_FAULT:
	case PDU_TYPE_BIND_NAK:
	    {
		unsigned int to_send;

		/* These types of packets are already fully formed.
		   We don't need to wrap them in an envelope.  They
		   also don't fragment. */

		to_send = call->out.buf_length - call->out.buf_pos;

		if (len < to_send)
		    code = CM_ERROR_RPC_MOREDATA;
		else
		    len = to_send;

		if (len > 0) {
		    memcpy(buffer, call->out.buf_data + call->out.buf_pos, len);
		}

		call->out.buf_pos += len;

		if (call->out.buf_pos < call->out.buf_length) {
		    call->status = MSRPC_CALL_MSGSENDING;
		} else {
		    call->status = MSRPC_CALL_MSGSENT;
		    MSRPC_DequeueCall(conn, call);
		    MSRPC_FreeCall(call);
		}
	    }
	    return code;
	}

	return CM_ERROR_INVAL;
    } else {
	return 0;
    }
}

pthread_once_t msrpc_user_slot_init = PTHREAD_ONCE_INIT;

DWORD msrpc_tls_user_slot = 0;

static void
msrpc_user_slot_init_func(void)
{
    msrpc_tls_user_slot = TlsAlloc();
}

cm_user_t *
MSRPC_GetCmUser(void)
{
    pthread_once(&msrpc_user_slot_init, msrpc_user_slot_init_func);

    return (cm_user_t *) TlsGetValue(msrpc_tls_user_slot);
}

static void
MSRPC_SetCmUser(cm_user_t * userp)
{
    pthread_once(&msrpc_user_slot_init, msrpc_user_slot_init_func);

    TlsSetValue(msrpc_tls_user_slot, userp);
}

typedef struct I_RpcAllocatedBuffer {
    DWORD magic;		/* == _RPCALLOCATEDBUFFER_MAGIC */
#define _RPCALLOCATEDBUFFER_MAGIC 0xa110ca73
    struct I_RpcAllocatedBuffer * next;

    /* Data goes here */
} I_RpcAllocatedBuffer;

#define I_RPCALLOCATEDBUFFER_FROM_PTR(v) (&(((I_RpcAllocatedBuffer *) (v))[-1]))

RPC_STATUS
RPC_ENTRY
I_RpcGetBuffer (IN OUT RPC_MESSAGE __RPC_FAR * Message)
{
    I_RpcAllocatedBuffer * newBuffer = I_RpcAllocate(Message->BufferLength + sizeof(I_RpcAllocatedBuffer));
    I_RpcAllocatedBuffer * oldBuffer = NULL;

    if (Message->Buffer) {
	oldBuffer = I_RPCALLOCATEDBUFFER_FROM_PTR(Message->Buffer);
    }

    if (newBuffer) {
	newBuffer->magic = _RPCALLOCATEDBUFFER_MAGIC;
	newBuffer->next = oldBuffer;

	Message->Buffer = &newBuffer[1];
    } else {
	return ERROR_OUTOFMEMORY;
    }

    return RPC_S_OK;
}

RPC_STATUS
RPC_ENTRY
I_RpcFreeBuffer (IN OUT RPC_MESSAGE __RPC_FAR * Message)
{
    if (Message->Buffer) {
	I_RpcAllocatedBuffer * buffer;

	buffer = I_RPCALLOCATEDBUFFER_FROM_PTR(Message->Buffer);

	while (buffer) {
	    I_RpcAllocatedBuffer * bnext;

	    bnext = buffer->next;

	    I_RpcFree(buffer);

	    buffer = bnext;
	}

	Message->Buffer = NULL;
    }

    return RPC_S_OK;
}

unsigned char  *
RPC_ENTRY
NdrServerInitializeNew(
    PRPC_MESSAGE            pRpcMsg,
    PMIDL_STUB_MESSAGE      pStubMsg,
    PMIDL_STUB_DESC         pStubDesc
    )
{
    ZeroMemory(pStubMsg, sizeof(*pStubMsg));

    pStubMsg->RpcMsg = pRpcMsg;
    pStubMsg->Buffer = pRpcMsg->Buffer;
    pStubMsg->BufferStart = pRpcMsg->Buffer;
    pStubMsg->BufferEnd = pStubMsg->Buffer + pRpcMsg->BufferLength;
    pStubMsg->BufferLength = pRpcMsg->BufferLength;
    pStubMsg->MemorySize = pRpcMsg->BufferLength;
    pStubMsg->pfnAllocate = pStubDesc->pfnAllocate;
    pStubMsg->pfnFree = pStubDesc->pfnFree;
    pStubMsg->StubDesc = pStubDesc;
    pStubMsg->dwDestContext = MSHCTX_LOCAL;

    return NULL;
}

void
MSRPC_Init(void) {
    RPC_SRVSVC_Init();
}

void
MSRPC_Shutdown(void) {
    RPC_SRVSVC_Shutdown();
}

int
MSRPC_IsWellKnownService(const clientchar_t * lastNamep)
{
    return
	cm_ClientStrCmpIA(lastNamep,  _C("\\srvsvc")) == 0 ||
	cm_ClientStrCmpIA(lastNamep,  _C("\\wkssvc")) == 0 ||
	cm_ClientStrCmpIA(lastNamep,  _C("\\spoolss")) == 0 ||
	cm_ClientStrCmpIA(lastNamep,  _C("\\winreg")) == 0 ||
	cm_ClientStrCmpIA(lastNamep,  _C("\\lsass")) == 0 ||
	cm_ClientStrCmpIA(lastNamep,  _C("\\lsarpc")) == 0 ||
	cm_ClientStrCmpIA(lastNamep,  _C("\\samr")) == 0 ||
	cm_ClientStrCmpIA(lastNamep,  _C("\\netlogon")) == 0 ||
	cm_ClientStrCmpIA(lastNamep,  _C("\\ntsvcs")) == 0 ||
	cm_ClientStrCmpIA(lastNamep,  _C("\\eventlog")) == 0 ||
	cm_ClientStrCmpIA(lastNamep,  _C("\\svcctl")) == 0 ||
	cm_ClientStrCmpIA(lastNamep,  _C("\\browse")) == 0 ||
	cm_ClientStrCmpIA(lastNamep,  _C("\\msgsvc")) == 0 ||
	cm_ClientStrCmpIA(lastNamep,  _C("\\w32time")) == 0 ||
	cm_ClientStrCmpIA(lastNamep,  _C("ipc$")) == 0;
}
