changeset 21413:8646c591b8eb

10997 Incorrect directory listing response for non-UNICODE clients Reviewed by: Matt Barden <Matt.Barden@nexenta.com> Reviewed by: Kevin Crowe <kevin.crowe@nexenta.com> Approved by: Richard Lowe <richlowe@richlowe.net>
author Gordon Ross <gwr@nexenta.com>
date Fri, 02 Oct 2015 15:45:00 -0400
parents 14d89c27b917
children 1ff4dd5d126b
files usr/src/common/smbsrv/smb_utf8.c usr/src/lib/smbsrv/libsmb/common/mapfile-vers usr/src/uts/common/fs/smbsrv/smb_kutil.c usr/src/uts/common/fs/smbsrv/smb_mbuf_marshaling.c usr/src/uts/common/fs/smbsrv/smb_trans2_find.c usr/src/uts/common/smbsrv/string.h
diffstat 6 files changed, 368 insertions(+), 242 deletions(-) [+]
line wrap: on
line diff
--- a/usr/src/common/smbsrv/smb_utf8.c	Mon Apr 22 13:51:05 2019 +0000
+++ b/usr/src/common/smbsrv/smb_utf8.c	Fri Oct 02 15:45:00 2015 -0400
@@ -22,7 +22,7 @@
  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  *
- * Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
  */
 
 /*
@@ -53,12 +53,13 @@
 #if defined(_KERNEL) || defined(_FAKE_KERNEL)
 #include <sys/types.h>
 #include <sys/sunddi.h>
-#else
+#else	/* _KERNEL || _FAKE_KERNEL */
 #include <stdio.h>
 #include <stdlib.h>
+#include <strings.h>
+#include <iconv.h>
 #include <assert.h>
-#include <strings.h>
-#endif
+#endif	/* _KERNEL || _FAKE_KERNEL */
 #include <smbsrv/string.h>
 
 
@@ -275,7 +276,7 @@
 
 /*
  * Returns the number of bytes that would be written if the multi-
- * byte string mbs was converted to a single byte character string,
+ * byte string mbs was converted to an OEM character string,
  * not counting the terminating null character.
  */
 size_t
@@ -290,10 +291,13 @@
 		if (nbytes == ((size_t)-1))
 			return ((size_t)-1);
 
-		if (wide_char & 0xFF00)
-			len += sizeof (smb_wchar_t);
-		else
-			++len;
+		/*
+		 * Assume OEM characters are 1-byte (for now).
+		 * That's true for cp850, which is the only
+		 * codeset this currently supports.  See:
+		 * smb_oem.c : smb_oem_codeset
+		 */
+		++len;
 
 		mbs += nbytes;
 	}
@@ -301,106 +305,174 @@
 	return (len);
 }
 
+/*
+ * Convert OEM strings to/from internal (UTF-8) form.
+ *
+ * We rarely encounter these anymore because all modern
+ * SMB clients use Unicode (UTF-16). The few cases where
+ * this IS still called are normally using ASCII, i.e.
+ * tag names etc. so short-cut those cases.  If we get
+ * something non-ASCII we have to call iconv.
+ *
+ * If we were to really support OEM code pages, we would
+ * need to have a way to set the OEM code page from some
+ * configuration value.  For now it's always CP850.
+ * See also ./smb_oem.c
+ */
+static char smb_oem_codepage[32] = "CP850";
 
 /*
- * stombs
+ * smb_oemtombs
  *
- * Convert a regular null terminated string 'string' to a UTF-8 encoded
- * null terminated multi-byte string 'mbstring'. Only full converted
- * UTF-8 characters will be written 'mbstring'. If a character will not
- * fit within the remaining buffer space or 'mbstring' will overflow
- * max_mblen, the conversion process will be terminated and 'mbstring'
- * will be null terminated.
+ * Convert a null terminated OEM string 'string' to a UTF-8 string
+ * no longer than max_mblen (null terminated if space).
  *
- * Returns the number of bytes written to 'mbstring', excluding the
- * terminating null character.
+ * If the input string contains invalid OEM characters, a value
+ * of -1 will be returned. Otherwise returns the length of 'mbs',
+ * excluding the terminating null character.
  *
  * If either mbstring or string is a null pointer, -1 is returned.
  */
 int
