]> git.ipfire.org Git - thirdparty/samba.git/commitdiff
libcli/security: add conditional ACE SDDL functions
authorDouglas Bagnall <douglas.bagnall@catalyst.net.nz>
Wed, 6 Sep 2023 03:50:43 +0000 (15:50 +1200)
committerAndrew Bartlett <abartlet@samba.org>
Tue, 26 Sep 2023 23:45:35 +0000 (23:45 +0000)
Signed-off-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
libcli/security/sddl_conditional_ace.c

index c829694111496418362446427171a15f98c71e17..7f29ee78b72da70a739a8e2a6ef10ae704f4fbcc 100644 (file)
 #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_END             128
+#define SDDL_FLAG_EXPECTING_PAREN_LITERAL   256
+
+#define SDDL_FLAG_IS_UNARY_OP               (1 << 20)
+#define SDDL_FLAG_IS_BINARY_OP              (1 << 21)
+#define SDDL_FLAG_IS_FAKE_OP                (1 << 22)
+#define SDDL_FLAG_IS_LITERAL                (1 << 23)
+#define SDDL_FLAG_IS_ATTR                   (1 << 24)
+
+
+/*
+ * A resource attribute ACE has a slightly different syntax for SIDs.
+ */
+#define SDDL_FLAG_IS_RESOURCE_ATTR_ACE      (1 << 30)
+
+
+#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 |   \
+                                 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 | \
+                             SDDL_FLAG_EXPECTING_END |             \
+                             SDDL_FLAG_IS_ATTR)
+
+#define SDDL_FLAGS_LITERAL (SDDL_FLAG_EXPECTING_BINARY_LOGIC_OP | \
+                           SDDL_FLAG_EXPECTING_END |\
+                           SDDL_FLAG_IS_LITERAL)
+
+#define SDDL_FLAGS_PAREN_END (SDDL_FLAG_EXPECTING_BINARY_LOGIC_OP | \
+                             SDDL_FLAG_EXPECTING_BINARY_OP | \
+                             SDDL_FLAG_EXPECTING_END |\
+                             SDDL_FLAG_IS_FAKE_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;
+};
+
+struct sddl_data {
+       const char *name;
+       uint32_t flags;
+       uint8_t op_precedence;
+       uint8_t nargs;
+};
+
+static 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_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_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_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_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] = {
+               "(",
+               SDDL_FLAG_IS_FAKE_OP,
+               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_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 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
 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:
+                       utf8_len = MIN(tok->data.unicode.length, 40);
+                       snprintf(line, sizeof(line),
+                                "%s.%.*s  (any type)\n",
+                                nom, utf8_len,
+                                tok->data.unicode.value
+                               );
+                       type = '?';
+                       break;
+
+               case CONDITIONAL_ACE_TOKEN_UNICODE:
+                       utf8_len = MIN(tok->data.unicode.length, 20);
+                       snprintf(line, sizeof(line),
+                                "%s.%.*s  (any type)\n",
+                                nom, utf8_len,
+                                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.bytes, 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,
+                           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 */
+       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), "%#"PRIo64, v);
+               } else if (base == CONDITIONAL_ACE_INT_BASE_10) {
+                       snprintf(buf, sizeof(buf), "%"PRId64, v);
+               } else {
+                       snprintf(buf, sizeof(buf), "%#"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;
+       }
+       /*
+        * We can use "%+ld" for the decimal sign, but "%+lx" and "%+lo" are
+        * invalid because %o and %x are unsigned.
+        */
+       if (base == CONDITIONAL_ACE_INT_BASE_10) {
+               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");
+       }
+
+       buf[0] = (v < 0) ? '-' : '+';
+
+       if (base == CONDITIONAL_ACE_INT_BASE_8) {
+               snprintf(buf + 1, sizeof(buf) - 1, "%#llo", llabs(v));
+       } else {
+               snprintf(buf + 1, sizeof(buf) - 1, "%#llx", 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,
+                                 size_t src_len,
+                                 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 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,
+                                       tok->data.local_attr.length,
+                                       &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,
+                              struct ace_condition_token *tok)
+{
+       char *quoted = NULL;
+       bool ok;
+       /*
+        * Apparently unicode strings have no mechanism for escapes, which is
+        * nice at this point.
+        *
+        * Probably we could rely on tok->data.unicode.value being
+        * nul-terminated and a talloc_asprintf().
+        */
+       quoted = talloc_size(ctx->mem_ctx, tok->data.unicode.length + 3);
+       if (quoted == NULL) {
+               return false;
+       }
+       quoted[0] = '"';
+       memcpy(quoted + 1, tok->data.unicode.value, tok->data.unicode.length);
+       quoted[tok->data.unicode.length + 1] = '"';
+       quoted[tok->data.unicode.length + 2] = '\0';
+       ok = sddl_write(ctx, quoted);
+       TALLOC_FREE(quoted);
+       return ok;
+}
+
+static bool sddl_write_octet_string(struct sddl_write_context *ctx,
+                                   struct ace_condition_token *tok)
+{
+       bool ok;
+       char *hex  = hex_encode_talloc(ctx->mem_ctx,
+                                      tok->data.bytes.bytes,
+                                      tok->data.bytes.length);
+       ok = sddl_write(ctx, "#");
+       if (!ok) {
+               return false;
+       }
+       ok = sddl_write(ctx, hex);
+       talloc_free(hex);
+       return ok;
+}
+
+
+static bool sddl_write_sid(struct sddl_write_context *ctx,
+                          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:
+                       ok = sddl_write_composite(ctx, t);
+                       break;
+               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.
  *
@@ -46,10 +1133,1582 @@ char *debug_conditional_ace(TALLOC_CTX *mem_ctx,
 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
+                        * which 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;
+       }
+       comp->message_offset = comp->offset;
+
+       if (comp->message == NULL) {
+               /*
+                * Previously unset message; prepend the position.
+                *
+                * This is the common case.
+                */
+               comp->message = talloc_asprintf(comp->mem_ctx,
+                                               "%"PRIu32": %s",
+                                               comp->offset, msg);
+               TALLOC_FREE(msg);
+               if (comp->message == NULL) {
+                       goto fail;
+               }
+               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 %"PRIu32": %s",
+                                       comp->message, comp->offset,
+                                       msg);
+       TALLOC_FREE(msg);
+       if (comp->message == NULL) {
+               goto fail;
+       }
+       return;
+fail:
+       comp->message = "failed to set error 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 codepoint 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 codepoints 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;
+       }
+
+       dest->length = utf8_len;
+       /* 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;
+       token.data.unicode.length = len;
+
+       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.bytes = talloc_array(comp->mem_ctx, uint8_t, length);
+       token.data.bytes.length = 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.bytes[i]);
+               if (!ok) {
+                       talloc_free(token.data.bytes.bytes);
+                       comp_error(comp, "inexplicable error in octet string");
+                       return false;
+               }
+       }
+       comp->offset += length * 2;
+       return write_sddl_token(comp, token);
+}
+
+
+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;
+       bool expecting_bare_sids =
+               comp->state & SDDL_FLAG_IS_RESOURCE_ATTR_ACE ? true : false;
+
+       if ((comp->state & SDDL_FLAG_EXPECTING_LITERAL) == 0) {
+               comp_error(comp, "did not expect a SID here");
+               return false;
+       }
+       if (expecting_bare_sids) {
+               /*
+                *  This flag is set for a resource ACE which doesn't have the
+                *  SID() wrapper around the the SID string, and not for a
+                *  conditional ACE, which must have the "SID(...)".
+                *
+                * 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;
+               }
+       } else {
+               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;
+       if (expecting_bare_sids) {
+               /* no trailing ')' in a resource attribute ACE */
+       } else {
+               /*
+                * 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_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 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++) {
+                       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 */
+                               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;
+       token.data.local_attr.length = i;
+       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) {
+                       token.type = sddl_attr_types[i].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)
+{
+       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 '{':
+               return parse_composite(comp);
+       default:
+               if (strchr("1234567890-+", c) != NULL) {
+                       return parse_int(comp);
+               }
+               if ((comp->state & SDDL_FLAG_IS_RESOURCE_ATTR_ACE) &&
+                   isupper(c)) {
+                       return parse_sid(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;
+       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 literal, which may recurse into another composite
+        * c) more possible whitespace
+        * d) a comma (or terminating '}')
+        *
+        * 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;
+               }
+               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);
+               if (!ok) {
+                       goto fail;
+               }
+               ok = eat_whitespace(comp, false);
+               if (! ok) {
+                       goto fail;
+               }
+               c = comp->sddl[comp->offset];
+               if (c == '}') {
+                       comp->offset++;
+                       break;
+               }
+               if (c != ',') {
+                       comp_error(comp,
+                                  "malformed composite (expected comma)");
+                       goto fail;
+               }
+               comp->offset++;
+       }
+       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);
+       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) {
+               /*
+                * Syntctically we allow parentheses to wrap a literal
+                * value after a Member_of 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);
+
+       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;
+                       }
+                       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);
+               } 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 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;
+       return true;
+}
+
 /*
  * Compile SDDL conditional ACE conditions.
  *
@@ -67,5 +2726,39 @@ struct ace_condition_script * ace_conditions_compile_sddl(
        size_t *message_offset,
        size_t *consumed_length)
 {
+       bool ok;
+       struct ace_condition_sddl_compiler_context comp = {};
+
+       /* just in case, a message for the next few tallocs */
+       *message = "allocation error";
+       *message_offset = 0;
+
+       ok = init_compiler_context(mem_ctx,
+                                  &comp,
+                                  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;
 }