view usr/src/lib/smbsrv/libmlrpc/common/ndr_ops.c @ 9914:15092dda0737

6847056 smb_ads_computer_op() dumps core when joining Win 2008 R2 domain 6848702 NDR buffer decode support 6797780 return code mismatches 6850931 SMB volume properties Quota tab display 6851425 Unable to create hard links via SMB/CIFS using cygwin 6840353 Fail to set or retrieve the share ACL using Windows test program
author Alan Wright <amw@Sun.COM>
date Fri, 19 Jun 2009 12:13:15 -0600
parents 5f1c6a3b0fad
children 1f599611bc1f
line wrap: on
line source

/*
 * 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 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * Server-side NDR stream (PDU) operations. Stream operations should
 * return TRUE (non-zero) on success or FALSE (zero or a null pointer)
 * on failure. When an operation returns FALSE, including ndo_malloc()
 * returning NULL, it should set the nds->error to indicate what went
 * wrong.
 *
 * When available, the relevant ndr reference is passed to the
 * operation but keep in mind that it may be a null pointer.
 *
 * Functions ndo_get_pdu(), ndo_put_pdu(), and ndo_pad_pdu()
 * must never grow the PDU data. A request for out-of-bounds data is
 * an error. The swap_bytes flag is 1 if NDR knows that the byte-
 * order in the PDU is different from the local system.
 */

#include <sys/types.h>
#include <stdarg.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <string.h>
#include <assert.h>

#include <smbsrv/libsmb.h>
#include <smbsrv/libmlrpc.h>
#include <smbsrv/ntstatus.h>

#define	NDOBUFSZ		128

#define	NDR_PDU_BLOCK_SIZE	(4*1024)
#define	NDR_PDU_BLOCK_MASK	(NDR_PDU_BLOCK_SIZE - 1)
#define	NDR_PDU_ALIGN(N) \
	(((N) + NDR_PDU_BLOCK_SIZE) & ~NDR_PDU_BLOCK_MASK)
#define	NDR_PDU_MAX_SIZE		(64*1024*1024)

static char *ndo_malloc(ndr_stream_t *, unsigned, ndr_ref_t *);
static int ndo_free(ndr_stream_t *, char *, ndr_ref_t *);
static int ndo_grow_pdu(ndr_stream_t *, unsigned long, ndr_ref_t *);
static int ndo_pad_pdu(ndr_stream_t *, unsigned long, unsigned long,
    ndr_ref_t *);
static int ndo_get_pdu(ndr_stream_t *, unsigned long, unsigned long,
    char *, int, ndr_ref_t *);
static int ndo_put_pdu(ndr_stream_t *, unsigned long, unsigned long,
    char *, int, ndr_ref_t *);
static void ndo_tattle(ndr_stream_t *, char *, ndr_ref_t *);
static void ndo_tattle_error(ndr_stream_t *, ndr_ref_t *);
static int ndo_reset(ndr_stream_t *);
static void ndo_destruct(ndr_stream_t *);
static void ndo_hexfmt(uint8_t *, int, int, char *, int);

/*
 * The ndr stream operations table.
 */
static ndr_stream_ops_t nds_ops = {
    ndo_malloc,
    ndo_free,
    ndo_grow_pdu,
    ndo_pad_pdu,
    ndo_get_pdu,
    ndo_put_pdu,
    ndo_tattle,
    ndo_tattle_error,
    ndo_reset,
    ndo_destruct
};

/*
 * nds_bswap
 *
 * Copies len bytes from src to dst such that dst contains the bytes
 * from src in reverse order.
 *
 * We expect to be dealing with bytes, words, dwords etc. So the
 * length must be non-zero and a power of 2.
 */
void
nds_bswap(void *srcbuf, void *dstbuf, size_t len)
{
	uint8_t *src = (uint8_t *)srcbuf;
	uint8_t *dst = (uint8_t *)dstbuf;

	if ((len != 0) && ((len & (len - 1)) == 0)) {
		src += len;

		while (len--)
			*dst++ = *(--src);
	}
}

/*
 * nds_initialize
 *
 * Initialize a stream. Sets up the PDU parameters and assigns the stream
 * operations and the reference to the heap. An external heap is provided
 * to the stream, rather than each stream creating its own heap.
 */
