summaryrefslogtreecommitdiffstats
path: root/source4/dsdb/samdb/ldb_modules/tests
diff options
context:
space:
mode:
Diffstat (limited to 'source4/dsdb/samdb/ldb_modules/tests')
-rwxr-xr-xsource4/dsdb/samdb/ldb_modules/tests/possibleinferiors.py265
-rw-r--r--source4/dsdb/samdb/ldb_modules/tests/test_audit_log.c2305
-rw-r--r--source4/dsdb/samdb/ldb_modules/tests/test_audit_log_errors.c639
-rw-r--r--source4/dsdb/samdb/ldb_modules/tests/test_audit_util.c1261
-rw-r--r--source4/dsdb/samdb/ldb_modules/tests/test_encrypted_secrets.c973
-rw-r--r--source4/dsdb/samdb/ldb_modules/tests/test_group_audit.c2022
-rw-r--r--source4/dsdb/samdb/ldb_modules/tests/test_group_audit.valgrind19
-rw-r--r--source4/dsdb/samdb/ldb_modules/tests/test_group_audit_errors.c266
-rw-r--r--source4/dsdb/samdb/ldb_modules/tests/test_unique_object_sids.c514
9 files changed, 8264 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..fecaa3e
--- /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(&regex, rs, 0);
+ assert_int_equal(0, ret);
+
+ ret = regexec(&regex, line, 0, NULL, 0);
+ assert_int_equal(0, ret);
+
+ regfree(&regex);
+ 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(&regex, rs, 0);
+ assert_int_equal(0, ret);
+
+ ret = regexec(&regex, line, 0, NULL, 0);
+ assert_int_equal(0, ret);
+
+ regfree(&regex);
+ 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(&regex, rs, 0);
+ assert_int_equal(0, ret);
+
+ ret = regexec(&regex, line, 0, NULL, 0);
+ assert_int_equal(0, ret);
+
+ regfree(&regex);
+ 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(&regex, rs, 0);
+ assert_int_equal(0, ret);
+
+ ret = regexec(&regex, line, 0, NULL, 0);
+ assert_int_equal(0, ret);
+
+ regfree(&regex);
+ 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(&regex, rs, 0);
+ assert_int_equal(0, ret);
+
+ ret = regexec(&regex, line, 0, NULL, 0);
+ assert_int_equal(0, ret);
+
+ regfree(&regex);
+ 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(&regex, rs, 0);
+ assert_int_equal(0, ret);
+
+ ret = regexec(&regex, line, 0, NULL, 0);
+ assert_int_equal(0, ret);
+
+ regfree(&regex);
+ TALLOC_FREE(ctx);
+
+}
+
+/*
+ * minimal unit test of commit_failure_hr, that ensures
+ * that all the expected content 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(&regex, rs, 0);
+ assert_int_equal(0, ret);
+
+ ret = regexec(&regex, line, 0, NULL, 0);
+ assert_int_equal(0, ret);
+
+ regfree(&regex);
+ 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(&regex, rs, 0);
+ assert_int_equal(0, ret);
+
+ ret = regexec(&regex, line, 0, NULL, 0);
+ assert_int_equal(0, ret);
+
+ regfree(&regex);
+ 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(&regex, rs, 0);
+ assert_int_equal(0, ret);
+
+ ret = regexec(&regex, line, 0, NULL, 0);
+ assert_int_equal(0, ret);
+
+ regfree(&regex);
+ 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..44f7b77
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/tests/test_audit_util.c
@@ -0,0 +1,1261 @@
+/*
+ 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();
+ assert_false(json_is_invalid(&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 multi-valued attribute
+ */
+ 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(&regex, rs, 0);
+ assert_int_equal(0, ret);
+
+ ret = regexec(&regex, line, 0, NULL, 0);
+ assert_int_equal(0, ret);
+
+ regfree(&regex);
+ 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..2d37d53
--- /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 that 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);
+}