mirror of
https://github.com/jfjallid/go-smb
synced 2026-06-08 15:02:50 +00:00
478 lines
10 KiB
Go
478 lines
10 KiB
Go
// MIT License
|
|
//
|
|
// # Copyright (c) 2025 Jimmy Fjällid
|
|
//
|
|
// 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.
|
|
package msdtyp
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
"strconv"
|
|
"strings"
|
|
"unicode/utf16"
|
|
)
|
|
|
|
func FromUnicodeString(buf []byte) (res string, err error) {
|
|
buflen := len(buf)
|
|
if (buflen % 2) != 0 {
|
|
err = fmt.Errorf("Invalid Unicode (UTF-16-LE) string")
|
|
return
|
|
}
|
|
|
|
s := make([]uint16, buflen/2)
|
|
err = binary.Read(bytes.NewReader(buf), le, &s)
|
|
if err != nil {
|
|
return
|
|
}
|
|
return string(utf16.Decode(s)), nil
|
|
}
|
|
|
|
func FromUnicode(buf []byte) ([]byte, error) {
|
|
s, err := FromUnicodeString(buf)
|
|
return []byte(s), err
|
|
}
|
|
|
|
func ToUnicode(input string) []byte {
|
|
codePoints := utf16.Encode([]rune(input))
|
|
b := bytes.Buffer{}
|
|
binary.Write(&b, le, &codePoints)
|
|
return b.Bytes()
|
|
}
|
|
|
|
// Return the values needed to encode a unicode string according to NDR (except for the Ptrs and MaxCount which has to be added manually)
|
|
func NewUnicodeStr(s string, addNullByte bool) (offset uint32, actualCount uint32, paddlen int, buffer []byte) {
|
|
if addNullByte {
|
|
s = nullTerminate(s)
|
|
}
|
|
buffer = ToUnicode(s)
|
|
actualCount = uint32(len(buffer) / 2)
|
|
offset = 0
|
|
paddlen = (len(buffer) % 4) //Got to be 4 byte aligned
|
|
if paddlen != 0 {
|
|
paddlen = 4 - paddlen
|
|
}
|
|
return
|
|
}
|
|
|
|
func nullTerminate(s string) string {
|
|
if s == "" {
|
|
s = "\x00"
|
|
} else if s[len(s)-1] != 0x00 {
|
|
return s + "\x00"
|
|
}
|
|
return s
|
|
}
|
|
|
|
func stripNullByte(s string) string {
|
|
if s == "" {
|
|
return ""
|
|
}
|
|
if s[len(s)-1] == 0x00 {
|
|
return s[:len(s)-1]
|
|
}
|
|
return s
|
|
}
|
|
|
|
// Borrowed from NDR but modified to support edge case where the unicode string is NOT terminated
|
|
// with a null byte
|
|
func ReadConformantVaryingString(r *bytes.Reader, nullTerminated bool) (s string, err error) {
|
|
// Read the Max count
|
|
var maxCount uint32
|
|
err = binary.Read(r, le, &maxCount)
|
|
if err != nil {
|
|
log.Errorln(err)
|
|
return
|
|
}
|
|
|
|
// Read the offset
|
|
var offset uint32
|
|
err = binary.Read(r, le, &offset)
|
|
if err != nil {
|
|
log.Errorln(err)
|
|
return
|
|
}
|
|
// Read the Actual count
|
|
var actualCount uint32
|
|
err = binary.Read(r, le, &actualCount)
|
|
if err != nil {
|
|
log.Errorln(err)
|
|
return
|
|
}
|
|
if offset > 0 {
|
|
_, err = r.Seek(int64(offset), io.SeekCurrent)
|
|
if err != nil {
|
|
log.Errorln(err)
|
|
return
|
|
}
|
|
}
|
|
|
|
if actualCount > 0 {
|
|
// Read the unicode string
|
|
unc := make([]byte, actualCount*2)
|
|
err = binary.Read(r, le, unc)
|
|
if err != nil {
|
|
log.Errorln(err)
|
|
return
|
|
}
|
|
|
|
// Edge case
|
|
if nullTerminated {
|
|
// Check for terminating null byte
|
|
lastIndex := len(unc) - 2
|
|
if bytes.Compare(unc[lastIndex:], []byte{0, 0}) == 0 {
|
|
unc = unc[:lastIndex]
|
|
}
|
|
}
|
|
|
|
s, err = FromUnicodeString(unc)
|
|
if err != nil {
|
|
log.Errorln(err)
|
|
return
|
|
}
|
|
}
|
|
|
|
paddLen := 4 - ((offset + actualCount*2) % 4)
|
|
|
|
if paddLen != 4 {
|
|
_, err = r.Seek(int64(paddLen), io.SeekCurrent)
|
|
if err != nil {
|
|
log.Errorln(err)
|
|
return
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func ReadConformantVaryingStringPtr(r *bytes.Reader, nullTerminated bool) (s string, err error) {
|
|
// Skip ReferentId Ptr
|
|
_, err = r.Seek(4, io.SeekCurrent)
|
|
if err != nil {
|
|
log.Errorln(err)
|
|
return
|
|
}
|
|
return ReadConformantVaryingString(r, nullTerminated)
|
|
}
|
|
|
|
// Borrowed from NDR but modified to support edge case where the unicode string is NOT terminated
|
|
// with a null byte
|
|
// Write a conformant and varying string to the output stream
|
|
func WriteConformantVaryingString(w io.Writer, s string, addNullByte bool) (n int, err error) {
|
|
offset, count, paddlen, buffer := NewUnicodeStr(s, addNullByte)
|
|
maxCount := count
|
|
if !addNullByte {
|
|
// Seems like NDR parsers does not like it if MaxCount is same as ActualCount
|
|
// for strings that are not null terminated
|
|
maxCount += 1
|
|
}
|
|
err = binary.Write(w, le, maxCount) // MaxCount
|
|
if err != nil {
|
|
return
|
|
}
|
|
n += 4
|
|
err = binary.Write(w, le, offset)
|
|
if err != nil {
|
|
return
|
|
}
|
|
n += 4
|
|
err = binary.Write(w, le, count)
|
|
if err != nil {
|
|
return
|
|
}
|
|
n += 4
|
|
_, err = w.Write(buffer)
|
|
if err != nil {
|
|
return
|
|
}
|
|
n += len(buffer)
|
|
padd := make([]byte, paddlen)
|
|
_, err = w.Write(padd)
|
|
if err != nil {
|
|
return
|
|
}
|
|
n += paddlen
|
|
return
|
|
}
|
|
|
|
// Write a ptr to a conformant and varying string to the output stream
|
|
func WriteConformantVaryingStringPtr(w io.Writer, s string, refid *uint32, addNullByte bool) (n int, err error) {
|
|
var n2 int
|
|
|
|
if s == "" {
|
|
// Empty strings are represented as a NULL Ptr
|
|
n, err = w.Write([]byte{0, 0, 0, 0})
|
|
if err != nil {
|
|
log.Errorln(err)
|
|
}
|
|
return
|
|
}
|
|
if *refid != 0 {
|
|
err = binary.Write(w, le, *refid)
|
|
if err != nil {
|
|
return
|
|
}
|
|
*refid++
|
|
n = 4
|
|
}
|
|
n2, err = WriteConformantVaryingString(w, s, addNullByte)
|
|
n += n2
|
|
return
|
|
}
|
|
|
|
// If maxCount is 0, use length of buf
|
|
func WriteConformantVaryingArray(w io.Writer, buf []byte, maxCount uint32) (n int, err error) {
|
|
actualCount := uint32(len(buf))
|
|
if maxCount == 0 {
|
|
maxCount = actualCount
|
|
}
|
|
err = binary.Write(w, le, maxCount)
|
|
if err != nil {
|
|
log.Errorln(err)
|
|
return
|
|
}
|
|
n += 4
|
|
|
|
err = binary.Write(w, le, uint32(0)) // Offset
|
|
if err != nil {
|
|
log.Errorln(err)
|
|
return
|
|
}
|
|
n += 4
|
|
|
|
err = binary.Write(w, le, actualCount)
|
|
if err != nil {
|
|
log.Errorln(err)
|
|
return
|
|
}
|
|
n += 4
|
|
|
|
_, err = w.Write(buf)
|
|
if err != nil {
|
|
log.Errorln(err)
|
|
return
|
|
}
|
|
n += len(buf)
|
|
paddlen := (len(buf) % 4) //Got to be 4 byte aligned?
|
|
if paddlen != 0 {
|
|
paddlen = 4 - paddlen
|
|
}
|
|
|
|
padd := make([]byte, paddlen)
|
|
_, err = w.Write(padd)
|
|
if err != nil {
|
|
log.Errorln(err)
|
|
return
|
|
}
|
|
n += paddlen
|
|
return
|
|
}
|
|
|
|
// If maxCount is 0, use length of buf
|
|
func WriteConformantVaryingArrayPtr(w io.Writer, buf []byte, maxCount uint32, refId *uint32) (n int, err error) {
|
|
var n2 int
|
|
|
|
if len(buf) == 0 && maxCount == 0 {
|
|
// Empty buffers are represented with a NULL Ptr?
|
|
n, err = w.Write([]byte{0, 0, 0, 0})
|
|
if err != nil {
|
|
log.Errorln(err)
|
|
}
|
|
return
|
|
}
|
|
if *refId != 0 {
|
|
err = binary.Write(w, le, *refId)
|
|
if err != nil {
|
|
log.Errorln(err)
|
|
return
|
|
}
|
|
*refId++
|
|
n = 4
|
|
}
|
|
n2, err = WriteConformantVaryingArray(w, buf, maxCount)
|
|
n += n2
|
|
return
|
|
}
|
|
|
|
func ReadConformantVaryingArray(r *bytes.Reader) (data []byte, maxLength uint32, err error) {
|
|
err = binary.Read(r, le, &maxLength)
|
|
if err != nil {
|
|
log.Errorln(err)
|
|
return
|
|
}
|
|
|
|
offset := uint32(0)
|
|
err = binary.Read(r, le, &offset)
|
|
if err != nil {
|
|
log.Errorln(err)
|
|
return
|
|
}
|
|
|
|
actualCount := uint32(0)
|
|
|
|
err = binary.Read(r, le, &actualCount)
|
|
if err != nil {
|
|
log.Errorln(err)
|
|
return
|
|
}
|
|
|
|
if offset > 0 {
|
|
_, err = r.Seek(int64(offset), io.SeekCurrent)
|
|
if err != nil {
|
|
log.Errorln(err)
|
|
return
|
|
}
|
|
}
|
|
|
|
if actualCount > 0 {
|
|
data = make([]byte, actualCount)
|
|
err = binary.Read(r, le, &data)
|
|
if err != nil {
|
|
log.Errorln(err)
|
|
return
|
|
}
|
|
}
|
|
|
|
paddlen := ((actualCount + offset) % 4)
|
|
if paddlen != 0 {
|
|
paddlen = 4 - paddlen
|
|
}
|
|
|
|
_, err = r.Seek(int64(paddlen), io.SeekCurrent)
|
|
if err != nil {
|
|
log.Errorln(err)
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
func ReadConformantVaryingArrayPtr(r *bytes.Reader) (data []byte, maxLength uint32, err error) {
|
|
// Skip ReferentId Ptr
|
|
_, err = r.Seek(4, io.SeekCurrent)
|
|
if err != nil {
|
|
log.Errorln(err)
|
|
return
|
|
}
|
|
return ReadConformantVaryingArray(r)
|
|
}
|
|
|
|
func WriteConformantArray(w io.Writer, buf []byte) (n int, err error) {
|
|
err = binary.Write(w, le, uint32(len(buf))) // MaxCount
|
|
if err != nil {
|
|
log.Errorln(err)
|
|
return
|
|
}
|
|
n += 4
|
|
_, err = w.Write(buf)
|
|
if err != nil {
|
|
log.Errorln(err)
|
|
return
|
|
}
|
|
n += len(buf)
|
|
paddlen := (len(buf) % 4) //Got to be 4 byte aligned?
|
|
if paddlen != 0 {
|
|
paddlen = 4 - paddlen
|
|
}
|
|
|
|
padd := make([]byte, paddlen)
|
|
_, err = w.Write(padd)
|
|
if err != nil {
|
|
log.Errorln(err)
|
|
return
|
|
}
|
|
n += paddlen
|
|
return
|
|
}
|
|
|
|
func WriteConformantArrayPtr(w io.Writer, buf []byte, refid *uint32) (n int, err error) {
|
|
var n2 int
|
|
|
|
if len(buf) == 0 {
|
|
// Empty buffers are represented with a NULL Ptr
|
|
n, err = w.Write([]byte{0, 0, 0, 0})
|
|
if err != nil {
|
|
log.Errorln(err)
|
|
}
|
|
return
|
|
}
|
|
if *refid != 0 {
|
|
err = binary.Write(w, le, refid)
|
|
if err != nil {
|
|
log.Errorln(err)
|
|
return
|
|
}
|
|
n = 4
|
|
*refid++
|
|
}
|
|
n2, err = WriteConformantArray(w, buf)
|
|
n += n2
|
|
return
|
|
}
|
|
|
|
func ConvertSIDtoStr(sid *SID) (s string) {
|
|
// Not sure what the first two bytes are but the
|
|
// Identifier Authority is stored as BigEndian while the rest is little endian
|
|
auth := binary.BigEndian.Uint32(sid.Authority[2:])
|
|
s = fmt.Sprintf("S-%d-%d", sid.Revision, auth)
|
|
//NOTE Seems that perhaps the sid.NumAuth (count) does not always accurately specify number of
|
|
// Sub Authoritys but rather number of DWORDS. E.g., a SubAuthority could take more than 1 DWORD?
|
|
for i := 0; i < int(sid.NumAuth); i++ {
|
|
s = fmt.Sprintf("%s-%d", s, sid.SubAuthorities[i])
|
|
}
|
|
return
|
|
}
|
|
|
|
func ConvertStrToSID(s string) (sid *SID, err error) {
|
|
sid = &SID{}
|
|
parts := strings.Split(s, "-")
|
|
if len(parts) < 4 {
|
|
err = fmt.Errorf("Invalid SID representation")
|
|
return
|
|
}
|
|
rev, err := strconv.ParseUint(parts[1], 10, 32)
|
|
if err != nil {
|
|
log.Errorln(err)
|
|
return
|
|
}
|
|
sid.Revision = byte(rev)
|
|
auth, err := strconv.ParseUint(parts[2], 10, 32)
|
|
if err != nil {
|
|
log.Errorln(err)
|
|
return
|
|
}
|
|
authBuf := make([]byte, 2, 6)
|
|
authBuf = binary.BigEndian.AppendUint32(authBuf, uint32(auth))
|
|
sid.Authority = authBuf
|
|
subCount := byte(0)
|
|
subAuths := make([]uint32, 0)
|
|
for _, part := range parts[3:] {
|
|
subA, err := strconv.ParseUint(part, 10, 32)
|
|
if err != nil {
|
|
log.Errorln(err)
|
|
return nil, err
|
|
}
|
|
subAuths = append(subAuths, uint32(subA))
|
|
subCount += 1
|
|
}
|
|
sid.SubAuthorities = subAuths
|
|
sid.NumAuth = subCount
|
|
return
|
|
}
|