diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-05 17:47:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-05 17:47:29 +0000 |
commit | 4f5791ebd03eaec1c7da0865a383175b05102712 (patch) | |
tree | 8ce7b00f7a76baa386372422adebbe64510812d4 /source4/dsdb/samdb/ldb_modules/tests | |
parent | Initial commit. (diff) | |
download | samba-upstream.tar.xz samba-upstream.zip |
Adding upstream version 2:4.17.12+dfsg.upstream/2%4.17.12+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'source4/dsdb/samdb/ldb_modules/tests')
9 files changed, 8263 insertions, 0 deletions
diff --git a/source4/dsdb/samdb/ldb_modules/tests/possibleinferiors.py b/source4/dsdb/samdb/ldb_modules/tests/possibleinferiors.py new file mode 100755 index 0000000..d28be8f --- /dev/null +++ b/source4/dsdb/samdb/ldb_modules/tests/possibleinferiors.py @@ -0,0 +1,265 @@ +#!/usr/bin/env python3 + +# Unix SMB/CIFS implementation. +# Copyright (C) Andrew Tridgell 2009 +# +# 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/>. +# + +"""Tests the possibleInferiors generation in the schema_fsmo ldb module""" + +import optparse +import sys + + +# Find right directory when running from source tree +sys.path.insert(0, "bin/python") + +from samba import getopt as options, Ldb +import ldb + +parser = optparse.OptionParser("possibleinferiors.py <URL> [<CLASS>]") +sambaopts = options.SambaOptions(parser) +parser.add_option_group(sambaopts) +credopts = options.CredentialsOptions(parser) +parser.add_option_group(credopts) +parser.add_option_group(options.VersionOptions(parser)) +parser.add_option("--wspp", action="store_true") + +opts, args = parser.parse_args() + +if len(args) < 1: + parser.print_usage() + sys.exit(1) + +url = args[0] +if (len(args) > 1): + objectclass = args[1] +else: + objectclass = None + + +def uniq_list(alist): + """return a unique list""" + set = {} + return [set.setdefault(e, e) for e in alist if e not in set] + + +lp_ctx = sambaopts.get_loadparm() + +creds = credopts.get_credentials(lp_ctx) + +ldb_options = [] +# use 'paged_search' module when connecting remotely +if url.lower().startswith("ldap://"): + ldb_options = ["modules:paged_searches"] + +db = Ldb(url, credentials=creds, lp=lp_ctx, options=ldb_options) + +# get the rootDSE +res = db.search(base="", expression="", + scope=ldb.SCOPE_BASE, + attrs=["schemaNamingContext"]) +rootDse = res[0] + +schema_base = rootDse["schemaNamingContext"][0] + + +def possible_inferiors_search(db, oc): + """return the possible inferiors via a search for the possibleInferiors attribute""" + res = db.search(base=schema_base, + expression=("ldapDisplayName=%s" % oc), + attrs=["possibleInferiors"]) + + poss = [] + if len(res) == 0 or res[0].get("possibleInferiors") is None: + return poss + for item in res[0]["possibleInferiors"]: + poss.append(str(item)) + poss = uniq_list(poss) + poss.sort() + return poss + + +# see [MS-ADTS] section 3.1.1.4.5.21 +# and section 3.1.1.4.2 for this algorithm + +# !systemOnly=TRUE +# !objectClassCategory=2 +# !objectClassCategory=3 + +def supclasses(classinfo, oc): + list = [] + if oc == "top": + return list + if classinfo[oc].get("SUPCLASSES") is not None: + return classinfo[oc]["SUPCLASSES"] + res = classinfo[oc]["subClassOf"] + for r in res: + list.append(r) + list.extend(supclasses(classinfo, r)) + classinfo[oc]["SUPCLASSES"] = list + return list + + +def auxclasses(classinfo, oclist): + list = [] + if oclist == []: + return list + for oc in oclist: + if classinfo[oc].get("AUXCLASSES") is not None: + list.extend(classinfo[oc]["AUXCLASSES"]) + else: + list2 = [] + list2.extend(classinfo[oc]["systemAuxiliaryClass"]) + list2.extend(auxclasses(classinfo, classinfo[oc]["systemAuxiliaryClass"])) + list2.extend(classinfo[oc]["auxiliaryClass"]) + list2.extend(auxclasses(classinfo, classinfo[oc]["auxiliaryClass"])) + list2.extend(auxclasses(classinfo, supclasses(classinfo, oc))) + classinfo[oc]["AUXCLASSES"] = list2 + list.extend(list2) + return list + + +def subclasses(classinfo, oclist): + list = [] + for oc in oclist: + list.extend(classinfo[oc]["SUBCLASSES"]) + return list + + +def posssuperiors(classinfo, oclist): + list = [] + for oc in oclist: + if classinfo[oc].get("POSSSUPERIORS") is not None: + list.extend(classinfo[oc]["POSSSUPERIORS"]) + else: + list2 = [] + list2.extend(classinfo[oc]["systemPossSuperiors"]) + list2.extend(classinfo[oc]["possSuperiors"]) + list2.extend(posssuperiors(classinfo, supclasses(classinfo, oc))) + if opts.wspp: + # the WSPP docs suggest we should do this: + list2.extend(posssuperiors(classinfo, auxclasses(classinfo, [oc]))) + else: + # but testing against w2k3 and w2k8 shows that we need to do this instead + list2.extend(subclasses(classinfo, list2)) + classinfo[oc]["POSSSUPERIORS"] = list2 + list.extend(list2) + return list + + +def pull_classinfo(db): + """At startup we build a classinfo[] dictionary that holds all the information needed to construct the possible inferiors""" + classinfo = {} + res = db.search(base=schema_base, + expression="objectclass=classSchema", + attrs=["ldapDisplayName", "systemOnly", "objectClassCategory", + "possSuperiors", "systemPossSuperiors", + "auxiliaryClass", "systemAuxiliaryClass", "subClassOf"]) + for r in res: + name = str(r["ldapDisplayName"][0]) + classinfo[name] = {} + if str(r["systemOnly"]) == "TRUE": + classinfo[name]["systemOnly"] = True + else: + classinfo[name]["systemOnly"] = False + if r.get("objectClassCategory"): + classinfo[name]["objectClassCategory"] = int(r["objectClassCategory"][0]) + else: + classinfo[name]["objectClassCategory"] = 0 + for a in ["possSuperiors", "systemPossSuperiors", + "auxiliaryClass", "systemAuxiliaryClass", + "subClassOf"]: + classinfo[name][a] = [] + if r.get(a): + for i in r[a]: + classinfo[name][a].append(str(i)) + + # build a list of subclasses for each class + def subclasses_recurse(subclasses, oc): + list = subclasses[oc] + for c in list: + list.extend(subclasses_recurse(subclasses, c)) + return list + + subclasses = {} + for oc in classinfo: + subclasses[oc] = [] + for oc in classinfo: + for c in classinfo[oc]["subClassOf"]: + if not c == oc: + subclasses[c].append(oc) + for oc in classinfo: + classinfo[oc]["SUBCLASSES"] = uniq_list(subclasses_recurse(subclasses, oc)) + + return classinfo + + +def is_in_list(list, c): + for a in list: + if c == a: + return True + return False + + +def possible_inferiors_constructed(db, classinfo, c): + list = [] + for oc in classinfo: + superiors = posssuperiors(classinfo, [oc]) + if (is_in_list(superiors, c) and + classinfo[oc]["systemOnly"] == False and + classinfo[oc]["objectClassCategory"] != 2 and + classinfo[oc]["objectClassCategory"] != 3): + list.append(oc) + list = uniq_list(list) + list.sort() + return list + + +def test_class(db, classinfo, oc): + """test to see if one objectclass returns the correct possibleInferiors""" + print("test: objectClass.%s" % oc) + poss1 = possible_inferiors_search(db, oc) + poss2 = possible_inferiors_constructed(db, classinfo, oc) + if poss1 != poss2: + print("failure: objectClass.%s [" % oc) + print("Returned incorrect list for objectclass %s" % oc) + print("search: %s" % poss1) + print("constructed: %s" % poss2) + for i in range(0, min(len(poss1), len(poss2))): + print("%30s %30s" % (poss1[i], poss2[i])) + print("]") + sys.exit(1) + else: + print("success: objectClass.%s" % oc) + + +def get_object_classes(db): + """return a list of all object classes""" + list = [] + for item in classinfo: + list.append(item) + return list + + +classinfo = pull_classinfo(db) + +if objectclass is None: + for oc in get_object_classes(db): + test_class(db, classinfo, oc) +else: + test_class(db, classinfo, objectclass) + +print("Lists match OK") diff --git a/source4/dsdb/samdb/ldb_modules/tests/test_audit_log.c b/source4/dsdb/samdb/ldb_modules/tests/test_audit_log.c new file mode 100644 index 0000000..885248e --- /dev/null +++ b/source4/dsdb/samdb/ldb_modules/tests/test_audit_log.c @@ -0,0 +1,2305 @@ +/* + Unit tests for the dsdb audit logging code code in audit_log.c + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2018 + + 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 <stdarg.h> +#include <stddef.h> +#include <setjmp.h> +#include <unistd.h> +#include <cmocka.h> + +int ldb_audit_log_module_init(const char *version); +#include "../audit_log.c" + +#include "lib/ldb/include/ldb_private.h" +#include <regex.h> +#include <float.h> + +/* + * Test helper to check ISO 8601 timestamps for validity + */ +static void check_timestamp(time_t before, const char* timestamp) +{ + int rc; + int usec, tz; + char c[2]; + struct tm tm; + time_t after; + time_t actual; + struct timeval tv; + + + rc = gettimeofday(&tv, NULL); + assert_return_code(rc, errno); + after = tv.tv_sec; + + /* + * Convert the ISO 8601 timestamp into a time_t + * Note for convenience we ignore the value of the microsecond + * part of the time stamp. + */ + rc = sscanf( + timestamp, + "%4d-%2d-%2dT%2d:%2d:%2d.%6d%1c%4d", + &tm.tm_year, + &tm.tm_mon, + &tm.tm_mday, + &tm.tm_hour, + &tm.tm_min, + &tm.tm_sec, + &usec, + c, + &tz); + assert_int_equal(9, rc); + tm.tm_year = tm.tm_year - 1900; + tm.tm_mon = tm.tm_mon - 1; + tm.tm_isdst = -1; + actual = mktime(&tm); + + /* + * The timestamp should be before <= actual <= after + */ + assert_in_range(actual, before, after); +} + +static void test_has_password_changed(void **state) +{ + struct ldb_context *ldb = NULL; + struct ldb_message *msg = NULL; + + TALLOC_CTX *ctx = talloc_new(NULL); + + ldb = ldb_init(ctx, NULL); + + /* + * Empty message + */ + msg = ldb_msg_new(ldb); + assert_false(has_password_changed(msg)); + TALLOC_FREE(msg); + + /* + * No password attributes + */ + msg = ldb_msg_new(ldb); + ldb_msg_add_string(msg, "attr01", "value01"); + assert_false(has_password_changed(msg)); + TALLOC_FREE(msg); + + /* + * No password attributes >1 entries + */ + msg = ldb_msg_new(ldb); + ldb_msg_add_string(msg, "attr01", "value01"); + ldb_msg_add_string(msg, "attr02", "value03"); + ldb_msg_add_string(msg, "attr03", "value03"); + assert_false(has_password_changed(msg)); + TALLOC_FREE(msg); + + /* + * userPassword set + */ + msg = ldb_msg_new(ldb); + ldb_msg_add_string(msg, "userPassword", "value01"); + assert_true(has_password_changed(msg)); + TALLOC_FREE(msg); + + /* + * clearTextPassword set + */ + msg = ldb_msg_new(ldb); + ldb_msg_add_string(msg, "clearTextPassword", "value01"); + assert_true(has_password_changed(msg)); + TALLOC_FREE(msg); + + /* + * unicodePwd set + */ + msg = ldb_msg_new(ldb); + ldb_msg_add_string(msg, "unicodePwd", "value01"); + assert_true(has_password_changed(msg)); + TALLOC_FREE(msg); + + /* + * dBCSPwd set + */ + msg = ldb_msg_new(ldb); + ldb_msg_add_string(msg, "dBCSPwd", "value01"); + assert_true(has_password_changed(msg)); + TALLOC_FREE(msg); + + /* + * All attributes set + */ + msg = ldb_msg_new(ldb); + ldb_msg_add_string(msg, "userPassword", "value01"); + ldb_msg_add_string(msg, "clearTextPassword", "value02"); + ldb_msg_add_string(msg, "unicodePwd", "value03"); + ldb_msg_add_string(msg, "dBCSPwd", "value04"); + assert_true(has_password_changed(msg)); + TALLOC_FREE(msg); + + /* + * first attribute is a password attribute + */ + msg = ldb_msg_new(ldb); + ldb_msg_add_string(msg, "userPassword", "value01"); + ldb_msg_add_string(msg, "attr02", "value02"); + ldb_msg_add_string(msg, "attr03", "value03"); + ldb_msg_add_string(msg, "attr04", "value04"); + assert_true(has_password_changed(msg)); + TALLOC_FREE(msg); + + /* + * last attribute is a password attribute + */ + msg = ldb_msg_new(ldb); + ldb_msg_add_string(msg, "attr01", "value01"); + ldb_msg_add_string(msg, "attr02", "value02"); + ldb_msg_add_string(msg, "attr03", "value03"); + ldb_msg_add_string(msg, "clearTextPassword", "value04"); + assert_true(has_password_changed(msg)); + TALLOC_FREE(msg); + + /* + * middle attribute is a password attribute + */ + msg = ldb_msg_new(ldb); + ldb_msg_add_string(msg, "attr01", "value01"); + ldb_msg_add_string(msg, "attr02", "value02"); + ldb_msg_add_string(msg, "unicodePwd", "pwd"); + ldb_msg_add_string(msg, "attr03", "value03"); + ldb_msg_add_string(msg, "attr04", "value04"); + assert_true(has_password_changed(msg)); + TALLOC_FREE(msg); + + TALLOC_FREE(ctx); +} + +static void test_get_password_action(void **state) +{ + struct ldb_context *ldb = NULL; + struct ldb_request *req = NULL; + struct ldb_reply *reply = NULL; + struct dsdb_control_password_acl_validation *pav = NULL; + int ret; + + TALLOC_CTX *ctx = talloc_new(NULL); + ldb = ldb_init(ctx, NULL); + + /* + * Add request, will always be a reset + */ + ldb_build_add_req(&req, ldb, ctx, NULL, NULL, NULL, NULL, NULL); + reply = talloc_zero(ctx, struct ldb_reply); + assert_string_equal("Reset", get_password_action(req, reply)); + TALLOC_FREE(req); + TALLOC_FREE(reply); + + /* + * No password control acl, expect "Reset" + */ + ldb_build_mod_req(&req, ldb, ctx, NULL, NULL, NULL, NULL, NULL); + reply = talloc_zero(ctx, struct ldb_reply); + assert_string_equal("Reset", get_password_action(req, reply)); + TALLOC_FREE(req); + TALLOC_FREE(reply); + + /* + * dsdb_control_password_acl_validation reset = false, expect "Change" + */ + ret = ldb_build_mod_req(&req, ldb, ctx, NULL, NULL, NULL, NULL, NULL); + assert_int_equal(ret, LDB_SUCCESS); + reply = talloc_zero(ctx, struct ldb_reply); + pav = talloc_zero(req, struct dsdb_control_password_acl_validation); + + ldb_reply_add_control( + reply, + DSDB_CONTROL_PASSWORD_ACL_VALIDATION_OID, + false, + pav); + assert_string_equal("Change", get_password_action(req, reply)); + TALLOC_FREE(req); + TALLOC_FREE(reply); + + /* + * dsdb_control_password_acl_validation reset = true, expect "Reset" + */ + ldb_build_mod_req(&req, ldb, ctx, NULL, NULL, NULL, NULL, NULL); + reply = talloc_zero(ctx, struct ldb_reply); + pav = talloc_zero(req, struct dsdb_control_password_acl_validation); + pav->pwd_reset = true; + + ldb_reply_add_control( + reply, + DSDB_CONTROL_PASSWORD_ACL_VALIDATION_OID, + false, + pav); + assert_string_equal("Reset", get_password_action(req, reply)); + TALLOC_FREE(req); + TALLOC_FREE(reply); + + TALLOC_FREE(ctx); +} + +/* + * Test helper to validate a version object. + */ +static void check_version(struct json_t *version, int major, int minor) +{ + struct json_t *v = NULL; + + assert_true(json_is_object(version)); + assert_int_equal(2, json_object_size(version)); + + v = json_object_get(version, "major"); + assert_non_null(v); + assert_int_equal(major, json_integer_value(v)); + + v = json_object_get(version, "minor"); + assert_non_null(v); + assert_int_equal(minor, json_integer_value(v)); +} + +/* + * minimal unit test of operation_json, that ensures that all the expected + * attributes and objects are in the json object. + */ +static void test_operation_json_empty(void **state) +{ + struct ldb_context *ldb = NULL; + struct ldb_module *module = NULL; + struct ldb_request *req = NULL; + struct ldb_reply *reply = NULL; + struct audit_private *audit_private = NULL; + + struct json_object json; + json_t *audit = NULL; + json_t *v = NULL; + json_t *o = NULL; + time_t before; + struct timeval tv; + int rc; + + + TALLOC_CTX *ctx = talloc_new(NULL); + + ldb = ldb_init(ctx, NULL); + audit_private = talloc_zero(ctx, struct audit_private); + + module = talloc_zero(ctx, struct ldb_module); + module->ldb = ldb; + ldb_module_set_private(module, audit_private); + + req = talloc_zero(ctx, struct ldb_request); + reply = talloc_zero(ctx, struct ldb_reply); + reply->error = LDB_SUCCESS; + + rc = gettimeofday(&tv, NULL); + assert_return_code(rc, errno); + before = tv.tv_sec; + json = operation_json(module, req, reply); + assert_int_equal(3, json_object_size(json.root)); + + + v = json_object_get(json.root, "type"); + assert_non_null(v); + assert_string_equal("dsdbChange", json_string_value(v)); + + v = json_object_get(json.root, "timestamp"); + assert_non_null(v); + assert_true(json_is_string(v)); + check_timestamp(before, json_string_value(v)); + + audit = json_object_get(json.root, "dsdbChange"); + assert_non_null(audit); + assert_true(json_is_object(audit)); + assert_int_equal(10, json_object_size(audit)); + + o = json_object_get(audit, "version"); + assert_non_null(o); + check_version(o, OPERATION_MAJOR, OPERATION_MINOR); + + v = json_object_get(audit, "statusCode"); + assert_non_null(v); + assert_true(json_is_integer(v)); + assert_int_equal(LDB_SUCCESS, json_integer_value(v)); + + v = json_object_get(audit, "status"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal("Success", json_string_value(v)); + + v = json_object_get(audit, "operation"); + assert_non_null(v); + assert_true(json_is_string(v)); + /* + * Search operation constant is zero + */ + assert_string_equal("Search", json_string_value(v)); + + v = json_object_get(audit, "remoteAddress"); + assert_non_null(v); + assert_true(json_is_null(v)); + + v = json_object_get(audit, "userSid"); + assert_non_null(v); + assert_true(json_is_null(v)); + + v = json_object_get(audit, "performedAsSystem"); + assert_non_null(v); + assert_true(json_is_boolean(v)); + assert_true(json_is_false(v)); + + + v = json_object_get(audit, "dn"); + assert_non_null(v); + assert_true(json_is_null(v)); + + v = json_object_get(audit, "transactionId"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal( + "00000000-0000-0000-0000-000000000000", + json_string_value(v)); + + v = json_object_get(audit, "sessionId"); + assert_non_null(v); + assert_true(json_is_null(v)); + + json_free(&json); + TALLOC_FREE(ctx); + +} + +/* + * unit test of operation_json, that ensures that all the expected + * attributes and objects are in the json object. + */ +static void test_operation_json(void **state) +{ + struct ldb_context *ldb = NULL; + struct ldb_module *module = NULL; + struct ldb_request *req = NULL; + struct ldb_reply *reply = NULL; + struct audit_private *audit_private = NULL; + + struct tsocket_address *ts = NULL; + + struct auth_session_info *sess = NULL; + struct security_token *token = NULL; + struct dom_sid sid; + const char *const SID = "S-1-5-21-2470180966-3899876309-2637894779"; + const char * const SESSION = "7130cb06-2062-6a1b-409e-3514c26b1773"; + struct GUID session_id; + + struct GUID transaction_id; + const char *const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773"; + + struct ldb_dn *dn = NULL; + const char *const DN = "dn=CN=USER,CN=Users,DC=SAMBA,DC=ORG"; + + struct ldb_message *msg = NULL; + + struct json_object json; + json_t *audit = NULL; + json_t *v = NULL; + json_t *o = NULL; + json_t *a = NULL; + json_t *b = NULL; + json_t *c = NULL; + json_t *d = NULL; + json_t *e = NULL; + json_t *f = NULL; + json_t *g = NULL; + time_t before; + struct timeval tv; + int rc; + + + TALLOC_CTX *ctx = talloc_new(NULL); + + ldb = ldb_init(ctx, NULL); + + audit_private = talloc_zero(ctx, struct audit_private); + GUID_from_string(TRANSACTION, &transaction_id); + audit_private->transaction_guid = transaction_id; + + module = talloc_zero(ctx, struct ldb_module); + module->ldb = ldb; + ldb_module_set_private(module, audit_private); + + tsocket_address_inet_from_strings(ctx, "ip", "127.0.0.1", 0, &ts); + ldb_set_opaque(ldb, "remoteAddress", ts); + + sess = talloc_zero(ctx, struct auth_session_info); + token = talloc_zero(ctx, struct security_token); + string_to_sid(&sid, SID); + token->num_sids = 1; + token->sids = &sid; + sess->security_token = token; + GUID_from_string(SESSION, &session_id); + sess->unique_session_token = session_id; + ldb_set_opaque(ldb, DSDB_SESSION_INFO, sess); + + msg = talloc_zero(ctx, struct ldb_message); + dn = ldb_dn_new(ctx, ldb, DN); + msg->dn = dn; + ldb_msg_add_string(msg, "attribute", "the-value"); + + req = talloc_zero(ctx, struct ldb_request); + req->operation = LDB_ADD; + req->op.add.message = msg; + + reply = talloc_zero(ctx, struct ldb_reply); + reply->error = LDB_ERR_OPERATIONS_ERROR; + + rc = gettimeofday(&tv, NULL); + assert_return_code(rc, errno); + before = tv.tv_sec; + json = operation_json(module, req, reply); + assert_int_equal(3, json_object_size(json.root)); + + v = json_object_get(json.root, "type"); + assert_non_null(v); + assert_string_equal("dsdbChange", json_string_value(v)); + + v = json_object_get(json.root, "timestamp"); + assert_non_null(v); + assert_true(json_is_string(v)); + check_timestamp(before, json_string_value(v)); + + audit = json_object_get(json.root, "dsdbChange"); + assert_non_null(audit); + assert_true(json_is_object(audit)); + assert_int_equal(11, json_object_size(audit)); + + o = json_object_get(audit, "version"); + assert_non_null(o); + check_version(o, OPERATION_MAJOR, OPERATION_MINOR); + + v = json_object_get(audit, "statusCode"); + assert_non_null(v); + assert_true(json_is_integer(v)); + assert_int_equal(LDB_ERR_OPERATIONS_ERROR, json_integer_value(v)); + + v = json_object_get(audit, "status"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal("Operations error", json_string_value(v)); + + v = json_object_get(audit, "operation"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal("Add", json_string_value(v)); + + v = json_object_get(audit, "remoteAddress"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal("ipv4:127.0.0.1:0", json_string_value(v)); + + v = json_object_get(audit, "userSid"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal(SID, json_string_value(v)); + + v = json_object_get(audit, "performedAsSystem"); + assert_non_null(v); + assert_true(json_is_boolean(v)); + assert_true(json_is_false(v)); + + v = json_object_get(audit, "dn"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal(DN, json_string_value(v)); + + v = json_object_get(audit, "transactionId"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal(TRANSACTION, json_string_value(v)); + + v = json_object_get(audit, "sessionId"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal(SESSION, json_string_value(v)); + + o = json_object_get(audit, "attributes"); + assert_non_null(v); + assert_true(json_is_object(o)); + assert_int_equal(1, json_object_size(o)); + + a = json_object_get(o, "attribute"); + assert_non_null(a); + assert_true(json_is_object(a)); + + b = json_object_get(a, "actions"); + assert_non_null(b); + assert_true(json_is_array(b)); + assert_int_equal(1, json_array_size(b)); + + c = json_array_get(b, 0); + assert_non_null(c); + assert_true(json_is_object(c)); + + d = json_object_get(c, "action"); + assert_non_null(d); + assert_true(json_is_string(d)); + assert_string_equal("add", json_string_value(d)); + + e = json_object_get(c, "values"); + assert_non_null(b); + assert_true(json_is_array(e)); + assert_int_equal(1, json_array_size(e)); + + f = json_array_get(e, 0); + assert_non_null(f); + assert_true(json_is_object(f)); + assert_int_equal(1, json_object_size(f)); + + g = json_object_get(f, "value"); + assert_non_null(g); + assert_true(json_is_string(g)); + assert_string_equal("the-value", json_string_value(g)); + + json_free(&json); + TALLOC_FREE(ctx); + +} + +/* + * unit test of operation_json, that ensures that all the expected + * attributes and objects are in the json object. + * In this case for an operation performed as the system user. + */ +static void test_as_system_operation_json(void **state) +{ + struct ldb_context *ldb = NULL; + struct ldb_module *module = NULL; + struct ldb_request *req = NULL; + struct ldb_reply *reply = NULL; + struct audit_private *audit_private = NULL; + + struct tsocket_address *ts = NULL; + + struct auth_session_info *sess = NULL; + struct auth_session_info *sys_sess = NULL; + struct security_token *token = NULL; + struct security_token *sys_token = NULL; + struct dom_sid sid; + const char *const SID = "S-1-5-21-2470180966-3899876309-2637894779"; + const char * const SESSION = "7130cb06-2062-6a1b-409e-3514c26b1773"; + const char * const SYS_SESSION = "7130cb06-2062-6a1b-409e-3514c26b1998"; + struct GUID session_id; + struct GUID sys_session_id; + + struct GUID transaction_id; + const char *const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773"; + + struct ldb_dn *dn = NULL; + const char *const DN = "dn=CN=USER,CN=Users,DC=SAMBA,DC=ORG"; + + struct ldb_message *msg = NULL; + + struct json_object json; + json_t *audit = NULL; + json_t *v = NULL; + json_t *o = NULL; + json_t *a = NULL; + json_t *b = NULL; + json_t *c = NULL; + json_t *d = NULL; + json_t *e = NULL; + json_t *f = NULL; + json_t *g = NULL; + time_t before; + struct timeval tv; + int rc; + + + TALLOC_CTX *ctx = talloc_new(NULL); + + ldb = ldb_init(ctx, NULL); + + audit_private = talloc_zero(ctx, struct audit_private); + GUID_from_string(TRANSACTION, &transaction_id); + audit_private->transaction_guid = transaction_id; + + module = talloc_zero(ctx, struct ldb_module); + module->ldb = ldb; + ldb_module_set_private(module, audit_private); + + tsocket_address_inet_from_strings(ctx, "ip", "127.0.0.1", 0, &ts); + ldb_set_opaque(ldb, "remoteAddress", ts); + + sess = talloc_zero(ctx, struct auth_session_info); + token = talloc_zero(ctx, struct security_token); + string_to_sid(&sid, SID); + token->num_sids = 1; + token->sids = &sid; + sess->security_token = token; + GUID_from_string(SESSION, &session_id); + sess->unique_session_token = session_id; + ldb_set_opaque(ldb, DSDB_NETWORK_SESSION_INFO, sess); + + sys_sess = talloc_zero(ctx, struct auth_session_info); + sys_token = talloc_zero(ctx, struct security_token); + sys_token->num_sids = 1; + sys_token->sids = discard_const(&global_sid_System); + sys_sess->security_token = sys_token; + GUID_from_string(SYS_SESSION, &sys_session_id); + sess->unique_session_token = sys_session_id; + ldb_set_opaque(ldb, DSDB_SESSION_INFO, sys_sess); + + msg = talloc_zero(ctx, struct ldb_message); + dn = ldb_dn_new(ctx, ldb, DN); + msg->dn = dn; + ldb_msg_add_string(msg, "attribute", "the-value"); + + req = talloc_zero(ctx, struct ldb_request); + req->operation = LDB_ADD; + req->op.add.message = msg; + + reply = talloc_zero(ctx, struct ldb_reply); + reply->error = LDB_ERR_OPERATIONS_ERROR; + + rc = gettimeofday(&tv, NULL); + assert_return_code(rc, errno); + before = tv.tv_sec; + json = operation_json(module, req, reply); + assert_int_equal(3, json_object_size(json.root)); + + v = json_object_get(json.root, "type"); + assert_non_null(v); + assert_string_equal("dsdbChange", json_string_value(v)); + + v = json_object_get(json.root, "timestamp"); + assert_non_null(v); + assert_true(json_is_string(v)); + check_timestamp(before, json_string_value(v)); + + audit = json_object_get(json.root, "dsdbChange"); + assert_non_null(audit); + assert_true(json_is_object(audit)); + assert_int_equal(11, json_object_size(audit)); + + o = json_object_get(audit, "version"); + assert_non_null(o); + check_version(o, OPERATION_MAJOR, OPERATION_MINOR); + + v = json_object_get(audit, "statusCode"); + assert_non_null(v); + assert_true(json_is_integer(v)); + assert_int_equal(LDB_ERR_OPERATIONS_ERROR, json_integer_value(v)); + + v = json_object_get(audit, "status"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal("Operations error", json_string_value(v)); + + v = json_object_get(audit, "operation"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal("Add", json_string_value(v)); + + v = json_object_get(audit, "remoteAddress"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal("ipv4:127.0.0.1:0", json_string_value(v)); + + v = json_object_get(audit, "userSid"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal(SID, json_string_value(v)); + + v = json_object_get(audit, "performedAsSystem"); + assert_non_null(v); + assert_true(json_is_boolean(v)); + assert_true(json_is_true(v)); + + v = json_object_get(audit, "dn"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal(DN, json_string_value(v)); + + v = json_object_get(audit, "transactionId"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal(TRANSACTION, json_string_value(v)); + + v = json_object_get(audit, "sessionId"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal(SYS_SESSION, json_string_value(v)); + + o = json_object_get(audit, "attributes"); + assert_non_null(v); + assert_true(json_is_object(o)); + assert_int_equal(1, json_object_size(o)); + + a = json_object_get(o, "attribute"); + assert_non_null(a); + assert_true(json_is_object(a)); + + b = json_object_get(a, "actions"); + assert_non_null(b); + assert_true(json_is_array(b)); + assert_int_equal(1, json_array_size(b)); + + c = json_array_get(b, 0); + assert_non_null(c); + assert_true(json_is_object(c)); + + d = json_object_get(c, "action"); + assert_non_null(d); + assert_true(json_is_string(d)); + assert_string_equal("add", json_string_value(d)); + + e = json_object_get(c, "values"); + assert_non_null(b); + assert_true(json_is_array(e)); + assert_int_equal(1, json_array_size(e)); + + f = json_array_get(e, 0); + assert_non_null(f); + assert_true(json_is_object(f)); + assert_int_equal(1, json_object_size(f)); + + g = json_object_get(f, "value"); + assert_non_null(g); + assert_true(json_is_string(g)); + assert_string_equal("the-value", json_string_value(g)); + + json_free(&json); + TALLOC_FREE(ctx); + +} + +/* + * minimal unit test of password_change_json, that ensures that all the expected + * attributes and objects are in the json object. + */ +static void test_password_change_json_empty(void **state) +{ + struct ldb_context *ldb = NULL; + struct ldb_module *module = NULL; + struct ldb_request *req = NULL; + struct ldb_reply *reply = NULL; + struct audit_private *audit_private = NULL; + + struct json_object json; + json_t *audit = NULL; + json_t *v = NULL; + json_t *o = NULL; + time_t before; + struct timeval tv; + int rc; + + + TALLOC_CTX *ctx = talloc_new(NULL); + + ldb = ldb_init(ctx, NULL); + audit_private = talloc_zero(ctx, struct audit_private); + + module = talloc_zero(ctx, struct ldb_module); + module->ldb = ldb; + ldb_module_set_private(module, audit_private); + + req = talloc_zero(ctx, struct ldb_request); + reply = talloc_zero(ctx, struct ldb_reply); + reply->error = LDB_SUCCESS; + + rc = gettimeofday(&tv, NULL); + assert_return_code(rc, errno); + before = tv.tv_sec; + json = password_change_json(module, req, reply); + assert_int_equal(3, json_object_size(json.root)); + + + v = json_object_get(json.root, "type"); + assert_non_null(v); + assert_string_equal("passwordChange", json_string_value(v)); + + v = json_object_get(json.root, "timestamp"); + assert_non_null(v); + assert_true(json_is_string(v)); + check_timestamp(before, json_string_value(v)); + + audit = json_object_get(json.root, "passwordChange"); + assert_non_null(audit); + assert_true(json_is_object(audit)); + assert_int_equal(10, json_object_size(audit)); + + o = json_object_get(audit, "version"); + assert_non_null(o); + + v = json_object_get(audit, "eventId"); + assert_non_null(v); + + v = json_object_get(audit, "statusCode"); + assert_non_null(v); + + v = json_object_get(audit, "status"); + assert_non_null(v); + + v = json_object_get(audit, "remoteAddress"); + assert_non_null(v); + + v = json_object_get(audit, "userSid"); + assert_non_null(v); + + v = json_object_get(audit, "dn"); + assert_non_null(v); + + v = json_object_get(audit, "transactionId"); + assert_non_null(v); + + v = json_object_get(audit, "sessionId"); + assert_non_null(v); + + v = json_object_get(audit, "action"); + assert_non_null(v); + + json_free(&json); + TALLOC_FREE(ctx); + +} + +/* + * minimal unit test of password_change_json, that ensures that all the expected + * attributes and objects are in the json object. + */ +static void test_password_change_json(void **state) +{ + struct ldb_context *ldb = NULL; + struct ldb_module *module = NULL; + struct ldb_request *req = NULL; + struct ldb_reply *reply = NULL; + struct audit_private *audit_private = NULL; + + struct tsocket_address *ts = NULL; + + struct auth_session_info *sess = NULL; + struct security_token *token = NULL; + struct dom_sid sid; + const char *const SID = "S-1-5-21-2470180966-3899876309-2637894779"; + const char * const SESSION = "7130cb06-2062-6a1b-409e-3514c26b1773"; + struct GUID session_id; + + struct GUID transaction_id; + const char *const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773"; + + struct ldb_dn *dn = NULL; + const char *const DN = "dn=CN=USER,CN=Users,DC=SAMBA,DC=ORG"; + + struct ldb_message *msg = NULL; + + struct json_object json; + json_t *audit = NULL; + json_t *v = NULL; + json_t *o = NULL; + time_t before; + struct timeval tv; + int rc; + + TALLOC_CTX *ctx = talloc_new(NULL); + + ldb = ldb_init(ctx, NULL); + + audit_private = talloc_zero(ctx, struct audit_private); + GUID_from_string(TRANSACTION, &transaction_id); + audit_private->transaction_guid = transaction_id; + + module = talloc_zero(ctx, struct ldb_module); + module->ldb = ldb; + ldb_module_set_private(module, audit_private); + + tsocket_address_inet_from_strings(ctx, "ip", "127.0.0.1", 0, &ts); + ldb_set_opaque(ldb, "remoteAddress", ts); + + sess = talloc_zero(ctx, struct auth_session_info); + token = talloc_zero(ctx, struct security_token); + string_to_sid(&sid, SID); + token->num_sids = 1; + token->sids = &sid; + sess->security_token = token; + GUID_from_string(SESSION, &session_id); + sess->unique_session_token = session_id; + ldb_set_opaque(ldb, DSDB_SESSION_INFO, sess); + + msg = talloc_zero(ctx, struct ldb_message); + dn = ldb_dn_new(ctx, ldb, DN); + msg->dn = dn; + ldb_msg_add_string(msg, "planTextPassword", "super-secret"); + + req = talloc_zero(ctx, struct ldb_request); + req->operation = LDB_ADD; + req->op.add.message = msg; + reply = talloc_zero(ctx, struct ldb_reply); + reply->error = LDB_SUCCESS; + + rc = gettimeofday(&tv, NULL); + assert_return_code(rc, errno); + before = tv.tv_sec; + json = password_change_json(module, req, reply); + assert_int_equal(3, json_object_size(json.root)); + + + v = json_object_get(json.root, "type"); + assert_non_null(v); + assert_string_equal("passwordChange", json_string_value(v)); + + v = json_object_get(json.root, "timestamp"); + assert_non_null(v); + assert_true(json_is_string(v)); + check_timestamp(before, json_string_value(v)); + + audit = json_object_get(json.root, "passwordChange"); + assert_non_null(audit); + assert_true(json_is_object(audit)); + assert_int_equal(10, json_object_size(audit)); + + o = json_object_get(audit, "version"); + assert_non_null(o); + check_version(o, PASSWORD_MAJOR,PASSWORD_MINOR); + + v = json_object_get(audit, "eventId"); + assert_non_null(v); + assert_true(json_is_integer(v)); + assert_int_equal(EVT_ID_PASSWORD_RESET, json_integer_value(v)); + + v = json_object_get(audit, "statusCode"); + assert_non_null(v); + assert_true(json_is_integer(v)); + assert_int_equal(LDB_SUCCESS, json_integer_value(v)); + + v = json_object_get(audit, "status"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal("Success", json_string_value(v)); + + v = json_object_get(audit, "remoteAddress"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal("ipv4:127.0.0.1:0", json_string_value(v)); + + v = json_object_get(audit, "userSid"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal(SID, json_string_value(v)); + + v = json_object_get(audit, "dn"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal(DN, json_string_value(v)); + + v = json_object_get(audit, "transactionId"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal(TRANSACTION, json_string_value(v)); + + v = json_object_get(audit, "sessionId"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal(SESSION, json_string_value(v)); + + v = json_object_get(audit, "action"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal("Reset", json_string_value(v)); + + json_free(&json); + TALLOC_FREE(ctx); + +} + + +/* + * minimal unit test of transaction_json, that ensures that all the expected + * attributes and objects are in the json object. + */ +static void test_transaction_json(void **state) +{ + + struct GUID guid; + const char * const GUID = "7130cb06-2062-6a1b-409e-3514c26b1773"; + + struct json_object json; + json_t *audit = NULL; + json_t *v = NULL; + json_t *o = NULL; + time_t before; + struct timeval tv; + int rc; + + GUID_from_string(GUID, &guid); + + rc = gettimeofday(&tv, NULL); + assert_return_code(rc, errno); + before = tv.tv_sec; + json = transaction_json("delete", &guid, 10000099); + + assert_int_equal(3, json_object_size(json.root)); + + + v = json_object_get(json.root, "type"); + assert_non_null(v); + assert_string_equal("dsdbTransaction", json_string_value(v)); + + v = json_object_get(json.root, "timestamp"); + assert_non_null(v); + assert_true(json_is_string(v)); + check_timestamp(before, json_string_value(v)); + + audit = json_object_get(json.root, "dsdbTransaction"); + assert_non_null(audit); + assert_true(json_is_object(audit)); + assert_int_equal(4, json_object_size(audit)); + + o = json_object_get(audit, "version"); + assert_non_null(o); + check_version(o, TRANSACTION_MAJOR, TRANSACTION_MINOR); + + v = json_object_get(audit, "transactionId"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal(GUID, json_string_value(v)); + + v = json_object_get(audit, "action"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal("delete", json_string_value(v)); + + v = json_object_get(audit, "duration"); + assert_non_null(v); + assert_true(json_is_integer(v)); + assert_int_equal(10000099, json_integer_value(v)); + + json_free(&json); + +} + +/* + * minimal unit test of commit_failure_json, that ensures that all the + * expected attributes and objects are in the json object. + */ +static void test_commit_failure_json(void **state) +{ + + struct GUID guid; + const char * const GUID = "7130cb06-2062-6a1b-409e-3514c26b1773"; + + struct json_object json; + json_t *audit = NULL; + json_t *v = NULL; + json_t *o = NULL; + time_t before; + struct timeval tv; + int rc; + + GUID_from_string(GUID, &guid); + + rc = gettimeofday(&tv, NULL); + assert_return_code(rc, errno); + before = tv.tv_sec; + json = commit_failure_json( + "prepare", + 987876, + LDB_ERR_OPERATIONS_ERROR, + "because", + &guid); + + assert_int_equal(3, json_object_size(json.root)); + + + v = json_object_get(json.root, "type"); + assert_non_null(v); + assert_string_equal("dsdbTransaction", json_string_value(v)); + + v = json_object_get(json.root, "timestamp"); + assert_non_null(v); + assert_true(json_is_string(v)); + check_timestamp(before, json_string_value(v)); + + audit = json_object_get(json.root, "dsdbTransaction"); + assert_non_null(audit); + assert_true(json_is_object(audit)); + assert_int_equal(7, json_object_size(audit)); + + o = json_object_get(audit, "version"); + assert_non_null(o); + check_version(o, TRANSACTION_MAJOR, TRANSACTION_MINOR); + + v = json_object_get(audit, "transactionId"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal(GUID, json_string_value(v)); + + v = json_object_get(audit, "action"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal("prepare", json_string_value(v)); + + v = json_object_get(audit, "statusCode"); + assert_non_null(v); + assert_true(json_is_integer(v)); + assert_int_equal(LDB_ERR_OPERATIONS_ERROR, json_integer_value(v)); + + v = json_object_get(audit, "status"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal("Operations error", json_string_value(v)); + v = json_object_get(audit, "status"); + assert_non_null(v); + + v = json_object_get(audit, "reason"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal("because", json_string_value(v)); + + v = json_object_get(audit, "duration"); + assert_non_null(v); + assert_true(json_is_integer(v)); + assert_int_equal(987876, json_integer_value(v)); + + json_free(&json); + +} + +/* + * minimal unit test of replicated_update_json, that ensures that all the + * expected attributes and objects are in the json object. + */ +static void test_replicated_update_json_empty(void **state) +{ + struct ldb_context *ldb = NULL; + struct ldb_module *module = NULL; + struct ldb_request *req = NULL; + struct ldb_reply *reply = NULL; + struct audit_private *audit_private = NULL; + struct dsdb_extended_replicated_objects *ro = NULL; + struct repsFromTo1 *source_dsa = NULL; + + struct json_object json; + json_t *audit = NULL; + json_t *v = NULL; + json_t *o = NULL; + time_t before; + struct timeval tv; + int rc; + + + TALLOC_CTX *ctx = talloc_new(NULL); + + ldb = ldb_init(ctx, NULL); + audit_private = talloc_zero(ctx, struct audit_private); + + module = talloc_zero(ctx, struct ldb_module); + module->ldb = ldb; + ldb_module_set_private(module, audit_private); + + source_dsa = talloc_zero(ctx, struct repsFromTo1); + ro = talloc_zero(ctx, struct dsdb_extended_replicated_objects); + ro->source_dsa = source_dsa; + req = talloc_zero(ctx, struct ldb_request); + req->op.extended.data = ro; + req->operation = LDB_EXTENDED; + reply = talloc_zero(ctx, struct ldb_reply); + reply->error = LDB_SUCCESS; + + rc = gettimeofday(&tv, NULL); + assert_return_code(rc, errno); + before = tv.tv_sec; + json = replicated_update_json(module, req, reply); + assert_int_equal(3, json_object_size(json.root)); + + + v = json_object_get(json.root, "type"); + assert_non_null(v); + assert_string_equal("replicatedUpdate", json_string_value(v)); + + v = json_object_get(json.root, "timestamp"); + assert_non_null(v); + assert_true(json_is_string(v)); + check_timestamp(before, json_string_value(v)); + + audit = json_object_get(json.root, "replicatedUpdate"); + assert_non_null(audit); + assert_true(json_is_object(audit)); + assert_int_equal(11, json_object_size(audit)); + + o = json_object_get(audit, "version"); + assert_non_null(o); + check_version(o, REPLICATION_MAJOR, REPLICATION_MINOR); + + v = json_object_get(audit, "statusCode"); + assert_non_null(v); + assert_true(json_is_integer(v)); + assert_int_equal(LDB_SUCCESS, json_integer_value(v)); + + v = json_object_get(audit, "status"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal("Success", json_string_value(v)); + + v = json_object_get(audit, "transactionId"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal( + "00000000-0000-0000-0000-000000000000", + json_string_value(v)); + + v = json_object_get(audit, "objectCount"); + assert_non_null(v); + assert_true(json_is_integer(v)); + assert_int_equal(0, json_integer_value(v)); + + v = json_object_get(audit, "linkCount"); + assert_non_null(v); + assert_true(json_is_integer(v)); + assert_int_equal(0, json_integer_value(v)); + + v = json_object_get(audit, "partitionDN"); + assert_non_null(v); + assert_true(json_is_null(v)); + + v = json_object_get(audit, "error"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal( + "The operation completed successfully.", + json_string_value(v)); + + v = json_object_get(audit, "errorCode"); + assert_non_null(v); + assert_true(json_is_integer(v)); + assert_int_equal(0, json_integer_value(v)); + + v = json_object_get(audit, "sourceDsa"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal( + "00000000-0000-0000-0000-000000000000", + json_string_value(v)); + + v = json_object_get(audit, "invocationId"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal( + "00000000-0000-0000-0000-000000000000", + json_string_value(v)); + + json_free(&json); + TALLOC_FREE(ctx); + +} + +/* + * unit test of replicated_update_json, that ensures that all the expected + * attributes and objects are in the json object. + */ +static void test_replicated_update_json(void **state) +{ + struct ldb_context *ldb = NULL; + struct ldb_module *module = NULL; + struct ldb_request *req = NULL; + struct ldb_reply *reply = NULL; + struct audit_private *audit_private = NULL; + struct dsdb_extended_replicated_objects *ro = NULL; + struct repsFromTo1 *source_dsa = NULL; + + struct GUID transaction_id; + const char *const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773"; + + struct ldb_dn *dn = NULL; + const char *const DN = "dn=CN=USER,CN=Users,DC=SAMBA,DC=ORG"; + + struct GUID source_dsa_obj_guid; + const char *const SOURCE_DSA = "7130cb06-2062-6a1b-409e-3514c26b1793"; + + struct GUID invocation_id; + const char *const INVOCATION_ID = + "7130cb06-2062-6a1b-409e-3514c26b1893"; + struct json_object json; + json_t *audit = NULL; + json_t *v = NULL; + json_t *o = NULL; + time_t before; + struct timeval tv; + int rc; + + + TALLOC_CTX *ctx = talloc_new(NULL); + + ldb = ldb_init(ctx, NULL); + + audit_private = talloc_zero(ctx, struct audit_private); + GUID_from_string(TRANSACTION, &transaction_id); + audit_private->transaction_guid = transaction_id; + + module = talloc_zero(ctx, struct ldb_module); + module->ldb = ldb; + ldb_module_set_private(module, audit_private); + + dn = ldb_dn_new(ctx, ldb, DN); + GUID_from_string(SOURCE_DSA, &source_dsa_obj_guid); + GUID_from_string(INVOCATION_ID, &invocation_id); + source_dsa = talloc_zero(ctx, struct repsFromTo1); + source_dsa->source_dsa_obj_guid = source_dsa_obj_guid; + source_dsa->source_dsa_invocation_id = invocation_id; + + ro = talloc_zero(ctx, struct dsdb_extended_replicated_objects); + ro->source_dsa = source_dsa; + ro->num_objects = 808; + ro->linked_attributes_count = 2910; + ro->partition_dn = dn; + ro->error = WERR_NOT_SUPPORTED; + + + req = talloc_zero(ctx, struct ldb_request); + req->op.extended.data = ro; + req->operation = LDB_EXTENDED; + + reply = talloc_zero(ctx, struct ldb_reply); + reply->error = LDB_ERR_NO_SUCH_OBJECT; + + rc = gettimeofday(&tv, NULL); + assert_return_code(rc, errno); + before = tv.tv_sec; + json = replicated_update_json(module, req, reply); + assert_int_equal(3, json_object_size(json.root)); + + + v = json_object_get(json.root, "type"); + assert_non_null(v); + assert_string_equal("replicatedUpdate", json_string_value(v)); + + v = json_object_get(json.root, "timestamp"); + assert_non_null(v); + assert_true(json_is_string(v)); + check_timestamp(before, json_string_value(v)); + + audit = json_object_get(json.root, "replicatedUpdate"); + assert_non_null(audit); + assert_true(json_is_object(audit)); + assert_int_equal(11, json_object_size(audit)); + + o = json_object_get(audit, "version"); + assert_non_null(o); + check_version(o, REPLICATION_MAJOR, REPLICATION_MINOR); + + v = json_object_get(audit, "statusCode"); + assert_non_null(v); + assert_true(json_is_integer(v)); + assert_int_equal(LDB_ERR_NO_SUCH_OBJECT, json_integer_value(v)); + + v = json_object_get(audit, "status"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal("No such object", json_string_value(v)); + + v = json_object_get(audit, "transactionId"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal(TRANSACTION, json_string_value(v)); + + v = json_object_get(audit, "objectCount"); + assert_non_null(v); + assert_true(json_is_integer(v)); + assert_int_equal(808, json_integer_value(v)); + + v = json_object_get(audit, "linkCount"); + assert_non_null(v); + assert_true(json_is_integer(v)); + assert_int_equal(2910, json_integer_value(v)); + + v = json_object_get(audit, "partitionDN"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal(DN, json_string_value(v)); + + v = json_object_get(audit, "error"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal( + "The request is not supported.", + json_string_value(v)); + + v = json_object_get(audit, "errorCode"); + assert_non_null(v); + assert_true(json_is_integer(v)); + assert_int_equal(W_ERROR_V(WERR_NOT_SUPPORTED), json_integer_value(v)); + + v = json_object_get(audit, "sourceDsa"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal(SOURCE_DSA, json_string_value(v)); + + v = json_object_get(audit, "invocationId"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal(INVOCATION_ID, json_string_value(v)); + + json_free(&json); + TALLOC_FREE(ctx); + +} + +/* + * minimal unit test of operation_human_readable, that ensures that all the + * expected attributes and objects are in the json object. + */ +static void test_operation_hr_empty(void **state) +{ + struct ldb_context *ldb = NULL; + struct ldb_module *module = NULL; + struct ldb_request *req = NULL; + struct ldb_reply *reply = NULL; + struct audit_private *audit_private = NULL; + + char *line = NULL; + const char *rs = NULL; + regex_t regex; + + int ret; + + TALLOC_CTX *ctx = talloc_new(NULL); + + ldb = ldb_init(ctx, NULL); + audit_private = talloc_zero(ctx, struct audit_private); + + module = talloc_zero(ctx, struct ldb_module); + module->ldb = ldb; + ldb_module_set_private(module, audit_private); + + req = talloc_zero(ctx, struct ldb_request); + reply = talloc_zero(ctx, struct ldb_reply); + reply->error = LDB_SUCCESS; + + line = operation_human_readable(ctx, module, req, reply); + assert_non_null(line); + + /* + * We ignore the timestamp to make this test a little easier + * to write. + */ + rs = "\\[Search] at \\[" + "[^[]*" + "\\] status \\[Success\\] remote host \\[Unknown\\]" + " SID \\[(NULL SID)\\] DN \\[(null)\\]"; + + ret = regcomp(®ex, rs, 0); + assert_int_equal(0, ret); + + ret = regexec(®ex, line, 0, NULL, 0); + assert_int_equal(0, ret); + + regfree(®ex); + TALLOC_FREE(ctx); + +} + +/* + * unit test of operation_json, that ensures that all the expected + * attributes and objects are in the json object. + */ +static void test_operation_hr(void **state) +{ + struct ldb_context *ldb = NULL; + struct ldb_module *module = NULL; + struct ldb_request *req = NULL; + struct ldb_reply *reply = NULL; + struct audit_private *audit_private = NULL; + + struct tsocket_address *ts = NULL; + + struct auth_session_info *sess = NULL; + struct security_token *token = NULL; + struct dom_sid sid; + const char *const SID = "S-1-5-21-2470180966-3899876309-2637894779"; + const char * const SESSION = "7130cb06-2062-6a1b-409e-3514c26b1773"; + struct GUID session_id; + + struct GUID transaction_id; + const char *const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773"; + + struct ldb_dn *dn = NULL; + const char *const DN = "dn=CN=USER,CN=Users,DC=SAMBA,DC=ORG"; + + struct ldb_message *msg = NULL; + + char *line = NULL; + const char *rs = NULL; + regex_t regex; + + int ret; + + + TALLOC_CTX *ctx = talloc_new(NULL); + + ldb = ldb_init(ctx, NULL); + + audit_private = talloc_zero(ctx, struct audit_private); + GUID_from_string(TRANSACTION, &transaction_id); + audit_private->transaction_guid = transaction_id; + + module = talloc_zero(ctx, struct ldb_module); + module->ldb = ldb; + ldb_module_set_private(module, audit_private); + + tsocket_address_inet_from_strings(ctx, "ip", "127.0.0.1", 0, &ts); + ldb_set_opaque(ldb, "remoteAddress", ts); + + sess = talloc_zero(ctx, struct auth_session_info); + token = talloc_zero(ctx, struct security_token); + string_to_sid(&sid, SID); + token->num_sids = 1; + token->sids = &sid; + sess->security_token = token; + GUID_from_string(SESSION, &session_id); + sess->unique_session_token = session_id; + ldb_set_opaque(ldb, DSDB_SESSION_INFO, sess); + + msg = talloc_zero(ctx, struct ldb_message); + dn = ldb_dn_new(ctx, ldb, DN); + msg->dn = dn; + ldb_msg_add_string(msg, "attribute", "the-value"); + + req = talloc_zero(ctx, struct ldb_request); + req->operation = LDB_ADD; + req->op.add.message = msg; + reply = talloc_zero(ctx, struct ldb_reply); + reply->error = LDB_SUCCESS; + + line = operation_human_readable(ctx, module, req, reply); + assert_non_null(line); + + /* + * We ignore the timestamp to make this test a little easier + * to write. + */ + rs = "\\[Add\\] at \\[" + "[^]]*" + "\\] status \\[Success\\] " + "remote host \\[ipv4:127.0.0.1:0\\] " + "SID \\[S-1-5-21-2470180966-3899876309-2637894779\\] " + "DN \\[dn=CN=USER,CN=Users,DC=SAMBA,DC=ORG\\] " + "attributes \\[attribute \\[the-value\\]\\]"; + + ret = regcomp(®ex, rs, 0); + assert_int_equal(0, ret); + + ret = regexec(®ex, line, 0, NULL, 0); + assert_int_equal(0, ret); + + regfree(®ex); + TALLOC_FREE(ctx); +} + +/* + * unit test of operation_json, that ensures that all the expected + * attributes and objects are in the json object. + * In this case the operation is being performed in a system session. + */ +static void test_as_system_operation_hr(void **state) +{ + struct ldb_context *ldb = NULL; + struct ldb_module *module = NULL; + struct ldb_request *req = NULL; + struct ldb_reply *reply = NULL; + struct audit_private *audit_private = NULL; + + struct tsocket_address *ts = NULL; + + struct auth_session_info *sess = NULL; + struct auth_session_info *sys_sess = NULL; + struct security_token *token = NULL; + struct security_token *sys_token = NULL; + struct dom_sid sid; + const char *const SID = "S-1-5-21-2470180966-3899876309-2637894779"; + const char * const SESSION = "7130cb06-2062-6a1b-409e-3514c26b1773"; + const char * const SYS_SESSION = "7130cb06-2062-6a1b-409e-3514c26b1999"; + struct GUID session_id; + struct GUID sys_session_id; + + struct GUID transaction_id; + const char *const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773"; + + struct ldb_dn *dn = NULL; + const char *const DN = "dn=CN=USER,CN=Users,DC=SAMBA,DC=ORG"; + + struct ldb_message *msg = NULL; + + char *line = NULL; + const char *rs = NULL; + regex_t regex; + + int ret; + + + TALLOC_CTX *ctx = talloc_new(NULL); + + ldb = ldb_init(ctx, NULL); + + audit_private = talloc_zero(ctx, struct audit_private); + GUID_from_string(TRANSACTION, &transaction_id); + audit_private->transaction_guid = transaction_id; + + module = talloc_zero(ctx, struct ldb_module); + module->ldb = ldb; + ldb_module_set_private(module, audit_private); + + tsocket_address_inet_from_strings(ctx, "ip", "127.0.0.1", 0, &ts); + ldb_set_opaque(ldb, "remoteAddress", ts); + + sess = talloc_zero(ctx, struct auth_session_info); + token = talloc_zero(ctx, struct security_token); + string_to_sid(&sid, SID); + token->num_sids = 1; + token->sids = &sid; + sess->security_token = token; + GUID_from_string(SESSION, &session_id); + sess->unique_session_token = session_id; + ldb_set_opaque(ldb, DSDB_NETWORK_SESSION_INFO, sess); + + sys_sess = talloc_zero(ctx, struct auth_session_info); + sys_token = talloc_zero(ctx, struct security_token); + sys_token->num_sids = 1; + sys_token->sids = discard_const(&global_sid_System); + sys_sess->security_token = sys_token; + GUID_from_string(SYS_SESSION, &sys_session_id); + sess->unique_session_token = sys_session_id; + ldb_set_opaque(ldb, DSDB_SESSION_INFO, sys_sess); + + msg = talloc_zero(ctx, struct ldb_message); + dn = ldb_dn_new(ctx, ldb, DN); + msg->dn = dn; + ldb_msg_add_string(msg, "attribute", "the-value"); + + req = talloc_zero(ctx, struct ldb_request); + req->operation = LDB_ADD; + req->op.add.message = msg; + reply = talloc_zero(ctx, struct ldb_reply); + reply->error = LDB_SUCCESS; + + line = operation_human_readable(ctx, module, req, reply); + assert_non_null(line); + + /* + * We ignore the timestamp to make this test a little easier + * to write. + */ + rs = "\\[Add\\] at \\[" + "[^]]*" + "\\] status \\[Success\\] " + "remote host \\[ipv4:127.0.0.1:0\\] " + "SID \\[S-1-5-21-2470180966-3899876309-2637894779\\] " + "DN \\[dn=CN=USER,CN=Users,DC=SAMBA,DC=ORG\\] " + "attributes \\[attribute \\[the-value\\]\\]"; + + ret = regcomp(®ex, rs, 0); + assert_int_equal(0, ret); + + ret = regexec(®ex, line, 0, NULL, 0); + assert_int_equal(0, ret); + + regfree(®ex); + TALLOC_FREE(ctx); +} + +/* + * minimal unit test of password_change_json, that ensures that all the expected + * attributes and objects are in the json object. + */ +static void test_password_change_hr_empty(void **state) +{ + struct ldb_context *ldb = NULL; + struct ldb_module *module = NULL; + struct ldb_request *req = NULL; + struct ldb_reply *reply = NULL; + struct audit_private *audit_private = NULL; + + char *line = NULL; + const char *rs = NULL; + regex_t regex; + int ret; + + TALLOC_CTX *ctx = talloc_new(NULL); + + ldb = ldb_init(ctx, NULL); + audit_private = talloc_zero(ctx, struct audit_private); + + module = talloc_zero(ctx, struct ldb_module); + module->ldb = ldb; + ldb_module_set_private(module, audit_private); + + req = talloc_zero(ctx, struct ldb_request); + reply = talloc_zero(ctx, struct ldb_reply); + reply->error = LDB_SUCCESS; + + line = password_change_human_readable(ctx, module, req, reply); + assert_non_null(line); + + /* + * We ignore the timestamp to make this test a little easier + * to write. + */ + rs = "\\[Reset] at \\[" + "[^[]*" + "\\] status \\[Success\\] remote host \\[Unknown\\]" + " SID \\[(NULL SID)\\] DN \\[(null)\\]"; + + ret = regcomp(®ex, rs, 0); + assert_int_equal(0, ret); + + ret = regexec(®ex, line, 0, NULL, 0); + assert_int_equal(0, ret); + + regfree(®ex); + TALLOC_FREE(ctx); +} + +/* + * minimal unit test of password_change_json, that ensures that all the expected + * attributes and objects are in the json object. + */ +static void test_password_change_hr(void **state) +{ + struct ldb_context *ldb = NULL; + struct ldb_module *module = NULL; + struct ldb_request *req = NULL; + struct ldb_reply *reply = NULL; + struct audit_private *audit_private = NULL; + + struct tsocket_address *ts = NULL; + + struct auth_session_info *sess = NULL; + struct security_token *token = NULL; + struct dom_sid sid; + const char *const SID = "S-1-5-21-2470180966-3899876309-2637894779"; + const char * const SESSION = "7130cb06-2062-6a1b-409e-3514c26b1773"; + struct GUID session_id; + + struct GUID transaction_id; + const char *const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773"; + + struct ldb_dn *dn = NULL; + const char *const DN = "dn=CN=USER,CN=Users,DC=SAMBA,DC=ORG"; + + struct ldb_message *msg = NULL; + + char *line = NULL; + const char *rs = NULL; + regex_t regex; + int ret; + + TALLOC_CTX *ctx = talloc_new(NULL); + + ldb = ldb_init(ctx, NULL); + + audit_private = talloc_zero(ctx, struct audit_private); + GUID_from_string(TRANSACTION, &transaction_id); + audit_private->transaction_guid = transaction_id; + + module = talloc_zero(ctx, struct ldb_module); + module->ldb = ldb; + ldb_module_set_private(module, audit_private); + + tsocket_address_inet_from_strings(ctx, "ip", "127.0.0.1", 0, &ts); + ldb_set_opaque(ldb, "remoteAddress", ts); + + sess = talloc_zero(ctx, struct auth_session_info); + token = talloc_zero(ctx, struct security_token); + string_to_sid(&sid, SID); + token->num_sids = 1; + token->sids = &sid; + sess->security_token = token; + GUID_from_string(SESSION, &session_id); + sess->unique_session_token = session_id; + ldb_set_opaque(ldb, DSDB_SESSION_INFO, sess); + + msg = talloc_zero(ctx, struct ldb_message); + dn = ldb_dn_new(ctx, ldb, DN); + msg->dn = dn; + ldb_msg_add_string(msg, "planTextPassword", "super-secret"); + + req = talloc_zero(ctx, struct ldb_request); + req->operation = LDB_ADD; + req->op.add.message = msg; + reply = talloc_zero(ctx, struct ldb_reply); + reply->error = LDB_SUCCESS; + + line = password_change_human_readable(ctx, module, req, reply); + assert_non_null(line); + + /* + * We ignore the timestamp to make this test a little easier + * to write. + */ + rs = "\\[Reset\\] at \\[" + "[^[]*" + "\\] status \\[Success\\] " + "remote host \\[ipv4:127.0.0.1:0\\] " + "SID \\[S-1-5-21-2470180966-3899876309-2637894779\\] " + "DN \\[dn=CN=USER,CN=Users,DC=SAMBA,DC=ORG\\]"; + + ret = regcomp(®ex, rs, 0); + assert_int_equal(0, ret); + + ret = regexec(®ex, line, 0, NULL, 0); + assert_int_equal(0, ret); + + regfree(®ex); + TALLOC_FREE(ctx); + +} + +/* + * minimal unit test of transaction_json, that ensures that all the expected + * attributes and objects are in the json object. + */ +static void test_transaction_hr(void **state) +{ + + struct GUID guid; + const char * const GUID = "7130cb06-2062-6a1b-409e-3514c26b1773"; + + char *line = NULL; + const char *rs = NULL; + regex_t regex; + int ret; + + TALLOC_CTX *ctx = talloc_new(NULL); + + GUID_from_string(GUID, &guid); + + line = transaction_human_readable(ctx, "delete", 23); + assert_non_null(line); + + /* + * We ignore the timestamp to make this test a little easier + * to write. + */ + rs = "\\[delete] at \\[[^[]*\\] duration \\[23\\]"; + + ret = regcomp(®ex, rs, 0); + assert_int_equal(0, ret); + + ret = regexec(®ex, line, 0, NULL, 0); + assert_int_equal(0, ret); + + regfree(®ex); + TALLOC_FREE(ctx); + +} + +/* + * minimal unit test of commit_failure_hr, that ensures + * that all the expected conten is in the log entry. + */ +static void test_commit_failure_hr(void **state) +{ + + struct GUID guid; + const char * const GUID = "7130cb06-2062-6a1b-409e-3514c26b1773"; + + char *line = NULL; + const char *rs = NULL; + regex_t regex; + int ret; + + TALLOC_CTX *ctx = talloc_new(NULL); + + GUID_from_string(GUID, &guid); + + line = commit_failure_human_readable( + ctx, + "commit", + 789345, + LDB_ERR_OPERATIONS_ERROR, + "because"); + + assert_non_null(line); + + /* + * We ignore the timestamp to make this test a little easier + * to write. + */ + rs = "\\[commit\\] at \\[[^[]*\\] duration \\[789345\\] " + "status \\[1\\] reason \\[because\\]"; + + ret = regcomp(®ex, rs, 0); + assert_int_equal(0, ret); + + ret = regexec(®ex, line, 0, NULL, 0); + assert_int_equal(0, ret); + + regfree(®ex); + TALLOC_FREE(ctx); +} + +static void test_add_transaction_id(void **state) +{ + struct ldb_module *module = NULL; + struct ldb_request *req = NULL; + struct audit_private *audit_private = NULL; + struct GUID guid; + const char * const GUID = "7130cb06-2062-6a1b-409e-3514c26b1773"; + struct ldb_control * control = NULL; + int status; + + TALLOC_CTX *ctx = talloc_new(NULL); + + audit_private = talloc_zero(ctx, struct audit_private); + GUID_from_string(GUID, &guid); + audit_private->transaction_guid = guid; + + module = talloc_zero(ctx, struct ldb_module); + ldb_module_set_private(module, audit_private); + + req = talloc_zero(ctx, struct ldb_request); + + status = add_transaction_id(module, req); + assert_int_equal(LDB_SUCCESS, status); + + control = ldb_request_get_control( + req, + DSDB_CONTROL_TRANSACTION_IDENTIFIER_OID); + assert_non_null(control); + assert_memory_equal( + &audit_private->transaction_guid, + control->data, + sizeof(struct GUID)); + + TALLOC_FREE(ctx); +} + +static void test_log_attributes(void **state) +{ + struct ldb_message *msg = NULL; + + char *buf = NULL; + char *str = NULL; + char lv[MAX_LENGTH+2]; + char ex[MAX_LENGTH+80]; + + TALLOC_CTX *ctx = talloc_new(NULL); + + + /* + * Test an empty message + * Should get empty attributes representation. + */ + buf = talloc_zero(ctx, char); + msg = talloc_zero(ctx, struct ldb_message); + + str = log_attributes(ctx, buf, LDB_ADD, msg); + assert_string_equal("", str); + + TALLOC_FREE(str); + TALLOC_FREE(msg); + + /* + * Test a message with a single secret attribute + */ + buf = talloc_zero(ctx, char); + msg = talloc_zero(ctx, struct ldb_message); + ldb_msg_add_string(msg, "clearTextPassword", "secret"); + + str = log_attributes(ctx, buf, LDB_ADD, msg); + assert_string_equal( + "clearTextPassword [REDACTED SECRET ATTRIBUTE]", + str); + TALLOC_FREE(str); + /* + * Test as a modify message, should add an action + * action will be unknown as there are no ACL's set + */ + buf = talloc_zero(ctx, char); + str = log_attributes(ctx, buf, LDB_MODIFY, msg); + assert_string_equal( + "unknown: clearTextPassword [REDACTED SECRET ATTRIBUTE]", + str); + + TALLOC_FREE(str); + TALLOC_FREE(msg); + + /* + * Test a message with a single attribute, single valued attribute + */ + buf = talloc_zero(ctx, char); + msg = talloc_zero(ctx, struct ldb_message); + ldb_msg_add_string(msg, "attribute", "value"); + + str = log_attributes(ctx, buf, LDB_ADD, msg); + assert_string_equal( + "attribute [value]", + str); + + TALLOC_FREE(str); + TALLOC_FREE(msg); + + /* + * Test a message with a single attribute, single valued attribute + * And as a modify + */ + buf = talloc_zero(ctx, char); + msg = talloc_zero(ctx, struct ldb_message); + ldb_msg_add_string(msg, "attribute", "value"); + + str = log_attributes(ctx, buf, LDB_MODIFY, msg); + assert_string_equal( + "unknown: attribute [value]", + str); + + TALLOC_FREE(str); + TALLOC_FREE(msg); + + /* + * Test a message with multiple attributes and a multi-valued attribute + * + */ + buf = talloc_zero(ctx, char); + msg = talloc_zero(ctx, struct ldb_message); + ldb_msg_add_string(msg, "attribute01", "value01"); + ldb_msg_add_string(msg, "attribute02", "value02"); + ldb_msg_add_string(msg, "attribute02", "value03"); + + str = log_attributes(ctx, buf, LDB_MODIFY, msg); + assert_string_equal( + "unknown: attribute01 [value01] " + "unknown: attribute02 [value02] [value03]", + str); + + TALLOC_FREE(str); + TALLOC_FREE(msg); + + /* + * Test a message with a single attribute, single valued attribute + * with a non printable character. Should be base64 encoded + */ + buf = talloc_zero(ctx, char); + msg = talloc_zero(ctx, struct ldb_message); + ldb_msg_add_string(msg, "attribute", "value\n"); + + str = log_attributes(ctx, buf, LDB_ADD, msg); + assert_string_equal("attribute {dmFsdWUK}", str); + + TALLOC_FREE(str); + TALLOC_FREE(msg); + + /* + * Test a message with a single valued attribute + * with more than MAX_LENGTH characters, should be truncated with + * trailing ... + */ + buf = talloc_zero(ctx, char); + msg = talloc_zero(ctx, struct ldb_message); + memset(lv, '\0', sizeof(lv)); + memset(lv, 'x', MAX_LENGTH+1); + ldb_msg_add_string(msg, "attribute", lv); + + str = log_attributes(ctx, buf, LDB_ADD, msg); + snprintf(ex, sizeof(ex), "attribute [%.*s...]", MAX_LENGTH, lv); + assert_string_equal(ex, str); + + TALLOC_FREE(str); + TALLOC_FREE(msg); + + TALLOC_FREE(ctx); +} + +/* + * minimal unit test of replicated_update_human_readable + */ +static void test_replicated_update_hr_empty(void **state) +{ + struct ldb_context *ldb = NULL; + struct ldb_module *module = NULL; + struct ldb_request *req = NULL; + struct ldb_reply *reply = NULL; + struct audit_private *audit_private = NULL; + struct dsdb_extended_replicated_objects *ro = NULL; + struct repsFromTo1 *source_dsa = NULL; + + const char* line = NULL; + const char *rs = NULL; + regex_t regex; + int ret; + + TALLOC_CTX *ctx = talloc_new(NULL); + + ldb = ldb_init(ctx, NULL); + audit_private = talloc_zero(ctx, struct audit_private); + + module = talloc_zero(ctx, struct ldb_module); + module->ldb = ldb; + ldb_module_set_private(module, audit_private); + + source_dsa = talloc_zero(ctx, struct repsFromTo1); + ro = talloc_zero(ctx, struct dsdb_extended_replicated_objects); + ro->source_dsa = source_dsa; + req = talloc_zero(ctx, struct ldb_request); + req->op.extended.data = ro; + req->operation = LDB_EXTENDED; + reply = talloc_zero(ctx, struct ldb_reply); + reply->error = LDB_SUCCESS; + + line = replicated_update_human_readable(ctx, module, req, reply); + assert_non_null(line); + /* + * We ignore the timestamp to make this test a little easier + * to write. + */ + rs = "at \\[[^[]*\\] " + "status \\[Success\\] " + "error \\[The operation completed successfully.\\] " + "partition \\[(null)\\] objects \\[0\\] links \\[0\\] " + "object \\[00000000-0000-0000-0000-000000000000\\] " + "invocation \\[00000000-0000-0000-0000-000000000000\\]"; + + ret = regcomp(®ex, rs, 0); + assert_int_equal(0, ret); + + ret = regexec(®ex, line, 0, NULL, 0); + assert_int_equal(0, ret); + + regfree(®ex); + TALLOC_FREE(ctx); + +} + +/* + * unit test of replicated_update_human_readable + */ +static void test_replicated_update_hr(void **state) +{ + struct ldb_context *ldb = NULL; + struct ldb_module *module = NULL; + struct ldb_request *req = NULL; + struct ldb_reply *reply = NULL; + struct audit_private *audit_private = NULL; + struct dsdb_extended_replicated_objects *ro = NULL; + struct repsFromTo1 *source_dsa = NULL; + + struct GUID transaction_id; + const char *const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773"; + + struct ldb_dn *dn = NULL; + const char *const DN = "dn=CN=USER,CN=Users,DC=SAMBA,DC=ORG"; + + struct GUID source_dsa_obj_guid; + const char *const SOURCE_DSA = "7130cb06-2062-6a1b-409e-3514c26b1793"; + + struct GUID invocation_id; + const char *const INVOCATION_ID = + "7130cb06-2062-6a1b-409e-3514c26b1893"; + + const char* line = NULL; + const char *rs = NULL; + regex_t regex; + int ret; + + + TALLOC_CTX *ctx = talloc_new(NULL); + + ldb = ldb_init(ctx, NULL); + + audit_private = talloc_zero(ctx, struct audit_private); + GUID_from_string(TRANSACTION, &transaction_id); + audit_private->transaction_guid = transaction_id; + + module = talloc_zero(ctx, struct ldb_module); + module->ldb = ldb; + ldb_module_set_private(module, audit_private); + + dn = ldb_dn_new(ctx, ldb, DN); + GUID_from_string(SOURCE_DSA, &source_dsa_obj_guid); + GUID_from_string(INVOCATION_ID, &invocation_id); + source_dsa = talloc_zero(ctx, struct repsFromTo1); + source_dsa->source_dsa_obj_guid = source_dsa_obj_guid; + source_dsa->source_dsa_invocation_id = invocation_id; + + ro = talloc_zero(ctx, struct dsdb_extended_replicated_objects); + ro->source_dsa = source_dsa; + ro->num_objects = 808; + ro->linked_attributes_count = 2910; + ro->partition_dn = dn; + ro->error = WERR_NOT_SUPPORTED; + + + req = talloc_zero(ctx, struct ldb_request); + req->op.extended.data = ro; + req->operation = LDB_EXTENDED; + + reply = talloc_zero(ctx, struct ldb_reply); + reply->error = LDB_ERR_NO_SUCH_OBJECT; + + line = replicated_update_human_readable(ctx, module, req, reply); + assert_non_null(line); + + /* + * We ignore the timestamp to make this test a little easier + * to write. + */ + rs = "at \\[[^[]*\\] " + "status \\[No such object\\] " + "error \\[The request is not supported.\\] " + "partition \\[dn=CN=USER,CN=Users,DC=SAMBA,DC=ORG\\] " + "objects \\[808\\] links \\[2910\\] " + "object \\[7130cb06-2062-6a1b-409e-3514c26b1793\\] " + "invocation \\[7130cb06-2062-6a1b-409e-3514c26b1893\\]"; + + ret = regcomp(®ex, rs, 0); + assert_int_equal(0, ret); + + ret = regexec(®ex, line, 0, NULL, 0); + assert_int_equal(0, ret); + + regfree(®ex); + TALLOC_FREE(ctx); +} + +int main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_has_password_changed), + cmocka_unit_test(test_get_password_action), + cmocka_unit_test(test_operation_json_empty), + cmocka_unit_test(test_operation_json), + cmocka_unit_test(test_as_system_operation_json), + cmocka_unit_test(test_password_change_json_empty), + cmocka_unit_test(test_password_change_json), + cmocka_unit_test(test_transaction_json), + cmocka_unit_test(test_commit_failure_json), + cmocka_unit_test(test_replicated_update_json_empty), + cmocka_unit_test(test_replicated_update_json), + cmocka_unit_test(test_add_transaction_id), + cmocka_unit_test(test_operation_hr_empty), + cmocka_unit_test(test_operation_hr), + cmocka_unit_test(test_as_system_operation_hr), + cmocka_unit_test(test_password_change_hr_empty), + cmocka_unit_test(test_password_change_hr), + cmocka_unit_test(test_transaction_hr), + cmocka_unit_test(test_commit_failure_hr), + cmocka_unit_test(test_log_attributes), + cmocka_unit_test(test_replicated_update_hr_empty), + cmocka_unit_test(test_replicated_update_hr), + }; + + cmocka_set_message_output(CM_OUTPUT_SUBUNIT); + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/source4/dsdb/samdb/ldb_modules/tests/test_audit_log_errors.c b/source4/dsdb/samdb/ldb_modules/tests/test_audit_log_errors.c new file mode 100644 index 0000000..2931768 --- /dev/null +++ b/source4/dsdb/samdb/ldb_modules/tests/test_audit_log_errors.c @@ -0,0 +1,639 @@ +/* + Unit tests for the dsdb audit logging code code in audit_log.c + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2018 + + 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/>. +*/ + +/* + * These tests exercise the error handling code + */ + +#include <stdarg.h> +#include <stddef.h> +#include <setjmp.h> +#include <unistd.h> +#include <cmocka.h> + +int ldb_audit_log_module_init(const char *version); +#include "../audit_log.c" +#include "lib/ldb/include/ldb_private.h" + +/* + * cmocka wrappers for json_new_object + */ +struct json_object __wrap_json_new_object(void); +struct json_object __real_json_new_object(void); +struct json_object __wrap_json_new_object(void) +{ + + bool use_real = (bool)mock(); + if (!use_real) { + return json_empty_object; + } + return __real_json_new_object(); +} + +/* + * cmocka wrappers for json_add_version + */ +int __wrap_json_add_version(struct json_object *object, int major, int minor); +int __real_json_add_version(struct json_object *object, int major, int minor); +int __wrap_json_add_version(struct json_object *object, int major, int minor) +{ + + int ret = (int)mock(); + if (ret) { + return ret; + } + return __real_json_add_version(object, major, minor); +} + +/* + * cmocka wrappers for json_add_version + */ +int __wrap_json_add_timestamp(struct json_object *object); +int __real_json_add_timestamp(struct json_object *object); +int __wrap_json_add_timestamp(struct json_object *object) +{ + + int ret = (int)mock(); + if (ret) { + return ret; + } + return __real_json_add_timestamp(object); +} +/* + * unit test of operation_json, that ensures that all the expected + * attributes and objects are in the json object. + */ +static void test_operation_json(void **state) +{ + struct ldb_context *ldb = NULL; + struct ldb_module *module = NULL; + struct ldb_request *req = NULL; + struct ldb_reply *reply = NULL; + struct audit_private *audit_private = NULL; + + struct tsocket_address *ts = NULL; + + struct auth_session_info *sess = NULL; + struct security_token *token = NULL; + struct dom_sid sid; + const char *const SID = "S-1-5-21-2470180966-3899876309-2637894779"; + const char * const SESSION = "7130cb06-2062-6a1b-409e-3514c26b1773"; + struct GUID session_id; + + struct GUID transaction_id; + const char *const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773"; + + struct ldb_dn *dn = NULL; + const char *const DN = "dn=CN=USER,CN=Users,DC=SAMBA,DC=ORG"; + + struct ldb_message *msg = NULL; + + struct json_object json; + + + /* + * Test setup + */ + TALLOC_CTX *ctx = talloc_new(NULL); + + ldb = ldb_init(ctx, NULL); + + audit_private = talloc_zero(ctx, struct audit_private); + GUID_from_string(TRANSACTION, &transaction_id); + audit_private->transaction_guid = transaction_id; + + module = talloc_zero(ctx, struct ldb_module); + module->ldb = ldb; + ldb_module_set_private(module, audit_private); + + tsocket_address_inet_from_strings(ctx, "ip", "127.0.0.1", 0, &ts); + ldb_set_opaque(ldb, "remoteAddress", ts); + + sess = talloc_zero(ctx, struct auth_session_info); + token = talloc_zero(ctx, struct security_token); + string_to_sid(&sid, SID); + token->num_sids = 1; + token->sids = &sid; + sess->security_token = token; + GUID_from_string(SESSION, &session_id); + sess->unique_session_token = session_id; + ldb_set_opaque(ldb, DSDB_SESSION_INFO, sess); + + msg = talloc_zero(ctx, struct ldb_message); + dn = ldb_dn_new(ctx, ldb, DN); + msg->dn = dn; + ldb_msg_add_string(msg, "attribute", "the-value"); + + req = talloc_zero(ctx, struct ldb_request); + req->operation = LDB_ADD; + req->op.add.message = msg; + + reply = talloc_zero(ctx, struct ldb_reply); + reply->error = LDB_ERR_OPERATIONS_ERROR; + + /* + * Fail on the creation of the audit json object + */ + + will_return(__wrap_json_new_object, false); + + json = operation_json(module, req, reply); + assert_true(json_is_invalid(&json)); + + /* + * Fail adding the version object . + */ + + will_return(__wrap_json_new_object, true); + will_return(__wrap_json_add_version, JSON_ERROR); + + json = operation_json(module, req, reply); + assert_true(json_is_invalid(&json)); + + /* + * Fail on creation of the wrapper. + */ + + will_return(__wrap_json_new_object, true); + will_return(__wrap_json_add_version, 0); + will_return(__wrap_json_new_object, true); + will_return(__wrap_json_new_object, true); + will_return(__wrap_json_new_object, true); + will_return(__wrap_json_new_object, false); + + json = operation_json(module, req, reply); + assert_true(json_is_invalid(&json)); + + /* + * Fail adding the timestamp to the wrapper object. + */ + will_return(__wrap_json_new_object, true); + will_return(__wrap_json_add_version, 0); + will_return(__wrap_json_new_object, true); + will_return(__wrap_json_new_object, true); + will_return(__wrap_json_new_object, true); + will_return(__wrap_json_new_object, true); + will_return(__wrap_json_add_timestamp, JSON_ERROR); + + json = operation_json(module, req, reply); + assert_true(json_is_invalid(&json)); + + /* + * Now test the happy path + */ + will_return(__wrap_json_new_object, true); + will_return(__wrap_json_add_version, 0); + will_return(__wrap_json_new_object, true); + will_return(__wrap_json_new_object, true); + will_return(__wrap_json_new_object, true); + will_return(__wrap_json_new_object, true); + will_return(__wrap_json_add_timestamp, 0); + + json = operation_json(module, req, reply); + assert_false(json_is_invalid(&json)); + json_free(&json); + + TALLOC_FREE(ctx); + +} + +/* + * minimal unit test of password_change_json, that ensures that all the expected + * attributes and objects are in the json object. + */ +static void test_password_change_json(void **state) +{ + struct ldb_context *ldb = NULL; + struct ldb_module *module = NULL; + struct ldb_request *req = NULL; + struct ldb_reply *reply = NULL; + struct audit_private *audit_private = NULL; + + struct tsocket_address *ts = NULL; + + struct auth_session_info *sess = NULL; + struct security_token *token = NULL; + struct dom_sid sid; + const char *const SID = "S-1-5-21-2470180966-3899876309-2637894779"; + const char * const SESSION = "7130cb06-2062-6a1b-409e-3514c26b1773"; + struct GUID session_id; + + struct GUID transaction_id; + const char *const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773"; + + struct ldb_dn *dn = NULL; + const char *const DN = "dn=CN=USER,CN=Users,DC=SAMBA,DC=ORG"; + + struct ldb_message *msg = NULL; + + struct json_object json; + + TALLOC_CTX *ctx = talloc_new(NULL); + + ldb = ldb_init(ctx, NULL); + + audit_private = talloc_zero(ctx, struct audit_private); + GUID_from_string(TRANSACTION, &transaction_id); + audit_private->transaction_guid = transaction_id; + + module = talloc_zero(ctx, struct ldb_module); + module->ldb = ldb; + ldb_module_set_private(module, audit_private); + + tsocket_address_inet_from_strings(ctx, "ip", "127.0.0.1", 0, &ts); + ldb_set_opaque(ldb, "remoteAddress", ts); + + sess = talloc_zero(ctx, struct auth_session_info); + token = talloc_zero(ctx, struct security_token); + string_to_sid(&sid, SID); + token->num_sids = 1; + token->sids = &sid; + sess->security_token = token; + GUID_from_string(SESSION, &session_id); + sess->unique_session_token = session_id; + ldb_set_opaque(ldb, DSDB_SESSION_INFO, sess); + + msg = talloc_zero(ctx, struct ldb_message); + dn = ldb_dn_new(ctx, ldb, DN); + msg->dn = dn; + ldb_msg_add_string(msg, "planTextPassword", "super-secret"); + + req = talloc_zero(ctx, struct ldb_request); + req->operation = LDB_ADD; + req->op.add.message = msg; + reply = talloc_zero(ctx, struct ldb_reply); + reply->error = LDB_SUCCESS; + + + /* + * Fail on the creation of the audit json object + */ + + will_return(__wrap_json_new_object, false); + json = password_change_json(module, req, reply); + + assert_true(json_is_invalid(&json)); + + /* + * Fail adding the version object . + */ + + will_return(__wrap_json_new_object, true); + will_return(__wrap_json_add_version, JSON_ERROR); + + json = password_change_json(module, req, reply); + assert_true(json_is_invalid(&json)); + + /* + * Fail on creation of the wrapper. + */ + + will_return(__wrap_json_new_object, true); + will_return(__wrap_json_add_version, 0); + will_return(__wrap_json_new_object, false); + + json = password_change_json(module, req, reply); + assert_true(json_is_invalid(&json)); + + /* + * Fail on creation of the time stamp. + */ + + will_return(__wrap_json_new_object, true); + will_return(__wrap_json_add_version, 0); + will_return(__wrap_json_new_object, true); + will_return(__wrap_json_add_timestamp, JSON_ERROR); + + json = password_change_json(module, req, reply); + assert_true(json_is_invalid(&json)); + + /* + * Now test the happy path + */ + will_return(__wrap_json_new_object, true); + will_return(__wrap_json_add_version, 0); + will_return(__wrap_json_new_object, true); + will_return(__wrap_json_add_timestamp, 0); + + json = password_change_json(module, req, reply); + assert_false(json_is_invalid(&json)); + json_free(&json); + + TALLOC_FREE(ctx); +} + + +/* + * minimal unit test of transaction_json, that ensures that all the expected + * attributes and objects are in the json object. + */ +static void test_transaction_json(void **state) +{ + + struct GUID guid; + const char * const GUID = "7130cb06-2062-6a1b-409e-3514c26b1773"; + + struct json_object json; + + GUID_from_string(GUID, &guid); + + + /* + * Fail on the creation of the audit json object + */ + + will_return(__wrap_json_new_object, false); + + json = transaction_json("delete", &guid, 10000099); + assert_true(json_is_invalid(&json)); + + /* + * Fail adding the version object . + */ + + will_return(__wrap_json_new_object, true); + will_return(__wrap_json_add_version, JSON_ERROR); + + json = transaction_json("delete", &guid, 10000099); + assert_true(json_is_invalid(&json)); + + /* + * Fail on creation of the wrapper. + */ + + will_return(__wrap_json_new_object, true); + will_return(__wrap_json_add_version, 0); + will_return(__wrap_json_new_object, false); + + json = transaction_json("delete", &guid, 10000099); + assert_true(json_is_invalid(&json)); + + /* + * Fail on creation of the time stamp. + */ + + will_return(__wrap_json_new_object, true); + will_return(__wrap_json_add_version, 0); + will_return(__wrap_json_new_object, true); + will_return(__wrap_json_add_timestamp, JSON_ERROR); + + json = transaction_json("delete", &guid, 10000099); + assert_true(json_is_invalid(&json)); + + /* + * Now test the happy path + */ + will_return(__wrap_json_new_object, true); + will_return(__wrap_json_add_version, 0); + will_return(__wrap_json_new_object, true); + will_return(__wrap_json_add_timestamp, 0); + + json = transaction_json("delete", &guid, 10000099); + assert_false(json_is_invalid(&json)); + json_free(&json); +} + +/* + * minimal unit test of commit_failure_json, that ensures that all the + * expected attributes and objects are in the json object. + */ +static void test_commit_failure_json(void **state) +{ + + struct GUID guid; + const char * const GUID = "7130cb06-2062-6a1b-409e-3514c26b1773"; + + struct json_object json; + + GUID_from_string(GUID, &guid); + + + /* + * Fail on the creation of the audit json object + */ + + will_return(__wrap_json_new_object, false); + + json = commit_failure_json( + "prepare", + 987876, + LDB_ERR_OPERATIONS_ERROR, + "because", + &guid); + assert_true(json_is_invalid(&json)); + + /* + * Fail adding the version object . + */ + + will_return(__wrap_json_new_object, true); + will_return(__wrap_json_add_version, JSON_ERROR); + + json = commit_failure_json( + "prepare", + 987876, + LDB_ERR_OPERATIONS_ERROR, + "because", + &guid); + assert_true(json_is_invalid(&json)); + + /* + * Fail on creation of the wrapper. + */ + + will_return(__wrap_json_new_object, true); + will_return(__wrap_json_add_version, 0); + will_return(__wrap_json_new_object, false); + + json = commit_failure_json( + "prepare", + 987876, + LDB_ERR_OPERATIONS_ERROR, + "because", + &guid); + assert_true(json_is_invalid(&json)); + + /* + * Fail on creation of the time stamp. + */ + + will_return(__wrap_json_new_object, true); + will_return(__wrap_json_add_version, 0); + will_return(__wrap_json_new_object, true); + will_return(__wrap_json_add_timestamp, JSON_ERROR); + + json = commit_failure_json( + "prepare", + 987876, + LDB_ERR_OPERATIONS_ERROR, + "because", + &guid); + assert_true(json_is_invalid(&json)); + + /* + * Now test the happy path + */ + + will_return(__wrap_json_new_object, true); + will_return(__wrap_json_add_version, 0); + will_return(__wrap_json_new_object, true); + will_return(__wrap_json_add_timestamp, 0); + + json = commit_failure_json( + "prepare", + 987876, + LDB_ERR_OPERATIONS_ERROR, + "because", + &guid); + assert_false(json_is_invalid(&json)); + json_free(&json); +} + +/* + * unit test of replicated_update_json, that ensures that all the expected + * attributes and objects are in the json object. + */ +static void test_replicated_update_json(void **state) +{ + struct ldb_context *ldb = NULL; + struct ldb_module *module = NULL; + struct ldb_request *req = NULL; + struct ldb_reply *reply = NULL; + struct audit_private *audit_private = NULL; + struct dsdb_extended_replicated_objects *ro = NULL; + struct repsFromTo1 *source_dsa = NULL; + + struct GUID transaction_id; + const char *const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773"; + + struct ldb_dn *dn = NULL; + const char *const DN = "dn=CN=USER,CN=Users,DC=SAMBA,DC=ORG"; + + struct GUID source_dsa_obj_guid; + const char *const SOURCE_DSA = "7130cb06-2062-6a1b-409e-3514c26b1793"; + + struct GUID invocation_id; + const char *const INVOCATION_ID = + "7130cb06-2062-6a1b-409e-3514c26b1893"; + struct json_object json; + + TALLOC_CTX *ctx = talloc_new(NULL); + + ldb = ldb_init(ctx, NULL); + + audit_private = talloc_zero(ctx, struct audit_private); + GUID_from_string(TRANSACTION, &transaction_id); + audit_private->transaction_guid = transaction_id; + + module = talloc_zero(ctx, struct ldb_module); + module->ldb = ldb; + ldb_module_set_private(module, audit_private); + + dn = ldb_dn_new(ctx, ldb, DN); + GUID_from_string(SOURCE_DSA, &source_dsa_obj_guid); + GUID_from_string(INVOCATION_ID, &invocation_id); + source_dsa = talloc_zero(ctx, struct repsFromTo1); + source_dsa->source_dsa_obj_guid = source_dsa_obj_guid; + source_dsa->source_dsa_invocation_id = invocation_id; + + ro = talloc_zero(ctx, struct dsdb_extended_replicated_objects); + ro->source_dsa = source_dsa; + ro->num_objects = 808; + ro->linked_attributes_count = 2910; + ro->partition_dn = dn; + ro->error = WERR_NOT_SUPPORTED; + + + req = talloc_zero(ctx, struct ldb_request); + req->op.extended.data = ro; + req->operation = LDB_EXTENDED; + + reply = talloc_zero(ctx, struct ldb_reply); + reply->error = LDB_ERR_NO_SUCH_OBJECT; + + + /* + * Fail on the creation of the audit json object + */ + + will_return(__wrap_json_new_object, false); + + json = replicated_update_json(module, req, reply); + assert_true(json_is_invalid(&json)); + + /* + * Fail adding the version object . + */ + + will_return(__wrap_json_new_object, true); + will_return(__wrap_json_add_version, JSON_ERROR); + + json = replicated_update_json(module, req, reply); + assert_true(json_is_invalid(&json)); + + /* + * Fail on creation of the wrapper. + */ + + will_return(__wrap_json_new_object, true); + will_return(__wrap_json_add_version, 0); + will_return(__wrap_json_new_object, false); + + json = replicated_update_json(module, req, reply); + assert_true(json_is_invalid(&json)); + + /* + * Fail on creation of the time stamp. + */ + + will_return(__wrap_json_new_object, true); + will_return(__wrap_json_add_version, 0); + will_return(__wrap_json_new_object, true); + will_return(__wrap_json_add_timestamp, JSON_ERROR); + + json = replicated_update_json(module, req, reply); + assert_true(json_is_invalid(&json)); + + /* + * Now test the happy path. + */ + will_return(__wrap_json_new_object, true); + will_return(__wrap_json_add_version, 0); + will_return(__wrap_json_new_object, true); + will_return(__wrap_json_add_timestamp, 0); + + json = replicated_update_json(module, req, reply); + assert_false(json_is_invalid(&json)); + json_free(&json); + + TALLOC_FREE(ctx); +} + +int main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_operation_json), + cmocka_unit_test(test_password_change_json), + cmocka_unit_test(test_transaction_json), + cmocka_unit_test(test_commit_failure_json), + cmocka_unit_test(test_replicated_update_json), + }; + + cmocka_set_message_output(CM_OUTPUT_SUBUNIT); + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/source4/dsdb/samdb/ldb_modules/tests/test_audit_util.c b/source4/dsdb/samdb/ldb_modules/tests/test_audit_util.c new file mode 100644 index 0000000..cf2546d --- /dev/null +++ b/source4/dsdb/samdb/ldb_modules/tests/test_audit_util.c @@ -0,0 +1,1260 @@ +/* + Unit tests for the dsdb audit logging utility code code in audit_util.c + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2018 + + 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 <stdarg.h> +#include <stddef.h> +#include <setjmp.h> +#include <unistd.h> +#include <cmocka.h> + +#include "../audit_util.c" + +#include "lib/ldb/include/ldb_private.h" + +static void test_dsdb_audit_add_ldb_value(void **state) +{ + struct json_object object; + struct json_object array; + struct ldb_val val = data_blob_null; + struct json_t *el = NULL; + struct json_t *atr = NULL; + char* base64 = NULL; + + TALLOC_CTX *ctx = talloc_new(NULL); + /* + * Test a non array object + */ + object = json_new_object(); + assert_false(json_is_invalid(&object)); + dsdb_audit_add_ldb_value(&object, val); + assert_true(json_is_invalid(&object)); + json_free(&object); + + array = json_new_array(); + /* + * Test a data_blob_null, should encode as a JSON null value. + */ + val = data_blob_null; + dsdb_audit_add_ldb_value(&array, val); + el = json_array_get(array.root, 0); + assert_true(json_is_null(el)); + + /* + * Test a +ve length but a null data ptr, should encode as a null. + */ + val = data_blob_null; + val.length = 1; + dsdb_audit_add_ldb_value(&array, val); + el = json_array_get(array.root, 1); + assert_true(json_is_null(el)); + + /* + * Test a zero length but a non null data ptr, should encode as a null. + */ + val = data_blob_null; + val.data = discard_const("Data on the stack"); + dsdb_audit_add_ldb_value(&array, val); + el = json_array_get(array.root, 2); + assert_true(json_is_null(el)); + + /* + * Test a printable value. + * value should not be encoded + * truncated and base64 should be missing + */ + val = data_blob_string_const("A value of interest"); + dsdb_audit_add_ldb_value(&array, val); + el = json_array_get(array.root, 3); + assert_true(json_is_object(el)); + atr = json_object_get(el, "value"); + assert_true(json_is_string(atr)); + assert_string_equal("A value of interest", json_string_value(atr)); + assert_null(json_object_get(el, "truncated")); + assert_null(json_object_get(el, "base64")); + + /* + * Test non printable value, should be base64 encoded. + * truncated should be missing and base64 should be set. + */ + val = data_blob_string_const("A value of interest\n"); + dsdb_audit_add_ldb_value(&array, val); + el = json_array_get(array.root, 4); + assert_true(json_is_object(el)); + atr = json_object_get(el, "value"); + assert_true(json_is_string(atr)); + assert_string_equal( + "QSB2YWx1ZSBvZiBpbnRlcmVzdAo=", + json_string_value(atr)); + atr = json_object_get(el, "base64"); + assert_true(json_is_boolean(atr)); + assert_true(json_boolean(atr)); + assert_null(json_object_get(el, "truncated")); + + /* + * test a printable value exactly max bytes long + * should not be truncated or encoded. + */ + val = data_blob_null; + val.length = MAX_LENGTH; + val.data = (unsigned char *)generate_random_str_list( + ctx, + MAX_LENGTH, + "abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + "1234567890!@#$%^&*()"); + + dsdb_audit_add_ldb_value(&array, val); + + el = json_array_get(array.root, 5); + assert_true(json_is_object(el)); + atr = json_object_get(el, "value"); + assert_true(json_is_string(atr)); + assert_int_equal(MAX_LENGTH, strlen(json_string_value(atr))); + assert_memory_equal(val.data, json_string_value(atr), MAX_LENGTH); + + assert_null(json_object_get(el, "base64")); + assert_null(json_object_get(el, "truncated")); + + + /* + * test a printable value exactly max + 1 bytes long + * should be truncated and not encoded. + */ + val = data_blob_null; + val.length = MAX_LENGTH + 1; + val.data = (unsigned char *)generate_random_str_list( + ctx, + MAX_LENGTH + 1, + "abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + "1234567890!@#$%^&*()"); + + dsdb_audit_add_ldb_value(&array, val); + + el = json_array_get(array.root, 6); + assert_true(json_is_object(el)); + atr = json_object_get(el, "value"); + assert_true(json_is_string(atr)); + assert_int_equal(MAX_LENGTH, strlen(json_string_value(atr))); + assert_memory_equal(val.data, json_string_value(atr), MAX_LENGTH); + + atr = json_object_get(el, "truncated"); + assert_true(json_is_boolean(atr)); + assert_true(json_boolean(atr)); + + assert_null(json_object_get(el, "base64")); + + TALLOC_FREE(val.data); + + /* + * test a non-printable value exactly max bytes long + * should not be truncated but should be encoded. + */ + val = data_blob_null; + val.length = MAX_LENGTH; + val.data = (unsigned char *)generate_random_str_list( + ctx, + MAX_LENGTH, + "abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + "1234567890!@#$%^&*()"); + + val.data[0] = 0x03; + dsdb_audit_add_ldb_value(&array, val); + base64 = ldb_base64_encode(ctx, (char*) val.data, MAX_LENGTH); + + el = json_array_get(array.root, 7); + assert_true(json_is_object(el)); + atr = json_object_get(el, "value"); + assert_true(json_is_string(atr)); + assert_int_equal(strlen(base64), strlen(json_string_value(atr))); + assert_string_equal(base64, json_string_value(atr)); + + atr = json_object_get(el, "base64"); + assert_true(json_is_boolean(atr)); + assert_true(json_boolean(atr)); + + assert_null(json_object_get(el, "truncated")); + TALLOC_FREE(base64); + TALLOC_FREE(val.data); + + /* + * test a non-printable value exactly max + 1 bytes long + * should be truncated and encoded. + */ + val = data_blob_null; + val.length = MAX_LENGTH + 1; + val.data = (unsigned char *)generate_random_str_list( + ctx, + MAX_LENGTH + 1, + "abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + "1234567890!@#$%^&*()"); + + val.data[0] = 0x03; + dsdb_audit_add_ldb_value(&array, val); + /* + * The data is truncated before it is base 64 encoded + */ + base64 = ldb_base64_encode(ctx, (char*) val.data, MAX_LENGTH); + + el = json_array_get(array.root, 8); + assert_true(json_is_object(el)); + atr = json_object_get(el, "value"); + assert_true(json_is_string(atr)); + assert_int_equal(strlen(base64), strlen(json_string_value(atr))); + assert_string_equal(base64, json_string_value(atr)); + + atr = json_object_get(el, "base64"); + assert_true(json_is_boolean(atr)); + assert_true(json_boolean(atr)); + + atr = json_object_get(el, "truncated"); + assert_true(json_is_boolean(atr)); + assert_true(json_boolean(atr)); + + TALLOC_FREE(base64); + TALLOC_FREE(val.data); + + json_free(&array); + TALLOC_FREE(ctx); +} + +static void test_dsdb_audit_attributes_json(void **state) +{ + struct ldb_message *msg = NULL; + + struct json_object o; + json_t *a = NULL; + json_t *v = NULL; + json_t *x = NULL; + json_t *y = NULL; + + TALLOC_CTX *ctx = talloc_new(NULL); + + + /* + * Test an empty message + * Should get an empty attributes object + */ + msg = talloc_zero(ctx, struct ldb_message); + + o = dsdb_audit_attributes_json(LDB_ADD, msg); + assert_true(json_is_object(o.root)); + assert_int_equal(0, json_object_size(o.root)); + json_free(&o); + + o = dsdb_audit_attributes_json(LDB_MODIFY, msg); + assert_true(json_is_object(o.root)); + assert_int_equal(0, json_object_size(o.root)); + json_free(&o); + + /* + * Test a message with a single secret attribute + * should only have that object and it should have no value + * attribute and redacted should be set. + */ + msg = talloc_zero(ctx, struct ldb_message); + ldb_msg_add_string(msg, "clearTextPassword", "secret"); + + o = dsdb_audit_attributes_json(LDB_ADD, msg); + assert_true(json_is_object(o.root)); + assert_int_equal(1, json_object_size(o.root)); + + a = json_object_get(o.root, "clearTextPassword"); + assert_int_equal(1, json_object_size(a)); + + v = json_object_get(a, "actions"); + assert_true(json_is_array(v)); + assert_int_equal(1, json_array_size(v)); + + a = json_array_get(v, 0); + v = json_object_get(a, "redacted"); + assert_true(json_is_boolean(v)); + assert_true(json_boolean(v)); + + json_free(&o); + + /* + * Test as a modify message, should add an action attribute + */ + o = dsdb_audit_attributes_json(LDB_MODIFY, msg); + assert_true(json_is_object(o.root)); + assert_int_equal(1, json_object_size(o.root)); + + a = json_object_get(o.root, "clearTextPassword"); + assert_true(json_is_object(a)); + assert_int_equal(1, json_object_size(a)); + + v = json_object_get(a, "actions"); + assert_true(json_is_array(v)); + assert_int_equal(1, json_array_size(v)); + + a = json_array_get(v, 0); + v = json_object_get(a, "redacted"); + assert_true(json_is_boolean(v)); + assert_true(json_boolean(v)); + + v = json_object_get(a, "action"); + assert_true(json_is_string(v)); + assert_string_equal("unknown", json_string_value(v)); + + json_free(&o); + TALLOC_FREE(msg); + + /* + * Test a message with a single attribute, single valued attribute + */ + msg = talloc_zero(ctx, struct ldb_message); + ldb_msg_add_string(msg, "attribute", "value"); + + o = dsdb_audit_attributes_json(LDB_ADD, msg); + assert_true(json_is_object(o.root)); + assert_int_equal(1, json_object_size(o.root)); + + a = json_object_get(o.root, "attribute"); + assert_true(json_is_object(a)); + assert_int_equal(1, json_object_size(a)); + + v = json_object_get(a, "actions"); + assert_true(json_is_array(v)); + assert_int_equal(1, json_array_size(v)); + + x = json_array_get(v, 0); + assert_int_equal(2, json_object_size(x)); + y = json_object_get(x, "action"); + assert_string_equal("add", json_string_value(y)); + + y = json_object_get(x, "values"); + assert_true(json_is_array(y)); + assert_int_equal(1, json_array_size(y)); + + x = json_array_get(y, 0); + assert_true(json_is_object(x)); + assert_int_equal(1, json_object_size(x)); + y = json_object_get(x, "value"); + assert_string_equal("value", json_string_value(y)); + + json_free(&o); + TALLOC_FREE(msg); + + /* + * Test a message with a single attribute, single valued attribute + * And as a modify + */ + msg = talloc_zero(ctx, struct ldb_message); + ldb_msg_add_string(msg, "attribute", "value"); + + o = dsdb_audit_attributes_json(LDB_MODIFY, msg); + assert_true(json_is_object(o.root)); + assert_int_equal(1, json_object_size(o.root)); + + a = json_object_get(o.root, "attribute"); + assert_true(json_is_object(a)); + assert_int_equal(1, json_object_size(a)); + + v = json_object_get(a, "actions"); + assert_true(json_is_array(v)); + assert_int_equal(1, json_array_size(v)); + + x = json_array_get(v, 0); + assert_int_equal(2, json_object_size(x)); + y = json_object_get(x, "action"); + assert_string_equal("unknown", json_string_value(y)); + + y = json_object_get(x, "values"); + assert_true(json_is_array(y)); + assert_int_equal(1, json_array_size(y)); + + x = json_array_get(y, 0); + assert_true(json_is_object(x)); + assert_int_equal(1, json_object_size(x)); + y = json_object_get(x, "value"); + assert_string_equal("value", json_string_value(y)); + + json_free(&o); + TALLOC_FREE(msg); + + /* + * Test a message with a multivalues attributres + */ + msg = talloc_zero(ctx, struct ldb_message); + ldb_msg_add_string(msg, "attribute01", "value01"); + ldb_msg_add_string(msg, "attribute02", "value02"); + ldb_msg_add_string(msg, "attribute02", "value03"); + + o = dsdb_audit_attributes_json(LDB_ADD, msg); + assert_true(json_is_object(o.root)); + assert_int_equal(2, json_object_size(o.root)); + + a = json_object_get(o.root, "attribute01"); + assert_true(json_is_object(a)); + assert_int_equal(1, json_object_size(a)); + + v = json_object_get(a, "actions"); + assert_true(json_is_array(v)); + assert_int_equal(1, json_array_size(v)); + + x = json_array_get(v, 0); + assert_int_equal(2, json_object_size(x)); + y = json_object_get(x, "action"); + assert_string_equal("add", json_string_value(y)); + + y = json_object_get(x, "values"); + assert_true(json_is_array(y)); + assert_int_equal(1, json_array_size(y)); + + x = json_array_get(y, 0); + assert_true(json_is_object(x)); + assert_int_equal(1, json_object_size(x)); + y = json_object_get(x, "value"); + assert_string_equal("value01", json_string_value(y)); + + a = json_object_get(o.root, "attribute02"); + assert_true(json_is_object(a)); + assert_int_equal(1, json_object_size(a)); + + v = json_object_get(a, "actions"); + assert_true(json_is_array(v)); + assert_int_equal(1, json_array_size(v)); + + x = json_array_get(v, 0); + assert_int_equal(2, json_object_size(x)); + y = json_object_get(x, "action"); + assert_string_equal("add", json_string_value(y)); + + y = json_object_get(x, "values"); + assert_true(json_is_array(y)); + assert_int_equal(2, json_array_size(y)); + + x = json_array_get(y, 0); + assert_true(json_is_object(x)); + assert_int_equal(1, json_object_size(x)); + v = json_object_get(x, "value"); + assert_string_equal("value02", json_string_value(v)); + + x = json_array_get(y, 1); + assert_true(json_is_object(x)); + assert_int_equal(1, json_object_size(x)); + v = json_object_get(x, "value"); + assert_string_equal("value03", json_string_value(v)); + + json_free(&o); + TALLOC_FREE(msg); + + TALLOC_FREE(ctx); +} + +static void test_dsdb_audit_get_remote_address(void **state) +{ + struct ldb_context *ldb = NULL; + const struct tsocket_address *ts = NULL; + struct tsocket_address *in = NULL; + + TALLOC_CTX *ctx = talloc_new(NULL); + + /* + * Test a freshly initialized ldb + * should return NULL + */ + ldb = ldb_init(ctx, NULL); + ts = dsdb_audit_get_remote_address(ldb); + assert_null(ts); + + /* + * opaque set to null, should return NULL + */ + ldb_set_opaque(ldb, "remoteAddress", NULL); + ts = dsdb_audit_get_remote_address(ldb); + assert_null(ts); + + /* + * Ensure that the value set is returned + */ + tsocket_address_inet_from_strings(ctx, "ip", "127.0.0.1", 0, &in); + ldb_set_opaque(ldb, "remoteAddress", in); + ts = dsdb_audit_get_remote_address(ldb); + assert_non_null(ts); + assert_ptr_equal(in, ts); + + TALLOC_FREE(ldb); + TALLOC_FREE(ctx); + +} + +static void test_dsdb_audit_get_ldb_error_string(void **state) +{ + struct ldb_context *ldb = NULL; + struct ldb_module *module = NULL; + const char *s = NULL; + const char * const text = "Custom reason"; + + TALLOC_CTX *ctx = talloc_new(NULL); + + ldb = ldb_init(ctx, NULL); + module = talloc_zero(ctx, struct ldb_module); + module->ldb = ldb; + + /* + * No ldb error string set should get the default error description for + * the status code + */ + s = dsdb_audit_get_ldb_error_string(module, LDB_ERR_OPERATIONS_ERROR); + assert_string_equal("Operations error", s); + + /* + * Set the error string that should now be returned instead of the + * default description. + */ + ldb_error(ldb, LDB_ERR_OPERATIONS_ERROR, text); + s = dsdb_audit_get_ldb_error_string(module, LDB_ERR_OPERATIONS_ERROR); + /* + * Only test the start of the string as ldb_error adds location data. + */ + assert_int_equal(0, strncmp(text, s, strlen(text))); + + TALLOC_FREE(ctx); +} + +static void test_dsdb_audit_get_user_sid(void **state) +{ + struct ldb_context *ldb = NULL; + struct ldb_module *module = NULL; + const struct dom_sid *sid = NULL; + struct auth_session_info *sess = NULL; + struct security_token *token = NULL; + struct dom_sid sids[2]; + const char * const SID0 = "S-1-5-21-2470180966-3899876309-2637894779"; + const char * const SID1 = "S-1-5-21-4284042908-2889457889-3672286761"; + struct dom_sid_buf sid_buf; + + + TALLOC_CTX *ctx = talloc_new(NULL); + + ldb = ldb_init(ctx, NULL); + module = talloc_zero(ctx, struct ldb_module); + module->ldb = ldb; + + /* + * Freshly initialised structures, will be no session data + * so expect NULL + */ + sid = dsdb_audit_get_user_sid(module); + assert_null(sid); + + /* + * Now add a NULL session info + */ + ldb_set_opaque(ldb, DSDB_SESSION_INFO, sess); + sid = dsdb_audit_get_user_sid(module); + assert_null(sid); + + /* + * Now add a session info with no user sid + */ + sess = talloc_zero(ctx, struct auth_session_info); + ldb_set_opaque(ldb, DSDB_SESSION_INFO, sess); + sid = dsdb_audit_get_user_sid(module); + assert_null(sid); + + /* + * Now add an empty security token. + */ + token = talloc_zero(ctx, struct security_token); + sess->security_token = token; + sid = dsdb_audit_get_user_sid(module); + assert_null(sid); + + /* + * Add a single SID + */ + string_to_sid(&sids[0], SID0); + token->num_sids = 1; + token->sids = sids; + sid = dsdb_audit_get_user_sid(module); + assert_non_null(sid); + dom_sid_str_buf(sid, &sid_buf); + assert_string_equal(SID0, sid_buf.buf); + + /* + * Add a second SID, should still use the first SID + */ + string_to_sid(&sids[1], SID1); + token->num_sids = 2; + sid = dsdb_audit_get_user_sid(module); + assert_non_null(sid); + dom_sid_str_buf(sid, &sid_buf); + assert_string_equal(SID0, sid_buf.buf); + + + /* + * Now test a null sid in the first position + */ + token->num_sids = 1; + token->sids = NULL; + sid = dsdb_audit_get_user_sid(module); + assert_null(sid); + + TALLOC_FREE(ctx); +} + +static void test_dsdb_audit_get_actual_sid(void **state) +{ + struct ldb_context *ldb = NULL; + const struct dom_sid *sid = NULL; + struct auth_session_info *sess = NULL; + struct security_token *token = NULL; + struct dom_sid sids[2]; + const char * const SID0 = "S-1-5-21-2470180966-3899876309-2637894779"; + const char * const SID1 = "S-1-5-21-4284042908-2889457889-3672286761"; + struct dom_sid_buf sid_buf; + + + TALLOC_CTX *ctx = talloc_new(NULL); + + ldb = ldb_init(ctx, NULL); + + /* + * Freshly initialised structures, will be no session data + * so expect NULL + */ + sid = dsdb_audit_get_actual_sid(ldb); + assert_null(sid); + + /* + * Now add a NULL session info + */ + ldb_set_opaque(ldb, DSDB_NETWORK_SESSION_INFO, NULL); + sid = dsdb_audit_get_actual_sid(ldb); + assert_null(sid); + + /* + * Now add a session info with no user sid + */ + sess = talloc_zero(ctx, struct auth_session_info); + ldb_set_opaque(ldb, DSDB_NETWORK_SESSION_INFO, sess); + sid = dsdb_audit_get_actual_sid(ldb); + assert_null(sid); + + /* + * Now add an empty security token. + */ + token = talloc_zero(ctx, struct security_token); + sess->security_token = token; + sid = dsdb_audit_get_actual_sid(ldb); + assert_null(sid); + + /* + * Add a single SID + */ + string_to_sid(&sids[0], SID0); + token->num_sids = 1; + token->sids = sids; + sid = dsdb_audit_get_actual_sid(ldb); + assert_non_null(sid); + dom_sid_str_buf(sid, &sid_buf); + assert_string_equal(SID0, sid_buf.buf); + + /* + * Add a second SID, should still use the first SID + */ + string_to_sid(&sids[1], SID1); + token->num_sids = 2; + sid = dsdb_audit_get_actual_sid(ldb); + assert_non_null(sid); + dom_sid_str_buf(sid, &sid_buf); + assert_string_equal(SID0, sid_buf.buf); + + + /* + * Now test a null sid in the first position + */ + token->num_sids = 1; + token->sids = NULL; + sid = dsdb_audit_get_actual_sid(ldb); + assert_null(sid); + + TALLOC_FREE(ctx); +} + +static void test_dsdb_audit_is_system_session(void **state) +{ + struct ldb_context *ldb = NULL; + struct ldb_module *module = NULL; + const struct dom_sid *sid = NULL; + struct auth_session_info *sess = NULL; + struct security_token *token = NULL; + struct dom_sid sids[2]; + const char * const SID0 = "S-1-5-21-2470180966-3899876309-2637894779"; + const char * const SID1 = "S-1-5-21-4284042908-2889457889-3672286761"; + + + TALLOC_CTX *ctx = talloc_new(NULL); + + ldb = ldb_init(ctx, NULL); + module = talloc_zero(ctx, struct ldb_module); + module->ldb = ldb; + + /* + * Freshly initialised structures, will be no session data + * so expect NULL + */ + assert_false(dsdb_audit_is_system_session(module)); + + /* + * Now add a NULL session info + */ + ldb_set_opaque(ldb, DSDB_SESSION_INFO, NULL); + assert_false(dsdb_audit_is_system_session(module)); + + /* + * Now add a session info with no user sid + */ + sess = talloc_zero(ctx, struct auth_session_info); + ldb_set_opaque(ldb, DSDB_SESSION_INFO, sess); + assert_false(dsdb_audit_is_system_session(module)); + + /* + * Now add an empty security token. + */ + token = talloc_zero(ctx, struct security_token); + sess->security_token = token; + assert_false(dsdb_audit_is_system_session(module)); + + /* + * Add a single SID, non system sid + */ + string_to_sid(&sids[0], SID0); + token->num_sids = 1; + token->sids = sids; + assert_false(dsdb_audit_is_system_session(module)); + + /* + * Add the system SID to the second position, + * this should be ignored. + */ + token->num_sids = 2; + sids[1] = global_sid_System; + assert_false(dsdb_audit_is_system_session(module)); + + /* + * Add a single SID, system sid + */ + token->num_sids = 1; + sids[0] = global_sid_System; + token->sids = sids; + assert_true(dsdb_audit_is_system_session(module)); + + /* + * Add a non system SID to position 2 + */ + sids[0] = global_sid_System; + string_to_sid(&sids[1], SID1); + token->num_sids = 2; + token->sids = sids; + assert_true(dsdb_audit_is_system_session(module)); + + /* + * Now test a null sid in the first position + */ + token->num_sids = 1; + token->sids = NULL; + sid = dsdb_audit_get_user_sid(module); + assert_null(sid); + + TALLOC_FREE(ctx); +} + +static void test_dsdb_audit_get_unique_session_token(void **state) +{ + struct ldb_context *ldb = NULL; + struct ldb_module *module = NULL; + struct auth_session_info *sess = NULL; + const struct GUID *guid; + const char * const GUID_S = "7130cb06-2062-6a1b-409e-3514c26b1773"; + struct GUID in; + char *guid_str; + struct GUID_txt_buf guid_buff; + + + TALLOC_CTX *ctx = talloc_new(NULL); + + ldb = ldb_init(ctx, NULL); + module = talloc_zero(ctx, struct ldb_module); + module->ldb = ldb; + + /* + * Test a freshly initialized ldb + * should return NULL + */ + guid = dsdb_audit_get_unique_session_token(module); + assert_null(guid); + + /* + * Now add a NULL session info + */ + ldb_set_opaque(ldb, DSDB_SESSION_INFO, NULL); + guid = dsdb_audit_get_unique_session_token(module); + assert_null(guid); + + /* + * Now add a session info with no session id + * Note if the memory has not been zeroed correctly all bets are + * probably off. + */ + sess = talloc_zero(ctx, struct auth_session_info); + ldb_set_opaque(ldb, DSDB_SESSION_INFO, sess); + guid = dsdb_audit_get_unique_session_token(module); + /* + * We will get a GUID, but it's contents will be undefined + */ + assert_non_null(guid); + + /* + * Now set the session id and confirm that we get it back. + */ + GUID_from_string(GUID_S, &in); + sess->unique_session_token = in; + guid = dsdb_audit_get_unique_session_token(module); + assert_non_null(guid); + guid_str = GUID_buf_string(guid, &guid_buff); + assert_string_equal(GUID_S, guid_str); + + TALLOC_FREE(ctx); + +} + +static void test_dsdb_audit_get_actual_unique_session_token(void **state) +{ + struct ldb_context *ldb = NULL; + struct auth_session_info *sess = NULL; + const struct GUID *guid; + const char * const GUID_S = "7130cb06-2062-6a1b-409e-3514c26b1773"; + struct GUID in; + char *guid_str; + struct GUID_txt_buf guid_buff; + + + TALLOC_CTX *ctx = talloc_new(NULL); + + ldb = ldb_init(ctx, NULL); + + /* + * Test a freshly initialized ldb + * should return NULL + */ + guid = dsdb_audit_get_actual_unique_session_token(ldb); + assert_null(guid); + + /* + * Now add a NULL session info + */ + ldb_set_opaque(ldb, DSDB_NETWORK_SESSION_INFO, NULL); + guid = dsdb_audit_get_actual_unique_session_token(ldb); + assert_null(guid); + + /* + * Now add a session info with no session id + * Note if the memory has not been zeroed correctly all bets are + * probably off. + */ + sess = talloc_zero(ctx, struct auth_session_info); + ldb_set_opaque(ldb, DSDB_NETWORK_SESSION_INFO, sess); + guid = dsdb_audit_get_actual_unique_session_token(ldb); + /* + * We will get a GUID, but it's contents will be undefined + */ + assert_non_null(guid); + + /* + * Now set the session id and confirm that we get it back. + */ + GUID_from_string(GUID_S, &in); + sess->unique_session_token = in; + guid = dsdb_audit_get_actual_unique_session_token(ldb); + assert_non_null(guid); + guid_str = GUID_buf_string(guid, &guid_buff); + assert_string_equal(GUID_S, guid_str); + + TALLOC_FREE(ctx); + +} + +static void test_dsdb_audit_get_remote_host(void **state) +{ + struct ldb_context *ldb = NULL; + char *rh = NULL; + struct tsocket_address *in = NULL; + + TALLOC_CTX *ctx = talloc_new(NULL); + + ldb = ldb_init(ctx, NULL); + + /* + * Test a freshly initialized ldb + * should return "Unknown" + */ + rh = dsdb_audit_get_remote_host(ldb, ctx); + assert_string_equal("Unknown", rh); + TALLOC_FREE(rh); + + /* + * opaque set to null, should return NULL + */ + ldb_set_opaque(ldb, "remoteAddress", NULL); + rh = dsdb_audit_get_remote_host(ldb, ctx); + assert_string_equal("Unknown", rh); + TALLOC_FREE(rh); + + /* + * Ensure that the value set is returned + */ + tsocket_address_inet_from_strings(ctx, "ip", "127.0.0.1", 42, &in); + ldb_set_opaque(ldb, "remoteAddress", in); + rh = dsdb_audit_get_remote_host(ldb, ctx); + assert_string_equal("ipv4:127.0.0.1:42", rh); + TALLOC_FREE(rh); + + TALLOC_FREE(ctx); + +} + +static void test_dsdb_audit_get_primary_dn(void **state) +{ + struct ldb_request *req = NULL; + struct ldb_message *msg = NULL; + struct ldb_context *ldb = NULL; + + struct ldb_dn *dn = NULL; + + const char * const DN = "dn=CN=USER,CN=Users,DC=SAMBA,DC=ORG"; + const char *s = NULL; + + TALLOC_CTX *ctx = talloc_new(NULL); + + req = talloc_zero(ctx, struct ldb_request); + msg = talloc_zero(ctx, struct ldb_message); + ldb = ldb_init(ctx, NULL); + dn = ldb_dn_new(ctx, ldb, DN); + + /* + * Try an empty request. + */ + s = dsdb_audit_get_primary_dn(req); + assert_null(s); + + /* + * Now try an add with a null message. + */ + req->operation = LDB_ADD; + req->op.add.message = NULL; + s = dsdb_audit_get_primary_dn(req); + assert_null(s); + + /* + * Now try an mod with a null message. + */ + req->operation = LDB_MODIFY; + req->op.mod.message = NULL; + s = dsdb_audit_get_primary_dn(req); + assert_null(s); + + /* + * Now try an add with a missing dn + */ + req->operation = LDB_ADD; + req->op.add.message = msg; + s = dsdb_audit_get_primary_dn(req); + assert_null(s); + + /* + * Now try a mod with a messing dn + */ + req->operation = LDB_ADD; + req->op.mod.message = msg; + s = dsdb_audit_get_primary_dn(req); + assert_null(s); + + /* + * Add a dn to the message + */ + msg->dn = dn; + + /* + * Now try an add with a dn + */ + req->operation = LDB_ADD; + req->op.add.message = msg; + s = dsdb_audit_get_primary_dn(req); + assert_non_null(s); + assert_string_equal(DN, s); + + /* + * Now try a mod with a dn + */ + req->operation = LDB_MODIFY; + req->op.mod.message = msg; + s = dsdb_audit_get_primary_dn(req); + assert_non_null(s); + assert_string_equal(DN, s); + + /* + * Try a delete without a dn + */ + req->operation = LDB_DELETE; + req->op.del.dn = NULL; + s = dsdb_audit_get_primary_dn(req); + assert_null(s); + + /* + * Try a delete with a dn + */ + req->operation = LDB_DELETE; + req->op.del.dn = dn; + s = dsdb_audit_get_primary_dn(req); + assert_non_null(s); + assert_string_equal(DN, s); + + /* + * Try a rename without a dn + */ + req->operation = LDB_RENAME; + req->op.rename.olddn = NULL; + s = dsdb_audit_get_primary_dn(req); + assert_null(s); + + /* + * Try a rename with a dn + */ + req->operation = LDB_RENAME; + req->op.rename.olddn = dn; + s = dsdb_audit_get_primary_dn(req); + assert_non_null(s); + assert_string_equal(DN, s); + + /* + * Try an extended operation, i.e. one that does not have a DN + * associated with it for logging purposes. + */ + req->operation = LDB_EXTENDED; + s = dsdb_audit_get_primary_dn(req); + assert_null(s); + + TALLOC_FREE(ctx); +} + +static void test_dsdb_audit_get_message(void **state) +{ + struct ldb_request *req = NULL; + struct ldb_message *msg = NULL; + const struct ldb_message *r = NULL; + + + TALLOC_CTX *ctx = talloc_new(NULL); + + req = talloc_zero(ctx, struct ldb_request); + msg = talloc_zero(ctx, struct ldb_message); + + /* + * Test an empty message + */ + r = dsdb_audit_get_message(req); + assert_null(r); + + /* + * Test an add message + */ + req->operation = LDB_ADD; + req->op.add.message = msg; + r = dsdb_audit_get_message(req); + assert_ptr_equal(msg, r); + + /* + * Test a modify message + */ + req->operation = LDB_MODIFY; + req->op.mod.message = msg; + r = dsdb_audit_get_message(req); + assert_ptr_equal(msg, r); + + /* + * Test a Delete message, i.e. trigger the default case + */ + req->operation = LDB_DELETE; + r = dsdb_audit_get_message(req); + assert_null(r); + + TALLOC_FREE(ctx); +} + +static void test_dsdb_audit_get_secondary_dn(void **state) +{ + struct ldb_request *req = NULL; + struct ldb_context *ldb = NULL; + + struct ldb_dn *dn = NULL; + + const char * const DN = "dn=CN=USER,CN=Users,DC=SAMBA,DC=ORG"; + const char *s = NULL; + + TALLOC_CTX *ctx = talloc_new(NULL); + + req = talloc_zero(ctx, struct ldb_request); + ldb = ldb_init(ctx, NULL); + dn = ldb_dn_new(ctx, ldb, DN); + + /* + * Try an empty request. + */ + s = dsdb_audit_get_secondary_dn(req); + assert_null(s); + + /* + * Try a rename without a dn + */ + req->operation = LDB_RENAME; + req->op.rename.newdn = NULL; + s = dsdb_audit_get_secondary_dn(req); + assert_null(s); + + /* + * Try a rename with a dn + */ + req->operation = LDB_RENAME; + req->op.rename.newdn = dn; + s = dsdb_audit_get_secondary_dn(req); + assert_non_null(s); + assert_string_equal(DN, s); + + /* + * Try an extended operation, i.e. one that does not have a DN + * associated with it for logging purposes. + */ + req->operation = LDB_EXTENDED; + s = dsdb_audit_get_primary_dn(req); + assert_null(s); + + TALLOC_FREE(ctx); +} + +static void test_dsdb_audit_get_operation_name(void **state) +{ + struct ldb_request *req = NULL; + + TALLOC_CTX *ctx = talloc_new(NULL); + + req = talloc_zero(ctx, struct ldb_request); + + req->operation = LDB_SEARCH; + assert_string_equal("Search", dsdb_audit_get_operation_name(req)); + + req->operation = LDB_ADD; + assert_string_equal("Add", dsdb_audit_get_operation_name(req)); + + req->operation = LDB_MODIFY; + assert_string_equal("Modify", dsdb_audit_get_operation_name(req)); + + req->operation = LDB_DELETE; + assert_string_equal("Delete", dsdb_audit_get_operation_name(req)); + + req->operation = LDB_RENAME; + assert_string_equal("Rename", dsdb_audit_get_operation_name(req)); + + req->operation = LDB_EXTENDED; + assert_string_equal("Extended", dsdb_audit_get_operation_name(req)); + + req->operation = LDB_REQ_REGISTER_CONTROL; + assert_string_equal( + "Register Control", + dsdb_audit_get_operation_name(req)); + + req->operation = LDB_REQ_REGISTER_PARTITION; + assert_string_equal( + "Register Partition", + dsdb_audit_get_operation_name(req)); + + /* + * Trigger the default case + */ + req->operation = -1; + assert_string_equal("Unknown", dsdb_audit_get_operation_name(req)); + + TALLOC_FREE(ctx); +} + +static void test_dsdb_audit_get_modification_action(void **state) +{ + assert_string_equal( + "add", + dsdb_audit_get_modification_action(LDB_FLAG_MOD_ADD)); + assert_string_equal( + "delete", + dsdb_audit_get_modification_action(LDB_FLAG_MOD_DELETE)); + assert_string_equal( + "replace", + dsdb_audit_get_modification_action(LDB_FLAG_MOD_REPLACE)); + /* + * Trigger the default case + */ + assert_string_equal( + "unknown", + dsdb_audit_get_modification_action(0)); +} + +static void test_dsdb_audit_is_password_attribute(void **state) +{ + assert_true(dsdb_audit_is_password_attribute("userPassword")); + assert_true(dsdb_audit_is_password_attribute("clearTextPassword")); + assert_true(dsdb_audit_is_password_attribute("unicodePwd")); + assert_true(dsdb_audit_is_password_attribute("dBCSPwd")); + + assert_false(dsdb_audit_is_password_attribute("xserPassword")); +} + +static void test_dsdb_audit_redact_attribute(void **state) +{ + assert_true(dsdb_audit_redact_attribute("userPassword")); + + assert_true(dsdb_audit_redact_attribute("pekList")); + assert_true(dsdb_audit_redact_attribute("clearTextPassword")); + assert_true(dsdb_audit_redact_attribute("initialAuthIncoming")); + + assert_false(dsdb_audit_redact_attribute("supaskrt")); +} + +int main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_dsdb_audit_add_ldb_value), + cmocka_unit_test(test_dsdb_audit_attributes_json), + cmocka_unit_test(test_dsdb_audit_get_remote_address), + cmocka_unit_test(test_dsdb_audit_get_ldb_error_string), + cmocka_unit_test(test_dsdb_audit_get_user_sid), + cmocka_unit_test(test_dsdb_audit_get_actual_sid), + cmocka_unit_test(test_dsdb_audit_is_system_session), + cmocka_unit_test(test_dsdb_audit_get_unique_session_token), + cmocka_unit_test(test_dsdb_audit_get_actual_unique_session_token), + cmocka_unit_test(test_dsdb_audit_get_remote_host), + cmocka_unit_test(test_dsdb_audit_get_primary_dn), + cmocka_unit_test(test_dsdb_audit_get_message), + cmocka_unit_test(test_dsdb_audit_get_secondary_dn), + cmocka_unit_test(test_dsdb_audit_get_operation_name), + cmocka_unit_test(test_dsdb_audit_get_modification_action), + cmocka_unit_test(test_dsdb_audit_is_password_attribute), + cmocka_unit_test(test_dsdb_audit_redact_attribute), + }; + + cmocka_set_message_output(CM_OUTPUT_SUBUNIT); + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/source4/dsdb/samdb/ldb_modules/tests/test_encrypted_secrets.c b/source4/dsdb/samdb/ldb_modules/tests/test_encrypted_secrets.c new file mode 100644 index 0000000..e639d4c --- /dev/null +++ b/source4/dsdb/samdb/ldb_modules/tests/test_encrypted_secrets.c @@ -0,0 +1,973 @@ +/* + Unit tests for the encrypted secrets code in encrypted_secrets.c + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2017 + + 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 <stdarg.h> +#include <stddef.h> +#include <setjmp.h> +#include <unistd.h> +#include <cmocka.h> + +int ldb_encrypted_secrets_module_init(const char *version); +#define TEST_ENCRYPTED_SECRETS +#include "../encrypted_secrets.c" + +struct ldbtest_ctx { + struct tevent_context *ev; + struct ldb_context *ldb; + struct ldb_module *module; + + const char *dbfile; + const char *lockfile; /* lockfile is separate */ + const char *keyfile; + + const char *dbpath; +}; + +/* -------------------------------------------------------------------------- */ +/* + * Replace the dsdb helper routines used by the operational_init function + * + */ +int dsdb_module_search_dn( + struct ldb_module *module, + TALLOC_CTX *mem_ctx, + struct ldb_result **_res, + struct ldb_dn *basedn, + const char * const *attrs, + uint32_t dsdb_flags, + struct ldb_request *parent) +{ + struct ldb_context *ldb = ldb_module_get_ctx(module); + struct ldb_message *msg = ldb_msg_new(ldb); + struct ldb_result *res = talloc_zero(mem_ctx, struct ldb_result); + + msg->dn = ldb_dn_new(msg, ldb, "@SAMBA_DSDB"); + ldb_msg_add_string( + msg, + SAMBA_REQUIRED_FEATURES_ATTR, + SAMBA_ENCRYPTED_SECRETS_FEATURE); + + res->msgs = talloc_array(mem_ctx, struct ldb_message*, 1); + res->msgs[0] = msg; + *_res = res; + return LDB_SUCCESS; +} + +int dsdb_module_reference_dn( + struct ldb_module *module, + TALLOC_CTX *mem_ctx, + struct ldb_dn *base, + const char *attribute, + struct ldb_dn **dn, + struct ldb_request *parent) +{ + return LDB_SUCCESS; +} +/* -------------------------------------------------------------------------- */ + +static void unlink_old_db(struct ldbtest_ctx *test_ctx) +{ + int ret; + + errno = 0; + ret = unlink(test_ctx->lockfile); + if (ret == -1 && errno != ENOENT) { + fail(); + } + + errno = 0; + ret = unlink(test_ctx->dbfile); + if (ret == -1 && errno != ENOENT) { + fail(); + } + + errno = 0; + ret = unlink(test_ctx->keyfile); + if (ret == -1 && errno != ENOENT) { + fail(); + } +} + +static void write_key(void **state, DATA_BLOB key) { + + struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state, + struct ldbtest_ctx); + FILE *fp = NULL; + int written = 0; + + fp = fopen(test_ctx->keyfile, "wb"); + assert_non_null(fp); + + written = fwrite(key.data, 1, key.length, fp); + assert_int_equal(written, key.length); + fclose(fp); +} + +static const struct ldb_module_ops eol_ops = { + .name = "eol", + .search = NULL, + .add = NULL, + .modify = NULL, + .del = NULL, + .rename = NULL, + .init_context = NULL +}; + +static int setup(void **state) +{ + struct ldbtest_ctx *test_ctx = NULL; + struct ldb_module *eol = NULL; + int rc; + + test_ctx = talloc_zero(NULL, struct ldbtest_ctx); + assert_non_null(test_ctx); + + test_ctx->ev = tevent_context_init(test_ctx); + assert_non_null(test_ctx->ev); + + test_ctx->ldb = ldb_init(test_ctx, test_ctx->ev); + assert_non_null(test_ctx->ldb); + + + + test_ctx->module = ldb_module_new( + test_ctx, + test_ctx->ldb, + "encrypted_secrets", + &ldb_encrypted_secrets_module_ops); + assert_non_null(test_ctx->module); + eol = ldb_module_new(test_ctx, test_ctx->ldb, "eol", &eol_ops); + assert_non_null(eol); + ldb_module_set_next(test_ctx->module, eol); + + test_ctx->dbfile = talloc_strdup(test_ctx, "apitest.ldb"); + assert_non_null(test_ctx->dbfile); + + test_ctx->lockfile = talloc_asprintf(test_ctx, "%s-lock", + test_ctx->dbfile); + assert_non_null(test_ctx->lockfile); + + test_ctx->keyfile = talloc_strdup(test_ctx, SECRETS_KEY_FILE); + assert_non_null(test_ctx->keyfile); + + test_ctx->dbpath = talloc_asprintf(test_ctx, + TEST_BE"://%s", test_ctx->dbfile); + assert_non_null(test_ctx->dbpath); + + unlink_old_db(test_ctx); + + rc = ldb_connect(test_ctx->ldb, test_ctx->dbpath, 0, NULL); + assert_int_equal(rc, 0); + *state = test_ctx; + return 0; +} + +static int setup_with_key(void **state) +{ + struct ldbtest_ctx *test_ctx = NULL; + DATA_BLOB key = data_blob_null; + uint8_t key_data[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}; + int rc; + + setup(state); + key.data = key_data; + key.length = sizeof(key_data); + + write_key(state, key); + + test_ctx = talloc_get_type_abort(*state, struct ldbtest_ctx); + { + struct ldb_message *msg = ldb_msg_new(test_ctx->ldb); + msg->dn = ldb_dn_new(msg, test_ctx->ldb, "@SAMBA_DSDB"); + ldb_msg_add_string( + msg, + SAMBA_REQUIRED_FEATURES_ATTR, + SAMBA_ENCRYPTED_SECRETS_FEATURE); + ldb_add(test_ctx->ldb, msg); + } + + rc = es_init(test_ctx->module); + assert_int_equal(rc, LDB_SUCCESS); + + return 0; +} + +static int teardown(void **state) +{ + struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state, + struct ldbtest_ctx); + + unlink_old_db(test_ctx); + talloc_free(test_ctx); + return 0; +} +/* + * No key file present. + * + * The key should be empty and encrypt_secrets should be false. + */ +static void test_no_key_file(void **state) +{ + struct ldbtest_ctx *test_ctx = + talloc_get_type_abort(*state, struct ldbtest_ctx); + struct es_data *data = NULL; + + int rc; + + rc = es_init(test_ctx->module); + assert_int_equal(rc, LDB_SUCCESS); + + data = talloc_get_type(ldb_module_get_private(test_ctx->module), + struct es_data); + + assert_false(data->encrypt_secrets); + assert_int_equal(0, data->keys[0].length); + +} + +/* + * Key file present. + * + * The key should be loaded and encrypt secrets should be true; + */ +static void test_key_file(void **state) +{ + struct ldbtest_ctx *test_ctx = + talloc_get_type_abort(*state, struct ldbtest_ctx); + struct es_data *data = NULL; + int rc; + DATA_BLOB key = data_blob_null; + uint8_t key_data[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}; + + key.data = key_data; + key.length = sizeof(key_data); + + write_key(state, key); + + + rc = es_init(test_ctx->module); + assert_int_equal(rc, LDB_SUCCESS); + + data = talloc_get_type(ldb_module_get_private(test_ctx->module), + struct es_data); + + assert_true(data->encrypt_secrets); + assert_int_equal(16, data->keys[0].length); + assert_int_equal(0, data_blob_cmp(&key, &data->keys[0])); + +} + +/* + * Key file present, short key. + * + * The key should be not be loaded and an error returned. + */ +static void test_key_file_short_key(void **state) +{ + struct ldbtest_ctx *test_ctx = + talloc_get_type_abort(*state, struct ldbtest_ctx); + int rc; + DATA_BLOB key = data_blob_null; + uint8_t key_data[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e}; + + key.data = key_data; + key.length = sizeof(key_data); + + write_key(state, key); + + + rc = es_init(test_ctx->module); + assert_int_equal(rc, LDB_ERR_OPERATIONS_ERROR); +} + +/* + * Key file present, long key. + * + * Only the first 16 bytes of the key should be loaded. + */ +static void test_key_file_long_key(void **state) +{ + struct ldbtest_ctx *test_ctx = + talloc_get_type_abort(*state, struct ldbtest_ctx); + struct es_data *data = NULL; + int rc; + DATA_BLOB key = data_blob_null; + uint8_t key_data[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0xf, + 0x10}; + + key.data = key_data; + key.length = sizeof(key_data); + + write_key(state, key); + + rc = es_init(test_ctx->module); + assert_int_equal(rc, LDB_SUCCESS); + + data = talloc_get_type(ldb_module_get_private(test_ctx->module), + struct es_data); + + assert_true(data->encrypt_secrets); + assert_int_equal(16, data->keys[0].length); + + /* + * Should have only read the first 16 bytes of the written key + */ + key.length = 16; + assert_int_equal(0, data_blob_cmp(&key, &data->keys[0])); +} + +/* + * Test gnutls_encryption and decryption. + */ +static void test_gnutls_value_decryption(void **state) +{ + struct ldbtest_ctx *test_ctx = + talloc_get_type_abort(*state, struct ldbtest_ctx); + const struct ldb_val plain_text = + data_blob_string_const("A text value"); + unsigned char iv_data[] = { + 0xe7, 0xa3, 0x85, 0x17, 0x45, 0x73, 0xf4, 0x25, + 0xa5, 0x56, 0xde, 0x4c, + }; + unsigned char encrypted_data[] = { + 0xac, 0x13, 0x86, 0x94, 0x3b, 0xed, 0xf2, 0x51, + 0xec, 0x85, 0x4d, 0x00, 0x37, 0x81, 0x46, 0x15, + 0x42, 0x13, 0xb1, 0x69, 0x49, 0x10, 0xe7, 0x9e, + 0x15, 0xbd, 0x95, 0x75, 0x6b, 0x0c, 0xc0, 0xa4, + }; + struct EncryptedSecret es = { + .iv = { + .data = iv_data, + .length = sizeof(iv_data), + }, + .header = { + .magic = ENCRYPTED_SECRET_MAGIC_VALUE, + .version = SECRET_ATTRIBUTE_VERSION, + .algorithm = ENC_SECRET_AES_128_AEAD, + }, + .encrypted = { + .data = encrypted_data, + .length = sizeof(encrypted_data), + } + }; + unsigned char es_keys_blob[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + }; + struct es_data data = { + .encrypt_secrets = true, + .keys[0] = { + .data = es_keys_blob, + .length = sizeof(es_keys_blob), + }, + .encryption_algorithm = GNUTLS_CIPHER_AES_128_GCM, + }; + struct PlaintextSecret *decrypted = + talloc_zero(test_ctx, struct PlaintextSecret); + int err = LDB_SUCCESS; + + gnutls_decrypt_aead(&err, + test_ctx, + test_ctx->ldb, + &es, + decrypted, + &data); + assert_int_equal(LDB_SUCCESS, err); + assert_int_equal(plain_text.length, decrypted->cleartext.length); + assert_int_equal(0, data_blob_cmp(&decrypted->cleartext, &plain_text)); +} + +/* + * Test gnutls_encryption and decryption. + */ +static void test_gnutls_value_encryption(void **state) +{ + struct ldbtest_ctx *test_ctx = + talloc_get_type_abort(*state, struct ldbtest_ctx); + struct ldb_val plain_text = data_blob_null; + struct ldb_val cipher_text = data_blob_null; + struct EncryptedSecret es; + + struct es_data *data = talloc_get_type( + ldb_module_get_private(test_ctx->module), + struct es_data); + int err = LDB_SUCCESS; + int rc; + + plain_text = data_blob_string_const("A text value"); + cipher_text = gnutls_encrypt_aead( + &err, + test_ctx, + test_ctx->ldb, + plain_text, + data); + assert_int_equal(LDB_SUCCESS, err); + + rc = ndr_pull_struct_blob( + &cipher_text, + test_ctx, + &es, + (ndr_pull_flags_fn_t) ndr_pull_EncryptedSecret); + assert_true(NDR_ERR_CODE_IS_SUCCESS(rc)); + assert_true(check_header(&es)); + + { + struct PlaintextSecret *decrypted = + talloc_zero(test_ctx, struct PlaintextSecret); + gnutls_decrypt_aead( + &err, + test_ctx, + test_ctx->ldb, + &es, + decrypted, + data); + assert_int_equal(LDB_SUCCESS, err); + assert_int_equal( + plain_text.length, + decrypted->cleartext.length); + assert_int_equal(0, + data_blob_cmp( + &decrypted->cleartext, + &plain_text)); + } +} + +static void test_gnutls_altered_header(void **state) +{ + struct ldbtest_ctx *test_ctx = + talloc_get_type_abort(*state, struct ldbtest_ctx); + struct ldb_val plain_text = data_blob_null; + struct ldb_val cipher_text = data_blob_null; + struct EncryptedSecret es; + + struct es_data *data = talloc_get_type( + ldb_module_get_private(test_ctx->module), + struct es_data); + int err = LDB_SUCCESS; + int rc; + + plain_text = data_blob_string_const("A text value"); + cipher_text = gnutls_encrypt_aead( + &err, + test_ctx, + test_ctx->ldb, + plain_text, + data); + assert_int_equal(LDB_SUCCESS, err); + + rc = ndr_pull_struct_blob( + &cipher_text, + test_ctx, + &es, + (ndr_pull_flags_fn_t) ndr_pull_EncryptedSecret); + assert_true(NDR_ERR_CODE_IS_SUCCESS(rc)); + assert_true(check_header(&es)); + + { + struct PlaintextSecret *decrypted = + talloc_zero(test_ctx, struct PlaintextSecret); + gnutls_decrypt_aead( + &err, + test_ctx, + test_ctx->ldb, + &es, + decrypted, + data); + assert_int_equal(LDB_SUCCESS, err); + assert_int_equal( + plain_text.length, + decrypted->cleartext.length); + assert_int_equal(0, + data_blob_cmp( + &decrypted->cleartext, + &plain_text)); + } + es.header.flags = es.header.flags ^ 0xffffffff; + { + struct PlaintextSecret *decrypted = + talloc_zero(test_ctx, struct PlaintextSecret); + gnutls_decrypt_aead( + &err, + test_ctx, + test_ctx->ldb, + &es, + decrypted, + data); + assert_int_equal(LDB_ERR_OPERATIONS_ERROR, err); + } +} + +static void test_gnutls_altered_data(void **state) +{ + struct ldbtest_ctx *test_ctx = + talloc_get_type_abort(*state, struct ldbtest_ctx); + struct ldb_val plain_text = data_blob_null; + struct ldb_val cipher_text = data_blob_null; + struct EncryptedSecret es; + + struct es_data *data = talloc_get_type( + ldb_module_get_private(test_ctx->module), + struct es_data); + int err = LDB_SUCCESS; + int rc; + + plain_text = data_blob_string_const("A text value"); + cipher_text = gnutls_encrypt_aead( + &err, + test_ctx, + test_ctx->ldb, + plain_text, + data); + assert_int_equal(LDB_SUCCESS, err); + + rc = ndr_pull_struct_blob( + &cipher_text, + test_ctx, + &es, + (ndr_pull_flags_fn_t) ndr_pull_EncryptedSecret); + assert_true(NDR_ERR_CODE_IS_SUCCESS(rc)); + assert_true(check_header(&es)); + + { + struct PlaintextSecret *decrypted = + talloc_zero(test_ctx, struct PlaintextSecret); + gnutls_decrypt_aead( + &err, + test_ctx, + test_ctx->ldb, + &es, + decrypted, + data); + assert_int_equal(LDB_SUCCESS, err); + assert_int_equal( + plain_text.length, + decrypted->cleartext.length); + assert_int_equal(0, + data_blob_cmp( + &decrypted->cleartext, + &plain_text)); + } + es.encrypted.data[0] = es.encrypted.data[0] ^ 0xff; + { + struct PlaintextSecret *decrypted = + talloc_zero(test_ctx, struct PlaintextSecret); + gnutls_decrypt_aead( + &err, + test_ctx, + test_ctx->ldb, + &es, + decrypted, + data); + assert_int_equal(LDB_ERR_OPERATIONS_ERROR, err); + } +} + +static void test_gnutls_altered_iv(void **state) +{ + struct ldbtest_ctx *test_ctx = + talloc_get_type_abort(*state, struct ldbtest_ctx); + struct ldb_val plain_text = data_blob_null; + struct ldb_val cipher_text = data_blob_null; + struct EncryptedSecret es; + + struct es_data *data = talloc_get_type( + ldb_module_get_private(test_ctx->module), + struct es_data); + int err = LDB_SUCCESS; + int rc; + + plain_text = data_blob_string_const("A text value"); + cipher_text = gnutls_encrypt_aead( + &err, + test_ctx, + test_ctx->ldb, + plain_text, + data); + assert_int_equal(LDB_SUCCESS, err); + + rc = ndr_pull_struct_blob( + &cipher_text, + test_ctx, + &es, + (ndr_pull_flags_fn_t) ndr_pull_EncryptedSecret); + assert_true(NDR_ERR_CODE_IS_SUCCESS(rc)); + assert_true(check_header(&es)); + + { + struct PlaintextSecret *decrypted = + talloc_zero(test_ctx, struct PlaintextSecret); + gnutls_decrypt_aead( + &err, + test_ctx, + test_ctx->ldb, + &es, + decrypted, + data); + assert_int_equal(LDB_SUCCESS, err); + assert_int_equal( + plain_text.length, + decrypted->cleartext.length); + assert_int_equal(0, + data_blob_cmp( + &decrypted->cleartext, + &plain_text)); + } + es.iv.data[0] = es.iv.data[0] ^ 0xff; + { + struct PlaintextSecret *decrypted = + talloc_zero(test_ctx, struct PlaintextSecret); + gnutls_decrypt_aead( + &err, + test_ctx, + test_ctx->ldb, + &es, + decrypted, + data); + assert_int_equal(LDB_ERR_OPERATIONS_ERROR, err); + } +} + +/* + * Test samba encryption and decryption and decryption. + */ + +/* + * Test message encryption. + * Test the secret attributes of a message are encrypted and decrypted. + * Test that the non secret attributes are not encrypted. + * + */ +static void test_message_encryption_decryption(void **state) +{ + struct ldbtest_ctx *test_ctx = + talloc_get_type_abort(*state, struct ldbtest_ctx); + struct ldb_context *ldb = test_ctx->ldb; + const char * const secrets[] = {DSDB_SECRET_ATTRIBUTES}; + const size_t num_secrets + = (sizeof(secrets)/sizeof(secrets[0])); + struct ldb_message *msg = ldb_msg_new(ldb); + const struct ldb_message *encrypted_msg = NULL; + struct es_data *data = talloc_get_type( + ldb_module_get_private(test_ctx->module), + struct es_data); + struct ldb_message_element *el = NULL; + int ret = LDB_SUCCESS; + size_t i; + unsigned int j; + + msg->dn = ldb_dn_new(msg, ldb, "dc=test"); + ldb_msg_add_string(msg, "cmocka_test_name01", "value01"); + for (i=0; i < num_secrets; i++) { + ldb_msg_add_string( + msg, + secrets[i], + secrets[i]); + } + ldb_msg_add_string(msg, "cmocka_test_name02", "value02"); + + encrypted_msg = encrypt_secret_attributes( + &ret, + test_ctx, + test_ctx->ldb, + msg, + data); + assert_int_equal(LDB_SUCCESS, ret); + + /* + * Check that all the secret attributes have been encrypted + * + */ + for (i=0; i < num_secrets; i++) { + el = ldb_msg_find_element(encrypted_msg, secrets[i]); + assert_non_null(el); + for (j = 0; j < el->num_values; j++) { + int rc = LDB_SUCCESS; + struct ldb_val dc = decrypt_value( + &rc, + test_ctx, + test_ctx->ldb, + el->values[j], + data); + assert_int_equal(LDB_SUCCESS, rc); + assert_memory_equal( + secrets[i], + dc.data, + dc.length); + TALLOC_FREE(dc.data); + } + } + + /* + * Check that the normal attributes have not been encrypted + */ + el = ldb_msg_find_element(encrypted_msg, "cmocka_test_name01"); + assert_non_null(el); + assert_memory_equal( + "value01", + el->values[0].data, + el->values[0].length); + + el = ldb_msg_find_element(encrypted_msg, "cmocka_test_name02"); + assert_non_null(el); + assert_memory_equal( + "value02", + el->values[0].data, + el->values[0].length); + + /* + * Now decrypt the message + */ + ret = decrypt_secret_attributes(test_ctx->ldb, + discard_const(encrypted_msg), + data); + assert_int_equal(LDB_SUCCESS, ret); + + /* + * Check that all the secret attributes have been decrypted + */ + for (i=0; i < num_secrets; i++) { + el = ldb_msg_find_element(encrypted_msg, secrets[i]); + assert_non_null(el); + for (j = 0; j < el->num_values; j++) { + assert_memory_equal( + secrets[i], + el->values[j].data, + el->values[j].length); + } + } + + /* + * Check that the normal attributes are intact + */ + el = ldb_msg_find_element(msg, "cmocka_test_name01"); + assert_non_null(el); + assert_memory_equal( + "value01", + el->values[0].data, + el->values[0].length); + + el = ldb_msg_find_element(msg, "cmocka_test_name02"); + assert_non_null(el); + assert_memory_equal( + "value02", + el->values[0].data, + el->values[0].length); + +} + +static void test_check_header(void **state) +{ + struct ldbtest_ctx *test_ctx = + talloc_get_type_abort(*state, struct ldbtest_ctx); + + struct ldb_val enc = data_blob_null; + struct EncryptedSecret *es = NULL; + int rc; + + /* + * Valid EncryptedSecret + */ + es = makeEncryptedSecret(test_ctx->ldb, test_ctx); + rc = ndr_push_struct_blob( + &enc, + test_ctx, + es, + (ndr_push_flags_fn_t) ndr_push_EncryptedSecret); + assert_true(NDR_ERR_CODE_IS_SUCCESS(rc)); + assert_true(check_header(es)); + TALLOC_FREE(enc.data); + TALLOC_FREE(es); + + /* + * invalid magic value + */ + es = makeEncryptedSecret(test_ctx->ldb, test_ctx); + es->header.magic = 0xca5cadee; + rc = ndr_push_struct_blob( + &enc, + test_ctx, + es, + (ndr_push_flags_fn_t) ndr_push_EncryptedSecret); + assert_true(NDR_ERR_CODE_IS_SUCCESS(rc)); + assert_false(check_header(es)); + TALLOC_FREE(enc.data); + TALLOC_FREE(es); + + /* + * invalid version + */ + es = makeEncryptedSecret(test_ctx->ldb, test_ctx); + es->header.version = SECRET_ATTRIBUTE_VERSION + 1; + rc = ndr_push_struct_blob( + &enc, + test_ctx, + es, + (ndr_push_flags_fn_t) ndr_push_EncryptedSecret); + assert_true(NDR_ERR_CODE_IS_SUCCESS(rc)); + assert_false(check_header(es)); + TALLOC_FREE(enc.data); + TALLOC_FREE(es); + + /* + * invalid algorithm + */ + es = makeEncryptedSecret(test_ctx->ldb, test_ctx); + es->header.algorithm = SECRET_ENCRYPTION_ALGORITHM + 1; + rc = ndr_push_struct_blob( + &enc, + test_ctx, + es, + (ndr_push_flags_fn_t) ndr_push_EncryptedSecret); + assert_true(NDR_ERR_CODE_IS_SUCCESS(rc)); + assert_false(check_header(es)); + TALLOC_FREE(enc.data); + TALLOC_FREE(es); +} + +/* + * Attempt to decrypt a message containing an unencrypted secret attribute + * this should fail + */ +static void test_unencrypted_secret(void **state) +{ + struct ldbtest_ctx *test_ctx = + talloc_get_type_abort(*state, struct ldbtest_ctx); + struct ldb_context *ldb = test_ctx->ldb; + struct ldb_message *msg = ldb_msg_new(ldb); + struct es_data *data = talloc_get_type( + ldb_module_get_private(test_ctx->module), + struct es_data); + int ret = LDB_SUCCESS; + + msg->dn = ldb_dn_new(msg, ldb, "dc=test"); + ldb_msg_add_string(msg, "unicodePwd", "value01"); + + ret = decrypt_secret_attributes(test_ctx->ldb, msg, data); + assert_int_equal(LDB_ERR_OPERATIONS_ERROR, ret); +} + +/* + * Test full decryption of a static value with static key + */ +static void test_record_decryption(void **state) +{ + struct ldbtest_ctx *test_ctx = + talloc_get_type_abort(*state, struct ldbtest_ctx); + unsigned char plain_data[] = { + 0xe6, 0xa6, 0xb8, 0xff, 0xdf, 0x06, 0x6c, 0xe3, + 0xea, 0xd0, 0x94, 0xbb, 0x79, 0xbd, 0x0a, 0x24 + }; + unsigned char encrypted_data[] = { + 0x0c, 0x00, 0x00, 0x00, 0x33, 0x91, 0x74, 0x25, + 0x26, 0xcc, 0x0b, 0x8c, 0x21, 0xc1, 0x13, 0xe2, + 0xed, 0xad, 0x5c, 0xca, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x1a, 0xdc, 0xc9, 0x11, 0x08, 0xca, 0x2c, 0xfb, + 0xc8, 0x32, 0x6b, 0x1b, 0x25, 0x7f, 0x52, 0xbb, + 0xae, 0x9b, 0x88, 0x52, 0xb0, 0x18, 0x6d, 0x9d, + 0x9b, 0xdd, 0xcd, 0x1b, 0x5f, 0x4a, 0x5c, 0x29, + 0xca, 0x0b, 0x36, 0xaa + }; + struct ldb_val cipher_text + = data_blob_const(encrypted_data, + sizeof(encrypted_data)); + unsigned char es_keys_blob[] = { + 0x1d, 0xae, 0xf5, 0xaa, 0xa3, 0x85, 0x0d, 0x0a, + 0x8c, 0x24, 0x5c, 0x4c, 0xa7, 0x0f, 0x81, 0x79 + }; + struct es_data data = { + .encrypt_secrets = true, + .keys[0] = { + .data = es_keys_blob, + .length = sizeof(es_keys_blob), + }, + .encryption_algorithm = GNUTLS_CIPHER_AES_128_GCM, + }; + int err = LDB_SUCCESS; + struct ldb_val dec = decrypt_value(&err, test_ctx, test_ctx->ldb, cipher_text, + &data); + assert_int_equal(LDB_SUCCESS, err); + assert_int_equal(sizeof(plain_data), dec.length); + assert_memory_equal(dec.data, plain_data, sizeof(plain_data)); +} + + +int main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown( + test_no_key_file, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_key_file, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_key_file_short_key, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_key_file_long_key, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_check_header, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_gnutls_value_decryption, + setup_with_key, + teardown), + cmocka_unit_test_setup_teardown( + test_gnutls_value_encryption, + setup_with_key, + teardown), + cmocka_unit_test_setup_teardown( + test_gnutls_altered_header, + setup_with_key, + teardown), + cmocka_unit_test_setup_teardown( + test_gnutls_altered_data, + setup_with_key, + teardown), + cmocka_unit_test_setup_teardown( + test_gnutls_altered_iv, + setup_with_key, + teardown), + cmocka_unit_test_setup_teardown( + test_message_encryption_decryption, + setup_with_key, + teardown), + cmocka_unit_test_setup_teardown( + test_unencrypted_secret, + setup_with_key, + teardown), + cmocka_unit_test_setup_teardown( + test_record_decryption, + setup_with_key, + teardown), + }; + + cmocka_set_message_output(CM_OUTPUT_SUBUNIT); + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/source4/dsdb/samdb/ldb_modules/tests/test_group_audit.c b/source4/dsdb/samdb/ldb_modules/tests/test_group_audit.c new file mode 100644 index 0000000..f7075f3 --- /dev/null +++ b/source4/dsdb/samdb/ldb_modules/tests/test_group_audit.c @@ -0,0 +1,2022 @@ +/* + Unit tests for the dsdb group auditing code in group_audit.c + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2018 + + 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 <stdarg.h> +#include <stddef.h> +#include <setjmp.h> +#include <unistd.h> +#include <cmocka.h> + +int ldb_group_audit_log_module_init(const char *version); +#include "../group_audit.c" + +#include "lib/ldb/include/ldb_private.h" +#include <regex.h> + +/* + * Mock version of dsdb_search_one + */ +struct ldb_dn *g_basedn = NULL; +enum ldb_scope g_scope; +const char * const *g_attrs = NULL; +uint32_t g_dsdb_flags; +const char *g_exp_fmt; +const char *g_dn = NULL; +int g_status = LDB_SUCCESS; +struct ldb_result *g_result = NULL; + +int dsdb_search_one(struct ldb_context *ldb, + TALLOC_CTX *mem_ctx, + struct ldb_message **msg, + struct ldb_dn *basedn, + enum ldb_scope scope, + const char * const *attrs, + uint32_t dsdb_flags, + const char *exp_fmt, ...) _PRINTF_ATTRIBUTE(8, 9) +{ + struct ldb_dn *dn = ldb_dn_new(mem_ctx, ldb, g_dn); + struct ldb_message *m = talloc_zero(mem_ctx, struct ldb_message); + m->dn = dn; + *msg = m; + + g_basedn = basedn; + g_scope = scope; + g_attrs = attrs; + g_dsdb_flags = dsdb_flags; + g_exp_fmt = exp_fmt; + + return g_status; +} + +int dsdb_module_search_dn( + struct ldb_module *module, + TALLOC_CTX *mem_ctx, + struct ldb_result **res, + struct ldb_dn *basedn, + const char * const *attrs, + uint32_t dsdb_flags, + struct ldb_request *parent) +{ + + g_basedn = basedn; + g_attrs = attrs; + g_dsdb_flags = dsdb_flags; + + *res = g_result; + + return g_status; +} +/* + * Mock version of audit_log_json + */ + +#define MAX_EXPECTED_MESSAGES 16 +static struct json_object messages[MAX_EXPECTED_MESSAGES]; +static size_t messages_sent = 0; + +void audit_message_send( + struct imessaging_context *msg_ctx, + const char *server_name, + uint32_t message_type, + struct json_object *message) +{ + messages[messages_sent].root = json_deep_copy(message->root); + messages[messages_sent].valid = message->valid; + messages_sent++; +} + +#define check_group_change_message(m, u, a, e) \ + _check_group_change_message(m, u, a, e, __FILE__, __LINE__); +/* + * declare the internal cmocka cm_print_error so that we can output messages + * in sub unit format + */ +void cm_print_error(const char * const format, ...); + +/* + * Validate a group change JSON audit message + * + * It should contain 3 elements. + * Have a type of "groupChange" + * Have a groupChange element + * + * The group change element should have 10 elements. + * + * There should be a user element matching the expected value + * There should be an action matching the expected value + */ +static void _check_group_change_message(const int message, + const char *user, + const char *action, + enum event_id_type event_id, + const char *file, + const int line) +{ + struct json_object json; + json_t *audit = NULL; + json_t *v = NULL; + const char* value; + int int_value; + int cmp; + + json = messages[message]; + + /* + * Validate the root JSON element + * check the number of elements + */ + if (json_object_size(json.root) != 3) { + cm_print_error( + "Unexpected number of elements in root %zu != %d\n", + json_object_size(json.root), + 3); + _fail(file, line); + } + + /* + * Check the type element + */ + v = json_object_get(json.root, "type"); + if (v == NULL) { + cm_print_error( "No \"type\" element\n"); + _fail(file, line); + } + + value = json_string_value(v); + cmp = strcmp("groupChange", value); + if (cmp != 0) { + cm_print_error( + "Unexpected type \"%s\" != \"groupChange\"\n", + value); + _fail(file, line); + } + + + audit = json_object_get(json.root, "groupChange"); + if (audit == NULL) { + cm_print_error("No groupChange element\n"); + _fail(file, line); + } + + /* + * Validate the groupChange element + */ + if ((event_id == EVT_ID_NONE && json_object_size(audit) != 10) || + (event_id != EVT_ID_NONE && json_object_size(audit) != 11)) { + cm_print_error("Unexpected number of elements in groupChange " + "%zu != %d\n", + json_object_size(audit), + 11); + _fail(file, line); + } + /* + * Validate the user element + */ + v = json_object_get(audit, "user"); + if (v == NULL) { + cm_print_error( "No user element\n"); + _fail(file, line); + } + + value = json_string_value(v); + cmp = strcmp(user, value); + if (cmp != 0) { + cm_print_error( + "Unexpected user name \"%s\" != \"%s\"\n", + value, + user); + _fail(file, line); + } + /* + * Validate the action element + */ + v = json_object_get(audit, "action"); + if (v == NULL) { + cm_print_error( "No action element\n"); + _fail(file, line); + } + + value = json_string_value(v); + cmp = strcmp(action, value); + if (cmp != 0) { + print_error( + "Unexpected action \"%s\" != \"%s\"\n", + value, + action); + _fail(file, line); + } + + /* + * Validate the eventId element + */ + v = json_object_get(audit, "eventId"); + if (event_id == EVT_ID_NONE) { + if (v != NULL) { + int_value = json_integer_value(v); + cm_print_error("Unexpected eventId \"%d\", it should " + "NOT be present", + int_value); + _fail(file, line); + } + } + else { + if (v == NULL) { + cm_print_error("No eventId element\n"); + _fail(file, line); + } + + int_value = json_integer_value(v); + if (int_value != event_id) { + cm_print_error("Unexpected eventId \"%d\" != \"%d\"\n", + int_value, + event_id); + _fail(file, line); + } + } +} + +#define check_timestamp(b, t)\ + _check_timestamp(b, t, __FILE__, __LINE__); +/* + * Test helper to check ISO 8601 timestamps for validity + */ +static void _check_timestamp( + time_t before, + const char *timestamp, + const char *file, + const int line) +{ + int rc; + int usec, tz; + char c[2]; + struct tm tm; + time_t after; + time_t actual; + struct timeval tv; + + + rc = gettimeofday(&tv, NULL); + assert_return_code(rc, errno); + after = tv.tv_sec; + + /* + * Convert the ISO 8601 timestamp into a time_t + * Note for convenience we ignore the value of the microsecond + * part of the time stamp. + */ + rc = sscanf( + timestamp, + "%4d-%2d-%2dT%2d:%2d:%2d.%6d%1c%4d", + &tm.tm_year, + &tm.tm_mon, + &tm.tm_mday, + &tm.tm_hour, + &tm.tm_min, + &tm.tm_sec, + &usec, + c, + &tz); + assert_int_equal(9, rc); + tm.tm_year = tm.tm_year - 1900; + tm.tm_mon = tm.tm_mon - 1; + tm.tm_isdst = -1; + actual = mktime(&tm); + + /* + * The time stamp should be before <= actual <= after + */ + if (difftime(actual, before) < 0) { + char buffer[40]; + strftime(buffer, + sizeof(buffer)-1, + "%Y-%m-%dT%T", + localtime(&before)); + cm_print_error( + "time stamp \"%s\" is before start time \"%s\"\n", + timestamp, + buffer); + _fail(file, line); + } + if (difftime(after, actual) < 0) { + char buffer[40]; + strftime(buffer, + sizeof(buffer)-1, + "%Y-%m-%dT%T", + localtime(&after)); + cm_print_error( + "time stamp \"%s\" is after finish time \"%s\"\n", + timestamp, + buffer); + _fail(file, line); + } +} + +#define check_version(v, m, n)\ + _check_version(v, m, n, __FILE__, __LINE__); +/* + * Test helper to validate a version object. + */ +static void _check_version( + struct json_t *version, + int major, + int minor, + const char* file, + const int line) +{ + struct json_t *v = NULL; + int value; + + if (!json_is_object(version)) { + cm_print_error("version is not a JSON object\n"); + _fail(file, line); + } + + if (json_object_size(version) != 2) { + cm_print_error( + "Unexpected number of elements in version %zu != %d\n", + json_object_size(version), + 2); + _fail(file, line); + } + + /* + * Validate the major version number element + */ + v = json_object_get(version, "major"); + if (v == NULL) { + cm_print_error( "No major element\n"); + _fail(file, line); + } + + value = json_integer_value(v); + if (value != major) { + print_error( + "Unexpected major version number \"%d\" != \"%d\"\n", + value, + major); + _fail(file, line); + } + + /* + * Validate the minor version number element + */ + v = json_object_get(version, "minor"); + if (v == NULL) { + cm_print_error( "No minor element\n"); + _fail(file, line); + } + + value = json_integer_value(v); + if (value != minor) { + print_error( + "Unexpected minor version number \"%d\" != \"%d\"\n", + value, + minor); + _fail(file, line); + } +} + +/* + * Test helper to insert a transaction_id into a request. + */ +static void add_transaction_id(struct ldb_request *req, const char *id) +{ + struct GUID guid; + struct dsdb_control_transaction_identifier *transaction_id = NULL; + + transaction_id = talloc_zero( + req, + struct dsdb_control_transaction_identifier); + assert_non_null(transaction_id); + GUID_from_string(id, &guid); + transaction_id->transaction_guid = guid; + ldb_request_add_control( + req, + DSDB_CONTROL_TRANSACTION_IDENTIFIER_OID, + false, + transaction_id); +} + +/* + * Test helper to add a session id and user SID + */ +static void add_session_data( + TALLOC_CTX *ctx, + struct ldb_context *ldb, + const char *session, + const char *user_sid) +{ + struct auth_session_info *sess = NULL; + struct security_token *token = NULL; + struct dom_sid *sid = NULL; + struct GUID session_id; + bool ok; + + sess = talloc_zero(ctx, struct auth_session_info); + token = talloc_zero(ctx, struct security_token); + sid = talloc_zero(ctx, struct dom_sid); + ok = string_to_sid(sid, user_sid); + assert_true(ok); + token->sids = sid; + sess->security_token = token; + GUID_from_string(session, &session_id); + sess->unique_session_token = session_id; + ldb_set_opaque(ldb, DSDB_SESSION_INFO, sess); +} + +static void test_get_transaction_id(void **state) +{ + struct ldb_request *req = NULL; + struct GUID *guid; + const char * const ID = "7130cb06-2062-6a1b-409e-3514c26b1773"; + char *guid_str = NULL; + struct GUID_txt_buf guid_buff; + + + TALLOC_CTX *ctx = talloc_new(NULL); + + + /* + * No transaction id, should return a zero guid + */ + req = talloc_zero(ctx, struct ldb_request); + guid = get_transaction_id(req); + assert_null(guid); + TALLOC_FREE(req); + + /* + * And now test with the transaction_id set + */ + req = talloc_zero(ctx, struct ldb_request); + assert_non_null(req); + add_transaction_id(req, ID); + + guid = get_transaction_id(req); + guid_str = GUID_buf_string(guid, &guid_buff); + assert_string_equal(ID, guid_str); + TALLOC_FREE(req); + + TALLOC_FREE(ctx); +} + +static void test_audit_group_hr(void **state) +{ + struct ldb_context *ldb = NULL; + struct ldb_module *module = NULL; + struct ldb_request *req = NULL; + + struct tsocket_address *ts = NULL; + + const char *const SID = "S-1-5-21-2470180966-3899876309-2637894779"; + const char * const SESSION = "7130cb06-2062-6a1b-409e-3514c26b1773"; + + struct GUID transaction_id; + const char *const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773"; + + + char *line = NULL; + const char *rs = NULL; + regex_t regex; + int ret; + + + TALLOC_CTX *ctx = talloc_new(NULL); + + ldb = ldb_init(ctx, NULL); + + GUID_from_string(TRANSACTION, &transaction_id); + + module = talloc_zero(ctx, struct ldb_module); + module->ldb = ldb; + + tsocket_address_inet_from_strings(ctx, "ip", "127.0.0.1", 0, &ts); + ldb_set_opaque(ldb, "remoteAddress", ts); + + add_session_data(ctx, ldb, SESSION, SID); + + req = talloc_zero(ctx, struct ldb_request); + req->operation = LDB_ADD; + add_transaction_id(req, TRANSACTION); + + line = audit_group_human_readable( + ctx, + module, + req, + "the-action", + "the-user-name", + "the-group-name", + LDB_ERR_OPERATIONS_ERROR); + assert_non_null(line); + + rs = "\\[the-action\\] at \\[" + "[^]]*" + "\\] status \\[Operations error\\] " + "Remote host \\[ipv4:127.0.0.1:0\\] " + "SID \\[S-1-5-21-2470180966-3899876309-2637894779\\] " + "Group \\[the-group-name\\] " + "User \\[the-user-name\\]"; + + ret = regcomp(®ex, rs, 0); + assert_int_equal(0, ret); + + ret = regexec(®ex, line, 0, NULL, 0); + assert_int_equal(0, ret); + + regfree(®ex); + TALLOC_FREE(ctx); + +} + +/* + * test get_parsed_dns + * For this test we assume Valgrind or Address Sanitizer will detect any over + * runs. Also we don't care that the values are DN's only that the value in the + * element is copied to the parsed_dns. + */ +static void test_get_parsed_dns(void **state) +{ + struct ldb_message_element *el = NULL; + struct parsed_dn *dns = NULL; + + TALLOC_CTX *ctx = talloc_new(NULL); + + el = talloc_zero(ctx, struct ldb_message_element); + + /* + * empty element, zero dns + */ + dns = get_parsed_dns(ctx, el); + assert_null(dns); + + /* + * one entry + */ + el->num_values = 1; + el->values = talloc_zero_array(ctx, DATA_BLOB, 1); + el->values[0] = data_blob_string_const("The first value"); + + dns = get_parsed_dns(ctx, el); + + assert_ptr_equal(el->values[0].data, dns[0].v->data); + assert_int_equal(el->values[0].length, dns[0].v->length); + + TALLOC_FREE(dns); + TALLOC_FREE(el); + + + /* + * Multiple values + */ + el = talloc_zero(ctx, struct ldb_message_element); + el->num_values = 2; + el->values = talloc_zero_array(ctx, DATA_BLOB, 2); + el->values[0] = data_blob_string_const("The first value"); + el->values[0] = data_blob_string_const("The second value"); + + dns = get_parsed_dns(ctx, el); + + assert_ptr_equal(el->values[0].data, dns[0].v->data); + assert_int_equal(el->values[0].length, dns[0].v->length); + + assert_ptr_equal(el->values[1].data, dns[1].v->data); + assert_int_equal(el->values[1].length, dns[1].v->length); + + TALLOC_FREE(ctx); +} + +static void test_dn_compare(void **state) +{ + + struct ldb_context *ldb = NULL; + struct parsed_dn *a; + DATA_BLOB ab; + + struct parsed_dn *b; + DATA_BLOB bb; + + int res; + + TALLOC_CTX *ctx = talloc_new(NULL); + const struct GUID *ZERO_GUID = talloc_zero(ctx, struct GUID); + + ldb = ldb_init(ctx, NULL); + ldb_register_samba_handlers(ldb); + + + /* + * Identical binary DN's + */ + ab = data_blob_string_const( + "<GUID=fbee08fd-6f75-4bd4-af3f-e4f063a6379e>;" + "OU=Domain Controllers,DC=ad,DC=testing,DC=samba,DC=org"); + a = talloc_zero(ctx, struct parsed_dn); + a->v = &ab; + + bb = data_blob_string_const( + "<GUID=fbee08fd-6f75-4bd4-af3f-e4f063a6379e>;" + "OU=Domain Controllers,DC=ad,DC=testing,DC=samba,DC=org"); + b = talloc_zero(ctx, struct parsed_dn); + b->v = &bb; + + res = dn_compare(ctx, ldb, a, b); + assert_int_equal(BINARY_EQUAL, res); + /* + * DN's should not have been parsed + */ + assert_null(a->dsdb_dn); + assert_memory_equal(ZERO_GUID, &a->guid, sizeof(struct GUID)); + assert_null(b->dsdb_dn); + assert_memory_equal(ZERO_GUID, &b->guid, sizeof(struct GUID)); + + TALLOC_FREE(a); + TALLOC_FREE(b); + + /* + * differing binary DN's but equal GUID's + */ + ab = data_blob_string_const( + "<GUID=efdc91e5-5a5a-493e-9606-166ed0c2651e>;" + "OU=Domain Controllers,DC=ad,DC=testing,DC=samba,DC=com"); + a = talloc_zero(ctx, struct parsed_dn); + a->v = &ab; + + bb = data_blob_string_const( + "<GUID=efdc91e5-5a5a-493e-9606-166ed0c2651e>;" + "OU=Domain Controllers,DC=ad,DC=testing,DC=samba,DC=org"); + b = talloc_zero(ctx, struct parsed_dn); + b->v = &bb; + + res = dn_compare(ctx, ldb, a, b); + assert_int_equal(EQUAL, res); + /* + * DN's should have been parsed + */ + assert_non_null(a->dsdb_dn); + assert_memory_not_equal(ZERO_GUID, &a->guid, sizeof(struct GUID)); + assert_non_null(b->dsdb_dn); + assert_memory_not_equal(ZERO_GUID, &b->guid, sizeof(struct GUID)); + + TALLOC_FREE(a); + TALLOC_FREE(b); + + /* + * differing binary DN's but and second guid greater + */ + ab = data_blob_string_const( + "<GUID=efdc91e5-5a5a-493e-9606-166ed0c2651d>;" + "OU=Domain Controllers,DC=ad,DC=testing,DC=samba,DC=com"); + a = talloc_zero(ctx, struct parsed_dn); + a->v = &ab; + + bb = data_blob_string_const( + "<GUID=efdc91e5-5a5a-493e-9606-166ed0c2651e>;" + "OU=Domain Controllers,DC=ad,DC=testing,DC=samba,DC=org"); + b = talloc_zero(ctx, struct parsed_dn); + b->v = &bb; + + res = dn_compare(ctx, ldb, a, b); + assert_int_equal(LESS_THAN, res); + /* + * DN's should have been parsed + */ + assert_non_null(a->dsdb_dn); + assert_memory_not_equal(ZERO_GUID, &a->guid, sizeof(struct GUID)); + assert_non_null(b->dsdb_dn); + assert_memory_not_equal(ZERO_GUID, &b->guid, sizeof(struct GUID)); + + TALLOC_FREE(a); + TALLOC_FREE(b); + + /* + * differing binary DN's but and second guid less + */ + ab = data_blob_string_const( + "<GUID=efdc91e5-5a5a-493e-9606-166ed0c2651d>;" + "OU=Domain Controllers,DC=ad,DC=testing,DC=samba,DC=com"); + a = talloc_zero(ctx, struct parsed_dn); + a->v = &ab; + + bb = data_blob_string_const( + "<GUID=efdc91e5-5a5a-493e-9606-166ed0c2651c>;" + "OU=Domain Controllers,DC=ad,DC=testing,DC=samba,DC=org"); + b = talloc_zero(ctx, struct parsed_dn); + b->v = &bb; + + res = dn_compare(ctx, ldb, a, b); + assert_int_equal(GREATER_THAN, res); + /* + * DN's should have been parsed + */ + assert_non_null(a->dsdb_dn); + assert_memory_not_equal(ZERO_GUID, &a->guid, sizeof(struct GUID)); + assert_non_null(b->dsdb_dn); + assert_memory_not_equal(ZERO_GUID, &b->guid, sizeof(struct GUID)); + + TALLOC_FREE(a); + TALLOC_FREE(b); + + TALLOC_FREE(ctx); +} + +static void test_get_primary_group_dn(void **state) +{ + + struct ldb_context *ldb = NULL; + struct ldb_module *module = NULL; + const uint32_t RID = 71; + struct dom_sid sid; + const char *SID = "S-1-5-21-2470180966-3899876309-2637894779"; + const char *DN = "OU=Things,DC=ad,DC=testing,DC=samba,DC=org"; + const char *dn; + + TALLOC_CTX *ctx = talloc_new(NULL); + + ldb = ldb_init(ctx, NULL); + ldb_register_samba_handlers(ldb); + + module = talloc_zero(ctx, struct ldb_module); + module->ldb = ldb; + + /* + * Pass an empty dom sid this will cause dom_sid_split_rid to fail; + * assign to sid.num_auths to suppress a valgrind warning. + */ + sid.num_auths = 0; + dn = get_primary_group_dn(ctx, module, &sid, RID); + assert_null(dn); + + /* + * A valid dom sid + */ + assert_true(string_to_sid(&sid, SID)); + g_dn = DN; + dn = get_primary_group_dn(ctx, module, &sid, RID); + assert_non_null(dn); + assert_string_equal(DN, dn); + assert_int_equal(LDB_SCOPE_BASE, g_scope); + assert_int_equal(0, g_dsdb_flags); + assert_null(g_attrs); + assert_null(g_exp_fmt); + assert_string_equal + ("<SID=S-1-5-21-2470180966-3899876309-71>", + ldb_dn_get_extended_linearized(ctx, g_basedn, 1)); + + /* + * Test dsdb search failure + */ + g_status = LDB_ERR_NO_SUCH_OBJECT; + dn = get_primary_group_dn(ctx, module, &sid, RID); + assert_null(dn); + + TALLOC_FREE(ldb); + TALLOC_FREE(ctx); +} + +static void test_audit_group_json(void **state) +{ + struct ldb_context *ldb = NULL; + struct ldb_module *module = NULL; + struct ldb_request *req = NULL; + + struct tsocket_address *ts = NULL; + + const char *const SID = "S-1-5-21-2470180966-3899876309-2637894779"; + const char * const SESSION = "7130cb06-2062-6a1b-409e-3514c26b1773"; + + struct GUID transaction_id; + const char *const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773"; + + enum event_id_type event_id = EVT_ID_USER_ADDED_TO_GLOBAL_SEC_GROUP; + + struct json_object json; + json_t *audit = NULL; + json_t *v = NULL; + json_t *o = NULL; + time_t before; + struct timeval tv; + int rc; + + + TALLOC_CTX *ctx = talloc_new(NULL); + + ldb = ldb_init(ctx, NULL); + + GUID_from_string(TRANSACTION, &transaction_id); + + module = talloc_zero(ctx, struct ldb_module); + module->ldb = ldb; + + tsocket_address_inet_from_strings(ctx, "ip", "127.0.0.1", 0, &ts); + ldb_set_opaque(ldb, "remoteAddress", ts); + + add_session_data(ctx, ldb, SESSION, SID); + + req = talloc_zero(ctx, struct ldb_request); + req->operation = LDB_ADD; + add_transaction_id(req, TRANSACTION); + + rc = gettimeofday(&tv, NULL); + assert_return_code(rc, errno); + before = tv.tv_sec; + json = audit_group_json(module, + req, + "the-action", + "the-user-name", + "the-group-name", + event_id, + LDB_SUCCESS); + assert_int_equal(3, json_object_size(json.root)); + + v = json_object_get(json.root, "type"); + assert_non_null(v); + assert_string_equal("groupChange", json_string_value(v)); + + v = json_object_get(json.root, "timestamp"); + assert_non_null(v); + assert_true(json_is_string(v)); + check_timestamp(before, json_string_value(v)); + + audit = json_object_get(json.root, "groupChange"); + assert_non_null(audit); + assert_true(json_is_object(audit)); + assert_int_equal(11, json_object_size(audit)); + + o = json_object_get(audit, "version"); + assert_non_null(o); + check_version(o, AUDIT_MAJOR, AUDIT_MINOR); + + v = json_object_get(audit, "eventId"); + assert_non_null(v); + assert_true(json_is_integer(v)); + assert_int_equal(EVT_ID_USER_ADDED_TO_GLOBAL_SEC_GROUP, + json_integer_value(v)); + + v = json_object_get(audit, "statusCode"); + assert_non_null(v); + assert_true(json_is_integer(v)); + assert_int_equal(LDB_SUCCESS, json_integer_value(v)); + + v = json_object_get(audit, "status"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal("Success", json_string_value(v)); + + v = json_object_get(audit, "user"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal("the-user-name", json_string_value(v)); + + v = json_object_get(audit, "group"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal("the-group-name", json_string_value(v)); + + v = json_object_get(audit, "action"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal("the-action", json_string_value(v)); + + json_free(&json); + TALLOC_FREE(ctx); +} + +static void test_audit_group_json_error(void **state) +{ + struct ldb_context *ldb = NULL; + struct ldb_module *module = NULL; + struct ldb_request *req = NULL; + + struct tsocket_address *ts = NULL; + + const char *const SID = "S-1-5-21-2470180966-3899876309-2637894779"; + const char * const SESSION = "7130cb06-2062-6a1b-409e-3514c26b1773"; + + struct GUID transaction_id; + const char *const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773"; + + enum event_id_type event_id = EVT_ID_USER_ADDED_TO_GLOBAL_SEC_GROUP; + + struct json_object json; + json_t *audit = NULL; + json_t *v = NULL; + json_t *o = NULL; + time_t before; + struct timeval tv; + int rc; + + + TALLOC_CTX *ctx = talloc_new(NULL); + + ldb = ldb_init(ctx, NULL); + + GUID_from_string(TRANSACTION, &transaction_id); + + module = talloc_zero(ctx, struct ldb_module); + module->ldb = ldb; + + tsocket_address_inet_from_strings(ctx, "ip", "127.0.0.1", 0, &ts); + ldb_set_opaque(ldb, "remoteAddress", ts); + + add_session_data(ctx, ldb, SESSION, SID); + + req = talloc_zero(ctx, struct ldb_request); + req->operation = LDB_ADD; + add_transaction_id(req, TRANSACTION); + + rc = gettimeofday(&tv, NULL); + assert_return_code(rc, errno); + before = tv.tv_sec; + json = audit_group_json(module, + req, + "the-action", + "the-user-name", + "the-group-name", + event_id, + LDB_ERR_OPERATIONS_ERROR); + assert_int_equal(3, json_object_size(json.root)); + + v = json_object_get(json.root, "type"); + assert_non_null(v); + assert_string_equal("groupChange", json_string_value(v)); + + v = json_object_get(json.root, "timestamp"); + assert_non_null(v); + assert_true(json_is_string(v)); + check_timestamp(before, json_string_value(v)); + + audit = json_object_get(json.root, "groupChange"); + assert_non_null(audit); + assert_true(json_is_object(audit)); + assert_int_equal(11, json_object_size(audit)); + + o = json_object_get(audit, "version"); + assert_non_null(o); + check_version(o, AUDIT_MAJOR, AUDIT_MINOR); + + v = json_object_get(audit, "eventId"); + assert_non_null(v); + assert_true(json_is_integer(v)); + assert_int_equal( + EVT_ID_USER_ADDED_TO_GLOBAL_SEC_GROUP, + json_integer_value(v)); + + v = json_object_get(audit, "statusCode"); + assert_non_null(v); + assert_true(json_is_integer(v)); + assert_int_equal(LDB_ERR_OPERATIONS_ERROR, json_integer_value(v)); + + v = json_object_get(audit, "status"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal("Operations error", json_string_value(v)); + + v = json_object_get(audit, "user"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal("the-user-name", json_string_value(v)); + + v = json_object_get(audit, "group"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal("the-group-name", json_string_value(v)); + + v = json_object_get(audit, "action"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal("the-action", json_string_value(v)); + + json_free(&json); + TALLOC_FREE(ctx); +} + +static void test_audit_group_json_no_event(void **state) +{ + struct ldb_context *ldb = NULL; + struct ldb_module *module = NULL; + struct ldb_request *req = NULL; + + struct tsocket_address *ts = NULL; + + const char *const SID = "S-1-5-21-2470180966-3899876309-2637894779"; + const char * const SESSION = "7130cb06-2062-6a1b-409e-3514c26b1773"; + + struct GUID transaction_id; + const char *const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773"; + + enum event_id_type event_id = EVT_ID_NONE; + + struct json_object json; + json_t *audit = NULL; + json_t *v = NULL; + json_t *o = NULL; + time_t before; + struct timeval tv; + int rc; + + + TALLOC_CTX *ctx = talloc_new(NULL); + + ldb = ldb_init(ctx, NULL); + + GUID_from_string(TRANSACTION, &transaction_id); + + module = talloc_zero(ctx, struct ldb_module); + module->ldb = ldb; + + tsocket_address_inet_from_strings(ctx, "ip", "127.0.0.1", 0, &ts); + ldb_set_opaque(ldb, "remoteAddress", ts); + + add_session_data(ctx, ldb, SESSION, SID); + + req = talloc_zero(ctx, struct ldb_request); + req->operation = LDB_ADD; + add_transaction_id(req, TRANSACTION); + + rc = gettimeofday(&tv, NULL); + assert_return_code(rc, errno); + before = tv.tv_sec; + json = audit_group_json(module, + req, + "the-action", + "the-user-name", + "the-group-name", + event_id, + LDB_SUCCESS); + assert_int_equal(3, json_object_size(json.root)); + + v = json_object_get(json.root, "type"); + assert_non_null(v); + assert_string_equal("groupChange", json_string_value(v)); + + v = json_object_get(json.root, "timestamp"); + assert_non_null(v); + assert_true(json_is_string(v)); + check_timestamp(before, json_string_value(v)); + + audit = json_object_get(json.root, "groupChange"); + assert_non_null(audit); + assert_true(json_is_object(audit)); + assert_int_equal(10, json_object_size(audit)); + + o = json_object_get(audit, "version"); + assert_non_null(o); + check_version(o, AUDIT_MAJOR, AUDIT_MINOR); + + v = json_object_get(audit, "eventId"); + assert_null(v); + + v = json_object_get(audit, "statusCode"); + assert_non_null(v); + assert_true(json_is_integer(v)); + assert_int_equal(LDB_SUCCESS, json_integer_value(v)); + + v = json_object_get(audit, "status"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal("Success", json_string_value(v)); + + v = json_object_get(audit, "user"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal("the-user-name", json_string_value(v)); + + v = json_object_get(audit, "group"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal("the-group-name", json_string_value(v)); + + v = json_object_get(audit, "action"); + assert_non_null(v); + assert_true(json_is_string(v)); + assert_string_equal("the-action", json_string_value(v)); + + json_free(&json); + TALLOC_FREE(ctx); +} +static void setup_ldb( + TALLOC_CTX *ctx, + struct ldb_context **ldb, + struct ldb_module **module, + const char *ip, + const char *session, + const char *sid) +{ + struct tsocket_address *ts = NULL; + struct audit_context *context = NULL; + + *ldb = ldb_init(ctx, NULL); + ldb_register_samba_handlers(*ldb); + + + *module = talloc_zero(ctx, struct ldb_module); + (*module)->ldb = *ldb; + + context = talloc_zero(*module, struct audit_context); + context->send_events = true; + context->msg_ctx = (struct imessaging_context *) 0x01; + + ldb_module_set_private(*module, context); + + tsocket_address_inet_from_strings(ctx, "ip", "127.0.0.1", 0, &ts); + ldb_set_opaque(*ldb, "remoteAddress", ts); + + add_session_data(ctx, *ldb, session, sid); +} + +/* + * Test the removal of a user from a group. + * + * The new element contains one group member + * The old element contains two group member + * + * Expect to see the removed entry logged. + * + * This test confirms bug 13664 + * https://bugzilla.samba.org/show_bug.cgi?id=13664 + */ +static void test_log_membership_changes_removed(void **state) +{ + struct ldb_context *ldb = NULL; + struct ldb_module *module = NULL; + const char * const SID = "S-1-5-21-2470180966-3899876309-2637894779"; + const char * const SESSION = "7130cb06-2062-6a1b-409e-3514c26b1773"; + const char * const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773"; + const char * const IP = "127.0.0.1"; + struct ldb_request *req = NULL; + struct ldb_message_element *new_el = NULL; + struct ldb_message_element *old_el = NULL; + uint32_t group_type = GTYPE_SECURITY_GLOBAL_GROUP; + int status = 0; + TALLOC_CTX *ctx = talloc_new(NULL); + + setup_ldb(ctx, &ldb, &module, IP, SESSION, SID); + + /* + * Build the ldb_request + */ + req = talloc_zero(ctx, struct ldb_request); + req->operation = LDB_ADD; + add_transaction_id(req, TRANSACTION); + + /* + * Populate the new elements, containing one entry. + * Indicating that one element has been removed + */ + new_el = talloc_zero(ctx, struct ldb_message_element); + new_el->num_values = 1; + new_el->values = talloc_zero_array(ctx, DATA_BLOB, 1); + new_el->values[0] = data_blob_string_const( + "<GUID=081519b5-a709-44a0-bc95-dd4bfe809bf8>;" + "CN=testuser131953,CN=Users,DC=addom,DC=samba," + "DC=example,DC=com"); + + /* + * Populate the old elements, with two elements + * The first is the same as the one in new elements. + */ + old_el = talloc_zero(ctx, struct ldb_message_element); + old_el->num_values = 2; + old_el->values = talloc_zero_array(ctx, DATA_BLOB, 2); + old_el->values[0] = data_blob_string_const( + "<GUID=cb8c2777-dcf5-419c-ab57-f645dbdf681b>;" + "cn=grpadttstuser01,cn=users,DC=addom," + "DC=samba,DC=example,DC=com"); + old_el->values[1] = data_blob_string_const( + "<GUID=081519b5-a709-44a0-bc95-dd4bfe809bf8>;" + "CN=testuser131953,CN=Users,DC=addom,DC=samba," + "DC=example,DC=com"); + + /* + * call log_membership_changes + */ + messages_sent = 0; + log_membership_changes(module, req, new_el, old_el, group_type, status); + + /* + * Check the results + */ + assert_int_equal(1, messages_sent); + + check_group_change_message( + 0, + "cn=grpadttstuser01,cn=users,DC=addom,DC=samba,DC=example,DC=com", + "Removed", + EVT_ID_USER_REMOVED_FROM_GLOBAL_SEC_GROUP); + + /* + * Clean up + */ + json_free(&messages[0]); + TALLOC_FREE(ctx); +} + +/* test log_membership_changes + * + * old contains 2 user dn's + * new contains 0 user dn's + * + * Expect to see both dn's logged as deleted. + */ +static void test_log_membership_changes_remove_all(void **state) +{ + struct ldb_context *ldb = NULL; + struct ldb_module *module = NULL; + const char * const SID = "S-1-5-21-2470180966-3899876309-2637894779"; + const char * const SESSION = "7130cb06-2062-6a1b-409e-3514c26b1773"; + const char * const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773"; + const char * const IP = "127.0.0.1"; + struct ldb_request *req = NULL; + struct ldb_message_element *new_el = NULL; + struct ldb_message_element *old_el = NULL; + int status = 0; + uint32_t group_type = GTYPE_SECURITY_BUILTIN_LOCAL_GROUP; + TALLOC_CTX *ctx = talloc_new(NULL); + + setup_ldb(ctx, &ldb, &module, IP, SESSION, SID); + + /* + * Build the ldb_request + */ + req = talloc_zero(ctx, struct ldb_request); + req->operation = LDB_ADD; + add_transaction_id(req, TRANSACTION); + + /* + * Populate the new elements, containing no entries. + * Indicating that all elements have been removed + */ + new_el = talloc_zero(ctx, struct ldb_message_element); + new_el->num_values = 0; + new_el->values = NULL; + + /* + * Populate the old elements, with two elements + */ + old_el = talloc_zero(ctx, struct ldb_message_element); + old_el->num_values = 2; + old_el->values = talloc_zero_array(ctx, DATA_BLOB, 2); + old_el->values[0] = data_blob_string_const( + "<GUID=cb8c2777-dcf5-419c-ab57-f645dbdf681b>;" + "cn=grpadttstuser01,cn=users,DC=addom," + "DC=samba,DC=example,DC=com"); + old_el->values[1] = data_blob_string_const( + "<GUID=081519b5-a709-44a0-bc95-dd4bfe809bf8>;" + "CN=testuser131953,CN=Users,DC=addom,DC=samba," + "DC=example,DC=com"); + + /* + * call log_membership_changes + */ + messages_sent = 0; + log_membership_changes(module, req, new_el, old_el, group_type, status); + + /* + * Check the results + */ + assert_int_equal(2, messages_sent); + + check_group_change_message( + 0, + "cn=grpadttstuser01,cn=users,DC=addom,DC=samba,DC=example,DC=com", + "Removed", + EVT_ID_USER_REMOVED_FROM_LOCAL_SEC_GROUP); + + check_group_change_message( + 1, + "CN=testuser131953,CN=Users,DC=addom,DC=samba,DC=example,DC=com", + "Removed", + EVT_ID_USER_REMOVED_FROM_LOCAL_SEC_GROUP); + + /* + * Clean up + */ + json_free(&messages[0]); + json_free(&messages[1]); + TALLOC_FREE(ctx); +} + +/* test log_membership_changes + * + * Add an entry. + * + * Old entries contains a single user dn + * New entries contains 2 user dn's, one matching the dn in old entries + * + * Should see a single new entry logged. + */ +static void test_log_membership_changes_added(void **state) +{ + struct ldb_context *ldb = NULL; + struct ldb_module *module = NULL; + const char * const SID = "S-1-5-21-2470180966-3899876309-2637894779"; + const char * const SESSION = "7130cb06-2062-6a1b-409e-3514c26b1773"; + const char * const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773"; + const char * const IP = "127.0.0.1"; + struct ldb_request *req = NULL; + struct ldb_message_element *new_el = NULL; + struct ldb_message_element *old_el = NULL; + uint32_t group_type = GTYPE_SECURITY_DOMAIN_LOCAL_GROUP; + int status = 0; + TALLOC_CTX *ctx = talloc_new(NULL); + + setup_ldb(ctx, &ldb, &module, IP, SESSION, SID); + + /* + * Build the ldb_request + */ + req = talloc_zero(ctx, struct ldb_request); + req->operation = LDB_ADD; + add_transaction_id(req, TRANSACTION); + + /* + * Populate the old elements adding a single entry. + */ + old_el = talloc_zero(ctx, struct ldb_message_element); + old_el->num_values = 1; + old_el->values = talloc_zero_array(ctx, DATA_BLOB, 1); + old_el->values[0] = data_blob_string_const( + "<GUID=081519b5-a709-44a0-bc95-dd4bfe809bf8>;" + "CN=testuser131953,CN=Users,DC=addom,DC=samba," + "DC=example,DC=com"); + + /* + * Populate the new elements adding two entries. One matches the entry + * in old elements. We expect to see the other element logged as Added + */ + new_el = talloc_zero(ctx, struct ldb_message_element); + new_el->num_values = 2; + new_el->values = talloc_zero_array(ctx, DATA_BLOB, 2); + new_el->values[0] = data_blob_string_const( + "<GUID=cb8c2777-dcf5-419c-ab57-f645dbdf681b>;" + "cn=grpadttstuser01,cn=users,DC=addom," + "DC=samba,DC=example,DC=com"); + new_el->values[1] = data_blob_string_const( + "<GUID=081519b5-a709-44a0-bc95-dd4bfe809bf8>;" + "CN=testuser131953,CN=Users,DC=addom,DC=samba," + "DC=example,DC=com"); + + /* + * call log_membership_changes + */ + messages_sent = 0; + log_membership_changes(module, req, new_el, old_el, group_type, status); + + /* + * Check the results + */ + assert_int_equal(1, messages_sent); + + check_group_change_message( + 0, + "cn=grpadttstuser01,cn=users,DC=addom,DC=samba,DC=example,DC=com", + "Added", + EVT_ID_USER_ADDED_TO_LOCAL_SEC_GROUP); + + /* + * Clean up + */ + json_free(&messages[0]); + TALLOC_FREE(ctx); +} + +/* + * test log_membership_changes. + * + * Old entries is empty + * New entries contains 2 user dn's + * + * Expect to see log messages for two added users + */ +static void test_log_membership_changes_add_to_empty(void **state) +{ + struct ldb_context *ldb = NULL; + struct ldb_module *module = NULL; + const char * const SID = "S-1-5-21-2470180966-3899876309-2637894779"; + const char * const SESSION = "7130cb06-2062-6a1b-409e-3514c26b1773"; + const char * const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773"; + const char * const IP = "127.0.0.1"; + struct ldb_request *req = NULL; + struct ldb_message_element *new_el = NULL; + struct ldb_message_element *old_el = NULL; + uint32_t group_type = GTYPE_SECURITY_UNIVERSAL_GROUP; + int status = 0; + TALLOC_CTX *ctx = talloc_new(NULL); + + /* + * Set up the ldb and module structures + */ + setup_ldb(ctx, &ldb, &module, IP, SESSION, SID); + + /* + * Build the request structure + */ + req = talloc_zero(ctx, struct ldb_request); + req->operation = LDB_ADD; + add_transaction_id(req, TRANSACTION); + + /* + * Build the element containing the old values + */ + old_el = talloc_zero(ctx, struct ldb_message_element); + old_el->num_values = 0; + old_el->values = NULL; + + /* + * Build the element containing the new values + */ + new_el = talloc_zero(ctx, struct ldb_message_element); + new_el->num_values = 2; + new_el->values = talloc_zero_array(ctx, DATA_BLOB, 2); + new_el->values[0] = data_blob_string_const( + "<GUID=cb8c2777-dcf5-419c-ab57-f645dbdf681b>;" + "cn=grpadttstuser01,cn=users,DC=addom," + "DC=samba,DC=example,DC=com"); + new_el->values[1] = data_blob_string_const( + "<GUID=081519b5-a709-44a0-bc95-dd4bfe809bf8>;" + "CN=testuser131953,CN=Users,DC=addom,DC=samba," + "DC=example,DC=com"); + + /* + * Run log membership changes + */ + messages_sent = 0; + log_membership_changes(module, req, new_el, old_el, group_type, status); + assert_int_equal(2, messages_sent); + + check_group_change_message( + 0, + "cn=grpadttstuser01,cn=users,DC=addom,DC=samba,DC=example,DC=com", + "Added", + EVT_ID_USER_ADDED_TO_UNIVERSAL_SEC_GROUP); + + check_group_change_message( + 1, + "CN=testuser131953,CN=Users,DC=addom,DC=samba,DC=example,DC=com", + "Added", + EVT_ID_USER_ADDED_TO_UNIVERSAL_SEC_GROUP); + + json_free(&messages[0]); + json_free(&messages[1]); + TALLOC_FREE(ctx); +} + +/* test log_membership_changes + * + * Test Replication Meta Data flag handling. + * + * 4 entries in old and new entries with their RMD_FLAGS set as below: + * old new + * 1) 0 0 Not logged + * 2) 1 1 Both deleted, no change not logged + * 3) 0 1 New tagged as deleted, log as deleted + * 4) 1 0 Has been undeleted, log as an add + * + * Should see a single new entry logged. + */ +static void test_log_membership_changes_rmd_flags(void **state) +{ + struct ldb_context *ldb = NULL; + struct ldb_module *module = NULL; + const char * const SID = "S-1-5-21-2470180966-3899876309-2637894779"; + const char * const SESSION = "7130cb06-2062-6a1b-409e-3514c26b1773"; + const char * const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773"; + const char * const IP = "127.0.0.1"; + struct ldb_request *req = NULL; + struct ldb_message_element *new_el = NULL; + struct ldb_message_element *old_el = NULL; + uint32_t group_type = GTYPE_SECURITY_GLOBAL_GROUP; + int status = 0; + TALLOC_CTX *ctx = talloc_new(NULL); + + setup_ldb(ctx, &ldb, &module, IP, SESSION, SID); + + /* + * Build the ldb_request + */ + req = talloc_zero(ctx, struct ldb_request); + req->operation = LDB_ADD; + add_transaction_id(req, TRANSACTION); + + /* + * Populate the old elements. + */ + old_el = talloc_zero(ctx, struct ldb_message_element); + old_el->num_values = 4; + old_el->values = talloc_zero_array(ctx, DATA_BLOB, 4); + old_el->values[0] = data_blob_string_const( + "<GUID=cb8c2777-dcf5-419c-ab57-f645dbdf681b>;" + "<RMD_FLAGS=0>;" + "cn=grpadttstuser01,cn=users,DC=addom," + "DC=samba,DC=example,DC=com"); + old_el->values[1] = data_blob_string_const( + "<GUID=cb8c2777-dcf5-419c-ab57-f645dbdf681c>;" + "<RMD_FLAGS=1>;" + "cn=grpadttstuser02,cn=users,DC=addom," + "DC=samba,DC=example,DC=com"); + old_el->values[2] = data_blob_string_const( + "<GUID=cb8c2777-dcf5-419c-ab57-f645dbdf681d>;" + "<RMD_FLAGS=0>;" + "cn=grpadttstuser03,cn=users,DC=addom," + "DC=samba,DC=example,DC=com"); + old_el->values[3] = data_blob_string_const( + "<GUID=cb8c2777-dcf5-419c-ab57-f645dbdf681e>;" + "<RMD_FLAGS=1>;" + "cn=grpadttstuser04,cn=users,DC=addom," + "DC=samba,DC=example,DC=com"); + + /* + * Populate the new elements. + */ + new_el = talloc_zero(ctx, struct ldb_message_element); + new_el->num_values = 4; + new_el->values = talloc_zero_array(ctx, DATA_BLOB, 4); + new_el->values[0] = data_blob_string_const( + "<GUID=cb8c2777-dcf5-419c-ab57-f645dbdf681b>;" + "<RMD_FLAGS=0>;" + "cn=grpadttstuser01,cn=users,DC=addom," + "DC=samba,DC=example,DC=com"); + new_el->values[1] = data_blob_string_const( + "<GUID=cb8c2777-dcf5-419c-ab57-f645dbdf681c>;" + "<RMD_FLAGS=1>;" + "cn=grpadttstuser02,cn=users,DC=addom," + "DC=samba,DC=example,DC=com"); + new_el->values[2] = data_blob_string_const( + "<GUID=cb8c2777-dcf5-419c-ab57-f645dbdf681d>;" + "<RMD_FLAGS=1>;" + "cn=grpadttstuser03,cn=users,DC=addom," + "DC=samba,DC=example,DC=com"); + new_el->values[3] = data_blob_string_const( + "<GUID=cb8c2777-dcf5-419c-ab57-f645dbdf681e>;" + "<RMD_FLAGS=0>;" + "cn=grpadttstuser04,cn=users,DC=addom," + "DC=samba,DC=example,DC=com"); + + /* + * call log_membership_changes + */ + messages_sent = 0; + log_membership_changes(module, req, new_el, old_el, group_type, status); + + /* + * Check the results + */ + assert_int_equal(2, messages_sent); + + check_group_change_message( + 0, + "cn=grpadttstuser03,cn=users,DC=addom,DC=samba,DC=example,DC=com", + "Removed", + EVT_ID_USER_REMOVED_FROM_GLOBAL_SEC_GROUP); + check_group_change_message( + 1, + "cn=grpadttstuser04,cn=users,DC=addom,DC=samba,DC=example,DC=com", + "Added", + EVT_ID_USER_ADDED_TO_GLOBAL_SEC_GROUP); + + /* + * Clean up + */ + json_free(&messages[0]); + json_free(&messages[1]); + TALLOC_FREE(ctx); +} + +static void test_get_add_member_event(void **state) +{ + assert_int_equal( + EVT_ID_USER_ADDED_TO_LOCAL_SEC_GROUP, + get_add_member_event(GTYPE_SECURITY_BUILTIN_LOCAL_GROUP)); + + assert_int_equal(EVT_ID_USER_ADDED_TO_GLOBAL_SEC_GROUP, + get_add_member_event(GTYPE_SECURITY_GLOBAL_GROUP)); + + assert_int_equal( + EVT_ID_USER_ADDED_TO_LOCAL_SEC_GROUP, + get_add_member_event(GTYPE_SECURITY_DOMAIN_LOCAL_GROUP)); + + assert_int_equal(EVT_ID_USER_ADDED_TO_UNIVERSAL_SEC_GROUP, + get_add_member_event(GTYPE_SECURITY_UNIVERSAL_GROUP)); + + assert_int_equal(EVT_ID_USER_ADDED_TO_GLOBAL_GROUP, + get_add_member_event(GTYPE_DISTRIBUTION_GLOBAL_GROUP)); + + assert_int_equal( + EVT_ID_USER_ADDED_TO_LOCAL_GROUP, + get_add_member_event(GTYPE_DISTRIBUTION_DOMAIN_LOCAL_GROUP)); + + assert_int_equal( + EVT_ID_USER_ADDED_TO_UNIVERSAL_GROUP, + get_add_member_event(GTYPE_DISTRIBUTION_UNIVERSAL_GROUP)); + + assert_int_equal(EVT_ID_NONE, get_add_member_event(0)); + + assert_int_equal(EVT_ID_NONE, get_add_member_event(UINT32_MAX)); +} + +static void test_get_remove_member_event(void **state) +{ + assert_int_equal( + EVT_ID_USER_REMOVED_FROM_LOCAL_SEC_GROUP, + get_remove_member_event(GTYPE_SECURITY_BUILTIN_LOCAL_GROUP)); + + assert_int_equal(EVT_ID_USER_REMOVED_FROM_GLOBAL_SEC_GROUP, + get_remove_member_event(GTYPE_SECURITY_GLOBAL_GROUP)); + + assert_int_equal( + EVT_ID_USER_REMOVED_FROM_LOCAL_SEC_GROUP, + get_remove_member_event(GTYPE_SECURITY_DOMAIN_LOCAL_GROUP)); + + assert_int_equal( + EVT_ID_USER_REMOVED_FROM_UNIVERSAL_SEC_GROUP, + get_remove_member_event(GTYPE_SECURITY_UNIVERSAL_GROUP)); + + assert_int_equal( + EVT_ID_USER_REMOVED_FROM_GLOBAL_GROUP, + get_remove_member_event(GTYPE_DISTRIBUTION_GLOBAL_GROUP)); + + assert_int_equal( + EVT_ID_USER_REMOVED_FROM_LOCAL_GROUP, + get_remove_member_event(GTYPE_DISTRIBUTION_DOMAIN_LOCAL_GROUP)); + + assert_int_equal( + EVT_ID_USER_REMOVED_FROM_UNIVERSAL_GROUP, + get_remove_member_event(GTYPE_DISTRIBUTION_UNIVERSAL_GROUP)); + + assert_int_equal(EVT_ID_NONE, get_remove_member_event(0)); + + assert_int_equal(EVT_ID_NONE, get_remove_member_event(UINT32_MAX)); +} + +/* test log_group_membership_changes + * + * Happy path test case + * + */ +static void test_log_group_membership_changes(void **state) +{ + struct ldb_context *ldb = NULL; + struct ldb_module *module = NULL; + const char * const SID = "S-1-5-21-2470180966-3899876309-2637894779"; + const char * const SESSION = "7130cb06-2062-6a1b-409e-3514c26b1773"; + const char * const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773"; + const char * const IP = "127.0.0.1"; + struct ldb_request *req = NULL; + struct ldb_message *msg = NULL; + struct ldb_message_element *el = NULL; + struct audit_callback_context *acc = NULL; + struct ldb_result *res = NULL; + struct ldb_message *new_msg = NULL; + struct ldb_message_element *group_type = NULL; + const char *group_type_str = NULL; + struct ldb_message_element *new_el = NULL; + struct ldb_message_element *old_el = NULL; + int status = 0; + TALLOC_CTX *ctx = talloc_new(NULL); + + setup_ldb(ctx, &ldb, &module, IP, SESSION, SID); + + /* + * Build the ldb message + */ + msg = talloc_zero(ctx, struct ldb_message); + + /* + * Populate message elements, adding a new entry to the membership list + * + */ + + el = talloc_zero(ctx, struct ldb_message_element); + el->name = "member"; + el->num_values = 1; + el->values = talloc_zero_array(ctx, DATA_BLOB, 1); + el->values[0] = data_blob_string_const( + "<GUID=081519b5-a709-44a0-bc95-dd4bfe809bf8>;" + "CN=testuser131953,CN=Users,DC=addom,DC=samba," + "DC=example,DC=com"); + msg->elements = el; + msg->num_elements = 1; + + /* + * Build the ldb_request + */ + req = talloc_zero(ctx, struct ldb_request); + req->operation = LDB_ADD; + req->op.add.message = msg; + add_transaction_id(req, TRANSACTION); + + /* + * Build the initial state of the database + */ + old_el = talloc_zero(ctx, struct ldb_message_element); + old_el->name = "member"; + old_el->num_values = 1; + old_el->values = talloc_zero_array(ctx, DATA_BLOB, 1); + old_el->values[0] = data_blob_string_const( + "<GUID=cb8c2777-dcf5-419c-ab57-f645dbdf681b>;" + "cn=grpadttstuser01,cn=users,DC=addom," + "DC=samba,DC=example,DC=com"); + + /* + * Build the updated state of the database + */ + res = talloc_zero(ctx, struct ldb_result); + new_msg = talloc_zero(ctx, struct ldb_message); + new_el = talloc_zero(ctx, struct ldb_message_element); + new_el->name = "member"; + new_el->num_values = 2; + new_el->values = talloc_zero_array(ctx, DATA_BLOB, 2); + new_el->values[0] = data_blob_string_const( + "<GUID=cb8c2777-dcf5-419c-ab57-f645dbdf681b>;" + "cn=grpadttstuser01,cn=users,DC=addom," + "DC=samba,DC=example,DC=com"); + new_el->values[1] = data_blob_string_const( + "<GUID=081519b5-a709-44a0-bc95-dd4bfe809bf8>;" + "CN=testuser131953,CN=Users,DC=addom,DC=samba," + "DC=example,DC=com"); + + group_type = talloc_zero(ctx, struct ldb_message_element); + group_type->name = "groupType"; + group_type->num_values = 1; + group_type->values = talloc_zero_array(ctx, DATA_BLOB, 1); + group_type_str = talloc_asprintf(ctx, "%u", GTYPE_SECURITY_GLOBAL_GROUP); + group_type->values[0] = data_blob_string_const(group_type_str); + + + new_msg->elements = talloc_zero_array(ctx, struct ldb_message_element, 2); + new_msg->num_elements = 2; + new_msg->elements[0] = *new_el; + new_msg->elements[1] = *group_type; + + res->count = 1; + res->msgs = &new_msg; + + acc = talloc_zero(ctx, struct audit_callback_context); + acc->request = req; + acc->module = module; + acc->members = old_el; + /* + * call log_membership_changes + */ + messages_sent = 0; + g_result = res; + g_status = LDB_SUCCESS; + log_group_membership_changes(acc, status); + g_result = NULL; + + /* + * Check the results + */ + assert_int_equal(1, messages_sent); + + check_group_change_message( + 0, + "CN=testuser131953,CN=Users,DC=addom,DC=samba,DC=example,DC=com", + "Added", + EVT_ID_USER_ADDED_TO_GLOBAL_SEC_GROUP); + + /* + * Clean up + */ + json_free(&messages[0]); + TALLOC_FREE(ctx); +} + +/* test log_group_membership_changes + * + * The ldb query to retrieve the new values failed. + * + * Should generate group membership change Failure message. + * + */ +static void test_log_group_membership_changes_read_new_failure(void **state) +{ + struct ldb_context *ldb = NULL; + struct ldb_module *module = NULL; + const char * const SID = "S-1-5-21-2470180966-3899876309-2637894779"; + const char * const SESSION = "7130cb06-2062-6a1b-409e-3514c26b1773"; + const char * const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773"; + const char * const IP = "127.0.0.1"; + struct ldb_request *req = NULL; + struct ldb_message *msg = NULL; + struct ldb_message_element *el = NULL; + struct audit_callback_context *acc = NULL; + struct ldb_message_element *old_el = NULL; + int status = 0; + TALLOC_CTX *ctx = talloc_new(NULL); + + setup_ldb(ctx, &ldb, &module, IP, SESSION, SID); + + /* + * Build the ldb message + */ + msg = talloc_zero(ctx, struct ldb_message); + + /* + * Populate message elements, adding a new entry to the membership list + * + */ + + el = talloc_zero(ctx, struct ldb_message_element); + el->name = "member"; + el->num_values = 1; + el->values = talloc_zero_array(ctx, DATA_BLOB, 1); + el->values[0] = data_blob_string_const( + "<GUID=081519b5-a709-44a0-bc95-dd4bfe809bf8>;" + "CN=testuser131953,CN=Users,DC=addom,DC=samba," + "DC=example,DC=com"); + msg->elements = el; + msg->num_elements = 1; + + /* + * Build the ldb_request + */ + req = talloc_zero(ctx, struct ldb_request); + req->operation = LDB_ADD; + req->op.add.message = msg; + add_transaction_id(req, TRANSACTION); + + /* + * Build the initial state of the database + */ + old_el = talloc_zero(ctx, struct ldb_message_element); + old_el->name = "member"; + old_el->num_values = 1; + old_el->values = talloc_zero_array(ctx, DATA_BLOB, 1); + old_el->values[0] = data_blob_string_const( + "<GUID=cb8c2777-dcf5-419c-ab57-f645dbdf681b>;" + "cn=grpadttstuser01,cn=users,DC=addom," + "DC=samba,DC=example,DC=com"); + + acc = talloc_zero(ctx, struct audit_callback_context); + acc->request = req; + acc->module = module; + acc->members = old_el; + /* + * call log_membership_changes + */ + messages_sent = 0; + g_result = NULL; + g_status = LDB_ERR_NO_SUCH_OBJECT; + log_group_membership_changes(acc, status); + + /* + * Check the results + */ + assert_int_equal(1, messages_sent); + + check_group_change_message( + 0, + "", + "Failure", + EVT_ID_NONE); + + /* + * Clean up + */ + json_free(&messages[0]); + TALLOC_FREE(ctx); +} + +/* test log_group_membership_changes + * + * The operation failed. + * + * Should generate group membership change Failure message. + * + */ +static void test_log_group_membership_changes_error(void **state) +{ + struct ldb_context *ldb = NULL; + struct ldb_module *module = NULL; + const char * const SID = "S-1-5-21-2470180966-3899876309-2637894779"; + const char * const SESSION = "7130cb06-2062-6a1b-409e-3514c26b1773"; + const char * const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773"; + const char * const IP = "127.0.0.1"; + struct ldb_request *req = NULL; + struct ldb_message *msg = NULL; + struct ldb_message_element *el = NULL; + struct ldb_message_element *old_el = NULL; + struct audit_callback_context *acc = NULL; + int status = LDB_ERR_OPERATIONS_ERROR; + TALLOC_CTX *ctx = talloc_new(NULL); + + setup_ldb(ctx, &ldb, &module, IP, SESSION, SID); + + /* + * Build the ldb message + */ + msg = talloc_zero(ctx, struct ldb_message); + + /* + * Populate message elements, adding a new entry to the membership list + * + */ + + el = talloc_zero(ctx, struct ldb_message_element); + el->name = "member"; + el->num_values = 1; + el->values = talloc_zero_array(ctx, DATA_BLOB, 1); + el->values[0] = data_blob_string_const( + "<GUID=081519b5-a709-44a0-bc95-dd4bfe809bf8>;" + "CN=testuser131953,CN=Users,DC=addom,DC=samba," + "DC=example,DC=com"); + msg->elements = el; + msg->num_elements = 1; + + /* + * Build the ldb_request + */ + req = talloc_zero(ctx, struct ldb_request); + req->operation = LDB_ADD; + req->op.add.message = msg; + add_transaction_id(req, TRANSACTION); + + /* + * Build the initial state of the database + */ + old_el = talloc_zero(ctx, struct ldb_message_element); + old_el->name = "member"; + old_el->num_values = 1; + old_el->values = talloc_zero_array(ctx, DATA_BLOB, 1); + old_el->values[0] = data_blob_string_const( + "<GUID=cb8c2777-dcf5-419c-ab57-f645dbdf681b>;" + "cn=grpadttstuser01,cn=users,DC=addom," + "DC=samba,DC=example,DC=com"); + + + acc = talloc_zero(ctx, struct audit_callback_context); + acc->request = req; + acc->module = module; + acc->members = old_el; + /* + * call log_membership_changes + */ + messages_sent = 0; + log_group_membership_changes(acc, status); + + /* + * Check the results + */ + assert_int_equal(1, messages_sent); + + check_group_change_message( + 0, + "", + "Failure", + EVT_ID_NONE); + + /* + * Clean up + */ + json_free(&messages[0]); + TALLOC_FREE(ctx); +} + +/* + * Note: to run under valgrind us: + * valgrind --suppressions=test_group_audit.valgrind bin/test_group_audit + * This suppresses the errors generated because the ldb_modules are not + * de-registered. + * + */ +int main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_audit_group_json), + cmocka_unit_test(test_audit_group_json_error), + cmocka_unit_test(test_audit_group_json_no_event), + cmocka_unit_test(test_get_transaction_id), + cmocka_unit_test(test_audit_group_hr), + cmocka_unit_test(test_get_parsed_dns), + cmocka_unit_test(test_dn_compare), + cmocka_unit_test(test_get_primary_group_dn), + cmocka_unit_test(test_log_membership_changes_removed), + cmocka_unit_test(test_log_membership_changes_remove_all), + cmocka_unit_test(test_log_membership_changes_added), + cmocka_unit_test(test_log_membership_changes_add_to_empty), + cmocka_unit_test(test_log_membership_changes_rmd_flags), + cmocka_unit_test(test_get_add_member_event), + cmocka_unit_test(test_get_remove_member_event), + cmocka_unit_test(test_log_group_membership_changes), + cmocka_unit_test(test_log_group_membership_changes_read_new_failure), + cmocka_unit_test(test_log_group_membership_changes_error), + }; + + cmocka_set_message_output(CM_OUTPUT_SUBUNIT); + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/source4/dsdb/samdb/ldb_modules/tests/test_group_audit.valgrind b/source4/dsdb/samdb/ldb_modules/tests/test_group_audit.valgrind new file mode 100644 index 0000000..1cf2b4e --- /dev/null +++ b/source4/dsdb/samdb/ldb_modules/tests/test_group_audit.valgrind @@ -0,0 +1,19 @@ +{ + ldb_modules_load modules not are freed + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:__talloc_with_prefix + fun:__talloc + fun:_talloc_named_const + fun:talloc_named_const + fun:ldb_register_module + fun:ldb_init_module + fun:ldb_modules_load_path + fun:ldb_modules_load_dir + fun:ldb_modules_load_path + fun:ldb_modules_load + fun:ldb_init +} + + diff --git a/source4/dsdb/samdb/ldb_modules/tests/test_group_audit_errors.c b/source4/dsdb/samdb/ldb_modules/tests/test_group_audit_errors.c new file mode 100644 index 0000000..ea9f2b7 --- /dev/null +++ b/source4/dsdb/samdb/ldb_modules/tests/test_group_audit_errors.c @@ -0,0 +1,266 @@ +/* + Unit tests for the dsdb group auditing code in group_audit.c + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2018 + + 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/>. +*/ + +/* + * These tests exercise the error handling routines. + */ + +#include <stdarg.h> +#include <stddef.h> +#include <setjmp.h> +#include <unistd.h> +#include <cmocka.h> + +int ldb_group_audit_log_module_init(const char *version); +#include "../group_audit.c" + +#include "lib/ldb/include/ldb_private.h" + +/* + * cmocka wrappers for json_new_object + */ +struct json_object __wrap_json_new_object(void); +struct json_object __real_json_new_object(void); +struct json_object __wrap_json_new_object(void) +{ + + bool use_real = (bool)mock(); + if (!use_real) { + return json_empty_object; + } + return __real_json_new_object(); +} + +/* + * cmocka wrappers for json_add_version + */ +int __wrap_json_add_version(struct json_object *object, int major, int minor); +int __real_json_add_version(struct json_object *object, int major, int minor); +int __wrap_json_add_version(struct json_object *object, int major, int minor) +{ + + int ret = (int)mock(); + if (ret) { + return ret; + } + return __real_json_add_version(object, major, minor); +} + +/* + * cmocka wrappers for json_add_version + */ +int __wrap_json_add_timestamp(struct json_object *object); +int __real_json_add_timestamp(struct json_object *object); +int __wrap_json_add_timestamp(struct json_object *object) +{ + + int ret = (int)mock(); + if (ret) { + return ret; + } + return __real_json_add_timestamp(object); +} + +/* + * Test helper to add a session id and user SID + */ +static void add_session_data( + TALLOC_CTX *ctx, + struct ldb_context *ldb, + const char *session, + const char *user_sid) +{ + struct auth_session_info *sess = NULL; + struct security_token *token = NULL; + struct dom_sid *sid = NULL; + struct GUID session_id; + bool ok; + + sess = talloc_zero(ctx, struct auth_session_info); + token = talloc_zero(ctx, struct security_token); + sid = talloc_zero(ctx, struct dom_sid); + ok = string_to_sid(sid, user_sid); + assert_true(ok); + token->sids = sid; + sess->security_token = token; + GUID_from_string(session, &session_id); + sess->unique_session_token = session_id; + ldb_set_opaque(ldb, DSDB_SESSION_INFO, sess); +} + +/* + * Test helper to insert a transaction_id into a request. + */ +static void add_transaction_id(struct ldb_request *req, const char *id) +{ + struct GUID guid; + struct dsdb_control_transaction_identifier *transaction_id = NULL; + + transaction_id = talloc_zero( + req, + struct dsdb_control_transaction_identifier); + assert_non_null(transaction_id); + GUID_from_string(id, &guid); + transaction_id->transaction_guid = guid; + ldb_request_add_control( + req, + DSDB_CONTROL_TRANSACTION_IDENTIFIER_OID, + false, + transaction_id); +} + +static void test_audit_group_json(void **state) +{ + struct ldb_context *ldb = NULL; + struct ldb_module *module = NULL; + struct ldb_request *req = NULL; + + struct tsocket_address *ts = NULL; + + const char *const SID = "S-1-5-21-2470180966-3899876309-2637894779"; + const char * const SESSION = "7130cb06-2062-6a1b-409e-3514c26b1773"; + + struct GUID transaction_id; + const char *const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773"; + + enum event_id_type event_id = EVT_ID_USER_REMOVED_FROM_GLOBAL_SEC_GROUP; + + struct json_object json; + + TALLOC_CTX *ctx = talloc_new(NULL); + + ldb = ldb_init(ctx, NULL); + + GUID_from_string(TRANSACTION, &transaction_id); + + module = talloc_zero(ctx, struct ldb_module); + module->ldb = ldb; + + tsocket_address_inet_from_strings(ctx, "ip", "127.0.0.1", 0, &ts); + ldb_set_opaque(ldb, "remoteAddress", ts); + + add_session_data(ctx, ldb, SESSION, SID); + + req = talloc_zero(ctx, struct ldb_request); + req->operation = LDB_ADD; + add_transaction_id(req, TRANSACTION); + + /* + * Fail on the creation of the audit json object + */ + + will_return(__wrap_json_new_object, false); + + json = audit_group_json(module, + req, + "the-action", + "the-user-name", + "the-group-name", + event_id, + LDB_ERR_OPERATIONS_ERROR); + assert_true(json_is_invalid(&json)); + + /* + * Fail adding the version object . + */ + + will_return(__wrap_json_new_object, true); + will_return(__wrap_json_add_version, JSON_ERROR); + + json = audit_group_json(module, + req, + "the-action", + "the-user-name", + "the-group-name", + event_id, + LDB_ERR_OPERATIONS_ERROR); + assert_true(json_is_invalid(&json)); + + /* + * Fail on creation of the wrapper. + */ + + will_return(__wrap_json_new_object, true); + will_return(__wrap_json_add_version, 0); + will_return(__wrap_json_new_object, false); + + json = audit_group_json(module, + req, + "the-action", + "the-user-name", + "the-group-name", + event_id, + LDB_ERR_OPERATIONS_ERROR); + assert_true(json_is_invalid(&json)); + + /* + * Fail adding the timestamp to the wrapper object. + */ + will_return(__wrap_json_new_object, true); + will_return(__wrap_json_add_version, 0); + will_return(__wrap_json_new_object, true); + will_return(__wrap_json_add_timestamp, JSON_ERROR); + + json = audit_group_json(module, + req, + "the-action", + "the-user-name", + "the-group-name", + event_id, + LDB_ERR_OPERATIONS_ERROR); + assert_true(json_is_invalid(&json)); + + + /* + * Now test the happy path + */ + will_return(__wrap_json_new_object, true); + will_return(__wrap_json_add_version, 0); + will_return(__wrap_json_new_object, true); + will_return(__wrap_json_add_timestamp, 0); + + json = audit_group_json(module, + req, + "the-action", + "the-user-name", + "the-group-name", + event_id, + LDB_ERR_OPERATIONS_ERROR); + assert_false(json_is_invalid(&json)); + + json_free(&json); + TALLOC_FREE(ctx); + +} + +/* + * Note: to run under valgrind us: + * valgrind --suppressions=test_group_audit.valgrind bin/test_group_audit + * This suppresses the errors generated because the ldb_modules are not + * de-registered. + * + */ +int main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_audit_group_json), + }; + + cmocka_set_message_output(CM_OUTPUT_SUBUNIT); + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/source4/dsdb/samdb/ldb_modules/tests/test_unique_object_sids.c b/source4/dsdb/samdb/ldb_modules/tests/test_unique_object_sids.c new file mode 100644 index 0000000..f9065e4 --- /dev/null +++ b/source4/dsdb/samdb/ldb_modules/tests/test_unique_object_sids.c @@ -0,0 +1,514 @@ +/* + Unit tests for the unique objectSID code in unique_object_sids.c + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2017 + + 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 <stdarg.h> +#include <stddef.h> +#include <setjmp.h> +#include <unistd.h> +#include <cmocka.h> + +int ldb_unique_object_sids_init(const char *version); +#include "../unique_object_sids.c" + +#include "../libcli/security/dom_sid.h" +#include "librpc/gen_ndr/ndr_security.h" + +#define TEST_BE "tdb" + +#define DOMAIN_SID "S-1-5-21-2470180966-3899876309-2637894779" +#define LOCAL_SID "S-1-5-21-2470180966-3899876309-2637894779-1000" +#define FOREIGN_SID "S-1-5-21-2470180966-3899876309-2637894778-1000" + +static struct ldb_request *last_request; + +/* + * ldb_next_request mock, records the request passed in last_request + * so it can be examined in the test cases. + */ +int ldb_next_request( + struct ldb_module *module, + struct ldb_request *request) +{ + last_request = request; + return ldb_module_done(request, NULL, NULL, LDB_SUCCESS); +} + +/* + * Test context + */ +struct ldbtest_ctx { + struct tevent_context *ev; + struct ldb_context *ldb; + struct ldb_module *module; + + const char *dbfile; + const char *lockfile; /* lockfile is separate */ + + const char *dbpath; + struct dom_sid *domain_sid; +}; + +/* + * Remove any database files created by the tests + */ +static void unlink_old_db(struct ldbtest_ctx *test_ctx) +{ + int ret; + + errno = 0; + ret = unlink(test_ctx->lockfile); + if (ret == -1 && errno != ENOENT) { + fail(); + } + + errno = 0; + ret = unlink(test_ctx->dbfile); + if (ret == -1 && errno != ENOENT) { + fail(); + } +} + +/* + * Empty module to signal the end of the module list + */ +static const struct ldb_module_ops eol_ops = { + .name = "eol", + .search = NULL, + .add = NULL, + .modify = NULL, + .del = NULL, + .rename = NULL, + .init_context = NULL +}; + +/* + * Test set up + */ +static int setup(void **state) +{ + struct ldbtest_ctx *test_ctx = NULL; + struct ldb_module *eol = NULL; + int rc; + + test_ctx = talloc_zero(NULL, struct ldbtest_ctx); + assert_non_null(test_ctx); + + test_ctx->ev = tevent_context_init(test_ctx); + assert_non_null(test_ctx->ev); + + test_ctx->ldb = ldb_init(test_ctx, test_ctx->ev); + assert_non_null(test_ctx->ldb); + + test_ctx->domain_sid = talloc_zero(test_ctx, struct dom_sid); + assert_non_null(test_ctx->domain_sid); + assert_true(string_to_sid(test_ctx->domain_sid, DOMAIN_SID)); + ldb_set_opaque(test_ctx->ldb, "cache.domain_sid", test_ctx->domain_sid); + + test_ctx->module = ldb_module_new( + test_ctx, + test_ctx->ldb, + "unique_object_sids", + &ldb_unique_object_sids_module_ops); + assert_non_null(test_ctx->module); + eol = ldb_module_new(test_ctx, test_ctx->ldb, "eol", &eol_ops); + assert_non_null(eol); + ldb_module_set_next(test_ctx->module, eol); + + test_ctx->dbfile = talloc_strdup(test_ctx, "duptest.ldb"); + assert_non_null(test_ctx->dbfile); + + test_ctx->lockfile = talloc_asprintf(test_ctx, "%s-lock", + test_ctx->dbfile); + assert_non_null(test_ctx->lockfile); + + test_ctx->dbpath = talloc_asprintf(test_ctx, + TEST_BE"://%s", test_ctx->dbfile); + assert_non_null(test_ctx->dbpath); + + unlink_old_db(test_ctx); + + rc = ldb_connect(test_ctx->ldb, test_ctx->dbpath, 0, NULL); + assert_int_equal(rc, LDB_SUCCESS); + + rc = unique_object_sids_init(test_ctx->module); + assert_int_equal(rc, LDB_SUCCESS); + + *state = test_ctx; + + last_request = NULL; + return 0; +} + +/* + * Test clean up + */ +static int teardown(void **state) +{ + struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state, + struct ldbtest_ctx); + + unlink_old_db(test_ctx); + talloc_free(test_ctx); + return 0; +} + +/* + * Add an objectSID in string form to the supplied message + * + * + */ +static void add_sid( + struct ldb_message *msg, + const char *sid_str) +{ + struct ldb_val v; + enum ndr_err_code ndr_err; + struct dom_sid *sid = NULL; + + sid = talloc_zero(msg, struct dom_sid); + assert_non_null(sid); + assert_true(string_to_sid(sid, sid_str)); + ndr_err = ndr_push_struct_blob(&v, msg, sid, + (ndr_push_flags_fn_t)ndr_push_dom_sid); + assert_true(NDR_ERR_CODE_IS_SUCCESS(ndr_err)); + assert_int_equal(0, ldb_msg_add_value(msg, "objectSID", &v, NULL)); +} + +/* + * The object is in the current local domain so it should have + * DB_FLAG_INTERNAL_UNIQUE_VALUE set + */ +static void test_objectSID_in_domain(void **state) +{ + struct ldbtest_ctx *test_ctx = + talloc_get_type_abort(*state, struct ldbtest_ctx); + struct ldb_context *ldb = test_ctx->ldb; + struct ldb_message *msg = ldb_msg_new(test_ctx); + struct ldb_message_element *el = NULL; + struct ldb_request *request = NULL; + struct ldb_request *original_request = NULL; + int rc; + + msg->dn = ldb_dn_new(msg, ldb, "dc=test"); + add_sid(msg, LOCAL_SID); + + rc = ldb_build_add_req( + &request, + test_ctx->ldb, + test_ctx, + msg, + NULL, + NULL, + ldb_op_default_callback, + NULL); + + assert_int_equal(rc, LDB_SUCCESS); + assert_non_null(request); + original_request = request; + + rc = unique_object_sids_add(test_ctx->module, request); + assert_int_equal(rc, LDB_SUCCESS); + + /* + * Check that a copy of the request was passed to the next module + * and not the original request + */ + assert_ptr_not_equal(last_request, original_request); + + /* + * Check the flag was set on the request passed to the next + * module + */ + el = ldb_msg_find_element(last_request->op.add.message, "objectSID"); + assert_non_null(el); + assert_true(el->flags & LDB_FLAG_INTERNAL_FORCE_UNIQUE_INDEX); + + /* + * Check the flag was not set on the original request + */ + el = ldb_msg_find_element(request->op.add.message, "objectSID"); + assert_non_null(el); + assert_false(el->flags & LDB_FLAG_INTERNAL_FORCE_UNIQUE_INDEX); + +} + +/* + * The object is not in the current local domain so it should NOT have + * DB_FLAG_INTERNAL_UNIQUE_VALUE set + */ +static void test_objectSID_not_in_domain(void **state) +{ + struct ldbtest_ctx *test_ctx = + talloc_get_type_abort(*state, struct ldbtest_ctx); + struct ldb_context *ldb = test_ctx->ldb; + struct ldb_message *msg = ldb_msg_new(test_ctx); + struct ldb_message_element *el = NULL; + struct ldb_request *request = NULL; + struct ldb_request *original_request = NULL; + int rc; + + msg->dn = ldb_dn_new(msg, ldb, "dc=test"); + add_sid(msg, FOREIGN_SID); + + rc = ldb_build_add_req( + &request, + test_ctx->ldb, + test_ctx, + msg, + NULL, + NULL, + ldb_op_default_callback, + NULL); + + assert_int_equal(rc, LDB_SUCCESS); + assert_non_null(request); + original_request = request; + + rc = unique_object_sids_add(test_ctx->module, request); + assert_int_equal(rc, LDB_SUCCESS); + + /* + * Check that the original request was passed to the next module + * and not a copy + */ + assert_ptr_equal(last_request, original_request); + + /* + * Check that the flag was not set on the objectSID element + */ + el = ldb_msg_find_element(msg, "objectSID"); + assert_non_null(el); + assert_false(el->flags & LDB_FLAG_INTERNAL_FORCE_UNIQUE_INDEX); +} + +/* + * No objectSID on the record so it should pass through the module untouched + * + */ +static void test_no_objectSID(void **state) +{ + struct ldbtest_ctx *test_ctx = + talloc_get_type_abort(*state, struct ldbtest_ctx); + struct ldb_context *ldb = test_ctx->ldb; + struct ldb_message *msg = ldb_msg_new(test_ctx); + struct ldb_request *request = NULL; + struct ldb_request *original_request = NULL; + int rc; + + msg->dn = ldb_dn_new(msg, ldb, "dc=test"); + assert_int_equal(LDB_SUCCESS, ldb_msg_add_string(msg, "cn", "test")); + + rc = ldb_build_add_req( + &request, + test_ctx->ldb, + test_ctx, + msg, + NULL, + NULL, + ldb_op_default_callback, + NULL); + + assert_int_equal(rc, LDB_SUCCESS); + assert_non_null(request); + original_request = request; + + rc = unique_object_sids_add(test_ctx->module, request); + assert_int_equal(rc, LDB_SUCCESS); + + /* + * Check that the original request was passed to the next module + * and not a copy + */ + assert_ptr_equal(last_request, original_request); + +} + +/* + * Attempt to modify an objectSID DSDB_CONTROL_REPLICATED_UPDATE_OID not set + * this should fail with LDB_ERR_UNWILLING_TO_PERFORM + */ +static void test_modify_of_objectSID_not_replicated(void **state) +{ + struct ldbtest_ctx *test_ctx = + talloc_get_type_abort(*state, struct ldbtest_ctx); + struct ldb_context *ldb = test_ctx->ldb; + struct ldb_message *msg = ldb_msg_new(test_ctx); + struct ldb_request *request = NULL; + int rc; + + msg->dn = ldb_dn_new(msg, ldb, "dc=test"); + add_sid(msg, LOCAL_SID); + + rc = ldb_build_mod_req( + &request, + test_ctx->ldb, + test_ctx, + msg, + NULL, + NULL, + ldb_op_default_callback, + NULL); + + assert_int_equal(rc, LDB_SUCCESS); + assert_non_null(request); + + rc = unique_object_sids_modify(test_ctx->module, request); + + assert_int_equal(rc, LDB_ERR_UNWILLING_TO_PERFORM); +} + + +/* + * Attempt to modify an objectSID DSDB_CONTROL_REPLICATED_UPDATE_OID set + * this should succeed + */ +static void test_modify_of_objectSID_replicated(void **state) +{ + struct ldbtest_ctx *test_ctx = + talloc_get_type_abort(*state, struct ldbtest_ctx); + struct ldb_context *ldb = test_ctx->ldb; + struct ldb_message *msg = ldb_msg_new(test_ctx); + struct ldb_message_element *el = NULL; + struct ldb_request *request = NULL; + struct ldb_request *original_request = NULL; + int rc; + + msg->dn = ldb_dn_new(msg, ldb, "dc=test"); + add_sid(msg, LOCAL_SID); + + rc = ldb_build_mod_req( + &request, + test_ctx->ldb, + test_ctx, + msg, + NULL, + NULL, + ldb_op_default_callback, + NULL); + assert_int_equal(rc, LDB_SUCCESS); + assert_non_null(request); + original_request = request; + + rc = ldb_request_add_control( + request, + DSDB_CONTROL_REPLICATED_UPDATE_OID, + false, + NULL); + assert_int_equal(rc, LDB_SUCCESS); + + rc = unique_object_sids_modify(test_ctx->module, request); + + assert_int_equal(rc, LDB_SUCCESS); + + /* + * Check that a copy of the request was passed to the next module + * and not the original request + */ + assert_ptr_not_equal(last_request, original_request); + + /* + * Check the flag was set on the request passed to the next + * module + */ + el = ldb_msg_find_element(last_request->op.add.message, "objectSID"); + assert_non_null(el); + assert_true(el->flags & LDB_FLAG_INTERNAL_FORCE_UNIQUE_INDEX); + + /* + * Check the flag was not set on the original request + */ + el = ldb_msg_find_element(request->op.add.message, "objectSID"); + assert_non_null(el); + assert_false(el->flags & LDB_FLAG_INTERNAL_FORCE_UNIQUE_INDEX); + +} + +/* + * Test the a modify with no object SID is passed through correctly + * + */ +static void test_modify_no_objectSID(void **state) +{ + struct ldbtest_ctx *test_ctx = + talloc_get_type_abort(*state, struct ldbtest_ctx); + struct ldb_context *ldb = test_ctx->ldb; + struct ldb_message *msg = ldb_msg_new(test_ctx); + struct ldb_request *request = NULL; + struct ldb_request *original_request = NULL; + int rc; + + msg->dn = ldb_dn_new(msg, ldb, "dc=test"); + assert_int_equal(LDB_SUCCESS, ldb_msg_add_string(msg, "cn", "test")); + + rc = ldb_build_mod_req( + &request, + test_ctx->ldb, + test_ctx, + msg, + NULL, + NULL, + ldb_op_default_callback, + NULL); + + assert_int_equal(rc, LDB_SUCCESS); + assert_non_null(request); + original_request = request; + + rc = unique_object_sids_modify(test_ctx->module, request); + assert_int_equal(rc, LDB_SUCCESS); + + /* + * Check that the original request was passed to the next module + * and not a copy + */ + assert_ptr_equal(last_request, original_request); + +} + +int main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown( + test_objectSID_in_domain, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_objectSID_not_in_domain, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_no_objectSID, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_modify_no_objectSID, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_modify_of_objectSID_not_replicated, + setup, + teardown), + cmocka_unit_test_setup_teardown( + test_modify_of_objectSID_replicated, + setup, + teardown), + }; + + cmocka_set_message_output(CM_OUTPUT_SUBUNIT); + return cmocka_run_group_tests(tests, NULL, NULL); +} |