sddl/parseSddl.go (112 lines of code) (raw):
// Copyright © Microsoft <wastore@microsoft.com>
//
// 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 sddl
import (
"errors"
"fmt"
"regexp"
"strings"
)
var translateSID = OSTranslateSID // this layer of indirection is to support unit testing. TODO: it's ugly to set a global to test. Do something better one day
func IffInt(condition bool, tVal, fVal int) int {
if condition {
return tVal
}
return fVal
}
func ParseSDDL(input string) (sddl SDDLString, err error) {
scope := 0 // if scope is 1, we're in an ACE string, if scope is 2, we're in a resource attribute.
inString := false // If a quotation mark was found, we've entered a string and should ignore all characters except another quotation mark.
elementStart := make([]int, 0) // This is the start of the element we're currently analyzing. If the array has more than one element, we're probably under a lower scope.
awaitingACLFlags := false // If this is true, a ACL section was just entered, and we're awaiting our first ACE string
var elementType rune // We need to keep track of which section of the SDDL string we're in.
for k, v := range input {
switch {
case inString: // ignore characters within a string-- except for the end of a string, and escaped quotes
if v == '"' && input[k-1] != '\\' {
inString = false
}
case v == '"':
inString = true
case v == '(': // this comes before scope == 1 because ACE strings can be multi-leveled. We only care about the bottom level.
scope++
if scope == 1 { // only do this if we're in the base of an ACE string-- We don't care about the metadata as much.
if awaitingACLFlags {
err := sddl.setACLFlags(input[elementStart[0]:k], elementType)
if err != nil {
return sddl, err
}
awaitingACLFlags = false
}
elementStart = append(elementStart, k+1) // raise the element start scope
err := sddl.startACL(elementType)
if err != nil {
return sddl, err
}
}
case v == ')':
// (...,...,...,(...))
scope--
if scope == 0 {
err := sddl.putACLElement(input[elementStart[1]:k], elementType)
if err != nil {
return sddl, err
}
elementStart = elementStart[:1] // lower the element start scope
}
case scope == 1: // We're at the top level of an ACE string
switch v {
case ';':
// moving to the next element
err := sddl.putACLElement(input[elementStart[1]:k], elementType)
if err != nil {
return sddl, err
}
elementStart[1] = k + 1 // move onto the next bit of the element scope
}
case scope == 0: // We're at the top level of a SDDL string
if k == len(input)-1 || v == ':' { // If we end the string OR start a new section
if elementType != 0x00 {
switch elementType {
case 'O':
// you are here:
// V
// O:...G:
// ^
// k-1
// string separations in go happen [x:y).
sddl.OwnerSID = strings.TrimSpace(input[elementStart[0]:IffInt(k == len(input)-1, len(input), k-1)])
case 'G':
sddl.GroupSID = strings.TrimSpace(input[elementStart[0]:IffInt(k == len(input)-1, len(input), k-1)])
case 'D', 'S': // These are both parsed WHILE they happen, UNLESS we're awaiting flags.
if awaitingACLFlags {
err := sddl.setACLFlags(strings.TrimSpace(input[elementStart[0]:IffInt(k == len(input)-1, len(input), k-1)]), elementType)
if err != nil {
return sddl, err
}
}
default:
return sddl, fmt.Errorf("%s is an invalid SDDL section", string(elementType))
}
}
if v == ':' {
// set element type to last character
elementType = rune(input[k-1])
// await ACL flags
if elementType == 'D' || elementType == 'S' {
awaitingACLFlags = true
}
// set element start to next character
if len(elementStart) == 0 { // start the list if it's empty
elementStart = append(elementStart, k+1)
} else if len(elementStart) > 1 {
return sddl, errors.New("elementStart too long for starting a new part of a SDDL")
} else { // assign the new element start
elementStart[0] = k + 1
}
}
}
}
}
if scope > 0 || inString {
return sddl, errors.New("string or scope not fully exited")
}
if err == nil {
if !sanityCheckSDDLParse(input, sddl) {
return sddl, errors.New("SDDL parsing sanity check failed")
}
}
return
}
var sddlWhitespaceRegex = regexp.MustCompile(`[\x09-\x0D ]`)
func sanityCheckSDDLParse(original string, parsed SDDLString) bool {
return sddlWhitespaceRegex.ReplaceAllString(original, "") ==
sddlWhitespaceRegex.ReplaceAllString(parsed.String(), "")
}