-smb_stombs(char *mbstring, char *string, int max_mblen)
+smb_oemtombs(char *mbs, const uint8_t *oems, int max_mblen)
 {
-	char *start = mbstring;
-	unsigned char *p = (unsigned char *)string;
-	int space_left = max_mblen;
-	int	len;
-	smb_wchar_t	wide_char;
-	char buf[4];
+	uchar_t *p;
+	int	oemlen;
+	int	rlen;
+	boolean_t need_iconv = B_FALSE;
 
-	if (!mbstring || !string)
+	if (mbs == NULL || oems == NULL)
 		return (-1);
 
-	while (*p && space_left > 2) {
-		wide_char = *p++;
-		len = smb_wctomb(mbstring, wide_char);
-		mbstring += len;
-		space_left -= len;
+	/*
+	 * Check if the oems is all ASCII (and get the length
+	 * while we're at it) so we know if we need to iconv.
+	 * We usually can avoid the iconv calls.
+	 */
+	oemlen = 0;
+	p = (uchar_t *)oems;
+	while (*p != '\0') {
+		oemlen++;
+		if (*p & 0x80)
+			need_iconv = B_TRUE;
+		p++;
 	}
 
-	if (*p) {
-		wide_char = *p;
-		if ((len = smb_wctomb(buf, wide_char)) < 2) {
-			*mbstring = *buf;
-			mbstring += len;
-			space_left -= len;
-		}
+	if (need_iconv) {
+		int	rc;
+		char	*obuf = mbs;
+		size_t	olen = max_mblen;
+		size_t	ilen = oemlen;
+#if defined(_KERNEL) || defined(_FAKE_KERNEL)
+		char *ibuf = (char *)oems;
+		kiconv_t ic;
+		int	err;
+
+		ic = kiconv_open("UTF-8", smb_oem_codepage);
+		if (ic == (kiconv_t)-1)
+			goto just_copy;
+		rc = kiconv(ic, &ibuf, &ilen, &obuf, &olen, &err);
+		(void) kiconv_close(ic);
+#else	/* _KERNEL || _FAKE_KERNEL */
+		const char *ibuf = (char *)oems;
+		iconv_t	ic;
+		ic = iconv_open("UTF-8", smb_oem_codepage);
+		if (ic == (iconv_t)-1)
+			goto just_copy;
+		rc = iconv(ic, &ibuf, &ilen, &obuf, &olen);
+		(void) iconv_close(ic);
+#endif	/* _KERNEL || _FAKE_KERNEL */
+		if (rc < 0)
+			return (-1);
+		/* Return val. is output bytes. */
+		rlen = (max_mblen - olen);
+	} else {
+	just_copy:
+		rlen = oemlen;
+		if (rlen > max_mblen)
+			rlen = max_mblen;
+		bcopy(oems, mbs, rlen);
 	}
-
-	*mbstring = '\0';
+	if (rlen < max_mblen)
+		mbs[rlen] = '\0';
 
-	/*LINTED E_PTRDIFF_OVERFLOW*/
-	return (mbstring - start);
+	return (rlen);
 }
 
-
 /*
- * mbstos
+ * smb_mbstooem
  *
- * Convert a null terminated multi-byte string 'mbstring' to a regular
- * null terminated string 'string'.  A 1-byte character in 'mbstring'
- * maps to a 1-byte character in 'string'. A 2-byte character in
- * 'mbstring' will be mapped to 2-bytes, if the upper byte is non-null.
- * Otherwise the upper byte null will be discarded to ensure that the
- * output stream does not contain embedded null characters.
+ * Convert a null terminated multi-byte string 'mbs' to an OEM string
+ * no longer than max_oemlen (null terminated if space).
  *
- * If the input stream contains invalid multi-byte characters, a value
- * of -1 will be returned. Otherwise the length of 'string', excluding
- * the terminating null character, is returned.
+ * If the input string contains invalid multi-byte characters, a value
+ * of -1 will be returned. Otherwise returns the length of 'oems',
+ * excluding the terminating null character.
  *
  * If either mbstring or string is a null pointer, -1 is returned.
  */
 int
-smb_mbstos(char *string, const char *mbstring)
+smb_mbstooem(uint8_t *oems, const char *mbs, int max_oemlen)
 {
-	smb_wchar_t wc;
-	unsigned char *start = (unsigned char *)string;
-	int len;
+	uchar_t *p;
+	int	mbslen;
+	int	rlen;
+	boolean_t need_iconv = B_FALSE;
 
-	if (string == NULL || mbstring == NULL)
+	if (oems == NULL || mbs == NULL)
 		return (-1);
 
-	while (*mbstring) {
-		if ((len = smb_mbtowc(&wc, mbstring, MTS_MB_CHAR_MAX)) < 0) {
-			*string = 0;
-			return (-1);
-		}
-
-		if (wc & 0xFF00) {
-			/*LINTED E_BAD_PTR_CAST_ALIGN*/
-			*((smb_wchar_t *)string) = wc;
-			string += sizeof (smb_wchar_t);
-		}
-		else
-		{
-			*string = (unsigned char)wc;
-			string++;
-		}
-
-		mbstring += len;
+	/*
+	 * Check if the mbs is all ASCII (and get the length
+	 * while we're at it) so we know if we need to iconv.
+	 * We usually can avoid the iconv calls.
+	 */
+	mbslen = 0;
+	p = (uchar_t *)mbs;
+	while (*p != '\0') {
+		mbslen++;
+		if (*p & 0x80)
+			need_iconv = B_TRUE;
+		p++;
 	}
 
-	*string = 0;
+	if (need_iconv) {
+		int	rc;
+		char	*obuf = (char *)oems;
+		size_t	olen = max_oemlen;
+		size_t	ilen = mbslen;
+#if defined(_KERNEL) || defined(_FAKE_KERNEL)
+		char *ibuf = (char *)mbs;
+		kiconv_t ic;
+		int	err;
 
-	/*LINTED E_PTRDIFF_OVERFLOW*/
-	return ((unsigned char *)string - start);
+		ic = kiconv_open(smb_oem_codepage, "UTF-8");
+		if (ic == (kiconv_t)-1)
+			goto just_copy;
+		rc = kiconv(ic, &ibuf, &ilen, &obuf, &olen, &err);
+		(void) kiconv_close(ic);
+#else	/* _KERNEL || _FAKE_KERNEL */
+		const char *ibuf = mbs;
+		iconv_t	ic;
+		ic = iconv_open(smb_oem_codepage, "UTF-8");
+		if (ic == (iconv_t)-1)
+			goto just_copy;
+		rc = iconv(ic, &ibuf, &ilen, &obuf, &olen);
+		(void) iconv_close(ic);
+#endif	/* _KERNEL || _FAKE_KERNEL */
+		if (rc < 0)
+			return (-1);
+		/* Return val. is output bytes. */
+		rlen = (max_oemlen - olen);
+	} else {
+	just_copy:
+		rlen = mbslen;
+		if (rlen > max_oemlen)
+			rlen = max_oemlen;
+		bcopy(mbs, oems, rlen);
+	}
+	if (rlen < max_oemlen)
+		oems[rlen] = '\0';
+
+	return (rlen);
 }
--- a/usr/src/lib/smbsrv/libsmb/common/mapfile-vers	Mon Apr 22 13:51:05 2019 +0000
+++ b/usr/src/lib/smbsrv/libsmb/common/mapfile-vers	Fri Oct 02 15:45:00 2015 -0400
@@ -270,7 +270,6 @@
 	smb_lookup_name;
 	smb_lookup_sid;
 	smb_match_netlogon_seqnum;
-	smb_mbstos;
 	smb_mbstowcs;
 	smb_mbtowc;
 	smb_msgbuf_base;
@@ -376,7 +375,6 @@
 	smb_smf_restart_service;
 	smb_smf_scf_fini;
 	smb_smf_scf_init;
-	smb_stombs;
 	smb_strcasecmp;
 	smb_string_decode;
 	smb_string_encode;
--- a/usr/src/uts/common/fs/smbsrv/smb_kutil.c	Mon Apr 22 13:51:05 2019 +0000
+++ b/usr/src/uts/common/fs/smbsrv/smb_kutil.c	Fri Oct 02 15:45:00 2015 -0400
@@ -70,22 +70,36 @@
 	31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
 };
 
+/*
+ * Given a UTF-8 string (our internal form everywhere)
+ * return either the Unicode (UTF-16) length in bytes,
+ * or the OEM length in bytes.  Which we return is
+ * determined by whether the client supports Unicode.
+ * This length does NOT include the null.
+ */
 int
 smb_ascii_or_unicode_strlen(struct smb_request *sr, char *str)
 {
 	if (sr->session->dialect >= SMB_VERS_2_BASE ||
 	    (sr->smb_flg2 & SMB_FLAGS2_UNICODE) != 0)
 		return (smb_wcequiv_strlen(str));
-	return (strlen(str));
+	return (smb_sbequiv_strlen(str));
 }
 
+/*
+ * Given a UTF-8 string (our internal form everywhere)
+ * return either the Unicode (UTF-16) length in bytes,
+ * or the OEM length in bytes.  Which we return is
+ * determined by whether the client supports Unicode.
+ * This length DOES include the null.
+ */
 int
 smb_ascii_or_unicode_strlen_null(struct smb_request *sr, char *str)
 {
 	if (sr->session->dialect >= SMB_VERS_2_BASE ||
 	    (sr->smb_flg2 & SMB_FLAGS2_UNICODE) != 0)
 		return (smb_wcequiv_strlen(str) + 2);
-	return (strlen(str) + 1);
+	return (smb_sbequiv_strlen(str) + 1);
 }
 
 int
--- a/usr/src/uts/common/fs/smbsrv/smb_mbuf_marshaling.c	Mon Apr 22 13:51:05 2019 +0000
+++ b/usr/src/uts/common/fs/smbsrv/smb_mbuf_marshaling.c	Fri Oct 02 15:45:00 2015 -0400
@@ -39,14 +39,14 @@
 #define	DECODE_ALLOCATION_ERROR	2
 #define	DECODE_CONVERSION_ERROR	3
 
-static int mbc_marshal_cstou8(char *, char *, size_t, char *, size_t);
 static int mbc_marshal_make_room(mbuf_chain_t *, int32_t);
 static void mbc_marshal_store_byte(mbuf_chain_t *, uint8_t);
 static int mbc_marshal_put_char(mbuf_chain_t *mbc, uint8_t);
 static int mbc_marshal_put_short(mbuf_chain_t *mbc, uint16_t);
 static int mbc_marshal_put_long(mbuf_chain_t *mbc, uint32_t);
 static int mbc_marshal_put_long_long(mbuf_chain_t *mbc, uint64_t);
-static int mbc_marshal_put_ascii_string(mbuf_chain_t *, char *, int);
+static int mbc_marshal_put_oem_string(smb_request_t *, mbuf_chain_t *,
+    char *, int);
 static int mbc_marshal_put_unicode_string(mbuf_chain_t *, char *, int);
 static int mbc_marshal_put_uio(mbuf_chain_t *, struct uio *);
 static int mbc_marshal_put_mbufs(mbuf_chain_t *mbc, mbuf_t *m);
@@ -58,10 +58,10 @@
 static uint64_t qswap(uint64_t ll);
 static int mbc_marshal_get_odd_long_long(mbuf_chain_t *mbc, uint64_t *data);
 static int mbc_marshal_get_long_long(mbuf_chain_t *mbc, uint64_t *data);
-static int mbc_marshal_get_ascii_string(smb_request_t *, mbuf_chain_t *,
-    uint8_t **ascii, int);
+static int mbc_marshal_get_oem_string(smb_request_t *, mbuf_chain_t *,
+    char **, int);
 static int mbc_marshal_get_unicode_string(smb_request_t *, mbuf_chain_t *,
-    uint8_t **, int);
+    char **, int);
 static int mbc_marshal_get_mbufs(mbuf_chain_t *, int32_t, mbuf_t **);
 static int mbc_marshal_get_mbuf_chain(mbuf_chain_t *, int32_t, mbuf_chain_t *);
 static int mbc_marshal_get_uio(mbuf_chain_t *, struct uio *);
@@ -152,7 +152,7 @@
 	uint8_t		c;
 	uint8_t		cval;
 	uint8_t		*cvalp;
-	uint8_t		**cvalpp;
+	char		**charpp;
 	uint16_t	wval;
 	uint16_t	*wvalp;
 	uint32_t	*lvalp;
@@ -294,7 +294,7 @@
 				return (-1);
 			if (cval != 2)
 				return (-1);
-			goto ascii_conversion;
+			goto oem_conversion;
 
 		case 'A':
 		case 'S':
@@ -310,27 +310,27 @@
 				goto unicode_translation;
 			/* FALLTHROUGH */
 
-		case 's':
-ascii_conversion:
+		case 's':	/* OEM string */
+oem_conversion:
 			ASSERT(sr != NULL);
-			cvalpp = va_arg(ap, uint8_t **);
+			charpp = va_arg(ap, char **);
 			if (!repc_specified)
 				repc = 0;
-			if (mbc_marshal_get_ascii_string(sr,
-			    mbc, cvalpp, repc) != 0)
+			if (mbc_marshal_get_oem_string(sr,
+			    mbc, charpp, repc) != 0)
 				return (-1);
 			break;
 
 		case 'U': /* Convert from unicode */
 unicode_translation:
 			ASSERT(sr != 0);
-			cvalpp = va_arg(ap, uint8_t **);
+			charpp = va_arg(ap, char **);
 			if (!repc_specified)
 				repc = 0;
 			if (mbc->chain_offset & 1)
 				mbc->chain_offset++;
 			if (mbc_marshal_get_unicode_string(sr,
-			    mbc, cvalpp, repc) != 0)
+			    mbc, charpp, repc) != 0)
 				return (-1);
 			break;
 