void
nds_initialize(ndr_stream_t *nds, unsigned pdu_size_hint,
    int composite_op, ndr_heap_t *heap)
{
	unsigned size;

	assert(nds);
	assert(heap);

	bzero(nds, sizeof (*nds));

	if (pdu_size_hint > NDR_PDU_MAX_SIZE)
		return;

	size = (pdu_size_hint == 0) ? NDR_PDU_BLOCK_SIZE : pdu_size_hint;
	nds->pdu_base_addr = malloc(size);
	assert(nds->pdu_base_addr);

	nds->pdu_max_size = size;
	nds->pdu_size = 0;
	nds->pdu_base_offset = (unsigned long)nds->pdu_base_addr;

	nds->ndo = &nds_ops;
	nds->heap = (struct ndr_heap *)heap;

	nds->m_op = NDR_MODE_TO_M_OP(composite_op);
	nds->dir  = NDR_MODE_TO_DIR(composite_op);

	nds->outer_queue_tailp = &nds->outer_queue_head;
}

void
nds_finalize(ndr_stream_t *nds, ndr_fraglist_t *frags)
{
	iovec_t *iov;
	ndr_frag_t *frag;
	uint32_t size = 0;

	bzero(frags, sizeof (ndr_fraglist_t));

	for (frag = nds->frags.head; frag; frag = frag->next)
		size += frag->len;

	if (size == 0 || size >= NDR_PDU_MAX_SIZE)
		return;

	frags->iov = malloc(nds->frags.nfrag * sizeof (iovec_t));
	if (frags->iov == NULL)
		return;

	frags->head = nds->frags.head;
	frags->tail = nds->frags.tail;
	frags->nfrag = nds->frags.nfrag;
	bzero(&nds->frags, sizeof (ndr_fraglist_t));

	frags->uio.uio_iov = frags->iov;
	frags->uio.uio_iovcnt = frags->nfrag;
	frags->uio.uio_offset = 0;
	frags->uio.uio_segflg = UIO_USERSPACE;
	frags->uio.uio_resid = size;

	iov = frags->uio.uio_iov;
	for (frag = frags->head; frag; frag = frag->next) {
		iov->iov_base = (caddr_t)frag->buf;
		iov->iov_len = frag->len;
		++iov;
	}
}

/*
 * nds_destruct
 *
 * Destroy a stream. This is an external interface to provide access to
 * the stream's destruct operation.
 */
void
nds_destruct(ndr_stream_t *nds)
{
	NDS_DESTRUCT(nds);
}

/*
 * ndo_malloc
 *
 * Allocate memory from the stream heap.
 */
/*ARGSUSED*/
static char *
ndo_malloc(ndr_stream_t *nds, unsigned len, ndr_ref_t *ref)
{
	return (ndr_heap_malloc((ndr_heap_t *)nds->heap, len));
}

/*
 * ndo_free
 *
 * Always succeeds: cannot free individual stream allocations.
 */
/*ARGSUSED*/
static int
ndo_free(ndr_stream_t *nds, char *p, ndr_ref_t *ref)
{
	return (1);
}

/*
 * ndo_grow_pdu
 *
 * This is the only place that should change the size of the PDU. If the
 * desired offset is beyond the current PDU size, we realloc the PDU
 * buffer to accommodate the request. For efficiency, the PDU is always
 * extended to a NDR_PDU_BLOCK_SIZE boundary. Requests to grow the PDU
 * beyond NDR_PDU_MAX_SIZE are rejected.
 *
 * Returns 1 to indicate success. Otherwise 0 to indicate failure.
 */
static int
ndo_grow_pdu(ndr_stream_t *nds, unsigned long want_end_offset, ndr_ref_t *ref)
{
	unsigned char *pdu_addr;
	unsigned pdu_max_size;

	ndo_printf(nds, ref, "grow %d", want_end_offset);

	pdu_max_size = nds->pdu_max_size;

	if (want_end_offset > pdu_max_size) {
		pdu_max_size = NDR_PDU_ALIGN(want_end_offset);

		if (pdu_max_size >= NDR_PDU_MAX_SIZE)
			return (0);

		pdu_addr = realloc(nds->pdu_base_addr, pdu_max_size);
		if (pdu_addr == 0)
			return (0);

		nds->pdu_max_size = pdu_max_size;
		nds->pdu_base_addr = pdu_addr;
		nds->pdu_base_offset = (unsigned long)pdu_addr;
	}

	nds->pdu_size = want_end_offset;
	return (1);
}

