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