@@ -519,6 +519,7 @@
 int
 smb_mbc_vencodef(mbuf_chain_t *mbc, const char *fmt, va_list ap)
 {
+	char 		*charp;
 	uint8_t		*cvalp;
 	timestruc_t	*tvp;
 	smb_vdb_t	*vdp;
@@ -549,7 +550,6 @@
 			repc = va_arg(ap, int);
 			c = *fmt++;
 			repc_specified = B_TRUE;
-
 		}
 
 		switch (c) {
@@ -659,7 +659,7 @@
 
 		case 'L':
 			tag = 2;
-			goto ascii_conversion;
+			goto oem_conversion;
 
 		case 'S':
 		case 'A':
@@ -680,12 +680,25 @@
 				goto unicode_translation;
 			/* FALLTHROUGH */
 
-		case 's':	/* ASCII/multibyte string */
-ascii_conversion:	cvalp = va_arg(ap, uint8_t *);
+		case 's':	/* OEM string */
+oem_conversion:
+			charp = va_arg(ap, char *);
 			if (!repc_specified)
 				repc = 0;
-			if (mbc_marshal_put_ascii_string(mbc,
-			    (char *)cvalp, repc) != 0)
+			if (mbc_marshal_put_oem_string(sr, mbc,
+			    charp, repc) != 0)
+				return (DECODE_NO_MORE_DATA);
+			break;
+
+		case 'U': /* Convert to unicode, align to word boundary */
+unicode_translation:
+			if (mbc->chain_offset & 1)
+				mbc->chain_offset++;
+			charp = va_arg(ap, char *);
+			if (!repc_specified)
+				repc = 0;
+			if (mbc_marshal_put_unicode_string(mbc,
+			    charp, repc) != 0)
 				return (DECODE_NO_MORE_DATA);
 			break;
 
@@ -728,18 +741,6 @@
 					return (DECODE_NO_MORE_DATA);
 			break;
 
-		case 'U': /* Convert to unicode, align to word boundary */
-unicode_translation:
-			if (mbc->chain_offset & 1)
-				mbc->chain_offset++;
-			cvalp = va_arg(ap, uint8_t *);
-			if (!repc_specified)
-				repc = 0;
-			if (mbc_marshal_put_unicode_string(mbc,
-			    (char *)cvalp, repc) != 0)
-				return (DECODE_NO_MORE_DATA);
-			break;
-
 		default:
 			ASSERT(0);
 			return (-1);
@@ -1068,74 +1069,88 @@
 }
 
 /*
- * When need to convert from UTF-8 (internal format) to a single
- * byte string (external format ) when marshalling a string.
+ * Marshal a UTF-8 string (str) into mbc, converting to OEM codeset.
+ * Also write a null unless the repc count limits the length we put.
  */
 static int
