mirror of
https://github.com/samba-team/samba.git
synced 2024-12-23 17:34:34 +03:00
4b8e9e3f0c
Resource Attribute ACEs have similar syntactical components to conditional ACEs -- enough so that it is worth reusing the same functions, but not quite enough so that it is exactly simple. Signed-off-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abartlet@samba.org>
3325 lines
81 KiB
C
3325 lines
81 KiB
C
/*
|
|
* Unix SMB implementation.
|
|
* Functions for understanding conditional ACEs
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "includes.h"
|
|
#include "librpc/gen_ndr/ndr_security.h"
|
|
#include "librpc/gen_ndr/conditional_ace.h"
|
|
#include "libcli/security/security.h"
|
|
#include "libcli/security/conditional_ace.h"
|
|
#include "libcli/security/claims-conversions.h"
|
|
#include "lib/util/tsort.h"
|
|
#include "lib/util/bytearray.h"
|
|
|
|
|
|
/* We're only dealing with utf-8 here. Honestly. */
|
|
#undef strncasecmp
|
|
|
|
|
|
#define SDDL_FLAG_EXPECTING_UNARY_OP 1
|
|
#define SDDL_FLAG_EXPECTING_BINARY_OP 2
|
|
#define SDDL_FLAG_EXPECTING_BINARY_LOGIC_OP 4
|
|
#define SDDL_FLAG_EXPECTING_LOCAL_ATTR 8
|
|
#define SDDL_FLAG_EXPECTING_NON_LOCAL_ATTR 16
|
|
#define SDDL_FLAG_EXPECTING_LITERAL 32
|
|
#define SDDL_FLAG_EXPECTING_PAREN 64
|
|
#define SDDL_FLAG_EXPECTING_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
|
|
* 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:
|
|
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.
|
|
*
|
|
* @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
|
|
* 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.
|
|
*
|
|
* @param mem_ctx
|
|
* @param sddl - the string to be parsed
|
|
* @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 char *sddl,
|
|
const char **message,
|
|
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;
|
|
}
|
|
|
|
|
|
|
|
static bool check_resource_attr_type(struct ace_condition_token *tok, char c)
|
|
{
|
|
/*
|
|
* Check that a token matches the expected resource ace type (TU, TS,
|
|
* etc).
|
|
*
|
|
* We're sticking to the [IUSDXB] codes rather than using converting
|
|
* earlier to tok->type (whereby this who thing becomes "if (tok->type
|
|
* == type)") to enable bounds checks on the various integer types.
|
|
*/
|
|
switch(c) {
|
|
case 'I':
|
|
/* signed int */
|
|
if (tok->type != CONDITIONAL_ACE_TOKEN_INT64) {
|
|
goto wrong_type;
|
|
}
|
|
return true;
|
|
case 'U':
|
|
/* unsigned int, let's check the range */
|
|
if (tok->type != CONDITIONAL_ACE_TOKEN_INT64) {
|
|
goto wrong_type;
|
|
}
|
|
if (tok->data.int64.value < 0) {
|
|
DBG_WARNING(
|
|
"invalid resource ACE value for unsigned TU\n");
|
|
goto error;
|
|
}
|
|
return true;
|
|
case 'S':
|
|
/* unicode string */
|
|
if (tok->type != CONDITIONAL_ACE_TOKEN_UNICODE) {
|
|
goto wrong_type;
|
|
}
|
|
return true;
|
|
case 'D':
|
|
/* SID */
|
|
if (tok->type != CONDITIONAL_ACE_TOKEN_SID) {
|
|
goto wrong_type;
|
|
}
|
|
return true;
|
|
case 'X':
|
|
/* Octet string */
|
|
if (tok->type != CONDITIONAL_ACE_TOKEN_OCTET_STRING) {
|
|
if (tok->type == CONDITIONAL_ACE_TOKEN_INT64) {
|
|
/*
|
|
* Windows 2022 will also accept even
|
|
* numbers of digits, like "1234"
|
|
* instead of "#1234". Samba does not.
|
|
*
|
|
* Fixing this is complicated by the
|
|
* fact that a leading '0' will have
|
|
* cast the integer to octal, while an
|
|
* A-F character will have caused it
|
|
* to not parse as a literal at all.
|
|
*
|
|
* This behaviour is not mentioned in
|
|
* MS-DTYP or elsewhere.
|
|
*/
|
|
DBG_WARNING("Octet sequence uses bare digits, "
|
|
"please prefix a '#'\n");
|
|
}
|
|
goto wrong_type;
|
|
}
|
|
return true;
|
|
case 'B':
|
|
/* Boolean, meaning an int that is 0 or 1 */
|
|
if (tok->type != CONDITIONAL_ACE_TOKEN_INT64) {
|
|
goto wrong_type;
|
|
}
|
|
if (tok->data.int64.value != 0 &&
|
|
tok->data.int64.value != 1) {
|
|
DBG_WARNING("invalid resource ACE value for boolean TB "
|
|
"(should be 0 or 1).\n");
|
|
goto error;
|
|
}
|
|
return true;
|
|
default:
|
|
DBG_WARNING("Unknown resource ACE type T%c\n", c);
|
|
goto error;
|
|
};
|
|
wrong_type:
|
|
DBG_WARNING("resource ace type T%c doesn't match value\n", c);
|
|
error:
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
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 |
|
|
SDDL_FLAG_IS_RESOURCE_ATTR_ACE);
|
|
|
|
/*
|
|
* 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 literal, of the right type (checked after)
|
|
* c) more possible whitespace
|
|
* d) a comma
|
|
*
|
|
* 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;
|
|
}
|
|
|
|
if (*comp->target_len == 0) {
|
|
goto fail;
|
|
}
|
|
|
|
ok = check_resource_attr_type(
|
|
&comp->target[*comp->target_len - 1],
|
|
attr_type_char);
|
|
if (! ok) {
|
|
goto fail;
|
|
}
|
|
|
|
ok = eat_whitespace(comp, false);
|
|
if (! ok) {
|
|
goto fail;
|
|
}
|
|
c = comp->sddl[comp->offset];
|
|
if (c == ')') {
|
|
break;
|
|
}
|
|
if (c != ',') {
|
|
comp_error(comp,
|
|
"malformed composite (expected comma)");
|
|
goto fail;
|
|
}
|
|
comp->offset++;
|
|
}
|
|
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 all parsed in the SDDL way.
|
|
* 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, 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);
|
|
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,
|
|
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;
|
|
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 had "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_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);
|
|
ctx.mem_ctx = tmp_ctx;
|
|
|
|
ok = claim_v1_to_ace_token(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,
|
|
strlen(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, str, 2, 2);
|
|
if (!ok) {
|
|
return NULL;
|
|
}
|
|
|
|
comp.state = SDDL_FLAG_EXPECTING_LITERAL;
|
|
ok = parse_literal(&comp);
|
|
|
|
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;
|
|
}
|