static int
ndo_pad_pdu(ndr_stream_t *nds, unsigned long pdu_offset,
    unsigned long n_bytes, ndr_ref_t *ref)
{
	unsigned char *data;

	data = (unsigned char *)nds->pdu_base_offset;
	data += pdu_offset;

	ndo_printf(nds, ref, "pad %d@%-3d", n_bytes, pdu_offset);

	bzero(data, n_bytes);
	return (1);
}

/*
 * ndo_get_pdu
 *
 * The swap flag is 1 if NDR knows that the byte-order in the PDU
 * is different from the local system.
 *
 * Returns 1 on success or 0 to indicate failure.
 */
static int
ndo_get_pdu(ndr_stream_t *nds, unsigned long pdu_offset,
    unsigned long n_bytes, char *buf, int swap_bytes, ndr_ref_t *ref)
{
	unsigned char *data;
	char hexbuf[NDOBUFSZ];

	data = (unsigned char *)nds->pdu_base_offset;
	data += pdu_offset;

	ndo_hexfmt(data, n_bytes, swap_bytes, hexbuf, NDOBUFSZ);

	ndo_printf(nds, ref, "get %d@%-3d = %s",
	    n_bytes, pdu_offset, hexbuf);

	if (!swap_bytes)
		bcopy(data, buf, n_bytes);
	else
		nds_bswap(data, (unsigned char *)buf, n_bytes);

	return (1);
}

/*
 * ndo_put_pdu
 *
 * This is a receiver makes right protocol. So we do not need
 * to be concerned about the byte-order of an outgoing PDU.
 */
/*ARGSUSED*/
static int
ndo_put_pdu(ndr_stream_t *nds, unsigned long pdu_offset,
    unsigned long n_bytes, char *buf, int swap_bytes, ndr_ref_t *ref)
{
	unsigned char *data;
	char hexbuf[NDOBUFSZ];

	data = (unsigned char *)nds->pdu_base_offset;
	data += pdu_offset;

	ndo_hexfmt((uint8_t *)buf, n_bytes, 0, hexbuf, NDOBUFSZ);

	ndo_printf(nds, ref, "put %d@%-3d = %s",
	    n_bytes, pdu_offset, hexbuf);

	bcopy(buf, data, n_bytes);
	return (1);
}

static void
ndo_tattle(ndr_stream_t *nds, char *what, ndr_ref_t *ref)
{
	ndo_printf(nds, ref, what);
}

static void
ndo_tattle_error(ndr_stream_t *nds, ndr_ref_t *ref)
{
	unsigned char *data;
	char hexbuf[NDOBUFSZ];

	data = (unsigned char *)nds->pdu_base_offset;
	if (ref)
		data += ref->pdu_offset;
	else
		data += nds->pdu_scan_offset;

	ndo_hexfmt(data, 16, 0, hexbuf, NDOBUFSZ);

	ndo_printf(nds, ref, "ERROR=%d REF=%d OFFSET=%d SIZE=%d/%d",
	    nds->error, nds->error_ref, nds->pdu_scan_offset,
	    nds->pdu_size, nds->pdu_max_size);
	ndo_printf(nds, ref, "      %s", hexbuf);
}

/*
 * ndo_reset
 *
 * Reset a stream: zap the outer_queue. We don't need to tamper
 * with the stream heap: it's handled externally to the stream.
 */
static int
ndo_reset(ndr_stream_t *nds)
{
	ndo_printf(nds, 0, "reset");

	nds->pdu_size = 0;
	nds->pdu_scan_offset = 0;
	nds->outer_queue_head = 0;
	nds->outer_current = 0;
	nds->outer_queue_tailp = &nds->outer_queue_head;

	return (1);
}

/*
 * ndo_destruct
 *
 * Destruct a stream: zap the outer_queue.
 * Note: heap management (creation/destruction) is external to the stream.
 */
static void
ndo_destruct(ndr_stream_t *nds)
{
	ndr_frag_t *frag;

	ndo_printf(nds, 0, "destruct");

	if (nds == NULL)
		return;

	if (nds->pdu_base_addr != NULL) {
		free(nds->pdu_base_addr);
		nds->pdu_base_addr = NULL;
		nds->pdu_base_offset = 0;
	}

	while ((frag = nds->frags.head) != NULL) {
		nds->frags.head = frag->next;
		free(frag);
	}

	bzero(&nds->frags, sizeof (ndr_fraglist_t));

	nds->outer_queue_head = 0;
	nds->outer_current = 0;
	nds->outer_queue_tailp = &nds->outer_queue_head;
}

/*
 * Printf style formatting for NDR operations.
 */
