summaryrefslogtreecommitdiffstats
path: root/libcli/security/sddl_conditional_ace.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--libcli/security/sddl_conditional_ace.c3476
1 files changed, 3476 insertions, 0 deletions
diff --git a/libcli/security/sddl_conditional_ace.c b/libcli/security/sddl_conditional_ace.c
new file mode 100644
index 0000000..e9d83b7
--- /dev/null
+++ b/libcli/security/sddl_conditional_ace.c
@@ -0,0 +1,3476 @@
+/*
+ * Unix SMB implementation.
+ * Functions for understanding conditional ACEs
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "librpc/gen_ndr/ndr_security.h"
+#include "librpc/gen_ndr/conditional_ace.h"
+#include "libcli/security/security.h"
+#include "libcli/security/conditional_ace.h"
+#include "libcli/security/claims-conversions.h"
+#include "lib/util/tsort.h"
+#include "lib/util/bytearray.h"
+
+
+/* We're only dealing with utf-8 here. Honestly. */
+#undef strncasecmp
+
+
+#define SDDL_FLAG_EXPECTING_UNARY_OP 1
+#define SDDL_FLAG_EXPECTING_BINARY_OP 2
+#define SDDL_FLAG_EXPECTING_BINARY_LOGIC_OP 4
+#define SDDL_FLAG_EXPECTING_LOCAL_ATTR 8
+#define SDDL_FLAG_EXPECTING_NON_LOCAL_ATTR 16
+#define SDDL_FLAG_EXPECTING_LITERAL 32
+#define SDDL_FLAG_EXPECTING_PAREN 64
+#define SDDL_FLAG_EXPECTING_PAREN_LITERAL 128
+#define SDDL_FLAG_NOT_EXPECTING_END_PAREN 256
+
+#define SDDL_FLAG_DEVICE 512
+
+#define SDDL_FLAG_IS_UNARY_OP (1 << 20)
+#define SDDL_FLAG_IS_BINARY_OP (1 << 21)
+
+
+#define SDDL_FLAGS_EXPR_START (SDDL_FLAG_EXPECTING_UNARY_OP | \
+ SDDL_FLAG_EXPECTING_LOCAL_ATTR | \
+ SDDL_FLAG_EXPECTING_NON_LOCAL_ATTR | \
+ SDDL_FLAG_EXPECTING_PAREN)
+
+#define SDDL_FLAGS_MEMBER_OP (SDDL_FLAG_EXPECTING_LITERAL | \
+ SDDL_FLAG_EXPECTING_PAREN_LITERAL | \
+ SDDL_FLAG_IS_UNARY_OP)
+
+#define SDDL_FLAGS_RELATIONAL_OP (SDDL_FLAG_EXPECTING_LITERAL | \
+ SDDL_FLAG_EXPECTING_PAREN_LITERAL | \
+ SDDL_FLAG_EXPECTING_NON_LOCAL_ATTR | \
+ SDDL_FLAG_IS_BINARY_OP)
+
+#define SDDL_FLAGS_CONTAINS_OP (SDDL_FLAG_EXPECTING_LITERAL | \
+ SDDL_FLAG_EXPECTING_NON_LOCAL_ATTR | \
+ SDDL_FLAG_IS_BINARY_OP)
+
+#define SDDL_FLAGS_EXISTS_OP (SDDL_FLAG_EXPECTING_LOCAL_ATTR | \
+ SDDL_FLAG_EXPECTING_NON_LOCAL_ATTR | \
+ SDDL_FLAG_IS_UNARY_OP)
+
+#define SDDL_FLAGS_LOGIC_OP (SDDL_FLAG_EXPECTING_LOCAL_ATTR | \
+ SDDL_FLAG_EXPECTING_NON_LOCAL_ATTR | \
+ SDDL_FLAG_EXPECTING_PAREN | \
+ SDDL_FLAG_EXPECTING_UNARY_OP | \
+ SDDL_FLAG_IS_BINARY_OP)
+
+#define SDDL_FLAGS_ATTRIBUTE (SDDL_FLAG_EXPECTING_BINARY_OP | \
+ SDDL_FLAG_EXPECTING_BINARY_LOGIC_OP)
+
+#define SDDL_FLAGS_LITERAL SDDL_FLAG_EXPECTING_BINARY_LOGIC_OP
+
+#define SDDL_FLAGS_PAREN_END (SDDL_FLAG_EXPECTING_BINARY_LOGIC_OP | \
+ SDDL_FLAG_EXPECTING_BINARY_OP)
+
+enum {
+ SDDL_NOT_AN_OP = 0,
+ SDDL_PRECEDENCE_EXISTS,
+ SDDL_PRECEDENCE_COMMON,
+ SDDL_PRECEDENCE_NOT,
+ SDDL_PRECEDENCE_AND,
+ SDDL_PRECEDENCE_OR,
+ SDDL_PRECEDENCE_PAREN_END,
+ SDDL_PRECEDENCE_PAREN_START,
+};
+
+struct ace_condition_sddl_compiler_context {
+ TALLOC_CTX *mem_ctx;
+ const uint8_t *sddl;
+ uint32_t length;
+ uint32_t offset;
+ uint32_t stack_depth;
+ uint32_t max_program_length;
+ uint32_t approx_size;
+ struct ace_condition_script *program;
+ struct ace_condition_token *stack;
+ struct ace_condition_token *target;
+ uint32_t *target_len;
+ const char *message;
+ uint32_t message_offset;
+ struct dom_sid *domain_sid;
+ uint32_t state;
+ uint8_t last_token_type;
+ bool allow_device;
+};
+
+struct sddl_data {
+ const char *name;
+ uint32_t flags;
+ uint8_t op_precedence;
+ uint8_t nargs;
+};
+
+static const struct sddl_data sddl_strings[256] = {
+ /* operators */
+ [CONDITIONAL_ACE_TOKEN_MEMBER_OF] = {
+ "Member_of",
+ SDDL_FLAGS_MEMBER_OP,
+ SDDL_PRECEDENCE_COMMON,
+ 1
+ },
+ [CONDITIONAL_ACE_TOKEN_DEVICE_MEMBER_OF] = {
+ "Device_Member_of",
+ SDDL_FLAGS_MEMBER_OP|SDDL_FLAG_DEVICE,
+ SDDL_PRECEDENCE_COMMON,
+ 1
+ },
+ [CONDITIONAL_ACE_TOKEN_MEMBER_OF_ANY] = {
+ /* [MS-DTYP] says "_Any", but windows prefers '_any' */
+ "Member_of_any",
+ SDDL_FLAGS_MEMBER_OP,
+ SDDL_PRECEDENCE_COMMON,
+ 1
+ },
+ [CONDITIONAL_ACE_TOKEN_DEVICE_MEMBER_OF_ANY] = {
+ "Device_Member_of_Any",
+ SDDL_FLAGS_MEMBER_OP|SDDL_FLAG_DEVICE,
+ SDDL_PRECEDENCE_COMMON,
+ 1
+ },
+ [CONDITIONAL_ACE_TOKEN_NOT_MEMBER_OF] = {
+ "Not_Member_of",
+ SDDL_FLAGS_MEMBER_OP,
+ SDDL_PRECEDENCE_COMMON,
+ 1
+ },
+ [CONDITIONAL_ACE_TOKEN_NOT_DEVICE_MEMBER_OF] = {
+ "Not_Device_Member_of",
+ SDDL_FLAGS_MEMBER_OP|SDDL_FLAG_DEVICE,
+ SDDL_PRECEDENCE_COMMON,
+ 1
+ },
+ [CONDITIONAL_ACE_TOKEN_NOT_MEMBER_OF_ANY] = {
+ "Not_Member_of_Any",
+ SDDL_FLAGS_MEMBER_OP,
+ SDDL_PRECEDENCE_COMMON,
+ 1
+ },
+ [CONDITIONAL_ACE_TOKEN_NOT_DEVICE_MEMBER_OF_ANY] = {
+ "Not_Device_Member_of_Any",
+ SDDL_FLAGS_MEMBER_OP|SDDL_FLAG_DEVICE,
+ SDDL_PRECEDENCE_COMMON,
+ 1
+ },
+ [CONDITIONAL_ACE_TOKEN_EQUAL] = {
+ "==",
+ SDDL_FLAGS_RELATIONAL_OP,
+ SDDL_PRECEDENCE_COMMON,
+ 2
+ },
+ [CONDITIONAL_ACE_TOKEN_NOT_EQUAL] = {
+ "!=",
+ SDDL_FLAGS_RELATIONAL_OP,
+ SDDL_PRECEDENCE_COMMON,
+ 2
+ },
+ [CONDITIONAL_ACE_TOKEN_LESS_THAN] = {
+ "<",
+ SDDL_FLAGS_RELATIONAL_OP,
+ SDDL_PRECEDENCE_COMMON,
+ 2
+ },
+ [CONDITIONAL_ACE_TOKEN_LESS_OR_EQUAL] = {
+ "<=",
+ SDDL_FLAGS_RELATIONAL_OP,
+ SDDL_PRECEDENCE_COMMON,
+ 2
+ },
+ [CONDITIONAL_ACE_TOKEN_GREATER_THAN] = {
+ ">",
+ SDDL_FLAGS_RELATIONAL_OP,
+ SDDL_PRECEDENCE_COMMON,
+ 2
+ },
+ [CONDITIONAL_ACE_TOKEN_GREATER_OR_EQUAL] = {
+ ">=",
+ SDDL_FLAGS_RELATIONAL_OP,
+ SDDL_PRECEDENCE_COMMON,
+ 2
+ },
+ [CONDITIONAL_ACE_TOKEN_CONTAINS] = {
+ "Contains",
+ SDDL_FLAGS_CONTAINS_OP,
+ SDDL_PRECEDENCE_COMMON,
+ 2
+ },
+ [CONDITIONAL_ACE_TOKEN_ANY_OF] = {
+ "Any_of",
+ SDDL_FLAGS_CONTAINS_OP,
+ SDDL_PRECEDENCE_COMMON,
+ 2
+ },
+ [CONDITIONAL_ACE_TOKEN_NOT_CONTAINS] = {
+ "Not_Contains",
+ SDDL_FLAGS_CONTAINS_OP,
+ SDDL_PRECEDENCE_COMMON,
+ 2
+ },
+ [CONDITIONAL_ACE_TOKEN_NOT_ANY_OF] = {
+ "Not_Any_of",
+ SDDL_FLAGS_CONTAINS_OP,
+ SDDL_PRECEDENCE_COMMON,
+ 2
+ },
+ [CONDITIONAL_ACE_TOKEN_AND] = {
+ "&&",
+ SDDL_FLAGS_LOGIC_OP,
+ SDDL_PRECEDENCE_AND,
+ 2
+ },
+ [CONDITIONAL_ACE_TOKEN_OR] = {
+ "||",
+ SDDL_FLAGS_LOGIC_OP,
+ SDDL_PRECEDENCE_OR,
+ 2
+ },
+ [CONDITIONAL_ACE_TOKEN_NOT] = {
+ "!",
+ (SDDL_FLAG_EXPECTING_PAREN |
+ SDDL_FLAG_EXPECTING_NON_LOCAL_ATTR |
+ SDDL_FLAG_IS_UNARY_OP),
+ SDDL_PRECEDENCE_NOT,
+ 1
+ },
+ [CONDITIONAL_ACE_TOKEN_EXISTS] = {
+ "Exists",
+ SDDL_FLAGS_EXISTS_OP,
+ SDDL_PRECEDENCE_EXISTS,
+ 1
+ },
+ [CONDITIONAL_ACE_TOKEN_NOT_EXISTS] = {
+ "Not_Exists",
+ SDDL_FLAGS_EXISTS_OP,
+ SDDL_PRECEDENCE_EXISTS,
+ 1
+ },
+ /* pseudo-operator pseudo-tokens */
+ [CONDITIONAL_ACE_SAMBA_SDDL_PAREN] = {
+ "(",
+ 0,
+ SDDL_PRECEDENCE_PAREN_START,
+ 0
+ },
+ [CONDITIONAL_ACE_SAMBA_SDDL_PAREN_END] = {
+ ")",
+ SDDL_FLAGS_PAREN_END,
+ SDDL_PRECEDENCE_PAREN_END,
+ 0
+ },
+
+ /*
+ * non-operators.
+ * The names here are only used for error messages.
+ *
+ * some of them will never actually be encountered (e.g. 8-bit
+ * integers).
+ */
+ [CONDITIONAL_ACE_TOKEN_INT8] = {
+ .name = "8-bit integer",
+ .flags = SDDL_FLAGS_LITERAL,
+ SDDL_NOT_AN_OP,
+ 0
+ },
+ [CONDITIONAL_ACE_TOKEN_INT16] = {
+ "16-bit integer",
+ SDDL_FLAGS_LITERAL,
+ SDDL_NOT_AN_OP,
+ 0
+ },
+ [CONDITIONAL_ACE_TOKEN_INT32] = {
+ "32-bit integer",
+ SDDL_FLAGS_LITERAL,
+ SDDL_NOT_AN_OP,
+ 0
+ },
+ [CONDITIONAL_ACE_TOKEN_INT64] = {
+ "64-bit integer",
+ SDDL_FLAGS_LITERAL,
+ SDDL_NOT_AN_OP,
+ 0
+ },
+
+ [CONDITIONAL_ACE_TOKEN_UNICODE] = {
+ "unicode",
+ SDDL_FLAGS_LITERAL,
+ SDDL_NOT_AN_OP,
+ 0
+ },
+ [CONDITIONAL_ACE_TOKEN_OCTET_STRING] = {
+ "byte string",
+ SDDL_FLAGS_LITERAL,
+ SDDL_NOT_AN_OP,
+ 0
+ },
+ [CONDITIONAL_ACE_TOKEN_COMPOSITE] = {
+ "composite list",
+ SDDL_FLAGS_LITERAL,
+ SDDL_NOT_AN_OP,
+ 0
+ },
+ [CONDITIONAL_ACE_TOKEN_SID] = {
+ "SID",
+ SDDL_FLAGS_LITERAL,
+ SDDL_NOT_AN_OP,
+ 0
+ },
+ [CONDITIONAL_ACE_LOCAL_ATTRIBUTE] = {
+ "local attribute",
+ SDDL_FLAGS_ATTRIBUTE,
+ SDDL_NOT_AN_OP,
+ 0
+ },
+ [CONDITIONAL_ACE_USER_ATTRIBUTE] = {
+ "user attribute",
+ SDDL_FLAGS_ATTRIBUTE,
+ SDDL_NOT_AN_OP,
+ 0
+ },
+ [CONDITIONAL_ACE_RESOURCE_ATTRIBUTE] = {
+ "resource attribute",
+ SDDL_FLAGS_ATTRIBUTE,
+ SDDL_NOT_AN_OP,
+ 0
+ },
+ [CONDITIONAL_ACE_DEVICE_ATTRIBUTE] = {
+ "device attribute",
+ SDDL_FLAGS_ATTRIBUTE|SDDL_FLAG_DEVICE,
+ SDDL_NOT_AN_OP,
+ 0
+ },
+ [CONDITIONAL_ACE_SAMBA_RESULT_BOOL] = {
+ "boolean result",
+ 0,
+ SDDL_NOT_AN_OP,
+ 0
+ },
+ [CONDITIONAL_ACE_SAMBA_RESULT_NULL] = {
+ "null result",
+ 0,
+ SDDL_NOT_AN_OP,
+ 0
+ },
+ [CONDITIONAL_ACE_SAMBA_RESULT_ERROR] = {
+ "error result",
+ 0,
+ SDDL_NOT_AN_OP,
+ 0
+ },
+};
+
+struct sddl_attr_type{
+ const char *name;
+ uint8_t code;
+};
+
+/*
+ * These are the prefixes for non-local attribute types. [MS-DTYP]
+ * styles them in title case ("@User."), but Windows itself seems to
+ * prefer all-caps, so that is how we render them.
+ */
+static const struct sddl_attr_type sddl_attr_types[] = {
+ {"USER.", CONDITIONAL_ACE_USER_ATTRIBUTE},
+ {"RESOURCE.", CONDITIONAL_ACE_RESOURCE_ATTRIBUTE},
+ {"DEVICE.", CONDITIONAL_ACE_DEVICE_ATTRIBUTE},
+};
+
+
+struct sddl_write_context {
+ TALLOC_CTX *mem_ctx;
+ char *sddl;
+ size_t len;
+ size_t alloc_len;
+};
+
+static bool sddl_write(struct sddl_write_context *ctx,
+ const char *s)
+{
+ size_t len = strlen(s);
+ if (ctx->alloc_len - ctx->len <= len ||
+ ctx->sddl == NULL) {
+ size_t old = ctx->alloc_len;
+ ctx->alloc_len = old + MAX(old / 2, len + 50);
+ if (ctx->alloc_len <= old ||
+ ctx->alloc_len - ctx->len <= len) {
+ return false;
+ }
+ ctx->sddl = talloc_realloc(ctx->mem_ctx, ctx->sddl,
+ char, ctx->alloc_len);
+
+ if (ctx->sddl == NULL) {
+ return false;
+ }
+ }
+ memcpy(ctx->sddl + ctx->len, s, len);
+ ctx->len += len;
+ ctx->sddl[ctx->len] = 0;
+ return true;
+}
+
+/*
+ * This is a helper function to create a representation of a
+ * conditional ACE. This is not SDDL, more like a disassembly,
+ * but it uses some of the same tables.
+ */
+char *debug_conditional_ace(TALLOC_CTX *mem_ctx,
+ struct ace_condition_script *program)
+{
+ size_t i;
+ size_t depth = 0;
+ char stack[] = " ";
+ char line[120];
+ struct sddl_write_context ctx = {
+ .mem_ctx = mem_ctx
+ };
+
+ for (i = 0; i < program->length; i++) {
+ struct ace_condition_token *tok = &program->tokens[i];
+ struct sddl_data s = sddl_strings[tok->type];
+ char hex[21];
+ char *utf8 = NULL;
+ int utf8_len;
+ char type;
+ char nom[40];
+ snprintf(nom, sizeof(nom), "\033[1;33m%20s\033[0m", s.name);
+ switch (tok->type) {
+ case CONDITIONAL_ACE_TOKEN_INT8:
+ case CONDITIONAL_ACE_TOKEN_INT16:
+ case CONDITIONAL_ACE_TOKEN_INT32:
+ case CONDITIONAL_ACE_TOKEN_INT64:
+ if (tok->data.int64.sign > 3 ||
+ tok->data.int64.base > 3) {
+ goto error;
+ }
+ snprintf(line, sizeof(line),
+ "%s %"PRIi64" %c%c\n",
+ nom,
+ tok->data.int64.value,
+ "?+-_"[tok->data.int64.sign],
+ "?odh"[tok->data.int64.base]
+ );
+ type = 'i';
+ break;
+
+ case CONDITIONAL_ACE_TOKEN_MEMBER_OF:
+ case CONDITIONAL_ACE_TOKEN_DEVICE_MEMBER_OF:
+ case CONDITIONAL_ACE_TOKEN_MEMBER_OF_ANY:
+ case CONDITIONAL_ACE_TOKEN_DEVICE_MEMBER_OF_ANY:
+ case CONDITIONAL_ACE_TOKEN_NOT_MEMBER_OF:
+ case CONDITIONAL_ACE_TOKEN_NOT_DEVICE_MEMBER_OF:
+ case CONDITIONAL_ACE_TOKEN_NOT_MEMBER_OF_ANY:
+ case CONDITIONAL_ACE_TOKEN_NOT_DEVICE_MEMBER_OF_ANY:
+ snprintf(line, sizeof(line),
+ "%s bool\n",
+ nom
+ );
+ type = 'b';
+ break;
+
+ case CONDITIONAL_ACE_TOKEN_EQUAL:
+ case CONDITIONAL_ACE_TOKEN_NOT_EQUAL:
+ case CONDITIONAL_ACE_TOKEN_LESS_THAN:
+ case CONDITIONAL_ACE_TOKEN_LESS_OR_EQUAL:
+ case CONDITIONAL_ACE_TOKEN_GREATER_THAN:
+ case CONDITIONAL_ACE_TOKEN_GREATER_OR_EQUAL:
+ case CONDITIONAL_ACE_TOKEN_CONTAINS:
+ case CONDITIONAL_ACE_TOKEN_ANY_OF:
+ case CONDITIONAL_ACE_TOKEN_NOT_CONTAINS:
+ case CONDITIONAL_ACE_TOKEN_NOT_ANY_OF:
+ case CONDITIONAL_ACE_TOKEN_AND:
+ case CONDITIONAL_ACE_TOKEN_OR:
+ snprintf(line, sizeof(line),
+ "%s bool\n",
+ nom
+ );
+ type = 'b';
+ break;
+
+ case CONDITIONAL_ACE_TOKEN_EXISTS:
+ case CONDITIONAL_ACE_TOKEN_NOT_EXISTS:
+ case CONDITIONAL_ACE_TOKEN_NOT:
+ snprintf(line, sizeof(line),
+ "%s bool\n",
+ nom
+ );
+ type = 'b';
+ break;
+
+ case CONDITIONAL_ACE_LOCAL_ATTRIBUTE:
+ case CONDITIONAL_ACE_USER_ATTRIBUTE:
+ case CONDITIONAL_ACE_RESOURCE_ATTRIBUTE:
+ case CONDITIONAL_ACE_DEVICE_ATTRIBUTE:
+ snprintf(line, sizeof(line),
+ "%s.%s (any type)\n",
+ nom,
+ tok->data.unicode.value
+ );
+ type = '?';
+ break;
+
+ case CONDITIONAL_ACE_TOKEN_UNICODE:
+ snprintf(line, sizeof(line),
+ "%s.%s (any type)\n",
+ nom,
+ tok->data.unicode.value
+ );
+ type = 'u';
+ break;
+
+ case CONDITIONAL_ACE_TOKEN_OCTET_STRING:
+ utf8_len = MIN(tok->data.bytes.length, 9);
+ hex_encode_buf(hex, tok->data.bytes.data, utf8_len);
+
+ snprintf(line, sizeof(line),
+ "%s %.*s (%d)\n",
+ nom, utf8_len * 2, hex, utf8_len);
+ type = 'o';
+ break;
+ case CONDITIONAL_ACE_TOKEN_SID:
+ utf8 = sddl_encode_sid(mem_ctx,
+ &tok->data.sid.sid,
+ NULL);
+ snprintf(line, sizeof(line),
+ "%s (%s)\n",
+ nom, utf8);
+ type = 'S';
+ break;
+ case CONDITIONAL_ACE_TOKEN_COMPOSITE:
+ snprintf(line, sizeof(line),
+ "%s %"PRIu32" direct members\n",
+ nom, tok->data.composite.n_members);
+ type = 'C';
+ break;
+
+ case CONDITIONAL_ACE_TOKEN_INVALID_OR_PADDING:
+ snprintf(line, sizeof(line),
+ "%s\n", nom);
+ type = '0';
+ break;
+ default:
+ snprintf(line, sizeof(line),
+ "unknown opcode %#02x\n", tok->type);
+ type = '!';
+ break;
+ }
+
+ if (s.nargs > depth) {
+ snprintf(nom, sizeof(nom),
+ "UNDER: -%zu", s.nargs - depth);
+ depth = 0;
+ sddl_write(&ctx, nom);
+ } else if (depth >= strlen(stack)) {
+ snprintf(nom, sizeof(nom),
+ "depth %zu", s.nargs - depth);
+ depth -= (s.nargs - 1);
+ sddl_write(&ctx, nom);
+ } else {
+ depth -= s.nargs;
+ stack[depth] = type;
+ depth++;
+ if (depth < strlen(stack)) {
+ stack[depth] = ' ';
+ }
+ sddl_write(&ctx, stack);
+ }
+ sddl_write(&ctx, line);
+ }
+ if (depth == 1 && stack[0] == 'b') {
+ snprintf(line, sizeof(line),
+ "\033[1;32mGOOD: finishes on a single bool\033[0m\n");
+ } else {
+ snprintf(line, sizeof(line),
+ "\033[1;31mBAD: should finish with a bool\033[0m\n");
+ }
+ sddl_write(&ctx, line);
+ return ctx.sddl;
+
+ error:
+ TALLOC_FREE(ctx.sddl);
+ return NULL;
+}
+
+
+struct sddl_node {
+ struct ace_condition_token *tok;
+ struct sddl_node *lhs;
+ struct sddl_node *rhs;
+ bool wants_parens;
+};
+
+static bool sddl_write_int(struct sddl_write_context *ctx,
+ const struct ace_condition_token *tok)
+{
+ int64_t v = tok->data.int64.value;
+ uint8_t sign = tok->data.int64.sign;
+ uint8_t base = tok->data.int64.base;
+ char buf[26]; /* oct(1<<63) + sign + \0 */
+ char sign_char;
+ if (sign > CONDITIONAL_ACE_INT_SIGN_NONE ||
+ base > CONDITIONAL_ACE_INT_BASE_16) {
+ return false;
+ }
+
+ /*
+ * we have 9 combinations of base/sign (+ some invalid combinations of
+ * actual sign vs claimed sign).
+ */
+ if (sign == CONDITIONAL_ACE_INT_SIGN_NONE) {
+ /* octal and hex will end up unsigned! */
+ if (base == CONDITIONAL_ACE_INT_BASE_8) {
+ snprintf(buf, sizeof(buf), "0%"PRIo64, v);
+ } else if (base == CONDITIONAL_ACE_INT_BASE_10) {
+ snprintf(buf, sizeof(buf), "%"PRId64, v);
+ } else {
+ snprintf(buf, sizeof(buf), "0x%"PRIx64, v);
+ }
+ return sddl_write(ctx, buf);
+ }
+ if (sign == CONDITIONAL_ACE_INT_SIGN_POSITIVE && v < 0) {
+ return false;
+ }
+ if (sign == CONDITIONAL_ACE_INT_SIGN_NEGATIVE && v > 0) {
+ /* note we allow "-0", because we will parse it. */
+ return false;
+ }
+ sign_char = (sign == CONDITIONAL_ACE_INT_SIGN_NEGATIVE) ? '-' : '+';
+ /*
+ * We can use "%+ld" for the decimal sign (except -0), but
+ * "%+lx" and "%+lo" are invalid because %o and %x are
+ * unsigned.
+ */
+ if (base == CONDITIONAL_ACE_INT_BASE_10) {
+ if (v == 0) {
+ snprintf(buf, sizeof(buf), "%c0", sign_char);
+ } else {
+ snprintf(buf, sizeof(buf), "%+"PRId64, v);
+ }
+ return sddl_write(ctx, buf);
+ }
+
+ if (v == INT64_MIN) {
+ /*
+ * llabs(INT64_MIN) will be undefined.
+ * The lengths we must go to to round trip!
+ */
+ if (base == CONDITIONAL_ACE_INT_BASE_8) {
+ return sddl_write(ctx, "-01000000000000000000000");
+ }
+ return sddl_write(ctx, "-0x8000000000000000");
+ }
+
+ if (base == CONDITIONAL_ACE_INT_BASE_8) {
+ snprintf(buf, sizeof(buf), "%c0%llo", sign_char, llabs(v));
+ } else {
+ snprintf(buf, sizeof(buf), "%c0x%llx", sign_char, llabs(v));
+ }
+ return sddl_write(ctx, buf);
+}
+
+
+static bool sddl_should_escape_utf16(uint16_t c)
+{
+ if (c <= ' ' || c > 126) {
+ return true;
+ }
+
+ switch (c) {
+ case '!':
+ case '"':
+ case '&':
+ case '(':
+ case ')':
+ case '<':
+ case '=':
+ case '>':
+ case '|':
+ case '%':
+ return true;
+ }
+
+ return false;
+}
+
+static bool sddl_encode_attr_name(TALLOC_CTX *mem_ctx,
+ const char *src,
+ char **dest,
+ size_t *dest_len)
+{
+ size_t i, j;
+ bool ok;
+ uint16_t *utf16 = NULL;
+ char *escaped = NULL;
+ size_t utf16_byte_len;
+ size_t utf16_len;
+ size_t src_len = strlen(src);
+ size_t escapees;
+ size_t required;
+ *dest = NULL;
+
+ /*
+ * Writing the string escapes can only really happen in
+ * utf-16.
+ */
+ ok = convert_string_talloc(mem_ctx,
+ CH_UTF8, CH_UTF16LE,
+ src, src_len,
+ &utf16, &utf16_byte_len);
+ if (!ok) {
+ return false;
+ }
+ utf16_len = utf16_byte_len / 2;
+
+ escapees = 0;
+ for (i = 0; i < utf16_len; i++) {
+ uint16_t c = utf16[i];
+ if (sddl_should_escape_utf16(c)) {
+ escapees++;
+ }
+ if (c == 0) {
+ /* we can't have '\0' (or "%0000") in a name. */
+ TALLOC_FREE(utf16);
+ return false;
+ }
+ }
+
+ required = src_len + escapees * 5;
+ escaped = talloc_size(mem_ctx, required + 1);
+ if (escaped == NULL) {
+ TALLOC_FREE(utf16);
+ return false;
+ }
+
+ if (escapees == 0) {
+ /* there is nothing to escape: the original string is fine */
+ memcpy(escaped, src, src_len);
+ escaped[src_len] = '\0';
+ *dest = escaped;
+ *dest_len = src_len;
+ TALLOC_FREE(utf16);
+ return true;
+ }
+
+ for (i = 0, j = 0; i < utf16_len && j < required; i++) {
+ uint16_t c = utf16[i];
+ if (sddl_should_escape_utf16(c)) {
+ if (j + 5 >= required) {
+ TALLOC_FREE(escaped);
+ TALLOC_FREE(utf16);
+ return false;
+ }
+ snprintf(escaped + j, 6, "%%%04x", c);
+ j += 5;
+ } else {
+ escaped[j] = c;
+ j++;
+ }
+ }
+ escaped[j] = '\0';
+
+ *dest = escaped;
+ *dest_len = j;
+
+ TALLOC_FREE(utf16);
+ return true;
+}
+
+static bool sddl_write_attr(struct sddl_write_context *ctx,
+ struct ace_condition_token *tok)
+{
+ char *name = NULL;
+ size_t name_len;
+ size_t i;
+ bool ok = sddl_encode_attr_name(ctx->mem_ctx,
+ tok->data.local_attr.value,
+ &name, &name_len);
+ if (!ok) {
+ return false;
+ }
+ for (i = 0; i < ARRAY_SIZE(sddl_attr_types); i++) {
+ struct sddl_attr_type x = sddl_attr_types[i];
+ if (x.code == tok->type) {
+ ok = sddl_write(ctx, "@");
+ if (! ok) {
+ return false;
+ }
+ ok = sddl_write(ctx, x.name);
+ if (! ok) {
+ return false;
+ }
+ break;
+ }
+ }
+
+ ok = sddl_write(ctx, name);
+ talloc_free(name);
+ return ok;
+}
+
+
+static bool sddl_write_unicode(struct sddl_write_context *ctx,
+ const struct ace_condition_token *tok)
+{
+ char *quoted = NULL;
+ bool ok;
+ /*
+ * We rely on tok->data.unicode.value being
+ * nul-terminated.
+ */
+ if (strchr(tok->data.unicode.value, '"') != NULL) {
+ /*
+ * There is a double quote in this string, but SDDL
+ * has no mechanism for escaping these (or anything
+ * else) in unicode strings.
+ *
+ * The only thing to do is fail.
+ *
+ * This cannot happen with an ACE created from SDDL,
+ * because the same no-escapes rule applies on the way
+ * in.
+ */
+ return false;
+ }
+
+ quoted = talloc_asprintf(ctx->mem_ctx, "\"%s\"",
+ tok->data.unicode.value);
+ if (quoted == NULL) {
+ return false;
+ }
+ ok = sddl_write(ctx, quoted);
+ TALLOC_FREE(quoted);
+ return ok;
+}
+
+static bool sddl_write_octet_string(struct sddl_write_context *ctx,
+ const struct ace_condition_token *tok)
+{
+ bool ok;
+ char *hex = hex_encode_talloc(ctx->mem_ctx,
+ tok->data.bytes.data,
+ tok->data.bytes.length);
+ ok = sddl_write(ctx, "#");
+ if (!ok) {
+ return false;
+ }
+ ok = sddl_write(ctx, hex);
+ talloc_free(hex);
+ return ok;
+}
+
+/*
+ * For octet strings, the Resource attribute ACE SDDL differs from conditional
+ * ACE SDDL, lacking the leading '#'.
+ */
+static bool sddl_write_ra_octet_string(struct sddl_write_context *ctx,
+ const struct ace_condition_token *tok)
+{
+ bool ok;
+ char *hex = hex_encode_talloc(ctx->mem_ctx,
+ tok->data.bytes.data,
+ tok->data.bytes.length);
+ ok = sddl_write(ctx, hex);
+ talloc_free(hex);
+ return ok;
+}
+
+
+static bool sddl_write_sid(struct sddl_write_context *ctx,
+ const struct ace_condition_token *tok)
+{
+ bool ok;
+ char *sddl = NULL;
+ char *sid = sddl_encode_sid(ctx->mem_ctx,
+ &tok->data.sid.sid,
+ NULL);
+ if (sid == NULL) {
+ return false;
+ }
+ sddl = talloc_asprintf(ctx->mem_ctx, "SID(%s)", sid);
+ if (sddl == NULL) {
+ talloc_free(sid);
+ return false;
+ }
+ ok = sddl_write(ctx, sddl);
+ talloc_free(sid);
+ talloc_free(sddl);
+ return ok;
+}
+
+static bool sddl_write_composite(struct sddl_write_context *ctx,
+ struct ace_condition_token *tok)
+{
+ /*
+ * Looks like {1, 2, 3, "four", {"woah, nesting", {6}}, SID(BA)}.
+ */
+ struct ace_condition_composite *c = &tok->data.composite;
+ uint32_t i;
+ bool ok;
+ ok = sddl_write(ctx, "{");
+ if (!ok) {
+ return false;
+ }
+ for (i = 0; i < c->n_members; i++) {
+ struct ace_condition_token *t = &c->tokens[i];
+ if (i > 0) {
+ ok = sddl_write(ctx, ", ");
+ if (!ok) {
+ return false;
+ }
+ }
+ switch (t->type) {
+ case CONDITIONAL_ACE_TOKEN_INT8:
+ case CONDITIONAL_ACE_TOKEN_INT16:
+ case CONDITIONAL_ACE_TOKEN_INT32:
+ case CONDITIONAL_ACE_TOKEN_INT64:
+ ok = sddl_write_int(ctx, t);
+ break;
+ case CONDITIONAL_ACE_TOKEN_UNICODE:
+ ok = sddl_write_unicode(ctx, t);
+ break;
+ case CONDITIONAL_ACE_TOKEN_OCTET_STRING:
+ ok = sddl_write_octet_string(ctx, t);
+ break;
+ case CONDITIONAL_ACE_TOKEN_SID:
+ ok = sddl_write_sid(ctx, t);
+ break;
+ case CONDITIONAL_ACE_TOKEN_COMPOSITE:
+ return false;
+ default:
+ return false;
+ }
+ if (!ok) {
+ return false;
+ }
+ }
+ ok = sddl_write(ctx, "}");
+ return ok;
+}
+
+static bool sddl_write_node(struct sddl_write_context *ctx,
+ struct sddl_node *node)
+{
+ struct ace_condition_token *tok = node->tok;
+ switch (tok->type) {
+ case CONDITIONAL_ACE_TOKEN_INT8:
+ case CONDITIONAL_ACE_TOKEN_INT16:
+ case CONDITIONAL_ACE_TOKEN_INT32:
+ case CONDITIONAL_ACE_TOKEN_INT64:
+ return sddl_write_int(ctx, tok);
+
+ case CONDITIONAL_ACE_TOKEN_MEMBER_OF:
+ case CONDITIONAL_ACE_TOKEN_DEVICE_MEMBER_OF:
+ case CONDITIONAL_ACE_TOKEN_MEMBER_OF_ANY:
+ case CONDITIONAL_ACE_TOKEN_DEVICE_MEMBER_OF_ANY:
+ case CONDITIONAL_ACE_TOKEN_NOT_MEMBER_OF:
+ case CONDITIONAL_ACE_TOKEN_NOT_DEVICE_MEMBER_OF:
+ case CONDITIONAL_ACE_TOKEN_NOT_MEMBER_OF_ANY:
+ case CONDITIONAL_ACE_TOKEN_NOT_DEVICE_MEMBER_OF_ANY:
+ case CONDITIONAL_ACE_TOKEN_EQUAL:
+ case CONDITIONAL_ACE_TOKEN_NOT_EQUAL:
+ case CONDITIONAL_ACE_TOKEN_LESS_THAN:
+ case CONDITIONAL_ACE_TOKEN_LESS_OR_EQUAL:
+ case CONDITIONAL_ACE_TOKEN_GREATER_THAN:
+ case CONDITIONAL_ACE_TOKEN_GREATER_OR_EQUAL:
+ case CONDITIONAL_ACE_TOKEN_CONTAINS:
+ case CONDITIONAL_ACE_TOKEN_ANY_OF:
+ case CONDITIONAL_ACE_TOKEN_NOT_CONTAINS:
+ case CONDITIONAL_ACE_TOKEN_NOT_ANY_OF:
+ case CONDITIONAL_ACE_TOKEN_AND:
+ case CONDITIONAL_ACE_TOKEN_OR:
+ case CONDITIONAL_ACE_TOKEN_EXISTS:
+ case CONDITIONAL_ACE_TOKEN_NOT_EXISTS:
+ case CONDITIONAL_ACE_TOKEN_NOT:
+ return sddl_write(ctx, sddl_strings[tok->type].name);
+
+ case CONDITIONAL_ACE_LOCAL_ATTRIBUTE:
+ case CONDITIONAL_ACE_USER_ATTRIBUTE:
+ case CONDITIONAL_ACE_RESOURCE_ATTRIBUTE:
+ case CONDITIONAL_ACE_DEVICE_ATTRIBUTE:
+ return sddl_write_attr(ctx, tok);
+
+ case CONDITIONAL_ACE_TOKEN_UNICODE:
+ return sddl_write_unicode(ctx, tok);
+
+ case CONDITIONAL_ACE_TOKEN_OCTET_STRING:
+ return sddl_write_octet_string(ctx, tok);
+
+ case CONDITIONAL_ACE_TOKEN_SID:
+ return sddl_write_sid(ctx, tok);
+
+ case CONDITIONAL_ACE_TOKEN_COMPOSITE:
+ return sddl_write_composite(ctx, tok);
+
+ case CONDITIONAL_ACE_TOKEN_INVALID_OR_PADDING:
+ /*
+ * This is only expected at the very end, which we
+ * can't (and don't need to) check here, but we can at
+ * least ensure it's the end of a sub-expression.
+ */
+ return (node->rhs == NULL);
+ default:
+ return false;
+ }
+ /* not expecting to get here */
+ return false;
+}
+
+
+static inline bool sddl_wants_outer_parens(struct sddl_node *node)
+{
+ /*
+ * Binary ops (having a LHS) are always parenthesised "(a == 2)"
+ *
+ * Member-of ops are too, for some reason.
+ */
+ return (node->lhs != NULL ||
+ node->tok->type == CONDITIONAL_ACE_TOKEN_MEMBER_OF ||
+ node->tok->type == CONDITIONAL_ACE_TOKEN_NOT_MEMBER_OF ||
+ node->tok->type == CONDITIONAL_ACE_TOKEN_MEMBER_OF_ANY ||
+ node->tok->type == CONDITIONAL_ACE_TOKEN_NOT_MEMBER_OF_ANY ||
+ node->tok->type == CONDITIONAL_ACE_TOKEN_DEVICE_MEMBER_OF ||
+ node->tok->type == CONDITIONAL_ACE_TOKEN_NOT_DEVICE_MEMBER_OF ||
+ node->tok->type == CONDITIONAL_ACE_TOKEN_DEVICE_MEMBER_OF_ANY ||
+ node->tok->type == CONDITIONAL_ACE_TOKEN_NOT_DEVICE_MEMBER_OF_ANY);
+}
+
+
+static inline bool sddl_wants_inner_parens(struct sddl_node *node,
+ struct sddl_node *child)
+{
+ /*
+ * logical operators are serialised with parentheses around their
+ * arguments (for NOT it is obligatory).
+ */
+ if (node->tok->type != CONDITIONAL_ACE_TOKEN_NOT &&
+ node->tok->type != CONDITIONAL_ACE_TOKEN_AND &&
+ node->tok->type != CONDITIONAL_ACE_TOKEN_OR) {
+ return false;
+ }
+ if (sddl_wants_outer_parens(child)) {
+ return false;
+ }
+ return true;
+}
+
+
+static void sddl_tree_resolve_parens(struct sddl_node *node)
+{
+ if (sddl_wants_outer_parens(node)) {
+ node->wants_parens = true;
+ }
+ if (node->lhs != NULL) {
+ bool p = sddl_wants_inner_parens(node, node->lhs);
+ node->lhs->wants_parens = p;
+ sddl_tree_resolve_parens(node->lhs);
+ }
+ if (node->rhs != NULL) {
+ bool p = sddl_wants_inner_parens(node, node->rhs);
+ node->rhs->wants_parens = p;
+ sddl_tree_resolve_parens(node->rhs);
+ }
+}
+
+static bool sddl_tree_to_sddl(struct sddl_write_context *ctx,
+ struct sddl_node *node)
+{
+ bool ok;
+ if (node->wants_parens) {
+ ok = sddl_write(ctx, "(");
+ if (! ok) {
+ return false;
+ }
+ }
+
+ if (node->lhs != NULL) {
+ ok = sddl_tree_to_sddl(ctx, node->lhs);
+ if (! ok) {
+ return false;
+ }
+ ok = sddl_write(ctx, " ");
+ if (!ok) {
+ return false;
+ }
+ }
+
+ ok = sddl_write_node(ctx, node);
+ if (!ok) {
+ return false;
+ }
+ if (node->rhs != NULL) {
+ /* NOT is a special case: "!(x)", not "! (x)" */
+ if (node->tok->type != CONDITIONAL_ACE_TOKEN_NOT) {
+ ok = sddl_write(ctx, " ");
+ if (!ok) {
+ return false;
+ }
+ }
+
+ ok = sddl_tree_to_sddl(ctx, node->rhs);
+ if (! ok) {
+ return false;
+ }
+ }
+ if (node->wants_parens) {
+ ok = sddl_write(ctx, ")");
+ if (!ok) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/*
+ * Convert conditional ACE conditions into SDDL conditions.
+ *
+ * @param mem_ctx
+ * @param program
+ * @return a string or NULL on error.
+ */
+char *sddl_from_conditional_ace(TALLOC_CTX *mem_ctx,
+ struct ace_condition_script *program)
+{
+ size_t i;
+ char *sddl = NULL;
+ struct sddl_node *nodes = NULL;
+ struct sddl_node **trees = NULL;
+ size_t n_trees = 0;
+ struct ace_condition_token *tok = NULL;
+ struct sddl_data s;
+ bool ok;
+ struct sddl_write_context ctx = {
+ .mem_ctx = mem_ctx
+ };
+
+ if (program->length == 0) {
+ /*
+ * The empty program is a special case.
+ */
+ return talloc_strdup(mem_ctx, "()");
+ }
+ nodes = talloc_zero_array(mem_ctx,
+ struct sddl_node,
+ program->length);
+ if (nodes == NULL) {
+ talloc_free(sddl);
+ return NULL;
+ }
+ trees = talloc_array(mem_ctx,
+ struct sddl_node*,
+ program->length);
+ if (trees == NULL) {
+ talloc_free(sddl);
+ talloc_free(nodes);
+ return NULL;
+ }
+
+ /*
+ * This loop constructs a tree, which we then traverse to get the
+ * SDDL. Consider this transformation:
+ *
+ * {A, B, ==, C, D, ==, &&} => "((A == B) && (C == D))"
+ *
+ * We keep an array of sub-trees, and add to it in sequence. When the
+ * thing we're adding takes arguments, we pop those off the tree list.
+ * So it would go through this sequence:
+ *
+ * len items
+ * 1: A
+ * 2: A, B
+ * 1: ==(A, B)
+ * 2: ==(A, B), C
+ * 3: ==(A, B), C, D
+ * 2: ==(A, B), ==(C, D)
+ * 1 &&(==(A, B), ==(C, D))
+ *
+ * Without building a tree it would be difficult to know how many
+ * parentheses to put before A.
+ *
+ * (A == B == C) should become
+ * {A B == C ==} which should be the same as
+ * ((A == B) == C)
+ */
+
+ for (i = 0; i < program->length; i++) {
+ tok = &program->tokens[i];
+ s = sddl_strings[tok->type];
+ nodes[i].tok = tok;
+ if (s.nargs > n_trees) {
+ goto error;
+ }
+ if (s.nargs >= 1) {
+ /*
+ * Read this note if you're trying to follow
+ * [MS-DTYP]. MS-DTYP uses 'LHS' to describe the
+ * operand of unary operators even though they are
+ * always displayed on the right of the operator. It
+ * makes everything much simpler to use rhs
+ * instead.
+ */
+ n_trees--;
+ nodes[i].rhs = trees[n_trees];
+
+ if (s.nargs == 2) {
+ n_trees--;
+ nodes[i].lhs = trees[n_trees];
+ }
+ }
+ trees[n_trees] = &nodes[i];
+ n_trees++;
+ }
+
+ if (n_trees != 1) {
+ goto error;
+ }
+
+ /*
+ * First we walk the tree to work out where to put parentheses (to
+ * match the canonical Windows representation).
+ *
+ * Doing it in the same traverse as the writing would be possible but
+ * trickier to get right.
+ */
+ sddl_tree_resolve_parens(trees[0]);
+ trees[0]->wants_parens = true;
+
+ /*
+ * Clamber over the tree, writing the string.
+ */
+ ok = sddl_tree_to_sddl(&ctx, trees[0]);
+
+ if (! ok) {
+ goto error;
+ }
+
+ talloc_free(trees);
+ talloc_free(nodes);
+ return ctx.sddl;
+
+ error:
+ talloc_free(sddl);
+ talloc_free(trees);
+ talloc_free(nodes);
+ return NULL;
+}
+
+
+
+static void comp_error(struct ace_condition_sddl_compiler_context *comp,
+ const char *fmt, ...) PRINTF_ATTRIBUTE(2,3);
+
+static void comp_error(struct ace_condition_sddl_compiler_context *comp,
+ const char *fmt, ...)
+{
+ char *msg = NULL;
+ va_list ap;
+ va_start(ap, fmt);
+ msg = talloc_vasprintf(comp->mem_ctx, fmt, ap);
+ va_end(ap);
+ if (msg == NULL) {
+ goto fail;
+ }
+
+ if (comp->message == NULL) {
+ /*
+ * Previously unset message; prepend the position.
+ *
+ * This is the common case.
+ */
+ comp->message_offset = comp->offset;
+ comp->message = msg;
+ return;
+ }
+ /*
+ * There's a message already so we'll try to append.
+ * This is unlikely to happen.
+ */
+ comp->message = talloc_asprintf(comp->mem_ctx,
+ "%s AND THEN %s",
+ comp->message,
+ msg);
+ TALLOC_FREE(msg);
+ if (comp->message == NULL) {
+ goto fail;
+ }
+ DBG_NOTICE("%s\n", comp->message);
+ return;
+fail:
+ comp->message = talloc_strdup(comp->mem_ctx,
+ "failed to set error message");
+ DBG_WARNING("%s\n", comp->message);
+}
+
+
+
+
+/*
+conditional-ace = "(" conditional-ace-type ";" [ace-flag-string] ";" ace-rights
+";" [object- guid] ";" [inherit-object-guid] ";" sid-string ";" "(" cond-expr
+")" ")"
+
+wspace = 1*(%x09-0D / %x20)
+
+literal-SID = "SID(" sid-string ")"
+
+term = [wspace] (memberof-op / exists-op / rel-op / contains-op / anyof-op /
+attr-name / rel- op2) [wspace]
+
+cond-expr = term / term [wspace] ("||" / "&&" ) [wspace] cond-expr / (["!"]
+[wspace] "(" cond-expr ")")
+
+memberof-op = ( "Member_of" / "Not_Member_of" / "Member_of_Any" /
+"Not_Member_of_Any" / "Device_Member_of" / "Device_Member_of_Any" /
+"Not_Device_Member_of" / "Not_Device_Member_of_Any" ) wspace sid-array
+
+exists-op = ( "Exists" / "Not_Exists") wspace attr-name
+
+rel-op = attr-name [wspace] ("<" / "<=" / ">" / ">=") [wspace] (attr-name2 /
+value) ; only scalars
+
+rel-op2 = attr-name [wspace] ("==" / "!=") [wspace] ( attr-name2 / value-array )
+; scalar or list
+
+contains-op = attr-name wspace ("Contains" / "Not_Contains") wspace (attr-name2
+/ value- array)
+
+anyof-op = attr-name wspace ("Any_of" / "Not_Any_of") wspace (attr-name2 /
+value-array)
+
+
+attr-name1 = attr-char1 *(attr-char1 / "@")
+
+attr-char1 = 1*(ALPHA / DIGIT / ":" / "." / "/" / "_")
+
+
+
+attr-name2 = ("@user." / "@device." / "@resource.") 1*attr-char2
+; new prefixed name form
+attr-char2 = attr-char1 / lit-char
+attr-name = attr-name1 / attr-name2
+ */
+
+
+
+static inline bool is_wspace(uint8_t c)
+{
+ /* wspace := %x09-0D | %x20 */
+ return (c == ' ' || c == '\x09' || c == '\x0A' ||
+ c == '\x0B' || c == '\x0C' || c == '\x0D');
+}
+
+static inline bool is_attr_char1(uint8_t c)
+{
+ /*
+ * attr-char1 = 1*(ALPHA / DIGIT / ":" / "." / "/" / "_")
+ * (ALPHA and DIGIT being ASCII only).
+ *
+ * These are used for local attributes, which we don't really
+ * expect to see in Samba AD.
+ *
+ * One example is "WIN://SYSAPPID", which is used in conditional ACEs
+ * that seem to relate to software installers; another is
+ * "APPID://PATH", used by Windows Applocker.
+ */
+ return (((c >= 'a') && (c <= 'z')) ||
+ ((c >= 'A') && (c <= 'Z')) ||
+ ((c >= '0') && (c <= '9')) ||
+ c == ':' || c == '.' || c == '/' || c == '_');
+}
+
+
+static ssize_t read_attr2_string(
+ struct ace_condition_sddl_compiler_context *comp,
+ struct ace_condition_unicode *dest)
+{
+ /*
+ * our SDDL is utf-8, but we need to convert to utf-16 and
+ * parse the escapes, then back to utf-8, because that's how
+ * the claims will appear.
+ *
+ * attr_char2 is used for attribute names that follow "@Class."
+ * specifiers. They can consume 5 characters to specify a single code
+ * unit, using "%1234" style escapes. Certain characters must be
+ * encoded this way, while others must be literal values. Because the
+ * %1234 refers to a utf-16 code unit, we really need to do the work
+ * in that codespace.
+ */
+ bool ok;
+ uint16_t *utf16 = NULL;
+ size_t utf16_byte_len;
+ size_t utf16_chars;
+ size_t utf8_len;
+ size_t src_len;
+ ssize_t i, j;
+ ssize_t max_len = comp->length - comp->offset;
+ const uint8_t *src = comp->sddl + comp->offset;
+
+ for (i = 0; i < max_len; i++) {
+ uint8_t c = src[i];
+ /*
+ * A double‐byte that must be escaped but isn't tells us that
+ * the attribute name has ended.
+ *
+ * The exception is '%', which must also be escaped
+ * (as "%0025"), but is obviously still expected in
+ * the escaped string.
+ */
+ if (strchr("!&()><=| \"", c) != NULL || is_wspace(c)) {
+ break;
+ }
+ }
+ if (i == max_len) {
+ /* too long, because we need at least one ')' */
+ comp_error(comp, "interminable attribute name");
+ return -1;
+ }
+ if (i == 0) {
+ /* too short! like "User.>= 4" */
+ comp_error(comp, "empty attribute name");
+ return -1;
+ }
+
+ if (unlikely(i > CONDITIONAL_ACE_MAX_LENGTH)) {
+ /*
+ * This is imprecise; the limit for the whole ACL is 64k.
+ * However there could be many escapes in the SDDL name which
+ * would reduce down to single utf16 code units in the
+ * compiled string.
+ */
+ comp_error(comp, "attribute is way too long (%zu)", i);
+ return -1;
+ }
+
+ src_len = i;
+
+ ok = convert_string_talloc(comp->mem_ctx,
+ CH_UTF8, CH_UTF16LE,
+ src, src_len,
+ &utf16, &utf16_byte_len);
+ if (!ok) {
+ comp_error(comp, "could not convert to utf-16");
+ return -1;
+ }
+ /*
+ * utf16_byte_len is in bytes, we want to count uint16s.
+ */
+ utf16_chars = utf16_byte_len / 2;
+
+ /* now the escapes. */
+ for (i = 0, j = 0;
+ j < utf16_chars && i < utf16_chars;
+ j++) {
+ uint16_t c = utf16[i];
+ if (c == '%') {
+ uint16_t v = 0;
+ size_t end = i + 5;
+ /*
+ * we need to read 4 hex characters.
+ * hex_byte() won't help because that is 8-bit.
+ */
+ if (end > utf16_chars) {
+ comp_error(comp,
+ "insufficient room for %% escape");
+ talloc_free(utf16);
+ return -1;
+ }
+ for (i++; i < end; i++) {
+ v <<= 4;
+ c = utf16[i];
+ if (c >= '0' && c <= '9') {
+ v += c - '0';
+ } else if (c >= 'A' && c <= 'F') {
+ v += c - 'A' + 10;
+ } else if (c >= 'a' && c <= 'f') {
+ v += c - 'a' + 10;
+ } else {
+ comp_error(comp, "invalid %% escape");
+ talloc_free(utf16);
+ return -1;
+ }
+ }
+ /*
+ * from MS-DTYP 2.5.1.1 Syntax (text, not ABNF), some
+ * characters must be literals, not escaped.
+ */
+ if ((v >= '0' && v <= '9') ||
+ (v >= 'A' && v <= 'Z') ||
+ (v >= 'a' && v <= 'z') ||
+ (v < 127 &&
+ strchr("#$'*+-;?@[\\]^_`{}~:/.", v) != NULL)) {
+ comp_error(comp, "invalid %% escape: "
+ "'%%%04x' should be literal '%c'",
+ v, v);
+ talloc_free(utf16);
+ return -1;
+ }
+ utf16[j] = v;
+ continue;
+ }
+ /*
+ * Note the characters "!&()><=|% \"" must be escaped per
+ * [MS-DTYP], but as we found the bounds of this string using
+ * those in utf-8 at the top of this function, we are not
+ * going to find them in the utf-16 now.
+ *
+ * Also, per [MS-DTYP], un-escaped whitespace is allowed, but
+ * effectively disallowed by Samba.
+ */
+ utf16[j] = utf16[i];
+ i++;
+ }
+
+ ok = convert_string_talloc(comp->mem_ctx,
+ CH_UTF16LE, CH_UTF8,
+ utf16, j * 2,
+ &dest->value, &utf8_len);
+ TALLOC_FREE(utf16);
+ if (!ok) {
+ comp_error(comp, "could not convert to utf-16");
+ return -1;
+ }
+
+ /* returning bytes consumed, not necessarily the length of token */
+ return src_len;
+}
+
+
+
+static bool eat_whitespace(struct ace_condition_sddl_compiler_context *comp,
+ bool trailing)
+{
+ /*
+ * Advance the offset to the first non-whitespace character.
+ *
+ * If trailing is false, there has to be something before the end of
+ * the string.
+ */
+ while (comp->offset < comp->length) {
+ if (! is_wspace(comp->sddl[comp->offset])) {
+ break;
+ }
+ comp->offset++;
+ }
+ if ((!trailing) && comp->offset == comp->length) {
+ comp_error(comp, "input ends unexpectedly");
+ return false;
+ }
+ return true;
+}
+
+static bool pop_sddl_token(struct ace_condition_sddl_compiler_context *comp,
+ struct ace_condition_token *token);
+
+static bool write_sddl_token(struct ace_condition_sddl_compiler_context *comp,
+ struct ace_condition_token token);
+
+static bool pop_write_sddl_token(
+ struct ace_condition_sddl_compiler_context *comp);
+
+
+static bool flush_stack_tokens(struct ace_condition_sddl_compiler_context *comp,
+ uint8_t type)
+{
+ bool ok;
+ uint8_t precedence = sddl_strings[type].op_precedence;
+ if (precedence == SDDL_PRECEDENCE_PAREN_START) {
+ /* paren has a special role */
+ return true;
+ }
+ /*
+ * Any operators on the top of the stack that have a "higher"
+ * precedence (tighter binding) to this one get popped off and written
+ * to the output. "higher" is in quotes because it means lower enum
+ * value.
+ *
+ * This works for binary operators, for example, with "(a == b == c)"
+ * (which is equivalent to "((a == b) == c)" via the left-to-right
+ * rule), we have:
+ * TOKEN dest PROGRAM STACK
+ * (
+ * a p
+ * == s a
+ * b p a ==
+ * == s a b ==
+ * flush stack
+ * s->p a b == ==
+ * c p a b ==
+ * ) a b == c ==
+ * flush stack
+ * a b == c ==
+ *
+ * but it is not right for unary operators, as in "(!(!(Exists
+ * a)))". As it turns out though, >= works for the unary
+ * operators and syntactic rules we have.
+ */
+ while (comp->stack_depth > 0) {
+ struct ace_condition_token *op =
+ &comp->stack[comp->stack_depth - 1];
+ if(sddl_strings[op->type].op_precedence > precedence) {
+ break;
+ }
+ if(sddl_strings[op->type].op_precedence == precedence &&
+ sddl_strings[op->type].flags & SDDL_FLAG_IS_UNARY_OP) {
+ break;
+ }
+
+ ok = pop_write_sddl_token(comp);
+ if (! ok) {
+ comp_error(comp,
+ "could not flush '%s' to program",
+ sddl_strings[op->type].name);
+ return false;
+ }
+ }
+ return true;
+}
+
+static bool push_sddl_token(struct ace_condition_sddl_compiler_context *comp,
+ struct ace_condition_token token)
+{
+ if (comp->stack_depth >= CONDITIONAL_ACE_MAX_TOKENS - 1) {
+ comp_error(comp, "excessive recursion");
+ return false;
+ }
+ if (sddl_strings[token.type].op_precedence == SDDL_NOT_AN_OP) {
+ comp_error(comp,
+ "wrong kind of token for the SDDL stack: %s",
+ sddl_strings[token.type].name);
+ return false;
+ }
+ /*
+ * Any operators on the top of the stack that have a "greater" or
+ * equal precedence to this one get popped off and written to the
+ * output.
+ */
+ flush_stack_tokens(comp, token.type);
+
+ token.data.op.sddl_position = comp->offset;
+
+ comp->stack[comp->stack_depth] = token;
+ comp->stack_depth++;
+ if (token.type != CONDITIONAL_ACE_SAMBA_SDDL_PAREN) {
+ comp->last_token_type = token.type;
+ }
+ return true;
+}
+
+static bool pop_sddl_token(struct ace_condition_sddl_compiler_context *comp,
+ struct ace_condition_token *token)
+{
+ if (comp->stack_depth == 0) {
+ comp_error(comp, "misbalanced expression");
+ return false;
+ }
+ comp->stack_depth--;
+ *token = comp->stack[comp->stack_depth];
+ return true;
+}
+
+
+static bool write_sddl_token(struct ace_condition_sddl_compiler_context *comp,
+ struct ace_condition_token token)
+{
+ /*
+ * This is adding a token to the program. Normally it will be to the
+ * main program list, but if we are constructing a composite list, then
+ * will be redirected there (via comp->target).
+ *
+ * We also conservatively track the overall size, so we don't waste
+ * time compiling something that is way too big.
+ */
+ DBG_INFO("writing %"PRIu32" %x %s\n",
+ *comp->target_len,
+ token.type,
+ sddl_strings[token.type].name);
+ comp->approx_size++;
+ if (comp->approx_size > CONDITIONAL_ACE_MAX_TOKENS) {
+ comp_error(comp, "program is too long "
+ "(over %d tokens)",
+ CONDITIONAL_ACE_MAX_TOKENS);
+ return false;
+ }
+ if (token.type != CONDITIONAL_ACE_SAMBA_SDDL_PAREN) {
+ comp->last_token_type = token.type;
+ }
+ comp->target[*comp->target_len] = token;
+ (*comp->target_len)++;
+ return true;
+}
+
+static bool pop_write_sddl_token(
+ struct ace_condition_sddl_compiler_context *comp)
+{
+ bool ok;
+ struct ace_condition_token token = {};
+ ok = pop_sddl_token(comp, &token);
+ if (!ok) {
+ comp_error(comp, "could not pop from op stack");
+ return false;
+ }
+ if (comp->target != comp->program->tokens) {
+ comp_error(comp, "compiler is seriously confused");
+ return false;
+ }
+
+ ok = write_sddl_token(comp, token);
+ if (!ok) {
+ comp_error(comp,
+ "could not write '%s' to program",
+ sddl_strings[token.type].name);
+ return false;
+ }
+ DBG_INFO(" written '%s'\n", sddl_strings[token.type].name);
+ return true;
+}
+
+
+
+static bool parse_expression(struct ace_condition_sddl_compiler_context *comp);
+static bool parse_composite(struct ace_condition_sddl_compiler_context *comp);
+
+
+
+
+static bool parse_oppy_op(struct ace_condition_sddl_compiler_context *comp)
+{
+ /*
+ * These ones look like operators and are operators.
+ */
+ bool ok;
+ struct ace_condition_token token = {};
+ uint8_t c, d;
+ uint32_t flag = SDDL_FLAG_EXPECTING_BINARY_OP;
+
+ if (comp->offset + 1 >= comp->length) {
+ comp_error(comp, "syntax error");
+ return false;
+ }
+
+ token.data.sddl_op.start = comp->offset;
+
+ /*
+ * These are all one or two characters long, and we always have room
+ * to peek ahead.
+ */
+ c = comp->sddl[comp->offset];
+ d = comp->sddl[comp->offset + 1];
+
+ if (c == '!') {
+ if (d == '=') {
+ comp->offset++;
+ token.type = CONDITIONAL_ACE_TOKEN_NOT_EQUAL;
+
+ } else {
+ token.type = CONDITIONAL_ACE_TOKEN_NOT;
+ flag = SDDL_FLAG_EXPECTING_UNARY_OP;
+ }
+ } else if (c == '=' && d == '=') {
+ comp->offset++;
+ token.type = CONDITIONAL_ACE_TOKEN_EQUAL;
+ } else if (c == '>') {
+ if (d == '=') {
+ comp->offset++;
+ token.type = CONDITIONAL_ACE_TOKEN_GREATER_OR_EQUAL;
+
+ } else {
+ token.type = CONDITIONAL_ACE_TOKEN_GREATER_THAN;
+ }
+ } else if (c == '<') {
+ if (d == '=') {
+ comp->offset++;
+ token.type = CONDITIONAL_ACE_TOKEN_LESS_OR_EQUAL;
+
+ } else {
+ token.type = CONDITIONAL_ACE_TOKEN_LESS_THAN;
+ }
+ } else if (c == '&' && d == '&') {
+ comp->offset++;
+ token.type = CONDITIONAL_ACE_TOKEN_AND;
+ flag = SDDL_FLAG_EXPECTING_BINARY_LOGIC_OP;
+ } else if (c == '|' && d == '|') {
+ comp->offset++;
+ token.type = CONDITIONAL_ACE_TOKEN_OR;
+ flag = SDDL_FLAG_EXPECTING_BINARY_LOGIC_OP;
+ } else {
+ comp_error(comp, "unknown operator");
+ return false;
+ }
+
+ if ((comp->state & flag) == 0) {
+ comp_error(comp, "unexpected operator");
+ return false;
+ }
+
+ comp->offset++;
+
+ ok = push_sddl_token(comp, token);
+ if (!ok) {
+ return false;
+ }
+
+ ok = eat_whitespace(comp, true);
+ return ok;
+}
+
+static bool parse_unicode(struct ace_condition_sddl_compiler_context *comp)
+{
+ /*
+ * This looks like "hello" (including the double quotes).
+ *
+ * Fortunately (for now), there is no mechanism for escaping
+ * double quotes in conditional ace strings, so we can simply
+ * look for the second quote without worrying about things
+ * like «\\\"».
+ */
+ struct ace_condition_token token = {};
+ char *s = NULL;
+ const uint8_t *src = NULL;
+ char *utf16 = NULL;
+ size_t len, max_len;
+ bool ok;
+ if (comp->sddl[comp->offset] != '"') {
+ comp_error(comp, "was expecting '\"' for Unicode string");
+ return false;
+ }
+ comp->offset++;
+ src = comp->sddl + comp->offset;
+ max_len = comp->length - comp->offset;
+ /* strnchr */
+ for (len = 0; len < max_len; len++) {
+ if (src[len] == '"') {
+ break;
+ }
+ }
+ if (len == max_len) {
+ comp_error(comp, "unterminated unicode string");
+ return false;
+ }
+
+ /*
+ * Look, this is wasteful, but it probably doesn't matter. We want to
+ * check that the string we're putting into the descriptor is valid,
+ * or we'll see errors down the track.
+ */
+ ok = convert_string_talloc(comp->mem_ctx,
+ CH_UTF8, CH_UTF16LE,
+ src, len,
+ &utf16, NULL);
+ if (!ok) {
+ comp_error(comp, "not valid unicode");
+ return false;
+ }
+ TALLOC_FREE(utf16);
+
+ s = talloc_array_size(comp->mem_ctx, 1, len + 1);
+ if (s == NULL) {
+ comp_error(comp, "allocation error");
+ return false;
+ }
+ memcpy(s, src, len);
+ s[len] = 0;
+ comp->offset += len + 1; /* +1 for the final quote */
+ token.type = CONDITIONAL_ACE_TOKEN_UNICODE;
+ token.data.unicode.value = s;
+
+ return write_sddl_token(comp, token);
+}
+
+
+static bool parse_octet_string(struct ace_condition_sddl_compiler_context *comp)
+{
+ /*
+ * This looks like '#hhhh...', where each 'hh' is hex for a byte, with
+ * the weird and annoying complication that '#' can be used to mean
+ * '0'.
+ */
+ struct ace_condition_token token = {};
+ size_t length, i;
+
+ if (comp->sddl[comp->offset] != '#') {
+ comp_error(comp, "was expecting '#' for octet string");
+ return false;
+ }
+ comp->offset++;
+ length = strspn((const char*)(comp->sddl + comp->offset),
+ "#0123456789abcdefABCDEF");
+
+ if (length & 1) {
+ comp_error(comp, "octet string has odd number of hex digits");
+ return false;
+ }
+
+ length /= 2;
+
+ token.data.bytes = data_blob_talloc_zero(comp->mem_ctx, length);
+ token.type = CONDITIONAL_ACE_TOKEN_OCTET_STRING;
+
+ for (i = 0; i < length; i++) {
+ /*
+ * Why not just strhex_to_str()?
+ *
+ * Because we need to treat '#' as '0' in octet string values,
+ * so all of the following are the same
+ * (equaling {0x10, 0x20, 0x30, 0x0}).
+ *
+ * #10203000
+ * #10203###
+ * #1#2#3###
+ * #10203#00
+ */
+ bool ok;
+ char pair[2];
+ size_t j = comp->offset + i * 2;
+ pair[0] = (comp->sddl[j] == '#') ? '0' : comp->sddl[j];
+ pair[1] = (comp->sddl[j + 1] == '#') ? '0' : comp->sddl[j + 1];
+
+ ok = hex_byte(pair, &token.data.bytes.data[i]);
+ if (!ok) {
+ talloc_free(token.data.bytes.data);
+ comp_error(comp, "inexplicable error in octet string");
+ return false;
+ }
+ }
+ comp->offset += length * 2;
+ return write_sddl_token(comp, token);
+}
+
+
+static bool parse_ra_octet_string(struct ace_condition_sddl_compiler_context *comp)
+{
+ /*
+ * Resource attribute octet strings resemble conditional ace octet
+ * strings, but have some important differences:
+ *
+ * 1. The '#' at the start is optional, and if present is
+ * counted as a zero.
+ *
+ * 2. An odd number of characters is implicitly left-padded with a zero.
+ *
+ * That is, "abc" means "0abc", "#12" means "0012", "f##"
+ * means "0f00", and "##" means 00.
+ */
+ struct ace_condition_token token = {};
+ size_t string_length, bytes_length, i, j;
+ bool ok;
+ char pair[2];
+
+ string_length = strspn((const char*)(comp->sddl + comp->offset),
+ "#0123456789abcdefABCDEF");
+
+ bytes_length = (string_length + 1) / 2;
+
+ if (bytes_length == 0) {
+ comp_error(comp, "zero length octet bytes");
+ return false;
+ }
+
+ token.data.bytes = data_blob_talloc_zero(comp->mem_ctx, bytes_length);
+ if (token.data.bytes.data == NULL) {
+ return false;
+ }
+ token.type = CONDITIONAL_ACE_TOKEN_OCTET_STRING;
+
+ j = comp->offset;
+ i = 0;
+ if (string_length & 1) {
+ /*
+ * An odd number of characters means the first
+ * character gains an implicit 0 for the high nybble.
+ */
+ pair[0] = 0;
+ pair[1] = (comp->sddl[0] == '#') ? '0' : comp->sddl[0];
+
+ ok = hex_byte(pair, &token.data.bytes.data[i]);
+ if (!ok) {
+ goto fail;
+ }
+ j++;
+ i++;
+ }
+
+ for (; i < bytes_length; i++) {
+ /*
+ * Why not just strhex_to_str() ?
+ *
+ * Because we need to treat '#' as '0' in octet string values.
+ */
+ if (comp->length - j < 2) {
+ goto fail;
+ }
+
+ pair[0] = (comp->sddl[j] == '#') ? '0' : comp->sddl[j];
+ pair[1] = (comp->sddl[j + 1] == '#') ? '0' : comp->sddl[j + 1];
+
+ ok = hex_byte(pair, &token.data.bytes.data[i]);
+ if (!ok) {
+ goto fail;
+ }
+ j += 2;
+ }
+ comp->offset = j;
+ return write_sddl_token(comp, token);
+
+fail:
+ comp_error(comp, "inexplicable error in octet string");
+ talloc_free(token.data.bytes.data);
+ return false;
+}
+
+
+static bool parse_sid(struct ace_condition_sddl_compiler_context *comp)
+{
+ struct dom_sid *sid = NULL;
+ const uint8_t *sidstr = NULL;
+ struct ace_condition_token token = {};
+ size_t end;
+ if (comp->length - comp->offset < 7) {
+ /* minimum: "SID(AA)" */
+ comp_error(comp, "no room for a complete SID");
+ return false;
+ }
+ /* conditional ACE SID string */
+ if (comp->sddl[comp->offset ] != 'S' ||
+ comp->sddl[comp->offset + 1] != 'I' ||
+ comp->sddl[comp->offset + 2] != 'D' ||
+ comp->sddl[comp->offset + 3] != '(') {
+ comp_error(comp, "malformed SID() constructor");
+ return false;
+ } else {
+ comp->offset += 4;
+ }
+
+ sidstr = comp->sddl + comp->offset;
+
+ sid = sddl_decode_sid(comp->mem_ctx,
+ (const char **)&sidstr,
+ comp->domain_sid);
+
+ if (sid == NULL) {
+ comp_error(comp, "could not parse SID");
+ return false;
+ }
+ end = sidstr - comp->sddl;
+ if (end >= comp->length || end < comp->offset) {
+ comp_error(comp, "apparent overflow in SID parsing");
+ return false;
+ }
+ comp->offset = end;
+ /*
+ * offset is now at the end of the SID, but we need to account
+ * for the ')'.
+ */
+ if (comp->sddl[comp->offset] != ')') {
+ comp_error(comp, "expected ')' to follow SID");
+ return false;
+ }
+ comp->offset++;
+
+ token.type = CONDITIONAL_ACE_TOKEN_SID;
+ token.data.sid.sid = *sid;
+ return write_sddl_token(comp, token);
+}
+
+
+
+static bool parse_ra_sid(struct ace_condition_sddl_compiler_context *comp)
+{
+ struct dom_sid *sid = NULL;
+ const uint8_t *sidstr = NULL;
+ struct ace_condition_token token = {};
+ size_t end;
+
+ if ((comp->state & SDDL_FLAG_EXPECTING_LITERAL) == 0) {
+ comp_error(comp, "did not expect a SID here");
+ return false;
+ }
+ /*
+ * Here we are parsing a resource attribute ACE which doesn't
+ * have the SID() wrapper around the SID string (unlike a
+ * conditional ACE).
+ *
+ * The resource ACE doesn't need this because there is no
+ * ambiguity with local attribute names, besides which the
+ * type has already been specified earlier in the ACE.
+ */
+ if (comp->length - comp->offset < 2){
+ comp_error(comp, "no room for a complete SID");
+ return false;
+ }
+
+ sidstr = comp->sddl + comp->offset;
+
+ sid = sddl_decode_sid(comp->mem_ctx,
+ (const char **)&sidstr,
+ comp->domain_sid);
+
+ if (sid == NULL) {
+ comp_error(comp, "could not parse SID");
+ return false;
+ }
+ end = sidstr - comp->sddl;
+ if (end >= comp->length || end < comp->offset) {
+ comp_error(comp, "apparent overflow in SID parsing");
+ return false;
+ }
+ comp->offset = end;
+ token.type = CONDITIONAL_ACE_TOKEN_SID;
+ token.data.sid.sid = *sid;
+ return write_sddl_token(comp, token);
+}
+
+
+static bool parse_int(struct ace_condition_sddl_compiler_context *comp)
+{
+ /*
+ * This one is relatively simple. strtoll() does the work.
+ */
+ long long v;
+ struct ace_condition_token token = {};
+ const char *start = (const char *)comp->sddl + comp->offset;
+ char *end = NULL;
+ const char *first_digit = start;
+ size_t len;
+ errno = 0;
+ v = strtoll(start, &end, 0);
+ if (errno != 0) {
+ comp_error(comp, "bad integer: %s", strerror(errno));
+ return false;
+ }
+ len = end - start;
+
+ if (len == 0) {
+ comp_error(comp, "unexpected non-integer");
+ return false;
+ }
+ if (comp->offset + len > comp->length) {
+ comp_error(comp, "impossible integer length: %zu!", len);
+ return false;
+ }
+
+ comp->offset += len;
+
+ /*
+ * Record the base and sign, which are used for recreating the SDDL.
+ *
+ * 'Sign' indicates whether there is a '+' or '-' sign. Base indicates
+ * whether the number was in hex, octal, or decimal. These make no
+ * difference to the evaluation of the ACE, just the display.
+ *
+ * This would not work reliably if eat_whitespace() is not called
+ * before parse_int(), but a) we know it is, and b) we don't *really*
+ * care if we lose these display hints.
+ */
+ if (*start == '-') {
+ token.data.int64.sign = CONDITIONAL_ACE_INT_SIGN_NEGATIVE;
+ first_digit++;
+ } else if (*start == '+') {
+ token.data.int64.sign = CONDITIONAL_ACE_INT_SIGN_POSITIVE;
+ first_digit++;
+ } else {
+ token.data.int64.sign = CONDITIONAL_ACE_INT_SIGN_NONE;
+ }
+ if (*first_digit == '0' && (end - first_digit) > 1) {
+ if ((end - first_digit > 2) &&
+ (first_digit[1] == 'x' ||
+ first_digit[1] == 'X')) {
+ token.data.int64.base = CONDITIONAL_ACE_INT_BASE_16;
+ } else {
+ token.data.int64.base = CONDITIONAL_ACE_INT_BASE_8;
+ }
+ } else {
+ token.data.int64.base = CONDITIONAL_ACE_INT_BASE_10;
+ }
+
+ token.data.int64.value = v;
+ token.type = CONDITIONAL_ACE_TOKEN_INT64;
+ return write_sddl_token(comp, token);
+}
+
+
+static bool parse_uint(struct ace_condition_sddl_compiler_context *comp)
+{
+ struct ace_condition_token *tok = NULL;
+ bool ok = parse_int(comp);
+ if (ok == false) {
+ return false;
+ }
+ /*
+ * check that the token's value is positive.
+ */
+ if (comp->target_len == 0) {
+ return false;
+ }
+ tok = &comp->target[*comp->target_len - 1];
+ if (tok->type != CONDITIONAL_ACE_TOKEN_INT64) {
+ return false;
+ }
+ if (tok->data.int64.value < 0) {
+ comp_error(comp, "invalid resource ACE value for unsigned TU claim");
+ return false;
+ }
+ return true;
+}
+
+
+static bool parse_bool(struct ace_condition_sddl_compiler_context *comp)
+{
+ struct ace_condition_token *tok = NULL;
+ bool ok = parse_int(comp);
+ if (ok == false || comp->target_len == 0) {
+ return false;
+ }
+ /*
+ * check that the token is 0 or 1.
+ */
+ tok = &comp->target[*comp->target_len - 1];
+ if (tok->type != CONDITIONAL_ACE_TOKEN_INT64) {
+ return false;
+ }
+ if (tok->data.int64.value != 0 && tok->data.int64.value != 1) {
+ comp_error(comp, "invalid resource ACE Boolean value");
+ return false;
+ }
+ return true;
+}
+
+
+static bool could_be_an_int(struct ace_condition_sddl_compiler_context *comp)
+{
+ const char *start = (const char*)(comp->sddl + comp->offset);
+ char* end = NULL;
+
+ if ((comp->state & SDDL_FLAG_EXPECTING_LITERAL) == 0) {
+ return false;
+ }
+
+ errno = 0;
+ /*
+ * See, we don't care about the strtoll return value, only
+ * whether it succeeds or not and what it finds at the end. If
+ * it succeeds, parse_int() will do it again for the value.
+ *
+ * Note that an out of range int will raise ERANGE (probably
+ * 34), so it will be read as a local attribute.
+ */
+ strtoll(start, &end, 0);
+ if (errno != 0 ||
+ end == start ||
+ end >= (const char*)comp->sddl + comp->length) {
+ return false;
+ }
+ /*
+ * We know *some* characters form an int, but if we run right
+ * into other attr1 characters (basically, letters), we won't
+ * count it as an int.
+ *
+ * For example, the "17" in "17p" is not an int. The "17" in
+ * "17||" is.
+ */
+ if (is_attr_char1(*end)) {
+ return false;
+ }
+ return true;
+}
+
+
+static bool parse_word(struct ace_condition_sddl_compiler_context *comp)
+{
+ /*
+ * Sometimes a bare word must be a local attribute, while in other
+ * cases it could also be a member-of or exists operator. Sometimes it
+ * could actually be a SID, which we discover when we've read as far
+ * as "SID(". Sometimes it might be a literal integer (attribute
+ * names can also consist entirely of digits).
+ *
+ * When it is an operator name, we have the complication that a match
+ * does not necessarily end the token. Consider "Member_of_Any" which
+ * contains the operator "Member_of". According to [MS-DTYP], a space
+ * is not necessary between the operator and the next token, but it
+ * does seem to be required for Windows 2022.
+ *
+ * Also, "Member_of" et. al. *could* be valid local attributes, which
+ * would make "(Member_of == 123)" a valid expression that we will
+ * fail to parse. This is not much of an issue for Samba AD where
+ * local attributes are not used.
+ *
+ * Operators are matched case-insensitively.
+ *
+ * There's another kind of attribute that starts with a '@', which we
+ * deal with in parse_attr2(). Those ones have full unicode glory;
+ * these ones are ASCII only.
+ */
+ size_t i, j, k;
+ bool ok;
+ uint8_t candidates[8];
+ size_t n_candidates = 0;
+ struct ace_condition_token token = {};
+ bool expecting_unary = comp->state & SDDL_FLAG_EXPECTING_UNARY_OP;
+ bool expecting_binary = comp->state & SDDL_FLAG_EXPECTING_BINARY_OP;
+ bool expecting_attr = comp->state & SDDL_FLAG_EXPECTING_LOCAL_ATTR;
+ bool expecting_literal = comp->state & SDDL_FLAG_EXPECTING_LITERAL;
+ const uint8_t *start = comp->sddl + comp->offset;
+ uint8_t c = start[0];
+ char *s = NULL;
+ if (! is_attr_char1(*start)) {
+ /* we shouldn't get here, because we peeked first */
+ return false;
+ }
+
+ /*
+ * We'll look for a SID first, because it simplifies the rest.
+ */
+ if (expecting_literal &&
+ comp->offset + 4 < comp->length &&
+ start[0] == 'S' &&
+ start[1] == 'I' &&
+ start[2] == 'D' &&
+ start[3] == '(') {
+ /* actually, we are parsing a SID. */
+ return parse_sid(comp);
+ }
+
+ if (expecting_binary || expecting_unary) {
+ /*
+ * Collect up the operators that can possibly be used
+ * here, including only those that start with the
+ * current letter and have the right arity/syntax.
+ *
+ * We don't expect more than 5 (for 'N', beginning the
+ * "Not_..." unary ops), and we'll winnow them down as
+ * we progress through the word.
+ */
+ int uc = toupper(c);
+ for (i = 0; i < 256; i++) {
+ const struct sddl_data *d = &sddl_strings[i];
+ if (sddl_strings[i].op_precedence != SDDL_NOT_AN_OP &&
+ uc == toupper((unsigned char)d->name[0])) {
+ if (d->flags & SDDL_FLAG_IS_UNARY_OP) {
+ if (!expecting_unary) {
+ continue;
+ }
+ } else if (!expecting_binary) {
+ continue;
+ }
+ candidates[n_candidates] = i;
+ n_candidates++;
+ if (n_candidates == ARRAY_SIZE(candidates)) {
+ /* impossible, really. */
+ return false;
+ }
+ }
+ }
+ } else if (could_be_an_int(comp)) {
+ /*
+ * if looks like an integer, and we expect an integer, it is
+ * an integer. If we don't expect an integer, it is a local
+ * attribute with a STUPID NAME. Or an error.
+ */
+ return parse_int(comp);
+ } else if (! expecting_attr) {
+ comp_error(comp, "did not expect this word here");
+ return false;
+ }
+
+ i = 1;
+ while (comp->offset + i < comp->length) {
+ c = start[i];
+ if (! is_attr_char1(c)) {
+ break;
+ }
+ if (n_candidates != 0) {
+ /*
+ * Filter out candidate operators that no longer
+ * match.
+ */
+ int uc = toupper(c);
+ k = 0;
+ for (j = 0; j < n_candidates; j++) {
+ size_t o = candidates[j];
+ uint8_t c2 = sddl_strings[o].name[i];
+ if (uc == toupper(c2)) {
+ candidates[k] = candidates[j];
+ k++;
+ }
+ }
+ n_candidates = k;
+ }
+ i++;
+ }
+
+ /*
+ * We have finished and there is a complete word. If it could be an
+ * operator we'll assume it is one.
+ *
+ * A complication is we could have matched more than one operator, for
+ * example "Member_of" and "Member_of_Any", so we have to look through
+ * the list of candidates for the one that ends.
+ */
+ if (n_candidates != 0) {
+ for (j = 0; j < n_candidates; j++) {
+ size_t o = candidates[j];
+ if (sddl_strings[o].name[i] == '\0') {
+ /* it is this one */
+
+ if (!comp->allow_device &&
+ (sddl_strings[o].flags & SDDL_FLAG_DEVICE))
+ {
+ comp_error(
+ comp,
+ "a device‐relative expression "
+ "will never evaluate to true "
+ "in this context (did you "
+ "intend a user‐relative "
+ "expression?)");
+ return false;
+ }
+
+ token.type = o;
+ token.data.sddl_op.start = comp->offset;
+ comp->offset += i;
+ ok = push_sddl_token(comp, token);
+ return ok;
+ }
+ }
+ }
+ /*
+ * if looks like an integer, and we expect an integer, it is
+ * an integer. If we don't expect an integer, it is a local
+ * attribute with a STUPID NAME.
+ */
+ if (could_be_an_int(comp)) {
+ return parse_int(comp);
+ }
+
+ if (! expecting_attr) {
+ comp_error(comp, "word makes no sense here");
+ return false;
+ }
+ /* it's definitely an attribute name */
+ token.type = CONDITIONAL_ACE_LOCAL_ATTRIBUTE;
+ if (comp->offset + i >= comp->length) {
+ comp_error(comp, "missing trailing ')'?");
+ return false;
+ }
+
+ s = talloc_memdup(comp->mem_ctx, start, i + 1);
+ if (s == NULL) {
+ comp_error(comp, "allocation error");
+ return false;
+ }
+ s[i] = 0;
+ token.data.local_attr.value = s;
+ comp->offset += i;
+ return write_sddl_token(comp, token);
+}
+
+static bool parse_attr2(struct ace_condition_sddl_compiler_context *comp)
+{
+ /*
+ * Attributes in the form @class.attr
+ *
+ * class can be "User", "Device", or "Resource", case insensitive.
+ */
+ size_t i;
+ bool ok;
+ size_t len;
+ struct ace_condition_token token = {};
+
+ if ((comp->state & SDDL_FLAG_EXPECTING_NON_LOCAL_ATTR) == 0) {
+ comp_error(comp, "did not expect @attr here");
+ return false;
+ }
+ if (comp->sddl[comp->offset] != '@') {
+ comp_error(comp, "Expected '@'");
+ return false;
+ }
+ comp->offset++;
+
+ for (i = 0; i < ARRAY_SIZE(sddl_attr_types); i++) {
+ int ret;
+ size_t attr_len = strlen(sddl_attr_types[i].name);
+ if (attr_len >= comp->length - comp->offset) {
+ continue;
+ }
+ ret = strncasecmp(sddl_attr_types[i].name,
+ (const char *) (comp->sddl + comp->offset),
+ attr_len);
+ if (ret == 0) {
+ const uint8_t code = sddl_attr_types[i].code;
+
+ if (!comp->allow_device &&
+ (sddl_strings[code].flags & SDDL_FLAG_DEVICE))
+ {
+ comp_error(comp,
+ "a device attribute is not "
+ "applicable in this context (did "
+ "you intend a user attribute?)");
+ return false;
+ }
+
+ token.type = code;
+ comp->offset += attr_len;
+ break;
+ }
+ }
+ if (i == ARRAY_SIZE(sddl_attr_types)) {
+ comp_error(comp, "unknown attribute class");
+ return false;
+ }
+
+ /*
+ * Now we are past the class and the '.', and into the
+ * attribute name. The attribute name can be almost
+ * anything, but some characters need to be escaped.
+ */
+
+ len = read_attr2_string(comp, &token.data.unicode);
+ if (len == -1) {
+ /* read_attr2_string has set a message */
+ return false;
+ }
+ ok = write_sddl_token(comp, token);
+ if (! ok) {
+ return false;
+ }
+ comp->offset += len;
+ ok = eat_whitespace(comp, false);
+ return ok;
+}
+
+static bool parse_literal(struct ace_condition_sddl_compiler_context *comp,
+ bool in_composite)
+{
+ uint8_t c = comp->sddl[comp->offset];
+ if (!(comp->state & SDDL_FLAG_EXPECTING_LITERAL)) {
+ comp_error(comp, "did not expect to be parsing a literal now");
+ return false;
+ }
+ switch(c) {
+ case '#':
+ return parse_octet_string(comp);
+ case '"':
+ return parse_unicode(comp);
+ case 'S':
+ return parse_sid(comp);
+ case '{':
+ if (in_composite) {
+ /* nested composites are not supported */
+ return false;
+ } else {
+ return parse_composite(comp);
+ }
+ default:
+ if (strchr("1234567890-+", c) != NULL) {
+ return parse_int(comp);
+ }
+ }
+ if (c > 31 && c < 127) {
+ comp_error(comp,
+ "unexpected byte 0x%02x '%c' parsing literal", c, c);
+ } else {
+ comp_error(comp, "unexpected byte 0x%02x parsing literal", c);
+ }
+ return false;
+}
+
+
+static bool parse_composite(struct ace_condition_sddl_compiler_context *comp)
+{
+ /*
+ * This jumps into a different parser, expecting a comma separated
+ * list of literal values, which might include nested literal
+ * composites.
+ *
+ * To handle the nesting, we redirect the pointers that determine
+ * where write_sddl_token() writes.
+ */
+ bool ok;
+ bool first = true;
+ struct ace_condition_token token = {
+ .type = CONDITIONAL_ACE_TOKEN_COMPOSITE
+ };
+ uint32_t start = comp->offset;
+ size_t alloc_size;
+ struct ace_condition_token *old_target = comp->target;
+ uint32_t *old_target_len = comp->target_len;
+
+ if (comp->sddl[start] != '{') {
+ comp_error(comp, "expected '{' for composite list");
+ return false;
+ }
+ if (!(comp->state & SDDL_FLAG_EXPECTING_LITERAL)) {
+ comp_error(comp, "did not expect '{' for composite list");
+ return false;
+ }
+ comp->offset++; /* past '{' */
+
+ /*
+ * the worst case is one token for every two bytes: {1,1,1}, and we
+ * allocate for that (counting commas and finding '}' gets hard because
+ * string literals).
+ */
+ alloc_size = MIN((comp->length - start) / 2 + 1,
+ CONDITIONAL_ACE_MAX_LENGTH);
+
+ token.data.composite.tokens = talloc_array(
+ comp->mem_ctx,
+ struct ace_condition_token,
+ alloc_size);
+ if (token.data.composite.tokens == NULL) {
+ comp_error(comp, "allocation failure");
+ return false;
+ }
+
+ comp->target = token.data.composite.tokens;
+ comp->target_len = &token.data.composite.n_members;
+
+ /*
+ * in this loop we are looking for:
+ *
+ * a) possible whitespace.
+ * b) a comma (or terminating '}')
+ * c) more possible whitespace
+ * d) a literal
+ *
+ * Failures use a goto to reset comp->target, just in case we ever try
+ * continuing after error.
+ */
+ while (comp->offset < comp->length) {
+ uint8_t c;
+ ok = eat_whitespace(comp, false);
+ if (! ok) {
+ goto fail;
+ }
+ c = comp->sddl[comp->offset];
+ if (c == '}') {
+ comp->offset++;
+ break;
+ }
+ if (!first) {
+ if (c != ',') {
+ comp_error(comp,
+ "malformed composite (expected comma)");
+ goto fail;
+ }
+ comp->offset++;
+
+ ok = eat_whitespace(comp, false);
+ if (! ok) {
+ goto fail;
+ }
+ }
+ first = false;
+ if (*comp->target_len >= alloc_size) {
+ comp_error(comp,
+ "Too many tokens in composite "
+ "(>= %"PRIu32" tokens)",
+ *comp->target_len);
+ goto fail;
+ }
+ ok = parse_literal(comp, true);
+ if (!ok) {
+ goto fail;
+ }
+ }
+ comp->target = old_target;
+ comp->target_len = old_target_len;
+ write_sddl_token(comp, token);
+ return true;
+fail:
+ talloc_free(token.data.composite.tokens);
+ comp->target = old_target;
+ comp->target_len = old_target_len;
+ return false;
+}
+
+
+static bool parse_paren_literal(struct ace_condition_sddl_compiler_context *comp)
+{
+ bool ok;
+ if (comp->sddl[comp->offset] != '(') {
+ comp_error(comp, "expected '('");
+ return false;
+ }
+ comp->offset++;
+ ok = parse_literal(comp, false);
+ if (!ok) {
+ return false;
+ }
+ if (comp->sddl[comp->offset] != ')') {
+ comp_error(comp, "expected ')'");
+ return false;
+ }
+ comp->offset++;
+ return true;
+}
+
+static bool parse_expression(struct ace_condition_sddl_compiler_context *comp)
+{
+ /*
+ * This expects a parenthesised expression.
+ */
+ bool ok;
+ struct ace_condition_token token = {};
+ uint32_t start = comp->offset;
+
+ if (comp->state & SDDL_FLAG_EXPECTING_PAREN_LITERAL) {
+ /*
+ * Syntactically we allow parentheses to wrap a
+ * literal value after a Member_of or >= op, but we
+ * want to remember that it just wants a single
+ * literal, not a general expression.
+ */
+ return parse_paren_literal(comp);
+ }
+
+ if (comp->sddl[start] != '(') {
+ comp_error(comp, "expected '('");
+ return false;
+ }
+
+ if (!(comp->state & SDDL_FLAG_EXPECTING_PAREN)) {
+ comp_error(comp, "did not expect '('");
+ return false;
+ }
+
+ token.type = CONDITIONAL_ACE_SAMBA_SDDL_PAREN;
+ token.data.sddl_op.start = start;
+ ok = push_sddl_token(comp, token);
+ if (!ok) {
+ return false;
+ }
+ comp->offset++; /* over the '(' */
+ comp->state = SDDL_FLAGS_EXPR_START;
+ DBG_INFO("%3"PRIu32": (\n", comp->offset);
+
+ comp->state |= SDDL_FLAG_NOT_EXPECTING_END_PAREN;
+
+ while (comp->offset < comp->length) {
+ uint8_t c;
+ ok = eat_whitespace(comp, false);
+ if (! ok) {
+ return false;
+ }
+ c = comp->sddl[comp->offset];
+ if (c == '(') {
+ ok = parse_expression(comp);
+ } else if (c == ')') {
+ if (comp->state & (SDDL_FLAG_IS_BINARY_OP |
+ SDDL_FLAG_IS_UNARY_OP)) {
+ /*
+ * You can't have "(a ==)" or "(!)"
+ */
+ comp_error(comp,
+ "operator lacks right hand argument");
+ return false;
+ }
+ if (comp->state & SDDL_FLAG_NOT_EXPECTING_END_PAREN) {
+ /*
+ * You can't have "( )"
+ */
+ comp_error(comp, "empty expression");
+ return false;
+ }
+ break;
+ } else if (c == '@') {
+ ok = parse_attr2(comp);
+ } else if (strchr("!<>=&|", c)) {
+ ok = parse_oppy_op(comp);
+ } else if (is_attr_char1(c)) {
+ ok = parse_word(comp);
+ } else if (comp->state & SDDL_FLAG_EXPECTING_LITERAL) {
+ ok = parse_literal(comp, false);
+ } else {
+ if (c > 31 && c < 127) {
+ comp_error(comp,
+ "unexpected byte 0x%02x '%c'", c, c);
+ } else {
+ comp_error(comp, "unexpected byte 0x%02x", c);
+ }
+ ok = false;
+ }
+
+ if (! ok) {
+ return false;
+ }
+ /*
+ * what did we just find? Set what we expect accordingly.
+ */
+ comp->state = sddl_strings[comp->last_token_type].flags;
+ DBG_INFO("%3"PRIu32": %s\n",
+ comp->offset,
+ sddl_strings[comp->last_token_type].name);
+ }
+ ok = eat_whitespace(comp, false);
+ if (!ok) {
+ return false;
+ }
+
+ if (comp->sddl[comp->offset] != ')') {
+ comp_error(comp, "expected ')' to match '(' at %"PRIu32, start);
+ return false;
+ }
+ /*
+ * we won't comp->offset++ until after these other error checks, so
+ * that their messages have consistent locations.
+ */
+ ok = flush_stack_tokens(comp, CONDITIONAL_ACE_SAMBA_SDDL_PAREN_END);
+ if (!ok) {
+ return false;
+ }
+ if (comp->stack_depth == 0) {
+ comp_error(comp, "mysterious nesting error between %"
+ PRIu32" and here",
+ start);
+ return false;
+ }
+ token = comp->stack[comp->stack_depth - 1];
+ if (token.type != CONDITIONAL_ACE_SAMBA_SDDL_PAREN) {
+ comp_error(comp, "nesting error between %"PRIu32" and here",
+ start);
+ return false;
+ }
+ if (token.data.sddl_op.start != start) {
+ comp_error(comp, "')' should match '(' at %"PRIu32
+ ", not %"PRIu32,
+ token.data.sddl_op.start, start);
+ return false;
+ }
+ comp->stack_depth--;
+ DBG_INFO("%3"PRIu32": )\n", comp->offset);
+
+ comp->offset++; /* for the ')' */
+ comp->last_token_type = CONDITIONAL_ACE_SAMBA_SDDL_PAREN_END;
+ comp->state = sddl_strings[comp->last_token_type].flags;
+
+ ok = eat_whitespace(comp, true);
+ return ok;
+}
+
+
+
+static bool init_compiler_context(
+ TALLOC_CTX *mem_ctx,
+ struct ace_condition_sddl_compiler_context *comp,
+ const enum ace_condition_flags ace_condition_flags,
+ const char *sddl,
+ size_t max_length,
+ size_t max_stack)
+{
+ struct ace_condition_script *program = NULL;
+
+ comp->sddl = (const uint8_t*)sddl;
+ comp->mem_ctx = mem_ctx;
+
+ program = talloc_zero(mem_ctx, struct ace_condition_script);
+ if (program == NULL) {
+ return false;
+ }
+ /*
+ * For the moment, we allocate for the worst case up front.
+ */
+ program->tokens = talloc_array(program,
+ struct ace_condition_token,
+ max_length);
+ if (program->tokens == NULL) {
+ TALLOC_FREE(program);
+ return false;
+ }
+ program->stack = talloc_array(program,
+ struct ace_condition_token,
+ max_stack + 1);
+ if (program->stack == NULL) {
+ TALLOC_FREE(program);
+ return false;
+ }
+ comp->program = program;
+ /* we can borrow the program stack for the operator stack */
+ comp->stack = program->stack;
+ comp->target = program->tokens;
+ comp->target_len = &program->length;
+ comp->length = strlen(sddl);
+ comp->state = SDDL_FLAG_EXPECTING_PAREN;
+ comp->allow_device = ace_condition_flags & ACE_CONDITION_FLAG_ALLOW_DEVICE;
+ return true;
+}
+
+/*
+ * Compile SDDL conditional ACE conditions.
+ *
+ * @param mem_ctx
+ * @param sddl - the string to be parsed
+ * @param ace_condition_flags - flags controlling compiler behaviour
+ * @param message - on error, a pointer to a compiler message
+ * @param message_offset - where the error occurred
+ * @param consumed_length - how much of the SDDL was used
+ * @return a struct ace_condition_script (or NULL).
+ */
+struct ace_condition_script * ace_conditions_compile_sddl(
+ TALLOC_CTX *mem_ctx,
+ const enum ace_condition_flags ace_condition_flags,
+ const char *sddl,
+ const char **message,
+ size_t *message_offset,
+ size_t *consumed_length)
+{
+ bool ok;
+ struct ace_condition_sddl_compiler_context comp = {};
+
+ *message = NULL;
+ *message_offset = 0;
+
+ ok = init_compiler_context(mem_ctx,
+ &comp,
+ ace_condition_flags,
+ sddl,
+ CONDITIONAL_ACE_MAX_LENGTH,
+ CONDITIONAL_ACE_MAX_TOKENS);
+ if (!ok) {
+ return NULL;
+ }
+
+ ok = parse_expression(&comp);
+ if (!ok) {
+ goto error;
+ }
+ if (comp.stack_depth != 0) {
+ comp_error(&comp, "incomplete expression");
+ goto error;
+ }
+ if (consumed_length != NULL) {
+ *consumed_length = comp.offset;
+ }
+ *message = comp.message;
+ *message_offset = comp.message_offset;
+ return comp.program;
+ error:
+ *message = comp.message;
+ *message_offset = comp.message_offset;
+ TALLOC_FREE(comp.program);
+ return NULL;
+}
+
+
+
+static bool parse_resource_attr_list(
+ struct ace_condition_sddl_compiler_context *comp,
+ char attr_type_char)
+{
+ /*
+ * This is a bit like parse_composite() above, but with the following
+ * differences:
+ *
+ * - it doesn't want '{...}' around the list.
+ * - if there is just one value, it is not a composite
+ * - all the values must be the expected type.
+ * - there is no nesting.
+ * - SIDs are not written with SID(...) around them.
+ */
+ bool ok;
+ bool first = true;
+ struct ace_condition_token composite = {
+ .type = CONDITIONAL_ACE_TOKEN_COMPOSITE
+ };
+ uint32_t start = comp->offset;
+ size_t alloc_size;
+ struct ace_condition_token *old_target = comp->target;
+ uint32_t *old_target_len = comp->target_len;
+
+ comp->state = SDDL_FLAG_EXPECTING_LITERAL;
+
+ /*
+ * the worst case is one token for every two bytes: {1,1,1}, and we
+ * allocate for that (counting commas and finding '}' gets hard because
+ * string literals).
+ */
+ alloc_size = MIN((comp->length - start) / 2 + 1,
+ CONDITIONAL_ACE_MAX_LENGTH);
+
+ composite.data.composite.tokens = talloc_array(
+ comp->mem_ctx,
+ struct ace_condition_token,
+ alloc_size);
+ if (composite.data.composite.tokens == NULL) {
+ comp_error(comp, "allocation failure");
+ return false;
+ }
+
+ comp->target = composite.data.composite.tokens;
+ comp->target_len = &composite.data.composite.n_members;
+
+ /*
+ * in this loop we are looking for:
+ *
+ * a) possible whitespace.
+ * b) a comma (or terminating ')')
+ * c) more possible whitespace
+ * d) a literal, of the right type (checked after)
+ *
+ * Failures use a goto to reset comp->target, just in case we ever try
+ * continuing after error.
+ */
+ while (comp->offset < comp->length) {
+ uint8_t c;
+ ok = eat_whitespace(comp, false);
+ if (! ok) {
+ goto fail;
+ }
+ c = comp->sddl[comp->offset];
+ if (c == ')') {
+ break;
+ }
+ if (!first) {
+ if (c != ',') {
+ comp_error(comp,
+ "malformed resource attribute ACE "
+ "(expected comma)");
+ goto fail;
+ }
+ comp->offset++;
+
+ ok = eat_whitespace(comp, false);
+ if (! ok) {
+ goto fail;
+ }
+ }
+ first = false;
+ if (*comp->target_len >= alloc_size) {
+ comp_error(comp,
+ "Too many tokens in resource attribute ACE "
+ "(>= %"PRIu32" tokens)",
+ *comp->target_len);
+ goto fail;
+ }
+ switch(attr_type_char) {
+ case 'X':
+ ok = parse_ra_octet_string(comp);
+ break;
+ case 'S':
+ ok = parse_unicode(comp);
+ break;
+ case 'U':
+ ok = parse_uint(comp);
+ break;
+ case 'B':
+ ok = parse_bool(comp);
+ break;
+ case 'I':
+ ok = parse_int(comp);
+ break;
+ case 'D':
+ ok = parse_ra_sid(comp);
+ break;
+ default:
+ /* it's a mystery we got this far */
+ comp_error(comp,
+ "unknown attribute type T%c",
+ attr_type_char);
+ goto fail;
+ }
+ if (!ok) {
+ goto fail;
+ }
+
+ if (*comp->target_len == 0) {
+ goto fail;
+ }
+ }
+ comp->target = old_target;
+ comp->target_len = old_target_len;
+
+ /*
+ * If we only ended up collecting one token into the composite, we
+ * write that instead.
+ */
+ if (composite.data.composite.n_members == 1) {
+ ok = write_sddl_token(comp, composite.data.composite.tokens[0]);
+ talloc_free(composite.data.composite.tokens);
+ } else {
+ ok = write_sddl_token(comp, composite);
+ }
+ if (! ok) {
+ goto fail;
+ }
+
+ return true;
+fail:
+ comp->target = old_target;
+ comp->target_len = old_target_len;
+ TALLOC_FREE(composite.data.composite.tokens);
+ return false;
+}
+
+
+
+struct CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1 *sddl_decode_resource_attr (
+ TALLOC_CTX *mem_ctx,
+ const char *str,
+ size_t *length)
+{
+ /*
+ * Resource attribute ACEs define claims in object SACLs. They look like
+ *
+ * "(RA; «flags» ;;;;WD;( «attribute-data» ))"
+ *
+ * attribute-data = DQUOTE 1*attr-char2 DQUOTE "," \
+ * ( TI-attr / TU-attr / TS-attr / TD-attr / TX-attr / TB-attr )
+ * TI-attr = "TI" "," attr-flags *("," int-64)
+ * TU-attr = "TU" "," attr-flags *("," uint-64)
+ * TS-attr = "TS" "," attr-flags *("," char-string)
+ * TD-attr = "TD" "," attr-flags *("," sid-string)
+ * TX-attr = "TX" "," attr-flags *("," octet-string)
+ * TB-attr = "TB" "," attr-flags *("," ( "0" / "1" ) )
+ *
+ * and the data types are *mostly* parsed in the SDDL way,
+ * though there are significant differences for octet-strings.
+ *
+ * At this point we only have the "(«attribute-data»)".
+ *
+ * What we do is set up a conditional ACE compiler to be expecting a
+ * literal, and ask it to parse the strings between the commas. It's a
+ * hack.
+ */
+ bool ok;
+ struct CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1 *claim = NULL;
+ struct ace_condition_sddl_compiler_context comp = {};
+ char attr_type;
+ struct ace_condition_token *tok;
+ uint32_t flags;
+ size_t len;
+ struct ace_condition_unicode attr_name = {};
+
+ ok = init_compiler_context(mem_ctx,
+ &comp,
+ ACE_CONDITION_FLAG_ALLOW_DEVICE,
+ str,
+ 3,
+ 3);
+ if (!ok) {
+ return NULL;
+ }
+ if (comp.length < 6 || comp.length > CONDITIONAL_ACE_MAX_LENGTH) {
+ DBG_WARNING("invalid resource attribute: '%s'\n", str);
+ goto error;
+ }
+ /*
+ * Resource attribute ACEs list SIDs in a bare form "S-1-2-3", while
+ * conditional ACEs use a wrapper syntax "SID(S-1-2-3)". As almost
+ * everything is the same, we are reusing the conditional ACE parser,
+ * with a flag set to tell the SID parser which form to expect.
+ */
+
+ /* Most examples on the web have leading whitespace */
+ ok = eat_whitespace(&comp, false);
+ if (!ok) {
+ return NULL;
+ }
+ if (comp.sddl[comp.offset] != '(' ||
+ comp.sddl[comp.offset + 1] != '"') {
+ DBG_WARNING("invalid resource attribute -- expected '(\"'\n");
+ goto error;
+ }
+ comp.offset += 2;
+
+ /*
+ * Read the name. Here we are not reading a token into comp->program,
+ * just into a unicode blob.
+ */
+ len = read_attr2_string(&comp, &attr_name);
+
+ if (len == -1) {
+ DBG_WARNING("invalid resource attr name: %s\n", str);
+ goto error;
+ }
+ comp.offset += len;
+
+ ok = eat_whitespace(&comp, false);
+ if (comp.offset + 6 > comp.length) {
+ DBG_WARNING("invalid resource attribute (too short): '%s'\n",
+ str);
+ goto error;
+ }
+ /*
+ * now we have the name. Next comes '",«T[IUSDXB]»,' followed
+ * by the flags, which are a 32 bit number.
+ */
+ if (comp.sddl[comp.offset] != '"' ||
+ comp.sddl[comp.offset + 1] != ','||
+ comp.sddl[comp.offset + 2] != 'T') {
+ DBG_WARNING("expected '\",T[IUSDXB]' after attr name\n");
+ goto error;
+ }
+ attr_type = comp.sddl[comp.offset + 3];
+
+ if (comp.sddl[comp.offset + 4] != ',') {
+ DBG_WARNING("expected ',' after attr type\n");
+ goto error;
+ }
+ comp.offset += 5;
+ comp.state = SDDL_FLAG_EXPECTING_LITERAL;
+ ok = parse_literal(&comp, false);
+ if (!ok ||
+ comp.program->length != 1) {
+ DBG_WARNING("invalid attr flags: %s\n", str);
+ goto error;
+ }
+
+ tok = &comp.program->tokens[0];
+ if (tok->type != CONDITIONAL_ACE_TOKEN_INT64 ||
+ tok->data.int64.value < 0 ||
+ tok->data.int64.value > UINT32_MAX) {
+ DBG_WARNING("invalid attr flags (want 32 bit int): %s\n", str);
+ goto error;
+ }
+ flags = tok->data.int64.value;
+ if (flags & 0xff00) {
+ DBG_WARNING("invalid attr flags, "
+ "stepping on reserved 0xff00 range: %s\n",
+ str);
+ goto error;
+ }
+ if (comp.offset + 3 > comp.length) {
+ DBG_WARNING("invalid resource attribute (too short): '%s'\n",
+ str);
+ goto error;
+ }
+ if (comp.sddl[comp.offset] != ',') {
+ DBG_WARNING("invalid resource attribute ace\n");
+ goto error;
+ }
+ comp.offset++;
+
+ ok = parse_resource_attr_list(&comp, attr_type);
+ if (!ok || comp.program->length != 2) {
+ DBG_WARNING("invalid attribute type or value: T%c, %s\n",
+ attr_type, str);
+ goto error;
+ }
+ if (comp.sddl[comp.offset] != ')') {
+ DBG_WARNING("expected trailing ')'\n");
+ goto error;
+ }
+ comp.offset++;
+ *length = comp.offset;
+
+ ok = ace_token_to_claim_v1(mem_ctx,
+ attr_name.value,
+ &comp.program->tokens[1],
+ &claim,
+ flags);
+ if (!ok) {
+ goto error;
+ }
+ TALLOC_FREE(comp.program);
+ return claim;
+ error:
+ TALLOC_FREE(comp.program);
+ return NULL;
+}
+
+
+static bool write_resource_attr_from_token(struct sddl_write_context *ctx,
+ const struct ace_condition_token *tok)
+{
+ /*
+ * this is a helper for sddl_resource_attr_from_claim(),
+ * recursing into composites if necessary.
+ */
+ bool ok;
+ char *sid = NULL;
+ size_t i;
+ const struct ace_condition_composite *c = NULL;
+ switch (tok->type) {
+ case CONDITIONAL_ACE_TOKEN_INT64:
+ /*
+ * Note that this includes uint and bool claim types,
+ * but we don't check the validity of the ranges (0|1
+ * and >=0, respectively), rather we trust the claim
+ * to be self-consistent in this regard. Going the
+ * other way, string-to-claim, we do check.
+ */
+ return sddl_write_int(ctx, tok);
+
+ case CONDITIONAL_ACE_TOKEN_UNICODE:
+ return sddl_write_unicode(ctx, tok);
+
+ case CONDITIONAL_ACE_TOKEN_SID:
+ /* unlike conditional ACE, SID does not have a "SID()" wrapper. */
+ sid = sddl_encode_sid(ctx->mem_ctx, &tok->data.sid.sid, NULL);
+ if (sid == NULL) {
+ return false;
+ }
+ return sddl_write(ctx, sid);
+
+ case CONDITIONAL_ACE_TOKEN_OCTET_STRING:
+ return sddl_write_ra_octet_string(ctx, tok);
+
+ case CONDITIONAL_ACE_TOKEN_COMPOSITE:
+ /*
+ * write each token, separated by commas. If there
+ * were nested composites, this would flatten them,
+ * but that isn't really possible because the token we
+ * are dealing with came from a claim, which has no
+ * facility for nesting.
+ */
+ c = &tok->data.composite;
+ for(i = 0; i < c->n_members; i++) {
+ ok = write_resource_attr_from_token(ctx, &c->tokens[i]);
+ if (!ok) {
+ return false;
+ }
+ if (i != c->n_members - 1) {
+ ok = sddl_write(ctx, ",");
+ if (!ok) {
+ return false;
+ }
+ }
+ }
+ return true;
+ default:
+ /* We really really don't expect to get here */
+ return false;
+ }
+}
+
+char *sddl_resource_attr_from_claim(
+ TALLOC_CTX *mem_ctx,
+ const struct CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1 *claim)
+{
+ char *s = NULL;
+ char attr_type;
+ bool ok;
+ struct ace_condition_token tok = {};
+ struct sddl_write_context ctx = {};
+ TALLOC_CTX *tmp_ctx = NULL;
+ char *name = NULL;
+ size_t name_len;
+
+ switch(claim->value_type) {
+ case CLAIM_SECURITY_ATTRIBUTE_TYPE_INT64:
+ attr_type = 'I';
+ break;
+ case CLAIM_SECURITY_ATTRIBUTE_TYPE_UINT64:
+ attr_type = 'U';
+ break;
+ case CLAIM_SECURITY_ATTRIBUTE_TYPE_STRING:
+ attr_type = 'S';
+ break;
+ case CLAIM_SECURITY_ATTRIBUTE_TYPE_SID:
+ attr_type = 'D';
+ break;
+ case CLAIM_SECURITY_ATTRIBUTE_TYPE_BOOLEAN:
+ attr_type = 'B';
+ break;
+ case CLAIM_SECURITY_ATTRIBUTE_TYPE_OCTET_STRING:
+ attr_type = 'X';
+ break;
+ default:
+ return NULL;
+ }
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ return NULL;
+ }
+ ctx.mem_ctx = tmp_ctx;
+
+ ok = claim_v1_to_ace_composite_unchecked(tmp_ctx, claim, &tok);
+ if (!ok) {
+ TALLOC_FREE(tmp_ctx);
+ return NULL;
+ }
+
+ /* this will construct the proper string in ctx.sddl */
+ ok = write_resource_attr_from_token(&ctx, &tok);
+ if (!ok) {
+ TALLOC_FREE(tmp_ctx);
+ return NULL;
+ }
+
+ /* escape the claim name */
+ ok = sddl_encode_attr_name(tmp_ctx,
+ claim->name,
+ &name, &name_len);
+
+ if (!ok) {
+ TALLOC_FREE(tmp_ctx);
+ return NULL;
+ }
+
+ s = talloc_asprintf(mem_ctx,
+ "(\"%s\",T%c,0x%x,%s)",
+ name,
+ attr_type,
+ claim->flags,
+ ctx.sddl);
+ TALLOC_FREE(tmp_ctx);
+ return s;
+}
+
+
+struct CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1 *parse_sddl_literal_as_claim(
+ TALLOC_CTX *mem_ctx,
+ const char *name,
+ const char *str)
+{
+ /*
+ * For testing purposes (and possibly for client tools), we
+ * want to be able to create claim literals, and we might as
+ * well use the SDDL syntax. So we pretend to be parsing SDDL
+ * for one literal.
+ */
+ bool ok;
+ struct CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1 *claim = NULL;
+ struct ace_condition_sddl_compiler_context comp = {};
+
+ ok = init_compiler_context(mem_ctx,
+ &comp,
+ ACE_CONDITION_FLAG_ALLOW_DEVICE,
+ str,
+ 2,
+ 2);
+ if (!ok) {
+ return NULL;
+ }
+
+ comp.state = SDDL_FLAG_EXPECTING_LITERAL;
+ ok = parse_literal(&comp, false);
+
+ if (!ok) {
+ goto error;
+ }
+ if (comp.program->length != 1) {
+ goto error;
+ }
+
+ ok = ace_token_to_claim_v1(mem_ctx,
+ name,
+ &comp.program->tokens[0],
+ &claim,
+ 0);
+ if (!ok) {
+ goto error;
+ }
+ TALLOC_FREE(comp.program);
+ return claim;
+ error:
+ TALLOC_FREE(comp.program);
+ return NULL;
+}