-mbc_marshal_put_ascii_string(mbuf_chain_t *mbc, char *mbs, int repc)
+mbc_marshal_put_oem_string(
+    smb_request_t *sr,
+    mbuf_chain_t *mbc,
+    char *str,
+    int repc)
 {
-	smb_wchar_t	wide_char;
-	int		nbytes;
-	int		length;
+	uint8_t		*s, *oembuf;
+	int		buflen;
+	int		oemlen;
+	int		putlen;
 
-	if ((length = smb_sbequiv_strlen(mbs)) == -1)
+	/*
+	 * First convert to OEM string.  The OEM string
+	 * will be no longer than the UTF-8 string.
+	 */
+	buflen = strlen(str) + 1;
+	oembuf = smb_srm_zalloc(sr, buflen);
+	oemlen = smb_mbstooem(oembuf, str, buflen);
+	if (oemlen == -1)
 		return (DECODE_NO_MORE_DATA);
 
-	length += sizeof (char);
+	/* null terminator */
+	if (oemlen < buflen)
+		oembuf[oemlen++] = '\0';
 
-	if ((repc > 0) && (repc < length))
-		length = repc;
-	if (mbc_marshal_make_room(mbc, length))
+	/* If specified, repc limits the length. */
+	putlen = oemlen;
+	if ((repc > 0) && (repc < putlen))
+		putlen = repc;
+
+	if (mbc_marshal_make_room(mbc, putlen))
 		return (DECODE_NO_MORE_DATA);
 
-	while (*mbs) {
-		/*
-		 * We should restore oem chars here.
-		 */
-		nbytes = smb_mbtowc(&wide_char, mbs, MTS_MB_CHAR_MAX);
-		if (nbytes == -1)
-			return (DECODE_NO_MORE_DATA);
-
-		mbc_marshal_store_byte(mbc, (uint8_t)wide_char);
-
-		if (wide_char & 0xFF00)
-			mbc_marshal_store_byte(mbc, wide_char >> 8);
-
-		mbs += nbytes;
+	s = oembuf;
+	while (putlen > 0) {
+		mbc_marshal_store_byte(mbc, *s);
+		s++;
+		putlen--;
 	}
 
-	mbc_marshal_store_byte(mbc, 0);
 	return (0);
 }
 
+/*
+ * Marshal a UTF-8 string (str) into mbc, converting to UTF-16.
+ * Also write a UTF-16 null (2 bytes) unless the repc count
+ * limits the length we put into the mbc.
+ */
 static int
-mbc_marshal_put_unicode_string(mbuf_chain_t *mbc, char *ascii, int repc)
+mbc_marshal_put_unicode_string(mbuf_chain_t *mbc, char *str, int repc)
 {
 	smb_wchar_t	wchar;
 	int		consumed;
 	int		length;
 
-	if ((length = smb_wcequiv_strlen(ascii)) == -1)
+	if ((length = smb_wcequiv_strlen(str)) == -1)
 		return (DECODE_NO_MORE_DATA);
 
+	/* null terminator */
 	length += sizeof (smb_wchar_t);
 
+	/* If specified, repc limits the length. */
 	if ((repc > 0) && (repc < length))
 		length = repc;
 
 	if (mbc_marshal_make_room(mbc, length))
 		return (DECODE_NO_MORE_DATA);
 	while (length > 0) {
-		consumed = smb_mbtowc(&wchar, ascii, MTS_MB_CHAR_MAX);
+		consumed = smb_mbtowc(&wchar, str, MTS_MB_CHAR_MAX);
 		if (consumed == -1)
 			break;	/* Invalid sequence */
 		/*
 		 * Note that consumed will be 0 when the null terminator
-		 * is encountered and ascii will not be advanced beyond
+		 * is encountered and str will not be advanced beyond
 		 * that point. Length will continue to be decremented so
 		 * we won't get stuck here.
 		 */
-		ascii += consumed;
+		str += consumed;
 		mbc_marshal_store_byte(mbc, wchar);
 		mbc_marshal_store_byte(mbc, wchar >> 8);
 		length -= sizeof (smb_wchar_t);
@@ -1375,37 +1390,36 @@
 }
 
 /*
- * mbc_marshal_get_ascii_string
+ * mbc_marshal_get_oem_string
  *
- * The ascii string in smb includes oem chars. Since the
- * system needs utf8 encodes unicode char, conversion is
- * required to convert the oem char to unicode and then
- * to encode the converted wchars to utf8 format.
- * Therefore, the **ascii returned will be in such format
- * instead of the real ASCII format.
+ * Decode an OEM string, returning its UTF-8 form in strpp,
+ * allocated using smb_srm_zalloc (automatically freed).
+ * If repc != 0, consume no more than repc bytes.
  */
 static int
-mbc_marshal_get_ascii_string(
+mbc_marshal_get_oem_string(
     smb_request_t	*sr,
     mbuf_chain_t	*mbc,
-    uint8_t		**ascii,
-    int			max_ascii)
+    char		**strpp,
+    int			repc)
 {
-	char		*rcvbuf;
-	char		*ch;
-	int		max;
-	int		length = 0;
+	uint8_t		*ch, *rcvbuf;
+	char 		*mbsbuf;
+	int		mbslen, mbsmax;
+	int		buflen;
+	int		oemlen;
 
-	max = MALLOC_QUANTUM;
-	rcvbuf = smb_srm_zalloc(sr, max);
+	buflen = MALLOC_QUANTUM;
+	rcvbuf = smb_srm_zalloc(sr, buflen);
 
-	if (max_ascii == 0)
-		max_ascii = 0xffff;
+	if (repc == 0)
+		repc = 0xffff;
 
+	oemlen = 0;
 	ch = rcvbuf;
 	for (;;) {
-		while (length < max) {
-			if (max_ascii-- <= 0) {
+		while (oemlen < buflen) {
+			if (repc-- <= 0) {
 				*ch++ = 0;
 				goto multibyte_encode;
 			}
@@ -1415,26 +1429,38 @@
 			}
 			if ((*ch++ = mbc_marshal_fetch_byte(mbc)) == 0)
 				goto multibyte_encode;
-			length++;
+			oemlen++;
 		}
-		max += MALLOC_QUANTUM;
-		rcvbuf = smb_srm_rezalloc(sr, rcvbuf, max);
-		ch = rcvbuf + length;
+		buflen += MALLOC_QUANTUM;
+		rcvbuf = smb_srm_rezalloc(sr, rcvbuf, buflen);
+		ch = rcvbuf + oemlen;
 	}
 
 multibyte_encode:
 	/*
-	 * UTF-8 encode the string for internal system use.
+	 * UTF-8 encode the return string for internal system use.
+	 * Allocated size is worst-case: 3x larger than OEM.
 	 */
-	length = strlen(rcvbuf) + 1;
-	*ascii = smb_srm_zalloc(sr, length * MTS_MB_CHAR_MAX);
-	return (mbc_marshal_cstou8("CP850", (char *)*ascii,
-	    (size_t)length * MTS_MB_CHAR_MAX, rcvbuf, (size_t)length));
+	mbsmax = (oemlen + 1) * MTS_MB_CHAR_MAX;
+	mbsbuf = smb_srm_zalloc(sr, mbsmax);
+	mbslen = smb_oemtombs(mbsbuf, rcvbuf, mbsmax);
+	if (mbslen == -1)
+		return (DECODE_NO_MORE_DATA);
+
+	*strpp = mbsbuf;
+	return (0);
 }
 
+/*
+ * mbc_marshal_get_unicode_string
+ *
+ * Decode a UTF-16 string, returning its UTF-8 form in strpp,
+ * allocated using smb_srm_zalloc (automatically freed).
+ * If repc != 0, consume no more than repc bytes.
+ */
 static int
 mbc_marshal_get_unicode_string(smb_request_t *sr,
-    mbuf_chain_t *mbc, uint8_t **ascii, int max_unicode)
+    mbuf_chain_t *mbc, char **strpp, int repc)
 {
 	int		max;
 	uint16_t	wchar;
@@ -1442,18 +1468,18 @@
 	int		emitted;
 	int		length = 0;
 
-	if (max_unicode == 0)
-		max_unicode = 0xffff;
+	if (repc == 0)
+		repc = 0xffff;
 
 	max = MALLOC_QUANTUM;
-	*ascii = smb_srm_zalloc(sr, max);
+	*strpp = smb_srm_zalloc(sr, max);
 
-	ch = (char *)*ascii;
+	ch = *strpp;
 	for (;;) {
 		while ((length + MTS_MB_CHAR_MAX) < max) {
-			if (max_unicode <= 0)
+			if (repc <= 0)
 				goto done;
-			max_unicode -= 2;
+			repc -= 2;
 
 			if (mbc_marshal_get_short(mbc, &wchar) != 0)
 				return (DECODE_NO_MORE_DATA);
@@ -1465,8 +1491,8 @@
 			ch += emitted;
 		}
 		max += MALLOC_QUANTUM;
-		*ascii = smb_srm_rezalloc(sr, *ascii, max);
-		ch = (char *)*ascii + length;
+		*strpp = smb_srm_rezalloc(sr, *strpp, max);
+		ch = *strpp + length;
 	}
 done:	*ch = 0;
 	return (0);
@@ -1571,29 +1597,3 @@
 	mbc->chain_offset += skip;
 	return (0);
 }
-
-/*
- * Converts oem string to UTF-8 string with an output string of max
- * maxconv bytes.  The string may be truncated or not null-terminated if
- * there is not enough room.
- *
- * returns -1, cnt (partial conversion)  or 0 (success)
- */
-
-static int
-mbc_marshal_cstou8(char *cs, char *outbuf, size_t maxconv,
-    char *inbuf, size_t srcbytes)
-{
-	kiconv_t	t2u;
-	size_t		inlen = srcbytes;
-	size_t		outlen = maxconv;
-	int		err = 0;
-	size_t		rc;
-
-	if ((t2u = kiconv_open("UTF-8", cs)) == (kiconv_t)-1)
-		return (-1);
-
-	rc = kiconv(t2u, &inbuf, &inlen, &outbuf, &outlen, &err);
-	(void) kiconv_close(t2u);
-	return ((int)rc);
-}
--- a/usr/src/uts/common/fs/smbsrv/smb_trans2_find.c	Mon Apr 22 13:51:05 2019 +0000
+++ b/usr/src/uts/common/fs/smbsrv/smb_trans2_find.c	Fri Oct 02 15:45:00 2015 -0400
@@ -213,7 +213,7 @@
  * various functions involved in FindFirst, FindNext.
  */
 typedef struct smb_find_args {
-	uint32_t fa_maxdata;
+	uint32_t fa_fixedsize;
 	uint16_t fa_infolev;
 	uint16_t fa_maxcount;
 	uint16_t fa_fflag;
@@ -225,7 +225,7 @@
 
 static int smb_trans2_find_entries(smb_request_t *, smb_xa_t *,
     smb_odir_t *, smb_find_args_t *);
-static int smb_trans2_find_get_maxdata(smb_request_t *, uint16_t, uint16_t);
+static int smb_trans2_find_get_fixedsize(smb_request_t *, uint16_t, uint16_t);
 static int smb_trans2_find_mbc_encode(smb_request_t *, smb_xa_t *,
     smb_fileinfo_t *, smb_find_args_t *);
 
@@ -320,9 +320,9 @@
 		odir_flags = SMB_ODIR_OPENF_BACKUP_INTENT;
 	}
 
-	args.fa_maxdata =
-	    smb_trans2_find_get_maxdata(sr, args.fa_infolev, args.fa_fflag);
-	if (args.fa_maxdata == 0)
+	args.fa_fixedsize =
+	    smb_trans2_find_get_fixedsize(sr, args.fa_infolev, args.fa_fflag);
+	if (args.fa_fixedsize == 0)
 		return (SDRC_ERROR);
 
 	status = smb_odir_openpath(sr, pn->pn_path, sattr, odir_flags, &od);
@@ -455,9 +455,9 @@
 	if (args.fa_fflag & SMB_FIND_WITH_BACKUP_INTENT)
 		sr->user_cr = smb_user_getprivcred(sr->uid_user);
 
-	args.fa_maxdata =
-	    smb_trans2_find_get_maxdata(sr, args.fa_infolev, args.fa_fflag);
-	if (args.fa_maxdata == 0)
+	args.fa_fixedsize =
+	    smb_trans2_find_get_fixedsize(sr, args.fa_infolev, args.fa_fflag);
+	if (args.fa_fixedsize == 0)
 		return (SDRC_ERROR);
 
 	od = smb_tree_lookup_odir(sr, odid);
@@ -536,6 +536,7 @@
 	smb_odir_resume_t odir_resume;
 	uint16_t	count, maxcount;
 	int		rc = -1;
+	int		LastEntryOffset = 0;
 	boolean_t	need_rewind = B_FALSE;
 
 	/*
@@ -565,6 +566,7 @@
 		if (rc != 0 || args->fa_eos != 0)
 			break;
 
+		LastEntryOffset = xa->rep_data_mb.chain_offset;
 		rc = smb_trans2_find_mbc_encode(sr, xa, &fileinfo, args);
 		if (rc == -1)
 			return (-1); /* fatal encoding error */
@@ -584,6 +586,15 @@
 	if (args->fa_eos != 0 && rc == ENOENT)
 		rc = 0;
 
+	/*
+	 * All but the ancient info levels start with NextEntryOffset.
+	 * That's supposed to be zero in the last entry returned.
+	 */
+	if (args->fa_infolev >= SMB_FIND_FILE_DIRECTORY_INFO) {
+		(void) smb_mbc_poke(&xa->rep_data_mb,
+		    LastEntryOffset, "l", 0);
+	}
+
 	/* save the last cookie returned to client */
 	if (count != 0)
 		smb_odir_save_fname(od, args->fa_lastkey, args->fa_lastname);
@@ -621,20 +632,19 @@
 }
 
 /*
- * smb_trans2_find_get_maxdata
+ * smb_trans2_find_get_fixedsize
  *
- * Calculate the minimum response space required for the specified
- * information level.
+ * Calculate the sizeof the fixed part of the response for the
+ * specified information level.
  *
- * A non-zero return value provides the minimum space required.
+ * A non-zero return value provides the fixed size.
  * A return value of zero indicates an unknown information level.
  */
 static int
-smb_trans2_find_get_maxdata(smb_request_t *sr, uint16_t infolev, uint16_t fflag)
+smb_trans2_find_get_fixedsize(smb_request_t *sr, uint16_t infolev,
+	uint16_t fflag)
 {
-	int maxdata;
-
-	maxdata = smb_ascii_or_unicode_null_len(sr);
+	int maxdata = 0;
 
 	switch (infolev) {
 	case SMB_INFO_STANDARD :
@@ -736,27 +746,53 @@
 	uint32_t	resume_key;
 	char		buf83[26];
 	smb_msgbuf_t	mb;
+	int		pad = 0;
 
 	namelen = smb_ascii_or_unicode_strlen(sr, fileinfo->fi_name);
 	if (namelen == -1)
 		return (-1);
 
-	/*
-	 * If ascii the filename length returned to the client should
-	 * include the null terminator for levels except STANDARD and
-	 * EASIZE.
-	 */
-	if (!(sr->smb_flg2 & SMB_FLAGS2_UNICODE)) {
-		if ((args->fa_infolev != SMB_INFO_STANDARD) &&
-		    (args->fa_infolev != SMB_INFO_QUERY_EA_SIZE))
+	if (args->fa_infolev < SMB_FIND_FILE_DIRECTORY_INFO) {
+		/*
+		 * Ancient info levels don't have a NextEntryOffset
+		 * field, so there's no padding for alignment.
+		 * The client expects a null after the file name,
+		 * and then the next entry.  The namelength field
+		 * never includes the null for these old levels.
+		 * Using the pad value to write the null because
+		 * we don't want to add that to namelen.
+		 * [MS-CIFS] sec. 2.8.1.{1-3}
+		 */
+		if ((sr->smb_flg2 & SMB_FLAGS2_UNICODE) != 0)
+			pad = 2; /* Unicode null */
+		else
+			pad = 1; /* ascii null */
+		next_entry_offset = args->fa_fixedsize + namelen + pad;
+		if (!MBC_ROOM_FOR(&xa->rep_data_mb, next_entry_offset))
+			return (1);
+	} else {
+		/*
+		 * Later info levels: The file name is written WITH
+		 * null termination, and the size of that null _is_
+		 * included in the namelen field.  There may also
+		 * be padding, and we pad to align(4) like Windows.
+		 * Don't include the padding in the "room for" test
+		 * because we want to ignore any error writing the
+		 * pad bytes after the last element.
+		 */
+		if ((sr->smb_flg2 & SMB_FLAGS2_UNICODE) != 0)
+			namelen += 2;
+		else
 			namelen += 1;
+		next_entry_offset = args->fa_fixedsize + namelen;
+		if (!MBC_ROOM_FOR(&xa->rep_data_mb, next_entry_offset))
+			return (1);
+		if ((next_entry_offset & 3) != 0) {
+			pad = 4 - (next_entry_offset & 3);
+			next_entry_offset += pad;
+		}
 	}
 
-	next_entry_offset = args->fa_maxdata + namelen;
-
-	if (MBC_ROOM_FOR(&xa->rep_data_mb, (args->fa_maxdata + namelen)) == 0)
-		return (1);
-
 	mb_flags = (sr->smb_flg2 & SMB_FLAGS2_UNICODE) ? SMB_MSGBUF_UNICODE : 0;
 	dsize32 = (fileinfo->fi_size > UINT_MAX) ?
 	    UINT_MAX : (uint32_t)fileinfo->fi_size;
@@ -938,8 +974,11 @@
 	    (args->fa_lno & 1) != 0)
 		args->fa_lno++;
 
-	(void) smb_mbc_encodef(&xa->rep_data_mb, "%u", sr,
-	    fileinfo->fi_name);
+	(void) smb_mbc_encodef(&xa->rep_data_mb, "%#u", sr,
+	    namelen, fileinfo->fi_name);
+
+	if (pad)
+		(void) smb_mbc_encodef(&xa->rep_data_mb, "#.", pad);
 
 	return (0);
 }
--- a/usr/src/uts/common/smbsrv/string.h	Mon Apr 22 13:51:05 2019 +0000
+++ b/usr/src/uts/common/smbsrv/string.h	Fri Oct 02 15:45:00 2015 -0400
@@ -20,7 +20,7 @@
  */
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2012 Nexenta Systems, Inc. All rights reserved.
+ * Copyright 2015 Nexenta Systems, Inc. All rights reserved.
  * Copyright (c) 2017 by Delphix. All rights reserved.
  */
 
@@ -136,6 +136,9 @@
 size_t smb_wcequiv_strlen(const char *);
 size_t smb_sbequiv_strlen(const char *);
 
+int smb_oemtombs(char *, const uint8_t *, int);
+int smb_mbstooem(uint8_t *, const char *, int);
+
 int smb_stombs(char *, char *, int);
 int smb_mbstos(char *, const char *);