/*
Unix SMB/CIFS implementation.
Copyright (C) Stefan Metzmacher 2025
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"
#define CLAIMS_TRANSFORMATION_INTERNALS 1
#include "libcli/security/claims_transformation.h"
#include "libcli/security/claims_transformation.tab.h"
#undef strcasecmp
static char *strip_quote(const char *phrase);
#define YYSTYPE __CLAIMS_TF_YY_STYPE
#define YYLTYPE __CLAIMS_TF_YY_LTYPE
_PRIVATE_ int __claims_tf_yy_lex(
YYSTYPE * yylval_param,
YYLTYPE * yylloc_param,
struct claims_tf_parser_state *ctf_ps,
yyscan_t yyscanner);
#define YY_DECL int __claims_tf_yy_lex \
(YYSTYPE * yylval_param, \
YYLTYPE * yylloc_param, \
struct claims_tf_parser_state *ctf_ps, \
yyscan_t yyscanner)
#define YY_USER_ACTION do { \
size_t __idx; \
yylloc->first_line = yylloc->last_line; \
yylloc->first_column = yylloc->last_column; \
for (__idx = 0; yytext[__idx] != '\0'; __idx++) { \
if (yytext[__idx] == '\n') { \
yylloc->last_line++; \
yylloc->last_column = 0; \
} else { \
yylloc->last_column++; \
} \
} \
ctf_ps->error.first_line = yylloc->first_line; \
ctf_ps->error.first_column = yylloc->first_column; \
ctf_ps->error.last_line = yylloc->last_line; \
ctf_ps->error.last_column = yylloc->last_column; \
} while(0);
%}
%option prefix="__claims_tf_yy_"
%option case-insensitive
%option bison-bridge
%option bison-locations
%option reentrant
%option noyywrap
%option nounput
%option noyyalloc
%option noyyrealloc
%option noyyfree
%option noinput
%option nounput
%option noyylineno
%option noyy_push_state
%option noyy_pop_state
%option noyy_top_state
%option noyyget_leng
%option noyyget_text
%option noyyget_lineno
%option noyyset_lineno
%option noyyget_in
%option noyyset_in
%option noyyget_out
%option noyyset_out
%option noyyget_lval
%option noyyset_lval
%option noyyget_lloc
%option noyyset_lloc
%option noyyget_debug
%option noyyset_debug
%%
\=\> return CLAIMS_TF_YY_IMPLY;
\; return CLAIMS_TF_YY_SEMICOLON;
\: return CLAIMS_TF_YY_COLON;
\, return CLAIMS_TF_YY_COMMA;
\. return CLAIMS_TF_YY_DOT;
\[ return CLAIMS_TF_YY_O_SQ_BRACKET;
\] return CLAIMS_TF_YY_C_SQ_BRACKET;
\( return CLAIMS_TF_YY_O_BRACKET;
\) return CLAIMS_TF_YY_C_BRACKET;
\=\= return CLAIMS_TF_YY_EQ;
\!\= return CLAIMS_TF_YY_NEQ;
\=\~ return CLAIMS_TF_YY_REGEXP_MATCH;
\!\~ return CLAIMS_TF_YY_REGEXP_NOT_MATCH;
\= return CLAIMS_TF_YY_ASSIGN;
\&\& return CLAIMS_TF_YY_AND;
issue return CLAIMS_TF_YY_ISSUE;
type return CLAIMS_TF_YY_TYPE;
value return CLAIMS_TF_YY_VALUE;
valuetype return CLAIMS_TF_YY_VALUE_TYPE;
claim return CLAIMS_TF_YY_CLAIM;
[_A-Za-z][_A-Za-z0-9]* {yylval->sval = talloc_strdup(talloc_tos(), yytext); return CLAIMS_TF_YY_IDENTIFIER;}
\"[^\"\n]*\" {yylval->sval = strip_quote(yytext); return CLAIMS_TF_YY_STRING;}
[ \t\n] /* ignore */
%%
static char *strip_quote(const char *phrase)
{
size_t phrase_len = 0;
char *stripped_phrase = NULL;
if (phrase == NULL) {
return NULL;
}
phrase_len = strlen(phrase);
if (phrase_len < 2 ||
phrase[0] != '\"' ||
phrase[phrase_len - 1] != '\"')
{
return talloc_strdup(talloc_tos(), phrase);
}
phrase++;
stripped_phrase = talloc_strndup(talloc_tos(), phrase, phrase_len - 2);
if (stripped_phrase == NULL) {
return NULL;
}
return stripped_phrase;
}
_PRIVATE_ void *yyalloc(yy_size_t bytes, yyscan_t yyscanner)
{
return talloc_size(yyscanner, bytes);
}
_PRIVATE_ void *yyrealloc(void *ptr, yy_size_t bytes, yyscan_t yyscanner)
{
return talloc_realloc_size(yyscanner, ptr, bytes);
}
_PRIVATE_ void yyfree(void *ptr, yyscan_t yyscanner)
{
if (ptr == yyscanner) {
talloc_free(yyscanner);
} else {
talloc_unlink(yyscanner, ptr);
}
}
_PRIVATE_ enum CLAIM_TYPE claims_tf_type_from_string(const char *str)
{
int cmp;
cmp = strcasecmp(str, "int64");
if (cmp == 0) {
return CLAIM_TYPE_INT64;
}
cmp = strcasecmp(str, "uint64");
if (cmp == 0) {
return CLAIM_TYPE_UINT64;
}
cmp = strcasecmp(str, "string");
if (cmp == 0) {
return CLAIM_TYPE_STRING;
}
cmp = strcasecmp(str, "boolean");
if (cmp == 0) {
return CLAIM_TYPE_STRING;
}
return 0;
}
static bool claims_tf_rule_verify_conditions(const struct claims_tf_rule *rule)
{
uint32_t csi;
/*
* TODO: do we need to verify that all
* optional condition_set identifiers
* are unique?
*
* At least the powershell commands
* on Windows don't verify this.
*/
for (csi = 0; csi < rule->num_condition_sets; csi++) {
const struct claims_tf_condition_set *cs =
&rule->condition_sets[csi];
uint32_t ci;
for (ci = 0; ci < cs->num_conditions; ci++) {
const struct claims_tf_condition *c =
&cs->conditions[ci];
enum CLAIM_TYPE vt;
if (c->string == NULL) {
return false;
}
if (c->property != CLAIMS_TF_PROPERTY_VALUE_TYPE) {
continue;
}
vt = claims_tf_type_from_string(c->string);
if (vt == 0) {
return false;
}
}
}
return true;
}
static bool claims_tf_rule_verify_vt_action(const struct claims_tf_rule *rule,
const struct claims_tf_property *property)
{
if (property->ref.property == CLAIMS_TF_PROPERTY_INVALID) {
enum CLAIM_TYPE vt;
if (property->string == NULL) {
return false;
}
vt = claims_tf_type_from_string(property->string);
if (vt == 0) {
return false;
}
return true;
}
if (property->ref.property != CLAIMS_TF_PROPERTY_VALUE_TYPE) {
return false;
}
return true;
}
static bool claims_tf_rule_verify_action(const struct claims_tf_rule *rule,
const struct claims_tf_property *property)
{
uint32_t csi;
if (property->ref.property == CLAIMS_TF_PROPERTY_INVALID) {
if (property->string == NULL) {
return false;
}
return true;
}
if (property->ref.identifier == NULL) {
return false;
}
for (csi = 0; csi < rule->num_condition_sets; csi++) {
const struct claims_tf_condition_set *cs =
&rule->condition_sets[csi];
bool ok;
if (cs->opt_identifier == NULL) {
continue;
}
ok = strequal(property->ref.identifier,
cs->opt_identifier);
if (ok) {
return true;
}
}
return false;
}
_PUBLIC_ bool claims_tf_rule_set_parse_blob(const DATA_BLOB *blob,
TALLOC_CTX *mem_ctx,
struct claims_tf_rule_set **_rule_set,
char **_error_string)
{
TALLOC_CTX *frame = talloc_stackframe();
struct claims_tf_parser_state *ctf_ps = NULL;
yyscan_t scanner = NULL;
YY_BUFFER_STATE buf = NULL;
uint32_t ri;
int rc;
#if __CLAIMS_TF_YY_DEBUG != 0
__claims_tf_yy_debug = 1;
#endif
rc = yylex_init(&scanner);
if (rc != 0) {
if (_error_string != NULL) {
*_error_string = talloc_asprintf(mem_ctx,
"yylex_init failed rc=%d",
rc);
}
TALLOC_FREE(frame);
return false;
}
buf = yy_scan_bytes((const char *)blob->data,
blob->length,
scanner);
if (buf == NULL) {
if (_error_string != NULL) {
*_error_string = talloc_asprintf(mem_ctx,
"yy_scan_bytes(length=%zu) failed",
blob->length);
}
yylex_destroy(scanner);
TALLOC_FREE(frame);
return false;
}
ctf_ps = talloc_zero(frame, struct claims_tf_parser_state);
if (ctf_ps == NULL) {
if (_error_string != NULL) {
*_error_string = talloc_asprintf(mem_ctx,
"talloc_zero failed");
}
yy_delete_buffer(buf, scanner);
yylex_destroy(scanner);
TALLOC_FREE(frame);
return false;
}
rc = __claims_tf_yy_parse(ctf_ps, scanner);
if (rc != 0) {
if (_error_string != NULL && ctf_ps->error.string != NULL) {
*_error_string = talloc_asprintf(mem_ctx,
"__claims_tf_yy_parse() failed rc=%d "
"fl=%d,fc=%d,ll=%d,lc=%d: %s",
rc,
ctf_ps->error.first_line,
ctf_ps->error.first_column,
ctf_ps->error.last_line,
ctf_ps->error.last_column,
ctf_ps->error.string);
} else if (_error_string != NULL) {
*_error_string = talloc_asprintf(mem_ctx,
"__claims_tf_yy_parse() failed rc=%d",
rc);
}
yy_delete_buffer(buf, scanner);
yylex_destroy(scanner);
TALLOC_FREE(frame);
return false;
}
yy_delete_buffer(buf, scanner);
yylex_destroy(scanner);
for (ri = 0; ri < ctf_ps->rule_set->num_rules; ri++) {
const struct claims_tf_rule *r =
&ctf_ps->rule_set->rules[ri];
bool ok;
ok = claims_tf_rule_verify_conditions(r);
if (!ok) {
if (_error_string != NULL) {
*_error_string = talloc_asprintf(mem_ctx,
"rule[%"PRIu32"] "
"has invalid conditions",
ri);
}
TALLOC_FREE(frame);
return false;
}
ok = claims_tf_rule_verify_vt_action(r, &r->action.value_type);
if (!ok) {
if (_error_string != NULL) {
*_error_string = talloc_asprintf(mem_ctx,
"rule[%"PRIu32"] "
"action.value_type invalid value type specifier",
ri);
}
TALLOC_FREE(frame);
return false;
}
/*
* Now verify that identifiers used
* in rule actions are also used
* as condition_set identifier.
*/
ok = claims_tf_rule_verify_action(r, &r->action.type);
if (!ok) {
if (_error_string != NULL) {
*_error_string = talloc_asprintf(mem_ctx,
"rule[%"PRIu32"] "
"action.type invalid tidentifier %s",
ri, r->action.type.ref.identifier);
}
TALLOC_FREE(frame);
return false;
}
ok = claims_tf_rule_verify_action(r, &r->action.value);
if (!ok) {
if (_error_string != NULL) {
*_error_string = talloc_asprintf(mem_ctx,
"rule[%"PRIu32"] "
"action.value invalid tidentifier %s",
ri, r->action.type.ref.identifier);
}
TALLOC_FREE(frame);
return false;
}
ok = claims_tf_rule_verify_action(r, &r->action.value_type);
if (!ok) {
if (_error_string != NULL) {
*_error_string = talloc_asprintf(mem_ctx,
"rule[%"PRIu32"] "
"action.value_type invalid tidentifier %s",
ri, r->action.type.ref.identifier);
}
TALLOC_FREE(frame);
return false;
}
}
*_rule_set = talloc_move(mem_ctx, &ctf_ps->rule_set);
TALLOC_FREE(frame);
if (_error_string != NULL) {
*_error_string = NULL;
}
return true;
}
/*
* This is a bit strange regarding whitespacing,
* but it's what the New-ADClaimTransformPolicy
* powershell command from Windows 2025 adds
* to the msDS-TransformationRules attribute.
*/
static const char * const claims_tf_xml_prefix_string =
" "
""
" "
""
" "
""
" "
""
"";
_PUBLIC_ char *claims_tf_policy_wrap_xml(TALLOC_CTX *mem_ctx,
const char *rules_string)
{
if (rules_string == NULL) {
errno = EINVAL;
return NULL;
}
if (strstr(rules_string, "]]>") != NULL) {
errno = EINVAL;
return NULL;
}
return talloc_asprintf(mem_ctx, "%s%s%s",
claims_tf_xml_prefix_string,
rules_string,
claims_tf_xml_suffix_string);
}
_PUBLIC_ bool claims_tf_policy_unwrap_xml(const DATA_BLOB *attr_val,
DATA_BLOB *rules)
{
DATA_BLOB prefix = data_blob_string_const(claims_tf_xml_prefix_string);
DATA_BLOB suffix = data_blob_string_const(claims_tf_xml_suffix_string);
size_t rules_ofs;
size_t suffix_ofs;
int cmp;
if (attr_val->length < (prefix.length + suffix.length)) {
return false;
}
rules_ofs = prefix.length;
suffix_ofs = attr_val->length - suffix.length;
cmp = memcmp(attr_val->data,
prefix.data,
prefix.length);
if (cmp != 0) {
return false;
}
cmp = memcmp(attr_val->data + suffix_ofs,
suffix.data,
suffix.length);
if (cmp != 0) {
return false;
}
rules->data = attr_val->data + rules_ofs;
rules->length = suffix_ofs - rules_ofs;
return true;
}