summaryrefslogtreecommitdiff
path: root/usr/src/common/smbsrv/smb_msgbuf.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/common/smbsrv/smb_msgbuf.c')
-rw-r--r--usr/src/common/smbsrv/smb_msgbuf.c709
1 files changed, 709 insertions, 0 deletions
diff --git a/usr/src/common/smbsrv/smb_msgbuf.c b/usr/src/common/smbsrv/smb_msgbuf.c
new file mode 100644
index 0000000000..d456dac683
--- /dev/null
+++ b/usr/src/common/smbsrv/smb_msgbuf.c
@@ -0,0 +1,709 @@
+/*
+ * 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 2007 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+ * Msgbuf buffer management implementation. The smb_msgbuf interface is
+ * typically used to encode or decode SMB data using sprintf/scanf
+ * style operations. It contains special handling for the SMB header.
+ * It can also be used for general purpose encoding and decoding.
+ */
+
+#include <sys/types.h>
+#include <sys/varargs.h>
+#include <sys/byteorder.h>
+#ifndef _KERNEL
+#include <stdlib.h>
+#include <syslog.h>
+#include <string.h>
+#include <strings.h>
+#else
+#include <sys/sunddi.h>
+#include <sys/kmem.h>
+#endif
+#include <smbsrv/string.h>
+#include <smbsrv/msgbuf.h>
+#include <smbsrv/smb.h>
+
+static int buf_decode(smb_msgbuf_t *, char *, va_list ap);
+static int buf_encode(smb_msgbuf_t *, char *, va_list ap);
+static void *smb_msgbuf_malloc(smb_msgbuf_t *, size_t);
+static int smb_msgbuf_chkerc(char *text, int erc);
+static void buf_decode_wcs(mts_wchar_t *, mts_wchar_t *, int wcstrlen);
+
+/*
+ * Returns the offset or number of bytes used within the buffer.
+ */
+size_t
+smb_msgbuf_used(smb_msgbuf_t *mb)
+{
+ /*LINTED E_PTRDIFF_OVERFLOW*/
+ return (mb->scan - mb->base);
+}
+
+/*
+ * Returns the actual buffer size.
+ */
+size_t
+smb_msgbuf_size(smb_msgbuf_t *mb)
+{
+ return (mb->max);
+}
+
+uint8_t *
+smb_msgbuf_base(smb_msgbuf_t *mb)
+{
+ return (mb->base);
+}
+
+/*
+ * Ensure that the scan is aligned on a word (16-bit) boundary.
+ */
+void
+smb_msgbuf_word_align(smb_msgbuf_t *mb)
+{
+ mb->scan = (uint8_t *)((uintptr_t)(mb->scan + 1) & ~1);
+}
+
+/*
+ * Ensure that the scan is aligned on a dword (32-bit) boundary.
+ */
+void
+smb_msgbuf_dword_align(smb_msgbuf_t *mb)
+{
+ mb->scan = (uint8_t *)((uintptr_t)(mb->scan + 3) & ~3);
+}
+
+/*
+ * Checks whether or not the buffer has space for the amount of data
+ * specified. Returns 1 if there is space, otherwise returns 0.
+ */
+int
+smb_msgbuf_has_space(smb_msgbuf_t *mb, size_t size)
+{
+ if (size > mb->max || (mb->scan + size) > mb->end)
+ return (0);
+
+ return (1);
+}
+
+/*
+ * Set flags the smb_msgbuf.
+ */
+void
+smb_msgbuf_fset(smb_msgbuf_t *mb, uint32_t flags)
+{
+ mb->flags |= flags;
+}
+
+/*
+ * Clear flags the smb_msgbuf.
+ */
+void
+smb_msgbuf_fclear(smb_msgbuf_t *mb, uint32_t flags)
+{
+ mb->flags &= ~flags;
+}
+
+/*
+ * smb_msgbuf_init
+ *
+ * Initialize a smb_msgbuf_t structure based on the buffer and size
+ * specified. Both scan and base initially point to the beginning
+ * of the buffer and end points to the limit of the buffer. As
+ * data is added scan should be incremented to point to the next
+ * offset at which data will be written. Max and count are set
+ * to the actual buffer size.
+ */
+void
+smb_msgbuf_init(smb_msgbuf_t *mb, uint8_t *buf, size_t size, uint32_t flags)
+{
+ mb->scan = mb->base = buf;
+ mb->max = mb->count = size;
+ mb->end = &buf[size];
+ mb->flags = flags;
+ mb->mlist.next = 0;
+}
+
+
+/*
+ * smb_msgbuf_term
+ *
+ * Destruct a smb_msgbuf_t. Free any memory hanging off the mlist.
+ */
+void
+smb_msgbuf_term(smb_msgbuf_t *mb)
+{
+ smb_msgbuf_mlist_t *item = mb->mlist.next;
+ smb_msgbuf_mlist_t *tmp;
+
+ while (item) {
+ tmp = item;
+ item = item->next;
+#ifndef _KERNEL
+ free(tmp);
+#else
+ kmem_free(tmp, tmp->size);
+#endif
+ }
+}
+
+
+/*
+ * smb_msgbuf_decode
+ *
+ * Decode a smb_msgbuf buffer as indicated by the format string into
+ * the variable arg list. This is similar to a scanf operation.
+ *
+ * On success, returns the number of bytes encoded. Otherwise
+ * returns a -ve error code.
+ */
+int
+smb_msgbuf_decode(smb_msgbuf_t *mb, char *fmt, ...)
+{
+ int rc;
+ uint8_t *orig_scan;
+ va_list ap;
+
+ va_start(ap, fmt);
+ orig_scan = mb->scan;
+ rc = buf_decode(mb, fmt, ap);
+ va_end(ap);
+
+ if (rc != SMB_MSGBUF_SUCCESS) {
+ (void) smb_msgbuf_chkerc("smb_msgbuf_decode", rc);
+ mb->scan = orig_scan;
+ return (rc);
+ }
+
+ /*LINTED E_PTRDIFF_OVERFLOW*/
+ return (mb->scan - orig_scan);
+}
+
+
+/*
+ * buf_decode
+ *
+ * Private decode function, where the real work of decoding the smb_msgbuf
+ * is done. This function should only be called via smb_msgbuf_decode to
+ * ensure correct behaviour and error handling.
+ */
+static int
+buf_decode(smb_msgbuf_t *mb, char *fmt, va_list ap)
+{
+ uint32_t ival;
+ uint8_t c;
+ uint8_t *cvalp;
+ uint8_t **cvalpp;
+ uint16_t *wvalp;
+ uint32_t *lvalp;
+ uint64_t *llvalp;
+ mts_wchar_t *wcs;
+ int repc;
+ int rc;
+
+ while ((c = *fmt++) != 0) {
+ repc = 1;
+
+ if (c == ' ' || c == '\t')
+ continue;
+
+ if (c == '(') {
+ while (((c = *fmt++) != 0) && c != ')')
+ ;
+
+ if (!c)
+ return (SMB_MSGBUF_SUCCESS);
+
+ continue;
+ }
+
+ if ('0' <= c && c <= '9') {
+ repc = 0;
+ do {
+ repc = repc * 10 + c - '0';
+ c = *fmt++;
+ } while ('0' <= c && c <= '9');
+ } else if (c == '#') {
+ repc = va_arg(ap, int);
+ c = *fmt++;
+ }
+
+ switch (c) {
+ case '.':
+ if (smb_msgbuf_has_space(mb, repc) == 0)
+ return (SMB_MSGBUF_UNDERFLOW);
+
+ mb->scan += repc;
+ break;
+
+ case 'c':
+ if (smb_msgbuf_has_space(mb, repc) == 0)
+ return (SMB_MSGBUF_UNDERFLOW);
+
+ cvalp = va_arg(ap, uint8_t *);
+ bcopy(mb->scan, cvalp, repc);
+ mb->scan += repc;
+ break;
+
+ case 'b':
+ if (smb_msgbuf_has_space(mb, repc) == 0)
+ return (SMB_MSGBUF_UNDERFLOW);
+
+ cvalp = va_arg(ap, uint8_t *);
+ while (repc-- > 0) {
+ *cvalp++ = *mb->scan++;
+ }
+ break;
+
+ case 'w':
+ rc = smb_msgbuf_has_space(mb, repc * sizeof (uint16_t));
+ if (rc == 0)
+ return (SMB_MSGBUF_UNDERFLOW);
+
+ wvalp = va_arg(ap, uint16_t *);
+ while (repc-- > 0) {
+ *wvalp++ = LE_IN16(mb->scan);
+ mb->scan += sizeof (uint16_t);
+ }
+ break;
+
+ case 'l':
+ rc = smb_msgbuf_has_space(mb, repc * sizeof (int32_t));
+ if (rc == 0)
+ return (SMB_MSGBUF_UNDERFLOW);
+
+ lvalp = va_arg(ap, uint32_t *);
+ while (repc-- > 0) {
+ *lvalp++ = LE_IN32(mb->scan);
+ mb->scan += sizeof (int32_t);
+ }
+ break;
+
+ case 'q':
+ rc = smb_msgbuf_has_space(mb, repc * sizeof (int64_t));
+ if (rc == 0)
+ return (SMB_MSGBUF_UNDERFLOW);
+
+ llvalp = va_arg(ap, uint64_t *);
+ while (repc-- > 0) {
+ *llvalp++ = LE_IN64(mb->scan);
+ mb->scan += sizeof (int64_t);
+ }
+ break;
+
+ case 'u': /* Convert from unicode if flags are set */
+ if (mb->flags & SMB_MSGBUF_UNICODE)
+ goto unicode_translation;
+ /*FALLTHROUGH*/
+
+ case 's':
+ ival = strlen((const char *)mb->scan) + 1;
+ if (smb_msgbuf_has_space(mb, ival) == 0)
+ return (SMB_MSGBUF_UNDERFLOW);
+
+ if ((cvalp = smb_msgbuf_malloc(mb, ival * 2)) == 0)
+ return (SMB_MSGBUF_UNDERFLOW);
+
+ if ((ival = mts_stombs((char *)cvalp,
+ (char *)mb->scan, ival * 2)) ==
+ (uint32_t)-1) {
+ return (SMB_MSGBUF_DATA_ERROR);
+ }
+
+ cvalpp = va_arg(ap, uint8_t **);
+ *cvalpp = cvalp;
+ mb->scan += (ival+1);
+ break;
+
+ case 'U': /* Convert from unicode */
+unicode_translation:
+ /*
+ * Unicode strings are always word aligned.
+ * The malloc'd area is larger than the
+ * original string because the UTF-8 chars
+ * may be longer than the wide-chars.
+ */
+ smb_msgbuf_word_align(mb);
+ /*LINTED E_BAD_PTR_CAST_ALIGN*/
+ wcs = (mts_wchar_t *)mb->scan;
+
+ /* count the null wchar */
+ repc = sizeof (mts_wchar_t);
+ while (*wcs++)
+ repc += sizeof (mts_wchar_t);
+
+ if (smb_msgbuf_has_space(mb, repc) == 0)
+ return (SMB_MSGBUF_UNDERFLOW);
+
+ /* Decode wchar string into host byte-order */
+ if ((wcs = smb_msgbuf_malloc(mb, repc)) == 0)
+ return (SMB_MSGBUF_UNDERFLOW);
+
+ /*LINTED E_BAD_PTR_CAST_ALIGN*/
+ buf_decode_wcs(wcs, (mts_wchar_t *)mb->scan,
+ repc / sizeof (mts_wchar_t));
+
+ /* Get space for translated string */
+ if ((cvalp = smb_msgbuf_malloc(mb, repc * 2)) == 0)
+ return (SMB_MSGBUF_UNDERFLOW);
+
+ /* Translate string */
+ (void) mts_wcstombs((char *)cvalp, wcs, repc * 2);
+
+ cvalpp = va_arg(ap, uint8_t **);
+ *cvalpp = cvalp;
+ mb->scan += repc;
+ break;
+
+ case 'M':
+ if (smb_msgbuf_has_space(mb, 4) == 0)
+ return (SMB_MSGBUF_UNDERFLOW);
+
+ if (mb->scan[0] != 0xFF ||
+ mb->scan[1] != 'S' ||
+ mb->scan[2] != 'M' ||
+ mb->scan[3] != 'B') {
+ return (SMB_MSGBUF_INVALID_HEADER);
+ }
+ mb->scan += 4;
+ break;
+
+ default:
+ return (SMB_MSGBUF_INVALID_FORMAT);
+ }
+ }
+
+ return (SMB_MSGBUF_SUCCESS);
+}
+
+
+/*
+ * smb_msgbuf_encode
+ *
+ * Encode a smb_msgbuf buffer as indicated by the format string using
+ * the variable arg list. This is similar to a sprintf operation.
+ *
+ * On success, returns the number of bytes encoded. Otherwise
+ * returns a -ve error code.
+ */
+int
+smb_msgbuf_encode(smb_msgbuf_t *mb, char *fmt, ...)
+{
+ int rc;
+ uint8_t *orig_scan;
+ va_list ap;
+
+ va_start(ap, fmt);
+ orig_scan = mb->scan;
+ rc = buf_encode(mb, fmt, ap);
+ va_end(ap);
+
+ if (rc != SMB_MSGBUF_SUCCESS) {
+ (void) smb_msgbuf_chkerc("smb_msgbuf_encode", rc);
+ mb->scan = orig_scan;
+ return (rc);
+ }
+
+ /*LINTED E_PTRDIFF_OVERFLOW*/
+ return (mb->scan - orig_scan);
+}
+
+
+/*
+ * buf_encode
+ *
+ * Private encode function, where the real work of encoding the smb_msgbuf
+ * is done. This function should only be called via smb_msgbuf_encode to
+ * ensure correct behaviour and error handling.
+ */
+static int
+buf_encode(smb_msgbuf_t *mb, char *fmt, va_list ap)
+{
+ uint8_t cval;
+ uint16_t wval;
+ uint32_t lval;
+ uint64_t llval;
+ uint32_t ival;
+ uint8_t *cvalp;
+ uint8_t c;
+ mts_wchar_t wcval;
+ int count;
+ int repc = 1;
+ int rc;
+
+ while ((c = *fmt++) != 0) {
+ repc = 1;
+
+ if (c == ' ' || c == '\t')
+ continue;
+
+ if (c == '(') {
+ while (((c = *fmt++) != 0) && c != ')')
+ ;
+
+ if (!c)
+ return (SMB_MSGBUF_SUCCESS);
+
+ continue;
+ }
+
+ if ('0' <= c && c <= '9') {
+ repc = 0;
+ do {
+ repc = repc * 10 + c - '0';
+ c = *fmt++;
+ } while ('0' <= c && c <= '9');
+ } else if (c == '#') {
+ repc = va_arg(ap, int);
+ c = *fmt++;
+ }
+
+ switch (c) {
+ case '.':
+ if (smb_msgbuf_has_space(mb, repc) == 0)
+ return (SMB_MSGBUF_OVERFLOW);
+
+ while (repc-- > 0)
+ *mb->scan++ = 0;
+ break;
+
+ case 'c':
+ if (smb_msgbuf_has_space(mb, repc) == 0)
+ return (SMB_MSGBUF_OVERFLOW);
+
+ cvalp = va_arg(ap, uint8_t *);
+ bcopy(cvalp, mb->scan, repc);
+ mb->scan += repc;
+ break;
+
+ case 'b':
+ if (smb_msgbuf_has_space(mb, repc) == 0)
+ return (SMB_MSGBUF_OVERFLOW);
+
+ while (repc-- > 0) {
+ cval = va_arg(ap, int);
+ *mb->scan++ = cval;
+ }
+ break;
+
+ case 'w':
+ rc = smb_msgbuf_has_space(mb, repc * sizeof (uint16_t));
+ if (rc == 0)
+ return (SMB_MSGBUF_OVERFLOW);
+
+ while (repc-- > 0) {
+ wval = va_arg(ap, int);
+ LE_OUT16(mb->scan, wval);
+ mb->scan += sizeof (uint16_t);
+ }
+ break;
+
+ case 'l':
+ rc = smb_msgbuf_has_space(mb, repc * sizeof (int32_t));
+ if (rc == 0)
+ return (SMB_MSGBUF_OVERFLOW);
+
+ while (repc-- > 0) {
+ lval = va_arg(ap, uint32_t);
+ LE_OUT32(mb->scan, lval);
+ mb->scan += sizeof (int32_t);
+ }
+ break;
+
+ case 'q':
+ rc = smb_msgbuf_has_space(mb, repc * sizeof (int64_t));
+ if (rc == 0)
+ return (SMB_MSGBUF_OVERFLOW);
+
+ while (repc-- > 0) {
+ llval = va_arg(ap, uint64_t);
+ LE_OUT64(mb->scan, llval);
+ mb->scan += sizeof (uint64_t);
+ }
+ break;
+
+ case 'u': /* conditional unicode */
+ if (mb->flags & SMB_MSGBUF_UNICODE)
+ goto unicode_translation;
+ /* FALLTHROUGH */
+
+ case 's':
+ cvalp = va_arg(ap, uint8_t *);
+ ival = strlen((const char *)cvalp) + 1;
+
+ if (smb_msgbuf_has_space(mb, ival) == 0)
+ return (SMB_MSGBUF_OVERFLOW);
+
+ ival =
+ mts_mbstos((char *)mb->scan, (const char *)cvalp);
+ mb->scan += ival + 1;
+ break;
+
+ case 'U': /* unicode */
+unicode_translation:
+ /*
+ * Unicode strings are always word aligned.
+ */
+ smb_msgbuf_word_align(mb);
+ cvalp = va_arg(ap, uint8_t *);
+
+ for (;;) {
+ rc = smb_msgbuf_has_space(mb,
+ sizeof (mts_wchar_t));
+ if (rc == 0)
+ return (SMB_MSGBUF_OVERFLOW);
+
+ count = mts_mbtowc(&wcval, (const char *)cvalp,
+ MTS_MB_CHAR_MAX);
+
+ if (count < 0) {
+ return (SMB_MSGBUF_DATA_ERROR);
+ } else if (count == 0) {
+ /*
+ * No longer need to do this now that
+ * mbtowc correctly writes the null
+ * before returning zero but paranoia
+ * wins.
+ */
+ wcval = 0;
+ count = 1;
+ }
+
+ /* Write wchar in wire-format */
+ LE_OUT16(mb->scan, wcval);
+
+ if (*cvalp == 0) {
+ /*
+ * End of string. Check to see whether
+ * or not to include the null
+ * terminator.
+ */
+ if ((mb->flags & SMB_MSGBUF_NOTERM) ==
+ 0)
+ mb->scan +=
+ sizeof (mts_wchar_t);
+ break;
+ }
+
+ mb->scan += sizeof (mts_wchar_t);
+ cvalp += count;
+ }
+ break;
+
+ case 'M':
+ if (smb_msgbuf_has_space(mb, 4) == 0)
+ return (SMB_MSGBUF_OVERFLOW);
+
+ *mb->scan++ = 0xFF;
+ *mb->scan++ = 'S';
+ *mb->scan++ = 'M';
+ *mb->scan++ = 'B';
+ break;
+
+ default:
+ return (SMB_MSGBUF_INVALID_FORMAT);
+ }
+ }
+
+ return (SMB_MSGBUF_SUCCESS);
+}
+
+
+/*
+ * smb_msgbuf_malloc
+ *
+ * Allocate some memory for use with this smb_msgbuf. We increase the
+ * requested size to hold the list pointer and return a pointer
+ * to the area for use by the caller.
+ */
+static void *
+smb_msgbuf_malloc(smb_msgbuf_t *mb, size_t size)
+{
+ smb_msgbuf_mlist_t *item;
+
+ size += sizeof (smb_msgbuf_mlist_t);
+
+#ifndef _KERNEL
+ if ((item = malloc(size)) == NULL)
+ return (NULL);
+#else
+ item = kmem_alloc(size, KM_SLEEP);
+#endif
+ item->next = mb->mlist.next;
+ item->size = size;
+ mb->mlist.next = item;
+
+ /*
+ * The caller gets a pointer to the address
+ * immediately after the smb_msgbuf_mlist_t.
+ */
+ return ((void *)(item + 1));
+}
+
+
+/*
+ * smb_msgbuf_chkerc
+ *
+ * Diagnostic function to write an appropriate message to the system log.
+ */
+static int
+smb_msgbuf_chkerc(char *text, int erc)
+{
+ static struct {
+ int erc;
+ char *name;
+ } etable[] = {
+ { SMB_MSGBUF_SUCCESS, "success" },
+ { SMB_MSGBUF_UNDERFLOW, "overflow/underflow" },
+ { SMB_MSGBUF_INVALID_FORMAT, "invalid format" },
+ { SMB_MSGBUF_INVALID_HEADER, "invalid header" },
+ { SMB_MSGBUF_DATA_ERROR, "data error" }
+ };
+
+ int i;
+
+ for (i = 0; i < sizeof (etable)/sizeof (etable[0]); ++i) {
+ if (etable[i].erc == erc) {
+ if (text == 0)
+ text = "smb_msgbuf_chkerc";
+ break;
+ }
+ }
+ return (erc);
+}
+
+static void
+buf_decode_wcs(mts_wchar_t *dst_wcstr, mts_wchar_t *src_wcstr, int wcstrlen)
+{
+ int i;
+
+ for (i = 0; i < wcstrlen; i++) {
+ *dst_wcstr = LE_IN16(src_wcstr);
+ dst_wcstr++;
+ src_wcstr++;
+ }
+}