|
| 1 | +// Copyright © Microsoft <wastore@microsoft.com> |
| 2 | +// |
| 3 | +// Permission is hereby granted, free of charge, to any person obtaining a copy |
| 4 | +// of this software and associated documentation files (the "Software"), to deal |
| 5 | +// in the Software without restriction, including without limitation the rights |
| 6 | +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| 7 | +// copies of the Software, and to permit persons to whom the Software is |
| 8 | +// furnished to do so, subject to the following conditions: |
| 9 | +// |
| 10 | +// The above copyright notice and this permission notice shall be included in |
| 11 | +// all copies or substantial portions of the Software. |
| 12 | +// |
| 13 | +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 14 | +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 15 | +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 16 | +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 17 | +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 18 | +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
| 19 | +// THE SOFTWARE. |
| 20 | + |
| 21 | +package sddl |
| 22 | + |
| 23 | +import ( |
| 24 | + "errors" |
| 25 | + "fmt" |
| 26 | + "regexp" |
| 27 | + "strings" |
| 28 | +) |
| 29 | + |
| 30 | +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 |
| 31 | + |
| 32 | +func IffInt(condition bool, tVal, fVal int) int { |
| 33 | + if condition { |
| 34 | + return tVal |
| 35 | + } |
| 36 | + return fVal |
| 37 | +} |
| 38 | + |
| 39 | +func ParseSDDL(input string) (sddl SDDLString, err error) { |
| 40 | + scope := 0 // if scope is 1, we're in an ACE string, if scope is 2, we're in a resource attribute. |
| 41 | + inString := false // If a quotation mark was found, we've entered a string and should ignore all characters except another quotation mark. |
| 42 | + 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. |
| 43 | + awaitingACLFlags := false // If this is true, a ACL section was just entered, and we're awaiting our first ACE string |
| 44 | + var elementType rune // We need to keep track of which section of the SDDL string we're in. |
| 45 | + for k, v := range input { |
| 46 | + switch { |
| 47 | + case inString: // ignore characters within a string-- except for the end of a string, and escaped quotes |
| 48 | + if v == '"' && input[k-1] != '\\' { |
| 49 | + inString = false |
| 50 | + } |
| 51 | + case v == '"': |
| 52 | + inString = true |
| 53 | + case v == '(': // this comes before scope == 1 because ACE strings can be multi-leveled. We only care about the bottom level. |
| 54 | + scope++ |
| 55 | + 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. |
| 56 | + if awaitingACLFlags { |
| 57 | + err := sddl.setACLFlags(input[elementStart[0]:k], elementType) |
| 58 | + |
| 59 | + if err != nil { |
| 60 | + return sddl, err |
| 61 | + } |
| 62 | + |
| 63 | + awaitingACLFlags = false |
| 64 | + } |
| 65 | + elementStart = append(elementStart, k+1) // raise the element start scope |
| 66 | + err := sddl.startACL(elementType) |
| 67 | + |
| 68 | + if err != nil { |
| 69 | + return sddl, err |
| 70 | + } |
| 71 | + } |
| 72 | + case v == ')': |
| 73 | + // (...,...,...,(...)) |
| 74 | + scope-- |
| 75 | + if scope == 0 { |
| 76 | + err := sddl.putACLElement(input[elementStart[1]:k], elementType) |
| 77 | + |
| 78 | + if err != nil { |
| 79 | + return sddl, err |
| 80 | + } |
| 81 | + |
| 82 | + elementStart = elementStart[:1] // lower the element start scope |
| 83 | + } |
| 84 | + case scope == 1: // We're at the top level of an ACE string |
| 85 | + switch v { |
| 86 | + case ';': |
| 87 | + // moving to the next element |
| 88 | + err := sddl.putACLElement(input[elementStart[1]:k], elementType) |
| 89 | + |
| 90 | + if err != nil { |
| 91 | + return sddl, err |
| 92 | + } |
| 93 | + |
| 94 | + elementStart[1] = k + 1 // move onto the next bit of the element scope |
| 95 | + } |
| 96 | + case scope == 0: // We're at the top level of a SDDL string |
| 97 | + if k == len(input)-1 || v == ':' { // If we end the string OR start a new section |
| 98 | + if elementType != 0x00 { |
| 99 | + switch elementType { |
| 100 | + case 'O': |
| 101 | + // you are here: |
| 102 | + // V |
| 103 | + // O:...G: |
| 104 | + // ^ |
| 105 | + // k-1 |
| 106 | + // string separations in go happen [x:y). |
| 107 | + sddl.OwnerSID = strings.TrimSpace(input[elementStart[0]:IffInt(k == len(input)-1, len(input), k-1)]) |
| 108 | + case 'G': |
| 109 | + sddl.GroupSID = strings.TrimSpace(input[elementStart[0]:IffInt(k == len(input)-1, len(input), k-1)]) |
| 110 | + case 'D', 'S': // These are both parsed WHILE they happen, UNLESS we're awaiting flags. |
| 111 | + if awaitingACLFlags { |
| 112 | + err := sddl.setACLFlags(strings.TrimSpace(input[elementStart[0]:IffInt(k == len(input)-1, len(input), k-1)]), elementType) |
| 113 | + |
| 114 | + if err != nil { |
| 115 | + return sddl, err |
| 116 | + } |
| 117 | + } |
| 118 | + default: |
| 119 | + return sddl, fmt.Errorf("%s is an invalid SDDL section", string(elementType)) |
| 120 | + } |
| 121 | + } |
| 122 | + |
| 123 | + if v == ':' { |
| 124 | + // set element type to last character |
| 125 | + elementType = rune(input[k-1]) |
| 126 | + |
| 127 | + // await ACL flags |
| 128 | + if elementType == 'D' || elementType == 'S' { |
| 129 | + awaitingACLFlags = true |
| 130 | + } |
| 131 | + |
| 132 | + // set element start to next character |
| 133 | + if len(elementStart) == 0 { // start the list if it's empty |
| 134 | + elementStart = append(elementStart, k+1) |
| 135 | + } else if len(elementStart) > 1 { |
| 136 | + return sddl, errors.New("elementStart too long for starting a new part of a SDDL") |
| 137 | + } else { // assign the new element start |
| 138 | + elementStart[0] = k + 1 |
| 139 | + } |
| 140 | + } |
| 141 | + } |
| 142 | + } |
| 143 | + } |
| 144 | + |
| 145 | + if scope > 0 || inString { |
| 146 | + return sddl, errors.New("string or scope not fully exited") |
| 147 | + } |
| 148 | + |
| 149 | + if err == nil { |
| 150 | + if !sanityCheckSDDLParse(input, sddl) { |
| 151 | + return sddl, errors.New("SDDL parsing sanity check failed") |
| 152 | + } |
| 153 | + } |
| 154 | + |
| 155 | + return |
| 156 | +} |
| 157 | + |
| 158 | +var sddlWhitespaceRegex = regexp.MustCompile(`[\x09-\x0D ]`) |
| 159 | + |
| 160 | +func sanityCheckSDDLParse(original string, parsed SDDLString) bool { |
| 161 | + return sddlWhitespaceRegex.ReplaceAllString(original, "") == |
| 162 | + sddlWhitespaceRegex.ReplaceAllString(parsed.String(), "") |
| 163 | +} |
0 commit comments