void
ndo_printf(ndr_stream_t *nds, ndr_ref_t *ref, const char *fmt, ...)
{
	va_list ap;
	char buf[NDOBUFSZ];

	va_start(ap, fmt);
	(void) vsnprintf(buf, NDOBUFSZ, fmt, ap);
	va_end(ap);

	if (nds)
		ndo_fmt(nds, ref, buf);
	else
		ndo_trace(buf);
}

/*
 * Main output formatter for NDR operations.
 *
 *	UI 03 ... rpc_vers           get 1@0   =    5 {05}
 *	UI 03 ... rpc_vers_minor     get 1@1   =    0 {00}
 *
 *	U       Marshalling flag (M=marshal, U=unmarshal)
 *	I       Direction flag (I=in, O=out)
 *	...     Field name
 *	get     PDU operation (get or put)
 *	1@0	Bytes @ offset (i.e. 1 byte at offset 0)
 *	{05}    Value
 */
void
ndo_fmt(ndr_stream_t *nds, ndr_ref_t *ref, char *note)
{
	ndr_ref_t	*p;
	int		indent;
	char		ref_name[NDOBUFSZ];
	char		buf[NDOBUFSZ];
	int		m_op_c = '?', dir_c = '?';

	switch (nds->m_op) {
	case 0:				m_op_c = '-';	break;
	case NDR_M_OP_MARSHALL:		m_op_c = 'M';	break;
	case NDR_M_OP_UNMARSHALL:	m_op_c = 'U';	break;
	default:			m_op_c = '?';	break;
	}

	switch (nds->dir) {
	case 0:				dir_c = '-';	break;
	case NDR_DIR_IN:		dir_c = 'I';	break;
	case NDR_DIR_OUT:		dir_c = 'O';	break;
	default:			dir_c = '?';	break;
	}

	for (indent = 0, p = ref; p; p = p->enclosing)
		indent++;

	if (ref && ref->name) {
		if (*ref->name == '[' && ref->enclosing) {
			indent--;
			(void) snprintf(ref_name, NDOBUFSZ, "%s%s",
			    ref->enclosing->name, ref->name);
		} else {
			(void) strlcpy(ref_name, ref->name, NDOBUFSZ);
		}
	} else {
		(void) strlcpy(ref_name, "----", NDOBUFSZ);
	}

	(void) snprintf(buf, NDOBUFSZ, "%c%c %-.*s %-*s  %s",
	    m_op_c, dir_c, indent,
	    "....+....+....+....+....+....",
	    20 - indent, ref_name, note);

	ndo_trace(buf);
}

/*ARGSUSED*/
void
ndo_trace(const char *s)
{
	/*
	 * Temporary fbt for dtrace until user space sdt enabled.
	 */
}

/*
 * Format data as hex bytes (limit is 10 bytes):
 *
 *	1188689424 {10 f6 d9 46}
 *
 * If the input data is greater than 10 bytes, an ellipsis will
 * be inserted before the closing brace.
 */
static void
ndo_hexfmt(uint8_t *data, int size, int swap_bytes, char *buf, int len)
{
	char *p = buf;
	int interp = 1;
	uint32_t c;
	int n;
	int i;

	n = (size > 10) ? 10 : size;
	if (n > len-1)
		n = len-1;

	switch (size) {
	case 1:
		c = *(uint8_t *)data;
		break;
	case 2:
		if (swap_bytes == 0) /*LINTED E_BAD_PTR_CAST_ALIGN*/
			c = *(uint16_t *)data;
		else
			c = (data[0] << 8) | data[1];
		break;
	case 4:
		if (swap_bytes == 0) { /*LINTED E_BAD_PTR_CAST_ALIGN*/
			c = *(uint32_t *)data;
		} else {
			c = (data[0] << 24) | (data[1] << 16)
			    | (data[2] << 8) | data[3];
		}
		break;
	default:
		c = 0;
		interp = 0;
		break;
	}

	if (interp)
		p += sprintf(p, "%4u {", c);
	else
		p += sprintf(p, " {");

	p += sprintf(p, "%02x", data[0]);
	for (i = 1; i < n; i++)
		p += sprintf(p, " %02x", data[i]);
	if (size > 10)
		p += sprintf(p, " ...}");
	else
		p += sprintf(p, "}");

	/*
	 * Show c if it's a printable character or wide-char.
	 */
	if (size < 4 && isprint((uint8_t)c))
		(void) sprintf(p, " %c", (uint8_t)c);
}