/* * 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. * * Copyright 2014 Nexenta Systems, Inc. All rights reserved. */ /* * 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 #include #include #if !defined(_KERNEL) && !defined(_FAKE_KERNEL) #include #include #include #include #else #include #include #endif #include #include #include 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); /* * 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; #if !defined(_KERNEL) && !defined(_FAKE_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 *bvalp; uint16_t *wvalp; uint32_t *lvalp; uint64_t *llvalp; char *cvalp; char **cvalpp; smb_wchar_t wchar; boolean_t repc_specified; int repc; int rc; while ((c = *fmt++) != 0) { repc_specified = B_FALSE; 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'); repc_specified = B_TRUE; } else if (c == '#') { repc = va_arg(ap, int); c = *fmt++; repc_specified = B_TRUE; } switch (c) { case '.': if (smb_msgbuf_has_space(mb, repc) == 0) return (SMB_MSGBUF_UNDERFLOW); mb->scan += repc; break; case 'c': /* get char */ if (smb_msgbuf_has_space(mb, repc) == 0) return (SMB_MSGBUF_UNDERFLOW); bvalp = va_arg(ap, uint8_t *); bcopy(mb->scan, bvalp, repc); mb->scan += repc; break; case 'b': /* get byte */ if (smb_msgbuf_has_space(mb, repc) == 0) return (SMB_MSGBUF_UNDERFLOW); bvalp = va_arg(ap, uint8_t *); while (repc-- > 0) { *bvalp++ = *mb->scan++; } break; case 'w': /* get word */ 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': /* get long */ 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': /* get quad */ 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': /* get string */ if (!repc_specified) repc = strlen((const char *)mb->scan) + 1; if (smb_msgbuf_has_space(mb, repc) == 0) return (SMB_MSGBUF_UNDERFLOW); if ((cvalp = smb_msgbuf_malloc(mb, repc * 2)) == 0) return (SMB_MSGBUF_UNDERFLOW); cvalpp = va_arg(ap, char **); *cvalpp = cvalp; /* Translate OEM to mbs */ while (repc > 0) { wchar = *mb->scan++; repc--; if (wchar == 0) break; ival = smb_wctomb(cvalp, wchar); cvalp += ival; } *cvalp = '\0'; if (repc > 0) mb->scan += repc; break; case 'U': /* get unicode string */ 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); if (!repc_specified) { /* * Count bytes, including the null. */ uint8_t *tmp_scan = mb->scan; repc = 2; /* the null */ while ((wchar = LE_IN16(tmp_scan)) != 0) { tmp_scan += 2; repc += 2; } } if (smb_msgbuf_has_space(mb, repc) == 0) return (SMB_MSGBUF_UNDERFLOW); /* * Get space for translated string * Allocates worst-case size. */ if ((cvalp = smb_msgbuf_malloc(mb, repc * 2)) == 0) return (SMB_MSGBUF_UNDERFLOW); cvalpp = va_arg(ap, char **); *cvalpp = cvalp; /* * Translate unicode to mbs, stopping after * null or repc limit. */ while (repc >= 2) { wchar = LE_IN16(mb->scan); mb->scan += 2; repc -= 2; if (wchar == 0) break; ival = smb_wctomb(cvalp, wchar); cvalp += ival; } *cvalp = '\0'; if (repc > 0) 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; uint8_t *bvalp; char *cvalp; uint8_t c; smb_wchar_t wchar; int count; boolean_t repc_specified; int repc; int rc; while ((c = *fmt++) != 0) { repc_specified = B_FALSE; 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'); repc_specified = B_TRUE; } else if (c == '#') { repc = va_arg(ap, int); c = *fmt++; repc_specified = B_TRUE; } switch (c) { case '.': if (smb_msgbuf_has_space(mb, repc) == 0) return (SMB_MSGBUF_OVERFLOW); while (repc-- > 0) *mb->scan++ = 0; break; case 'c': /* put char */ if (smb_msgbuf_has_space(mb, repc) == 0) return (SMB_MSGBUF_OVERFLOW); bvalp = va_arg(ap, uint8_t *); bcopy(bvalp, mb->scan, repc); mb->scan += repc; break; case 'b': /* put byte */ 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': /* put word */ 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': /* put long */ 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': /* put quad */ 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': /* put string */ cvalp = va_arg(ap, char *); if (!repc_specified) { repc = smb_sbequiv_strlen(cvalp); if (repc == -1) return (SMB_MSGBUF_OVERFLOW); if (!(mb->flags & SMB_MSGBUF_NOTERM)) repc++; } if (smb_msgbuf_has_space(mb, repc) == 0) return (SMB_MSGBUF_OVERFLOW); while (repc > 0) { count = smb_mbtowc(&wchar, cvalp, MTS_MB_CHAR_MAX); if (count < 0) return (SMB_MSGBUF_DATA_ERROR); cvalp += count; if (wchar == 0) break; *mb->scan++ = (uint8_t)wchar; repc--; if (wchar & 0xff00) { *mb->scan++ = wchar >> 8; repc--; } } if (*cvalp == '\0' && repc > 0 && (mb->flags & SMB_MSGBUF_NOTERM) == 0) { *mb->scan++ = 0; repc--; } while (repc > 0) { *mb->scan++ = 0; repc--; } break; case 'U': /* put unicode string */ unicode_translation: /* * Unicode strings are always word aligned. */ smb_msgbuf_word_align(mb); cvalp = va_arg(ap, char *); if (!repc_specified) { repc = smb_wcequiv_strlen(cvalp); if (!(mb->flags & SMB_MSGBUF_NOTERM)) repc += 2; } if (!smb_msgbuf_has_space(mb, repc)) return (SMB_MSGBUF_OVERFLOW); while (repc >= 2) { count = smb_mbtowc(&wchar, cvalp, MTS_MB_CHAR_MAX); if (count < 0) return (SMB_MSGBUF_DATA_ERROR); cvalp += count; if (wchar == 0) break; LE_OUT16(mb->scan, wchar); mb->scan += 2; repc -= 2; } if (*cvalp == '\0' && repc >= 2 && (mb->flags & SMB_MSGBUF_NOTERM) == 0) { LE_OUT16(mb->scan, 0); mb->scan += 2; repc -= 2; } while (repc > 0) { *mb->scan++ = 0; repc--; } 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); #if !defined(_KERNEL) && !defined(_FAKE_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); }